进程间通信之信号
时间:2018-03-13作者:华清远见
一:信号的基本介绍 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式(进程在运行过程中,随时可能被各种信号打断)。 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了那些系统事件。 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进 程恢复执行再传递个它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞取消时才被传递给进程。 二:信号的产生 A.用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如ctr+c产生SIGINT, ctr + \产生SIGQUI信号,ctr + z产生SIGTSTP。 B.硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给当前进程 。我们常见的段错误。 C.一个进程调用int kill(pid_t pid,int sig)函数可以给另一个进程发送信号。 D.可以用kill命令给某个进程发送信号,如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。 E.当内核检测到某种软件条件发生时也可以通过信号通知进程,例如闹钟超时产生 SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。 三:linux操作系统支持的信号 A. kill -l命令查看当前系统支持的所有的信号
B:常用信号的含义
四:linux中进程对信号处理 忽略信号,即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及 SIGSTOP。 捕捉信号,定义并注册信号处理函数,当信号发生时,执行相应的处理函数。 【重点】。 执行缺省操作,Linux对每种信号都规定了默认操作 五:相关API
1:信号的发送(kill和raise) 函数原型:int kill(pid_t pid, int sig); 函数功能:给进程 id 为 pid 的进程发送信号 函数参数:@param pid : 发送信号的目标进程的 id
@param sig : 发送的信号编号,例如:9(SIGKILL) 返回值:成功调用返回 0 ,失败返回 -1 ,并设置 errno 函数原型:int raise(int sig); 函数功能:给当前进程自己发送信号 函数参数:@param sig : 发送的信号编号 返回值:成功调用返回 0 ,失败返回 -1 ,并设置 errno .*练习 我们通过终端kill -9 某个进程终止过一个进程,现在我们使用kill函数来终止一下。过程: 父进程创建一个子进程,父进程拿到子进程的进程ID;子进程中while循环打印hello,sleep(1); 父进程sleep(5)之后,给子进程发送9这个信号来终止子进程。 2:信号的捕捉(signal) 知识点回顾 void func(int);//函数的声明 void (*func)(int);//定义函数指针,指向void (int)类型的函数typedef int a;//给int类型的a起别名 typedef void (*funcp)(int);//给类型为void (*)(int);的函数指针起别名funcp 捕捉信号的处理过程:
#include <signal.h> typedef void (*sighandler_t)(int);//指向函数的指针,表示信号处理函数的形式sighandler_t signal(int signum, sighandler_t handler); 函数功能 : 将信号与信号处理函数进行关联函数参数:@param signum : 信号的编号 @param handler : 信号处理函数的指针SIG_DFL : 表示默认操作 SIG_IGN : 表示忽略信号(SIGKILL和SIGSTOP时不能被忽略的) 返回值:成功调用返回信号处理函数的指针,否则,返回SIG_ERR
注意:sighandler_t handler中的int保存的是调用这个函数是因为哪个信号触发的,带过来对应的信号值。 .* 练习 (1)忽略ctrl+c对进程的终止信号。signal(SIGINT, SIG_IGN); (2)在信号处理函数中将对应的信号的描述信息进行打印。 .* 练习: fork前采用signal信号处理函数不阻塞,不轮询的方式回收僵尸态子进程[waitpid()函数]。 在信号处理函数signal_handler()中对信号进行收尸操作。然后利用fork函数创建一个子进程。休眠10s后退出。父进程是一个死循环,每秒输出"father do something…"的字符串。 提示: 子进程在终止时会给父进程发SIGCHLD,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数。我们这里调用waitpid非阻塞的回收僵尸态子进程。这样父进程只需要专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用waitpid函数清理子进程即可。 一般信号对僵尸态子进程的处理方法: <1>父进程采用signal(SIGCHLD, hand_signal),采用信号处理函数,对接收到的SIGCHLD进行进行处理。在接收到SIGCHLD信号的时候,采用waitpid利用非阻塞的方式的释放它们的资源。若是使用wait()函数的话,父进程会阻塞。 [推荐使用] <2>父进程采用signal(SIGCHLD, SIG_IGN),忽略SIGCHLD信号,这样子进程结束后,就不需要父进程来wait和释放资源。它会自动被过继给老祖宗init进程,int进程会负责释放他的资源,这样就不会产生僵尸态子进程。 3:定时闹钟函数(alarm) unsigned int alarm(unsigned int seconds); 函数功能:给进程启动一个定时器,经过seconds秒后把SIGALRM信号发送给当前进程。函数参数:@seconds 秒 返回值:成功返回0,失败返回 -1 注意:一个进程只能有一个闹钟事件,若是多次使用alarm函数,则闹钟时间被刷新。 .*练习 (1)main函数中设置2s定时器,然后注册SIGALRM信号的处理函数,处理函数中打印当前时间到屏幕上
(2)我们现实中经常有这样的需求,需要每隔2s执行某个函数,这样怎么处理呢? 答案:在定时器处理函数里边,再次刷新闹钟alarm(2); 4:信号的等待(pause) int pause(void); 特点:挂起一个进程,直到进程收到一个信号,进程会继续执行 上边的练习,在while循环中pause()一下。现象:不加pause()之前printf("n = d\n", ++n);会200ms打印一次,然后1秒打印一次时间,加上pause()之后,现象则是1s打印一次printf("n = d\n", ++n),时间也是1s一次。因为pause()会将进程挂起,接收到信号之后会继续,进程1s接收一次ALARM信号,则进程会1s会被唤醒一次。
#include <stdio.h>
#include <time.h> #include <signal.h> void handler(int sig) {
time_t tim = time(NULL); 相关资讯
发表评论
|