2017-2018-1 20155320第十周课下作业-IPC
2017-2018-1 20155320第十周課下作業-IPC
研究Linux下IPC機制:原理,優缺點,每種機制至少給一個示例,提交研究博客的鏈接
- 共享內存
- 管道
- FIFO
- 信號
- 消息隊列
共享內存
共享內存允許兩個或更多進程訪問同一塊內存。當一個進程改變了這塊內存中的內容的的時候,其他進程都會察覺到這個更改。
進程間需要共享的數據放入內核的共享內存區,進程可以把共享內存映射到自己進程的地址空間去,所以進程可以直接讀取內存,不需要任何數據的拷貝。
共享內存原理
system V IPC機制下的共享內存本質是一段特殊的內存區域,進程間需要共享的數據被放在該共享內存區域中,所有需要訪問該共享區域的進程都要把該共享區域映射到本進程的地址空間(虛擬地址空間)中去。這樣一個使用共享內存的進程可以將信息寫入該空間,而另一個使用共享內存的進程又可以通過簡單的內存讀操作獲取剛才寫入的信息,使得兩個不同進程之間進行了一次信息交換,從而實現進程間的通信。共享內存允許一個或多個進程通過同時出現在它們的虛擬地址空間的內存進行通信,而這塊虛擬內存的頁面被每個共享進程的頁表條目所引用,同時并不需要在所有進程的虛擬內存都有相同的地址。進程對象對于共享內存的訪問通過key(鍵)來控制,同時通過key進行訪問權限的檢查。
相關函數
1、shmget
- 函數說明:得到一個共享內存標識符或創建一個共享內存對象并返回共享內存標識符。
- 參數:
key ftok函數返回的I P C鍵值
size 大于0的整數,新建的共享內存大小,以字節為單位,獲取已存在的共享內存塊標識符時,該參數為0
shmflg IPC_CREAT||IPC_EXCL 執行成功,保證返回一個新的共享內存標識符,附加參數指定IPC對象存儲權限,如|0666
返回值:成功返回共享內存的標識符,出錯返回-1,并設置error錯誤位。
2、shmat
- 函數說明:連接共享內存標識符為shmid的共享內存,連接成功后把共享內存區對象映射到調用進程的地址空間
- 參數:
shmid: 共享內存標識符
shmaddr: 指定共享內存出現在進程內存地址的什么位置,通常指定為NULL,讓內核自己選擇一個合適的地址位置
shmflg: SHM_RDONLY 為只讀模式,其他參數為讀寫模式
返回值:成功返回附加好的共享內存地址,出錯返回-1,并設置error錯誤位
3、shmdt
-函數說明:與shmat函數相反,是用來斷開與共享內存附加點的地址,禁止本進程訪問此片共享內存,需要注意的是,該函數并不刪除所指定的共享內存區,而是將之前用shmat函數連接好的共享內存區脫離目前的進程
- 參數:shmddr 連接共享內存的起始地址
- 返回值:成功返回0,出錯返回-1,并設置error。
4、shmctl
- 函數說明:控制共享內存塊
參數:
shmid:共享內存標識符IPC_STAT:得到共享內存的狀態,把共享內存的shmid_ds結構賦值到buf所指向的buf中
IPC_SET:改變共享內存的狀態,把buf所指向的shmid_ds結構中的uid、gid、mode賦值到共享內存的shmid_ds結構內
IPC_RMID:刪除這塊共享內存
buf:共享內存管理結構體返回值:成功返回0,出錯返回-1,并設置error錯誤位。
實例(參考Linux-IPC之共享內存的實例)
以下為刪除共享內存實例
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <stdlib.h> #include <sys/shm.h> #define PROJ_ID 1 int main(int argc,char* argv[]) { if(argc!=2) { printf("error args\n"); return -1; } key_t skey; skey=ftok(argv[1],PROJ_ID); if(-1==skey) { perror("ftok"); return -1; } printf("the key is %d\n",skey); int shmid; shmid=shmget(skey,1<<12,0600|IPC_CREAT); if(-1==shmid) { perror("shmget"); return -1; } printf("the shmid is %d\n",shmid); int ret; if(-1==ret) { perror("shmctl_rmid"); return -1; } return 0; }- 運行結果為
管道(PIPE)
管道兩端可分別用描述字fd[0]以及fd[1]來描述,需要注意的是,管道的兩端是固定了任務的。即一端只能用于讀,由描述字fd[0]表示,稱其為管道讀端;另一端則只能用于寫,由描述字fd[1]來表示,稱其為管道寫端。如果試圖從管道寫端讀取數據,或者向管道讀端寫入數據都將導致錯誤發生。一般文件的I/O函數都可以用于管道,如close、read、write等等。
管道特點
- 只支持單向數據流;
- 只能用于具有親緣關系的進程之間;
- 沒有名字;
- 管道的緩沖區是有限的(管道制存在于內存中,在管道創建時,為緩沖區分配一個頁面大小);
管道所傳送的是無格式字節流,這就要求管道的讀出方和寫入方必須事先約定好數據的格式,比如多少字節算作一個消息(或命令、或記錄)等等;
函數
管道的創建
fd為兩個文件描述符:fd[0]用來讀,fd[1]用來寫。
1.父子進程的單向通信方式如下圖:
一個進程創建一個管道——>派生一個自身的拷貝——>父進程關閉管道的讀出端,子進程的寫入端關閉(上圖中的虛線)——>父子進程就建立了單向通信了。
實例
#include<stdio.h> #include<unistd.h> #include<string.h>int main() {int p[2];int pid;char *str = "HelloWorld";char buf[128];memset(buf,'\0',128);if(pipe(p) == -1){printf("function pipe() calls failed.");return -1;}if((pid=fork()) == -1) //創建一個子進程{printf("function fork() calls failed.\n");return -1;}else if(pid == 0) //在子進程中{printf("In sub : pid=%d\n",getpid());write(p[1],str,strlen(str)); //向無名管道中寫入str}else { //在父進程中printf("In father : pid=%d\n",getpid());read(p[0],buf,strlen(str)); //讀取無名管道printf("In father : buf=%s\n",buf);} }FIFO(有名管道)
- FIFO不同于管道之處在于它提供一個路徑名與之關聯,以FIFO的文件形式存在于文件系統中。這樣,即使與FIFO的創建進程不存在親緣關系的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信(能夠訪問該路徑的進程以及FIFO的創建進程之間)
FIFO的應用
(1)在shell中時常會用到管道(作為輸入輸入的重定向),在這種應用方式下,管道的創建對于用戶來說是透明的;
(2)用于具有親緣關系的進程間通信,用戶自己創建管道,并完成讀寫操作。
相關函數
- 有名管道的創建
該函數的第一個參數是一個普通的路徑名,也就是創建后FIFO的名字。第二個參數與打開普通文件的open()函數中的mode 參數相同。
實例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /*在這里設置打開管道文件的mode為只讀形式*/ #define FIFOMODE (O_CREAT | O_RDWR | O_NONBLOCK) #define OPENMODE (O_RDONLY | O_NONBLOCK) #define FIFO_SERVER "myfifo" int main(void) { char buf[100]; int fd; int readnum; /*創建有名管道,設置為可讀寫,無阻塞,如果不存在則按照指定權限創建*/ if ((mkfifo(FIFO_SERVER, FIFOMODE) < 0) && (errno != EEXIST)) { printf("cannot create fifoserver/n"); exit(1); } printf("Preparing for reading bytes... .../n"); /*打開有名管道,并設置非阻塞標志*/ if ((fd = open(FIFO_SERVER, OPENMODE)) < 0) { perror("open"); exit(1); } while (1) { /*初始化緩沖區*/ bzero(buf, sizeof(buf)); /*讀取管道數據*/ if ((readnum = read(fd, buf, sizeof(buf))) < 0) { if (errno == EAGAIN) { printf("no data yet/n"); } } /*如果讀到數據則打印出來,如果沒有數據,則忽略*/ if (readnum != 0) { buf[readnum] = '/0'; printf("read %s from FIFO_SERVER/n", buf); } sleep(1); } return 0; }信號
信號量是進程/線程同步的一種方式,有時候我們需要保護一段代碼,使它每次只能被一個執行進程/線程運行,這種工作就需要一個二進制開關;
有時候需要限制一段代碼可以被多少個進程/線程執行,這就需要用到關于計數信號量。信號量開關是二進制信號量的一種邏輯擴展,兩者實際調用的函數都是一樣。
工作原理
由于信號量只能進行兩種操作等待和發送信號,即P(sv)和V(sv),他們的行為是這樣的:
P(sv):如果sv的值大于零,就給它減1;如果它的值為零,就掛起該進程的執行
V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就給它加1.
相關函數
作用是創建一個新信號量集或取得一個已有信號量集
int semget(key_t key, int num_sems, int sem_flags);第一個參數key是整數值(唯一非零),不相關的進程可以通過它訪問一個信號量
第二個參數num_sems指定需要的信號量數目,它的值幾乎總是1。
第三個參數sem_flags是一組標志,當想要當信號量不存在時創建一個新的信號量,可以和值IPC_CREAT做按位或操作。設置了IPC_CREAT標志后,即使給出的鍵是一個已有信號量的鍵,也不會產生錯誤。而IPC_CREAT | IPC_EXCL則可以創建一個新的,唯一的信號量,如果信號量已存在,返回一個錯誤。
它的作用是改變信號量的值,原型為:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);該函數用來直接控制信號量信息,它的原型為:
- 前兩個參數與前面一個函數中的一樣,command通常是下面兩個值中的其中一個
- SETVAL:用來把信號量初始化為一個已知的值。p 這個值通過union
- semun中的val成員設置,其作用是在信號量第一次使用前對它進行設置。
- IPC_RMID:用于刪除一個已經無需繼續使用的信號量標識符。
實例
// 加入信號量操作后的程序#include "mysem.h" #include <stdio.h> #include <unistd.h>int main() {int semid = create_sems(10); // 創建一個包含10個信號量的信號集init_sems(semid, 0, 1); // 初始化編號為 0 的信號量值為1pid_t id = fork(); // 創建子進程if( id < 0){perror("fork");return -1;}else if (0 == id){// child int sem_id = get_sems();while(1){P(sem_id, 0); // 對該信號量集中的0號信號 做P操作printf("你");fflush(stdout);sleep(1);printf("好");printf(":");fflush(stdout);sleep(1);V(sem_id, 0);}}else{// fatherwhile(1){P(semid,0);printf("在");sleep(1);printf("嗎");printf("?");fflush(stdout);V(semid, 0);}wait(NULL);}destroy_sems(semid);return 0; }消息隊列
消息隊列就是一個消息的鏈表。可以把消息看作一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的進程可以向其中按照一定的規則添加新消息;對消息隊列有讀權限的進程則可以從消息隊列中讀走消息
相關函數
1、ftok函數
#include <sys/ipc.h> #include <sys/types.h> key_t ftok(const char* path, int id);- ftok 函數把一個已存在的路徑名和一個整數標識轉換成一個key_t值,即IPC關鍵字
- path 參數就是你指定的文件名(已經存在的文件名),一般使用當前目錄。當產生鍵時,只使用id參數的低8位。
id 是子序號, 只使用8bit (1-255) - 返回值:若成功返回鍵值,若出錯返回(key_t)-1
在一般的UNIX實現中,是將文件的索引節點號取出(inode),前面加上子序號的到key_t的返回值
2、msgget函數
#include <sys/msg.h> #include <sys/ipc.h> int msgget(key_t key, int msgflag);- msgget 通常是調用的第一個函數,功能是創建一個新的或已經存在的消息隊列。此消息隊列與key相對應。
- key 參數 即ftok函數生成的關鍵字
flag參數 :
IPC_CREAT: 如果IPC不存在,則創建一個IPC資源,否則打開已存在的IPC。
IPC_EXCL :只有在共享內存不存在的時候,新的共享內存才建立,否則就產生錯誤。
IPC_EXCL與IPC_CREAT一起使用,表示要創建的消息隊列已經存在。如果該IPC資源存在,則返回-1。
IPC_EXCL標識本身沒有太大的意義,但是和IPC_CREAT標志一起使用可以用來保證所得的對象時新建的,而不是打開已有的對象。
- 返回值 若成功返回消息隊列ID,若出錯則返回-1
3、msgsnd函數和msgrcv函數
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflag); ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int msgflag);- msgsnd 將數據放到消息隊列中 msgrcv 從消息隊列中讀取數據
- msqid:消息隊列的識別碼
- msgp:指向消息緩沖區的指針,用來暫時存儲發送和接受的消息。是一個允許用戶定義的通用結構
4、msgctl函數
#include <sys/types.g> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);- msgctl 函數 可以直接控制消息隊列的行為
- msqid 消息隊列id
- cmd :命令
- IPC_STAT 讀取消息隊列的數據結構msqid_ds, 并將其存儲在 buf指定的地址中
- IPC_SET 設置消息隊列的數據結構msqid_ds 中的ipc_perm元素的值,這個值取自buf 參數
- IPC_RMID 從內核中移除消息隊列。
返回值:如果成功返回0,失敗返回-1
實例
消息發送端:send.c
1 /*send.c*/ 2 #include <stdio.h> 3 #include <sys/types.h> 4 #include <sys/ipc.h> 5 #include <sys/msg.h> 6 #include <errno.h> 7 8 #define MSGKEY 1024 9 10 struct msgstru 11 { 12 long msgtype; 13 char msgtext[2048]; 14 }; 15 16 main() 17 { 18 struct msgstru msgs; 19 int msg_type; 20 char str[256]; 21 int ret_value; 22 int msqid; 23 24 msqid=msgget(MSGKEY,IPC_EXCL); /*檢查消息隊列是否存在*/ 25 if(msqid < 0){ 26 msqid = msgget(MSGKEY,IPC_CREAT|0666);/*創建消息隊列*/ 27 if(msqid <0){ 28 printf("failed to create msq | errno=%d [%s]\n",errno,strerror(errno)); 29 exit(-1); 30 } 31 } 32 33 while (1){ 34 printf("input message type(end:0):"); 35 scanf("%d",&msg_type); 36 if (msg_type == 0) 37 break; 38 printf("input message to be sent:"); 39 scanf ("%s",str); 40 msgs.msgtype = msg_type; 41 strcpy(msgs.msgtext, str); 42 /* 發送消息隊列 */ 43 ret_value = msgsnd(msqid,&msgs,sizeof(struct msgstru),IPC_NOWAIT); 44 if ( ret_value < 0 ) { 45 printf("msgsnd() write msg failed,errno=%d[%s]\n",errno,strerror(errno)); 46 exit(-1); 47 } 48 } 49 msgctl(msqid,IPC_RMID,0); //刪除消息隊列 50 }消息接收端 receive.c
1 /*receive.c */ 2 #include <stdio.h> 3 #include <sys/types.h> 4 #include <sys/ipc.h> 5 #include <sys/msg.h> 6 #include <errno.h> 7 8 #define MSGKEY 1024 9 10 struct msgstru 11 { 12 long msgtype; 13 char msgtext[2048]; 14 }; 15 16 /*子進程,監聽消息隊列*/ 17 void childproc(){ 18 struct msgstru msgs; 19 int msgid,ret_value; 20 char str[512]; 21 22 while(1){ 23 msgid = msgget(MSGKEY,IPC_EXCL );/*檢查消息隊列是否存在 */ 24 if(msgid < 0){ 25 printf("msq not existed! errno=%d [%s]\n",errno,strerror(errno)); 26 sleep(2); 27 continue; 28 } 29 /*接收消息隊列*/ 30 ret_value = msgrcv(msgid,&msgs,sizeof(struct msgstru),0,0); 31 printf("text=[%s] pid=[%d]\n",msgs.msgtext,getpid()); 32 } 33 return; 34 } 35 36 void main() 37 { 38 int i,cpid; 39 40 /* create 5 child process */ 41 for (i=0;i<5;i++){ 42 cpid = fork(); 43 if (cpid < 0) 44 printf("fork failed\n"); 45 else if (cpid ==0) /*child process*/ 46 childproc(); 47 } 48 } 49ps:消息隊列與管道的區別
消息隊列與管道的區別以及有名管道相比,具有更大的靈活性,首先,它提供有格式字節流,有利于減少開發人員的工作量;
其次,消息具有類型,在 實際應用中,可作為優先級使用。這兩點是管道以及有名管道所不能比的。同樣,消息隊列可以在幾個進程間復用,而不管這幾個進程是否具有親緣關系,這一點與 有名管道很相似;但消息隊列是隨內核持續的,與有名管道(隨進程持續)相比,生命力更強,應用空間更大。
轉載于:https://www.cnblogs.com/ljq1997/p/8013220.html
總結
以上是生活随笔為你收集整理的2017-2018-1 20155320第十周课下作业-IPC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人机交互实验3
- 下一篇: 获取指定长度的随机字符串