当前位置:Linux教程 - Linux - UNIX下两个进程之间共享内存和信号量的用法浅析

UNIX下两个进程之间共享内存和信号量的用法浅析

**************************************************************
生产者
**************************************************************/

#include
#include
#include
#include
#include
#include

#define SHMKEY (key_t) 0x100
#define SEMKEY (key_t) 0x200

#define IFLAGS (IPC_CREAT|IPC_EXCL)
#define ERR ((struct databuf *) -1)

struct databuf{
int d_buf[10];
};

static int shmid,semid;

pr_error(char *mess)
{
perror(mess);
exit(1);
}

getseg(struct databuf* *pdata)
{
/*取得共享内存的key,如果还没建立的话,就建立(IPC_CREAT)*/
if((shmid =shmget(SHMKEY,sizeof(struct databuf),0600|IFLAGS)) < 0)

pr_error(""shmget"");
/*取得共享内存的指针,使用共享内存时可以和malloc分配的内存一样*/
if((*pdata = (struct databuf *)(shmat(shmid,0,0))) == ERR)
pr_error(""shmat"");
}

int getsem()
{ /*建立信号量(且称为A和B)*/
if((semid = semget(SEMKEY,2,0600|IFLAGS)) < 0)
pr_error(""semget"");
/*设置信号量A初值为0*/
if(semctl(semid,0,SETVAL,0) < 0)
pr_error(""semctl"");
/*设置信号量B初值为1*/
if(semctl(semid,1,SETVAL,1) < 0)
pr_error(""semctl"");
return(semid);
}

main()
{
int semid;
struct databuf *buf;
semid = getsem();
getseg(&buf);
writer(semid,buf);
exit(0);
}

struct sembuf p1 = {0,-1,0},p2 = {1,-1,0},
v1 = {0,1,0},v2 = {1,1,0};

writer(int semid,struct databuf *buf)
{
int i,j;
for(i = 0;i < 100;i++){
/*写共享内存*/
for(j = 0;j < 10;j++){
buf -> d_buf[j] = 10 * i + j;
}
/*V(A) A==1,唤醒消费者进程,读缓冲区*/
semop(semid,&v1,1);
/*P(B) B=0;准备写缓冲区*/
semop(semid,&p2,1);
}
return;
}

/**************************************************************
消费者
**************************************************************/
#include
#include
#include
#include
#include
#include

#define SHMKEY (key_t) 0x100
#define SEMKEY (key_t) 0x200

#define IFLAGS (IPC_CREAT)
#define ERR ((struct databuf *) -1)

struct databuf{
int d_buf[10];
};

static int shmid,semid;

pr_error(char *mess)
{
perror(mess);
exit(1);
}

getseg(struct databuf* *pdata)
{
if((shmid =shmget(SHMKEY,sizeof(struct databuf),0600|IFLAGS)) < 0)

pr_error(""shmget"");

if((*pdata = (struct databuf *)(shmat(shmid,0,0))) == ERR)
pr_error(""shmat"");
}

int getsem()
{
if((semid = semget(SEMKEY,2,0600|IFLAGS)) < 0)
pr_error(""semget"");

if(semctl(semid,0,SETVAL,0) < 0)
pr_error(""semctl"");

if(semctl(semid,1,SETVAL,1) < 0)
pr_error(""semctl"");

return(semid);
}

void remove_s()
{
/*删除共享内存*/
if(shmctl(shmid,IPC_RMID,NULL) < 0)
pr_error(""shmctl"");
/*删除信号量*/
if(semctl(semid,IPC_RMID,NULL) < 0)
pr_error(""semctl"");
}

main()
{
int semid;
struct databuf *buf;
semid = getsem();
getseg(&buf);
reader(semid,buf);
remove_s();
exit(0);
}

struct sembuf p1 = {0,-1,0},p2 = {1,-1,0},
v1 = {0,1,0},v2 = {1,1,0};

reader(int semid, struct databuf *buf)
{
int i,j;
for(i = 0;i <100;i++){
/*P(A) A==-1,如果没数据的话,blocking,有的话,读*/
semop(semid,&p1,1);
for(j = 0;j < 10;j++){
printf("" %d "",buf -> d_buf[j]);
}
printf("" "");
semop(semid,&v2,1);
/*V(B) B++,唤醒写进程*/
}
return;
}

我们把问题简化为信号量A=0,B=1
进程P1为写进程,P2为读进程
P1: P2:
for(i=0;i<100;i++) for(i=0;i<100;i++)
{ {
write data; P(A);
V(A); read data;
P(B); V(B);
} }
错误一目了然!(希望大家能看出来)
应改为:
P1: P2:
for(i=0;i<100;i++) for(i=0;i<100;i++)
{ {
P(B); P(A);
write data; read data;
V(A); V(B);
} }
程序的大概流程就是这样了.但程序存在几个Bug:
1.必须生产者进程先运行.如果消费者进程先运行,生产者运行失败.
2.生产者进程写完退出后,消费者进程不能正确删除共享内存和信号量
3.程序不能输出0-9.程序的本意是输出0-999的.
我来分析一下这几个BUG产生的原因:
BUG 1:
因为如果消费者先运行,则消费者进程先创建了共享内存和信号量,等到
生产者运行时,shmget用0600|IPC_CREAT|IPC_EXCL的标志调用,由于有
IPC_EXCL,所以返回errno为EEXIST的共享内存已存在的错误.
BUG 2:
因为消费者进程到最后一个循环里,读完数据,在semop(semid,&v2,1)处
blockiing,因为生产者进程已经退出.所以消费者进程不能正确删除共享
内存和信号量.
现在先改正BUG 1和BUG 2:
1).把消费者进程的IFLAGS也定义成(IPC_CREAT|IPC_EXCL)
2)分别把消费者和生产者源文件中的函数改为:
getseg(struct databuf* *pdata)
{
/*取得共享内存的key,如果还没建立的话,就建立(IPC_CREAT)*/
if((shmid =shmget(SHMKEY,sizeof(struct databuf),0600|IFLAGS)) < 0)

{
if( errno == EEXIST)
{
shmid=shmget(SHMKEY,sizeof(struct databuf),0600|IPC_CREAT);
if(shmid <0)
pr_error(""shmget"");
}
else
pr_error(""shmget"");
}
/*取得共享内存的指针,使用共享内存时可以和malloc分配的内存一样*/
if((*pdata = (struct databuf *)(shmat(shmid,0,0))) == ERR)
pr_error(""shmat"");
}

int getsem()
{ /*建立信号量(且称为A和B)*/
if((semid = semget(SEMKEY,2,0600|IFLAGS)) < 0)
{
if(errno == EEXIST)
{
semid=semget(SEMKEY,2,0600|IPC_CREAT);
if( semid <0)
pr_error(""semget"");
}
else
pr_error(""semget"");
}
/*设置信号量A初值为0*/
if(semctl(semid,0,SETVAL,0) < 0)
pr_error(""semctl"");
/*设置信号量B初值为1*/
if(semctl(semid,1,SETVAL,1) < 0)
pr_error(""semctl"");
return(semid);
}
3)
writer(int semid,struct databuf *buf)
{
int i,j;
for(i = 0;i < 100;i++){
/*P(B) B=0;准备写缓冲区*/
semop(semid,&p2,1);
/*写共享内存*/
for(j = 0;j < 10;j++){
buf -> d_buf[j] = 10 * i + j;
}
/*V(A) A==1,唤醒消费者进程,读缓冲区*/
semop(semid,&v1,1);

}
return;
}
4).
把remove_s()函数移到生产者中调用,去掉消费者的调用.
修正后,程序不论消费者进程还是生产者进程先运行,都可以
正确的运行.不过......
BUG 3:
程序在改正BUG 1和BUG 2后,消费者的进程输出是:
10 11 12 13 14 15 16 17 18 19
20 21 ...............
不能输出0-9.原因如下:
A=0 B=1 假设writer先运行.
writer(int semid,struct databuf *buf)
{
int i,j;
for(i = 0;i < 100;i++){
/*P(B) B--;准备写缓冲区*/
1 semop(semid,&p2,1);
/*写共享内存*/
for(j = 0;j < 10;j++){
2 buf -> d_buf[j] = 10 * i + j;
}
/*V(A) A++,唤醒消费者进程,读缓冲区*/
3 semop(semid,&v1,1);
}
return;
}

reader(int semid, struct databuf *buf)
{
int i,j;
for(i = 0;i <100;i++){
/*P(A) A--,如果没数据的话,blocking,有的话,读*/
1 semop(semid,&p1,1);
for(j = 0;j < 10;j++){
2 printf("" %d "",buf -> d_buf[j]);
}
printf("" "");
3 semop(semid,&v2,1);
/*V(B) B++,唤醒写进程*/
}
return;
}
执行顺序:(w表示writer, r表示reader)
w1: i=0,B--(B==0)
w2: 写buf:0 1 2 3 4 5 6 7 8 9
w3: A++(A==1)唤醒reader,如果reader还没运行,返回.
w1: i=1 B--(B==-1) blocking(直到reader运行)
reader运行执行
r1: i=0,A--(A==0)
r2: 读buf输出0 1 2 3 4 5 6 7 8 9
r3: B++(B==0)writer被唤醒
r1: i=1,A--(A==-1) blocking to wait A==0
这样分析应该是正确的.(生产者先运行的情况也相似)但结果却不是
输出0-9.为什么呢?也就是说reader的第一次读之前,writer已经写过了两次
缓冲区!!!经过探索后,终于明白:
原来是信号量初值被初始化了两次!!!分析:
w1: i=0,B--(B==0)
w2: 写buf:0 1 2 3 4 5 6 7 8 9
w3: A++(A==1)唤醒reader,如果reader还没运行,返回.
w1: i=1 B--(B==-1) blocking(直到reader运行)
reader运行执行
先执行set A=0 B=1.由于B==1,writer被唤醒,又在reader的读之前被调度,
所以就写了两次缓冲区!!!
r1: i=0,A--(A==0)
r2: 读buf输出10 11 12 13 14 15 16 17 18 19
r3: B++(B==0)writer被唤醒
r1: i=1,A--(A==-1) blocking to wait A==0
原因找到,把两个程序中的getsem()改为:
int getsem()
{ /*建立信号量(且称为A和B)*/
if((semid = semget(SEMKEY,2,0600|IFLAGS)) < 0)
{
if(errno == EEXIST)
{
semid=semget(SEMKEY,2,0600|IPC_CREAT);
if( semid <0)
pr_error(""semget"");
else
/*加上这一句,如果已存在,不再初始化信号量*/
return semid;
}
else
pr_error(""semget"");
}
/*设置信号量A初值为0*/
if(semctl(semid,0,SETVAL,0) < 0)
pr_error(""semctl"");
/*设置信号量B初值为1*/
if(semctl(semid,1,SETVAL,1) < 0)
pr_error(""semctl"");
return(semid);
}
完整的程序如下:
/**************************************************************
生产者
**************************************************************/

#include
#include
#include
#include
#include
#include
#include

#define SHMKEY (key_t) 0x100
#define SEMKEY (key_t) 0x200

#define IFLAGS (IPC_CREAT|IPC_EXCL)
#define ERR ((struct databuf *) -1)

struct databuf{
int d_buf[10];
};

static int shmid,semid;

pr_error(char *mess)
{
perror(mess);
exit(1);
}

getseg(struct databuf* *pdata)
{
/*取得共享内存的key,如果还没建立的话,就建立(IPC_CREAT)*/
if((shmid =shmget(SHMKEY,sizeof(struct databuf),0600|IFLAGS)) < 0)

{
if( errno == EEXIST)
{
shmid=shmget(SHMKEY,sizeof(struct databuf),0600|IPC_CREAT);
if(shmid <0)
pr_error(""shmget"");
}
else
pr_error(""shmget"");
}
/*取得共享内存的指针,使用共享内存时可以和malloc分配的内存一样*/
if((*pdata = (struct databuf *)(shmat(shmid,0,0))) == ERR)
pr_error(""shmat"");
}
int getsem()
{ /*建立信号量(且称为A和B)*/
if((semid = semget(SEMKEY,2,0600|IFLAGS)) < 0)
{
if(errno == EEXIST)
{
semid=semget(SEMKEY,2,0600|IPC_CREAT);
if( semid <0)
pr_error(""semget"");
else
/*加上这一句,如果已存在,不再初始化信号量*/
return semid;
}
else
pr_error(""semget"");
}
/*设置信号量A初值为0*/
if(semctl(semid,0,SETVAL,0) < 0)
pr_error(""semctl"");
/*设置信号量B初值为1*/
if(semctl(semid,1,SETVAL,1) < 0)
pr_error(""semctl"");
return(semid);
}
void remove_s()
{
/*删除共享内存*/
if(shmctl(shmid,IPC_RMID,NULL) < 0)
pr_error(""shmctl"");
/*删除信号量*/
if(semctl(semid,IPC_RMID,NULL) < 0)
pr_error(""semctl"");
}
main()
{
int semid;
struct databuf *buf;
semid = getsem();
getseg(&buf);
writer(semid,buf);
remove_s();
exit(0);
}

struct sembuf p1 = {0,-1,0},p2 = {1,-1,0},
v1 = {0,1,0},v2 = {1,1,0};

writer(int semid,struct databuf *buf)
{
int i,j;
for(i = 0;i < 100;i++){
/*P(B) B=0;准备写缓冲区*/
semop(semid,&p2,1);
/*写共享内存*/
for(j = 0;j < 10;j++){
buf -> d_buf[j] = 10 * i + j;
}
/*V(A) A==1,唤醒消费者进程,读缓冲区*/
semop(semid,&v1,1);

}
return;
}

/**************************************************************
消费者
**************************************************************/
#include
#include
#include
#include
#include
#include
#include

#define SHMKEY (key_t) 0x100
#define SEMKEY (key_t) 0x200

#define IFLAGS (IPC_CREAT|IPC_EXCL)
#define ERR ((struct databuf *) -1)

struct databuf{
int d_buf[10];
};

static int shmid,semid;

pr_error(char *mess)
{
perror(mess);
exit(1);
}

getseg(struct databuf* *pdata)
{
/*取得共享内存的key,如果还没建立的话,就建立(IPC_CREAT)*/
if((shmid =shmget(SHMKEY,sizeof(struct databuf),0600|IFLAGS)) < 0)

{
if( errno == EEXIST)
{
shmid=shmget(SHMKEY,sizeof(struct databuf),0600|IPC_CREAT);
if(shmid <0)
pr_error(""shmget"");
}
else
pr_error(""shmget"");
}
/*取得共享内存的指针,使用共享内存时可以和malloc分配的内存一样*/
if((*pdata = (struct databuf *)(shmat(shmid,0,0))) == ERR)
pr_error(""shmat"");
}
int getsem()
{ /*建立信号量(且称为A和B)*/
if((semid = semget(SEMKEY,2,0600|IFLAGS)) < 0)
{
if(errno == EEXIST)
{
semid=semget(SEMKEY,2,0600|IPC_CREAT);
if( semid <0)
pr_error(""semget"");
else
/*加上这一句,如果已存在,不再初始化信号量*/
return semid;
}
else
pr_error(""semget"");
}
/*设置信号量A初值为0*/
if(semctl(semid,0,SETVAL,0) < 0)
pr_error(""semctl"");
/*设置信号量B初值为1*/
if(semctl(semid,1,SETVAL,1) < 0)
pr_error(""semctl"");
return(semid);
}


main()
{
int semid;
struct databuf *buf;
semid = getsem();
getseg(&buf);
reader(semid,buf);
exit(0);
}

struct sembuf p1 = {0,-1,0},p2 = {1,-1,0},
v1 = {0,1,0},v2 = {1,1,0};

reader(int semid, struct databuf *buf)
{
int i,j;
for(i = 0;i <100;i++){
/*P(A) A==-1,如果没数据的话,blocking,有的话,读*/
semop(semid,&p1,1);
for(j = 0;j < 10;j++){
printf("" %d "",buf -> d_buf[j]);
}
printf("" "");
semop(semid,&v2,1);
/*V(B) B++,唤醒写进程*/
}
return;
}


总结:
进程间的通信可以通过共享内存来实现,但共享内存的访问涉及到信号量
的使用.两个进程都要检测信号量和共享内存是否已经被建立,并且信号量的
初始化只能一次!!!关于信号量和共享内存的函数就不列出来讲解了.请大家
翻翻参考书吧.也多谢supermario提出问题.通过这次的学习,自己对信号量的
编程就更清楚了.