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: 目标进程的进程 ID
    • 0: 表示当前进程(等同于 getpgrp()
    • 正整数: 指定具体进程的 ID
    • 负数: 无效,返回错误

getpgrp 参数:

  • 无参数,总是操作当前进程

5. 返回值 链接到标题

  • 成功时:返回进程组 ID(pid_t 类型)
  • 失败时:返回 -1,并设置 errno

6. 常见 errno 错误码 链接到标题

  • ESRCH: 指定的进程不存在
  • EPERM: 权限不足(无法访问指定进程的信息)
  • EINVAL: 无效的进程 ID(如负数)

7. 相似函数,或关联函数 链接到标题

  • setpgid(): 设置进程的进程组 ID
  • getsid(): 获取进程的会话 ID
  • setsid(): 创建新会话
  • getpid(): 获取进程 ID
  • getppid(): 获取父进程 ID
  • tcgetpgrp(): 获取前台进程组 ID
  • tcsetpgrp(): 设置前台进程组 ID
  • kill(): 向进程或进程组发送信号

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. 注意事项 链接到标题

使用进程组相关函数时需要注意:

  1. 权限问题: 访问其他进程信息可能需要权限
  2. 进程生命周期: 进程结束后相关信息可能不可用
  3. 信号安全: 在信号处理函数中使用要谨慎
  4. 错误处理: 始终检查返回值和 errno
  5. 竞争条件: 多进程环境中的时序问题

总结 链接到标题

getpgidgetpgrp 是进程组管理的基础函数:

getpgid 特点:

  • 可以获取任意进程的进程组 ID
  • 更加灵活,支持指定目标进程
  • 需要处理更多错误情况

getpgrp 特点:

  • 专门用于获取当前进程的进程组 ID
  • 无参数,使用简单
  • 不会失败(除非系统错误)

关键应用:

  1. 作业控制和 shell 实现
  2. 信号管理和批量操作
  3. 进程关系分析和调试
  4. 系统管理和监控工具

正确理解和使用这些函数对于编写健壮的 Unix/Linux 程序非常重要,特别是在需要进行进程管理和信号处理的场景中。