首页 > 系统相关 >进程间的信号处理

进程间的信号处理

时间:2022-11-26 12:59:03浏览次数:40  
标签:fork 调用 SIGCHLD 信号处理 终止 信号 进程

进程状态

Linux系统下进程通常存在6种不同的状态,分为:就绪态、运行态、僵尸态、可中断睡眠状态(浅度睡眠)、不可中断睡眠状态(深度睡眠)以及暂停态。

就绪态(Ready):指该进程满足被 CPU 调度的所有条件但此时并没有被调度执行,只要得到 CPU就能够直接运行;意味着该进程已经准备好被 CPU 执行,当一个进程的时间片到达,操作系统调度程序会从就绪态链表中调度一个进程;

运行态:指该进程当前正在被 CPU 调度运行,处于就绪态的进程得到 CPU 调度就会进入运行态;

僵尸态:僵尸态进程其实指的就是僵尸进程,指该进程已经结束、但其父进程还未给它"收尸";

可中断睡眠状态:可中断睡眠也称为浅度睡眠,表示睡的不够"死",还可以被唤醒,一般来说可以通过信号来唤醒;

不可中断睡眠状态:不可中断睡眠称为深度睡眠,深度睡眠无法被信号唤醒,只能等待相应的条件成立才能结束睡眠状态。把浅度睡眠和深度睡眠统称为等待态(或者叫阻塞态),表示进程处于一种等待状态,等待某种条件成立之后便会进入到就绪态,所以处于等待态的进程是无法参与进程系统调度的;

暂停态:暂停并不是进程的终止,表示进程暂停运行,一般可通过信号将进程暂停,譬如SIGSTOP信号,处于暂停态的进程是可以恢复进入到就绪态的,譬如收到 SIGCONT 信号。

进程间关系

进程间还存在着其它一些层次关系,譬如进程组和会话,所以由此可知进程间存在着多种不同的关系,主要包括:无关系(相互独立)、父子进程关系、进程组以及会话。

a.无关系
两个进程间没有任何关系,相互独立。

b.父子进程关系
两个进程间构成父子进程关系,譬如一个进程 fork()创建出了另一个进程,那么这两个进程间就构成了父子进程关系,调用 fork()的进程称为父进程、而被 fork()创建出来的进程称为子进程;当然,如果父进程先于子进程结束,那么 init 进程就会成为子进程的父进程,它们之间同样也是父子进程关系。

c.进程组
每个进程除了有一个进程 ID、父进程 ID 之外,还有一个进程组 ID,用于标识该进程属于哪一个进程组,进程组是一个或多个进程的集合,这些进程并不是孤立的,它们彼此之间或者存在父子、兄弟关系,或者在功能上有联系。
假设为了完成一个任务,需要并发运行 100个进程,但当处于某种场景时需要终止这 100 个进程,若没有进程组就需要一个一个去终止,这样非常麻烦且容易出现一些问题,有了进程组的概念之后,就可以将这 100 个进程设置为一个进程组,这些进程共享一个进程组 ID,这样一来,终止这 100 个进程只需要终止该进程组即可。

父子进程

创建子进程

有两种模式,第一种复制父进程的大部分内容(一般直接调用fork函数),第二种重新开始,不需要去继承父进程的数据段、堆、栈以及继承了父进程打开的文件描述符(一般是调用fork函数后,子进程调用exec)。

第一种(需要继承)

一个现有的进程可以调用 fork()函数创建一个新的进程,调用 fork()函数的进程称为父进程
由 fork()函数创建出来的进程被称为子进程,每个进程都会从 fork()函数的返回处继续执行,
会导致调用 fork()返回两次值,可通过返回值来区分是子进程还是父进程。
子进程是父进程的一个副本,子进程拷贝了父进程的数据段、堆、栈以及继承了父进程打开的文件描述符,父进程与子进程并不共享这些存储空间
fork()调用成功后,将会在父进程中返回子进程的 PID,而在子进程中返回值是 0

父子进程文件共享

因为调用 fork()函数之后,子进程会获得父进程所有文件描述符的副本
这也意味着父、子进程对应的文件描述符均指向相同的文件表
一方更新了文件偏移量,另一方也会进行更新(相当于父子进程交替写文件,会进行往下更新,而不是覆盖)

监视子进程

父进程需要知道子进程于何时被终止,并且需要知道子进程的终止状态信息,是正常终止还是异常终止亦或者被信号终止等
所以需要父进程对子进程的监视

wait()函数(有些限制最好用waitpid)
wait()可以等待任意子进程终止,获得子进程终止状态信息
调用时和之前没有子进程终止,就会阻塞
调用之前有一个或多个子进程终止,调用wait()不会阻塞,除了返回终止信息,还会给子进程"收尸",一次wait只能处理一次

子进程的终止状态可以用宏来进行检查
WIFEXITED(stat_val)
        如果子进程为正常终止则为非零值。
WEXITSTATUS(stat_val)
        如果WIFEXITED返回的是非零值,WEXITSTATUS可以返回子进程调用_exit()或 exit()时指定的退出状态
WIFSIGNALED(stat_val)
        由于接收到未捕获的信号而终止的子进程返回为非零值
WTERMSIG(stat_val)
        WIFSIGNALED返回为非零值,WTERMSIG可以返回导致子进程终止的信号编号

void signal_handler(int signo) 
{
    pid_t pid = 0;
    int isExited, isSignaled, status, sigNum;

    // 打印信号信息
    printf("[ ABORT ] pid=%d recv signo=%d", getpid(), signo);

    // 信号处理
    switch (signo) 
    {
    case SIGCHLD: 
    {
        // SIGCHLD信号处理

        while ((pid = waitpid(-1, &status, WNOHANG)) > 0) 
        {
            isExited = WIFEXITED(status);
            isSignaled = WIFSIGNALED(status);
            sigNum = WTERMSIG(status);

            printf("[ABORT] pid=%d, sig=%d, recv sigNum=%d, isSignaled=%d, isExited=%d", pid, signo, sigNum, isSignaled, isExited);

        }
    } 
    break;
    default: 
        break;
    }
}

孤儿进程

父进程先于子进程结束,也就是意味着,此时子进程变成了一个"孤儿"
在 Linux 系统当中,所有的孤儿进程都自动成为 init 进程(进程号为 1)的子进程
某一子进程的父进程结束后,该子进程调用 getppid()将返回 1,init 进程变成了孤儿进程的"养父"

僵尸进程

进程结束之后,通常需要其父进程为其"收尸",回收子进程占用的一些内存资源,父进程通过调用
wait()(或其变体 waitpid()、waitid()等)函数回收子进程资源,归还给系统。
如果父进程并没有调用 wait()函数然后就退出了,那么此时 init 进程将会接管它的子进程并
自动调用 wait(),故而从系统中移除僵尸进程。
但是父进程有自己的事情做,不能总在wait()阻塞,或者轮询的使用waitpid()
这时候因为子进程的结束是异步的,所以使用信号机制来处理

SIGCHLD 信号

当父进程的某个子进程终止时,父进程会收到 SIGCHLD 信号;
当父进程的某个子进程因收到信号而停止(暂停运行)或恢复时,内核也可能向父进程发送该信号。
这时候我们可以用信号捕获它,再调用wait()处理
当调用信号处理函数时,会暂时将引发调用的信号添加到进程的信号掩码中(除非 sigaction()指定了 SA_NODEFER 标志),这样一来,当 SIGCHLD 信号处理函数正在为一个终止的子进程"收尸"时,如果相继有两个子进程终止,即使产生了两次 SIGCHLD 信号,父进程也只能捕获到一次 SIGCHLD 信号,结果是,父进程的 SIGCHLD 信号处理函数每次只调用一次 wait(),那么就会导致有些僵尸进程成为"漏网之鱼"。
父进程收到了SIGCHLD 信号,在使用信号处理函数的时候,会把这个信号放入信号掩码中,因为SIGCHLD是不可靠信号,后面的SIGCHLD将会被丢弃。

标签:fork,调用,SIGCHLD,信号处理,终止,信号,进程
From: https://www.cnblogs.com/zhanggaofeng/p/16927243.html

相关文章

  • Android init进程打印不全的问题
    在抓Android内核的log时,init进程的log往往打印不全,这是因为内核限制了log的输出,在内核代码中找到下面的文件,并按照下面的提示把代码注释掉,然后重新编译内核,再刷到设备中,ini......
  • Linux 基础-查看进程命令 ps 和 top
    Linux基础-查看进程命令ps和top1,使用ps命令找出CPU占用高的进程ps是进程状态(processstatus)的缩写,它能显示系统中活跃的/运行中的进程的信息。它提供了当......
  • [Linux高并发服务器]进程间通信简介
    [Linux高并发服务器]进程间通信简介摘自​​牛客项目课Linux高并发服务器​​概念进程间通信IPC:InterProcessesCommunication为什么需要进程间通信进程是一个独立的资......
  • [Linux 高并发服务器] 进程创建以及GDB多进程调试
    [Linux高并发服务器]进程创建进程创建系统允许一个进程创建新进程,新进程就是子进程,子进程还可以创建子进程,形成树结构模型我们可以使用​​fork​​函数创建子进程/*#......
  • [Linux 高并发服务器] 进程状态的转换
    [Linux高并发服务器]进程状态的转换根据​​牛客的c++项目课程​​做笔记,图片和知识点均摘录自该课程进程的状态转换进程状态反映进程执行过程的变化,这些状态随着进程的执......
  • [Linux 高并发服务器] 进程概述
    [Linux高并发服务器]进程概述程序和进程程序程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程二进制格式表示,用于描述可执行文件格式的元信息,内核利用......
  • 利用Windows的命令行工具tasklist和findstr,start结合计划任务实现一种进程监控的方案
    利用Windows的命令行工具tasklist和findstr,start结合计划任务实现一种进程监控的方案Windows虽然是以UI来见长的。但是,真正的POWERFUL工具,还是命令行的。今天,介绍一种......
  • [ Linux ] 进程间通信之共享内存
    在上篇博文我们了解了通过管道完成进程间通信,我们了解匿名管道和命名管道,并且通过编码模拟实现使用了匿名管道和命名管道。我们知道要让进程间完成通信必须让这两个进程首先......
  • ASP.Net 附加调试,aspnet_wp.exe进程不能启动解决办法 .
    1.检查配置文件设置(第一解决办法).看一下web.config中:<compilationdefaultLanguage="c#"debug="true"/>中是否:debug="true"?如果为false,......
  • 4-3 基于缓存 + Node 多进程实现动态命令加载和执行
    1node多进程开发1.1进程(在操作系统中如何查看进程的嵌套关系)官方文档1.什么是进程(Process)进程是计算现中的程序关于某数据集合上的一次运行活动,是系统进行资......