进程间关系和守护进程

一、进程组

1.1 概念

平时提到的进程,它属于进程组,进程组的作用是解决某一任务。进程组由一个或多个进程组成,一般来说第一个启动的进程一般就是进程组组长。

注意:进程组的生命周期与进程组长无关!当进程组最后的进程退出时,该进程组才消失。

1.2 实例

测试命令:sleep 1000 | sleep 2000 | sleep 3000,下面是结果:

PGID 是进程组的编号,也就是组长的 PID 即第一个启动进程的 id。PPID 是父进程,这里其实就是bash。

进程在创建进程时,创建出来的进程依然属于这个进程组(代码我放到了文章末尾):

二、会话

会话与进程组息息相关,一个会话中会有多个进程组。会话是把一次登录里的所有进程组织在一起,统一管理。

当远端登录(像用 Xshell 登录云服务器)时,系统会给我们分配一个 bash 进程和一个伪终端文件。bash 进程和伪终端文件都在服务器上,Xshell会模拟一个终端并且它还是 SSH 客户端,用户输入数据后加密完通过SSH协议交给服务器,然后通过 sshd 服务转发给伪终端文件,最后bash进程去处理。

会话也是有 id 叫 SID,它一般是第一个进程组的组长的 id,也就是bash。

可以看到,上面红框表示会话id,篮框表示启动进程的父id(我是启动了一个bash进程,所以 SID 是 bash的父进程id,这个id在去查就是 sh,sh 就是这个会话的第一个进程)。

三、控制终端

通过控制终端就可以管理进程(或进程组、会话),它是属性存在 PCB 里(具体来说存储的是该会话 tty 设备的指针)。当新建一个会话后会主动打开并绑定一个 tty,这时tty就是控制终端。当收到数据时通过 tty 来对前台作业进行操作。

上面我说的伪终端文件就是 tty 设备文件的一种,是通过软件模拟出来的。用于远程登录等。

补充:一个会话只能绑定一个控制终端; 后台作业默认会被 tty 的读 / 写信号阻塞,只有前台进程组能直接读写控制终端。对于守护进程来说他没有前后台一说,因为没有控制终端。通过 tty 命令可以查看本次会话的空控制终端。

四、作业控制

4.1 介绍

作业是面向用户的,用户为了完成某项任务而启动的进程。Shell控制的前后台不是进程而是作业或进程组。前台作业或后台作业可以由多个进程组成。前台作业只能有一个(只有它才能接收到键盘的信息),后台作业可以由多个。

4.2 作业号

想启动后台作业,在启动命令时加 & 即可:

1是作业号,2648606是 pid,不过它是进程组的最后一个进程。通过 jobs 可以查看后台进程:

jobs参数:

  1. -l: 显示作业详细信息
  2. -p: 只显示作业的 PID

对于一个用户来说只能有一个默认作业,用 + 表示。还有一种是 -,他表示即将称为默认作业的作业。区别是默认作业对于像bg、fg等操作可以不写作业号。Running表示正在运行,另外还有Done、Stopped等。

4.3 作业切换

4.3.1 前台变后台

用 ctrl + z :

这时会处于暂停状态,然后用 bg 作业号(默认作业可不写作业号):

4.3.2 后台变前台

用 fg 作业号(默认作业可不写作业号):

五、守护进程

要部署一个服务时,如果放到当前会话中,那么当这次会话结束的时候,你部署的任务可能会直接被强制结束,也可能不会但一定会受到影响。为了解决该问题,可以把该服务的进程放到一个新会话中,让系统去管理它。守护进程是没有 tty 的。

通过 setsid() 可以新建一个会话,不过有个要求就是不能是组长去新建会话(这是为了避免混淆)。下面是实现守护进程:

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

void Deamon()
{
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    if(fork() > 0)
        exit(0);

    setsid();

    // 修改进程的cwd
    chdir("/");

    // 关闭标准输入和标准输出(一般不直接关,可能会有后续问题)
    int fd = open("/dev/null", O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd); // 只是关掉 "临时用的文件描述符",完全不影响 0、1、2 它们依然是向 null 文件输入输出
                   // 这时 null 文件的引用计数是3
    }
}

把服务器变成守护进程化很简单,调用它即可:

cpp 复制代码
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }

    uint16_t localport = std::stoi(argv[1]);

    Daemon();

    std::unique_ptr<TcpServer> svr(new TcpServer(localport,HandlerRequest));
    svr->Loop();

    return 0;
}

六、代码

cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        std::cout << "子进程创建失败" << std::endl;
        exit(-1);
    }
    else if(id == 0)
    {
        int cnt = 5;
        while(cnt--)
        {
            std::cout << "我是子进程, pid:" << getpid() << std::endl;
            sleep(1);
        }
    }

    std::cout << "我是父进程, pid:" << getpid() << std::endl;
    wait(NULL);

    return 0;
}
相关推荐
sbjdhjd1 小时前
02 (中)| K8s Pod 生产化落地:从配置到优化全流程
linux·运维·云原生·kubernetes·开源·podman·kubelet
皓月盈江1 小时前
Linux Ubuntu系统如何编辑Docker容器内的文件
linux·ubuntu·docker·容器·靶场·vulhub·编辑docker内文件
jingyu飞鸟1 小时前
linux系统二进制安装MySQL 8.4、8.0版本数据库,配置crontab和xtrabackup数据库热备份脚本
linux·数据库·mysql
无限进步_1 小时前
从Multics到Linux:操作系统的自由之路
linux·运维·服务器
China_Yanhy1 小时前
【云原生实战】从零构建无节点 EKS:Karpenter 极简注入与全自动算力接管指南
linux·运维·云原生
北山有鸟1 小时前
常用的快捷键
linux·前端·chrome·单片机·学习
岳来1 小时前
Linux Capabilities(能力机制)细分学习
linux·root
QH139292318801 小时前
R&S®SMBV100B 矢量信号发生器 5G/Wi-Fi/GNSS 主力源
网络·科技·嵌入式硬件·集成测试·信息与通信
皮卡蛋炒饭.1 小时前
传输层协议TCP
服务器·网络·tcp/ip