setpgid系统调用及示例

setpgid 函数详解

1. 函数介绍

setpgid 是Linux系统调用,用于设置进程的进程组ID(Process Group ID)。进程组是进程的集合,用于信号分发和作业控制。通过设置进程组,可以将相关进程组织在一起,便于统一管理和控制。

2. 函数原型

#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);

3. 功能

setpgid 将指定进程(由pid标识)加入到指定的进程组(由pgid标识)。如果进程组不存在,则创建新的进程组。这个函数主要用于作业控制和信号管理。

4. 参数

  • pid_t pid: 目标进程ID(0表示当前进程)
  • pid_t pgid: 进程组ID(0表示使用pid作为进程组ID)

5. 返回值

  • 成功: 返回0
  • 失败: 返回-1,并设置errno

6. 相似函数,或关联函数

  • getpgid: 获取进程的进程组ID
  • setpgrp: 设置进程组(等同于setpgid(0,0))
  • getsid: 获取会话ID
  • setsid: 创建新会话

7. 示例代码

示例1:基础setpgid使用

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>

/**
 * 显示进程组信息
 */
void show_process_group_info() {
    pid_t pid = getpid();
    pid_t pgid = getpgid(0);
    pid_t ppid = getppid();
    
    printf("进程ID: %d\n", pid);
    printf("父进程ID: %d\n", ppid);
    printf("进程组ID: %d\n", pgid);
    printf("会话ID: %d\n", getsid(0));
    printf("\n");
}

/**
 * 演示基础setpgid使用方法
 */
int demo_setpgid_basic() {
    pid_t original_pgid, new_pgid;
    int result;
    
    printf("=== 基础setpgid使用示例 ===\n");
    
    // 显示原始进程组信息
    printf("1. 原始进程组信息:\n");
    show_process_group_info();
    original_pgid = getpgid(0);
    
    // 将当前进程移动到新的进程组
    printf("2. 创建新的进程组:\n");
    pid_t new_group_id = getpid();  // 使用当前进程ID作为新进程组ID
    
    printf("   尝试将进程 %d 移动到进程组 %d\n", getpid(), new_group_id);
    result = setpgid(0, new_group_id);
    
    if (result == 0) {
        printf("   ✓ 成功创建并加入新进程组\n");
        
        // 验证设置结果
        new_pgid = getpgid(0);
        printf("   新的进程组ID: %d\n", new_pgid);
        
        if (new_pgid == new_group_id) {
            printf("   ✓ 进程组设置正确\n");
        } else {
            printf("   ✗ 进程组设置可能有问题\n");
        }
        
        show_process_group_info();
    } else {
        printf("   ✗ 创建新进程组失败: %s\n", strerror(errno));
        if (errno == EACCES) {
            printf("   原因:不允许从会话领导进程更改进程组\n");
        } else if (errno == EINVAL) {
            printf("   原因:进程ID或进程组ID无效\n");
        } else if (errno == EPERM) {
            printf("   原因:权限不足\n");
        } else if (errno == ESRCH) {
            printf("   原因:指定的进程不存在\n");
        }
    }
    
    return 0;
}

int main() {
    return demo_setpgid_basic();
}

示例2:父子进程组管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>

/**
 * 子进程函数
 */
void child_process(int child_id) {
    printf("子进程 %d 启动\n", getpid());
    printf("  子进程 %d 的原始进程组: %d\n", getpid(), getpgid(0));
    
    // 子进程创建自己的进程组
    pid_t new_pgid = getpid();
    printf("  子进程 %d 尝试创建新进程组: %d\n", getpid(), new_pgid);
    
    int result = setpgid(0, new_pgid);
    if (result == 0) {
        printf("  ✓ 子进程 %d 成功创建新进程组\n", getpid());
        printf("  子进程 %d 当前进程组: %d\n", getpid(), getpgid(0));
    } else {
        printf("  ✗ 子进程 %d 创建新进程组失败: %s\n", getpid(), strerror(errno));
    }
    
    // 模拟一些工作
    sleep(3);
    printf("子进程 %d 结束\n", getpid());
    exit(0);
}

/**
 * 演示父子进程组管理
 */
int demo_parent_child_groups() {
    pid_t child1_pid, child2_pid;
    pid_t original_pgid;
    
    printf("=== 父子进程组管理演示 ===\n");
    
    // 显示父进程信息
    printf("父进程信息:\n");
    printf("  进程ID: %d\n", getpid());
    printf("  原始进程组: %d\n", getpgid(0));
    printf("  会话ID: %d\n", getsid(0));
    printf("\n");
    
    // 创建第一个子进程
    printf("1. 创建第一个子进程:\n");
    child1_pid = fork();
    if (child1_pid == 0) {
        child_process(1);
    } else if (child1_pid > 0) {
        printf("  父进程创建子进程1: PID=%d\n", child1_pid);
        printf("  子进程1的进程组: %d\n", getpgid(child1_pid));
    } else {
        perror("创建子进程1失败");
        return -1;
    }
    
    // 创建第二个子进程
    printf("\n2. 创建第二个子进程:\n");
    child2_pid = fork();
    if (child2_pid == 0) {
        child_process(2);
    } else if (child2_pid > 0) {
        printf("  父进程创建子进程2: PID=%d\n", child2_pid);
        printf("  子进程2的进程组: %d\n", getpgid(child2_pid));
    } else {
        perror("创建子进程2失败");
        // 清理已创建的子进程
        kill(child1_pid, SIGKILL);
        return -1;
    }
    
    // 父进程也创建新进程组
    printf("\n3. 父进程创建新进程组:\n");
    original_pgid = getpgid(0);
    pid_t parent_pgid = getpid();
    
    printf("  父进程尝试创建新进程组: %d\n", parent_pgid);
    int result = setpgid(0, parent_pgid);
    if (result == 0) {
        printf("  ✓ 父进程成功创建新进程组\n");
        printf("  父进程当前进程组: %d\n", getpgid(0));
    } else {
        printf("  ✗ 父进程创建新进程组失败: %s\n", strerror(errno));
    }
    
    // 等待子进程结束
    printf("\n4. 等待子进程结束:\n");
    int status;
    pid_t finished_pid;
    
    while ((finished_pid = wait(&status)) > 0) {
        printf("  子进程 %d 已结束,退出状态: %d\n", finished_pid, WEXITSTATUS(status));
    }
    
    // 恢复父进程原始进程组
    printf("\n5. 恢复父进程原始进程组:\n");
    result = setpgid(0, original_pgid);
    if (result == 0) {
        printf("  ✓ 父进程成功恢复原始进程组: %d\n", getpgid(0));
    } else {
        printf("  ✗ 父进程恢复原始进程组失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_parent_child_groups();
}

示例3:进程组信号处理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 信号处理函数
 */
void signal_handler(int sig) {
    printf("进程 %d 接收到信号 %d (%s)\n", getpid(), sig, strsignal(sig));
}

/**
 * 工作进程函数
 */
void worker_process(int worker_id) {
    // 设置信号处理
    signal(SIGTERM, signal_handler);
    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    
    printf("工作进程 %d 启动,进程组: %d\n", getpid(), getpgid(0));
    
    // 创建自己的进程组
    pid_t new_pgid = getpid();
    if (setpgid(0, new_pgid) == 0) {
        printf("工作进程 %d 成功创建进程组: %d\n", getpid(), new_pgid);
    }
    
    // 模拟长时间工作
    for (int i = 0; i < 10; i++) {
        printf("工作进程 %d 正在工作... (%d/10)\n", getpid(), i + 1);
        sleep(2);
    }
    
    printf("工作进程 %d 结束\n", getpid());
    exit(0);
}

/**
 * 演示进程组信号处理
 */
int demo_process_group_signaling() {
    pid_t workers[3];
    pid_t original_pgid;
    
    printf("=== 进程组信号处理演示 ===\n");
    
    // 保存原始进程组
    original_pgid = getpgid(0);
    printf("主进程原始进程组: %d\n", original_pgid);
    
    // 创建工作进程组
    printf("\n1. 创建工作进程组:\n");
    for (int i = 0; i < 3; i++) {
        workers[i] = fork();
        if (workers[i] == 0) {
            worker_process(i + 1);
        } else if (workers[i] > 0) {
            printf("  创建工作进程 %d: PID=%d\n", i + 1, workers[i]);
            
            // 将子进程加入同一进程组
            pid_t group_id = workers[0];  // 使用第一个子进程的PID作为组ID
            if (setpgid(workers[i], group_id) == 0) {
                printf("  工作进程 %d 加入进程组: %d\n", workers[i], group_id);
            } else {
                printf("  工作进程 %d 加入进程组失败: %s\n", workers[i], strerror(errno));
            }
        } else {
            perror("创建工作进程失败");
            // 清理已创建的进程
            for (int j = 0; j < i; j++) {
                kill(workers[j], SIGKILL);
            }
            return -1;
        }
    }
    
    // 显示进程组信息
    printf("\n2. 进程组信息:\n");
    for (int i = 0; i < 3; i++) {
        pid_t pgid = getpgid(workers[i]);
        printf("  工作进程 %d (PID=%d) 进程组: %d\n", i + 1, workers[i], pgid);
    }
    
    // 等待一段时间后发送信号
    printf("\n3. 等待5秒后发送信号到进程组...\n");
    sleep(5);
    
    // 向进程组发送信号(发送给组内所有进程)
    pid_t target_group = getpgid(workers[0]);
    printf("向进程组 %d 发送 SIGUSR1 信号\n", target_group);
    
    if (kill(-target_group, SIGUSR1) == 0) {  // 负数表示发送给整个进程组
        printf("✓ 成功发送信号到进程组\n");
    } else {
        printf("✗ 发送信号失败: %s\n", strerror(errno));
    }
    
    // 等待一段时间观察信号处理
    sleep(3);
    
    // 发送终止信号
    printf("\n4. 发送终止信号:\n");
    printf("向进程组 %d 发送 SIGTERM 信号\n", target_group);
    
    if (kill(-target_group, SIGTERM) == 0) {
        printf("✓ 成功发送终止信号\n");
    } else {
        printf("✗ 发送终止信号失败: %s\n", strerror(errno));
    }
    
    // 等待所有工作进程结束
    printf("\n5. 等待工作进程结束:\n");
    int status;
    pid_t finished_pid;
    int finished_count = 0;
    
    while (finished_count < 3) {
        finished_pid = wait(&status);
        if (finished_pid > 0) {
            finished_count++;
            printf("  工作进程 %d 已结束,退出状态: %d\n", 
                   finished_pid, WEXITSTATUS(status));
        } else {
            break;
        }
    }
    
    printf("所有工作进程已完成\n");
    return 0;
}

int main() {
    return demo_process_group_signaling();
}

示例4:会话和进程组管理

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 显示会话和进程组信息
 */
void show_session_info() {
    printf("=== 会话和进程组信息 ===\n");
    printf("进程ID: %d\n", getpid());
    printf("父进程ID: %d\n", getppid());
    printf("进程组ID: %d\n", getpgid(0));
    printf("会话ID: %d\n", getsid(0));
    printf("前台进程组: %d\n", tcgetpgrp(STDIN_FILENO));
    printf("\n");
}

/**
 * 会话领导进程
 */
void session_leader() {
    printf("会话领导进程启动 (PID: %d)\n", getpid());
    show_session_info();
    
    // 创建新会话
    pid_t sid = setsid();
    if (sid != -1) {
        printf("✓ 成功创建新会话: %d\n", sid);
        show_session_info();
    } else {
        printf("✗ 创建新会话失败: %s\n", strerror(errno));
        exit(1);
    }
    
    // 创建子进程组
    for (int i = 0; i < 2; i++) {
        pid_t child_pid = fork();
        if (child_pid == 0) {
            // 子进程
            printf("子进程 %d 启动\n", getpid());
            
            // 创建自己的进程组
            pid_t new_pgid = getpid();
            if (setpgid(0, new_pgid) == 0) {
                printf("子进程 %d 成功创建进程组: %d\n", getpid(), new_pgid);
            }
            
            // 模拟工作
            for (int j = 0; j < 5; j++) {
                printf("子进程 %d 工作中... (%d/5)\n", getpid(), j + 1);
                sleep(2);
            }
            
            printf("子进程 %d 结束\n", getpid());
            exit(0);
        } else if (child_pid > 0) {
            printf("会话领导进程创建子进程: %d\n", child_pid);
            
            // 将子进程加入特定进程组
            if (setpgid(child_pid, child_pid) == 0) {
                printf("子进程 %d 加入进程组成功\n", child_pid);
            }
        }
    }
    
    // 等待子进程结束
    int status;
    pid_t finished_pid;
    while ((finished_pid = wait(&status)) > 0) {
        printf("子进程 %d 已结束\n", finished_pid);
    }
    
    printf("会话领导进程结束\n");
    exit(0);
}

/**
 * 演示会话和进程组管理
 */
int demo_session_management() {
    pid_t session_leader_pid;
    
    printf("=== 会话和进程组管理演示 ===\n");
    
    // 显示初始信息
    printf("1. 初始进程信息:\n");
    show_session_info();
    
    // 创建会话领导进程
    printf("2. 创建会话领导进程:\n");
    session_leader_pid = fork();
    if (session_leader_pid == 0) {
        session_leader();
    } else if (session_leader_pid > 0) {
        printf("主进程创建会话领导进程: %d\n", session_leader_pid);
        
        // 等待会话领导进程结束
        int status;
        waitpid(session_leader_pid, &status, 0);
        printf("会话领导进程已结束\n");
    } else {
        perror("创建会话领导进程失败");
        return -1;
    }
    
    // 显示最终信息
    printf("\n3. 最终进程信息:\n");
    show_session_info();
    
    return 0;
}

int main() {
    return demo_session_management();
}

示例5:作业控制演示

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/**
 * 后台作业进程
 */
void background_job(int job_id) {
    printf("后台作业 %d 启动 (PID: %d)\n", job_id, getpid());
    
    // 创建独立的进程组
    if (setpgid(0, 0) == 0) {
        printf("后台作业 %d 成功创建进程组\n", job_id);
    } else {
        printf("后台作业 %d 创建进程组失败: %s\n", job_id, strerror(errno));
    }
    
    // 模拟长时间运行的作业
    for (int i = 0; i < 20; i++) {
        printf("后台作业 %d 运行中... (%d/20)\n", job_id, i + 1);
        sleep(1);
        
        // 检查是否收到终止信号
        if (i == 10) {
            // 模拟作业暂停和恢复
            printf("后台作业 %d 暂停工作\n", job_id);
            sleep(2);
            printf("后台作业 %d 恢复工作\n", job_id);
        }
    }
    
    printf("后台作业 %d 完成\n", job_id);
    exit(0);
}

/**
 * 作业控制管理器
 */
typedef struct {
    pid_t pid;
    int job_id;
    int status;  // 0:运行, 1:暂停, 2:完成
    char command[256];
} job_t;

/**
 * 显示作业列表
 */
void show_job_list(job_t *jobs, int count) {
    printf("\n=== 作业列表 ===\n");
    printf("%-5s %-8s %-10s %s\n", "作业ID", "进程ID", "状态", "命令");
    printf("----------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        const char *status_str;
        switch (jobs[i].status) {
            case 0: status_str = "运行"; break;
            case 1: status_str = "暂停"; break;
            case 2: status_str = "完成"; break;
            default: status_str = "未知"; break;
        }
        
        printf("%-5d %-8d %-10s %s\n", 
               jobs[i].job_id, jobs[i].pid, status_str, jobs[i].command);
    }
    printf("\n");
}

/**
 * 演示作业控制
 */
int demo_job_control() {
    job_t jobs[3];
    int job_count = 0;
    
    printf("=== 作业控制演示 ===\n");
    
    // 创建后台作业
    printf("1. 创建后台作业:\n");
    for (int i = 0; i < 3; i++) {
        pid_t job_pid = fork();
        if (job_pid == 0) {
            background_job(i + 1);
        } else if (job_pid > 0) {
            // 父进程记录作业信息
            jobs[job_count].pid = job_pid;
            jobs[job_count].job_id = i + 1;
            jobs[job_count].status = 0;  // 运行中
            snprintf(jobs[job_count].command, sizeof(jobs[job_count].command), 
                     "background_job_%d", i + 1);
            job_count++;
            
            printf("  创建后台作业 %d: PID=%d\n", i + 1, job_pid);
            
            // 将子进程加入独立进程组
            if (setpgid(job_pid, job_pid) == 0) {
                printf("  作业 %d 成功创建独立进程组\n", i + 1);
            }
        } else {
            perror("创建后台作业失败");
        }
    }
    
    // 显示初始作业列表
    show_job_list(jobs, job_count);
    
    // 演示向特定作业发送信号
    printf("2. 向作业发送信号:\n");
    if (job_count > 0) {
        printf("  向作业 1 (PID=%d) 发送 SIGUSR1 信号\n", jobs[0].pid);
        if (kill(jobs[0].pid, SIGUSR1) == 0) {
            printf("  ✓ 信号发送成功\n");
        } else {
            printf("  ✗ 信号发送失败: %s\n", strerror(errno));
        }
    }
    
    // 等待一段时间
    printf("\n3. 等待作业运行...\n");
    sleep(5);
    
    // 显示当前作业状态
    show_job_list(jobs, job_count);
    
    // 演示作业控制操作
    printf("4. 作业控制操作:\n");
    
    // 暂停一个作业
    if (job_count > 1) {
        printf("  暂停作业 2 (PID=%d)\n", jobs[1].pid);
        if (kill(-getpgid(jobs[1].pid), SIGSTOP) == 0) {  // 发送给整个进程组
            jobs[1].status = 1;  // 暂停
            printf("  ✓ 作业 2 已暂停\n");
        } else {
            printf("  ✗ 暂停作业 2 失败: %s\n", strerror(errno));
        }
    }
    
    // 显示更新后的作业列表
    show_job_list(jobs, job_count);
    
    // 恢复暂停的作业
    if (job_count > 1) {
        printf("  恢复作业 2 (PID=%d)\n", jobs[1].pid);
        if (kill(-getpgid(jobs[1].pid), SIGCONT) == 0) {
            jobs[1].status = 0;  // 运行中
            printf("  ✓ 作业 2 已恢复\n");
        } else {
            printf("  ✗ 恢复作业 2 失败: %s\n", strerror(errno));
        }
    }
    
    // 等待所有作业完成
    printf("\n5. 等待所有作业完成:\n");
    int completed_jobs = 0;
    while (completed_jobs < job_count) {
        int status;
        pid_t finished_pid = waitpid(-1, &status, WNOHANG);
        if (finished_pid > 0) {
            // 找到完成的作业并更新状态
            for (int i = 0; i < job_count; i++) {
                if (jobs[i].pid == finished_pid) {
                    jobs[i].status = 2;  // 完成
                    printf("  作业 %d (PID=%d) 已完成\n", jobs[i].job_id, finished_pid);
                    completed_jobs++;
                    break;
                }
            }
        } else if (finished_pid == 0) {
            // 没有作业完成,短暂等待
            usleep(100000);  // 100ms
        } else {
            // 错误
            if (errno != ECHILD) {
                perror("等待作业完成时出错");
            }
            break;
        }
    }
    
    // 显示最终作业列表
    show_job_list(jobs, job_count);
    
    printf("作业控制演示完成\n");
    return 0;
}

int main() {
    return demo_job_control();
}

setpgid 使用注意事项

系统要求:

  1. 内核版本: 支持进程组管理的Linux内核
  2. 权限要求: 通常不需要特殊权限
  3. 架构支持: 支持所有主流架构

参数规则:

  1. pid为0: 表示当前进程
  2. pgid为0: 表示使用pid作为进程组ID
  3. 进程限制: 只能操作当前会话中的进程
  4. 会话限制: 不能将进程移到不同会话的进程组

错误处理:

  1. EACCES: 不允许从会话领导进程更改进程组
  2. EINVAL: 进程ID或进程组ID无效
  3. EPERM: 权限不足
  4. ESRCH: 指定的进程不存在

性能考虑:

  1. 系统调用开销: 频繁调用有性能开销
  2. 内核数据结构: 需要更新内核中的进程组信息
  3. 同步操作: 涉及内核数据结构的同步更新

安全考虑:

  1. 进程控制: 可能影响其他进程的行为
  2. 信号传播: 进程组信号会影响组内所有进程
  3. 会话管理: 不当的会话操作可能影响终端控制

最佳实践:

  1. 时机选择: 在进程创建后尽早设置进程组
  2. 错误处理: 妥善处理各种错误情况
  3. 状态检查: 验证设置结果的正确性
  4. 资源清理: 及时清理不需要的进程组
  5. 文档记录: 记录进程组管理策略

进程组和会话概念

进程组(Process Group):

  • 定义: 一组相关进程的集合
  • 用途: 信号分发、作业控制
  • 特点: 同一进程组的进程可以被统一控制

会话(Session):

  • 定义: 一个或多个进程组的集合
  • 用途: 终端控制、登录会话管理
  • 特点: 会话领导进程控制整个会话

关系层次:

会话 (Session)
└── 进程组 (Process Group)
    └── 进程 (Process)

常见使用场景

1. Shell作业控制:

// 创建后台作业的独立进程组
setpgid(child_pid, child_pid);

2. 守护进程:

// 创建独立会话,脱离控制终端
setsid();

3. 多进程应用:

// 将相关进程组织到同一进程组
setpgid(worker_pid, group_leader_pid);

信号与进程组

组信号发送:

// 向整个进程组发送信号
kill(-pgid, SIGTERM);

前台进程组:

// 设置前台进程组(控制终端)
tcsetpgrp(STDIN_FILENO, pgid);

总结

setpgid 是Linux系统中重要的进程管理函数,提供了:

  1. 进程组控制: 灵活的进程组管理能力
  2. 作业控制: 支持shell风格的作业控制
  3. 信号管理: 便于向进程组发送信号
  4. 会话管理: 支持会话和终端控制

通过合理使用 setpgid,可以构建更加灵活和可控的多进程应用程序。在实际应用中,需要注意进程组的生命周期管理、错误处理和信号控制等关键问题。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注