linux基礎——linux進程間通信(IPC)機制總結
管道的實現機制:
管道是由內核管理的一個緩衝區,相當於我們放入內存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩衝區不需要很大,它被設計成爲環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。
pipe函數原型:
- #include <unistd.h>
- int pipe(int file_descriptor[2]);//建立管道,該函數在數組上填上兩個新的文件描述符後返回0,失敗返回-1。
- eg.int fd[2]
- int result = pipe(fd);
通過使用底層的read和write調用來訪問數據。 向file_descriptor[1]寫數據,從file_descriptor[0]中讀數據。寫入與讀取的順序原則是先進先出。 命名管道是一種特殊類型的文件,它在系統中以文件形式存在。這樣克服了管道的弊端,他可以允許沒有親緣關係的進程間通信。
創建管道的兩個系統調用原型:
- #include <sys/types.h>
- #include <sys/stat.h>
- int mkfifo(const char *filename,mode_t mode); //建立一個名字爲filename的命名管道,參數mode爲該文件的權限(mode%~umask),若成功則返回0,否則返回-1,錯誤原因存於errno中。
- eg.mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );
具體操作方法只要創建了一個命名管道然後就可以使用open、read、write等系統調用來操作。創建可以手工創建或者程序中創建。
- int mknod(const char *path, mode_t mode, dev_t dev); //第一個參數表示你要創建的文件的名稱,第二個參數表示文件類型,第三個參數表示該文件對應的設備文件的設備號。只有當文件類型爲 S_IFCHR 或 S_IFBLK 的時候該文件纔有設備號,創建普通文件時傳入0即可。
- eg.mknod(FIFO_FILE,S_IFIFO|0666,0);
信號機制是unix系統中最爲古老的進程之間的通信機制,用於一個或幾個進程之間傳遞異步信號。信號可以有各種異步事件產生,比如鍵盤中斷等。shell也可以使用信號將作業控制命令傳遞給它的子進程。
- #include <sys/types.h>
- #include <signal.h>
- void (*signal(int sig,void (*func)(int)))(int); //用於截取系統信號,第一個參數爲信號,第二個參數爲對此信號掛接用戶自己的處理函數指針。返回值爲以前信號處理程序的指針。
- eg.int ret = signal(SIGSTOP, sig_handle);
由於signal不夠健壯,推薦使用sigaction函數。
- int kill(pid_t pid,int sig); //kill函數向進程號爲pid的進程發送信號,信號值爲sig。當pid爲0時,向當前系統的所有進程發送信號sig。
- int raise(int sig);//向當前進程中自舉一個信號sig, 即向當前進程發送信號。
- #include <unistd.h>
- unsigned int alarm(unsigned int seconds); //alarm()用來設置信號SIGALRM在經過參數seconds指定的秒數後傳送給目前的進程。如果參數seconds爲0,則之前設置的鬧鐘會被取消,並將剩下的時間返回。使用alarm函數的時候要注意alarm函數的覆蓋性,即在一個進程中採用一次alarm函數則該進程之前的alarm函數將失效。
- int pause(void); //使調用進程(或線程)睡眠狀態,直到接收到信號,要麼終止,或導致它調用一個信號捕獲函數。
消息隊列是內核地址空間中的內部鏈表,通過linux內核在各個進程直接傳遞內容,消息順序地發送到消息隊列中,並以幾種不同的方式從隊列中獲得,每個消息隊列可以用IPC標識符唯一地進行識別。內核中的消息隊列是通過IPC的標識符來區別,不同的消息隊列直接是相互獨立的。每個消息隊列中的消息,又構成一個獨立的鏈表。
消息隊列頭文件:
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/msg.h>
- struct msgbuf{
- long mtype;
- char mtext[1];//柔性數組
- }
- struct msgbuf{
- long mtype;
- char mtext[1];//柔性數組
- }
<div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">struct msgid_ds{</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;"> struct ipc_perm msg_perm{</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;"> time_t msg_stime;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;"> time_t msg_rtime;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;"> time_t msg_ctime;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;"> unsigned long _msg_cbuyes;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;"> ..........</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;"> };</div>
- struct ipc_perm{
- key_t key;
- uid_t uid;
- gid_t gid;
- .......
- };
- key_t ftok( const char * fname, int id );//參數一爲目錄名稱, 參數二爲id。如指定文件的索引節點號爲65538,換算成16進製爲0x010002,而你指定的ID值爲38,換算成16進製爲0x26,則最後的key_t返回值爲0x26010002。
- eg.key_t key = key =ftok(".", 1);
- int msgget(key_t key,int msgflag); //msgget用來創建和訪問一個消息隊列。程序必須提供一個鍵值來命名特定的消息隊列。
- eg.int msg_id = msgget(key, IPC_CREATE | IPC_EXCL | 0x0666);//根據關鍵字創建一個新的隊列(IPC_CREATE),如果隊列存在則出錯(IPC_EXCL),擁有對文件的讀寫執行權限(0666)。
- int msgsnd(int msgid,const void *msgptr,size_t msg_sz,int msgflg); //msgsnd函數允許我們把一條消息添加到消息隊列中。msgptr只想準備發送消息的指針,指針結構體必須以一個長整型變量開始。
- eg.struct msgmbuf{
- int mtype;
- char mtext[10];
- };
- struct msgmbuf msg_mbuf;
- msg_mbuf.mtype = 10;//消息大小10字節
- memcpy(msg_mbuf.mtext, "測試消息", sizeof("測試消息"));
- int ret = msgsnd(msg_id, &msg_mbuf, sizeof("測試消息"), IPC_NOWAIT);
- int msgrcv(int msgid, void *msgptr, size_t msg_sz, long int msgtype, int msgflg); //msgrcv可以通過msqid對指定消息隊列進行接收操作。第二個參數爲消息緩衝區變量地址,第三個參數爲消息緩衝區結構大小,但是不包括mtype成員長度,第四個參數爲mtype指定從隊列中獲取的消息類型。
- eg.int ret = msgrcv(msg_id, &msg_mbuf, 10, 10, IPC_NOWAIT | MSG_NOERROR);
- int msgctl(int msqid,int cmd,struct msqid_ds *buf); //msgctl函數主要是一些控制如刪除消息隊列等操作。 cmd值如下:
- IPC_STAT:獲取隊列的msgid_ds結構,並把它存到buf指向的地址。
- IPC_SET:將隊列的msgid_ds設置爲buf指向的msgid_ds。
- IPC_RMID:內核刪除消息隊列,最後一項填NULL, 執行操作後,內核會把消息隊列從系統中刪除。
信號量是一種計數器,用於控制對多個進程共享的資源進行的訪問。它們常常被用作一個鎖機制,在某個進程正在對特定的資源進行操作時,信號量可以防止另一個進程去訪問它。
信號量是特殊的變量,它只取正整數值並且只允許對這個值進行兩種操作:等待(wait)和信號(signal)。(P、V操作,P用於等待,V用於信號)
p(sv):如果sv的值大於0,就給它減1;如果它的值等於0,就掛起該進程的執行
V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行;如果沒有其他進程因等待sv而掛起,則給它加1
簡單理解就是P相當於申請資源,V相當於釋放資源
信號量頭文件:
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/sem.h>
- struct semid_ds{
- struct ipc_perm sem_perm;
- unsigned short sem_nsems;
- time_t sem_otime;
- time_t sem_ctime;
- ...
- }
- union semun{
- int val;
- struct semid_ds *buf;
- unsigned short *array;
- struct seminfo *__buf;
- }
信號量操作sembuf結構:
- struct sembuf{
- ushort sem_num;//信號量的編號
- short sem_op;//信號量的操作。如果爲正,則從信號量中加上一個值,如果爲負,則從信號量中減掉一個值,如果爲0,則將進程設置爲睡眠狀態,直到信號量的值爲0爲止。
- short sem_flg;//信號的操作標誌,一般爲IPC_NOWAIT。
- }
常用函數:
- int semget(key_t key, int num_sems, int sem_flags); //semget函數用於創建一個新的信號量集合 , 或者訪問一個現有的集合(不同進程只要key值相同即可訪問同一信號量集合)。第一個參數key是ftok生成的鍵值,第二個參數num_sems可以指定在新的集合應該創建的信號量的數目,第三個參數sem_flags是打開信號量的方式。
- eg.int semid = semget(key, 0, IPC_CREATE | IPC_EXCL | 0666);//第三個參數參考消息隊列int msgget(key_t key,int msgflag);第二個參數。
- int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops); //semop函數用於改變信號量的值。第二個參數是要在信號集合上執行操作的一個數組,第三個參數是該數組操作的個數 。
- eg.struct sembuf sops = {0, +1, IPC_NOWAIT};//對索引值爲0的信號量加一。
- semop(semid, &sops, 1);//以上功能執行的次數爲一次。
- int semctl(int sem_id, int sem_num, int command,...); //semctl函數用於信號量集合執行控制操作,初始化信號量的值,刪除一個信號量等。 類似於調用msgctl(), msgctl()是用於消息隊列上的操作。第一個參數是指定的信號量集合(semget的返回值),第二個參數是要執行操作的信號量在集合中的索引值(例如集合中第一個信號量下標爲0),第三個command參數代表要在集合上執行的命令。
- IPC_STAT:獲取某個集合的semid_ds結構,並把它存儲到semun聯合體的buf參數指向的地址。
- IPC_SET:將某個集合的semid_ds結構的ipc_perm成員的值。該命令所取的值是從semun聯合體的buf參數中取到。
- IPC_RMID:內核刪除該信號量集合。
- GETVAL:返回集合中某個信號量的值。
- SETVAL:把集合中單個信號量的值設置成爲聯合體val成員的值。
共享內存是在多個進程之間共享內存區域的一種進程間的通信方式,由IPC爲進程創建的一個特殊地址範圍,它將出現在該進程的地址空間(這裏的地址空間具體是哪個地方?)中。其他進程可以將同一段共享內存連接到自己的地址空間中。所有進程都可以訪問共享內存中的地址,就好像它們是malloc分配的一樣。如果一個進程向共享內存中寫入了數據,所做的改動將立刻被其他進程看到。
注意:共享內存本身並沒有同步機制,需要程序員自己控制。
共享內存頭文件:
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/shm.h>
- strcut shmid_ds{
- struct ipc_perm shm_perm;
- size_t shm_segsz;
- time_t shm_atime;
- time_t shm_dtime;
- ......
- }
- int shmget(key_t key,size_t size,int shmflg); //shmget函數用來創建一個新的共享內存段, 或者訪問一個現有的共享內存段(不同進程只要key值相同即可訪問同一共享內存段)。第一個參數key是ftok生成的鍵值,第二個參數size爲共享內存的大小,第三個參數sem_flags是打開共享內存的方式。
- eg.int shmid = shmget(key, 1024, IPC_CREATE | IPC_EXCL | 0666);//第三個參數參考消息隊列int msgget(key_t key,int msgflag);
- void *shmat(int shm_id,const void *shm_addr,int shmflg); //shmat函數通過shm_id將共享內存連接到進程的地址空間中。第二個參數可以由用戶指定共享內存映射到進程空間的地址,shm_addr如果爲0,則由內核試着查找一個未映射的區域。返回值爲共享內存映射的地址。
- eg.char *shms = (char *)shmat(shmid, 0, 0);//shmid由shmget獲得
- int shmdt(const void *shm_addr); //shmdt函數將共享內存從當前進程中分離。 參數爲共享內存映射的地址。
- eg.shmdt(shms);
- int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//shmctl函數是控制函數,使用方法和消息隊列msgctl()函數調用完全類似。參數一shm_id是共享內存的句柄,cmd是向共享內存發送的命令,最後一個參數buf是向共享內存發送命令的參數。
內存映射文件,是由一個文件到一塊內存的映射。內存映射文件與虛擬內存有些類似,通過內存映射文件可以保留一個地址的區域,
- #include <sys.mman.h>
- void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset); //mmap函數將一個文件或者其它對象映射進內存。 第一個參數爲映射區的開始地址,設置爲0表示由系統決定映射區的起始地址,第二個參數爲映射的長度,第三個參數爲期望的內存保護標誌,第四個參數是指定映射對象的類型,第五個參數爲文件描述符(指明要映射的文件),第六個參數是被映射對象內容的起點。成功返回被映射區的指針,失敗返回MAP_FAILED[其值爲(void *)-1]。
- int munmap(void* start,size_t length); //munmap函數用來取消參數start所指的映射內存起始地址,參數length則是欲取消的內存大小。如果解除映射成功則返回0,否則返回-1,錯誤原因存於errno中錯誤代碼EINVAL。
- int msync(void *addr,size_t len,int flags); //msync函數實現磁盤文件內容和共享內存取內容一致,即同步。第一個參數爲文件映射到進程空間的地址,第二個參數爲映射空間的大小,第三個參數爲刷新的參數設置。
共享內存和內存映射文件的區別:
共享內存是內存映射文件的一種特殊情況,內存映射的是一塊內存,而非磁盤上的文件。共享內存的主語是進程(Process),操作系統默認會給每一個進程分配一個內存空間,每一個進程只允許訪問操作系統分配給它的哪一段內存,而不能訪問其他進程的。而有時候需要在不同進程之間訪問同一段內存,怎麼辦呢?操作系統給出了 創建訪問共享內存的API,需要共享內存的進程可以通過這一組定義好的API來訪問多個進程之間共有的內存,各個進程訪問這一段內存就像訪問一個硬盤上的文件一樣。
套接字機制不但可以單機的不同進程通信,而且使得跨網機器間進程可以通信。
套接字的創建和使用與管道是有區別的,套接字明確地將客戶端與服務器區分開來,可以實現多個客戶端連到同一服務器。
服務器套接字連接過程描述:
首先,服務器應用程序用socket創建一個套接字,它是系統分配服務器進程的類似文件描述符的資源。 接着,服務器調用bind給套接字命名。這個名字是一個標示符,它允許linux將進入的針對特定端口的連接轉到正確的服務器進程。 然後,系統調用listen函數開始接聽,等待客戶端連接。listen創建一個隊列並將其用於存放來自客戶端的進入連接。 當客戶端調用connect請求連接時,服務器調用accept接受客戶端連接,accept此時會創建一個新套接字,用於與這個客戶端進行通信。
客戶端套接字連接過程描述:
客戶端首先調用socket創建一個未命名套接字,讓後將服務器的命名套接字作爲地址來調用connect與服務器建立連接。
只要雙方連接建立成功,我們就可以像操作底層文件一樣來操作socket套接字實現通信。
幾個基礎函數定義:
- #include <sys/types.h>
- #include <sys/socket.h>
- int socket(it domain,int type,int protocal);
- int bind(int socket,const struct sockaddr *address,size_t address_len);
- int listen(int socket,int backlog);
- int accept(int socket,struct sockaddr *address,size_t *address_len);
- int connect(int socket,const struct sockaddr *addrsss,size_t address_len);
詳細請看:http://blog.csdn.net/a987073381/article/details/51869000