【Linux】进程间关系与守护进程

目录

进程组

什么是进程组

组长进程

会话

什么是会话

作业控制

守护进程


进程组

什么是进程组

我们在命令行中输入sleep 1 | sleep 2 | sleep 3,然后查看进程,

我们看到它们有不同的pid,表明它们是不同的进程,它们的ppid是一样的,那么它们是兄弟进程。除了pid和ppid外,我们看到第三列还有pgid(process group id,进程组id),它们对应的pgid是一样的,表明它们属于同一个组,这三个进程的组长是第一个被创建的进程,即sleep 1000。我们再来写这样一段简单代码:

我们发现这一个进程也有自己的pgid,其pgid就是它自己的pid本身。也就是说,哪怕只有一个进程,也能自成进程组。

我们再来写这样一段代码:

我们发现,它们的pid不一样说明这是两个进程。但是我们发现它们的pgid是一样的,就是父进程的pid。如果我们将来在写一个多进程程序的时候,多个进程其实是以进程组的方式来共同完成一件任务的。进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程

组长进程

每一个进程组都有一个组长进程,组长id就是其进程id。

  • 进程组长的作用:可以创建一个进程组或者创建该进程组中的进程。
  • 进程组的生命周期:从进程组创建开始到其中最后一个进程离开为止。注意:只要某个进程组中有一个进程存在,则该进程就存在,与其组长进程是否已经终止无关

-e 选项表示every的意思,表示输出每一个进程的信息。

-o 选项以逗号操作符(,)作为分界符,可以指定要输出的列

会话

什么是会话

在使用xshell登录linux服务器时,linux会为我们创建一个终端文件,还创建一个bash进程,以进程组的方式来给我们提供命令行解释的服务。bash进程通过终端文件进行读写数据,

一个会话内部可以有多个进程组。

我们再看这样的命令:

加上&表示让进程在后端运行,同时继续运行自己写的process_task进程:

我们发现,运行的sleep和process_task,它们属于不同的进程组。更重要的是,后面有一列叫SID,即会话ID,它们的会话ID是一样的,会话ID是一样的。会话ID一般是会话中的第一个进程,一般是bash。会话可以看成是一个或多个进程组的集合, 一个会话可以包含多个进程组。 每一个会话也有一个会话 ID(SID)

如果想把这个进程放在后台,就在后面加&。

这种前台进程运行时,我们输入一些命令pwd、ls,发现没有反应。

实际上,同一个会话中,可以运行同时存在多个进程组,但是任何时刻只允许一个前台进程(组),可以允许多个后台进程(组)。当刚开始时,前台进程是bash,输入指令,bash就会响应,而当执行我们自己的任务进程时,我们的任务就变成了前台进程了,这是bash自动把自己切换到后台。当输入指令时,都给了前台进程,bash就收不到了,输入的命令当然就无法执行了。当把我们自己的进程终止了,bash又把自己重新切换到前台,等待用户输入。所以,我们现在发现,所谓前后台,就是谁应该从标准输入中获取数据!这就是后台进程使用ctrl+c也杀不掉的原因。这样正是我们自己运行的程序可以通过ctrl+c终止。

作业控制

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

当我们把完成某个作业的进程放在后台运行时,如下

[1]表示作业号,

我们再创建一个作业,

作业号就是2了,如果我们想查询当前的作业,可以使用jobs (-l),

这两个作业都在后台,如果想把某个作业放在前台,比如想把1号作业放在前台运行,就输入fg 1,此时这个作业就能接收键盘输入的命令,

如果想把刚才移到前台的作业放到后台,就需要先暂停这个前台作业,使用ctrl+z暂停,

然后再输入bg 2,将2号作业放到后台,

关于默认作业:对于一个用户来说,只能有一个默认作业(+) ,同时也只能有一个即将成为默认作业的作业(-),当默认作业退出后,该作业会成为默认作业。

常见作业状态

|-------------------|-------------------|
| 作业状态 | 含义 |
| 正在运行【Running】 | 后台作业(&),表示正在执行 |
| 完成【Done】 | 作业已完成,返回的状态码为0 |
| 完成并退出【Done(code)】 | 作业已完成并退出,返回的状态码非0 |
| 已停止【Stopped】 | 前台作业,当前被Ctrl+Z终止 |
| 已终止【Terminated】 | 作业被终止 |

守护进程

进程组,无论是前台还是后台,都属于同一个会话。如果我们把某一个进程组从当前会话a中拿出来,形成一个单独的会话b,这样和刚才的会话a由包含关系变成并列关系。即使把a会话删除,也并不影响b会话,我们把这个进程称为守护进程。这个守护进程不会因用户登录或退出受影响,那一个进程如何将自己变成独立的会话从而变成守护进程呢?就需要使用setsid函数:

#include <unistd.h>
pid_t setsid(void); # 功能:创建会话
                    # 返回值:创建成功返回SID,失败返回-1

当某一个进程调用setsid函数来创建一个会话,前提是调用进程不能是一个进程组的组长。需要注意的是,这个接口如果调用进程原来是进程组组长,则会报错。为了避免这种情况,可以使用fork创建子进程,然后让父进程退出,子进程调用setsid。此时这个子进程(守护进程)就变成了孤儿进程,守护进程是孤儿进程的一种特殊情况。

该调用接口后会发生:

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

在变成守护进程后,不需要和用户进行输入输出了,我们可以将012重定向到/dev/null,这样就从/dev/null里读不到内容,写入/dev/null的内容也会被自动丢弃(/dev/null是一个字符文件)。也就是说,我们可以以读写方式打开/dev/null,把012全部重定向到/dev/null。并且可以选择是否更改工作目录。我们把守护进程写成一个函数Daemon:

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

const std::string defaultpath = "/";
const std::string defaultdev = "/dev/null";

void Daemon(bool ischdir, bool isclose)
{
    // 1.忽略不要的信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2.fork
    if (fork() > 0)
        exit(0);

    // 3.setsid
    setsid();

    // 4.确认是否要更改工作目录
    if (ischdir)
        chdir(defaultpath.c_str());

    // 5.对012进行重定向
    if (isclose)
    {
        ::close(0);
        ::close(1);
        ::close(2);
    }
    else
    {
        int fd = open(defaultdev.c_str(), O_RDWR);
        if(fd > 0)
        {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            ::close(fd);
        }
    }
}

然后再main函数中调用Daemon,让当前进程成为守护进程,

此时这个process_task进程并没有结束,而是在后端运行。由于process_task不属于当前会话,因此不会阻塞我们输入的命令。并且我们可以查到这个进程:

我们发现其ppid是1,也就是变成了孤儿进程,被系统领养。其pid、pgid、sid都是1086564,自成进程组,自成会话。其所对应的默认工作路径:

其所对应的012都指向了/dev/null:

未来如果后面有打印输入的程序,就什么都不做。

如果想让这种进程结束,只能使用kill命令。如果我们把Deamon里面的参数设为true,那么

发现其工作目录变成了根目录,

由于所有的012都被关掉了,就看不到了。

至此,我们将一个进程守护进程化了。现在假如写了一个网络服务器,在启动这个服务器之前就可以守护它了!

相关推荐
追光天使30 分钟前
Mac/Linux 快速部署TiDB
linux·macos·tidb
余生H33 分钟前
前端的Python应用指南(一):快速构建 Web 服务器 - Flask vs Node.js 对比
服务器·前端·python
Joyner201836 分钟前
ubuntu批量依赖库拷贝(ldd)
linux·运维·ubuntu
csdn_金手指41 分钟前
Jenkins持续交付web应用,通过docker制作相关的镜像进行发布部署
运维·jenkins
Steven_Mmm41 分钟前
初试Docker
运维·docker·容器
Jason_C_42 分钟前
【NVIDIA】启动ubuntu后显卡驱动丢失
linux·运维·ubuntu
阑梦清川43 分钟前
基于ubuntu的mysql 8.0安装教程
linux·mysql·ubuntu
轨迹H1 小时前
kali设置中文输入法
linux·网络安全·渗透测试·kali
Snow_Dragon_L1 小时前
【MySQL】表操作
linux·数据库·后端·sql·mysql·ubuntu
19999er2 小时前
CDN信息收集(小迪网络安全笔记~
服务器·网络·笔记·安全·web安全