78. getpgid - 获取进程的进程组ID 链接到标题
1. 函数介绍 链接到标题
getpgid
是一个 Linux 系统调用,用于获取指定进程的进程组 ID(Process Group ID)。进程组是 Unix/Linux 系统中用于作业控制和信号管理的重要概念,它将相关的进程组织在一起,使得可以对整个组进行统一的操作。
2. 函数原型 链接到标题
#include <unistd.h>
#include <sys/types.h>
pid_t getpgid(pid_t pid);
79. getpgrp - 获取当前进程的进程组ID 链接到标题
1. 函数介绍 链接到标题
getpgrp
是一个 Linux 系统调用,用于获取当前进程的进程组 ID。它是 getpgid
的简化版本,专门用于获取调用进程自身的进程组 ID。
2. 函数原型 链接到标题
#include <unistd.h>
#include <sys/types.h>
pid_t getpgrp(void);
3. 功能对比 链接到标题
函数 | 功能 | 参数 | 返回值 |
---|---|---|---|
getpgid(pid) |
获取指定进程的进程组ID | pid_t pid (进程ID) |
进程组ID |
getpgrp() |
获取当前进程的进程组ID | 无参数 | 进程组ID |
4. 参数说明 链接到标题
getpgid 参数:
pid_t pid
: 目标进程的进程 ID0
: 表示当前进程(等同于getpgrp()
)- 正整数: 指定具体进程的 ID
- 负数: 无效,返回错误
getpgrp 参数:
- 无参数,总是操作当前进程
5. 返回值 链接到标题
- 成功时:返回进程组 ID(
pid_t
类型) - 失败时:返回 -1,并设置
errno
6. 常见 errno 错误码 链接到标题
ESRCH
: 指定的进程不存在EPERM
: 权限不足(无法访问指定进程的信息)EINVAL
: 无效的进程 ID(如负数)
7. 相似函数,或关联函数 链接到标题
setpgid()
: 设置进程的进程组 IDgetsid()
: 获取进程的会话 IDsetsid()
: 创建新会话getpid()
: 获取进程 IDgetppid()
: 获取父进程 IDtcgetpgrp()
: 获取前台进程组 IDtcsetpgrp()
: 设置前台进程组 IDkill()
: 向进程或进程组发送信号
8. 示例代码 链接到标题
示例1:基本使用 - 获取进程组信息 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
int main() {
pid_t my_pid, my_pgid, current_pgid;
printf("=== 进程组信息获取 ===\n");
// 获取当前进程 ID
my_pid = getpid();
printf("当前进程 ID: %d\n", my_pid);
// 获取父进程 ID
pid_t parent_pid = getppid();
printf("父进程 ID: %d\n", parent_pid);
// 使用 getpgid 获取当前进程的进程组 ID
my_pgid = getpgid(my_pid);
if (my_pgid == -1) {
perror("getpgid 失败");
exit(EXIT_FAILURE);
}
printf("使用 getpgid 获取的进程组 ID: %d\n", my_pgid);
// 使用 getpgrp 获取当前进程的进程组 ID
current_pgid = getpgrp();
if (current_pgid == -1) {
perror("getpgrp 失败");
exit(EXIT_FAILURE);
}
printf("使用 getpgrp 获取的进程组 ID: %d\n", current_pgid);
// 验证两者结果是否相同
if (my_pgid == current_pgid) {
printf("✓ getpgid 和 getpgrp 返回相同结果\n");
} else {
printf("✗ 结果不一致!\n");
}
// 检查进程组 ID 是否等于进程 ID(初始状态)
if (my_pgid == my_pid) {
printf("进程是进程组的领导者(初始状态)\n");
} else {
printf("进程不是进程组的领导者\n");
}
return 0;
}
示例2:错误处理和特殊情况 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
void test_getpgid(pid_t pid, const char *description) {
printf("\n测试 %s:\n", description);
printf(" 目标进程 ID: %d\n", pid);
pid_t pgid = getpgid(pid);
if (pgid == -1) {
printf(" getpgid 调用失败: %s\n", strerror(errno));
switch (errno) {
case ESRCH:
printf(" 原因: 进程不存在\n");
break;
case EPERM:
printf(" 原因: 权限不足\n");
break;
default:
printf(" 其他错误\n");
break;
}
} else {
printf(" 进程组 ID: %d\n", pgid);
}
}
int main() {
pid_t my_pid = getpid();
printf("=== getpgid 错误处理测试 ===\n");
// 测试正常情况
test_getpgid(my_pid, "当前进程");
test_getpgid(0, "使用 0 作为参数");
// 测试不存在的进程
test_getpgid(999999, "不存在的进程");
// 测试负数进程 ID
test_getpgid(-1, "负数进程 ID");
// 创建子进程进行测试
pid_t child_pid = fork();
if (child_pid == -1) {
perror("fork 失败");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
// 子进程
printf("\n=== 子进程信息 ===\n");
printf("子进程 ID: %d\n", getpid());
printf("父进程 ID: %d\n", getppid());
printf("进程组 ID: %d\n", getpgrp());
// 子进程睡眠一段时间
sleep(3);
exit(0);
} else {
// 父进程
printf("\n=== 父进程测试子进程 ===\n");
printf("子进程 ID: %d\n", child_pid);
test_getpgid(child_pid, "子进程(运行中)");
// 等待子进程结束
int status;
waitpid(child_pid, &status, 0);
printf("子进程已结束\n");
// 再次测试已结束的进程
test_getpgid(child_pid, "已结束的子进程");
}
return 0;
}
示例3:进程组操作演示 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
void print_process_info(const char *label) {
printf("%s:\n", label);
printf(" 进程 ID: %d\n", getpid());
printf(" 父进程 ID: %d\n", getppid());
printf(" 进程组 ID: %d\n", getpgrp());
printf(" 会话 ID: %d\n", getsid(0));
printf("\n");
}
void signal_handler(int sig) {
printf("进程 %d 收到信号 %d\n", getpid(), sig);
}
int main() {
pid_t original_pgid = getpgrp();
printf("=== 进程组操作演示 ===\n");
print_process_info("初始状态");
// 设置信号处理
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
// 创建第一个子进程
pid_t child1 = fork();
if (child1 == 0) {
// 第一个子进程
print_process_info("第一个子进程(创建后)");
// 加入新的进程组
if (setpgid(0, 0) == 0) {
printf("第一个子进程成功创建新进程组\n");
print_process_info("第一个子进程(加入新进程组后)");
} else {
perror("第一个子进程设置进程组失败");
}
// 执行一些工作
sleep(5);
exit(0);
}
// 创建第二个子进程
pid_t child2 = fork();
if (child2 == 0) {
// 第二个子进程
print_process_info("第二个子进程(创建后)");
// 加入第一个子进程的进程组
if (setpgid(0, child1) == 0) {
printf("第二个子进程成功加入第一个子进程的进程组\n");
print_process_info("第二个子进程(加入后)");
} else {
perror("第二个子进程设置进程组失败");
}
// 执行一些工作
sleep(5);
exit(0);
}
// 父进程等待
sleep(1);
printf("=== 进程组关系验证 ===\n");
printf("父进程组 ID: %d\n", getpgrp());
printf("第一个子进程组 ID: %d\n", getpgid(child1));
printf("第二个子进程组 ID: %d\n", getpgid(child2));
// 验证进程组关系
pid_t child1_pgid = getpgid(child1);
pid_t child2_pgid = getpgid(child2);
if (child1_pgid == child2_pgid) {
printf("✓ 两个子进程在同一个进程组中\n");
} else {
printf("✗ 两个子进程在不同进程组中\n");
}
if (child1_pgid != original_pgid) {
printf("✓ 子进程不在父进程的进程组中\n");
} else {
printf("✗ 子进程仍在父进程的进程组中\n");
}
// 等待所有子进程结束
int status;
waitpid(child1, &status, 0);
waitpid(child2, &status, 0);
printf("所有子进程执行完成\n");
return 0;
}
示例4:作业控制和信号处理 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
volatile sig_atomic_t child_finished = 0;
void sigchld_handler(int sig) {
child_finished = 1;
printf("收到 SIGCHLD 信号\n");
}
void sigterm_handler(int sig) {
printf("进程 %d 收到 SIGTERM 信号,进程组 ID: %d\n", getpid(), getpgrp());
}
int main() {
printf("=== 作业控制演示 ===\n");
// 设置信号处理
signal(SIGCHLD, sigchld_handler);
signal(SIGTERM, sigterm_handler);
print_process_info("主进程");
// 创建工作进程组
pid_t worker_group_leader = fork();
if (worker_group_leader == 0) {
// 工作组领导者
if (setpgid(0, 0) == -1) {
perror("设置进程组失败");
exit(1);
}
printf("工作组领导者创建,PID: %d, PGID: %d\n", getpid(), getpgrp());
// 创建工作组成员
for (int i = 0; i < 3; i++) {
pid_t member = fork();
if (member == 0) {
// 工作组成员
if (setpgid(0, getppid()) == -1) {
perror("成员设置进程组失败");
}
printf("工作组成员 %d 创建,PID: %d, PGID: %d\n",
i + 1, getpid(), getpgrp());
// 成员工作
sleep(10);
exit(0);
} else {
// 父进程继续创建下一个成员
if (setpgid(member, getpgrp()) == -1) {
if (errno != EACCES) { // 可能已经设置过了
perror("父进程设置子进程组失败");
}
}
}
}
// 领导者工作
sleep(10);
exit(0);
} else {
// 主进程
if (setpgid(worker_group_leader, worker_group_leader) == -1) {
if (errno != EACCES) {
perror("主进程设置工作组失败");
}
}
printf("工作组创建完成\n");
printf("工作组 ID: %d\n", getpgid(worker_group_leader));
// 显示当前所有相关进程组信息
printf("\n进程组信息汇总:\n");
printf("主进程组: %d\n", getpgrp());
printf("工作组: %d\n", getpgid(worker_group_leader));
// 等待工作组完成
printf("等待工作组完成...\n");
int status;
pid_t finished_pid;
while ((finished_pid = wait(&status)) > 0) {
printf("进程 %d 完成,退出状态: %d\n", finished_pid, WEXITSTATUS(status));
}
printf("所有工作进程完成\n");
}
return 0;
}
9. 进程组概念说明 链接到标题
进程组 (Process Group):
- 一组相关进程的集合
- 用于作业控制和信号管理
- 每个进程组有一个领导者(进程组ID等于领导者PID)
- 可以向整个进程组发送信号
会话 (Session):
- 一个或多个进程组的集合
- 通常与终端会话相关
- 会话领导者通常是登录 shell
前台进程组:
- 与终端直接交互的进程组
- 可以接收来自终端的信号(如 Ctrl+C)
10. 实际应用场景 链接到标题
场景1:shell 作业控制 链接到标题
// 检查命令是否在后台运行
int is_background_process() {
pid_t pgid = getpgrp();
pid_t fg_pgid = tcgetpgrp(STDIN_FILENO);
return pgid != fg_pgid;
}
场景2:信号处理 链接到标题
// 向进程组发送信号
int terminate_process_group(pid_t target_pid) {
pid_t pgid = getpgid(target_pid);
if (pgid != -1) {
return kill(-pgid, SIGTERM); // 负数表示进程组
}
return -1;
}
场景3:进程管理 链接到标题
// 检查进程组状态
int check_process_group_status(pid_t pgid) {
// 实现进程组状态检查逻辑
return 0;
}
11. 注意事项 链接到标题
使用进程组相关函数时需要注意:
- 权限问题: 访问其他进程信息可能需要权限
- 进程生命周期: 进程结束后相关信息可能不可用
- 信号安全: 在信号处理函数中使用要谨慎
- 错误处理: 始终检查返回值和
errno
- 竞争条件: 多进程环境中的时序问题
总结 链接到标题
getpgid
和 getpgrp
是进程组管理的基础函数:
getpgid 特点:
- 可以获取任意进程的进程组 ID
- 更加灵活,支持指定目标进程
- 需要处理更多错误情况
getpgrp 特点:
- 专门用于获取当前进程的进程组 ID
- 无参数,使用简单
- 不会失败(除非系统错误)
关键应用:
- 作业控制和 shell 实现
- 信号管理和批量操作
- 进程关系分析和调试
- 系统管理和监控工具
正确理解和使用这些函数对于编写健壮的 Unix/Linux 程序非常重要,特别是在需要进行进程管理和信号处理的场景中。