您的位置:首页 > 运维架构 > Linux

【Linux系统编程】信号 (上)

2020-03-06 10:50 369 查看

00. 目录

文章目录

01. 信号概述

信号Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

“中断”在我们生活中经常遇到,譬如,我正在房间里打游戏,突然送快递的来了,把正在玩游戏的我给“中断”了,我去签收快递( 处理中断 ),处理完成后,再继续玩我的游戏。这里我们学习的“信号”就是属于这么一种“中断”。我们在终端上敲“Ctrl+c”,就产生一个“中断”,相当于产生一个信号,接着就会处理这么一个“中断任务”(默认的处理方式为中断当前进程)。

信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。

一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数。如下图所示:

注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。

02. 信号编号

Linux 可使用命令:

kill -l
(“l” 为字母),查看相应的信号。

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ 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
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$

列表中,编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义, 可靠信号则可以让用户自定义使用。

03. 信号产生方式

3.1 当用户按某些终端键时,将产生信号。

终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT,终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。

3.2 硬件异常将产生信号。

除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。

3.3 软件异常将产生信号。

当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。

3.4 调用 kill() 函数将发送信号。

注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

3.5 运行 kill 命令将发送信号。

此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。

04. kill发送信号

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

int kill(pid_t pid, int sig);
功能:给指定进程发送指定信号(不一定杀死)

参数:
pid : 取值有 4 种情况 :
pid > 0:  将信号传送给进程 ID 为pid的进程。
pid = 0 :  将信号传送给当前进程所在进程组中的所有进程。
pid = -1 : 将信号传送给系统内所有的进程。
pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。

返回值:
成功:0
失败:-1

注意:使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。

测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>

int main(void)
{
pid_t pid = -1;

pid = fork();
if (-1 == pid)
{
perror("fork");
goto err0;
}
else if (0 == pid)
{
while(1)
{
printf("child process do thing --- Hello uplooking\n");
sleep(1);
}

exit(0);
}

printf("parent process do thing\n");

sleep(3);

//向指定的进程发送指定的信号
//kill(pid, SIGKILL);
kill(pid, 9);

return 0;
err0:
return 1;
}

测试结果:

05. pause等待信号

#include <unistd.h>

int pause(void);
功能:
等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。
参数:
无。
返回值:
直到捕获到信号才返回 -1,且 errno 被设置成 EINTR。

测试代码:

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

int main(int argc, char *argv[])
{
printf("in pause function\n");
pause();

return 0;
}

测试结果:

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out
in pause function
^C
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$

没有产生信号前,进程一直阻塞在 pause() 不会往下执行,假如,我们按“Ctrl+c”,pause() 会捕获到此信号,中断当前进程。

06. 信号处理方式

一个进程收到一个信号的时候,可以用如下方法进行处理:

1)执行系统默认动作

对大多数信号来说,系统默认动作是用来终止该进程。

2)忽略此信号

接收到此信号后没有任何动作。

3)执行自定义信号处理函数

用用户定义的信号处理函数处理该信号。

注意:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。

产生一个信号,我们可以让其执行自定义信号处理函数。假如有函数 A, B, C,我们如何确定信号产生后只调用函数 A,而不是函数 B 或 C。这时候,我们需要一种规则规定,信号产生后就调用函数 A,就像交通规则一样,红灯停绿灯行,信号注册函数 signal() 就是做这样的事情。

07. 信号处理函数

#include <signal.h>

typedef void (*sighandler_t)(int);// 回调函数的声明
sighandler_t signal(int signum,sighandler_t handler);

功能:
注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。
此函数不会阻塞。

参数:
signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l
进行相应查看。

handler: 取值有 3 种情况:
SIG_IGN:忽略该信号
SIG_DFL:执行系统默认动作
信号处理函数名:自定义信号处理函数,如:fun

回调函数的定义如下:
void fun(int signo)
{
// signo 为触发的信号,为 signal() 第一个参数的值
}

注意:信号处理函数应该为可重入函数。

返回值:
成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,
必须在前面先声明此函数指针的类型。

失败:返回 SIG_ERR

测试程序一:

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

// 信号处理函数
void signal_handler(int signo)
{
if(signo == SIGINT){
printf("recv SIGINT\n");
}else if(signo == SIGQUIT){
printf("recv SIGQUIT\n");
}
}

int main(int argc, char *argv[])
{
printf("wait for SIGINT OR SIGQUIT\n");

/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */
// 信号注册函数
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);

// 等待信号
pause();
pause();

return 0;
}

在终端里敲“Ctrl+c”或“Ctrl+\”,自动调用其指定好的回调函数 signal_handler():

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out
wait for SIGINT OR SIGQUIT
^Crecv SIGINT
^\recv SIGQUIT
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$

测试程序二:

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

// 回调函数的声明
typedef void (*sighandler_t)(int);

void fun1(int signo)
{
printf("in fun1\n");
}

void fun2(int signo)
{
printf("in fun2\n");
}

int main(int argc, char *argv[])
{
sighandler_t previous = NULL;

// 第一次返回 NULL
previous = signal(SIGINT,fun1);
if(previous == NULL)
{
printf("return fun addr is NULL\n");
}

// 下一次返回此信号上一次注册的信号处理函数的地址。
previous = signal(SIGINT, fun2);
if(previous == fun1)
{
printf("return fun addr is fun1\n");
}

// 还是返回 NULL,因为处理的信号变了
previous = signal(SIGQUIT,fun1);
if(previous == NULL)
{
printf("return fun addr is NULL\n");
}

return 0;
}

执行结果:

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out
return fun addr is NULL
return fun addr is fun1
return fun addr is NULL
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$

08. 附录

8.1 参考博客:【linux系统编程】进程间通信:信号中断处理

  • 点赞 2
  • 收藏
  • 分享
  • 文章举报
沧海一笑-dj 博客专家 发布了641 篇原创文章 · 获赞 2566 · 访问量 91万+ 他的留言板 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: