- 进程的功能以及概念
- 信号的生命周期以及相关的接口
- 自定义信号的捕捉流程
- 信号集以及阻塞信号集
- 了解一个SIGCHLD信号
- 信号的功能以及概念
信号的功能:信号就是通知我们某一个事件的发生
信号的概念:信号就是一个软件中断,在一个合适的时机打断我们当前的操作去处理我们信号的。
查询我们进程信号的命令:kill -l
得到我们一共62种信号,其中1-31号信号是非可靠的信号,就是信号容易丢失。也称为非实时信号,34-64号信号是可靠信号,就是信号不会丢失,也称为实时信号。 - 信号的生命周期:从信号的产生-信号在进程中的注册-信号在进程中的注销-信号的处理。
- 信号的产生
信号产生可以从两个方面来说,一个是硬件产生信号,一个是软件产生信号
- 硬件产生信号: ctrl+c ctrl+\ ctrl+z,其实对应的就是产生的2号信号SIGINT、3号信号SIGQUIT和10号信号SIGTSTP。
- 软件产生信号
- kill命令产生信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig)
就是给进程id为pid的进程发送我们的sig信号
#include <signal.h>
int raise(int sig);
给自身进程发送一个sig信号
#include <stdlib.h>
void abort(void);
给自身发送一个SIGABRT信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
在seconds的时间后给进程发送一个SIGALRM信号,其实就是一个定时器,当参数seconds为0的时候就是取消上一个定时器,不为0的时候如果是第一个定时的话就实现为闹钟,不是就替代前一个闹钟。返回值第一次返回0,不是第一次就返回上一个定时器剩余的时间数。
-
信号的注册
其实我们是用128位的位图来对信号进行标记的,其实是一个结构体sigset_t,我们使用链表qigqueue来接收我们的信号,信号到来的时候会组织一个对应信号的节点添加到队列中,对于可靠信号和非可靠信号我们有着不同的处理方式,对于非可靠信号,到信号到来的时候我们先判断我们的位图是否已经被标记,如果已经被标记了,什么都不用做,如果没有标记我们将信号添加到我们的队列中,并且标记我们的位图此信号已经被添加进来了。对于我们可靠信号,当信号来临的时候我们检查位图是否被标记,如果没有将对饮的位置进行标记,如果已经被标记了就不用管理位图,对于两种情况都需要添加节点到我们的队列中。(其实修改位图就是修改我们的味觉信号集)。
味觉信号:注册了但是没有被处理的信号
未决:是一个信号从产生到信号处理之间的过程
对应的还有一个阻塞信号集(等会儿讲解)
-
注销信号
注销信号其实是我们信号注册的相对,对于我们的可靠信号来说,注销信号是寻找我们链表上是否含有要被删除的节点,如果没有什么都不做,如果有的话我们就删除该节点,并且遍历我们的链表判断是否在链表中还含有我们要被删除的节点吗,如果有就直接退出,如果没有还要修改对应的位图。对于我们非可靠的信号就相对简单一点,如果有的话就删除节点并且修改位图,没有的话就什么都不做。
-
信号的处理
- signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:信号编号
handler:函数指针,用于我们信号处理函数。
- sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
signum:是信号编号
act:是一个结构体
struct sigaction {
void (*sa_handler)(int);//函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);//带参数的函数指针
sigset_t sa_mask;//l临时阻塞信号集
int sa_flags;//选择方式,一般为0
void (*sa_restorer)(void);
};
act就是我们实现的信号处理,old就是用来接收以前此信号的处理方式。old一般用来恢复我们的原来的操作。
例子:捕捉我们定时器产生的信号
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4 #include <sys/types.h>
5 #include <sys/time.h>
6 //捕捉函数
7 void catch_sig(int num){
8 printf("%d信号已经被捕捉了\n",num);
9 }
10 int main(){
11 //使用sigaction信号捕捉信号
12 //定义一个act
13 struct sigaction act;
14 act.sa_flags = 0;
15 act.sa_handler = catch_sig;
16 sigemptyset(&act.sa_mask);
17 sigaction(SIGALRM,&act,NULL);
18 //setitimer产生周期信号
19 struct itimerval myit = {{3,0},{5,0}};//第一次5s,再之后进行3s一次
20 setitimer(ITIMER_REAL,&myit,NULL);
21 while(1){
22 printf("who can kill me!\n");
23 sleep(1);
24 }
25
26 return 0;
27 }
-
自定义信号的处理流程
当我们的main函数中出现了系统调用,中断或者异常的时候就会从用户态切换到内核,完成内核功能返回用户态的时候就会判断是否有信号待处理(此时调用一个do_signal),如果有并且这个信号的处理方式是自定义的话就返回用户态执行信号处理函数,处理完成之后执行sig_return返回内核中,并且再次检测我们内核中是否还有待处理的信号,如果没有的话就返回main函数,如果还有就继续执行处理我们的信号。
-
信号集与阻塞信号集
信号集就是利用我们的位图对我们的信号进行标记,实际中使用一个sigset_t结构体中有一个数组long int_val[16];来表示,
阻塞信号集:就是用来表示我们阻止我们信号被递达。
其实含有三个我们信号集相关的数组,也就是我们的未决信号集和阻塞信号集和我们信号处理函数的集合。信号处理函数的集合是存储的我们信号处理函数的函数指针。
在我们信号到来的时候我们先去判断我们的阻塞信号集是否是阻塞的,如果已经是阻塞的话我们将不再进行操作了,没有没有被阻塞的话我们将在未决信号集中寻找我们的信号并且可以在处理函数的集合中寻找相对应的处理函数。
注意:9号和19号信号是不能被阻塞的。
信号集处理接口:
1.sigprocmask设置我们的阻塞集
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:
a.SIG_BLOCK,让set中的集合加入都加入block集合中
b.SIG_UNBLOCK,让set中的集合从block集合中解除
c.SIG_SETMASK,将block集合设置为set
old:用来接收原来的阻塞集合(一般用来恢复我们的原有状态)
2.获取未决信号集
#include <signal.h>
int sigpending(sigset_t *set);
set:传出参数,用来获取我们的味觉信号集
3. 添加、删除、判断一个信号是否在一个集合中
#include <signal.h>
int sigemptyset(sigset_t *set);
//清空set集合,将所有信号移除阻塞信号集
int sigfillset(sigset_t *set);
//将set集合全部置为1,也就是将所有的信号添加为阻塞信号集
int sigaddset(sigset_t *set, int signum);
//将某一个信号添加到我们的阻塞信号集中
int sigdelset(sigset_t *set, int signum);
//将某一个信号从阻塞信号集中删除
int sigismember(const sigset_t *set, int signum);
//判断某一个信号是否在阻塞信号集中
例子:打印我们的信号集,并且在我们硬件产生信号后我们的信号集对应改变
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4
5 int main(){
6
7 sigset_t pen,sigproc;
8 //设置一个阻塞信号
9 sigemptyset(&sigproc);
10 sigaddset(&sigproc,SIGINT);//按键信号ctrl c;
11 //设置阻塞信号集
12 sigprocmask(SIG_BLOCK,&sigproc,NULL);
13
14 while(1){
15 sigpending(&pen);
16 int i = 1;
17 for(i = 1;i < 32;i++){
18 if(sigismember(&pen,i)){//判断信号是不是在信号集中
19 printf("1");
20 }
21 else{
22 printf("0");
23 }
24 }
25 printf("\n");
26 sleep(1);
27 }
28 return 0;
29 }
- SIGCHLD信号
SIGCHLD信号是子进程退出信号
在我们的进程章节我们使用wait和waitpid来等待我们的子进程退出,虽然我们能够对子进程进行回收,但是我们的父进程需要一直的等待我们子进程的退出,虽然我们也可以使用waitpid设置非阻塞等待,但是我们知道我们子进程在退出的时候都会发送一个SIGCHLD信号,我们就可以使用此信号对子进程进行回收,由于我们SIGCHLD信号的默认的处理方式是忽略,所以我们在以前此信号到达之后就被忽略了。我们需要自定义去处理我们的SIGCHLD信号。但是我们处理的时候需要注意,我们的SIGCHLD是非可靠信号,当我们同时有多个子进程退出的时候在信号处理函数中调用wait回收函数的时候只能回收一个,所以我们需要一定的逻辑处理,使用while循环进行处理即可。
例子:回收10个子进程
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4 #include <sys/wait.h>
5 void catch_sig(int num){//每次只能进行一个回收。当多次执行的时候我们不能全部回收
6
7 printf("现在回收退出了\n");
8 pid_t pid ;
9 while((pid = waitpid(-1,NULL,WNOHANG))>0);
10 if(pid>0){
11 printf("wait child%dok",pid);
12 }
13 printf("子进程回收完毕\n");
14 }
15 int main(){
16 int i = 0;
17 pid_t pid;
18 for(i = 0;i<10;i++){
19 pid = fork();
20 if(pid == 0){
21 break;
22 }
23 }
24 if(i == 10){
25 //父进程,注册捕捉函数
26 struct sigaction act;
27 act.sa_flags = 0;
28 sigemptyset(&act.sa_mask);
29 act.sa_handler = catch_sig;
30 sigaction(SIGCHLD,&act,NULL);
31 while(1){
32 sleep(1);
33 }
34 }else if(i<10){
35 printf("i am %d child,pid = %d\n",i,getpid());
36 sleep(1);//此时不睡觉的话会产生最后一个不会被回收,会产生僵尸进程
37 }
38 return 0;
39 }
40
看我们的程序,在执行过后出现结果:
你也许会产生疑问,为什么10个子进程没有回收10次呢,这是因为我们有点进程在同一个时间同时被回收了,你可以尝试将信号处理函数中的while循环后面的分号去掉,会出现如下的结果
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)