Linux下C編程 進程通信 (IPC)
在Linux中存在下面幾種進程間通信方式:
1.POSIX無名信號量
2.System V信號量
3.System V消息隊列
4.System V共享內存
5.管道(FIFO)
--------------------------------------------------------------------------------
1。POSIX無名信號量
如果你學習過操作系統,那么肯定熟悉PV操作了.PV操作是原子操作.也就是操作是不可以中斷的,在一定的時間內,只能夠有一個進程的代碼在CPU上面執行.在系統當中,有時候為了順利的使用和保護共享資源,大家提出了信號的概念. 假設我們要使用一臺打印機, 如果在同一時刻有兩個進程在向打印機輸出,那么最終的結果會是什么呢.為了處理這種情況,POSIX標準提出了有名信號量和無名信號量的概念,由于 Linux只實現了無名信號量,我們在這里就只是介紹無名信號量了. 信號量的使用主要是用來保護共享資源,使的資源在一個時刻只有一個進程所擁有.為此我們可以使用一個信號燈.當信號燈的值為某個值的時候,就表明此時資源不可以使用.否則就表>示可以使用. 為了提供效率,系統提供了下面幾個函數?
POSIX的無名信號量的函數有以下幾個:






sem_init創建一個信號燈,并初始化其值為value.pshared決定了信號量能否在幾個進程間共享.由于目前Linux還沒有實現進程間共享信號燈,所以這個值只能夠取0.
sem_destroy是用來刪除信號燈的.
sem_wait調用將阻塞進程,直到信號燈的值大于0.這個函數返回的時候自動的將信號燈的值的件一.
sem_post和sem_wait相反,是將信號燈的內容加一同時發出信號喚醒等待的進程..
sem_trywait和sem_wait相同,不過不阻塞的,當信號燈的值為0的時候返回EAGAIN,表示以后重試.
sem_getvalue得到信號燈的值.
由于Linux不支持,我們沒有辦法用源程序解釋了.
2。System V信號量
信號燈的主要用途是保護臨界資源(在一個時刻只被一個進程所擁有). System V信號量的函數主要有下面幾個.
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;? /* ?使用那一個信號? */
short ?sem_op;? /* ?進行什么操作? */
short ?sem_flg;? /* ?操作的標志? */
};
ftok函數是根據pathname和proj來創建一個關鍵字.
semget創建一個信號量.成功時返回信號的ID,key是一個關鍵字,可以是用ftok創建的也可以是IPC_PRIVATE表明由系統選用一個關鍵字. nsems表明我們創建的信號個數.semflg是創建的權限標志,和我們創建一個文件的標志相同.
semctl對信號量進行一系列的控制.semid是要操作的信號標志,semnum是信號的個數,cmd是操作的命令.經常用的兩個值是:SETVAL(設置信號量的值)和IPC_RMID(刪除信號燈).arg是一個給cmd的參數.
semop是對信號進行操作的函數.semid是信號標志,spos是一個操作數組表明要進行什么操作,nspos表明數組的個數. 如果 sem_op大于0,那么操作將sem_op加入到信號量的值中,并喚醒等待信號增加的進程. 如果為0,當信號量的值是0的時候,函數返回,否則阻塞直到信號量的值為0. 如果小于0,函數判斷信號量的值加上這個負值.如果結果為0喚醒等待信號量為0的進程,如果小與0函數阻塞.如果大于0,那么從信號量里面減去這個值并返回.
下面我們一以一個實例來說明這幾個函數的使用方法.
void ?init_semaphore_struct( struct ?sembuf? * sem, int ?semnum, int ?semop, int ?semflg)
{
? /* ?初始話信號燈結構? */
?sem -> sem_num = semnum;
?sem -> sem_op = semop;
?sem -> sem_flg = semflg;
}
int ?del_semaphore( int ?semid)
{
? /* ?信號燈并不隨程序的結束而被刪除,如果我們沒刪除的話(將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?表示由系統選擇一個關鍵字來創建?? */
? /* ?創建以后信號燈的初始值為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 );
? /* ?開始的時候有一個系統資源(一個標準錯誤輸出)? */
? 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 );
?}
? /* ?創建一個進程鏈? */
? 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;
? /* ?這里要求資源,進入原子操作? */
? 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));
?}
? /* ?不能夠在其他進程反問信號燈的時候,我們刪除了信號燈? */
? 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 );
}
3。SystemV消息隊列
為了便于進程之間通信,我們可以使用管道通信 SystemV也提供了一些函數來實現進程的通信.這就是消息隊列.
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;??? /* ?消息類型? */


}
msgget函數和semget一樣,返回一個消息隊列的標志.
msgctl和semctl是對消息進行控制.
msgsnd和msgrcv函數是用來進行消息通訊的.msgid是接受或者發送的消息隊列標志. msgp是接受或者發送的內容.msgsz是消息的大小. 結構msgbuf包含的內容是至少有一個為msgtype.其他的成分是用戶定義的.對于發送函數msgflg指出緩沖區用完時候的操作.接受函數指出無消息時候的處理.一般為 0. 接收函數msgtype指出接收消息時候的操作.
如果msgtype=0,接收消息隊列的第一個消息.大于0接收隊列中消息類型等于這個值的第一個消息.小于0接收消息隊列中小于或者等于 msgtype絕對值的所有消息中的最小一個消息. 我們以一個實例來解釋進程通信.下面這個程序有server和client組成.先運行服務端后運行客戶端.
服務端 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 ???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 );
}??
注意服務端創建的消息隊列最后沒有刪除,我們要使用ipcrm命令來刪除的.
4。SystemV共享內存
還有一個進程通信的方法是使用共享內存.SystemV提供了以下幾個函數以實現共享內存.
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是共享內存的大小.
shmat是用來連接共享內存的.shmdt是用來斷開共享內存的.
shmaddr,shmflg我們只要用0代替就可以了.在使用一個共享內存之前我們調用 shmat得到共享內存的開始地址,使用結束以后我們使用shmdt斷開這個內存.?
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 );
?}?
}?
這個程序是父進程將參數寫入到共享內存,然后子進程把內容讀出來.最后我們要使用ipcrm釋放資源的.先用ipcs找出ID然后用ipcrm shm ID刪除.?
管道有無名管道和有名管道兩種,無名管道一般在父子進程中使用。
無名管道的使用方法一般是:
int?pipe(int?filedes[2]);
filedes[0]用于讀出數據,讀取時必須關閉寫入端,即close(filedes[1]);
filedes[1]用于寫入數據,寫入時必須關閉讀取端,即close(filedes[0])。
無名管道的使用方法是:
#include?<sys/stat.h>
int?mkfifo(const?char?*pathname,?mode_t?mode);
讀寫管道與讀寫文件的操作相同。