《操作系统原理、实现与实践》实践项目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;
}
View Code

之后还要修改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的内存管理方式,具体的代码内容在这里:(内核修改部分+测试程序)。

最终呈现的内容如下所示:

 

 完结撒花!

posted @ 2023-04-06 09:33  6954717  阅读(254)  评论(0编辑  收藏  举报