linux -- 信号
信号及信号来源
使用kill命令杀死进程的实质,是向目标进程发送了一个信号,当目标进程接收到这个信号后,会根据信号的处理函数,执行指
定动作。
比如:keil 杀死进程就是使用的9号信号
使用“kill-l”命令可查看系统中的信号
产生信号的五种情况:
linux系统中信号的状态:
linux 系统中信号的处理方式:
信号的默认动作:
信号的产生
系统调用
系统调用中发送信号常用的函数有kill()、raise()、abort()等,其中kill是最常用的函数,该函数的作用是给指定进程发送信号,但是否杀死进程取决于所发送信号的默认动作。kilI()存在于函数库signal.h中,其函数声明如下:
int kill(pid_t pid, int sig);
案例1:使用fork()函数创建一个子进程,在子进程中使用kilI()发送信号,杀死父进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main()
{pid_t pid;pid = fork();if (pid == 0){ //子进程sleep(1);printf("child pid=%d,ppid=%d\n", getpid(), getppid());kill(getppid(), SIGKILL);//发送信号SIGKILL给父进程}else if (pid > 0){ //父进程while (1){printf("parent pid=%d,ppid=%d\n", getpid(), getppid());}}return 0;
}
为了保证父进程能接收到子进程发送的信号,在父进程执行的代码段中添加循环,保持父进程的运行;
子进程的代码段中调用了kill()函数发送SIGKILL信号给父进程,在此之前使子进程先沉睡1秒。编译案例,执行程序,执行结果如下:
当终端输出Killed时,表明子进程发送的信号SIGKILL成功杀死了父进程。
除kilI()外,
raise()、abort()和pause()也是常用的系统调用。
raise()函数的功能是发送指定信号给当前进程自身,该函数存在于函数库signal.h中,其函数声明如下:
int raise(int sig);
若raise()函数调用成功,则返回0;否则返回非0。其参数sig为要发送信号的编号,使用kilI()函数可以实现与该函数相同的功能,该函数与kilI()之间的关系如下:
raise(sig= = kill(getpid(),sig)
abort()函数的功能是给当前进程发送异常终止信号SIGABRT,终止当前进程,并生成core文件,该函数存在于函数库stdlib.h中,其函数声明如下:
void abort(void);
该函数在调用之时会先解除阻塞信号SIGABRT,然后才发送信号给自己。它不会返回任何值,可以视为百分百调用成功。
pause()函数的作用是造成进程主动挂起,等待信号唤醒。调用该函数后进程将主动放齐CPU,进入阻塞状态,直到有信号通达将其唤醒,才继续工作。pause()存在于函数库unistd.h中,其声明如下:
int pause(void);
pause()函数的参数列表为空,不一定有返回值。根据唤醒进程信号不同的默认动作,pause()函数可能有以下几种情况:
(1)若信号的默认处理动作是终止进程,则进程终止,pause()函数没有机会返回;
(2)若信号的默认处理动作是忽略,进程继续处于持起状态,pause()函数不返回;
(3)若信号的处理动作是捕捉,则调用完信号处理函数后,pause返回-1,并将errno设置为EINTR,表示“被信号中断”
由以上情况可知,pause()只有错误返回值。另外,需要注意的是,若信号被屏蔽,使用pause()函数挂起的进程无法被其唤醒。
软件条件
当满足某种软件条件时,也可以驱使内核发送信号。Linux系统中的alarm()函数就是一个典型的产生软件条件信号的信号源。
alarm()
当满足某种软件条件时,也可以驱使内核发送信号。Linux系统中的alarm()函数就是一个典型的产生软件条件信号的信号源。
alarm()函数的功能相当于计时器,驱使内核在指定秒数后发送信号到调用该函数的进程。alarm()函数存在于函数库unistd.h中,其函数声明如下
unsigned int alarm (unsigned int seconds);
alarm()函数的参数seconds用于指定计时秒数;函数的返回值根据函数的调用情况有几种不同的结果
若进程中不是第一次调用alarm(),且上一个的alarm()尚有剩余秒数,则该函数成功调用后会返回旧计时器的剩余秒数,否则返回0。例如在定时器alarm(5)启动3秒后,新定时器alarm(4)启动,那么alarm(4)的返回值为2;若3秒后第三个定时器alarm(2)启动,那么alarm(2)的返回值为0;若额外设置alarm(0),将会取消计时器。计时器采用自然定时法,无论当前进程是否处于运行态,计时器都会计时。
计时结束后,内核会发送14号信号SIGALRM到当前进程,进程受到SIGALRM信号后执行该信号的动作,若该信号被进程屏蔽,进程将无法接收到该信号。
案例7-2:在程序中设置计时器,使进程在指定秒数后终止运行。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{alarm(1); //设置计时器while (1) //循环保证进程不退出printf("process will finish.\n");return 0;
}
在案例7-2中先设置了一个1秒的计时器:为了保证进程在信号到达之前保持运行,又在进程中添加while循环,使进程不断打印信息。1秒后计时器会驱使内核发送SIGALRM信号到进程,因此进程会在1秒之后结束。
编译案例7-2,执行程序,观察到屏幕不断打印“proces willfinish.”。1秒后停止打印并输出Alarmclock·表示计时器生效,使进程终止。
setitimer()
setitimer()函数也可以设置定时器。与alarm()相比,它精确到微秒,精度更高,并且可实现周期定时。该函数存在于函数库sys/time.h中,函数声明如下:
若setitimer()函数成功调用则返回0;否则返回-1并设置errno。
该函数有3个参数,其中参数 which用来设置以何种方式计时。which有3个取值,不同的值对应不同的计时方法,产生不同的信号。which取值及对应含义如下:
- 若参数为ITIMER_REAL,使用自然定时法计时,计算自然流逝的时间,计时结束递送14号信号SIGALRM。
- 若参数为ITIMER_VIRTUAL,只计算进程占用CPU 的时间,计时结束后递送26号信号SIGVTALRM。
- 若参数为ITIMER_PROF ,计算进程占用CPU 以及执行系统调用的时间,即进程在用户空间和内核空间运行时间的总和,计时结束后递送27号信号SIGPROF。
setitimer()的 第二个参数是一个传入参数,表示计时器定时时长,其本质是一个itimerval类型数据结构的指针,itimerval中有两个timerval类型的成员,这两个成员也是结构体类型。
itimerval与timeval定义如下:
成员 it_interval和 it_value分别指定间隔时间和初始定时时间。
若只指定it_value,则只实现一次定时;
若同时指定it_interval,则用来实现重复定时。
setitimer()的工作机制是,先对it_value倒计时,当 it_value计时结束时,触发信号发送条件。然后重置it_value为 it_interval,继续对it_value倒计时,如此一直循环。
setitimer()函数的第三个参数用来保存先前设置的new_value值,通常设置为NULL。
案例7-3:使用setitimer()函数实现alarm()函数。
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <error.h>
unsigned int my_alarm(unsigned int sec)
{struct itimerval it, oldit;int ret;it.it_value.tv_sec = sec; //指定时间it.it_value.tv_usec = 0;it.it_interval.tv_sec = 0; //指定重复次数it.it_interval.tv_usec = 0;ret = setitimer(ITIMER_REAL, &it, &oldit);if (ret == 1){perror("setitimer");exit(1);}return oldit.it_value.tv_sec;
}
int main()
{my_alarm(1);while (1)printf("process will finish\n");return 0;
}
alarm()只实现一次计时,因此 my_alarm()中调用的setitimer()的参数it的成员 it_interval 的值都为0;
因为alarm()只精确到秒,所以setitimer()中参数it表示微秒的成员变量it_value.tv_usec设置为0即可。
kill命令
举个例子:
先新建5个进程
信号阻塞
信号集设定函数
sigprocmask()
sigpending()
案例4:以2号信号为例,通过位操作函数sigprocmask0与siqpending0获取信号状态。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
void printset(sigset_t *ped) //pending打印函数
{int i;for (i = 1; i < 32; i++){if ((sigismember(ped, i) == 1))putchar('1');elseputchar('0');}printf("\n");
}
int main()
{sigset_t set, oldset, ped; //信号集定义sigemptyset(&set); //初始化自定义信号集setsigaddset(&set, SIGINT); //将2号信号SIGINT加入setsigprocmask(SIG_BLOCK, &set, &oldset);//位操作while (1){sigpending(&ped);printset(&ped);sleep(1);}return 0;
}
编译该案例,执行程序,终端会不断打印进程PCB中的未决信号集。初始情况下进程未决信号集中的每一位都应为0,因此打印的信息如下:
使用kill命令或组合按键Cturl+C驱使内核发送信号SIGINT给当前进程。进程第一次接收到信号SIGINT后,sigproemask()函数被触发,此后终端打印的信息如下:
之后继续向进程发送SIGINT信号,终端打印信息不变,说明信号SIGINT被成功屏蔽。
信号捕获
signal()
案例5:为2号信号SIGINT设置自定义信号处理函数,并在信号处理函数中将函数恢复为默认值。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
void sig_int(int signo) //自定义信号处理函数
{printf(".........catch you,SIGINT\n");signal(SIGINT, SIG_DFL); //信号处理函数执行
}
int main()
{signal(SIGINT, sig_int); //捕获信号SIGINT,修改信号处理函数while (1); //等待信号递达return 0;
}
sigaction()函数
案例6:使用sigacign()函数修改2号信号SIGINT的默认动作。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
void sig_int(int signo)
{printf("...........catch you,SIGINT,signo=%d\n", signo);sleep(5); //模拟信号处理函数执行时间
}
int main()
{struct sigaction act, oldact;act.sa_handler = sig_int; //修改信号处理函数指针sigemptyset(&act.sa_mask); //初始化位图,表示不屏蔽任何信号sigaddset(&act.sa_mask, SIGINT); //更改信号SIGINT的信号处理函数act.sa_flags = 0; //设置flags,屏蔽自身所发信号sigaction(SIGINT, &act, &oldact);while (1);return 0;
}
sleep()函数自实现
案例7:mysleep()函数自实现
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void sig_alrm(int signo)
{//do something...
}unsigned int mysleep(unsigned int seconds)
{struct sigaction newact, oldact;unsigned int unslept;newact.sa_handler = sig_alrm;sigemptyset(&newact.sa_mask);newact.sa_flags = 0;sigaction(SIGALRM, &newact, &oldact); //屏蔽信号SIGALRMalarm(seconds); //倒计时sigaction(SIGALRM, &oldact, NULL); //解除信号屏蔽pause(); //挂起等待信号return alarm(0); //返回
}
int main()
{while (1){mysleep(2);printf("two seconds passed.\n");}return 0;
}
这里实现的mysleep()函数中使用计时器alarm()函数作为计时工具,进入睡眠状态的进程不应有其他操作,因此使用pause()函数将程序挂起;
另外为了保证进程在进入况睡状态后不被由其他进程发送的SIGALRM信号干扰,计时器启动之前应先屏蔽SIGALRM信号:在计时器计时结束后,SIGALRM信号将进程唤醒,此时进程应能接收SIGALRM信号,因此在pause()之前调用sigaction()函数解除了屏蔽;
最后返回alarm(0),因为alarm(0)默认返回0或上一个计时器的剩余秒数,所以mysleep()函数直接返回alarm(0)的返回值即可。此外,alarm(0)也是取消计时的一个安全方法。
根据程序的执行结果可知,自实现的mysleep()函数实现了sleep()函数的功能,但其实这个函数仍是存在问题的。这就是我们接下来要讲解的程序执行的时序问题时序竞态。
时序竞态
案例8:使用alarm()和sigsuspend()自实现mysleep()函数
#include <stdio.h>
#include <signal.h>
#include <stdio.h>
void sig_alrm(int signo)
{//do something...
}
unsigned int mysleep(unsigned int seconds)
{struct sigaction newact, oldact;sigset_t newmask, oldmask, suspmask;unsigned int unslept;//①为SIGALRM设置捕捉函数newact.sa_handler = sig_alrm;sigemptyset(&newact.sa_mask);newact.sa_flags = 0;sigaction(SIGALRM, &newact, &oldact);//②设置阻塞信号集,屏蔽SIGALRM信号sigemptyset(&newmask);sigaddset(&newmask, SIGALRM);sigprocmask(SIG_BLOCK, &newmask, &oldmask);//③设置计时器alarm(seconds);//④构造临时阻塞信号集suspmask = oldmask;sigdelset(&suspmask, SIGALRM);//⑤采用临时阻塞信号集suspmask替换原有阻塞信号集(不包含SIGALRM信号)sigsuspend(&suspmask); //挂起进程,等待信号递达unslept = alarm(0);//⑥恢复SIGALRM原有的处理动作,呼应注释①sigaction(SIGALRM, &oldact, NULL);//⑦解除对SIGALRM的屏蔽,呼应注释②sigprocmask(SIG_SETMASK, &oldmask, NULL);return unslept;
}
int main()
{while (1){mysleep(2);printf("two seconds passed\n");}return 0;
}
SIGCHLD信号
案例9:使用信号捕捉函数浦获SIGCHLD信号,实现子进程的回收。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void sys_err(char *str)
{perror(str);exit(1);
}
void do_sig_child(int signo) //信号处理函数
{waitpid(0, NULL, WNOHANG);
}
int main(void)
{pid_t pid;int i;for (i = 0; i < 5; i++) { //子进程创建if ((pid = fork()) == 0)break;else if (pid < 0) //容错处理sys_err("fork");}if (pid == 0) { //子进程分支int n = 1;while (n--) {printf("child ID %d\n", getpid());}exit(i + 1);}else if (pid > 0) { //父进程分支struct sigaction act;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);while (1) {printf("Parent ID %d\n", getpid());sleep(1);}}return 0;
}
案例10:使用信号捕提函数捷获SIGCHLD信号,实现多个子进程的回收。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void sys_err(char *str)
{perror(str);exit(1);
}
void do_sig_child(int signo) //信号处理函数
{int status;pid_t pid;while ((pid = waitpid(0, &status, WNOHANG)) > 0) {//判断子进程状态if (WIFEXITED(status))printf("child %d exit %d\n", pid, WEXITSTATUS(status));else if (WIFSIGNALED(status))printf("child %d cancel signal %d\n", pid, WTERMSIG(status));}
}
int main(void)
{pid_t pid;int i;for (i = 0; i < 10; i++) {if ((pid = fork()) == 0) //创建一个子进程break;else if (pid < 0) //容错处理sys_err("fork");}if (pid == 0) { //子进程执行流程int n = 1;while (n--) {printf("child ID %d\n", getpid());sleep(1);}return i + 1;}else if (pid > 0) { //父进程执行流程struct sigaction act;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL); //注册捕获函数while (1) { //保证父进程运行printf("Parent ID %d\n", getpid());sleep(1);}}return 0;
}
本章主要介绍了Linux系统中信号的概念、产生方式以及信号的相关操作。通过本章的学习,读者应掌握信号的基本概念,包括信号产生条件、信号状态、处理方式和默认处理动作等,并熟练使用与信号操作相关的函数。除此之外,本章还介绍了在编程中使用信号时可能出现的时序问题以及使用信号回收子进程的方法,这也是信号学习中应着重掌握的知识。信号是Linux系统编程中非常重要的一部分知识,读者应做到在程序中熟练使用信号,达到优化程序的目的。
linux -- 信号相关推荐
- Linux shell 学习笔记(12)— linux 信号、后台运行脚本、作业控制、定时运行任务
1. 处理信号 1.1 Linux 信号 常见的 Linux 信号如下表所示: 信号 值 描述 1 SIGHUP 挂起进程 2 SIGINT 终止进程 3 SIGQUIT 停止进程 9 SIGKILL ...
- linux信号(signal) 机制分析
1 信号本质 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件.在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的. ...
- linux 信号 core,Shell 信号发送与捕捉
原标题:Shell 信号发送与捕捉 作者:李振良OK 1.Linux信号类型 信号(Signal):信号是在软件层次上对中断机制的一种模拟,通过给一个进程发送信号,执行相应的处理函数. 进程可以通过三 ...
- Linux信号 一 信号可靠性与分类
开发SNMP的时候用到了Linux信号机制,总结了一下关于信号的知识. 信号是一种进程间通信手段,本质是一种软件中断,用来处理异步事件.信号机制是Unix家族里一个古老的通信机制.传统的信号机制有一些 ...
- linux信号使用,linux信号使用注意事项
1.不要在信号处理函数中处理复杂的事情 2.信号处理函数中不能有互斥锁会造成死锁,可以用信号量替代 3.信号是置位方式实现,多次发送相同的信号可能只会收到一次 4.子进程具有继承父类信号屏蔽,不能在信 ...
- 非常好的一篇对linux信号(signal)的解析
[摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核对于信号的处理流程包括信号的触发/注册/执 ...
- 【Linux系统编程】Linux信号列表
00. 目录 文章目录 00. 目录 01. Linux信号编号 02. 信号简介 03. 特殊信号 04. 附录 01. Linux信号编号 在 Linux 下,每个信号的名字都以字符 SIG 开头 ...
- linux信号以及core
linux信号以及core 何为信号 信号(signal)用于通知进程发生了某种情况.进程有以下3种处理信号的方式: 忽略信号.有些信号表示硬件异常,例如,除以0或访问进程地址空间以外的存储单元等,因 ...
- linux 信号没有被处理方法,[计算机]Linux 信号signal处理机制.doc
[计算机]Linux 信号signal处理机制 Linux 信号signal处理机制 信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念.Linux对信号机制的大致实现方法.如何使 ...
- Linux信号实践(2) --信号分类
信号分类 不可靠信号 Linux信号机制基本上是从UNIX系统中继承过来的.早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是: 1.进程每次处理信号后,就将对信号 ...
最新文章
- NameNode任务线程之FSNamesystem$ReplicationMonitor
- JFrame windowbuiler的使用基础
- REPL (read-evaluate-print-loop)概念-读取评估打印循环
- 利用脚本将文字插入到图片或进行多个图片拼接
- 在 Spring Boot 中使用 Spring AOP 和 AspectJ 来测量方法的执行时间
- python的结构_Python结构的选择,python,之
- linux后台运行命令,nohup
- 自由口通信模式下计算机读写PLC存储区的程序
- 绘制几何图形——使用android.graphics类 onDraw
- Tronado自定义Form组件
- 毕业设计 嵌入式电子时钟设计与实现
- 格雷码转换成二进制c语言程序,各位老师格雷码和二进制有什么区别,怎么转换....
- 往年报名破千人,南京大学计算机系2022年夏令营来袭
- 安卓QQ聊天记录导出、备份完全攻略
- 剑斩楼兰的将军之路:多属性决策模型。
- 油猴插件安装以及好用的脚本推荐
- When you are old - 当你老去时(译)
- [USACO18DEC]Fine Dining
- CCF CSP 201609-2 火车购票(C++语言100分)[简单模拟题]
- 使用Laravel提交POST请求出现The page has expired due to inactivity错误
热门文章
- radware负载均衡器组网架构
- 艾默生:默默消化在7.5亿美元并吞华为之后
- MTK6797 双摄帧同步问题确认(软同步)
- Airtest入门篇-1开篇
- airtest学习笔记
- 西门子PLC模拟量滤波程序,西门子1200和1500通用,有电压或者电流或者热电偶选择
- 【文献阅读】用GAN来做遥感图像的变化检测(M. A. Lebedev等人,ISPRS,2018)
- 安防智能视频平台EasyCVR后台界面流量统计显示问题的优化
- html打字练习测试代码,javascript写的一个练习打字的小程序
- 中国汽轮机及辅机制造发展状况与经营效益预测报告(2022-2027年)