本節(jié)主要講Linux進程間通信
在Linux中较曼,各個進程都共享內(nèi)核空間,因此LInux進程通信中的管道,消息隊列等都將相關(guān)數(shù)據(jù)保存在內(nèi)核空間中必逆。進程間通信也可以在用戶空間進行,例如揽乱,同一個進程中的線程可以使用相同的用戶空間地址就可以通信名眉。不同進程之間的空間盡管相互隔離,但也可以通過共享內(nèi)存的方式進行通信凰棉,共享內(nèi)存的申請需要內(nèi)核來完成损拢。
進程間通信可以分為:
- 管道:無名管道(父子進程或兩進程有同一個祖先)和有名管道(fifo,用于不同進程間通信)
- 信號:僅用于向一個進程或者進程組發(fā)送某個事件已發(fā)送的標(biāo)志
- 信號量:用于用戶態(tài)代碼的時序控制撒犀。
- 消息隊列:讓進程在預(yù)定義的消息隊列中寫消息和讀消息實現(xiàn)進程之間的通信
- 共享內(nèi)存:使兩個不同進程的內(nèi)存空間映射到相同的物理區(qū)域探橱,這種方式比較高效
- 套接字:不同進程之間通過socket通信申屹。
1 無名管道
1. ls|more 的內(nèi)部實現(xiàn)
- shell調(diào)用pipe()系統(tǒng)調(diào)用,創(chuàng)建一個管道并返回一對文件描述符隧膏。 3讀哗讥,4寫
- shell調(diào)用兩次fork()創(chuàng)建兩個子進程,使用exec()將兩個子進程替換為ls和more進程
- shell使用close()關(guān)閉文件并釋放描述符3胞枕,4
- ls使用dup2(4,1)將文件描述符復(fù)制到標(biāo)準(zhǔn)輸出上杆煞,使用close()關(guān)閉文件并釋放描述符3,4
-
more使用dup2(,0)將文件描述符復(fù)制到標(biāo)準(zhǔn)輸入上腐泻,使用close()關(guān)閉文件并釋放描述符3决乎,4
2. pipe調(diào)用
#include <unistd.h>
int pipe(int file_descriptor[2]);
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
int data_processed = 0;
int filedes[2];
const char data[] = "Hello pipe!";
char buffer[BUFSIZ + 1];
pid_t pid;
//清空緩沖區(qū)
memset(buffer, '\0', sizeof(buffer));
if(pipe(filedes) == 0)
{
//創(chuàng)建管道成功
//通過調(diào)用fork創(chuàng)建子進程
pid = fork();
if(pid == -1)
{
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if(pid == 0)
{
//子進程中
//讀取數(shù)據(jù)
data_processed = read(filedes[0], buffer, BUFSIZ);
printf("Read %d bytes: %s\n", data_processed, buffer);
exit(EXIT_SUCCESS);
}
else
{
//父進程中
//寫數(shù)據(jù)
data_processed = write(filedes[1], data, strlen(data));
printf("Wrote %d bytes: %s\n", data_processed, data);
//休眠2秒,主要是為了等子進程先結(jié)束派桩,這樣做也只是純粹為了輸出好看而已
//父進程其實沒有必要等等子進程結(jié)束
sleep(2);
exit(EXIT_SUCCESS);
}
}
exit(EXIT_FAILURE);
}
3. popen函數(shù)和pclose函數(shù)
popen函數(shù)和pclose函數(shù)對pipe()系統(tǒng)調(diào)用進行了封裝构诚。
#include <stdio.h>
FILE* popen (const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);
poen函數(shù)允許一個程序?qū)⒘硪粋€程序作為新進程來啟動,并可以傳遞數(shù)據(jù)給它或者通過它接收數(shù)據(jù)铆惑。command是要運行的程序名和相應(yīng)的參數(shù)范嘱。open_mode只能是"r(只讀)"和"w(只寫)"的其中之一。
很多時候员魏,我們根本就不知道輸出數(shù)據(jù)的長度丑蛤,為了避免定義一個非常大的數(shù)組作為緩沖區(qū),我們可以以塊的方式來發(fā)送數(shù)據(jù)撕阎,一次讀取一個塊的數(shù)據(jù)并發(fā)送一個塊的數(shù)據(jù)受裹,直到把所有的數(shù)據(jù)都發(fā)送完。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
FILE *read_fp = NULL;
FILE *write_fp = NULL;
char buffer[BUFSIZ + 1];
int chars_read = 0;
//初始化緩沖區(qū)
memset(buffer, '\0', sizeof(buffer));
//打開ls和grep進程
read_fp = popen("ls -l", "r");
write_fp = popen("grep rwxrwxr-x", "w");
//兩個進程都打開成功
if(read_fp && write_fp)
{
//讀取一個數(shù)據(jù)塊
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while(chars_read > 0)
{
buffer[chars_read] = '\0';
//把數(shù)據(jù)寫入grep進程
fwrite(buffer, sizeof(char), chars_read, write_fp);
//還有數(shù)據(jù)可讀虏束,循環(huán)讀取數(shù)據(jù)棉饶,直到讀完所有數(shù)據(jù)
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
}
//關(guān)閉文件流
pclose(read_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
4.總結(jié)
popen函數(shù)優(yōu)缺點
優(yōu)點 在Linux中所有的參數(shù)擴展都是由shell來完成的。所以在啟動程序(command中的命令程序)之前先啟動shell來分析命令字符串镇匀,也就可以使各種shell擴展(如通配符)在程序啟動之前就全部完成照藻,這樣我們就可以通過popen啟動非常復(fù)雜的shell命令。 缺點::對于每個popen調(diào)用坑律,不僅要啟動一個被請求的程序岩梳,還要啟動一個shell,即每一個popen調(diào)用將啟動兩個進程晃择,從效率和資源的角度看冀值,popen函數(shù)的調(diào)用比正常方式要慢一些。從函數(shù)的原型我們可以看到宫屠,它跟popen函數(shù)的一個重大區(qū)別是列疗,popen函數(shù)是基于文件流(FILE)工作的,而pipe是基于文件描述符工作的浪蹂,所以在使用pipe后抵栈,數(shù)據(jù)必須要用底層的read和write調(diào)用來讀取和發(fā)送告材。
現(xiàn)在有這樣一個問題,假如父進程向管道file_pipe1寫數(shù)據(jù)古劲,而子進程在管道file_pipe[0]中讀取數(shù)據(jù)斥赋,當(dāng)父進程沒有向file_pipe1寫數(shù)據(jù)時,子進程則沒有數(shù)據(jù)可讀产艾,則子進程會發(fā)生什么呢疤剑?再者父進程把file_pipe1關(guān)閉了,子進程又會有什么反應(yīng)呢闷堡?
當(dāng)寫數(shù)據(jù)的管道沒有關(guān)閉隘膘,而又沒有數(shù)據(jù)可讀時,read調(diào)用通常會阻塞杠览,但是當(dāng)寫數(shù)據(jù)的管道關(guān)閉時弯菊,read調(diào)用將會返回0而不是阻塞。注意踱阿,這與讀取一個無效的文件描述符不同管钳,read一個無效的文件描述符返回-1。
2. 信號量
為了防止出現(xiàn)因多個程序同時訪問一個共享資源而引發(fā)的一系列問題扫茅,我們需要一種方法蹋嵌,它可以通過生成并使用令牌來授權(quán)育瓜,在任一時刻只能有一個執(zhí)行線程訪問代碼的臨界區(qū)域葫隙。臨界區(qū)域是指執(zhí)行數(shù)據(jù)更新的代碼需要獨占式地執(zhí)行。而信號量就可以提供這樣的一種訪問機制躏仇,讓一個臨界區(qū)同一時間只有一個線程在訪問它恋脚,也就是說信號量是用來調(diào)協(xié)進程對共享資源的訪問的。
信號量是一個特殊的變量焰手,程序?qū)ζ湓L問都是原子操作糟描,且只允許對它進行等待(即P(信號變量))和發(fā)送(即V(信號變量))信息操作。最簡單的信號量是只能取0和1的變量书妻,這也是信號量最常見的一種形式船响,叫做二進制信號量。而可以取多個正整數(shù)的信號量被稱為通用信號量躲履。這里主要討論二進制信號量见间。
1. semget函數(shù)
它的作用是創(chuàng)建一個新信號量或取得一個已有信號量,原型為:
int semget(key_t key, int num_sems, int sem_flags);
第一個參數(shù)key是整數(shù)值(唯一非零)工猜,用來標(biāo)識一個全局唯一的信號量集米诉,要通過信號量通信的進程需要使用相同的鍵值來創(chuàng)建/獲取該信號量。
第二個參數(shù)num_sems指定需要的信號量數(shù)目篷帅,它的值幾乎總是1史侣。
第三個參數(shù)sem_flags是一組標(biāo)志拴泌,當(dāng)想要當(dāng)信號量不存在時創(chuàng)建一個新的信號量,可以和值IPC_CREAT做按位或操作惊橱。設(shè)置了IPC_CREAT標(biāo)志后蚪腐,即使給出的鍵是一個已有信號量的鍵,也不會產(chǎn)生錯誤税朴。而IPC_CREAT | IPC_EXCL則可以創(chuàng)建一個新的削茁,唯一的信號量,如果信號量已存在掉房,返回一個錯誤驶忌。
2.semop函數(shù)
它的作用是改變信號量的值,原型為:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget返回的信號量標(biāo)識符览妖,sembuf結(jié)構(gòu)的定義如下:
struct sembuf{
short sem_num;//除非使用一組信號量站蝠,否則它為0
short sem_op;//信號量在一次操作中需要改變的數(shù)據(jù),通常是兩個數(shù)哪亿,一個是-1粥烁,即P(等待)操作,
//一個是+1蝇棉,即V(發(fā)送信號)操作讨阻。
short sem_flg;//通常為SEM_UNDO,使操作系統(tǒng)跟蹤信號,
//并在進程沒有釋放該信號量而終止時篡殷,操作系統(tǒng)釋放信號量
};
3.semctl函數(shù)
該函數(shù)用來直接控制信號量信息钝吮,它的原型為:
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四個參數(shù),它通常是一個union semum結(jié)構(gòu)板辽,定義如下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
command通常是下面兩個值中的其中一個
SETVAL:用來把信號量初始化為一個已知的值奇瘦。這個值通過union semun中的val成員設(shè)置,其作用是在信號量第一次使用前對它進行設(shè)置劲弦。
IPC_RMID:用于刪除一個已經(jīng)無需繼續(xù)使用的信號量標(biāo)識符耳标。
4. 使用信號量通信實例
如果程序是第一次被調(diào)用(為了區(qū)分,第一次調(diào)用程序時帶一個要輸出到屏幕中的字符作為一個參數(shù))邑跪,則需要調(diào)用set_semvalue函數(shù)初始化信號并將message字符設(shè)置為傳遞給程序的參數(shù)的第一個字符次坡,同時第一個啟動的進程還負責(zé)信號量的刪除工作。如果不刪除信號量画畅,它將繼續(xù)在系統(tǒng)中存在砸琅,即使程序已經(jīng)退出,它可能在你下次運行此程序時引發(fā)問題夜赵,而且信號量是一種有限的資源明棍。
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
static int sem_id = 0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
int main(int argc, char *argv[])
{
char message = 'X';
int i = 0;
//創(chuàng)建信號量
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
if(argc > 1)
{
//程序第一次被調(diào)用,初始化信號量
if(!set_semvalue())
{
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
//設(shè)置要輸出到屏幕中的信息寇僧,即其參數(shù)的第一個字符
message = argv[1][0];
sleep(2);
}
for(i = 0; i < 10; ++i)
{
//進入臨界區(qū)
if(!semaphore_p())
exit(EXIT_FAILURE);
//向屏幕中輸出數(shù)據(jù)
printf("%c", message);
//清理緩沖區(qū)摊腋,然后休眠隨機時間
fflush(stdout);
sleep(rand() % 3);
//離開臨界區(qū)前再一次向屏幕輸出數(shù)據(jù)
printf("%c", message);
fflush(stdout);
//離開臨界區(qū)沸版,休眠隨機時間后繼續(xù)循環(huán)
if(!semaphore_v())
exit(EXIT_FAILURE);
sleep(rand() % 2);
}
sleep(10);
printf("\n%d - finished\n", getpid());
if(argc > 1)
{
//如果程序是第一次被調(diào)用,則在退出前刪除信號量
sleep(3);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
static int set_semvalue()
{
//用于初始化信號量兴蒸,在使用信號量前必須這樣做
union semun sem_union;
sem_union.val = 1;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
return 0;
return 1;
}
static void del_semvalue()
{
//刪除信號量
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "Failed to delete semaphore\n");
}
static int semaphore_p()
{
//對信號量做減1操作视粮,即等待P(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;//P()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_p failed\n");
return 0;
}
return 1;
}
static int semaphore_v()
{
//這是一個釋放操作,它使信號量變?yōu)榭捎贸鹊剩窗l(fā)送信號V(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;//V()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_v failed\n");
return 0;
}
return 1;
}
5.總結(jié)
信號量是一個特殊的變量蕾殴,程序?qū)ζ湓L問都是原子操作,且只允許對它進行等待(即P(信號變量))和發(fā)送(即V(信號變量))信息操作岛啸。我們通常通過信號來解決多個進程對同一資源的訪問競爭的問題钓觉,使在任一時刻只能有一個執(zhí)行線程訪問代碼的臨界區(qū)域,也可以說它是協(xié)調(diào)進程間的對同一資源的訪問權(quán)坚踩,也就是用于同步進程的荡灾。
3. 共享內(nèi)存
共享內(nèi)存就是允許兩個不相關(guān)的進程訪問同一段物理內(nèi)存,是最高效的IPC通信機制瞬铸。但是批幌,共享內(nèi)存并未提供同步機制,也就是說嗓节,在第一個進程結(jié)束對共享內(nèi)存的寫操作之前荧缘,并無自動機制可以阻止第二個進程開始對它進行讀取。所以我們通常需要用其他的機制來同步對共享內(nèi)存的訪問拦宣,例如前面說到的信號量截粗。
1.shmget函數(shù)
該函數(shù)用來創(chuàng)建共享內(nèi)存,它的原型為:
int shmget(key_t key, size_t size, int shmflg);
key恢着,與信號量的semget函數(shù)一樣桐愉,程序需要提供一個參數(shù)key(非0整數(shù))财破,它有效地為共享內(nèi)存段命名掰派,shmget函數(shù)成功時返回一個與key相關(guān)的共享內(nèi)存標(biāo)識符(非負整數(shù)),用于后續(xù)的共享內(nèi)存函數(shù)左痢。調(diào)用失敗返回-1.
size以字節(jié)為單位指定需要共享的內(nèi)存容量
shmflg是權(quán)限標(biāo)志靡羡,它的作用與open函數(shù)的mode參數(shù)一樣,如果要想在key標(biāo)識的共享內(nèi)存不存在時俊性,創(chuàng)建它的話略步,可以與IPC_CREAT做或操作。共享內(nèi)存的權(quán)限標(biāo)志與文件的讀寫權(quán)限一樣定页,舉例來說趟薄,0644,它表示允許一個進程創(chuàng)建的共享內(nèi)存被內(nèi)存創(chuàng)建者所擁有的進程向共享內(nèi)存讀取和寫入數(shù)據(jù),同時其他用戶創(chuàng)建的進程只能讀取共享內(nèi)存典徊。
2.shmat函數(shù)
第一次創(chuàng)建完共享內(nèi)存時杭煎,它還不能被任何進程訪問恩够,shmat函數(shù)的作用就是用來啟動對該共享內(nèi)存的訪問,并把共享內(nèi)存連接到當(dāng)前進程的地址空間羡铲。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
shm_id是由shmget函數(shù)返回的共享內(nèi)存標(biāo)識蜂桶。
shm_addr指定共享內(nèi)存連接到當(dāng)前進程中的地址位置,通常為空也切,表示讓系統(tǒng)來選擇共享內(nèi)存的地址扑媚。
shm_flg是一組標(biāo)志位,通常為0雷恃。
3. shmdt函數(shù)
該函數(shù)用于將共享內(nèi)存從當(dāng)前進程中分離疆股。注意,將共享內(nèi)存分離并不是刪除它倒槐,只是使該共享內(nèi)存對當(dāng)前進程不再可用押桃。它的原型如下:
int shmdt(const void *shmaddr);
參數(shù)shmaddr是shmat函數(shù)返回的地址指針,調(diào)用成功時返回0导犹,失敗時返回-1.
4.shmctl函數(shù)
與信號量的semctl函數(shù)一樣唱凯,用來控制共享內(nèi)存,它的原型如下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shm_id是shmget函數(shù)返回的共享內(nèi)存標(biāo)識符
command是要采取的操作谎痢,它可以取下面的三個值 :
- IPC_STAT:把shmid_ds結(jié)構(gòu)中的數(shù)據(jù)設(shè)置為共享內(nèi)存的當(dāng)前關(guān)聯(lián)值磕昼,即用共享內(nèi)存的當(dāng)前關(guān)聯(lián)值覆蓋shmid_ds的值。
- IPC_SET:如果進程有足夠的權(quán)限节猿,就把共享內(nèi)存的當(dāng)前關(guān)聯(lián)值設(shè)置為shmid_ds結(jié)構(gòu)中給出的
- IPC_RMID:刪除共享內(nèi)存段
buf是一個結(jié)構(gòu)指針票从,它指向共享內(nèi)存模式和訪問權(quán)限的結(jié)構(gòu)。
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
5. 共享內(nèi)存實例
shmdata.h的源代碼如下:
#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{
int written;//作為一個標(biāo)志滨嘱,非0:表示可讀峰鄙,0表示可寫
char text[TEXT_SZ];//記錄寫入和讀取的文本
};
#endif
源文件shmread.c的源代碼如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h"
int main()
{
int running = 1;//程序是否繼續(xù)運行的標(biāo)志
void *shm = NULL;//分配的共享內(nèi)存的原始首地址
struct shared_use_st *shared;//指向shm
int shmid;//共享內(nèi)存標(biāo)識符
//創(chuàng)建共享內(nèi)存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//將共享內(nèi)存連接到當(dāng)前進程的地址空間
shm = shmat(shmid, 0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %X\n", (int)shm);
//設(shè)置共享內(nèi)存
shared = (struct shared_use_st*)shm;
shared->written = 0;
while(running)//讀取共享內(nèi)存中的數(shù)據(jù)
{
//沒有進程向共享內(nèi)存定數(shù)據(jù)有數(shù)據(jù)可讀取
if(shared->written != 0)
{
printf("You wrote: %s", shared->text);
sleep(rand() % 3);
//讀取完數(shù)據(jù),設(shè)置written使共享內(nèi)存段可寫
shared->written = 0;
//輸入了end太雨,退出循環(huán)(程序)
if(strncmp(shared->text, "end", 3) == 0)
running = 0;
}
else//有其他進程在寫數(shù)據(jù)吟榴,不能讀取數(shù)據(jù)
sleep(1);
}
//把共享內(nèi)存從當(dāng)前進程中分離
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
//刪除共享內(nèi)存
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
源文件shmwrite.c的源代碼如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shmdata.h"
int main()
{
int running = 1;
void *shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1];//用于保存輸入的文本
int shmid;
//創(chuàng)建共享內(nèi)存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//將共享內(nèi)存連接到當(dāng)前進程的地址空間
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n", (int)shm);
//設(shè)置共享內(nèi)存
shared = (struct shared_use_st*)shm;
while(running)//向共享內(nèi)存中寫數(shù)據(jù)
{
//數(shù)據(jù)還沒有被讀取,則等待數(shù)據(jù)被讀取,不能向共享內(nèi)存中寫入文本
while(shared->written == 1)
{
sleep(1);
printf("Waiting...\n");
}
//向共享內(nèi)存中寫入數(shù)據(jù)
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
//寫完數(shù)據(jù)囊扳,設(shè)置written使共享內(nèi)存段可讀
shared->written = 1;
//輸入了end吩翻,退出循環(huán)(程序)
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
//把共享內(nèi)存從當(dāng)前進程中分離
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
6. 總結(jié)
共享內(nèi)存的優(yōu)缺點:
1、優(yōu)點:我們可以看到使用共享內(nèi)存進行進程間的通信真的是非常方便锥咸,而且函數(shù)的接口也簡單狭瞎,數(shù)據(jù)的共享還使進程間的數(shù)據(jù)不用傳送,而是直接訪問內(nèi)存搏予,也加快了程序的效率熊锭。同時,它也不像匿名管道那樣要求通信的進程有一定的父子關(guān)系。
2碗殷、缺點:共享內(nèi)存沒有提供同步的機制劣针,這使得我們在使用共享內(nèi)存進行進程間通信時,往往要借助其他的手段來進行進程間的同步工作亿扁。
4. 消息隊列
1. msgget函數(shù)
該函數(shù)用來創(chuàng)建和訪問一個消息隊列捺典。它的原型為:
int msgget(key_t, key, int msgflg);
與其他的IPC機制一樣,程序必須提供一個鍵來命名某個特定的消息隊列从祝。msgflg是一個權(quán)限標(biāo)志襟己,表示消息隊列的訪問權(quán)限,它與文件的訪問權(quán)限一樣牍陌。msgflg可以與IPC_CREAT做或操作擎浴,表示當(dāng)key所命名的消息隊列不存在時創(chuàng)建一個消息隊列,如果key所命名的消息隊列存在時毒涧,IPC_CREAT標(biāo)志會被忽略贮预,而只返回一個標(biāo)識符。
它返回一個以key命名的消息隊列的標(biāo)識符(非零整數(shù))契讲,失敗時返回-1.
2.msgsnd函數(shù)
該函數(shù)用來把消息添加到消息隊列中仿吞。它的原型為:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函數(shù)返回的消息隊列標(biāo)識符。
msg_ptr是一個指向準(zhǔn)備發(fā)送消息的指針捡偏,但是消息的數(shù)據(jù)結(jié)構(gòu)卻有一定的要求唤冈,指針msg_ptr所指向的消息結(jié)構(gòu)一定要是以一個長整型成員變量開始的結(jié)構(gòu)體,接收函數(shù)將用這個成員來確定消息的類型银伟。所以消息結(jié)構(gòu)要定義成這樣:
struct my_message{
long int message_type;
/* The data you wish to transfer*/
};
msg_sz是msg_ptr指向的消息的長度你虹,注意是消息的長度,而不是整個結(jié)構(gòu)體的長度彤避,也就是說msg_sz是不包括長整型消息類型成員變量的長度傅物。
msgflg用于控制當(dāng)前消息隊列滿或隊列消息到達系統(tǒng)范圍的限制時將要發(fā)生的事情。
如果調(diào)用成功琉预,消息數(shù)據(jù)的一分副本將被放到消息隊列中董饰,并返回0,失敗時返回-1.
3.msgrcv函數(shù)
該函數(shù)用來從一個消息隊列獲取消息模孩,它的原型為
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
msgid, msg_ptr, msg_st的作用也函數(shù)msgsnd函數(shù)的一樣尖阔。
msgtype可以實現(xiàn)一種簡單的接收優(yōu)先級。如果msgtype為0榨咐,就獲取隊列中的第一個消息。如果它的值大于零谴供,將獲取具有相同消息類型的第一個信息块茁。如果它小于零,就獲取類型等于或小于msgtype的絕對值的第一個消息。
msgflg用于控制當(dāng)隊列中沒有相應(yīng)類型的消息可以接收時將發(fā)生的事情数焊。
調(diào)用成功時永淌,該函數(shù)返回放到接收緩存區(qū)中的字節(jié)數(shù),消息被復(fù)制到由msg_ptr指向的用戶分配的緩存區(qū)中佩耳,然后刪除消息隊列中的對應(yīng)消息遂蛀。失敗時返回-1.
4.msgctl函數(shù)
該函數(shù)用來控制消息隊列,它與共享內(nèi)存的shmctl函數(shù)相似干厚,它的原型為:
int msgctl(int msgid, int command, struct msgid_ds *buf);
command是將要采取的動作李滴,它可以取3個值,
IPC_STAT:把msgid_ds結(jié)構(gòu)中的數(shù)據(jù)設(shè)置為消息隊列的當(dāng)前關(guān)聯(lián)值蛮瞄,即用消息隊列的當(dāng)前關(guān)聯(lián)值覆蓋msgid_ds的值所坯。
IPC_SET:如果進程有足夠的權(quán)限,就把消息列隊的當(dāng)前關(guān)聯(lián)值設(shè)置為msgid_ds結(jié)構(gòu)中給出的值
IPC_RMID:刪除消息隊列
buf是指向msgid_ds結(jié)構(gòu)的指針挂捅,它指向消息隊列模式和訪問權(quán)限的結(jié)構(gòu)芹助。msgid_ds結(jié)構(gòu)至少包括以下成員:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
成功時返回0,失敗時返回-1.
5. 消息隊列通信實例
接收信息的程序源文件為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結(jié)束
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);
}
發(fā)送信息的程序的源文件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)
{
//輸入數(shù)據(jù)
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
data.msg_type = 1; //注意2
strcpy(data.text, buffer);
//向隊列發(fā)送數(shù)據(jù)
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
//輸入end結(jié)束輸入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
exit(EXIT_SUCCESS);
}
5.進程間傳遞文件描述符
Linux 系統(tǒng)系下,子進程會自動繼承父進程已打開的描述符伺糠。但是声诸,兩個無關(guān)的進程要想共享文件描述符需要文件描述符的傳遞了。但是退盯,傳遞文件描述符并不是傳遞文件描述符的整數(shù)值彼乌,而是在接受進程中創(chuàng)建一個新的文件描述符,并且該文件描述符和發(fā)送進程中的文件描述符指向內(nèi)核中相同的文件表項渊迁。
簡單的說慰照,首先需要在這兩個進程之間建立一個 Unix 域套接字接口作為消息傳遞的通道( Linux 系統(tǒng)上使用 socketpair 函數(shù)可以很方面便的建立起傳遞通道),然后發(fā)送進程調(diào)用 sendmsg 向通道發(fā)送一個特殊的消息琉朽,內(nèi)核將對這個消息做特殊處理毒租,從而將打開的描述符傳遞到接收進程。然后接收方調(diào)用 recvmsg 從通道接收消息箱叁,從而得到打開的描述符墅垮。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h> /* for socketpair */
#define MY_LOGO "– Tony Bai"
static int send_fd(int fd, int fd_to_send)
{
struct iovec iov[1];
struct msghdr msg;
char buf[1];
if (fd_to_send >= 0) {
msg.msg_accrights = (caddr_t)&fd_to_send;
msg.msg_accrightslen = sizeof(int);
} else {
msg.msg_accrights = (caddr_t)NULL;
msg.msg_accrightslen = 0;
}
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov[0].iov_base = buf;
iov[0].iov_len = 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
if(sendmsg(fd, &msg, 0) < 0) {
printf("sendmsg error, errno is %d\n", errno);
return errno;
}
return 0;
}
static int recv_fd(int fd, int *fd_to_recv)
{
struct iovec iov[1];
struct msghdr msg;
char buf[1];
msg.msg_accrights = (caddr_t)fd_to_recv;
msg.msg_accrightslen = sizeof(int);
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov[0].iov_base = buf;
iov[0].iov_len = 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
if (recvmsg(fd, &msg, 0) < 0) {
return errno;
}
if(msg.msg_accrightslen != sizeof(int)) {
*fd_to_recv = -1;
}
return 0;
}
int x_sock_set_block(int sock, int on)
{
int val;
int rv;
val = fcntl(sock, F_GETFL, 0);
if (on) {
rv = fcntl(sock, F_SETFL, ~O_NONBLOCK&val);
} else {
rv = fcntl(sock, F_SETFL, O_NONBLOCK|val);
}
if (rv) {
return errno;
}
return 0;
}
int main() {
pid_t pid;
int sockpair[2];
int rv;
char fname[256];
int fd;
rv = socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair);
if (rv < 0) {
printf("Call socketpair error, errno is %d\n", errno);
return errno;
}
pid = fork();
if (pid == 0) {
/* in child */
close(sockpair[1]);
for ( ; ; ) {
rv = x_sock_set_block(sockpair[0], 1);
if (rv != 0) {
printf("[CHILD]: x_sock_set_block error, errno is %d\n", rv);
break;
}
rv = recv_fd(sockpair[0], &fd);
if (rv < 0) {
printf("[CHILD]: recv_fd error, errno is %d\n", rv);
break;
}
if (fd < 0) {
printf("[CHILD]: child process exit normally!\n");
break;
}
/* 處理fd描述符對應(yīng)的文件 */
rv = write(fd, MY_LOGO, strlen(MY_LOGO));
if (rv < 0) {
printf("[CHILD]: write error, errno is %d\n", rv);
} else {
printf("[CHILD]: append logo successfully\n");
}
close(fd);
}
exit(0);
}
/* in parent */
for ( ; ; ) {
memset(fname, 0, sizeof(fname));
printf("[PARENT]: please enter filename:\n");
scanf("%s", fname);
if (strcmp(fname, "exit") == 0) {
rv = send_fd(sockpair[1], -1);
if (rv < 0) {
printf("[PARENT]: send_fd error, errno is %d\n", rv);
}
break;
}
fd = open(fname, O_RDWR | O_APPEND);
if (fd < 0) {
if (errno == ENOENT) {
printf("[PARENT]: can’t find file ‘%s’\n", fname);
continue;
}
printf("[PARENT]: open file error, errno is %d\n", errno);
}
rv = send_fd(sockpair[1], fd);
if (rv != 0) {
printf("[PARENT]: send_fd error, errno is %d\n", rv);
}
close(fd);
}
wait(NULL);
return 0;
}