寄雁传书谢不能———Linux中的信号
什么是信号
信号是unix和Linux系统响应某些条件而产生的一个事件。简单的理解信号就是一种Linux/unix环境下进程间通讯的一种机制,用来提醒进程一个事件已经发生。
信号是软件中断,是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。
信号的产生
(1)硬件来源:除数为零、无效的存储访问等硬件异常产生信号,这些事件通常由硬件(如:CPU)检测到,并将其通知给Linux操作系统内核,然后内核生成相应的信号,并把信号发送给该事件发生时正在进行的程序。
(2)软件来源:用户在终端下调用kill命令向进程发送任务信号;进程调用kill或sigqueue函数发送信号;当检测到某种软件条件已经具备时发出信号,如由alarm或settimer设置的定时器超时时将生成SIGALRM信号等多种情景均可产生信号。
(3)键盘输入:用户键入了能够产生信号的终端特殊字符。其中包括中断字符(Ctrl+C)、暂停字符(Ctrl+Z)。
Linux中的信号
在 /usr/include/bits/signum.h 下我们可以查看Linux系统的信号,每一个信号都有一个编号和一个宏定义的名称,当我们使用这些信号时,最好使用它们的名称,因为在不同的系统中,信号的编号可能是不同的。
我们也可以使用 kill -l 命令来查看所有的信号:
可以看到Linux中系统一共支持64种信号,其中1到31号信号为普通信号(不可靠信号),34到64为实时信号(可靠信号)。
可靠信号与不可靠信号的区别:
(1)这里的不可靠主要是不支持信号队列,就是当多个信号发生在进程中的时候(收到信号的速度超过进程处理的速度的时候),这些没来的及处理的信号就会被丢掉,仅仅留下一个信号。
(2)可靠信号是多个信号发送到进程的时候(收到信号的速度超过进程处理信号的速度的时候),这些没来的及处理的信号就会排入进程的队列。等进程有机会来处理的时候,依次再处理,信号不丢失。
这里详细列举了 1~31 号信号的来源,作为参考:
#define SIGHUP 1 /* Hangup (POSIX). */
//当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
#define SIGINT 2 /* Interrupt (ANSI). */
//当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号.默认动作为终止进程。
#define SIGQUIT 3 /* Quit (POSIX). */
//当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号.默认动作为终止进程。
#define SIGILL 4 /* Illegal instruction (ANSI). */
//CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
#define SIGTRAP 5 /* Trace trap (POSIX). */
//该信号由断点指令或其他 trap指令产生.默认动作为终止进程并产生core文件.
#define SIGABRT 6 /* Abort (ANSI). */
//调用abort函数时产生该信号.默认动作为终止进程并产生core文件.
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
//非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件.
#define SIGFPE 8 /* Floating-point exception (ANSI). */
//在发生致命的运算错误时发出.
//不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误.默认动作为终止进程并产生core文件.
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
//无条件终止进程。本信号不能被忽略,处理和阻塞.默认动作为终止进程.
//它向系统管理员提供了可以杀死任何进程的方法。
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
//用户定义的信号.即程序员可以在程序中定义并使用该信号.默认动作为终止进程。
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
//指示进程进行了无效内存访问.默认动作为终止进程并产生core文件.
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
//另外一个用户自定义信号,程序员可以在程序中定义并使用该信号.默认动作为终止进程.
#define SIGPIPE 13 /* Broken pipe (POSIX). */
//向一个没有读端的管道写数据.默认动作为终止进程.
#define SIGALRM 14 /* Alarm clock (POSIX). */
//定时器超时,超时的时间 由系统调用alarm设置.默认动作为终止进程.
#define SIGTERM 15 /* Termination (ANSI). */
//程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止.通常用来要示程序正常退出.
//执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
#define SIGSTKFLT 16 /* Stack fault. */
//Linux专用,数学协处理器的栈异常(不懂什么意思)
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
//子进程结束时,父进程会收到这个信号.默认动作为忽略这个信号.
#define SIGCONT 18 /* Continue (POSIX). */
//如果进程已停止,则使其继续运行.默认动作为继续/忽略
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
//停止进程的执行.信号不能被忽略,处理和阻塞.默认动作为暂停进程.
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
//停止终端交互进程的运行.按下<ctrl+z>组合键时发出这个信号.默认动作为暂停进程.
#define SIGTTIN 21 /* Background read from tty (POSIX). */
//后台进程读终端控制台.默认动作为暂停进程.
#define SIGTTOU 22 /* Background write to tty (POSIX). */
//该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生.默认动作为暂停进程.
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
//套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达.默认动作为忽略该信号。
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
//进程执行时间超过了分配给该进程的CPU时间系统产生该信号并发送给该进程.默认动作为终止进程。
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
//超过文件的最大长度设置.默认动作为终止进程.
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
//虚拟时钟超时时产生该信号.类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间.默认动作为终止进程。
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
//类似于SIGVTALRM,它不仅包括该进程占用CPU时间还包括执行系统调用时间.默认动作为终止进程。
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
//窗口变化大小时发出.默认动作为忽略该信号.
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
//此信号向进程指示发出了一个异步IO事件.默认动作为忽略
#define SIGPWR 30 /* Power failure restart (System V). */
//关机.默认动作为终止进程。
#define SIGSYS 31 /* Bad system call. */
//无效的系统调用.默认动作为终止进程并产生core文件
对信号的回应
信号因某些事件而产生后,会于稍后被传递给某一进程,进程也会采取某些措施来回应信号。(在产生和到达期间,信号处于等待状态。)
信号到达后,进程视具体信号执行如下默认操作之一。
(1)忽略信号:内核将信号丢弃,信号对进程没有任何影响(进程永远不知道曾经出现过该信号)。
(2)终止进程:这有时是指进程异常终止,而不是进程因调用exit()而发生的正常终止。
(3)产生核心转储文件,同时进程终止:核心转储文件包含对进程虚拟内存的镜像,可将其加载到调试器中以检查进程终止时的状态。
(4)停止进程:使进程暂停执行。(不是终止)
(5)执行之前被暂停的进程。
除了根据特定信号而采取默认行为之外,程序也能改变信号到达时的响应行为。程序可以将对信号的处置设置为如下之一
1、 忽略此信号(忽略)。(SIG_IGN)
2、 执行该信号的默认处理动作。(SIG_DEF)
3、提供一个信号处理函数,要求内核在处理信号时切换到用户态执行处理这个函数。
如果不想按照默认处理动作来处理信号,我们可以通过signal函数来对信号自定义捕捉并设置响应方式。
改变信号响应方式:signal()
signal (系统调用)
头文件 :#include<signal.h>
函数原型: void(*signal(int sig, void(*func)(int)))(int)
函数说明: signal是带有sig和func两个参数的函数。准备捕获或者忽略的指定信号由sig给出,接受到指定的信号后所采取的 响应方式由参数func决定。func是一个信号处理函数,它必须有一个int型参数并且返回类型为void。func可以由以下两个特殊值来代替:
SIG_DEF 采取默认的响应方式
SIG_IGN 忽略该信号
函数功能: 该函数可以改变对某一信号的响应方式。比如:第17号信号SIGCHLD,该信号是在子进程结束时发送给父进程,系统对此信号的默认响应方式是忽略该信号,而我们可以通过signal函数将其响应方式改为打印个“hello world”。具体操作如下:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<signal.h>
#include<unistd.h>
void func(int sig)
{
printf("hello world\n");
}
int main()
{
signal(SIGCHLD, func); //当接收到SIGCHLD信号时,执行信号处理函数func
pid_t pid = fork();
assert( pid != -1);
int t = 0;
if(pid == 0)
{
t = 3;
int i = 0;
for(; i < t; i++) //子进程会先于父进程结束
{
sleep(1);
printf("child = %d\n", i);
}
}
else
{
t = 7;
int j = 0;
for(; j < t; j++)
{
sleep(1);
printf("parent = %d\n", j);
}
}
exit(0);
}
运行结果为:
向指定进程发送信号:kill()
kill 系统调用
头文件:#include<sys/types.h>
#include<signal.h>
函数原型:int kill(pid_t pid, int sig)
函数说明:函数kill将参数sig指定的信号发送给由参数pid所指定的进程
返回值:成功时返回 0;失败时返回 -1,并设置errno变量。
还记得我们以前使用过得 kill 命令吗?其具体做法是:kill +参数+ pid,即可强制结束某一进程。现在我们知道,其实现原理就是通过向进程发送信号达到的。在不指定信号的情况下,将默认发送SIGTERM(15)信号终止指定进程。当kill命令无法结束某一进程时,我们还使用过 kill -9 pid,其原理大家应该也能猜到,就是发送SIGKILL(9)信号无条件终止这个进程。
知道了原理,我们就可以自己编写一道程序实现与kill命令相似的功能。
首先,我们写一个会产生死循环的函数,名为 kill_test。代码如下:
#include<stdio.h>
#include<stdlib.h>
int main()
{
while(1)
{
sleep(1);
printf("please kill me\n");
}
exit(0);
}
将这个程序运行后,我们通过 kill 命令可以很轻松地将其终止:
然后这里我们自己写一个名为kill的程序,其代码为:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<signal.h>
int main(int argc, char* argv[])
{
if(argc != 2) //只接受两个参数
{
printf("argc error"); //否则打印错误信息
exit(0);
}
int pid = 0;
sscanf(argv[1],"%d",&pid); //从参数中提取目标进程的pid
if(kill(pid,SIGTERM) == -1) //把SIGTERM(15)信号发送给指定进程
{
perror("kill error"); //如果失败,打印错误信息
}
exit(0);
}
再次运行这个死循环程序,我们就可以用自己的kill程序结束掉它:
两者所达到的目的是相同的。
下一篇: 12306如何修改用户名?