一、进程基础概念

1. 程序与进程区别

  • 程序:存放在磁盘(外存)上的可执行文件,属于静态文件。
  • 进程:程序的一次动态执行实例,操作系统把程序加载到内存运行,拥有创建、调度、消亡完整生命周期。

同一个程序可以多次运行,生成多个相互独立的进程。

2. 进程常用 shell 命令

表格

命令 作用
ps -ef 查看全量进程信息,展示 PID、PPID
ps -aux 查看进程 CPU、内存占用情况
pstree 查看进程树,观察父子进程关系
top 实时查看进程资源占用
kill -9 pid 强制杀死指定 PID 进程
killall -9 程序名 杀死该程序产生的全部进程
./a.out & 将程序放到后台运行
jobs 查看当前终端的后台任务
fg 编号 把后台任务切换到前台执行
  • PID:进程 ID,操作系统分配,每个进程唯一标识。
  • PPID:父进程 ID,代表创建当前进程的父进程 PID。

3. 进程内存布局

进程被操作系统加载到内存,内存分为 5 大区域:

  1. 文本段 (.text):存放程序二进制代码、指令,只读。
  2. 数据段 (.data):已经初始化的全局变量、static 静态变量。
  3. bss 段:未初始化全局 / 静态变量,程序启动自动置 0。
  4. 栈 (stack):局部变量、函数形参、函数调用栈帧;函数退出自动回收;空间小,由系统自动管理。
  5. 堆 (heap)malloc / calloc / realloc手动申请,必须free手动释放;空间大,使用不当容易产生内存泄漏。

重点记忆:局部变量存放在栈;全局 /static 变量存放在数据段 /bss;malloc 申请的内存全部在堆区。

4. 获取自身 PID、PPID 系统调用

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

pid_t getpid(void);   // 获取自己进程PID
pid_t getppid(void);  // 获取父进程PID

二、fork () 创建子进程(重中之重)

函数原型

#include <unistd.h>
pid_t fork(void);
返回值规则(fork 最核心:一次调用,两次返回)
  1. -1:fork 失败,子进程创建失败。
  2. 0:当前代码运行在子进程中。
  3. >0:当前代码运行在父进程中,返回值就是新创建子进程的 PID。

💡核心原理:fork 之后子进程复制父进程的代码、数据、堆、栈、文件描述符;采用写时复制机制,只有内存发生修改的时候,才会真正拷贝物理内存,提升效率。

示例代码:1 父 1 子

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>

int main(void)
{
    pid_t pid;
    pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(0 == pid)
    {
        //子进程执行分支
        printf("子进程: pid=%d, ppid=%d\n",getpid(),getppid());
    }
    else
    {
        //父进程执行分支
        printf("父进程: pid=%d, 子pid=%d\n",getpid(),pid);
    }
    return 0;
}

经典坑点:连续两次 fork

pid1 = fork();
pid2 = fork();

执行两次 fork,一共产生 2^2 = 4 个进程! 父进程创建子 1;父、子 1 都会继续执行第二个 fork,分别再生子 2、子 3。 如果只想实现1 个父进程创建 2 个子进程:子进程内部必须exit(0)或者return 0,阻止子进程继续向下执行第二个 fork。

子进程退出方式

  1. exit(0):任意函数内结束进程;0 代表正常退出,非 0 代表异常退出。
  2. _exit():纯系统调用,直接退出,不会刷新 IO 缓冲区。
  3. return 0;只在 main 函数中等价于 exit (0);普通函数里面 return 只是函数返回,不会结束进程

❗注意:break只能跳出循环 /switch,不能结束进程,不能用来代替 exit

三、进程回收 wait /waitpid

  • 僵尸进程 (Zombie):子进程先退出,父进程没有调用接口回收子进程资源,子进程 PCB 控制块残留在内核。
  • 孤儿进程:父进程先退出,子进程失去父进程,会被系统init/systemd进程收养。

1.wait()

#include <sys/wait.h>
pid_t wait(int *wstatus);
  • 阻塞等待任意一个子进程结束;获取子进程退出状态,释放子进程 PCB 资源,消除僵尸进程。
  • 返回值:成功返回退出子进程 PID;失败返回‑1。
  • 参数 wstatus:传出参数,保存子进程退出状态,可以配合宏解析:
    • WIFEXITED(status):子进程正常退出则为真
    • WEXITSTATUS(status):拿到子进程 exit 传递的返回值

2.waitpid ()(实际开发优先使用,可以设置非阻塞)

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

pid 参数:

  • pid>0:等待指定 PID 的子进程
  • pid=-1:等待任意子进程,效果等价 wait

options 选项:

  • 0:阻塞模式
  • WNOHANG:非阻塞,没有子进程结束立刻返回 0,不会卡住父进程。

项目中优先使用 waitpid + WNOHANG,避免父进程被无限阻塞。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐