信号概述:
信号就是软件中断,很多比较重要的应用程序都需要处理信号。信号为linux提供了一种处理异步事件的方法。信号是异步的,一个进程不需要通过进行别的操作来等待信号,比如在终端输入ctrl + c来中断程序,会通过信号机制停止一个程序或者及早终止管道的下一个程序。
1,信号名字和编号
信号一般都为"SIG"开头,信号函数和结构体等定义在头文件signal.h
中,信号编号都为正整数。
信号一般用kill -l
查看信号编号等,如下:
上图1-31的信号是早期linux所支持的信号,是不可靠信号(非实时的),编号34-64信号是后来扩充的是可靠信号(实时信号),不可靠信号和可靠信号最大的区别是在于前者不支持排队(意味着如果内核以及注册了这个信号,那么便不会再去注册,对于进程来说,便不会知道本次信号的发生),可能造成信号丢失,而后者的注册机制是每收到一个可靠信号就会去注册这个信号,不会丢失。
可靠信号和不可靠信号
~不可靠信号:信号可能会丢失,一旦信号丢失了,进程并不能知道信号丢失
~可靠信号:也是阻塞信号,当发送了一个阻塞信号,并且该信号的动作时系统默认动作或捕捉该信号,如果信号从发出以后会一直保持未决的状态,直到该进程对此信号解除了阻塞,或将对此信号的动作更改为忽略。
对于信号来说,信号编号小于等于31的信号都是不可靠信号,之后的信号为可卡信号,系统会根据有信号队列,将信号在递达之前进行阻塞。
信号的阻塞和未决是通过信号的状态字来管理的,该状态字是按位来管理信号的状态。每个信号都有独立的阻塞字,规定了当前要阻塞地达到该进程的信号集。
信号阻塞状态字(block),1代表阻塞、0代表不阻塞;信号未决状态字(pending)的1代表未决,0代表信号可以抵达了;它们都是每一个bit代表一个信号
~阻塞和未决是如何工作的?
比如向进程发送SIGINT信号,内核首先会判断该进程的信号阻塞状态字是否阻塞状态,如果该信号被设置为阻塞的状态,也就是阻塞状态字对应位为1,那么信号未决状态字(pending)相应位会被内核设置为1;如果该信号阻塞解除了,也就是阻塞状态字设置为了0,那么信号未决状态字(pending)相应位会被内核设置为0,表示信号此时可以抵达了,也就是可以接收该信号了。
注:阻塞状态字用户可以读写,未决状态字用户只能读,是由内核来设置表示信号递达状态的。
2 SIGINT
默认处理动作是终止进程
3 SIGQUIT
的默认处理动作是终止进程并且Core Dump.
Core Dump(核心转储):当一个进程要异常终止时,可以选择吧进程的用户空间内存数据全部保存到磁盘中,文件名通常是core,这就是Core Dump。(进程异常终止是因为有bug,比如非法访问内存段错误)。
2,信号处理
信号的处理有三种方法,分别是忽略,捕捉,和默认动作;
~忽略信号 大多数信号都可以使用这个方式来处理信号,但是有两种信号无法被忽略;SIGKILL
和SIGSTOP
。以为他们向内核和root用户提供了进程终止和停止的可靠方法。
~捕捉信号 需要告诉内核,用户希望如何处理某种信号,说白了就是写一个信号处理函数,然后这个函数告诉内核。当该信号产生是,应该如何处理
~系统默认动作 对于系统来说,系统都对应由默认的处理动作,当发生了该信号,系统自动执行。
信号使用方法:
可以通过在键盘输入比如Ctrl c等来发送某一个信号
也可以在终端用kill -信号编号 进程PID
例如杀死进程kill -9 pid
杀死pid进程
3,信号处理函数的注册:
<1>signal
和kill
函数的使用
signal
(信号处理函数注册)
~原型:
#include <signal.h>
typedef void (*sighandler_t)(int);//信号处理函数
sighandler_t signal(int signum, sighandler_t handler);//参数1为信号编号,参数二为信号处理函数
~代码演示:
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void handler(int signum)//捕捉到信号后执行的函数
{
printf("get signum = %d\n",signum);
switch(signum){
case 2:printf("SIGINT\n");break;
case 9:printf("SIGKILL\n");break;
case 10:printf("SIGUSR1\n");break;
}
}
int main()
{
printf("pid = %d\n",getpid());//得到该进程的pid号便于发信号
signal(SIGINT,SIG_DFL);//对信号SIGINT进行捕捉,并且默认动作
signal(SIGKILL,handler);//对信号SIGKILL进行捕捉,注:SIGKILL信号无法忽略或者重新注册
signal(SIGUSR1,handler);//对信号SIGUSR1进行捕捉。
while(1);
return 0;
}
注:信号处理处理捕捉到执行函数处理还有两个宏来忽略和默认动作:
SIG_IGN(忽略)
SIG_DFL(默认动作)
kill
函数(信号发送函数)原型:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);//参数1为进程pid,第二个参数为信号编号
代码示例如下:
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc,char*argv[])//使用main函数传参,第一个参数为信号编号,第二个为进程pid
{
int signum;
int pid;
signum = atoi(argv[1]);//将main函数参数从ASCII转换成int类型,并赋值给signum(进程编号)
pid = atoi(argv[2]);//将main函数参数从ASCII转换成int类型,并赋值给pid(进程pid)
kill(pid,signum);
printf("send successful!\n");
return 0;
}
也可以使用system
进行信号发送(类似于kill -9 pid):
注:利用sprintf
函数将想要转换的整型存在字符串数组中
代码如下:
#include <stdio.h>
#include <stdlib.h>
int main(int argc ,char* argv[])
{
char str[128];
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
sprintf(str,"kill -%d %d",signum,pid);
system(str);
return 0;
}
<2>sigaction
函数和sigqueue
函数
sigaction
函数(信号处理函数注册):
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);//信号处理函数,不接受额外数据,SIG_IGN为忽略,SIG_DFL为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *);//信号处理函数,能够接受额外数据和sigqueue函数配合使用
sigset_t sa_mask;//阻塞关键字得信号集,可以在调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值
int sa_flags;//影响信号的行为,SA_SIGINFO表示可以接受数据、
}
//回调函数sa_handler和函数sa_sigaction函数只能选择一个
从上面可以看出函数sigaction
的signum
为信号编号,结构体act
是对信号的配置(接不接受数据等),第三个oldact
是对信号之前的配置进行备份。
在这里额外说一下struct sigaction
结构体中的 sa_mask
成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。这样的目的是,在调用信号处理函数时,就可以阻塞默写信号了。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。
sigaction 的时效性:当对某一个信号设置了指定的动作的时候,那么,直到再次显式调用 sigaction并改变动作之前都会一直有效。
关于结构体中的 flag 属性的详细配置,在此不做详细的说明了,只说明其中一点。如果设置为 SA_SIGINFO
属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction
这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。在此,还要特别说明一下,sa_sigaction
和 sa_handler
使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。
关于void (*sa_sigaction)(int, siginfo_t *, void *);
处理函数来说还需要有一些说明。void* 是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。
结构体详细内容如下:
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count;
POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_lower; /* Lower bound when address violation
occurred (since Linux 3.19) */
void *si_upper; /* Upper bound when address violation
occurred (since Linux 3.19) */
int si_pkey; /* Protection key on PTE that caused
fault (since Linux 4.6) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
}
关于发送的数据是存放在两个地方的,sigval_t si_value
这个成员中有保存了发送过来的数据:
union sigval_t {
int sival_int;
void *sival_ptr;
};
可以对其进行输出等操作;
代码演示(发送信号注册)
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void handler(int signum, siginfo_t *info, void *context)
{
printf("get signum = %d\n",signum); //打印出信号编号
if(context != NULL){//判断内容为非空时执行以下操作
printf("get data = %d\n",info->si_int);//输出整数数据
printf("get data = %d\n",info->si_value.sival_int);//输出信号所携带的整数数据
}
}
int main()
{
struct sigaction act;//定义结构体
printf("pid = %d\n",getpid());
act.sa_sigaction = handler;//定义信号处理函数
act.sa_flags = SA_SIGINFO;//配置信号为接受数据
sigaction(SIGUSR1,&act,NULL);//注册信号为SIGUSR1,结构体为act,不需要备份配置
while(1);
return 0;
}
注:执行此函数时只需./直接执行,会一直阻塞,收到一个SIGUSR1信号时候输出数据为10,和下面的函数配套使用
sigqueue函数(信号发送函数):
函数原型:
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);//信号发送函数第一个参数为进程pid,第二个为信号编号,第三个是数据。
union sigval {//要发送的数据类型
int sival_int;
void *sival_ptr;
};
代码简单实现:
#include <stdio.h>
#include <signal.h>
int main(int argc ,char *argv[])//将需要的进程pid和信号编号通过main函数传参进来
{
int signum;
int pid;
union sigval value;
value.sival_int = 10;
signum = atoi(argv[1]); //将信号编号有ASCII转换为整型数赋值给signum
pid = atoi(argv[2]);//将信号编号有ASCII转换为整型数赋值给pid
sigqueue(pid,signum,value);
return 0;
}
注:执行此代码时需要加上信号编号和pid如./a.out 9 pid
上个代码的进程pid已经打印出来无需输入命令查看,也可以在终端输入:ps -aux|grep a.out(sigaction代码可执行文件名)
来查看进程pid