操作系统实验报告6
实验内容
-
实验内容:进程间通信—共享内存。
(1)、验证:编译运行课件 Lecture 08 例程代码:
- Linux 系统调用示例 reader-writer 问题:Algorithms 8-1 ~ 8-3.
- POSIX API 应用示例 producer-consumer 问题:Algorithms 8-4 ~ 8-5.
(2)、设计:Alg.8-1 ~ 8-3 示例只解决单字符串的读写。修改程序将共享空间组织成一个结构类型(比如学号、姓名)的循环队列进行 FIFO 操作,采用共享内存变量控制队列数据的同步(参考数据结构课程有关内容)。
实验环境
- 架构:Intel x86_64 (虚拟机)
- 操作系统:Ubuntu 20.04
- 汇编器:gas (GNU Assembler) in AT&T mode
- 编译器:gcc
技术日志
(1)、验证实验:
- 在头文件alg.8-0-shmdata.h中定义必要的数据和结构
#define TEXT_SIZE 4*1024
定义了每一条消息的大小
#define TEXT_NUM 1
定义了消息的最大条数
消息的的总大小不能超过当前最大共享内存,不然会发生无效参数的错误
#define PERM S_IRUSR|S_IWUSR|IPC_CREAT
定义了用户的读、写、创建权限,PERM | 0666
代表该文件拥有者、拥有者所在组其他成员、其他用户组的成员对该文件有读写的权限,但是没有操作的权限,PERM | 0777
另外有操作的权限。
#define ERR_EXIT(m)
do {
perror(m);
exit(EXIT_FAILURE);
} while(0)
ERR_EXIT(m)
定义了一个异常处理的模板
struct shared_struct {
int written; /* flag = 0: buffer writable; others: readable */
char mtext[TEXT_SIZE]; /* buffer for message reading and writing */
};
shared_struct
定义了一个共享内存中使用的结构体,其中mtext[TEXT_SIZE]
是提供给消息进行读写的缓冲区,written
为0时代表缓冲区可写,为其它值代表缓冲区可读。
- 验证实验示例 reader-writer 问题:Algorithms 8-1 ~ 8-3
执行程序命令:
gcc -o alg.8-2-shmread.o alg.8-2-shmread.c
gcc -o alg.8-3-shmwrite.o alg.8-3-shmwrite.c
gcc alg.8-1-shmcon.c
./a.out ./alg.8-2-shmread.o
执行截图:
分析:
实验内容原理:
首先,进程8-1会通过ftok()
函数获取一个IPC键值,然后使用shmget()
函数根据之前的IPC键值和一个计划编号创建一个共享内存对象,并获得共享内存的ID值shmid
,再根据这个共享内存ID值使用shmat()
函数把共享内存区对象映射到调用进程的地址空间,允许本进程访问共享内存,并返回一个类型为void *
的附加好的共享内存地址给shmptr,将这个void *
类型的指针shmptr强制转换为struct shared_struct *
类型的指针并赋给shared之后,就可以将shared看作为一个共享对象在共享内存区进行操作了,一开始shared的成员变量written
设为0,表示缓冲区可写。
此时进程8-1已经不再需要使用共享内存,所以及时使用shmdt()
函数使其与共享内存脱离。
通过vfork()
得到的两个进程8-2 shmread和8-3 shmwrite同时运行,分别充当reader和writer的角色,两个进程都使用shmget()
和shmat()
函数附加到同一块共享内存上,进程8-1直到这两个进程运行完再继续运行。
当shared的成员变量written
为0时,缓冲区可写,充当reader的进程8-2 shmread暂时休眠,充当writer的进程8-3 shmwrite开始执行,利用fgets()
函数从键盘读入字符串后,将字符串复制到了shared变量的成员变量mtext
缓冲区中,并将written
设为1,写结束。
当shared的成员变量written
为1时,缓冲区可读,充当writer的进程8-3 shmwrite暂时休眠,充当reader的进程8-2 shmread开始执行,将shared变量的成员变量mtext
缓冲区中的字符串打印出来后,将written
设为0,读结束。
当写的过程输入的字符串为end
时,两个进程都使用shmdt()
函数从共享内存脱离出来。而且都退出,返回父进程8-1执行完毕后,程序结束。
实现细节解释:
首先执行程序8-1 shmcon:
首先程序8-1 shmcon作为进程开始运行,一开始确定共享内存shmsize
大小为TEXT_NUM * sizeof(struct shared_struct)
,然后打印语句max record number = 1, shm size = 4100
,表示最大可以记录消息的条数为1,共享内存的大小为4100
然后进入一个条件判断语句,如果argc < 2
,那么说明编译命令出错,打印Usage: ./a.out pathname
提示我们补全路径名,以便之后根据文件信息使用ftok()函数建立共享内存ID值,并返回EXIT_FAILURE
表示异常退出。
如果编译命令正确,那么继续往下执行,这里为了不用之后使用creat()函数建立一个新文件,我的编译命令为./a.out ./alg.8-2-shmread.o
,因为alg.8-2-shmread.o
文件已存在,就不需要再创建新文件。将argv[1]
中的内容即./alg.8-2-shmread.o
复制到字符型数组pathname
中,并获取alg.8-2-shmread.o
中的文件信息,如果获取文件信息失败,进行异常处理,使用creat(pathname, O_RDWR)
函数在原来文件路径处创建一个新的同名文件,并把creat()函数的返回值赋给ret,如果ret为-1,说明创建失败,使用宏定义ERR_EXIT("creat()")
打印错误信息和原因,否则打印语句shared file object created
,说明创建成功。
接着,通过ftok(pathname, 0x27)
指定系统建立共享内存时的ID值,ftok()函数会根据参数pathname
的文件信息和序号参数0x27
的计划编号合成IPC key键值并赋给变量key,从而避免用户使用key值时发生冲突。其中序号参数最多有256个即0x00~0xff(最好不使用0)。如果key值为-1,说明ftok()失败,使用宏定义ERR_EXIT("shmcon: ftok()")
打印错误信息,否则打印key generated: IPC key = 27053fb0
,表示生成成功的IPC key键值为27053fb0
。
接下来,使用shmget((key_t)key, shmsize, 0666|PERM)
创建一个共享内存对象,并把共享存储的ID作为返回值赋给变量shmid,其中参数(key_t)key
为转换为(key_t)
类型的之前通过ftok()函数得到的IPC键值,参数shmsize
为新建的共享内存大小,参数0666|PERM)
表示赋给用户、用户组的其它成员、其它用户有的进程对共享内存有读写的权限,如果shmid为-1,说明shmget()失败,使用宏定义ERR_EXIT("shmcon: shmget()")
打印错误信息,否则打印shmcon: shmid = 32
,表示生成成功的共享内存ID值为32
。
然后,使用shmptr = shmat(shmid, 0, 0)
把共享内存区对象映射到调用进程的地址空间,允许本进程访问共享内存,返回类型为void *
的附加好的共享内存地址给shmptr,其中参数shmid
是共享内存标识符,第二个参数和第三个参数都是0,代表让内核决定共享内存出现在内存地址的什么位置和共享内存具有可读可写权限。如果shmptr为(void *)-1,说明shmat()失败,使用宏定义ERR_EXIT("shmcon: shmat()")
打印错误信息,否则打印shmcon: shared Memory attached at 0x7f0bee26f000
,表示附加好的共享内存地址为0x7f0bee26f000
。
将(void *)类型的指针shmptr强制转换为(struct shared_struct *)类型的指针并赋给shared,设置shared的成员变量written为0,表示shared的缓冲区可写,然后将格式化后的字符串ipcs -m | grep '32'
赋给字符型数组cmd_str,打印语句------ Shared Memory Segments ------
作为分割线,然后使用system(cmd_str)
进行使用系统调用语句ipcs -m | grep '32'
,其中ipcs -m
表示向标准输出中写入一些关于当前共享内存段的信息,grep '32'
表示查找共享内存段中符合32
的共享内存,语句ipcs -m | grep '32'
向标准输出中写入一些关于共享内存ID值为32的共享内存信息,为0x27053fb0 32 ubuntu 666 4100 1
。其中0x27053fb0
代表IPC键值,32
代表共享内存ID值,ubuntu
代表当前用户的用户名,666
是权限,代表共享内存可读可写,4100
代表共享内存的大小,1
代表当前进程允许访问这片共享内存。
接着判断shmdt(shmptr)
的返回值是否为-1,如果是,则shmdt()失败,使用宏定义ERR_EXIT("shmcon: shmdt()")
打印错误信息,,否则shmdt()成功,会断开与共享内存附加点的地址,将先前用shamt()附加(attach)好的共享内存脱离(detach)目前的进程。
再次使用system(cmd_str)
进行使用系统调用语句ipcs -m | grep '32'
,发现这次0x27053fb0 32 ubuntu 666 4100 0
,最后一项发生变化,从1变成了0,说明当前进程不能访问这片共享内存。
然后将IPC键值变量key的十六进制形式转换为字符串赋给key_str,将{" ", key_str, 0}
赋给char *类型的argv1[]。
接着,使用vfork()函数创建一个新进程,并把返回值赋给childpid1,如果变量childpid1
小于0,说明在vfork()的过程中出错,则进行错误处理,使用宏定义ERR_EXIT("shmcon: 1st vfork()")
打印错误信息。
如果vfork()函数在执行过程中没有出错,则继续判断变量childpid1
的值,若变量childpid1
为0,说明是子进程,执行结果为使用execv("./alg.8-2-shmread.o", argv1)
引发程序8-2 shmread作为进程和父进程8-1 shmcon异步执行。
第一个vfork()进入执行进程8-2 shmread:
在进程8-2 shmread中,首先使用sscanf(argv[1], "%x", &key);
将argv[1]中的内容以整型十六进制形式赋给key_t型变量key,然后打印shmread: IPC key = 27053fb0
,说明IPC键值为27053fb0
,和之前在作为父进程的8-1 shmcon中生成IPC键值的一样。
接着使用shmget((key_t)key, TEXT_NUM*sizeof(struct shared_struct), 0666|PERM);
创建一个共享内存对象,并把共享存储的ID作为返回值赋给变量shmid,其中参数(key_t)key
为IPC键值27053fb0
,参数shmsize
为共享内存大小,参数0666|PERM)
表示赋给用户、用户组的其它成员、其它用户有的进程对共享内存有读写的权限,如果shmid为-1,说明shmget()失败,使用宏定义ERR_EXIT("shread: shmget()")
打印错误信息,否则由于之前内核中已经存在与IPC键值27053fb0
相等的内存,所以返回该共享内存的标识符32
给shmid。
然后使用shmat(shmid, 0, 0)
把共享内存区对象映射到调用进程的地址空间,允许本进程访问共享内存,返回类型为void *
的附加好的共享内存地址给shmptr,其中参数shmid
是共享内存标识符,第二个参数和第三个参数都是0,代表让内核决定共享内存出现在内存地址的什么位置和共享内存具有可读可写权限。如果shmptr为(void *)-1,说明shmat()失败,使用宏定义ERR_EXIT("shread: shmat()")
打印错误信息,否则打印shmread: shmid = 32
表示共享内存的标识符为32
,打印shmread: shared memory attached at 0x7f179682c000
,表示附加好的共享内存地址为0x7f179682c000
,打印shmread process ready ...
表示进程8-2 shmread已经可以访问共享内存。
接着,将(void *)类型的指针shmptr强制转换为(struct shared_struct *)类型的指针并赋给shared,进入一个while循环,当shared的成员变量written为0时,进程进入一个休眠1s的循环,说明消息还没有准备好被读发送,进程8-2要继续等待,直到shared的成员变量written为1,说明shared的缓冲区可以读了,这时退出休眠1s的循环,打印语句You wrote:
和输入到shared的成员变量mtext中的字符串,然后将shared的成员变量written置为0,表示缓冲区此时可写但不可读,如果之前输入到shared的成员变量mtext中的字符串为"end"
的话,那么退出整个while循环,输入的为其它字符串则继续循环。
接着判断shmdt(shmptr)
的返回值是否为-1,如果是,则shmdt()失败,使用宏定义ERR_EXIT("shread: shmdt()")
打印错误信息,,否则shmdt()成功,会断开与共享内存附加点的地址,将先前用shamt()附加(attach)好的共享内存脱离(detach)目前的进程8-2 shmread。
休眠1s,使用语句exit(EXIT_SUCCESS)
,表示退出成功,结束进程8-2 shmread。
第二个vfork()进入执行进程8-3 shmwrite:
接着在进程8-1 shmcon中,如果变量childpid1
大于0,说明是父进程,执行结果为使用vfork()函数创建一个新进程,并把返回值赋给childpid2,如果变量childpid2
小于0,说明在vfork()的过程中出错,则进行错误处理,使用宏定义ERR_EXIT("shmwrite: 2nd vfork()")
打印错误信息。
如果vfork()函数在执行过程中没有出错,则继续判断变量childpid2
的值,若变量childpid2
为0,说明是子进程,执行结果为使用execv("./alg.8-3-shmwrite.o", argv1)
引发程序8-3 shmwrite作为进程和父进程8-1 shmcon异步执行。
在进程8-3 shmwrite中,先使用sscanf(argv[1], "%x", &key);
将argv[1]中的内容以整型十六进制形式赋给key_t型变量key,然后打印shmwrite: IPC key = 27053fb0
,说明IPC键值为27053fb0
,和之前在作为父进程的8-1 shmcon中生成IPC键值的一样。
接着使用shmget((key_t)key, TEXT_NUM*sizeof(struct shared_struct), 0666|PERM);
创建一个共享内存对象,并把共享存储的ID作为返回值赋给变量shmid,其中参数(key_t)key
为IPC键值27053fb0
,参数shmsize
为共享内存大小,参数0666|PERM)
表示赋给用户、用户组的其它成员、其它用户有的进程对共享内存有读写的权限,如果shmid为-1,说明shmget()失败,使用宏定义ERR_EXIT("shmwrite: shmget()")
打印错误信息,否则由于之前内核中已经存在与IPC键值27053fb0
相等的内存,所以返回该共享内存的标识符32
给shmid。
然后使用shmat(shmid, 0, 0)
把共享内存区对象映射到调用进程的地址空间,允许本进程访问共享内存,返回类型为void *
的附加好的共享内存地址给shmptr,其中参数shmid
是共享内存标识符,第二个参数和第三个参数都是0,代表让内核决定共享内存出现在内存地址的什么位置和共享内存具有可读可写权限。如果shmptr为(void *)-1,说明shmat()失败,使用宏定义ERR_EXIT("shmwrite: shmat()")
打印错误信息,否则打印shmwrite: shmid = 32
表示共享内存的标识符为32
,打印shmwrite: shared memory attached at 0x7fd1e6b06000
,表示附加好的共享内存地址为0x7fd1e6b06000
,打印shmwrite process ready ...
表示进程8-3 shmread已经可以访问共享内存。
接着,将(void *)类型的指针shmptr强制转换为(struct shared_struct *)类型的指针并赋给shared,进入一个while循环,当shared的成员变量written为1时,进程进入一个休眠1s的循环,说明消息还没有准备好读入,进程8-3要继续等待,直到shared的成员变量written为0,说明shared的缓冲区可以写了,这时退出休眠1s的循环,打印语句Enter some text:
,用fgets(buffer, BUFSIZ, stdin)
语句从标准输入即键盘读入内容到可读长度为BUFSIZ
的字符型数组buffer
中,然后用strncpy(shared->mtext, buffer, TEXT_SIZE)
将buffer中最大长度为TEXT_SIZE
的内容复制到shared的成员变量mtext中,接着打印语句shared buffer:
和mtext中的内容,设置将shared的成员变量written置为1,表示缓冲区此时可读但不可写,如果buffer中的字符串为"end"
的话,那么退出整个while循环。
接着判断shmdt(shmptr)
的返回值是否为-1,如果是,则shmdt()失败,使用宏定义ERR_EXIT("shmwrite: shmdt()")
打印错误信息,否则shmdt()成功,会断开与共享内存附加点的地址,将先前用shamt()附加(attach)好的共享内存脱离(detach)目前的进程8-3 shmwrite。使用语句exit(EXIT_SUCCESS)
,表示退出成功,结束进程8-3 shmwrite。
若变量childpid2
大于0,说明是父进程,执行结果为使用语句wait(&childpid1)
和wait(&childpid2)
等待子进程8-2 shmread和子进程8-3 shmwrite执行完再继续执行,此时共享内存的ID值可以被任意知道IPC键值的进程删除,等待子进程执行完后,判断shmctl(shmid, IPC_RMID, 0)
的返回值是否为-1,如果是,则shmctl()失败,使用宏定义ERR_EXIT("shmcon: shmctl(IPC_RMID)")
打印错误信息,否则shmctl()成功,释放标识符为32的共享内存区,再次使用system(cmd_str)
语句尝试向标准输出中写入一些关于共享内存ID值为32的共享内存信息,结果并没有任何输出,最后打印nothing found ...
。
最后使用语句exit(EXIT_SUCCESS)
,表示退出成功,结束进程8-1 shmcon。
从上图可以看到,三个进程使用的共享内存IPC键值都为0x27053fb0
,说明使用的是同一块共享内存。
可以看到,三个进程附加到共享内存的地址都不同,说明不同进程附加到同一块共享内存的共享内存地址不一定相同。
三次使用system(cmd_str)
语句尝试向标准输出中写入一些关于共享内存ID值为32的共享内存信息,第一次进程8-1 shmcon使用shmat()语句附加到了共享内存,可以看到信息的最后一项为1,说明当前进程8-1 shmcon可以访问共享内存,第二次进程8-1 shmcon使用shmdt()语句脱离共享内存,可以看到信息的最后一项为0,说明当前进程8-1 shmcon不能访问共享内存,最后8-1 shmcon使用shmctl()语句释放了共享内存区,可以看到没有任何信息,说明共享内存已经被释放了。
- 验证实验示例 producer-consumer 问题:Algorithms 8-4 ~ 8-5
执行程序命令:
gcc alg.8-4-shmpthreadcon.c -lrt
gcc -o alg.8-5-shmproducer.o alg.8-5-shmproducer.c -lrt
gcc -o alg.8-6-shmconsumer.o alg.8-6-shmconsumer.c -lrt
./a.out myshm
执行截图:
分析:
实验内容原理:
首先,进程8-4会使用shm_open(argv[1], O_CREAT|O_RDWR, 0666)
语句创建一个共享内存对象/dev/shm/myshm
。
然后通过vfork()
得到的两个进程8-5 shmproducer和8-6 shmconsumer同时运行,分别充当producer和consumer的角色,两个进程都使用shm_open()
函数对同一个共享内存对象进行操作,其中producer有读写的权限,consumer只有读的权限,然后使用mmap()
函数在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系,从而对同一块共享内存进行操作,进程8-4直到这两个进程运行完再继续运行。
充当producer的进程8-5 shmproducer把自己的字符串message_0
里的内容赋给共享内存对象,充当consumer的进程8-6 shmconsumer从共享内存对象中打印,得到同一份内容,然后两个进程都退出返回父进程8-4执行完毕后,程序结束。
实现细节解释:
首先执行程序8-4 shmpthreadcon:
首先程序8-4 shmpthreadcon作为进程开始运行,进入一个条件判断语句,如果argc < 2
,那么说明编译命令出错,打印Usage: ./a.out pathname
提示我们补全路径名,以便之后根据文件信息使用ftok()函数建立共享内存ID值,并返回EXIT_FAILURE
表示异常退出。
如果编译命令正确,那么继续往下执行,使用shm_open(argv[1], O_CREAT|O_RDWR, 0666)
语句创建一个共享内存对象,权限为可读可写,其中O_CREAT
表示若文件不存在则创建它,权限为0666
指定的可读可写,O_RDWR
表示可读可写,并把返回值赋给变量fd,如果fd为-1,表示shm_open()失败,使用宏定义ERR_EXIT("con: shm_open()")
打印错误信息,否则将/dev/shm/filename
作为共享内存对象,如果这个文件不存在那么创建它。因为在编译命令中./a.out myshm
,所以filename
为myshm
,/dev/shm/myshm
为共享内存对象。
程序继续向下运行,系统调用命令ls -l /dev/shm/
,使用ls -l
命令查看/dev/shm/
目录下的文件信息,可以看到,第一行的total
为0,代表该目录下所有文件所占空间的总和为0,接下来是该目录下唯一一个文件即共享内存对象myshm一个7个字段的列表-rw-rw-r-- 1 ubuntu ubuntu 0 3月 30 10:57 myshm
,其中-rw-rw-r--
表示该文件是一个普通文件,用户和用户组的其他成员对该文件有读写的权限,其他用户只有读的权限,1
代表这个文件只有一个文件名,只有一个指向该链接的硬链接,ubuntu ubuntu
表示文件拥有者和文件拥有者所在组,0
代表文件所占用的空间,3月 30 10:57
表示文件最近访问或修改时间,myshm
表示文件名。
接着,确定共享内存shmsize
大小为TEXT_NUM * sizeof(struct shared_struct)
,然后使用ftruncate(fd, shmsize)
语句,将参数fd指定的文件大小改为参数shmsize指定的大小,并把返回值赋给ret,如果ret的值为-1,则ftruncate()失败,使用宏定义ERR_EXIT("con: ftruncate()")
打印错误信息,否则继续向下。
将{" ", argv[1], 0}
赋给char *类型的argv1[]。
接着,使用vfork()函数创建一个新进程,并把返回值赋给childpid1,如果变量childpid1
小于0,说明在vfork()的过程中出错,则进行错误处理,使用宏定义ERR_EXIT("shmpthreadcon: 1st vfork()")
打印错误信息。
如果vfork()函数在执行过程中没有出错,则继续判断变量childpid1
的值,若变量childpid1
为0,说明是子进程,执行结果为使用execv("./alg.8-5-shmproducer.o", argv1)
引发程序8-5 shmproducer作为进程和父进程8-4 shmpthreadcon异步执行。
第一个vfork()进入执行进程8-5 shmproducer:
在进程8-5 shmproducer中,首先定义一个const char *类型字符串message_0为Hello World!
,然后使用shm_open(argv[1], O_RDWR, 0666)
把/dev/shm/myshm
作为共享内存对象,权限为可读可写,并把返回值赋给fd,如果fd值为-1,说明shm_open()失败,使用宏定义ERR_EXIT("con: shm_open()")
打印错误信息,否则向下执行。
接着,确定共享内存shmsize
大小为TEXT_NUM * sizeof(struct shared_struct)
,然后使用(char *)mmap(0, shmsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)
在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系,其中第一个参数0
表示由系统决定映射区的起始地址,第二个参数shmsize
代表映射区的长度,第三个参数PROT_READ|PROT_WRITE
表示内容可读可写,第四个参数MAP_SHARED
表示与其它所有映射这个对象的进程共享映射空间,第五个参数fd
是先前的文件描述词,第六个参数0
代表被映射对象内容的起点,并把返回值被映射区的指针赋给shmptr,如果shmptr的值为(void *)-1,则mmap()失败,使用宏定义ERR_EXIT("producer: mmap()")
打印错误信息,否则继续向下。
将message_0的内容以标准格式赋给shmptr,接着打印语句produced message:
和shmptr中的字符串,这里是produced message: Hello World!
,最后使用语句exit(EXIT_SUCCESS)
,表示退出成功,结束进程8-5 shmproducer。
第二个vfork()进入执行进程8-6 shmconsumer:
接着在进程8-4 shmpthreadcon中,如果变量childpid1
大于0,说明是父进程,执行结果为使用vfork()函数创建一个新进程,并把返回值赋给childpid2,如果变量childpid2
小于0,说明在vfork()的过程中出错,则进行错误处理,使用宏定义ERR_EXIT("shmpthreadcon: 2nd vfork()")
打印错误信息。
如果vfork()函数在执行过程中没有出错,则继续判断变量childpid2
的值,若变量childpid2
为0,说明是子进程,执行结果为使用execv("./alg.8-6-shmconsumer.o", argv1)
引发程序8-6 shmconsumer作为进程和父进程8-4 shmpthreadcon异步执行。
在进程8-6 shmconsumer中,先使用shm_open(argv[1], O_RDONLY, 0444)
把/dev/shm/myshm
作为共享内存对象,权限为只可读,并把返回值赋给fd,如果fd值为-1,说明shm_open()失败,使用宏定义ERR_EXIT("consumer: shm_open()")
打印错误信息,否则向下执行。
接着,确定共享内存shmsize
大小为TEXT_NUM * sizeof(struct shared_struct)
,然后使用(char *)mmap(0, shmsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)
在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系,其中第一个参数0
表示由系统决定映射区的起始地址,第二个参数shmsize
代表映射区的长度,第三个参数PROT_READ
表示内容只可读,第四个参数MAP_SHARED
表示与其它所有映射这个对象的进程共享映射空间,第五个参数fd
是先前的文件描述词,第六个参数0
代表被映射对象内容的起点,并把返回值被映射区的指针赋给shmptr,如果shmptr的值为(void *)-1,则mmap()失败,使用宏定义ERR_EXIT("consumer: mmap()")
打印错误信息,否则继续向下。
打印语句consumed message:
和shmptr中的字符串,这里是consumed message: Hello World!
,最后使用语句exit(EXIT_SUCCESS)
,表示退出成功,结束进程8-6 shmconsumer。
若变量childpid2
大于0,说明是父进程,执行结果为使用语句wait(&childpid1)
和wait(&childpid2)
等待子进程8-5 shmproducer和子进程8-6 shmconsumer执行完再继续执行,此时共享内存对象可以被任意知道文件名的进程删除,等待子进程执行完后,判断shm_unlink(argv[1])
的返回值是否为-1,如果是,则shm_unlink()失败,使用宏定义ERR_EXIT("con: shm_unlink()")
打印错误信息,否则shm_unlink()成功,释放共享内存,删除之前的共享内存对象myshm
文件,再次使用system(ls -l /dev/shm/)
,使用ls -l
命令查看/dev/shm/
目录下的文件信息,结果显示该目录下文件所占空间的总大小为0,没有任何文件。
最后使用语句exit(EXIT_SUCCESS)
,表示退出成功,结束进程8-6 shmconsumer。
(2)、设计实验:
执行程序命令:
gcc shmcon.c
gcc -o shmread.o shmread.c
gcc -o shmwrite.o shmwrite.c
./a.out ./shmread.o
程序设计分析:
大体参照验证实验 reader-writer 问题:Algorithms 8-1 ~ 8-3的代码,
头文件shmdata.h
改为:
#define TEXT_SIZE 4*1024 /* = PAGE_SIZE, size of each message */
#define TEXT_NUM 20 /* maximal number of mesages */
/* total size can not exceed current shmmax,
or an 'invalid argument' error occurs when shmget */
// 学生信息多字符串读写
typedef struct {
char text[TEXT_SIZE];
} Queue;
struct shared_struct {
Queue mtext[TEXT_NUM + 1]; // buffer for message reading and writing
int front; //队首元素的下标
int rear; //队尾元素的下标
};
// 将学生的学号、姓名、学院组织成一个结构类型进行读写
/*typedef struct {
int id;
char name[20];
char department[20];
} Queue;
struct shared_struct {
Queue students[TEXT_NUM + 1]; // buffer for message reading and writing
int front; // 队首元素的下标
int rear; // 队尾元素的下标
};*/
#define PERM S_IRUSR|S_IWUSR|IPC_CREAT
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
其中关键的数据结构为:
typedef struct {
char text[TEXT_SIZE];
} Queue;
/* a demo structure, modified as needed */
struct shared_struct {
Queue mtext[TEXT_NUM + 1]; /* buffer for message reading and writing */
int front;
int rear;
};
在共享内存中使用的结构体中定义了一个成员变量Queue mtext[TEXT_NUM + 1]
,是一个最大容量为TEXT_NUM
即20的循环队列,以及队首下标front
,队尾下标rear
,判断循环队列为空的条件为front == rear
,此时只能写不能读,判断循环队列为满的条件为(rear + 1) % (TEXT_NUM + 1) == front
,此时只能读不能写。
因为在结构体shared_struct已经定义了一个最大容量为TEXT_NUM
20的循环队列,所以各个c文件在计算申请共享内存的大小时,需要把语句从原来示例代码的shmsize = sizeof(struct shared_struct)
改为shmsize = TEXT_NUM*sizeof(struct shared_struct)
。
在shmcon.c种初始化结构体shared_struct时,需要将其中的循环队列的队首元素和队尾元素得的下标都设为0,即shared->front = 0
和shared->rear = 0
。
在进行读写时,设置读写可以同时进行,为了方便观察效果,每次进行读时,让充当reader的进程shmread进行sleep(3)
休眠3秒,即每3秒读一次,这样留给我们足够的时间进行写,方便观察效果。
在读写的过程中,判断循环队列满的条件为(shared->rear + 1) % (TEXT_NUM + 1) == shared->front
,此时不能写,程序会打印语句The queue is full, please wait until you see "Enter some text: "
,提示用户需要等待有信息从循环队列读出,不再为满才能继续进行写,在充当writer的shmwrite.c中,通过以下代码进行控制:
while ((shared->rear + 1) % (TEXT_NUM + 1) == shared->front) {
sleep(1);
}
判断循环队列满空的条件为shared->front == shared->rear
,此时不能读,需要等待有信息写入循环队列,不再为空才能继续进行读,在充当writer的shmwrite.c中,通过以下代码进行控制:
while (shared->front == shared->rear) {
sleep(1);
}
当有信息写入循环队列时,设置循环队列的队尾下标加一,代码为shared->rear = (shared->rear + 1) % (TEXT_NUM + 1)
,当有信息读出循环队列时,设置循环队列的队首下标加一,代码为shared->front = (shared->front + 1) % (TEXT_NUM + 1)
。
当写入循环队列的信息为"end"
时,此时充当writer的进程shmwrite先把"end"
写入循环队列中,把循环队列的队尾下标加一,然后检测到输入了end
之后,进程shmwrite退出,此时充当reader进程shmread还在运行,当shmread把循环队列中的信息都输出后,到最后一个信息"end"
时,进程shmread把"end"
输出后,也退出了。
执行结果分析:
可以看到,数据读出的顺序是按照数据写入shaerd buffer中的顺序来的,说明实现的循环队列的先进先出。
通过设计,每次写入时,展示shared buffer中的信息以及其的元素个数,方便达到最大容量20时进行观察。
可以看到,当shared buffer中的元素达到20时,弹出提示The queue is full, please wait until you see "Enter some text: "
,此时无法再向循环队列中写入信息。当看到You wrote:
时,说明有信息从循环队列中读出,循环队列不再为满,可以继续写入,此时"Enter some text: "
又弹出,说明可以继续进行写入操作。
可以看到,当shared buffer中的元素被全部读出后,读的操作也停止。
可以看到,输入"end"
后,提示You can't enter any text now
,此时不能再输入数据到shared buffer中,等到最后"end"
也从缓冲区中读出后,进程退出,最后可以看到程序结束。
若想将将学生的学号、姓名、学院组织成一个结构类型进行读写,可把关键结构改为:
typedef struct {
int id;
char name[20];
char department[20];
} Queue;
struct shared_struct {
Queue students[TEXT_NUM + 1]; // buffer for message reading and writing
int front; // 队首元素的下标
int rear; // 队尾元素的下标
};