FIFO 程序間通訊
## IPC FIFO 簡介
FIFO(Named Pipe,具名管道)是一種 IPC(Inter-Process Communication)機制,讓不同的程序透過檔案系統中的一個特殊檔案交換資料。
### FIFO 的主要特性
- **具有名稱**:通常存在於檔案系統中,例如 `/tmp/myfifo`。
- **可供無親緣關係的程序使用**:不同於匿名 pipe,不要求程序之間具有父子關係。
- **單向、串流式通訊**:資料沒有訊息邊界,使用 `read()` 和 `write()` 讀寫位元組資料。
- **先進先出**:先寫入的資料會先被讀出。
- **核心提供緩衝區**:資料會暫存在核心的 FIFO buffer 中。
- **通常會阻塞**:
- `open(path, O_WRONLY)` 通常會等待讀端開啟。
- `open(path, O_RDONLY)` 通常會等待寫端開啟。
- `read()` 沒有資料時可能阻塞。
- `write()` 緩衝區滿時可能阻塞。
- **不適合隨機存取**:不能使用 `lseek()`。
- **資料不會永久保存**:FIFO 檔案本身只是一個通訊端點,資料會被讀出或在程序結束後消失。
在 POSIX 系統中,若單次寫入資料不超過 `PIPE_BUF`,通常可保證該次寫入不會與其他程序的寫入交錯。
---
## C 中使用 FIFO 的 System Call
常用的系統呼叫如下:
| 函式 | 功能 |
|---|---|
| `mkfifo()` | 建立 FIFO 特殊檔案 |
| `open()` | 開啟 FIFO,指定讀取或寫入模式 |
| `read()` | 從 FIFO 讀取資料 |
| `write()` | 寫入資料到 FIFO |
| `close()` | 關閉 FIFO |
| `unlink()` | 刪除 FIFO 檔案 |
嚴格來說,C 程式通常呼叫的是 libc 提供的函式包裝器,而這些函式會進一步執行對應的 Linux System Call。
---
## 簡單範例
以下程式可當作讀端或寫端執行。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h> // O_RDONLY, O_WRONLY
#include <sys/stat.h> // mkfifo
#include <unistd.h> // read, write, close
#include <errno.h>
#define FIFO_PATH "/tmp/example_fifo"
int main(int argc, char *argv[])
{
int fd;
if (argc != 2) {
fprintf(stderr, "用法:%s read|write\n", argv[0]);
return 1;
}
/*
* 建立 FIFO。
* 若 FIFO 已經存在,mkfifo() 會失敗,但不算錯誤。
*/
if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {
perror("mkfifo");
return 1;
}
if (strcmp(argv[1], "write") == 0) {
const char *msg = "Hello from FIFO writer!\n";
/*
* 若目前沒有讀端,這裡通常會阻塞,
* 直到另一個程序以 O_RDONLY 開啟 FIFO。
*/
fd = open(FIFO_PATH, O_WRONLY);
if (fd == -1) {
perror("open for writing");
return 1;
}
if (write(fd, msg, strlen(msg)) == -1) {
perror("write");
}
close(fd);
}
else if (strcmp(argv[1], "read") == 0) {
char buffer[128];
ssize_t n;
/*
* 若目前沒有寫端,這裡通常會阻塞,
* 直到另一個程序以 O_WRONLY 開啟 FIFO。
*/
fd = open(FIFO_PATH, O_RDONLY);
if (fd == -1) {
perror("open for reading");
return 1;
}
n = read(fd, buffer, sizeof(buffer) - 1);
if (n == -1) {
perror("read");
}
else {
buffer[n] = '\0';
printf("收到資料:%s", buffer);
}
close(fd);
/*
* 讀端結束後刪除 FIFO 名稱。
* 注意:若其他程序仍開啟 FIFO,該開啟的描述元仍可使用。
*/
unlink(FIFO_PATH);
}
else {
fprintf(stderr, "參數必須是 read 或 write\n");
return 1;
}
return 0;
}
```
### 編譯與執行
```bash
gcc fifo.c -o fifo
```
先開啟讀端:
```bash
./fifo read
```
再開啟另一個終端機執行寫端:
```bash
./fifo write
```
讀端會顯示:
```text
收到資料:Hello from FIFO writer!
```
這個例子中:
1. `mkfifo()` 建立 `/tmp/example_fifo`。
2. 讀端使用 `open(..., O_RDONLY)` 開啟。
3. 寫端使用 `open(..., O_WRONLY)` 開啟。
4. 寫端透過 `write()` 傳送資料。
5. 讀端透過 `read()` 接收資料。
6. 雙方使用 `close()` 關閉 FIFO,最後可用 `unlink()` 移除 FIFO 檔案。
相關學習地圖、教學課程
C 語言,系統程式