【Liunx】进程间关系与守护进程
1. 进程组
1.1 什么是进程组
- 进程组是一个或多个进程的集合,每个进程除 PID 外,还属于一个进程组。
- 每个进程组有唯一的进程组 ID(PGID),类型为
pid_t(正整数)。 - 查看方式:
ps -eo pid,pgid,ppid,comm | grep 进程名,其中-e输出所有进程,-o指定输出列(PID、PGID、PPID、命令)。
1.2 组长进程
- 组长进程的 PID 等于进程组的 PGID。
- 作用:可创建进程组或组内进程。
- 生命周期:从创建到最后一个进程离开为止,与组长是否终止无关(只要组内有进程存在,进程组就存在)。
2. 会话
2.1 什么是会话
- 会话是一个或多个进程组的集合,有唯一的会话 ID(SID)。
- 示例:通过管道和后台运行符
&创建进程组集合,如proc2 | proc3 &和proc4 | proc5 | proc6 &会形成两个进程组,同属一个会话。 - 查看方式:
ps axj(a显示所有用户进程,x显示无控制终端进程,j显示作业控制相关信息),可观察到同一管道中的进程 PGID 相同。

每次登录成功,系统必须为用户新建一个会话,会话内部必须默认有一个进程组,叫做bash(独立进程,独立进程组)。将该会话的ID设置成为bash进程的pid!
2.2 如何创建会话
- 调用
setsid函数,原型:#include <unistd.h> pid_t setsid(void); // 成功返回SID,失败返回-1 - 限制:调用进程不能是进程组组长,否则报错。解决方式:先
fork创建子进程,父进程终止,子进程执行setsid(子进程继承 PGID 但 PID 不同,不会是组长)。 - 调用后效果:
- 调用进程成为新会话的会话首进程(会话中唯一进程)。
- 调用进程成为进程组组长(新 PGID 为其 PID)。
- 切断与原控制终端的联系(若存在)。
2.3 会话 ID(SID)
- 会话首进程的 PID 即为 SID(会话首进程必为进程组组长,故 SID 等价于其 PGID)。
3. 控制终端
- 定义:用户登录系统后,Shell 进程关联的终端(终端设备或伪终端),由 Shell 启动的进程会继承该控制终端,默认标准输入 / 输出 / 错误指向控制终端。
- 核心关系:
- 一个会话最多有一个控制终端,由会话首进程打开。
- 会话首进程若与控制终端连接,则称为控制进程。
- 会话中的进程组分为前台进程组(1 个)和后台进程组(多个)。
- 终端特殊按键信号(
Ctrl+C→SIGINT,Ctrl+\→SIGQUIT,Ctrl+Z→SIGTSTP)仅发送给前台进程组。 - 终端断开时,挂断信号发送给控制进程(会话首进程)。
4. 作业控制
4.1 作业与作业控制
- 作业:用户为完成任务启动的进程集合,可包含单个或多个进程(通常通过管道协作)。
- 作业控制:Shell 通过前后台管理作业(而非单个进程),支持同时运行 1 个前台作业和多个后台作业。
4.2 作业号
- 后台作业通过
&标识,执行后返回作业号(如[1])和进程 PID。 - 默认作业标识:
+:当前默认作业(fg 命令缺省时操作对象)。-:即将成为默认作业(当前默认作业退出后)。- 无符号:其他作业。
4.3 作业状态
- 运行中:后台作业正常执行。
- 已停止:通过
Ctrl+Z挂起(接收SIGTSTP信号)。 - 完成:作业执行结束。
4.4 作业操作
- 挂起:前台作业按
Ctrl+Z,状态变为 “已停止”。 - 切回前台:
fg [作业号],缺省操作默认作业(带+的作业)。 - 查看作业:
jobs(显示所有后台 / 挂起作业),-l显示详细信息(含 PID),-p只显示 PID。
5. 守护进程
- 定义:在后台长期运行的进程,脱离控制终端,独立于用户会话,通常用于提供服务(如服务器进程)。
- 实现代码(Daemon.hpp):
#pragma once #include <iostream> #include <cstdlib> #include <signal.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h>const char *root = "/"; const char *dev_null = "/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();// 4. 可选:切换工作目录到根目录(避免依赖原目录)if (ischdir) chdir(root);// 5. 关闭或重定向标准输入/输出/错误(脱离终端I/O)if (isclose){close(0);close(1);close(2);}else{// 重定向到/dev/null(丢弃I/O)int fd = open(dev_null, O_RDWR);if (fd > 0){dup2(fd, 0); // 标准输入重定向dup2(fd, 1); // 标准输出重定向dup2(fd, 2); // 标准错误重定向close(fd);}} }
6. 服务守护进程化示例
// 用法:./server port
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]);// 调用守护进程函数,不切换目录,不关闭文件描述符(重定向到/dev/null)Daemon(false, false);// 启动服务器(示例)std::unique_ptr<TcpServer> svr(new TcpServer(localport, HandlerRequest));svr->Loop();return 0;
}
