发信人: kevintz() 
整理人: wenbobo(2002-12-06 22:58:17), 站内信件
 | 
 
 
           UNIX下两个进程之间共享内存和信号量的用法浅析
   
    (仅把此文献给一个女孩子.她的名字叫......)
 
    话说C语言板斧supermario最近为了找工作,被逼无奈去考试,对方出了
 几道题,其中之一就是进程间的同步问题,他不知道自己的水平去到那,就
 心生一计:先把程序贴出来,然后...... :))) ,他还威胁我们:看你们是不是
 能够理解程序的结构,其中有个BUG,看看谁能找出来!!! 
    怎知,有一无名网虫kevintz,又称sillyboy者,竟然入了supermario的圈
 套,于是有了本文.写完本文后,kevintz还对supermario的险恶居心懵然不知,
 真令人心有余悸呀.
    闲话休提,言归正传.supermario贴出的程序如下:
    producer.c生产者程序 
    customer.c消费者程序 
    编译环境:DEC AS1000/Digital UNIX V4.0B 
              (sillyboy注:在很多Unix下都可编译)
 /************************************************************** 
 生产者 
 **************************************************************/ 
 
 #include <stdio.h> 
 #include <signal.h> 
 #include <sys/types.h> 
 #include <sys/ipc.h> 
 #include <sys/shm.h> 
 #include <sys/sem.h> 
 
 #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 <stdio.h> 
 #include <signal.h> 
 #include <sys/types.h> 
 #include <sys/ipc.h> 
 #include <sys/shm.h> 
 #include <sys/sem.h> 
 
 #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("\n"); 
       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("\n"); 
 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 <stdio.h> 
 #include <signal.h> 
 #include <sys/types.h> 
 #include <sys/ipc.h> 
 #include <sys/shm.h> 
 #include <sys/sem.h> 
 #include <errno.h>
 
 #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 <stdio.h> 
 #include <signal.h> 
 #include <sys/types.h> 
 #include <sys/ipc.h> 
 #include <sys/shm.h> 
 #include <sys/sem.h> 
 #include <errno.h>
 
 #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("\n"); 
       semop(semid,&v2,1);
       /*V(B) B++,唤醒写进程*/ 
    } 
    return; 
 } 
 
 总结:
     进程间的通信可以通过共享内存来实现,但共享内存的访问涉及到信号量
 的使用.两个进程都要检测信号量和共享内存是否已经被建立,并且信号量的
 初始化只能一次!!!关于信号量和共享内存的函数就不列出来讲解了.请大家
 翻翻参考书吧.也多谢supermario提出问题.通过这次的学习,自己对信号量的
 编程就更清楚了.
     supermario已答应我把这篇文章收入精华版的,hahaha......hahaha.....
 
     仅把此文献给一个女孩子,她的名字叫.....(PF).MIFILWH&IHSLM2!!!
     
  -- 黄昏里,那一抹夕阳又向西...
  ※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 202.105.37.130]
  | 
 
 
 |