信号:singal

发布于:2022-11-14 ⋅ 阅读:(315) ⋅ 点赞:(0)

信号

这里的信号:OS提供的一种软件层面的通信机制

什么是信号

信号是一种通知进程某件事情发生了的一种通信机制

通过向进程发送某个信号,可以告诉进程发生了什么事情,进程收到这个信号后,就知道某事情发生了,进程可以做出相应的响应(处理)

信号是一种向进程发送通知,告诉其某件事情发生了的一种简单通信机制

信号也是一种通信机制,目的就是用于告诉进程发生了什么事情

Linux所实现的信号这种通信机制,与IPC(进程间通信)所不同的是:

信号属于不精确通信,信号只能告诉进程大概发生了什么事情,但是不能准确的告诉进程详细的细节信息

信号有区别于精确IPC之外,自己的使用场合

本章所涉及到各个API就是专门与信号相关的API

有关信号的内容

	1)比如ctrl+c为什么可以结束进程
	
	2)产生指针错误时为什么进程能够自动结束,并提示产生了“segment fault”(段错误)。
	
	3)kill命令到底是怎么结束进程的

所有的信号机制的实现,原理都是相通

  • c、c++、java的线程库有类似的信号概念
  • QT界面开发时有类似信号和槽的概念

信号的相关概念

信号的命名

  • Linux定义了很多的信号,所有的信号都是一个整数编号
  • 为了好辨识,Linux系统给这些整数编号都定义了对应的宏名
    • 宏名都是以SIG开头,比如SIGABRT
    • SIG:signal的缩写;ABRT:abort的缩写

宏名SIG____,____是对信号的描述

调用abort函数时,该函数就会向进程发送一个SIGABRT信号

谁会向进程发送信号

  1. 其他进程发送信号

    在命令行终端窗口通过kill命令向某个进程发送一个信号将其终止

  2. 内核发送信号

    Linux内核可能会发送该事件对应的信号给某个进程

    ex:

    进程从管道文件读取数据,但是管道文件的读权限被关闭了,进程会被内核发送一个SIGPIPE信号,提示读管道出错了

    • 指针错误,发送SIGSEGV
  3. 底层硬件发送信号

    底层硬件发生了某个事件,会向进程发送对应的某个信号

    但是硬件先是发送到内核,然后内核转发该硬件信号

    比如:

    按下ctrl+c按键终止进程时,内核收到ctrl+c按键后,会向正在运行的进程发送SIGINT信号,将其异常终止

进程收到信号的处理

分别是忽略、捕获、默认

忽略的意思就是说,进程就当信号从来没有发生过

捕获的意思就是说,进程会调用相应的处理函数,进行相应的处理

如果不忽略也不捕获的话,此时进程会使用系统设置的默认处理方式来处理信号

信号的种类

在命令行执行kill -l,可以显示所有的信号列表(没有32,33)

为什么信号多

//每个信号代表着某种事件,一般情况下,当进程收到某个信号时,就表示该信号所代表的事件发生了。
//可能发生的事件会有很多种,所以弄出了这么多的信号,每个信号对应着自己特有的事件。
  • 35~64:这些信号是Linux后期增设的信号,这些个信号不需要关心,不用了解
  • 1~34:也不是所有的信号都要掌握,我们只关心其中常用的信号

对于这些个信号,理解才是关键

常用信号

kill + PID如果杀不死进程,中间加入:-SIGKILL 或 -9


ctrl+\ 和 ctrl+c 都可以终止进程,但是\会产生core文件

如果发送的这些信号的处理方式是终止,那么进程会被终止掉

kill()

kill -l:查询信号列表

进程是否会被信号终止,是由信号的处理方式来决定的,而不是kill命令

  • kill所起到的作用只是发送信号

发送信号的完整格式:kill -信号编号 PID

kill PID,默认发送的是15(SIGTERM)信号,等价于kill -SIGTERM PID或者kill -15 PID

  • 只有发送15这个信号时才能省略信号编号,发送其它信号时必须写明信号编号

注意:

命令+&是在后台,让出命令行窗口,交给shell;在终端输入fg即可切换

pkill()

用法与kill差不多,只不过kill是按照PID来识别进程的,pkill是按照名字来识别进程的

Pkill -信号编号 名字:pkill -SIGINT a.out

如果不写明信号编号的话,默认发送的是15(SIGTERM)这个信号

信号的发送与接收

一般来说,大多数发送信号的原因,都是因为内核、硬件发生了某些事件

向某个进程发送该事件专用的信号,告诉该进程这个事件发生了

不过对于我们自己写的进程来说,其实更多是接收信号,而不是发送信号

自己发送信号(非内核和硬件)的情况

  1. ctrl+C、ctrl+\发送信号,终止正在运行进程
  2. 命令行执行kill命令发送信号,终止进程
  3. 因为某些特殊需求,在我们自己的程序里面,需要调用函数向另一个进程发送某个信号
比如A进程和B进程需要协同工作,A进程将相应事件准备好以后,可能需要发送一个信号给B进程,通知B进程,B进程收到信号后,就知道该事件已经准备好,可以配合A进程做事了。
--------
自己进程调用kill函数发送信号时,最好发送的是SIGUSR1、SIGUSR2这两个自定义信号
发送专用事件的信号的话,很可能造成误会

接收信号

在一般情况下,进程并不会去重新设置信号的处理方式,而是使用信号的默认处理方式来处理信号

如果需要重新设置信号的处理方式,可以调用相应API来重新设置信号的处理方式。

core文件

同样终止进程-----ctrl+c不产生core文件,ctrl+\产生core文件

  • core dumped

什么是core文件

​ 用于保存程序(进程)在当前结束的这一刻,进程在内存中的代码和数据

**作用:**core文件可以用于分析进程在结束时的状况

由于进程代码和数据都是二进制的,需要特殊软件翻译后才能看懂

1.只有某个些信号在终止进程时才会产生core文件
2.一般情况下并不会创建这个文件,因为系统默认将产生core的设置给关闭了,只有打开后这个设置后才会保存core文件。						
	所以当你看到提示core dumped,这就表示这个信号终止进程时,会产生core文件,只不过由于关闭了设置,因此core文件被丢弃了,dumped就是丢弃的意思。						

signal()

#include <signal.h>
//功能:设置某个信号的处理方式
//处理方式可以被设置为忽略,捕获,默认
typedef void (*sighandler_t)(int);
/*
成功:返回上一次的处理方式
失败:返回SIG_ERR宏值,并且设置errno
*/
//signum:信号编号
//handler:信号处理方式
//sighandler_t handler也有直接写成void (*handler)(int)
sighandler_t signal(int signum, sighandler_t handler);
/*
                (a)忽略:SIG_IGN
				(b)默认:SIG_DFL
				(c)捕获:填写类型为void (*)(int)的捕获函数的地址
*/

进程的进程表(task_struct)中会有一个“信号处理方式登记表

专门用于记录信号的处理方式

  • 调用signal函数设置某个信号的处理方式时,会将信号的处理方式登记到该表中

每个进程拥有独立的task_struct结构体变量,因而每个进程的“信号处理方式登记表”都是独立的,所以每个进程对信号的处理方式自然也是独立的,互不干扰

捕获函数也被称为信号处理函数			
			void signal_fun1(int signo)
			{
								...
			}			
			void signal_fun2(int signo)
			{
								...
			}
			int main(void)
			{
				signal(SIGINT, signal_fun1);
				signal(SIGSEGV, signal_fun2);
				return 0;
			}

#include<signal.h>
int main(){
    //SIGINT 2  ctrl+C终止进程
    //采用:SIG_INT后,该方式就不起作用了
    signal(SIGINT,SIG_IGN);
    //再次还原成默认就起作用了
    signal(SIGINT,SIG_DFL);
    while(1);
    return 0;
}

把ctrl+\的信号也设置成忽略

此时:只能在终端通过ps -al查看pid,通过kill杀死,或pkill+文件名

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void signal_fun1(int signo){
    printf("SIGINT singo=%d\n",signo);
}
void signal_fun2(int signo){
    printf("SIGQUIT singo=%d\n",signo);
}
int main(){
    signal(SIGINT,signal_fun1);
    signal(SIGQUIT,SIG_IGN);
    signal(SIGQUIT,signal_fun2);
    while(1);
    return 0;
}
guojiawei@ubantu-gjw:~/Desktop/signal$ ./main
^CSIGINT singo=2
^\SIGQUIT singo=3
^CSIGINT singo=2
^\SIGQUIT singo=3
已终止----------pkill main

signal()返回值

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
typedef void (*sighandler_t)(int);
void signal_fun1(int signo){
    printf("SIGQUIT singo=%d\n",signo);
}
int main(){
    //sighandler_t handler也有直接写成void (*handler)(int)
    //sighandler_t signal(int signum, sighandler_t handler);
    sighandler_t ret=NULL;
    ret=signal(SIGINT,SIG_IGN);
    ret=signal(SIGINT,SIG_DFL);
    ret=signal(SIGINT,signal_fun1);
    ret=signal(SIGINT,SIG_DFL);
    if(ret==SIG_ERR){
        perror("signal fails!");
    }
    else if(ret==SIG_IGN){
        printf("ignore!\n");
    }
    else if(ret==SIG_DFL){
        printf("default!\n");
    }
    else{
        printf("capture=%p\n",ret);
    }
    while(1);
    return 0;
}
/*每一次返回值是上一次的结果*/

调用捕获函数的过程

  • 当信号没有发生时,进程正常运行
  • 当信号没有发生时,进程正常运行
    • 一看信号的处理方式是捕获,就会从“信号处理方式登记表”中将捕获函数的地址取出并执行捕获函数
    • 捕获函数执行完毕后,恢复进程的正常运行

当信号来时,如果当前有一条指令正在运行,会先等这条指令运行执行完毕后再去调用信号处理函数

如果信号处理函数有提前执行return的话,会提前返回到主线

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
typedef void (*sighandler_t)(int);
void signal_fun1(int signo){
    printf("SIGQUIT singo=%d\n",signo);
    exit(1);//_exit()
}
void process_exit_deal(void){
    printf("save data in list!\n");
}
int main(){
    atexit(process_exit_deal);
    signal(SIGINT,signal_fun1);
    while(1);
    return 0;
}
/*
^CSIGQUIT singo=2
save data in list!
*/
//exit很重要,会在exit前执行进程处理函数:atexit
  1. 信号被设置为SIG_IGN(忽略)
    进程将不会再接收到这个信号,这信号对进城没有任何影响

  2. 设置为捕获时,需要将handler设置为捕获函数的地址,类型为void (*)(int)

    为了确保和捕获函数的类型统一:

    SIG_DFL、SIG_IGN和SIG_ERR宏的类型也必须是void (*)(int)

    这几个宏定义在了<signal.h>头文件中

    #define SIG_DFL	((void (*)(int))0)	
    #define SIG_IGN	((void (*)(int))1)
    #define SIG_ERR	((void (*)(int))-1)
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<signal.h>
    int main(){
        printf("SIG_ERR=%d\n",SIG_ERR);
        printf("SIG_IGN=%d\n",SIG_IGN);
        printf("SIG_DFL=%d\n",SIG_DFL);
        while(1);
        return 0;
    }
    /*有警告
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./a.out 
    SIG_ERR=-1
    SIG_IGN=1
    SIG_DFL=0
    */
    

    除了SIGKILL这个信号外,其它所有的信号都可被忽略和捕获

Linux规定SIGKILL这个信号一定不能被忽略和捕获


网站公告

今日签到

点亮在社区的每一天
去签到