在Linux中存在下面幾種進(jìn)程間通信方式:
1.POSIX無(wú)名信號(hào)量
2.System V信號(hào)量
3.System V消息隊(duì)列
4.System V共享內(nèi)存
5.管道(FIFO)
--------------------------------------------------------------------------------
1。POSIX無(wú)名信號(hào)量
如果你學(xué)習(xí)過操作系統(tǒng),那么肯定熟悉PV操作了.PV操作是原子操作.也就是操作是不可以中斷的,在一定的時(shí)間內(nèi),只能夠有一個(gè)進(jìn)程的代碼在CPU上面執(zhí)行.在系統(tǒng)當(dāng)中,有時(shí)候?yàn)榱隧樌氖褂煤捅Wo(hù)共享資源,大家提出了信號(hào)的概念. 假設(shè)我們要使用一臺(tái)打印機(jī), 如果在同一時(shí)刻有兩個(gè)進(jìn)程在向打印機(jī)輸出,那么最終的結(jié)果會(huì)是什么呢.為了處理這種情況,POSIX標(biāo)準(zhǔn)提出了有名信號(hào)量和無(wú)名信號(hào)量的概念,由于 Linux只實(shí)現(xiàn)了無(wú)名信號(hào)量,我們?cè)谶@里就只是介紹無(wú)名信號(hào)量了. 信號(hào)量的使用主要是用來(lái)保護(hù)共享資源,使的資源在一個(gè)時(shí)刻只有一個(gè)進(jìn)程所擁有.為此我們可以使用一個(gè)信號(hào)燈.當(dāng)信號(hào)燈的值為某個(gè)值的時(shí)候,就表明此時(shí)資源不可以使用.否則就表>示可以使用. 為了提供效率,系統(tǒng)提供了下面幾個(gè)函數(shù)?
POSIX的無(wú)名信號(hào)量的函數(shù)有以下幾個(gè):
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)建一個(gè)信號(hào)燈,并初始化其值為value.pshared決定了信號(hào)量能否在幾個(gè)進(jìn)程間共享.由于目前Linux還沒有實(shí)現(xiàn)進(jìn)程間共享信號(hào)燈,所以這個(gè)值只能夠取0.
sem_destroy是用來(lái)刪除信號(hào)燈的.
sem_wait調(diào)用將阻塞進(jìn)程,直到信號(hào)燈的值大于0.這個(gè)函數(shù)返回的時(shí)候自動(dòng)的將信號(hào)燈的值的件一.
sem_post和sem_wait相反,是將信號(hào)燈的內(nèi)容加一同時(shí)發(fā)出信號(hào)喚醒等待的進(jìn)程..
sem_trywait和sem_wait相同,不過不阻塞的,當(dāng)信號(hào)燈的值為0的時(shí)候返回EAGAIN,表示以后重試.
sem_getvalue得到信號(hào)燈的值.
由于Linux不支持,我們沒有辦法用源程序解釋了.
2。System V信號(hào)量
信號(hào)燈的主要用途是保護(hù)臨界資源(在一個(gè)時(shí)刻只被一個(gè)進(jìn)程所擁有). System V信號(hào)量的函數(shù)主要有下面幾個(gè).
key_t?ftok(
char
?
*
pathname,
char
?proj);
int
?semget(key_t?key,
int
?nsems,
int
?semflg);
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;?
/*
?使用那一個(gè)信號(hào)?
*/
short
?sem_op;?
/*
?進(jìn)行什么操作?
*/
short
?sem_flg;?
/*
?操作的標(biāo)志?
*/
};
ftok函數(shù)是根據(jù)pathname和proj來(lái)創(chuàng)建一個(gè)關(guān)鍵字.
semget創(chuàng)建一個(gè)信號(hào)量.成功時(shí)返回信號(hào)的ID,key是一個(gè)關(guān)鍵字,可以是用ftok創(chuàng)建的也可以是IPC_PRIVATE表明由系統(tǒng)選用一個(gè)關(guān)鍵字. nsems表明我們創(chuàng)建的信號(hào)個(gè)數(shù).semflg是創(chuàng)建的權(quán)限標(biāo)志,和我們創(chuàng)建一個(gè)文件的標(biāo)志相同.
semctl對(duì)信號(hào)量進(jìn)行一系列的控制.semid是要操作的信號(hào)標(biāo)志,semnum是信號(hào)的個(gè)數(shù),cmd是操作的命令.經(jīng)常用的兩個(gè)值是:SETVAL(設(shè)置信號(hào)量的值)和IPC_RMID(刪除信號(hào)燈).arg是一個(gè)給cmd的參數(shù).
semop是對(duì)信號(hào)進(jìn)行操作的函數(shù).semid是信號(hào)標(biāo)志,spos是一個(gè)操作數(shù)組表明要進(jìn)行什么操作,nspos表明數(shù)組的個(gè)數(shù). 如果 sem_op大于0,那么操作將sem_op加入到信號(hào)量的值中,并喚醒等待信號(hào)增加的進(jìn)程. 如果為0,當(dāng)信號(hào)量的值是0的時(shí)候,函數(shù)返回,否則阻塞直到信號(hào)量的值為0. 如果小于0,函數(shù)判斷信號(hào)量的值加上這個(gè)負(fù)值.如果結(jié)果為0喚醒等待信號(hào)量為0的進(jìn)程,如果小與0函數(shù)阻塞.如果大于0,那么從信號(hào)量里面減去這個(gè)值并返回.
下面我們一以一個(gè)實(shí)例來(lái)說(shuō)明這幾個(gè)函數(shù)的使用方法.
#define
?PERMS?S_IRUSR|S_IWUSR
void
?init_semaphore_struct(
struct
?sembuf?
*
sem,
int
?semnum,
int
?semop,
int
?semflg)
{
?
/*
?初始話信號(hào)燈結(jié)構(gòu)?
*/
?sem
->
sem_num
=
semnum;
?sem
->
sem_op
=
semop;
?sem
->
sem_flg
=
semflg;
}
int
?del_semaphore(
int
?semid)
{
?
/*
?信號(hào)燈并不隨程序的結(jié)束而被刪除,如果我們沒刪除的話(將1改為0)
?可以用ipcs命令查看到信號(hào)燈,用ipcrm可以刪除信號(hào)燈的
?
*/
?
#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)選擇一個(gè)關(guān)鍵字來(lái)創(chuàng)建??
*/
?
/*
?創(chuàng)建以后信號(hào)燈的初始值為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
);
?
/*
?開始的時(shí)候有一個(gè)系統(tǒng)資源(一個(gè)標(biāo)準(zhǔn)錯(cuò)誤輸出)?
*/
?
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)建一個(gè)進(jìn)程鏈?
*/
?
for
(i
=
0
;i
<
n;i
++
)
??
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)程反問信號(hào)燈的時(shí)候,我們刪除了信號(hào)燈?
*/
?
while
((wait(
&
status)
==-
1
)
&&
(errno
==
EINTR));
?
/*
?信號(hào)燈只能夠被刪除一次的?
*/
?
if
(i
==
1
)
??
if
(del_semaphore(semid)
==-
1
)
?fprintf(stderr,
"
[%d]:Destroy?Semaphore?Error:%s\n\a
"
,?getpid(),strerror(errno));
?exit(
0
);
}
3。SystemV消息隊(duì)列
為了便于進(jìn)程之間通信,我們可以使用管道通信 SystemV也提供了一些函數(shù)來(lái)實(shí)現(xiàn)進(jìn)程的通信.這就是消息隊(duì)列.
int
?msgget(key_t?key,
int
?msgflg);
int
?msgsnd(
int
?msgid,
struct
?msgbuf?
*
msgp,
int
?msgsz,
int
?msgflg);
int
?msgrcv(
int
?msgid,
struct
?msgbuf?
*
msgp,
int
?msgsz,
long
?msgtype,
int
?msgflg);
int
?msgctl(Int?msgid,
int
?cmd,
struct
?msqid_ds?
*
buf);
struct
?msgbuf?{
long
?msgtype;???
/*
?消息類型?
*/
.?
/*
?其他數(shù)據(jù)類型?
*/
}
msgget函數(shù)和semget一樣,返回一個(gè)消息隊(duì)列的標(biāo)志.
msgctl和semctl是對(duì)消息進(jìn)行控制.
msgsnd和msgrcv函數(shù)是用來(lái)進(jìn)行消息通訊的.msgid是接受或者發(fā)送的消息隊(duì)列標(biāo)志. msgp是接受或者發(fā)送的內(nèi)容.msgsz是消息的大小. 結(jié)構(gòu)msgbuf包含的內(nèi)容是至少有一個(gè)為msgtype.其他的成分是用戶定義的.對(duì)于發(fā)送函數(shù)msgflg指出緩沖區(qū)用完時(shí)候的操作.接受函數(shù)指出無(wú)消息時(shí)候的處理.一般為 0. 接收函數(shù)msgtype指出接收消息時(shí)候的操作.
如果msgtype=0,接收消息隊(duì)列的第一個(gè)消息.大于0接收隊(duì)列中消息類型等于這個(gè)值的第一個(gè)消息.小于0接收消息隊(duì)列中小于或者等于 msgtype絕對(duì)值的所有消息中的最小一個(gè)消息. 我們以一個(gè)實(shí)例來(lái)解釋進(jìn)程通信.下面這個(gè)程序有server和client組成.先運(yùn)行服務(wù)端后運(yùn)行客戶端.
服務(wù)端 server.c
#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)
#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)建的消息隊(duì)列最后沒有刪除,我們要使用ipcrm命令來(lái)刪除的.
4。SystemV共享內(nèi)存
還有一個(gè)進(jìn)程通信的方法是使用共享內(nèi)存.SystemV提供了以下幾個(gè)函數(shù)以實(shí)現(xiàn)共享內(nèi)存.
int
?shmget(key_t?key,
int
?size,
int
?shmflg);
void
?
*
shmat(
int
?shmid,
const
?
void
?
*
shmaddr,
int
?shmflg);
int
?shmdt(
const
?
void
?
*
shmaddr);
int
?shmctl(
int
?shmid,
int
?cmd,
struct
?shmid_ds?
*
buf);
shmget和shmctl沒有什么好解釋的.size是共享內(nèi)存的大小.
shmat是用來(lái)連接共享內(nèi)存的.shmdt是用來(lái)斷開共享內(nèi)存的.
shmaddr,shmflg我們只要用0代替就可以了.在使用一個(gè)共享內(nèi)存之前我們調(diào)用 shmat得到共享內(nèi)存的開始地址,使用結(jié)束以后我們使用shmdt斷開這個(gè)內(nèi)存.?
#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
);
?}?
}?
這個(gè)程序是父進(jìn)程將參數(shù)寫入到共享內(nèi)存,然后子進(jìn)程把內(nèi)容讀出來(lái).最后我們要使用ipcrm釋放資源的.先用ipcs找出ID然后用ipcrm shm ID刪除.?
5、管道(FIFO)
管道有無(wú)名管道和有名管道兩種,無(wú)名管道一般在父子進(jìn)程中使用。
無(wú)名管道的使用方法一般是:
#include?<unistd.h>
int?pipe(int?filedes[2]);
filedes[0]用于讀出數(shù)據(jù),讀取時(shí)必須關(guān)閉寫入端,即close(filedes[1]);
filedes[1]用于寫入數(shù)據(jù),寫入時(shí)必須關(guān)閉讀取端,即close(filedes[0])。
無(wú)名管道的使用方法是:
#include?<sys/types.h>
#include?<sys/stat.h>
int?mkfifo(const?char?*pathname,?mode_t?mode);
讀寫管道與讀寫文件的操作相同。