• OS>>信号的产生,信号的保存,信号的捕捉



    请添加图片描述

    背景

    1. 对信号的处理的方式是早于信号的生成
    2. 没有任何行为在不贯彻OS的情况下,可以对内核数据结构进行访问。因此无论是以什么方式产生的信号,最终都是要通过OS传递给进程,
    3. 信号是一种异步通信方式,即信号可以在进程生命周期的任意时刻发送,可能因为当前进程正在处理某个任务而暂时搁置对信号的处理
    4. 信号是一种触发条件,对其的处理动作(函数),一定已经内置在OS内核中
    5. 即使进程接受了信号,也会因为当前的任务而搁置对信号的处理,最后在合适的时候去处理这个信号,这个时机是内核态到用户态转换的是时候。
    6. 进程奔溃的本质是收到了相应的信号,执行了相应的默认动作,杀死了进程

    信号种类

    在这里插入图片描述

    1. 1~31为普通信号,34 ~64为实时信号。

    2. 9号信号不可被自定义捕捉,不可被屏蔽,这是一种OS的保护机制

    信号捕捉

    一般而言信号的处理有3种方式:

    1. 默认动作,由OS内核提供的对该信号捕捉后的处理行为
    2. 忽略,OS捕捉了该信号,但是不做任何的处理行为
    3. 自定义动作,由用户通过使用signal函数修改OS捕捉该信号的行为为自定义的地址。

    signal

    #include 
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    
    • 1
    • 2
    • 3
    1. 修改OS采取的默认行为,为自定义的行为;只有当信号递达时,才会采取相应的行为。
    2. signal内部会回调 handler函数,并个handler传递signum;
    3. 本质上是修改函数指针数组中的元素。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WG7OrsNe-1668181890657)(./%E4%BF%A1%E5%8F%B7.assets/image-20221110122209599.png)]

    事后调试

    .1. 在linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置。

    1. 如果需要,OS会设置退出信息中的core dump标志位,将进程中的数据传储到磁盘中,方便后期调试。

    2. 一般core dump 标志位是不被设置的,可以通过 ulimit -a 查看是否设置

    3. 进程如果异常退出,会生成一个core.pid的文件,但是并不是所有的异常都会生成core.pid文件,如9号信号

    4. core dump文件可以帮助确定代码错误的地方,这称为事后调试

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbkNdF2r-1668181890658)(./%E4%BF%A1%E5%8F%B7.assets/image-20221110140808177.png)]

    ulimit

    1. ulimit -a查看 core dump 是否设置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T8rRuqfk-1668181890660)(./%E4%BF%A1%E5%8F%B7.assets/image-20221110132215269-1668057736196-3.png)]

    1. ulimit -选项 +数据

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0v8bk3pu-1668181890662)(./%E4%BF%A1%E5%8F%B7.assets/image-20221110132504806.png)]

    kill命令模拟

    用户层调用kill函数,底层扔会转移到OS方面发送信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsg3pJw0-1668181890664)(./%E4%BF%A1%E5%8F%B7.assets/image-20221110145421035.png)]

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void Usage(const char *proc)
    {
        printf("Please Usage : %s -signo  who \n", proc);
    }
    // ./test signo who
    int main(int argc, char *argv[])
    {
        if (argc != 3)
        {
            Usage(argv[0]);
            return 1;
        }
        int signo = atoi(argv[1]+1);
        int who = atoi(argv[2]);
        kill(who,signo );//用户层调用kill函数,底层扔会转移到OS方面发送信息
        printf("signo %d who %d\n", signo, who);    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    信号产生

    1. 站在进程的内外去分析信号产生的方式

    2. 无论什么产生方式,最终都会转为OS向进程发送信号,因为任何行为都不能越过OS,去访问内核数据

    信号的保存

    1. OS向进程发送了信号,但是进程可能会忙于自己的任何无法即可响应这个信号,因此在进程PCB中有必要需要一个保存信号的容器

    2. 每个信号都要其对应的信号码,OS内核中采取位图来标识是否收到信号,对于多个重复信号只响应一个

    3. 关于信号的保存相关的容器有3个:block,pending,handler表

    4. block和pending都是一种由OS内核提供的位图结构,handler是函数指针数组表

    5. block是阻塞又叫信号屏蔽字,用于表明是否阻塞某个信号

    6. pending(未决表),用于存放没有被递达的信号,每个比特位表示是否保存某个信号

    7. handler:函数指针表;信号递达时的处理动作:默认,忽略,自定义,其中默认是对0的函数指针强转,忽略是1的函数指针强转

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u8isvTUi-1668181890666)(./%E4%BF%A1%E5%8F%B7.assets/image-20221111190132844.png)]

    8. 依据这三个表产生3种转态:信号递达,信号未决,信号阻塞

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDP60DEV-1668181890668)(./%E4%BF%A1%E5%8F%B7.assets/image-20221111185815458.png)]

    信号递达

    实际执行信号的处理动作叫信号递达

    动作有:默认,忽略,自定义捕捉

    信号未决

    1. 信号从产生到递达之间的转态也就是信号只是被保存了,并没有立即响应,称为信号未决。
    2. 处于未决,可能一会就可以递达,也可以因阻塞,直到取消阻塞才能递达

    信号阻塞/屏蔽

    进程暂时屏蔽某种信号,也称为信号阻塞;

    阻塞2个情况:

    1. 当信号第一次来的时候,保存信号,但是阻塞其递达

    2. 当进程需要信号递达时,阻塞其递达

    操作

    block和pending是由内核提供的一种位图数据结构,这个内核数据结构类型是sigset_t

    #include 
    //初始化
    int sigemptyset(sigset_t *set);//位图全部置为0
    int sigfillset(sigset_t *set);//位图全部置1
    //添加
    int sigaddset (sigset_t *set, int signo);//向位图中添加一个信号,即信号对应位置1
    //删除
    int sigdelset(sigset_t *set, int signo);//将某个信号位置0
    //查找
    int sigismember(const sigset_t *set, int signo);//判断某个信号是否存在set中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    block表的操作

    sigprocmask修改的是block表

    #include 
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    //oldset用于接收老的block中的数据
    
    • 1
    • 2
    • 3
    int how注意
    SIG_SETMASK将set信号赋值到block中赋值,用的比较多
    SIG_BLOCK将set中的信号添加到block中是添加,不是赋值
    SIG_UNBLOCK从block中去除set中的信号

    在这里插入图片描述

    void ShowSet(sigset_t *set)
    {    for(int i=1;i<32;++i)
        {
            if(sigismember(set,i))
            {
                printf("1");
            }else
            {
                printf("0");
            }
        }
        cout<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    pending表的操作

    对于pending,OS只允许我们获取pending表中的内容,对于信号的保存的由OS负责。

    #include 
    //获取pending位图中的内容
    int sigpending(sigset_t *se);
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    void ShowSet(sigset_t *set)
    {
        printf("cur pending process:");
        for (int i = 1; i < 32; ++i)
        {
            if (sigismember(set, i))
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        cout << endl;
    }
    void handler(int signo)
    {
        sigset_t oset;
        sigpending(&oset);
        ShowSet(&oset);
        sleep(1);
        // exit(1);
    }
    int main()
    {
        signal(2, handler);
        sigset_t iset; //输入参数
        sigset_t oset; //输出参数
        sigemptyset(&iset);
        sigemptyset(&oset);
        sigaddset(&iset, 2);
        // sigaddset(&iset,3);
        // sigaddset(&iset,9);
        sigprocmask(SIG_SETMASK, &iset, &oset);
        int cnt = 0;
        while (1)
        {
            sigset_t tmp;
            sigemptyset(&tmp);
            sigpending(&tmp);
            ShowSet(&tmp);
            sleep(1);
            ++cnt;
            if (cnt == 10)
            {
                printf("恢复2号\n");
                sigprocmask(SIG_UNBLOCK, &iset, NULL);
                //break;
                // cnt=0;
            }
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    handler表操作

    1. handler表就是一个函数指针数组。
    2. signal和 sigaction都可以对handler表进行修改
    #include 
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    
    
    • 1
    • 2
    • 3
    • 4
    #include 
    struct sigaction {
               void     (*sa_handler)(int);//这个就是函数指针
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };
    int sigaction(int signum, const struct sigaction *act,
                        struct sigaction *oldact);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    sa_handler :函数指针

    sa_mask:用与当进行信号处理动作时,屏蔽其他信号的一种内核位图结构

    当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字 。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MEaj671H-1668181890671)(./%E4%BF%A1%E5%8F%B7.assets/image-20221111235026908.png)]

    void handler(int signo)
    {
        while(1)
        {
            printf("get a signo %d\n",signo);
            //printf函数底层会用到OS内核的write函数,也就是说其会从用户态转到内核态
            //如果检测到其它信号就会递达
            sleep(1);
        }
    }
    int main()
    {
    // struct sigaction {
    //                void     (*sa_handler)(int);
    //                void     (*sa_sigaction)(int, siginfo_t *, void *);
    //                sigset_t   sa_mask;
    //                int        sa_flags;
    //                void     (*sa_restorer)(void);
    //            };
    
       //signal(2,handler);
        struct sigaction act;
        memset(&act,0,sizeof(struct sigaction));
        act.sa_handler=handler; 
        //添加屏蔽信号
        // sigemptyset(&act.sa_mask);
        // sigaddset(&act.sa_mask,3);
        // sigprocmask(SIG_BLOCK,&act.sa_mask,NULL);
    
    
    
        sigaction(2,&act,NULL);
            while(1)
            {
                printf("hello word\n");
                sleep(1);
            }
    
        return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    信号捕捉

    1. 进程从内核态向用户态的转换时,进行信号检测,发生信号递达
    2. 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。

    内核态与用户态

    在这里插入图片描述

    1. 电脑启动,OS也要启动,OS能被启动的原因就是电脑会自动到内核代码与数据区,执行相应的启动代码
    2. 通过内核页表,执行内核代码时,处于内核转态;通过用户级页表执行用户代码时,处于用户态;这通过CPU中的一种状态寄存器进行区分。
    3. 其实很多的库函数(printf)都是对内核代码的封装(像数学相关的函数sin,cos等就不是,其直接使用CPU进行计算),系统调用函数更是。要想执行这些函数,必须由用户身份转换到内核身份才能使用内核函数、代码与数据。
    4. . 状态的改变本质是权限的改变,只有具有的内核权限才能执行内存代码。
    5. .使用的进程都共用同一套内核代码,这保证了无论进程怎么被调度都不会影响对内核代码的使用

    信号递达

    信号的捕捉发生在:进程从内核态到用户态的转换时,OS会进行信号检测,如果有信号就递达即捕捉信号。递达的方式:默认,忽略,自定义方式;如果使用自定义方式,会转换为用户态,之后会进入内核态,再次检测信号,直到递达结束,返回用户态。会呈现以下图片

    在这里插入图片描述

    信号总结

    1. 信号的产生方式很多,但最终只能通过OS向进程传递信号,对于信号的不能立即递达的信号,会被保存在pending表中,是否能被递达看的是block阻塞表,递达方式看的是handler表。

    2. 当进程从内核态返回到用户态时,OS会进行信号检测来决定是否递达某些信号。

    3. 对于普通信号,不会保存后续的重复信号,对于实时信号,采用链表保存重复的信号

  • 相关阅读:
    图神经推荐系统笔记整理
    stm32定时器之简单封装
    SPFA算法详解
    【Codeforces Round #805 (Div. 3)(A~C)】
    Apifox 可视化响应功能,让你的接口数据一目了然
    常见的一些Linux命令
    [云原生案例2.3 ] Kubernetes的部署安装 【多master集群架构高可用 ---- (二进制安装部署)】
    如何解决响应结果中文乱码问题
    1天精通Apipost--全网最全gRPC调试和智能Mock讲解!
    【思考总结】正项级数可以条件收敛吗?【负项级数?任意项级数?】
  • 原文地址:https://blog.csdn.net/qq_55439426/article/details/127815197