zoukankan      html  css  js  c++  java
  • 操作系统实验05-信号量实现和应用

    实验内容

    本次实验的基本内容是在Linux 0.11的内核中实现信号量,并向用户提供使用信号量的接口,用户使用该接口解决一个实际的进程同步问题。实验的主要内容包括如下两个部分:

    1.实现信号量

    在Linux 0.11内核上(Linux 0.11内核中没有定义信号量)实现信号量,并创建相应的系统调用以供用户使用。应提供的系统接口主要包括:

    int CreateSemaphore(char * semname);
    

    该操作用来在内核中创建一个信号量,输入的参数是信号量的名字,返回的是信号量的一个整数标识semid,信号量是一种内核资源,不应该无限制的创建,所以在内核中信号量可以被组织成一个数组,此时semid就是创建的信号量在内核信号量数组中的下标。如果这个名为semname的信号量已经创建,则返回这个已创建的信号量的标识semid,也即多个具有相同名字的信号量创建接口返回相同的返回值,即相同的semid

    int SetSemaphore(int semid, int value);
    

    用来设置信号量的值,其中semid是信号量标识,value是要设置的信号量值,该函数的返回值是信号量的当前值。该接口通常用来设置信号量的初值。

    int WaitSemaphore(int semid);
    

    该函数就是信号量的P操作,其功能就是对信号量的值减1,如果其值小于0则令调用进程等待在信号量semid上。

    int SignalSemaphore(int semid);
    

    该函数就是信号量的V操作,其功能就是对信号量的值加1,如果其值小于等于0则令唤醒等待在信号量semid上的进程。

    2.使用信号量

    在定义了信号量的Linux 0.11操作系统上编写用户程序来演示信号量的作用。该用户程序解决就是传统的生产者—消费者问题,要求编写的用户程序完成下面的任务:
    1.编写的主程序演示生产者—消费者两个进程的同步过程;
    2.编写的主程序创建两个进程:生产者进程和消费者进程;
    3.编写生产者进程和消费者进程的代码。
    4.要求对比三种设置下的运行结果:

    • 没有信号量下的生产者—消费者。
    • 有信号量,1个生产者进程,1个消费者进程,用for循环控制生产者(消费者)各执行N次。
    • 有信号量,N个生产者进程,N个消费者进程。

    步骤

    一、实现信号量

    1.新建sem.h

    在linux-0.11/include/linux目录下新建sem.h,定义信号量的数据结构。sem.h的代码如下

    #ifndef _SEM_H
    #define _SEM_H
    
    #include <linux/sched.h>
    
    #define SEMTABLE_LEN    20
    #define SEM_NAME_LEN    20
    
    
    typedef struct semaphore{
        char name[SEM_NAME_LEN];
        int value;
        struct task_struct *queue;
    } sem_t;
    extern sem_t semtable[SEMTABLE_LEN];
    
    #endif
    

    代码截图如下:

    2.新建sem.c

    在linux-0.11/kernel目录下,新建实现信号量函数的源代码文件sem.c。

    #include <linux/sem.h>
    #include <linux/sched.h>
    #include <unistd.h>
    #include <asm/segment.h>
    #include <linux/tty.h>
    #include <linux/kernel.h>
    #include <linux/fdreg.h>
    #include <asm/system.h>
    #include <asm/io.h>
    //#include <string.h>
    
    sem_t semtable[SEMTABLE_LEN];
    int cnt = 0;
    
    sem_t *sys_sem_open(const char *name,unsigned int value)
    {
        char kernelname[100];   
        int isExist = 0;
        int i=0;
        int name_cnt=0;
        while( get_fs_byte(name+name_cnt) != '')
        name_cnt++;
        if(name_cnt>SEM_NAME_LEN)
        return NULL;
        for(i=0;i<name_cnt;i++)
        kernelname[i]=get_fs_byte(name+i);
        int name_len = strlen(kernelname);
        int sem_name_len =0;
        sem_t *p=NULL;
        for(i=0;i<cnt;i++)
        {
            sem_name_len = strlen(semtable[i].name);
            if(sem_name_len == name_len)
            {
                    if( !strcmp(kernelname,semtable[i].name) )
                    {
                        isExist = 1;
                        break;
                    }
            }
        }
        if(isExist == 1)
        {
            p=(sem_t*)(&semtable[i]);
            //printk("find previous name!
    ");
        }
        else
        {
            i=0;
            for(i=0;i<name_len;i++)
            {
                semtable[cnt].name[i]=kernelname[i];
            }
            semtable[cnt].value = value;
            p=(sem_t*)(&semtable[cnt]);
             //printk("creat name!
    ");
            cnt++;
         }
        return p;
    }
    
    
    int sys_sem_wait(sem_t *sem)
    {
        cli();
        while( sem->value <= 0 )        //
            sleep_on(&(sem->queue));    //这两条语句顺序不能颠倒,很重要,是关于互斥信号量能不能正确工作的!!!
        sem->value--;               
        sti();
        return 0;
    }
    int sys_sem_post(sem_t *sem)
    {
        cli();
        sem->value++;
        if( (sem->value) <= 1)
            wake_up(&(sem->queue));
        sti();
        return 0;
    }
    
    int sys_sem_unlink(const char *name)
    {
        char kernelname[100];   /* 应该足够大了 */
        int isExist = 0;
        int i=0;
        int name_cnt=0;
        while( get_fs_byte(name+name_cnt) != '')
                name_cnt++;
        if(name_cnt>SEM_NAME_LEN)
                return NULL;
        for(i=0;i<name_cnt;i++)
                kernelname[i]=get_fs_byte(name+i);
        int name_len = strlen(name);
        int sem_name_len =0;
        for(i=0;i<cnt;i++)
        {
            sem_name_len = strlen(semtable[i].name);
            if(sem_name_len == name_len)
            {
                    if( !strcmp(kernelname,semtable[i].name))
                    {
                            isExist = 1;
                            break;
                    }
            }
        }
        if(isExist == 1)
        {
            int tmp=0;
            for(tmp=i;tmp<=cnt;tmp++)
            {
                semtable[tmp]=semtable[tmp+1];
            }
            cnt = cnt-1;
            return 0;
        }
        else
            return -1;
    }
    

    3.修改unistd.h

    在unistd.h内增加新的系统调用编号(之前的实验中也有做系统调用,所以这里直接记录一下修改的结果,过程就大概省略了一些)

    #define __NR_sem_open   72 
    #define __NR_sem_wait   73
    #define __NR_sem_post   74
    #define __NR_sem_unlink 75
    

    4.修改system_call.s

    在system_call.s文件中找到nr_system_calls并将其值更改为76(因为增加了四个sem系统调用函数)

    nr_system_calls = 76
    

    5.修改sys.h

    • 增加四个函数,函数如下:
    extern int sys_sem_open();
    extern int sys_sem_wait();
    extern int sys_sem_post();
    extern int sys_sem_unlink();
    
    • 将函数名放在数组内:
    sys_sem_open,sys_sem_wait,sys_sem_post,sys_sem_unlink
    

    代码截图如下:

    6.修改Makefile

    将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/sem.h ../include/linux/kernel.h 
    ../include/unistd.h
    ......
    

    Makefile截图如下:

    7.挂载hdc并准备相关文件

    进入oslab根目录执行sudo ./mount-hdc,随后将unistd.h复制到usr/include下,将sem.h复制到usr/include/linux下,最后使用sudo umount hdc卸载hdc

    二、编写生产者-消费者检验程序

    1.新建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)
    
    const char *FILENAME = "/usr/root/buffer_file";    /* 消费生产的产品存放的缓冲文件的路径 */
    const int NR_CONSUMERS = 5;                        /* 消费者的数量 */
    const int NR_ITEMS = 50;                        /* 产品的最大量 */
    const int BUFFER_SIZE = 10;                        /* 缓冲区大小,表示可同时存在的产品数量 */
    sem_t *metux, *full, *empty;                    /* 3个信号量 */
    unsigned int item_pro, item_used;                /* 刚生产的产品号;刚消费的产品号 */
    int fi, fo;                                        /* 供生产者写入或消费者读取的缓冲文件的句柄 */
    
    
    int main(int argc, char *argv[])
    {
        char *filename;
        int pid;
        int i;
    
        filename = argc > 1 ? argv[1] : FILENAME;
        /* O_TRUNC 表示:当文件以只读或只写打开时,若文件存在,则将其长度截为0(即清空文件)
         * 0222 和 0444 分别表示文件只写和只读(前面的0是八进制标识)
         */
        fi = open(filename, O_CREAT| O_TRUNC| O_WRONLY, 0222);    /* 以只写方式打开文件给生产者写入产品编号 */
        fo = open(filename, O_TRUNC| O_RDONLY, 0444);            /* 以只读方式打开文件给消费者读出产品编号 */
    
        metux = sem_open("METUX", 1);    /* 互斥信号量,防止生产消费同时进行 */
        full = sem_open("FULL", 0);        /* 产品剩余信号量,大于0则可消费 */
        empty = sem_open("EMPTY", BUFFER_SIZE);    /* 空信号量,它与产品剩余信号量此消彼长,大于0时生产者才能继续生产 */
    
        item_pro = 0;
    
        if ((pid = fork()))    /* 父进程用来执行消费者动作 */
        {
            printf("pid %d:	producer created....
    ", pid);
            /* printf()输出的信息会先保存到输出缓冲区,并没有马上输出到标准输出(通常为终端控制台)。
             * 为避免偶然因素的影响,我们每次printf()都调用一下stdio.h中的fflush(stdout)
             * 来确保将输出立刻输出到标准输出。
             */
            fflush(stdout);
    
            while (item_pro <= NR_ITEMS)    /* 生产完所需产品 */
            {
                sem_wait(empty);
                sem_wait(metux);
    
                /* 生产完一轮产品(文件缓冲区只能容纳BUFFER_SIZE个产品编号)后
                 * 将缓冲文件的位置指针重新定位到文件首部。
                 */
                if(!(item_pro % BUFFER_SIZE))
                    lseek(fi, 0, 0);
    
                write(fi, (char *) &item_pro, sizeof(item_pro));        /* 写入产品编号 */
                printf("pid %d:	produces item %d
    ", pid, item_pro);
                fflush(stdout);
                item_pro++;
    
                sem_post(full);        /* 唤醒消费者进程 */
                sem_post(metux);
            }
        }
        else    /* 子进程来创建消费者 */
        {
            i = NR_CONSUMERS;
            while(i--)
            {
                if(!(pid=fork()))    /* 创建i个消费者进程 */
                {
                    pid = getpid();
                    printf("pid %d:	consumer %d created....
    ", pid, NR_CONSUMERS-i);
                    fflush(stdout);
    
                    while(1)
                    {
                        sem_wait(full);
                        sem_wait(metux);
    
                        /* read()读到文件末尾时返回0,将文件的位置指针重新定位到文件首部 */
                        if(!read(fo, (char *)&item_used, sizeof(item_used)))
                        {
                            lseek(fo, 0, 0);
                            read(fo, (char *)&item_used, sizeof(item_used));
                        }
    
                        printf("pid %d:	consumer %d consumes item %d
    ", pid, NR_CONSUMERS-i+1, item_used);
                        fflush(stdout);
    
                        sem_post(empty);    /* 唤醒生产者进程 */
                        sem_post(metux);
    
                        if(item_used == NR_ITEMS)    /* 如果已经消费完最后一个商品,则结束 */
                            goto OK;
                    }
                }
            }
        }
    OK:
        close(fi);
        close(fo);
        return 0;
    }
    

    将此文件移动到usr/root目录下,此目录需要挂载hdc才可以访问。移动成功之后直接重新编译linux并在虚拟环境内运行。

    2.编译运行pc.c

    运行linux-0.11之后,首先编译pc.c,使用命令gcc -o pc pc.c,随后运行pc,使用命令./pc > sem_output即可,最终在虚拟环境内输入sync把修改的数据写入磁盘。

    3.查看sem_output

    首先挂载hdc,然后进入usr/root目录并在终端内执行sudo less sem_output命令,可看到下图结果:

    4.对比有无信号量

    删除pc.c文件中关于信号量的代码,重新编译运行后得出如下结果

    回答问题

    在有无信号量的不同条件下对比运行结果后可以发现,如果去掉所有与信号量有关的代码,编译运行程序之后可以发现输出的数字顺序完全混乱。
    信号量不存在的情况下,进程之间无法同步或者协作,造成此种情况的有如下原因:

    • 一种情况是缓冲区满了,生产者还在写入数据,会造覆盖掉部分数据。
    • 一种是缓冲区为空,消费者尝试读取数据,读到的数据是已输出的数据。
    • 多个进程对文件缓冲区同时访问,造成了程序崩溃。
  • 相关阅读:
    nodejs 教程
    文摘
    TED字幕摘抄
    Gamma函数相关matlab代码
    js以excel为模板的打印
    ASP.NET动态生成GridView的使用
    IE报错:缺少标识符、字符串或数字
    ExtJS Ext.Ajax.request最好设为同步
    javascript onclick 函数不执行
    Ext.grid.GridPanel数据转json
  • 原文地址:https://www.cnblogs.com/mirage-mc/p/12913993.html
Copyright © 2011-2022 走看看