进程间关系与守护进程

目录

一、进程组

什么是进程组?进程组是一个或者多个进程的集合,一个进程组可以有多个进程。每一个进程组都有自己的进程组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都是一样的,即这个进程是独立的一个会话、会话中唯一的一个进程组的进程。

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

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

杀掉这个守护进程:

相关推荐
安於宿命10 分钟前
【Linux】软硬链接
linux·运维·服务器·c++
冰冰的coco11 分钟前
Linux c++常用技术
linux·c++
就爱学编程12 分钟前
重生之我在异世界学编程之C语言:深入函数递归篇
c语言·开发语言
ASHIDEH12 分钟前
C语言实现贪吃蛇小游戏
c语言·开发语言·数据结构
cmgdxrz13 分钟前
Linux系统操作03|chmod、vim
linux·运维·服务器
南桥几晴秋15 分钟前
【算法刷题指南】前缀和
c++·算法·前缀和
七Du°C糊涂18 分钟前
ProTBB (四):设计模式与 ProTBB
c++
百川Cs34 分钟前
【Linux】VFS虚拟文件系统介绍
linux·运维·服务器
丶Darling.36 分钟前
linux网络编程 | c | epoll实现IO多路转接服务器
linux·服务器·c语言
群联云防护小杜36 分钟前
出海服务器可以用国内云防护吗
运维·服务器·前端·网络·php