用多少眼泪才能让你相信 发表于 2024-11-18 14:29:01

信号保存和信号处置惩罚

目次


信号保存中告急的概念
内核中信号的保存
对sigset_t操作的函数
对block,pendding,handler三张表的操作
sigpromask
​编辑
sigpending
是否有sighandler函数呢?
案例
信号处置惩罚
操作系统是如何运行的?
硬件中断 
时钟中断
软中断
死循环
缺⻚中断?内存碎⽚处置惩罚?除零野指针错误?
明白用户态和内核态
信号的自界说捕捉 
sigaction
验证壅闭信号
处置惩罚完解除对信号的壅闭
验证在处置惩罚前将pending表清零 
验证sa_mask的作用
可重入函数
案例
volatile
SIGCHLD信号
waitpid增补
验证子进程退出会给父进程发送SIGCHLD信号
 基于信号对子进程回收
问题1:
办理方法循环回收
问题2: 



信号保存中告急的概念


信号捕捉的三种方式:1.默认 SIG_DFL  default
                                    2.忽略  SIG_ING    ingore
                                    3.自界说
信号递达:实际实行信号的处置惩罚动作
信号未决:信号从产生到递达的状态
信号壅闭:进程可以壅闭某个信号  -->壅闭/屏蔽特定信号,信号产生了,肯定把信号pendding(保存),并且信号永世不递达,除非解除壅闭。

os信号的保存都与以上的三个概念有关

壅闭 vs 忽略
壅闭发生在为信号递达之前
忽略发生在信号递达

内核中信号的保存

内核数据布局表示图
https://i-blog.csdnimg.cn/direct/7a8ba347f3c5401fa3ab37e5e681321a.png
pending:信号未决
block: 信号壅闭
handler:信号递达

进程维护了以上的布局,以是进程是可以识别信号的。
以后对进程信号的操作都是为绕着这三张表睁开的。

内核中的相应的数据布局
https://i-blog.csdnimg.cn/direct/6395055421d247039637035d30863e19.png
 sigset_t 是信号集,也是信号屏蔽字(signal mask)(有block,pending)
雷同umask

对sigset_t操作的函数

https://i-blog.csdnimg.cn/direct/d719d0e6d9724030a0b4a84e64e5268a.png

https://i-blog.csdnimg.cn/direct/75c19430847a42b7915c737143f17503.png

对block,pendding,handler三张表的操作

sigpromask

读取/修改进程屏蔽字 block表 
https://i-blog.csdnimg.cn/direct/57cab4988b704f85b4703e925eed8993.png

set:进程中的block表修改基于set 
oldset:修改前block表的模样
how:以何种方式修改mask(block表)
1. SIG_BLOCK:  mask = mask | set  新增
2.SIG_UNBLOCK : mask = mask & ~set  去除指定信号
3.SIG_SETMASK: mask = set  覆盖

sigpending

https://i-blog.csdnimg.cn/direct/6e721708e1ab4e94b1edc9b059f32e1e.png
通过该函数可以知道pending表的环境
但为什么不设置一个输入型参数,修改pending表呢?
因为信号的产生都是在修改pending表

是否有sighandler函数呢?

答案:没有
那么如何修改handler表呢?
signal就不停在修改啊
是否有名顿开的感觉啊。
https://i-blog.csdnimg.cn/direct/583345b4016e4beca707359a46b0f61a.png

案例

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>
#include <vector>
void PrintBlock(const sigset_t &block)
{
    std::cout<<"block"<<"["<<getpid()<<"]: ";
    for (int i = 31; i > 0; i--)
    {

      if (sigismember(&block, i))
      {
            std::cout << 1;
      }
      else
      {
            std::cout << 0;
      }
    }
    std::cout << std::endl;
}

void handler(int signo)
{
    std::cout <<"signo:"<<signo<<" 信号递达"<<std::endl;
    exit(0);
}
int main()
{
    signal(2,handler);
    sigset_t block, oldblock;

    sigemptyset(&block);
    // 将二号新号加入
    sigaddset(&block, 2);
    //阻塞二号信号
    int ret = sigprocmask(SIG_SETMASK, &block, &oldblock);
    if (ret < 0)
    {
      perror("sigpromask");
      exit(1);
    }
    int cnt = 0;
    while (true)
    {
      PrintBlock(block);
      sleep(3);
      if(cnt==5)
      {
            PrintBlock(oldblock);
            //取消对二号信号的阻塞
            sigprocmask(SIG_SETMASK, &oldblock, nullptr);
            
      }
      cnt++;
    }
    return 0;
}
 实行结果https://i-blog.csdnimg.cn/direct/61a7a1c5c6204f26920dc7fb77e77a43.png
一开始block表中二号信号被壅闭,纵然收到了二号信号也不能处置惩罚,当修改block表二号没被壅闭时,在实行二号信号相应的处置惩罚方法。

信号处置惩罚

信号没被处置惩罚,被保存起来---->合适的时候
那么什么是合适的时候?
进程从用户态切换会用户态时,检测额pending && block,决定是否用handler表处置惩罚信号。


os的运行状态:
1.用户态 ---> 实行用户写的代码
2.内核态  -->实行内核代码
信号自界说捕捉的流程
https://i-blog.csdnimg.cn/direct/26ce7fab2fd849b19f15f09b93a6438e.png
https://i-blog.csdnimg.cn/direct/2c7a0268f4c44fee80f1adcbc7a2dd1a.png

操作系统是如何运行的?

硬件中断 

硬件中断的处置惩罚流程
 https://i-blog.csdnimg.cn/direct/94590a58d1c145dcb5a32ed6b64659e2.png
中断向量表在os启动时就添补好了。
时钟中断

进程是由操作系统调度的,那么操作系统谁有谁调度的呢?
https://i-blog.csdnimg.cn/direct/79d72d7ca256429ea14ee0aaadaf2b1e.png
有个设备时钟源定期向cpu发送中断,cpu处置惩罚中断,实行中断处置惩罚进程,而查表查到的终端服务是进程调度。
这样操作系统不就能自主调度了吗
时钟源是外设,将中断传给cpu效率有点低,如今的时钟源都是继续在cpu中。

主频就是控制CPU工作的时钟信号的频率
主频越快,相应单元时间内操作系统实行的中断服务越多,进程调度越频繁,os的性能越高。

进程调度不肯定切换进程
https://i-blog.csdnimg.cn/direct/98fef5e3847644e28319a562007fbd75.png
软中断


是否能不通过外设来产生中断呢?
可以,比如os支持系统调用,cpu设计了汇编指令(int 0x80 或 syscall),让cpu在内部触发中断逻辑,
这样就可已在软件中触发中断-->再根据中断号,查系统调用表,实行相应的系统调用
https://i-blog.csdnimg.cn/direct/bd30d18b737d4ea98bee47f19ead58f8.png
系统调用号的本质就是数组下标
https://i-blog.csdnimg.cn/direct/07d3b54ecb8c42169d9ea43f25c5a05d.png
https://i-blog.csdnimg.cn/direct/089e4bf3894d48d0840aa1bef947577d.png
https://i-blog.csdnimg.cn/direct/1c51da837e5048d8be1cb41490c15f08.png
死循环

无论是软中断还是硬件中断
os必要什么方法直接,向中断向量表中添加就可以了:os的本质就是一个死循环
void main(void) /* 这⾥确实是void,并没错。 */
{ /* 在startup 程序(head.s)中就是这样假设的。 */
...
/*
* 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到⼀个信号才会返
* 回就绪运⾏态,但任务0(task0)是唯⼀的意外情况(参⻅'schedule()'),因为任
* 务0 在任何空闲时间⾥都会被激活(当没有其它任务在运⾏时),
* 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运⾏,如果没
* 有的话我们就回到这⾥,⼀直循环执⾏'pause()'。
*/
for (;;)
pause();
} // end main ---------------------------------------------------------------------------------------------------------------------------------

用户怎么把中断号给os?寄存器(如eax)
os怎么把返回值返回给用户? 也是寄存器
用户和内核之间通报信息用的是寄存器。

系统调用-->是通过软中断完成的
中断号-->系统调用号写入寄存器eax-->软中断-->查表--->实行相应的方法-->将返回值写入寄存器返回给用户

既然实行系统调用必要触发软中断,那我们使用系统调用函数值没有效 int 0x80 或 syscall?
因为linux提供的系统调用接口,不是C语言函数,而是  系统调用号 + 约定的通报参数 ,用于内核和用户间通报信息的 系统调用号和返回值的寄存器  + syscall  或 int 0x80
也就是说GNU glibc 把真正的系统调用封装--->C语言封装版的系统调用

OS是躺在中断处置惩罚进程上的代码块。

 

缺⻚中断?内存碎⽚处置惩罚?除零野指针错误?

这些错误都是通过软中断来处置惩罚的。


软中断有: 1.陷阱  (int 0x80 或 syscall)
                    2.非常
   CPU内部的软中断,⽐如int 0x80大概syscall,我们叫做 陷阱   •   CPU内部的软中断,⽐如除零/野指针等,我们叫做 非常。(以是,能明白“缺⻚非常”   为什么这么叫了吗?)   明白用户态和内核态

内核页表:整个系统只有一张
用户页表:每个进程都有一张
 对于任何进程,不管如何调度,任何进程都只能找到一个os
为什么只有一张内核页表?
因为用户最关心的是系统调用。
用户关心系统调用的地点吗?
不关心,只必要知道系统调用号就可以了。


系统调用是在进程的地点空间中发起的,但实在行是在内核地点空间中完成的。
1.调用任何函数(库中)都是在我们自己进程中的地点空间中进行的
操作系统无论怎么切换进程,进程都只能找到一个操作系统
2.os调用方法的实行是在内核地点空间中实行的
不管是通过哪个进程的地点空间进入内核,都是通过软中断进行操作的。

   ⽤⼾态就是执⾏⽤⼾GB时所处的状态   •   内核态就是执⾏内核GB时所处的状态       如何区分内核态还是用户态?    cpu中有cs段寄存器    https://i-blog.csdnimg.cn/direct/eac72ec3b33a483ba7c1479970a8291a.png用户如何进入内核态?
1.时钟/外设中断
2.非常   cpu--->软中断
3.陷阱    系统调用(int 0x80,syscall)

信号的自界说捕捉 

https://i-blog.csdnimg.cn/direct/32fb7fc9b13e4b078d85b71d82f5cf0a.png
1.前三步很好明白,那么为什么要转为用户态来调用处置惩罚方法,直接在内核态来处置惩罚不好吗?
 内核和用户的权限不同,有安全风险;并且用户来处置惩罚,如果出了错误用户担责任。
2.信号处置惩罚完只能从内核返回,因为处置惩罚函数的调用与main()函数之间不存在调用关心(栈区)
3.如何继续实行原来的程序?
cpu中有pc寄存器(pc指针),指向下一条要实行的命令,只必要恢复pc指针的值就可以了。

sigaction

作用检查并修改handler表
https://i-blog.csdnimg.cn/direct/503dc9f4507f400eb06de020229221dd.png
sigaction布局 
https://i-blog.csdnimg.cn/direct/c43b619a42fe45bda46a38a96d900f8b.png
sa_handler :函数指针
sa_mask:自界说屏蔽的信号 

信号处置惩罚期间,如果处置惩罚方法有系统调用,那么就陷入内核态
os不答应信号处置惩罚方法嵌套--->实现该方法的方式是将block表相应的信号置为1(壅闭该信号),但信号处置惩罚完会自动解除壅闭
什么时候pending表清0?
在调用信号处置惩罚之前,因为如果是处置惩罚完之后清零,那么就无法区分pending中的1是处置惩罚进程时 收到的1,还是处置惩罚完时的1                                                                                                                                                                                                                
验证壅闭信号

void PrintBlock()
{
    sigset_t block;
    sigprocmask(SIG_SETMASK,nullptr,&block);
    for(int i = 31;i>0;i--)
    {
      if(sigismember(&block,i))
            std::cout<<1;
      else
            std::cout<<0;
    }
    std::cout<<std::endl;
}
void handler(int signo)
{
    std::cout<<"signo: "<<signo<<std::endl;
    while(true)
    {
      PrintBlock();
      sleep(3);
    }
}

int main()
{
    //signal(2,handler);
    struct sigaction act,oldact;
    act.sa_handler = handler;
    sigaction(2,&act,&oldact);
    //std::cout<<"执行"<<std::endl;
    PrintBlock();
    while(true)
    {
      pause();
    }
}

https://i-blog.csdnimg.cn/direct/ff24cbabc26f43c29d6db8b53d956374.png
处置惩罚完解除对信号的壅闭

void PrintBlock()
{
    sigset_t block;
    sigprocmask(SIG_SETMASK,nullptr,&block);
    for(int i = 31;i>0;i--)
    {
      if(sigismember(&block,i))
            std::cout<<1;
      else
            std::cout<<0;
    }
    std::cout<<std::endl;
}
void handler(int signo)
{
    std::cout<<"signo: "<<signo<<std::endl;
    // while(true)
    // {
    //   PrintBlock();
    //   sleep(3);
    // }
    PrintBlock();
    sleep(3);
}

int main()
{
    //signal(2,handler);
    struct sigaction act,oldact;
    act.sa_handler = handler;
    sigaction(2,&act,&oldact);
    //std::cout<<"执行"<<std::endl;
    PrintBlock();
    while(true)
    {
      pause();
      PrintBlock();
    }
} https://i-blog.csdnimg.cn/direct/0002bc8ee90b4946bd37066a94d39030.png
验证在处置惩罚前将pending表清零 

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>
#include <vector>
void PrintBlock()
{
    sigset_t block;
    sigprocmask(SIG_SETMASK, nullptr, &block);
    std::cout<<"block: ";
    for (int i = 31; i > 0; i--)
    {
      if (sigismember(&block, i))
            std::cout << 1;
      else
            std::cout << 0;
    }
    std::cout << std::endl;
}
void PrintPending()
{
    sigset_t pending;
    sigpending(&pending);
    sigprocmask(SIG_SETMASK, nullptr, &pending);
    std::cout<<"pending: ";
    for (int i = 31; i > 0; i--)
    {
      if (sigismember(&pending, i))
            std::cout << 1;
      else
            std::cout << 0;
    }
    std::cout << std::endl;
}
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    // while(true)
    // {
    //   PrintBlock();
    //   sleep(3);
    // }
    PrintBlock();
    PrintPending();
    sleep(3);
}

int main()
{
    // signal(2,handler);
    struct sigaction act, oldact;
    act.sa_handler = handler;
    sigaction(2, &act, &oldact);
    // std::cout<<"执行"<<std::endl;
    PrintBlock();
    PrintPending();
    while (true)
    {

      pause();
      PrintBlock();
      PrintPending();
    }
}

 https://i-blog.csdnimg.cn/direct/c577f9684b44404983f3644f2d15226b.png


验证sa_mask的作用

void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    // while(true)
    // {
    //   PrintBlock();
    //   sleep(3);
    // }
    PrintBlock();
    //PrintPending();
    while(true)
    {

    }
    //sleep(3);
}

int main()
{
    // signal(2,handler);
    struct sigaction act, oldact;
    act.sa_handler = handler;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask,2);
    sigaddset(&mask,3);//把三号信号屏蔽
    act.sa_mask = mask;//设置屏蔽信号
    sigaction(2, &act, &oldact);
    // std::cout<<"执行"<<std::endl;
    PrintBlock();
    //PrintPending();
    while (true)
    {

      pause();
      //PrintBlock();
      //PrintPending();
    }
} https://i-blog.csdnimg.cn/direct/58c3bfa2bf574a6cbc6c2025e0d59c44.png
屏蔽了2号和3号信号 

可重入函数


一个函数被两个以上的实行流同时进入--->重入(重复进入)
重入,不出问题--->可重入函数
重入,出问题---> 不可重入函数--->涉及对全局资源的处置惩罚

大部分库中的函数都是不可重入函数
重入和不可重入没有优劣之分,只是特性。
案例

https://i-blog.csdnimg.cn/direct/ae1aeeb3fc574b379e48b80ca2d64329.png
因为中断,将node2插入链表,head指向node2,但返回main函数继续实行时,head又指向了node1导致找不到node2,也就造成了内存泄漏。main和信号的处置惩罚是两个实行流,进入的都是inset函数。

volatile

volatile是易变关键字

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int flag = 1;
void handler(int signo)
{
    printf("signo:%d\n",signo);
    printf("flag -> 0\n");
    flag = 0;

}
int main()
{
    signal(2,handler);
    while(flag);
   
    printf("quit normal!\n");
    return 0;
} https://i-blog.csdnimg.cn/direct/c0cedd751e7344879704a457d88bc36b.png 没开启编译器优化,会实时更新进程地点空间中flag的值

https://i-blog.csdnimg.cn/direct/0e3c2e31f38141dfa6cf853ce88b65c1.png
当把编译器的优化等级开高一些,为什么不退出了呢?
https://i-blog.csdnimg.cn/direct/0fa32f13af0a442f9c72667b09087fdb.png
开了优化后,纵然进程内存地点空间中的flag改变了,但cpu中的flag不会根据进程地点空间中的flag更新寄存器中的flag,以是不停死循环没有退出 。
volatile flag = 1;//加上volatile关键字 纵然优化编译器优化等级开高了,也实时通过进程地点空间中值修改寄存器中的值。 
https://i-blog.csdnimg.cn/direct/fd0bd465afdf4ef198d3bdd460b99c91.png

SIGCHLD信号

当子进程退回后----(给父进程发送)--->SIGCHLD信号
waitpid增补

https://i-blog.csdnimg.cn/direct/ea2f788970654677a2ba529f07f5d2e9.png
https://i-blog.csdnimg.cn/direct/1fa0eb7aade9422e830bb4b33d9faf8f.png
pid为-1时,可以等候任何子进程。

验证子进程退出会给父进程发送SIGCHLD信号

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    pid_t ret = waitpid(-1, nullptr, 0);//pid==-1,表示等待任何子进程
    if (ret > 0)
    {
      std::cout<<"子进程: "<<ret<<"退出"<<std::endl;
    }
    else if (ret < 0)
    {
      std::cout << "wait error" << std::endl;
    }
    sleep(3);
}
int main()
{
    signal(SIGCHLD, handler);
    pid_t pid = fork();
    if (pid == 0)
    {
      std::cout << "我是子进程: ";
      std::cout << getpid() << std::endl;
      exit(0);
    }
    std::cout << "我是父进程: " << getpid() << std::endl;
    while (true);
    return 0;
} https://i-blog.csdnimg.cn/direct/cc5b1d2123604fb1a622c67dd38e863a.png
 基于信号对子进程回收

问题1:

如果有n个子进程同时给父进程发送SIGCHLD信号,这是只能记录和处置惩罚一个信号;
如果pending记录了一个信号,其他的信号再发过来也不会被记录。
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    pid_t ret = waitpid(-1,nullptr,0);
    if(ret>0)
    {
            std::cout << "子进程: " << ret << "退出" << std::endl;
    }
    else if(ret<0)
    {
      std::cout<<"wait error"<<std::endl;
    }
}
int main()
{
    signal(SIGCHLD, handler);
    pid_t pid = fork();
    for (int i = 0; i < 5; i++)
    {
      pid_t pid = fork();
      if (pid == 0)
      {
            std::cout << "我是子进程: ";
            std::cout << getpid() << std::endl;
            exit(0);
      }
    }
    while(true);
    return 0;
} https://i-blog.csdnimg.cn/direct/da01e8f2b964459d958c47af79964660.png
创建了10个子进程只有7个子进程退出,剩余的子进程发出的信号没有被处置惩罚,这些子进程变为了僵尸
https://i-blog.csdnimg.cn/direct/f65614ebb6154472a35e5201353ceaec.png
办理方法循环回收 



void handler(int signo)
{
    while (true)
    {
      pid_t ret = waitpid(-1, nullptr, 0);
      if (ret > 0)
      {
            std::cout << "子进程: " << ret << "退出" << std::endl;
            // std::cout << "wait success" << std::endl;
      }
      else if (ret < 0)
      {
            std::cout << "暂时回收完毕" << std::endl;
            break;
      }
    }
} 运行结果:子进程全都被回收 
 https://i-blog.csdnimg.cn/direct/e1dc021360c543699a54604a717eaf3c.png
没有僵尸子进程
https://i-blog.csdnimg.cn/direct/4064239fff9f42708c30baa543c0db00.png
问题2: 

基于问题1,如果10个子进程中,有几个进程迟迟不退出那么,就会壅闭在信号处置惩罚那里等候子进程的退出
办理方法
https://i-blog.csdnimg.cn/direct/9fdc9e1735cb4c11aa29b24c74e9754f.png
options中有个WNOHANG,如果没有子进程竣事则立刻返回0
这样处置惩罚方法就有壅闭变为了非壅闭,如果有子进程退出则又会在进入循环回收,一次往复,避免了子进程出现僵尸。 
https://i-blog.csdnimg.cn/direct/d777b29b0fbd449885219d85ad501e9b.png

pid_t ret = waitpid(-1, nullptr, WNOHANG);

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 信号保存和信号处置惩罚