《操作系统原理、实现与实践》实践项目5&6:信号量&地址映射与共享
5.10更新:
因为新布置的大作业也是和这一部分相关的(信号量),索性就不新开一个篇章来记录了。
我们记录一下再复现的时候没有记录的问题:
(1)实验过程中首先遇到的问题是难以实现一个程序里同时运行producer和consumer的操作,这里我们采用了利用信号量的操作途径,利用互斥锁和进程id,完成了producer和consumer的区分;
(2)接下来遇到的问题是要记得将我们所修改的内核同步修改到bochs里面的linux-0.11不然无法达成成功修改;
(3)最后有遇到的问题是producer和consumer的控制端输出,这个地方我选用的方法是在系统调用的时候进行输出一部分,然后在pc.c中输出一部分,两部分相互组合共同形成了输出;不过因为stdout同时只能输出一个,所以这个地方的输出顺序只能保证同一类型的输出递增。
再测试运行截图:
教员布置的操作系统的第一个大作业,其中有一些知识相对而言比较重要,包括一些操作过程都具有重复实验的价值;所以写一个blog记录一下,方便一下以后重复这个大实验或是复现。
分为三个部分:实验环境的配置、实验5的实现、实验6的实现。
实验环境的配置:
KylinOS应该是可以实现的,实现的原理同我下面所叙述的过程。
资源下载:
我这里采取的实验环境是VMware上的Ubuntu 20.04 64bit,采取的子环境是从助教学长那里下载的rar压缩包,在windows的环境下解压后会有一个xxx.tar.gz的压缩包文件以及一个gcc,g++的3.4版本的安装版,由于这个实验在3.4环境下做是比较合适的,所以我们需要将我们的gcc,g++的版本调整至3.4;
环境更改,gcc多版本共存
dpkg表示“Debian Packager”,是Debian开发的套件管理系统,
在对应的gcc,g++的那个文件夹里面,运行如下的指令:
sudo dpkg --force-depends -i gcc-3.4-base_3.4.6-6ubuntu3_amd64.deb sudo dpkg --force-depends -i gcc-3.4_3.4.6-6ubuntu3_amd64.deb sudo dpkg --force-depends -i cpp-3.4_3.4.6-6ubuntu3_amd64.deb sudo dpkg --force-depends -i g++-3.4_3.4.6-6ubuntu3_amd64.deb sudo dpkg --force-depends -i libstdc++6-dev_3.4.6-6ubuntu3_amd64.deb
接着可以用
gcc --version g++ --version
来查看自己所安装的版本是不是正确的版本,此时应该显示的是3.4
环境配置并make
我们需要解压xxx.tar.gz压缩包(在linux环境下),然后对于在其解压后的linux-0.11的文件夹内进行make。
大概率此时的make是会显示各种错误的,所以我们需要配置一些环境。
sudo apt-get install bin86 sudo apt-get install gcc-multilib sudo apt-get install build-essential sudo apt-get install bochs bochs-x bochs-sdl sudo dpkg --add-architecture i386 sudo apt-get update sudo apt install libsm6:i386 sudo apt install libx11-6:i386 sudo apt install libxpm4:i386
按顺序尝试以上的命令进行配置环境,如果还是不成功make就试试非i386版本的安装。(其实以上的命令就是根据显示的出的make中的error来配置修改的)
最后make成功之后,会显示sync的结果,尝试./run与./dbg-asm测试,成功之后则说明之前的步骤都是没问题的。
实验5的实现:
实践目标
实验五的主要实现部分是sem.c,实质就是一个信号量的交换。
这个地方我在实现的时候关于系统调用的函数写错了,在大佬的帮助下才改正的,卡了很久。
实验5的实践目标有两个:
1. 在Linux0.11(没有实现信号量)上实现信号量有关的如下系统调用:
1 sem_t *sem_open(const char *name, unsigned int value); 2 int sem_wait(sem_t *sem); 3 int sem_post(sem_t *sem); 4 int sem_unlink(const char *name);
第一个函数的功能是创建一个名字为name的信号量或者打开名字为name的信号量;
第二个函数的功能是信号量的P操作:将信号量计数器减一,表示“申请占用一个资源”;
第三个函数的功能是信号量的V操作:将信号量计数器加一,表示“释放信号量”;
第四个函数的功能是删除名字为name的信号量;
2. 利用上面实现的信号量系统调用,编写bochs上的测试程序"pc.c"来模拟生产者-消费者之间的同步,类似于握手的操作。
根据题例,建立1个生产者进程(producer),5个消费者进程(consumer);用文件建立一个共享缓存区;
生产者依次向缓存区写入整数0,1,2,...,499,每个消费者进程从缓存区中读取100个数,对于其读取的每一个数字均需要打印到stdout上。(须注意,缓存区文件最多只能保存10个数。)
实现信号量有关的系统调用
首先要先在oslab/linux-0.11/include/linux文件夹下添加sem.h头文件,需要实现的内容很简单,就是实现对于信号量的结构定义,此处我们借助了sched.h中的tss的队列结构,节省了一些代码量,内容如下:
#ifndef _SEM_H_ #define _SEM_H_ #include<linux/sched.h> typedef struct semaphore_t { char name[20]; int value; struct task_struct *queue; } sem_t; #endif
最主要的是sem.c文件的编写,此处我们选择在oslab/linux-0.11/kernel中编写sem.c(其实在别的地方也一样,都要修改对应位置的Makefile)
关于sem.c的实现:
#include <unistd.h> #include <string.h> #include <linux/sem.h> #include <asm/segment.h> #include <asm/system.h> #include <linux/kernel.h> #define SEM_LIST_LENGTH 5 //信号量最多多少,原题为10,此处设为5 sem_t sem_list[SEM_LIST_LENGTH] = { {"\0",0,NULL}, {"\0",0,NULL}, {"\0",0,NULL}, {"\0",0,NULL}, {"\0",0,NULL} }; sem_t *sys_sem_open(const char *name, unsigned int value) { if(name == NULL) { printk("name == NULL\n"); //name not initial return NULL; } char nbuf[20]; int i = 0; for(i = 0; i < 20; i++) nbuf[i] = get_fs_byte(name + i); //from asm/segment.h //从(name+i)中取出一个char sem_t * result = NULL; for(i = 0; i < SEM_LIST_LENGTH; i++) //遍历查找 { if(sem_list[i].name[0] == '\0') break; if(!strcmp(sem_list[i].name, nbuf)) { result = &sem_list[i]; printk("Found sem %s.\n", result->name); return result; //找到了,返回 } } // 没找到,新创建一个 strcpy(sem_list[i].name, nbuf); sem_list[i].value = value; sem_list[i].queue = NULL; result = &sem_list[i]; printk("Have created a sem %s, value = %d.\n", result->name, result->value); return result; } //P operation //0 succeed, 1 fail int sys_sem_wait(sem_t * sem) { if(sem == NULL || sem < sem_list || sem > sem_list + SEM_LIST_LENGTH) { printk("P(sem) error!\n"); //非有效地址 return -1; } cli(); //close trap while(sem->value <= 0) sleep_on(&(sem->queue)); sem->value--; sti(); //open trap return 0; } //V operation //0 succeed, 1 fail int sys_sem_post(sem_t * sem) { if(sem == NULL || sem < sem_list || sem > sem_list + SEM_LIST_LENGTH) { printk("V(sem) error!\n"); return -1; } cli(); //close trap sem->value++; if(sem->value <= 1) //ATTENTION! 前面加1了这里应是和1比较 wake_up(&(sem->queue)); sti(); return 0; } //delete sem(name) //0 succeed, 1 fail int sys_sem_unlink(const char *name) { if(name == NULL) return -1; char nbuf[20]; int i = 0; for(i = 0; i < 20; i++) { nbuf[i] = get_fs_byte(name + i); //同上 if(nbuf[i] == '\0') break; } i = 0; for(i = 0; i < SEM_LIST_LENGTH; i++) { if(strcmp(sem_list[i].name, nbuf)) { sem_list[i].name[0] == '\0'; sem_list[i].value = 0; sem_list[i].queue = NULL; } //operate } if(i == SEM_LIST_LENGTH) return -1; //Not Found return 0; //succeed }
在编写完这一部分的程序之后,我们记得要去修改对应文件夹里的Makefile,比如我们这里是在kernel/文件夹下修改的,那么我们就要对应地修改kernel/文件夹下的Makefile,增添如下信息:
... OBJS = sched.o system_call.o traps.o asm.o fork.o \ panic.o printk.o vsprintf.o sys.o exit.o \ signal.o mktime.o sem.o ... ### Dependencies: sem.s sem.o: sem.c ../include/linux/kernel.h ../include/unistd.h \ ../include/linux/sem.h ../include/linux/sched.h ...
然后为了实现对应的系统调用的完整呈现,我们需要修改linux-0.11/include/unistd.h和linux-0.11/include/linux/sys.h,产生完整的系统调用。
对于unistd.h的修改:
#define __NR_sem_open 72 #define __NR_sem_wait 73 #define __NR_sem_post 74 #define __NR_sem_unlink 75
对于sys.h的修改:
extern int sys_sem_open(); extern int sys_sem_wait(); extern int sys_sem_post(); extern int sys_sem_unlink(); sys_call_table[]加上 , sys_sem_open, sys_sem_wait, sys_sem_post, sys_sem_unlink...
修改系统调用的总数,在kernel/system_call.s中修改:
nr_system_calls = 76
实现完上述修改后,在linux-0.11/下进行make clean并make,出现sync之后则表示make成功,可以进行下一步的操作了。
实现bochs中的生产者、消费者的调用
为了方便代码的编写,我们可以先将bochs挂载到我们编写代码的环境下面,在oslab/的文件夹下采用命令:
sudo ./mount-hdc
然后在hdc/usr/root文件夹下编写对应的程序,此处我们把他命名为pc.c程序:
第一个版本是我自己编写的pc.c:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <sys/stat.h> #include <semaphore.h> #include <sys/types.h> #include <sys/wait.h> #define M 530 #define N 5 /*消费者进程数*/ #define BUFSIZE 10 int main() { sem_t *empty, *full, *mutex; int fd, i, j, k, child, data; pid_t pid; int buf_out = 0; //pos out int buf_in = 0; //pos in empty = sem_open("empty", O_CREAT | O_EXCL, 0644, BUFSIZE); full = sem_open("full", O_CREAT | O_EXCL, 0644, 0); mutex = sem_open("mutex", O_CREAT | O_EXCL, 0644, 1); fd = open("buffer.txt", O_CREAT | O_TRUNC | O_RDWR,0666); lseek(fd,BUFSIZE*sizeof(int),SEEK_SET); write(fd,(char *)&buf_out,sizeof(int)); if(!(pid = fork())) /*Producer*/ { printf("Producer pid = %d\n", getpid()); for (i = 0 ; i < M; i++) { sem_wait(empty); sem_wait(mutex); /*写入一个字符*/ lseek(fd, buf_in*sizeof(int), SEEK_SET); write(fd,(char *)&i,sizeof(int)); buf_in = (buf_in + 1) % BUFSIZE; sem_post(mutex); sem_post(full); /*唤醒消费者*/ } printf("Producer end.\n"); fflush(stdout); return 0; } else if(pid < 0) { perror("Fail to fork!\n"); return -1; } /*Consumer*/ for( j = 0; j < N ; j++ ) { if((pid=fork())==0) { for( k = 0; k < M/N; k++ ) { sem_wait(full);/*一开始为0会阻塞此处*/ sem_wait(mutex); lseek(fd,BUFSIZE*sizeof(int),SEEK_SET); read(fd,(char *)&buf_out,sizeof(int)); /*读取数据*/ lseek(fd,buf_out*sizeof(int),SEEK_SET); read(fd,(char *)&data,sizeof(int)); buf_out = (buf_out + 1) % BUFSIZE; lseek(fd,BUFSIZE*sizeof(int),SEEK_SET); write(fd,(char *)&buf_out,sizeof(int)); sem_post(mutex); sem_post(empty);/*唤醒生产者*/ /*Consume*/ printf("%d: %d\n",getpid(),data); fflush(stdout); } printf("child-%d: pid = %d end.\n", j, getpid()); return 0; } else if(pid<0) { perror("Fail to fork!\n"); return -1; } } child = N + 1; while(child--) wait(NULL); /*释放信号量*/ sem_unlink("full"); sem_unlink("empty"); sem_unlink("mutex"); /*释放资源*/ close(fd); return 0; }
第二个版本是大佬编写的pc.c:
#define __LIBRARY__ #include <unistd.h> #include <linux/sem.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/sched.h> _syscall2(sem_t *, sem_open, const char *, name, unsigned int, value) _syscall1(int, sem_wait, sem_t *, sem) _syscall1(int, sem_post, sem_t *, sem) _syscall1(int, sem_unlink, const char *, name) sem_t *mutex, *fill, *empty; const int product_num = 12; const int buffer = 4; const int consumer_num = 2; int product_finished = 0; int item_consumed = 0; int main() { int pid; int i; mutex = sem_open("mutex", 1); fill = sem_open("fill", 0); empty = sem_open("empty", buffer); if (mutex == NULL) { printf("null\n"); return 0; } if ((pid = fork())) { printf("pid: %d, producer start\n", pid); fflush(stdout); while(product_finished <= product_num) { sem_wait(empty); sem_wait(mutex); printf("pid: %d, produced item %d\n", pid, product_finished); fflush(stdout); product_finished++; sem_post(fill); sem_post(mutex); } } else { /* consumer */ i = consumer_num; while(i--) { if (!(pid = fork())) { pid = getpid(); printf("pid: %d, consumer start\n", pid); fflush(stdout); while(1) { sem_wait(fill); sem_wait(mutex); printf("pid: %d, consumed item %d\n", pid, item_consumed); fflush(stdout); item_consumed++; sem_post(empty); sem_post(mutex); if (item_consumed == product_num) return 0; } } } } return 0; }
之后还要修改hdc中的unistd.h,要将这个的unistd.h修改的和linux-0.11下的一致。
修改完之后,我们在oslab文件夹下运行:sudo umount hdc取消hdc的挂载。
测试
最后我们在bochs下运行./run,测试一下信号量的实现过程,在运行之前要对之前的pc.c用gcc编译一下再运行,运行结果如下所示:
经过以上的操作,我们就把信号量的实现完成了,接下来我们就要正式开始实验6的任务工作了。
实验6的实现:
实践目标
在开始之前,我们仍然是要先明确我们的实践目标,这个实践的目标有两个:(1)用Bochs调试工具跟踪Linux 0.11的地址转换过程;(2)实现基于共享物理页框的进程间内存共享。
其中第一个实践的目标比较基于理论上的一些知识,需要我们对于概念的理解有一定的要求;第二个实践的目标与实践5有一些相像的地方,均需要通过实现相应的函数来达到进程间内存共享的目的。
跟踪地址转换过程-获得虚拟地址
在之前的环境中是已经配置好了我们的过程所需要的环境的,接下来我们直接跟着实践所描述的步骤进行操作(这个过程实践所指导的就已经非常详细了,以下我们将重视演示过程):
要在Linux 0.11上编写一个test.c程序,要跟踪的地址就是这个程序中的地址:
#include<stdio.h> int i = 0x12345678; int main(void) { printf("The logical address of i is 0x%08x", &i); fflush(stdout); wihle(i); //控制程序加载 return 0; }
要记得用./dbg-asm加载,以方便我们对于bochs进行调试运行;
此时对其进行编译执行之后会呈现的内容是"The logical address of i is 0x00003004";
此时打印的是i的逻辑地址,又因为while(i);语句控制了test程序一直运行,不会主动退出,此时我们可以利用此来进行调试命令查看。
在非内核的运行状态下时(如果是内核,就在命令行窗口里面接着使用c+crtl c的组合操作),此后显示的是000f:00000062的jz指令,我们需要跟踪到的指令是cmp指令,所以我们采用n命令单步执行直到cmp指令:
接下来使用“u/7”命令,显示从当前位置开始的七条指令的反汇编代码,然后是要开始寻找和逻辑地址DS:0x3004对应的物理地址,开始跟踪地址转换过程,先采用sreg看一下各个寄存器的信息:
LDTR此时,s=0x68,表示的是当前进程的LDT存在GDT的13号位置上(二进制表示后,低三位为控制符),此时我们通过GDTR知道GDT的起始位置在0x54b8(不同机器此处可能不同),每一项占64位(即8个字节),所以要查找的物理地址是0x54b8+13*8,用命令"xp/2w 0x5cb8+13*8"可以查看此处的内容:
对应的数0xc2d00068 0x000082f9中的标红加粗数字组合以后为“0x00f9c2d0”,这里是根据了x86的格式转换,具体内容请查看x86文档,此处注意是高8字节在右,低8字节在左。
接下来我们就可以通过"xp/8w 0x00f9c2d0"查看LDT表的前四项内容:
此时DS的索引值为0x17,表示的是LDT中的第16个字节开始的内容,LDT每项占8个字节,所以第三项"0x00003fff 0x10c0f300"即为所要查找的内容,以同样的方法可以得到DS段在虚拟内存空间中的起始地址,加上偏移量0x3004可得DS:0x3004对应的虚拟地址:0x10000000 + 0x3004 = 0x10003004,可知虚拟地址为0x10003004。
跟踪地址转换过程-虚拟地址转化到物理地址
这一步的内容核心是查找页表,主要应用的是页式管理的内容。我们需要算出虚拟地址中的页目录号、页表号和页内偏移,他们分别对应32位虚拟地址的前10位、中间10位和末尾12位。
我们已知虚拟地址位0x10003004,所以我们可以通过转换得知页目录号、页表号和页内偏移分别为:64,3,4。
采用creg指令查看CR3寄存器的指向,CR3即指向的页目录表的基址,可得知:
接着我们查看第65个页目录项,第三个页表项,查询过程如下:
注意后三位为控制位,在查找的时候要分辨清楚。最后我们可以知道转化成的物理地址是:0x00fa7004.
此时查看一下0x00fa7004的值,发现值确实为test.c中i的初值,说明过程是正确的。
最后我们通过直接修改内存来改变i的值,使用命令"setpmem 0x00fa7004 4 0"实现,表示从0x00fa7004地址开始的4个字节均设置为0,然后再用c命令继续bochs的运行,则此时while(i);就会退出循环,我们看到了进程test成功退出了,说明i的修改成功了,即说明我们的修改是成功的。
内存的共享的实现
首先,在开始之前,我们先要明确我们的目的,即实现两个系统调用,来完成内存的共享。这两个系统调用分别是shmget()和shmat(),在Linux0.11中其本身是没有这种系统调用的。
系统调用的实现
我们需要实现的两个调用为shmget()和shmat(),两个调用的原型为:
int shmget(key_t key, size_t size, int shmflg); void *shmat(int shmid, const void *shmaddr, int shmflg);
shmget()的调用会新建或者打开一页的物理内存作为共享内存,并返回该页共享内存的shmid,即共享内存在操作系统中的标识。生成的shmid唯一地与key相关,如果多个进程使用相同的key来调用shmget(),则会返回相同的shmid。同时如果size>MAX_SIZE,则返回-1,置errno为EINVAL,若无空闲内存,也返回-1,置errno为ENOMEM。
shmat()会将shmid指定的共享页面映射到当前进程的虚拟地址空间中,并返回一个逻辑地址p,调用进程可以读写p来读写这一页的共享内存,两个进程都调用shmat可以关联到同一页内存上,形成共享内存页结构,实现了基于共享内存的进程间通信。如果shmid非法,返回-1,置errno为EINVAL。
我们先从添加系统调用号开始:
同时在unistd中添加shm_ds的结构体声明以及函数调用:
然后我们在include/linux/sys.h中添加系统调用的定义:
之后我们在kernel/system_call.s中增加系统调用数为78
然后我们就可以正式地来编写shm.c了,此处我们选择在linux-0.11/kernel/文件夹下编写:
#define __LIBRARY__ #include <unistd.h> //引入调用 #include <linux/kernel.h> #include <errno.h> //引入错误提示 #include <linux/sched.h> //引入结构 #include <linux/mm.h> static shm_ds shm_list[SHM_SIZE]; //严格的来说,其初始值应该默认是0? int sys_shmget(unsigned int key, size_t size) { int i; unsigned long page; if (size > PAGE_SIZE) { printk("Shmget size overflow (%u > %ud). \n", size, PAGE_SIZE); return -ENOMEM; //error1 } if (key == 0) { printk("Shmget key number cannot be 0.\n"); return -EINVAL; //error2 } for (i = 0; i < SHM_SIZE; i++) //先进行查询 if (shm_list[i].key == key) return i; //查询到,则(打开这个界面)返回 page = get_free_page(); //没查询到,新建立一个非空页面 if (!page) return -ENOMEM; printk("Shmget get page address: 0x%08x\n", page); for (i = 0; i < SHM_SIZE; i++) { if (shm_list[i].key == 0) { shm_list[i].key = key; //赋值占用 shm_list[i].size = size; shm_list[i].page = page; return i; } } return -1; } void *sys_shmat(int shmid) { unsigned long data_base, brk; if (shmid < 0 || SHM_SIZE <= shmid || shm_list[shmid].page == 0 || shm_list[shmid].key <= 0) //如果此shmid对应地无效 return (void *)-EINVAL; data_base = get_base(current->ldt[2]); printk("Shmat data_base = 0x%08x, page = 0x%08x\n", data_base, shm_list[shmid].page); brk = current->brk + data_base; current->brk += PAGE_SIZE; if (!put_page(shm_list[shmid].page, brk)) //如果put_page失败 return (void *)-ENOMEM; return (void *)(current->brk - PAGE_SIZE); //返回p }
具体的内容此处都写了注释,关键的应用函数是get_free_page(),get_base(),put_page()。
在shmat()函数中,我们利用了在进程的PCB中记录了brk指针的逻辑地址的信息,通过进程开始的虚拟地址就可以得到brk指针的虚拟地址/线性地址(段基址+逻辑地址)。
然后我们修改一下shm.c文件在其中的kernel/下的Makefile:
然后我们在linux-0.11文件夹下make clean之后再make一下,出现sync则表示我们修改内核成功了:
producer&consumer的实现
此时我们可以采取挂载一下sudo ./mount-hdc,实现较为便捷的编程。
采用如下命令进入Linux-0.11的root文件夹下编程:
sudo ./mount-hdc cd /oslab/hdc/usr/root vim producer.c vim consumer.c
记得在开始之前,我们要把linux-0.11/中的unistd.h文件拷贝到hdc/usr/root/中去:
(sudo ./mount-hdc) cp ./linux-0.11/include/unistd.h ./hdc/usr/include/
接下来我们将编写producer.c和consumer.c:
producer.c:
#define __LIBRARY__ #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <stdio.h> #include <linux/sem.h> _syscall2(sem_t*,sem_open,const char *,name,unsigned int,value); _syscall1(int,sem_wait,sem_t*,sem); _syscall1(int,sem_post,sem_t*,sem); _syscall1(int,sem_unlink,const char *,name); _syscall1(void*,shmat,int,shmid); _syscall2(int,shmget,int,key,int,size); #define NUMBER 520 #define BUFSIZE 10 sem_t *empty, *full, *mutex; int i,shmid; int *p; int buf_in = 0; int main() { if((mutex = sem_open("mutex",1)) == NULL) { perror("sem open error!\n"); return -1; } if((empty = sem_open("empty",10)) == NULL) { perror("sem open error!\n"); return -1; } if((full = sem_open("full",0)) == NULL) { perror("sem open error!\n"); return -1; } shmid = shmget(1234, BUFSIZE); printf("Shmid %d\n",shmid); if(shmid == -1) return -1; p = (int*) shmat(shmid); printf("Producer start.\n"); fflush(stdout); for( i = 0 ; i < NUMBER; i++) { sem_wait(empty); sem_wait(mutex); p[buf_in] = i; //输入数据, produce buf_in = ( buf_in + 1)% BUFSIZE; sem_post(mutex); sem_post(full); } printf("Producer end.\n"); fflush(stdout); sem_unlink("full"); sem_unlink("empty"); sem_unlink("mutex"); return 0; }
consumer.c:
#define __LIBRARY__ #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <stdio.h> #include <linux/sem.h> _syscall2(sem_t*,sem_open,const char *,name,unsigned int,value); _syscall1(int,sem_wait,sem_t*,sem); _syscall1(int,sem_post,sem_t*,sem); _syscall1(int,sem_unlink,const char *,name); _syscall1(void*,shmat,int,shmid); _syscall2(int,shmget,int,key,int,size); #define NUMBER 520 #define BUFSIZE 10 sem_t *empty, *full, *mutex; int i,shmid,data; int *p; int buf_out = 0; int main() { if((mutex = sem_open("mutex",1)) == NULL) { perror("sem open error!\n"); return -1; } if((empty = sem_open("empty",10)) == NULL) { perror("sem open error!\n"); return -1; } if((full = sem_open("full",0)) == NULL) { perror("sem open error!\n"); return -1; } printf("consumer start.\n"); fflush(stdout); shmid = shmget(1234, BUFSIZE); printf("shmid:%d\n",shmid); if(shmid == -1) return -1; p = (int *)shmat(shmid); for (i = 0; i < NUMBER; i++ ) { sem_wait(full); sem_wait(mutex); data = p[buf_out]; buf_out = (buf_out + 1) % BUFSIZE; sem_post(mutex); sem_post(empty); printf("%d: %d\n",getpid(),data); //consume fflush(stdout); } printf("consumer end.\n"); fflush(stdout); sem_unlink("full"); sem_unlink("empty"); sem_unlink("mutex"); return 0; }
之后我们再编译一下:
gcc -o producer producer.c gcc -o consumer consumer.c
sudo umount hdc
最后我们在linux-0.11中运行以下命令:
./producer &
./consumer
运行测试:
以上的编写方式会出现Kernel Panic。
所以我们可以采取以下的方案将Kernel Panic转化为Segmentation Fault,通过修改memory.c的内存管理方式,具体的代码内容在这里:(内核修改部分+测试程序)。
最终呈现的内容如下所示:
完结撒花!