几点说明:
该系列文章中所用结构数据代码均来自linux2.6.39.
文章中若有不对或某些功能更好的实现方法,请指出或直接留言。
转载请注明出处:谢谢:http://blog.csdn.net/muge0913/article/details/7342977
1、那年,一步一步学linux c ---getchar()详解
2、那年,一步一步学linux c ---getch()详解
3、那年,一步一步学linux c ---关于EOF
4、那年,一步一步学linux c ---关于静态链接库
5、那年,一步一步学linux c ---关于动态链接库
6、那年,一步一步学linux c ---windows下的链接库~~~
7、那年,一步一步学linux c ---华为面试题之extern
8、那年,一步一步学linux c ---华为面试题之Variable
9、那年,一步一步学linux c ---浅谈动态内存
10、那年,一步一步学linux c ---让系统更安全之锁定内存~~~
11、那年,一步一步学linux c ---内存映像~~那些事~~
12、那年,一步一步学linux c ---“侠肝义胆”之进程
13、那年,一步一步学linux c ---关于进程等待~
14、那年,一步一步学linux c ---退出进程~
15、那年,一步一步学linux c ---信号处理~
16、那年,一步一步学linux c ---信号具体含义解释~
17、那年,一步一步学linux c ---信号处理方法 && 实际应用~
18、那年,一步一步学linux c ---signal 和sigaction
19、那年,一步一步学linux c ---信号集及操作
20、那年,一步一步学linux c ---sigprocmask 阻塞进程
21、那年,一步一步学linux c ---sigsuspend 进程阻塞
22、那年,一步一步学linux c ---sigsuspend 执行过程分析
23、那年,一步一步学linux c ---raise
24、那年,一步一步学linux c ---alarm
25、那年,一步一步学linux c ---setitimer
26、那年,一步一步学linux c ---信号处理潜在危险!!!
27、那年,一步一步学linux c ---文件类型和属性
28、那年,一步一步学linux c ---linux文件组织信息
29、那年,一步一步学linux c ---网络编程之通信机制和体系结构模式
30、那年,一步一步学linux c ---socket实例
31、那年,一步一步学linux c ---undefined reference to ‘pthread_create'问题解决
32、那年,一步一步学linux c ---线程互斥实例
33、那年,一步一步学linux c ---底层终端编程实例
34、那年,一步一步学linux c ---共享内存通信实例
35、那年,一步一步学linux c ---消息队列实例
36、那年,一步一步学linux c ---文件操作
EOF是 End Of File 的缩写。在C语言中,它是在标准库中定义的一个宏。
(1) 判断文件结束
多数人认为文件中有一个EOF,用于表示文件的结尾. 但这个观点实际上是错误的,在文
件所包含的数据中,并没有什么文件结束符. 对getc 而言, 如果不能从文件中读取,
则返回一个整数 -1,这就是所谓的EOF. 返回 EOF 无非是出现了两种情况,一是文件已
经读完; 二是文件读取出错,反正是读不下去了.
文件结束符EOF,Windows下为组合键Ctrl+Z,Unix/Linux下为组合键Ctrl+D
在linux中ctrl+c是程序结束命令就是向程序发送kill消息
一、getchar的两点总结:
1.当你输入一些有效数据时,最后加上enter键或ctrl+D键getchar才会从键盘缓冲区中读取数值。如下面程序段:
若是按enter键结束的,最后会打印出enter键当然此键是不可见的,并等待下次的输入。
若是按ctrl+D结束的,直接打印有效数据,并等待下次输入。
当你没有输入有效数据按下enter键会直接打印出enter键(当然这个键是不可见的),并再次等待下次的输入,直接按下ctrl+D键时,程序执行下面的程序代码,不在等待输入。
2.getchar()的返回值一般情况下是字符,但也可能是负值,即返回EOF。
这里要强调的一点就是,getchar函数通常返回终端所输入的字符,这些字符系统中对应的ASCII值都是非负的。因此,很多时候,我们会写这样的两行代码:
[cpp] view plaincopyprint?这样就很有可能出现问题。因为getchar函数除了返回终端输入的字符外,在遇到Ctrl+D(Linux下)即文件结束符EOF时,getchar()的返回EOF,这个EOF在函数库里一般定义为-1。因此,在这种情况下,getchar函数返回一个负值,把一个负值赋给一个char型的变量是不正确的。为了能够让所定义的变量能够包含getchar函数返回的所有可能的值,正确的定义方法如下(K&R C中特别提到了这个问题):
[cpp] view plaincopyprint?二、EOF的两点总结(主要指普通终端中的EOF)
1.EOF作为文件结束符时的情况:
EOF虽然是文件结束符,但并不是在任何情况下输入Ctrl+D(Windows下Ctrl+Z)都能够实现文件结束的功能,只有在下列的条件下,才作为文件结束符。
(1)遇到getcahr函数执行时,输入第一个字符时就直接输入Ctrl+D,就可以跳出getchar(),去执行程序的其他部分;
(2)在前面输入的字符为换行符时,接着输入Ctrl+D;
(3)在前面有字符输入且不为换行符时,要连着输入两次Ctrl+D,这时第二次输入的Ctrl+D起到文件结束符的功能,第一次的Ctrl+D使getchar开始读取键盘缓冲区中的数据。
其实,这三种情况都可以总结为只有在getchar()提示新的一次输入时,直接输入Ctrl+D才相当于文件结束符。
2.EOF作为行结束符时的情况,这时候输入Ctrl+D并不能结束getchar(),而只能引发getchar()提示下一轮的输入。
这种情况主要是在进行getchar()新的一行输入时,当输入了若干字符(不能包含换行符)之后,直接输入Ctrl+D,此时的Ctrl+D并不是文件结束符,而只是相当于换行符的功能,即结束当前的输入。以上面的代码段为例,如果执行时输入abc,然后Ctrl+D,程序输出结果为:
abcabc
注意:第一组abc为从终端输入的,然后输入Ctrl+D,就输出第二组abc,同时光标停在第二组字符的c后面,然后可以进行新一次的输入。这时如果再次输入Ctrl+D,则起到了文件结束符的作用,结束getchar()。
如果输入abc之后,然后回车,输入换行符的话,则终端显示为:
abc//第一行,带回车
abc//第二行
//第三行
其中第一行为终端输入,第二行为终端输出,光标停在了第三行处,等待新一次的终端输入。
从这里也可以看出Ctrl+D和换行符分别作为行结束符时,输出的不同结果。
EOF的作用也可以总结为:当终端有字符输入时,Ctrl+D产生的EOF相当于结束本行的输入,将引起getchar()新一轮的输入;当终端没有字符输入或者可以说当getchar()读取新的一次输入时,输入Ctrl+D,此时产生的EOF相当于文件结束符,程序将结束getchar()的执行。
【补充】本文第二部分中关于EOF的总结部分,适用于终端驱动处于一次一行的模式下。也就是虽然getchar()和putchar()确实是按照每次一个字符 进行的。但是终端驱动处于一次一行的模式,它的输入只有到"\n"或者EOF时才结束,因此,终端上得到的输出也都是按行的。
如果要实现终端在读一个字符就结束输入的话,下面的程序是一种实现的方法(参考《C专家编程》,略有改动)
编译运行该程序,则当如入一个字符时,直接出处一个字符,然后程序结束。
由此可见,由于终端驱动的模式不同,造成了getchar()输入结束的条件不一样。普通模式下需要回车或者EOF,而在一次一个字符的模式下,则输入一个字符之后就结束了。
总结:EOF并不是存在于文件中的,而是一种状态,当读到文件末尾或者读取出错时就会返回这个值来判断文件结束。(即即使读取错误可能也被认为文件结束,所以就需要用feof 和 ferror来判断是不是真的文件结束了)
当用getchar(c)时,即使c定义成字符型,也可以结束,主要是c与-1比较时,c也会从char转换为整型值。
写个小程序验证了一下
[java] view plaincopyprint?
得到的结果为ffffffff,所以c即使定义为char型,读取文件等时还是能正常结束。
在C语言的层面上,对代码的重复利用通常是通过库(library)的方式来实现的。传统意义上的库指的是以后缀.a结尾的文件。严格来讲,函数库应当分为两种:静态链接库和动态链接库,也称动态共享库。静态链接库通常是指以.a为后缀的文件,而动态链接库则常常以.so为后缀名。
静态链接库其实就是把一个或多个目标文件(即编译生成的.o文件)归档在一个文件中。此后,当需要使用这个静态库中的某个功能时,将这个静态库与要生成的应用程序链接在一起。
来讲讲ar工具~~~~
在Linux上平台上最常用的归档工具是GNU的tar,但是要构建静态库却不能使用tar,而要使用另一个工具ar。tar和ar都是归档工具,但是它们的目的是不同的。tar仅仅是用来创建归档文件(即通常以.tar为后缀的文件)的,ar也完成上述工作,但是做了一些额外的处理,它会为被归档的目标文件中的符号建立索引,当和应用程序链接时,建立的这些索引将回收链接过程。
ar比较经常用到的就是有三个命令选项:r(插入)、c(创建)和s(建立索引),而且这三个选项往往是一起使用。参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。参数c:创建一个库。不管库是否存在,都将创建。参数s:创建目标文件索引,这在创建较大的库时能加快时间。(补充:如果不需要创建索引,可改成大写S参数;如果。a文件缺少索引,可以使用ranlib命令添加)
现在假设有两个C文件,foo.cbar.c。首先将foo.c和bar.c编译为目标文件foo.o和bar.o,然后将这两个目标文件归档为一个静态链接库。
继续~~~~~
执行下令命令:~~~~
这基于PC平台的,如果是对于嵌入式平台的构建静态链接库而言,过程也是完全一样,唯一需要改变的可能是所用的工具名称。比如,如果要是为ARM-Linux构建静态库,那么可能需要使用arm-linux-ar。这里还有一个工具是nm,它可以用来取得目标文件的符号(symbol)信息。这里,nm打印出了libfoobar.a中的两个符号:foo和bar。这两个符号表示的都是函数,因此它们的符号值为0,符号类型为T(text,即表示该符号位于代码段)。最后一列给出的是符号的名称。
现的静态库是有了,要怎么使用这样的静态库呢。应用程序要使用静态库就必须要与静态库链接起来。这里假设有一个main.c的C文件。应用程序与静态库的链接是在编译期完成的.
总结一下啦~~~~
静态链接库是一种“复制式”的链接过程。何谓“复制式”的链接过程呢,当静态链接库与应用程序链接时,链接器会将静态链接库复制一份到最终得到的可执行代码中去。比如:现在有两个应用程序A和B,两者都要用到libfoobar.a所提供的功能。那么,在编译链接A时,链接器将复制一份libfoobar.a到A最终的可执行代码中去,libfoobar.a中的调试信息也会被复制,同样,在链接B时,链接器也会复制一份libfoobar.a到B最终的可执行代码中去。这就是“复制式”链接的意义。
查看foobar程序用到的动态链接库:
在上篇文章中,是对静态链接库的介绍,其实有了上面的介绍动态链接库的制作就简单了,这篇来制作动态链接库~~~
创建动态链接库:
或手动指定库路径
这里的-B 选项就添加 /path/to/lib 到gcc搜索的路径之中。这样链接没有问题但是方法II
中手动链接好的程序在执行时候仍旧需要指定库路径(链接和执行是分开的)。需要添加系
统变量 LD_LIBRARY_PATH :
查看动态链接库 和上次比 有发现没~~~~~~
直接写过程~~~~~
VisualC++6.0创建一个静态库。源文件的代码很简单,
头文件代码:
如果你需要在windows上面创建一个静态库,那么你需要进行下面的操作,
一步一步执行就行了~~~
(1)打开visual C++ 6.0工具,单击【File】-> 【New】->【Projects】
(2)选择【Win32 Static Library】,同时在【Project Name】写上项目名称,在【Location】选择项目保存地址
(3)单击【Ok】,继续单击【Finish】,再单击【Ok】,这样一个静态库工程就创建好了
(4)重新单击【File】->【New】->【Files】,选择【C++ Source Files】,
(5)选中【Add to pproject】,将源文件加入到刚才创建的工程中去,在File中输入文件名+.c后缀
(6)重复4、5的操作,加入一个文件名+.h头文件
(7)分别在头文件和源文件中输入上面的代码,单击F7按钮,即可在Debug目录中生成*.lib
华为C语言面试题
如何引用一个已经定义过的全局变量?
答:extern
可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错
3. 此外,extern修饰符可用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同
1.extern用在变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放在*.h中并用extern来声明。
2.如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有区别:
extern void print_externifo(); 和voidprint_externifo();
extern在 函数中的使用:
如果定义函数的c/cpp文件在对应的头文件中声明了定义的函数,那么在其他c/cpp文件中要使用这些函数,只需要包含这个头文件即可。
如果你不想包含头文件,那么在c/cpp中声明该函数。一般来说,声明定义在本文件的函数不用“extern”,声明定义在其他文件中的函数用“extern”,这样在本文件中调用别的文件定义的函数就不用包含头文件include“*.h”来声明函数,声明后直接使用即可。
举个例子:
编译:
华为面试题:
程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。
一点小总结:
1、关于堆栈,印象最深的就是在做uboot移植,在你准备进入c代码时,你必须给c代码一个良好的运行环境,其中重要的一项就是设置堆栈。
2、关于静态内存分配和动态内存分配的区别及过程
1) 静态内存分配是在编译时完成的,不需要占用CPU资源;动态分配内存是在运行时完成的,动态内存的分配与释放需要占用CPU资源;
2) 静态内存分配是在栈上分配的,动态内存是堆上分配的;
3) 动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要;
4) 静态分配内存需要在编译前确定内存块的大小,而动态分配内存不需要编译前确定内存大小,根据运行时环境确定需要的内存块大小,按照需要分配内存即可。可以这么说,静态内存分配是按计划分配,而动态内存分配是按需分配。
5) 静态分配内存是把内存的控制权交给了编译器,而动态内存是把内存的控制权交给了程序员;
综上所述,静态分配内存适合于编译时就已经可以确定需要占用内存多少的情况,而在编译时不能确定内存需求量时可使用动态分配内存;但静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,如果处理不当容易造成内存泄漏。
使用动态内存时需要用户自己去申请资源和释放资源。用户可以随时的分配所需空间,根据需要分配空间大小,并在最后释放申请内存。
动态内存也存在隐患:在大型的项目当中管理申请的动态内存是很复杂的,以及释放申请的内存有难想起的。在释放动态内存时可能不止一个指针指向了该内存,所以释放的时候是很容易出错的。内存无法释放就会造成内存泄露,这也就是为什么服务器要经常的每个一段时间重启的原因。
内存管理操作:
分配内存函数:
函数malloc中size是分配内存的大小,以字节为单位。
函数calloc中size是数据项的大小,nmemb是数据项的个数。所以分配内存大小为size*nmemb
malloc和calloc的最大区别是calloc会把申请到的内出初始化为0
调用成功都会返回分配内存的指针,调用失败都返回NULL
内存的调整:
对于realloc(),函数原型是void* realloc(void *ptr,size_t size),改变ptr所指内存区域的大小为size长度,size可以大于或小于原动态内存的大小,realloc通常是在原数据的基础上调整动态内存的大小是,原数据内容不变。当size大于原来的数据,且在原来位置无法调整时,realloc会重新开辟内存,把原来的数据复制到这来。如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。有一点需要注意:当分配内存成功之后,应将原本的指针ptr=NULL,否则会形成野指针,可能造成系统崩溃。不论是以上那种方式申请内存,在申请内存之后,最终都要用free释放空间,不然会造成内存泄漏。
如果ptr为NULL时realloc相当于malloc,如果size=0时相当于free
内存的释放:
free用于释放有malloc或calloc申请的动态内存。内存释放后再去使用指针会发生错误。
实例如下:
[cpp] view plaincopyprint?要把free(p2);注释掉
否则会出错,以为p1,p2同时指向了一个内存,通过p1释放了内存块,当通过p2再次释放内存当然就出错了。
pchar也指向了内存块,但是又过p1释放的内存,因为调用完alloc_test后pchar配释放了当内存块并没有被释放。
Linux 实现了请求页面调度,页面调度是说页面从硬盘按需交换进来,当不再需要的时候交换出去。这样做允许系统中每个进程的虚拟地址空间和实际物理内存的总量再没有直接的联系,因为在硬盘上的交换空间能给进程一个物理内存几乎无限大的错觉。
交换对进程来说是透明的,[url=]应用[/url]程序一般都不需要关心(甚至不需要知道)内核页面调度的行为。然而,在下面两种情况下,应用程序可能像影响系统的页面调度:
确定性(Determinism)
时间约束严格的应用程序需要确定的行为。如果一些内存操作引起了页错误,导致昂贵的磁盘操作,应用程序的速度便不能达到要求,不能按时做计划中的操作。如果能确保需要的页面总在内存中且从不被交换进磁盘,应用程序就能保证内存操作不会导致页错误,提供一致的,可确定的程序行为,从而提供了效能。
安全性(Security)
如果内存中含有私人秘密,这秘密可能最终被页面调度以不加密的方式储存到硬盘上。
例如,如果一个用户的私人密钥正常情况下是以加密的方式保存在磁盘上的,一个在内存中为加密的密钥备份最后保存在了交换文件中。在一个高度注重安全的环境中,这样做可能是不能被接受的。这样的应用程序可以请求将密钥一直保留在物理内存上。当然,改变内核的行为会导致系统整体性能的负面影响。当页面被锁定在内存中,一个应用程序的安全性可能提高了,但这能使得另外一个应用程序的页面被交换出去。如果内核的设计是值得信任的,它总是最优地将页面交换出去(看上去将来最不会被使用的页面)。
如果用户不希望某块内存在暂时不用时置换到磁盘上,可以对该内存进行内存锁定。
相关函数如下:
)
函数:mlock锁定一片内存区域,addr为内存地址,length要锁定的长度。
munlock接触已锁定的内存
mlockall一次锁定多个内存页。flag取值有两个MCL_CURRENT锁定所用内存页,MCL_FUTURE锁定为进程分配的地址空间内存页。munlockall用于解除锁定的内存。
注:只有超级用户才能进行锁定和解除内存操作。
内存映像其实就是在内存中创建一个和外存文件完全相同的映像。用户可以将整个文件映射到内存中也可以部分映射到内存。系统会将对内存映像的改动如实的反映到外存文件中。从而实现了通过内存映像对外存文件的操作。
内存映像的特点:
1、 可以加快对IO的操作速度。
2、 用户可以通过指针对文件进行操作,间接~~~
3、 实现了文件数据的共享,将外存文件映射到共享内存中,很方便的实现了数据共享,并能完成把数据保存到外存的工作。
注:内存映像只能对内部可以定位的文件进行操作,如普通文件。不能对管道,套接字文件进行操作。
创建内存映射:
start为指针通常设为NULL,表示映射内存有系统决定。因为指定内存会经常出错。
length为内存映像占用的内存空间大小。以字节为单位。
port表示内存映像的安全性。
PROT_EXEC表示被映像内存可能有机器码,可执行。
PORT_NONE表示被映像内存不能被访问。
PORT_READ表示被映像内存可读
PORT_WRITE表示被映像内存可写
flag内存映像标志:
MAP_FIXED表示如果无法从start地址建立内存映像,则出错返回。
MAP_PRIVATE表示对内存映像进行的改动不反映到外存文件中。
MAP_SHARED表示对内存映像进行的改动反映到外存文件中。
fd文件描述符
offset表示所映像的内容距文件头的距离。
撤销内存映射:
改变内存属性:
修改保护值:
addr表示地址和上面相同。
length内存映像大小同上。
prot重新设定的保护值。
成功返回0失败返回-1
修改内存镜像大小:
flg用于设置是否在需要移动内存镜像时移动该镜像。如:MRMAP_MAYMOVE
调用成功返回新的起始地址,失败返回-1
程序如下:
如果我们把计算机上的操作系统及各种各样的软件看成一系列的有机生命,而不是指令集,那么这就是一个进程的世界,在进程的世界中同样有“道德”和“法制法规”,窥探进程世界,看它的侠肝义胆,风雨江湖路~~~~~
linux支持多个进程同时进行,也就是我们常说的现代操作系统中的多道程序设计,所谓同时是linux系统调度各个进程分别占用cpu的时间。由于每个时间片的时间很小和宏观时间相比,给人的感觉是多个进程在运行。
注:总结下就是在微观是串行,在宏观上是并行。
为了提高程序的运行效率,程序往往分成多个部分组成,这也就是说的并发程序设计。并发程序中各进程是相互独立的,在必要的时候会通过相应的机制进行通信。若进程间要共享资源,为了避免出现冲突,常通过相应通信机制使它们轮流使用共享资源。在进程进行通信时,会出现一个进程等另一个进程完,才能继续运行的情况,这也需要进程间通信以了解对方的运行情况。有时进程间会出现互斥现象,这是会用到锁机制。在并发程序设计中,进程的创建和结束是由用户决定的。这也就出现了父进程和子进程概念。
进程的创建:
[cpp] view plaincopyprint?在这简述,fork创建的子进程是父进程的一个拷贝,但是和父进程使用不同的数据段和堆栈。vfork和fork基本相同但是vfork不会复制父进程的数据段,它们共享数据段。这是因为vfork常和exec函数使用去调用一个程序如ls命令,开启一个新的进程。vfork后父进程会等待子进程运行结束或调用了exit。fork后父进程和子进程的运行顺序是不确定的。
下面是体现它们性质的程序:
[cpp] view plaincopyprint?把上面的fork改为vfork即可
fork:
vfork:
等待进程改变其状态。所有下面哪些调用都被用于等待子进程状态的改 变,获取状态已改变的子进程信息。状态改变可被认为是:1.子进程已终止。2.信号导致子进程停止执行。3.信号恢复子进程的执行。在子进程终止的情况 下,wait调用将允许系统释放与子进程关联的资源。如果不执行wait,终止了的子进程会停留在"zombie"状态。
如果发现子进程改变了状态,这些调用会立即返回。反之,调用会被阻塞直到子进程状态改变,或者由信号处理句柄所中断(假如系统调用没有通过sigaction的SA_RESTART标志重启动)。
wait 系统调用挂起当前执行中的进程,直到它的一个子进程终止。waitpid挂起当前进程的执行,直到指定的子进程状态发生变化。默认,waitpid只等待 终止状态的子进程,但这种行为可通过选项来改变。waitid系统调用对于等待哪个子进程状态改变提供了更精确的控制。
子进程已终 止,父进程尚未对其执行wait操作,子进程会转入“僵死”状态。内核为“僵死”状态的进程保留最少的信息量(进程标识,终止状态,资源使用信息),过后 父进程执行wait时可以获取子进程信息。只要僵死的进程不通过wait从系统中移去,它将会占据内核进程表中的一个栏位。如果进程表被填满,内核将不能 再产生新进程。如果父进程已终止,它的僵死子进程将由init进程收养,并自动执行wait将它们移去。
wait(等待子进程中断或结束)
函数说明
wait()会暂时停止目前进程的执行(挂起父进程),直到有信号来到或子进程结束。如果在调用 wait()时子进程已经结束,则 wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数 status 可以设成 NULL。如果调用wait的进程没有子进程则会调用失败,子进程的结束状态值请参考 waitpid( )
如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中。
linux中常用退出函数:
atexit:在其中注册的无参数函数在退出时调用。成功返回0失败返回-1,并影响errno
on_exit:在其中注册的有参数函数在退出时调用。成功返回0失败返回-1,并影响errno
assert是宏定义,检查是否出错,出错则退出。
abort发送SIGABRT消息结束当前进程。
exit和_exit函数都是用来终止进程的。当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。但是,这两个函数是有区别的。
exit()函数与_exit()函数最大区别就在于exit()函数在调用do_exit之前要检查文件的打开情况,把文件缓冲区的内容写回文件。
由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数。
exit的函数声明在stdlib.h头文件中。
_exit的函数声明在unistd.h头文件当中。
下面的实例比较了这两个函数的区别。printf函数就是使用缓冲I/O的方式,该函数在遇到“\n”换行符时自动的从缓冲区中将记录读出。实例就是利用这个性质进行比较的。
exit.c源码
输出信息:
Usingexit...
Thisis the content in buffer
则只输出:
Usingexit...
说明:在一个进程调用了exit之后,该进程并不会马上完全小时,而是留下一个称为僵尸进程(Zombie)的数据结构。僵尸进程是一种非常特殊的进程,它几乎已经放弃了所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其它进程收集,除此之外,僵尸进程不再占有任何内存空间。
信号处理是linux程序的一个特色。用信号处理来模拟操作系统的中断功能。要想使用信号处理功能,你要做的就是填写一个信号处理函数即可。
执行,,,,,
信号及其简介
信号是一种进程通信的方法,他应用于异步事件的处理。信号的实现是一种软中断。它被发送为一个正在运行的进程,已告知进程某个事件发生了。
1) SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控
制进程结束时, 通知同一session内的各个作业,这时它们与控制终端不再关联.
2) SIGINT 程序终止(interrupt)信号,通常是从终端发出中断指令如ctrl+c或delete键
3) SIGQUIT 和SIGINT类似,但由QUIT字符(通常是Ctrl+\)来控制.进程在因收到
SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号.
4) SIGILL 执行了非法指令.通常是因为可执行文件本身出现错误,或者试图执行
数据段. 堆栈溢出时也有可能产生这个信号.
5) SIGTRAP 跟踪陷阱信号,由断点指令或其它trap指令产生.由debugger使用.
6) SIGABRT 调用abort时产生的信号,将会使进程非正常结束。
6) SIGIOT IO错误信号.
7) SIGBUS 系统总线错误时产生的信号,非法地址,包括内存地址对齐(alignment)出错.eg: 访问一个四个字长
的整数, 但其地址不是4的倍数.
8) SIGFPE 在发生致命的算术运算错误时发出.不仅包括浮点运算错误,还包括溢
出及除数为0等其它所有的算术的错误.
9) SIGKILL 可以终止任何进程的信号,只能由管理员发出,该信号不会被捕捉和忽略。
10) SIGUSR1 留给用户使用,用户可在应用程序中自行定义。
11) SIGSEGV 试图访问未分配给自己的内存,或试图往没有写权限的内存地址写数据,非法使用内存地址信号。
12) SIGUSR2 留给用户使用
13) SIGPIPE 当一个进程对管道进行完读后进行写时产生的信号。
14) SIGALRM 时钟定时信号,由alarm函数设定的时间终止时产生的信号。
15) SIGTERM 程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理.通常用来要求程序自己正常退出.shell命令kill缺省产生这个信号.
17) SIGCHLD 子进程结束或中断时产生该信号,父进程会收到这个信号.通过该信号父进程可以知道子进程的运行情况。但大多数情况下此信号会被忽略。
18) SIGCONT 让一个停止(stopped)的进程继续执行.本信号不能被阻塞.
19) SIGSTOP 停止(stopped)进程的执行.注意它和terminate以及interrupt的区别:
该进程还未结束, 只是暂停执行.本信号不能被阻塞,处理或忽略.
20) SIGTSTP 停止进程的运行,但该信号可以被处理和忽略.用户键入SUSP字符时
(通常是Ctrl-Z)发出这个信号
21) SIGTTIN 当后台作业要从用户终端读数据时,中断驱动器产生的信号。当读入数据的进程阻塞或忽略这个信号,或读取数据的进程所在进程组是一个孤立进程组时,信号不会发生,并且发生读错误。errno被设为ETO
22) SIGTTOU 类似于SIGTTIN,当后台作业要从用户终端读数据时,中断驱动器产生的信号。当读入数据的进程阻塞或忽略这个信号,或读取数据的进程所在进程组是一个孤立进程组时,信号不会发生,并且发生读错误。errno被设为ETO。唯一不同的是进程可以选择后台写。
23) SIGURG socket上出现紧急情况是发出的信息。
24) SIGXCPU 超过CPU时间资源限制.这个限制可以由getrlimit/setrlimit来读取/改变
25) SIGXFSZ 超过文件大小资源限制.
26) SIGVTALRM 虚拟时钟信号.类似于SIGALRM,但是计算的是该进程占用的CPU时间.
27) SIGPROF 类似于SIGALRM/SIGVTALRM,但包括该进程用的CPU时间以及系统调用的时间.
28) SIGWINCH 窗口大小改变时发出.
29) SIGIO 文件描述符准备就绪,可以开始进行输入/输出操作.
30) SIGPWR Power failure 电源失效信号。
31)SIGEMT实时硬件发生错误时产生的信号。
有两个信号可以停止进程:SIGTERM和SIGKILL。 SIGTERM比较友好,进程能捕捉这个信号,根据您的需要来关闭程序。在关闭程序之前,您可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。
对于SIGKILL信号,进程是不能忽略的。这是一个 “我不管您在做什么,立刻停止”的信号。假如您发送SIGKILL信号给进程,Linux就将进程停止在那里。
注:有六个信号被称为作业信号,SIGCHLD,SIGCONT,SIGSTOP,SIGSTP,SIGTTNI,SIGTTOU.这些信号都是用于协调和组织各个进程的,也就是实现所谓的作业控制。通常情况下用户不需要对这些信号进行处理,shell会自动完成对这些信号的处理工作。信号之间是相互影响的,当进程接收到SIGCONT信号时,被系统悬挂的SIGSTOP,SIGSTP,SIGTTIN,SIGTTOU将失效。同样进程接收到SIGSTOP,SIGSTP,SIGTTIN,SIGTTOU时,SIGCONT将失效。
信号是用于一步事件的。当一个信号发生时,程序会按照已经设定好的程序来执行相应的操作。
进程对信号处理的方法一般有两种:
1、捕捉信号:当一个信号发送个进程时,该进程会调用此信号注册的信号处理函数,来完成相应的操作。对应于每个信号系统一般会有相应的默认处理函数(一般为终止进程)。所以可以设置信号为默认的处理函数。
2、忽略信号。当信号发送时,进程忽略信号。
注:有两个信号是无法捕捉和忽略的SIGKILL和SIGSTOP。它们是提供给管理员,可以在任何时刻终止某个进程而设定的。
对信号处理的要求:在用户编程时有时进程需要对某信号进行立即响应。对有些实时进程来说,当它执行时是不愿意被打断的,这是就需要把接收的信号挂起。
信号的使用:
信号最常见的一个应用就是发生错误时通知进程结束。对于许多错误,如bus错误,浮点错误,调用内存错误等都有相应的信号通知进程。
此外信号还有其他用途。如运行一个大型的科学运算程序,若是在一个无穷循环中用printf来显示运行状态,势必造成运行效率的下降。所以通过信号,人为的向进程发送消息,来查看运行状态,就大大的提高了运行效率。
要对一个信号进行处理,就需要给出此信号发生时系统所调用的处理函数。可以对一个特定的信号(除去SIGKILL和SIGSTOP信号)注册相应的处理函数。注册某个信号的处理函数后,当进程接收到此信号时,无论进程处于何种状态,就会停下当前的任务去执行此信号的处理函数。
1、注册信号函数。
signumber表示信号处理函数对应的信号。func是一个函数指针。此函数有一整型参数,并返回void型。其实func还可以取其他定值如:SIG_IGN,SIG_DFL.
SIG_IGN表示:忽略signumber所指出的信号。SIG_DFL表示表示调用系统默认的处理函数。signal函数的返回值类型同参数func,是一个指向某个返回值为空并带有一个整型参数的函数指针。其正确返回值应为上次该信号的处理函数。错误返回SIG_ERR
signal示例如下:
通常情况下一个用户进程需要处理多个信号。可以在一个程序中注册多个信号处理函数。一个信号可以对应一个处理函数,同时多个信号可以对应一个处理函数。
对于SIGINT信号 我们可以用ctrl+c或ctrl+z来中断进程,来执行SIGINT注册的函数。
2、 高级信号处理。
在linux系统提供了一个功能更强的系统调用。
此函数除能注册信号函数外还提供了更加详细的信息,确切了解进程接收到信号,发生的具体细节。
struct sigaction的定义如下:在linux2.6.39/include/asm-generic/signal.h中实现
siginfo_t在linux2.6.39/include/asm-generic/siginfo.h中实现:
sa_flags的取值如下表,取0表示选用所有默认选项。
SA_NOCLDSTOP:用于表示信号SIGCHLD,当子进程被中断时,不产生此信号,当且仅当子进程结束时产生此信号。
SA_NOCLDWATI:当信号为SIGCHLD,时可避免子进程僵死。
SA_NODEFER:当信号处理函数正在进行时,不堵塞对于信号处理函数自身信号功能。
SA_NOMASK:同SA_NODEFER
SA_ONESHOT:当用户注册的信号处理函数被执行过一次后,该信号的处理函数被设为系统默认的处理函数。
SA_RESETHAND:同SA_ONESHOT
SA_RESTART:是本来不能重新于运行的系统调用自动重新运行。
SA_SIGINFO:表明信号处理函数是由SA_SIGACTION指定的,而不是由SA_HANDLER指定的,它将显示更多的信号处理函数信息。
其实sinaction完全可以替换signal函数
在实际的应用中一个应用程序需要对多个信号进行处理,为了方便,linux系统引进了信号集的概念。信号集用多个信号组成的数据类型sigset_t.可用以下的系统调用设置信号集中所包含的数据。
1、常见信号及定义如图:
2、sigset_t在linux2.6.39/include/asm-generic/signal.h中定义
3、相应的系统调用函数:
set表示信号集指针,setnumber表示信号。
sigemptyset用于将set指向的信号集设为空,即不包含任何信号。
sigfillset用于将set指向的信号集设为满,即包含所有的信号。
sigaddset用于向信号集中添加信号。
sigdelset用于向信号集中删除信号。
以上函数成功返回0,失败返回-1
4、另外,int sigismember(const sigset_t set,int signumber),用于检测signumber是否在set中,若属于返回1,不是返回0.