Linux软件编程学习笔记(四)——进程

一、什么是程序

在学习进程之前,首先要区分:

复制代码
程序(Program)
进程(Process)

1. 程序

程序可以理解为:

存放在外存中的一段代码和相关数据的集合。

例如我们编写:

复制代码
#include <stdio.h>

int main(void)
{
    printf("Hello Linux\n");

    return 0;
}

编译:

复制代码
gcc hello.c -o hello

生成:

复制代码
hello

此时 hello 是一个程序

它存放在磁盘上,本身是静态的。


二、什么是进程

进程可以理解为:

程序的一次动态执行过程。

例如:

复制代码
./hello

执行以后:

复制代码
磁盘上的 hello
       ↓
    加载到内存
       ↓
    创建进程
       ↓
      CPU
       ↓
    执行代码

课程资料中指出:

程序是存放在外存中的一段代码集合;进程是程序动态执行的过程,包括创建、调度和消亡。


三、程序和进程的区别

可以这样记:

程序 进程
静态的 动态的
存放在磁盘 运行在内存
是代码和数据的集合 是程序的一次运行
一个程序可以对应多个进程 每个进程有自己的进程信息

例如:

复制代码
./a.out
./a.out
./a.out

虽然运行的是同一个:

复制代码
a.out

但实际上可以产生三个不同的进程:

复制代码
a.out ──→ PID 1001
a.out ──→ PID 1002
a.out ──→ PID 1003

四、Linux 中查看进程

Linux 提供了很多查看进程的命令。

1. ps

复制代码
ps

查看当前终端中的进程。


2. ps -ef

复制代码
ps -ef

查看系统中的进程信息。

常见输出:

复制代码
UID    PID    PPID    C    STIME    TTY    TIME    CMD

其中:

复制代码
PID
 ↓
进程ID

PPID
 ↓
父进程ID

课程资料中也将 ps -efpstreeps -aux 列为常用进程查看命令。


五、PID ------ 进程ID

PID:

复制代码
Process ID

即:

进程的唯一标识符。

例如:

复制代码
PID
1001
1002
1003

每一个运行中的进程都有自己的 PID。

可以使用:

复制代码
ps -ef

查看。

课程资料中也明确指出,PID 用于区分进程。


六、PPID ------ 父进程ID

PPID:

复制代码
Parent Process ID

表示:

当前进程的父进程 PID。

例如:

复制代码
父进程
PID = 1000
   │
   ├── 子进程 PID = 1001
   │
   └── 子进程 PID = 1002

那么:

复制代码
1001 的 PPID = 1000
1002 的 PPID = 1000

所以:

复制代码
PID
 ↓
我是谁

PPID
 ↓
谁创建了我

七、进程的父子关系

Linux 中进程可以形成一种树形结构:

复制代码
init/systemd
      │
      ├── 进程A
      │     │
      │     ├── 进程C
      │     └── 进程D
      │
      └── 进程B

可以使用:

复制代码
pstree

查看。

例如:

复制代码
systemd
 ├─ sshd
 │   └─ bash
 │       └─ a.out
 └─ ...

因此 Linux 中的进程之间具有明显的父子关系


八、进程空间

进程运行时需要自己的虚拟地址空间。

课程资料按照 32 位系统的典型情况,将进程空间划分为:

复制代码
0 ~ 4G

并进一步划分为文本段、数据段以及系统数据区域。

可以简单理解为:

复制代码
高地址
┌──────────────────┐
│      栈 Stack     │
├──────────────────┤
│                  │
│       ↓          │
│                  │
│      空闲空间     │
│                  │
│       ↑          │
│                  │
├──────────────────┤
│      堆 Heap      │
├──────────────────┤
│   BSS 数据区      │
├──────────────────┤
│   Data 数据区     │
├──────────────────┤
│   Text 代码区     │
└──────────────────┘
低地址

九、文本段 Text

文本段主要存放:

复制代码
程序执行指令

例如:

复制代码
printf("Hello");

编译以后会转换成机器指令,这些指令在进程运行时位于代码区域。


十、数据段 Data

数据区域主要存放程序中的数据。

例如:

复制代码
int a = 10;
static int b = 20;

以及字符串常量等。

课程资料中将字符串常量、初始化/未初始化的全局变量和静态变量等列入数据相关区域。


十一、堆区 Heap

堆区主要用于:

复制代码
malloc()
calloc()
realloc()

动态申请的内存。

例如:

复制代码
int *p = malloc(sizeof(int) * 10);

申请的空间通常来自堆区。


十二、栈区 Stack

栈区主要用于:

复制代码
局部变量
函数参数
函数调用信息

例如:

复制代码
void fun(void)
{
    int a = 10;
}

其中:

复制代码
a

是局部变量,通常位于栈区。


十三、进程的特点

进程具有一个非常重要的特点:

进程之间的地址空间相互独立。

例如:

复制代码
进程A
┌─────────────┐
│   0~4G      │
└─────────────┘

进程B
┌─────────────┐
│   0~4G      │
└─────────────┘

虽然每个进程看到的虚拟地址空间可以都是:

复制代码
0 ~ 4G

但是它们实际上对应不同的物理内存映射。

课程资料也强调了进程空间独立以及分时复用的特点。


十四、进程调度

计算机中可能同时存在很多进程:

复制代码
进程A
进程B
进程C
进程D

但 CPU 在某一个时间点实际上只能执行有限数量的任务。

所以操作系统需要:

进程调度。


十五、时间片

CPU 分配给某个进程的一小段执行时间叫:

时间片(Time Slice)

例如:

复制代码
时间 →
────────────────────────>

A   B   C   A   B   C
│   │   │   │   │   │

从宏观上看:

复制代码
A、B、C

似乎同时运行。

实际上 CPU 在不断进行任务切换。

课程资料将时间片定义为 CPU 在进程任务中执行的一小段时间。


十六、常见进程调度算法

课程资料中列出的常见调度算法包括:

复制代码
1. 先来先执行
2. 高优先级调度
3. 时间片轮转
4. 多级队列反馈
5. 负载均衡

其中学习阶段尤其需要理解:

复制代码
时间片轮转

可以简单理解为:

复制代码
A → B → C → A → B → C

十七、进程状态

进程并不是一直处于运行状态。

Linux 中常见的进程状态包括:

复制代码
R
S
D
T
Z
X

课程资料也列出了这些状态。


1. R ------ Running / Runnable

复制代码
R

表示:

运行或可运行状态。

也就是说进程正在运行,或者已经准备好运行、等待 CPU 调度。


2. S ------ Interruptible Sleep

复制代码
S

表示:

可中断睡眠/等待状态。

例如:

复制代码
sleep(10);

进程暂时等待。

收到合适的事件或信号后,可以继续运行。


3. D ------ Uninterruptible Sleep

复制代码
D

表示:

不可中断睡眠状态。

通常与某些内核 I/O 等等待有关。


4. T ------ Stopped

复制代码
T

表示:

进程被暂停。

例如:

复制代码
kill -STOP PID

可以让进程进入停止状态。


5. Z ------ Zombie

复制代码
Z

表示:

僵尸进程。

这是本节非常重要的内容,后面会重点讲。


6. X ------ Dead

表示进程已经结束,相关资源正在被系统回收。


十八、fork() ------ 创建进程

Linux 创建子进程最重要的函数:

复制代码
fork()

函数原型:

复制代码
#include <unistd.h>

pid_t fork(void);

作用:

创建一个新的子进程。

课程资料中明确给出了 fork() 的功能和返回值。


十九、fork() 最重要的特点

fork() 调用一次:

返回两次。

这是进程学习中最重要的一句话之一。

例如:

复制代码
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    printf("before fork\n");

    fork();

    printf("after fork\n");

    return 0;
}

可能输出:

复制代码
before fork
after fork
after fork

为什么?

因为:

复制代码
fork()之前
     │
     │
     ▼
   一个进程
     │
     │ fork()
     ▼
┌───────────┐
│           │
父进程      子进程
│           │
└───────────┘
     │
两边都会继续执行后面的代码

二十、fork() 的返回值

这是考试和写代码时必须记住的:

复制代码
fork() < 0
    ↓
创建失败

fork() == 0
    ↓
当前是子进程

fork() > 0
    ↓
当前是父进程

在父进程中:

复制代码
fork()

返回:

子进程 PID。

在子进程中返回:

复制代码
0

课程资料明确给出了这一返回规则。


二十一、利用 fork() 区分父子进程

最经典的代码:

复制代码
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    pid_t pid = 0;

    pid = fork();

    if(pid < 0)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("我是子进程\n");
    }
    else
    {
        printf("我是父进程\n");
    }

    return 0;
}

判断依据:

复制代码
if(pid == 0)

就是:

当前代码正在子进程中执行。

而:

复制代码
else

就是:

当前代码正在父进程中执行。


二十二、getpid() ------ 获取自己的 PID

函数:

复制代码
pid_t getpid(void);

功能:

获取当前进程自己的 PID。

例如:

复制代码
printf("PID = %d\n", getpid());

课程资料中也明确给出了 getpid() 的功能。


二十三、getppid() ------ 获取父进程 PID

函数:

复制代码
pid_t getppid(void);

功能:

获取当前进程的父进程 PID。

例如:

复制代码
printf("PID  = %d\n", getpid());
printf("PPID = %d\n", getppid());

可以得到:

复制代码
PID  = 1234
PPID = 1200

说明:

复制代码
当前进程 PID = 1234
父进程 PID   = 1200

二十四、fork() 综合练习

创建一个子进程,并分别打印:

复制代码
父进程 PID
父进程 PPID

子进程 PID
子进程 PPID

代码:

复制代码
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    pid_t pid = 0;

    pid = fork();

    if(pid < 0)
    {
        perror("fork");
        return -1;
    }

    if(pid == 0)
    {
        printf("子进程:PID = %d, PPID = %d\n",
               getpid(), getppid());
    }
    else
    {
        printf("父进程:PID = %d, PPID = %d\n",
               getpid(), getppid());
    }

    return 0;
}

二十五、fork() 后变量是否共享?

例如:

复制代码
int num = 10;

fork();

num++;

很多初学者会误以为:

复制代码
父进程 num = 11
子进程 num = 12

或者认为两个进程共同操作一个 num

实际上:

fork() 后父子进程拥有各自独立的地址空间。

因此父子进程修改普通变量不会直接影响对方。

例如:

复制代码
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    int num = 10;
    pid_t pid = 0;

    pid = fork();

    if(pid == 0)
    {
        num++;
        printf("child num = %d\n", num);
    }
    else
    {
        num++;
        printf("parent num = %d\n", num);
    }

    return 0;
}

两边通常都会输出:

复制代码
11

而不是一个变成 12


二十六、fork() 后代码执行位置

这是理解 fork() 的关键:

复制代码
printf("A\n");

fork();

printf("B\n");

执行流程:

复制代码
             printf A
                │
                ▼
              fork
             /    \
            /      \
       父进程      子进程
          │           │
       printf B    printf B

因此:

复制代码
A
B
B

二十七、创建两个子进程

如果:

复制代码
fork();
fork();

那么需要特别注意进程数量。

第一次:

复制代码
       P
       │
     fork
    /    \
   P      C1

第二次 fork() 会被两个进程分别执行:

复制代码
       P
      / \
     /   \
   P1     C1
   │       │
   C2      C3

最终:

复制代码
4个进程

因此:

复制代码
fork() 一次 → 2个
fork() 两次 → 4个
fork() 三次 → 8个

理论上:

复制代码
进程数 = 2^n

但实际还要考虑条件判断等因素。


二十八、exit() ------ 进程退出

函数:

复制代码
#include <stdlib.h>

void exit(int status);

作用:

结束当前进程。

例如:

复制代码
exit(0);

表示正常结束。

课程资料中也将 exit() 定义为进程结束函数。


二十九、return 和 exit 的区别

main() 中:

复制代码
return 0;

和:

复制代码
exit(0);

都可以结束程序。

例如:

复制代码
int main(void)
{
    return 0;
}

相当于正常结束进程。

但是在普通函数中:

复制代码
void fun(void)
{
    return;
}

只表示:

返回调用者。

而:

复制代码
void fun(void)
{
    exit(0);
}

表示:

直接结束整个进程。

课程资料也特别区分了 returnexit:主函数中的 returnexit 效果相近,而普通函数中的 return 只是返回调用者,exit 则结束整个进程。


三十、进程为什么会产生僵尸进程?

假设:

复制代码
父进程
   │
   └── 子进程

子进程先结束:

复制代码
父进程
   │
   └── 子进程结束

但父进程没有回收子进程的退出信息。

这时子进程可能进入:

复制代码
Z

即:

僵尸进程(Zombie Process)

课程资料中指出,进程代码执行结束后,如果相关退出信息/资源没有被父进程及时回收,就可能形成僵尸进程。


三十一、为什么会有僵尸进程?

子进程结束以后:

复制代码
代码执行完毕
      ↓
子进程退出
      ↓
父进程还没有回收
      ↓
保留退出状态等信息
      ↓
僵尸进程

可以理解为:

人已经死了,但是"死亡证明"还留在系统中等待父进程领取。


三十二、如何避免僵尸进程?

主要有两种思路:

方法一:父进程调用 wait()

复制代码
wait(NULL);

让父进程主动回收子进程。

方法二:父进程先结束

父进程结束后,子进程成为:

孤儿进程

由系统中的特殊进程接管并最终负责回收。

课程资料中也给出了这两种思路。


三十三、wait() ------ 回收子进程

函数:

复制代码
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);

功能:

等待并回收子进程。

课程资料中明确说明 wait() 用于回收子进程空间,并且具有阻塞功能,可以实现多任务同步。


三十四、wait() 的阻塞

例如:

复制代码
pid = fork();

if(pid == 0)
{
    sleep(5);
    printf("child\n");
}
else
{
    wait(NULL);
    printf("parent\n");
}

执行过程:

复制代码
父进程
  │
  ├── wait()
  │     ↓
  │   阻塞等待
  │
  │
子进程
  │
  ├── sleep(5)
  │
  ├── 结束
  │
  └────────────→
                │
                ↓
             父进程继续
                │
                ↓
             printf

因此:

wait() 不仅能够回收子进程,还可以实现父子进程之间的同步。


三十五、wait() 的返回值

成功:

复制代码
子进程 PID

失败:

复制代码
-1

课程资料也明确列出了这一返回值规则。


三十六、获取子进程退出状态

例如:

复制代码
int status = 0;

wait(&status);

可以通过宏判断:

复制代码
WIFEXITED(status)

判断:

子进程是否正常退出。

然后:

复制代码
WEXITSTATUS(status)

获取:

子进程通过 exit() 返回的退出值。

课程资料中还列出了:

复制代码
WIFSIGNALED()
WTERMSIG()

用于判断子进程是否被信号终止以及获取终止信号编号。


三十七、完整的 wait() 示例

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid = 0;
    int status = 0;

    pid = fork();

    if(pid < 0)
    {
        perror("fork");
        return -1;
    }

    if(pid == 0)
    {
        printf("子进程运行\n");

        exit(10);
    }
    else
    {
        wait(&status);

        if(WIFEXITED(status))
        {
            printf("子进程正常退出\n");
            printf("exit code = %d\n",
                   WEXITSTATUS(status));
        }
    }

    return 0;
}

输出可能:

复制代码
子进程运行
子进程正常退出
exit code = 10

三十八、waitpid() ------ 回收指定子进程

如果一个父进程有多个子进程:

复制代码
父进程
 ├── 子进程A
 ├── 子进程B
 └── 子进程C

那么:

复制代码
wait(NULL);

只能等待某个已经结束的子进程。

如果希望:

指定回收某一个子进程

可以使用:

复制代码
waitpid()

函数:

复制代码
pid_t waitpid(pid_t pid, int *wstatus, int options);

课程资料中明确说明 waitpid() 可以回收指定 PID 的子进程,并支持阻塞和非阻塞方式。


三十九、waitpid() 的参数

复制代码
waitpid(pid, &status, 0);

其中:

复制代码
pid
 ↓
要等待的子进程 PID

&status
 ↓
保存退出状态

0
 ↓
阻塞等待

例如:

复制代码
pid_t child_pid;

child_pid = fork();

if(child_pid == 0)
{
    sleep(3);
    exit(20);
}
else
{
    waitpid(child_pid, NULL, 0);
    printf("子进程已经结束\n");
}

四十、WNOHANG ------ 非阻塞等待

waitpid() 的第三个参数可以设置:

复制代码
WNOHANG

例如:

复制代码
waitpid(pid, &status, WNOHANG);

含义:

非阻塞等待。

如果子进程还没有结束:

复制代码
返回 0

如果子进程已经结束:

复制代码
返回子进程 PID

课程资料也明确说明了这一点。


四十一、wait 和 waitpid 对比

函数 作用
wait() 等待任意一个子进程
waitpid() 可以指定子进程
wait() 阻塞
waitpid(..., 0) 阻塞
waitpid(..., WNOHANG) 非阻塞

记忆:

复制代码
wait
 ↓
随便等一个

waitpid
 ↓
等指定 PID

四十二、孤儿进程

如果:

复制代码
父进程先结束

而:

复制代码
子进程还没有结束

此时子进程就成为:

孤儿进程(Orphan Process)

例如:

复制代码
父进程
   │
   └── 子进程
          │
          │ 父进程退出
          ↓
       孤儿进程

之后会被系统中的特殊父进程接管。

注意:

孤儿进程本身不是错误。

而僵尸进程通常是需要避免长期存在的。


四十三、僵尸进程和孤儿进程对比

僵尸进程 孤儿进程
子进程已经结束 子进程仍然运行
父进程没有及时回收 父进程提前结束
状态通常为 Z 可以正常运行
需要及时回收 会被其他父进程接管

最容易记忆:

复制代码
子死父不收
    ↓
僵尸

父死子还活
    ↓
孤儿

四十四、exec 函数族

进程创建之后,还有一个非常重要的问题:

如何让子进程执行另外一个程序?

这时就需要:

复制代码
exec 函数族

常见函数:

复制代码
execl()
execlp()
execle()

execv()
execvp()
execvpe()

课程资料中完整列出了这些函数。


四十五、exec 的作用

例如当前程序是:

复制代码
parent

父进程创建子进程:

复制代码
parent
   │
  fork
   │
   └── child

然后子进程调用:

复制代码
execl(...)

就可以让这个子进程的进程空间加载并执行另外一个程序。

可以理解为:

复制代码
fork()
   ↓
创建子进程
   ↓
exec()
   ↓
子进程执行另一个程序

四十六、exec 不会创建新进程

这是非常容易考的一个知识点。

复制代码
fork()

负责:

创建进程。

而:

复制代码
exec()

负责:

让当前进程执行另一份程序代码。

所以:

复制代码
fork → 新进程
exec → 替换当前进程执行内容

课程资料中也将 exec 的功能描述为利用进程空间执行另一段代码。


四十七、exec 函数族命名规律

这个非常适合总结成表格:

字母 含义
l list,参数以列表形式传递
v vector,参数以指针数组形式传递
p PATH 中查找程序
e 可以指定环境变量

课程资料也明确给出了 l/v/p/e 的含义。


四十八、execl()

例如执行:

复制代码
ls -l

可以:

复制代码
execl("/bin/ls",
      "ls",
      "-l",
      NULL);

这里:

复制代码
/bin/ls
   ↓
程序路径

"ls"
   ↓
argv[0]

"-l"
   ↓
argv[1]

NULL
   ↓
参数结束

四十九、execlp()

如果使用:

复制代码
execlp("ls",
       "ls",
       "-l",
       NULL);

就不需要写:

复制代码
/bin/ls

因为:

复制代码
p
↓
PATH

会根据环境变量 PATH 搜索程序。


五十、execv()

execv() 的参数使用:

复制代码
指针数组

例如:

复制代码
char *argv[] =
{
    "ls",
    "-l",
    NULL
};

execv("/bin/ls", argv);

这里:

复制代码
argv[0] = "ls"
argv[1] = "-l"
argv[2] = NULL

这就是你前面练习中经常出现的:

复制代码
char *tmpbuff[]

这种形式。


五十一、execv 和 execl 的区别

这是学习 exec 时非常重要的区别。

execl

参数一个一个写:

复制代码
execl("/bin/ls",
      "ls",
      "-l",
      "-a",
      NULL);

execv

使用数组:

复制代码
char *argv[] =
{
    "ls",
    "-l",
    "-a",
    NULL
};

execv("/bin/ls", argv);

所以:

复制代码
l → list
v → vector

五十二、fork + exec

Linux 编程中最经典的组合:

复制代码
fork()
 +
exec()

例如:

复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
    pid_t pid = 0;

    pid = fork();

    if(pid < 0)
    {
        perror("fork");
        return -1;
    }

    if(pid == 0)
    {
        execlp("ls", "ls", "-l", NULL);

        perror("execlp");
        exit(1);
    }

    printf("我是父进程\n");

    return 0;
}

执行过程:

复制代码
              父进程
                │
              fork
             /    \
            /      \
       父进程       子进程
                    │
                  exec
                    │
                    ↓
                  ls -l

五十三、为什么经常使用 fork + exec?

因为:

复制代码
fork()

负责:

创建一个新的执行实体。

而:

复制代码
exec()

负责:

让新的子进程执行指定程序。

因此 Shell 执行命令时,本质上也涉及类似的机制:

复制代码
Shell
 │
 ├── fork()
 │
 └── 子进程
       │
       └── exec()
             │
             └── 执行 ls

这也是为什么前面的 Shell 学习和现在的进程学习能够连接起来。


五十四、exec 成功后为什么不执行后面的代码?

例如:

复制代码
printf("A\n");

execl("/bin/ls", "ls", NULL);

printf("B\n");

如果 execl() 成功:

复制代码
A
ls输出

通常不会看到:

复制代码
B

因为成功执行 exec 后:

当前进程已经开始执行新的程序映像。

而如果:

复制代码
execl(...) == -1

说明执行失败:

复制代码
继续执行后面的代码

所以经常写:

复制代码
execl(...);

perror("execl");
exit(1);

五十五、环境变量

进程运行过程中还会使用:

复制代码
环境变量

例如:

复制代码
echo $PATH

可能得到:

复制代码
/usr/local/bin:/usr/bin:/bin

环境变量可以理解为:

进程运行过程中保存的一些环境信息。

课程资料也将环境变量定义为进程执行代码过程中临时存放的信息变量。


五十六、PATH 环境变量

例如:

复制代码
ls

我们没有写:

复制代码
/bin/ls

为什么系统知道在哪里?

因为:

复制代码
PATH

例如:

复制代码
/usr/local/bin
/usr/bin
/bin

Shell 会在这些目录中查找可执行文件。

因此:

复制代码
execlp("ls", "ls", NULL);

中的:

复制代码
p

就和 PATH 有关系。


五十七、查看环境变量

Shell 中:

复制代码
env

可以查看环境变量。

例如:

复制代码
echo $PATH
echo $HOME
echo $PWD

C 语言中也可以通过环境变量相关接口进行操作。

例如:

复制代码
extern char **environ;

课程资料中也给出了:

复制代码
extern char **environ;

这一环境变量接口。


五十八、进程常用命令总结

这一节需要掌握的 Linux 命令:

复制代码
ps
ps -ef
ps -aux
pstree
top
kill
killall
jobs
fg

以及后台执行:

复制代码
./a.out &

课程资料中也列出了后台执行、查看后台任务、前后台切换以及进程优先级相关命令。


五十九、kill ------ 发送信号

例如:

复制代码
kill PID

可以向指定进程发送信号。

强制终止:

复制代码
kill -9 PID

也可以:

复制代码
killall -9 进程名

课程资料中也给出了:

复制代码
kill -9 进程PID
killall -9 进程名

等操作。


六十、后台运行进程

例如:

复制代码
./a.out &

最后的:

复制代码
&

表示:

后台运行。

查看后台任务:

复制代码
jobs

将后台任务放到前台:

复制代码
fg 编号

六十一、进程完整生命周期

现在可以把进程的整个生命周期串起来:

复制代码
                 程序
                  │
                  ↓
               创建进程
                  │
               fork()
                  │
                  ↓
              就绪/运行
                  │
                  ↓
              CPU调度
                  │
         ┌────────┴────────┐
         │                 │
       等待资源           继续运行
         │                 │
         └────────┬────────┘
                  ↓
                exit()
                  │
                  ↓
             进程结束
                  │
             父进程wait
                  │
                  ↓
              资源回收
                  │
                  ↓
                消亡

如果父进程没有及时回收:

复制代码
exit()
  ↓
Z
  ↓
僵尸进程

总结

知识点 核心内容
程序 存储在磁盘上的代码和数据
进程 程序的一次动态执行
PID 当前进程的 ID
PPID 父进程 ID
fork() 创建子进程
getpid() 获取自己的 PID
getppid() 获取父进程 PID
exit() 结束进程
wait() 等待并回收子进程
waitpid() 等待并回收指定子进程
exec() 执行另一份程序代码
僵尸进程 子进程结束但父进程尚未回收
孤儿进程 父进程结束但子进程仍在运行
ps 查看进程
pstree 查看进程关系
kill 向进程发送信号
相关推荐
扛枪的书生24 分钟前
Linux 三剑客 - Awk 命令指南
linux·运维
MaximusCoder1 小时前
等保测评命令——weblogic
运维·安全·安全威胁分析
xixingzhe21 小时前
nginx无网络升级
服务器·nginx
喜欢吃燃面1 小时前
HTTP协议深度解析:从请求响应到状态管理与安全传输
linux·网络协议·学习
Tanner_SL1 小时前
Linux笔记之正则表达式和文本处理grep,sed,awk命令
linux·正则表达式
学linux的QQ蛋1 小时前
Linux 文件 IO vs 标准 IO:缓冲区、目录操作、常用函数汇总
linux·运维·服务器
比兔代理2 小时前
静态住宅IP在品牌保护中的角色:稳定出口与长期监测
服务器·网络·ip
苍狗T2 小时前
k8s 控制器管理
linux·运维·云原生·容器·kubernetes
Linux运维技术栈2 小时前
筑牢业务信息安全防线:雷池WAF+后端日志溯源的全链路防护体系落地实践
linux·安全·雷池