Linux信号----进程间异步的通信机制

信号

一 、 概念和功能:

信号实际上是一个软中断,用于通知进程发生了某些事,该如何处理。
实际上也归为一类进程间通信方式, 信号的生命周期:信号的产生-信号的注册-信号的阻塞(/屏蔽)-信号的注销-信号的处理

二 、 查看信号:

kill -l ,查看linux下全部信号,可看出信号是由编号和宏组成;
这里写图片描述

kill并不是杀死一个进程,而是为了给某一个指定的进程发送信号。

linux下有62 个信号,分为两类:1-31是非可靠信号(并非实信号),1-31是继承unix而来的,每个信号都对应一个指定事件,非可靠代表这个信号可能会丢失,如果有相同的信号已经注册到这个进程没有被处理,那么接下来的相同信号就会丢掉。
34-64,为可靠信号(实信号)

三 、信号产生方式:

1.硬件中断 (键盘按键中断,ctrl c);
2.硬件异常; (段错误,内存访问错误,core dump,默认发生的信号是11 号SIGSEGV)
3.命令产生:kill 命令 (kill -n pid),默认情况下发送的是信号15SIGTERM
4.软件条件产生:kill函数,raise函数,alarm函数,sigqueue函数

给进程发送信号的接口函数


int kill(pid_t pid,int sig)         //向指定进程发送信号
参数:进程id,指定发送给哪个进程	;信号的编号,指定发送哪个信号   (信号是由编号和宏组成  2 SIGINIT)

int raise(int sig)            //给当前进程发送信号

int sigqueue(pid_t pid,int sig,union sigval value) //给指定进程发送信号,并且可以传一个参数过去,value是要携带的数据,其类型是union sigval

Unsigned int  alarm(unsigned  int seconds)//在指定second秒后给进程发送信号

这里写图片描述

四 、 core dump
   当一个程序要异常终止时,可以选择吧进程用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做core  Dump。
   程序异常的时候,会记录一个核心转储文件,在这个文件中记录程序运行数据 。当一个程序奔溃时,这个错误可能只是偶尔发生,这种错误将很难定位,因为不确定到底什么时候才会崩溃,因此这个转储文件(core 文件)就非常重要,它可以帮助我们使用gdb调试 查看,定位错误。  
    ulimit -c size ,可以设置core dump的大小,例如 ulimit -c 1024
    gdb进出程序,运行执行程序(run ),bt,用来查看调用栈。在gdb中使用命令:core-file core.3996()来加载程序的的运行数据,然后就可以定位错误了。 但是程序的核心转储功能是**默认关闭的**,转储文件默认大小为0,因此运行数据中可能有安全性信息,以及文件增多会占用资源 查看文件/改变文件的大小:

信号的注册

将信号记录在进程中,让进程知道有这样一个信号来了。

信号是记录在进程的PCB中 。
信号集合:在PCB中sigset_t 结构体
进程记录一个信号的时候,通过这个结构体的位图来记录

注册:修改进程中PCB中信号pending的位图,添加一个信号的sigqueue结构体节点(pending位图0变1)
注销:修改进程中PCB中信号pending的位图,删除指定信号的sigqueue的结构体节点(pending位图1变0)

这里写图片描述

信号的阻塞与屏蔽

信号阻塞:因为进程PCB 中还有一个结构,这个结构就是block阻塞集合,

在pcb中有 一个pending结构中存储当前接收到的信号,还有一个结构体blocked用于存储 现在有哪些信号要被阻塞。 进程看到了pending集合中都收到了哪些信号,然后就要开始处理这些信号,但在处理这些信号之前,进程先比对一下这些信号有没有存在于blocked集合中,如果存在了(位图为1),意味着现在这个信号不被处理,直到解除阻塞 。

这里写图片描述

信号阻塞接口:
阻塞: int sigprocmask(int how,sigset_t *set,sigset_t *oldset)
how:对集合所做的操作,SIG_BLOCK(对set集合,SIG_UNBLOCK,SIG_SETMASK

实现信号阻塞屏蔽
//先定义集合
//将信号添加到集合中:intsigfillset(sigset_t *set);
int sigpending(sigseet_t set)//将当前pending集合中的信号取出来放到set中 int sigismember(const sigset_tset,int signum) 判断信号是否在集合中

信号的注销

就是从pending中将要处理的信号移除但分情况: 非可靠信号: 注册的时候,是给sigqueue链表添加一个信号结点,并且将pending集合对应位图置1,当这个信号注册的时候,如果位图已经置1,代表信号已经注册过了,那就不做任何操作,不添加新结点 注销:删除链表中的节点,将对应位图置 0 可靠信号: 注册:是给sigqueue链表添加一个信号节点,不管这个可靠信号是否已经注册过,如果没有注册过,就添加新节点,对应位图置1 注销:删除一个节点,然后查看链表中有没有相同的节点,如果有,那么信号对应的位图 110 依然为1,若果没有相同的节点,代表这个信号全部被处理,因此对应位图置0 111 112 可靠信号因为每次信号到来都会添加新节点,所以不会丢失信号。

#信号处理(信号的递达)
当进程收到一个信号时,就意味着现在有一个重要的事情要处理,因此会打断我们当前的操作,然后去处理这件事

  • 什么时候处理信号呢?
    进程从内核态切换到用户态时会去检测是否有信号要处理。

信号的处理方式:

  1. 默认处理:系统定义好的处理方式

  2. 忽略处理方式:忽略和阻塞完全不同,一个被忽略的信号来了以后就直接丢弃

  3. 自定义处理方式:用户自己定义一个的一个处理信号的方式,使操作系统按照定义的这个处理方式来处理这个信号

信号忽略和信号注册的区别,阻塞一个信号后,信号依然会注册在pending集合中,而忽略信号,则信号直接丢弃,不会注册

信号处理方式接口:

sighandler_t signal (int signum, sighandler_t handler)
功能:修改一个信号的处理方式
参数:指定修改哪个信号; 第二个是处理的方式,SIG_IGN(忽略处理),SIG_DFL(默认处理)

int sigaction(int signum,const struct signation *act, struct signation *oldact)
功能:修改信号的处理方式
参数:signum指定修改哪个参数;act是指定的处理动作;oldact 保存信号原来的处理方式
结构体:
struct sigaction {
void (*sa_handler)(int); //自定义处理方式
void (*sa_sigaction)(int, siginfo_t *, void *); //也是自定义处理方式,可以接收信号携带的参数
sigset_t sa_mask; //在处理信号时要阻塞的其他信号(避免其他信号打扰自定义操作)
int sa_flags; //决定用哪个函数作为信号处理接口,默认0则是sa-handler,给定SA_SIGINFO则是sa_sigaction
void (*sa_restorer)(void);
};

信号的捕捉

针对自定义处理方式,进程捕捉到信号然后进行处理的 过程

进程是从内核态切换到用户态时,需要判断是否有信号要处理,然后返回用户态去自定义处理,在返回内核,在返回用户态
如何实现从用户态到内存态的切换?中断,异常,系统调用

可重入函数

可重入函数:某个函数调用的时候,如果中间操作被打断,在其他地方有重复多次调用,不影响运行结果(对其他地方的调用产生影响),这个函数就叫可重入函数
不可重入函数:多次调用影响结果。

可重入函数的特点:操作了一些公共数据

竞态条件:

如果我们的某个操作不是一个原子操作,那么意味着这个操作有可能被打断然后去做其他的事情,这时做其他事情可能产生一些逻辑问题

int  sigsuspend(const sigsey_t *mask)     
  //集合了临时阻塞指定信号,并陷入阻塞等待的一个原子操作


 功能:临时使用mask中的信号替换阻塞结合blocked中的信号,然后进入阻塞等待,唤醒后还原。
 也就是说临时替换阻塞信号,进入休眠,唤醒时 ,在将原来的阻塞信号替换回

自己写的sleep为例,讲解竞态条件,说的是alarm函数和pause函数间如果被打断去干其他事情,可能造成pause永久阻塞

volatile:c语言的关键字,保证内存的可见性。每次处理这个被volatile修饰的变量时,都会从内存中重新加载变量的值都内存器中

因为程序在优化的时候,如果一个变量使用的频率很高的话(比如全局变量)那么,这个变量有可能被优化为只向内存器加载一次,往后直接使用寄存器中保存的值。 而不关心它在内存里的值,因此可能会造成一些逻辑错误

SIGCHILD 信号
自定义处理,循环的原因是对子进程资源随退随回收,且大致不影响逻辑。
信号是会打断进程的阻塞操作,唤醒正在休眠的进程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux系统提供了各种系统调用API用于进程之间通信:    无名管道PIPE    命名管道FIFO    消息队列    共享内存    信号量    文件锁    信号signal....其中还包括system V和POSIX 两种接口标准,除此之外,Linux系统自身还扩展了自己的一套API接口用于进程通信,比如signalfd、timerfd、eventfd等。本视频教程为《Linux系统编程》第05期,本期课程将会带领大家学习Linux下将近15种进程通信IPC工具的使用,了解它们的通信机制、编程实例、使用场景、内核中的实现以及各自的优缺点。本课程会提供PDF版本的PPT课件和代码,学员购买课程后可到课程主页自行下载嵌入式自学路线指导图:------------------------------------------------------------------------------------------------------                   《嵌入式工程师自我修养》嵌入式自学系列教程                                          作者:王利涛------------------------------------------------------------------------------------------------------一线嵌入式工程师精心打造,嵌入式学习路线六步走: 第 1 步:Linux三剑客零基础玩转Linux+UbuntuGit零基础实战:Linux开发技能标配vim从入门到精通基础篇:零基础学习vim基本命令vim从入门到精通定制篇:使用插件打造嵌入式开发IDEmakefile工程实践基础篇:从零开始一步一步写项目的Makefilemakefile工程实践第2季:使用Autotools自动生成Makefile软件调试基础理论printf打印技巧Linux内核日志与打印使用QEMU搭建u-boot+Linux+NFS嵌入式开发环境第 2 步:C语言嵌入式Linux高级编程第1期:C语言进阶学习路线指南第2期:计算机架构与ARM汇编程序设计第3期:程序的编译、链接和运行原理第4期:堆栈内存管理第6期:数据存储与指针第7期:嵌入式数据结构与Linux内核的OOP思想第8期:C语言的模块化编程第9期:CPU和操作系统入门      搞内核驱动开发、光会C语言是不行的!      你还需要学习的有很多,包括:计算机体系架构、ARM汇编、程序的编译链接运行原理、CPU和操作系统原理、堆栈内存管理、指针、linux内核中的面向对象思想、嵌入式系统架构、C语言的模块化编程.....第 3 步:Linux系统编程第00期:Linux系统编程入门第01期:揭开文件系统的神秘面纱第02期:文件I/O编程实战第03期:I/O缓存与内存映射第04期:打通进程与终端的任督二脉第05期:进程通信-------------------we are here!‍    第 4 步:Linux内核编程‍    练乾坤大挪移,会不会九阳神功,是一道坎。搞驱动内核开发,懂不懂内核也是一道坎。第 5 步:嵌入式驱动开发    芯片原理、datasheet、硬件电路、调试手段、总线协议、内核机制、框架流程....第 6 步:项目实战    嵌入式、嵌入式人工智能、物联网、智能家居...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值