C语言进程创建与管理完全指南

进程是操作系统资源分配的基本单位。本文将详细介绍 Linux 下进程的创建、终止、管理以及进程间通信方式,帮助你掌握多进程编程技能。

进程基础概念

每个进程都有唯一的 PID(进程 ID),进程之间相互独立,通过内核调度执行。进程的主要状态包括:

  • 运行态 (R) - 正在 CPU 上执行或就绪
  • 睡眠态 (S/D) - 等待事件或资源
  • 停止态 (T) - 暂停执行
  • 僵死态 (Z) - 进程已终止但资源未回收

fork 创建进程

fork 是创建新进程的主要方式,调用一次返回两次:

fork_demo.c
#include 
#include 
#include 
#include 

int main(void) {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork failed");
        exit(1);
    }

    if (pid == 0) {
        /* 子进程 */
        printf("Child: PID=%d, Parent PID=%d\n",
               getpid(), getppid());
        sleep(2);
        exit(42);  /* 子进程退出码 */
    } else {
        /* 父进程 */
        printf("Parent: forked child PID=%d\n", pid);

        int status;
        pid_t terminated_pid = wait(&status);  /* 等待子进程 */

        if (WIFEXITED(status)) {
            printf("Child %d exited with code %d\n",
                   terminated_pid, WEXITSTATUS(status));
        }
    }
    return 0;
}

vfork 与 fork 的区别

vfork 在早期用于更高效的进程创建,但有一些特殊行为:

  • 子进程共享父进程的内存地址空间(直到 exec)
  • 子进程先运行,父进程等待子进程调用 exec 或 exit
  • 现代系统推荐使用 fork + copy-on-write
vfork_demo.c
#include 
#include 
#include 

int main(void) {
    int x = 10;
    pid_t pid = vfork();

    if (pid == 0) {
        x = 20;  /* 修改会影响父进程 */
        printf("Child: x=%d\n", x);
        _exit(0);  /* 必须用 _exit */
    }
    printf("Parent: x=%d\n", x);
    return 0;
}

exec 系列函数

exec 用新程序替换当前进程的映像:

exec_demo.c
#include 
#include 
#include 

int main(void) {
    pid_t pid = fork();

    if (pid == 0) {
        /* 子进程执行新程序 */
        execlp("ls", "ls", "-l", NULL);
        /* 如果 execlp 返回,说明出错 */
        perror("execlp failed");
        exit(1);
    } else {
        wait(NULL);  /* 等待子进程 */
        printf("Child finished\n");
    }
    return 0;
}

wait 系列函数

父进程需要等待子进程结束以回收资源:

wait_demo.c
#include 
#include 
#include 
#include 

int main(void) {
    for (int i = 0; i < 3; i++) {
        if (fork() == 0) {
            printf("Child %d (PID=%d) exiting\n", i, getpid());
            exit(i);
        }
    }

    /* 等待所有子进程 */
    int count = 0;
    while (wait(NULL) > 0) {
        count++;
    }
    printf("Waited for %d children\n", count);
    return 0;
}

进程间通信 (IPC)

常见进程间通信方式:

  • 管道 (Pipe) - 血缘进程间通信
  • FIFO (命名管道) - 非血缘进程间通信
  • 消息队列 - 消息传递
  • 共享内存 - 最高效的 IPC
  • 信号量 - 进程同步
pipe_demo.c
#include 
#include 
#include 

int main(void) {
    int pipefd[2];
    pipe(pipefd);  /* 创建管道 */

    if (fork() == 0) {
        close(pipefd[0]);  /* 关闭读端 */
        char msg[] = "Hello from child!";
        write(pipefd[1], msg, sizeof(msg));
        close(pipefd[1]);
        exit(0);
    } else {
        close(pipefd[1]);  /* 关闭写端 */
        char buf[100];
        int n = read(pipefd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, n);
        close(pipefd[0]);
        wait(NULL);
    }
    return 0;
}

进程管理最佳实践

  1. 避免僵尸进程 - 父进程必须调用 wait/waitpid 回收子进程
  2. 避免孤儿进程 - 父进程退出时确保有 init 收养子进程
  3. 正确使用 exit vs _exit - _exit 不刷新缓冲区
  4. 信号处理 - 处理 SIGCHLD 避免过多僵尸
  5. 资源限制 - 使用 setrlimit 控制子进程资源

总结

进程是 Linux 系统的核心概念。fork 用于创建进程,exec 用于执行新程序,wait 用于回收子进程。掌握这些基础 API,才能完成多进程程序的开发。