进程间关系与守护进程

目录

一、进程组

什么是进程组?进程组是一个或者多个进程的集合,一个进程组可以有多个进程。每一个进程组都有自己的进程组ID------PGID

如图:这里创建了3个进程

查看进程信息:

3个进程的PGID是一样的,说明它们是在同一个进程组里面

每一个进程组都有一个进程组长,有多个进程,那么进程组长是第一个进程,可看上图。第一个进程的PID与PGID相同;如果一个进程组里只有一个进程,那么这个进程自己就是组长。

一个进程组的组长可以创建进程组或者创建该组的进程:父进程创建子进程

进程组的生命周期:从进程组创建开始到其中最后一个进程离开为止。只要进程组还有一个进程存在,那么该进程组就是存在的,与该进程组的组长进程是否存在无关

让父进程先结束,子进程变成孤儿:

cpp 复制代码
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        while(true)
        {
            cout << "I am child process" << endl;
            sleep(1);
        }
    }
    cout << "I am father process" << endl;
    sleep(5);
    return 0;
}

可以看出,只要进程组还有一个进程存在,进程组就不会消失

二、会话

什么是会话?会话是一个或者多个进程组的集合,一个会话可以包含多个进程组,会话也有自己的会话ID

当用户登录xshell时,系统会给用户提供一个终端文件和一个bash进程,这个bash进程自己为一个进程组;如果用户再登录新的窗口,那么也会有新的终端文件和bash进程。这两者的结合就是一个会话

一个会话可以有多个进程组:同时开启sleep进程组和mytest进程组

SID就是会话ID,会话ID通常是会话中的第一个进程组的第一个进程,一般是bash

这里是让sleep进程在后台运行,后面加个取地址;mytest在前台

规定:

  • 一个会话可以运行同时存在多个进程组
  • 但是只允许一个前台进程组,可以有多个后台进程组

三、终端控制

什么是终端控制?用户通过终端登录系统后得到一个Shell进程,这个终端就是Shell进程的控制终端。控制终端是保存在PCB中的信息,因为fork进程会复制PCB中的信息,所以由Shell进程启动的其他进程的控制终端也是这个终端。

四、作业控制

什么是作业?作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程管道。

什么是作业控制?Shell 分前后台来控制的不是进程而是作业 或者进程组。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell 可以同时运行一个前台作业和任意多个后台作业,这称为作业控制

如下图:一个进程组有三个进程

这是前台进程组,用ctrl C 杀掉了

还是这个进程组,3个进程,变成后台:

后面加上取地址符&就是后台进程组,中括号里面的1代表作业编号,后面跟的一串数字是进程ID:

可以看出,是进程组最后一个进程的pid

再创建一个进程组:变成后台

用jobs或者jobs -l 查看后台进程组的信息:

Running表示后台的进程组正在运行

后台 -> 前台:fg 作业编号

这里用ctrl C把一号作业杀了

再次查看:jobs

只有2号作业是后台,并且运行着

把2号作业也变成前台:

前台 -> 后台:先stop(ctrl Z),再 bg + 作业编号

总结:进程组以 sleep 1000 | sleep 2000 为例

创建前台进程组:sleep 1000 | sleep 2000

创建后台进程组:sleep 1000 | sleep 2000 &
注意 :后台进程组接收不到数据,所以用户使用ctrl C或ctrl Z无效,必须是前台进程组才能接收这些信号

后台->前台:fg + 作业编号

前台->后台:stop(ctrl+Z) bg+作业编号

+: 表示该作业号是默认作业

-:表示该作业即将成为默认作业

无符号:表示其他作业

五、守护进程

什么是守护进程?一个会话有多个进程组,将一个进程组分离出来,这个进程组自己是独立的会话,如果这个进程组只有一个进程,那么该进程就是守护进程。

为什么有守护进程?如果之前该进程所在的会话销毁了,那么这个进程也随之销毁,变成守护进程,它就不受原来的会话的状态的影响。

怎么变成守护进程?=》一个进程如何将自己变成独立的会话?

调用setsid函数创建会话,调用进程不能是一个进程组的组长,所以为了避免这种情况,可以fork创建子进程,让子进程去调用setsid函数,父进程先退出,这样就不会出错。

该接口调用之后会发生:

  • 调用进程会变成新会话的会话首进程。此时,新会话中只有唯一的一个进程
  • 调用进程会变成进程组组长。新进程组 ID 就是当前调用进程 ID
  • 该进程没有控制终端。如果在调用 setsid 之前该进程存在控制终端,则调用之后会切断联系

守护进程化:

cpp 复制代码
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string root = "/";
const std::string path = "dev/null";

void Deamon()
{
    // 1.先忽略掉引起程序异常退出的信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2.创建子进程, 让子进程调用setsid
    pid_t id = fork();
    if(id > 0) exit(0);

    // 3.setsid
    setsid();

    // 4.更改当前进程的工作目录
    chdir(root.c_str());

    // 5.不需要和用户的输入输出错误关联
    int fd = open(path.c_str(), O_RDWR);
    if(fd > 0)
    {
        // 重定向
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        ::close(fd);
    }
}

这里与标准输入、输出、错误去关联,并不是直接close掉,因为有可能后续的代码还有输入输出的操作,就会导致出错。用重定向的方式,既能避免用户输入输出的干扰,又能防止其他问题出错。重定向到 /dev/null 这个文件中,这是字符设备文件

往这文件读取,什么也读不到,因为文件的内容已经被丢弃了;往这文件写入,写入的内容也会自动被丢弃。

下面做下测试:

cpp 复制代码
int main()
{
    // 守护进程化
    Deamon();
    while(true)
    {
        sleep(1);// 模拟一个任务
    }
    return 0;
}

运行后发现什么也没有,其实是那个进程已经变成了守护进程,它自己是独立的一个会话,不受当前会话的影响。可以查看该进程信息:

该进程就是调用setsid函数前父进程创建的子进程,调用setsid函数后,它是守护进程,也是孤儿进程,可以发现,它的PID、PGID、SID都是一样的,即这个进程是独立的一个会话、会话中唯一的一个进程组的进程。

查看守护进程的当前工作目录:

守护进程的标准输入、输出、错误重定向的位置:

杀掉这个守护进程:

相关推荐
小糯米6011 小时前
C++顺序表和vector
开发语言·c++·算法
yuanmenghao1 小时前
Linux 性能实战 | 第 7 篇 CPU 核心负载与调度器概念
linux·网络·性能优化·unix
独望漫天星辰2 小时前
C++ 多态深度解析:从语法规则到底层实现(附实战验证代码)
开发语言·c++
qq_297574672 小时前
Linux 服务器 Java 开发环境搭建保姆级教程
java·linux·服务器
70asunflower2 小时前
Emulation,Simulation,Virtualization,Imitation 的区别?
linux·docker
王老师青少年编程2 小时前
2024年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)
c++·题解·真题·csp·信奥赛·csp-s·提高组
神梦流3 小时前
ops-math 算子库的扩展能力:高精度与复数运算的硬件映射策略
服务器·数据库
神梦流3 小时前
GE 引擎的内存优化终局:静态生命周期分析指导下的内存分配与复用策略
linux·运维·服务器
凡人叶枫3 小时前
C++中输入、输出和文件操作详解(Linux实战版)| 从基础到项目落地,避坑指南
linux·服务器·c语言·开发语言·c++
CSDN_RTKLIB3 小时前
使用三方库头文件未使用导出符号情景
c++