文章目录
- 概念引入
- ps细节展示
- 什么是进程组
- 什么是会话
- 细节演示
- 有关指令的处理
- 用户级任务和进程组的关系
- 关系
- 不同
- 什么是守护进程
- 如何创建守护进程
- 代码说明
- 如何关闭守护进程
- 问题
概念引入
我们在之前的章节中已将看过进程相关的概念, 本篇介绍守护进程
进程还有进程组, 作业, 会话的概念
终端进程的启动父进程都是bash
ps细节展示
当我们在终端输入sleep10000
, 查看这个进程相关的信息
其中的信息是
SID
表示是会话
STAT
表示状态
TTY
表示是当前打开的终端是谁
UID
表示用户身份, 当前sleep进程是以用户UID为1000的用户执行的进程
可以看到这个sleep进程的PID
和PGID
一样, 这样的进程自成进程组
即进程的PID
和PGID
一样, 这样的进程自成进程组
进程组默认是在一个会话中的
什么是进程组
当输入指令 sleep 1000 | sleep 2000 | sleep 3000
时
同时启动的多个进程, 是属于同一个进程组的
每次登陆Linux, 系统都会给用户提供,
- bash
- 提供一个终端, 用于给用户提供命令行解析服务
什么是会话
其中, 这个1和2在一起, 这个叫一个会话
在命令行中启动的所有的进程, 最终默认都是在当期会话内部的一个进程组(可以是一个进程自成为进程组)
当我们建立三个终端时, 此时查看bash进程
对于每一个bash都是自成进程组, 自成会话
清理所有的任务, 首先执行sleep 100000
再执行, sleep 20000 |sleep 300000 | sleep 40000
再次查看进程信息
其中sleep 100000
是自成进程组, 自成会话
另外三个是为一组进程组, 但因为是同属一个终端的指令, 所以是属于一个会话
画图演示具体细节:
细节演示
有关指令的处理
1.jobs
查看当前会话中的所有进程
2. ctrl + z
将当前进程放在后台
- 放在后台的应用, 默认从1开始有作业编号
- 放在后台的进程, 默认会处于stopped, 可以使用
bg %作业编号
的方式, 恢复进程为running, 也可以使用bg, 不加编号, 默认是最近的一次被挂起(ctrl + z)的作业- 使用指令
fg %+作业编号
的数字, 可以将任务放在前台, 也可以简单使用fg, 不加编号也行, 与上述同理
用户级任务和进程组的关系
关系
- 进程组: 技术方面的表述
- 用户级任务: 用户级概念
不同
每次启动Linux都会创建一个新的会话, 会话与会话之间是隔离的
每次启动的进程是受到用户登录和注销影响的, 如果这个进程出现异常, 那么有可能会导致这个bash会话被关闭, 这与OS的处理机制有关
现在想让我们的服务(进程), 不受用户的登录和注册影响, 那么只需要 把这个进程变成守护进程
什么是守护进程
- 守护进程是一个独立的会话
- 不隶属于任何一个bash会话
如何创建守护进程
使用setsid
接口
进程在调用这个会话的时候, 该进程会自己成为一个会话, 未来这个会话中只有我自己
前提是, 这个进程不是进程组的组长, 组长一般是多个进程的第一个
所以要成功调用, 一般是创建子进程, 让父进程直接退出
即守护进程一定是孤儿进程, 他的父进程一定是bash进程(1)
代码说明
Deamon.hpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const char *root = "/";
const char *dev_null = "/dev/null";
void Deamon(bool ischdir, int isclosefd)
{
// 1.忽略可能引起进程异常的信号(取决于应用场景)
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
// 2.让自己不要成为组长
if (fork() > 0)
exit(0); // 父进程退出
// 3.设置让自己成为一个新的会话, 后面的代码是子进程在走
setsid();
// 4.每一个进程都有自己的CWD(PWD的路径), 是否将当前进程的CWD设置成根目录(好处是, 这样的顶级方式查找)
// 1. 防止依赖临时文件
// 守护进程通常不需要访问任何特定的文件或目录。如果守护进程在其启动时的工作目录中创建了临时文件或其他资源,可能会导致不必要的依赖。将 CWD 设置为根目录可以确保守护进程不会依赖于任何特定的工作目录。
// 2. 避免权限问题
// 如果守护进程在其启动时的工作目录中创建文件或修改文件,可能会遇到权限问题。例如,如果守护进程运行在非特权用户账户下,而该用户的家目录或工作目录可能不具备相应的写权限,这会导致守护进程无法正常工作。将 CWD 设置为根目录可以避免这些问题。
// 3. 减少对系统的影响
// 将 CWD 设置为根目录可以减少守护进程对其启动环境的依赖,从而减少其对系统的影响。这意味着守护进程不会意外地修改或依赖于其启动时的工作目录中的文件或目录。
// 4. 便于管理和维护
// 将 CWD 设置为根目录可以使守护进程的管理和维护更加简单。例如,如果守护进程需要在日志中记录其工作目录,将 CWD 设置为根目录可以确保日志的一致性和可预测性。
// 5. 避免挂载点问题
// 如果守护进程在其启动时的工作目录位于一个挂载点上(例如,一个可移动设备或网络文件系统),那么如果该挂载点变得不可用(例如,设备断开连接或网络中断),守护进程可能会出现问题。将 CWD 设置为根目录可以避免这些问题。
if (ischdir)
chdir(root); // 更改目录为根目录
// 5.守护进程是一个独立的会话, 不需要和用户的输入输出进行关联, 关闭标准输入输出(不推荐, 万一真的存在从哪个文件读取输入到另一个文件, 此时就会出错)
// 所以好的做法是使用 /dev/null, 向这个文件写入的任何东西都会丢弃, 读的时候读到文件结尾
int fd = open(dev_null, O_RDWR);
if (isclosefd) // 是否是直接关闭文件描述符的方式
{
close(0);
close(1);
close(2);
}
else // 重定向到null文件的形式
{
if (fd > 0)
{
// int dup2(int oldfd, int newfd);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
}
main.cc
#include "Daemon.hpp"
#include <unistd.h>
int main()
{
// 守护进程
Deamon(false, false);
// 要执行的核心代码
while(1)
{
sleep(1);
}
return 0;
}
结果演示
父进程为bash, 为一个会话组
其中, 这个tty为?, 表示终端无关性
当关闭这个终端, 再次启动查看, 他还是存在, 且信息表示的内容与当期一样
查看信息 ls /proc/3596 -l, 里面都是该守护进程系相关的信息
在查看这个fd文件夹查看文件描述符信息
如何关闭守护进程
那要停止这个守护进程就需要使用kill -9 ID, 更推荐使用信号处理机制, 在代码当中, 发送某个信号时, 首先会进行资源的安全关闭, 再进行退出操作, 这边就不再修改
当然相比上述的守护进程的启动, 更推荐使用这样的方式来启动
问题
- 系统有没有提供进程变为守护进程的方式呢?
- 之前的tcpserver udpserver怎么转化为守护进程呢?
以tcpserver为例:
server是在Main.cc文件内的, 所以对此进行修改, 还要将名字改为tcp_serverd, 这样专业一点, 因为守护进程都是以d结尾的, 就像mysql 的服务叫做mysqld
同时, tcpserver的日志显示是要保存在文件中更好一点的
综上, 这个代码可以为:
其他的不变, 这样一个服务器的雏形就有了
日志正常
serverd启动正常, 服务正常
守护进程完成