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

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