Linux Signal and Sigset
2021-07-16 18:49:52
C/C++
130

Linux Signal and Sigset

信号是软中断,许多重要的程序都需要处理信号。信号为系统 提供了一种处理异步事件的方法。

简述

1. 信号的名字和编号

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。 信号定义在signal.h头文件中,信号名都定义为正整数。 具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。

macos ~ $ kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL
 5) SIGTRAP  6) SIGABRT  7) SIGEMT   8) SIGFPE
 9) SIGKILL 10) SIGBUS  11) SIGSEGV 12) SIGSYS
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG
17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD
21) SIGTTIN 22) SIGTTOU 23) SIGIO   24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM   27) SIGPROF 28) SIGWINCH
29) SIGINFO 30) SIGUSR1 31) SIGUSR2
centos7 # 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
SIGHUP       1          /* Hangup (POSIX).  */                          终止进程     终端线路挂断
SIGINT       2          /* Interrupt (ANSI).  */                        终止进程     中断进程 Ctrl+C
SIGQUIT      3          /* Quit (POSIX).  */                            建立CORE文件终止进程,并且生成core文件 Ctrl+\
SIGILL       4          /* Illegal instruction (ANSI).  */              建立CORE文件,非法指令
SIGTRAP      5          /* Trace trap (POSIX).  */                      建立CORE文件,跟踪自陷
SIGABRT      6          /* Abort (ANSI).  */
SIGIOT       6          /* IOT trap (4.2 BSD).  */                      建立CORE文件,执行I/O自陷
SIGBUS       7          /* BUS error (4.2 BSD).  */                     建立CORE文件,总线错误
SIGFPE       8          /* Floating-point exception (ANSI).  */         建立CORE文件,浮点异常
SIGKILL      9          /* Kill, unblockable (POSIX).  */               终止进程     杀死进程
SIGUSR1      10         /* User-defined signal 1 (POSIX).  */           终止进程     用户定义信号1
SIGSEGV      11         /* Segmentation violation (ANSI).  */           建立CORE文件,段非法错误
SIGUSR2      12         /* User-defined signal 2 (POSIX).  */           终止进程     用户定义信号2
SIGPIPE      13         /* Broken pipe (POSIX).  */                     终止进程     向一个没有读进程的管道写数据
SIGALARM     14         /* Alarm clock (POSIX).  */                     终止进程     计时器到时
SIGTERM      15         /* Termination (ANSI).  */                      终止进程     软件终止信号
SIGSTKFLT    16         /* Stack fault.  */
SIGCLD       SIGCHLD    /* Same as SIGCHLD (System V).  */
SIGCHLD      17         /* Child status has changed (POSIX).  */        忽略信号     当子进程停止或退出时通知父进程
SIGCONT      18         /* Continue (POSIX).  */                        忽略信号     继续执行一个停止的进程
SIGSTOP      19         /* Stop, unblockable (POSIX).  */               停止进程     非终端来的停止信号
SIGTSTP      20         /* Keyboard stop (POSIX).  */                   停止进程     终端来的停止信号 Ctrl+Z
SIGTTIN      21         /* Background read from tty (POSIX).  */        停止进程     后台进程读终端
SIGTTOU      22         /* Background write to tty (POSIX).  */         停止进程     后台进程写终端
SIGURG       23         /* Urgent condition on socket (4.2 BSD).  */    忽略信号     I/O紧急信号
SIGXCPU      24         /* CPU limit exceeded (4.2 BSD).  */            终止进程     CPU时限超时
SIGXFSZ      25         /* File size limit exceeded (4.2 BSD).  */      终止进程     文件长度过长
SIGVTALRM    26         /* Virtual alarm clock (4.2 BSD).  */           终止进程     虚拟计时器到时
SIGPROF      27         /* Profiling alarm clock (4.2 BSD).  */         终止进程     统计分布图用计时器到时
SIGWINCH     28         /* Window size change (4.3 BSD, Sun).  */       忽略信号     窗口大小发生变化
SIGPOLL      SIGIO      /* Pollable event occurred (System V).  */
SIGIO        29         /* I/O now possible (4.2 BSD).  */              忽略信号     描述符上可以进行I/O
SIGPWR       30         /* Power failure restart (System V).  */
SIGSYS       31         /* Bad system call.  */
SIGUNUSED    31

1) SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联.

2) SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出

3) SIGQUIT 和 SIGINT类似, 但由QUIT字符(通常是Ctrl+)来控制. 进程在因收到 SIGQUIT 退出时会产生core文件, 在这个意义上类似于一个程序错误信号.

4) SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号.

5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用.

6) SIGABRT 程序自己发现错误并调用abort时产生.

6) SIGIOT 在PDP-11上由iot指令产生, 在其它机器上和SIGABRT一样.

7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数.

8) SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误.

9) SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞, 处理和忽略.

10) SIGUSR1 留给用户使用

11) SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

12) SIGUSR2 留给用户使用

13) SIGPIPE Broken pipe

14) SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

15) SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号.

17) SIGCHLD 子进程结束时, 父进程会收到这个信号.

18) SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符

19) SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.

20) SIGTSTP 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl+Z)发出这个信号

21) SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.

22) SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.

23) SIGURG 有"紧急"数据或out-of-band数据到达socket时产生.

24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变

25) SIGXFSZ 超过文件大小资源限制.

26) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

27) SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.

28) SIGWINCH 窗口大小改变时发出.

29) SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作.

30) SIGPWR Power failure

有两个信号可以停止进程:SIGTERM和SIGKILL。 SIGTERM 比较友好,进程能捕捉这个信号,根据您的需要来关闭程序。

在关闭程序之前,您可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。

对于 SIGKILL 信号,进程是不能忽略的。这是一个 “我不管您在做什么,立刻停止”的信号。假如您发送SIGKILL信号给进程,Linux就将进程停止在那里。

程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP 
不能恢复至默认动作的信号有:SIGILL,SIGTRAP 
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ 
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM 
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU 
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH 
此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞

2. 信号的处理

信号的处理有三种方法,分别是:忽略(SIG_IGN)、捕捉(customize_handle)和默认动作(SIG_DFL)

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。 具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。

信号处理函数的注册

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

//typedef void (*sighandler_t)(int);
void handler(int signum) {
    if(signum == SIGIO)
        printf("SIGIO   signal: %d\n", signum);
    else if(signum == SIGUSR1)
        printf("SIGUSR1   signal: %d\n", signum);
    else
        printf("error\n");
}

int main(void) {
    //sighandler_t signal(int signum, sighandler_t handler);
    signal(SIGIO, handler);
    signal(SIGUSR1, handler);
    printf("%d  %d\n", SIGIO, SIGUSR1);
    for(;;) {
        sleep(10000);
    }
    return 0;
}

两个问题需要说明一下:

  1. 当执行一个程序时,所有信号的状态都是系统默认或者忽略状态的。除非是 调用exec进程忽略了某些信号。exec 函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不会改变 。
  2. 当一个进程调动了 fork 函数,那么子进程会继承父进程的信号处理方式。

信号处理发送函数

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig);    //等价于kill(getpid(),signo)
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

int main(int argc, char** argv) {
    if(3 != argc) {
        printf("[Arguments ERROR!]\n");
        printf("\tUsage:\n");
        printf("\t\t%s <Target_PID> <Signal_Number>\n", argv[0]);
        return -1;
    }
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);
    //int kill(pid_t pid, int sig);
    if(pid > 0 && sig > 0) {
        kill(pid, sig);
    } else {
        printf("Target_PID or Signal_Number MUST bigger than 0!\n");
    }
    return 0;
}

关于 kill 函数,还有一点需要额外说明,上面的程序限定了 pid 必须为大于0的正整数,其实 kill 函数传入的 pid 可以是小于等于0的整数。 pid > 0:将发送个该 pid 的进程 pid == 0:将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限想这些进程发送信号。 pid < 0:将信号发送给进程组ID 为 pid 的绝对值得,并且发送进程具有权限向其发送信号的所有进程 pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。(不包括系统进程集中的进程)

中断的系统调用

  如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。系统调用返回出错,将errno的值设置为EINTR。当一个信号发生时,进程捕捉到,意味着已经发生了某种事情,可以唤醒阻塞系统调用进行相关操作。低速系统调用是可能使进程永远阻塞的一类系统调用。与被中断的系统调用相关的问题是必须显示的处理出错返回。例如在进行多操作时候,多的过程被中断了,中断结束后希望重新启动读操作。代码如下:

 again:
     if((n=read(fd,buf,BUFSIZE))<0) {
        if(errno == EINTR)
           goto again;
        /*handle other errnors*/
     }

自动重新启动的系统调用包括:ioctl、read、readv、write、writew、wait和waitpid。

可重入函数

  进程捕捉到信号并对其进行处理,进程正在执行的指令序列就被信号处理程序临时中断,首先执行该信号处理程序中的指令,如果从信号处理程序返回,则继续在捕捉到信号时进程正在执行的正常指令中返回。在信号处理程序中,不能判断捕捉到信号时进程在何处执行,这样不能保证在中断处理结束后能够正确返回到进程的执行指令中。为了保证进程在处理完中断后能够正确返回,需要保证调用的是可重入的函数。不可重入函数包括:(1)使用静态数据结构,(2)调用malloc或free,(3)标准I/O函数。信号处理程序中调用一个不可重入的函数,则结果是不可预测的。例如getpwnam函数是个不可重入的,因为其结果存放在静态存储单元中,信号处理程序调用后,返回给正常调用者的信息可能是被返回给信号处理程序的信息覆盖。程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <pwd.h>
#include <string.h>

static void my_alarm(int signo) {
    struct passwd *rootptr;
    printf("in signal handler.\n");
    if ((rootptr = getpwnam("root")) == NULL) {
        perror("getpwnam() error");
        exit(-1);
    }
    printf("return value:,pw_name = %s\n",rootptr->pw_name);
    alarm(1);
}

int main() {
    struct passwd  *ptr;
    signal(SIGALRM,my_alarm);
    alarm(1);
    while(1) {
        if((ptr = getpwnam("anker")) == NULL) {
             perror("getpwnam() error");
            exit(-1);
        }
        if(strcmp(ptr->pw_name,"anker") != 0)
            printf("return value corrupted!,pw_name = %s\n",ptr->pw_name);
        else
            printf("return value not corrupted!,pw_name = %s\n",ptr->pw_name);
    }
}

alarm和pause函数

  使用alarm函数设置计时器,在将来某个指定时间该计时器会超时,产生SIGALRM信号,默认动作时终止调用alarm函数的进程,每个进程只能有一个闹钟时钟,如果在调用alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。当在调用alarm()前已经设置了一个闹钟,那么我们可以调用alarm(0)来取消此闹钟,并返回剩余时间。puase函数使调用进程挂起直到捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回-1,并将errno设置为EINTR。函数原型如下:

  #include <unistd.h>
  unsigned int alarm(unsigned int seconds); //返回0或者以前设置的闹钟时间的余留秒数
  int pause(void);

写个程序练习以上函数,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/wait.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <errno.h>
 7 #include <signal.h>
 8 static void sig_kill(int signo)
 9 {
10     printf("Received from kill().\n");
11 }
12 static void sig_alarm(int signo)
13 {
14     printf("Receiver form aralm().\n");
15 }
16 int main()
17 {
18 
19     signal(SIGHUP,sig_kill);
20     signal(SIGALRM,sig_alarm);
21 
22     printf("kill() is called.\n");
23     kill(getpid(),SIGHUP);
24     printf("alarm() is called.\n");
25     alarm(3);
26     printf("pause() is called.\n");
27     pause();
28     printf("raise() is called.\n");
29     raise(SIGHUP);
30     exit(0);
31 }

alarm只设定一个闹钟,时间到达并执行其注册函数之后,闹钟便失效。如果想循环设置闹钟,需在其注册函数中在调用alarm函数。写个程序可以设置一个循环闹钟,使得程序每个多少秒执行以下,执行完成之后开始计时,时间都接着执行,循环指定主程序结束。程序如下:

 1 #include <unistd.h>
 2 #include <signal.h>
 3 #include <stdio.h>
 4 void sig_alarm(int signo)
 5 {
 6     printf("Alarming.\n");
 7     signal(SIGALRM, sig_alarm); //让内核做好准备,一旦接受到SIGALARM信号,就执行sig_alarm
 8     alarm(5);
 9 }
10 void main()
11 {
12     int i;
13     signal(SIGALRM, sig_alarm);//让内核做好准备,一旦接受到SIGALARM信号,就执行sig_alarm
14     alarm(5);
15     for(i=1;i<21;i++)
16     {
17         printf("sleep %d ...\n",i);
18         sleep(1);
19     }
20 }

可靠信号和不可靠信号

不可靠信号:信号可能会丢失,一旦信号丢失了,进程并不能知道信号丢失 可靠信号:也是阻塞信号,当发送了一个阻塞信号,并且该信号的动作时系统默认动作或捕捉该信号,如果信号从发出以后会一直保持未决的状态,直到该进程对此信号解除了阻塞,或将对此信号的动作更改为忽略。 对于信号来说,信号编号小于等于31的信号都是不可靠信号,之后的信号为可靠信号,系统会根据信号队列,将信号在递达之前进行阻塞。

信号的阻塞和未决是通过信号的状态字来管理的,该状态字是按位来管理信号的状态。每个信号都有独立的阻塞字,规定了当前要阻塞地达到该进程的信号集。

信号阻塞状态字(block),1代表阻塞、0代表不阻塞;信号未决状态字(pending)的1代表未决,0代表信号可以抵达了;它们都是每一个bit代表一个信号

  • 阻塞和未决是如何工作的? 比如向进程发送SIGINT信号,内核首先会判断该进程的信号阻塞状态字是否阻塞状态,如果该信号被设置为阻塞的状态,也就是阻塞状态字对应位为1,那么信号未决状态字(pending)相应位会被内核设置为1;如果该信号阻塞解除了,也就是阻塞状态字设置为了0,那么信号未决状态字(pending)相应位会被内核设置为0,表示信号此时可以抵达了,也就是可以接收该信号了。 阻塞状态字用户可以读写,未决状态字用户只能读,是由内核来设置表示信号递达状态的。 PS:这里额外说明以下,只有支持了 POSIX.1实时扩展的系统才支持排队的功能(也就阻塞状态下多次同一信号发送给某一进程可以得到多次,而不是一次)。
  • 关于进程关于信号的阻塞状态字的设置 可以通过int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);函数来获取或者设置。

该函数管理信号,是通过信号集的数据结构来进行管理的,信号集可以通过以下的函数进行管理。 信号集操作函数(状态字表示)

#include <signal.h>
int sigemptyset(sigset_t *set);  //初始化 set 中传入的信号集,清空其中所有信号
int sigfillset(sigset_t *set);  //把信号集填1,让 set 包含所有的信号
int sigaddset(sigset_t *set, int signum);//把信号集对应位置为1
int sigdelset(sigset_t *set, int signum);//把信号集对应位置为0
int sigismember(const sigset_t *set, int signum); //判断signum是否在信号集

sigprocmask

对于信号集分配好内存空间,需要使用初始化函数来初始化。初始化完成后,可以在该集合中添加、删除特定的信号。 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 其中 how 变量决定了是如何操作该状态字。 SIG_BLOCK(增):set包含了我们希望添加到当前信号阻塞字的信号, 即所指向的信号集中所包含的信号加到当前的信号掩码中,作为新的信号屏蔽字, 相当于 mask=mask|set SIG_UNBLOCK(删):set包含了我们希望从当前信号阻塞字中解除阻塞的信号,即将参数set所指向的信号集中的信号从当前的信号掩码中移除, 相当于 mask=mask&~set SIG_SETMASK(改):设置当前信号阻塞字为set所指的值,即设置当前信号掩码为参数newset所指向的信号集中所包含的信号,相当于 mask=set

pending是由内核来根据block设置的,只可以读取其中的数据,来段判断信号是否会递达。通过设置block可以将希望阻塞的信号进行阻塞,对应的pending会由内核来设置

设置信号阻塞、未达的步骤:

  1. 分配内存空间sigset sigset bset;
  2. 置空sigemptyset(&bset);
  3. 添加信号sigaddset(&bset, SIGINT);
  4. 添加其他需要管理的信号....
  5. 设置信号集中的信号处理方案(此处为解除阻塞)sigprocmask(SIG_UNBLOCK, &bset, NULL);
  • 简化版设置阻塞状态字
#include <signal.h>
int sigpending(sigset_t *set);

这个函数使用很简单,对于调用他的进程来说,其中信号集中的信号是阻塞不能递送的,那么,也就一定会是当前未决的。

  • 原子操作的信号阻塞字的恢复并进入休眠状态
#include <signal.h>
int sigsuspend(const sigset_t *mask);

为何会出现原子性的解除阻塞的函数呢? 因为,当信号被阻塞的时候,产生了信号,那么该信号的递送就要推迟到这个信号被解除了阻塞为止。如果此时,应用程序正好处在,解除 SIGINT 的阻塞和 pause 之间,那么此时,会产生问题,可能永远 pause 不能够等到SIGINT 信号来打断他,造成程序永久阻塞在 pause 处。 为了解决这个问题,,需要在一个原子性的操作来恢复信号的屏蔽字,然后才能让进程进入休眠状态,以保证不会出现上述的问题。

进程的信号屏蔽字设置为

信号注册函数——高级版

我们已经成功完成了信号的收发,那么为什么会有高级版出现呢?其实之前的信号存在一个问题就是,虽然发送和接收到了信号,可是总感觉少些什么,既然都已经把信号发送过去了,为何不能再携带一些数据呢? 正是如此,我们需要另外的函数来通过信号传递的过程中,携带一些数据。咱么先来看看发送的函数吧。

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只能任选其一

这个函数的原版帮助信息,可以通过man 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 */
   int      si_band;     /* Band event */
   int      si_fd;       /* File descriptor */
}

其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。 关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。

那么,kill 函数发送的信号是无法携带数据的,我们现在还无法验证发送收的部分,那么,我们先来看看发送信号的高级用法后,我们再来看看如何通过信号来携带数据吧。

信号发送函数——高级版

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
};

使用这个函数之前,必须要有几个操作需要完成

  1. 使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
  2. sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。

sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。

sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。

但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。

那么我们来尝试一下,发送一个携带有额外数据的信号吧。

接收端

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

//void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum, siginfo_t * info, void * context) {
    if(signum == SIGIO)
        printf("SIGIO   signal: %d\n", signum);
    else if(signum == SIGUSR1)
        printf("SIGUSR1   signal: %d\n", signum);
    else
        printf("error\n");

    if(context)
    {
        printf("content: %d\n", info->si_int);
        printf("content: %d\n", info->si_value.sival_int);
    }
}

int main(void) {
    //int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    struct sigaction act;

    /*
     struct sigaction {
     void     (*sa_handler)(int);
     void     (*sa_sigaction)(int, siginfo_t *, void *);
     sigset_t   sa_mask;
     int        sa_flags;
     };
     */
    act.sa_sigaction = handler;
    act.sa_flags = SA_SIGINFO;

    sigaction(SIGIO, &act, NULL);
    sigaction(SIGUSR1, &act, NULL);
    for(;;)
    {
        sleep(10000);
    }
    return 0;
}

发送端

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>


int main(int argc, char** argv)
{
    if(4 != argc)
    {
        printf("[Arguments ERROR!]\n");
        printf("\tUsage:\n");
        printf("\t\t%s <Target_PID> <Signal_Number> <content>\n", argv[0]);
        return -1;
    }
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    if(pid > 0 && sig > 0)
    {
        //int sigqueue(pid_t pid, int sig, const union sigval value);
        union sigval val;
        val.sival_int = atoi(argv[3]);
        printf("send: %d\n", atoi(argv[3]));
        sigqueue(pid, sig, val);
    }
    else
    {
        printf("Target_PID or Signal_Number MUST bigger than 0!\n");
    }

    return 0;
}

信号集

  通过信号集(signal set)表示多个信号,这样方便操作多个信号。信号集的数据类型为sigset_t,信号集操作函数如下:

信号集及信号集操作函数, 信号集被定义为一种数据类型:

 typedef struct {
        unsigned long sig[_NSIG_WORDS];
} sigset_t

信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。

#include <signal.h>
sigemptyset(sigset_t *set) 初始化由set指向的信号集,清除其中所有信号
sigfillset(sigset_t *set) 初始化由set指向的信号集,使其包括所有信号
sigaddset(sigset_t *set, int signum) 添加一个指定的信号
sigdelset(sigset_t *set, int signum) 删除一个指定信号
sigismember(const sigset_t *set, int signum) 判断signum是否在set所指向的信号集中
int sigaction( int sig, const struct sigaction *act,struct sigaction *oact ) 检查、修改和指定信号相关联的信号响应。

头文件:#include <signal.h>
函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
函数说明:一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。sigprocmask()可以用来检测或改变目前的信号屏蔽字,其操作依参数how来决定,如果参数oldset不是NULL指针,那么目前的信号屏蔽字会由此指针返回。

参数how:
SIG_BLOCK   该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。
SIG_UNBLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望解除阻塞的信号.
SIG_SETMASK 该进程新的信号屏蔽是set指向的值,如果set是个空指针,则不改变该进程的信号屏蔽字,how的值也无意义。

返回值:执行成功返回0,失败返回-1。
错误代码:

EFAULT 参数set,oldset指针地址无法存取
EINTR    此调用被中断

sigpending

sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能传递, 因而也一定是当前未决的。

函数原型为: int sigpending(sigset_t *set)。

写个程序进行信号屏蔽测试,程序如下:

1 #include <sys/wait.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <errno.h>
 5 #include <signal.h>
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 
 9 static void sig_quit(int signo);
10 
11 int main()
12 {
13     sigset_t    newmask,oldmask,pendmask;
14     if(signal(SIGQUIT,sig_quit) == SIG_ERR)
15     {
16         perror("signal() error");
17         exit(-1);
18     }
19     sigemptyset(&newmask);
20     //添加一个退出信号
21     sigaddset(&newmask,SIGQUIT);
22     //将newmask信号信设置为阻塞,原信号集保存在oldmask中
23     if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) == -1)
24     {
25         perror("sigprocmask() error");
26         exit(-1);
27     }
28     sleep(5);
29     //获取阻塞的信号集
30     if(sigpending(&pendmask) == -1)
31     {
32         perror("sigpending() error");
33         exit(-1);
34     }
35     //判断SIGQUIT是否是阻塞的
36     if(sigismember(&pendmask,SIGQUIT))
37         printf("\nSIGQUIT is pending.\n");
38     //恢复原来的信号集
39     if(sigprocmask(SIG_SETMASK,&oldmask,NULL) == -1)
40     {
41         perror("sigprocmask() error");
42         exit(-1);
43     }
44     printf("SITQUIT unblocked\n");
45     sleep(5);
46     exit(0);
47 }
48 
49 static void sig_quit(int signo)
50 {
51     printf("caught SIGQUIT.\n");
52     if(signal(SIGQUIT,SIG_DFL) == SIG_ERR)
53     {
54         perror("signal() error");
55         exit(-1);
56     }
57 }

程序执行结果如下:在中断上键入Ctlr+\退出字符。

img

第二次运行时候,在进程休眠的时候产生多次SIGQUIT信号,但是解除了该信号的阻塞后,只会向进程发送一个SIGQUIT,系统没有对信号进行排队。

sigaction

sigaction函数的功能是检查或修改与指定信号相关联的处理动作或同时执行这两种操作,可以用sigaction函数实现signal函数。函数原型及结构参数如下:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

struct sigaction {
  void     (*sa_handler)(int);
  void     (*sa_sigaction)(int, siginfo_t *, void *);
  sigset_t   sa_mask;
  int        sa_flags;
  void     (*sa_restorer)(void);
};

现用sigaction函数实现signal函数,被信号中断的系统调用都能够重启。程序如下:

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <signal.h>
 6 
 7 typedef void Sigfunc(int);
 8 
 9 Sigfunc* mysignal(int signo,Sigfunc *func)
10 {
11     struct sigaction    act,oact;
12     act.sa_handler = func;  //设置中断处理程序
13     sigemptyset(&act.sa_mask);  //初始化信号集
14     act.sa_flags = 0;
15     if(signo == SIGALRM)   //将SIGALRM信号设置为系统调用不会自动重启
16     {
17     #ifdef SA_INTERRUPT
18         act.sa_flags |= SA_INTERRUPT;
19     #endif
20     }
21     else   //其余信号设置为系统调用自动重启
22     {
23     #ifdef  SA_RESTART
24         act.sa_flags |= SA_RESTART;
25     #endif
26     }
27     if(sigaction(signo,&act,&oact)<0)
28         return (SIG_ERR);
29     return (oact.sa_handler);
30 }
31 
32 static void sig_func(int signo)
33 {
34     printf("Recevied a SIGALRM signal.\n");
35 }
36 
37 int main()
38 {
39     printf("Starting.\n");
40     mysignal(SIGALRM,sig_func);
41     alarm(2);
42     pause();  //等待信号出现
43     exit(0);
44 }

sigsetjmp & siglongjmp

 这两个函数是对非局部转移的setjmp和longjmp函数的改进,在信号处理程序中进行非局部转移时应当使用这两个函数。函数原型如下:  

int sigsetjmp(sigjmp_buf env, int savesigs); void siglongjmp(sigjmp_buf env, int val);

在sigsetjmp函数中,如果savesigs非0,则sigsetjmp在env中保存进程的当前信号屏蔽字,调用siglongjmp从其中恢复保存的信号屏蔽字。

写个程序练习一下,程序如下:

1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <signal.h>
 7 
 8 static void printcharacter(const char* str);
 9 static void sig_usr(int signo);
10 void TELL_WAIT(void);
11 void TELL_PARENT(pid_t pid);
12 void TELL_CHILD(pid_t pid);
13 void WAIT_PARENT(void);
14 void WAIT_CHILD(void);
15 static volatile sig_atomic_t sigflag;
16 static sigset_t newmask,oldmask,zeromask;
17 
18 int main()
19 {
20     pid_t   pid;
21     TELL_WAIT();
22     pid = fork();
23     switch(pid)
24     {
25     case -1:
26         perror("fork() error");
27         exit(-1);
28     case 0:   //让子进程优先执行
29         //WAIT_PARENT();
30         printcharacter("output from child prcess.\n");
31         TELL_PARENT(getppid());
32         break;
33     default:
34         WAIT_CHILD();
35         printcharacter("output from parent prcess.\n");
36         //TELL_CHILD(pid);
37     }
38     exit(0);
39 }
40 
41 static void printcharacter(const char* str)
42 {
43     const char *ptr;
44     setbuf(stdout,NULL);
45     for(ptr=str;*ptr!='\0';ptr++)
46         putc(*ptr,stdout);
47 }
48 static void sig_usr(int signo)  //信号处理程序
49 {
50     sigflag = 1;
51 }
52 void TELL_WAIT(void)
53 {
54     signal(SIGUSR1,sig_usr);
55     signal(SIGUSR2,sig_usr);
56     sigemptyset(&zeromask);
57     sigemptyset(&newmask);
58     //添加信号集
59     sigaddset(&newmask,SIGUSR1);
60     sigaddset(&newmask,SIGUSR2);
61     sigprocmask(SIG_BLOCK,&newmask,&oldmask);  //设置信号为阻塞
62 }
63 void TELL_PARENT(pid_t pid)
64 { 
65     kill(pid,SIGUSR2); //向子进程发送信号
66 }
67 void TELL_CHILD(pid_t pid)
68 {
69     kill(pid,SIGUSR1);//向父进程发送信号
70 }    
71 void WAIT_PARENT(void)
72 {
73     while(sigflag == 0)
74       sigsuspend(&zeromask);
75     sigflag = 0;
76     sigprocmask(SIG_SETMASK,&oldmask,NULL);
77 }
78 void WAIT_CHILD(void)
79 {
80     while(sigflag == 0)
81       sigsuspend(&zeromask); //阻塞进程
82     sigflag = 0;
83     sigprocmask(SIG_SETMASK,&oldmask,NULL);
84 }

当调用一个信号处理程序时,被捕捉到的信号添加到进程的当前信号屏蔽字中,当从信号处理程序返回时,恢复原来的屏蔽字,siglongjmp恢复了有sigsetjmp保存的信号屏蔽字。

sigsuspend

  该函数在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。函数原型如下:

int sigsuspend(const sigset_t *mask); 
返回值:-1,并将errno设置为EINTR
将进程的信号屏蔽字设置为由mask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。
如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
注意: 此函数没有成功返回值。如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig)   //信号处理程序
{
   if(sig == SIGINT)
      printf("SIGINT sig");
   else if(sig == SIGQUIT)
      printf("SIGQUIT sig");
   else
      printf("SIGUSR1 sig");
}

int main() {
    sigset_t new,old,wait;   //三个信号集
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, 0);    //可以捕捉以下三个信号:SIGINT/SIGQUIT/SIGUSR1
    sigaction(SIGQUIT, &act, 0);
    sigaction(SIGUSR1, &act, 0);

    sigemptyset(&new);
    sigaddset(&new, SIGINT);  //SIGINT信号加入到new信号集中
    sigemptyset(&wait);
    sigaddset(&wait, SIGUSR1);  //SIGUSR1信号加入wait
    sigprocmask(SIG_BLOCK, &new, &old);       //将SIGINT阻塞,保存当前信号集到old中

    //临界区代码执行    

    if(sigsuspend(&wait) != -1)  //程序在此处挂起;用wait信号集替换new信号集。即:过来SIGUSR1信号,阻塞掉,程序继续挂起;过来其他信号,例如SIGINT,则会唤醒程序。执行sigsuspend的原子操作。注意:如果“sigaddset(&wait, SIGUSR1);”这句没有,则此处不会阻塞任何信号,即过来任何信号均会唤醒程序。
        printf("sigsuspend error");
    printf("After sigsuspend");
    sigprocmask(SIG_SETMASK, &old, NULL);
    return 0;
}

sigsuspend的原子操作是: (1)设置新的mask阻塞当前进程(上面是用wait替换new,即阻塞SIGUSR1信号) (2)收到SIGUSR1信号,阻塞,程序继续挂起;收到其他信号,恢复原先的mask(即包含SIGINT信号的)。 (3)调用该进程设置的信号处理函数(程序中如果先来SIGUSR1信号,然后过来SIGINT信号,则信号处理函数会调用两次,打印不同的内容。第一次打印SIGINT,第二次打印SIGUSR1,因为SIGUSR1是前面阻塞的) (4)待信号处理函数返回,sigsuspend返回了。(sigsuspend将捕捉信号和信号处理函数集成到一起了)

注:sigsuspend实际是将sigprocmask和pause结合起来原子操作, 类似如下:

sigprocmask(SIG_BLOCK, &new, &old);  //将SIGINT信号阻塞,同时保存当前信号集
pause();
sigprocmask(SIG_SETMASK, &old, NULL);  



该函数可以保护不希望由信号中断的代码临界区,写个程序,使用该函数保护临界区,使其不被特定信号中断,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <signal.h>
 6 #include <errno.h>
 7 #include <setjmp.h>
 8 void pr_mask(const char *str);
 9 static void sig_int(int);
10 
11 int main()
12 {
13     sigset_t    newmask,oldmask,waitmask;
14     pr_mask("program start: ");
15     signal(SIGINT,sig_int);
16     sigemptyset(&waitmask);
17     sigaddset(&waitmask,SIGUSR1);
18     sigemptyset(&newmask);
19     sigaddset(&newmask,SIGINT);
20     sigprocmask(SIG_BLOCK,&newmask,&oldmask);
21     pr_mask("in critical region");
22     //修改进程屏蔽字,在捕捉信号之前,将进程挂起
23     sigsuspend(&waitmask);
24     pr_mask("after return form sigsuspend: ");
25     sigprocmask(SIG_SETMASK,&oldmask,NULL);
26     pr_mask("program exit: ");
27     exit(0);
28 }
29 
30 void pr_mask(const char *str)
31 {
32     sigset_t sigset;
33     int errno_save;
34     errno_save = errno;
35     if(sigprocmask(0,NULL,&sigset)<0)
36     {
37         perror("sigprocmask() error");
38         exit(-1);
39     }
40     printf("%s\n",str);
41     if(sigismember(&sigset,SIGINT))
42         printf("SIGINT \n");
43     if(sigismember(&sigset,SIGQUIT))
44         printf("SIGQUIT \n");
45     if(sigismember(&sigset,SIGUSR1))
46         printf("SIGUSR1 \n");
47     if(sigismember(&sigset,SIGALRM))
48         printf("SIGALRM \n");
49     errno = errno_save;
50 }
51 
52 static void sig_int(int signo)
53 {
54     pr_mask("\nin sig_int: ");
55 }

从结果可以看出在调用sigsuspend函数,将SIGUSR1信号添加到进程信号屏蔽字中,当从sissuspend返回时,信号屏蔽字恢复为调用它之前的值。

进程之间存在资源竞争,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 
 7 static void printcharacter(const char* str)
 8 {
 9     const char *ptr;
10     setbuf(stdout,NULL);
11     for(ptr=str;*ptr!='\0';ptr++)
12         putc(*ptr,stdout);
13 }
14 
15 int main()
16 {
17     pid_t   pid;
18     pid = fork();
19     switch(pid)
20     {
21     case -1:
22         perror("fork() error");
23         exit(-1);
24     case 0:
25         printcharacter("output from child prcess.\n");
26         break;
27     default:
28         printcharacter("output from parent prcess.\n");
29     }
30     exit(0);
31 }

多次运行,从结果看出,父子进程之间存在资源竞争,导致输出结果是随机的。接下来采用信号机制实现父子进程之间的同步实现TELL_WAIT、TELL_PARENT、TELL_CHILD、 WAIT_PARENT和WAIT_CHILD。程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <signal.h>
 7 
 8 static void printcharacter(const char* str);
 9 static void sig_usr(int signo);
10 void TELL_WAIT(void);
11 void TELL_PARENT(pid_t pid);
12 void TELL_CHILD(pid_t pid);
13 void WAIT_PARENT(void);
14 void WAIT_CHILD(void);
15 static volatile sig_atomic_t sigflag;
16 static sigset_t newmask,oldmask,zeromask;
17 
18 int main()
19 {
20     pid_t   pid;
21     TELL_WAIT();
22     pid = fork();
23     switch(pid)
24     {
25     case -1:
26         perror("fork() error");
27         exit(-1);
28     case 0:
29         //WAIT_PARENT();
30         printcharacter("output from child prcess.\n");
31         TELL_PARENT(getppid());
32         break;
33     default:
34         WAIT_CHILD();
35         printcharacter("output from parent prcess.\n");
36         //TELL_CHILD(pid);
37     }
38     exit(0);
39 }
40 
41 static void printcharacter(const char* str)
42 {
43     const char *ptr;
44     setbuf(stdout,NULL);
45     for(ptr=str;*ptr!='\0';ptr++)
46         putc(*ptr,stdout);
47 }
48 static void sig_usr(int signo)
49 {
50     sigflag = 1;
51 }
52 void TELL_WAIT(void)
53 {
54     signal(SIGUSR1,sig_usr);  //设置信号
55     signal(SIGUSR2,sig_usr);
56     sigemptyset(&zeromask);
57     sigemptyset(&newmask);
58     sigaddset(&zeromask,SIGUSR1);
59     sigaddset(&newmask,SIGUSR2);
60     sigprocmask(SIG_BLOCK,&newmask,&oldmask);  //将信号设置为阻塞
61 }
62 void TELL_PARENT(pid_t pid)
63 {
64     kill(pid,SIGUSR2); //向子进程发生信号
65 }
66 void TELL_CHILD(pid_t pid)
67 {
68     kill(pid,SIGUSR1); //想父进程发送信号
69 }
70 void WAIT_PARENT(void)
71 {
72     while(sigflag == 0)
73       sigsuspend(&zeromask);//将进程挂起。等待信号处理程序返回
74     sigflag = 0;
75     sigprocmask(SIG_SETMASK,&oldmask,NULL);
76 }
77 void WAIT_CHILD(void)
78 {
79     while(sigflag == 0)
80       sigsuspend(&zeromask);  //将进程挂起。等待信号处理程序返回
81     sigflag = 0;
82     sigprocmask(SIG_SETMASK,&oldmask,NULL);
83 }

abort

  abort函数的功能是使异常终止,此函数将SIGABRT信号发送给调用进程,让进程捕捉SIGABRT信号目的是在进程终止之前由其执行所需的清理操作。默认情况是终止调用进程。可以采用sigaction和kill函数来实现abort,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <signal.h>
 7 
 8 void myabort()
 9 {
10     sigset_t  mask;
11     struct sigaction action;
12     sigaction(SIGABRT,NULL,&action);
13     if(action.sa_handler == SIG_IGN)
14     {
15         action.sa_handler = SIG_DFL;
16         sigaction(SIGABRT,&action,NULL);
17     }
18     if(action.sa_handler == SIG_DFL)
19         fflush(NULL);
20     sigfillset(&mask);
21     sigdelset(&mask,SIGABRT);
22     sigprocmask(SIG_SETMASK,&mask,NULL);
23     kill(getpid(),SIGABRT);
24     fflush(NULL);
25     action.sa_handler = SIG_DFL;
26     sigaction(SIGABRT,&action,NULL);
27     sigprocmask(SIG_SETMASK,&mask,NULL);
28     kill(getpid(),SIGABRT);
29     exit(1);
30 }
31 static void sig_abort(int signo)
32 {
33     printf("abort signal.\n");
34 }
35 
36 int main()
37 {
38     signal(SIGABRT,sig_abort);
39     myabort();
40     pause();
41     exit(0);
42 }

system

  POSIX.1要求system函数忽略SIGINT和SITQUIT信号,阻塞SIGCHLD。采用信号实现一个system函数,程序如下:

View Code 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <signal.h>
 7 
 8 int mysystem(const char*cmdstring)
 9 {
10     pid_t   pid;
11     int     status;
12     struct  sigaction   ignore,saveintr,savequit;
13     sigset_t    chldmask,savemask;
14 
15     if(cmdstring == NULL)
16         return 1;
17     ignore.sa_handler = SIG_IGN;
18     sigemptyset(&ignore.sa_mask);
19     ignore.sa_flags = 0;
20     if(sigaction(SIGINT,&ignore,&savequit)<0)
21     {
22         perror("sigaction() error");
23         exit(-1);
24     }
25     if(sigaction(SIGQUIT,&ignore,&savequit) <0)
26     {
27         perror("sigaction() error");
28         exit(-1);
29     }
30     sigemptyset(&chldmask);
31     sigaddset(&chldmask,SIGCHLD);
32     if(sigprocmask(SIG_BLOCK,&chldmask,&savemask) < 0)
33     {
34         perror("sigprocmask() error");
35         exit(-1);
36     }
37     if((pid = fork()) == -1)
38     {
39         perror("fork() error");
40         exit(-1);
41     }
42     else if(pid == 0)
43     {
44         sigaction(SIGINT,&saveintr,NULL);
45         sigaction(SIGQUIT,&savequit,NULL);
46         sigprocmask(SIG_SETMASK,&savemask,NULL);
47         execl("/bin/sh","sh","-c",cmdstring,(char *)0);
48         _exit(-127);
49     }
50     else
51     {
52         while(waitpid(pid,&status,0) < 0)
53         {
54             if(errno != EINTR)
55             {
56                 status = -1;
57                 break;
58             }
59         }
60     }
61     if (sigaction(SIGINT,&saveintr,NULL)<0)
62         return -1;
63 }
64 
65 int main()
66 {
67     printf("Pint date:\n");
68     mysystem("date");
69     printf("Print process:\n");
70     mysystem("ps");
71     exit(0);
72 }

sleep

此函数使调用进程被挂起,直到满足下列条件之一:(1)已经经过seconds所指定的墙上时钟时间(2)调用进程捕捉到一个信号并从信号处理程序返回。sleep的可靠实现如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <signal.h>
 7 
 8 static void sig_alrm(int signo)
 9 {
10 
11 }
12 
13 unsigned int mysleep(unsigned int nsecs)
14 {
15     struct sigaction    newact,oldact;
16     sigset_t            newmask,oldmask,suspmask;
17     unsigned int        unslept;
18 
19     newact.sa_handler = sig_alrm;
20     sigemptyset(&newact.sa_mask);
21     newact.sa_flags = 0;
22     sigaction(SIGALRM,&newact,&oldact);
23     sigemptyset(&newmask);
24     sigaddset(&newmask,SIGALRM);
25     sigprocmask(SIG_BLOCK,&newmask,&oldmask);
26     alarm(nsecs);
27     suspmask = oldmask;
28     sigdelset(&suspmask,SIGALRM);
29     sigsuspend(&suspmask);
30     unslept = alarm(0);
31     sigprocmask(SIG_SETMASK,&oldmask,NULL);
32     return unslept;
33 }
34 
35 int main()
36 {
37     int i;
38     printf("Program starting.\n");
39     printf("sleep 5 seconds.....\n");
40     for(i=1;i<=5;++i)
41     {
42         printf("The %dth second.\n",i);
43         mysleep(1);
44     }
45     printf("wake up.\n");
46     exit(0);
47 }

参考文献

  • https://www.jianshu.com/p/f445bfeea40a