【Linux】进程间通信之信号机制

这篇具有很好参考价值的文章主要介绍了【Linux】进程间通信之信号机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

信号的概念

信号是一个程序中断,且是一个软中断,收到一个信号后,具体怎么处理该信号,什么时候处理是由进程决定的,所以是软中断。

信号的种类:使用kill -l 命令可以查看有多少个信号

【Linux】进程间通信之信号机制,Linux,linux,运维,服务器

  • 1~31是非可靠信号
  • 34~64是可靠信号
  • 非可靠信号:当前信号有可能丢失,丢失就无法执行该信号
  • 可靠信号:当前信号不可能会丢失的

可以通过man 7 signal 查看信号的具体含义命令

信号的产生

硬件产生(按键盘中的按键)

  • 终止进程组合键 ctrl + c

    在程序运行过程中我们按下 ctrl + c 就可以中断进程运行

    组合键ctrl + c 本质上是SIGINT 信号(2号信号),是一个终止信号,终止正在进行的这个前台进程,该组合键对后台进程没有任何作用

  • 暂停进程组合键 ctrl + z

    在程序运行过程中我们按下 ctrl + z 就可以暂停进程运行,此时该进程的进程状态是T,意为暂停状态

    组合键 ctrl + z 本质上是SIGTSTP信号(20号信号),是一个暂停信号,让正在运行的前台程序暂停运行

  • 产生核心转储文件组合键 ctrl + |

    组合键 ctrl + | 本质上是SIGQUIT信号(3号信号),是一个结束进程并产生核心转储文件的信号

    核心转储文件:核心转储文件中存储的是程序异常终止进程产生的一个文件,进程终止瞬间将程序的内存映像,包括程序代码、变量、堆栈、寄存器状态等,以及导致程序崩溃的错误信息写到这个磁盘文件,其中信息常用于调试寻找程序错误原因

    核心转储:是指在计算机程序发生严重错误或崩溃时,操作系统将程序在崩溃瞬间的内存状态和相关信息保存到一个文件中的过程。这个文件被称为核心转储文件(Core Dump File)。核心转储通常在以下情况产生:程序崩溃:当程序遇到无法处理的错误,如访问无效内存地址、除以零等,操作系统会生成核心转储文件;异常信号:当程序收到操作系统发送的某些异常信号,如段错误(Segmentation Fault)或浮点异常(Floating Point Exception),也可能触发生成核心转储文件。

    当进程异常退出或收到信号退出时,却没有产生核心转储文件,此时可以通过ulimit -a命令查看core file size设置情况,如果它被设置为0,我们需要命令ulimit -c unlimited修改设置值为unlimited后,进程异常退出后才能产生核心转储文件

    我们可以用gdb来加载核心转储文件并检查程序的状态和堆栈,找到错误原因,修复bug

    gdb调试核心转储文件寻找错误地方:gdb [可执行文件名] [核心转储文件]

一些非法行为对应的信号量:

非法行为 信号量 信号名
解引用空指针 11号信号并产生核心转储文件 SIGSEGV
访问越界 11号信号并产生核心转储文件 SIGSEGV
动态分配空间free两次 6号信号并产生核心转储文件 SIGABRT

软件产生

kill函数

int kill(pid_t pid, int sig)

功能:kill 函数在操作系统中用于发送信号给进程,以控制和影响其行为。这些信号可以用于各种目的,包括终止进程、重新加载配置、重新启动等。需要注意的是,kill 函数的名称可能会导致误解,因为它实际上并不是用来强制终止进程的专门函数。

一些常见信号编号包括

  • 1:SIGHUP(终端挂起)
  • 2:SIGINT(中断信号,通常由Ctrl+C发送)
  • 9:SIGKILL(强制终止)
  • 15:SIGTERM(正常终止)
  • 20:SIGTSTP(挂起进程)

头文件:sys/types.h、signal.h

参数:

  • pid : 进程标识符,给哪一个进程发送信号
  • sig : 信号值,具体发送哪一个信号

返回值:

  • 成功:返回信号值
  • 失败:返回 -1

kill命令

kill命令可以指定给具体进程发送具体信号量

kill -signal pid
  • signal:信号量,可以是具体数值,也可以是信号名字
  • pid : 进程标识符

abort函数

void abort(void);

功能:可以向进程发送SIGABRT信号(6号信号),使进程异常终止,并关闭刷新进程打开的流。哪一个进程调用该函数,便向该进程传送SIGABRT信号(6号信号),其实abort内部封装了kill函数

头文件:stdlib.h

raise函数

#include <signal.h>
int raise(int sig);

功能:sig 是要发送的信号编号。调用 raise 函数会向当前进程发送指定的信号。这个函数实际上是一个库函数的封装,其底层实现会调用系统调用来发送信号。

捕捉信号后的处理方式

#define SIG_DFL	((__sighandler_t)0)	/* default signal handling */
#define SIG_IGN	((__sighandler_t)1)	/* ignore signal */
#define SIG_ERR	((__sighandler_t)-1)	/* error return from signal */

默认处理方式SIG_DFL

#define SIG_DFL	((__sighandler_t)0)	/* default signal handling */
//SIG_DFL就是 __sighandler_t结构体类型 的0

默认处理方式:SIG_ DFL:操作系统当中已经定义号信号的处理方式了,例如2号信号用于终止进程;11号信号用户终止进程,并且产生核心转储文件

忽略处理方式SIG_IGN

还记得我们之前说僵尸进程子进程先于父进程退出,子进程在退出的时候,会告知父进程,其实就是子进程向父进程发送了一个SIGCHLD信号,但是父进程接收到信息之后是忽略处理,父进程并没有回收子进程的退出状态信息,就是说父进程对SIGCHLD信号的处理方式是忽略处理,从而导致子进程变成僵尸进程。

#define SIG_IGN	((__sighandler_t)1)	/* ignore signal */
//SIG_IGN就是 __sighandler_t结构体类型 的1

自定义信号处理方式

自定义处理方式,就是让程序猿自己定义某一个信号的处理方式

signal函数

typedef void (*sighandler_t)(int);		// void handler(int)
sighandler_t signal(int signum, sighandler_t handler);

功能:当进程接收到了signum信号时,调用handler函数,执行handler函数中的代码,该信号以前需要执行的任务不再执行

头文件:signal.h

参数:

  • signum : 信号量值,要处理的信号
  • handler : 信号处理句柄,就是一个函数指针(回调函数),当进程接收到了signum这个信号时,进程需要调用handler函数去做一些事先规定好的事情

注意:因为9号信号是强杀信号,不能被自定义处理

sigaciotn函数

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能:自定义信号处理方式函数:程序员定义一个函数去处理接收到的信号

头文件:signal.h

参数:

  • signum : 信号量值
  • act : 输入型参数,保存 对signum信号 所采取的措施信息
  • oldact : 输出型参数,保存 以前对signum信号 所采取的措施信息

sigaction结构体详解:

struct sigaction {
    void     (*sa_handler)(int);
    		//保存了内核对信号的处理方式的函数指针:默认处理方式和忽略处理方式
    void     (*sa_sigaction)(int, siginfo_t *, void *); 
    		//函数指针,保存自定义处理函数,但是没有使用
    		//当要使用的时候,配合sa_flags一起使用。
    		//当sa_flags的值为SA_SIGINFO的时候,信号按照sa_sigaction保存的函数地址进行处理
    sigset_t   sa_mask;		
    		//信号集位图,保存收到的信号。
   			//当进程在处理信号的时候,如果还在收到信号,
   			//则放到该信号位图当中,后续再放到进行的信号位图当中
    int        sa_flags;	//填入宏
    void     (*sa_restorer)(void);	//保留字段
};
含义
SA_SIGINFO 操作系统在处理信号的时候,调用的就是sa_sigaction函数指针当中保存的函数
0 操作系统在处理信号的时候,调用的就是sa_handler函数指针当中保存的函数

内核源码中的sigaction结构体源码:

struct sigaction {
	union {
	  __sighandler_t	_sa_handler;
	  void (*_sa_sigaction)(int, struct siginfo *, void *);
	} _u;
	sigset_t	sa_mask;
	int		sa_flags;
};
#define sa_handler	_u._sa_handler
#define sa_sigaction	_u._sa_sigaction

typedef char* __user __sighandler_t;
typedef struct {
	unsigned long sig[_NSIG_WORDS];
} sigset_t;

signal函数与sigaction函数的关系:

  • signal函数只是修改了sigaction结构体中的_sa_sigaction
  • sigaction函数修改了整个sigaction结构体
  • signal函数内部调用了sigaction函数

【Linux】进程间通信之信号机制,Linux,linux,运维,服务器

信号的注册

一个进程收到一个信号,这个过程称之为注册,信号的注册和注销并不是一个过程,是两个独立的过程

内核中信号注册位图以及sigqueue队列的的了解:都是task_ struct结构体内部的内容;每一个进程都有自己独有的注册位图和sigqueue队列

进程中的未决信号集(位图)

位图:进程的task_struct中位图的初始定义:

struct task_struct {
    ...
      struct sigpending pending;  
    ...
}

sigpending:内核源码的include\linux\signal.h中sigpending:

struct sigpending {
	struct list_head list;//双向链表
	sigset_t signal;
};

sigset_t:内核源码的 include\asm-generic\signal.h中定义了sigset_t:

typedef struct {
	unsigned long sig[_NSIG_WORDS];
} sigset_t;

_NSIG_WORDS:内核源码的 include\asm-generic\signal.h中定义了_NSIG_WORDS:

#define _NSIG		64
#define _NSIG_BPW	__BITS_PER_LONG
#define _NSIG_WORDS	(_NSIG / _NSIG_BPW)

__BITS_PER_LONG:内核源码的 arch\alpha\include\asm\bitsperlong.h中定义了__BITS_PER_LONG:

#define __BITS_PER_LONG 64

最终我们发现:位图就是一个unsigned long sig[1],Linux操作系统中long占8个字节,即64位
,每一个信号,在该位图中存在一个与之对应的比特位,当信号对应的比特位为1时,表示当前进程接收到该信号

【Linux】进程间通信之信号机制,Linux,linux,运维,服务器

非实时信号(非可靠信号)的注册

第一次注册:修改sig位图(0->1),修改sigqueue队列

第二次注册:相同信号值的信号,在前一个信号未被处理的前提下:修改sig位图(1->1),并不会添加sigqueue节点,也就是说再次添加,不会添加sigqueue节点

非实时信号容易信号丢失的原因就是再次注册的时候不会添加sigqueue节点

实时信号(可靠信号)的注册

第一次注册:修改sig位图(0->1),修改sigqueue队列

第二次注册:相同信号值的信号:修改sig位图(1->1),添加sigqueue节点到sigqueue队列中
,也就是说,再次添加,会再次添加siquque节点

sigqueue源码:

struct sigqueue {
	struct list_head list;
	int flags;
	siginfo_t info;
	struct user_struct *user;
};

信号的注销

非可靠信号的注销

如果信号已经处理完,则将处理完的信号对应位图中的比特位从1变成0,并将该信号的sigqueue节点从sigqueue队列中出队

可靠信号的注销

如果信号已经处理完了,则将该信号的sigqueue节点从sigqueue队列中出队,同时需要判断sigqueue队列中是否还有与出队的该信号相同的sigqueue节点:如果还有相同的sigqueue节点:则不修改位图中的对应比特位;如果没有:将该信号对应位图中的比特位从1变成0

信号的捕捉流程

【Linux】进程间通信之信号机制,Linux,linux,运维,服务器

如果没有收到信号,执行顺序为1->2->3->4
如果收到了信号,执行顺序为1->2->5->6->7->3->4

main函数调用了一个系统调用函数或者调用库函数(库函数底层也是封装的系统调用函数)后,cpu从用户态切换到内核态,内核态调用系统调用函数后想回到用户态需要调用do_signal函数,do_signal函数的功能是检查进程是否接受到信号:如果接受到信号,调用sigcb函数去处理信号(默认处理方式则不需要切换到用户态,直接在内核态进行信号处理;自定义处理方式需要切换到用户态进行信号处理),信号处理完毕后调用sig_return函数表明信号处理完,再调用do_signal函数检查是否接受到新的信号;如果未接收到信号,直接调用sys_return函数,让cpu从内核态切换到用户态

信号阻塞

信号阻塞的理解

信号的注册是信号注册, 信号阻塞是信号阻塞。信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号之后,由于阻塞, 暂时不处理该信号 。

task_struct源码中定义了信号阻塞位图和信号注册位图

struct task_struct{
	...
	sigset_t blocked, real_blocked;	// 信号阻塞位图
	struct sigpending pending;	// 信号注册位图 位于这个结构体内部
	...
}

当信号阻塞位图block中对应信号的位为1,表示当前进程阻塞该信号

当进程进入内核状态,准备返回到用户态时,调用do_signal函数时,接收到了一个信号,如果该信号的阻塞位图中的对应位置为1,则不会立即去处理该信号

等到该信号的阻塞位图上的该信号对应位变成1之后才会去处理该信号,可靠信号发送了几次处理几次,非可靠信号发送大于等于1次都是处理1次

设置阻塞位图函数

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

功能:设置阻塞位图

头文件:signal.h

参数:

  • how:想让sigprocmask函数做什么事情

    • SIG_ BLOCK:设置某个信号为阻塞状态
    • SIG_ UNBLOCK:设置某个信号为非阻寒状态
    • SIG_ SETMASK:替换阻塞位图,用第二个参数“set”,替换原来的阻寨位图
  • set:新替换入的阻塞位图,可以为NULL

  • alodset:原阻塞位图,可以为NULL

SIG_BLOCK设置阻塞原理:按位或,将新的要阻塞的信号加入了阻塞位图中 (原阻塞位图 | 新阻塞位图)
SIG_UNBLOCK解除阻塞原理:按位与,将要解除阻塞的信号的比特位变成0(原阻塞位图 & 新阻塞位图)文章来源地址https://www.toymoban.com/news/detail-662471.html

到了这里,关于【Linux】进程间通信之信号机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux进程间通信(信号量)

    信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即 P(信号变量))和发 送(即 V(信号变量))信息操作。最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式, 叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号

    2024年02月07日
    浏览(36)
  • Linux进程间通信【消息队列、信号量】

    ✨个人主页: 北 海 🎉所属专栏: Linux学习之旅 🎃操作环境: CentOS 7.6 阿里云远程服务器 在 System V 通信标准中,还有一种通信方式: 消息队列 ,以及一种实现互斥的工具: 信号量 ;随着时代的发展,这些陈旧的标准都已经较少使用了,但作为 IPC 中的经典知识,我们可

    2024年02月08日
    浏览(50)
  • 【Linux】进程间通信——System V信号量

    目录 写在前面的话 一些概念的理解 信号量的引入 信号量的概念及使用            System V信号量是一种较低级的IPC机制 ,使用的时候需要手动进行操作和同步。在现代操作系统中,更常用的是 POSIX信号量 (通过 sem_* 系列的函数进行操作)或更高级的同步原语(如互斥锁

    2024年02月11日
    浏览(46)
  • 【Linux】进程间通信之共享内存/消息队列/信号量

    共享内存是通过让不同的进程看到同一个内存块的方式。 我们知道,每一个进程都会有对应的PCB-task_struct ,独立的进程地址空间,然后通过页表将地址映射到物理内存中。此时我们就可以让OS在内存中申请一块空间,然后将创建好的内存空间映射到进程的地址空间中,两个需

    2024年02月05日
    浏览(46)
  • 【Linux】进程间通信 --- 管道 共享内存 消息队列 信号量

    等明年国庆去西藏洗涤灵魂,laozi不伺候这无聊的生活了 1. 通过之前的学习我们知道,每个进程都有自己独立的内核数据结构,例如PCB,页表,物理内存块,mm_struct,所以具有独立性的进程之间如果想要通信的话,成本一定是不低的。 2. a.数据传输:一个进程需要将它的数据

    2023年04月17日
    浏览(46)
  • Linux进程间通信 - 信号(signal) 与 管道(pipe) 与 消息队列

    什么是进程间通信,就是进程与进程之间进行通信,互相发送消息;可以通过 信号 或者 管道 或者 消息队列 或者 信号量 去通信! 目录 一、信号 1. 信号简介  2. 都有那些信号? 3. 注册信号的函数 1). signal 2). sigaction (项目中强烈推荐使用) 4. 信号发送 1). kill 函数 2). alarm 函

    2024年02月01日
    浏览(38)
  • 【Linux】进程间通信——system V共享内存 | 消息队列 | 信号量

    共享内存是一种在多个进程之间进行进程间通信的机制。它允许多个进程访问相同的物理内存区域,从而实现高效的数据交换和通信。 因为 进程具有独立性(隔离性) ,内核数据结构包括对应的代码、数据与页表都是独立的。OS系统为了让进程间进行通信,必须让不同的进

    2024年02月15日
    浏览(50)
  • Linux之进程间通信——system V(共享内存、消息队列、信号量等)

    本文介绍了另一种进程间通信——system V,主要介绍了共享内存,消息队列、信号量,当然消息队列了信号量并非重点,简单了解即可。 共享内存 :不同的进程为了进行通信看到的同一个内存块,该内存块被称为共享内存。 进程具有独立性,它的内核数据结构包括对应的代

    2024年02月08日
    浏览(57)
  • 【Linux】详解进程通信中信号量的本质&&同步和互斥的概念&&临界资源和临界区的概念

             访问资源在安全的前提下,具有一定的顺序性,就叫做同步 。在多道程序系统中,由于资源有限,进程或线程之间可能产生冲突。同步机制就是为了解决这些冲突,保证进程或线程之间能够按照既定的顺序访问共享资源。同步机制有助于避免竞态条件和死锁(

    2024年04月25日
    浏览(38)
  • 运维 | 查看 Linux 服务器 IP 地址

    大多数在操作 Linux 系统时,我们经常需要知道服务器的 IP 比便于后续的一系列操作,这时候有快速查看主机 IP 的命令行操作,能够有效的帮助我们 本章节主要记录一些常用查看服务器 IP 的命令,希望对大家有所帮助。 查看 Linux 服务器的 IP 地址的命令大体上有以下几种。

    2024年04月27日
    浏览(76)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包