linux系统编程——进程信号编程

信号概述:
信号就是软件中断,很多比较重要的应用程序都需要处理信号。信号为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,信号处理
信号的处理有三种方法,分别是忽略,捕捉,和默认动作;
~忽略信号 大多数信号都可以使用这个方式来处理信号,但是有两种信号无法被忽略;SIGKILLSIGSTOP。以为他们向内核和root用户提供了进程终止和停止的可靠方法。
~捕捉信号 需要告诉内核,用户希望如何处理某种信号,说白了就是写一个信号处理函数,然后这个函数告诉内核。当该信号产生是,应该如何处理
~系统默认动作 对于系统来说,系统都对应由默认的处理动作,当发生了该信号,系统自动执行。
信号使用方法:
可以通过在键盘输入比如Ctrl c等来发送某一个信号
也可以在终端用kill -信号编号 进程PID例如杀死进程kill -9 pid杀死pid进程
3,信号处理函数的注册:
<1>signalkill函数的使用
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函数只能选择一个

从上面可以看出函数sigactionsignum为信号编号,结构体act是对信号的配置(接不接受数据等),第三个oldact是对信号之前的配置进行备份。
在这里额外说一下struct sigaction结构体中的 sa_mask 成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。这样的目的是,在调用信号处理函数时,就可以阻塞默写信号了。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。
sigaction 的时效性:当对某一个信号设置了指定的动作的时候,那么,直到再次显式调用 sigaction并改变动作之前都会一直有效。
关于结构体中的 flag 属性的详细配置,在此不做详细的说明了,只说明其中一点。如果设置为 SA_SIGINFO 属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction 这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。在此,还要特别说明一下,sa_sigactionsa_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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值