多進程必備頭文件
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
1.linux進程之間的通信種類
1. 管道
命名管道named pipe 管道允許親緣關係進程間的通信。命名管道還允許無親緣關係進程間通信。
2 信號 signal
在軟件層模擬中斷機制,通知進程某事發生。它是比較複雜的通信方式,用於通知進程有某事件發生,一個進程收到一個信號與處理器收到一箇中斷請求效果上可以說是一樣的。
3 消息隊列
Messge Queue 是消息的鏈接表,包括 posix 消息隊列和 SystemV 消息隊列。它克服了前兩種通信方式中信息量有限的缺點。具有寫權限的進程可以按照一定的規則向消息隊列中添加新消息。對消息隊列有讀權限的進程則可以從消息隊列中讀取消息。
4 共享內存
Shared memory 可以說是最有用的進程間通信方式,是最快的可用ipc形式。是針對其他通信機制運行效率較低而設計。它使得多個進程可以訪問同一塊內存空間,不同進程可以及時看到對方進程中對共享內存中數據的更新。這種通信方式需要依靠某種同步機制,如互斥鎖和信號量等。
5 信號量
Semaphore 進程間同步。主要作爲進程之間以及同一進程的不同線程之間的同步和互斥手段。
6 套接字 socket
用於網絡中不同機器間進程通信。用於不同計算機之間通信。
2.管道
2.1標準流管道
FILE* popen(const char* command, const char* open_mode);
功能:允許一個程序將另一個程序作爲新進程來啓動,並可以傳遞數據給它或者通過它接收數據。
形式參數:
command :要運行的字符串(命令)。
open_mode :必須是“r”或“w”。
返回值:創建成功返回管道文件指針,失敗返回NULL。
int pclose(FILE* fp);
功能:用 popen啓動的進程結束時,我們可以用 pclose 函數關閉與之關聯的文件流。
形式參數:popen函數的返回值,
返回值:
關閉成功:返回0。
關閉失敗:返回負數。
例子:從標準管道流中讀取打印/etc/profile 的內容。
#include <stdio.h>
int main()
{
char buf[512] = {0};
FILE* fp = popen("cat /etc/profile", "r");
while(fgets(buf, sizeof(buf), fp)){
puts(buf);
}
pclose(fp);
return 0;
}
2.2無名管道(PIPE)
int pipe(int fds[2]);
作用:創建一個無名管道。通過調用 pipe 函數獲取這對打開的文件描述符後,一個進程就可以從 fds[0]中讀數據,而另一個進程就可以往 fds[1]中寫數據。但是必須有繼承關係才能使用。
形式參數;
fds[2]:fds[0]存放可讀的文件描述符,fds[1]存放可寫文件描述符。
返回值:
創建成功:返回 0。
創建失敗:返回 1。
例子:創建父子進程,創建無名管道,父寫子讀。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fds[2] = {0};
pipe(fds);
char buf[32];
if(fork() == 0)
{
sleep(2); //保證父進程有機會把數據寫入
read(fds[0], buf, sizeof(buf)); //子進程從讀端讀數據
puts(buf);
close(fds[0]);
close(fds[1]);
}
else{
write(fds[1], "hello", 6); //父進程向寫端寫數據
waitpid(-1, NULL, 0); //等子退出
close(fds[0]);
close(fds[1]);
}
return 0;
}
2.3命名管道
創建和刪除命名管道
int mkfifo(const char *pathname, mode_t mode);
int unlink(const char *pathname);
作用:創建和刪除命名管道(FIFO 文件)。
形式參數:
pathname :要創建的FIFO文件的全路徑名;
mode :爲文件訪問權限,比如 0666。
返回值:
如果創建成功,則返回 0,否則-1。
例子:用函數創建FIFO文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[]) //演示通過命令行傳遞參數
{
if(argc != 2){
puts("Usage: MkFifo.exe {filename}");//如果輸入參數不是兩個,表示輸入有誤,並且返回-1。
return -1;
}
if(mkfifo(argv[1], 0666) == -1){
perror("mkfifo fail");
return -2;
}
//unlink(argv[1]);//加上這句會將創建的 FIFO 文件刪除。
return 0;
}
例子:使用命名管道(寫)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd = open("f.fifo", O_WRONLY); //1. 打開(判斷是否成功打開略)
write(fd, "hello", 6); //2. 寫
close(fd); //3. 關閉
return 0;
}
例子:使用命名管道(讀)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
char buf[128];
int fd = open("f.fifo", O_RDONLY);
read(fd, buf, sizeof(buf));
puts(buf);
close(fd);
return 0;
}
3.共享內存
3.1內存映射
1)mmap函數(把文件映射到內存)
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);//有6個形參
函數功能:用來將某個文件內容映射到內存中,對該內存區域的存取即是直接對該文件內容的讀寫。通過這樣可以加快文件訪問速度。
形式參數:
start:指向欲對應的內存起始地址;通常設爲 NULL,代表讓系統自動選定地址,映射成功後該地址會返回。
Length:代表將文件中多大的部分對應到內存。
Prot:代表映射區域的保護方式有下列組合:
PROT_EXEC | 映射區域可被執行 |
---|---|
PROT_READ | 映射區域可被讀取 |
PROT_WRITE | 映射區域可被寫入 |
PROT_NONE | 映射區域不能存取 |
flags:會影響映射區域的各種特性。flags可以取值如下:
MAP_FIXED | 如果參數 start 所指的地址無法成功建立映射時,則放棄映射,不對地址做修正。通常不鼓勵用此標誌。 |
---|---|
MAP_SHARED | 對映射區域的寫入數據會複製迴文件內,而且允許其他映射該文件的進程共享。 |
MAP_PRIVATE | 對映射區域的寫入操作會產生一個映射文件的複製。即私人的“寫入時複製”(copy on write)對此區域作的任何修改都不會寫回原來的文件內容。 |
MAP_ANONYMOUS | 建立匿名映射。 |
MAP_DENYWRITE | 只允許對映射區域的寫入操作,而不能對 fd 指向的文件進行讀寫,對該文件直接寫入的操作將會被拒絕。 |
MAP_LOCKED | 將映射區域鎖定住,這表示該區域不會被置換(swap)。 |
fd:open()返回的文件描述詞,代表欲映射到內存的文件。
Offset:爲文件映射的偏移量,通常設置爲 0,代表從文件最前方開始對應;
返回值: 成功,返回映射的內存的起始地址。失敗返回NULL。
2)msync函數(同步內存)
int msync(const void *start,size_t length, int flags);
功能:把對內存區域所做的更改同步到文件。
形式參數:
Start:映射函數的Start參數的值
Length:代表將文件中多大的部分對應到內存。
Flags:會影響映射區域的各種特性。
一般把參數設置成和mmap函數一樣。
返回值:成功返回0,失敗返回負數。
3)munmap函數(取消映射)
int munmap(void *start, size_t length);
功能:用來取消參數 start 所指的映射內存起始地址
形式參數:
start :所指的取消映射內存起始地址
length :則是欲取消的內存大小。
返回值:如果解除映射成功則返回 0,否則返回-1
注:當進程結束,映射內存會自動解除,但關閉對應的文件描述詞時不會解除映射。
例子:建立內存映射,父進程寫,子進程讀
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
int fd = open("a.txt", O_RDWR | O_CREAT);
if(fd == -1)
{
perror("open error");
exit(-1);
}
if(fork() > 0)
{
char buf[64] = {'\0'};
write(fd, buf, sizeof(buf));//往文件中寫數據
lseek(fd, 0, 0);//把文件座標定位到哦開頭
char* pStr = (char*)mmap(NULL, 64, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);//把文件內容映射到內存中
memset(pStr, 0, 64);//初始化內存
memcpy(pStr, "world", 6);往內存寫數據
sleep(10);
munmap(pStr, 64);//取消內存映射
close(fd);
}
else
{
sleep(2); //保證父有時間寫
char* pStr = (char*)mmap(NULL, 64, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);//把文件內容映射到內存中
puts(pStr);//把內存的數據讀取到屏幕上
munmap(pStr, 64);//取消內存映射
close(fd);**加粗樣式**
}
return 0;
}
3.2共享內存
1)ftok函數(創建關鍵字)
key_t ftok(const char *pathname, int proj_id);
函數功能:用於創建一個關鍵字,可以用該關鍵字關聯一個共享內存段。
形式參數:
Pathname:爲一個全路徑文件名,並且該文件必須可訪問。
proj_id:通常傳入一非 0 字符。
通過 pathname 和 proj_id 組合可以創建唯一的 key。
返回值:如果調用成功,返回一關鍵字,否則返回-1。
2)shmget函數(創建/打開共享內存)
int shmget(key_t key, int size, int shmflg);
功能:用於創建或打開一共享內存段,該內存段由函數的第一個參數標識。
參數:
key :是一個與共享內存段相關聯的關鍵字。key的值既可以用ftok函數產生,也可以是 IPC_RPIVATE(用於創建一個只屬於創建進程的共享內存,主要用於父子通信),表示總是創建新的共享內存段。
Size:指定共享內存段的大小,以字節爲單位。
Shmflg:是一掩碼合成值,可以是訪問權限值與(IPC_CREAT或IPC_EXCL)的合成。
Shmflg | 意思 |
---|---|
IPC_CREAT | 表示如果不存在該內存段,則創建它。 |
IPC_EXCL | 表示如果該內存段存在,則函數返回失敗結果(-1)。 |
返回值 成功則返回一個該共享內存段的唯一標識號(唯一的標識了這個共享內存段)。否則返回-1。
注:對任何進程都是唯一且相同的。
3)shmat函數(映射到進程空間地址)
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:將共享內存段映射到進程空間的某一地址。
形式參數:
Shmid:共享內存段的標識 通常應該是 shmget 的成功返回值。
Shmaddr:共享內存連接到當前進程中的地址位置。通常是 NULL,表示讓系統來選擇共享內存出現的地址。
Shmflg:一組位標識,通常爲 0 即可。
返回值:如果調用成功,返回映射後的進程空間的首地址,否則返回(void*)-1。
4)shmdt函數(共享內存段與進程空間分離)
int shmdt(const void *shmaddr);
功能:用於將共享內存段與進程空間分離。
參數:
shmaddr 爲 shmat 的成功返回值。
返回值:成功返回 0,失敗時返回-1
特別說明:將共享內存分離並沒刪除它,只是使得該共享內存對當前進程不再可用。
5)shmctl函數
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:共享內存的控制函數,可以用來刪除共享內存段
形式參數:
Shmid:共享內存段標識 通常應該是 shmget 的成功返回值。
Cmd:對共享內存段的操作方式。可選爲 IPC_STAT,IPC_SET,IPC_RMID。通常爲 IPC_RMID,表示刪除共享內存段。
Buf:共享內存段的信息結構體數據,通常爲 NULL。
返回值:成功返回 0,失敗時返回-1
例如:shmctl(kshareMem,IPC_RMID,NULL)表示刪除調共享內存段 kHareMem
例子:創建共享內存並且寫入數據
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int main()
{
key_t key = ftok("b.dat",1); //1. 寫入端先用 ftok 函數獲得 key
if(key < 0)
{
printf("請創建文件b.dat!!!\r\n");
return 1;
}
int shmid = shmget(key,4096,IPC_CREAT); //2. 寫入端用 shmget 函數創建一共享內存段
printf("key = %d shmid = %d\n", key, shmid);
char *p = (char *)shmat(shmid, NULL, 0);//3. 獲得共享內存段的首地址
memset(p, 0, 4096);
memcpy(p, "hello world", 4096); //4. 往共享內存段中寫入內容
shmdt(p); //5. 關閉共享內存段
return 0;
}
例子:創建共享內存並且讀出數據
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int main()
{
key_t key = ftok("b.dat",1);
if(key < 0)
{
printf("請創建文件b.dat!!!\r\n");
return 1;
}
int shmid = shmget(key,4096,IPC_CREAT);
printf("key = %d shmid = %d\n", key, shmid);
char *p = (char *)shmat(shmid, NULL, 0);
printf("receive the data:%s\n",p); //4. 讀取共享內存段中的內容
shmctl(shmid, IPC_RMID, 0); //5. 刪除共享內存段
return 0;
}
4.消息隊列
1)msgget函數(創建和訪問一個消息隊列)
int msgget(key_t key, int msgflg);
功能:創建和訪問一個消息隊列。
參數:
Key:唯一標識一個消息隊列的關鍵字。如果爲 IPC_PRIVATE(值爲 0)表示創建一個只由調用進程使用的消息隊列。
Msgflg:指明隊列的訪問權限和創建標誌。創建標誌的可選值爲 IPC_CREAT 和 IPC_EXCL。
Msgflg值 | 意義 |
---|---|
IPC_CREAT | 要麼返回新創建的消息隊列id,要麼返回具有相同 key 值的消息隊列 id; |
IPC_EXCL:和IPC_CREAT (用”或”連接) | 要麼創建新的消息隊列,要麼當隊列存在時,調用失敗並返回-1。 |
返回值:成功的時候,返回一個消息隊列的唯一標識符 id(跟進程 ID 是一個類型);失敗的時候,會返回-1;
2)msgsnd函數(將消息添加到消息隊列中和從一個消息隊列中獲取信息)
int msgsnd(int msqid, struct msgbuf * msgp, size_t msgsz, int msgflg);
功能:用來將消息添加到消息隊列中和從一個消息隊列中獲取信息。
參數:
msgid:指明消息隊列的 ID; 通常是 msgget 函數成功的返回值。
Msgp:消息結構體
struct msgbuf {
long mtype; // 消息類別
char mtext[10]; //消息內容
};
Msgsz:消息體的大小,每個消息體最大不要超過 4K;
Msgflg:可以爲 0(通常爲 0)或 IPC_NOWAIT。
Msgflg | 意義 |
---|---|
0 | 當隊列滿並調用 msgsnd 或隊列空時調用 msgrcv 將會阻塞,直到隊列條件滿足爲止。 |
IPC_NOWAIT | msgsnd 和 msgrcv 都不會阻塞,此時如果隊列滿並調用 msgsnd 或隊列空時調用 msgrcv 將返回錯誤 |
返回值:成功返回0,失敗返回負數
3)msgrcv函數(將消息添加到消息隊列中和從一個消息隊列中獲取信息)
ssize_t msgrcv(int msqid, struct msgbuf * msgp, size_t msgsz, long msgtyp, int msgflg);
功能:用來將消息添加到消息隊列中和從一個消息隊列中獲取信息。
參數:
msgid:指明消息隊列的 ID; 通常是 msgget 函數成功的返回值。
Msgp:消息結構體
Msgsz:消息體的大小,每個消息體最大不要超過 4K;
Msgtyp:有 3 種選項:
Msgtyp | 意義 |
---|---|
msgtyp == 0 | 接收隊列中的第 1 個消息(通常爲 0) |
msgtyp > 0 | 接收對列中的第 1 個類型等於 msgtyp 的消息 |
msgtyp < 0 | 接收其類型小於或等於 msgtyp 絕對值的第 1 個最低類型消息。 |
Msgflg:可以爲 0(通常爲 0)或 IPC_NOWAIT。
Msgflg | 意義 |
---|---|
0 | 當隊列滿並調用 msgsnd 或隊列空時調用 msgrcv 將會阻塞,直到隊列條件滿足爲止。 |
IPC_NOWAIT | msgsnd 和 msgrcv 都不會阻塞,此時如果隊列滿並調用 msgsnd 或隊列空時調用 msgrcv 將返回錯誤 |
返回值:成功返回0,失敗返回負數
4)msgctl函數(刪除消息隊列)
int msgctl(int msqid, int cmd, struct msqid_ds * buf);
功能:消息隊列的控制函數,常用來刪除消息隊列。
參數:
msqid: 是由 msgget 返回的消息隊列標識符。
cmd: 通常爲 IPC_RMID 表示刪除消息隊列。
buf: 通常爲 NULL 即可。
返回值:成功返回0,失敗返回負數
例子:有親緣關係的消息隊列
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
struct MSG{ //消息結構體
long mtype; //消息類別
char buf[64]; //消息內容
};
int main()
{
int msgid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);//先創建消息隊列,在後面創建子進程,若是沒有親緣關係則
//int msgid = msgget((key_t)1235,0666 | IPC_CREAT); //這個是沒有親緣關係時使用的消息隊列創建參數
printf("msgid = %d\n", msgid);
struct MSG msg;
memset(&msg, 0, sizeof(struct MSG));//給結構體msg初始化爲0
if(fork() > 0)
{
msg.mtype = 1;//設置消息類別爲1
strcpy(msg.buf,"aaaa");//設置消息內容爲aaaa
msgsnd(msgid, &msg, sizeof(struct MSG), 0);//發送一條信息
msg.mtype = 2;//設置消息類別爲2
strcpy(msg.buf,"bbbb");//設置消息內容爲bbbb
msgsnd(msgid, &msg, sizeof(struct MSG), 0);//發送另一條信息
wait(NULL);
msgctl(msgid, IPC_RMID, NULL);//刪除消息隊列
}
else
{
sleep(2); //讓父進程有時間往消息隊列裏面寫
msgrcv(msgid, &msg, sizeof(struct MSG), 2, 0);//接受類別爲2的消息
puts(msg.buf);
msgrcv(msgid, &msg, sizeof(struct MSG), 1, 0);//接受類別爲1的消息
puts(msg.buf);
}
return 0;
}
4.信號量
1)semget函數
int semget(key_t key,int nsems,int flag);
功能:創建一個信號量集或訪問一個已存在的信號量集。
參數:
key :是唯一標識一個信號量的關鍵字。
key 值 | 意義 |
---|---|
IPC_PRIVATE(值爲 0 | (創建一個只有創建者進程纔可以訪問的信號量),表示創建一個只由調用進程使用的信號量; |
非 0 值 | (可以通過 ftok 函數獲得)表示創建一個可以被多個進程共享的信號量。 |
nsems:需要使用的信號量數目。
如果是創建新集合,則必須指定 nsems。
如果引用一個現存的集合,則將 nsems 指定爲 0。
Flag:是一組標誌,其作用與open函數的各種標誌很相似。
Flag值 | 意義 |
---|---|
IPC_CREAT | 要麼返回新創建的消息隊列id,要麼返回具有相同 key 值的消息隊列 id; |
IPC_EXCL:和IPC_CREAT (用”或”連接) | 要麼創建新的消息隊列,要麼當隊列存在時,調用失敗並返回-1。 |
返回值:成功時,返回一個稱爲信號量集標識符的整數;出錯時,返回-1。
2)semop函數
int semop(int semid,struct sembuf *sops,size_t nops);
函數功能:用於改變信號量對象中各個信號量的狀態。
參數:
semid:由 semget 返回的信號量標識符ID。
Sops:指向一個結構體數組的指針。
struct sembuf{
short sem_num; //要操作的信號量在信號量集中的編號,第一個信號量的編號是0
Short sem_op; /*sem_op 成員的值是信號量在一次操作中需要改變的數值。
通常只會用到兩個值,一個是-n,也就是p(減一)操作,它等待信號量變爲可用;
一個是+n,也就是v(加一)操作,它發送信號通知信號量現在可用。*/
short sem_flg; //通常設爲: SEM_UNDO,程序結束,信號量爲 semop 調用前的值。
};
nops:爲 sops 指向的 sembuf 結構數組的長度。
返回值:成功時,返回 0;失敗時,返回-1.
3)semctl函數
int semctl(int semid, int semnum, int cmd, union semun arg);
函數功能:用來直接控制信號量信息。
參數:
semid :由 semget 返回的信號量標識符。
semnum :要進行操作的集合中信號量的編號,當要操作到成組的信號量時,從 0 開始,表示這是第一個也是唯一的一個信號量。
cmd 爲執行的操作。
cmd | 操作 |
---|---|
IPC_RMID | 立即刪除信號集,喚醒所有被阻塞的進程 |
GETVAL | 根據 semun指定的編號返回相應信號的值, 此時該函數返回值就是你要獲得的信號量的值,不再是 0或-1 |
SETVAL | 根據 semun 指定的編號設定相應信號的) |
GETALL | 獲取所有信號量的值, 此時第二個參數爲 0, 並且會將所有信號的值存入 semun.array 所指向的數組的各個元素中,此時需要用到第四個參數 union semun arg |
SETALL | 將 semun.array 指向的數組的所有元素的值設定到信號量集中, 此時第二個參數爲 0,此時需要用到第四個參數 union semun arg。 |
arg: 是一個 union semun 類型(具體的需要由程序員自己定義)。
union semun{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
};
返回值:成功時,返回 0;失敗時,返回-1。
例子 :創建4種信號量,並且進行賦值,和對信號量的增減
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <string.h>
union semun
{
int val; //信號量的編號
struct semid_ds *buf; //
unsigned short* array; //這個指針存放信號量的值
};
int main()
{
int i;
int key = ftok("b.dat", 1);
int id = semget(key, 4,0666| IPC_CREAT);//建立4種信號量
unsigned short sz0[4], sz1[4] = {10, 20, 30, 40};
union semun arg;
arg.array = sz1;//指針地址傳遞
semctl(id, 0, SETALL, arg);//把arg地址的值 賦值 給4種信號量
printf("total number:%d %d %d %d\n", semctl(id, 0, GETVAL),semctl(id, 1, GETVAL),semctl(id, 2, GETVAL),semctl(id, 3, GETVAL)); //獲得信號的值,並且打印到屏幕上
struct sembuf aaa = {0, 10, 0};//第0個信號量進行加10
semop(id, &aaa, 1);
struct sembuf aaaa = {2, -5, 0};//第2個信號量進行減5
semop(id, &aaaa, 1);
arg.array = sz0;
semctl(id, 0, GETALL, arg);//得到4種信號量的值,給arg的地址
for(i = 0; i < 4; i++)
printf("%d:%d\n", i, sz0[i]);
semctl(id, IPC_RMID, 0);//刪除信號集
return 0;
}