会吃番茄耶
26-06-11 22:28 微博认证:超话粉丝大咖(赵梦超话)

# 进程、线程,进程间通信(管道和命名管道)

## 一、进程和线程的区别

进程是操作系统进行**资源分配**的基本单位,线程是 CPU 进行**调度执行**的基本单位。

一个程序运行起来之后,操作系统会为它创建一个进程。每个进程都有自己独立的地址空间、代码段、数据段、堆、栈、文件描述符等资源。不同进程之间相互独立,一个进程崩溃通常不会直接影响其他进程。

线程是进程内部的执行流。一个进程中可以有多个线程,这些线程共享同一个进程的地址空间、全局变量、堆区、文件描述符等资源,但是每个线程有自己独立的栈和寄存器上下文。

可以简单理解为:

```text
进程:一个独立运行的程序
线程:进程里面的一个执行任务
```

例如,一个嵌入式设备中有一个主控程序 `main_app`,它作为一个进程运行。这个进程内部可以创建多个线程:

```text
main_app 进程
├── 图像采集线程
├── 图像处理线程
├── 网络通信线程
└── 日志记录线程
```

这些线程属于同一个进程,可以方便地共享图像缓存、状态变量和配置参数。

## 二、进程和线程的对比

| 对比项 | 进程 | 线程 |
| -------------- | -------------------------------------- | -------------------------------- |
| 基本概念 | 资源分配的基本单位 | CPU 调度的基本单位 |
| 地址空间 | 独立地址空间 | 共享进程地址空间 |
| 创建和切换开销 | 较大 | 较小 |
| 通信方式 | 需要 IPC,如管道、消息队列、共享内存等 | 可直接共享全局变量 |
| 稳定性 | 一个进程崩溃一般不影响其他进程 | 一个线程崩溃可能导致整个进程异常 |
| 编程难度 | 通信相对麻烦 | 需要注意互斥、同步、竞态问题 |
| 适合场景 | 模块独立、可靠性要求高 | 数据共享频繁、实时性要求高 |

## 三、嵌入式 Linux 中如何选择进程和线程

在嵌入式 Linux 中,选择进程还是线程,主要看任务之间是否需要强隔离、是否频繁共享数据、实时性要求高不高。

如果模块之间相对独立,并且希望某个模块异常时不影响其他模块,通常选择多进程。例如:

```text
主控进程
网络服务进程
日志进程
升级进程
看门狗进程
```

这些模块之间功能相对独立,使用多进程可以提高系统稳定性。比如网络服务进程崩溃后,可以单独重启,不一定影响主控进程。

如果多个任务之间联系紧密,需要频繁共享数据,或者对实时性和资源开销要求较高,通常选择多线程。例如:

```text
图像采集线程
图像处理线程
控制输出线程
串口接收线程
```

这些线程之间经常需要共享图像帧、传感器数据或控制状态,用多线程更方便,开销也更小。

总结原则:

```text
强调隔离性、稳定性、安全性:选进程
强调效率、实时性、共享数据:选线程
```

例如在嵌入式视觉系统中,可以把整个视觉程序作为一个进程,在进程内部使用多个线程完成采集、处理、显示和通信:

```text
vision_app 进程
├── camera_capture 线程
├── image_process 线程
├── result_send 线程
└── display 线程
```

但如果系统中还有独立的网络管理、日志管理、设备升级等功能,则可以拆成多个进程。

## 四、管道和命名管道的区别

管道和命名管道都是 Linux 中常用的进程间通信方式,本质上都是通过内核缓冲区传递数据。数据一般按照先进先出的顺序传输。

管道也叫匿名管道,一般通过 `pipe()` 创建,没有文件名,主要用于具有亲缘关系的进程之间通信,例如父子进程。匿名管道的生命周期较短,通常随进程结束而释放。

命名管道也叫 FIFO,一般通过 `mkfifo()` 创建,在文件系统中有一个具体路径名。因此,即使两个进程没有亲缘关系,只要它们打开同一个 FIFO 文件,也可以进行通信。命名管道会一直存在于文件系统中,直到被手动删除。

可以简单理解为:

```text
匿名管道:没有名字,适合父子进程通信
命名管道:有文件名,适合独立进程通信
```

## 五、管道和命名管道对比

| 对比项 | 匿名管道 pipe | 命名管道 FIFO |
| ------------------ | -------------------------- | ---------------------------------- |
| 是否有文件名 | 没有 | 有 |
| 创建方式 | `pipe()` | `mkfifo()` |
| 适用进程关系 | 通常用于父子进程、兄弟进程 | 可用于无亲缘关系进程 |
| 是否存在于文件系统 | 不存在 | 存在 |
| 生命周期 | 随进程结束释放 | 文件存在则 FIFO 存在 |
| 使用复杂度 | 简单、轻量 | 需要路径、权限和清理 |
| 典型场景 | shell 管道、父子进程通信 | 独立程序之间通信、守护进程接收命令 |

## 六、匿名管道例子

Linux shell 中常见的管道符 `|` 本质上就是匿名管道,例如:

```bash
ls | grep test
```

这条命令的含义是:`ls` 输出当前目录下的文件列表,然后把输出结果通过管道传给 `grep test`,由 `grep` 筛选出包含 `test` 的行。

它的大致进程关系是:

```text
shell 进程
├── ls 进程
└── grep 进程
```

`ls` 和 `grep` 一般都是 shell 创建出来的子进程,它们是兄弟进程。shell 先创建一个匿名管道,然后让 `ls` 的标准输出连接到管道写端,让 `grep` 的标准输入连接到管道读端。

数据流向如下:

```text
ls 的标准输出 ---> 匿名管道 ---> grep 的标准输入
```

所以 `ls | grep test` 可以理解为:

```text
ls 负责产生数据
管道负责传输数据
grep 负责过滤数据
```

## 七、命名管道例子

命名管道适合两个独立启动的程序之间通信。比如一个进程负责发送控制命令,另一个进程负责接收命令。

首先创建一个命名管道:

```bash
mkfifo /tmp/cmd_fifo
```

打开第一个终端,作为接收端:

```bash
cat /tmp/cmd_fifo
```

此时 `cat` 会阻塞等待数据。

再打开第二个终端,作为发送端:

```bash
echo "led_on" > /tmp/cmd_fifo
```

这时第一个终端会输出:

```text
led_on
```

数据流向如下:

```text
发送端 echo ---> /tmp/cmd_fifo ---> 接收端 cat
```

用完之后可以删除命名管道:

```bash
rm /tmp/cmd_fifo
```

## 八、嵌入式 Linux 中命名管道的使用场景

命名管道常用于无亲缘关系进程之间的简单通信。例如在嵌入式设备中,一个后台守护进程负责控制 LED、蜂鸣器、摄像头等设备,另一个前台程序负责发送控制命令。

例如:

```text
控制程序 ---> /tmp/device_cmd_fifo ---> 设备管理进程
```

控制程序发送:

```bash
echo "led_on" > /tmp/device_cmd_fifo
echo "led_off" > /tmp/device_cmd_fifo
echo "camera_start" > /tmp/device_cmd_fifo
```

设备管理进程读取这些命令后执行对应操作。

这种方式的优点是实现简单、调试方便,可以直接用 shell 命令测试;缺点是它更适合简单的单向通信,不适合复杂的结构化消息交互。

## 九、管道和命名管道怎么选

如果是父进程和子进程之间通信,或者像 `ls | grep test` 这种 shell 命令之间的数据传递,优先使用匿名管道。

如果是两个独立运行的程序之间通信,比如主控程序和后台守护进程、采集程序和处理程序之间通信,可以使用命名管道。

选择原则:

```text
父子进程、兄弟进程之间简单通信:用匿名管道
无亲缘关系的独立进程通信:用命名管道
简单命令或状态通知:命名管道很合适
复杂消息、多类型数据:可以考虑消息队列
大数据传输:可以考虑共享内存
```

## 总结

进程是资源分配的基本单位,线程是 CPU 调度的基本单位。进程拥有独立地址空间,稳定性好,但创建、切换和通信开销较大;线程共享进程资源,创建和切换开销小,通信方便,但需要注意同步互斥问题,一个线程异常可能影响整个进程。在嵌入式 Linux 中,如果模块独立、强调稳定性和隔离性,通常选择多进程;如果任务之间共享数据频繁、强调实时性和效率,通常选择多线程。

管道和命名管道都是进程间通信方式。匿名管道没有文件名,一般通过 `pipe()` 创建,常用于父子进程或兄弟进程之间通信,例如 `ls | grep test` 中,`ls` 和 `grep` 都是 shell 创建的子进程,它们通过匿名管道通信。命名管道也叫 FIFO,通过 `mkfifo()` 创建,在文件系统中有路径名,因此可以用于无亲缘关系的进程之间通信。例如使用 `mkfifo /tmp/cmd_fifo` 创建 FIFO,一个终端执行 `cat /tmp/cmd_fifo` 等待数据,另一个终端执行 `echo "led_on" > /tmp/cmd_fifo` 发送命令。总结来说,父子进程通信常用匿名管道,独立程序之间简单通信常用命名管道。

发布于 日本