信号发送与处理

问题

按下 CTRL + C之后,命令行中的前台进程会被终止。为什么???

什么是信号?

信号是一种“软件中断”,用来处理异步事件

  1. 内核发送信号到某个进程,通知进程事件的发生
  2. 事件可能来自硬件,可能来自用户输入,可能来自除零错误

信号是一种类型的进程间通信方式(一个进程向另外一个进程发送信号)

  1. A进程发送事件T,向B进程发送信号,B进程执行动作响应事件
  2. 进程可以对接收的不同信号进行不同动作响应(信号 -> 处理)

信号的分类

硬件异常

        内核检测到硬件错误,发送相应信号给相关进程

终端信号(用户交互信号,最典型:ctrl+c)

        在终端输入 “特殊字符” 等价于向前台进程组发送信号

软件信号

        在软件层面(进程代码中)触发的信号(发送给自身或其他进程)

硬件异常信号

信号 说明
SIGBUS 7 总线错误,进程发生了内存访问错误
SIGFPE 8 算术错误,FPE表示浮点异常
SIGILL 9 指令错误,进程尝试执行非法指令
SIGSEGV 11 段错误,进程访问了非法内存区域

终端相关信号

  1. SIGINT(Ctrl + c) :程序终止信号,用于通知前台进程组终止进程
  2. SIGQUIT(Ctrl + \) :与SIGINT类似,进程收到该信号退出时可产生 coredump 文件
  3. SIGTSTP(Ctrl + Z) :停止进程的运行,进程收到该信号后可以选择处理或忽略。Ctrl + Z :进程收到该信号后停止运行(状态发生转换),后续可以恢复运行状态。

软件相关信号

  1. 子进程退出:父进程收到 SIGCHLD 信号
  2. 父进程退出:子进程可能收到信号(什么信号?)
  3. 定时器到期:alarm(),ualarm(),timer_create(),...
  4. 主动发信号:kill(),raise(),...

内核与信号

1.内核检测到相关的错误,将信号发送给对应的进程。

2.进程可以通过系统调用,告诉内核,自己需要信号。比如:时钟信号。比如进程A告诉内核,我需要时钟信号,那么内核就定期给进程A发送时钟信号。

3.进程间通信:进程A通过内核 将信号 发送给进程B

信号的发送与处理,无非就是以上三种情况。

信号的默认处理

默认处理方式 说明 示例
ignore 进程丢弃信号不会产生任何影响 SIGCHLD SIGURG
terminate 终止进程 SIGKILL SIGHUP
coredump 终止进程并产生转储文件 SIGQUIT SIGILL
stop/continue 停止进程执行/恢复进程执行 SIGSTOP,SIGCONT

知识加油站:System V vs BSD

System V :也被称为 AT&T SystemV,是Unix操作系统众多版本中的一支

BSD :加州大学巴克利分校开创,Unix衍生系统,代表由此派生出的各种套件集合

Linux之所以被称为类Unix操作系统(Unix Like),部分原因就是Linux的操作风格是介于上述二者之间,且不同的厂商为了照顾不同的用户,其发行版本的操作风格有存在差异。

自定义信号发送

#include

#include

int kill(pid_t pid,int sig);

//pid > 0 ==> 发送信号给进程 ,pid == 0 ==> 发送信号给进程组 ,pid < -1  比如-1000,将信号发送给PGID为-1000的进程组

int raise(int sig); //信号处理完毕才返回

notes:

标准信号是unix系统中的信号,编号范围从1到31。

实时信号是Linux独有的信号,编号范围从32到64。

下面来看几个demo示例。

signal

信号发送与处理_第1张图片

 可以看到在终端按下 CTRL + C 之后,信号处理函数 signal_handler就不断地调用。

sysv_signal

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    //signal(SIGINT,signal_handler);
    sysv_signal(SIGINT,signal_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Chandler : sig = 2
^C
zhaixue@ubuntu:~/DTLinux/0-11$ 

bsd_signal

//#define _GNU_SOURCE
#define  _XOPEN_SOURCE  600
#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    //signal(SIGINT,signal_handler);
    //sysv_signal(SIGINT,signal_handler);
    bsd_signal(SIGINT,signal_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$ 

发送自定义信号

由于信号在表现形式上是一个整数值,所以将值为40的信号映射到 信号处理函数 signal_handler上来。

main.c

//#define _GNU_SOURCE
#define  _XOPEN_SOURCE  600
#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    signal(SIGINT,signal_handler);
    signal(40,signal_handler);
    //sysv_signal(SIGINT,signal_handler);
    //bsd_signal(SIGINT,signal_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}

想发送值为40的信号给信号处理函数。signal(40,signal_handler);

test.c

#include 
#include 
#include 
#include 
#include 

int main(int argc,char* argv[])
{
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    printf("send sig(%d) to process(%d)...\n",sig,pid);

    kill(pid,sig); //send signal

    raise(SIGINT); //end execute

    while(1)
    {
        printf("while...\n");  
        sleep(1);
    }

    return 0;
}

编译输出:

zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out &
[1] 9437
zhaixue@ubuntu:~/DTLinux/0-11$ gcc test.c -o test.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./test.out 9437 40
send sig(40) to process(9437)...
handler : sig = 40

zhaixue@ubuntu:~/DTLinux/0-11$ 

思考

以上signal、sysv_signal、bsd_signal 三种自定义信号处理函数有什么区别?

信号的OneShot特性

System V风格的signal函数,注册的信号处理是一次性的。

  1. 进程收到信号后,调用由signal注册的处理函数。
  2. 处理函数一旦执行,进程通过默认的方式处理后续相同信号。
  3. 如果想要重复触发,那么必须再次调用signal注册函数。
  4. BSD 风格的 signal 函数不存在OneShot,能够自动反复触发处理函数的调用。

信号的自身屏蔽特性

在信号处理函数执行期间,很有可能再次收到当前信号

        即:处理A信号的时候,再次收到A信号

对于 System V风格的 signal 函数,会引起信号处理函数的重入

        即:调用处理函数的过程中,再次触发处理函数的调用

在注册信号处理函数时:

        System V风格的 signal 不屏蔽任何信号

        BSD风格的 signal 会屏蔽当前注册的信号

        问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?

系统调用重启特性

        系统调用期间,可能受到信号,此时进程必须从系统调用中返回

        对于执行时间较长的系统调用(write / read),被信号中断的可能性很大

                如果希望信号处理之后,被中断的系统调用能够重启,则:

                可以通过条件 errno == EINTR 判断重启系统调用

 

-System V风格的 signal 函数(只能手动重启)

        系统调用被信号中断后,直接返回 -1,并且 errno == EINTR

-BSD风格的 signal 函数(自动重启)

        系统调用被中断,内核在信号处理函数结束后,自动重启系统调用

实验OneShot特性

OneShot特性,只有System V风格的 signal 函数才有。

main.c

#define _GNU_SOURCE
//#define  _XOPEN_SOURCE  600
//#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    //signal(SIGINT,signal_handler);
    //signal(40,signal_handler);
    sysv_signal(SIGINT,signal_handler);
    //bsd_signal(SIGINT,signal_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}

编译运行:

zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Chandler : sig = 2
^C
zhaixue@ubuntu:~/DTLinux/0-11$ 

可以看到,只按下了一次 ctrl + c ,进程就退出了。因为 sysv_signal(SIGINT,signal_handler); 是一次性的,一旦 signal_handler 信号处理函数被调用了,那么这个信号处理函数:signal_handler 就已失效了。

接下来,我们接着实验 BSD风格的信号处理函数。

//#define _GNU_SOURCE
#define  _XOPEN_SOURCE  600
#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    //signal(SIGINT,signal_handler);
    //signal(40,signal_handler);
    //sysv_signal(SIGINT,signal_handler);
    bsd_signal(SIGINT,signal_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$ 

可以看到每次按下 ctrl + c,就会调用一次 信号处理函数。Linux中默认的信号处理函数 signal 和 BSD风格的信号处理函数行为是一致的。

信号重入实验

main.c

#define _GNU_SOURCE
//#define  _XOPEN_SOURCE  600
//#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 

void delay_handler(int sig)
{
    int i = 0;

    printf("begin delay handler...\n");

    for(i=0;i<5;i++)
    {
        printf("sleep %d ...\n",i);
        sleep(1);
    }

    printf("end delay handler...\n");
}

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    //signal(SIGINT,signal_handler);
    //signal(40,signal_handler);
    sysv_signal(SIGINT,delay_handler);
    //bsd_signal(SIGINT,signal_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^C
zhaixue@ubuntu:~/DTLinux/0-11$ 

可以看到这一次 oneShot特性。

zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
^C
zhaixue@ubuntu:~/DTLinux/0-11$ 

来修改一下 void delay_handler(int sig) 函数,在void delay_handler(int sig)函数中再一次调用 sysv_signal(SIGINT,delay_handler); 那么就相当于去掉OneShot特性。

#define _GNU_SOURCE
//#define  _XOPEN_SOURCE  600
//#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 

void delay_handler(int sig)
{
    int i = 0;

    sysv_signal(SIGINT,delay_handler);

    printf("begin delay handler...\n");

    for(i=0;i<5;i++)
    {
        printf("sleep %d ...\n",i);
        sleep(1);
    }

    printf("end delay handler...\n");
}

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    //signal(SIGINT,signal_handler);
    //signal(40,signal_handler);
    sysv_signal(SIGINT,delay_handler);
    //bsd_signal(SIGINT,signal_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}

编译运行:

zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
sleep 3 ...
sleep 4 ...
end delay handler...
sleep 3 ...
sleep 4 ...
end delay handler...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$ 

可以看到OneShot特性相当于已经没有了。

接下来看 BSD风格的信号处理函数,会有什么样的结果。

main.c

//#define _GNU_SOURCE
#define  _XOPEN_SOURCE  600
#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 

void delay_handler(int sig)
{
    int i = 0;

    //sysv_signal(SIGINT,delay_handler);

    printf("begin delay handler...\n");

    for(i=0;i<5;i++)
    {
        printf("sleep %d ...\n",i);
        sleep(1);
    }

    printf("end delay handler...\n");
}

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int main(void)
{
    //signal(SIGINT,signal_handler);
    //signal(40,signal_handler);
    //sysv_signal(SIGINT,delay_handler);
    bsd_signal(SIGINT,delay_handler);

    while(1)
    {
        sleep(1);
    }

    return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
^Csleep 3 ...
sleep 4 ...
end delay handler...
begin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
begin delay handler...
sleep 0 ...
^Csleep 1 ...
sleep 2 ...
^Csleep 3 ...
sleep 4 ...
end delay handler...
begin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$ 

可以看到不会发生重入,必须等待当前的信号处理函数处理完,才能够处理下一次的信号。当下一次信号来的时候,必须排队等着。

Linux 默认的 signal 信号处理函数 和 BSD风格的 bsd_signal(SIGINT,delay_handler) 信号处理函数是一致的。

系统调用重启实验

main.c

#define _GNU_SOURCE
//#define  _XOPEN_SOURCE  600
//#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 
#include 

void delay_handler(int sig)
{
    int i = 0;

    //sysv_signal(SIGINT,delay_handler);

    printf("begin delay handler...\n");

    for(i=0;i<5;i++)
    {
        printf("sleep %d ...\n",i);
        sleep(1);
    }

    printf("end delay handler...\n");
}

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int r_read(char* data,int len)
{
    int ret = -1;

    while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
    {
        printf("restart syscall mannually...\n");
    }

    if(ret != -1)
    {
        data[ret] = 0;
    }

    return ret;
}

int main(void)
{
    char buf[32]={0};
    //signal(SIGINT,delay_handler);
    //signal(40,signal_handler);
    sysv_signal(SIGINT,signal_handler);
    //bsd_signal(SIGINT,delay_handler);

    r_read(buf,32);

    printf("input: %s\n",buf);

    return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
1234^Chandler : sig = 2
restart syscall mannually...
test
input: test

zhaixue@ubuntu:~/DTLinux/0-11$ 

可以看到 read 系统调用,被信号给中断了。

实验 BSD 风格的 signal 函数。

//#define _GNU_SOURCE
#define  _XOPEN_SOURCE  600
#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 
#include 

void delay_handler(int sig)
{
    int i = 0;

    //sysv_signal(SIGINT,delay_handler);

    printf("begin delay handler...\n");

    for(i=0;i<5;i++)
    {
        printf("sleep %d ...\n",i);
        sleep(1);
    }

    printf("end delay handler...\n");
}

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int r_read(char* data,int len)
{
    int ret = -1;

    while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
    {
        printf("restart syscall mannually...\n");
    }

    if(ret != -1)
    {
        data[ret] = 0;
    }

    return ret;
}

int main(void)
{
    char buf[32]={0};
    //signal(SIGINT,signal_handler);
    //signal(40,signal_handler);
    //sysv_signal(SIGINT,signal_handler);
    bsd_signal(SIGINT,signal_handler);

    r_read(buf,32);

    printf("input: %s\n",buf);

    return 0;
}
zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
1234^Chandler : sig = 2
^Chandler : sig = 2
^Chandler : sig = 2
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$ 

可以看到,BSD风格的 signal 函数会自动重启系统调用。和 sysv 风格的信号函数不一样,没有打印 restart syscall mannually... 出来。

默认风格的 signal 函数 和 BSD风格的 signal 函数一样,会自动重启系统调用。

问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?会不会重入?

main.c

//#define _GNU_SOURCE
#define  _XOPEN_SOURCE  600
#define _POSIX_C_SOURCE 200800L

#include 
#include 
#include 
#include 
#include 
#include 

void delay_handler(int sig)
{
    int i = 0;

    //sysv_signal(SIGINT,delay_handler);

    printf("begin delay handler...\n");

    for(i=0;i<5;i++)
    {
        printf("sleep %d ...\n",i);
        sleep(1);
    }

    printf("end delay handler...\n");
}

void signal_handler(int sig)
{
    printf("handler : sig = %d\n",sig);
}

int r_read(char* data,int len)
{
    int ret = -1;

    while(data && ((ret = read(STDIN_FILENO,data,len-1)) == -1) && (errno == EINTR))
    {
        printf("restart syscall mannually...\n");
    }

    if(ret != -1)
    {
        data[ret] = 0;
    }

    return ret;
}

int main(void)
{
    char buf[32]={0};
    //signal(SIGINT,signal_handler);
    bsd_signal(40,signal_handler);
    //sysv_signal(SIGINT,signal_handler);
    bsd_signal(SIGINT,delay_handler);

    r_read(buf,32);

    printf("input: %s\n",buf);

    return 0;
}

test.c

#include 
#include 
#include 
#include 
#include 

int main(int argc,char* argv[])
{
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    printf("send sig(%d) to process(%d)...\n",sig,pid);

    kill(pid,sig); //send signal

    raise(SIGINT); //end execute

    while(1)
    {
        printf("while...\n");  
        sleep(1);
    }

    return 0;
}

zhaixue@ubuntu:~/DTLinux/0-11$ gcc main.c -o main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./main.out 
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
handler : sig = 40
sleep 3 ...
sleep 4 ...
end delay handler...
^Cbegin delay handler...
sleep 0 ...
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
end delay handler...
^\Quit (core dumped)
zhaixue@ubuntu:~/DTLinux/0-11$ 

zhaixue@ubuntu:~/DTLinux/0-11$ gcc test.c -o test.out
zhaixue@ubuntu:~/DTLinux/0-11$ ps -ajx | grep main.out
   8966   11517   11517    8966 pts/0      11517 S+    1000   0:00 ./main.out
  11392   11524   11523   11392 pts/1      11523 S+    1000   0:00 grep --color=auto main.out
zhaixue@ubuntu:~/DTLinux/0-11$ ./test.out 11517 40
send sig(40) to process(11517)...

zhaixue@ubuntu:~/DTLinux/0-11$ 

问题:BSD风格的 signal 函数,处理A信号期间,如果收到B信号会发生什么?会不会重入?

会立即马上的处理B信号,处理完B信号之后,马上返回处理A信号。

注意事项

-并非所有的系统调用对信号中断都表现同样的行为

        一些系统调用支持信号中断后自动重启

        read()、write()、wait()、waitpid()、ioctl() ...

        一些系统调用完全不支持中断后自动重启

        poll()、select()、usleep()、...

结论

函数 OneShot 屏蔽自身 重启系统调用
signal(...) false true true
sysv_signal(...) yes false false
bsd_signal(...) false true true

在信号处理上,Linux系统更接近 BSD风格的操作;默认的signal函数在不同的 Linux发行版上语义可能不同,从代码移植行角度,避免直接使用 signal(...)函数。

你可能感兴趣的:(#,Linux,系统/文件编程,Linux系统,信号发送与处理)