linux下进程的信号量
信号:
- 信号的基本概念
- 信号与异常处理
- 信号的处理方法
- 信号的可靠性
- 函数可重入性
- 信号集
- 信号屏蔽
首先来介绍一下什么是信号,信号的基本概念:
1:信号(signal)机制是Linux系统中最为古老的进程之间的通信机制,解决进程在正常运行过程中被中断的问题,导致进程的处理流程会发生变化
2:信号是软件中断
3:信号是异步事件
a:不可预见
b:信号有自己的名称和编号
c:信号和异常处理机制
4:信号发生的来源
a:硬件来源,如按键的事件,或者硬件故障,是由驱动程重点内容序产生
b:软件来源:最常用发送信号的系统函数 kill(),raise(),alarm()和setitimer()等函数
软件来源还包括一些非法运算等操作,软件设置条件如(gdb调试),信号由内核产生;
5:查看linux系统内置的信号,在前面博客中其实也提到相关信号:
使用kill -l来查看信号列表
信号是没有优先级之分的;
1-31称为非实时信号,发送的信号可能会丢失,不支持信号排队,
32-64成为实时信号,支持信号排队,发送的多个实时信号都会被接受
前面31个信号类型在linux中的/usr/include/bits/signum.h文件中会有详细定义
6: 信号的历史变革
信号出现在早期的Unix中
早期信号模型是不可靠的
BSD和System V分别对早期信号进行扩展,但是相互不兼容
POSIX统一了上述两种模型,提供了可靠信号模型
信号的处理方式:
进程可以通过三种方式来响应和处理一个信号
1:忽略信号
SIGKILL和SIGSTOP永远不能忽略
忽略硬件异常
进程启动时SIGUSR1和SIGUSR2两个信号会被忽略
2:执行默认操作
每个信号有默认动作,大部分信号默认动作都是终止进程
3:捕获信号
告诉内核出现信号时调用自己的处理函数
SIGKILL和SIGSTOP不能被捕获
信号的处理方法
信号注册(接收)函数
1. signal函数
signal函数
#include<signal.h>
void (*signal(int signo,void(*func)(int))) (int);
返回:若成功则返回先前的信号处理函数指针(func),出错返回SIG_ERR
功能:向内核登记信号处理函数
参数:
signo
要登记的信号值,一般用信号的宏来
func
信号处理函数指针
SIG_IGN
忽略信号
SIG_DFL
采用系统默认的方式处理信号,执行默认操作
使用man signal可以查看这个函数的具体的函数定义
非常简单的实例代码
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
void signal_handler(int signo){
printf("signo:%d\n",signo);
}
int main(int agrc,char * argv[]){
//ctrl+c
if(signal(SIGINT,signal_handler) == SIG_ERR){
perror("sigint error");
}
if(signal(SIGSEGV,signal_handler) == SIG_ERR){
perror("sigsegv error");
}
//SIGUSR1,SIGUSR2在不捕获的状态下,默认是忽略的
if(signal(SIGUSR2,signal_handler) == SIG_ERR){
perror("siguser1 error");
}
if(signal(SIGUSR1,signal_handler) == SIG_ERR){
perror("siguser2 error");
}
//sigkill和sigstop是不允许被忽略和捕获的,在注册的时候就已经会报错
if(signal(SIGKILL,SIG_DFL) == SIG_ERR){
perror("sigkill error");
}
if(signal(SIGSTOP,SIG_DFL) == SIG_ERR){
perror("signal stop error");
}
//ctrl+z
if(signal(SIGTSTP,signal_handler) == SIG_ERR){
perror("signal tstp error");
}
int i = 0;
while(++i < 50){
printf("pid:%d,count:%d\n",getpid(),i);
sleep(1);
}
return 0;
}
怎么使用信号来处理僵尸进程
SIGCHILD信号
子进程状态发生改变(子进程结束)产生该信号,父进程需要使用wait调用来等待子进程结束并回收它。
避免僵尸进程
子进程结束之后自动产生的该信号;
当父进程捕获到SIGCHILD信号之后,一定要去调用wait(0)函数,否则子进程会成为僵尸进程
优点:只有当子进程终止之后,父进程才调用wait函数,而如果父进程直接去调用wait函数的话,那这个时候可能就会导致父亲进程会直接阻塞住。这样就会导致父进程执行的效率极低
父亲进程通过注册去监听回收僵尸进程部分的代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<sys/types.h>
void singal_handler(int signo){
printf("%d occred %d\n",getpid(),signo);
//释放子进程(僵尸进程)
wait(NULL);
}
void outNum(int num){
int i = 0;
for(i = 0;i<num;i++){
printf("%d count %d\n",getpid(),i);
sleep(2);
}
}
int main(int argc,char * argv[]){
pid_t pid = fork();
if(pid < 0){
perror("fork error\n");
exit(1);
}else if(pid > 0){
//注册等级子进程deaded的时候的信号
if(signal(SIGCHLD,singal_handler)==SIG_ERR){
perror("signal child error");
}
outNum(20);
}else {
outNum(10);
}
return 0;
}
信号注册(接收)函数
信号发送:
除了内核和超级用户,并不是每个进程都可以向其他进程发送信号
一般的进程只能向具有相同uid和gid的进程发送信号或相同进程组中的其他进程发送信号
常用的发送信号的函数有
1:kill(),
2:raise(),
3:alarm(),
4:setitimer(),
5:abort()等
1:kill函数和raise函数
#include<signal.h>
int kill(pid_t pid,int signo);
返回:成功返回0,出错返回-1
功能,向指定的进程发送某一个信号
int raise(int signo);
返回:成功返回0,出错返回-1
功能:向进程本身发送一个信号,相当于kill(getpid(),signo);
参数:
pid 接受信号的pid
signo 要发送的信号值
kill函数将信号发送给进程或者进程组
0为空信号,常常用来检测特定的进程是否存在
参数pid取值:
pid > 0 将信号发给进程ID为pid的进程
pid==0将信号发给与发送进程同一进程组的所有进程
pid<0将信号发送给进程组ID等于pid的绝对值
pid==-1将信号发送给发送进程有权限向他们发送信号的系统上的所有进程
2:alarm函数:
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
useconds_t ualarm(useconds_t usecs, useconds_t interval);
返回:0或以前设置的定时器时间余留的秒数
alarm函数可设置定时器 ,当定时器超时,产生一个SIGALRM信号
该信号由内核产生,在制定的seconds秒之后,给进程本身发送 一个SIGALRM信号
参数为0,取消以前设置的定时器
ualarm是微秒为单位的
上面信号发送的两个函数的调用其实也相对来说比较简单。在这里就不去进行演示了。
总结一下进程的信号量问题:
其实linux进程的信号量问题给我的感觉就好像是android里面的广播机制一样,如果你想要去接受到广播的话,那么就必须提前去注册一个广播的监听,这样才会有接受广播的资格,而进程信号其实也是,要想在进程中去监听信号的话,也必须要先通过singal去注册,而发广播其实就是linux进程信号的发送方式,可以通过kill,raise,等函数调用。去向指定进程(注册过信号的)发送消息。而指定进程根据消息类型类选择执行的方式。
谢谢大家的观看。写的不好的地方,希望能够给予指出。
一起交流学习的qq群号为324652573