Linux中的信号(注册,注销,处理,阻塞)

2024-03-01 0 967
目录
  • 信号注册
  • 信号注销
  • 信号处理
  • 信号阻塞
    • PCB如何获取signal函数使用自定义信号
    • sigaction函数
  • 可重入函数
    • volatile关键字
      • sigchld
        • 总结

          未决:pending(汉语翻译:待定,即将发生)

          • 信号产生但没有被处理,pending表0变1,表示进程收到对应信号(准确的来说叫信号写入)
          • 阻塞,为了拦截对应信号做出对应的处理动作。不想处理某些信号,拦不住别人给进程发
          • 信号待定是可以随时递达(被处理),如果没有被阻塞,信号会在合适的时候递达(被处理)
          • 什么叫合适的时候:从内核态切换到用户态时进行信号的检测和处理
          • 如果信号阻塞,该信号永远处于未决状态
          • 总之pending表就是随时能处理信号,一旦设置了阻塞,只有解除阻塞才能处理信号

          信号注册

          信号注册:在进程中做标记,让进程能够知道自己收到某个信号,官方说法操作系统内核给进程发送信号的过程,这个过程称为注册

          • PCB中有一个pending未决(没有被处理)信号的集合(是一个位图结构),标记进程收到了某个信号
          • PCB中有一个sigqueue链表(实质是一个双向链表),进程每收到一个信号,就是给该链表中添加一个对应信号的节点,并且修改位图,将信号值对应位置置1

          非可靠信号注册:

          • 如果没有收到信号则注册(添加节点,位图置1)
          • 如果之前收到了信号还未处理,则什么都不做(丢弃)

          可靠信号注册:

          • 不管位图是否置1(是否已经注册,还没处理),都会添加一个信号节点

          可以通过sigqueue函数在发送信号的同时携带数据

          信号注销

          什么是注销:

          • 在信号被处理之前,消除信号存在的痕迹
          • 主要防止信号被重复处理

          非可靠信号的注销:

          删除信号的信息节点,位图置0

          可靠信号的注销:

          删除信号的一个信息节点,当前没有相同节点则位图置0

          信号处理

          处理就是调用信号的处理函数

          有三种处理方式

          默认处理:系统中已经定义好了处理方式

          忽略处理:空的处理方式

          SIGCHLD:子进程再退出时给父进程发送一个SIGCHLD信号,父进程对于该信号的处理方式是忽略处理 该信号对于所有进程而言都是忽略处理

          自定义处理:自己定义一个处理函数,替换掉处理函数

          自定义处理函数signal

          用handler函数,替换signum信号当前的处理函数

          Linux中的信号(注册,注销,处理,阻塞)

          自定义信号的捕捉流程(捕捉流程:从用户态进入内核态,找到信号并处理的过程)

          进入内核的原因:

          • 系统调用函数
          • 中断
          • 进程异常

          当前执行流在用户态收到信号,不能立即处理信号,而是从用户态切换到内核态时处理

          进入内核后,执行流并不知道要处理信号,而是执行一些功能当要从内核态返回到用户态,一定会调用do_signal函数判断当前进程有无收到信号,收到则立即处理,没有直接返回到用户态

          如果有信号就执行信号对应动作

          如果是自定义处理方式,会从内核态切换到用户态执行自定义函数,调用sigreturn函数再次进入到内核(不能以内核身份执行用户写的自定义代码)

          再去调用dosignal判断是否有其它信号需要处理

          信号阻塞

          阻塞(阻塞不是在处理信号)

          信号被阻塞后依然会注册,但是暂时不处理(直到被解除阻塞)

          PCB中有个block阻塞信号集合,哪个信号被添加到block阻塞集合中,表示收到信号但暂时不处理

          更改或获取进程的信号block表(将信号集合设置进内核)

          int sigprocmask(int how, const sigset_t* set, sigset_t* oset)

          how:对PCB中信号阻塞集合进行的操作

          SIG_BLOCK:block|=set

          把set集合中的信号添加到block集合中,阻塞set集合中的信号

          SIG_UNBLOCK:block&=~set

          解除

          SIG_SETMASK:block=set

          重新设置block表

          oldset将修改前block集合中的信息添加到old中,便于还原

          是输出型参数,将原block返回,不想返回设置为NULL

          要屏蔽2号信号,将2号信号添加到set中

          set是输入型参数

          • 函数在阻塞指定信号时,就要传进去一个集合, 意味着要将所有的信号添加到一个集合中
          • 所以要先定义一个信号集合的变量sigset_t
          • 通过sigsetempty(sigset* set)先将集合中的数据清空
          • 通过sigaddset(sigset_t* set, int sugnum)—-将signum添加到set集合中(单个添加)
          • 通过sigfillset(sigset_t *set)————————将所有信号添加到set中
          • 通过sigdelset(sigset_t *set, int signum)—-移除指定信号
          • 通过sigismember(sigset_t *set, int signum)–按段信号signum是(返回1)否(-1)在set信号集合中

          9号和19号不能被阻塞,忽略,自定义

          僵尸进程是已经死了的

          SIGPIPE

          • 管道读端关闭,继续写,会在write会触发异常

          SIGCHLD

          • 子进程退出时给父进程发送的信号
          • 这个信号的默认处理方式是忽略,因此未对子进程退出做出处理

          修改SIGCHLD的默认处理方法(让回调函数内部wait子进程)其实并不合理

          因为该信号是非可靠信号,大量子进程同时退出,发出的多个非可靠信号可能只注册了一次

          可直接显示忽略,明确告诉OS,子进程退出我不关心,直接把资源回收

          signal(SIGCHLD, SIG_IGN)

          或者在回调函数内部,使用waitpid循环等待每一个子进程退出的信号

          int sigpending(sigset_t *set)

          获取当前进程的pending信号集(输出型参数),放入set集合中

          PCB如何获取signal函数使用自定义信号

          在PCB有一个结构体指针struct sighand_struct* sighand,该指针指向结构体sighand_struct;

          该结构体中有一个struct k_sigaction action[_NSIG]数组,数组中的每个元素是一个多重嵌套结构体,如图

          Linux中的信号(注册,注销,处理,阻塞)

          • action数组中一个元素就保存了一个信号对应的处理方式,
          • 例如,2号信号对应一个action数组中的元素,2号信号的处理方式就保存在_sighander_t类型的变量中
          • 该类型的变量保存了三种数据,就是上述的三种信号处理方式,默认处理、忽略处理、自定义处理

          PCB与signal函数的关系

          • 在调用signal函数,参数sighandler保存了新函数地址,该新地址是传送给PCB中action数组的元素,该元素内容被修改
          • 就通过signal函数改变了2号信号的处理方式,
          • 当进程在处理2号时通过PCB一步步找到action数组找到2号信号的处理方式,发现其处理方式是自定义的函数
          • 从而调用自定义的函数

          sigaction函数

          修改指定信号的处理动作

          改变action数组元素中第二个结构体,而signal函数只是改变该结构体下的一个成员变量

          需要配合其他函数使用

          Linux中的信号(注册,注销,处理,阻塞)

          sa_handler是信号的处理方式

          sa_mask:信号正在被处理时,内存会将当前信号加入到信号屏蔽字,信号返回时回复成原来的信号屏蔽字

          这样保证了在处理某个信号时,此信号再次产生就会被阻塞直到当前处理结束

          Linux中的信号(注册,注销,处理,阻塞)

          需要用到的清空函数(上图黑色部分指错了):

          Linux中的信号(注册,注销,处理,阻塞)

          可重入函数

          现有一个链表需要进行头插,在执行头插函数时,代码进入内核在退出时发现一个信号,该信号也是在执行头插代码

          这样会发生内存泄漏,信号所调用的头插函数插入的节点找不到了,这种函数就称为不可重入函数

          如果只是访问变量或者参数,则称为可重入函数

          volatile关键字

          Linux中的信号(注册,注销,处理,阻塞)

          在编译的时候,全局变量flags只进行逻辑运算,编译器会自动将其优化,将变量优化到寄存器中

          不从内存中读取,而信号修改flag的值只是对内存值修改

          防止编译时因优化,使CPU不从内存中取值,而从寄存器中取值

          sigchld

          子进程退出时不是默默的退出

          子进程退出时会给父进程发送SIGCHLD信号,该信号的默认处理方式是IGN(忽略)

          无论是阻塞还是非阻塞,都需要父进程主动去询问

          则现在提前告诉子进程,子进程死亡时发消息,父进程收到后回收资源即可

          所以通过signal函数修改SIGCHLD信号的处理方式,改为wait等待子进程即可

          前七个子进程结束,通过waitpid等待成功,在等待第八个子进程退出时,父进程会阻塞在waitpid函数处

          通过sigaction将SIGCHLD的处理动作设置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程

          这两个忽略其实没有任何区别

          意义:手动设置了SIG_IGN,除了修改父进程,子进程也会受影响,让子进程退出不给对应父进程发送信号

          在OS层面上,手动设置信号,会在设置子进程时不给父进程发消息,并自动释放

          总结

          以上为个人经验,希望能给大家一个参考,也希望大家多多支持悠久资源网。

          收藏 (0) 打赏

          感谢您的支持,我会继续努力的!

          打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
          点赞 (0)

          悠久资源 Linux服务器 Linux中的信号(注册,注销,处理,阻塞) https://www.u-9.cn/server/linux/174401.html

          常见问题

          相关文章

          发表评论
          暂无评论
          官方客服团队

          为您解决烦忧 - 24小时在线 专业服务