(六)信号

目录

1. 信号

1.1 什么是信号

1.2 信号的命名 

1.3 谁会向进程发送信号

1.4 进程收到信号后,进程会如何处理

1.5 都有哪些信号

1.5.1 信号列表

1.5.2 常用信号

2. signal函数

函数原型

调用捕获函数的过程

值得强调的地方

3. 子进程对父进程信号的继承情况

3.1 fork创建子进程,但是没有exec加载新程序时,信号的继承情况

3.2 当有调用exec加载新程序时

3.3 总结

4、kill、raise、alarm、pause、abort函数

4.1 kill、raise

4.2 alarm、pause

4.3 abort函数    

5. 使用信号唤醒休眠函数

5.1 会导致休眠的函数

5.2 唤醒的方法

5.3 唤醒的过程

5.4 我想继续休眠怎么办

5.5 休眠函数自动重启

6. 信号的发送、接收和处理的过程  

6.1 信号屏蔽字

6.1.1 信号屏蔽字的作用,以及它被放在了哪里

6.1.2 屏蔽字长啥样子

6.1.3 我们可不可以自己修改信号屏蔽字,实现某个信号的打开和屏蔽呢?

6.2 未处理信号集

6.2.1 作用

6.2.2 什么时候会记录

6.2.3 什么时候处理记录的“未处理信号”

6.3 信号处理的完整过程

7. 修改信号屏蔽字的API

7.1 修改的原理

7.2 设置变量的API    

7.3 使用变量修改屏蔽字的API

8.sigaction函数


1. 信号

1.1 什么是信号

信号是一种向进程发送通知,告诉其某件事情发生了的一种简单通信机制。

1.2 信号的命名 

Linux下边定义了很多的信号,所有的信号都是一个整数编号,不过为了好辨识,Linux系统给这些整数编号都定义了对应的宏名, 宏名都是以SIG开头

1.3 谁会向进程发送信号

总结起来,会有三个“人”会向进程发送信号,分别是“另一进程”、“OS内核”、“硬件”。

            

1.4 进程收到信号后,进程会如何处理

三种处理方式,分别是忽略、捕获、默认。

1.5 都有哪些信号

1.5.1 信号列表

(1)35~64:这些信号是Linux后期增设的信号,这些个信号不需要关心

(2)1~34:也不是所有的信号都要掌握,我们只关心其中常用的信号

1.5.2 常用信号

(1)为什么当进程收到某些信号时,会被终止呢?

因为你发送的这些信号的处理方式是终止,所以进程会被终止掉。

(2)kill命令

1)kill的作用

2)pkill

(3)信号的发送与接收

1)发送

     一般来说,大多数发送信号的原因,都是因为内核、硬件发生了某些事件时,才会向某个进程发送

我们自己发送信号的原因无非如下几种情况:

2)接收

   对于我们自己写的进程来说,最常见信号操作的还是接收信号,不过在一般情况下,我们进程并不会去重新设置信号的处理方式,而是使用信号的默认处理方式来处理信号

(4)core文件

1)什么是core文件

      用于保存程序(进程)在当前结束的这一刻,进程在内存中的代码和数据,core文件可以用于分析进程在结束时的状况,不过由于进程代码和数据都是二进制的,一般需要特殊软件翻译后才能看懂。

2)并不是所有的信号在终止进程时都会产生core文件

3)如果你不想丢弃core文件怎么办

     对相关的系统文件进行设置就可以了,core文件一般默认保存在当前路径下。

2. signal函数

函数原型

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

  

调用捕获函数的过程

  1. 当信号没有发生时,进程正常运行,当信号发生时,进程的正常运行会被中断,然后去处理信号
  2. 一看信号的处理方式是捕获,就会从“信号处理方式登记表”中将捕获函数的地址取出并执行捕获函数,
  3. 捕获函数执行完毕后,恢复进程的正常运行。

不过当信号来时,如果当前有一条指令正在运行,会先等这条指令运行执行完毕后再去调用信号处理函数。  

不过如果捕获函数有调用exit或者_exit的话,进程会被终止,不过是正常终止。

值得强调的地方

(1)信号被设置为SIG_DFL时,表示将处理方式设置为默认

           其实在不做任何处理方式设置的情况下,信号的处理方式就是系统设置的默认处理方式。

(2)信号被设置为SIG_IGN(忽略)时

           进程将不会再接收到这个信号,这信号对进城没有任何影响。

(3)设置为捕获时,需要将handler设置为捕获函数的地址,类型为void (*)(int)

           为了确保和捕获函数的类型统一,SIG_DFL、SIG_IGN和SIG_ERR宏的类型也必须是void (*)(int)。

(4)除了SIGKILL这两个信号外,其它所有的信号都可被忽略和捕获。

3. 子进程对父进程信号的继承情况

父进程fork出子进程时,子进程会继承父进程很多的属性,其中就包括信号

3.1 fork创建子进程,但是没有exec加载新程序时,信号的继承情况

在fork子进程之前,如果父进程调用signal设置了某个信号的处理方式的话,那么fork出的子进程会继承父进程对该信号设置的处理方式

父进程将信号的处理方式设置为捕获时,捕获函数对子进程也是有效的。

再次强调,只有在fork之前,父进程所设置的信号处理方式,才会被子进程继承。

 

(1)为什么捕获函数在子进程里面依然有效。

            因为子进程复制了父进程的代码和数据,子进程自然也会包含信号处理函数的代码,所在子进程中依然有效。

(2)子进程可以自己调用signal函数,修改掉所继承的处理方式。

(3)那如果父进程是在if(ret > 0){}里面设置得呢?

            这就是父进程自己的设置,跟子进程没有关系。

3.2 当有调用exec加载新程序时

3.2.1 fork之前,父进程设置的处理方式是忽略 或 默认时

exec加载新程序后,忽略和默认设置依然有效。

3.2.2 fork之前,父进程设置处理方式是捕获时

新程序的代码会覆盖子进程中原有的父进程的代码,信号捕获函数的代码也会被覆盖,

既然捕获函数已经不存在了,捕获处理方式自然也就没有意义了,所以信号的处理方式会被还原为默认处理方式。

 

终之,如果子进程所继承的信号处理方式是捕获的话,exec加载新程序后,捕获处理方式会被还原为默认处理方式。    

3.3 总结

  

4、kill、raise、alarm、pause、abort函数

4.1 kill、raise

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

//kill命令就是调用这个函数来实现。

#include <signal.h>

int raise(int sig);

  

4.2 alarm、pause

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

int pause(void);

  

4.3 abort函数    

也被称为叫自杀函数,之所以称为自杀函数,是因为调用该函数时,会向当前进程发一个SIGABRT信号

这个信号的默认处理方式是终止,因此如果不忽略和捕获的话,会将当前进程终止掉。

5. 使用信号唤醒休眠函数

5.1 会导致休眠的函数

我们调用sleep、pause等函数时,这些函数会使进程进入休眠状态,如果你不想继续休眠时怎么办?

可以使用信号将其唤醒。

5.2 唤醒的方法

给信号登记一个空捕获函数即可,当然你也可以在捕获函数写你要的代码,不过如果仅仅只是用于唤醒的话,捕获函数的内容一般都是空的。

5.3 唤醒的过程

当信号发送给进程后,会中断当前休眠的函数,然后去执行捕获函数,捕获函数执行完毕返回后,不再调用休眠函数,而是执行休眠函数之后的代码,这样函数就被唤醒了。

5.4 我想继续休眠怎么办

自己手动重新启动休眠函数(重新调用休眠函数)。

 

根据被信号捕获后 pause的返回值 调用goto

5.5 休眠函数自动重启

比如使用read从键盘读取数据,当键盘没有输入数据时,read会休眠,不过函数被信号唤醒后,会自动重启read的调用。

当read函数休眠时,如果被信号唤醒了,当捕获函数返回后,read会自动重启。

 

对于绝大多数休眠函数来说,被信号中断后,如果你想继续休眠的话,需要自己去手动重启

6. 信号的发送、接收和处理的过程  

6.1 信号屏蔽字

6.1.1 信号屏蔽字的作用,以及它被放在了哪里

6.1.2 屏蔽字长啥样子

为了方便理解,我们简单地认为屏蔽字就是一个64位的unsigned int数,每一位对应着一个信号

0表示信号可以 被立即处理    1表示该信号被屏蔽了,暂不处理。

6.1.3 我们可不可以自己修改信号屏蔽字,实现某个信号的打开和屏蔽呢?

可以

只不过在默认情况下,信号屏蔽字中所有的位都为0,也就说默认将所有的信号都打开了。

6.2 未处理信号集

6.2.1 作用

跟屏蔽字一样,也一个64位的无符号整形数,专门用于记录未处理的信号。

“未处理信号集”同样也是被放在了进程的进程表中(task_struct)。

6.2.2 什么时候会记录

6.2.3 什么时候处理记录的“未处理信号”

当屏蔽字中该信号的位变成0时(被打开了),此时就回去检查“未处理信号”,看该信号有没有未决情况,有的话就处它。

6.3 信号处理的完整过程

    

7. 修改信号屏蔽字的API

7.1 修改的原理

(1)定义一个64位的与屏蔽字类似的变量

(2)将该变量设置为要的值 ----------将某信号对应的位设置为0或者为1。

(3)使用这个变量中的值来修改屏蔽字

   

7.2 设置变量的API    

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);

   set就是我们前面说的变量,至于变量名也可以定义为其它的名字,不一定非要叫set。

  

7.3 使用变量修改屏蔽字的API

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

  

8.sigaction函数

sigaction函数相当于是signal函数的复杂版

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章