2007 年 11 月 20日, 星期二
可重入代码
一、可重入函数1)什么是可重入性?可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反, 不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。
2)可重入函数:不为连续的调用持有静态数据。 不返回指向静态数据的指针;所有数据都由函数的调用者提供。 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。绝不调用任何不可重入函数。
3)不可重入函数:函数中使用了静态变量,无论是全局静态变量还是局部静态变量。 函数返回静态变量。 函数中调用了不可重入函数。函数体内使用了静态的数据结构;函数体内调用了malloc()或者free()函数;函数体内调用了其他标准I/O函数。函数是singleton中的成员函数而且使用了不使用线程独立存储的成员变量 。总的来说,如果一个函数在重入条件下使用了未受保护的共享的资源,那么它是不可重入的。
4)示例在多线程条件下,函数应当是线程安全的,进一步,更强的条件是可重入的。可重入函数保证了在多线程条件下,函数的状态不会出现错误。以下分别是一个不可重入和可重入函数的示例:
//c code
static int tmp;
void func1(int* x, int* y) {
tmp=*x;
*x=*y;
*y=tmp;
}
void func2(int* x, int* y) {
int tmp;
tmp=*x;
*x=*y;
*y=tmp;
}
func1是不可重入的,func2是可重入的。因为在多线程条件下,操作系统会在func1还没有执行完的情况下,切换到另一个线程中,那个线程可能再次调用func1,这样状态就错了。
二、函数编写规范
1 :对所调用函数的错误返回码要仔细、全面地处理
2 :明确函数功能,精确(而不是近似)地实现函数设计
3 :编写可重入函数时,应注意局部变量的使用(如编写C/C++ 语言的可重入函数时,应使用auto 即缺省态局部变量或寄存器变量) 说明:编写C/C++语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。
4 :编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P 、V 操作)等手段对其加以保护 说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。 示例:假设Exam是int型全局变量,函数Squre_Exam返回Exam平方值。那么如下函数不具有可重入性。
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam赋与另一个不同的para值,所以当控制重新回到“temp = Square_Exam( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。
unsigned int example( int para )
{
unsigned int temp;
[申请信号量操作] // 若申请不到“信号量”,说明另外的进程正处于
Exam = para; // 给Exam赋值并计算其平方过程中(即正在使用此
temp = Square_Exam( ); // 信号),本进程必须等待其释放信号后,才可继
[释放信号量操作] // 续执行。若申请到信号,则可继续执行,但其
// 它进程必须等待本进程释放信号量后,才能再使
// 用本信号。
return temp;
}
5 :在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责 说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。
6 :防止将函数的参数作为工作变量 说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。 示例:如下函数的实现就不太好。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count;
*sum = 0;
for (count = 0; count < num; count++)
{
*sum += data[count]; // sum成了工作变量,不太好。
}
}若改为如下,则更好些。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count ;
int sum_temp;
sum_temp = 0;
for (count = 0; count < num; count ++)
{
sum_temp += data[count];
}
*sum = sum_temp;
}
7 :函数的规模尽量限制在200 行以内 说明:不包括注释和空格行。
8 :一个函数仅完成一件功能
9 :为简单功能编写函数 说明:虽然为仅用一两行就可完成的功能去编函数好象没有必要,但用函数可使功能明确化,增加程序可读性,亦可方便维护、测试。 示例:如下语句的功能不很明显。
value = ( a > b ) ? a : b ;改为如下就很清晰了。
int max (int a, int b)
{
return ((a > b) ? a : b);
}
value = max (a, b);
或改为如下。
#define MAX (a, b) (((a) > (b)) ? (a) : (b))
value = MAX (a, b);
10:不要设计多用途面面俱到的函数 说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。
11:函数的功能应该是可以预测的,也就是只要输入数据相同就应产生同样的输出 说明:带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在C/C++语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值为指针类型时,则必须是STATIC的局部变量的地址作为返回值,若为AUTO类,则返回为错针。示例:如下函数,其返回值(即功能)是不可预测的。
unsigned int integer_sum( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是static类型的。
// 若改为auto类型,则函数即变为可预测。
for (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
12 :尽量不要编写依赖于其他函数内部实现的函数 说明:此条为函数独立性的基本要求。由于目前大部分高级语言都是结构化的,所以通过具体语言的语法要求与编译器功能,基本就可以防止这种情况发生。但在汇编语言中,由于其灵活性,很可能使函数出现这种情况。 示例:如下是在DOS下TASM的汇编程序例子。过程Print_Msg的实现依赖于Input_Msg的具体实现,这种程序是非结构化的,难以维护、修改。
... // 程序代码
proc Print_Msg // 过程(函数)Print_Msg
... // 程序代码
jmp LABEL
... // 程序代码
endp
proc Input_Msg // 过程(函数)Input_Msg
... // 程序代码
LABEL:
... // 程序代码
endp
13 :避免设计多参数函数,不使用的参数从接口中去掉 说明:目的减少函数间接口的复杂度。
14 :非调度函数应减少或防止控制参数,尽量只使用数据参数 说明:本建议目的是防止函数间的控制耦合。调度函数是指根据输入的消息类型或控制命令,来启动相应的功能实体(即函数或过程),而本身并不完成具体功能。控制参数是指改变函数功能行为的参数,即函数要根据此参数来决定具体怎样工作。非调度函数的控制参数增加了函数间的控制耦合,很可能使函数间的耦合度增大,并使函数的功能不唯一。 示例:如下函数构造不太合理。
int add_sub( int a, int b, unsigned char add_sub_flg )
{
if (add_sub_flg == INTEGER_ADD)
{
return (a + b);
}
else
{
return (a b);
}
}不如分为如下两个函数清晰。
int add( int a, int b )
{
return (a + b);
}
int sub( int a, int b )
{
return (a b);
}
15 :检查函数所有参数输入的有效性
16 :检查函数所有非参数输入的有效性,如数据文件、公共变量等 说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入之前,应进行必要的检查。
17 :函数名应准确描述函数的功能
18 :使用动宾词组为执行某操作的函数命名。如果是OOP 方法,可以只有动词(名词是对象本身) 示例:参照如下方式命名函数。
void print_record( unsigned int rec_ind ) ;
int input_record( void ) ;
unsigned char get_current_color( void ) ;
19 :避免使用无意义或含义不清的动词为函数命名 说明:避免用含义不清的动词如process、handle等为函数命名,因为这些动词并没有说明要具体做什么。
20 :函数的返回值要清楚、明了,让使用者不容易忽视错误情况 说明:函数的每种出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误或忽视错误返回码。
21 :除非必要,最好不要把与函数返回值类型不同的变量,以编译系统默认的转换方式或强制的转换方式作为返回值返回
22 :让函数在调用点显得易懂、容易理解
23 :在调用函数填写参数时,应尽量减少没有必要的默认数据类型转换或强制数据类型转换 说明:因为数据类型转换或多或少存在危险。
24 :避免函数中不必要语句,防止程序中的垃圾代码 说明:程序中的垃圾代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。
25 :防止把没有关联的语句放到一个函数中 说明:防止函数或过程内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到同一个函数或过程中。随机内聚给函数或过程的维护、测试及以后的升级等造成了不便,同时也使函数或过程的功能不明确。使用随机内聚函数,常常容易出现在一种应用场合需要改进此函数,而另一种应用场合又不允许这种改进,从而陷入困境。 在编程时,经常遇到在不同函数中使用相同的代码,许多开发人员都愿把这些代码提出来,并构成一个新函数。若这些代码关联较大并且是完成一个功能的,那么这种构造是合理的,否则这种构造将产生随机内聚的函数。 示例:如下函数就是一种随机内聚。
void Init_Var( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的长与宽 */
Point.x = 10;
Point.y = 10; /* 初始化“点”的坐标 */
}
矩形的长、宽与点的坐标基本没有任何关系,故以上函数是随机内聚。 应如下分为两个函数:
void Init_Rect( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的长与宽 */
}
void Init_Point( void )
{
Point.x = 10;
Point.y = 10; /* 初始化“点”的坐标 */
}
26:如果多段代码重复做同一件事情,那么在函数的划分上可能存在问题 说明:若此段代码各语句之间有实质性关联并且是完成同一件功能的,那么可考虑把此段代码构造成一个新的函数。
27:功能不明确较小的函数,特别是仅有一个上级函数调用它时,应考虑把它合并到上级函数中,而不必单独存在 说明:模块中函数划分的过多,一般会使函数间的接口变得复杂。所以过小的函数,特别是扇入很低的或功能不明确的函数,不值得单独存在。
28 :设计高扇入、合理扇出(小于7 )的函数 说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。 扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总是1,表明函数的调用层次可能过多,这样不利程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。 较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。
2007 年 11 月 19日, 星期一
1118,我失眠了
11月18号,她结婚了。
女人间的关系真的是很巧妙,虽然说是好朋友,却暗地里较劲,当听到别人工作比你好,嫁的比你好时,内心的感觉真的是不知道如何形容,而当你听到某某工作不如意,男朋友找不到,或者找到不怎么样时,却还不惜打个长途电话去表示慰问,表示惋惜,表示祝愿。每天也是到处打听别人个人生活的内幕,总希望听到些什么。
越是距离越近的朋友,越是这样比拼着。她和我在学校就是比较好的朋友,来南京工作也一起租房子,后来我结婚了,女人的敏感导致我们几乎不怎么联系了,今天她结婚,我去参加婚礼,发现她现在很好,人也漂亮了,我的心里酸酸的,回来之后也失眠了
我总觉得自己应该比她我,我长得比她漂亮,在学校也比她受欢迎,智商不比她低,性格,可能就是我这种不坚持,随性的性格导致我的命运。注定了我现在的平凡。
其实什么东西都是要认真和努力的,而我其实很缺乏这点,没有坚定的恒心和毅力,什么都期望老天的降临。老天对我也没有这么恩惠。
其实根人比,是因为对自己的魅力缺乏信心,对自己缺乏信心,就是因为自己的知识的贫乏,人生的路漫漫的,我不要把精力放在别人的身上, 自己的内涵是最重要的,
2007 年 09 月 21日, 星期五
嵌入式_main 到main 到底做了什么
标准库中包含了部分依赖于ARM semihosted执行环境的函数,这部分函数的函数名中包含有单个或两个下划线“-”,需要重新实现这部分函数。如果在程序中定义这些函数,则编译器就会使用新定义的函数,这个过程称为库函数的裁减。一般情况下,只需要重新定义很少的几个函数就可以使用C库。ARM应用系统开始执行用户应用程序,必须先将应用程序加载到执行域,建立应用程序的执行环境。使用C库时,这些繁琐的工作就大部分由c函数来完成了。汇编程序完成系统初始化后,跳转到C程序的人口_main()(注意:不是main(),当C程序中定义了main()主函数时,编译器就会生成_main代码)。由_main()引导库函数完成C执行环境的初始化,具体过程如下:
◇将非启动代码的RO和RW执行域代码从加载域地址复制到执行域地址;
◇将ZI域清零;
◇跳转到_rt_entry。
调用_main()将大大简化汇编启动代码的编写,汇编代码仅需完成系统硬件的初始化,而没有必要将代码从加载域地址复制到执行域地址,以及ZI域清零等工作。特别是当使用分布式加载时_main()的作用就更加明显了。但是_main()并没有建立C库运行必须的环境,这项工作由_rt_entry()完成,主要调用过程为:
◇调用_rt_stackheap_init()建立堆和栈;
◇调用_rt_lib_init()初始化引用的库函数;如果需要,建立main()函数的参数argc和argv等;
◇调用main()函数,执行应用程序,可以应用库函数;
◇用main()函数的返回值作参数调用exit()。
_rt_entry并不是C函数,它是用ARM C库编程的起始点。_rt_entry不能用C语言宴现,因为这时候堆栈还没有建立,堆栈由_ rt_stackheap_init()来建立。
上面简单介绍了C程序使用库函数时的调用过程,由_rt—stackheap_init()建立C库使用的内存模型--堆和栈。因为ARM库是建立在semihosted执行环境的,它实现的内存模型是基于这个环境的,所以必须修改这个内存模型建立机制。表1列出了需要重新实现的函数,实现了这些函数,应用程序就可以脱离宿主机环境独立运行了。其中,必须重新实现的是_user initial_stackheap(),因为默认的实现是基于semihosted执行环境的,该函数被_n_stackheap_init()调用创建内存模型,其他两个函数没有默认的实现。
2007 年 07 月 24日, 星期二
Tornado 2.2 中vxsim出问题的解决方法
然后,一直出现这样的错误提示:
error: image specified can not be run as a vxworks simulator with processor number 0
无法搞定。
最终还是有高手解决了这个问题,这里先谢谢这些令人敬佩的前辈们。
方法如下:
用google搜索,找到这样一个帖子:[url]http: //groups.google.com/group/comp.os.vxworks/browse_thread/thread/b0b0be25ad9bd729/23123bdbbea37a6f?lnk=st&q=%E2%80%99initialize+before+timeout%E2%80%98&rnum=1&hl=zh-CN#23123bdbbea37a6f[/url]
'G#E@5R*_$l 里面讲了这个问题是由于安装了微软8月份的安全补丁而引起的,卸载掉那个补丁就好了。它的编号为:KB917422。那个注册表项里面J];o�F4xhzy
有卸载它的命令,拷贝到命令行方式下运行,就把它卸载掉了。然后重启系统,重新执行Tornado,就没有那个问题了,一切正常。6~v3KLV
查找注册表:KB917422)K7zs)zuz-[
找到键名: UnistallCommand
.`^B f-a WS 类型为:REG_SZ
wE-M(A^)Kp 键值:C:WINDOWS$NtUninstallKB917422$spuninstspuninst.exe
4b"T)C"|C 拷贝键值C:WINDOWS$NtUninstallKB917422$spuninstspuninst.exe %`%D4m FJ
复制在 开始->运行 nIX+_Y
执行即可卸载这个补丁!!!)^fA v5M
问题解决!!!
2007 年 05 月 25日, 星期五
c与c++的相互引用
c++虽然是一种面向对象的语言,但是还是保留了很多面向过程的特点。比如说它可以定义不属于任何类的全局变量和函数。但是c++毕竟是一种面向对象的程序设计语言,为了支持函数重载,c++对全局变量的处理和c有明显的不一样。
被extern“c”修饰的变量和函数是按照c语言方式编译和连接的。
作为一种面向对象的语言,c++支持函数重载,而过程语言c则不支持,函数被c++编译后在函数库中的语言和c语言的不同。可以举例说明:
void foo(int x, int y)
该函数被c编译后在符号库中的名字为——foo。但是c++则会根据编译其的不同,生成类似——foo——int——int之类的函数名。
c++ 靠这种机制进行重载。
C中引用c++的变量和函数。也需要申明extern “c”
2007 年 05 月 16日, 星期三
20070506
1 定义的数组维数必须是常量或者常量表达式。因为数组的维数必须在编译时刻决定。
类似,int a=2
int b[a];
虽然a已经定义,但是由于a不是常量表达式,所以a的值只能在运行时候访问。所以b[a]这种定义是不行的。
异常处理--溢出
c++ 中有一套比较完善的异常处理机智
比如一个除零异常
#include<iostream>
double div(double,double);
main(){
try
{
cout<<"7.0/2.0"<<div(7.0,3.0)<<endl;
cout<<"7.0/0.0"<<div(7.0,3.0)<<endl;cout<<"7.0/1.0"<<div(7.0,3.0)<<endl;
}
catch(double)
{
cout<< "exception of deviding zero.n"
}
cout<<"this is ok";
}
}
double div(double a,double b);
{
if (b==0)
thorw b;
return a/b;
}
如果发生异常,产生异常的函数退出堆栈,处理异常处理函数。如果没有异常处理函数,则调用系统默认处理函数,终止程序。
而对于类型的上溢和下溢时不能通过语言来发现的。因为在运行期间时刻检查数据类型的溢出是不太现实的。
但是c++头文件limints提供与内置类型表示有关的信息,例如一个类型能表示的最大值最小值。可以使用这些头文件来防止溢出和下溢。见c++primer.119.
查看全文2007 年 05 月 15日, 星期二
20070505-c++复习
c++的信息隐藏技术,使用函数调用访问数据成员,而不是直接访问内存地址。 我们知道函数调用都会有压栈操作,会不会给系统带来额外的负担了。c++ 的函数调用基本上都是内联函数,所以基本上不会带来额外的负担 。
而c++ 在基类设计中,当基类中有与类型相关的函数时,需要申明为虚函数,能够被派生类实现。虚函数是动态邦定的,所以就无法实现在静态编译的时候实现内联。这是比较影响效率,所以只有在真正需要实现虚函数时,才实现虚函数。
指向常量的指针,不能通过指针修改指向常量的值。但是指针的值是可以修改,也就是指向的对象是可以修改的。在实际的程序中,指向const的指针常被用作函数的形式参数,它作为一个约定来保证:被传递给函数的实际对象在函数中不会被修改。指向常量可以指向普通变量,只想普通变量,那么普通变量的值也不能被修改。所以传递参数利用这点进行做的
在进行参数传递的时候,
2007 年 02 月 07日, 星期三
2006年我的工作总结
2006年结束了,从表面上来看,我完成了领导布置的任务,涉及的领域非常广泛,表面来看也有不少深度,但是很多都是依赖于实习生解决的。扪心自问。我并不觉得我的水平提高了多少。基础也没有打得更加扎实。我其实还是那样眼高手低,心高气傲,不能有一点点受气。也不想脚踏实地。
但是待遇是不能少的,我已经完成了任务,并且比他们的业绩好,个人能力是否提高是我的问题,工作有成绩我就应该有较高的报酬,一切还是看年终奖吧,如果比较满意,我就不跳槽了。不满意,就离开这儿吧。
在这儿的话,其实我看不到前途的,因为他其实也是一个非常肤浅的人,动手能力估计为0。总是希望我跟他一样,什么都知道一点,但又不深入。我现在已经厌烦了这种人,我希望我是一个扎扎实实做实事的人。我还年轻,我希望我在这两年内改变这一切。
在他的手下做事情,就会变成这种人,如果工资足够高,那我也就算了,所以说一切看年终吧
不管是否决定在这儿继续下去。我在2007年希望能够彻底的改变自己。
1 不要太急躁,我经常和领导吵架,吵完了也把事情也作了。这是没有意义的事情
2 不要眼高手低。什么事情都能够把自己归零,冲头开始自己完成。
3 所以我打算把专业课重新复习一遍,顺便把程序编编,这样跳槽也有了准备。考博也有了基础。只有真正的知识才是自己的。
查看全文2006年我的工作总结
2006年结束了,从表面上来看,我完成了领导布置的任务,涉及的领域非常广泛,表面来看也有不少深度,但是很多都是依赖于实习生解决的。扪心自问。我并不觉得我的水平提高了多少。基础也没有打得更加扎实。我其实还是那样眼高手低,心高气傲,不能有一点点受气。也不想脚踏实地。
但是待遇是不能少的,我已经完成了任务,并且比他们的业绩好,个人能力是否提高是我的问题,工作有成绩我就应该有较高的报酬,一切还是看年终奖吧,如果比较满意,我就不跳槽了。不满意,就离开这儿吧。
在这儿的话,其实我看不到前途的,因为他其实也是一个非常肤浅的人,动手能力估计为0。总是希望我跟他一样,什么都知道一点,但又不深入。我现在已经厌烦了这种人,我希望我是一个扎扎实实做实事的人。我还年轻,我希望我在这两年内改变这一切。
在他的手下做事情,就会变成这种人,如果工资足够高,那我也就算了,所以说一切看年终吧
不管是否决定在这儿继续下去。我在2007年希望能够彻底的改变自己。
1 不要太急躁,我经常和领导吵架,吵完了也把事情也作了。这是没有意义的事情
2 不要眼高手低。什么事情都能够把自己归零,冲头开始自己完成。
3 所以我打算把专业课重新复习一遍,顺便把程序编编,这样跳槽也有了准备。考博也有了基础。只有真正的知识才是自己的。
查看全文2006 年 09 月 28日, 星期四
linux比较文章命令
如果想对两个有序的文件进行比较,可以使用comm命令。
语法:comm [- 123 ] file1 file2
说明:该命令是对两个已经排好序的文件进行比较。其中file1和file2是已排序的文件。comm读取这两个文件,然后生成三列输出:仅在file1中出现的行;仅在file2中出现的行;在两个文件中都存在的行。如果文件名用“- ”,则表示从标准输入读取。
选项1、2或3抑制相应的列显示。例如comm - 12就只显示在两个文件中都存在的行;comm - 23只显示在第一个文件中出现而未在第二个文件中出现的行;comm - 123则什么也不显
语法:diff [选项] file1 file2
说明:该命令告诉用户,为了使两个文件file1和file2一致,需要修改它们的哪些行。如果用“- ”表示file1或fiie2,则表示标准输入。如果file1或file2是目录,那么diff将使用该目录中的同名文件进行比较。
心神不定的工作
在一个其实不错的公司混着。时时刻刻想着跳槽,忍受不了一点点领导的气,想用跳槽来表示他狗眼不识泰山。
最近真的有公司跟我联系了。一家小一点的公司请我去当linux开发组组长。但时机真正来临的时候,我却害怕了。害怕失去稳定的工作,害怕自己不够格当领导,甚至怀疑自己到底是不是高手,技术能力够不够当一个领导。
后来又一家不错的外企也跟我联系了,由于内心一直向往的企业,面试起来也非常紧张,知道的都忘了,能说会道的我也变得非常紧张起来。不知道该如何表达东西。
也许还是积累不够把。我还需要修炼。对心理素质和技术进行修炼。真正的高手是什么都不惧怕的。只有当我成为真正的高手时,我才能展现我我自信的一面来。所有的现象只能表示我内心的不自信和不成熟。
我还是决定在这里继续呆下去。不管在什么单位,都是自己去适应社会,而不是去社会适应我,一个地方有一个地方的企业文化。当你改变不了时,就适应它吧。
2006 年 07 月 14日, 星期五
konqueror移植成功并中文化
最近正是拖那个所谓领导的福。出了钱的netscute不用,非要用konqueror。还让我一个刚出学校的人来移植,从对qt一点概念都没有,到最后的konqueror能够显示中文,不知道是废了多大的力气,非常感谢我手下的几个实习生,有他们的帮助,才让我顺利的完成konqueror的移植。
看来是我总结经验的时候了:
其实qt和qtopia的移植非常简单,掌握一个准则基本上就可以ok了
1 一 定要把版本给选对了
2 redhat一定要装全了
3 arm-linux编译器lib中关于uuid的补丁一定要打上
4 jpeglib 库一定要重新交叉编译,并且install到arm-linux下
5最关键的一点还是:环境变量一定要设置正确了
konqueror的移植:要有自己找错误的本事,不要configure或者make时有错误就呆着了,要去log中找到相应变量的值,并且值是否正确。
我在网上看得最多的错误时QT》2。2之类的错误,这个错误有点莫名其妙,大部分不是真正是因为版本所造成的,而是环境变量没有设置正确造成的
编译器很关键了,到了最后一步,也看就要产生konqueror可执行文件的时候,可能会报一堆错误,konqueror的mailist也说这是编译器的问题,我认为确实是编译器的问题,在2。95。3中,需要加上corss-complie=1。我也不知道为什么??
2006 年 06 月 23日, 星期五
进程读书笔记1
进程拥有的空间:
每个进程会分配到一个独一无二的数字编号,pid。每个进程还拥有自己的堆栈(这个空间保存函数的变量)。还有自己的环境空间,其中环境空间设置出来的环境是供这个进程专用。
我们在linux中,每个终端设置的环境变量在另一个终端中并没有用。这就是因为两个终端是两个进程,它们拥有不同的环境变量空间。而每个进程中启动的程序都是这个bash的子进程,而这个子进程都共享这bash设置的环境变量。
unix系统有一个进程调度器,在终端可以通过renice来改变进程的优先级顺序。
启动新进程的方法:
1 通过system调用来启动
源代码
main()
{printf("start a new program");
system("ps -aux");
printf("end a program");
exit(0);
};
}
运行的结果只有test出来的信息,前面和后面的printf信息都没有出来,这是什么原因:只能打印start。然后就跳到了ps -aux程序。不能到printf(“end”)
将system("ps -aux &"),也没有讲这个命令打到后台,也就是说end 也没有打印出来 。
2 新进程的启动和程序参数传递有各种办法。
exec函数可以把当前进程替换为新进程。新进程由path和file指定。
2006 年 04 月 12日, 星期三
女人感悟
做人难,做女人更难。
女人心思细腻,对生活又有太多的期待,然而生活给女人的却不多,留不住地青春,盼不到的王子,等不来的奢华生活,进不去的贵族城堡。女人总在失望中继续自己的梦想,挫折过后的盼望,那种对梦想的偏执造就了一个女人一生的痛。从某种意义上来说,一个幸福的女人是很难的。
2006 年 04 月 05日, 星期三
linux内核解读入门
针对好多Linux 爱好者对内核很有兴趣却无从下口,本文旨在介绍一种解读linux内核源码的入门方法,而不是解说linux复杂的内核机制;一.核心源程序的文件组织:
1.Linux核心源程序通常都安装在/usr/src/linux下,而且它有一个非常简单的编号约定:任何偶数的核心(例如2.0.30)都是一个稳定地发行的核心,而任何奇数的核心(例如2.1.42)都是一个开发中的核心。
本文基于稳定的2.2.5源代码,第二部分的实现平台为 Redhat Linux 6.0。
2.核心源程序的文件按树形结构进行组织,在源程序树的最上层你会看到这样一些目录:
●Arch :arch子目录包括了所有和体系结构相关的核心代码。它的每一个子目录都代表一种支持的体系结构,例如i386就是关于intel cpu及与之相兼容体系结构的子目录。PC机一般都基于此目录;
●Include: include子目录包括编译核心所需要的大部分头文件。与平台无关的头文件在 include/linux子目录下,与 intel cpu相关的头文件在include/asm-i386子目录下,而include/scsi目录则是有关scsi设备的头文件目录;
●Init: 这个目录包含核心的初始化代码(注:不是系统的引导代码),包含两个文件main.c和Version.c,这是研究核心如何工作的一个非常好的起点。
●Mm :这个目录包括所有独立于 cpu 体系结构的内存管理代码,如页式存储管理内存的分配和释放等;而和体系结构相关的内存管理代码则位于arch/*/mm/,例如arch/i386/mm/Fault.c
●Kernel:主要的核心代码,此目录下的文件实现了大多数linux系统的内核函数,其中最重要的文件当属sched.c;同样,和体系结构相关的代码在arch/*/kernel中;
●Drivers: 放置系统所有的设备驱动程序;每种驱动程序又各占用一个子目录:如,/block 下为块设备驱动程序,比如ide(ide.c)。如果你希望查看所有可能包含文件系统的设备是如何初始化的,你可以看drivers/block/genhd.c中的device_setup()。它不仅初始化硬盘,也初始化网络,因为安装nfs文件系统的时候需要网络其他: 如, Lib放置核心的库代码; Net,核心与网络相关的代码; Ipc,这个目录包含核心的进程间通讯的代码; Fs ,所有的文件系统代码和各种类型的文件操作代码,它的每一个子目录支持一个文件系统,例如fat和ext2;
Scripts, 此目录包含用于配置核心的脚本文件等。
一般,在每个目录下,都有一个 .depend 文件和一个 Makefile 文件,这两个文件都是编译时使用的辅助文件,仔细阅读这两个文件对弄清各个文件这间的联系和依托关系很有帮助;而且,在有的目录下还有Readme 文件,它是对该目录下的文件的一些说明,同样有利于我们对内核源码的理解;
二.解读实战:为你的内核增加一个系统调用
虽然,Linux 的内核源码用树形结构组织得非常合理、科学,把功能相关联的文件都放在同一个子目录下,这样使得程序更具可读性。然而,Linux 的内核源码实在是太大而且非常复杂,即便采用了很合理的文件组织方法,在不同目录下的文件之间还是有很多的关联,分析核心的一部分代码通常会要查看其它的几个相关的文件,而且可能这些文件还不在同一个子目录下。
体系的庞大复杂和文件之间关联的错综复杂,可能就是很多人对其望而生畏的主要原因。当然,这种令人生畏的劳动所带来的回报也是非常令人着迷的:你不仅可以从中学到很多的计算机的底层的知识(如下面将讲到的系统的引导),体会到整个操作系统体系结构的精妙和在解决某个具体细节问题时,算法的巧妙;而且更重要的是:在源码的分析过程中,你就会被一点一点地、潜移默化地专业化;甚至,只要分析十分之一的代码后,你就会深刻地体会到,什么样的代码才是一个专业的程序员写的,什么样的代码是一个业余爱好者写的。
为了使读者能更好的体会到这一特点,下面举了一个具体的内核分析实例,希望能通过这个实例,使读者对 Linux的内核的组织有些具体的认识,从中读者也可以学到一些对内核的分析方法。
以下即为分析实例:
【一】操作平台:
硬件:cpu intel Pentium II ;
软件:Redhat Linux 6.0; 内核版本2.2.5【二】相关内核源代码分析:
1.系统的引导和初始化:Linux 系统的引导有好几种方式:常见的有 Lilo, Loadin引导和Linux的自举引导
(bootsect-loader),而后者所对应源程序为arch/i386/boot/bootsect.S,它为实模式的汇编程序,限于篇幅在此不做分析;无论是哪种引导方式,最后都要跳转到 arch/i386/Kernel/setup.S, setup.S主要是进行时模式下的初始化,为系统进入保护模式做准备;此后,系统执行 arch/i386/kernel/head.S (对经压缩后存放的内核要先执行 arch/i386/boot/compressed/head.S); head.S 中定义的一段汇编程序setup_idt ,它负责建立一张256项的 idt 表(Interrupt Descriptor Table),此表保存着所有自陷和中断的入口地址;其中包括系统调用总控程序 system_call 的入口地址;当然,除此之外,head.S还要做一些其他的初始化工作;
2.系统初始化后运行的第一个内核程序asmlinkage void __init start_kernel(void) 定义在
/usr/src/linux/init/main.c中,它通过调用usr/src/linux/arch/i386/kernel/traps.c 中的一个函数
void __init trap_init(void) 把各自陷和中断服务程序的入口地址设置到 idt 表中,其中系统调用总控程序system_cal就是中断服务程序之一;void __init trap_init(void) 函数则通过调用一个宏set_system_gate(SYSCALL_VECTOR,&system_call); 把系统调用总控程序的入口挂在中断0x80上; 其中SYSCALL_VECTOR是定义在 /usr/src/linux/arch/i386/kernel/irq.h中的一个常量0x80; 而 system_call 即为中断总控程序的入口地址;中断总控程序用汇编语言定义在/usr/src/linux/arch/i386/kernel/entry.S中;
3.中断总控程序主要负责保存处理机执行系统调用前的状态,检验当前调用是否合法, 并根据系统调用向量,使处理机跳转到保存在 sys_call_table 表中的相应系统服务例程的入口; 从系统服务例程返回后恢复处理机状态退回用户程序;
而系统调用向量则定义在/usr/src/linux/include/asm-386/unistd.h 中;sys_call_table 表定义在/usr/src/linux/arch/i386/kernel/entry.S 中; 同时在 /usr/src/linux/include/asm-386/unistd.h 中也定义了系统调用的用户编程接口;
4.由此可见 , linux 的系统调用也象 dos 系统的 int 21h 中断服务, 它把0x80 中断作为总的入口, 然后转到保存在 sys_call_table 表中的各种中断服务例程的入口地址 , 形成各种不同的中断服务;
由以上源代码分析可知, 要增加一个系统调用就必须在 sys_call_table 表中增加一项 , 并在其中保存好自己的系统服务例程的入口地址,然后重新编译内核,当然,系统服务例程是必不可少的。
由此可知在此版linux内核源程序中,与系统调用相关的源程序文件就包括以下这些:
1.arch/i386/boot/bootsect.S
2.arch/i386/Kernel/setup.S
3.arch/i386/boot/compressed/head.S
4.arch/i386/kernel/head.S
5.init/main.c
6.arch/i386/kernel/traps.c
7.arch/i386/kernel/entry.S
8.arch/i386/kernel/irq.h
9.include/asm-386/unistd.h
当然,这只是涉及到的几个主要文件。而事实上,增加系统调用真正要修改文件只有include/asm-386/unistd.h和arch/i386/kernel/entry.S两个;
【三】 对内核源码的修改:
1.在kernel/sys.c中增加系统服务例程如下:
asmlinkage int sys_addtotal(int numdata)
{
int i=0,enddata=0;
while(i<=numdata)
enddata+=i++;
return enddata;
}
该函数有一个 int 型入口参数 numdata , 并返回从 0 到 numdata 的累加值; 当然也可以把系统服务例程放在一个自己定义的文件或其他文件中,只是要在相应文件中作必要的说明;
2.把 asmlinkage int sys_addtotal( int) 的入口地址加到sys_call_table表中:
arch/i386/kernel/entry.S 中的最后几行源代码修改前为:
... ...
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */
.rept NR_syscalls-190
.long SYMBOL_NAME(sys_ni_syscall)
.endr
修改后为: ... ...
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */
/* add by I */
.long SYMBOL_NAME(sys_addtotal)
.rept NR_syscalls-191
.long SYMBOL_NAME(sys_ni_syscall)
.endr
3. 把增加的 sys_call_table 表项所对应的向量,在include/asm-386/unistd.h 中进行必要申明,以供用户进程和其他系统进程查询或调用:
增加后的部分 /usr/src/linux/include/asm-386/unistd.h 文件如下:
... ...
#define __NR_sendfile 187
#define __NR_getpmsg 188
#define __NR_putpmsg 189
#define __NR_vfork 190
/* add by I */
#define __NR_addtotal 191
4.测试程序(test.c)如下:
#include
#include
_syscall1(int,addtotal,int, num)
main()
{
int i,j;
do
printf("Please input a numbern");
while(scanf(" d",&i)==EOF);
if((j=addtotal(i))==-1)
printf("Error occurred in syscall-addtotal();n");
printf("Total from 0 to d is d n",i,j);
}
对修改后的新的内核进行编译,并引导它作为新的操作系统,运行几个程序后可以发现一切正常;在新的系统下对测试程序进行编译(*注:由于原内核并未提供此系统调用,所以只有在编译后的新内核下,此测试程序才能可能被编译通过),运行情况如下:
$gcc -o test test.c
$./test
Please input a number
36
Total from 0 to 36 is 666
可见,修改成功;
而且,对相关源码的进一步分析可知,在此版本的内核中,从/usr/src/linux/arch/i386/kernel/entry.S
文件中对 sys_call_table 表的设置可以看出,有好几个系统调用的服务例程都是定义在/usr/src/linux/kernel/sys.c 中的同一个函数:
asmlinkage int sys_ni_syscall(void)
{
return -ENOSYS;
}
例如第188项和第189项就是如此:
... ...
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */
... ...
而这两项在文件 /usr/src/linux/include/asm-386/unistd.h 中却申明如下:
... ...
#define __NR_sendfile 187
#define __NR_getpmsg 188 /* some people actually want streams */
#define __NR_putpmsg 189 /* some people actually want streams */
#define __NR_vfork 190
由此可见,在此版本的内核源代码中,由于asmlinkage int sys_ni_syscall(void) 函数并不进行任何操作,所以包括 getpmsg, putpmsg 在内的好几个系统调用都是不进行任何操作的,即有待扩充的空调用; 但它们却仍然占用着sys_call_table表项,估计这是设计者们为了方便扩充系统调用而安排的; 所以只需增加相应服务例程(如增加服务例程getmsg或putpmsg),就可以达到增加系统调用的作用。
结语:当然对于庞大复杂的 linux 内核而言,一篇文章远远不够,而且与系统调用相关的代码也只是内核中极其微小的一部分;但重要的是方法、掌握好的分析方法;所以上的分析只是起个引导的作用,而正真的分析还有待于读者自己的努力。:)