System V IPC 三大件(消息隊列、信號量、共享內存)是 Unix 經典進程間通信機制,Linux 完整支持。消息隊列以「消息」為單位、支持類型過濾、內核持久化,適合結構化數據交換、命令分發等場景。本文系統講解 msgget/msgsnd/msgrcv/msgctl 四大核心調用、數據結構設計、同步模式、常見陷阱及完整雙向通信範例。
key_t,通常由 ftok(pathname, proj_id) 生成,用於協調無關進程訪問同一隊列。long mtype(必須 > 0),接收時可指定類型實現優先級/分類讀取。msgctl(IPC_RMID) 或重啟機器清除。MSGMAX(默認 8KB)、隊列最大字節 MSGMNB(默認 16KB)、系統最大隊列數 MSGMNI,可通過 /proc/sys/kernel/msg* 調整。#include <sys/msg.h>
int msgget(key_t key, int msgflg);
| msgflg 組合 | 行為 |
|---|---|
IPC_CREAT | 不存在則創建,存在則返回現有隊列 |
IPC_CREAT | IPC_EXCL | 不存在則創建,存在則報錯 EEXIST |
0666 | IPC_CREAT | 創建時設置權限(受 umask 影響) |
單獨 IPC_EXCL | 無意義 |
返回:成功得 msqid,失敗 -1 設 errno。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgp 指向用戶定義結構,首字段必須是 long mtype。msgsz = sizeof(struct) - sizeof(long)(不含類型字段)。msgflg:
0:隊列滿時阻塞。IPC_NOWAIT:隊列滿立即返回 EAGAIN。ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgtyp:
> 0:讀取第一個類型等於該值的消息。== 0:讀取隊列第一條消息(不論類型)。< 0:讀取類型值 ≤ |msgtyp| 的第一條消息(實現優先級)。msgflg:
0:無匹配消息時阻塞。IPC_NOWAIT:立即返回 ENOMSG。MSG_NOERROR:消息長度超過 msgsz 時截斷而非報錯。mtype),隊列中該消息被移除。int msgctl(int msqid, int cmd, struct msqid_ds *buf);
| cmd | 功能 |
|---|---|
IPC_STAT | 讀取隊列內部 msqid_ds 到 buf |
IPC_SET | 從 buf 設置 msg_perm.uid/gid/mode |
IPC_RMID | 立即刪除隊列,喚醒所有阻塞進程 |
IPC_INFO | 獲取系統級限制信息 (Linux 擴展) |
struct msqid_ds 關鍵字段:
struct msqid_ds {
struct ipc_perm msg_perm; // 權限、uid/gid
time_t msg_stime; // 最後 msgsnd 時間
time_t msg_rtime; // 最後 msgrcv 時間
time_t msg_ctime; // 最後 msgctl 時間
unsigned long msg_cbytes; // 當前隊列字節數
msgqnum_t msg_qnum; // 當前消息數
msglen_t msg_qbytes; // 隊列最大字節數 (可修改)
pid_t msg_lspid; // 最後發送進程 PID
pid_t msg_lrpid; // 最後接收進程 PID
};
// 基礎模板
struct mymsg {
long mtype; // 必須首字段,>0
char mtext[256]; // 承載數據
};
// 進階:多類型聯合體
struct msg_payload {
long mtype;
union {
struct { int cmd; int arg1; arg2; } ctrl;
struct { char data[1024]; } bulk;
struct { int fd; } fd_pass; // 配合 SCM_RIGHTS 傳遞文件描述符
} payload;
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <signal.h>
#define KEY_PATH "/tmp/msgqueue_key"
#define PROJ_ID 0x4D
#define MSG_TYPE_CMD 1
#define MSG_TYPE_RSP 2
struct msgbuf {
long mtype;
char text[512];
};
int msqid;
void cleanup(int sig) {
msgctl(msqid, IPC_RMID, NULL);
printf("\nServer shutdown, queue removed.\n");
exit(0);
}
int main() {
key_t key = ftok(KEY_PATH, PROJ_ID);
if (key == -1) { perror("ftok"); exit(1); }
msqid = msgget(key, 0666 | IPC_CREAT);
if (msqid == -1) { perror("msgget"); exit(1); }
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
printf("Server started, msqid=%d, waiting for commands...\n", msqid);
struct msgbuf msg;
while (1) {
// 阻塞讀取命令
ssize_t n = msgrcv(msqid, &msg, sizeof(msg.text), MSG_TYPE_CMD, 0);
if (n == -1) {
if (errno == EINTR) continue; // 信號中斷
perror("msgrcv");
break;
}
msg.text[n] = '\0';
printf("Received command: %s\n", msg.text);
// 處理並回覆
snprintf(msg.text, sizeof(msg.text), "RSP: %s", msg.text);
msg.mtype = MSG_TYPE_RSP;
if (msgsnd(msqid, &msg, strlen(msg.text)+1, 0) == -1) {
perror("msgsnd rsp");
}
}
cleanup(0);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <unistd.h>
#define KEY_PATH "/tmp/msgqueue_key"
#define PROJ_ID 0x4D
#define MSG_TYPE_CMD 1
#define MSG_TYPE_RSP 2
struct msgbuf {
long mtype;
char text[512];
};
int main() {
key_t key = ftok(KEY_PATH, PROJ_ID);
if (key == -1) { perror("ftok"); exit(1); }
int msqid = msgget(key, 0666); // 不創建,僅連接
if (msqid == -1) { perror("msgget"); exit(1); }
struct msgbuf msg;
char input[512];
printf("Client connected. Type commands (empty to quit):\n");
while (fgets(input, sizeof(input), stdin)) {
size_t len = strlen(input);
if (len <= 1) break;
input[len-1] = '\0'; // 去換行
msg.mtype = MSG_TYPE_CMD;
strncpy(msg.text, input, sizeof(msg.text)-1);
msg.text[sizeof(msg.text)-1] = '\0';
if (msgsnd(msqid, &msg, strlen(msg.text)+1, 0) == -1) {
perror("msgsnd");
break;
}
// 等待響應
ssize_t n = msgrcv(msqid, &msg, sizeof(msg.text), MSG_TYPE_RSP, 0);
if (n == -1) { perror("msgrcv"); break; }
msg.text[n] = '\0';
printf("Response: %s\n", msg.text);
}
return 0;
}
gcc server.c -o server
gcc client.c -o client
# 終端1
./server
# 終端2
./client
# 輸入命令,觀察雙向交互
| 主題 | 要點 |
|---|---|
| 非阻塞輪詢 | msgrcv(..., IPC_NOWAIT) + ENOMSG 判斷;或用 select/poll 監聽 /dev/mqueue (Linux 3.5+ 支持),但 System V msgqueue 不直接支持 fd 輪詢。 |
| 消息優先級 | 發送時用不同 mtype,接收用負 msgtyp 讀取 ≤ 某值的優先消息。 |
| 大消息分片 | 單消息受限 MSGMAX,大數據需應用層分片重組,或改用共享內存+消息隊列通知。 |
| 權限問題 | 創建時 0666 & ~umask,不同用戶需同組或 msgctl(IPC_SET) 修改權限。 |
| 僵屍隊列清理 | 進程異常退出未調用 IPC_RMID,隊列永久駐留。建議:啟動腳本用 ipcs -q 檢查、ipcrm -q msqid 清理;或守護進程定期掃描無引用隊列。 |
| 系統限制調整 | sysctl -w kernel.msgmax=65536 kernel.msgmnb=65536 kernel.msgmni=32000,持久化寫入 /etc/sysctl.d/99-msgqueue.conf。 |
| 特性 | System V (msgget) | POSIX (mq_open) |
|---|---|---|
| API 風格 | 整數 msqid、key_t | 文件描述符 mqd_t、路徑名 /name |
| 優先級 | 靠 mtype 模擬 | 原生支持 mq_send 優先級參數 |
| 通知機制 | 無(需輪詢/信號) | mq_notify 支持信號/線程回調 |
| 超時收發 | 無 | mq_timedsend/receive |
| 持久性 | 內核持久,重啟消失 | 同,或掛載 mqueuefs 可見 |
| 推薦場景 | 老代碼維護、跨 Unix 移植 | 新項目、需優先級/通知/超時 |
System V 消息隊列雖 API 古老,但穩定、跨平台、內核持久化特性使其在嵌入式、老牌 Unix 遷移、簡單命令分發場景仍具生命力。掌握 ftok/msgget/msgsnd/msgrcv/msgctl 全鏈路、消息結構設計、權限與限制調整、僵屍隊列治理,即可靈活應用。新項目優先考慮 POSIX mqueue 或更高級中間件,但在維護既有系統時,本文提供的完整範例與最佳實踐將助你游刃有餘。