1) 正常返回的时候,waitpid返回收集到的子进程的进程ID;
2) 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可
收集,则返回0; 3) 调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。
例如:当pid所指示的子进程不存在,或此进程存在,但不是调用进程的
子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
3.3.3.2.3 捕获SIGCHLD信号,在处理函数中调用wait()或waitpid().
void sigchld_handler(int signo) {
pid_t pid; int stat;
while( (pid=waitpid(-1,&stat,WNOHANG))>0 )
{} return; }
void main() {
struct sigaction act,oldact; act.sa_handler=sigchld_handler; sigemptyset(&act.sa_mask); act.sa_flags=0;
if(sigaction(SIGCHLD,&act,&oldact)<0) {
…… } }
3.3.3.2.4 两次调用fork().
第一次调用fork()产生的子进程的主进程,调用exit(0), 第二次调用fork()产生的子进程成为孤儿(orphaned process)进程,交给init管理,孤儿进程退出时.系统会把它清理干净。
int main() {
int i;
pid_t pid; pid=fork(); if(pid==0)
{ for(i=0;i<5;i++) {
if(fork(0)==0) { sleep(1); exit(0); } }
exit(0);
}
for(;;){}
}
总结:进程的一生
随着一句fork,一个新进程呱呱落地,但它这时只是老进程的一个克隆。 然后随着exec,新进程脱胎换骨,离家独立,开始了为人民服务的职业生涯。
人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个\,从容地离我们而去;也可以是自杀,自杀有2种方式,一种是调用exit函数,一种是在main函数内使用return,无论哪一种方式,它都可以留下遗书,放在返回值里保留下来;它还甚至能可被谋杀,被其它进程通过另外一些方式结束他的生命。
进程死掉以后,会留下一具僵尸,wait和waitpid充当了殓尸工,把僵尸推去火化,使其最终归于无形。
3.3.4守护进程
Linux有三种进程:核心进程、守护进程、用户进程。 守护进程在后台运行,如:打印管理程序、http服务器 Linux的大多数服务器就是用守护进程实现的。
守护进程的编程要点
不同Unix环境下守护进程的编程规则并不一致,但守护进程的编程原则其实都一样,区别在于具体的实现细节不同。这个原则就是要满足守护进程的特性。同时,Linux是基于Syetem V的SVR4并遵循Posix标准,实现起来与BSD4相比更方便。编程要点如下; 1. 在后台运行
在进程中调用fork后,使父进程终止,让Daemon在子进程中后台执行。
if(pid=fork())
exit(0);//是父进程,结束父进程,子进程继续
2. 脱离控制终端,登录会话和进程组
进程与控制终端,登录会话和进程组之间的关系:
进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。 登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。会话过程对控制终端具有独占性
由于控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: setsid();
说明:当进程是会话组长时setsid()调用失败。
第一点已经保证进程不是会话组长。 setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,同时与控制终端脱离。
1. 忽略SIGHUP,再次调用fork(),然后父进程退出。
目的:禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
2. 关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下,以及引起无法预料的错误。按如下方法关闭它们:
for(i=0;i 关闭打开的文件描述符close(i);)
3. 改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir(\
4. 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0),使得进程具有完全的写权限;
5. 处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。 signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。
守护进程实例
这个守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp目录中的日志test.log报告运行状态。
初始化程序中的init_daemon函数负责生成守护进程。可以利用init_daemon函数生成自己的守护进程。 1. init.c清单
#include < unistd.h >
#include < signal.h > #include < sys/param.h > #include < sys/types.h > #include < sys/stat.h > void init_daemon(void) {
int pid; int i;
if(pid=fork())
exit(0);//是父进程,结束父进程 else if(pid< 0)
exit(1);//fork失败,退出
//第一子进程,在后台继续执行
setsid(); //第一子进程成为新的会话组长和进程组长 //并与控制终端分离
if(pid=fork())
exit(0); //是第一子进程,结束第一子进程 else if(pid< 0)
exit(1); //fork失败,退出
//第二子进程,继续
//第二子进程不再是会话组长
for(i=0;i< NOFILE;++i)//关闭打开的文件描述符
close(i);
chdir(\改变工作目录到/tmp umask(0);//重设文件创建掩模 return; }
2. test.c清单
#include < stdio.h > #include < time.h >
void init_daemon(void);//守护进程初始化函数 main() {
FILE *fp;
time_t t;
init_daemon();//初始化为Daemon
while(1)//每隔一分钟向test.log报告运行状态 {
sleep(60);//睡眠一分钟
if((fp=fopen(\ {
t=time(0);
fprintf(fp,\ fclose(fp); } } }
编译:gcc -g -o test init.c test.c 执行:./test
查看进程:ps -ef
从输出可以发现test守护进程的各种特性满足上面的要求。
3.4.进程间通讯处理 3.4.1共享内存需要锁机制
由于共享内存在设计时没有处理锁机制,故当有多个进程在访问共享 内存时就会产生问题.如:一个进程修改一个共享内存单元,另一个进程在 读该共享内存单元时可能有第三个进程立即修改该单元,从而会影响程序 的正确性.同时还有分时系统对各进程是分时间片处理的,可能会引起不 同的正确性问题.按操作系统的运作方式,则有读锁和写锁来保证数据的 一致性.所以没有锁机制的共享内存,必须和信号量一起使用,才能保证共 享内存的正确操作.
3.4.2消息队列需要关键值
消息队列的操作在进程取得消息队列的访问权限后就必须通过关键 值来读消息队列中的相同关键值的消息,写消息时带入消息关键值.这样 可以通过不同的关键值区分不同的交易,使得在同一个消息队列可以供多 种消息同时使用而不冲突.若读消息队列使用关键值0则读取消息队列中 第一个消息,不论其关键值如何.
3.4.3信号需要信号处理函数设置和再设置
在用户进程需要对某个中断做自己定义的处理时,可以自己定义中断