首先,要明确信号和信号量两者没有任何关系。信号:Linux系统提供的让用户(进程)给其它进程发送异步信息的一种方式,属于软中断。信号量属于进程间通信。在进程运行期间,信号可以分为如下阶段:信号的产生、信号的保存、信号的处理。
1. 信号的产生
我们可以使用 kill -l 的指令查看系统中的信号列表,这些信号本质上就是宏,宏的定义可以在signal.h 中找到,通过 man 7 signal 可以查看每个信号的具体含义,其中编号34以上的是实时信号,暂不讨论:
1.1 按键产生信号
Ctrl+C 产生的SIGINT的默认处理动作是终止进程, Ctrl+\ 产生的SIGQUIT的默认处理动作是终止进程并且Core Dump(云服务器系统中默认关闭),Ctrl+Z 产生的SIGTSTP的默认处理动作是暂停进程。对于按键输入的信息是由键盘驱动和操作系统联合解释的,而操作系统是通过硬件中断技术知道键盘在输入数据。
1.2 系统调用产生信号
1. kill 函数
kill 命令是调用kill函数实现的,kill函数可以给一个指定的进程发送指定的信号。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
成功返回0,错误返回-1
2. raise 函数
raise 函数可以给当前进程发送指定的信号。(类似与 kill(getpid(),信号) )
#include <signal.h>
int raise(int sig);
成功返回0,错误返回-1
3. abort 函数
abort 函数使当前进程接收到指定的 SIGABRT 信号而异常终止。(类似与 kill(getpid(),6) )
#include <stdlib.h>
void abort(void);
类似exit函数,abort函数总是会成功的,所以没有返回值。
1.3 软件条件产生信号
1. SIGPIPE
是一种由软件条件产生的信号,当管道文件读写条件不满足时会产生。
2. SIGALRM
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
1.4 硬件异常产生信号
1.5 Core Dump
status在学习进程等待的时候,我们了解到wait和waitpid都有一个共同的参数status,它是子进程的退出状态信息。这里的core dump标志进程是否接收到信号,默认为0,所以信号没有0信号.
在信号中,我们可以看到一些信号的默认操作是核心转储Core,但在云服务器上,Core和Term都是终止进程。是因为为了防止未知的core dump 一直进行,导致服务器磁盘被打满,所以云服务器默认将core 退出,进行特定处理,默认core是关闭的。
我们可以通过如下指令查看到core是被关闭的:
可以通过如下指令打开core功能:
打开之后,当进程接受到某些默认处理是Core的信号终止后,会把该进程在内存中的与调试有关的核心数据转储到磁盘中形成core、core.pid的文件,方便我们事后进行调试。
2. 信号的保存
2.1 相关概念
2.2 在内核中的表示
2.3 sigset_t
2.4 信号集操作函数
2.4.1 操作sigset_t类型的函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
成功返回0,出错返回-1
2.4.2 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集):
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
2.4.3 sigpending
#include <signal.h>
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
3. 信号的处理
3.1 内核对信号的捕捉
首先,我们要明确信号就是通过软件的方式,来模拟硬件中断。通过高频率、不间断的向cpu发送中断,cpu就会不断地处理中断。cpu根据中断向量表,就会去执行对应的功能(比如操作系统、响应键盘),所以操作系统就会一直执行。
每个进程都有自己的PCB和进程空间如下。可以看到除了常用的用户空间,还有内核空间。这两个空间都有一张对应的页面来映射物理地址空间,分别是用户级页表和内核级页表。要知道我们访问OS,本质就是通过进程的内核空间来访问的,这也是为什么无论进程怎么切换,总能找到OS。
当CPU处理进程的时候,因为内核数据不暴露给外面,所以需要区分是用户级别还是内核级别,从而访问不同级别的页表,在CPU中就有了CS寄存器中存放了权限标识(0:内核态 3:用户态),CR3寄存器存放对应级别页表的物理地址(关于这两个寄存器不做详述)。所以这时就必须要区分当前用户的运行模式:用户态、内核态。
信号的处理过程中,一共有4次的状态切换(用户态与内核态)。如图是信号的捕捉流程:
3.2 sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
调用成功则返回0,出错则返回- 1
若 oact 指针非 空 , 则通过 oact 传出该信号原来的处理动作
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
4. 可重入函数
5. volatile
作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量 的任何操作,都必须在真实的内存中进行操作。
下面我们通过代码,来观察volatile在信号中的作用:
#include <stdio.h>
#include <signal.h>
int flag = 0;
//volatile int flag = 0;
void handler(int sig)
{
(void)sig;
printf("chage flag 0 to 1\n");
flag = 1;
}
int main()
{
signal(2, handler);
while (!flag);
printf("process quit normal\n");
return 0;
}
标准情况下,当按下Ctrl+C时2号信号被捕捉,执行handler 函数,从而导致while循环退出。但现在我们可以在gcc编译时加上参数强制编译器优化,使用man gcc 可以看到,默认优化级别是0。优化逻辑是:while循环中没有对flag进行操作,为了提高效率,每次对flag进行逻辑运算时,不再从内存中读取,而是直接在cpu的相关寄存器中读取。这时,我们在handler 函数中对flag的修改是对内存中的flag修改,寄存器中的flag还是0,存在数据二义性,所以按下Ctrl+C也不能终止循环。
而当我们使用volatile后,强制编译器读取flag时,从内存中读取,就可以解决上面的问题。
6. SIGCHLD信号
其实, 子进程在终止时会给父进程发 SIGCHLD 信号, 该信号的默认处理动作是忽略, 父进程可以自定义 SIGCHLD 信号 的处理函数 , 这样父进程只需专心处理自己的工作 , 不必关心子进程了 , 子进程终止时会通知父进程 , 父进程在信号处理 函数中调用 wait 清理子进程即可。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{
pid_t id;
while ((id = waitpid(-1, NULL, WNOHANG)) > 0)
{
printf("wait child success: %d\n", id);
}
printf("child is quit! %d\n", getpid());
}
int main()
{
signal(SIGCHLD, handler);
pid_t cid;
if ((cid = fork()) == 0)
{ // child
printf("child : %d\n", getpid());
sleep(3);
exit(1);
}
while (1)
{
printf("father proc is doing some thing!\n");
sleep(1);
}
return 0;
}