欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

寄雁传书谢不能———Linux中的信号

程序员文章站 2022-03-19 13:21:37
...

什么是信号

信号是unixLinux系统响应某些条件而产生的一个事件。简单的理解信号就是一种Linux/unix环境下进程间通讯的一种机制,用来提醒进程一个事件已经发生。

 

信号是软件中断,是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。


信号的产生

1)硬件来源:除数为零、无效的存储访问等硬件异常产生信号,这些事件通常由硬件(:CPU)检测到,并将其通知给Linux操作系统内核,然后内核生成相应的信号,并把信号发送给该事件发生时正在进行的程序。

2)软件来源:用户在终端下调用kill命令向进程发送任务信号;进程调用killsigqueue函数发送信号;当检测到某种软件条件已经具备时发出信号,如由alarmsettimer设置的定时器超时时将生成SIGALRM信号等多种情景均可产生信号。

3)键盘输入:用户键入了能够产生信号的终端特殊字符。其中包括中断字符(Ctrl+C)、暂停字符(Ctrl+Z)。

Linux中的信号

/usr/include/bits/signum.h 下我们可以查看Linux系统的信号,每一个信号都有一个编号和一个宏定义的名称,当我们使用这些信号时,最好使用它们的名称,因为在不同的系统中,信号的编号可能是不同的。

寄雁传书谢不能———Linux中的信号

我们也可以使用 kill -l 命令来查看所有的信号:

寄雁传书谢不能———Linux中的信号

可以看到Linux中系统一共支持64种信号,其中131号信号为普通信号(不可靠信号),3464为实时信号(可靠信号)。

 

可靠信号与不可靠信号的区别:

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是带有sigfunc两个参数的函数。准备捕获或者忽略的指定信号由sig给出,接受到指定的信号后所采取的 响应方式由参数func决定。func是一个信号处理函数,它必须有一个int型参数并且返回类型为voidfunc可以由以下两个特殊值来代替:

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);
    
}

运行结果为:

寄雁传书谢不能———Linux中的信号

向指定进程发送信号:kill()

kill 系统调用

 

头文件:#include<sys/types.h>

 #include<signal.h>

函数原型:int killpid_t pid, int sig

函数说明:函数kill将参数sig指定的信号发送给由参数pid所指定的进程

返回值:成功时返回 0;失败时返回 -1,并设置errno变量。

 

还记得我们以前使用过得 kill 命令吗?其具体做法是:kill +参数+ pid,即可强制结束某一进程。现在我们知道,其实现原理就是通过向进程发送信号达到的。在不指定信号的情况下,将默认发送SIGTERM15)信号终止指定进程。当kill命令无法结束某一进程时,我们还使用过 kill -9 pid,其原理大家应该也能猜到,就是发送SIGKILL9)信号无条件终止这个进程。

知道了原理,我们就可以自己编写一道程序实现与kill命令相似的功能。

   首先,我们写一个会产生死循环的函数,名为 kill_test。代码如下:


#include<stdio.h>
#include<stdlib.h>

int main()
{
    while(1)
    {
        sleep(1);
        printf("please kill me\n");    
    }

    exit(0);
    
}

将这个程序运行后,我们通过 kill 命令可以很轻松地将其终止:

寄雁传书谢不能———Linux中的信号

然后这里我们自己写一个名为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程序结束掉它:

寄雁传书谢不能———Linux中的信号


两者所达到的目的是相同的。





相关标签: 信号