系统编程(信号)
文章目录
一、进程之间的通信 – 信号
1.什么是信号
信号是一种异步通信机制,一般情况下,进程什么时候会收到信号、收到什么信号是无法事先预料的
(就像你家的门铃,你不知道它什么时候会响,但是门铃响的时候我们可以下楼开门(处理))
2.在linux下,有哪些信号
例如:
19) SIGSTOP //signal 信号
信号值)SIG+信号名字
其实信号的名字与信号值是等价的,它们是宏定义来的,被定义在一个头文件:
/usr/include/asm-generic/signal.h
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
3.在linux下,这些信号究竟是由谁来发出?
(1)由系统来发出。
14) SIGALRM -> 当在程序中调用alarm()时,如果到点了,就会自动发出这个信号。
17) SIGCHLD -> 当子进程退出时,自动发出这个信号给父进程。SIGCHLD = signal child
(2)信号也可以由用户来发送。
如果是由用户来发送,则需要学习 kill / killall 这两个命令。
4.用户如何发送信号给进程?
方法一:
(1)首先查看目标进程的PID号。 -> ps -ef(或者使用getpid打印自己的进程id号)
PID号
gec 4630 4310 0 00:30 pts/4 00:00:00 ./a.out
(2)通过kill命令发送9号信号给该进程,杀死进程。(kill -9 杀死进程这个指令很常用)
kill -9 4630
kill -SIGKILL 4630 或者 kill -KILL 4630
说明:kill是给pid值发送信号
方法二:
直接通过killall命令给进程名字发送信号
killall -9 a.out
killall -SIGKILL a.out 或者 killall -KILL a.out -> 只要名字为a.out,都会收到这个信号。
说明:killall是给进程名发送信号
小练习:写一个进程fork之后,父进程一直执行,子进程也一直执行,分别用kill和killall干掉他们
代码参考:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
pid_t id;
id = fork();
if (id == -1)
{
perror("open fork fail");
return -1;
}
else if (id > 0)
{
while (1)
{
printf("[%d]父进程\n",getpid());
sleep(1);
}
wait(NULL);
}
else if (id == 0)
{
while (1)
{
printf("[%d]子进程\n",getpid());
sleep(1);
}
exit(0);
}
return 0;
}
二、关于信号的函数接口
1.如何发送信号给另外一个进程? -> kill()
#include <sys/types.h>
#include <signal.h> -> linux信号的专属头文件
int kill(pid_t pid, int sig);
函数作用:
向指定进程或者进程组,发送一个指定的信号
参数:
pid:进程号
sig:信号值
举例:
#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc, char*argv[]) // ./a.out 3445
{
//获取你要发送的信号的进程的ID号
int id = atoi(argv[1]);
//发送信号
kill(id,SIGINT);
return 0;
}
2.如何捕捉信号? -> signal()
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针(函数句柄)
void(*sighandler_t)(int);//定义一个函数指针
sighandler_t signal(int signum, sighandler_t handler);
函数作用:
捕捉一个指定的信号,即预先为某信号的到来做好准备
参数:
signum 需要捕捉的信号
handler
SIG_IGN 忽略该信号 英/ɪɡˈnɔː®/ ignore
SIG_DFL 执行该信号的默认动作 英/dɪˈfɔːlt/ default
void (*p)(int) 执行由 p 指向的信号响应函数
返回值:
成功返回 最近一次调用该函数时第二个参数的值
失败返回 SIG_ERR (#define SIG_ERR -1)
注意:
1.所谓的捕捉信号就是获取当这个信号来之后,去执行信号响应函数,原本的信号默认动作就不会执行了。
2.当调用signal函数之后 ,程序不会阻塞,而是往下面代码执行,但是这个捕捉设置是全局有效
3.SIGKILL 、SIGSTOP 不能被捕捉,只能执行默认的动作
注意:
typedef void (*sighandler_t)(int); //给自定义的函数指针类型取了一个别名叫做sighandler_t
void (*sighandler_t)(int); //自定义了一个函数指针类型,这个指针指向的是一个函数,函数的参数时一个int,函数的返回值是void
举例:
#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
//信号响应函数 ,也就是说signal捕捉到指定的信号之后,去执行这个函数
void signalHandle(int arg)
{
printf("arg:%d\n",arg);
printf("signalHandle 听说你想杀死我\n");
}
int main(int argc, char*argv[]) // ./a.out 3445
{
//捕捉信号SIGINT,去执行信号响应函数
signal(SIGINT,signalHandle);
while(1);
return 0;
}
运行结果:
3.挂起进程,直到收到一个信号为止->pause()
#include <unistd.h>
int pause(void);
参数:无
返回值:
收到非致命信号或者已经被捕捉的信号 -1
收到致命信号导致进程异常退出 不返回
注意:pause( )是在响应函数返回之后,随后再返回的。
举例:
#include<stdio.h>
#include <unistd.h>
int main(int argc, char*argv[]) // ./a.out 3445
{
printf("main start\n");
//修改SIGUSR2信号的动作属性,将它变成非致命信号
signal(SIGUSR2,signalHandle);
//将当前进程挂起,直到收到一个信号,程序才会往下面执行
pause();//会阻塞
printf("end\n");
return 0;
}
说明:
10) SIGUSR1与12) SIGUSR2 通常被开发者常用自定义
pause()可以充当while(1)的作用。
拓展:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
// 在C语言中函数名就是指针
void handler(int arg)
{
printf("收到信号下午开始上课:%d\n",arg);
}
void handler2(int arg)
{
printf("收到信号下午开始发信号:%d\n",arg);
}
int main()
{
printf("[%d] main process\n",getpid());
//响应信号的动作---handler
signal(SIGUSR1,handler); //不会阻塞
signal(SIGUSR2,handler2);
//进程挂起,此处会阻塞,直到收到信号继续运行
pause(); //阻塞 10000的写法
//for(;;)它的效率要高于while(1)
//原因是因为for(;;)反汇编后的汇编代码要比while(1)小
// while(1); //5000 的写法
// for(;;); //8000
return 0;
}
4.自己给自己发送信号-> raise()
#include <signal.h>
int raise(int sig);
参数:
sig: 发送的信号。
返回值:
成功:0
失败:非0
举例:
#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
//信号响应函数 ,也就是说signal捕捉到指定的信号之后,去执行这个函数
void signalHandle1(int arg) //arg 信号值
{
printf("arg:%d\n",arg);
printf("signalHandle1 听说你想杀死我\n");
}
int main(int argc, char*argv[]) // ./a.out 3445
{
printf("main start\n");
//捕捉信号SIGUSR1,去执行信号响应函数
signal(SIGUSR1,signalHandle1);
while(1)
{
sleep(1);
//自己给自己发送信号SIGUSR1
raise(SIGUSR1);
}
return 0;
}
运行结果:
小练习:使用raise实现如下效果:一直打印"我在打游戏",每隔10秒闹钟一次打印,“放下游戏出来搞学习”。
代码参考:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
//arg是指你收到的信号的值
void handler(int arg)
{
printf("放下游戏出来搞学习:%d\n",arg);
}
int main(void)
{
int i = 0;
//非阻塞一直生效
signal(SIGUSR1, handler);
while (1)
{
//10秒之后给自己发一个信号SIGUSR1
if(i == 10)
{
i = 0;
raise(SIGUSR1);
}
printf("我在打游戏.....%d\n",i);
i++;
sleep(1);
}
return 0;
}
运行结果:
小练习:验证一下子进程退出的时候,是否给父进程发出了SIGCHLD信号
代码参考:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//子进程退出会给父进程发送SIGCHLD信号
void handle(int arg)
{
printf("收到子进程的退出信号:%d\n",arg);
// wait(NULL);//wait函数可以放到这个地方来接受子进程资源
}
int main()
{
pid_t id = fork();
if (id < 0)
{
}
else if (id > 0) // 父进程
{
//修改SIGCHLD信号的响应动作
//此处只适用于父进程
signal(SIGCHLD,handle);
wait(NULL);
}
else if (id == 0) // 子进程
{
exit(0);
}
return 0;
}
运行结果:
三、信号的处理(除SIGKILL,SIGSTOP)
1.忽略(将信号丢弃)
signal(signum,SIG_IGN); //signal ignore
2.缺省(默认动作)
signal(signum,SIG_DFL); //signal default
3.捕捉(去执行指定的函数)
signal(signum,function);
以三个动作都和signal有关,取决于第二个参数
4.阻塞(信号挂起)
设置阻塞之后,来了阻塞的指定信号,并不是将信号丢弃,而是将信号挂起,等到解除阻塞之后才去响应这个信号
这个动作和信号有关
注意:
1、不能够单独某一个信号为阻塞
2、先将信号添加到
注意:
9) SIGKILL和 19) SIGSTOP 这两个信号不能被忽略,阻塞、捕捉。必须是执行默认动作
四、linux系统信号集
1.什么是信号集?
信号集是一个集合,而每一个成员都是一个信号,通过将信号加入到信号集中,再设置阻塞状态给信号集,
那么整个信号集里面所有的信号都会变成阻塞的状态。
2.信号阻塞与信号忽略有什么区别?
信号响应: 收到信号之后,会响应信号的动作。 signal(signum,function);
信号忽略: 收到信号之后,直接丢弃这个信号。signal(signum,SIG_IGN); //signal ignore
信号阻塞: 进程在阻塞某一个信号前提下,收到了这个信号,不会马上响应,而是要等到解除阻塞之后,才会响应这个信号。
(这个信号没有被响应时,不会丢弃,而是放在一个挂起队列中)
五、信号集处理函数
1.信号集如何定义?
信号集其实就是一个变量,数据类型是: sigset_t。
定义信号集: sigset_t set
#include <signal.h>
int sigemptyset(sigset_t *set); 清空信号集
int sigfillset(sigset_t *set); 将linux下所有的信号都加入到信号集中(很少见)
int sigaddset(sigset_t *set, int signum); 在指定的信号集set中,添加一个指定的信号signum
int sigdelset(sigset_t *set, int signum);在指定的信号集set中,删除一个指定的信号signum
int sigdelset(sigset_t *set, int signum);在指定的信号集set中,删除一个指定的信号signum
int sigismember(const sigset_t *set, int signum); 测试某一个信号是不是在集合中
参数:
set:需要判断的信号集的地址
signum: 需要测试的信号
返回值:
成功:0
失败:-1
sigismember 函数在集合中返回1,不在集合中返回0,失败返回 -1
举例: 写一个程序,先清空信号集,再把SIGUSR1加入到集合中,判断信号是不是在集合中。
int main(int argc, char*argv[]) // ./a.out 3445
{
//1)先定义一个信号集变量
sigset_t set;
//2) 初始化(清空)
sigemptyset(&set); //清空信号集
//3)将信号 添加到集合中 SIGUSR1 SIGUSR2
sigaddset(&set,SIGUSR1); //在指定的信号集set中,添加一个指定的信号signum到集合中
sigaddset(&set,SIGUSR2);
//4) 判断 SIGUSR2信号是否在集合中,如果在打印yes ,否则 打印 no
// sigismember 信号在集合中 返回1 否则 返回 0
if(sigismember(&set, SIGUSR2))
printf("yes\n");
else
printf("no\n");
return 0;
}
六、如何设置信号集为阻塞状态? -> sigprocmask()
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
how:
SIG_BLOCK -> 设置为阻塞的属性 block阻塞
SIG_UNBLOCK -> 解除阻塞
set: 你要设置哪个信号集,将这个信号集的地址传递过来
oldset:保留之前状态的指针,如果不关心,则填NULL。
返回值:
成功:0
失败:-1
举例:sigset set;
信号 -> set
sigprocmask(SIG_BLOCK,&set,NULL); -> 设置为阻塞
…
sigprocmask(SIG_UNBLOCK,&set,NULL); -> 解除阻塞
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
void signalHandle(int signum)
{
printf("捕捉到%d信号,去执行别的事情.....\n",signum);
int cnt = 5;
while(cnt--)
{
sleep(1);
printf("我正在清理垃圾...%d\n",cnt);
}
}
//SIGINT
int main(int argc,char**argv) // ./a.out 46725
{
//将SIGUSR2信号设置一个信号响应函数
signal(SIGUSR2,signalHandle);
signal(60,signalHandle);
signal(SIGINT,handle3); //ctrl+c相当于SIGINT,此时被修改了,所以ctrl+c无法终止进
//1、先定义一个信号集合变量 ---数组
sigset_t set;
//2、清空信号集合变量 -----数组清空
sigemptyset(&set);
//3、将你想要设置的信号 一个个地加入到集合变量中 --数组中每个元素挨个进行赋值
sigaddset(&set,SIGUSR1);
sigaddset(&set,SIGUSR2);
sigaddset(&set,60);
//将上面的信号集合set中的所有信号 设置为阻塞状态
sigprocmask(SIG_BLOCK, &set,NULL);
int cnt = 30;
while(cnt--){
sleep(1);
printf("[%d]主进程正在执行很重要任务%d.....\n",getpid(),cnt);
}
//等上面很重要的事情 完成了,再解除阻塞 ,响应信号
sigprocmask(SIG_UNBLOCK, &set,NULL);
return 0;
}
小练习: 创建一个子进程,父进程将SIGUSR1加入到信号集中,判断信号在不在集合中,再设置该信号为阻塞状态,
该状态持续10s(那么在10S内,对SIGUSR1都是阻塞的),10s后,解除阻塞,看看会不会响应信号?
子进程在10S内发送一个SIGUSR1给父进程。
1)主要看,信号发过去之后,如果是马上响应----信号响应
2)如果是10s后响应—信号阻塞—》对
3)永远都不响应----》信号忽略
代码参考:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void handle(int arg)
{
printf("今天又是元气满满的一天:%d\n", arg);
}
int main()
{
/*
父进程空间
*/
pid_t id = fork();
if (id == -1)
{
perror("fork fail");
return -1;
}
else if (id > 0) // 父进程
{
//信号的响应函数
signal(SIGUSR1, handle);
// 1)先定义一个信号集变量(不要定义成指针变量*set)
sigset_t set;
// 2) 初始化(清空)(通过函数sigemptyset给set赋值 传地址)
sigemptyset(&set); // 清空信号集
// 3)将信号 添加到集合中 SIGUSR1 SIGUSR2
sigaddset(&set, SIGUSR1); // 在指定的信号集set中,添加一个指定的信号signum到集合中
// 4) 判断信号SIGUSR1是否在集合中
if (sigismember(&set, SIGUSR1))
// if(sigismember(&set, SIGINT))
printf("yes\n");
else
printf("no\n");
// 设置信号集为阻塞 --- 次持续10秒
sigprocmask(SIG_BLOCK, &set, NULL); // -> 设置为阻塞
int cnt = 10;
while(cnt--)
{
printf("新的一天,头脑不能阻塞.....%d\n",cnt);
sleep(1);
}
sigprocmask(SIG_UNBLOCK, &set, NULL); // -> 设置为非阻塞
}
else if (id == 0) // 子进程
{
sleep(5);
printf("子进程给父进程发送SIGUSR1信号\n");
//子进程5秒之后给父进程发送SIGUSR1信号
kill(getppid(),SIGUSR1);
}
return 0;
}
运行结果:
小练习:验证阻塞属性会被子进程继承。(验证的时候可以其它进程给子进程发送信号)
代码参考:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void handle(int arg)
{
printf("今天又是元气满满的一天:%d\n", arg);
}
int main()
{
/*
父进程空间:这一段空间父进程会拷贝一份给父进程
*/
signal(SIGUSR1, handle);
// 1)先定义一个信号集变量(不要定义成指针变量*set)
sigset_t set;
// 2) 初始化(清空)(通过函数sigemptyset给set赋值 传地址)
sigemptyset(&set); // 清空信号集
// 3)将信号 添加到集合中 SIGUSR1 SIGUSR2
sigaddset(&set, SIGUSR1); // 在指定的信号集set中,添加一个指定的信号signum到集合中
// 设置信号集为阻塞 --- 次持续10秒
sigprocmask(SIG_BLOCK, &set, NULL); // -> 设置为阻塞
pid_t id = fork();
if (id == -1)
{
perror("fork fail");
return -1;
}
else if (id > 0) // 父进程
{
/*
父进程10秒之后变成了非阻塞
*/
int cnt = 10;
while (cnt--)
{
printf("[%d]我是你的好大爹...%d\n",getpid(),cnt);
sleep(1);
}
sigprocmask(SIG_UNBLOCK, &set, NULL); // -> 设置为非阻塞
while(1);
}
else if (id == 0) // 子进程
{
/*
子进程一直都是阻塞
*/
int cnt=0;
while(1)
{
printf("[%d]我是你的好大儿...%d\n",getpid(),cnt++);
sleep(1);
}
}
return 0;
}
拓展
alarm(5)//定时5秒自己给自己发送SIGALRM
练习告诉自己10秒之后,把手机扔掉,搞学习。
代码参考:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void signalHandle( int sigNum )
{
printf("把手机扔掉, 搞学习\n");
}
int main(int argc, char const *argv[])
{
signal( SIGALRM, signalHandle );
alarm(5); //5秒之后给自己发送SIGALRM信号
pause(); //收到SIGALRM信号之后,进程不再挂起,继续执行
// while(1);
return 0;
}