• HIT OS2020 Spring Lab4


    实验4 信号量的实现与应用

    4.1 实验目的

    • 加深对进程同步与互斥概念的认识;
    • 掌握信号量的使用,并应用它解决生产者——消费者问题;
    • 掌握信号量的实现原理。

    4.2 实验内容

    本次实验的基本内容是:

    • 在 Ubuntu 下编写程序,用信号量解决生产者——消费者问题;
    • 在 0.11 中实现信号量,用生产者—消费者程序检验之。
    • 用信号量解决生产者—消费者问题
      在 Ubuntu 上编写应用程序 pc.c ,解决经典的生产者—消费者问题,完成下面的功能:
    • 建立一个生产者进程, N 个消费者进程( N>1 );
    • 用文件建立一个共享缓冲区;
    • 生产者进程依次向缓冲区写入整数 0,1,2,...,M, M>=500 ;
    • 消费者进程从缓冲区读数,每次读一个,并将读出的数字从缓冲区删除,然后将本进程 ID 和数字输出到标准输出;
    • 缓冲区同时最多只能保存 10 个数。
      一种可能的输出效果是:
    10: 0
    10: 1
    10: 2
    10: 3
    10: 4
    11: 5
    11: 6
    12: 7
    10: 8
    12: 9
    12: 10
    12: 11
    12: 12
    ……
    11: 498
    11: 499
    

    其中 ID 的顺序会有较大变化,但冒号后的数字一定是从 0 开始递增加一的。
    pc.c 中将会用到 sem_open() 、 sem_close() 、 sem_wait() 和 sem_post() 等信号量相关的系统调用,请查阅相关文档。
    《UNIX环境高级编程》是一本关于 Unix/Linux 系统级编程的相当经典的教程。 电子版可在网站上下载,后续实验也用得到。如果你对 POSIX 编程感兴趣,建议买一本常备手边。

    4.2.1 实现信号量

    Linux 在 0.11 版还没有实现信号量,Linus 把这件富有挑战的工作留给了你。 如果能实现一套山寨版的完全符合 POSIX 规范的信号量,无疑是很有成就感的。但时间暂时不允许我们这么做,所以先弄一套缩水版的类 POSIX 信号量,它的函数原型和标准并不完全相同,而且只包含如下系统调用:

    sem_t *sem_open(const char *name, unsigned int value);
    int sem_wait(sem_t *sem);
    int sem_post(sem_t *sem);
    int sem_unlink(const char *name);
    

    sem_t是信号量类型,根据实现的需要自定义。
    sem_open()的功能是创建一个信号量,或打开一个已经存在的信号量。
    name是信号量的名字。不同的进程可以通过提供同样的name而共享同一个信号量。如果该信号量不存在,就创建新的名为name的信号量;如果存在,就打开已经存在的名为name的信号量。value是信号量的初值,仅当新建信号量时,此参数才有效,其余情况下它被忽略。当成功时,返回值是该信号量的唯一标识(比如,在内核的地址、ID等),由另两个系统调用使用。如失败,返回值是NULLsem_wait()就是信号量的P原子操作。如果继续运行的条件不满足,则令调用进程等待在信号量sem上。返回0表示成功,返回-1表示失败。
    sem_post()就是信号量的V原子操作。如果有等待sem的进程,它会唤醒其中的一个。返回0表示成功,返回-1表示失败。
    sem_unlink()的功能是删除名为name的信号量。返回0表示成功,返回-1表示失败。
    kernel目录下新建sem.c文件实现如上功能。然后将pc.c从Ubuntu移植到0.11下,测试自己实现的信号量。

    4.3 实验问题

    4.3.1 在 pc.c 中去掉所有与信号量有关的代码,再运行程序,执行效果有变化吗?为什么会这样?

    • 会有变化
    • 可能造成如下的情况
      - 乱序输出:可能缓冲区已经满了,但是生产者仍然在持续写入其他数据,导致之前的数据被覆盖,从而导致无序输出;亦或者可能缓冲区已经为空,但是此时消费者仍然在读取数据,此时也会导致乱序输出,
      - 程序崩溃:可能出现多个进程因为没有MUTEX信号而出现越界访问代码的情况,这样会使得程序崩溃。

    4.3.2 实验的设计者在第一次编写生产者——消费者程序的时候,是这么做的,这样可行吗?如果可行,那么它和标准解法在执行效果上会有什么不同?如果不可行,那么它有什么问题使它不可行?

    Producer()
    {
        P(Mutex);  //互斥信号量
        // 生产一个产品item;
        P(Empty);  //空闲缓存资源
        // 将item放到空闲缓存中;
        V(Full);  //产品资源
        V(Mutex);
    }
    
    Consumer()
    {
        P(Mutex);
        P(Full);
        // 从缓存区取出一个赋值给item;
        V(Empty);
        // 消费产品item;
        V(Mutex);
    }
    
    • 不可以,这样会造成死锁的情况出现。当Empty信号为0,而Mutex为1的时候,各个进程就会相互等待,从而进入死锁状态。

    4.4 实验过程

    4.4.1 整体说明

    在实验过程中,我们实现的多个函数都要以系统调用的形式在Linux0.11中进行使用,因此,需要根据实验2的内容对makefile、unistd.h、system_call.s等文件进行修改,修改的过程不在此赘述,可以参考另一篇讲实验2的博客。

    4.4.2 sem_open()函数

    sem_t *sys_sem_open(const char *name,unsigned int value) //sem_t的定义会在后边给出,name是信号量的名称,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]);
        }
        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]);
            cnt++;//并且将这个信号放入信号表中
         }
        return p;
    }
    

    这个函数实现的是打开信号量的操作。

    4.4.3 sem_wait()函数

    int sys_sem_wait(sem_t *sem)
    {
        cli();//关中断
        while(sem->value <= 0) //进程等待直到信号量的值大于0
        {        
            sleep_on(&(sem->queue));   
        }
        sem->value--;
        sti();//开启中断
        return 0;
    }
    

    这个函数实现了进程的等待过程。

    4.4.4 sem_post()函数

    int sys_sem_post(sem_t *sem)
    {
        cli();
        sem->value++;
        if((sem->value) <= 1)//唤醒在信号量上等待的进程
        {
            wake_up(&(sem->queue));
        }    
        sti();
        return 0;
    }
    

    这个函数用于唤醒对应的进程。

    4.4.5 sem_wait()函数

    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;
    }
    

    这个函数用于删除名为name的信号量。

    4.4.6 sem.h

    这个文件定义了sem_t这个数据类型。

    #ifndef _SEM_H
    #define _SEM_H
    
    #include <linux/sched.h>
    
    #define SEM_NAME_LEN 50
    #define SEMTABLE_LEN 20
    typedef struct sem_t{
        char name[SEM_NAME_LEN];//信号量名称
        unsigned int value;//初值
        struct task_struct *queue;//等待信号量的进程指针
    }sem_t;
    extern sem_t semtable[SEMTABLE_LEN];
    sem_t *sem_open(const char name, unsigned int value);
    int sem_wait(sem_t *sem);
    int sem_post(sem_t *sem);
    int sem_unlink(const char *name);
    
    #endif
    

    4.4.7 pc.c

    #define __LIBRARY__
    #include <unistd.h>
    #include <stdio.h>
    #include <linux/sem.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.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)
    
    #define NUMBUFFER 550 /*打出数字的总数*/
    #define NUMPRO 5 /*消费者进程个数*/
    #define MAXSIZE 10 /*缓冲区大小*/
    
    sem_t *empty, *full, *mutex;
    
    int main()
    {
        int i, j, k;
        FILE *fp = NULL;
        int buf_out = 0;/*从缓冲区读取的位置*/
        int buf_in = 0;/*写入缓冲区的位置*/
        empty = (sem_t *)sem_open("EMPTY", MAXSIZE);/*打开对应的信号量*/
        full = (sem_t *)sem_open("FULL", 0);
        mutex = (sem_t *)sem_open("MUTEX", 1);
        fp = fopen("/var/buffer.dat", "wb+"); /*存在文件中。*/
        fseek(fp, MAXSIZE * sizeof(int), SEEK_SET);
        fwrite(&buf_out, 1, sizeof(buf_out), fp);
        fflush(fp);
        if(!fork())
        {
            for(i = 0;i < NUMBUFFER;i ++)
            {
                sem_wait(empty);
                sem_wait(mutex);
                fseek(fp, buf_in * sizeof(int), SEEK_SET);
                fwrite(&i, 1, sizeof(i), fp);
                fflush(fp);
                buf_in = (buf_in + 1) % MAXSIZE;
                sem_post(mutex);
                sem_post(full);
            }
            return 0;
        }
    
        for(i = 0;i < NUMPRO; i++)
        {
            if(!fork())
            {
                for(j = 0;j < NUMBUFFER / NUMPRO; j++)
                {
                    int cost;
                    sem_wait(full);
                    sem_wait(mutex);
                    fflush(stdout);
                    fseek(fp, MAXSIZE * sizeof(int), SEEK_SET);
                    fread(&buf_out, sizeof(int), 1, fp);
                    fseek(fp, buf_out * sizeof(int), SEEK_SET);
                    fread(&cost, sizeof(int), 1, fp);
                    printf("%d:	%d
    ", getpid(), cost);
                    fflush(stdout);
                    buf_out = (buf_out + 1) % MAXSIZE;
                    fseek(fp, MAXSIZE * sizeof(int), SEEK_SET);
                    fwrite(&buf_out, 1, sizeof(buf_out),fp);
                    fflush(fp);
                    sem_post(mutex);
                    sem_post(empty);
                }
                return 0;
            }
        }
    
        wait(NULL);
        sem_unlink("EMPTY");
        sem_unlink("FULL");
        sem_unlink("MUTEX");
        fclose(fp);
        return 0;
    }
    

    4.4.8 实验结果

    ....
    17:	543
    17:	544
    17:	545
    17:	546
    17:	547
    17:	548
    17:	549
    

    最终可以输出550个数字。

  • 相关阅读:
    剑指offer 数值的整数次方
    剑指offer 链表中倒数第K个节点
    剑指offer反转链表
    剑指offer 栈的压入弹出 顺序
    剑指offer 二叉搜索树后续遍历序列 判断
    剑指offer 复杂链表的复制 (有向图的复制)
    剑指offer 二叉搜索树与双向链表
    2018/03/15 常用的超全局变量
    2018/03/14 每日一个Linux命令 之 ln
    PHP使用 zip 扩展压缩文件
  • 原文地址:https://www.cnblogs.com/winston8086/p/13258591.html
Copyright © 2020-2023  润新知