跳转到内容

C 语言进程控制

来自代码酷

C语言进程控制[编辑 | 编辑源代码]

进程控制是C语言系统编程中的核心概念之一,它允许程序创建、管理和终止进程。在多任务操作系统中,进程是程序执行的基本单位,掌握进程控制技术对于开发高效、稳定的系统级应用至关重要。

进程基础[编辑 | 编辑源代码]

在Unix/Linux系统中,进程(Process)是一个正在执行的程序的实例。每个进程都有:

  • 独立的地址空间
  • 唯一的进程ID(PID)
  • 自己的资源分配(文件描述符、内存等)

进程状态[编辑 | 编辑源代码]

进程在其生命周期中会经历多种状态:

stateDiagram-v2 [*] --> 新建(New) 新建 --> 就绪(Ready): 初始化完成 就绪 --> 运行(Running): 被调度 运行 --> 就绪: 时间片用完 运行 --> 阻塞(Blocked): I/O请求 阻塞 --> 就绪: I/O完成 运行 --> 终止(Terminated): 执行结束

进程创建[编辑 | 编辑源代码]

C语言通过`fork()`系统调用创建新进程。

fork()系统调用[编辑 | 编辑源代码]

`fork()`会创建一个与父进程几乎完全相同的子进程,区别仅在于:

  • 返回值不同
  • 进程ID不同
  • 父进程ID不同
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        // fork失败
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程代码
        printf("这是子进程,PID=%d\n", getpid());
    } else {
        // 父进程代码
        printf("这是父进程,PID=%d,子进程PID=%d\n", getpid(), pid);
    }
    
    return 0;
}

输出示例:

这是父进程,PID=1234,子进程PID=1235
这是子进程,PID=1235

注意:父进程和子进程的执行顺序是不确定的,由系统调度决定。

进程终止[编辑 | 编辑源代码]

进程可以通过以下方式终止: 1. 正常退出(`exit()`或`_exit()`) 2. 异常终止(收到信号)

exit() vs _exit()[编辑 | 编辑源代码]

| 函数 | 是否刷新缓冲区 | 是否调用atexit注册的函数 | |------------|----------------|--------------------------| | exit() | 是 | 是 | | _exit() | 否 | 否 |

#include <stdlib.h>
#include <unistd.h>

void cleanup() {
    printf("执行清理函数\n");
}

int main() {
    atexit(cleanup);
    
    printf("使用exit():\n");
    exit(0);  // 会输出"执行清理函数"
    
    // 以下代码不会执行
    printf("使用_exit():\n");
    _exit(0);  // 不会输出"执行清理函数"
}

进程等待[编辑 | 编辑源代码]

父进程可以使用`wait()`或`waitpid()`等待子进程结束,并获取其退出状态。

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

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程
        printf("子进程开始\n");
        sleep(2);
        printf("子进程结束\n");
        exit(42);  // 子进程返回42
    } else {
        // 父进程
        int status;
        printf("父进程等待子进程...\n");
        wait(&status);
        
        if (WIFEXITED(status)) {
            printf("子进程正常退出,返回值: %d\n", WEXITSTATUS(status));
        } else {
            printf("子进程异常终止\n");
        }
    }
    
    return 0;
}

输出示例:

父进程等待子进程...
子进程开始
子进程结束
子进程正常退出,返回值: 42

进程替换[编辑 | 编辑源代码]

`exec`系列函数用于将当前进程替换为一个新程序:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("准备执行ls命令...\n");
    
    char *args[] = {"ls", "-l", NULL};
    execvp("ls", args);
    
    // 如果exec成功,以下代码不会执行
    perror("exec失败");
    return 1;
}

输出示例:

准备执行ls命令...
total 24
-rwxr-xr-x 1 user group 12345 Jan 1 10:00 a.out
-rw-r--r-- 1 user group   678 Jan 1 09:00 example.c

实际应用案例[编辑 | 编辑源代码]

案例1:简单的shell实现[编辑 | 编辑源代码]

以下代码展示了一个简化版shell如何利用进程控制:

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

#define MAX_LINE 80

int main() {
    char line[MAX_LINE];
    char *args[MAX_LINE/2 + 1];
    
    while (1) {
        printf("myshell> ");
        fflush(stdout);
        
        if (!fgets(line, MAX_LINE, stdin)) {
            break;  // 读取失败或EOF
        }
        
        // 解析命令
        int i = 0;
        args[i] = strtok(line, " \t\n");
        while (args[i] != NULL && i < MAX_LINE/2) {
            args[++i] = strtok(NULL, " \t\n");
        }
        
        if (i == 0) continue;  // 空命令
        
        // 内置命令处理
        if (strcmp(args[0], "exit") == 0) {
            break;
        }
        
        // 创建子进程执行命令
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            execvp(args[0], args);
            perror("exec失败");
            exit(1);
        } else if (pid > 0) {
            // 父进程等待子进程
            wait(NULL);
        } else {
            perror("fork失败");
        }
    }
    
    return 0;
}

案例2:并行任务处理[编辑 | 编辑源代码]

利用`fork()`创建多个子进程并行处理任务:

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

#define NUM_WORKERS 4

void worker(int id) {
    printf("Worker %d (PID=%d) 开始工作\n", id, getpid());
    sleep(id * 2);  // 模拟工作
    printf("Worker %d 完成工作\n", id);
    exit(0);
}

int main() {
    printf("主进程 (PID=%d) 启动\n", getpid());
    
    for (int i = 0; i < NUM_WORKERS; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            worker(i);
            exit(0);  // 确保子进程不会继续循环
        } else if (pid < 0) {
            perror("fork失败");
            exit(1);
        }
    }
    
    // 等待所有子进程结束
    for (int i = 0; i < NUM_WORKERS; i++) {
        wait(NULL);
    }
    
    printf("所有工作完成\n");
    return 0;
}

进程间通信(IPC)简介[编辑 | 编辑源代码]

进程间通信是进程控制的重要扩展,常见方法包括: 1. 管道(pipe) 2. 共享内存 3. 消息队列 4. 信号量 5. 套接字(socket)

简单管道示例[编辑 | 编辑源代码]

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

int main() {
    int fd[2];
    if (pipe(fd) == -1) {
        perror("pipe失败");
        exit(1);
    }
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程 - 写入数据
        close(fd[0]);  // 关闭读取端
        char msg[] = "Hello from child!";
        write(fd[1], msg, sizeof(msg));
        close(fd[1]);
        exit(0);
    } else {
        // 父进程 - 读取数据
        close(fd[1]);  // 关闭写入端
        char buf[100];
        read(fd[0], buf, sizeof(buf));
        printf("父进程收到: %s\n", buf);
        close(fd[0]);
        wait(NULL);
    }
    
    return 0;
}

安全注意事项[编辑 | 编辑源代码]

1. 避免僵尸进程:父进程必须等待子进程或设置SIGCHLD处理 2. 防止孤儿进程:子进程未结束前父进程不应退出 3. 资源释放:确保所有文件描述符被正确关闭 4. 信号处理:正确处理可能中断系统调用的信号

总结[编辑 | 编辑源代码]

C语言的进程控制提供了强大的系统编程能力,包括:

  • 使用`fork()`创建新进程
  • 使用`exec`系列函数替换进程映像
  • 使用`wait()`/`waitpid()`同步进程
  • 使用`exit()`/_exit()`终止进程

掌握这些基础知识后,可以进一步学习更高级的进程间通信技术和多线程编程。