Linux进程管理之创建、终止、回收与替换操作完全指南

 更新时间:2025年12月18日 09:00:03   作者:南烟斋..  
进程是Linux系统的核心概念之一,理解进程的创建、终止、回收和替换是系统编程的基石,本文将系统性地介绍Linux进程管理的各个方面,有需要的小伙伴可以了解下

引言

进程是Linux系统的核心概念之一,理解进程的创建、终止、回收和替换是系统编程的基石。本文将系统性地介绍Linux进程管理的各个方面,包括父子进程关系、写时复制技术、进程终止方式、僵尸进程处理、进程回收机制以及exec函数族的使用。

一、父子进程与写时复制

1.1 fork创建进程

在Linux中,通过fork()系统调用创建新进程:

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

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

1.2 写时复制(Copy-On-Write)

传统理解fork()会完全复制父进程的内存空间给子进程,效率低下。

现代Linux(2.6+内核)实现

  • 立即共享fork()刚完成时,子进程与父进程共享所有内存页
  • 按需复制:只有当父子进程中的任意一方尝试修改某个内存页时,内核才会复制该页
  • 效率优势:避免了不必要的内存复制,大幅提升性能
int shared_data = 100;  // 父子进程共享

pid_t pid = fork();
if (pid == 0) {
    // 子进程
    shared_data = 200;  // 此时触发写时复制
    printf("子进程修改后: %d\n", shared_data);
} else {
    // 父进程
    sleep(1);
    printf("父进程的值: %d\n", shared_data);  // 仍为100
}

二、进程的终止:8种情况详解

进程可以通过多种方式终止,了解这些情况对编写健壮程序至关重要。

2.1 正常终止方式

方式说明代码示例
1. main函数return​在main函数中使用return语句return 0;
2. exit()库函数​执行完整清理工作exit(0);
3. exit()/Exit()​立即退出,不执行清理_exit(0);

exit()与_exit()的关键区别

// exit()示例
#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("这条消息会被输出");  // 在缓冲区
    exit(0);  // 刷新缓冲区,输出消息
    // 还会执行atexit()注册的清理函数
}

// _exit()示例
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("这条消息可能不会输出");  // 在缓冲区
    _exit(0);  // 不刷新缓冲区,消息丢失
    // 不执行任何清理函数
}

exit函数参数说明

exit(0);           // 成功退出
exit(EXIT_SUCCESS); // 同exit(0)
exit(EXIT_FAILURE); // 失败退出,值为1
exit(1);           // 自定义错误码

2.2 异常终止方式

方式说明触发条件
4. abort()产生SIGABRT信号abort();
5. 信号终止被信号杀死kill(pid, SIGKILL);
6. 主线程退出多线程程序主线程return主线程返回
7. pthread_exit主线程调用退出函数pthread_exit(NULL);
8. 线程被取消线程被pthread_cancel最后一个线程被取消

三、进程终止后的状态管理

3.1 僵尸进程(Zombie Process)

产生原因

  • 子进程先于父进程终止
  • 父进程没有调用wait()waitpid()回收子进程状态
  • 子进程用户空间被释放,但内核PCB仍保留

识别僵尸进程

# 使用ps命令查看
ps aux | grep Z
# 或
ps -eo pid,stat,command | grep '^.*Z'

# 使用top命令查看
top
# 在Tasks行查看zombie数量

top命令显示示例

top - 14:25:00 up 1 day,  3:45,  2 users,  load average: 0.00, 0.01, 0.05
Tasks: 120 total,   1 running, 119 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.3 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1986.8 total,    245.3 free,    987.2 used,    754.3 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.    857.8 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
47317 root      20   0       0      0      0 Z   0.0   0.0   0:00.00 a.out <defunct>

危害

  • 占用内核PCB资源
  • 大量僵尸进程导致内核内存耗尽
  • 系统不稳定甚至崩溃

3.2 孤儿进程(Orphan Process)

产生原因

  • 父进程先于子进程终止
  • 子进程被init进程(PID=1)收养

特点

  • 不会对系统造成危害
  • 由新的父进程(init)负责回收
  • 无需特别处理

四、进程回收机制

4.1 wait函数 - 阻塞回收

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

功能:阻塞等待任意子进程退出并回收状态

参数

status:存储子进程退出状态,NULL表示不关心状态

返回值

  • 成功:返回 回收的子进程PID
  • 失败:返回-1

状态检查宏

if (WIFEXITED(status)) {
    // 正常结束
    printf("退出码: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
    // 信号终止
    printf("被信号杀死: %d\n", WTERMSIG(status));
}

完整示例

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

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程
        printf("子进程运行3秒\n");
        sleep(3);
        exit(42);  // 退出码42
    } else {
        // 父进程
        printf("父进程等待子进程...\n");
        int status;
        pid_t child_pid = wait(&status);
        
        if (WIFEXITED(status)) {
            printf("子进程%d正常退出,返回值: %d\n", 
                   child_pid, WEXITSTATUS(status));
        }
    }
    
    return 0;
}

4.2 waitpid函数 - 精确控制回收

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

参数详解

参数含义常用值
pid指定回收的进程>0:特定子进程
-1:任意子进程
0:同组进程
status退出状态指针wait()
options控制选项0:阻塞等待
WNOHANG:非阻塞

阻塞模式示例

// 等价于 wait(status)
waitpid(-1, status, 0);

非阻塞模式示例

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

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程运行5秒
        sleep(5);
        exit(0);
    }
    
    // 父进程非阻塞回收
    int status;
    pid_t result;
    
    do {
        result = waitpid(pid, &status, WNOHANG);
        if (result == 0) {
            printf("子进程还未退出,父进程可以做其他事...\n");
            sleep(1);
        }
    } while (result == 0);
    
    printf("子进程已回收\n");
    return 0;
}

五、exec函数族:进程替换

5.1 exec基本概念

功能:用新程序替换当前进程的代码段

特点

  • 执行成功不返回(原代码被覆盖)
  • 失败返回-1
  • 通常与fork()搭配使用

执行exec前后的内存变化

执行前:                    执行后:
+-----------------+        +-----------------+
| 原程序代码段     |        | 新程序代码段     |
| main() {        |        | (如ls的实现代码) |
|   exec("ls");   |  →    |                 |
|   ...           |        |                 |
| }               |        |                 |
+-----------------+        +-----------------+
| 数据段、堆栈等   |        | 数据段、堆栈等   |
| 保持不变        |        | 可能被新程序重置  |
+-----------------+        +-----------------+

5.2 exec函数族成员

函数名后缀含义:

  • l:参数列表(list),逐个传递
  • v:参数数组(vector),数组传递
  • p:使用PATH环境变量查找程序
  • e:自定义环境变量
函数参数查找参数传递环境变量
execl路径+文件名列表继承
execlpPATH查找列表继承
execv路径+文件名数组继承
execvpPATH查找数组继承

5.3 使用示例

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

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程:执行ls -l命令
        
        // 方法1:execl
        execl("/bin/ls", "ls", "-l", "/home", NULL);
        
        // 方法2:execv
        // char *args[] = {"ls", "-l", "/home", NULL};
        // execv("/bin/ls", args);
        
        // 方法3:execlp(使用PATH)
        // execlp("ls", "ls", "-l", "/home", NULL);
        
        // 如果exec失败才会执行到这里
        perror("exec failed");
        _exit(1);
    } else {
        // 父进程
        wait(NULL);
        printf("子进程执行完毕\n");
    }
    
    return 0;
}

调用自己的程序

// 假设当前目录有可执行程序myapp
char *args[] = {"./myapp", "arg1", "arg2", NULL};
execv("./myapp", args);

六、相关工具函数

6.1 system函数

#include <stdlib.h>

int system(const char *command);

功能:执行shell命令(内部使用fork+exec实现)

限制:不能执行需要修改父进程状态的命令

示例

system("ls -l");  // 列出目录
system("date");   // 显示日期

6.2 工作目录管理

#include <unistd.h>

// 获取当前工作目录
char *getcwd(char *buf, size_t size);
// buf: 存储路径的缓冲区
// size: 缓冲区大小
// 返回: 指向buf的指针,失败返回NULL

// 改变当前工作目录
int chdir(const char *path);
// path: 新路径
// 返回: 0成功,-1失败

示例

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

int main() {
    char cwd[1024];
    
    // 获取当前目录
    if (getcwd(cwd, sizeof(cwd)) != NULL) {
        printf("当前目录: %s\n", cwd);
    }
    
    // 改变目录
    if (chdir("/tmp") == 0) {
        printf("切换到/tmp成功\n");
        getcwd(cwd, sizeof(cwd));
        printf("新目录: %s\n", cwd);
    }
    
    return 0;
}

七、综合应用实例

7.1 安全的子进程管理框架

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

// 信号处理:避免僵尸进程
void sigchld_handler(int sig) {
    int saved_errno = errno;
    while (waitpid(-1, NULL, WNOHANG) > 0) {
        // 循环回收所有已终止的子进程
    }
    errno = saved_errno;
}

int main() {
    // 注册SIGCHLD信号处理
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
    sigaction(SIGCHLD, &sa, NULL);
    
    // 创建多个子进程
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        
        if (pid < 0) {
            perror("fork failed");
            continue;
        } else if (pid == 0) {
            // 子进程执行任务
            printf("子进程%d启动 (PID=%d)\n", i, getpid());
            sleep(i + 1);  // 模拟工作
            printf("子进程%d结束\n", i);
            exit(0);
        } else {
            printf("父进程创建了子进程%d (PID=%d)\n", i, pid);
        }
    }
    
    // 父进程继续工作
    printf("父进程继续执行其他任务...\n");
    for (int i = 0; i < 10; i++) {
        printf("父进程工作 %d/10\n", i + 1);
        sleep(1);
    }
    
    printf("父进程结束\n");
    return 0;
}

7.2 进程池模式示例

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

#define WORKER_COUNT 3

void worker_process(int id) {
    printf("工作进程%d (PID=%d) 启动\n", id, getpid());
    
    // 执行实际工作
    for (int i = 0; i < 3; i++) {
        printf("工作进程%d: 任务%d\n", id, i);
        sleep(1);
    }
    
    printf("工作进程%d 结束\n", id);
    exit(0);
}

int main() {
    printf("主进程启动 (PID=%d)\n", getpid());
    
    // 创建工作进程
    for (int i = 0; i < WORKER_COUNT; i++) {
        pid_t pid = fork();
        
        if (pid < 0) {
            perror("fork failed");
            exit(1);
        } else if (pid == 0) {
            worker_process(i);
        }
    }
    
    // 等待所有工作进程完成
    int status;
    for (int i = 0; i < WORKER_COUNT; i++) {
        pid_t child_pid = wait(&status);
        if (WIFEXITED(status)) {
            printf("工作进程%d正常结束\n", child_pid);
        }
    }
    
    printf("所有工作进程完成,主进程结束\n");
    return 0;
}

总结与最佳实践

关键要点回顾

主题核心概念重要函数
进程创建写时复制优化性能fork()
进程终止8种终止方式,区别exit和_exitexit(), _exit()
僵尸进程父进程未回收的终止子进程wait(), waitpid()
进程回收阻塞/非阻塞回收状态waitpid(pid, status, WNOHANG)
进程替换执行新程序,不返回execl(), execv()系列
工具函数系统命令、目录管理system(), getcwd(), chdir()

最佳实践建议

  • 始终检查系统调用返回值,特别是fork()exec()wait()系列
  • 及时回收子进程,避免僵尸进程积累
  • 使用非阻塞waitpid管理多个子进程,避免父进程阻塞
  • fork+exec是标准模式:先创建进程,再替换为实际要运行的程序
  • 处理SIGCHLD信号:自动回收子进程,提高程序健壮性
  • 注意exec的参数格式:最后一个参数必须是NULL
  • 区分exit和_exit:需要清理时用exit(),紧急退出用_exit()

常见问题排查

  • 僵尸进程过多:父进程没有正确调用wait()系列函数
  • 子进程没执行exec:检查exec参数是否正确,特别是路径和NULL结尾
  • 资源泄漏:确保文件描述符、内存等在子进程中正确释放
  • 竞争条件:父进程在子进程之前终止可能导致意外结果

通过掌握这些进程管理技术,您将能够编写出健壮、高效的Linux系统程序。理解进程的完整生命周期(创建→运行→终止→回收)是系统编程的基础,也是进一步学习多线程、进程间通信等高级主题的前提。

到此这篇关于Linux进程管理之创建、终止、回收与替换操作完全指南的文章就介绍到这了,更多相关Linux进程管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Linux与Windows跨平台文件共享的实现方案

    Linux与Windows跨平台文件共享的实现方案

    在智慧工厂的应用场景中,常常需要通过工控机集中读取多台设备生成的日志来获取运行数据,由于设备通常运行 Windows 系统,而工控机多采用 Linux 环境,所以本文介绍了Linux与Windows跨平台文件共享的实现方案,需要的朋友可以参考下
    2025-04-04
  • Ubuntu12.04建立内核树实现过程详解

    Ubuntu12.04建立内核树实现过程详解

    这篇文章主要介绍了Ubuntu12.04建立内核树实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Linux命令学习总结之rm命令

    Linux命令学习总结之rm命令

    rm命令用来删除Linux系统中的文件或目录。通常情况下rm不会删除目录,你必须通过指定参数-r或-R来删除目录。接下来通过本文给大家分享Linux命令学习总结之rm命令,对linux rm命令相关知识感兴趣的朋友一起学习吧
    2016-01-01
  • Linux SSHD启动失败:OpenSSL版本不匹配问题分析与解决方法

    Linux SSHD启动失败:OpenSSL版本不匹配问题分析与解决方法

    在 Linux 服务器上,sshd 可能因 OpenSSL 版本不匹配而启动失败,本篇文章将详细分析该错误的原因,并提供多种解决方案,文中通过代码示例讲解的非常详细,需要的朋友可以参考下
    2025-07-07
  • Linux端口映射转发的方法

    Linux端口映射转发的方法

    这篇文章主要介绍了Linux端口映射转发的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • 使用VMware虚拟机安装Linux(CentOS7镜像)

    使用VMware虚拟机安装Linux(CentOS7镜像)

    这篇文章介绍了使用VMware虚拟机安装Linux(CentOS7镜像)的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • apache日志配置一例

    apache日志配置一例

    apache日志配置一例,包括指定存储目录与格式、自动删除过期的日志文件,有需要的朋友可以参考下
    2013-02-02
  • 在Linux环境下采用压缩包方式安装JDK 13的方法

    在Linux环境下采用压缩包方式安装JDK 13的方法

    JDK(Java Development Kit)是Sun公司(后被Oracle收购)推出的面向对象程序设计语言的开发工具包,拥有这个工具包之后我们就可以使用Java语言进行程序设计和开发。这篇文章主要介绍了在Linux环境下采用压缩包方式安装JDK 13,需要的朋友可以参考下
    2019-10-10
  • Linux命令行上如何使用日历详解

    Linux命令行上如何使用日历详解

    这篇文章主要给大家介绍了Linux命令行上如何使用日历的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用linux具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2018-04-04
  • CentOS 5.4下的Memcache安装步骤(Linux+Nginx+PHP+Memcached)

    CentOS 5.4下的Memcache安装步骤(Linux+Nginx+PHP+Memcached)

    CentOS 5.4下的Memcache安装步骤分享,想要配置Linux+Nginx+PHP+Memcached运行环境的朋友可以参考下
    2012-02-02

最新评论