详解sigaction「建议收藏」

详解sigaction「建议收藏」一、内核如何实现信号的捕捉如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:1.用户程序注册了SIGQUIT信号的处理函数sighandler。2.当前正在执行main函数,这时发生中断或异常切换到内核态。3.在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQU

大家好,又见面了,我是你们的朋友全栈君。

一、内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:
1. 用户程序注册了SIGQUIT信号的处理函数sighandler。
2. 当前正在执行main函数,这时发生中断或异常切换到内核态。
3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
5. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

详解sigaction「建议收藏」

上图出自ULK。

二、sigaction函数

#include <signal.h>

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向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);
          };

将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

需要注意的是sa_restorer 参数已经废弃不用,sa_handler主要用于不可靠信号(实时信号当然也可以,只是不能带信息),sa_sigaction用于实时信号可以带信息(siginfo_t),两者不能同时出现。sa_flags有几个选项,比较重要的有两个:SA_NODEFER 和 SA_SIGINFO,当SA_NODEFER设置时在信号处理函数执行期间不会屏蔽当前信号;当SA_SIGINFO设置时与sa_sigaction 搭配出现,sa_sigaction函数的第一个参数与sa_handler一样表示当前信号的编号,第二个参数是一个siginfo_t 结构体,第三个参数一般不用。当使用sa_handler时sa_flags设置为0即可。

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 kernel 2.6.32) */
          }

需要注意的是并不是所有成员都在所有信号中存在定义,有些成员是共用体,读取的时候需要读取对某个信号来说恰当的有定义的部分。

下面用sigaction函数举个小例子:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*************************************************************************
    
> File Name: process_.c
    
> Author: Simba
    
> Mail: dameng34@163.com
    
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
     
#define ERR_EXIT(m)
    
do
{
        
perror
(m);
        
exit
(EXIT_FAILURE);
    
}
while
(0)
     
void
handler(
int
sig);
     
     
int
main(
int
argc,
char
*argv[])
{
    
struct
sigaction act;
    
act.sa_handler = handler;
    
sigemptyset(&act.sa_mask);
    
act.sa_flags = 0;
     
    
if
(sigaction(SIGINT, &act, NULL) < 0)
        
ERR_EXIT(
"sigaction error"
);
     
    
for
(; ;)
        
pause();
     
    
return
0;
     
}
     
void
handler(
int
sig)
{
    
printf
(
"rev sig=%dn"
, sig);
}

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sigaction 
^Crev sig=2
^Crev sig=2
^Crev sig=2

………………………

即按下ctrl+c 会一直产生信号而被处理打印recv语句。

其实我们在前面文章说过的signal 函数是调用sigaction 实现的,而sigaction函数底层是调用 do_sigaction() 函数实现的。可以自己实现一个my_signal 函数,如下:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*************************************************************************
    
> File Name: process_.c
    
> Author: Simba
    
> Mail: dameng34@163.com
    
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
     
#define ERR_EXIT(m)
    
do
{
        
perror
(m);
        
exit
(EXIT_FAILURE);
    
}
while
(0)
     
void
handler(
int
sig);
/* 系统调用signal()实际上调用了sigaction() */
__sighandler_t my_signal(
int
sig, __sighandler_t handler);
     
int
main(
int
argc,
char
*argv[])
{
    
my_signal(SIGINT, handler);
     
    
for
(; ;)
        
pause();
     
    
return
0;
     
}
     
__sighandler_t my_signal(
int
sig, __sighandler_t handler)
{
    
struct
sigaction act;
    
struct
sigaction oldact;
    
act.sa_handler = handler;
    
sigemptyset(&act.sa_mask);
    
act.sa_flags = 0;
     
    
if
(sigaction(sig, &act, &oldact) < 0)
        
return
SIG_ERR;
     
    
return
oldact.sa_handler;
// 返回先前的处理函数指针
}
     
void
handler(
int
sig)
{
    
printf
(
"rev sig=%dn"
, sig);
}

输出测试的一样的,需要注意的是 signal函数成功返回先前的handler,失败返回SIG_ERR。而sigaction 是通过oact 参数返回先前的handler,成功返回0,失败返回-1。

下面再举个小例子说明sa_mask 的作用:

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*************************************************************************
    
> File Name: process_.c
    
> Author: Simba
    
> Mail: dameng34@163.com
    
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 
************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
     
#define ERR_EXIT(m)
    
do
{
        
perror
(m);
        
exit
(EXIT_FAILURE);
    
}
while
(0)
     
void
handler(
int
sig);
     
     
int
main(
int
argc,
char
*argv[])
{
    
struct
sigaction act;
    
act.sa_handler = handler;
    
sigemptyset(&act.sa_mask);
    
sigaddset(&act.sa_mask, SIGQUIT);
// 在信号处理函数执行期间屏蔽SIGQUIT信号,完毕后会抵达
    
/* 注意sigprocmask中屏蔽的信号是一直不能抵达的,除非解除了阻塞*/
    
act.sa_flags = 0;
     
    
if
(sigaction(SIGINT, &act, NULL) < 0)
        
ERR_EXIT(
"sigaction error"
);
     
    
for
(; ;)
        
pause();
     
    
return
0;
     
}
     
void
handler(
int
sig)
{
    
printf
(
"rev sig=%dn"
, sig);
    
sleep(5);
}

先按下ctrl+c ,然后马上ctrl+,程序是不会马上终止的,即等到handler处理完毕SIGQUIT信号才会抵达。

simba@ubuntu:~/Documents/code/linux_programming/APUE/signal$ ./sa_mask 
^Crev sig=2
^
5s过后接着才输出Quit (core dumped),即在信号处理函数执行期间sa_mask集合中的信号被阻塞直到运行完毕。

sa_flags 和 sa_sigaction 参数的示例看这里

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/141485.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Vim 3 vimrc[通俗易懂]

    Vim 3 vimrc[通俗易懂]文章目录什么是vimrc基本修改UI相关配置编码相关配置文件相关配置编辑器相关配置按键映射“键我的vimrc小结什么是vimrcvimrc是Vim的配置文件,Vim在启动时会加载vimrc文件,你能想到的几乎所有的配置(包括主题,快捷键,插件设置等等),都可以配置在vimrc中,所以,vimrc在Vim使用过程中有着至关重要的地位.Vim是极…

  • java实现10种排序算法[通俗易懂]

    java实现10种排序算法[通俗易懂]1.冒泡排序(BubbleSort)importjava.util.Arrays;//冒泡排序publicclassBubbleSort_01{ publicstaticvoidmain(String[]args){ inta[]={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48}; //记录比较次数 intcount=0; //i=0,第一轮比较 for(inti=0;i<a.length-1;i

  • SpringBoot集成redis「建议收藏」

    SpringBoot集成redis「建议收藏」今天,日月在这里教大家如何使用springBoot集成redis,说实话比较简单,网上也有大把的教程。先套用一下网上的简介。定义REmoteDIctionaryServer(Redis)是一个由SalvatoreSanfilippo写的key-value存储系统。Redis是一个开源的使用ANSIC语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value…

  • java权限管理面试_java shiro面试题

    java权限管理面试_java shiro面试题1、简单介绍一下Shiro框架?ApacheShiro是Java的一个安全框架。使用Shiro可以非常容易的开发出足够好的应用。其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成功能:认证、授权、加密、会话管理、与Web集成、缓存等。三个核心组件:Subject,SecurityManager和Realms。●Subject:即“当前操作用户”。但是在Shi…

    2022年10月14日
  • 8、Cocos2dx 3.0三,找一个小游戏开发3.0存储器管理的版本号

    8、Cocos2dx 3.0三,找一个小游戏开发3.0存储器管理的版本号

  • 51单片机汇编教程[通俗易懂]

    51单片机汇编教程[通俗易懂]  很多电子爱好者,都想学习单片机这门技术。下面的这一系列教程是www.51hei.com专门为初学者入门而准备的,从底层硬件入手基于汇编和c两种语言,详细的介绍了单片机的原理,指令,寄存器,以及接口等,后面还为你准备了一些小的设计。都是从单片机最基本的东西讲起,相信你一定能看懂,并且学会单片机这门有意思的技术,有什么问题可在文章后面的评论留言。  第1课:单片机简叙第2课:单片…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号