提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
一、信号的概念
- 信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(也称为事件)。
- 信号是硬件中断的软件模拟,是一种软中断。每个信号用一个整型常量宏表示,以 SIG 开头,比如 SIGCHLD、SIGINT等,它们在系统头文件<signal.h>中定义,也可以通过在 shell 下键入 kill –l 查看信号列表,或者键入 man 7 signal 查看更详细的说明。
$kill -l
$man 7 signal
- 软件层面: 异步事件机制
自己杀自己:同步,别人杀死自己:异步
产生信号的手段:
①:硬件产生 ctrl+c 异步
② :硬件成生/0 同步
③ : 软件产生 kill 异步
④: 软件产生 abort 同步
二、Linux中信号(signal函数)
1.种类
SIGINT Ctrl+C
SIGQUIT CRTL+
SIGKILL CRTL+TOP
SIGUSR1 自定义行为 一般用来实现进程的有序退出
2. 信号的实现机制
- 信号的生成来自内核,对应了 task_struct 的sigpending成员,表示待处理信号的集合(位图),当一个进程处于一个可以接受信号状态的时候(这种状态被称为响应时机),它会从sigpending中取得信号,并执行默认行为、忽略或者是自定义信号处理函数。
- 因此信号的实现可以分为两个阶段,信号产生表示内核已知信号发生并修改进程的数据结构;信号递送表示内核执行信号处理流程。
- 已经产生但是还没有传递的信号被称为挂起信号(pending signal)或者是未决信号。如果信号一直处于未决状态,那么就称进程阻塞了信号传递。
- 由进程的某个操作产生的信号称为同步信号(synchronous signals),例如在代码中除 0;由像用户击键这样的进程外部事件产生的信号叫做异步信号(asynchronous signals)。
- 同步和异步是编程当中一个非常重要的概念,同步表示事件之间的执行顺序是确定的,这种事件处理方式就更符合人类的认知习惯;异步表示事件之间的执行顺序是随机的,异步模式在使用良好的情况下更符合真实的物理世界,也能实现更高的执行效率。
- 目前,很多框架都是采用异步的方式实现底层请求处理,但是框架实现了良好的封装,这样程序员可以比较容易地用同步的方式编写程序代码,间接地使用异常方式提高运行效率。
- 用户使用自定义信号处理函数的主要目的就是实现进程的有序退出。如果没有实现进程的有序退出,就产生一些严重的运行事故,比如著名的“东京证券交易所系统宕机事件”。
3. 信号的处理:
1. 默认递送行为
- 进程接收信号后以默认的方式处理。
例如连接到终端的进程,用户按下 ctrl+c ,将导致内核向进程发送一个 SIGINT 的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号( 对应的信号处理函数使用是 signal(SIGINT,SIG_DFL) )。
默认处理有5种可能:
① Term表示终止进程;
② Ign表示忽略信号;
③ Core表示终止进程并产生core文件;
④ Stop表示暂停进程;
⑤ Cont表示继续进程。
- 实现进程的有序退出。
2. 忽略信号
进程可以通过代码,显示地忽略某个信号的处理。比如如果将SIGSEGV信号进行忽略(使用信号处理函数 signal(SIGSEGV,SIG_IGN) ),这是程序运行如果访问到空指针,就不会报错了。但是某些信号比如SIGKILL是不能被忽略的。
3. 捕捉信号并处理(具体见4):
进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。
4. 修改信号的递送行为
1. 函数signal注册信号
- signal 函数可以用来捕获信号并且指定对应的信号处理行为。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//这个函数是由ISO C定义的,因为要支持多个平台,所以它对信号的支持功能非常有限
-
typedef void (*sighandler_t)(int);
typedef 给类型、函数指针 起别名 sighandler_t
-
sighandler_t signal(int signum, sighandler_t handler);
signal 的第 1 个参数 signum 表示要捕捉的信号,第 2 个参数是个函数指针,表示捕获信号后执行的函数(这里就是一个回调函数)。
-
sighandler_t 是信号捕捉函数,是一个回调函数,在 signal 函数中注册,注册后在整个进程运行过程中均有效,并且对不同的信号可以注册同一个回调函数。该函数只有一个整型参数,表示信号值。
-
signal 如果调用成功,返回以前该信号的处理函数的地址,否则返回SIG_ERR。
-
signum 信号的类型;
sighandler_t handler 递送行为。
#include <func.h>
void sigfunc(int signum){
printf("signum = %d is coming\n",signum);//signum表示信号的具体数值
}
int main()
{
signal(SIGINT, sigfunc);//将SIGINT信号的处理行为注册成sigfunc
printf("proces begin!\n");
while(1);
return 0;
}
- 启动进程以后,如果向这个进程发送键盘中断,可以看到进程不会终止,反而会调用回调函数打印一些信息。
- 所以从这里可以了解到bash进程的实现原理,bash进程会注册SIGINT信号,这样当从键盘输入中断时,bash进程不会终止了。
2. 函数signal注册信号
-
需要特别注意的是,回调函数并不是由进程自己执行,而是当进程处于内核态的时候(比如处理系统调用或者是中断),由内核执行的。
-
因为信号处理流程是由内核发起的操作,而且内核执行的上下文(上下文表示执行过程中的寄存器状态)和进程上下文是完全独立的,所以当使用到stdout这类用户态缓冲区时,内核态代码执行结束的时候并不会主动去清理缓冲区。
-
所以,要记得加换行!!!!!
-
9号KILL信号不可以被注册。
信号递送过程再产生一个同类信号
在递送过程中,临时屏蔽信号
3. 注册多个信号
- 使用 signal 函数是可以同时注册多个信号的。
#include <func.h>
void sigfunc(int signum){
printf("signum = %d is coming\n",signum);//signum表示信号的具体数值
}
int main()
{
signal(SIGINT, sigfunc);//将SIGINT信号的处理行为注册成sigfunc
signal(SIGQUIT, SIG_IGN);
printf("proces begin!\n");
while(1);
return 0;
}
4. 信号递送过程再产生一个同类信号
-
接受到了另一个相同类型信号,那么当前的信号处理流程是会不会被中断的,CPU会继续原来的信号处理流程,执行完毕以后再响应新来到的信号。
-
如果接受到了连续重复的相同类型的信号,后面重复的信号会被忽略,从而该信号处理流程只能至多执行一次。
-
底层原理:
mask 屏蔽集合:
① 信号不在mask中 产生信号马上递送
② 信号在mask中,产生信号放入pending
pending 未决集合:
每种光信号只占1bit,判断有没有信号处于未决
当解除屏蔽时,如果pending有信号,就取出递送之。
5. 信号递送过程产生一个不同类信号
-
接受到了另一个不同类型信号,那么当前的信号处理流程是会被中断的,CPU会先转移执行新到来的信号处理流程,执行完毕以后再恢复原来信号的处理流程。
-
内核里面,某个进程的所有的挂起信号都是使用一个位图(被称为信号屏蔽字)进行管理的。
-
当进程处于某个信号处理流程的时候,如果再产生一个同类型信号,信号处理流程不会中断,而信号屏蔽字中的对应位会设置为1,表示此时存在一个挂起信号,随后产生的同类型信号将不再被记录。
-
之所以设计同类信号无法中断,是考虑到信号处理流程可能会修改静态数据或者堆数据(这种函数被称为不可重入函数),如果中断处理流程,可能会导致进程破坏。
5. signal的其他性质
使用传送终端了低俗系统调用之后,自动重启低俗系统调用。
二、sigacation函数
1. 简介
- 在 signal 处理机制下,在一些特殊的场景,它满足这样的行为:
- 注册一个信号处理函数,并且处理完毕一个信号之后,不需要重新注册,就能够捕捉下一个信号。
- 如果信号处理函数正在处理信号,并且还没有处理完毕时,又产生了一个同类型的信号,那么会依 次处理信号,并且忽略多余的信号。
- 如果信号处理函数正在处理信号,并且还没有处理完毕时,又产生了一个不同类型的信号,那么会 中断当前处理流程,跳转新信号的处理流程。
- 如果程序阻塞在一个系统调用(比如 read )时,产生一个信号,这时会有两种不同类型的行为,
一种大多数系统调用的行为,例如读写磁盘文件时的等待,信号递送时会让系统调用返回错误再接
着进入信号处理函数;另一种是先跳转到信号处理函数,等信号处理完毕后,再重新启动系统调
用,这些系统调用往往是低速系统调用,比如读写管道、终端和网络设备,又比如 wait 和 waitpid 等等。
-
显然如果使用 signal 函数,在这些场景下的执行流程是固定的并且无法调整的,而使用函数
sigaction 就可以自定义这些场景下进程的行为。
-
三大性质:
① 一次注册,永久生效
② 传送x信号,临时屏蔽x
③ 自动重启低速系统调用
include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction
*oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
oldact参数表示之前的回调函数,通常会传入空指针,所以我们主要关心act参数。
- sa_handler成员或者sa_sigaction成员(一般使用sa_sigaction)是用来描述信号处理的回调函 数。
- 如果回调函数不是SIG_IGN或者SIG_DFL时,sa_mask成员描述了一个信号集,调用回调函数以
前,该信号集要加入进程的信号屏蔽字中,回调函数返回的时候再恢复原来的信号屏蔽字,除此以 外,正在处理的信号默认是被阻塞的。(额外临时屏蔽)
- sa_flags参数表示信号处理方式掩码,可以用来设置信号的处理模式。(属性)
- sa_restorer成员暂时无用。
2. sa_flags的取值集合
//SA_SIGINFO
//SA_RESETHAND
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
//printf("%d is coming", signum);
printf("%d is coming\n", signum);//必须添加\n
}
int main()
{
struct sigaction act;
memset(&act,0,sizeof(act));
//act.sa_flags = SA_SIGINFO;
act.sa_flags = SA_SIGINFO|SA_RESETHAND;
act.sa_sigaction = sigfunc;
int ret = sigaction(SIGINT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
while(1);
return 0;
}
- 低速系统调用的中断处理流程,首先是使用signal的处理流程,在这种情况系统调用会自动重启。
//使用ps命令查看进程状态时,进程阻塞在wait_w
#include <func.h>
void sigfunc(int signum){
printf("signum = %d is coming\n",signum);
}
int main()
{
signal(SIGINT,sigfunc);
char buf[128] = {0};
read(STDIN_FILENO,buf,sizeof(buf));
puts(buf);
return 0;
}
- 如果使用 sigaction 的处理流程,那么系统调用会出错并终止。
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);//必须添加\n
}
int main()
{
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = sigfunc;
int ret = sigaction(SIGINT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
char buf[128] = {0};
ret = read(STDIN_FILENO,buf,sizeof(buf));
ERROR_CHECK(ret,-1,"read");
return 0;
}//read: Interrupted system call
- 增加SA_RESTART可以自动重启低速系统调用。
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
printf("%d is coming\n", signum);//必须添加\n
}
int main()
{
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_flags = SA_SIGINFO|SA_RESTART;
act.sa_sigaction = sigfunc;
int ret = sigaction(SIGINT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
char buf[128] = {0};
ret = read(STDIN_FILENO,buf,sizeof(buf));
ERROR_CHECK(ret,-1,"read");
return 0;
}
3. sa_mask设置阻塞集合
1. 简介
在信号处理流程中,如果递送了新的不同类型信号,在没有指定SA_NODEFER参数的情况下,新信号将
会中断正在执行的信号处理流程。为了避免这种中断行为,可以使用sa_mask参数来增加一些信号的阻
塞操作。
typedef struct
{
unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
} __sigset_t;
typedef __sigset_t sigset_t;
//
sigset_t的本质就是一个位图,共有1024位。
2. sa_mask 临时屏蔽
- 使用sa_mask参数来增加一些信号的阻塞操作,临时屏蔽值发生在递送过程中。
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
printf("before %d is coming\n", signum);
sleep(3);
printf("after %d is coming\n", signum);
}
int main()
{
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = sigfunc;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
int ret = sigaction(SIGINT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
ret = sigaction(SIGQUIT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
while(1);
return 0;
}
- 需要特别注意的是阻塞屏蔽和忽略信号有着既然不同的含义,阻塞表示信号产生了但是还未递送,内核会维护一个所有未决信号的位图,如果信号已经被阻塞,再次产生信号就会被忽略了。被阻塞的信号将会后续执行,而被忽略的信号就被丢弃了。
3. 系统调用sigpending获取未决信号
未决信号:已经产生未传递的信号。
- 使用系统调用 sigpending 可以获取当前所有未决信号(已经产生没有递送的信号)的集合。
- 通常这个系统调用是在回调函数当中使用的,用于检查当前是否阻塞了某个信号。
#include <func.h>
void sigfunc(int signum, siginfo_t *p, void *p1){
printf("before %d is coming\n", signum);
sleep(3);
sigset_t pendingSet;
sigpending(&pendingSet);
if(sigismember(&pendingSet,SIGQUIT)){
printf("SIGQUIT is pending!\n");
}
else{
printf("SIGQUIT is not pending!\n");
}
printf("after %d is coming\n", signum);
}
int main()
{
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = sigfunc;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
int ret = sigaction(SIGINT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
ret = sigaction(SIGQUIT,&act,NULL);
ERROR_CHECK(ret,-1,"sigaction");
while(1);
return 0;
}
3. 系统调用sigprocmask实现全程阻塞
永久屏蔽
三、其他一些系统调用+
4. kill 发送信号
5. raise给自己发送信号
6. alarm 信号
间隔定时器:
7. pause函数
等待一个信号的产生
8. 时钟
tick 嘀嗒
真实时钟 墙上时间
虚拟时钟 进程在用户态cpu下才统计的时间
实用 用户态+内核态
间隔
9. 时钟处理
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。