使用 pxshm 和 pxsem 实现生产者和消费者

使用共享内存和信号量实现生产者和消费者程序。程序为UNIX高级环境编程卷二中的源码,位置:unpv22e\pxshm\

共享结构体如下:

cliserv2.h

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <fcntl.h>
 6 #include <sys/select.h>
 7 #include <sys/stat.h>    /* for S_xxx file mode constants */
 8 #include <sys/mman.h>    /* Posix shared memory */
 9 #include <semaphore.h>
10 #include <errno.h>
11 #include <string.h>
12 
13 #define    FILE_MODE    (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
14 
15 #define    MESGSIZE    256        /* max #bytes per message, incl. null at end */
16 #define    NMESG         16        /* max #messages */
17 
18 struct shmstruct {        /* struct stored in shared memory */
19   sem_t    mutex;            /* three Posix memory-based semaphores */
20   sem_t    nempty;
21   sem_t    nstored;
22   int    nput;            /* index into msgoff[] for next put */
23   long    noverflow;        /* number of overflows ,overflows by senders */
24   sem_t    noverflowmutex;    /* mutex for noverflow counter */
25   long    msgoff[NMESG];    /* offset in shared memory of each message */
26   char    msgdata[NMESG * MESGSIZE];    /* the actual messages */
27 };
28 
29 void
30 err_quit(char *ptr)
31 {
32     fprintf(stderr, "%s\n", ptr);
33     exit(1);
34 }
35 
36 void
37 err_sys(char *ptr)
38 {
39     fprintf(stderr, "%s\n", ptr);
40     exit(1);
41 }
View Code

客户端根据用户指令决定生产多少产品,进程最后会推出, 但下次启动时原先共享内存值依旧存在, 客户端不做销毁共享内存动作.

此处管理 msgdata 缓存区的逻辑如下: 

缓存区有 NMESG 个大小是 MESGSIZE 的连续字符串空间, 也就是说缓存区大小可以存16个字符串,字符串最大256个字符.

msgoff 数组用于存放缓存区16个单元的起始地址,也就是偏移量.该数组中的值在服务器端初始化,16个字符串的起始地址是确定的.

客户端通过 nput 从 msgoff 数组中获得下一个存放单元的首地址, 因变量 nput 记录了客户端生产产品的个数, 该变量是连续累计的共享变量,不受客户端进程退出的影响,所以它可以保证客户端生产的产品一定会放到下一个存放单元.当然此处我们允许存满后再从第一个单元开始存.

服务端代码如下:

server2.c

 1 #include    "cliserv2.h"
 2 
 3 int main(int argc, char **argv)
 4 {
 5     int        fd, index, lastnoverflow, temp;
 6     long    offset;
 7     struct shmstruct    *ptr;
 8 
 9     if (argc != 2)
10         err_quit("usage: server2 <name>");
11 
12         /* 4create shm, set its size, map it, close descriptor */
13     shm_unlink(argv[1]);        /* OK if this fails */
14     fd = shm_open(argv[1], O_RDWR | O_CREAT | O_EXCL, FILE_MODE);
15     ptr = (struct shmstruct *)mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
16                MAP_SHARED, fd, 0);
17     ftruncate(fd, sizeof(struct shmstruct));
18     close(fd);
19 
20         /* 4initialize the array of offsets */
21     for (index = 0; index < NMESG; index++)
22         ptr->msgoff[index] = index * MESGSIZE; /* save: 0, 1*256, 2*256, 3*256, ... */
23 
24         /* 4initialize the semaphores in shared memory */
25     sem_init(&ptr->mutex, 1, 1);
26     sem_init(&ptr->nempty, 1, NMESG);
27     sem_init(&ptr->nstored, 1, 0);
28     sem_init(&ptr->noverflowmutex, 1, 1);
29 
30         /* 4this program is the consumer */
31         /* client side might run faster than server side, and may be it has inputed all messages to share memory,
32            as the same time, server side just output one message from share memory,
33            so the variable "noverflow" is finally result be print in first "for" loop, 
34            and remain "for" loop this variable will be unchanged,
35            so it just be print once. 
36         */
37     index = 0;
38     lastnoverflow = 0;
39     for ( ; ; ) {                   /* for loop to output message from share memory */
40         sem_wait(&ptr->nstored);
41         sem_wait(&ptr->mutex);
42         offset = ptr->msgoff[index];
43         printf("index = %d: %s\n", index, &ptr->msgdata[offset]);
44         if (++index >= NMESG)
45             index = 0;                /* circular buffer */
46         sem_post(&ptr->mutex);
47         sem_post(&ptr->nempty);
48 
49         sem_wait(&ptr->noverflowmutex);
50         temp = ptr->noverflow;        /* number of overflows */
51         sem_post(&ptr->noverflowmutex);
52         if (temp != lastnoverflow) {
53             printf("noverflow = %d\n", temp);
54             lastnoverflow = temp;
55         }
56     }
57 
58     exit(0);
59 }
View Code

服务器端进程是死循环的,每次启动会销毁之前的共享内存并且重新开辟(如果存在的话), 接口shm_unlink用于销毁共享内存.

客户端代码如下

client2.c

 1 #include    "cliserv2.h"
 2 
 3 int     sleep_us(unsigned int nusecs);
 4 
 5 int main(int argc, char **argv)
 6 {
 7     int        fd, i, nloop, nusec;
 8     pid_t    pid;
 9     char    mesg[MESGSIZE];
10     long    offset;
11     struct shmstruct    *ptr;
12 
13     if (argc != 4)
14         err_quit("usage: client2 <name> <#loops> <#usec>");
15     nloop = atoi(argv[2]);
16     nusec = atoi(argv[3]);
17 
18         /* 4open and map shared memory that server must create */
19     fd = shm_open(argv[1], O_RDWR, FILE_MODE);
20     ptr = mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
21                MAP_SHARED, fd, 0);
22     close(fd);
23 
24     pid = getpid();
25     for (i = 0; i < nloop; i++) {
26         sleep_us(nusec);    /* overflow will be happend when no time delay */
27         snprintf(mesg, MESGSIZE, "pid %ld: message %d", (long) pid, i);
28 
29         /* If the semaphore pointed to by sem has non-zero count, the count is
30            atomically decreased and sem_trywait immediately returns 0.  
31         If the semaphore count is zero, sem_trywait immediately returns with error EAGAIN 
32         and never decrease the semaphore count, the return value of function is -1.
33         */
34         if (sem_trywait(&ptr->nempty) == -1) {  
35             if (errno == EAGAIN) {    
36                 sem_wait(&ptr->noverflowmutex); /* the probability of variable noverflow be accessed low than the variable msgoff and nput, so set spcial lock for this variable will be cut down the process waiting time. */
37                 ptr->noverflow++;        /* number of overflows, it means how many message were ignored. */
38                 sem_post(&ptr->noverflowmutex);
39                 continue;                       /* if no empty slot, we give up this message */
40             } else
41                 err_sys("sem_trywait error");
42         }
43         sem_wait(&ptr->mutex);
44         offset = ptr->msgoff[ptr->nput];
45         if (++(ptr->nput) >= NMESG)
46             ptr->nput = 0;        /* circular buffer */
47         sem_post(&ptr->mutex);
48         strcpy(&ptr->msgdata[offset], mesg); /* the variable offset was changed in critical region, so we ensure this copy action can be preform out of critical region */
49         sem_post(&ptr->nstored);
50     }
51     exit(0);
52 }
53 
54 int
55 sleep_us(unsigned int nusecs)
56 {
57     struct timeval    tval;
58 
59     if (nusecs == 0)
60         return(0);
61 
62     for ( ; ; ) {
63         tval.tv_sec = nusecs / 1000000;
64         tval.tv_usec = nusecs % 1000000;
65         if (select(0, NULL, NULL, NULL, &tval) == 0)
66             return(0);        /* all OK */
67         /*
68          * Note than on an interrupted system call there's not
69          * much we can do, since the timeval{} isn't updated with the time
70          * remaining.  We could obtain the clock time before the call, and
71          * then obtain the clock time here, subtracting them to determine
72          * how long select() blocked before it was interrupted, but that
73          * seems like too much work :-)
74          */
75         if (errno != EINTR)
76             return(-1);
77         /* else go around again */
78     }
79 }
View Code

客户端在生产速度超过服务器端消费速度的情况下可能出现溢出的现象,此时程序做丢弃处理.

共享变量 noverflow 用于记录丢弃的产品个数.

另外 为 noverflow 变量设置 noverflowmutex 专用锁是为了降低进程间出现等待的概率, 因为显然程序访问 noverflow 的概率小于其他共享变量.

Makefile

 1 all: server2 client2
 2 
 3 server2: server2.c cliserv2.h
 4     gcc -lrt -o server2 server2.c cliserv2.h 
 5 
 6 client2: client2.c cliserv2.h
 7     gcc -lrt -o client2 client2.c cliserv2.h 
 8 
 9 clean:
10     rm -f server2 client2 
View Code

Note:

1.Makefile写错编译时出现未定义接口的情况,而事实并非如此,此时可以将编译指令单独拿出来试试;

2.若不知道代码中使用的库接口依赖那些头文件和库,可以用$man function 查询.

posted on 2015-12-24 16:39  BlueSky~  阅读(191)  评论(0编辑  收藏  举报

导航