Linux下的進(jìn)程通信(IPC)
前言:
1.POSIX無名信號量
2.System V信號量
3.System V消息隊列
4.System V共享內(nèi)存
1:POSIX無名信號量
如果你學(xué)習(xí)過操作系統(tǒng),那么肯定熟悉PV操作了.PV操作是原子操作.也就是操作是不可以中斷的,在一定的時間內(nèi),只能夠有一個進(jìn)程的代碼在CPU上面執(zhí)行.在系統(tǒng)當(dāng)中,有時候為了順利的使用和保護(hù)共享資源,大家提出了信號的概念. 假設(shè)我們要使用一臺打印機(jī),如果在同一時刻有兩個進(jìn)程在向打印機(jī)輸出,那么最終的結(jié)果會是什么呢.為了處理這種情況,POSIX標(biāo)準(zhǔn)提出了有名信號量和無名信號量的概念,由于Linux只實現(xiàn)了無名信號量,我們在這里就只是介紹無名信號量了. 信號量的使用主要是用來保護(hù)共享資源,使的資源在一個時刻只有一個進(jìn)程所擁有.為此我們可以使用一個信號燈.當(dāng)信號燈的值為某個值的時候,就表明此時資源不可以使用.否則就表>示可以使用. 為了提供效率,系統(tǒng)提供了下面幾個函數(shù)
POSIX的無名信號量的函數(shù)有以下幾個:
#include
int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem);
sem_init創(chuàng)建一個信號燈,并初始化其值為value.pshared決定了信號量能否在幾個進(jìn)程間共享.由于目前Linux還沒有實現(xiàn)進(jìn)程間共享信號燈,所以這個值只能夠取0. sem_destroy是用來刪除信號燈的.sem_wait調(diào)用將阻塞進(jìn)程,直到信號燈的值大于0.這個函數(shù)返回的時候自動的將信號燈的值的件一.sem_post和sem_wait相反,是將信號燈的內(nèi)容加一同時發(fā)出信號喚醒等待的進(jìn)程..sem_trywait和sem_wait相同,不過不阻塞的,當(dāng)信號燈的值為0的時候返回EAGAIN,表示以后重試.sem_getvalue得到信號燈的值.
由于Linux不支持,我們沒有辦法用源程序解釋了.
這幾個函數(shù)的使用相當(dāng)簡單的.比如我們有一個程序要向一個系統(tǒng)打印機(jī)打印兩頁.我們首先創(chuàng)建一個信號燈,并使其初始值為1,表示我們有一個資源可用.然后一個進(jìn)程調(diào)用sem_wait由于這個時候信號燈的值為1,所以這個函數(shù)返回,打印機(jī)開始打印了,同時信號燈的值為0 了. 如果第二個進(jìn)程要打印,調(diào)用sem_wait時候,由于信號燈的值為0,資源不可用,于是被阻塞了.當(dāng)?shù)谝粋€進(jìn)程打印完成以后,調(diào)用sem_post信號燈的值為1了,這個時候系統(tǒng)通知第二個進(jìn)程,于是第二個進(jìn)程的sem_wait返回.第二個進(jìn)程開始打印了.
不過我們可以使用線程來解決這個問題的.我們會在后面解釋什么是線程的.編譯包含上面這幾個函數(shù)的程序要加上 -lrt選賢,以連接librt.so庫
2:System V信號量
為了解決上面哪個問題,我們也可以使用System V信號量.很幸運的是Linux實現(xiàn)了System V信號量.這樣我們就可以用實例來解釋了. System V信號量的函數(shù)主要有下面幾個.
#include
#include
#include
key_t ftok(char *pathname,char proj);
int semget(key_t key,int nsems,int sem);
int semctl(int semid,int semnum,int cmd,union semun arg);
int semop(int semid,struct sembuf *spos,int nspos);
struct sembuf {
short sem_num; /* 使用那一個信號 */
short sem_op; /* 進(jìn)行什么操作 */
short sem_; /* 操作的標(biāo)志 */
};
ftok函數(shù)是根據(jù)pathname和proj來創(chuàng)建一個關(guān)鍵字.semget創(chuàng)建一個信號量.成功時返回信號的ID,key是一個關(guān)鍵字,可以是用ftok創(chuàng)建的也可以是IPC_PRIVATE表明由系統(tǒng)選用一個關(guān)鍵字. nsems表明我們創(chuàng)建的信號個數(shù).sem是創(chuàng)建的權(quán)限標(biāo)志,和我們創(chuàng)建一個文件的標(biāo)志相同.
semctl對信號量進(jìn)行一系列的控制.semid是要操作的信號標(biāo)志,semnum是信號的個數(shù),cmd是操作的命令.經(jīng)常用的兩個值是:SETVAL(設(shè)置信號量的值)和IPC_RMID(刪除信號燈).arg是一個給cmd的參數(shù).
semop是對信號進(jìn)行操作的函數(shù).semid是信號標(biāo)志,spos是一個操作數(shù)組表明要進(jìn)行什么操作,nspos表明數(shù)組的個數(shù). 如果sem_op大于0,那么操作將sem_op加入到信號量的值中,并喚醒等待信號增加的進(jìn)程. 如果為0,當(dāng)信號量的值是0的時候,函數(shù)返回,否則阻塞直到信號量的值為0. 如果小于0,函數(shù)判斷信號量的值加上這個負(fù)值.如果結(jié)果為0喚醒等待信號量為0的進(jìn)程,如果小與0函數(shù)阻塞.如果大于0,那么從信號量里面減去這個值并返回.
下面我們一以一個實例來說明這幾個函數(shù)的使用方法.這個程序用標(biāo)準(zhǔn)錯誤輸出來代替我們用的打印機(jī).
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PERMS S_IRUSR|S_IWUSR
void init_semaphore_struct(struct sembuf *sem,int semnum,
int semop,int sem)
{
/* 初始話信號燈結(jié)構(gòu) */
sem->sem_num=semnum;
sem->sem_op=semop;
sem->sem_=sem;
}
int del_semaphore(int semid)
{
/* 信號燈并不隨程序的結(jié)束而被刪除,如果我們沒刪除的話(將1改為0)
可以用ipcs命令查看到信號燈,用ipcrm可以刪除信號燈的
*/
#if 1
return semctl(semid,0,IPC_RMID);
#endif
}
int main(int argc,char **argv)
{
char buffer[MAX_CANON],*c;
int i,n;
int semid,semop_ret,status;
pid_t childpid;
struct sembuf semwait,semsignal;
if((argc!=2)||((n=atoi(argv[1]))<1))
{
fprintf(stderr,"Usage:%s number\n\a",argv[0]);
exit(1);
}
/* 使用IPC_PRIVATE 表示由系統(tǒng)選擇一個關(guān)鍵字來創(chuàng)建 */
/* 創(chuàng)建以后信號燈的初始值為0 */
if((semid=semget(IPC_PRIVATE,1,PERMS))==-1)
{
fprintf(stderr,"[%d]:Acess Semaphore Error:%s\n\a",
getpid(),strerror(errno));
exit(1);
}
/* semwait是要求資源的操作(-1) */
init_semaphore_struct(&semwait,0,-1,0);
/* semsignal是釋放資源的操作(+1) */
init_semaphore_struct(&semsignal,0,1,0);
/* 開始的時候有一個系統(tǒng)資源(一個標(biāo)準(zhǔn)錯誤輸出) */
if(semop(semid,&semsignal,1)==-1)
{
fprintf(stderr,"[%d]:Increment Semaphore Error:%s\n\a",
getpid(),strerror(errno));
if(del_semaphore(semid)==-1)
fprintf(stderr,"[%d]:Destroy Semaphore Error:%s\n\a",
getpid(),strerror(errno));
exit(1);
}
/* 創(chuàng)建一個進(jìn)程鏈 */
for(i=0;ibr> if(childpid=fork()) break;
sprintf(buffer,"[i=%d]-->[Process=%d]-->[Parent=%d]-->[Child=%d]\n",
i,getpid(),getppid(),childpid);
c=buffer;
/* 這里要求資源,進(jìn)入原子操作 */
while(((semop_ret=semop(semid,&semwait,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
{
fprintf(stderr,"[%d]:Decrement Semaphore Error:%s\n\a",
getpid(),strerror(errno));
}
else
{
while(*c!='\0')fputc(*c++,stderr);
/* 原子操作完成,趕快釋放資源 */
while(((semop_ret=semop(semid,&semsignal,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
fprintf(stderr,"[%d]:Increment Semaphore Error:%s\n\a",
getpid(),strerror(errno));
}
/* 不能夠在其他進(jìn)程反問信號燈的時候,我們刪除了信號燈 */
while((wait(&status)==-1)&&(errno==EINTR));
/* 信號燈只能夠被刪除一次的 */
if(i==1)
if(del_semaphore(semid)==-1)
fprintf(stderr,"[%d]:Destroy Semaphore Error:%s\n\a",
getpid(),strerror(errno));
exit(0);
}
信號燈的主要用途是保護(hù)臨界資源(在一個時刻只被一個進(jìn)程所擁有).
3:SystemV消息隊列
為了便于進(jìn)程之間通信,我們可以使用管道通信 SystemV也提供了一些函數(shù)來實現(xiàn)進(jìn)程的通信.這就是消息隊列.
#include
#include
#include
int msgget(key_t key,int msg);
int msgsnd(int msgid,struct msgbuf *msgp,int msgsz,int msg);
int msgrcv(int msgid,struct msgbuf *msgp,int msgsz,
long msgtype,int msg);
int msgctl(Int msgid,int cmd,struct msqid_ds *buf);
struct msgbuf {
long msgtype; /* 消息類型 */
....... /* 其他數(shù)據(jù)類型 */
}
msgget函數(shù)和semget一樣,返回一個消息隊列的標(biāo)志.msgctl和semctl是對消息進(jìn)行控制. msgsnd和msgrcv函數(shù)是用來進(jìn)行消息通訊的.msgid是接受或者發(fā)送的消息隊列標(biāo)志. msgp是接受或者發(fā)送的內(nèi)容.msgsz是消息的大小. 結(jié)構(gòu)msgbuf包含的內(nèi)容是至少有一個為msgtype.其他的成分是用戶定義的.對于發(fā)送函數(shù)msg指出緩沖區(qū)用完時候的操作.接受函數(shù)指出無消息時候的處理.一般為0. 接收函數(shù)msgtype指出接收消息時候的操作.
如果msgtype=0,接收消息隊列的第一個消息.大于0接收隊列中消息類型等于這個值的第一個消息.小于0接收消息隊列中小于或者等于msgtype絕對值的所有消息中的最小一個消息. 我們以一個實例來解釋進(jìn)程通信.下面這個程序有server和client組成.先運行服務(wù)端后運行客戶端.
服務(wù)端 server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MSG_FILE "server.c"
#define BUFFER 255
#define PERM S_IRUSR|S_IWUSR
struct msgtype {
long mtype;
char buffer[BUFFER+1];
};
int main()
{
struct msgtype msg;
key_t key;
int msgid;
if((key=ftok(MSG_FILE,'a'))==-1)
{
fprintf(stderr,"Creat Key Error:%s\a\n",strerror(errno));
exit(1);
}
if((msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL))==-1)
{
fprintf(stderr,"Creat Message Error:%s\a\n",strerror(errno));
exit(1);
}
while(1)
{
msgrcv(msgid,&msg,sizeof(struct msgtype),1,0);
fprintf(stderr,"Server Receive:%s\n",msg.buffer);
msg.mtype=2;
msgsnd(msgid,&msg,sizeof(struct msgtype),0);
}
exit(0);
}
--------------------------------------------------------------------------------
客戶端(client.c)
#include
#include
#include
#include
#include
#include
#include
#include
#define MSG_FILE "server.c"
#define BUFFER 255
#define PERM S_IRUSR|S_IWUSR
struct msgtype {
long mtype;
char buffer[BUFFER+1];
};
int main(int argc,char **argv)
{
struct msgtype msg;
key_t key;
int msgid;
if(argc!=2)
{
fprintf(stderr,"Usage:%s string\n\a",argv[0]);
exit(1);
}
if((key=ftok(MSG_FILE,'a'))==-1)
{
fprintf(stderr,"Creat Key Error:%s\a\n",strerror(errno));
exit(1);
}
if((msgid=msgget(key,PERM))==-1)
{
fprintf(stderr,"Creat Message Error:%s\a\n",strerror(errno));
exit(1);
}
msg.mtype=1;
strncpy(msg.buffer,argv[1],BUFFER);
msgsnd(msgid,&msg,sizeof(struct msgtype),0);
memset(&msg,'\0',sizeof(struct msgtype));
msgrcv(msgid,&msg,sizeof(struct msgtype),2,0);
fprintf(stderr,"Client receive:%s\n",msg.buffer);
exit(0);
}
注意服務(wù)端創(chuàng)建的消息隊列最后沒有刪除,我們要使用ipcrm命令來刪除的.
4:SystemV共享內(nèi)存
還有一個進(jìn)程通信的方法是使用共享內(nèi)存.SystemV提供了以下幾個函數(shù)以實現(xiàn)共享內(nèi)存.
#include
#include
#include
int shmget(key_t key,int size,int shm);
void *shmat(int shmid,const void *shmaddr,int shm);
int shmdt(const void *shmaddr);
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
shmget和shmctl沒有什么好解釋的.size是共享內(nèi)存的大小. shmat是用來連接共享內(nèi)存的.shmdt是用來斷開共享內(nèi)存的.不要被共享內(nèi)存詞語嚇倒,共享內(nèi)存其實很容易實現(xiàn)和使用的.shmaddr,shm我們只要用0代替就可以了.在使用一個共享內(nèi)存之前我們調(diào)用shmat得到共享內(nèi)存的開始地址,使用結(jié)束以后我們使用shmdt斷開這個內(nèi)存.
#include
#include
#include
#include
#include
#include
#include
#include
#define PERM S_IRUSR|S_IWUSR
int main(int argc,char **argv)
{
int shmid;
char *p_addr,*c_addr;
if(argc!=2)
{
fprintf(stderr,"Usage:%s\n\a",argv[0]);
exit(1);
}
if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)
{
fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno));
exit(1);
}
if(fork())
{
p_addr=shmat(shmid,0,0);
memset(p_addr,'\0',1024);
strncpy(p_addr,argv[1],1024);
exit(0);
}
else
{
c_addr=shmat(shmid,0,0);
printf("Client get %s",c_addr);
exit(0);
}
}
這個程序是父進(jìn)程將參數(shù)寫入到共享內(nèi)存,然后子進(jìn)程把內(nèi)容讀出來.最后我們要使用ipcrm釋放資源的.先用ipcs找出ID然后用ipcrm shm ID刪除.