linux信号处理机制基础(下)
发送信号
kill命令
用专门的系统命令发送信号
kill [-信号] PID
缺省发送SIGTERM(15)信号若不指明具体信号
若要指明具体信号,可以使用信号编号,也可以使用信号名称,而且信号名称中的“SIG'前缀可以省略不写。例如
kill -9 1234
kil -SIGKILL 1234 5678
kil -kill -1 (会终止当前用户下所有进程)
超级用户可以发给任何进程,而普通用户只能发给自己的进程
#include <signal.h>
int kill(pid_t pid, int signum);
功能:向指定的进程发送信号
参数:pid 可以如下取值
-1 -向系统中的所有进程发送信号
>0 -向特定进程(由pid标识)发送信号
signum:信号编号,取0可用于检査pid进程是否存在,如不存在kil函数会返回-1,且errno为ESRCH
返回值:成功(至少发出去一个信号)返回0,失败返回-1
代码演示
父进程向子进程发送2号信号(此外本代码还实现了对子进程存在的判断),可以通过注释掉子进程对2号信号的捕捉实现2号信号对子进程的终止。注意,终止该进程后,由于此时没有对该进程进行收尸,代码第一次判断是该进程任然存在,经waitpid函数收尸后,该进程死亡释放剩余资源。
//发送信号
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<errno.h>
#include<sys/wait.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n",getpid(),signum);
return;
}
int main(){
//父进程创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码,对2号信号进行捕获处理
if(pid == 0){
if(signal(SIGINT,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
while(1){}
return 0;
}
//父进程代码,向子进程发送2号信号
getchar();
if(kill(pid,SIGINT) == -1){
perror("kill");
return -1;
}
//判断子进程是否存在
getchar();
if(kill(pid,0) == -1){
if(errno == ESRCH){
printf("子进程不存在\n");
}else{
perror("kill");
return -1;
}
}else{
printf("子进程存在\n");
if(waitpid(pid,NULL,WNOHANG) == -1){
perror("waitpid");
return -1;
}else{
getchar();
if(kill(pid,0) == -1){
if(errno == ESRCH){
printf("子进程不存在\n");
}else{
perror("kill");
return -1;
}
}
}
}
return 0;
}
暂停
pause函数
#include <unistd.h>
int pause(void);
功能:无限睡眠
返回值:成功阻塞,失败返回-1
该函数使调用进(线)程进入无时限的睡眠兴态,直到有信号终止了该进程或被其捕获。如果有信号被调用进程捕获,在信号处理函数返回以后,pause函数才会返回,其返回值-1,同时置errno为EINTR,表示阻塞的被信号打断。pause函数要么不返回,要么系统返回-1,永远不会返回0.
代码演示
//暂停
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:%d号信号处理开始\n", getpid(), signum);
sleep(5);
printf("%d进程:%d号信号处理完毕\n", getpid(), signum);
return;
}
int main(){
//对2号信号进行捕获
if(signal(SIGINT,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
printf("%d进程:执行pause()函数,进入无限睡眠\n", getpid());
int res = pause();
printf("%d进程:pause()函数返回值:%d\n",getpid(),res);
return 0;
}
思考辨析
思考一下这里在代码执行过程中为什么只能通过键盘输入crtl + c按键组合,该进程才会收到2号信号进入信号处理函数。我的理解是这里我们通过一个进程来执行,当我们执行kill命令时由于当前进程已经陷入了睡眠,它不会自己主动发送信号,只能等待来自外部的中断(信号)。kill命令是无法通过当前一个睡眠的进程来发送信号到指定进程。而键盘命令则是其他进程来控制的,它并没有睡眠,因此可以通过键盘正常发送信号。
睡眠
sleep函数
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:有限睡眠
参数:seconds 以秒为单位的睡眠时限
返回值:返回0或剩余秒数。
- 该函数使调用进程睡眠seconds秒,除非有信号终止了调用进程或被其捕获
- 如果有信号被调用进程捕获,在信号处理函数返回以后,sleep函数才会返回且返回值为剩余的秒数,否则该函数将返回0,表示睡眠充足
usleep函数
#include <unistd.h>
int usleep (useconds t usec);
功能:更精确的有限睡眠
参数:usec 以微秒(1微秒=10-6秒)为单位的睡眠时限
返回值:成功返回0,失败返回-1
如果有信号被调用进程捕获,信号处理函数返回以后,usleep函数才会返回,且返回值为-1,同时置errno为EINTR,表示阻塞的系统调用被信号中断
代码演示
//睡眠
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:%d号信号处理开始\n", getpid(), signum);
sleep(5);
printf("%d进程:%d号信号处理完毕\n",getpid(), signum);
return;
}
int main(){
//对2号信号进行捕获
if(signal(SIGINT,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
printf("%d进程:执行pause()函数,进入有限睡眠\n", getpid());
int res = sleep(10);
printf("%d进程:sleep()函数返回值:%d\n",getpid(),res);
return 0;
}
闹钟
alarm函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置闹钟
参数:seconds 以秒为单位的闹钟时间。
返回值:返回0或先前所设闹钟的剩余秒效,
- alarm函数使系统内核在该函数被调用以后seconds秒的时候,向调用进程发送SIGALRM(14)信号
- 若在调用该函数前已设过闹钟且尚未到期,则该函数会重设闹钟,并返回先前所设闹钟的剩余秒数,否则返回0
- 若seconds取0,则表示取消先前设过且尚未到期的闹钟
代码演示
//闹钟
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n",getpid(),signum);
return;
}
int main(){
//对14号信号进行捕获处理
if(signal(SIGALRM,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
//设定闹钟
printf("alarm(10)返回%d\n",alarm(10));
getchar();
printf("alarm(5)返回%d\n",alarm(5));
pause();//阻塞进程,等待信号
return 0;
}
信号集
- 多个信号组成的信号集合谓之信号集
- 系统内核用sigset_t类型表示信号集
- 在<signal.h>中又被定义为 typedef __sigset_t sigset_t;
- 在<sigset.h>中有如下类型定义
- #define _SIGSET_NWORDS(1024/(8*sizeof (unsigned long int)))
- typedef struct {
- unsigned long int val[ _SIGSET_NWORDS];
- } __sigset_t;
- sigset_t类型是一个结构体,但该结构体中只有一个成员,是一个包含32个元素的整数数组(针对32位系统而言)
- 不论是32位系统还是64系统,对于sigset_t这样的一个结构体变量都是128个字节大小即1024个比特位
sigfillset函数
#include <signal.h>
int sigfillset (sigset t* sigset);
功能:填满信号集,即将信号集的全部信号位置1
参数:sigset 信号集
返回值:成功返回0,失败返回-1
sigemptyset函数
#include <signal.h>
int sigemptyset (sigset t* sigset);功能:清空信号集,即将信号集的全部信号位清0
参数:sigset 信号集
返回值:成功返回0,失败返回-1
sigaddset函数
#include <signal.h>
int sigaddset (sigset t* sigset, int signum);
功能:加入信号,即将信号集中与指定信号编号对应的信号位置1
参数:sigset 信号集
signum:信号编号
返回值:成功返回0,失败返回-1
sigdelset函数
#include <signal.h>
int sigdelset (sigset_t*sigset, int signum);功能:删除信号,即将信号集中与指定信号编号对应的信号位清0
参数:sigset 信号集
signum:信号编号
返回值:成功返回0,失败返回-1
sigismember函数
#include <signal.h>
int sigismember (const sigset_t*sigset, int signum);功能:判断信号集中是否有某信号,即检查信号集中与指定信号编号对应的信号位是否为1
参数:sigset 信号集
signum:信号编号
返回值:有则返回1,没有返回0,失败返回-1
代码演示
#include<stdio.h>
#include<signal.h>
//打印一个字节的8位内容
void printb(char byte){
for(int i = 0; i < 8; i++){
printf("%d",(byte & 1 << 7 - i) ? 1 : 0);
}
printf(" ");
return ;
}
//把一块存储区所有字节比特位都打印出来
void printm(void *buf, size_t size){
for(int i = 0; i < size; i++){
printb(((char *)buf)[size - 1 - i]);
if((i + 1) % 8 == 0){
printf("\n");
}
}
}
int main(){
//信号集
sigset_t set;
//填满信号集
sigfillset(&set);
printm(&set,sizeof(set));
//清空信号集
sigemptyset(&set);
printm(&set,sizeof(set));
//添加一个信号到信号集
sigaddset(&set,SIGINT);
printm(&set,sizeof(set));
//删除一个信号
sigdelset(&set,SIGINT);
printm(&set,sizeof(set));
//判断一个信号是否在信号集中
printf("信号集中%s2号信号\n",sigismember(&set,SIGINT) ? "有" : "无");
return 0;
}
执行结果
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111110 01111111 11111111 11111111 11111111
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
信号集中无2号信号
[1] + Done "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-ai4ciolt.fpe" 1>"/tmp/Microsoft-MIEngine-Out-cd41dqpd.34f"
day06$
信号屏蔽
递送、未决与掩码
- 当信号产生时,系统内核会在其维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程就叫做递送(delivery)
- 信号从产生到完成递送之间存在一定的时间间隔,处于这段时间间隔中的信号状态称为未决(pending)
- 每个进程都有一个信号掩码(sig币mask),它实际上是一个信号集,位于该信号集中的信号一旦产生,并不会被递送给相应的进程,而是会被阻塞(block)在未决状态
- 在信号处理函数执行期间,这个正在被处理的信号总是处于信号掩码中,如直到上一个针对该信号的处理过程结束以后果又有该信号产生,则会被阻塞,才会被递送
- 当进程正在执行类似更新数据库这样的敏感任务时,可能不希望被某些信号运中断。这时可以通过信号掩码暂时屏蔽而非忽略掉这些信号,使其一旦产生即被阻塞于未决状态,带特定任务完成后后,再回过头来处理这些信号
可靠和不可靠信号的屏蔽
- 对于可靠信号,通过sigprocmask函数设置信号掩码以后,每种被屏蔽信号中的每个信号都会被阻塞,并按先后顺序排队,一旦解除屏蔽,这些信号会被依次递送
- 对于不可靠信号,通过sigprocmask函数设置信号掩码以后,每种被屏蔽信号中只有第一个会被阻塞,并在解除屏蔽后被递送,其余的则全部丢失
#include <signal.h>
int sigprocmask (int how, const sigset_t*sigset,sigset_t* oldset);
功能:设置调用进程的信号掩码
参数:how:修改信号掩码的方式,可取以下值SIG_BLOCK -将sigset中的信号加入当前信号掩码
SIG_UNBLOCK -从当前信号掩码中删除sigset中的信号
SIG_SETMASK -把sigset设置成当前信号掩码
sigset:信号集,取NULL则忽略此参数
oldset:输出原信号掩码,取NULL则忽略此参数
返回值:成功返回0,失败返回-1
代码演示(可以将注释内容与对应内容替换以观察可靠信号与不可靠信号的区别)
//屏蔽信号集
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/wait.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n",getpid(),signum);
return;
}
//假装执行数据库更新操作
void updatedb(void){
for(int i = 0;i < 5;i++){
printf("%d进程:正在执行更新第%d条数据...\n",getpid(),i+1);
sleep(1);
}
return;
}
int main(){
//父进程对2号信号进行捕获处理
int signum = 50/*SIGINT*/;
if(signal(signum,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
//屏蔽2号信号
printf("%d进程:屏蔽d号信号\n",getpid(),signum);
sigset_t set;
sigemptyset(&set);
sigaddset(&set,signum);
sigset_t oldset;
if(sigprocmask(SIG_SETMASK,&set,&oldset) == -1){
perror("sigprocmask");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程向父进程发送2号信号
if(pid == 0){
for(int i = 0;i < 5;i++){
printf("%d进程:向父进程发送%d号信号\n",getpid(),signum);
if(kill(getppid(),signum) == -1){
perror("kill");
return -1;
}
}
return 0;
}
//父进程假装执行数据库更新操作
updatedb();
//解除父进程对2号信号的屏蔽
printf("%d进程:解除对%d号信号的屏蔽\n",getpid(),signum);
if(sigprocmask(SIG_SETMASK,&oldset,NULL) == -1){
perror("sigprocmask");
return -1;
}
//父进程收尸
if(waitpid(pid,NULL,0) == -1){
perror("waitpid");
return -1;
}
return 0;
}
执行结果1(不可靠信号)
day06$./sigprocmask
16029进程:屏蔽d号信号
16029进程:正在执行更新第1条数据...
16030进程:向父进程发送2号信号
16030进程:向父进程发送2号信号
16030进程:向父进程发送2号信号
16030进程:向父进程发送2号信号
16030进程:向父进程发送2号信号
16029进程:正在执行更新第2条数据...
16029进程:正在执行更新第3条数据...
16029进程:正在执行更新第4条数据...
16029进程:正在执行更新第5条数据...
16029进程:解除对2号信号的屏蔽
16029进程:捕获到2号信号
执行结果2(可靠信号)
day06$./sigprocmask
15075进程:屏蔽d号信号
15075进程:正在执行更新第1条数据...
15076进程:向父进程发送50号信号
15076进程:向父进程发送50号信号
15076进程:向父进程发送50号信号
15076进程:向父进程发送50号信号
15076进程:向父进程发送50号信号
15075进程:正在执行更新第2条数据...
15075进程:正在执行更新第3条数据...
15075进程:正在执行更新第4条数据...
15075进程:正在执行更新第5条数据...
15075进程:解除对50号信号的屏蔽
15075进程:捕获到50号信号
15075进程:捕获到50号信号
15075进程:捕获到50号信号
15075进程:捕获到50号信号
15075进程:捕获到50号信号