通过处理僵死进程来引入信号的概念;
一、僵死进程:
a.概念:当父进程未结束,子进程结束且父进程未获取子进程的退出状态。
b.需要知道的一个知识点是当进程执行结束,进程主体(执行代码、数据、资源)都释放,但其PCB并未释放,只是在适当的时机才释放。
二、怎样处理僵死进程呢?
想象一下,进程号是有限的,大量的产生僵死进程,将会因为没有可用的进程号而导致系统不能产生新的进程。因此我们要处理僵死进程:
方法一:父进程调用pid_t wait( int *status )函数来获取子进程的退出状态。
a.函数功能:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了一个已经变成僵死进程的子进程,wait就会收 集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
b.函数返回值:当wait()与fork()配套出现时,如果在使用fork()之前调用了wait(),wait()的返回值则为-1,正常情况下返回子进程的PID。
c.参数status用来保存被收集退出时的一些状态,它是一个指向int类型的指针。如果我们对子进程是如何死掉的毫不在意,只想把这个僵死进程消灭掉,我们就可以设定这个参数为NULL;既pid=wait(NULL);调用成功,就会返回被收集的在子进程的PID,如果调用的进程没有子进程,调用失败,返回-1。
通过以上了解,我们就会想:辛苦创建了一个子进程,最终子进程和父进程成了串行运行,实际上没有达到提高效率的目的。
方法二:怎样异步处理僵死进程?
我们提出信号的概念:
(1)、信号:系统先定义好的某些特定的事件,可以被发生,也可以被接受。发生和接受的主体都是进程。
(2)、系统中信号的定义:/usr/include/bits/signum.h
(3)、信号的来源:按照产生条件的不同可以分为硬件和软件两种:
a.硬件方式:当用户在终端上按下某键时,将产生信号;如果按下组合键(Ctrl+c 代表1) SIGINT中断信号;Ctrl+\代表是3) SIGQUIT退出信号;Ctrl+z代表19) SIGTSTP信号)后将产生一个信号。
硬件异常产生信号:除数据、无效的存储访问等,这些事件通常由硬件(如CPU)检测到,并将其通知给Linux操作系统内核,然后内核生成相应的信号,并把信号发送给该事件发生时正在进行的程序。
b.软件方式:用户在终端下调用kill命令向进程发送任务信号。
(4)、信号的种类:在shell下输入kill -l 可显示Linux系统支持的全部信号;信号的值定义在signal.h中。每个信号都由一个编号和宏定义名称。
(5)、信号的接收:在接受信号的进程PCB结构中有long signal;通过signal来表示接受到的信号。
(6)、进程对信号的响应:当进程发生时,用户可以要求进程以以下三种方式之一对信号做出响应:
a.默认信号:按系统默认方式处理,大部分信号的默认操作是终止操作,且所有的实时信号的默认动作都是终止进程。
b.忽略信号:大多数信号都可以使用这种方式进行处理,但是SIGKILL和SIGSTOP这两个信号不能被忽略,同时这两个信号也不能捕获和阻塞。此外,如果会忽略某些由硬件异常产生的信号(如非法存储访问或除以0),则进程的行为是不可预测的。
c.捕捉信号:对于捕捉的信号,可以为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部实现对信号的处理。
(7)、修改信号的响应方式:修改PCB结构中,struct sigaction结构体数组中对应信号值作为下标的函数指针。
函数原型:void (* signal (int signum, void (*fun) ) (int) ) (int);//signum指定信号的值;int指函数指针对前面信号值得处理;
帮助理解:tepedef void (* fun_handle)(int);
fun_handle signal( int signum,fun_handler fun);
返回值:调用成功返回最后一次安装信号signum而调用signal()时的fun值。失败则返回SIG_ERR.
(8)、修改信号的响应方式的时机:进程刚开始就执行修改关注的信号的响应方式。
代码中signal在何时调用呢:一般情况main函数开始第一行调用。
示例:编写程序实现用户第一次输入Ctrl+c时,输出helloworld;第二次输入Ctrl+c时程序结束:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void fun(int);
int count = 0;
void main()
{
signal(SIGINT, fun);
while(1)
{
sleep(2);
printf("main running\n");
}
}
void fun(int sign)
{
printf("hello world\n");
signal(SIGINT, SIG_DFL);
}
(9)、信号的发送:int kill( pid_t pid, int signum);//pid表示接受信号的进程;signum表示信号类型
头文件:#include<stdio.h>
#include<sys/types.h>
返回值:成功为0;失败为-1;
举例:用户传入pid向指定进程发送信号:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include<sys/types.h>
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("please input pid\n");
exit(0);
}
int pid = 0;
sscanf(argv[1], "%d", &pid);
printf("pid == %d\n", pid);
if(kill(pid, SIGCHLD) == -1)
{
perror(NULL);
exit(0);
}
}
(10)、最后结合信号来处理僵死进程:父进程不阻塞并且处理僵死进程,那子进程结束时,只需向其父进程发送一个SIGCHLD信号。
优势:a.父进程和子进程可以并行处理;
b.可以处理所有的僵死进程;
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
void fun(int sign)
{
printf("fun was called\n");
pid_t pid = wait(NULL);
printf("pid == %d\n", pid);
printf("fun over\n");
}
void main()
{
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
printf("child start\n");
sleep(2);
printf("child end\n");
// 子进程结束时会向其父进程发送SIGCHLD
kill(getppid(), SIGINT);
}
else
{
signal(SIGCHLD, fun);
printf("father start\n");
sleep(10000);
printf("father end\n");
}
}