主題:Linux進程間通信機制
概要:信號量、共享內存和消息隊列
編輯:新建 20151210
參考資料:
Linux程序設計,第四版,人民郵電出版社
進程間通信機制,IPC(Inter-Process Communication,進程間通信)機制,主要有三個方面的內容:
信號量:用於管理對資源的訪問。
共享內存:用於在程序間高效的共享數據。
消息隊列:在程序之間傳遞數據的一種簡單方法。
1 信號量
程序中通常存在一部分“臨界代碼”,我們需要確保只有一個進程(或一個執行線程)可以進入這個臨界代碼並擁有對資源獨佔式的訪問權。
信號量的一個更正式的定義是:它是一個特殊變量,只允許對它進行等待(wait)和發送信號(signal)這兩種操作。而在Linux編程中,“等待”和“發送信號”都已具有特殊的含義,所以用以下兩種操作進行定義:
P(信號量變量):用於等待
V(信號量變量):用於發送信號
P、V操作的定義非常簡單,假設一個信號量變量sv(最常見的是隻有0和1的變量)。
P(sv):申請資源,如果sv的值大於0,就給它減1;如果它的值等於0,就掛起該進程的執行。
V(sv):釋放資源,如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因待sv而被掛起,就給它加1。
信號量函數的定義如下:
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
int semctl(int semid, int semnum, int cmd, ... );
int semop(int semid,struct sembuf*sops,unsign ednsops);
1.1 semget
semget函數的作用是創建一個新信號量或取得一個已有信號量的鍵,原型爲:
intsemget(key_t key,int nsems,int semflg);
@key:一個整數值,不相關的進程通過它訪問同一個信號量,程序對所有信號量的訪問都是間接的,它先提供一個鍵,再由系統生成一個相應的信號量標識符。只有semget函數才直接使用信號量鍵,所有其他信號量函數都使用由semget函數返回的信號量標識符。
@nsems:信號量數目,幾乎總是1
@semflg:一組標制,低端的9個bit是該信號量的權限,其作用類似於文件的訪問權限。通常與IPC_CREAT按位或操作,來創建一個信號量。
1.2 semop函數
semop函數用於改變信號量的值,原型爲:
int semop(int semid,struct sembuf*sops,unsign ednsops)
@semid:由semget函數返回的信號量標識符。
@sops:指向struct sembuf的指針,結構如下:
struct sembuf
{
ushort sem_num; /semaphore index in array/
short sem_op; /semaphore operation/
short sem_flg; /operation flags/
}
sem_num將要處理的信號量的個數,除非是一組信號量,否則一般設爲0;
sem_op要執行的操作,通常-1爲P操作,+1爲V操作。
sem_flg操作標誌,通常設爲SEM_UNDO,信號量沒被釋放程序就異常終止時,操作系統將自動釋放該進程擁有的信號量。
@ ednsops: 數組中的操作個數。
1.3 semctl函數
semctl函數用來直接控制信號量信息,原型如下:
int semctl(int semid, int semnum, int cmd, … );
@semid:由semget函數返回的信號量標識符。
@semnum:信號量編號,一般取值爲0,表示是一個唯一的信號量。
@ cmd:表示將要採取的動作。通常取SETVAL和IPC_RMID分別表示初始化一個信號量爲一個已知的值和刪除一個無需繼續使用的信號量標識符。
e.g.1:
書中一個演示成對打印字符的程序,如果運行不帶參數的實例,就打印字符’O’,如果運行帶參數的實例,就打印字符’X’,因爲P V操作的存在,字符都是成對出現。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
union semun
{
int val;
struct semid_ds * buf;
unsigned short * array;
};
static int set_semvalue(void);
static int del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
int main(int argc,int ** argv)
{
int i;
int pause_time;
char op_char='O';
srand((unsigned int)getpid());
sem_id=semget((key_t)1234,1,0666|IPC_CREAT);
if (argc>1)
{
if(!set_semvalue())
{
fprintf(stderr,"failed init semaphore\n");
exit(EXIT_FAILURE);
}
op_char='X';
sleep(2);
}
for (i=0;i<10;i++)
{
if (!semaphore_p()) exit(EXIT_FAILURE);
op_char+=1;
printf("%c",op_char);
fflush(stdout);
pause_time=rand()%3;
sleep(pause_time);
printf("%c",op_char);
fflush(stdout);
if (!semaphore_v())
{
exit(EXIT_FAILURE);
}
pause_time=rand()%2;
sleep(pause_time);
}
printf("\n---finished",getpid());
if (argc>1)
{
sleep(10);
del_semvalue();
}
}
static int set_semvalue()
{
union semun sem_union;
sem_union.val=1;
if (-1==semctl(sem_id,0,SETVAL,sem_union)) //--SETVAL command,init a semaphore,set as the value of semun's val member
{
fprintf(stderr,"Failed to init semaphore\n");
return 0;
}
return 1;
}
static int del_semvalue()
{
union semun sem_union;
sem_union.val=1;
if (-1==semctl(sem_id,0,IPC_RMID,sem_union)) //--SETVAL command,del a semaphore
{
fprintf(stderr,"Failed to del semaphore\n");
return 0;
}
return 1;
}
static int semaphore_p()
{
struct sembuf sem_b;
sem_b.sem_num=0;
sem_b.sem_op=-1;
sem_b.sem_flg=SEM_UNDO;
if (semop(sem_id,&sem_b,1)==-1)
{
fprintf(stderr,"semaphore_p failed\n");
return 0;
}
return 1;
}
static int semaphore_v()
{
struct sembuf sem_b;
sem_b.sem_num=0;
sem_b.sem_op=1;
sem_b.sem_flg=SEM_UNDO;
if (semop(sem_id,&sem_b,1)==-1)
{
fprintf(stderr,"semaphore_v failed\n");
return 0;
}
return 1;
}
2共享內存
共享內存允許兩個不相關的進程訪問同一個邏輯內存。共享內存是在兩個正在運行的進程之間傳遞數據的一種非常有效的方式。一般用共享內存來提供對大塊內存區域的有效訪問,共享內存的同步機制由程序員負責。
共享內存使用與信號量類似的函數,定義如下:
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
2.1 shmget
用shmget來創建共享內存:
int shmget(key_t key, size_t size, int shmflg)
@key: 同信號量類似,提供key值有效的爲共享內存命名。
@size:指定共享內存的大小,以字節爲單位。
@shmflg:與信號量相似,權限標誌,通常與IPC_CREAT結合使用。
2.2 shmat
第一次創建共享內存時,它不能被任何進程訪問,要想訪問該共享內存,必須將其連接到一個進程的地址空間中,這項工作由shmat完成,定義如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg)
@shm_id:共享內存標識符。
@shm_addr:指定共享內存連接到當前進程中的地址位置,通常是一個空指針,表示由系統來選擇共享內存出現的地址。
@shmflg:一組位標誌,兩個可能取值SHM_RND和SHM_RDONLY。
2.3 shmdt
shmdt將共享內存從當前進程中分離。
int shmdt(const void *shmaddr)
@shmaddr:是由shmat返回的指針。
成功時返回0,失敗時返回-1。
2.4 shmctl
共享內存控制函數:
int shmctl(int shm_id, int cmd, struct shmid_ds *buf)
@shm_id:共享內存標識符。
@cmd:要採取的動作,取值如下:
IPC_STAT:把shmid_ds結構中的數據設置爲共享內存的當前值。
IPC_SET:如果進程有足夠的權限,就把共享內存的當前關聯值設爲shmid_ds結構中給出的值。
IPC_RMID:刪除共享內存段。
@buf:指針,指向一個包含共享內存模式和訪問權限的結構。
e.g.2:
示例2中提供一個生產者—消費者程序,shm1.c作爲消費者程序,用於創建一個共享內存段,然後把寫到它裏面的數據都顯示出來。shm2.c作爲生產者程序,將連接一個已有的共享內存段,並允許我們向其中輸入數據。
首先在shm_com.h中定義一個公共的頭文件,用來定義希望分發的共享內存。
#define TEXT_SIZE 2048
struct shared_used_st
{
int turn_for_you;
char some_text[TEXT_SIZE];
};
shm1.c:
#include <unistd.h>
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
int shmid;
struct shared_used_st * shared_stuff=NULL;
void * shared_memory=(void *) 0;
int running=1;
//--create shared memory
shmid=shmget((key_t)1234,sizeof(struct shared_used_st),0666 | IPC_CREAT);
if (-1==shmid)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
//--attch shared memory
shared_memory=shmat(shmid,(void *)0,0);
if ((void *)-1==shared_memory)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("memory attached at %X \n",(int)shared_memory);
shared_stuff=(struct shared_used_st * )shared_memory;
shared_stuff->turn_for_you=0;
while(running)
{
if (shared_stuff->turn_for_you)
{
printf("Get msg from shared stuff:%s\n",shared_stuff->some_text);
sleep(rand()%4);
shared_stuff->turn_for_you=0;
if (0==strncmp(shared_stuff->some_text,"end",3))
{
running=0;
}
}
}
if (-1==shmdt(shared_memory))
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
if (-1==shmctl(shmid,IPC_RMID,0))
{
fprintf(stderr,"shmctl(RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
shm2.c:
#include <unistd.h>
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
int shmid;
struct shared_used_st * shared_stuff=NULL;
void * shared_memory=(void *) 0;
char buffer[BUFSIZ];
int running=1;
//--create shared memory
shmid=shmget((key_t)1234,sizeof(struct shared_used_st),0666 | IPC_CREAT);
if (-1==shmid)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
//--attch shared memory
shared_memory=shmat(shmid,(void *)0,0);
if ((void *)-1==shared_memory)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("memory attached at %X \n",(int)shared_memory);
shared_stuff=(struct shared_used_st * )shared_memory;
//shared_stuff->turn_for_you=1;
while(running)
{
while(shared_stuff->turn_for_you)
{
sleep(1);
printf("waiting for client....");
}
shared_stuff->turn_for_you=1;
printf("Enter some text:");
fgets(buffer,BUFSIZ,stdin);
strncpy(shared_stuff->some_text,buffer,TEXT_SIZE);
if (0==strncmp(buffer,"end",3))
{
running=0;
}
}
if (-1==shmdt(shared_memory))
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
if (-1==shmctl(shmid,IPC_RMID,0))
{
fprintf(stderr,"shmctl(RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
3 消息隊列
消息隊列提供了一種從一個進程向另一個進程發送一個數據塊的方法。而且,每個數據塊都被認爲含有一個類型,接收進程可以獨立地接收含有不同類型值的數據塊。
消息隊列函數的定義如下:
#include<sys/msg>
int msgget(key_t, key, int msgflg)
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg)
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg)
int msgctl(int msgid, int command, struct msgid_ds *buf)
3.1 msgget
與前面類似,用於創建一個消息隊列標識符。
3.2 msgsend
msgsend用於把消息添加到消息隊列中。
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg)
3.3 msgrcv
msgrcv用於從一個消息隊列中獲取信息。
3.4 msgctl
與共享內存的控制函數一樣。
e.g.3:
示例3,提供一個消息隊列的生產者—消費者程序,msg1.c用於接收消息,msg2.c用於發送消息,兩個程序都可以創建消息隊列,但只有接收者在接收完最後一個消息之後可以刪除它。
msg1.c:
#include <unistd.h>
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
typedef struct
{
long int msg_type;
char some_text[BUFSIZ];
}my_msg_st;
int main()
{
int runing=1;
my_msg_st some_data;
int msg_to_receive=0;
int msgID;
msgID=msgget((key_t)1124,0666|IPC_CREAT);
if (-1==msgID)
{
fprintf(stderr,"msgget error\n");
exit(1);
}
while (runing)
{
if (-1==msgrcv(msgID,&some_data,BUFSIZ,msg_to_receive,0))
{
fprintf(stderr,"msgrcv error\n");
exit(1);
}
printf("Rcv msg:%s",some_data.some_text);
if (0==strncmp(some_data.some_text,"end",3))
{
runing=0;
}
}
if (msgctl(msgID,IPC_RMID,0)==-1)
{
fprintf(stderr,"msgctl error\n");
exit(1);
}
exit(0);
}
msg2.c:
#include <unistd.h>
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#define MAX_TEXT 512
typedef struct
{
long int msg_type;
char some_text[BUFSIZ];
}my_msg_st;
int main()
{
int runing=1;
my_msg_st some_data;
//int msg_to_receive=0;
int msgID;
char buffer[BUFSIZ];
msgID=msgget((key_t)1124,0666|IPC_CREAT);
if (-1==msgID)
{
fprintf(stderr,"msgget error\n");
exit(1);
}
while (runing)
{
printf("Enter some text:");
fgets(buffer,BUFSIZ,stdin);
some_data.msg_type=1;
strncpy(some_data.some_text,buffer,MAX_TEXT);
if (-1==msgsnd(msgID,&some_data,MAX_TEXT,0))
{
fprintf(stderr,"msgsend error\n");
exit(1);
}
if (0==strncmp(buffer,"end",3))
{
runing=0;
}
}
if (msgctl(msgID,IPC_RMID,0)==-1)
{
fprintf(stderr,"msgctl error\n");
exit(1);
}
exit(0);
}