一.信号的概念
1.1 信号的机制
信号是软件层面上实现的中断,早期常被称为“软中断”
由于信号是通过软件方法实现的,其实现手段导致信号有很强的延时性,但是对用户而言,这个延时时间非常短,不易察觉。
每个进程收到的所有信号都是由内核复杂发送和处理
1.2 信号的状态
1.递达:递送并且到达进程
2.未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
1.3 信号的处理方式
1.执行默认动作
2.忽略(丢弃)
3.捕捉(调用用户处理函数)
PS:Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
1.4 阻塞信号集(信号屏蔽字):将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)
1.5 未决信号集
1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回0。这一时刻往往非常短暂。
2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称为未决信号集。在屏蔽解除前,信号一直处于未决状态。
1.6 信号的4要素
1.编号
2.名称
3.事件
4.默认处理动作:
Term:终止进程
lgn: 忽略信号(默认即时对该种信息忽略操作)
Core:终止进程,生成Core文件。(查验进程死亡原因,用于gdb调试)
Stop: 停止(暂停)进程
Cont:继续运行进程
PS: 查看信号的相关信息可以通过 man 7 signal命令来查阅man page文档
二.产生信号的五种方式:
2.1 终端按键产生信号
Ctrl+c ------ 2) SIGINT(终止/中断) “INT” == interrupt
Ctrl+z ------ 20) SIGTSTP(暂停/停止) “T” == terminal 终端
Ctrl+\-------3) SIGQUIT(退出)
2.2 系统调用产生
1.kill:给指定进程发送指定信号(不一定杀死)
int kill(pid_t pid, int sig); 成功:0;失败:-1(ID非法,信号非法,普通用户杀init进程等权级问题),设置errno
sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
pid > 0:发送信号给指定进程。
pid == 0:发送信号给与调用kill函数进程属于同一进程组的所有进程。
pid < 0:取|pid|发给对应的进程组。
pid == -1:发送给进程有权限发送的linux系统中所有进程
PS
进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。
权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。kill -9 (root 用户的pid)是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。只能向自己创建的进程发送信号。普通用户基本规则是:发送实际或有效用户ID==接收者实际或有效用户ID
2.raise:给当前进程发送指定信号(自己给自己发)
raise(signo) == kill(getpid(), signo);
int raise(int sig); 成功:0,失败:非0值
3.abort:给自己发送异常终止信号 -------6)SIGABRT信号,终止并产生core文件
void abort(void); 改函数无返回
2.3 软件条件产生
1.alarm函数:设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。
PS:每个进程有且只有唯一一个定时器,定时与进程状态无关(自然定时法),就绪,运行,挂起(阻塞,暂停)、终止、僵尸…无论进程处于何种状态,alarm都计时。
unsigned int alarm(unsigned int seconds); 返回0或剩余的秒数,无失败。
常用:取消定时器alarm(0)或返回久闹钟余下秒数。
2.setitimer函数:设置定时器(闹钟)。可代替alarm函数。精确微秒us,可以实现周期定时。
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
参数:which:指定定时方式
1.自然定时:ITIMER_REAL ----14)SIGLARM ----计算自然时间
2.虚拟空间计时(用户空间):ITIMER_VIRTUAL-----26)SIGVTALRM ---只计算进程占用CPU的时间
3.运行时计时(用户+内核):ITIMER_PROF---27)SIGPROF ---计算占用CPU以及执行系统调用的时间
struct itimerval里面的参数
it_interval:用来设定两次定时任务之间间隔的时间
it_value:定时的时长
DEMO利用setitimer周期打印信息
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
typedef void(*sighandler_t) (int);
using namespace std;
void do_something(int signalno){
cout << "catch a signal" << endl;
cout << "signal number: " << signalno << endl;
}
int main(){
sighandler_t handler = signal(SIGALRM, do_something);
if(handler == SIG_ERR){
perror("signal error");
exit(1);
}
struct itimerval it;
it.it_interval.tv_sec = 2;
it.it_interval.tv_usec = 0;
it.it_value.tv_sec = 2;
it.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &it, NULL);
int second = -1;
while(true){
cout << ++second % 2 + 1 << " second" << endl;
sleep(1);
}
return 0;
}
2.4 硬件异常产生信号
非法访问内存(段错误)--------11) SIGSEGV(段错误)
除0错误---------8) SIGFPE(浮点数除外)”F” == float
内存对齐出错(总线错误)--------7)SIGBUS
2.5 命令产生
kill, killall
三.信号集操作函数
内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。
3.1 信号集设定
sigset_t set; //typedef unsigned long sigset_t
int sigemptyset(sigset_t *set); //将某个信号集清0 成功:0; 失败:-1
int sigfillset(sigset_t *set);//将某个信号集置1成功:0; 失败:-1
int sigaddset(sigset_t *set, int signum);//将某个信号加入信号集 成功:0; 失败:-1
int sigdelset(sigset_t *set, int signum);//将某个信号清出信号集 成功:0; 失败:-1
int sigismember(const sigset_t *set, int signum);//判断某个信号是否在信号集中 返回值:在集合:1; 不在:0;出错:-1
PS:sigset_t 类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
3.2 sigprocmask函数
用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB中)
严格注意:屏蔽信号只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);成功:0,失败:-1,设置errno
参数:
set:传入参数,是一个位图,set中哪个位置置1,就表示当前进程屏蔽哪个信号。
oldset:传出参数,保存旧的信号屏蔽集。
how参数取值:假设当前的信号屏蔽字为mask
1.SIG_BLOCK:当how设置为此值,set表示需要屏蔽的信号。相当于mask = mask | ~set
2.SIG_UNBLOCK:当how 设置为此,set表示需要解除屏蔽的信号。相当于mask = mask & ~set
3.SIG_SETMASK:当how 设置为此,set表示用于替代原始屏蔽集的新屏蔽集。相当于mask = set。
PS:调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
3.3 sigpending函数
读取当前进程的未决信号集
int sigpending(sigset_t *set);
set 为传出参数。
返回值:成功:0;失败:-1,设置errno
四.信号捕捉
4.1 signal函数
使用signal注册一个信号捕捉函数:
typedef void(*sighandler_t) (int);
sighandler_t signal(int signal_no, sighandler_t handler);
该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数
4.2 sigaction函数
修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数:
signum:信号编号
act:传入参数,新的处理方式
oldact:传出参数,旧的处理方式。
struct sigaction结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *); //较少使用
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void); // 过时,弃用
};
其中
1.sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可以赋值为SIG_IGN表忽略或SIG_DFL表执行默认动作。
2.sa_mask:调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性的设置。
3.sa_flags:0,表使用默认属性(回调函数执行期间自动屏蔽本信号)
SA_SIGINFO:选用sa_sigaction来指定捕捉函数
SA_INTERRURT:系统调用被信号中断后,不重启
SA_RESTART:自动重启
SA_DEFER:不自动屏蔽本信号
信号捕捉特性
1.进程正常运行时,默认PCB中有一个信号屏蔽字,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由这个默认的信号屏蔽字来指定,而是用sa_mask来指定。调用完信号处理函数,再恢复使用默认信号屏蔽字。
2.XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。
3.阻塞的常规信号不支持排队,产生多次只记录一次(后32个实时信号支持排队)
4.3 Linux内核实现信号捕捉过程
DEMO
捕捉Ctrl + c信号和周期性的SIGALRM信号
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <string>
using namespace std;
void do_something(int signalno){
cout << "catch a signal" << endl;
cout << "signal number: " << signalno << endl;
cout << "signal name: ";
cout << (signalno == SIGINT ? "SIGINT" : "SIGALRM");
cout << endl;
int cal = 0;
while(cal++ < 2){
cout << "Dealing with the signal of ";
cout << (signalno == SIGINT ? "SIGINT" : "SIGALRM");
cout << endl;
sleep(1);
}
}
int main(){
sigset_t set, time_set;
sigemptyset(&set);
sigemptyset(&time_set);
//将SIGALRM加入屏蔽集,在Ctrl + c信号处理期间将忽略SIGALRM信号
sigaddset(&set, SIGALRM);
//将SIGINT加入屏蔽集, 在SIGALRM信号处理期间将忽略终端Ctrl+c信号
sigaddset(&time_set, SIGINT);
struct sigaction act, time_act;
act.sa_handler = do_something;
act.sa_mask = set;
act.sa_flags = 0;
time_act.sa_handler = do_something;
time_act.sa_mask = time_set;
time_act.sa_flags = 0;
//注册Ctrl + c信号捕捉函数
sigaction(SIGINT, &act, NULL);
//注册SIGALRM信号捕捉函数
sigaction(SIGALRM, &time_act, NULL);
struct itimerval it;
it.it_interval.tv_sec = 2;
it.it_interval.tv_usec = 0;
it.it_value.tv_sec = 2;
it.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &it, NULL);
int second = -1;
while(true);
return 0;
}
Comments