進程間的通信主要分為本機器進程間的通信和不同機器間進程的通信柱衔。本文主要描述本機進程間的通信阵赠。
一腊敲、傳統(tǒng)Linux的通信機制
1. 無名管道(PIPE)
無名管道是一種半雙工通信方式叹括,數(shù)據(jù)只能單方向流動,而且只能用于具有親緣關(guān)系的進程绍坝,并且這種親緣關(guān)系常常指的是父子進程徘意。
pipe(建立管道):
頭文件: #include<unistd.h>
函數(shù)的一般形式: int pipe(int filedes[2]);
函數(shù)說明:
pipe()會建立管道,并將文件描述詞由參數(shù)filedes數(shù)組返回轩褐。
filedes[0]為管道里的讀取端
filedes[1]則為管道的寫入端椎咧。
返回值: 若成功則返回零,否則返回-1把介,錯誤原因存于errno中勤讽。
錯誤代碼:
EMFILE: 進程已用完文件描述詞最大量。
ENFILE: 系統(tǒng)已無文件描述詞可用拗踢。
EFAULT: 參數(shù) filedes 數(shù)組地址不合法脚牍。
// 案例:
#include <stdio.h>
#include <unistd.h> //pipe, read,write,fork
#include <stdlib.h> //exit
#include <string.h>
#define MAX_NUM 32
int main()
{
int pipe_fd[2];
int ret = -1;
pid_t pid = -1;
char message[MAX_NUM] = {'\0'};
pipe(pipe_fd);
write(pipe_fd[1], "HelloWorld", 10); //管道初始化
pid = fork();
if (0 == pid)
{
while(1)
{
memset(message, '\0', MAX_NUM);
ret = read(pipe_fd[0], message, MAX_NUM); //讀取管道中的信息
if (-1 == ret)
{
perror("read:");
exit(1);
}
else if (0 == ret)
{
printf("There is nothing to say from child;\n");
}
else
{
printf("The child say: %s", memssage);
}
sleep(1);
printf("parent input:\n");
scanf("%s",message);
ret = write(pipefd[1], message, strlen(message));
if (-1 == ret)
{
perror("write:");
exit(1);
}
sleep(1);
}
}
else if (0 < pid)
{
while(1)
{
memset(message, '\0', MAX_NUM);
ret = read(pipe_fd[0], message, MAX_NUM); //讀取管道中的信息
if (-1 == ret)
{
perror("read:");
exit(1);
}
else if (0 == ret)
{
printf("There is nothing to parent from child;\n");
}
else
{
printf("The parent say: %s", message);
}
sleep(1);
printf("child input:\n");
scanf("%s",message);
ret = write(pipefd[1], message, strlen(message));
if (-1 == ret)
{
perror("write:");
exit(1);
}
sleep(1);
}
}
else
{
perror("fork:");
exit(1);
}
return 0;
}
2. 有名管道(FIFO)
有名管道同樣是一種半雙工通信方式,只不過它允許無親緣關(guān)系的進程進行通信巢墅。
mkfifo():
頭文件:#include <sys/types.h> #include <sys/stat.h>
函數(shù)的一般形式:int mkfifo( const char *pathname, mode_t mode );
函數(shù)說明:創(chuàng)建一個有名管道
參數(shù)設(shè)置:
pathname:在文件系統(tǒng)中創(chuàng)建一個專用文件诸狭;
mode:規(guī)定專用文件的讀寫權(quán)限。(讀君纫、寫驯遇、執(zhí)行權(quán)限)
返回值:成功返回0,失敗返回 -1蓄髓。
錯誤代碼:
EACCESS 參數(shù)pathname所指定的目錄路徑無可執(zhí)行的權(quán)限
EEXIST 參數(shù)pathname所指定的文件已存在叉庐。
ENAMETOOLONG 參數(shù)pathname的路徑名稱太長。
ENOENT 參數(shù)pathname包含的目錄不存在
ENOSPC 文件系統(tǒng)的剩余空間不足
ENOTDIR 參數(shù)pathname路徑中的目錄存在但卻非真正的目錄双吆。
EROFS 參數(shù)pathname指定的文件存在于只讀文件系統(tǒng)內(nèi)眨唬。
// 案例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#define PATHNAME "./myfile"
int main(void)
{
char out_buf[80];
int fd;
int ret = -1;
ret = mkfifo( PATHNAME, 0777 ); //創(chuàng)建有名管道
pid_t pid = -1;
pid = fork();
if (0 == pid)
{
char input_buf[] = "Hello!\n";
fd = open( PATHNAME, O_WRONLY );
ret = write( fd, input_buf, strlen(input_buf) );
close( fd );
}
else
{
fd = open( PATHNAME, O_RDONLY);
ret = read( fd, out_buf, sizeof(buf) );
printf("The message from the FIFO is:%s\n", out_buf );
close( fd );
}
return 0;
}
3. 信號(signal)
信號的API
1. 傳送信號給指定進程
kill()
頭文件: #include <sys/types.h> #include <signal.h>
函數(shù)的一般形式int kill(pid_t pid,int sig)
函數(shù)功能:該函數(shù)可以將參數(shù)sig指定的信號傳給參數(shù)pid指定的進程.
參數(shù)設(shè)置:
pid:
1. pid>0 :將信號傳給進程識別碼為pid的進程.
2. pid=0 :將信號傳給和當前進程相同進程族的所有進程.比如父進程產(chǎn)生子進程后,父子進程均屬于一個進程組.
3. pid=-1:將信號廣播傳送給系統(tǒng)中所有的進程.
4. pid<0 :將信號傳給進程組識別碼為pid絕對值的所有進程.
sig: 傳遞的信號.由于信號在linux中以一個數(shù)字編碼相對應(yīng)好乐,因此該參數(shù)為int型.
返回值:成功返回0,失敗返回-1瓦宜。
錯誤代碼:
EINVAL 一個無效的信號蔚万;
EPERM 該進程沒有權(quán)限發(fā)送信號給目標進程;
ESRCH pid或者process group不存在临庇。
// 案例:主進程向子進程發(fā)信號以終止子進程.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
int main(void)
{
pid_t pid;
pid=fork();
if(pid<0)
perror("fork");
else if(0==pid)
{
while(1)
{
printf("I am child process\n");
sleep(1);
}
}
else
{
sleep(3);
printf("I am a father process, i will send signal now\n");
kill(pid,SIGINT); //也可以寫成:kill(pid,2)
}
return 0;
}
2. 向自己發(fā)送一信號
raise()
頭文件:#include<signal.h>
函數(shù)的一般形式:int raise(int sig)
函數(shù)功能:該函數(shù)可以向自己發(fā)送一個sig信號.當然也可以使用kill()函數(shù)來實現(xiàn).
返回值:成功返回0反璃,失敗返回一個非零值。
// 案例:
int main()
{
int i=0;
while(1)
{
i++;
if(i==3)
raise(SIGINT);
printf("i am father process\n");
sleep(1);
}
return 0;
}
執(zhí)行結(jié)果:當i=3時假夺,函數(shù)raise()向自己進程發(fā)送了終止信號淮蜈,因此只打印兩條信息.
i am father process
i am father process
在linux的64個信號中,大多數(shù)在默認情況下都是終止當前信號已卷。包括SIGINT梧田,當?shù)搅硕〞r時間后,內(nèi)核發(fā)出SIGINT信號,該信號會終止當前進程裁眯。
3. 設(shè)置信號傳送鬧鈴
alarm()
頭文件:#include<unistd.h>
函數(shù)的一般形式:unsigned int alarm(unsigned int seconds)
函數(shù)功能:用于設(shè)置信號SIGALRM經(jīng)過參數(shù)seconds所指定的秒數(shù)后傳送給當前進程鹉梨。
返回值:返回剩余的秒數(shù),直到任何之前預(yù)定的報警是由于交付,或者零如果沒有previ在預(yù)定的報警。
// 案例:
int main(void)
{
if(alarm(3)<0)
//當調(diào)用該函數(shù)時穿稳,系統(tǒng)就啟動了定時器存皂,定時到3s后向當前進程發(fā)送一個SIGALRM信號.
//該信號默認情況下是終止當前進程.
perror("alarm");
while(1)
{
sleep(1);
printf("i am father process\n");
}
return 0;
}
執(zhí)行結(jié)果:
i am father process
i am father process
在linux的64個信號中,大多數(shù)在默認情況下都是終止當前信號.包括SIGALRM逢艘,當?shù)搅硕〞r時間后旦袋,內(nèi)核發(fā)出SIGALRM信號,該信號會終止當前進程.
4. 讓進程暫停直到信號出現(xiàn)
pause()
頭文件:#include<unistd.h>
函數(shù)的一般形式:int pause(void)
函數(shù)功能:用于將調(diào)用進程掛起直到捕捉到信號為止.該信號可以判斷信號是否已到它改。
返回值:只有當一個信號被signal-catching函數(shù)返回疤孕。在這種情況下暫停()返回 -1,errno設(shè)置捕獲搔课。
錯誤代碼:
EINTR 捕獲一個信號被signal-catching函數(shù)返回
// 案例:
int main(void)
{
pid_t pid = -1;
pid = fork();
if(pid<0)
perror("fork");
else if(0 == pid)
{
if(pause()<0)
perror("pause");
while(1)
{
printf("hello i am child process\n");
sleep(1);
}
}
else
{
sleep(3);
printf("I am a father process, I will send signal now\n");
kill(pid,SIGINT);
}
return 0;
}
執(zhí)行結(jié)果:
系統(tǒng)在延遲3s后打印輸出"i am a father process,i will send signal now"胰柑,然后結(jié)束當前進程.
注意,程序并不會打印輸出"hello i am child process".
在linux的64個信號中爬泥,大多數(shù)在默認情況下都是終止當前信號.包括SIGALRM柬讨,當?shù)搅硕〞r時間后,內(nèi)核發(fā)出SIGALRM信號袍啡,該信號會終止當前進程
信號的處理函數(shù)
在linux中踩官,對于信號的處理主要有兩種.一種是使用signal()函數(shù),另一種是使用信號集函數(shù)組境输。
1. 使用signal()函數(shù)
頭文件:#include<signal.h>
函數(shù)原型:
void (*signal(int signo,void (*func)(int)))(int)
sighandler_t signal(int signum, sighandler_t handler);
函數(shù)功能: 該函數(shù)會根據(jù)函數(shù)指針func所指向的函數(shù)來處理參數(shù)signo所指定的信號蔗牡。
參數(shù)設(shè)置:
第一個參數(shù):一個整數(shù)
第二個整數(shù):是函數(shù)指針,它所指向的就是需要處理該信號的函數(shù)嗅剖。
返回值:是一個函數(shù)地址辩越,返回上一次的信號處理函數(shù)的指針.若出錯,則返回SIG_ERR.
// 案例:
void my_func1(int sign_no)
{
if(sign_no == SIGINT)
printf("ni hao\n");
else if(sign_no == SIGQUIT)
printf("hello world \n");
}
void my_func2(int sign_no)
{
if(sign_no == SIGINT)
printf("love china\n");
else if(sign_no == SIGQUIT)
printf("kill japanese\n");
}
int main()
{
void (*p)(int sig_no); //定義參數(shù)為int型的函數(shù)指針p
printf("Waiting for SIGINT of SIGQUIT\n");
signal(SIGINT, my_func1); //使用函數(shù)my_func1來處理信號SIGINT
pause(); //等待用戶輸入SIGINT類的信號信粮,比如ctrl+c
p=signal(SIGINT,my_func2);
//使用函數(shù)my_func2來處理信號SIGINT黔攒,并將返回值賦給p
//signal()返回上一次信號處理函數(shù)的指針,而上一次處理函數(shù)是my_func1强缘,因此p指向函數(shù)my_func1.
pause();
//等待用戶輸入SIGINT類的信號督惰,比如ctrl+c
signal(SIGQUIT,p);
//使用函數(shù)my_func1來處理信號SIGQUIT
pause();
exit(0);
}
執(zhí)行結(jié)果:
Waiting for SIGINT of SIGQUIT
ni hao //按下ctrl+c (SIGINT類信號)
love china //按下ctrl+c (SIGINT類信號)
hello world //按下ctrl+\ (SIGQUIT類信號)
該函數(shù)的第二個參數(shù)func是信號處理函數(shù),它有3中寫法:
1旅掂、SIG_IGN :忽略該信號
2赏胚、SIG_DEL :采用默認方式處理該函數(shù).在linux中多數(shù)信號信號的默認是終止當前進程.
3、自定義信號處理函數(shù) :如上例所示.
2. 信號集函數(shù)組
在上文中的"使用signal()函數(shù)來處理信號"商虐,每次只能處理一個信號.如果批量處理觉阅,則會顯得非常麻煩.因此崖疤,在linux中就支持信號集函數(shù)組來批處理信號.這是我自己的理解。當然留拾,為了支持信號集處理戳晌,需要下面幾個函數(shù):
- int sigemptyset(sigset_t *set)
初始化信號集合set,并將其設(shè)置為空痴柔,即信號集合中無信號. - int sigfillset(sigset_t *set)
初始化信號集合沦偎,將信號集合設(shè)置為所有信號的集合.比如將linux中的64個信號都放在信號集合set內(nèi). - int sigaddset(sigset_t *set, int signo)
將信號signo加入到信號集合set中. - int sigdelset(sigset_t *set, int signo)
將信號signo從信號集中刪除. - int sigismember(sigset_t *set int signo)
查詢信號signo是否在信號集合set中. - int sigprocmask(int how, const sigset_t *set, sigset_t *oset)
將指定的信號集合加入到進程的信號阻塞集合中,或從中刪除.
參數(shù)設(shè)置:
第一個參數(shù)how決定了函數(shù)的操作方式:
SIG_BLOCK:增加一個信號集合到當前進程的阻塞集合中.
SIG_UNBLOCK:從當前阻塞集合中刪除一個信號集合.
SIG_SETMASK:將當前信號集合設(shè)置為信號阻塞集合.
第二個參數(shù)set:阻塞信號的集合.
第三個參數(shù)oset:若提供了oset咳蔚,則當前進程信號阻塞集合將會保存在oset中.若不想保存豪嚎,
則設(shè)置為NULL.
// 案例:將信號SIGINT加入到阻塞信號集合中,再將信號SIGINT從阻塞信號集合中刪除.
int main(void)
{
sigset_t intmask; //設(shè)置阻塞集合intmask
int i;
sigemptyset(&intmask); //初始化阻塞集合谈火,并清空
//將信號SIGINT加入到阻塞集合中侈询,即ctrl+c(SIGINT信號的一種)不再有效.
sigaddset(&intmask,SIGINT);
while(1)
{
fprintf(stdout,"SIGINT signal blocked\n");
//添加阻塞集合intmask到系統(tǒng)的阻塞集合中
sigprocmask(SIG_BLOCK,&intmask,NULL);
for(i=0;i<10;i++)
//每秒打印出字符串,在此期間執(zhí)行ctrl+c(SIGINT信號的一種)糯耍,程序無法停止
//SIGINT信號被加入了系統(tǒng)的阻塞集合中扔字,因此系統(tǒng)將不會對該信號作出反應(yīng)
{
fprintf(stdout,"Blocked calculation is finished\n");
sleep(1);
}
fprintf(stdout,"SIGINT signal unblocked\n");
//將阻塞集合intmask從系統(tǒng)的阻塞集合中刪除
sigprocmask(SIG_UNBLOCK,&intmask,NULL);
for(i=0;i<10;i++)
//每秒打印出字符串,在此期間執(zhí)行ctrl+c(SIGINT信號的一種)温技,程序可以停止
//SIGINT信號從了系統(tǒng)的阻塞集合中刪除了革为,因此系統(tǒng)對該信號將作出反應(yīng)
{
fprintf(stdout,"Unblocked calculation is finished\n");
sleep(1);
}
}
exit(0);
}
二、System V IPC:
1. 共享內(nèi)存( shared memory)
共享內(nèi)存是映射一段能被其他進程所訪問的內(nèi)存舵鳞,這段共享內(nèi)存由一個進程創(chuàng)建震檩,但多個進程都可以訪問。共享內(nèi)存是最快的 IPC 方式蜓堕,它是針對其他進程間通信方式運行效率低而專門設(shè)計的抛虏。它往往與其他通信機制,如信號量套才,配合使用迂猴,來實現(xiàn)進程間的同步和通信。
注意:共享內(nèi)存并未提供同步機制背伴,也就是說错忱,在第一個進程結(jié)束對共享內(nèi)存的寫操作之前,并無自動機制可以阻止第二個進程開始對它進行讀取挂据。
- shmget()
頭文件:#include <sys/ipc.h> #include <sys/shm.h>
函數(shù)的一般形式:int shmget(key_t key, size_t size, int shmflg);
函數(shù)說明:創(chuàng)建共享內(nèi)存
參數(shù)設(shè)置:
key:為有效的共享內(nèi)存段命名
size:以字節(jié)為單位指定共享內(nèi)存的容量大小
shmflg:權(quán)限標志,和open函數(shù)的mode參數(shù)一樣儿普;如果要想在key標識的共享內(nèi)存不存在時崎逃,創(chuàng)建它的話,可以與IPC_CREAT做或操作眉孩。
返回值:成功返回一個共享內(nèi)存標識符(非負整數(shù))个绍,失敗返回 -1勒葱。
- shmat()
頭文件: #include <sys/types.h> #include <sys/shm.h>
函數(shù)的一般形式:
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
函數(shù)說明:第一次創(chuàng)建完共享內(nèi)存時,它還不能被任何進程訪問巴柿,shmat函數(shù)的作用就是用來啟動對該共享內(nèi)存的訪問凛虽,并把共享內(nèi)存連接到當前進程的地址空間。
參數(shù)設(shè)置:
shmid:共享內(nèi)存標識符广恢。
shmaddr:指定共享內(nèi)存連接到當前進程中的地址位置凯旋,通常為NULL,表示讓系統(tǒng)來選擇共享內(nèi)存的地址钉迷。
shmflg:一組標志位至非,通常為0。
返回值:成功時返回一個指向共享內(nèi)存第一個字節(jié)的指針糠聪,如果調(diào)用失敗返回-1荒椭。
- shmdt()
頭文件:#include <sys/types.h> #include <sys/shm.h>
函數(shù)的一般形式:
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
函數(shù)說明:用于將共享內(nèi)存從當前進程中分離。注意舰蟆,將共享內(nèi)存分離并不是刪除它趣惠,只是使該共享內(nèi)存對當前進程不再可用。
參數(shù)設(shè)置:
shmid:共享內(nèi)存標識符身害。
shmaddr:指定共享內(nèi)存連接到當前進程中的地址位置味悄,通常為NULL,表示讓系統(tǒng)來選擇共享內(nèi)存的地址题造。
shmflg:一組標志位傍菇,通常為0。
返回值:成功返回0界赔,失敗返回 -1丢习。
錯誤代碼:
- shmctl()
頭文件:#include <sys/ipc.h> #include <sys/shm.h>
函數(shù)的一般形式:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函數(shù)說明:用來控制共享內(nèi)存
參數(shù)設(shè)置:
shmid:共享內(nèi)存標識符
cmd:command是要采取的操作,它可以取下面的三個值 :
IPC_STAT:把shmid_ds結(jié)構(gòu)中的數(shù)據(jù)設(shè)置為共享內(nèi)存的當前關(guān)聯(lián)值淮悼,即用共享內(nèi)存的當前關(guān)聯(lián)值覆蓋shmid_ds的值咐低。
IPC_SET:如果進程有足夠的權(quán)限,就把共享內(nèi)存的當前關(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)见擦。
// shmid_ds 的結(jié)構(gòu)體
struct shmid_ds
{
struct ipc_perm shm_perm;/* 操作權(quán)限*/
int shm_segsz; /*段的大小(以字節(jié)為單位)*/
time_t shm_atime; /*最后一個進程附加到該段的時間*/
time_t shm_dtime; /*最后一個進程離開該段的時間*/
time_t shm_ctime; /*最后一個進程修改該段的時間*/
unsigned short shm_cpid; /*創(chuàng)建該段進程的pid*/
unsigned short shm_lpid; /*在該段上操作的最后1個進程的pid*/
short shm_nattch; /*當前附加到該段的進程的個數(shù)*/
/*下面是私有的*/
unsigned short shm_npages; /*段的大懈睢(以頁為單位)*/
unsigned long *shm_pages; /*指向frames->SHMMAX的指針數(shù)組*/
struct vm_area_struct *attaches; /*對共享段的描述*/
};
struct ipc_perm
{
key_t __key; /*Key supplied to shmget(2)*/
uid_t uid; /*Effective UID of owner*/
gid_t gid; /*Effective GID of owner*/
uid_t cuid; /*Effective UID of creator*/
gid_t cgid; /*Effective GID of creator*/
unsigned short mode; /*Permissions + SHM_DEST and SHM_LOCKED flags*/
unsigned short __seq; /*Sequence number*/
};
返回值:失敗返回 -1鲤屡。
// shmdata.h
#ifndef _SHMDATA_H
#define _SHMDATA_H
#define SIZE 1024
struct chm_mes
{
int shm_num; //作為一個標志,非0:表示可讀福侈,0表示可寫
char text[SIZE]; //記錄寫入和讀取的文本
};
#endif
// shmread.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include "shmdata.h"
int main()
{
int running = 1;//程序是否繼續(xù)運行的標志
void *shm = NULL;//分配的共享內(nèi)存的原始首地址
struct shm_mes *shared;//指向shm
int shmid;//共享內(nèi)存標識符
//創(chuàng)建共享內(nèi)存
shmid = shmget((key_t)1234, sizeof(struct shm_mes), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//將共享內(nèi)存連接到當前進程的地址空間
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 chm_mes*)shm;
shared->shm_num = 0;
while(running)//讀取共享內(nèi)存中的數(shù)據(jù)
{
//沒有進程向共享內(nèi)存定數(shù)據(jù)有數(shù)據(jù)可讀取
if(shared->shm_num != 0)
{
printf("You wrote: %s", shared->text);
sleep(rand() % 3);
//讀取完數(shù)據(jù)酒来,設(shè)置shm_num使共享內(nèi)存段可寫
shared->shm_num = 0;
//輸入了end,退出循環(huán)(程序)
if(strncmp(shared->text, "end", 3) == 0)
running = 0;
}
else//有其他進程在寫數(shù)據(jù)肪凛,不能讀取數(shù)據(jù)
sleep(1);
}
//把共享內(nèi)存從當前進程中分離
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 chm_mes *shared = NULL;
char buffer[SIZE + 1];//用于保存輸入的文本
int shmid;
//創(chuàng)建共享內(nèi)存
shmid = shmget((key_t)1234, sizeof(struct chm_mes), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//將共享內(nèi)存連接到當前進程的地址空間
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 chm_mes*)shm;
while(running)//向共享內(nèi)存中寫數(shù)據(jù)
{
//數(shù)據(jù)還沒有被讀取堰汉,則等待數(shù)據(jù)被讀取,不能向共享內(nèi)存中寫入文本
while(shared->shm_num == 1)
{
sleep(1);
printf("Waiting...\n");
}
//向共享內(nèi)存中寫入數(shù)據(jù)
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, SIZE);
//寫完數(shù)據(jù)辽社,設(shè)置shm_num使共享內(nèi)存段可讀
shared->shm_num = 1;
//輸入了end,退出循環(huán)(程序)
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
//把共享內(nèi)存從當前進程中分離
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
共享內(nèi)存的優(yōu)缺點:
1翘鸭、優(yōu)點:我們可以看到使用共享內(nèi)存進行進程間的通信真的是非常方便滴铅,而且函數(shù)的接口也簡單,數(shù)據(jù)的共享還使進程間的數(shù)據(jù)不用傳送就乓,而是直接訪問內(nèi)存汉匙,也加快了程序的效率。同時档址,它也不像匿名管道那樣要求通信的進程有一定的父子關(guān)系盹兢。
2、缺點:共享內(nèi)存沒有提供同步的機制守伸,這使得我們在使用共享內(nèi)存進行進程間通信時绎秒,往往要借助其他的手段來進行進程間的同步工作。
2. 信號量(semophore)
信號量是一個特殊的變量尼摹,程序?qū)ζ湓L問都是原子操作见芹,且只允許對它進行等待(即P(信號變量))和發(fā)送(即V(信號變量))信息操作。最簡單的信號量是只能取0和1的變量蠢涝,這也是信號量最常見的一種形式玄呛,叫做二進制信號量。而可以取多個正整數(shù)的信號量被稱為通用信號量和二。
1. semget()
頭文件:#include <sys/types.h> <sys/ipc.h> <sys/sem.h>
函數(shù)的一般形式:int semget(key_t key, int nsems, int semflg)
函數(shù)說明:得到一個信號量集標識符或創(chuàng)建一個信號量集對象并返回信號量集標識符
參數(shù)設(shè)置:
key:0(IPC_PRIVATE):1.會建立新信號量集對象徘铝。2.大于0的32位整數(shù):視參數(shù)semflg來確定
操作,通常要求此值來源于ftok返回的IPC鍵值
nsems:創(chuàng)建信號量集中信號量的個數(shù)惯吕,該參數(shù)只在創(chuàng)建信號量集時有效
semflg:
0:取信號量集標識符惕它,若不存在則函數(shù)會報錯
IPC_CREAT:當semflg&IPC_CREAT為真時,如果內(nèi)核中不存在鍵值與key相等的信號量集废登,
則新建一個信號量集淹魄;如果存在這樣的信號量集,返回此信號量集的標識符堡距。
IPC_CREAT|IPC_EXCL:如果內(nèi)核中不存在鍵值與key相等的信號量集甲锡,則新建一個消息隊列;
如果存在這樣的信號量集則報錯羽戒。
返回值:成功返回信號量的標識符缤沦,失敗返回-1。
錯誤代碼:
EACCESS:沒有權(quán)限
EEXIST:信號量集已經(jīng)存在易稠,無法創(chuàng)建
EIDRM:信號量集已經(jīng)刪除
ENOENT:信號量集不存在疚俱,同時semflg沒有設(shè)置IPC_CREAT標志
ENOMEM:沒有足夠的內(nèi)存創(chuàng)建新的信號量集
ENOSPC:超出限制
2. semop()
頭文件:#include <sys/types.h> <sys/ipc.h> <sys/sem.h>
函數(shù)的一般形式:int semop(int semid, struct sembuf *sops, unsigned nsops)
函數(shù)說明:對信號量集標識符為semid中的一個或多個信號量進行P操作或V操作
參數(shù)設(shè)置:
semid:信號量集標識符
sops:指向進行操作的信號量集結(jié)構(gòu)體數(shù)組的首地址。
struct sembuf
{
short semnum; /*信號量集合中的信號量編號缩多,0代表第1個信號量*/
short val;/*若val>0進行V操作信號量值加val呆奕,表示進程釋放控制的資源 */
/*若val<0進行P操作信號量值減val,若(semval-val)<0(semval為該信號量值)衬吆,
則調(diào)用進程阻塞梁钾,直到資源可用;若設(shè)置IPC_NOWAIT不會睡眠逊抡,進程直接返回
EAGAIN錯誤*/
/*若val==0時阻塞等待信號量為0姆泻,調(diào)用進程進入睡眠狀態(tài),直到信號值為0冒嫡;
若設(shè)置IPC_NOWAIT拇勃,進程不會睡眠,直接返回EAGAIN錯誤*/
short flag; /*0 設(shè)置信號量的默認操作*/
/*IPC_NOWAIT設(shè)置信號量操作不等待*/
/*SEM_UNDO 選項會讓內(nèi)核記錄一個與調(diào)用進程相關(guān)的UNDO記錄孝凌,如果該進程崩潰方咆,
則根據(jù)這個進程的UNDO記錄自動恢復(fù)相應(yīng)信號量的計數(shù)值*/
};
nsops:進行操作信號量的個數(shù),即sops結(jié)構(gòu)變量的個數(shù)蟀架,需大于或等于1瓣赂。最常見設(shè)置此值等于1,
只完成對一個信號量的操作
返回值:成功返回信號量的標識符片拍,失敗返回-1煌集。
錯誤代碼:
E2BIG:一次對信號量個數(shù)的操作超過了系統(tǒng)限制
EACCESS:權(quán)限不夠
EAGAIN:使用了IPC_NOWAIT,但操作不能繼續(xù)進行
EFAULT:sops指向的地址無效
EIDRM:信號量集已經(jīng)刪除
EINTR:當睡眠時接收到其他信號
EINVAL:信號量集不存在,或者semid無效
ENOMEM:使用了SEM_UNDO捌省,但無足夠的內(nèi)存創(chuàng)建所需的數(shù)據(jù)結(jié)構(gòu)
ERANGE:信號量值超出范圍
3. semctl()
頭文件:#include <sys/types.h> <sys/ipc.h> <sys/sem.h>
函數(shù)的一般形式:int semctl(int semid, int semnum, int cmd, union semun arg)
函數(shù)說明:得到一個信號量集標識符或創(chuàng)建一個信號量集對象并返回信號量集標識符
參數(shù)設(shè)置:
semid:信號量集標識符
semnum:信號量集數(shù)組上的下標苫纤,表示某一個信號量
cmd:
IPC_STAT:從信號量集上檢索semid_ds結(jié)構(gòu),并存到semun聯(lián)合體參數(shù)的成員buf的地址中
IPC_SET:設(shè)置一個信號量集合的semid_ds結(jié)構(gòu)中ipc_perm域的值纲缓,并從semun的buf中取出值
IPC_RMID:從內(nèi)核中刪除信號量集合
GETALL:從信號量集合中獲得所有信號量的值卷拘,并把其整數(shù)值存到semun聯(lián)合體成員的一個指針數(shù)組中
GETNCNT:返回當前等待資源的進程個數(shù)
GETPID:返回最后一個執(zhí)行系統(tǒng)調(diào)用semop()進程的PID
GETVAL:返回信號量集合內(nèi)單個信號量的值
GETZCNT:返回當前等待100%資源利用的進程個數(shù)
SETALL:與GETALL正好相反
SETVAL:用聯(lián)合體中val成員的值設(shè)置信號量集合中單個信號量的值
arg:
union semun
{
short val; /*SETVAL用的值*/
struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds結(jié)構(gòu)*/
unsigned short* array; /*SETALL色徘、GETALL用的數(shù)組值*/
struct seminfo *buf; /*為控制IPC_INFO提供的緩存*/
} arg;
返回值:成功返回大于或等于0恭金;失敗返回-1。
錯誤代碼:
EACCESS:權(quán)限不夠
EFAULT:arg指向的地址無效
EIDRM:信號量集已經(jīng)刪除
EINVAL:信號量集不存在褂策,或者semid無效
EPERM:進程有效用戶沒有cmd的權(quán)限
ERANGE:信號量值超出范圍
// 案例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* array for GETALL, SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
};
/***對信號量數(shù)組semnum編號的信號量做P操作***/
int P(int semid, int semnum)
{
struct sembuf sops = {semnum, -1, SEM_UNDO};
return (semop(semid, &sops,1));
}
/***對信號量數(shù)組semnum編號的信號量做V操作***/
int V(int semid, int semnum)
{
struct sembuf sops = {semnum, +1, SEM_UNDO};
return (semop(semid, &sops,1));
}
int main(int argc, char **argv)
{
int key ;
int semid,ret;
union semun arg;
struct sembuf semop;
int flag ;
key = ftok("/tmp", 0x66 ) ;
if ( key < 0 )
{
perror("ftok key error") ;
return -1 ;
}
/***本程序創(chuàng)建了三個信號量横腿,實際使用時只用了一個0號信號量***/
semid = semget(key, 3, IPC_CREAT | 0600);
if (semid == -1)
{
perror("create semget error");
return ;
}
if ( argc == 1 )
{
arg.val = 1;
/***對0號信號量設(shè)置初始值***/
ret =semctl(semid, 0, SETVAL, arg);
if (ret < 0 )
{
perror("ctl sem error");
semctl(semid, 0, IPC_RMID, arg);
return -1 ;
}
}
/***取0號信號量的值***/
ret = semctl(semid, 0, GETVAL, arg);
printf("after semctl setval sem[0].val = [%d]\n", ret);
system("date") ;
printf("P operate begin\n") ;
flag = P(semid, 0) ;
if ( flag )
{
perror("P operate error") ;
return -1 ;
}
printf("P operate end\n") ;
ret = semctl(semid, 0, GETVAL, arg);
printf("after P sem[0].val = [%d]\n", ret);
system("date") ;
if ( argc == 1 )
{
sleep(120) ;
}
printf("V operate begin\n") ;
if (V(semid, 0) < 0)
{
perror("V operate error") ;
return -1 ;
}
printf("V operate end\n") ;
ret = semctl(semid, 0, GETVAL, arg);
printf("after V sem[0].val = %d\n", ret);
system("date") ;
if ( argc >1 )
{
semctl(semid, 0, IPC_RMID, arg);
}
return 0 ;
}
3. 消息隊列(message queue)
消息隊列是由消息的鏈表,存放在內(nèi)核中并由消息隊列標識符標識斤寂。消息隊列克服了信號傳遞信息少耿焊、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點。
1. msgget()
頭文件:#include <sys/types.h> <sys/ipc.h> <sys/msg.h>
函數(shù)的一般形式:int msgget(key_t key, int msgflg)
函數(shù)說明:得到消息隊列標識符或創(chuàng)建一個消息隊列對象并返回消息隊列標識符
參數(shù)設(shè)置:
key:
0(IPC_PRIVATE):會建立新的消息隊列
大于0的32位整數(shù):視參數(shù)msgflg來確定操作遍搞。通常要求此值來源于ftok返回的IPC鍵值
msgflg:
0:取消息隊列標識符罗侯,若不存在則函數(shù)會報錯
IPC_CREAT:當msgflg&IPC_CREAT為真時,如果內(nèi)核中不存在鍵值與key相等的消息隊列溪猿,則新建一個消息隊列钩杰;如果存在這樣的消息隊列纫塌,返回此消息隊列的標識符
IPC_CREAT|IPC_EXCL:如果內(nèi)核中不存在鍵值與key相等的消息隊列,則新建一個消息隊列讲弄;如果存在這樣的消息隊列則報錯
返回值:成功返回消息隊列標識符措左,失敗返回 -1。
錯誤代碼:
EACCES:指定的消息隊列已存在避除,但調(diào)用進程沒有權(quán)限訪問它
EEXIST:key指定的消息隊列已存在怎披,而msgflg中同時指定IPC_CREAT和IPC_EXCL標志
ENOENT:key指定的消息隊列不存在同時msgflg中沒有指定IPC_CREAT標志
ENOMEM:需要建立消息隊列,但內(nèi)存不足
ENOSPC:需要建立消息隊列瓶摆,但已達到系統(tǒng)的限制
2. msgctl()
頭文件:#include <sys/types.h> <sys/ipc.h> <sys/msg.h>
函數(shù)的一般形式:int msgctl(int msqid, int cmd, struct msqid_ds *buf)
函數(shù)說明:獲取或者設(shè)置消息隊列的屬性
參數(shù)設(shè)置:
msqid:消息隊列的標識符
cmd:
IPC_STAT:獲得msgid的消息隊列頭數(shù)據(jù)到buf中
IPC_SET:設(shè)置消息隊列的屬性凉逛,要設(shè)置的屬性需先存儲在buf中,可設(shè)置的屬性包括:msg_perm.uid群井、msg_perm.gid状飞、msg_perm.mode以及msg_qbytes
buf:消息隊列管理結(jié)構(gòu)體
返回值:成功返回0,失敗返回 -1蝌借。
錯誤代碼:
EACCESS:參數(shù)cmd為IPC_STAT昔瞧,確無權(quán)限讀取該消息隊列
EFAULT:參數(shù)buf指向無效的內(nèi)存地址
EIDRM:標識符為msqid的消息隊列已被刪除
EINVAL:無效的參數(shù)cmd或msqid
EPERM:參數(shù)cmd為IPC_SET或IPC_RMID,卻無足夠的權(quán)限執(zhí)行
3. msgsnd()
頭文件:#include <sys/types.h> <sys/ipc.h> <sys/msg.h>
函數(shù)的一般形式:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
函數(shù)說明:將msgp消息寫入到標識符為msqid的消息隊列
參數(shù)設(shè)置:
msqid:消息隊列標識符
msgp:
發(fā)送給隊列的消息菩佑。msgp可以是任何類型的結(jié)構(gòu)體自晰,但第一個字段必須為long類型,
即表明此發(fā)送消息的類型稍坯,msgrcv根據(jù)此接收消息酬荞。msgp定義的參照格式如下:
struct s_msg
{ /*msgp定義的參照格式*/
long type; /* 必須大于0,消息類型 */
char mtext[256]; /*消息正文,可以是其他任何類型*/
} msgp;
msgsz:要發(fā)送消息的大小瞧哟,不含消息類型占用的4個字節(jié),即mtext的長度
msgflg:
0:當消息隊列滿時混巧,msgsnd將會阻塞,直到消息能寫進消息隊列
IPC_NOWAIT:當消息隊列已滿的時候勤揩,msgsnd函數(shù)不等待立即返回
IPC_NOERROR:若發(fā)送的消息大于size字節(jié)咧党,則把該消息截斷,截斷部分將被丟棄陨亡,
且不通知發(fā)送進程傍衡。
返回值:成功返回0,失敗返回 -1负蠕。
錯誤代碼:
EAGAIN:參數(shù)msgflg設(shè)為IPC_NOWAIT蛙埂,而消息隊列已滿
EIDRM:標識符為msqid的消息隊列已被刪除
EACCESS:無權(quán)限寫入消息隊列
EFAULT:參數(shù)msgp指向無效的內(nèi)存地址
EINTR:隊列已滿而處于等待情況下被信號中斷
EINVAL:無效的參數(shù)msqid、msgsz或參數(shù)消息類型type小于0
msgsnd()為阻塞函數(shù)遮糖,當消息隊列容量滿或消息個數(shù)滿會阻塞绣的。消息隊列已被刪除,則返回EIDRM錯誤;被信號中斷返回E_INTR錯誤屡江。
如果設(shè)置IPC_NOWAIT消息隊列滿或個數(shù)滿時會返回-1芭概,并且置EAGAIN錯誤。
msgsnd()解除阻塞的條件有以下三個條件:
- 不滿足消息隊列滿或個數(shù)滿兩個條件盼理,即消息隊列中有容納該消息的空間谈山。
- msqid代表的消息隊列被刪除。
- 調(diào)用msgsnd函數(shù)的進程被信號中斷宏怔。
4. msgrcv()
頭文件:#include <sys/types.h> <sys/ipc.h> <sys/msg.h>
函數(shù)的一般形式:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
函數(shù)說明:從標識符為msqid的消息隊列讀取消息并存于msgp中,讀取后把此消息從消息隊列中刪除
參數(shù)設(shè)置:
msqid:消息隊列標識符
msgp:存放消息的結(jié)構(gòu)體畴椰,結(jié)構(gòu)體類型要與msgsnd函數(shù)發(fā)送的類型相同
msgsz:要接收消息的大小臊诊,不含消息類型占用的4個字節(jié)
msgtyp:
0:接收第一個消息
>0:接收類型等于msgtyp的第一個消息
<0:接收類型等于或者小于msgtyp絕對值的第一個消息
msgflg:
0: 阻塞式接收消息,沒有該類型的消息msgrcv函數(shù)一直阻塞等待
IPC_NOWAIT:如果沒有返回條件的消息調(diào)用立即返回斜脂,此時錯誤碼為ENOMSG
IPC_EXCEPT:與msgtype配合使用返回隊列中第一個類型不為msgtype的消息
IPC_NOERROR:如果隊列中滿足條件的消息內(nèi)容大于所請求的size字節(jié)抓艳,則把該消息截
斷,截斷部分將被丟棄
返回值:成功返回實際讀取的消息長度帚戳,失敗返回 -1玷或。
錯誤代碼:
E2BIG:消息數(shù)據(jù)長度大于msgsz而msgflag沒有設(shè)置IPC_NOERROR
EIDRM:標識符為msqid的消息隊列已被刪除
EACCESS:無權(quán)限讀取該消息隊列
EFAULT:參數(shù)msgp指向無效的內(nèi)存地址
ENOMSG:參數(shù)msgflg設(shè)為IPC_NOWAIT,而消息隊列中無消息可讀
EINTR:等待讀取隊列內(nèi)的消息情況下被信號中斷
msgrcv()解除阻塞的條件有以下三個:
- 消息隊列中有了滿足條件的消息片任。
- msqid代表的消息隊列被刪除偏友。
- 調(diào)用msgrcv()的進程被信號中斷。
5. 消息隊列案例
// msgqueue.h
#ifndef MSGQUEUE_H
#define MSGQUEUE_H
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <error.h>
#define BUF_SIZE 1024
struct my_msg //定義magsnd()的消息結(jié)構(gòu)體
{
long mtype;
char mtext[BUF_SIZE];
};
int get_key();
void send_msg(int msgid, struct my_msg msg_buf);
void receive_msg(int msgid, struct my_msg *qbuf, long type);
void del_msg(int msgid);
#endif
// msgqueue.c
#include "msgqueue.h"
int get_key()
{
int key;
key = ftok("mymsg.tmp", 1); //ftok()是系統(tǒng)IPC鍵值的格式轉(zhuǎn)換函數(shù)
}
void send_msg(int msgid, struct my_msg msg_buf) //發(fā)送消息
{
printf("enter the msg text\n");
fgets(msg_buf.mtext, BUF_SIZE, stdin); //消息的內(nèi)容
msg_buf.mtype = 1; //消息的類型
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// 發(fā)送消息
if(msgsnd(msgid, (void *)&msg_buf, BUF_SIZE,0) == -1)
{
perror("send msg error");
exit(1);
}
printf("send msg success\n");
}
void receive_msg(int msgid, struct my_msg *qbuf, long type)
{
printf("正在接收消息\n");
/*ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);*/
if(msgrcv(msgid, qbuf, BUF_SIZE, type, IPC_NOWAIT) == -1)
{
printf("rcv msg error\n");
exit(1);
}
else
printf("Type:%ld, Text:%s", qbuf->mtype, qbuf->mtext);
}
void del_msg(int msgid)
{
if(msgctl(msgid, IPC_RMID,0) == -1)
{
perror("rm msg error");
exit(1);
}
}
//main.c 在同一個進程中
#include "msgqueue.h"
int main()
{
struct my_msg msg_buf;
key_t key;
int msgid;
key = get_key();
if(key < 0)
{
perror("get key error");
exit(1);
}
msgid = msgget(key, IPC_CREAT | 0644); //創(chuàng)建消息隊列
if(msgid < 0)
{
perror("msgget error");
exit(1);
}
printf("get msg success\n");
send_msg(msgid, msg_buf);
receive_msg(msgid, &msg_buf, 0);
del_msg(msgid);
}
下面的是在不同的進程中通信
// send.c 發(fā)送端
#include "msgqueue.h"
int main()
{
struct my_msg msg_buf;
key_t key;
int msgid;
key = get_key();
if(key < 0)
{
perror("get key error");
exit(1);
}
msgid = msgget(key, IPC_CREAT | 0644); //創(chuàng)建消息隊列
if(msgid < 0)
{
perror("msgget error");
exit(1);
}
printf("get msg success\n");
send_msg(msgid, msg_buf);
printf("send msg success\n");
del_msg(msgid);
}
// receive.c 接收端
#include "msgqueue.h"
int main()
{
struct my_msg msg_buf;
key_t key;
int msgid;
key = get_key();
if(key < 0)
{
perror("get key error");
exit(1);
}
msgid = msgget(key, IPC_CREAT | 0644); //創(chuàng)建消息隊列
if(msgid < 0)
{
perror("msgget error");
exit(1);
}
printf("get msg success\n");
receive_msg(msgid, &msg_buf, 0);
printf("receive msg success\n");
del_msg(msgid);
}
備注:
套接字通信主要用于不同機器之間的通信对供,在此處不做說明位他,另外單做說明。