UNIX再学习 -- XSI IPC通信方式
生活随笔
收集整理的這篇文章主要介紹了
UNIX再学习 -- XSI IPC通信方式
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
有 3 種稱作 XSI IPC 的IPC:消息隊列、信號量以及共享存儲器。我們先來介紹它們相類似的特征:
一、相似特征
1、標識符和鍵
每個內核中的 IPC 結構(消息隊列、信號量和共享存儲器)都用一個非負整數(shù)的標識符加以引用。 標識符是 IPC 對象的內部名。為使多個合作進程能夠在同一 IPC 對象上匯聚,需要提供一個外部命名方案。為此,每個 IPC 對象都與一個鍵相關聯(lián)。 無論何時創(chuàng)建 IPC 結構(通常調用 msgget、semget 或 shmget 創(chuàng)建),都應指定一個鍵。這個鍵的數(shù)據(jù)類型是基本系統(tǒng)數(shù)據(jù)類型 key_t,通常在頭文件 <sys/types.h> 中被定義為長整型(并沒找到)。這個鍵由內核變換成標識符。 有多種方法使客戶進程和服務器進程在同一 IPC 結構上匯聚。 (1)服務器進程可以指定鍵 IPC_PRIVATE 創(chuàng)建一個新 IPC 結構,將返回的標識符存放在某處 (如一個文件)以便客戶進程取用。鍵 IPC_PRIVATE 保證服務器進程創(chuàng)建一個新 IPC 結構。這種技術的缺點是:文件系統(tǒng)操作需要服務器進程將整型標識符寫到文件中,此后客戶進程又要讀這個文件取得此標識符。 IPC_PRIVATE 鍵也可用于父進程子關系。父進程指定 IPC_PRIVATE 創(chuàng)建一個新 IPC 結構,所返回的標識符可供 fork 后的子進程使用。接著,子進程又可將此標識符作為 exec 函數(shù)的一個參數(shù)傳給一個新程序。 (2)可以在一個公用頭文件中定義一個客戶進程和服務器都認可的鍵。然后服務器進程指定此鍵創(chuàng)建一個新的 IPC 結構。這種方法的問題是該鍵可能已與一個 IPC 結構相結合,在此情況下,get 函數(shù)(msgget、semget 或 shmget)出錯返回。服務器進程必須處理這一錯誤,刪除已存在的 IPC 結構,然后試著再創(chuàng)建它。 (3)客戶進程和服務器進程認同一個路徑名和項目 ID (項目ID是 0~255之間的字符值),接著,調用函數(shù) ftok 將這兩個值變換為一個鍵。然后在方法(2)中使用此鍵。fotk 提供的唯一服務就是由一個路徑名和項目 ID 產生一個鍵。 #include<sys/ipc.h> key_t ftok(const char *path, int id); 返回值:成功則返回鍵,出錯則返回(key_t)-1. path 參數(shù)必須引用一個現(xiàn)存文件。當產生鍵是,只使用 id 參數(shù)的低 8 位。ftok 創(chuàng)建的鍵通常是用下列方式構成的:按給定的路徑名取得其 stat 結構,從該結構中取出部分 st_dev 和 st_ino 字段,然后再與項目 ID 結合起來。如果兩個路徑名引用兩個不同的文件,那么對這兩個路徑名調用 ftok 通常返回不同的鍵。但是因為i節(jié)點號和鍵通常都存放在長整型中,于是創(chuàng)建鍵時可能會丟失信息,這意味著,如果使用用一個項目 ID,那么對于不同文件的兩個路徑名可能產生相同的鍵。
3 個 get函數(shù)(msgget,semget 和 shmget)都有兩個類似的參數(shù):一個 key 和一個整型 flag。如果滿足下列兩個條件之一,則創(chuàng)建一個新的 IPC 結構:
1. key 是 IPC_PRIVATE。
2. key 當前未與特定類型的 IPC 結構相結合,并且 flag 中指定了 IPC_CREAT位。
注意:
1.為了訪問現(xiàn)存的隊列,key 必須等于創(chuàng)建該隊列時所指定的鍵,并且不應指定 IPC_CREAT。
2.為了訪問一個現(xiàn)存隊列,決不能指定 IPC_PROVATE 作為鍵。因為這是一個特殊的鍵值,用于創(chuàng)建一個新隊列。
3.如果希望創(chuàng)建一個新的 IPC 結構,而且要確保不是引用具有同一標識符的一個現(xiàn)行 IPC 結構,那么必須在 flag 中同時指定 IPC_CREAT 和 IPC_EXECL 位。這樣做了以后,如果 IPC 結構已經存在,就會造成出錯,返回 EEXIST。
2、權限結構
XSI IPC 為每一個 IPC 結構設置了一個 ipc_perm 結構。該結構規(guī)定了權限的所有者。它至少包含以下成員:struct ipc_perm{ uid_t uid; //擁有者的有效用戶ID gid_t gid; //擁有者有效組ID uid_t cuid; //創(chuàng)建者有效用戶ID gid_t cgid; //創(chuàng)建者有效組ID mode_t mode; //訪問權限 ......... };在創(chuàng)建IPC結構時,對所有字段都賦初值。以后,可以調用 msgctl,semctl 或 shmctl 修改 uid,gid 和 mode 字段,為了改變這些值,調用進程必須是 IPC 結構的創(chuàng)建者或超級用戶。更改這些字段類似于對文件調用 chown 和 chmod。mode 字段值類似于普通文件的訪問權限,但是沒有執(zhí)行權限。
3、優(yōu)點缺點
缺點:1、IPC 結構在系統(tǒng)范圍內起作用,沒有訪問計數(shù),例如,如果進程創(chuàng)建了一個消息隊列,在該隊列中放入了幾則消息,然后終止,但是該消息隊列及其內容不會被刪除。與管道相比,當最后一個訪問管道的進程終止時,管道就完全被刪除了,對于 FIFO 而言,雖然當最后一個引用 FIFO 的進程終止時其名字仍保留在系統(tǒng)中,直至顯式地刪除它,但是留在 FIFO 中的數(shù)據(jù)卻在此時全部被刪除。
2、這些 IPC 結構在文件系統(tǒng)中沒有名字,我們不能使用 stat 系列函數(shù)訪問和修改他們的特性。為此不得不增加全新的系統(tǒng)調用(msgget,semop,shmat等)。我們不能使用 ls 命令見到 IPC 對象,不能使用 rm 命令刪除它們,也不能使用 chmod 等函數(shù)更改他們的訪問權限。于是就不得不增加新的命令 ipcs 和 ipcrm。
3、IPC 不使用文件描述符,所以不能對它們使用多路轉換 IO 函數(shù):select 和 poll。
優(yōu)點:
1、可靠 2、流是受控的 3、面向記錄 4、可以用非先進先出方式處理
二、消息隊列 (重點)
1、消息隊列介紹
消息隊列是一個由系統(tǒng)內核負責存儲和管理,并通過消息隊列標識符引用的消息鏈表。 我們消息隊列簡稱為隊列,其標識符簡稱為隊列 ID。可以通過 msgget 函數(shù)創(chuàng)建一個新的消息隊列或獲取一個已有的消息隊列。可以通過 msgsnd 函數(shù)向消息隊列的尾端追加消息,所追加的消息除了包含消息數(shù)據(jù)以外,還包含消息類型和數(shù)據(jù)長度(以字節(jié)為單位)。可以通過 msgrcv 函數(shù)從消息隊列中提取消息,但不一定非按先進先出的順序提取,也可以按消息的類型提取。
2、基本特點
相較于其他幾種 IPC 機制,消息隊列具有明顯的優(yōu)勢
(1)流量控制 如果系統(tǒng)資源(內存)短缺或者接受消息的進程來不及處理更多的消息,則發(fā)送消息的進程會在系統(tǒng)內核的控制下進入睡眠狀體,待條件滿足后再被內核喚醒,繼續(xù)之前的發(fā)送過程。 (2)面向記錄 每個消息都是完整的信息單元,發(fā)送端是一個消息一個消息地發(fā),接收端也是一個消息一個消息地收,而不像管道那樣收發(fā)兩端所面對的都是字節(jié)流,彼此間沒有結構上的一致性. (3)類型過濾 先進先出是隊列的固有特征,但消息隊列支持按類型提取消息的做法,這就比嚴格先進先出的管道具有更大的靈活性。 (4)天然同步 消息隊列本身就具有同步機制,空隊列不可讀,滿隊列不可寫,不發(fā)則不收,無需像共享內存那樣編寫額外的同步代碼。不同系統(tǒng)對消息隊列的限制是不一樣的
在 Linux 系統(tǒng)中,最大消息數(shù)是根據(jù)最大隊列數(shù)和隊列中所允許的最大數(shù)據(jù)量來決定的。其中最大隊列數(shù)還要根據(jù)系統(tǒng)上安裝的 RAM 的數(shù)量來決定。注意,隊列的最大字節(jié)數(shù)限制進一步限制了隊列中將要存儲的消息的最大長度。3、常用函數(shù)
(1)函數(shù) msget:創(chuàng)建/獲取消息隊列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg); 返回值:成功返回消息隊列標識符,失敗返回 -1《1》參數(shù)解析
key:消息隊列鍵 msgflg:創(chuàng)建標志,可取以下值: ? ? 0 ? ? ? ? ? ? ? ? ? ? 獲取,不存在即失敗 ? ? IPC_CREAT ? ?創(chuàng)建,不存在即創(chuàng)建,已存在即獲取 ? ? IPC_EXCL ? ? ? 排斥,已存在即失敗(2)函數(shù) msgsnd:發(fā)送信息
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 返回值:成功返回 0,失敗返回 -1《1》參數(shù)解析
msqid:消息隊列標識符msgp:指向一個包含消息類型和消息數(shù)據(jù)的內存塊。該內存塊的前 4 個字節(jié)必須是一個大于 0 的整數(shù),代表消息類型,其后緊跟消息數(shù)據(jù)。消息數(shù)據(jù)長度用 msgsz 參數(shù)表示。msgsz:期望發(fā)送消息數(shù)據(jù)(不含消息類型)的字節(jié)數(shù)。msgflg:發(fā)送標志,一般取 0 即可。《2》函數(shù)解析
注意 msgsnd 函數(shù)的 msgp 參數(shù)所指向的內存塊中包含消息類型,其值必須大于 0,但該函數(shù)的 msgsz 參數(shù)所表示的期望發(fā)送字節(jié)數(shù)中卻不包含消息類型所占的 4 個字節(jié)。如果系統(tǒng)內核中的消息未達上限,則 msgsnd 函數(shù)會將欲發(fā)送消息加入指定的消息隊列并立即返回 0,否則該函數(shù)會阻塞,直到系統(tǒng)內核允許加入新消息為止(比如有消息因被接收而離開消息隊列)。若 msgflg 參數(shù)中包含 IPC_NOWAIT 位,則 msgsnd 函數(shù)在系統(tǒng)內核中的消息已達上限的情況下不會阻塞,而是返回 -1,并置 errno 為 EAGAIN。
(3)函數(shù) msgrcv:接收消息
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg); 返回值:成功返回所接收消息數(shù)據(jù)的字節(jié)數(shù),失敗返回 -1.《1》參數(shù)解析
msqid:消息隊列標識符msgp:指向一塊包含消息類型(4 字節(jié))和消息數(shù)據(jù)的內存。msgsz:期望接收消息數(shù)據(jù)(不含消息類型)的字節(jié)數(shù)。msgtyp:消息類型,可取以下值:? ? msgtyp == 0 ? ?提取消息隊列的第一條消息? ? msgtyp > 0 ? ?若 msgflg 參宿不包含 MSG_EXCEPT 位,則提取消息隊列的第一條類型為 msgtyp 的消息;若 ? ? ? ? ? ? msgflg 參數(shù)包含 MSG_EXCEPT 位,則提取消息隊列的第一條類型不為 msgtyp 的消息。? ? msgtyp < 0 ? ?提取消息隊列中類型小于等于 msgtyp 的絕對值的消息,類型越小的消息越被優(yōu)先提取。msgflg:接收標志,一般取 0 即可。《2》函數(shù)解析
注意 msgrcv 函數(shù)的 msgp 參數(shù)所指向的內核塊中包含消息類型,其值由該函數(shù)輸出,但該函數(shù)的 msgsz 參數(shù)所表示的期望接收字節(jié)數(shù)以及該函數(shù)所返回的實際接收字節(jié)數(shù)都不包含消息類型所占的 4 個字節(jié)。若存在與 msgtyp 參數(shù)匹配的消息,但其數(shù)據(jù)長度大于 msgsz 參數(shù),且 msgflg 參數(shù)包含 MSG_NOERROR 位,則只截取該消息數(shù)據(jù)的前 msgsz 字節(jié)返回,剩余部分直接丟棄;但如果 msgflg 參數(shù)不包含 MSG_NOERROR 位,則不處理該消息,直接返回 -1,并置 errno 為 E2BIG。msgrcv 函數(shù)根據(jù) msgtyp 參數(shù)對消息隊列中的消息有選擇地接收,只有滿足條件的消息才會被復制到應用程序緩沖區(qū)并從內核中刪除。如果滿足 msgtyp 條件的消息不只一條,則按照先進先出的規(guī)則提取。
若消息隊列中有可接收消息,則 msgrcv 函數(shù)會將該消息移出消息隊列,并立即返回所接收到的消息數(shù)據(jù)的字節(jié)數(shù),表示接收成功,否則此函數(shù)會阻塞,直到消息隊列中有可接收消息為止。 若 msgflg 參數(shù)包含 IPC_NOWAIT 位,則 msgrcv 函數(shù)在消息隊列中沒有可接收消息的情況下不會阻塞,而是返回 -1,并置 errno 為 ENOMSG。
(4)函數(shù) msgctl:銷毀或控制消息隊列
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf); 返回值:成功返回 0,失敗返回 -1.《1》參數(shù)解析
msqid:消息隊列標識符 cmd:控制命令,可取以下值: ? ? IPC_STAT ? ?獲取消息隊列的屬性,通過 buf 參數(shù)輸出。 ? ? IPC_SET ? ? ?設置消息隊列的屬性,通過 buf 參數(shù)輸入。 ? ? 僅以下四個屬性可以設置: msqid_ds::msg_perm.uid //擁有者用戶 IDmsqid_ds::msg_perm.gid //擁有者組 IDmsqid_ds::msg_perm.mode //權限msqid_ds::msg_qbytes //隊列最大字節(jié)數(shù)? ? IPC_RMID ? ?立即刪除消息隊列,所有處于阻塞狀態(tài)的對該消息隊列的 msgsnd 和 msgrcv 函數(shù)調用,都會立即返回失敗,且 errno 為 EIDRM。 buf ? ?msqid_ds 類型的消息隊列屬性結構。4、示例說明
//msgA.c 接收端 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <signal.h> #include <sys/ipc.h> #include <sys/msg.h> #include <errno.h> #include <sys/stat.h>#define MSG_FILE "/home/tarena/project/c_test/a.txt" #define BUFFER 255 #define PERM S_IRUSR | S_IWUSRstruct msgtype {long mtype;char buffer[BUFFER + 1]; };int main (void) {struct msgtype msg;key_t key;int msgid;if ((key = ftok (MSG_FILE, 'a')) == -1)perror ("ftok"), exit (1);if ((msgid = msgget (key, PERM | IPC_CREAT | IPC_EXCL)) == -1)perror ("msgget"), exit (1);while (1){msgrcv (msgid, &msg, sizeof (struct msgtype), 1, 0);printf ("Server Receive: %s\n", msg.buffer);msg.mtype = 2;msgsnd (msgid, &msg, sizeof (struct msgtype), 0);}return 0; }//msgB.c 發(fā)送端 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h>#define MSG_FILE "/home/tarena/project/c_test/a.txt" #define BUFFER 255 #define PERM S_IRUSR | S_IWUSRstruct msgtype {long mtype;char buffer[BUFFER + 1]; };int main (void) {struct msgtype msg;key_t key;int msgid;if ((key = ftok (MSG_FILE, 'a')) == -1)perror ("ftok"), exit (1); if ((msgid = msgget (key, PERM)) == -1)perror ("msgget"), exit (1);msg.mtype = 1;strcpy (msg.buffer, "這是客戶端發(fā)出的消息內容");msgsnd (msgid, &msg, sizeof (struct msgtype), 0);memset (&msg, '\0', sizeof (struct msgtype));msgrcv (msgid, &msg, sizeof (struct msgtype), 2, 0);printf ("Client Receive: %s\n", msg.buffer);return 0; }輸出結果: 在一個終端執(zhí)行 # ./msgA Server Receive: 這是客戶端發(fā)出的消息內容 Server Receive: 這是客戶端發(fā)出的消息內容 Server Receive: 這是客戶端發(fā)出的消息內容 Server Receive: 這是客戶端發(fā)出的消息內容在另一個終端執(zhí)行 # ./msgB Client Receive: 這是客戶端發(fā)出的消息內容 # ./msgB Client Receive: 這是客戶端發(fā)出的消息內容 # ./msgB Client Receive: 這是客戶端發(fā)出的消息內容 # ./msgB Client Receive: 這是客戶端發(fā)出的消息內容5、相關指令
(1)ipcs 命令
《1》功能ipcs 命令用于報告Linux中進程間通信設施的狀態(tài),顯示的信息包括消息列表、共享內存和信號量的信息。《2》選項-a:顯示全部可顯示的信息; -q:顯示活動的消息隊列信息; -m:顯示活動的共享內存信息; -s:顯示活動的信號量信息。 《3》示例ipcs -q 表示查看當前系統(tǒng)中存在的消息隊列 //查看 # ipcs -q ------ Message Queues -------- key msqid owner perms used-bytes messages 0x61017a67 0 root 600 0 0 0x61017a6c 32769 root 600 0 0
(2)ipcrm 命令
《1》功能ipcrm命令用來刪除一個或更多的消息隊列、信號量集或者共享內存標識。《2》選項-m SharedMemory id 刪除共享內存標識 SharedMemoryID。與 SharedMemoryID 有關聯(lián)的共享內存段以及數(shù)據(jù)結構都會在最后一次拆離操作后刪除。 -M SharedMemoryKey 刪除用關鍵字 SharedMemoryKey 創(chuàng)建的共享內存標識。與其相關的共享內存段和數(shù)據(jù)結構段都將在最后一次拆離操作后刪除。 -q MessageID 刪除消息隊列標識 MessageID 和與其相關的消息隊列和數(shù)據(jù)結構。 -Q MessageKey 刪除由關鍵字 MessageKey 創(chuàng)建的消息隊列標識和與其相關的消息隊列和數(shù)據(jù)結構。 -s SemaphoreID 刪除信號量標識 SemaphoreID 和與其相關的信號量集及數(shù)據(jù)結構。 -S SemaphoreKey 刪除由關鍵字 SemaphoreKey 創(chuàng)建的信號標識和與其相關的信號量集和數(shù)據(jù)結構。 《3》示例ipcrm -q ID 表示刪除指定的消息隊列//刪除 # ipcrm -q 32769//復查 # ipcs -q------ Message Queues -------- key msqid owner perms used-bytes messages 0x61017a67 0 root 600 0 0
三、信號量
1、信號量介紹
信號量與其它幾種 IPC 機制(管道、共享內存和消息隊列)都不一樣,它的目的不是在進程之間搭建數(shù)據(jù)流通的橋梁,而是提供一個可為多個進程共同訪問的計數(shù)器,實時跟蹤可用資源的數(shù)量,以解決多個用戶分享有限資源時的競爭與沖突問題。2、基本特點
為了獲得共享資源,進程需要按以下步驟進行。
(1)測試控制該資源的信號量 (2)若信號量的值大于 0,說明還有可分配資源,則進程獲得該資源,并將信號量的值減 1,表示可分配資源少了一個。 (3)若信號量的值等于 0,說明沒有可分配資源,則進程進入睡眠狀態(tài),直到信號量的值再度大于 0,這時會有一個正在睡眠中等待該資源的進程被系統(tǒng)內核喚醒,它將返回執(zhí)行步驟 1,而其他進程則繼續(xù)在睡眠中等待。 (4)當進程不再使用所獲得的資源時,應將控制該資源的信號量的值加 1,表示可分配資源多了一個。此時那些正在睡眠中等待該資源的進程中的一個將被系統(tǒng)內核喚醒。 從有關信號的湊走步驟不難看出,對信號量所做的測試和加減操作都是必須是原子化的,用戶空間的全局變量顯然無法勝任,因此信號量通常被實現(xiàn)在系統(tǒng)內核之中。system V 的信號量比起通常意義上的信號量要復雜一些
(1)信號量并非被定義為一個簡單的非負整數(shù),相反必須把一個或多個信號量放在一起,組成一個信號量集來使用 (2)信號量的創(chuàng)建和初始化必須被分作兩步而不能在一個原子操作中完成。 (3)與其它幾種 XSI IPC 對象一樣,信號量也是系統(tǒng)級對象。如果某進程在其正常或異常終止前,沒有恢復信號量里的資源計數(shù),也沒有通過函數(shù)或命令刪除該信號對象,那么這種狀態(tài)將一直保持下去,并將以后運行的進程構成影響。內核為每個信號量集合維護著一個 semid_ds 結構:
struct semid_ds{ struct ipc_perm sem_perm; unsigned short sem_nsems; //信號量在信號量集中的編號 time_t sem_otime; 最后調用semop()的時間。 time_t sem_ctime; 最后進行change的時間。 .... }每個信號量由一個無名結構表示,它至少包含下列成員:
struct{ unsigned short semval; //信號量值,>=0 pid_t sempid; //最后使用信號量的pid unsigned short semcnt; //等待semval變?yōu)榇笥谄洚斍爸档木€程或進程數(shù) unsigned short semzcnt; //等待semval變成0的線程或進程數(shù) }下圖列出了影響信號量集合的系統(tǒng)限制:
3、常用函數(shù)?
(1)函數(shù) semget:創(chuàng)建/獲取信號量
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); 返回值:成功返回信號量標識符,失敗返回 -1《1》參數(shù)解析
key:信號量鍵 nsems:信號量個數(shù) semflg:創(chuàng)建標志,可取以下值: ? ? 0 ? ? ? ? ? ? ? ? ? ?獲取,不存在即失敗 ? ? IPC_CREAT ? ?創(chuàng)建,不存在即創(chuàng)建,已存在即獲取 ? ? IPC_EXCL ? ? ? 排斥,已存在即失敗(2)函數(shù) semop:操作信號量
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, unsigned nsops); 返回值:成功返回 0,失敗返回 -1《1》參數(shù)解析
semid:信號量標識符 sops:操作結構體數(shù)組 nsops:操作結構體數(shù)組長度《2》函數(shù)解析
作為 semop 函數(shù)的參數(shù),sops 和 nsops 所表示的數(shù)組由若干操作結構體組成,操作結構體的類型為 sembuf struct sembuf {unsigned short sem_num; //信息量編號short sem_op; //操作數(shù)short sem_flg; //操作標志 };該結構體數(shù)組中的每個元素通過其信號量編號成員與信號量集中的一個特定的信號量對應,表示對該信號量的操作。
semop 函數(shù)對 sops 指向的包含 nsops 個元素的操作結構體數(shù)組中的每個元素執(zhí)行如下操作: 1)若 sem_op 大于 0,則將其加到 semid 信號量集第 sem_num 號信號量的值上,以表示對資源的釋放。 2)若 sem_op 小于 0,則從 semid 信號量集第 sem_num 號信號量的值中減去其絕對值,以表示對資源的獲取;如果不夠減(信號量的值不能為負),則此函數(shù)會阻塞,直到夠減為止,以表示對資源的等待;但如果 sem_flg 包含 IPC_NOWAIT 位,則即使不夠減也不會阻塞,而是返回 -1,并置 errno 為 EAGAIN,以便在等待資源時還可做其他處理。 3)若 sem_op 等于 0,則直到 semid 信號量第 sem_num 號信號量的值為 0 時才返回,除非 sem_flg 含 IPC_NOWAIT 位。
(3)函數(shù) semctl:銷毀或控制信號量
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...); 返回值:成功返回 0(cmd 取某些值存在例外),失敗返回 -1《1》參數(shù)解析
semid:信號量集標識符 semnum:信號量編號。只有針對信號量集中某個具體信號量的操作,才需要此參數(shù);針對整個信號量集的操作,此參數(shù)將被忽略,置 0 即可。 cmd:控制命令《2》函數(shù)解析?
該函數(shù)的復雜性在于因控制命令 cmd 參數(shù)取值的不同,函數(shù)參數(shù)的個數(shù)、類型以及返回值的意義也會有所不同。1)獲取信號量集的屬性
int semctl (int semid, 0, IPC_STAT, struct semid_ds* buf);2)設置信號量集的屬性
int semctl (int semid, 0, IPC_SET, struct semid_ds* buf);僅以下四個屬性可以設置: semid_ds::sem_perm.uid //擁有者用戶ID semid_ds::sem_perm.gid //擁有者組 ID semid_ds::sem_perm.mode //權限3)刪除信號量集
int semctl (int semid , 0, IPC_RMID);立即刪除信號量集,所有處于阻塞裝填的對該信號量的 semop 函數(shù)調用,都會立即返回失敗,且 errno 為 EIDRM。4)獲取信號量集中每個信號量的值
int semctl (int semid , 0, GETALL, unisigned short* array);5)設置信號量集中每個信號量的值
int semctl (int semid , 0, SETALL, unisigned short* array);6)獲取信號量集中指定信號量的值
int semctl (int semid ,int semnum, GETVAL);成功返回 semid 信號量集中第 semnum 號信號量的值。7)設置信號量集中指定信號量的值
int semctl (int semid ,int semnum, SETVAL, int val);8)獲取信號量集的內核參數(shù)
int semctl (int semid ,0, IPC_INFO, struct seminfo* buf);4、示例說明
//semA.c 使用信號量集實現(xiàn)進程間的通信 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <signal.h>//定義全局變量保存信號量集的ID int semid;void fa(int signo) {printf("正在刪除信號量集,請稍后...\n");sleep(2);int res = semctl(semid,0,IPC_RMID);if(-1 == res){perror("semctl"),exit(-1);}printf("刪除信號量集成功\n");exit(0); }int main(void) {//1.獲取key值,使用ftok函數(shù)key_t key = ftok(".",200);if(-1 == key){perror("ftok"),exit(-1);}printf("key = %#x\n",key);//2.創(chuàng)建信號量集,使用semget函數(shù)semid = semget(key,1/*信號量集的大小*/,IPC_CREAT|IPC_EXCL|0644);if(-1 == semid){perror("semget"),exit(-1);}printf("semid = %d\n",semid);//3.初始化信號量集,使用semctl函數(shù)int res = semctl(semid,0/*信號量集的下標*/,SETVAL,5/*初始值*/);if(-1 == res){perror("semctl"),exit(-1);}printf("信號量集初始化完畢\n");//4.刪除信號量集,使用信號2處理printf("刪除信號量集,請按ctrl+c...\n");signal(2,fa);while(1);return 0; }//semB.c 使用信號量集實現(xiàn)進程間的通信 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>int main(void) {//1.獲取key值,使用ftok函數(shù)key_t key = ftok(".",200);if(-1 == key){perror("ftok"),exit(-1);}printf("key = %#x\n",key);//2.獲取信號量集,使用semget函數(shù)int semid = semget(key,1,0);if(-1 == semid){perror("semget"),exit(-1);}printf("semid = %d\n",semid);//3.創(chuàng)建10個子進程模擬搶占資源int i = 0;for(i = 0; i < 10; i++){//創(chuàng)建子進程pid_t pid = fork();if(0 == pid)//子進程{//準備結構體變量struct sembuf op;op.sem_num = 0;//下標op.sem_op = -1;//計數(shù)減1op.sem_flg = 0;//標志//使用semop函數(shù)占用資源semop(semid,&op,1/*大小*/);printf("申請共享資源成功\n");sleep(20);op.sem_op = 1;//使用semop函數(shù)釋放資源semop(semid,&op,1);printf("釋放共享資源完畢\n");exit(0);//終止}}return 0; }輸出結果: 在一個終端執(zhí)行: # ./semA key = 0xc801135e semid = 196608 信號量集初始化完畢 刪除信號量集,請按ctrl+c... ^C正在刪除信號量集,請稍后... 刪除信號量集成功在另一個終端執(zhí)行: # ./semB key = 0xc801135e semid = 196608 申請共享資源成功 申請共享資源成功 申請共享資源成功 申請共享資源成功 申請共享資源成功 # ./semB key = 0xc801135e semid = 196608 申請共享資源成功 申請共享資源成功 申請共享資源成功 申請共享資源成功 申請共享資源成功 釋放共享資源完畢 釋放共享資源完畢 釋放共享資源完畢 釋放共享資源完畢 釋放共享資源完畢 釋放共享資源完畢5、相關指令
ipcs -s 表示查看當前系統(tǒng)中存在的信號量//查看 # ipcs -s------ Semaphore Arrays -------- key semid owner perms nsems 0xc801135e 32768 root 644 1 ipcrm -s ID 表示刪除指定的信號量
//刪除 # ipcrm -s 32768//復查 # ipcs -s------ Semaphore Arrays -------- key semid owner perms nsems
四、共享存儲
1、共享存儲介紹
兩個或者更多進程,共享同一塊由系統(tǒng)內核負責維護的內存區(qū)域,其地址空間通常被映射到堆和棧之間。2、基本特點
多個進程通過共享內存通信,所傳輸?shù)臄?shù)據(jù)通過各個進程的虛擬內存被直接反映到同一塊物理內存中,這就避免了在不同進程之間來回復制數(shù)據(jù)的開銷。因此,基于共享內存的進程通信,是速度最快的進程間通信方式。 共享內存本身缺乏足夠的同步機制,這就需要程序員編寫額外的代碼來實現(xiàn)。例如服務器進程正在把數(shù)據(jù)寫入共享內存,在這個寫入過程完成之前,客戶機進程就不能讀取該共享內存中的數(shù)據(jù)。為了建立進程之間的這種同步,可能需要借助于其它的進程間通信機制,如信號或者信號量等,甚至文件鎖,而這無疑會增加系統(tǒng)開銷。3、常用函數(shù)
(1)函數(shù) shmget:創(chuàng)建新的或獲取已有的共享內存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); 返回值:成功返回共享內存標識符,失敗返回 -1《1》參數(shù)解析
key:共享內存鍵 size:共享內存大小(以字節(jié)為單位),自動向上圓整至頁(4096)的整數(shù)倍。若欲創(chuàng)建新的共享內存,必須指定 ? ? ? ? ? ? ? ?size 參數(shù);若只為獲取已有的共享內存,size 參數(shù)可取 0. shmflg:創(chuàng)建標志,可取以下值: ? ? 0 ? ? ? ? ? ? ? ? ? ? 獲取,不存在即失敗 ? ? IPC_CREAT ? ?創(chuàng)建,不存在即創(chuàng)建,已存在即獲取 ? ? IPC_EXCL ? ? ? 排斥,已存在即失敗(2)函數(shù) shmat:加載共享內存
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); 返回值:成功返回共享內存起始地址,失敗返回 -1《1》參數(shù)解析
shmid:共享內存標識符 shmaddr:指定映射地址,可置 NULL,由系統(tǒng)自動選擇 shmflg:加載標志,可取以下值: ? ? 0 ? ? ? ? ? ? ? ? ? ? ? ? ? 以讀寫方式使用共享內存 ? ? SHM_RDONLY ? ?以只讀方式使用共享內存 ? ? SHM_RND ? ? ? ? ? 只在 shmaddr 參數(shù)非 NULL 時起作用,表示對該參數(shù)自動向下圓整至頁(4096)的整數(shù)倍《2》函數(shù)解析
shmat 函數(shù)負責將給定共享內存映射到調用進程的虛擬內存空間,返回映射區(qū)的起始地址,同時將系統(tǒng)內核中共享內存對象的加載計數(shù)(shmid_ds::shm_nattch)加 1 調用進程在獲得 shmat 函數(shù)返回的共享內存起始地址以后,就可以像訪問普通內存一樣訪問共享內存中的數(shù)據(jù)。(3)卸載共享內存
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr); 返回值:成功返回 0,失敗返回 -1《1》參數(shù)解析
shmaddr:共享內存起始地址《2》函數(shù)解析
shmdt 函數(shù)負責從調用進程的虛擬內存中解除 shmaddr 所指向的映射區(qū)到共享內存的映射,同時將系統(tǒng)內核中共享內存對象的加載計數(shù)(shmid_ds::shm_nattch)減 1(4)函數(shù) shmctl:銷毀/控制共享內存
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf); 返回值:成功返回 0,失敗返回 -1《1》參數(shù)解析
shmid:共享內存標識符 cmd:控制命令,可取以下值: ? ? IPC_STAT ? ?獲取共享內存的屬性,通過 buf 參數(shù)輸出 ? ? IPC_SET ? ? ? 設置共享內存的屬性,通過 buf 參數(shù)輸入 ? ? 僅以下三個屬性可以設置: shmid_ds::shm_perm.uid //擁有者用戶 IDshmid_ds::shm_perm.gid //擁有者組 IDshmid_ds::shm_perm.mode //權限? ? IPC_RMIF ? ?銷毀共享內存,其實并非真的銷毀,而只是做一個銷毀標志,禁止任何進程對該共享內存形成新的加載,但已有的加載依然保留。只有當其使用者們紛紛卸載,直至其加載計數(shù)降為 0 時,共享內存才會真的被銷毀。 ? ? buf ? ?shmid_ds 類型的共享內存屬性結構4、示例說明
//msgA.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <sys/stat.h> #include <sys/shm.h> #define MSG_FILE "/home/tarena/project/c_test/a.txt" #define BUFFER 4096 #define PERM S_IRUSR|S_IWUSR int main() {key_t key;if((key=ftok(MSG_FILE,'a'))==-1){perror("Creat Key Error");exit(1);}int shmid = shmget (key, BUFFER, PERM | IPC_CREAT | IPC_EXCL);if (shmid == -1){perror ("shmget");exit (EXIT_FAILURE);}void* shmaddr = shmat (shmid, NULL, 0);if (shmaddr == (void*)-1){perror ("shmat");exit (EXIT_FAILURE);}strcpy (shmaddr, "這是放入共享內存的內容!");if (shmdt (shmaddr) == -1){perror ("shmdt");exit (EXIT_FAILURE);}sleep(10);if (shmctl (shmid, IPC_RMID, NULL) == -1){perror ("shmctl");exit (EXIT_FAILURE);}return 0; } //msgB.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <sys/stat.h> #include <sys/shm.h> #define MSG_FILE "/home/tarena/project/c_test/a.txt" #define BUFFER 4096 #define PERM S_IRUSR|S_IWUSR int main(int argc,char **argv) {key_t key;if((key=ftok(MSG_FILE,'a'))==-1){perror("Creat Key Error");exit(1);}int shmid = shmget (key, BUFFER, PERM);if (shmid == -1){perror ("shmget");exit (EXIT_FAILURE);}void* shmaddr = shmat (shmid, NULL, 0);if (shmaddr == (void*)-1){perror ("shmat");exit (EXIT_FAILURE);}printf ("%s\n", (char*)shmaddr);if (shmdt (shmaddr) == -1){perror ("shmdt");exit (EXIT_FAILURE);}return 0; } 輸出結果: 在一個終端執(zhí)行: # ./msgA在另一個終端執(zhí)行: # ./msgB 這是放入共享內存的內容!5、相關指令
ipcs -m 表示查看當前系統(tǒng)中存在的共享存儲 //查看 # ipcs -m------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 0 root 600 393216 2 dest 0x00000000 196609 root 700 25740 2 dest 0x00000000 163842 root 700 3219768 2 dest 0x00000000 98307 root 700 130752 2 dest 0x00000000 229380 root 700 17028 2 dest 0x00000000 262149 root 700 13332 2 dest 0x00000000 294918 root 700 15180 2 dest 0x61017b76 393223 root 600 4096 0 ipcrm -m ID 表示刪除指定的共享存儲//刪除 # ipcrm -m 393223//復查 # ipcs -m------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 0 root 600 393216 2 dest 0x00000000 196609 root 700 25740 2 dest 0x00000000 163842 root 700 3219768 2 dest 0x00000000 98307 root 700 130752 2 dest 0x00000000 229380 root 700 17028 2 dest 0x00000000 262149 root 700 13332 2 dest 0x00000000 294918 root 700 15180 2 dest
五、未講部分
協(xié)同進程 POSIX 信號量 客戶進程-服務器進程屬性總結
以上是生活随笔為你收集整理的UNIX再学习 -- XSI IPC通信方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UNIX再学习 -- 进程间通信之管道
- 下一篇: 元宇宙综观—愿景、技术和应对