Linux&C ——信号以及信号处理

狂风中的少年 提交于 2020-01-01 00:25:51
  • linux信号的简单介绍
  • 信号的捕捉和处理
  • 信号处理函数的返回
  • 信号的发送
  • 信号的屏蔽

一:linux信号的简单介绍。
信号提供给我们一种异步处理事件的方法,由于进程之间彼此的地址空间是独立的,所以进程之间的通信就需要特殊的机制,而信号是进程之间唯一的异步通信方式。我们平时可以接触到的信号来源一般有:用户从键盘键入、一些硬件的异常、用户使用kill命令或者函数发送或者当系统检测到某种软件已经具有发出信号的条件(比如alarm或settimer设置的定时器超时时将生成SIGALRAM信号),linux下的信号种类有60多种,我们可以输入下面的命令查看:

$ kill -l

这里写图片描述

其中1~31为不可靠信号(可能会丢失,信号不支持排队),33~64为可靠信号,也叫做实时信号。在linux中,当导致信号的事件发生时,内核就会产生一个信号,信号产生后,内核通常会在进程表中设置某种形式的标志,即内核向一个进程递送了一个信号,信号产生和递送之间的时间间隔叫做“信号未决”。我们用户在产生信号之后可以要求进程对信号做出以下处理,例如捕捉信号,处理信号,忽略信号等。
二:信号的捕捉和处理。
1:signal()函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

这个定义等价于:

void(*signal(int signum,void(*)(int)))(int)

signal函数的第一个参数是即将要处理的信号的编号,第二个参数是一个指向函数的指针,此函数为我们的信号处理函数,它是一个返回值为void,参数为int的函数。

2:sigaction()函数
int sigaction(int signum(指定的信号), const struct sigaction *act(若非空,则为指定信号设置新的处理函数), struct sigaction *oldact(若非空,则存储旧的信号处理函数));

可以说signal函数是sigaction函数的简化版,因为struct sigaction act这个结构体中除了信号处理函数之外,我们还可以指定一些其他的信息,比如一般情况下我们在一个信号处理函数没有返回的时候是默认阻塞此信号的,但是我们可以通过设置act中的sa_flags = SA_NOMASK来设置信号的状态为在信号处理函数结束之前允许再次递送。

3:pause()函数
int pause(void);

pause函数的作用是将当前的进程挂起,等待任意一个信号中断。下面这个程序先调用pause然后从右边的shell发送信号,一旦收到信号,就会结束进程,下面的代码不会执行。

这里写图片描述

三:信号处理函数的返回
信号处理函数可以自己正常返回,也可以调用下面四个函数返回
1:setjmp和longjmp函数

int setjmp(jmp_buf env);
env:一个全局变量,通过调用setjmp将目前的栈状态信息保存,当调用longjmp时能用来恢复栈状态的信息
int longjmp(jmp_buf env, int val);
val:此参数是setjmp的返回值。

两个函数配合使用,但是有缺点,因为我们在信号处理函数中会自动阻塞正在被处理的信号,如果我们调用longjmp函数返回,是不会解除阻塞的,即被处理过的信号会一直阻塞着,需要我们手动去处理,为了避免这种手动处理,我们会用到下面这两个函数:

int sigsetjmp(sigjmp_buf env, int savesigs);
savesigs:只要它的值非0,则sigsetjmp会在env中保存当前信号的屏蔽字,在调用siglongjmp时,会从其中恢复保存的信号屏蔽字。
void siglongjmp(sigjmp_buf env, int val);

四:信号的发送
1:kill函数:

int kill(pid_t pid, int sig);
pid:>0 : pid表示某一个进程的pid, =0 :给当前进程所属组中的所有进程发送, =-1 :把信号广播给系统中除了自己和init进程 <-1 :把信号发送给属于进程组-pid的所有进程。
注意:只有root用户才可以给所有的进程发送信号,普通用户只能同一组或者给自己发送信号。

2:raise()函数

int raise(int sig);
sig:表示要发送的信号,给调用raise的进程发送。

3:sigqueue()函数

int sigqueue(pid_t pid,int sig,const union sigval value);
union sigval //此共用体的第一个元素与siginfo_t中si_int 一样
{
int sival_int;
void *sival_ptr;
};
sigqueue函数是alarm函数的加强版本,它的第三个参数可以和sigaction配合使用在两个进程之间传输数据,即我们在信号发送的时候让它的第三个参数value.sival_int携带数值,而后我们在sigaction中将sa_flags = SA_SIGINFO,则信号处理函数由三参数的sa_sigaction指定,此时siginfo_t 结构体中的成员si_int 就可以接受发送的数据。
4:alarm()函数
unsigned int alarm(unsigned int seconds);
alarm函数设置定时器,时间过了就会给调用进程发送信号SIGALRM信号,只会发送一次,如果需要多次,则需要多次调用。
下面程序简单介绍了ping命令的实现:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>

//此程序的目的在于模拟ping的工作原理,

void handler_sigalarm(int signo)
{
    printf("send a request packet\n");
    alarm(1);        //每隔一秒就会给进程发送一个SIGALRM信号
}

int main(int argc,char *argv[])
{
    signal(SIGALRM,handler_sigalarm); //signal 函数在此处理SIGALRM,

    raise(SIGALRM);  //首先由raise函数给进程发送一个SIGALRM信号,signal函数捕获到发送的信号,进入信号处理函数,再次由alarm函数每隔一秒发送一个SIGALRM信号
    while(1);

    return 0;
}

这里写图片描述

五:信号的屏蔽
1:信号集:linux下信号有60多种,POSIX标准定义了数据类型sigset_t来表示信号集,并且定义了许多函数来操作信号集。

  int sigemptyset(sigset_t *set);  //初始化信号集,使其为空
  int sigfillset(sigset_t *set);       //初始化信号集,使其包括所有信号
  int sigaddset(sigset_t *set, int signum); //添加一个信号到信号集
  int sigdelset(sigset_t *set, int signum);  //删除一个信号
  int sigismember(const sigset_t *set, int signum); //判断一个信号是否在信号集中

2:信号屏蔽:也可以说信号阻塞
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); //信号屏蔽
how:设置相关信号屏蔽码的操作。
set :非空的话为信号设置新的屏蔽码
oldset:非空的话将原来的信号屏蔽码返回

int sigpending(sigset_t *set); //获取当前未决信号集,注意未决队列里面的信号如果没有被处理完毕,那么首先会执行信号处理函数

int sigsuspend(const sigset_t *mask); //将信号屏蔽码设置为mask,然后等待信号的发生并且执行信号处理函数,sigpending函数可以保证改变进程的屏蔽码和将进程挂起是原子操作。

版权声明:本文为博主原创文章,未经博主允许不得转载。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!