linux_6.進程間通信
基本概念
進程:運行着的程序 進程間通信 :管道、信號量、共享內存、消息隊列、套接字(網絡編程) 臨界資源:同一時刻,只允許一個進程(線程)訪問的資源。 臨界區:訪問臨界資源的代碼段。 原子操作:不可被分割(中斷)的操作
管道
1. 分類: 有名管道 無名管道 2. 管道的實現方式:通過頭指針寫,尾指針讀 頭指針到尾指針爲管道現有數據 3. 有名和無名管道的區別:1.無名管道必須是父子進程之間才能使用,有名管道可在任意進程間使用 (tip:數據是在內存中存放,打開管道文件後,在內存中分配了一塊空間,讀寫數據就是對內存進行操作) 4. 傳輸方式:管道是半雙工(單工模式是指兩臺計算機傳輸信息時只准一臺機子發送數據,另一臺只准接受數據 ;半雙工模式是指兩臺計算機傳輸信息時同一時間內只准一臺計算機發送數據;全雙工模式是指兩臺計算機傳輸信息時不論什麼時間都可以任意傳輸數據) 5. 無名管道實現:fd[0]固定的讀端 fd[1]固定的寫端 pipe fork()之後文件描述符引用加 1 父進程關閉fd[0],子進程還可以使用 管道的寫端徹底關閉,讀端返回一個 0值 ; 管道的讀端徹底關閉,寫端引發異常,也就收到SIGPIPE 異常退出一般爲信號,某個事件觸發信號 6.管道特點:1. 寫端關閉,讀端停止返回0 2.兩端任一端未運行,則另一段阻塞 7.文件操作 :1.打開文件 2.或讀或寫 3.關閉文件 1. int open(const char *pathname, in flags); 第一個參數爲文件路徑,第二個參數爲O_RDONLY, O_WRONLY, or O_RDWR 2. int open(const char *pathname, int flags, mode_t mode); 前兩個參數同上,第三個參數一般創建文件時需要O_CREAT, O_EXCL,O_NOCTTY, and O_TRUNC. 3. ssize_t read(int fd, void *buf, size_t count); 返回值爲實際讀到的字節數,fd爲文件描述符,count爲期望讀到的字節數 4. ssize_t write(int fd, const void *buf, size_t count);返回值爲實際寫到的字節數,fd爲文件描述符,count爲期望讀到的字節數 8.tips: mkfifo FIFO 創建管道文件 exit(0)爲正常退出 exit(1)爲非正常退出 fd文件描述符從下標最小未用的開始 0 標準輸入 1 標準輸出 2 標準錯誤輸出 strlen()用來計算指定的字符串s 的長度,不包括結束字符"\0" length()包括結束字符"\0" 創建的管道文件放在磁盤中,打開管道文件的數據放在內存中,一旦關機,數據消失不保存 普通文件在磁盤中
代碼實現:
- 無名管道
1 #include< stdio.h> 2 #include< string.h> 3 #include< stdlib.h> 4 #include< assert.h> 5 #include< unistd.h> 6 #include< fcntl.h> 7 8 int main() 9 { 10 int fd[2] ; 11 pipe(fd); 12 13 pid_t pid = fork(); //fork之後父子進程的讀段和寫段均可使用,最後只關閉一次,不能將父子進程所有都關閉,無法完全結束進程 14 15 if(pid == 0) 16 { 17 close(fd[1]); //不用寫端,關閉寫段 18 char buff[128]= {0}; 19 int n = 0; 20 while(n = read(fd[0],buff,127) > 0) 21 { 22 printf("child buff=%s",buff); 23 memset(buff,0,128); 24 } 25 close(fd[0]); //用完讀端,再關閉讀段 26 } 27 else 28 { 29 close(fd[0]); //不用讀端,關閉讀段 30 char buff[128] = {0}; 31 while(1) 32 { 33 sleep(1); 34 printf("input:"); 35 fgets(buff,128,stdin); 36 if(strncmp(buff,"end",3)==0) 37 { 38 break; 39 } 40 write(fd[1],buff,strlen(buff)); 41 } 42 close(fd[1]);//用完寫端,關閉寫段 43 } 44 }
- 有名管道
寫端: 1 #include< stdio.h> 2 #include< string.h> 3 #include< stdlib.h> 4 #include< assert.h> 5 #include< unistd.h> 6 #include< fcntl.h> 7 8 int main() 9 { 10 int fd = open("fifo",O_WRONLY); 11 assert(fd != -1); 12 13 printf("fd=%d\n",fd); 14 while(1) 15 { 16 char buff[128]={0}; 17 printf("input:\n"); 18 19 fgets(buff,128,stdin); 20 21 if(strncmp(buff,"end",3)==0) 22 { 23 break; 24 } 25 write(fd,buff,strlen(buff)); 26 } 27 close(fd); 28 exit(0); 29 30 } 讀端: 1 #include< stdio.h> 2 #include< string.h> 3 #include< stdlib.h> 4 #include< assert.h> 5 #include< unistd.h> 6 #include< fcntl.h> 7 8 int main() 9 { 10 int fd = open("fifo",O_RDONLY); 11 assert(fd != -1); 12 printf("fd=%d\n",fd); 13 char buff[128]={0}; 14 int n = 0; 15 while((n = read(fd,buff,127))>0) 16 { 17 printf("read:(n=%d):%s",n,buff); 18 memset(buff,0,128); 19 } 20 close(fd); 21 exit(0); 22 23 }
- 信號量
- 定義:信號量:特殊的變量,只能取正整數值,並且+1(v操作 )和-1(p操作)是原子操作,如果信號量爲0,再進行-1操作時會阻塞。 作用:同步進程 方法:控制程序的推進速度
相關函數
<1 semget():全新創建一個信號量,或者獲取一個已有的信號量 int semget(key_t key, int nsems, int semflg);第一個參數 key 是整數值(唯一非零),不相關的進程可以通過它訪問一個信號量,它代表程序可能要使用的某個資源,程序對所有信號量的訪問都是間接的,程序先通過調用semget函數並提供一個鍵,再由系統生成一個相應的信號標識符(semget函數的返回值),只有 semget 函數才直接使用信號量鍵,所有其他的信號量函數使用由semget函數返回的信號量標識符。如果多個程序使用相同的key值,key將負責協調工作。 第二個參數 num_sems 指定需要的信號量數目,它的值幾乎總是1。 第三個參數 sem_flags 是一組標誌,當想要當信號量不存在時創建一個新的信號量,可以和值 IPC_CREAT 做按位或操作。設置了 IPC_CREAT 標誌後,即使給出的鍵是一個已有信號量的鍵,也不會產生錯誤。而IPC_CREAT | IPC_EXCL 則可以創建一個新的,唯一的信號量,如果信號量已存在,返回一個錯誤。 返回值:semget 函數成功返回一個相應信號標識符(非零),失敗返回-1.
<2 semop():對信號量值進行修改 +1 -1 int semop(int semid, struct sembuf *sops, unsigned nsops);
第一個參數 sem_id 是由 semget 返回的信號量標識符; 第二個參數 sembuf結構的定義如下:
struct sembuf{ short sem_num;//除非使用一組信號量,否則它爲0 short sem_op;//信號量在一次操作中需要改變的數據,通常是兩個數,一個是-1,即P(等待)操作,一個是+1,即V(發送信號)操作。 short sem_flg;//通常爲SEM_UNDO,使操作系統跟蹤信號,並在進程沒有釋放該信號量而終止時,操作系統釋放信號量 };
第三個參數默認值爲 1
<3 semctl():對信號量做控制,爲信號量賦初始值 int semctl(int semid, int semnum, int cmd, ...);
前兩個參數與前面一個函數中的一樣; 第三個參數 command通常是下面兩個值中的其中一個 SETVAL:用來把信號量初始化爲一個已知的值。p 這個值通過union semun中的val成員設置,其作用是在信號量第一次使用前對它進行設置。 IPC_RMID:用於刪除一個已經無需繼續使用的信號量標識符。 如果有第四個參數,它通常是一個union semum結構,定義如下: union semun{ int val; struct semid_ds *buf; unsigned short *arry; };
代碼
a.c 1 #include< stdio.h> 2 #include< stdlib.h> 3 #include< unistd.h> 4 #include"sem.h" 5 6 int main() 7 { 8 sem_init(); 9 int i=0; 10 for(;i<10;i++) 11 { 12 sem_p(); 13 printf("a"); 14 fflush(stdout); 15 int n=rand()%3; 16 // sleep(n); 17 18 printf("a"); 19 fflush(stdout); 20 sem_v(); 21 sleep(n); 22 } 23 sem_destroy(); 24 } b.c 1 #include< stdio.h> 2 #include< stdlib.h> 3 #include< unistd.h> 4 #include"sem.h" 5 6 int main() 7 { 8 sem_init(); 9 int i=0; 10 for(;i<10;i++) 11 { 12 sem_p(); 13 printf("b"); 14 fflush(stdout); 15 int n=rand()%3; 16 //sleep(n); 17 18 printf("b"); 19 fflush(stdout); 20 sem_v(); 21 sleep(n); 22 } 23 sem_destroy(); 24 } sem.c 1 #include"sem.h" 2 3 static int semid = 0; 4 void sem_init() 5 { 6 semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600); 7 if(semid == -1) 8 { 9 semid = semget((key_t)1234,1,IPC_CREAT|0600); 10 if(semid == -1) 11 { 12 perror("semget error"); 13 } 14 } 15 else 16 { 17 union semun a; 18 a.val = 1; 19 if(semctl(semid,0,SETVAL,a) == -1) 20 { 21 perror("semctl error"); 22 23 } 24 } 25 } 26 27 void sem_p() 28 { 29 struct sembuf buf; 30 buf.sem_num = 0; 31 buf.sem_op = -1; 32 buf.sem_flg = SEM_UNDO; 33 34 if(semop(semid,&buf,1)==-1) 35 { 36 perror("p error"); 37 } 38 } 40 void sem_v() 41 { 42 struct sembuf buf; 43 buf.sem_num = 0; 44 buf.sem_op = 1; 45 buf.sem_flg = SEM_UNDO; 46 47 if(semop(semid,&buf,1)==-1) 48 { 49 perror("v error"); 50 } 51 } 52 void sem_destroy() 53 { 54 55 if(semctl(semid,0,IPC_RMID) == -1) 56 { 57 perror("destroy error"); 58 } 59 } sem.h 1 #include< stdio.h> 2 #include< stdlib.h> 3 #include< string.h> 4 #include< unistd.h> 5 #include< sys/sem.h> 6 7 union semun 8 { 9 int val; 10 }; 11 void sem_init(); 12 void sem_p(); 13 void sem_v(); 14 void sem_destroy();
- 共享內存
- 實現原理:共享內存區域說白了就是多個進程共享的一塊物理內存地址。假設有10個進程將這塊區域映射到自己的虛擬地址上,那麼,這10個進程間就可以相互通信。由於是同一塊區域在10個進程的虛擬地址上,當第一個進程向這塊共享內存的虛擬地址中寫入數據時,其他9個進程也都會看到。因此共享內存是進程間通信的一種最快的方式。
相關函數
<1 int shmget(key_t key, size_t size, int shmflg); 創建共享內存空間,或者獲取已有的一塊共享內存空間 第一個參數,與信號量的semget函數一樣,程序需要提供一個參數key(非0整數),它有效地爲共享內存段命名,不相關的進程可以通過該函數的返回值訪問同一共享內存,它代表程序可能要使用的某個資源,程序對所有共享內存的訪問都是間接的,程序先通過調用shmget函數並提供一個鍵,再由系統生成一個相應的共享內存標識符(shmget函數的返回值) 第二個參數,size以字節爲單位指定需要共享的內存容量 第三個參數,shmflg是權限標誌,它的作用與open函數的mode參數一樣,如果要想在key標識的共享內存不存在時,創建它的話,可以與IPC_CREAT做或操作。共享內存的權限標誌與文件的讀寫權限一樣,舉例來說,0644,它表示允許一個進程創建的共享內存被內存創建者所擁有的進程向共享內存讀取和寫入數據,同時其他用戶創建的進程只能讀取共享內存。 返回值:shmget函數成功時返回一個與key相關的共享內存標識符(非負整數),用於後續的共享內存函數。調用失敗返回-1. <2 void* shmat(int shmid, const void *shmaddr, int shmflg); 把共享內存映射到當前的進程的虛擬地址空間中 第一個參數,shm_id是由shmget函數返回的共享內存標識。 第二個參數,shm_addr指定共享內存連接到當前進程中的地址位置,通常爲空,表示讓系統來選擇共享內存的地址。 第三個參數,shm_flg是一組標誌位,通常爲0。 調用成功時返回一個指向共享內存第一個字節的指針,如果調用失敗返回-1. <3 int shmdt(const void *shmaddr); 斷開映射 參數shmaddr是shmat函數返回的地址指針 返回值:調用成功時返回0,失敗時返回-1. <4 int shmctl(int shmid, int cmd, struct shmid_ds *buf); 移除共享內存空間 第一個參數,shm_id是shmget函數返回的共享內存標識符。 第二個參數,command是要採取的操作,它可以取下面的三個值 : IPC_STAT:把shmid_ds結構中的數據設置爲共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋shmid_ds的值。 IPC_SET:如果進程有足夠的權限,就把共享內存的當前關聯值設置爲shmid_ds結構中給出的值 IPC_RMID:刪除共享內存段 第三個參數,buf是一個結構指針,它指向共享內存模式和訪問權限的結構。 shmid_ds結構至少包括以下成員: struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
優點:
採用共享內存進行通信的一個主要好處是效率高,因爲進程可以直接讀寫內存,而不需要任何數據的拷貝。而對於像管道和消息隊裏等通信方式,則需要再內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次:一次從輸入文件到共享內存區,另一次從共享內存到輸出文件。 一般而言,進程之間在共享內存時,並不總是讀寫少量數據後就解除映射,有新的通信時在重新建立共享內存區域;而是保持共享區域,直到通信完畢爲止。 這樣,數據內容一直保存在共享內存中,並沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回文件,因此,採用共享內存的通信方式效率非常高。
代碼實現
Main.c 的代碼: #include< stdio.h> #include< sys/shm.h> #include< string.h> #include< assert.h #include< stdlib.h> void main() { int shmid = shmget((key_t)1234, 256, IPC_CREAT|0600); assert(shmid != -1); char *s = (char *)shmat(shmid, NULL, 0); assert((int)s != -1); strcpy(s, "hello"); shmdt(s); } Test.c 代碼: #include < stdio.h> #include < sys/shm.h> #include < string.h> #include < assert.h> #include < stdlib.h> void main() { int shmid = shmget((key_t)1234, 256, IPC_CREAT|0600); assert(shmid != -1); char *s = (char *)shmat(shmid, NULL, 0); assert((int)s != -1); printf(“s = %s\n”, s); shmdt(s); }
tips: 共享內存沒有解決同步問題,如果寫成循環輸入輸出,需要考慮同步問題
模型類似生產者、消費者 信號量:S1(0) S2(1) A B pS2 pS1 fgets(s) printf(buff) strcpy(buff,s) vS1 vS2
- 消息隊列
定義
消息隊列,就是一個消息的鏈表,是一系列保存在內核中消息的列表。用戶進程可以向消息隊列添加消息,也可以向消息隊列讀取消息。可以把消息看做一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的進程可以向消息隊列中按照一定的規則添加新消息,對消息隊列有讀權限的進程可以從消息隊列中讀取消息。優點
消息隊列與管道通信相比,其優勢是對每個消息指定特定的消息類型,接收的時候不需要按照隊列次序,而是可以根據自定義條件接收特定類型的消息。主要函數
<1 int msgget(key_t, key, int msgflg); 第一個參數,與其他的IPC機制一樣,程序必須提供一個鍵來命名某個特定的消息隊列。 第二個參數,msgflg是一個權限標誌,表示消息隊列的訪問權限,它與文件的訪問權限一樣。msgflg可以與IPC_CREAT做或操作,表示當key所命名的消息隊列不存在時創建一個消息隊列,如果key所命名的消息隊列存在時,IPC_CREAT標誌會被忽略,而只返回一個標識符。 返回值:它返回一個以key命名的消息隊列的標識符(非零整數),失敗時返回-1. <2 int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg); 第一個參數,msgid是由msgget函數返回的消息隊列標識符。 第二個參數,msg_ptr是一個指向準備發送消息的指針,但是消息的數據結構卻有一定的要求,指針msg_ptr所指向的消息結構一定要是以一個長整型成員變量開始的結構體,接收函數將用這個成員來確定消息的類型。所以消息結構要定義成這樣: struct my_message{ long int message_type; /* The data you wish to transfer*/ }; 第三個參數,msg_sz是msg_ptr指向的消息的長度,注意是消息的長度,而不是整個結構體的長度,也就是說msg_sz是不包括長整型消息類型成員變量的長度。 第四個參數,msgflg用於控制當前消息隊列滿或隊列消息到達系統範圍的限制時將要發生的事情。 返回值:如果調用成功,消息數據的一分副本將被放到消息隊列中,並返回0,失敗時返回-1. <3 int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg); 前三個參數同上 第四個參數,msgtype可以實現一種簡單的接收優先級。如果msgtype爲0,就獲取隊列中的第一個消息。如果它的值大於零,將獲取具有相同消息類型的第一個信息。如果它小於零,就獲取類型等於或小於msgtype的絕對值的第一個消息。 第五個參數,msgflg用於控制當隊列中沒有相應類型的消息可以接收時將發生的事情。 返回值:調用成功時,該函數返回放到接收緩存區中的字節數,消息被複制到由msg_ptr指向的用戶分配的緩存區中,然後刪除消息隊列中的對應消息。失敗時返回-1. <4 int msgctl(int msgid, int command, struct msgid_ds *buf); 第一個參數,msgid是由msgget函數返回的消息隊列標識符。 第二個參數,command是將要採取的動作,它可以取3個值, IPC_STAT:把msgid_ds結構中的數據設置爲消息隊列的當前關聯值,即用消息隊列的當前關聯值覆蓋msgid_ds的值。 IPC_SET:如果進程有足夠的權限,就把消息列隊的當前關聯值設置爲msgid_ds結構中給出的值 IPC_RMID:刪除消息隊列 第三個參數,buf是指向msgid_ds結構的指針,它指向消息隊列模式和訪問權限的結構。msgid_ds結構至少包括以下成員: struct msgid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; }; 返回值:成功時返回0,失敗時返回-1.
代碼
接收信息的程序源文件爲msgreceive.c的源代碼爲: #include < unistd.h> #include < stdlib.h> #include < stdio.h> #include < string.h> #include < errno.h> #include < sys/msg.h> struct msg_st { long int msg_type; char text[BUFSIZ]; }; int main() { int running = 1; int msgid = -1; struct msg_st data; long int msgtype = 0; //注意1 //建立消息隊列 msgid = msgget((key_t)1234, 0666 | IPC_CREAT); if(msgid == -1) { fprintf(stderr, "msgget failed with error: %d\n", errno); exit(EXIT_FAILURE); } //從隊列中獲取消息,直到遇到end消息爲止 while(running) { if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1) { fprintf(stderr, "msgrcv failed with errno: %d\n", errno); exit(EXIT_FAILURE); } printf("You wrote: %s\n",data.text); //遇到end結束 if(strncmp(data.text, "end", 3) == 0) running = 0; } //刪除消息隊列 if(msgctl(msgid, IPC_RMID, 0) == -1) { fprintf(stderr, "msgctl(IPC_RMID) failed\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } 發送信息的程序的源文件msgsend.c的源代碼爲: #include < unistd.h> #include < stdlib.h> #include < stdio.h> #include < string.h> #include < sys/msg.h> #include < errno.h> #define MAX_TEXT 512 struct msg_st { long int msg_type; char text[MAX_TEXT]; }; int main() { int running = 1; struct msg_st data; char buffer[BUFSIZ]; int msgid = -1; //建立消息隊列 msgid = msgget((key_t)1234, 0666 | IPC_CREAT); if(msgid == -1) { fprintf(stderr, "msgget failed with error: %d\n", errno); exit(EXIT_FAILURE); } //向消息隊列中寫消息,直到寫入end while(running) { //輸入數據 printf("Enter some text: "); fgets(buffer, BUFSIZ, stdin); data.msg_type = 1; //注意2 strcpy(data.text, buffer); //向隊列發送數據 if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1) { fprintf(stderr, "msgsnd failed\n"); exit(EXIT_FAILURE); } //輸入end結束輸入 if(strncmp(buffer, "end", 3) == 0) running = 0; sleep(1); } exit(EXIT_SUCCESS); }
tips:部分見博客 http://blog.csdn.net/ljianhui/article/details/10287879