1. 程式人生 > >7.7-UC-第七課:進程通信

7.7-UC-第七課:進程通信

min ipcs 字節 套接字 pcs lose 便在 交換 emp

================第七課 進程通信================
一、基本概念------------
1. 何為進程間通信~~~~~~~~~~~~~~~~~
進程間通信(Interprocess Communication, IPC)是指兩個,或多個進程之間進行數據交換的過程。
2. 進程間通信分類~~~~~~~~~~~~~~~~~
1) 簡單進程間通信:命令行參數、環境變量、信號、文件。
2) 傳統進程間通信:管道(fifo/pipe)。
3) XSI進程間通信:共享內存、消息隊列、信號量。
4) 網絡進程間通信:套接字。
二、傳統進程間通信——管道--------------------------
1. 管道是Unix系統最古老的進程間通信方式。
2. 歷史上的管道通常是指半雙工管道, 只允許數據單向流動。現代系統大都提供全雙工管道, 數據可以沿著管道雙向流動。
3. 有名管道(fifo):基於有名文件(管道文件)的管道通信。
1) 命令形式
# mkfifo fifo# echo hello > fifo # cat fifo
2) 編程模型
------+----------+------------+----------+------ 步驟 | 進程A | 函數 | 進程B | 步驟------+----------+------------+----------+------ 1 | 創建管道 | mkfifo | ---- | 2 | 打開管道 | open | 打開管道 | 1 3 | 讀寫管道 | read/write | 讀寫管道 | 2 4 | 關閉管道 | close | 關閉管道 | 3 5 | 刪除管道 | unlink | ---- |------+----------+------------+----------+------
範例:wfifo.c、rfifo.c
圖示:fifo.bmp
4. 無名管道(pipe):適用於父子進程之間的通信。
#include <unistd.h>
int pipe (int pipefd[2]);
1) 成功返回0,失敗返回-1。
2) 通過輸出參數pipefd返回兩個文件描述符, 其中pipefd[0]用於讀,pipefd[1]用於寫。
3) 一般用法
A. 調用該函數在內核中創建管道文件,並通過其輸出參數, 獲得分別用於讀和寫的兩個文件描述符;
B. 調用fork函數,創建子進程;
C. 寫數據的進程關閉讀端(pipefd[0]), 讀數據的進程關閉寫端(pipefd[1]);
D. 傳輸數據;
E. 父子進程分別關閉自己的文件描述符。
圖示:pipe.bmp
範例:pipe.c
三、XSI進程間通信-----------------
1. IPC標識~~~~~~~~~~
內核為每個進程間通信維護一個結構體形式的IPC對象。該對象可通過一個非負整數的IPC標識來引用。
與文件描述符不同,IPC標識在使用時會持續加1,當達到最大值時,向0回轉。
2. IPC鍵值~~~~~~~~~~
IPC標識是IPC對象的內部名稱。若多個進程需要在同一個IPC對象上會合,則必須通過鍵值作為其外部名稱來引用該對象。
1) 無論何時,只要創建IPC對象,就必須指定一個鍵值。
2) 鍵值的數據類型在sys/types.h頭文件中被定義為key_t, 其原始類型就是長整型。
3. 客戶機進程與服務器進程在IPC對象上的三種會合方式~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1) 服務器進程以IPC_PRIVATE為鍵值創建一個新的IPC對象, 並將該IPC對象的標識存放在某處(如文件中), 以方便客戶機進程讀取。
2) 在一個公共頭文件中, 定義一個客戶機進程和服務器進程都認可的鍵值, 服務器進程用此鍵值創建IPC對象, 客戶機進程用此鍵值獲取該IPC對象。
3) 客戶機進程和服務器進程, 事先約定好一個路徑名和一個項目ID(0-255), 二者通過ftok函數, 將該路徑名和項目ID轉換為一致的鍵值。
#include <sys/types.h>#include <sys/ipc.h>
key_t ftok (const char* pathname, int proj_id);
pathname - 一個真實存在的文件或目錄的路徑名。
proj_id - 項目ID,僅低8位有效,其值域為[0,255]。
成功返回鍵值,失敗返回-1。
註意:起作用的是pathname參數所表示的路徑,而非pathname字符串本身。因此假設當前目錄是/home/soft01/uc/day07,則ftok (".", 100);和ftok ("/home/soft01/uc/day07", 100);的返回值完全相同。
4. IPC對象的創建~~~~~~~~~~~~~~~~
1) 若以IPC_PRIVATE為鍵值創建IPC對象, 則永遠創建成功。
2) 若所指定的鍵值在系統範圍內未與任何IPC對象相結合, 且創建標誌包含IPC_CREAT位,則創建成功。
3) 若所指定的鍵值在系統範圍內已與某個IPC對象相結合, 且創建標誌包含IPC_CREAT和IPC_EXCL位,則創建失敗。
5. IPC對象的銷毀/控制~~~~~~~~~~~~~~~~~~~~~
IPC_STAT - 獲取IPC對象屬性IPC_SET - 設置IPC對象屬性IPC_RMID - 刪除IPC對象
四、共享內存------------
1. 基本特點~~~~~~~~~~~
1) 兩個或者更多進程, 共享同一塊由系統內核負責維護的內存區域, 其地址空間通常被映射到堆和棧之間。
圖示:shm.bmp
2) 無需復制信息,最快的一種IPC機制。
3) 需要考慮同步訪問的問題。
4) 內核為每個共享內存, 維護一個shmid_ds結構體形式的共享內存對象。
2. 常用函數~~~~~~~~~~~
#include <sys/shm.h>
1) 創建/獲取共享內存
int shmget (key_t key, size_t size, int shmflg);
A. 該函數以key參數為鍵值創建共享內存, 或獲取已有的共享內存。
B. size參數為共享內存的字節數, 建議取內存頁字節數(4096)的整數倍。 若希望創建共享內存,則必需指定size參數。 若只為獲取已有的共享內存,則size參數可取0。
C. shmflg取值:
0 - 獲取,不存在即失敗。
IPC_CREAT - 創建,不存在即創建, 已存在即獲取,除非...
IPC_EXCL - 排斥,已存在即失敗。
D. 成功返回共享內存標識,失敗返回-1。
2) 加載共享內存
void* shmat (int shmid, const void* shmaddr, int shmflg);
A. 將shmid參數所標識的共享內存, 映射到調用進程的地址空間。
B. 可通過shmaddr參數人為指定映射地址, 也可將該參數置NULL,由系統自動選擇。
C. shmflg取值:
0 - 以讀寫方式使用共享內存。
SHM_RDONLY - 以只讀方式使用共享內存。
SHM_RND - 只在shmaddr參數非NULL時起作用。 表示對該參數向下取內存頁的整數倍, 作為映射地址。
D. 成功返回映射地址,失敗返回-1。
E. 內核將該共享內存的加載計數加1。
3) 卸載共享內存
int shmdt (const void* shmaddr);
A. 從調用進程的地址空間中, 取消由shmaddr參數所指向的,共享內存映射區域。
B. 成功返回0,失敗返回-1。
C. 內核將該共享內存的加載計數減1。
4) 銷毀/控制共享內存
int shmctl (int shmid, int cmd, struct shmid_ds* buf);
struct shmid_ds { struct ipc_perm shm_perm; // 所有者及其權限 size_t shm_segsz; // 大小(以字節為單位) time_t shm_atime; // 最後加載時間 time_t shm_dtime; // 最後卸載時間 time_t shm_ctime; // 最後改變時間 pid_t shm_cpid; // 創建進程PID pid_t shm_lpid; // 最後加載/卸載進程PID shmatt_t shm_nattch; // 當前加載計數 ...};
struct ipc_perm { key_t __key; // 鍵值 uid_t uid; // 有效屬主ID gid_t gid; // 有效屬組ID uid_t cuid; // 有效創建者ID gid_t cgid; // 有效創建組ID unsigned short mode; // 權限字 unsigned short __seq; // 序列號};
A. cmd取值:
IPC_STAT - 獲取共享內存的屬性,通過buf參數輸出。
IPC_SET - 設置共享內存的屬性,通過buf參數輸入, 僅以下三個屬性可設置:
shmid_ds::shm_perm.uid shmid_ds::shm_perm.gid shmid_ds::shm_perm.mode
IPC_RMID - 標記刪除共享內存。 並非真正刪除共享內存,只是做一個刪除標記, 禁止其被繼續加載,但已有加載依然保留。 只有當該共享內存的加載計數為0時, 才真正被刪除。
B. 成功返回0,失敗返回-1。
3. 編程模型~~~~~~~~~~~
------+--------------+--------+--------------+------ 步驟 | 進程A | 函數 | 進程B | 步驟------+--------------+--------+--------------+------ 1 | 創建共享內存 | shmget | 獲取共享內存 | 1 2 | 加載共享內存 | shmat | 加載共享內存 | 2 3 | 使用共享內存 | ... | 使用共享內存 | 3 4 | 卸載共享內存 | shmdt | 卸載共享內存 | 4 5 | 銷毀共享內存 | shmctl | ---- |------+--------------+--------+--------------+------
範例:wshm.c、rshm.c
五、消息隊列------------
1. 基本特點~~~~~~~~~~~
1) 消息隊列是一個由系統內核負責存儲和管理, 並通過消息隊列標識引用的數據鏈表。
2) 可以通過msgget函數創建一個新的消息隊列, 或獲取一個已有的消息隊列。 通過msgsnd函數向消息隊列的後端追加消息, 通過msgrcv函數從消息隊列的前端提取消息。
3) 消息隊列中的每個消息單元除包含消息數據外, 還包含消息類型和數據長度。
4) 內核為每個消息隊列, 維護一個msqid_ds結構體形式的消息隊列對象。
2. 常用函數~~~~~~~~~~~
#include <sys/msg.h>
1) 創建/獲取消息隊列
int msgget (key_t key, int msgflg);
A. 該函數以key參數為鍵值創建消息隊列, 或獲取已有的消息隊列。
B. msgflg取值:
0 - 獲取,不存在即失敗。
IPC_CREAT - 創建,不存在即創建, 已存在即獲取,除非...
IPC_EXCL - 排斥,已存在即失敗。
C. 成功返回消息隊列標識,失敗返回-1。
2) 向消息隊列發送消息
int msgsnd (int msqid, const void* msgp, size_t msgsz, int msgflg);
A. msgp參數指向一個包含消息類型和消息數據的內存塊。 該內存塊的前4個字節必須是一個大於0的整數, 代表消息類型,其後緊跟消息數據。 消息數據的字節長度用msgsz參數表示。
+---------------+-----------------+msgp -> | 消息類型 (>0) | 消息數據 | +---------------+-----------------+ |<----- 4 ----->|<---- msgsz ---->|
註意:msgsz參數並不包含消息類型的字節數(4)。
B. 若內核中的消息隊列緩沖區有足夠的空閑空間, 則此函數會將消息拷入該緩沖區並立即返回0, 表示發送成功,否則此函數會阻塞, 直到內核中的消息隊列緩沖區有足夠的空閑空間為止 (比如有消息被接收)。
C. 若msgflg參數包含IPC_NOWAIT位, 則當內核中的消息隊列緩沖區沒有足夠的空閑空間時, 此函數不會阻塞,而是返回-1,errno為EAGAIN。
D. 成功返回0,失敗返回-1。
3) 從消息隊列接收消息
ssize_t msgrcv (int msqid, void* msgp, size_t msgsz, long msgtyp, int msgflg);
A. msgp參數指向一個包含消息類型(4字節), 和消息數據的內存塊, 其中消息數據緩沖區的字節大小用msgsz參數表示。
B. 若所接收到的消息數據字節數大於msgsz參數, 即消息太長,且msgflg參數包含MSG_NOERROR位, 則該消息被截取msgsz字節返回,剩余部分被丟棄。
C. 若msgflg參數不包含MSG_NOERROR位,消息又太長, 則不對該消息做任何處理,直接返回-1,errno為E2BIG。
D. msgtyp參數表示期望接收哪類消息:
=0 - 返回消息隊列中的第一條消息。
>0 - 若msgflg參數不包含MSG_EXCEPT位, 則返回消息隊列中第一個類型為msgtyp的消息; 若msgflg參數包含MSG_EXCEPT位, 則返回消息隊列中第一個類型不為msgtyp的消息。
<0 - 返回消息隊列中類型小於等於msgtyp的絕對值的消息。 若有多個,則取類型最小者。
+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | 3 | 4 | 2 | 1 | 3 | 2 | 4 | 3 | 3 |-> rear +- -+- -+- -+- -+- -+- -+- -+- -+- -+ front -> | ... | ... | ... | ... | ... | ... | ... | ... | ... | +-----+-----+-----+-----+-----+-----+-----+-----+-----+ ^ ^ ^ ^ 4 3 2 1
msgrcv (..., ..., ..., 3, ...);
E. 若消息隊列中有可接收消息, 則此函數會將該消息移出消息隊列並立即返回0, 表示接收成功,否則此函數會阻塞, 直到消息隊列中有可接收消息為止。
F. 若msgflg參數包含IPC_NOWAIT位, 則當消息隊列中沒有可接收消息時,此函數不會阻塞, 而是返回-1,errno為ENOMSG。
G. 成功返回所接收到的消息數據的字節數,失敗返回-1。
4) 銷毀/控制消息隊列
int msgctl (int msqid, int cmd, struct msqid_ds* buf);
struct msqid_ds { struct ipc_perm msg_perm; // 權限信息 time_t msg_stime; // 隨後發送時間 time_t msg_rtime; // 最後接收時間 time_t msg_ctime; // 最後改變時間 unsigned long __msg_cbytes; // 消息隊列中的字節數 msgqnum_t msg_qnum; // 消息隊列中的消息數 msglen_t msg_qbytes; // 消息隊列能容納的最大字節數 pid_t msg_lspid; // 最後發送進程PID pid_t msg_lrpid; // 最後接收進程PID};
struct ipc_perm { key_t __key; // 鍵值 uid_t uid; // 有效屬主ID gid_t gid; // 有效屬組ID uid_t cuid; // 有效創建者ID gid_t cgid; // 有效創建組ID unsigned short mode; // 權限字 unsigned short __seq; // 序列號};
A. cmd取值:
IPC_STAT - 獲取消息隊列的屬性,通過buf參數輸出。
IPC_SET - 設置消息隊列的屬性,通過buf參數輸入, 僅以下四個屬性可設置:
msqid_ds::msg_perm.uid msqid_ds::msg_perm.gid msqid_ds::msg_perm.mode msqid_ds::msg_qbytes
IPC_RMID - 立即刪除消息隊列。 此時所有阻塞在對該消息隊列的, msgsnd和msgrcv函數調用, 都會立即返回失敗,errno為EIDRM。
B. 成功返回0,失敗返回-1。
3. 編程模型~~~~~~~~~~~
------+--------------+---------------+--------------+------ 步驟 | 進程A | 函數 | 進程B | 步驟------+--------------+---------------+--------------+------ 1 | 創建消息隊列 | msgget | 獲取消息隊列 | 1 2 | 發送接收消息 | msgsnd/msgrcv | 發送接收消息 | 2 3 | 銷毀消息隊列 | msgctl | ---- |------+--------------+---------------+--------------+------
範例:wmsq.c、rmsq.c
練習:基於消息隊列的本地銀行。
代碼:bank/
六、信號量----------
1. 基本特點~~~~~~~~~~~
1) 計數器,用於限制多個進程對有限共享資源的訪問。
2) 多個進程獲取有限共享資源的操作模式
A. 測試控制該資源的信號量;
B. 若信號量大於0,則進程可以使用該資源, 為了表示此進程已獲得該資源,需將信號量減1;
C. 若信號量等於0,則進程休眠等待該資源, 直到信號量大於0,進程被喚醒,執行步驟A;
D. 當某進程不再使用該資源時,信號量增1, 正在休眠等待該資源的其它進程將被喚醒。
2. 常用函數~~~~~~~~~~~
#include <sys/sem.h>
1) 創建/獲取信號量
int semget (key_t key, int nsems, int semflg);
A. 該函數以key參數為鍵值創建一個信號量集合 (nsems參數表示集合中的信號量數), 或獲取已有的信號量集合(nsems取0)。
B. semflg取值:
0 - 獲取,不存在即失敗。
IPC_CREAT - 創建,不存在即創建, 已存在即獲取,除非...
IPC_EXCL - 排斥,已存在即失敗。
C. 成功返回信號量集合標識,失敗返回-1。
2) 操作信號量
int semop (int semid, struct sembuf* sops, unsigned nsops);
struct sembuf { unsigned short sem_num; // 信號量下標 short sem_op; // 操作數 short sem_flg; // 操作標記};
A. 該函數對semid參數所標識的信號量集合中, 由sops參數所指向的包含nsops個元素的, 結構體數組中的每個元素,依次執行如下操作:
a) 若sem_op大於0, 則將其加到第sem_num個信號量的計數值上, 以表示對資源的釋放;
b) 若sem_op小於0, 則從第sem_num個信號量的計數值中減去其絕對值, 以表示對資源的獲取;
c) 若第sem_num個信號量的計數值不夠減(信號量不能為負), 則此函數會阻塞,直到該信號量夠減為止, 以表示對資源的等待;
d) 若sem_flg包含IPC_NOWAIT位, 則當第sem_num個信號量的計數值不夠減時, 此函數不會阻塞,而是返回-1,errno為EAGAIN, 以便在等待資源的同時還可做其它處理;
e) 若sem_op等於0, 則直到第sem_num個信號量的計數值為0時才返回, 除非sem_flg包含IPC_NOWAIT位。
B. 成功返回0,失敗返回-1。
3) 銷毀/控制信號量
int semctl (int semid, int semnum, int cmd);int semctl (int semid, int semnum, int cmd, union semun arg);
union semun { int val; // Value for SETVAL struct semid_ds* buf; // Buffer for IPC_STAT, IPC_SET unsigned short* array; // Array for GETALL, SETALL struct seminfo* __buf; // Buffer for IPC_INFO};
struct semid_ds { struct ipc_perm sem_perm; // Ownership and permissions time_t sem_otime; // Last semop time time_t sem_ctime; // Last change time unsigned short sem_nsems; // No. of semaphores in set};
struct ipc_perm { key_t __key; // 鍵值 uid_t uid; // 有效屬主ID gid_t gid; // 有效屬組ID uid_t cuid; // 有效創建者ID gid_t cgid; // 有效創建組ID unsigned short mode; // 權限字 unsigned short __seq; // 序列號};
A. cmd取值:
IPC_STAT - 獲取信號量集合的屬性,通過arg.buf輸出。
IPC_SET - 設置信號量集合的屬性,通過arg.buf輸入, 僅以下四個屬性可設置:
semid_ds::sem_perm.uid semid_ds::sem_perm.gid semid_ds::sem_perm.mode
IPC_RMID - 立即刪除信號量集合。 此時所有阻塞在對該信號量集合的, semop函數調用,都會立即返回失敗, errno為EIDRM。
GETALL - 獲取信號量集合中每個信號量的計數值, 通過arg.array輸出。
SETALL - 設置信號量集合中每個信號量的計數值, 通過arg.array輸入。
GETVAL - 獲取信號量集合中, 第semnum個信號量的計數值, 通過返回值輸出。
SETVAL - 設置信號量集合中, 第semnum個信號量的計數值, 通過arg.val輸入。
註意:只有針對信號量集合中具體某個信號量的操作,才會使用semnum參數。針對整個信號量集合的操作,會忽略semnum參數。
B. 成功返回值因cmd而異,失敗返回-1。
3. 編程模型~~~~~~~~~~~
------+------------+--------+------------+------ 步驟 | 進程A | 函數 | 進程B | 步驟------+------------+--------+------------+------ 1 | 創建信號量 | semget | 獲取信號量 | 1 2 | 初始信號量 | semctl | ---- | 3 | 加減信號量 | semop | 加減信號量 | 2 4 | 銷毀信號量 | semctl | ---- |------+------------+--------+------------+------
範例:csem.c、gsem.c
七、IPC命令-----------
1. 顯示~~~~~~~
ipcs -m - 顯示共享內存(m: memory)ipcs -q - 顯示消息隊列(q: queue)ipcs -s - 顯示信號量(s: semphore)ipcs -a - 顯示所有IPC對象(a: all)
2. 刪除~~~~~~~
ipcrm -m ID - 刪除共享內存ipcrm -q ID - 刪除消息隊列ipcrm -s ID - 刪除信號量

來自為知筆記(Wiz)

7.7-UC-第七課:進程通信