信号发送
信号是 Linux 系统响应某些条件而产生的一个事件, 接收到该信号的进程会执行相应的操作。
信号的产生有三种方式:
1)由硬件产生, 如从键盘输入 Ctrl+C 可以终止当前进程
2)由其他进程发送, 如可在 shell 进程下, 使用命令 kill -信号标号 PID, 向指定进程发送信号。
3)异常, 进程异常时会发送信号
本篇只关注在应用层对信号的处理。 在 Ubuntu 终端输入 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
信号 | 说明 | 默认操作 |
SIGABRT | 由 abort()发送 | 终止且进行内存转储 |
SIGALRM | 由 alarm()发送 | 终止 |
SIGBUS | 硬件或对齐错误 | 终止且进行内存转储 |
SIGCHLD | 子进程终止 | 忽略 |
SIGCONT | 进程停止后继续执行 | 忽略 |
SIGFPE | 算术异常 | 终止且进行内存转储 |
SIGHUP | 进程的控制终端关闭(最常见的是 用户登出) | 终止 |
SIGILL | 进程试图执行非法指令 | 终止且进行内存转储 |
SIGINT | 用户产生中断符(Ctrl-C) | 终止 |
SIGIO | 异步 IO 事件(Ctrl-C) | 终止(a) |
SIGKILL | 不能被捕获的进程终止信号 | 终止 |
SIGPIPE | 向无读取进程的管道写入 | 终止 |
SIGPROF | 向无读取进程的管道写入 | 终止 |
SIGPWR | 断电 | 终止 |
SIGQUIT | 用户产生退出符(Ctrl-\) | 终止且进行内存转储 |
SIGSEGV | 无效内存访问 | 终止且进行内存转储 |
SIGSTKFLT | 协处理器栈错误 | 终止(b) |
SIGSTOP | 挂起进程 | 停止 |
SIGSYS | 进程试图执行无效系统调用 | 终止且进行内存转储 |
SIGTERM | 可以捕获的进程终止信号 | 终止 |
SIGTRAP | 进入断点 | 终止且进行内存转储 |
SIGSTP | 用户生成挂起操作符(Ctrl-Z) | 停止 |
SIGTTIN | 后台进程从控制终端读 | 停止 |
SIGTTOU | 后台进程向控制终端写 | 停止 |
SIGURG | 紧急 I/O 未处理 | 忽略 |
SIGUSR1 | 进程自定义的信号 | 终止 |
SIGUSR2 | 进程自定义的信号 | 终止 |
SIGVTALRM | 用 ITIMER_VIRTUAL 为参数调 用 setitimer()时产生 | 终止 |
SIGWINCH | 控制终端窗口大小改变 | 忽略 |
SIGXCPU | 进程资源超过限制 | 终止且进行内存转储 |
SIGXFSZ | 文件资源超过限制 | 终止且进行内存转储 |
下面是几个常用的函数:
函数 | int kill(pid_t pid, int sig) |
头文件 | #include <sys/types.h> #include <signal.h> |
参数 pid | 大于 0, 时为向 PID 为 pid 的进程发送信号 等于 0, 向同一个进程组的进程发送信号; 等于-1, 除发送进程自身外, 向所有进程 ID 大于 1 的进程发送信号。 小于-1, 向组 ID 等于该 pid 绝对值的进程组内所有进程发送信号。 |
参数 sig | 设置发送的信号; 等于 0 时为空信号, 无信号发送。 常用来进行错误检 查。 |
返回值 | 执行成功时, 返回值为 0; 错误时, 返回-1, 并设置相应的错误代码 errno |
功能 | 用于向任何进程组或进程发送信号。 |
函数 | int raise(int sig) |
头文件 | #include <signal.h> |
参数 sig | 信号 |
功能 | 相当于 kill(getpid(),sig), 向进程自身发送信号 |
函数 | unsigned int alarm(unsigned int seconds) |
头文件 | #include <unistd.h> |
参数 | 设定的时间 |
功能 | 设定的时间超过后产生 SIGALARM 信号, 默认动作是终止进程。 |
注意 | 每个进程只能有一个 alarm()函数, 时间到后要想再次使用要重新注册。 |
使用规则:
实验 1 代码: 在程序中实现: 自己给自己发送信号。
raise.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(void)
{
printf("raise before\n");
raise(9);
printf("raise after\n");
return 0;
}
编译运行, 如下图所示:
实验 2 代码 kill.c 发送信号。
kill.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
pid_t pid;
int sig;
if(argc < 3){
printf("Usage:%s <pid_t> <signal>\n",argv[0]);
return -1;
}
sig = atoi(argv[2]);
pid = atoi(argv[1]);
kill(pid,sig);
return 0;
}
test.c
#include <stdio.h>
#include <unistd.h>
void main(void)
{
while(1){
sleep(1);
printf("hello world\n");
}
}
编译运行 test, 如下图所示, 进程会循环打印 hello world。
重新打开另一个窗口, 编译 kill.c, 然后查看 test 进程的 pid 号, 运行测试如下图所示;
与此同时, 显示 test 的窗口显示, test 进程被杀死, 如下图所示:
实验 3 代码 alarml.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int i;
alarm(3);
while(1){
sleep(1);
i++;
printf("i = %d\n",i);
}
return 0;
}
编译 alarm.c,并运行。 如下图所示, 设定的时间(3 秒) 超过后产生 SIGALARM 信号, 默认动作是终止进程。
信号接收
接收信号: 如果要让我们接收信号的进程可以接收到信号, 那么这个进程就不能停止。 让进程不停止有三种方法:
while
sleep
pause
方法一;
while.c
#include <stdio.h>
#include <unistd.h>
void main(void)
{
while(1){
sleep(1);
printf("hello world\n");
}
}
编译运行结果如下所示, 按 ctrl+C 会发送 SIGINT 信号:
方法二;
sleep.c
#include <stdio.h>
#include <unistd.h>
void main(void)
{
sleep(60);
}
编译运行, 如下图所示, 会休眠 60s.
方法三
使用 pause()函数, 函数详解如下:
函数 | int pause(void) |
头文件 | #include <unistd.h> |
返回值 | 进程被信号中断后一直返回-1 |
功能 | 将进程挂起, 等待信号 |
pause.c
#include <stdio.h>
#include <unistd.h>
void main(void)
{
printf("pause before\n");
pause();
printf("pause after\n");
}
编译程序并运行, 如下图所示:
输入以下命令查看进程, 如下图所示:
按 ctrl+C 键, pause 进程终止, 再次查看 pause 的进程, 如下图所示:
信号处理
信号是由操作系统来处理的, 说明信号的处理在内核态。 信号不一定会立即被处理, 此时会储存在信号的信号表中。处理过程示意图:
由上图中可看出信号有三种处理方式:
1.默认方式(通常是终止进程) ,
2.忽略, 不进行任何操作。
3.捕捉并处理调用信号处理器(回调函数形式) 。
函数 | sighandler_t signal(int signum, sighandler_t handler); 可以简化成 signal(参数 1, 参数 2); |
头文件 | #include <unistd.h> typedef void (*sighandler_t)(int); |
参数 1 | 我们要进行处理的信号, 系统的信号我们可以在终端键入 kill -l 查看。 |
参数 2 | 处理的方式(是系统默认还是忽略还是捕获) 忽略该信号, 填写“SIG_IGN” ; 采用系统默认方式处理该信号, 填写“SIG_DFL” ; 捕获到信号后执行此函数内容, 定义格式为“typedef void (*sighandler_t)(int)” , sighandler_t 代表一个函数指针。 |
返回值 | 调用成功返回最后一次注册信号调用 signal()时的 handler 值; 失败返回 SIG_ERR。 |
功能 | 改变收到信号后的动作。 |
实验 1 代码实现信号忽略
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(void)
{
signal(SIGINT,SIG_IGN);
while(1){
printf("wait signal\n");
sleep(1);
}
return 0;
}
编译运行程序, 如下图所示, 当我们按下 ctrl+C 键的时候, 信号被忽略。
实验 2: 代码实现采用系统默认方式处理该信号
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(void)
{
signal(SIGINT,SIG_DFL);
while(1){
printf("wait signal\n");
sleep(1);
}
return 0;
}
编译运行程序, 如下图所示, 按 ctrl+c 可以终止程序。
实验 3 代码实现捕获到信号后执行此函数内容
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void myfun(int sig)
{
if(sig == SIGINT){
printf("get sigint\n");
}
}
int main(void)
{
signal(SIGINT,myfun);
while(1){
sleep(1);
printf("wait signal\n");
}
return 0;
}
编译运行程序如下图所示,当我们按下 ctrl+c 时, 显示 myfun 函数里面的打印信息。
我们再打开另一个终端, 输入如下图所示的命令也可以实现同样的效果。