1.信号
1)信号是进程之间事件异步通知的一种方式,属于软中断。
2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
信号的种类
1~31:非可靠信号
34~64:可靠信号
2.常见的信号
硬件产生
- SIGINT (2):ctrl+c 终止信号
- SIGQUIT (3):ctrl+\ 终止信号
- SIGTSTP (20):ctrl+z 暂停信号
- SIGALRM:闹钟信号 收到此信号后定时结束,结束进程
- SIGCHLD:子进程状态改变,父进程收到信号
- SIGKILL:杀死信号
软件产生
- kill函数
kill(pid_t pid, int signo)
- kill -[信号][pid] :
kill -9 12121
- abort(pid_t pid):
6号信号(SIGABORT) // double free
- 解引用空指针+内存访问越界---->进程收到 SIGSEGV(11)
3.信号的注册
- 在使用sig数组的时候不是按照数组的方式去使用的,而是按照比特位的方式去使用的
- 对于sig位图当中的bit位,每一个比特位有与之对应的信号,直到将我们当前操作系统的信号表示完毕
- 当进程收到一个非可靠信号
- 第一件事情:将非可靠信号对应的比特位更改为1
- 第二件事情:添加sigqueue节点到sigqueue队列当中
- 注意:队列当中已经有了该信号的sigqueue节点,则不添加
- 如果进程收到一个可靠信号
- 第一件事情:在sig位图当中更改该信号对应的比特位为1
- 第二件事情:不论之前sigqueue队列当中是否存在该信号的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中;
4.信号的注销
- 非可靠信号的注销
- 将该信号的sigqueue节点从sigqueue队列当中的出队操作
- 信号在sig位图当中对应的比特位从1置为0
- 可靠信号的注销
- 将该信号的sigqueue节点从sigqueue队列当中进行出队操作
- 需要判断sigqueue队列当中是否还有相同的sigqueue节点
没有:信号在sig位图当中对应的比特位从1置为0
有:不会更改sig位图当中对应的比特位
5.信号的处理方式
1)默认处理(SIG_DFL):执行该信号的默认处理动作。注意,对大多数信号的系统默认动作是终止该进程。
2)忽略处理(SIG_IGN):对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
3)捕捉信号(自定义信号处理方式):定义信号处理函数,当信号发生时,执行相应的处理函数。
sighanler_t signal(int signum, sighhandler_t handler); // 将signum的处理方式修改为handler
typedef void (*sighandler_t )(int); // 上行函数的自定义处理方式
// 自定义处理应用
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigback(int signo)
{
printf("signo :%d\n", signo);
}
int main()
{
signal(2, sigback);
while(1)
{
printf("linux so easy!\n");
sleep(1);
}
return 0;
}
6.sigaction函数
sigaction
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
参数
- signum:待更改的信号的值
- act:将信号处理函数改变act
- oldact:信号之前的处理方式
struct sigaction {
void (*sa_handler)(); /* 保存内核对信号的处理方式 */
sigset_t sa_mask; /* 保存的是当前进程在处理信号的时候,收到的信号 */
int sa_flags; /* SA_SIGINFO:对信号处理程序提供了附加信息*/
}
sigempty
int sigempty(sigset_t *set); // 将位图的所有比特位设置为0
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigback(int signo)
{
printf("signo: %d\n", signo);
}
int main()
{
// act--->入参
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = sigback;
// old--->出参
struct sigaction oldact;
sigaction(2, &act, &oldact);
getchar();
sigaction(2, &oldact, NULL);
while(1)
{
printf("linux so easy!\n");
sleep(1);
}
return 0;
}
7.信号的捕捉流程
- 用户空间到内核空间:中断、异常、系统调用函数、库函数时(库函数底层大多数都是封装系统调用函数的)
捕捉信号
从用户态到内核态的过程是权限升级的过程,而从内核态到用户态的过程是要从安全方面考虑的,因为操作系统不相信任何人,只相信它自己,所以要想要访问操作系统内部进行访问时必须通过系统调用接口。
操作系统的代码只能由操作系统执行,用户的代码就只能由用户执行,因此就会有内核态和用户态两种状态之间的相互转变。
8.信号在内核的三种状态
1)信号递达(Delivery):实际执行信号的处理动作称为信号递达;
2)信号未决(Pending):信号从产生到递达之间的状态;
3)信号阻塞(Block):被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作;
信号屏蔽字
// 读取或更改进程的信号屏蔽字
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数
-
how:告诉sigprocmask函数,应该进行什么操作
SIG_BLOCK:设置每个信号为阻塞
SIG_UNBLOCK:解除对某个信号的阻塞
SIG_SETMASK:替换阻塞位图 -
set:用来设置阻塞位图
SIG_BLOCK:设置某个信号为阻塞 block(new) = block(old) | set
SIG_UNBLOCK:解除对某个信号的阻塞 block(new) = block(old) & (~set)
SIG_SETMASK:替换阻塞位图 block(new) = set -
oldset:原来的阻塞位图
#include <signal.h>
#include <iostream>
using namespace std;
int main()
{
sigset_t sigset;
if(sigprocmask(0, NULL, &sigset) < 0){
cout << "sigprocmask error"<< endl;
}
if(sigismember(&sigset, SIGINT)) printf("SIGINT");
if(sigismember(&sigset, SIGQUIT)) printf("SIGQUIT");
if(sigismember(&sigset, SIGSR1)) printf("SIGSR1");
if(sigismember(&sigset, SIGALRM)) printf("SIGALRM");
printf("\n");
return 0;
}
未决信号集
// 读取当前进程的信号未决集,通过set参数传出
int sigpending(sigset_t *set);
返回值
- 成功返回0,出错返回-1。
8.信号的阻塞
- 信号的阻塞并不会干扰信号的注册,只不过当前的进程不能立即处理
- 当我们将block位图当中对应信号的比特位置为1,表示当前进程阻塞该信号
- 当进程收到一个信号的时候,进程还是一如既往的对该信号进行注册
- 当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,这会儿不会立即去处理该信号了
- 不会立即处理:延迟处理