?
?
相關(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元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

