?
?
相關函數列表
//管道 #include <unistd.h> int pipe(int fd[2]); //標準I/O庫提供了兩個函數,實現的操作是創建一個管道fork一個子進程關閉未 //使用的管道端,執行一個shell運行命令,然后等待命令終止 //type類似fopen函數,有"r","w"或者"rw"等 #include <stdio.h> FILE *popen(const char *cmdstring, const char *type); int pclose(FILE *fp); //FIFO有時也被稱為命名管道,未命名的管道只能在兩個相關進程之間使用,而且這兩個相關的進程 //還要有一個共同的創建了它們的祖先進程,但FIFO不相關的進程也能交換數據 #include <sys/stat.h> int mkfifo(const char *path, mode_t mode); int mkfifoat(int fd, const char *path, mode_t mode); //下面函數提供的唯一服務就是由一個路徑名和項目ID產生一個鍵 #include <sys/ipc.h> key_t ftok(const char *path, int id); //XSI IPC為每一個IPC結構關聯了一個ipc_perm結構,該結構規定了權限和所有者,至少包含下面成員 struct ipc_perm { uid_t uid; //用戶有效ID gid_t gid; //用戶有效組ID uid_t cuid; //創建有效用戶ID gid_t cgid; //創建有效組ID mode_t mode //訪問模式 }; //消息隊列 //每個隊列都有一個msqid_ds結構與其相關聯 struct msqid_ds { struct ipc_perm msg_perm; //權限所有者結構 msgqnum_t msg_qnum; //隊列中有多少消息 msglen_t msg_qbytes; //隊列的最大字節數 pid_t msg_lspid; //mgsend()的 pid pid_t msg_lrpid; //msgrcv()的 pid time_t msg_stime; //last msgsend() time time_t msg_rtime; //last-msgrcv() time time_t msg_ctime; //last-change time }; //打開一個現有的消息隊列或創建一個新隊列 #include <sys/msg.h> int msgget(key_t key, int flag); //操作隊列,cmd參數指定對msqid指定的隊列要執行的命令 //IPC_STAT 取此隊列的msqid_ds結構,并將它存放在buf指向的結構中 //IPC_SET 將字段msg_perm.uid, msg_perm.gid, msg_perm.mode和 msg_qbytes從buf指向的結構 // 復制到與這個隊列相關的msqid_ds結構中,此命令只能由下列兩種進程執行:1)一種是其 // 有效用戶ID等于msg_perm.cuid或msg_perm.uid, 2)是一種具有超級用戶特權的進程,只 // 有超級用戶才能增加msg_qbytes的值 //IPC_RMID 從系統中山川村該消息隊列以及仍在該隊列中的所有數據。這種數據立即生效。仍在使用 // 這一消息隊列的其他進程再他們下一次試圖對此隊列進行操作時,將得到EIDRM錯誤。此 // 命令只能由下列兩種進程執行: 1)一種是有效用戶ID等于msg_perm.cuid或msg_perm.uid // 2)另一種是具有超級用戶特權的進程 #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_id *buf); //調用下面函數將數據放到消息隊列中 //flag參數的值可以指定為IPC_NOWAIT類似于文件I/O的非阻塞I/O標志, #include <sys/msg.h> int msgsend(int msqid, const void *ptr, size_t nbytes, int flag); //prt參數指向一個長整型。它包含了正的整型消息類型,其后緊接著的是消息數據 struct mymesg { long mtype; char mtext[512]; }; //從隊列中取消息 //和msgsend一樣,ptr參數指向一個長整型數,其后是存儲實際消息數據的緩沖區,nbytes指定數據 //緩沖區的長度,若flag中設置了MSG_NOERROR,則消息會截斷,參數type可以指定想要哪一種消息 //type==0 返回隊列中的第一個消息 //type>0 返回隊列中消息類型為type的第一個消息 //type<0 返回隊列中消息類型值小于等于type絕對值的消息 #include <sys/msg.h> ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag); //消息隊列 //每個隊列都有一個msqid_ds結構與其相關聯 struct msqid_ds { struct ipc_perm msg_perm; msgqunm_t msg_qnum; msglen_t msg_qbytes; pid_t msg_lspid; //pid of last msgsend() pid_t msg_lrpid; //pid of last msgrcv() time_t msg_stime; //last-msgsend() time time_t msg_rtime; //last-msgrcv() time time_t msg_ctime; //last-change time }; //信號量 //每個信號量由一個無名結構表示,它至少包含下列成員 struct { unsigned short semval; pid_t sempid; unsigned short semncnt; unsigned short semzcnt; }; //獲得一個信號量ID #include <sys/sem.h> int semget(key_t key, int nsems, int flag); //用于操作信號量的函數,其中cmd參數是以下10種命令的一種: //IPC_STAT 對此集合取semid_ds結構,并存儲在由arg.buf指向的結構中 //IPC_SET 按arg.buf指向的結構中的值設置與集合相關的結構中的sem_perm.uid等值 //IPC_RMID 從系統中刪除該信號量集合 //GETVAL 返回成員semnum的semval值 //SETVAL 設置成員semnum的semval值 //GETPID 返回成員semnum的sempid值 //GETNCNT 返回成員semnum的semncnt值 //GETALL 取該集合中所有的信號量值 //SETALL 將該集合中所有的信號量都設置成arg.array指向的數組中的值 #include <sys/sem.h> int semctl(int semid, int semunm, int cmd, ...); //自動執行信號量集合上的從操作數組 #include <sys/sem.h> int semop(int semid, struct sembuf semoparray[], size_t nops); //參數semoparray是一個指針,它指向一個由sembuf結構表示的信號量操作數組 struct sembuf { unsigned short sem_num; short sem_op; short sem_flag; }; //共享內存 //內核為每個共享內存儲段維護者一個結構,該結構至少要為每個共享存儲段包含以下成員 struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; pid_t shm_lpid; pid_t shm_cpid; shmatt_t shm_nattch; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; }; //獲得一個共享存儲的標識符 #include <sys/shm.h> int shmget(key_t key, size_t size, int flag); //對共享內存執行多種操作 //參數cmd指定下列5種命令中的一種,使其在shmid指定的段上執行 //IPC_STAT 取此段的shmid_ds結構,并將它存儲在由buf指向的結構中 //IPC_SET 按buf指向的結構中的值設置與此共享存儲段相關的shmid_ds結構中下一些字段 //IPC_RMID 從系統中刪除該共享存儲段 //SHM_LOCK 在內存中對共享存儲段加鎖,只能由超級用戶執行 //SHM_UNLOCK 解鎖共享存儲段,只能由超級用戶執行 #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf); //一旦創建了一個共享存儲段,進程就看以調用shmat將其連接到它的地址空間中 //共享存儲段連接到調用進程的哪個地址上與addr參數以及flag中是否指定SHM_RND位有關 //1.如果addr為0,則此段連接到由內核選擇的第一個可用地址上,這是推薦的方式 //2.如果addr非0,并且沒有指定SHM_RND,則此段連接到addr所指定的地址上 //3.如果addr非0,并且指定了SHM_RND,則此段連接到(addr-(addr mod SHMLBA))所表示的地址上, // SHM_RND命令的意思是取整,SHMLBA的意思是 低邊界地址倍數 //如果flag中指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段 #include <sys/shm.h> void *shmat(int shmid, const void *addr, int flag); //共享存儲段的操作已經結束時,調用下面函數與該段分離,但這并不從系統中刪除其標識符以及相關 //的數據結構,知道某個進程帶IPC_RMID命令的調用shmctl特地的刪除它為止 #include <sys/shm.h> int shmdt(const void *addr); //POSIX信號量 //使用下面函數來創建一個新的命名信號量或者使用一個現有信號量 #include <semaphore.h> sem_t *sem_open(const char *name, int oflag, .../* mode_t mode, unsigned int value */); //下面函數用來釋放任何信號量相關的資源 #include <semaphore.h> int sem_close(sem_t *sem); //下面函數用來銷毀一個命名信號量 #include <semaphore.h> int sem_unlink(const char *name); //使用下面函數用來實現信號量的減1操作,try函數會避免阻塞 #include <semaphore.h> #include <time.h> int sem_trywait(sem_t *sem); int sem_wait(sem_t *sem); int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsprt); //使用下面函數使信號量值增1,這和解鎖一個二進制信號量或者釋放一個計數信號量相關的資源過程 //是類似的 #include <semaphore.h> int sem_pos(sem_t *sem); //創建一個未命名的信號量 #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); //對未命名信號量的使用已經完成時,可以調用下面函數丟棄它 #include <semaphore.h> int sem_destroy(sem_t *sem); //調用下面函數來檢索信號量值 #include <semaphore.h> int sem_getvalue(sem_t *restrict sem, int *restrict valp);
?
?
進程間通訊(Inter Process Communication IPC)
UNIX系統IPC包括半雙工管道,全雙工管道,FIFO,XSI消息隊列,XSI信號量,XSI共享存儲,套接字
?
?
管道
?
管道是UNIX系統IPC的最古老形式,所有UNIX系統都提供此種通訊機制,管道有
下面兩種限制
1)歷史上他們是半雙工的(即數據只能在一個方向上流動),限制某些系統提供全
? ?雙工的管道,但是為了最佳的可移植性我們決不預先假設系統支持全雙工管道
2)管道只能在具有公共祖先的兩個進程之間使用,通常一個管道由一個進程創建,
? 在進程調用fork之后,這個管道就能在父進程和子進程之間使用了
? FOFO沒有第二種局限性,UNIX域套接字沒有這兩種局限性
?
經由參fd返回兩個文件描述符,fd[0]為讀而打開,fd[1]為寫而打開。 ?fd[1]的輸出是fd[0]
對于一個從子進程到父進程的管道,父進程關閉fd[1],子進程關閉fd[0]
?
?
FIFO(命名管道)
當open一個FIFO時,非阻塞標志(O_NONBLOCK)會產生下列影響
1)在一般情況下(沒有指定O_NONBLOCK),只讀open要阻塞到某個其他進程為寫而打開這個FIFO為止,
? ?類似的,只寫open要阻塞到某個其他進程為讀而打開它為止
2)如果指定了O_NONBLOCK,則只讀open立即返回。但是如果沒有進程為讀而打開一個FIFO,那么只寫
? ?open將返回-1,并將errno設置為ENXIO
FIFO有以下兩種用途
1)shell命令使用FIFO將數據從一條管道傳送到另一條時,無需創建中間臨時文件
2)客戶進程--服務器進程應用程序中,FIFO用作匯聚點,在客戶進程和服務器進程二者之間傳遞數據
客戶端和服務端通訊模式
?
?
XSI IPC
有三種XSI IP:消息隊列,信號量以及共享存儲
每個內核中的IPC結構(消息隊列,信號量和共享內存)都有一個非負整數的 標示符(identifier)加以引用
如要向一個消息隊列發送消息或者從一個消息隊列取消息,只需要知道其隊列標識符。
無論何時IPC結構都應指向一個鍵,這個鍵的數據類型是基本類型是系統數據類型key_t,包含在頭文件
<sys/types.h>中
?
有多種方法使客戶進程和服務器進程再同一IPC結構上匯聚
1)服務器進程可以指定鍵IPC_PRIVATE創建一個新IPC結構,將返回的標識符存放在某處(如一個文件)以便
? 客戶進程取用。鍵IPC_PRIVATE保證服務器進程創建一個新IPC結構,這種技術的缺點: 文件系統操作需要
? 服務器進程將整型標識符寫到文件中,此后客戶進程又要讀這個文件去的此標識符
? IPC_PRIVATE鍵也可用于父進程子關系,父進程指定IPC_PRIVATE創建一個新IPC結構,所返回的標識符
? 可供fork后的子進程使用。接著子進程又可將此標識符為exec函數的一個參數傳給一個新程序
2)可以在一個公用頭文件中定義一個客戶進程和服務器進程都認可的鍵。然后服務器進程指定此鍵創建一個
? ?新的IPC結構,這種方式問題是該鍵可能與一個IP結構相結合,在此情況下get函數出錯返回,服務器進程
? ?必須處理這一錯誤,刪除已存在的IPC結構然后試著再創建它
3)客戶進程和服務器進程認同一個路徑名和項目ID(項目ID是0--255之間的字符值)接著,調用函數fork將兩個
? ?值變換為一個鍵,然后在方法 2)中使用此鍵
XSI IPC權限
權限 | 位 |
用戶讀 | 0400 |
用戶寫(更改) | 0200 |
組讀 | 0040 |
組寫(更改) | 0020 |
其他讀 | 0004 |
其他寫(更改) | 0002 |
?
XSI IPC的問題
1)IPC結構是在系統范圍內起作用,沒有引用計數,如果進程創建了一個消息隊列,并且在該隊列中放入
? 幾條消息然后終止,那么該消息內容不會被刪除,他們會一直留在系統中直至發生下列動作
? ? ?a)某個進程調用msgrcv或者msgctl讀消息或刪除消息隊列
? ? ?b)某個進程執行ipcrm命令刪除消息隊列
? ? ?c)正在自舉的系統刪除消息隊列
? 與管道消息,當最后一個引用管道的進程終止時,管道就被完全刪除了,對FIFO而言,最后一個應用FIFO
? 的進程終止時,雖然FIFO的名字仍然保留在系統中直到被顯示的刪除,但是FIFO中的數據已被刪除了
2)這些IPC結構在文件系統中沒有名字,不能用文件I/O的方式去訪問和修改他們。為了支持這些IPC對象,
? 內核中增加了十幾個全新的系統調用(msgget,semop,shmat等)我們不能用ls 命令查看IPC對象,不能用
? rm刪除他們,于是又增加了新命令ipcs 和 ipcrm
? 因為這些形式的IP不使用文件描述符,所以不能對他們使用多路轉換I/O,這使得它很難一次使用一個以上
? 這樣的IPC結構
?
不同形式IPC之間的特征比較
IPC類型 | 無連接 | 可靠地 | 流控制 | 記錄 | 消息類型或優先級 |
消息隊列 | 否 | 是 | 是 | 是 | 是 |
STREAMS | 否 | 是 | 是 | 是 | 是 |
UNIX域套接字 | 否 | 是 | 是 | 否 | 否 |
UNIX域數據報套接字 | 是 | 是 | 否 | 是 | 否 |
FIFO(非STREAMS) | 否 | 是 | 是 | 否 | 否 |
?
?
信號量
信號量與已經介紹過的IPC結構不同,它是一個計數器,用于為多個進程提供對共享數據對象的訪問
為獲得共享資源,進程需要執行下列操作
1)測試控制該資源的信號量
2)若此信號量的值為正,則進程可以使用該資源,在這種情況下進程會將信號量值減1
3)否則若此信號量的值為0,則進程進入休眠狀態,直至信號量大于0,進程被喚醒后返回步驟1)
常用的信號量形式為稱為二元信號量(binary semaphore),它控制單個資源
?
遺憾的是XSI信號量與此相比要復雜的多,以下三種特性造成了這種不必要的復雜
1)信號量并非是單個非負值,而必需定義為含有一個或者多個信號量的集合,當創建信號量時,要指定集合
? ?中信號量的數量
2)信號量的創建是獨立于它的初始化的,這是一個致命的缺點,因為不能原子的創建一個信號量集合,并且
? ?對該集合中的各個信號量賦初始值
3)即使么有進程正在使用各種形式的XSI IPC,它們仍然是存在的,有的程序在終止時并沒有釋放已經分配給
? ?它們的信號量
?
?
/dev/zerio的存儲映射
在讀設備/dev/zero時,該設備是0字節無限資源,它也接收寫向它的任何數據,但是又忽略這些數據,我們對此設備作為IPC的興趣在于,當對其進行存儲映射時,它具有一些特殊性質:
1)創建一個未命名的存儲區,其長度是mmap的第二個參數,將其向上取整為系統的最近頁長
2)存儲區都初始化為0
3)如果多個進程的共同祖先進程對mmap指定了MAP_SHARED標志,則這些進程可共享此存儲區
這種方式優點: 在調用mmap創建映射區之前,無需再一個實際文件。映射/dev/zero自動創建一個指定長度的
? 營社區。
這種方式缺點: 它只在兩個相關進程之間起作用,但在相關進程之間使用線程可能更簡單
?
進程間共享內存的方式
?
?
POSIX信號量接口意在解決XSI信號量接口的幾個缺陷
1)相比于XSI接口,POSIX信號量接口考慮到了更高性能實現
2)POSIX信號量接口使用更簡單,沒有信號量集,在熟悉的文件系統操作后一些接口被模式化了,盡管沒有
? ?要求一定要在文件系統中實現,但是一些系統的的確是這么實現的
3)POSIX信號量在刪除時表現更完美,回憶一下,當一個XSI信號量被刪時信號量標識符會失敗,使用POSIX
? ?信號量時,操作能繼續正常工作指導該信號量的最后一次引用被釋放
?
POSIX信號量有兩種形式: 命名的和未命名的
它們的差異在于創建和銷毀的形式上,但其他工作一樣
未命名信號量只存在內存中,并要求能使用心涼的進程必須可以訪問內存,這意味著它們只能應用在同一
? 進程的線程,或不同進程中已映射相同內存內容直到它們的地址空間中的線程
命名信號量可以通過名字訪問,因此可以被任何已知它們名字的進程中的線程使用
?
?
?
參考
?
?
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
