共享記憶體通訊
## IPC Shared Memory 簡介
Shared Memory(共享記憶體)是一種 IPC(Inter-Process Communication,行程間通訊)機制,讓多個行程將同一段實體記憶體映射到自己的虛擬位址空間,直接讀寫其中資料。
### 主要特性
- **速度快**:資料直接位於共享記憶體中,不需要像 Pipe 或 Socket 一樣反覆複製資料。
- **可供多個行程存取**:不同 Process 可將同一段記憶體映射到自己的位址空間。
- **需要同步機制**:Shared Memory 本身不提供互斥或同步功能,若多個行程同時讀寫,可能發生 Race Condition,通常要搭配:
- Semaphore
- Mutex(需配置在 shared memory 中,且設定為 process-shared)
- File lock
- 其他同步機制
- **生命週期由核心管理**:以 System V Shared Memory 為例,記憶體區段會由核心管理,必須明確移除。
- **資料格式由程式自行定義**:Shared Memory 只是一塊記憶體,不會自動處理字串、結構或訊息邊界。
---
## System V Shared Memory 常用 System Call
C 程式通常透過以下函式使用 System V Shared Memory:
### 1. `shmget()`
建立或取得共享記憶體區段。
```c
int shmget(key_t key, size_t size, int shmflg);
```
常見參數:
- `key`:共享記憶體的識別鍵
- `size`:共享記憶體大小
- `shmflg`:
- `IPC_CREAT`:不存在時建立
- `0666`:權限設定
成功時回傳 Shared Memory ID,失敗回傳 `-1`。
---
### 2. `shmat()`
將共享記憶體附加(attach)到目前行程的位址空間。
```c
void *shmat(int shmid, const void *shmaddr, int shmflg);
```
通常使用:
```c
char *data = shmat(shmid, NULL, 0);
```
成功時回傳映射後的位址,失敗時回傳 `(void *) -1`。
---
### 3. `shmdt()`
將共享記憶體從目前行程的位址空間分離(detach)。
```c
int shmdt(const void *shmaddr);
```
這只代表目前行程不再使用該記憶體,不一定會刪除共享記憶體區段。
---
### 4. `shmctl()`
控制或刪除共享記憶體。
```c
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
```
刪除共享記憶體:
```c
shmctl(shmid, IPC_RMID, NULL);
```
通常流程為:
```text
shmget() → shmat() → 讀寫資料 → shmdt() → shmctl(IPC_RMID)
```
---
## 簡單範例:Parent 與 Child 透過 Shared Memory 通訊
以下程式建立一段共享記憶體,Parent 與 Child 透過 `fork()` 共用它:
- Child 寫入一段訊息
- Parent 等待 Child 結束後讀取訊息
- 最後 Parent 清除共享記憶體
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>
#define SHM_SIZE 1024
int main(void)
{
int shmid;
char *shared_data;
pid_t pid;
/*
* IPC_PRIVATE 表示建立一個新的共享記憶體區段。
* 0666 為讀寫權限。
*/
shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
/*
* 將共享記憶體附加到目前行程。
*/
shared_data = shmat(shmid, NULL, 0);
if (shared_data == (void *)-1) {
perror("shmat");
shmctl(shmid, IPC_RMID, NULL);
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
shmdt(shared_data);
shmctl(shmid, IPC_RMID, NULL);
exit(EXIT_FAILURE);
}
if (pid == 0) {
/* Child:寫入共享記憶體 */
strcpy(shared_data, "Hello from child process!");
printf("Child: message written to shared memory.\n");
shmdt(shared_data);
exit(EXIT_SUCCESS);
} else {
/* Parent:等待 Child 完成 */
waitpid(pid, NULL, 0);
printf("Parent received: %s\n", shared_data);
/* Parent 不再使用共享記憶體 */
shmdt(shared_data);
/* 標記刪除共享記憶體區段 */
shmctl(shmid, IPC_RMID, NULL);
}
return 0;
}
```
### 編譯與執行
```bash
gcc -Wall -o shm_example shm_example.c
./shm_example
```
可能輸出:
```text
Child: message written to shared memory.
Parent received: Hello from child process!
```
## 注意事項
上述範例使用 `waitpid()`,因此 Parent 會等 Child 寫入完成後才讀取,所以不會發生同步問題。但在實際應用中,如果兩個行程同時持續讀寫 Shared Memory,就必須搭配 Semaphore 或其他同步機制。
另外,System V Shared Memory 和 POSIX Shared Memory 是兩種常見介面:
- System V:`shmget()`、`shmat()`、`shmdt()`、`shmctl()`
- POSIX:`shm_open()`、`ftruncate()`、`mmap()`、`munmap()`、`shm_unlink()`
兩者都能實現共享記憶體,但 API 與生命週期管理方式不同。
相關學習地圖、教學課程
C 語言,系統程式