第10章——《信号》

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012570105/article/details/82826441

实验环境介绍

  • gcc:4.8.5
  • glibc:glibc-2.17-222.el7.x86_64
  • os:Centos7.4
  • kernel:3.10.0-693.21.1.el7.x86_64

信号概念

概念

函数signal
  • SIGKILL和SIGSTOP不能被捕获
  • fork之后,子进程继承父进程的处理方式
  • 测试代码
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

static void
sig_int(int signo)
{
    switch (signo) {
    case SIGINT: printf("pid: %d get sigint signal\n", getpid()); break;
    default: printf("pid: %d get other signal\n", getpid()); break;
    }
}

int
main(int argc, char *argv[])
{
    if (signal(SIGINT, sig_int) == SIG_ERR) {
        printf("register signal INT error");
        exit(EXIT_FAILURE);
    }

    fork();
    while (1) {
        sleep(1);
    }
    return (0);
}

result:
[manjingliu@localhost part_10]$ ./signal 
pid: 38328 get sigint signal
pid: 38329 get sigint signal

[manjingliu@localhost ~]$ ps -ef | grep signal
manjing+ 38328 21634 0 12:46 pts/3 00:00:00 ./signal
manjing+ 38329 38328 0 12:46 pts/3 00:00:00 ./signal
manjing+ 38331 38276 0 12:46 pts/2 00:00:00 grep --color=auto signal
[manjingliu@localhost ~]$ kill -2 38328
[manjingliu@localhost ~]$ kill -2 38329
  • fork exec之后,不继承信号注册

不可靠信号

# 35号信号以前的都是不可靠信号
 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
  • 不可靠信号每次调用完信号处理函数后,除非再次设置该信号的信号处理函数,否则就将该信号设置为默认处理方式

但是在我的实验环境中并不需要重新设置,也能继续使用该信号处理函数

  • 不可靠信号不支持排队
    • 比如一个进程将SIGINT信号设置为阻塞,那么发送多个SIGINT信号,对于前面那个进程而言都只算做只收到一个SIGINT信号
  • 测试代码如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

void catch_signal(int signo, siginfo_t *info, void *p);

int main(int arg, char *args[])
{
    pid_t pid = 0;
    struct sigaction act;
    act.sa_sigaction = catch_signal;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;

    //注册SIGINT信号
    if (sigaction(SIGINT, &act, NULL) != 0) {
        printf("sigaction SIGINT failed !\n");
        return -1;
    }
    //注册SIGTMIN信号
    if (sigaction(SIGRTMIN, &act, NULL) != 0) {
        printf("sigaction SIGINT failed !\n");
        return -1;
    }

    //注册SIGUSR1信号
    if (sigaction(SIGUSR1, &act, NULL) != 0) {
        printf("sigaction SIGINT failed !\n");
        return -1;
    }

    //阻塞SIGINT信号和SIGTMIN信号
    sigset_t bset;
    sigemptyset(&bset);
    sigaddset(&bset, SIGINT);
    sigaddset(&bset, SIGRTMIN);

    //更新进程屏蔽信号状态字
    if (sigprocmask(SIG_BLOCK, &bset, NULL) != 0) {
        printf("sigprocmask() failed !\n");
        return -1;
    }

    pid = fork();
    if (pid == -1) {
        printf("fork() failed ! error message:%s\n", strerror(errno));
        return -1;
    }

    if (pid == 0) {
        int i = 0, ret = 0;
        union sigval v1;
        union sigval v2;

        for (i = 0; i < 3; i++) {
            v1.sival_int = 201 + i;
            ret = sigqueue(getppid(), SIGINT, v1);
            if (ret != 0) {
                printf("发送不可靠信号SIGINT失败! error message:%s\n", strerror(errno));
            } else {
                printf("发送不可靠信号SIGINT成功!\n");
            }
        }

        for (i = 0; i < 3; i++) {
            v2.sival_int = 301 + i;
            ret = sigqueue(getppid(), SIGRTMIN, v2);
            if (ret != 0) {
                printf("发送可靠信号SIGTMIN失败! error message:%s\n", strerror(errno));
            } else {
                printf("发送可靠信号SIGTMIN成功!\n");
            }
        }
        //发送SIGUSR1信号
        if (kill(getppid(), SIGUSR1) != 0) {
            printf("kill() failed ! error message;%s\n", strerror(errno));
        }
        exit(0);
    } else {
        int res = 0, status = 0;
        while (1) {
            res = wait(&status);
            if (res == -1) {
                if (errno == EINTR)
                    continue;
            }
            break;
        }
    }

    return 0;
}

void
catch_signal(int signo, siginfo_t *info, void *p)
{
    switch (signo) {
    case SIGINT:
        printf("accept SIGINT! recv data=%d\n",info->si_value.sival_int);
        break;
    case 34:
        //SIGRTMIN似乎不是一个确定的int类型
        printf("accept SIGRTMIN! recv data=%d\n",info->si_value.sival_int);
        break;
    case SIGUSR1:
        printf("accept SIGUSR1!\n");

        //取消信号阻塞
        sigset_t uset;
        sigemptyset(&uset);
        sigaddset(&uset, SIGINT);
        sigaddset(&uset, SIGRTMIN);
        sigprocmask(SIG_UNBLOCK, &uset, NULL);
        printf("阻塞解除了!\n");
        break;
    }
}
result:
[manjingliu@localhost part_10]$ ./signal 
发送不可靠信号SIGINT成功!
发送不可靠信号SIGINT成功!
发送不可靠信号SIGINT成功!
发送可靠信号SIGTMIN成功!
发送可靠信号SIGTMIN成功!
发送可靠信号SIGTMIN成功!
accept SIGUSR1!
accept SIGRTMIN! recv data=301
accept SIGRTMIN! recv data=302
accept SIGRTMIN! recv data=303
accept SIGINT! recv data=201
阻塞解除了!

中断的系统调用

  • 系统调用分为两种:低速系统调用和其他系统调用。低速系统调用是会使进程永远阻塞的一类系统调用,包括:
    • 某些类型文件(如读管道、终端设备和网络设备)的数据不存在,则可能使调用者永远被阻塞
    • 如果数据不能被相同的类型立即接受,则写操作可能会使调用者永远被阻塞
    • 在某种条件发生之前打开某些类型文件,可能发生阻塞(比如打开一个终端I/O,需要先等待与之连接的调制解调器应答)
    • pause函数和wait函数
    • 某些ioctl函数
    • 某些进程间通信函数(第15章再来讨论)

注意在这些低速系统调用中,一个值得注意的例外是与磁盘I/O有关的系统调用。虽然读写一个磁盘文件可能暂时性阻塞调用者(在磁盘驱动程序将请求排入队列,然后再适当时间执行请求期间)。但是除非发生硬件错误,I/O操作总会很快返回,并使调用者不再阻塞状态

重启系统调用

  • 与信号相关的函数的实现语义
    语义

可重入函数

  • 在进行信号处理时,进程正在执行的正常指令就被信号处理程序临时中断,他首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用exit或longjmp),则继续执行在捕获到信号时进程正在执行的正常指令序列
  • 在信号处理程序中,不能判断捕捉到信号时进程时进程执行到何处。如果进程正在malloc,然后这时捕捉到信号,然后再信号处理程序中我们又进行malloc。这样会对进程造成破坏。这些函数会使用一些全局的变量,从而在信号处理程序中可能会破坏数据。
  • 在信号处理程序中可以调用的可重入的函数如下:
    可重入函数
  • 上述没有列出的大多数函数都是不可重入的
    • 因为他们使用静态数据结构,无论是全局静态变量还是局部静态变量。
    • 他们调用malloc或者free
    • 他们是标准I/O函数,标准I/O库的很多实现都以不可重入方式使用全局数据结构

如果在信号处理函数中调用上图的函数时,应该在调用前保存errno,然后在调用后恢复(在捕获SIGCHLD信号的时候,通常会调用wait,此函数会更新errno,所以也得处理恢复)。如果主例程正在以非可重入方式更新一个数据结构可能产生信号。如果不是从信号处理函数返回,而是调用siglongjmp,那么那个数据结构可能是部分更新的。如果要做到全局更新这个数据结构,而同时要捕获信号进行处理,而这些信号处理函数又要调用siglongjmp,那么在更新这个数据结构的时候要阻塞此类信号
* 函数返回静态变量
* 函数中调用了不可重入函数
* 函数是singleton中的成员函数,而且使用了不使用线程独立存储的成员变量
* 在许多编译器/处理器中,浮点数是不可重入的。有些不允许在ISR中做浮点运算。

SIGCHLD信号

  • SIGCLD的早期处理方式:
    • 如果把这个信号设置为SIG_IGN,则调用进程的子进程将不产生僵尸进程。子进程终止的时,将其状态丢弃。如果调用进程随后调用一个wait函数,那么它将阻塞直到所有子进程都终止,然后wait会返回-1,并将其errno设置为ECHILD。
    • 如果将SIGCHLD设置为捕获,则内核立即检查是否有子进程准备好被等待,如果是这样则调用SIGCHLD的处理程序
  • 以下代码可能在早期SIGCLD、signal语义实现上运行有问题
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdarg.h>

static void	sig_cld(int);

#define MAXLINE 1024

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
 char	buf[MAXLINE];

 vsnprintf(buf, MAXLINE-1, fmt, ap);
 if (errnoflag)
  snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
    strerror(error));
 strcat(buf, "\n");
 fflush(stdout);	/* in case stdout and stderr are the same */
 fputs(buf, stderr);
 fflush(NULL);	/* flushes all stdio output streams */
}

void
err_msg(const char *fmt, ...)
{
 va_list	ap;

 va_start(ap, fmt);
 err_doit(0, 0, fmt, ap);
 va_end(ap);
}

int
main()
{
 pid_t	pid;

 if (signal(SIGCLD, sig_cld) == SIG_ERR)
  err_msg("signal error");
 if ((pid = fork()) < 0) {
  err_msg("fork error");
 } else if (pid == 0) {	/* child */
  sleep(2);
  _exit(0);
 }

 pause();	/* parent */
 exit(0);
}

static void
sig_cld(int signo)	/* interrupts pause() */
{
 pid_t	pid;
 int	status;

 printf("SIGCLD received\n");

// 这里可能有问题,早期的signal,
// 在调用的时候会检查内核中是否有子进程结束进行信号处理函数的调用
// 这里会一直循环调用sig_cld
// 解决方案是:先进行wait再用signal的注册
// 实际上现在的实现标准,应该不用再次注册信号处理函数
 if (signal(SIGCLD, sig_cld) == SIG_ERR)	/* reestablish handler */
  perror("signal error");

 if ((pid = wait(&status)) < 0)	/* fetch child status */
  perror("wait error");

 printf("pid = %d\n", pid);
}

可靠信号术语与语义

  • 向进程递送一个信号时,信号产生和递送之间的时间间隔内,称信号是未决的(pending)
  • 进程可以将信号阻塞在信号队列里面,尽管对该信号的处理是默认或者捕获。直到进程忽略该信号或者解除该信号的阻塞,该信号才解除了阻塞。
  • 内核在把信号递送给进程时,才决定对这个信号的处理
  • 调用sigpending函数可以返回队列中的未决信号
  • 如果在对某个信号进行了阻塞,如果该信号是不支持POSIX.1的实时扩展,那么尽管这个信号发生了多次,队列中仍然只有一个该种信号递送给该进程,则称该信号不支持排队。如果支持POSIX.1的实时扩展,则称这信号支持排队,内核会将产生的多个该信号都递送给该进程
  • 注意,不可排队信号已经在被处理的时候,如果此时又来一个相同的信号,那么这个信号会放在未决队列里面,只是该进程只能存在1个这种信号。测试代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdarg.h>


#define MAXLINE 1024

static void
sig_int(int signo);

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
 char	buf[MAXLINE];

 vsnprintf(buf, MAXLINE-1, fmt, ap);
 if (errnoflag)
  snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
    strerror(error));
 strcat(buf, "\n");
 fflush(stdout);	/* in case stdout and stderr are the same */
 fputs(buf, stderr);
 fflush(NULL);	/* flushes all stdio output streams */
}

void
err_msg(const char *fmt, ...)
{
 va_list	ap;

 va_start(ap, fmt);
 err_doit(0, 0, fmt, ap);
 va_end(ap);
}

int
main()
{
 pid_t	pid;

 if (signal(SIGINT, sig_int) == SIG_ERR)
  err_msg("signal error");
	
 while (1)
  pause();	/* parent */
 exit(0);
}

static void
sig_int(int signo)	/* interrupts pause() */
{
 pid_t	pid;
 int	status;

 printf("SIGINT received\n");

 sleep(10);
}

result:
[root@localhost part_10]# ./interrupt 
^CSIGINT received                // 这里立马处理了一个INT信号
^C^C^C^C^C^C^C^C^C^C^C^C^CSIGINT received //信号处理函数睡眠10秒,此时信号处理函数没有返回,发送多个INT信号
^CSIGINT received            // 尽管发送了多个,但是未决队列只能存在一个,
  • 关于sigprocmask介绍的一个理解
    • apue中有这么一句话:在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>

void sig_int( int ); 
void pr_mask( const char *);	//call to funtion pr_mask
void oops(void *msg);

int main(int argc , char *argv[]){
 
 setbuf( stdout , NULL );	//close buffer
 pr_mask( "before catching signal ,mask sig :" );
 
 if( signal( SIGINT , sig_int ) == SIG_ERR )
  oops( "signal" );
	
 //keep loop to catch signal interuption
 while(1)
 //	pause();
  sleep( 3 );
 
 pr_mask( "end catching signal ,mask sig :" );
 
 return 0;
}
 
//signal handler
void sig_int( int signo ){
 pr_mask( "in signal handler:" );
}

void pr_mask( const char *str ){
 sigset_t set;
 int errno_save;	//get the pre errno
 
 errno_save = errno;
 
 if( sigprocmask( 0, NULL , &set ) == -1 )
  oops( " sigmask" );
 else{
  printf( "\n%s" , str );
  if( sigismember( &set , SIGQUIT ) )
   printf( " SIGQUIT" );
  if( sigismember( &set , SIGINT ) )
   printf( " SIGINT" );
  if( sigismember( &set , SIGUSR1 ) )
   printf( " SIGUSR1" );
  if( sigismember( &set , SIGALRM ) )
   printf( " SIGALRM" );
 }
 errno = errno_save ;
}

 
void oops(void *msg){
 perror(msg);
 exit(1);
}

[root@localhost part_10]# ./interrupt 

before catching signal ,mask sig :^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT^C
in signal handler: SIGINT
  • 也就是说,因为程序中我们并没有屏蔽中断信号,而当进程在处理中断信号的时候是屏蔽了接下来的中断信号,所以在信号处理函数中会有SIGINT出现, 而当信号处理函数完成,准确的说是sigprocmask完成前, 接下来的SIGINT都会被接收处理.

  • 对于sigsuspend函数的测试

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

void 
un_int()
{
    printf("\nsigsuspend catch SIGINT\n");
    printf("The sigsuspend already restore the old mask for sigprocmask.Now still in the signal handle,next will sleep 5 seconds you can press ctrl-c to test\n");
    sleep(5);
    return;
}

int 
main(int argc, char *argv[])
{
    sigset_t set;
    
    // include all signals
    sigfillset(&set);
    
    // register cb
    signal(SIGINT,fun_int);

    // bock all signals
    sigprocmask(SIG_SETMASK,&set,NULL);
    
    // 去除INT信号
    sigdelset(&set,SIGINT);
    printf("Here is the sigprocmask signal block, before use sigsuspend\n");
    sleep(5); // 这里就可以触发信号了
    printf("\nsleep over,next is sigsuspend\n");

    // 重新设置没有INT信号集的阻塞信号集,并等待信号递送到进程
    sigsuspend(&set);
    printf("\nI have return from the handler.I will sleep 5 seconds You can press ctrl-c to send sigint to test the mask of SIGINT restore from sigsuspend for sigprocmask\n");
    sleep(10);
    printf("\nTest finish\n");
    return 0;
}

result:
[root@localhost part_10]# 
[root@localhost part_10]# ./interrupt 
Here is the sigprocmask signal block, before use sigsuspend
^C^C^C^C^C^C^C
sleep over,next is sigsuspend

sigsuspend catch SIGINT
The sigsuspend already restore the old mask for sigprocmask.Now still in the signal handle,next will sleep 5 seconds you can press ctrl-c to test

I have return from the handler.I will sleep 5 seconds You can press ctrl-c to send sigint to test the mask of SIGINT restore from sigsuspend for sigprocmask

Test finish

猜你喜欢

转载自blog.csdn.net/u012570105/article/details/82826441