进程信号

信号的生命周期

信号的产生 –> 信号的注册 –>(信号的屏蔽/阻塞)–> 信号的注销 –> 信号的处理

下面将以信号的生命周期顺序来整理一下进程信号的详细内容:


信号的产生

进程信号的产生方式:

  • 硬件中断
当用户按下某些按键时,想进程发送的信号
ctrl + c  /  ctrl + | 等
  • 硬件异常
访问非法地址空间,cpu运算出错等
  • kill 命令
kill -n pid
  • 软件条件产生
kill函数
raise函数
alarm函数
sigqueue函数 后文会一一讲到

查看所有的信号
kill -l 命令

[liu@localhost ~]$ kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
 6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX    
[liu@localhost ~]$ 

Core Dump (核心转储)
在运行一个有Bug的程序中,会出先一种信息叫做段错误,通常是硬件访问异常所导致的,而Core Dump的存在就是程序出现这种错误的时候可以方便的查询原因

但是系统默认的core dump时关闭的,即大小为0,查看命令:ulimit -c

[liu@bogon 08]$ ulimit -c
0

设置core dump的大小

ulimit -c +大小
[liu@bogon 08]$ ulimit -c 128
[liu@bogon 08]$ ulimit -c
128

调用系统函数向进程发送信号

  • kill 接口函数
int kill(pid_t pid, int sig); 
The kill() system call can be used to send any signal to any process group or process.
参数分析:
    pid:要发送的进程的进程pid
    sig:要发送的信号

kill 函数可以向一个进程组或者进程发送任意信号

例子:

[[email protected] 08]$ cat kill.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>


int main(int argc, char* argv[])
{

    printf("start\n");
    if (!kill( getpid(), SIGABRT))
    {
        ;
    }
    return 0;
}

这个进程给自己本身发送段错误的信号
来看看运行结果

[liu@bogon 08]$ cc -o kill kill.c 
[liu@bogon 08]$ ./kill 
start
Aborted (core dumped)

这样就实现了在代码中对一个进程发送信号。

  • abort接口函数
void abort(void);
The  abort() first unblocks the SIGABRT signal, and then raises that signal for the calling process. 

abort只要调用一定成功使进程异常终止

[[email protected] 08]$ cat abort.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>


int main(int argc, char* argv[])
{

    printf("start\n");

    abort();

    return 0;
}
[liu@bogon 08]$ cc -o abort abort.c 
[liu@bogon 08]$ ./abort 
start
Aborted (core dumped)
  • alarm 接口函数
alarm其实就是一个定时器,到了时间会向进程发送SIGALARM信号

       alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds. 

例子:

[[email protected] 08]$ cat alarm.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int main()
{
    alarm(5);
    while(1)
    {

    }
    return 0;
}

运行结果

[liu@bogon 08]$ cc -o alarm alarm.c 
[liu@bogon 08]$ ./alarm 
Alarm clock

信号的注册

在进程pcb结构中,task_struct中有一个保存着信号是否在进程中存在,会有一个64位位图来保存,这张表叫做pending表,标记的是信号从产生到递达之间的状态。

这里写图片描述

信号的注册(安装)主要有两个系统调用接口来完成:signal 与 sigaction 。

所有的信号都有默认的处理动作,即接收到了一个信号之后,进程都会打断当前操作去完成信号默认的处理动作,但是我们也可以用其信号来执行自己想完成的动作。

  • signal
typedef void (*sighandler_t)(int);//函数指针 指向自定义该信号的处理方式

       sighandler_t signal(int signum, sighandler_t handler);
//signum 为sig接收到的信号
//handler 为自定义的信号处理方式
其中有几个宏:
    SIG_ERR : 返回错误
    SIG_DEF:  执行默认动作
    SIG_IGN:  忽略信号

例子(一个自定义SIGINT信号的处理动作)

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <signal.h>
  4 #include <unistd.h>
  5 #include <errno.h>
  6 
  7 void diy(int signum)
  8 {
  9     printf("this is sig %d\n", signum);
 10 }
 11 
 12 int main()
 13 {
 14     signal(SIGINT, diy);
 15     while(1)
 16     {
 17         pause();
 18     }         
 19     return 0;   
 20 }          

运行之后

[liu@bogon 08]$ ./sigint 
^Cthis is sig 2
^Cthis is sig 2
^Cthis is sig 2
^Cthis is sig 2

遇到SIGINT信号的时候并没有发生阻断,而是运行我们自己定义的新的函数。

  • sigaction
signal函数只能进行简单的信号安装操作,真正在逐步淘汰,Linux提供了更加强大的sigaction函数安装信号 

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *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);
           };//不关注

例子(继续自定义SIGAINT信号)

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <signal.h>
  4 #include <unistd.h>
  5 #include <errno.h>
  6 
  7 void diy(int signum)
  8 {
  9     printf("this is sig %d\n", signum);
 10 }
 11 
 12 int main()
 13 {
 14     //signal(SIGINT, diy);
 15 
 16     struct sigaction act, oact;
 17 
 18     act.sa_handler = diy;
 19     sigemptyset(&act.sa_mask);
 20     act.sa_flags = 0;
 21 
 22     sigaction(SIGINT, &act, &oact);
 23 
 24     while(1)
 25     {
 26         pause();
 27     }
 28     return 0;
 29 }

运行结果:

[liu@bogon 08]$ ./sigint 
^Cthis is sig 2
^Cthis is sig 2
^Cthis is sig 2
^Z
[3]+  Stopped                 ./sigint
[liu@bogon 08]$ 

信号的屏蔽/阻塞

sigset_t:
每个信号只有一个比特位的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。异常,未决和阻塞标志可以用相同的数据结构类型sigset_t类型来存储;称之为信号机,这个类型可以表示每个信号的 “有效” 或者 “无效” 状态,所以进程在处理信号之前不仅仅会看未决信号集合(pending位图表),也会看block位图表。

设置进程信号屏蔽集

函数sigprocmask可以用来设置当前进程屏蔽的信号集合,函数如下

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 
//参数1为更改该集的方式 
  SIG_BLOCK:将第二个参数所描述的集合添加到进程的信号屏蔽集和中 
  SIG_UNBLOCK:将第二个参数所描述的信号集合从进程的屏蔽信号集合中删除 
  SIG_SETMASK:无论之前屏蔽了哪些信号,都已第二个参数描述的对象

如果参数set为空,则how的值毫无意义,这个可以查询当前的屏蔽信号集合

获取当前的未决信号

sigpending可以获取当前的未决信号集合

函数声明

int sigpending(sigset_t *set);

信号集合的操作

#include

int sigemptyset(sigset_t *set);  
 //初始化set中所指向的信号集合,使其中所以的信号对应的bit都清空为0, 表示该集合不包含任何信号 

int sigfiillset(sigset_t *set);  
 //初始化set所指向的信号机,是其中的所有信号对应的bit置位,表示该信号机的有效信号包含系统支持的所有信号 

int sigaddset(sigset_t *set, int signo); 
 //第一个参数位要从集合中删除信号的信号集合,第二个为要添加的信号的信号集合

int sigdelset(sigset_t *set, int signo);
 //第一个参数为添加到信号集变量,第二个参数为要删除的信号

int sigismenber(const sigset_t * set, int signo);
 //查看第二个参数的信号是否在第一个参数信号集当中 

信号集存储结构

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <signal.h>
  4 #include <unistd.h>
  5 
  6 void out(sigset_t set)
  7 {
  8     int i = 0;
  9     for(; i < 32; i++)
 10     {
 11         printf("%8x ", set.__val[i]);
 12         if((i+1)%8 == 0)
 13         {
 14             printf("\n");
 15         }
 16     }
 17 }
 18 
 19 int main()
 20 {
 21     sigset_t set;
 22     printf("after empty the set\n");
 23     sigemptyset(&set);
 24     out(set);
 25 
 26     printf("\nafter fill the set\n");
 27     sigfillset(&set);
 28 
 29     out(set);
 30     return 0;
 31 }

运行结果

[[email protected] 08]$ ./store 
after empty the set
       0        0        0        0        0        0        0        0 
       0        0        0        0        0        0        0        0 
       0        0        0        0        0        0        0        0 
       0        0        0        0        0        0        0        0 

after fill the set
7fffffff fffffffe ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff 
ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff 
ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff 
ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff

信号阻塞例子:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <signal.h>
  5 
  6 
  7 void _sprintf(sigset_t set)
  8 {
  9     int i = 0;
 10     for(; i < 32 ; ++i)
 11     {
 12         if(sigismember(&set, i))
 13         {
 14             printf("1");
 15         }
 16 
 17         printf("0");
 18     }
 19 
 20     return NULL;
 21 }
 22 
 23 int main()
 24 {
 25     sigset_t set;
 26     sigemptyset(&set);
 27     _sprintf(set);
 28 
 29     return 0;
 30 }

运行结果

[liu@localhost 08]$ ./sigprocmask 
100000000000000000000000000000000

自己实现一个sleep函数

 11 #include <stdio.h>
 12 #include <stdlib.h>
 13 #include <unistd.h>
 14 #include <signal.h>
 15 
 16 void mysleep(int sig)
 17 {
 18 
 19 }
 20 
 21 int main()
 22 {
 23     struct sigaction act;
 24     struct sigaction oact;
 25     int sec;
 26 
 27     sigemptyset(&act.sa_mask);
 28     act.sa_flags = 0;
 29     act.sa_handler = mysleep;
 30 
 31     sigaction(SIGALRM, &act, &oact);
 32 
 33     alarm(5);
 34     pause();
 35 
 36     sigaction(SIGALRM, &oact, NULL);
 37     return 0;
 38 }

竞态条件与sigsuspend函数
在上文提到的sleep函数中的步骤为:

  • 注册SIGALRM信号的处理函数
  • 调用alarm函数
  • 调用pause函数接收到信号
  • 完成sleep

    但是系统处理进程的方式是依靠时间片并发,当出现了一个异步事件,无法保证在alarm所规定的时间切回到pause,这样就会产生错误。

函数sigsuspend是将解除信号屏蔽,与挂起等待信号两步编程了一个原子操作,这样就不要担心上述问题了。

sigsuspend其实就是一个升级版的pause

原文链接:加载失败,请重新获取