signal()和kill()的简介
signal()
signal(int signum, void (*)(int) handler)
signal()会接受一个int类型的信号量signum,这个信号量可以是运行中产生的也可以是用户通过键盘输入的,接收到之后会跳转到handler函数,并执行handler函数
signal()的参数:
- signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数有handler给出
- handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void
- handler也可以是下面两个特殊值
SIG_IGN:屏蔽该信号
SIG_DFL:恢复默认行为
SIG_DFL 为 ((void(*)(int))0)
SIG_IGN 为 ((void(*)(int))1)
(void(*)())0表示将常数0转型为“指向返回值为void的函数的指针”
signal()的一般应用:
- 用于响应一个用户自定义的信号:站在应用程序的角度,注册一个信号处理函数
- 用于忽略一个信号:设置信号默认处理信号的安装和回复
kill()
kill(pid_t pid, int signum)
kill()会向进程id为pid的进程发送一个信号signum,ID为pid的进程会使用signal()来接收signum信号,并通过signal()做出相对应的响应
用一个流程图来表示kill()和signal()之间的交互:
那么signum这个参数都有哪些?又是怎么产生的呢?
可以分为以下几种情况:
Signum | Description |
---|---|
SIGABRT | 由调用abort函数产生,进程非正常退出 |
SIGALRM | 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时 |
SIGBUS | 某种特定的硬件异常,通常由内存访问引起 |
SIGCANCEL | 由Solaris Thread Library内部使用,通常不会使用 |
SIGCHLD | 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略 |
SIGCONT | 当被stop的进程恢复运行的时候,自动发送 |
SIGEMT | 和实现相关的硬件异常 |
SIGFPE | 数学相关的异常,如被0除,浮点溢出,等等 |
SIGFREEZE | Solaris专用,Hiberate或者Suspended时候发送 |
SIGHUP | 发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送 |
SIGILL | 非法指令异常 |
SIGINFO | BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程 |
SIGINT | 由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程 |
SIGIO | 异步IO事件 |
SIGIOT | 实现相关的硬件异常,一般对应SIGABRT |
SIGKILL | 无法处理和忽略。中止某个进程 |
SIGLWP | 由Solaris Thread Libray内部使用 |
SIGPIPE | 在reader中止之后写Pipe的时候发送 |
SIGPOLL | 当某个事件发送给Pollable Device的时候发送 |
SIGPROF | Setitimer指定的Profiling Interval Timer所产生 |
SIGPWR | 和系统相关。和UPS相关。 |
SIGQUIT | 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程 |
SIGSEGV | 非法内存访问 |
SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
SIGSTOP | 中止进程。无法处理和忽略。 |
SIGSYS | 非法系统调用 |
SIGTERM | 请求中止进程,kill命令缺省发送 |
SIGTHAW | Solaris专用,从Suspend恢复时候发送 |
SIGTRAP | 实现相关的硬件异常。一般是调试异常 |
SIGTSTP | Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程 |
SIGTTIN | 当Background Group的进程尝试读取Terminal的时候发送 |
SIGTTOU | 当Background Group的进程尝试写Terminal的时候发送 |
SIGURG | 当out-of-band data接收的时候可能发送 |
SIGUSR1 | 用户自定义signal 1 |
SIGUSR2 | 用户自定义signal 2 |
SIGVTALRM | setitimer函数设置的Virtual Interval Timer超时的时候 |
SIGWAITING | Solaris Thread Library内部实现专用 |
SIGWINCH | 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程 |
SIGXCPU | 当CPU时间限制超时的时候 |
SIGXFSZ | 进程超过文件大小限制 |
SIGXRES | Solaris专用,进程超过资源限制的时候发送 |
例1
编写程序:用 fork( )创建两个子进程,再用系统调用 signal( )让父进程捕捉键盘上来的中断信号(即按^c 键);捕捉到中断信号后,父进程用系统调用 kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parent process is killed!
版本一
//
// main.cpp
// 进程通信
//
// Created by Chen on 2020/11/26.
//
//#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> //含kill()
//using namespace::std;
pid_t p1,p2;
void stop(int sig){
kill(p1, SIGUSR1);
kill(p2, SIGUSR2);
}
void stop1(int sig){
printf("Child process1 is killed by parent!\n");
}
void stop2(int sig){
printf("Child process2 is killed by parent!\n");
}
int main(int argc, const char * argv[]) {
p1 = fork();
if (p1 > 0) {
p2 = fork();
if (p2 > 0) {
// cout<<"this is parents"<<endl;
signal(SIGINT,stop);
wait(NULL);
wait(NULL);
printf("Parent process is killed!\n");
}
if (p2 == 0) {
// cout<<"this is p2"<<endl;
signal(SIGINT, SIG_IGN);
//屏蔽默认的 SIGINT信号处理,这句话非常重要,我会在下面分析原因
signal(SIGUSR2, stop1);
pause();
}
}
if (p1 == 0) {
// cout<<"this is p1"<<endl;
signal(SIGINT, SIG_IGN); //屏蔽默认的 SIGINT信号处理,同上
signal(SIGUSR1,stop1);
pause();
}
return 0;
}
接下来我们回到主函数main来分析一下整个流程:
- 第一句话
p1 = fork();
我们在这里创建了一个子进程p1,如果p1的返回值是大于0的说明此时是父进程,所以此时可以创建另一个子进程p2
如果不懂fork函数干了什么?为什么要在if判断中生成p2 ?为什么p1、p2返回值>0说明是父进程,返回值是0说明是子进程?可以参考这篇文章
- 如果p2的返回值也是大于0的
if (p2 > 0)
说明还是在父进程中
此时根据我们的题目要求:系统调用 signal( )让父进程捕捉键盘上来的中断信号(即按^c 键),我们需要在此执行一次signal函数,也就是这句代码 signal(SIGINT,stop);
用来接收Ctrl + C,接收之后我们还需要用signal函数去发送一个信号signal(SIGINT,stop);
,发送的这个信号是一个返回值为 void (*)(int) 类型的函数 注意:返回的是一个函数!
-
父进程在发送完信号之后会调用wait函数去等待其他进程(他所创建的两个子进程)执行完毕,然后在做输出,也就是这句话
printf("Parent process is killed!\n");
-
如果刚刚p2的返回值是0,说明此时是p2子进程
首先执行了这句话!!!!!!!!
signal(SIGINT, SIG_IGN);
为什么要去执行这句话呢?
因为,子进程会从父进程中继承Ctrl + C信号,即为其默认的处理程序,在子进程中并没有屏蔽Ctrl + C信号,因此,当输入Ctrl + C信号时,子进程会在处理父进程为其指定的信号之前,调用默认的处理Ctrl + C信号的程序,直接退出。因此,解决这个问题的办法,就是在子进程中屏蔽掉系统默认的对Ctrl + C信号的处理
而signal(SIGINT, SIG_IGN);
就是将Ctrl + C信号直接屏蔽掉
-
屏蔽掉Ctrl + C信号之后,执行
signal(SIGUSR2, stop2);
接收到SIGUSR2
信号之后跳转执行stop2
函数,输出printf("Child process2 is killed by parent!\n");
结束 -
子进程p1同理与p2的过程,在此不进行赘述
通过以上分析可以得到,如果我们没有在两个子进程里面添加signal(SIGINT, SIG_IGN);
这句话,那么我们的运行结果就是这样的
版本二:
//
// main.c
// 进程通信
//
// Created by Chen on 2020/11/26.
//
//编写程序:用 fork( )创建两个子进程,
//再用系统调用 signal( )让父进程捕捉键盘上来的中断信号(即按^c 键);
//捕捉到中断信号后,父进程用系统调用 kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
//Child process1 is killed by parent!
//Child process2 is killed by parent!
//父进程等待两个子进程终止后,输出如下的信息后终止:
//Parent process is killed!
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> //含kill()
int wait_mark;
void stop(){
wait_mark = 0;
}
void waiting(){
while(wait_mark != 0);
}
int main(int argc, const char * argv[]) {
pid_t p1,p2;
//signal(SIGINT,stop); //这里即将被取消注释
while((p1=fork())==-1); //创建子进程p1
if (p1 > 0) {
while((p2=fork())==-1); //创建子进程p2
if (p2 > 0) {
wait_mark = 1;
signal(SIGINT,stop); //接受到^c信号,转到stop函数(这里即将被注释)
waiting();
kill(p1,16); //向p1发送软中断信号16
kill(p2,17); //向p2发送软中断信号17
wait(NULL); //同步
wait(NULL);
printf("Parent process is killed!\n");
exit(0);
} else {
wait_mark = 1;
signal(17, stop); //接收到软中断信号17,转到stop函数
waiting();
lockf(stdout,1,0);
printf("Child process2 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
} else {
wait_mark = 1;
signal(16, stop); //接收到软中断信号16,转到stop函数
waiting();
lockf(stdout,1,0);
printf("Child process1 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
return 0;
}
此时可以发现运行之后只输出了Parent process is killed!
为什么呢?
我们知道,同一个程序中创建的所有进程之间是没有优先级之分的,也就是说,OS可能先运行父进程,也可能先运行子进程!
如果OS先运行了子进程,但是子进程的用户自定义信号量是SIGUSR1
不是SIGINT
!此时子进程直接被Ctrl + C给结束掉了。。。
即使先运行父进程,子进程也会从父进程中继承Ctrl + C信号,即为其默认的处理程序,在子进程中并没有屏蔽Ctrl + C信号,因此,当输入Ctrl + C信号时,子进程会在处理父进程为其指定的信号之前,调用默认的处理Ctrl + C信号的程序,直接退出。
根据版本一我们知道,可以在子进程中屏蔽掉系统默认的对Ctrl + C信号的处理来完成p1、p2的输出,那么有没有其他方法呢?
我们知道创建一个子进程,那么这个子进程的代码内容和父进程是完全一样的,但是!执行的起点是不一样的
比如说,子进程p1的起点就是if (p1 > 0)
int main(int argc, const char * argv[]) {
pid_t p1,p2;
while((p1=fork())==-1); //创建子进程p1
if (p1 > 0) { //p1起点!!!!!!
while((p2=fork())==-1); //创建子进程p2
if (p2 > 0) {
wait_mark = 1;
...
那么我们在进程的起始位置之前就发送信号量,是不是就可以解决子进程直接使用从父进程中继承Ctrl + C信号,从而导致不能运行的问题
改过之后的代码是:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> //含kill()
int wait_mark;
void stop(){
wait_mark = 0;
}
void waiting(){
while(wait_mark != 0);
}
int main(int argc, const char * argv[]) {
pid_t p1,p2;
signal(SIGINT,stop); //这里被取消注释了
while((p1=fork())==-1); //创建子进程p1
if (p1 > 0) {
while((p2=fork())==-1); //创建子进程p2
if (p2 > 0) {
wait_mark = 1;
//signal(SIGINT,stop); //接受到^c信号,转到stop函数(这里被注释了)
waiting();
kill(p1,16); //向p1发送软中断信号16
kill(p2,17); //向p2发送软中断信号17
wait(NULL); //同步
wait(NULL);
printf("Parent process is killed!\n");
exit(0);
} else {
wait_mark = 1;
signal(17, stop); //接收到软中断信号17,转到stop函数
waiting();
lockf(stdout,1,0);
printf("Child process2 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
} else {
wait_mark = 1;
signal(16, stop); //接收到软中断信号16,转到stop函数
waiting();
lockf(stdout,1,0);
printf("Child process1 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
return 0;
}
例2
编写程序实现进程的管道通信。用系统调用 pipe( )建立一管道,二个子进程 P1 和 P2 分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
父进程从管道中读出二个来自子进程的信息并显示(要求先接收 P1,后 P2)。
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
int pid1,pid2;
main(){
int fd[2];
char outpipe[100],inpipe[100];
pipe(fd); //创建一个管道
while((pid1=fork())==-1);
if(pid1==0){
lockf(fd[1],1,0);
sprintf(outpipe,"child 1 process is sending message!");
//把串放入数组outpipe中
write(fd[1],outpipe,50); //向管道写长为50字节的串
sleep(5); //自我阻塞5秒
lockf(fd[1],0,0);
exit(0);
}else{
while((pid2=fork())==-1);
if(pid2==0){
lockf(fd[1],1,0); //互斥
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}else{
wait(0); //同步
read(fd[0],inpipe,50); //从管道中读长为50字节的串
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}
本文所涉及的函数汇总(指导书给的定义)
懒的转换了,凑合一下