1,信号有那些是不能被屏蔽的???(SIGKILL和SIGSTOP)
2,僵尸进程是什嘛样子的,产生子进程的时候给其分配空间了之后,在变成僵尸进程之后,对这块空间是如何处理
的,是将其全部释放掉???还是保留部分空间给(子进程的进程ID、终止状态以及资源利用信息(CPU时间,内
存使用量),因为父进程可能会用到这些东西,????
信号(signal)是Linux进程间通信的一种机制,全称为软中断信号,也被称为软中断。信号本质上是在软件层次上对硬件中断机制的一种模拟。信号提供了一种处理异步事件的方法。每个信号的名字都以SIG字符开头,为正整型常量,定义在
信号有3种处理方式:
(1) 忽略此信号。大多数信号都使用这种方式处理。但是有两种信号不能被忽略:SIGKILL和SIGSTOP。原因是这两种信号向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存引用、除0错误等),那进程的运行行为是未定义的。
(2) 捕捉信号。通知内核在某种信号发生时,调用一个用户函数来处理该信号。同样地,不能捕捉SIGKILL和SIGSTOP信号。
(3) 执行系统默认动作。对大多数信号而言,系统默认动作就是终止该信号。
进程捕捉到信号并对其进行处理时,正常执行的指令序列就会被中断,首先需要执行信号处理程序,之后则应该接着执行之前未完成的指令序列。但是在信号处理程序中,并不能判断捕捉到信号时进程执行到什么地方,如果进程正在执行malloc,而此时由于捕捉到信号而插入信号处理函数也要调用malloc,此时,由于malloc通常会为它所分配的存储区维护一个链表,而插入信号处理函数时,该进程正在修改链表,那么结果是进程环境遭到破坏,丢失重要信息。
Single Unix Specification说明了在信号处理程序中保证调用安全的函数,这些函数是可再入的,称为异步信号安全(async-signal safe)函数。除了可再入外,在信号处理期间,它会阻塞任何引起不一致的信号发送。
一般不可再入函数有如下特点:
(1) 使用静态数据结构;
(2) 调用malloc或free;
(3) 属于标准I/O函数,因为很多标准I/O库都以不可再入的方式使用了全局数据结构。
不可靠的信号是指信号在处理之前可能丢失。在发送一个信号给进程时,我们说向进程递送(delivery)了一个信号。在信号产生(generation)和递送之间的时间间隔内,称该信号是未决(pending)的。
如果进程采用“阻塞信号递送”(每个信号都有一个信号屏蔽字(signal mask),它规定了当前要阻塞递送到该进程的信号集。进程可以调用sigprocmask来检测和更改当前信号屏蔽字。进程调用sigpending函数来判断哪些信号是设置为阻塞并处于pending状态),而且对该信号的处理是采用系统默认动作或者捕捉该信号,那么该信号将一直保持未决状态,直到进程对信号解除阻塞,或者对该信号的处理改为忽略。
每种信号都会有一个默认动作。默认动作就是脚本或程序接收到该信号所做出的默认操作。常见的默认动作有终止进
程、退出程序、忽略信号、重启暂停的进程等,上表中也对部分默认动作进行了说明。
kill -signal pid
signal为要发送的信号,可以是信号名称或数字;pid为接收信号的进程ID。例如:
kill -1 1001将SIGHUP信号发送给进程ID为1001的程序,程序会终止执行
kill -9 1001
trap commands signals
commands为Linux系统命令或用户自定义命令;signals为要捕获的信号,可以为信号名称或数字。
捕获到信号后,可以有三种处理:
trap "rm -f $WORKDIR/work1$$ $WORKDIR/dataout$$; exit" 2
trap "rm $WORKDIR/work1$$ $WORKDIR/dataout$$; exit" 1 2
trap 'rm $WORKDIR/work1$$ $WORKDIR/dataout$$; exit' 1 2
$ trap '' 2
也可以同时忽略多个信号:
$ trap '' 1 2 3 15
注意:必须被引号包围,不能写成下面的形式:
$ trap 2
$ trap 1 2
将恢复SIGHUP 和 SIGINT 信号的默认动作。
接下来谈谈僵尸进程:
比如进程采用exit()退出的时候,操作系统会进行一系列的处理工作,包括关闭打开的文件描述符、占用的内存等等,但是,操作系统也会为该进程保留少量的信息,比如进程ID号等信息,因而占用了系统的资源。在一种极端的情况下,档僵尸进程过多的时候,占用了大量的进程ID,系统将无法产生新的进程,相当于系统的资源被耗尽。
给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)。
这里提到的这些信息等等都是将其写入到内核中的,该进程所占有的全部空间都已经释放掉了。。。。。
如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(init进程将wait它们,从而去除僵尸状态)。
但通常情况下,我们是不愿意留存僵尸进程的,它们占用内核中的空间,最终可能导致我们耗尽进程资源。那么为什么会产生僵尸进程以及如何避免产生僵尸进程呢?下边我将从这两个方面进行分析。
僵尸进程的原因
我们知道,要在当前进程中生成一个子进程,一般需要调用fork这个系统调用,fork这个函数的特别之处在于一次调用,两次返回,一次返回到父进程中,一次返回到子进程中,我们可以通过返回值来判断其返回点:
pid_t child = fork();
if( child < 0 ) { //fork error.
perror("fork process fail.\n");
} else if( child ==0 ) { // in child process
printf(" fork succ, this run in child process\n ");
} else { // in parent process
printf(" this run in parent process\n ");
}
让父进程休眠600s, 然后子进程先退出,我们就可以看到先退出的子进程成为僵尸进程了(进程状态为Z)
但是,但是:子进程如果处理的时间比较长的话,主进程会被挂起
避免产生僵尸进程:
我们知道了僵尸进程产生的原因,下边我们看看如何避免产生僵尸进程。
子进程的子进程的避免:
对于这样的情况可以采取连续fork()两次的方法。简而言之,首先父进程首先创建子进程,子进程创建孙子进程,由孙子进程处理事务,
而子进程再创建完孙子进程后,就退出。此时,孙子进程的父进程,也就是子进程退出了,因此孙子进程变为了一个孤儿进程,Linux进程处理
孤儿的进程的方式,是init进程接管孤儿进程,而init进程的子进程不会成为僵尸进程。
一般,为了防止产生僵尸进程,在fork子进程之后我们都要wait它们;同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的。如下代码所示:
void sig_chld( int signo ) {
pid_t pid;
int stat;
pid = wait(&stat);
printf( "child %d exit\n", pid );
return;
}
int main() {
signal(SIGCHLD, &sig_chld);
}
然后,即便我们捕获SIGCHLD信号并且调用wait来清理退出的进程,仍然不能彻底避免产生僵尸进程;我们来看一种特殊的情况:
我们假设有一个client/server的程序,对于每一个连接过来的client,server都启动一个新的进程去处理来自这个client的请求。然后我们有一个client进程,在这个进程内,发起了多个到server的请求(假设5个),则server会fork 5个子进程来读取client输入并处理(同时,当客户端关闭套接字的时候,每个子进程都退出);当我们终止这个client进程的时候 ,内核将自动关闭所有由这个client进程打开的套接字,那么由这个client进程发起的5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个。server端接受到这5个FIN的时候,5个子进程基本在同一时刻终止。这就又导致差不多在同一时刻递交5个SIGCHLD信号给父进程,如图:正是这种同一信号多个实例的递交造成了我们即将查看的问题。
我们首先运行服务器程序,然后运行客户端程序,运用ps命令看以看到服务器fork了5个子进程,如图:
然后我们Ctrl+C终止客户端进程,在我机器上边测试,可以看到信号处理函数运行了3次,还剩下2个僵尸进程,如图:
通过上边这个实验我们可以看出,建立信号处理函数并在其中调用wait并不足以防止出现僵尸进程,其原因在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为Unix信号一般是不排队的 。 更为严重的是,本问题是不确定的,依赖于客户FIN到达服务器主机的时机,信号处理函数执行的次数并不确定。正确的解决办法是调用waitpid而不是wait,这个办法的方法为:信号处理函数中,在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,他告知waitpid在有尚未终止的子进程在运行时不要阻塞。(我们不能在循环内调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞,wait将会阻塞到现有的子进程中第一个终止为止),下边的程序分别给出了这两种处理办法(func_wait, func_waitpid)。
僵尸进程的深层次探究:
什么是僵尸进程?
首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。
而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。
任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称外僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。
僵尸进程的目的?
设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。
如何避免僵尸进程?
第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
僵尸进程处理办法
#include
#include
pid_t wait(int *status);
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:
pid = wait(NULL);
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
可以上述的一些宏判断子进程的退出情况:
#include
#include
pid_t waitpid(pid_t pid, int *status, int options);
参数:
status:如果不是空,会把状态信息写到它指向的位置,与wait一样
options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起
The value of options is an OR of zero or more of the following con-
stants:
WNOHANG return immediately if no child has exited.
WUNTRACED also return if a child has stopped (but not traced via
ptrace(2)). Status for traced children which have stopped
is provided even if this option is not specified.
WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by delivery
of SIGCONT.
返回值:如果成功返回等待子进程的ID,失败返回-1
pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。
pid > 0 等待其进程I D与p i d相等的子进程。
pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。
pid < -1 等待其组I D等于p i d的绝对值的任一子进程
示例:
如以下代码会创建100个子进程,但是父进程并未等待它们结束,所以在父进程退出前会有100个僵尸进程。
#include#include int main() { int i; pid_t pid; for(i=0; i<100; i++) { pid = fork(); if(pid == 0) break; } if(pid>0) { printf("press Enter to exit..."); getchar(); } return 0; }
其中一个解决方法即是编写一个SIGCHLD信号处理程序来调用wait/waitpid来等待子进程返回。
#include#include #include #include #include void wait4children(int signo) { int status; wait(&status); } int main() { int i; pid_t pid; signal(SIGCHLD, wait4children); for(i=0; i<100; i++) { pid = fork(); if(pid == 0) break; } if(pid>0) { printf("press Enter to exit..."); getchar(); } return 0; }
但是通过运行程序发现还是会有僵尸进程,而且每次僵尸进程的数量都不定。这是为什么呢?其实主要是因为Linux的信号机制是不排队的,假如在某一时间段多个子进程退出后都会发出SIGCHLD信号,但父进程来不及一个一个地响应,所以最后父进程实际上只执行了一次信号处理函数。但执行一次信号处理函数只等待一个子进程退出,所以最后会有一些子进程依然是僵尸进程。
虽然这样但是有一点是明了的,就是收到SIGCHLD必然有子进程退出,而我们可以在信号处理函数里循环调用waitpid函数来等待所有的退出的子进程。至于为什么不用wait,主要原因是在wait在清理完所有僵尸进程后再次等待会阻塞。
所以最佳方案如下:
#include#include #include #include #include #include void wait4children(int signo) { int status; while(waitpid(-1, &status, WNOHANG) > 0); } int main() { int i; pid_t pid; signal(SIGCHLD, wait4children); for(i=0; i<100; i++) { pid = fork(); if(pid == 0) break; } if(pid>0) { printf("press Enter to exit..."); getchar(); } return 0; }
这里使用waitpid而不是使用wait的原因在于:我们在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,它告诉waitpid在有尚未终止的子进程在运行时不要阻塞。我们不能在循环内调用wait,因为没有办法防止wait在正运行的子进程尚有未终止时阻塞。
本文转载:http://www.cnblogs.com/yuxingfirst/p/3165407.html
:http://blog.chinaunix.net/uid-27064719-id-4757432.html
:http://www.cnblogs.com/wuchanming/p/4020463.html