PIPE 程序間通訊
## IPC PIPE 簡介
`PIPE`(管線)是一種常見的 IPC(Inter-Process Communication,行程間通訊)機制,主要用來讓具有親屬關係的行程,例如父行程與子行程,交換資料。
### PIPE 的主要特性
1. **單向通訊**
- 一個 Pipe 通常是單向的:
- `fd[0]`:讀取端
- `fd[1]`:寫入端
- 若要雙向通訊,通常需要建立兩個 Pipe。
2. **以位元組串流傳輸**
- Pipe 不保留訊息邊界,資料會以連續的 byte stream 形式傳送。
3. **通常用於相關行程**
- 匿名 Pipe 通常在 `fork()` 之前建立,讓父子行程繼承檔案描述元。
- 若是不相關的行程,則可使用 FIFO(named pipe)。
4. **核心管理的緩衝區**
- Pipe 的資料暫存在核心空間中。
- 寫入端沒有讀取者時,寫入可能失敗或收到 `SIGPIPE`。
- 當 Pipe 沒有資料時,`read()` 預設會阻塞等待。
- 當所有寫入端都關閉後,讀取端的 `read()` 會回傳 `0`,表示 EOF。
5. **具有阻塞特性**
- `write()` 可能在 Pipe 緩衝區滿時阻塞。
- `read()` 可能在 Pipe 沒資料時阻塞。
- 可使用 `fcntl()` 設定為 non-blocking 模式。
6. **寫入的原子性**
- 對於不超過 `PIPE_BUF` 的單次寫入,系統通常保證不會與其他寫入交錯。
---
## C 如何透過 System Call 使用 PIPE
建立 Pipe 的核心系統呼叫為:
```c
int pipe(int pipefd[2]);
```
成功後:
```c
pipefd[0] // 讀取端
pipefd[1] // 寫入端
```
常見流程如下:
1. 呼叫 `pipe()` 建立 Pipe。
2. 呼叫 `fork()` 建立子行程。
3. 父子行程分別關閉不使用的檔案描述元。
4. 使用:
- `write()` 寫入資料
- `read()` 讀取資料
- `close()` 關閉 Pipe
5. 父行程可使用 `wait()` 等待子行程結束。
雖然 C 程式通常呼叫的是 `pipe()`、`read()`、`write()` 等函式,但這些函式一般是對 Linux System Call 的封裝。
---
## 簡單範例:父行程透過 Pipe 傳資料給子行程
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main(void)
{
int pipefd[2];
pid_t pid;
char buffer[128];
/*
* 建立 Pipe
* pipefd[0]:讀取端
* pipefd[1]:寫入端
*/
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
/* 子行程:負責讀取 */
close(pipefd[1]); // 子行程不需要寫入端
ssize_t n = read(pipefd[0], buffer, sizeof(buffer) - 1);
if (n == -1) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[n] = '\0';
printf("子行程收到資料:%s\n", buffer);
close(pipefd[0]);
exit(EXIT_SUCCESS);
} else {
/* 父行程:負責寫入 */
const char *message = "Hello from parent process!";
close(pipefd[0]); // 父行程不需要讀取端
if (write(pipefd[1], message, strlen(message)) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
close(pipefd[1]); // 關閉後,子行程之後可收到 EOF
wait(NULL); // 等待子行程結束
}
return 0;
}
```
### 編譯與執行
```bash
gcc pipe_example.c -o pipe_example
./pipe_example
```
可能輸出:
```text
子行程收到資料:Hello from parent process!
```
### 範例流程說明
```text
父行程 子行程
| |
| pipe() |
| fork() -------------------->|
| |
| write(pipefd[1], ...) |
|----------------------------->|
| read(pipefd[0], ...)
```
這個範例中,父行程透過 Pipe 的寫入端傳送字串,子行程則透過讀取端接收資料。關閉未使用的 Pipe 端非常重要,否則可能造成資源浪費,或使讀取端無法正確判斷 EOF。
相關學習地圖、教學課程
C 語言,系統程式