主题: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);
}