操作系统原理与实践7-信号量的实现和应用

操作系统原理与实践”实验报告

信号量的实现和应用

信号量的实现和应用

实验目的

.加深对进程同步与互斥概念的认识;

.掌握信号量的使用,并应用它解决生产者——消费者问题;

.掌握信号量的实现原理。

实验内容

本次实验的基本内容是:

  • 1.在Ubuntu下编写程序,用信号量解决生产者——消费者问题;
  • 2.在0.11中实现信号量,用生产者—消费者程序检验之。
  • 3.用信号量解决生产者—消费者问题

在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
……
11: 498
11: 499

其中ID的顺序会有较大变化,但冒号后的数字一定是从0开始递增加一的。

实现信号量

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是信号量类型,根据实现的需要自定义。

实验报告

一、实验步骤

1、ubuntu下的实验程序

程序中使用了系统定义了的以下几个信号量的函数:

sem_open,sem_wait,sem_post,sem_getvalue,sem_unlink。

实验过程中遇到的难点主要是关于文件做缓冲区的问题,由于在程序中使用了同一个文件来供生产者与消费者共用,这样就有一个如何控制缓冲区的问题,即,如何能保证消费者按顺序取出生产者放在缓冲区中的数据?如何能控制生产者只使用10个位置而不导致文件增大?开始时试图使用一个静态变量来解决,证明不可行。又试图使用单独一个信号量来控制,发现也不可行。最终采用的方案是在消费者取出一个数据后即将缓冲区中的后继数据前移,并减小文件大小(即减小缓冲区长度),而生产者每次都将生产的数据放在文件尾处(即缓冲区的尾部)。这样就可以实现消费者从头部取数据,生产者从尾部放数据,从而实现缓冲区不增,按顺序提取。当然这里的缓冲区大小仅有10个,所以,移动缓冲区中的数据,并不会造成太多的性能损失,如果缓冲区很大,那么可能会影响程序的性能。本人也试图去查看系统中关于缓冲区的控制方法,但由于时间关系,没有对相关的代码进行研究,相信如果了解了系统中关于缓冲区的控制方法会有所帮助的。当然,这里减小文件大小使用了系统提供的ftruncate函数。如果系统没有该函数,则必须自己提供。

此外,实验程序如果让生产者提前退出,则会发生消费者进程异常,且还会多产生一个消费者进程,不知是何原因,未能解决,所以此次实验所有子进程均未结束。想大约可以通过加一个信号量来标识是否要退出来解决子进程退出问题,但由于时间关系未能解决,有时间会继续解决该问题。

实验程序代码:

#include    <semaphore.h> 
#include    <stdio.h> 
#include    <stdlib.h> 
#include    <unistd.h> 
#include    <linux/kernel.h> 
#include    <fcntl.h> 
#include    <linux/types.h> 
#include    <sys/stat.h> 
#define BUFFERSIZE    10 

sem_t *empty; // = sem_open("empty",O_CREAT | O_EXCL,0644,BUFFERSIZE); 
sem_t *full; // = sem_open("full",O_CREAT | O_EXCL,0644,0); 
sem_t *mutex; // = sem_open("mutex",O_CREAT | O_EXCL,0644,1); 

int main(void) 
{ 
    char err_desc[255]; 
    char empty_sem[]= "empty"; 
    char full_sem[]= "full"; 
    char mutex_sem[]="mutex"; 
    int in = open("pc.log", O_CREAT|O_RDWR, 0666); 
    int of = open("pc.log",O_CREAT|O_RDWR,0666); 
    int itemValue = -1; 
    int fileLen,tmpValue,i,j; 
    //文件大小设置为0; 
    ftruncate(in,0); 
    empty = sem_open(empty_sem,O_CREAT,0644,BUFFERSIZE); 
    if(empty == SEM_FAILED) 
    { 
        printf("create semaphore empty error!\n"); 
        exit(1); 
    } 
    full = sem_open(full_sem,O_CREAT,0644,0); 
    if(full == SEM_FAILED) 
    { 
        printf("create semaphore full error!\n"); 
        exit(1); 
    } 

    mutex = sem_open(mutex_sem,O_CREAT,0644,1); 
    if(mutex == SEM_FAILED) 
    { 
        printf("create semaphore mutex error!\n"); 
        exit(1); 
    } 

    /* 测试用来查看定义是否真的成功
    sem_getvalue(empty,&tmpValue); 
    printf("now empty's value = %d\n",tmpValue); 
    sem_getvalue(mutex,&tmpValue); 
    printf("now mutex's value = %d\n",tmpValue); 
    sem_getvalue(full,&tmpValue); 
    printf("now full's value = %d\n",tmpValue); 
    */     
    if(!fork()) 
    { 
        //printf("producer process %u !now itemValue=%d\n",getpid(),itemValue); 
        while(itemValue<500) //根据实验要求,这里是499就不输出500
        { 
            itemValue++; 
            //printf("now produce %d\n",itemValue); 
            if(sem_wait(empty)!=0) 
            { 
                printf("in producer sem_wait(empty) error!\n"); 
                perror(err_desc); 
                break; 
            } 
            //producer 
            if(sem_wait(mutex)!=0) 
            { 
                printf("in producer sem_wait(mutex) error!\n"); 
                perror(err_desc); 
                //printf("error msg : %s\n",err_desc); 
                break; 
            } 
            //将数值写到文件尾部 
            lseek(in,0,SEEK_END); 
            write(in,&itemValue,sizeof(itemValue)); 
            if(sem_post(mutex)!=0) 
            { 
                printf("in producer sem_post(mutex) error!\n"); 
                perror(err_desc); 
                break; 
            } 
            //发送full信号 
            if(sem_post(full)!=0) 
            { 
                printf("in producer sem_post(full) error!\n"); 
                perror(err_desc); 
                break; 
            } 

        } //producer process 

        //如果此处退出生产者,则会导致消费者也异外退出且会多生成一个消费者原因不明。
        while(1) 
            ; 
        //close(in); 
    } 

    for(i=0; i < 5; i++) 
    { 
        if(!fork()) 
        { 
            //printf("customer process(%u) begin to run!\n",getpid()); 
            while(1) 
            { 
                if(sem_wait(full)!=0) 
                { 
                    printf("in customer %u sem_wait(full) error!\n",getpid()); 
                    perror(err_desc); 
                    break; 
                } 

                if(sem_wait(mutex)!=0) 
                { 
                    printf("in customer %u,sem_post(empty) error!",getpid()); 
                    perror(err_desc); 
                    break; 
                } 
                //读取第一个数值并显示 
                lseek(of,0,SEEK_SET); 
                read(of,&itemValue,sizeof(itemValue)); 
                printf("%u:%d\n",getpid(),itemValue); 
                //将其余数值前移,截断文件4个字节 
                fileLen=lseek(in,0,SEEK_END); 
                for(j=1;j<(fileLen/sizeof(itemValue));j++) 
                { 
                    lseek(in,j*sizeof(itemValue),SEEK_SET); 
                    read(in,&tmpValue,sizeof(tmpValue)); 
                    lseek(in,(j-1)*sizeof(itemValue),SEEK_SET); 
                    write(in,&tmpValue,sizeof(tmpValue)); 
                } 
                ftruncate(in,fileLen-sizeof(itemValue)); 

                if(sem_post(mutex)!=0) 
                { 
                    printf("in customer %u,sem_post(empty) error!\n",getpid()); 
                    perror(err_desc); 
                    break; 
                } 
                //发送empty 
                if(sem_post(empty)!=0) 
                { 
                    //此时是否表明已经生产结束? 
                    printf("in customer %u,sem_post(empty) error!\n",getpid()); 
                    perror(err_desc); 
                } 

            } 
            close(of); 
        } 
    } 

    //printf("now, main process exit!\n"); 
    //return 
    return 0; 
}

2、linux0.11下信号量的实现

这个实验中也要实现系统调用,基本上可以说是第二个实验的复习,所以在这里也再次将系统调用的添加回顾一下。涉及的系统文件有以下几个:

  • /inlude/linux/sys.h //添加系统调用表以及系统调用内核函数定义
  • /kernel/systemcall.s //系统调用的宏定义
  • /include/unistd.h //定义系统调用函数的对外接口以及系统调用号的宏定义,此次将sem_t的结构直接放在此文件中,没有定义semaphore.h文件。
  • /kernel/Makefile //编译内核时将新系统调用文件添加进去
  • /kernel/semaphore.c //实现信号量的函数文件。
  • /kernel/include/fcntl.h //定义#define F_CHSIZE 8 /melon added 2015-6-24/
  • /kernel/fs/fcntl.c //修改fcntl函数,增加修改文件长度的实现

此次使用的信号量的结构定义:

#define NR_SEMAPHORE    64     //为简单实现,系统共可以使用的信号量为64个
#define NR_SEMANAME    255     //信号量的名字长度
typedef struct semaphore 
{ 
    char sem_name[NR_SEMANAME]; 
    int value; 
    struct task_struct * semp; 
}sem_t;                //这里为和ubuntu保持一致,使用了sem_t这个别名。

由于linux0.11中并没有实现ftruncate函数来修改文件长度,所以必须添加一个关于ftruncate的实现,由于仅在验证程序中使用,所以仅修改了fcntl函数,增加修改文件长度的代码。其使用方法为:

fcntl(fd,F_CHSIZE,n),其中,fd为打开的文件号,n为文件长度。实现代码如下:


int sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) 
{     
    struct file * filp; 

    if (fd >= NR_OPEN || !(filp = current->filp[fd])) 
        return -EBADF; 
    switch (cmd) { 
        case F_DUPFD: 
            return dupfd(fd,arg); 
        case F_GETFD: 
            return (current->close_on_exec>>fd)&1; 
        case F_SETFD: 
            if (arg&1) 
                current->close_on_exec |= (1<<fd); 
            else 
                current->close_on_exec &= ~(1<<fd); 
            return 0; 
        case F_GETFL: 
            return filp->f_flags; 
        case F_SETFL: 
            filp->f_flags &= ~(O_APPEND | O_NONBLOCK); 
            filp->f_flags |= arg & (O_APPEND | O_NONBLOCK); 
            return 0; 
        case F_GETLK:    case F_SETLK:    case F_SETLKW: 
            return -1; 
        case F_CHSIZE:  //新增的修改文件长度代码
            current->filp[fd]->f_inode->i_size=arg; 
            return 0; 
        default: 
            return -1; 
    } 
}

Semaphore.c文件代码:


#define __LIBRARY__ 
#include <unistd.h> 
#include <stdarg.h> 
#include <errno.h> 
#include <asm/segment.h> 
#include <asm/system.h> 
#include <string.h> 
#include <linux/kernel.h> 
#include <linux/sched.h> 

static sem_t semaphore_list[NR_SEMAPHORE]={{"",0,NULL}};  //全局的信号量数组初始化

/*
以下这两个函数,后来被弃用,发现如果使用,则调度的顺序会与使用系统sleep_on、wake_up函数时大不相同,而且有时会导致某个消费者不能被唤醒。由于实验程序中使用分支来判断是否睡眠,本人分析可能会导致一个唤醒信号会同时唤醒多个消费者,而多个消费者中可能只有一个能正确取到数据,其他消费者在此一轮中均无法取得数据,但程序并未发生异常错误。所以此次实验没有进一步研究关于唤醒问题。
*/
void sys_sem_sleep(struct task_struct **p) 
{ 
    struct task_struct *tmp; 

    if (!p) 
        return; 

    tmp = *p; 
    *p = current; 

    current->state = TASK_UNINTERRUPTIBLE; 

    schedule(); 
    /*被唤醒后应该到这里,所以把前一个写回去,此处是错误的,因为不知哪一个进程会在此前修改指针,所以此处不应该写回去。 */
    *p=tmp; 
} 

void sys_sem_wakeup(struct task_struct **p) 
{ 
    if (p && *p) { 
        (**p).state=0; 
        *p=NULL; 
    } 
} 
/*
以下为信号量的实现函数,为方便调试,比试验指导上多加一个sem_getvalue函数,这样在必要时可以取出信号量的值查看是否正确。
*/
/*新建一个信号量*/ 
sem_t * sys_sem_open(const char * semname, int value) 
{ 
    int i,cursem=-1; 
    char curname[NR_SEMANAME]; 
    //int name_len=0; 
    char* p=(char *)semname; 
    char c; 
    //取得名字 
    for(i=0;i<NR_SEMANAME;i++) 
    { 
        c=get_fs_byte(p++); 
        if(c=='\0') 
            break; 
        else 
            curname[i]=c;//sema[cursem].sem_name[i]=c; 
    } 
    curname[i]='\0'; 

    //查看是否已经过定义,如果有,则直接返回已定义的,而且忽略值 
    for(i=0; i<NR_SEMAPHORE; i++) 
    { 
        /*if(semaphore_list[i].sem_name[0] == '\0') 
        { 
            cursem=i; 
            break; 
        } 
        else*/ 

        //printk("return semaphore, id is %d,the name is %s,the value is %d\n",i,semaphore_list[i].sem_name,semaphore_list[i].value); 
        if(strcmp(curname,semaphore_list[i].sem_name)==0) 
        { 
            printk("return semaphore, id is %d,the name is %s,the value is %d\n",i,semaphore_list[i].sem_name,semaphore_list[i].value); 
            return &semaphore_list[i]; 
        } 

    } 
    //取出未用的空位
    for(i=0; i<NR_SEMAPHORE; i++) 
    { 
        if(semaphore_list[i].sem_name[0] == '\0') 
        { 
            cursem=i; 
            break; 
        } 
    } 

    if(cursem==-1) 
    { 
        printk("now,no blank list,cursem=%d\n",cursem); 
        return NULL; 
    } 
    i=0; 
    for(;curname[i]!='\0';i++) 
    { 
        semaphore_list[cursem].sem_name[i]=curname[i]; 
    } 
    semaphore_list[cursem].sem_name[i]='\0'; 
    semaphore_list[cursem].value=value; 
    semaphore_list[cursem].semp=NULL; 

    //printk("now semaphore_list[%d] is created! sem's value =%d\n",cursem,semaphore_list[cursem].value); 

    // 
    return &semaphore_list[cursem]; 
} 

/**/ 
int sys_sem_wait(sem_t * sem) 
{ 
    cli(); 
    sem->value--; 
    //printk("semaphore [%s] is wait! value =%d\n",sem->sem_name,sem->value); 
    //printk("process [%u] wait !pointor is %u,semp is %u\n",current->pid,current,sem->semp); 
    if(sem->value<0) 
    { 
        //printk("process [%u] sleep!pointor is %u,semp is %u\n",current->pid,current,sem->semp); 
        //sem->semp=current; 
        sleep_on(&(sem->semp)); 
        //sys_sem_sleep(&(sem->semp)); 
        //schedule(); 
    } 
    sti(); 
    return 0; 
} 

/**/ 
int sys_sem_post(sem_t * sem) 
{ 
    cli(); 
    sem->value++; 
    //printk("semaphore [%s] is post! value =%d\n",sem->sem_name,sem->value); 
    //printk("process [%u] post!pointor is %u,semp is %u\n",current->pid,current,sem->semp); 
    if(sem->value<=0) 
    { 
        //wake_up 
        //printk("process [%u] wakeup!current pointor is %u,semp is %u\n",current->pid,current,sem->semp); 
        wake_up(&(sem->semp)); 
        //sys_sem_wakeup(&(sem->semp)); 
    } 
    sti(); 
    return 0; 
} 

int sys_sem_getvalue(sem_t * sem) 
{ 
    // 
    if(sem != NULL) 
        return sem->value; 
    else 
        return -1; 
} 

/*删除一个信号量*/ 
int sys_sem_unlink(const char * name) 
{ 
    int i;//,cursem=-1; 
    char curname[NR_SEMANAME]; 
    //int name_len=0; 
    char* p=(char*)name; 
    char c; 
    //取得名字 
    for(i=0;i<NR_SEMANAME;i++) 
    { 
        c=get_fs_byte(p++); 
        if(c=='\0') 
            break; 
        else 
            curname[i]=c;//sema[cursem].sem_name[i]=c; 
    } 
    curname[i]='\0'; 
    //判断是否有同名的存在 
    for(i=0; i<NR_SEMAPHORE; i++) 
    { 
        if(strcmp(curname,semaphore_list[i].sem_name)==0) 
        { 
            semaphore_list[i].sem_name[0]='\0'; 
            semaphore_list[i].value=0; 
            semaphore_list[i].semp=NULL; 
            return 0; 
        } 
    } 
    // 
    return -1; 
}

linux0.11中使用的验证程序:

#define __LIBRARY__ 
#include    <stdio.h> 
#include    <stdlib.h> 
#include    <unistd.h> 
#include    <linux/kernel.h> 
#include    <fcntl.h> 
#include    <sys/types.h> 
#define BUFFERSIZE    10 

static _syscall2(sem_t *,sem_open,const char *,name,int,value); 
static _syscall1(int,sem_post,sem_t *,sem); 
static _syscall1(int,sem_wait,sem_t *,sem); 
static _syscall1(int,sem_getvalue,sem_t *,sem); 
static _syscall1(int,sem_unlink,const char*,name); 

sem_t *empty; 
sem_t *full; 
sem_t *mutex; 

int main(void) 
{ 

    char err_desc[255]; 
    char empty_sem[]= "empty"; 
    char full_sem[]= "full"; 
    char mutex_sem[]="mutex"; 

    int in = open("pc.log", O_CREAT|O_TRUNC|O_RDWR, 0666); 
    int of = open("pc.log", O_CREAT|O_TRUNC|O_RDWR, 0666); 
    int log = open("pclog.log", O_CREAT|O_TRUNC|O_RDWR, 0666); 
    char buflog[255];  //用来格式化输出到文件中的字符串缓冲区 
    int tmp; 

    int itemValue = -1; 
    int fileLen,tmpValue,i,j; 
    //新添加的修改文件长度的函数调用
    if(fcntl(in,F_CHSIZE,0)!=0) 
    { 
        printf("in main process. ftruncate error!\n"); 
        close(in); 
        close(of); 
        return 0; 
    } 
    empty = sem_open(empty_sem,BUFFERSIZE); 

    if(empty == NULL) 
    { 
        printf("create semaphore empty error!\n"); 
        exit(1); 
    }     

    full = sem_open(full_sem,0); 
    if(full ==NULL) 
    { 
        printf("create semaphore full error!\n"); 
        exit(1); 
    } 

    mutex = sem_open(mutex_sem,1); 
    if(mutex == NULL) 
    { 
        printf("create semaphore mutex error!\n"); 
        exit(1); 
    } 

    tmpValue=sem_getvalue(empty); 
    printf("now empty's value = %d\n",tmpValue); 

    tmpValue=sem_getvalue(mutex); 
    printf("now mutex's value = %d\n",tmpValue); 

    tmpValue=sem_getvalue(full); 
    printf("now full's value = %d\n",tmpValue); 

    if(!fork()) 
    { 
        printf("producer process %u !now itemValue=%d\n",getpid(),itemValue); 

        while(itemValue<50) 
        { 
            itemValue++; 

            if(sem_wait(empty)!=0) 
            { 
                printf("in producer sem_wait(empty) error!\n"); 
                perror(err_desc); 
                break; 
            } 


            if(sem_wait(mutex)!=0) 
            { 
                printf("in producer sem_wait(mutex) error!\n"); 
                perror(err_desc); 

                break; 
            } 

            fileLen=lseek(in,0,SEEK_END); 
            write(in,&itemValue,sizeof(itemValue)); 

            if(sem_post(mutex)!=0) 
            { 
                printf("in producer sem_post(mutex) error!\n"); 
                perror(err_desc); 
                break; 
            } 

            if(sem_post(full)!=0) 
            { 
                printf("in producer sem_post(full) error!\n"); 
                perror(err_desc); 
                break; 
            } 

        } 
        /*
        tmpValue=sem_getvalue(empty); 
        printf("now empty's value = %d\n",tmpValue); 

        tmpValue=sem_getvalue(mutex); 
        printf("now mutex's value = %d\n",tmpValue); 

        tmpValue=sem_getvalue(full); 
        printf("now full's value = %d\n",tmpValue); 
        */
        while(1) 
            ; 

        close(in); 
    } 

    for(i=0; i < 5; i++) 
    { 
        if(!fork()) 
        { 
            printf("customer process(%u) begin to run!\n",getpid()); 

            while(1) 
            { 

                if(sem_wait(full)!=0) 
                { 
                    printf("in customer %u sem_wait(full) error!\n",getpid()); 
                    perror(err_desc); 
                    break; 
                } 

                if(sem_wait(mutex)!=0) 
                { 
                    printf("in customer %u,sem_post(empty) error!",getpid()); 
                    perror(err_desc); 
                    break; 
                } 

                lseek(of,0,SEEK_SET); 
                read(of,&itemValue,sizeof(itemValue)); 
                /*printf("%u:%d\n",getpid(),itemValue);*/ 
                /*
                由于虚拟机中的linux0.11在输出时会出现乱码,而修改输出到文件中时会发生无法正确输出实验结果,因此此处修改为实验结果直接输出到一个记录文件中。
                */
                lseek(log,0,SEEK_END); 
                sprintf(buflog,"%u:%d\n",getpid(),itemValue); 
                write(log,&buflog,sizeof(char)*strlen(buflog)); 

                //修改缓冲区长度
                fileLen=lseek(in,0,SEEK_END); 
                for(j=1;j<(fileLen/sizeof(itemValue));j++) 
                { 
                    lseek(in,j*sizeof(itemValue),SEEK_SET); 
                    read(in,&tmpValue,sizeof(tmpValue)); 
                    lseek(in,(j-1)*sizeof(itemValue),SEEK_SET); 
                    write(in,&tmpValue,sizeof(tmpValue)); 
                } 

                if(fcntl(in,F_CHSIZE,fileLen-sizeof(tmpValue))!=0) 
                { 
                    printf("ftruncate error!\n"); 
                    break; 
                } 

                if(sem_post(mutex)!=0) 
                { 
                    printf("in customer %u,sem_post(empty) error!\n",getpid()); 
                    perror(err_desc); 
                    break; 
                } 

                if(sem_post(empty)!=0) 
                { 

                    printf("in customer %u,sem_post(empty) error!\n",getpid()); 
                    perror(err_desc); 

                } 
            } 

            close(of); 
        } 
    } 

    printf("now, main process exit!\n"); 

    return 0; 
}

二、实验总结

使用信号量来同步进程必须要考虑清楚退出机制问题,也就是信号量的释放时机如何选择以及如何安排,否则会造成信号量的占用无法释放。由于时间问题(本人水平有限,仅实现信号量就花费了一周多的业余时间,遗留问题在以后会继续研究解决)此问题在本次实验中并未解决。

ubuntu系统下的信号量是可以在线程中共享使用的,默认也可以用于进程同步。所以以此看来信号量就是内核的一个全局数据,如何使用取决于程序设计者。但全局数据也是全局资源,必然是有限的,所以使用信号量时应该慎重考虑周全。

另:一个有趣的问题是,由于实验程序未能正确结束,所以得到实验结果后6个子进程均在后台运行,且生产者进程状态为0,其余消费者进程均为2。但经实验,在ubuntu15.04(64位)下,6个子进程均能得到结束,也就是说在15.04版本下得出实验结果后6个子进程均被系统收回了。但在14.04与12.04下均不做收回。不知是不是15.04对进程的收回机制做了变动。

三、实验问题

完成实验后,在实验报告中回答如下问题:

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

答:去掉进程同步结构,会造成输出混乱。因为无法保证按顺序取数据,写数据时机也不能保证,可能会出现竞争(即同时多进程对文件进行读写)而死锁。但本次实验未发生,大概是实验的次数不足够多。

实验的设计者在第一次编写生产者——消费者程序的时候,是这么做的:

Producer()
{
    P(Mutex);  //互斥信号量
    生产一个产品item;
    P(Empty);  //空闲缓存资源
    将item放到空闲缓存中;
    V(Full);  //产品资源
    V(Mutex);
}

Consumer()
{
    P(Mutex);  
    P(Full);  
    从缓存区取出一个赋值给item;
    V(Empty);
    消费产品item;
    V(Mutex);
}

这样可行吗?如果可行,那么它和标准解法在执行效果上会有什么不同?如果不可行,那么它有什么问题使它不可行?

这样做不可行,如果消费者在mutex上睡眠,当被唤醒时后运行后会再次修改mutex值,而无法保证mutex始终在0、1之间变换,这样就不能保证对临界资源的保护。

实验截图

图片描述

图片描述

图片描述

图片描述

最后补充 主动结束全部进程

在以上部分的验证程序中,所有子进程均无法正常结束,现通过增加一个退出信号量来主动结束全部子进程。在ubuntu14.04下验证通过。但未在linux0.11下验证,想来也应该是可以的,所以偷一次懒吧!!^_^


#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include    <linux/kernel.h>
#include    <fcntl.h>
#include    <linux/types.h>
#include    <sys/stat.h>
#define BUFFERSIZE    10

sem_t *empty; // = sem_open("empty",O_CREAT | O_EXCL,0644,BUFFERSIZE);
sem_t *full; // = sem_open("full",O_CREAT | O_EXCL,0644,0);
sem_t *mutex; // = sem_open("mutex",O_CREAT | O_EXCL,0644,1);
sem_t *closetime;

int main(void)
{

    char err_desc[255];
    char empty_sem[]= "empty";
    char full_sem[]= "full";
    char mutex_sem[]="mutex";
    char closetime_sem[]="closetime";

    int in = open("pc.log", O_CREAT|O_RDWR, 0666);
    int of = open("pc.log",O_CREAT|O_RDWR,0666);

    int itemValue = -1;
    int fileLen,tmpValue,i,j,closeValue,fullValue;

    //文件大小设置为0;
    ftruncate(in,0);    
    empty = sem_open(empty_sem,O_CREAT,0644,BUFFERSIZE);

    if(empty == SEM_FAILED)
    {
        printf("create semaphore empty error!\n");
        return 1;
    }


    full = sem_open(full_sem,O_CREAT,0644,0);
    if(full == SEM_FAILED)
    {
        printf("create semaphore full error!\n");
        return 1;
    }

    mutex = sem_open(mutex_sem,O_CREAT,0644,1);
    if(mutex == SEM_FAILED)
    {
        printf("create semaphore mutex error!\n");
        return 1;
    }

    closetime = sem_open("closetime",O_CREAT,0644,0);
    if(closetime == SEM_FAILED)
    {
        printf("create semaphore closetime error!\n");
        return 1;
    }

    /*
    sem_getvalue(empty,&tmpValue);
    printf("now empty's value = %d\n",tmpValue);
    sem_getvalue(mutex,&tmpValue);
    printf("now mutex's value = %d\n",tmpValue);
    sem_getvalue(full,&tmpValue);
    printf("now full's value = %d\n",tmpValue);
    sem_getvalue(closetime,&tmpValue);
    printf("now closetime's value = %d\n",tmpValue);
    */

    if(!fork())
    {
        //printf("producer process %u !now itemValue=%d\n",getpid(),itemValue);

        while(itemValue<499)
        {
            itemValue++;
            //printf("now produce %d\n",itemValue);
            if(sem_wait(empty)!=0)
            {
                printf("in producer sem_wait(empty) error!\n");
                perror(err_desc);
                break;
            }

            //producer
            if(sem_wait(mutex)!=0)
            {
                printf("in producer sem_wait(mutex) error!\n");
                perror(err_desc);
                break;
            }
            //将数值写到文件尾部
            lseek(in,0,SEEK_END);
            write(in,&itemValue,sizeof(itemValue));

            //发送full信号
            if(sem_post(full)!=0)
            {
                printf("in producer sem_post(full) error!\n");
                perror(err_desc);
                break;
            }
            if(sem_post(mutex)!=0)
            {
                printf("in producer sem_post(mutex) error!\n");
                perror(err_desc);
                break;
            }

        } //producer process

        //now the producer finish the work, so post the signal to close all process.
        sem_getvalue(closetime,&closeValue);
        if(closeValue==0)
        {
            for(i=0;i<5;i++)
            {
                if(sem_post(closetime)!=0)
                {
                    printf("in producer sem_post(closetime) error!\n");
                    perror(err_desc);
                    break;
                }
            }
        }

        //check the closetime's value 
        sem_getvalue(closetime,&closeValue);
        while(closeValue>0)
        {

            sem_getvalue(full,&fullValue);
            if(fullValue<=0) //if full's value lessthan zero, so post the full
            {
                sem_post(full);
            }
            sem_getvalue(closetime,&closeValue);
        }

        printf("now all process exit.\n");

        //close all semaphores
        sem_unlink("empty");
        sem_unlink("full");
        sem_unlink("mutex");
        sem_unlink("closetime");

        close(of);
        close(in);

        exit(0);
    }

    for(i=0; i < 5; i++)
    {
        if(!fork())
        {
            //printf("customer process(%u) begin to run!\n",getpid());

            while(1)
            {

                //customer
                if(sem_wait(full)!=0)
                {
                    printf("in customer %u sem_wait(full) error!\n",getpid());
                    perror(err_desc);
                    break;
                }

                //check in the closttime's value
                sem_getvalue(closetime,&tmpValue);
                if(tmpValue>0) //it is time for exit!
                {

                    fileLen=lseek(in,0,SEEK_END);
                    //printf("now customer [%u] is ready to exit! filelen=%d,closetime's value=%d\n",getpid(),fileLen,tmpValue);
                    if(fileLen==0) //now it is time to exit!
                    {
                        if(sem_wait(closetime)!=0)
                        {
                            printf("in customer [%u], sem_wait(closetime) error!\n");
                            perror(err_desc);
                        }
                        break;
                    }
                }

                if(sem_wait(mutex)!=0)
                {
                    printf("in customer %u,sem_post(empty) error!",getpid());
                    perror(err_desc);
                    break;
                }
                //读取第一个数值并显示
                lseek(of,0,SEEK_SET);
                read(of,&itemValue,sizeof(itemValue));
                printf("%u:%d\n",getpid(),itemValue);
                //将其余数值前移,截断文件4个字节
                fileLen=lseek(in,0,SEEK_END);

                for(j=1;j<(fileLen/sizeof(itemValue));j++)
                {
                    lseek(in,j*sizeof(itemValue),SEEK_SET);
                    read(in,&tmpValue,sizeof(tmpValue));
                    lseek(in,(j-1)*sizeof(itemValue),SEEK_SET);
                    write(in,&tmpValue,sizeof(tmpValue));
                }
                ftruncate(in,fileLen-sizeof(itemValue));

                //发送empty
                if(sem_post(empty)!=0)
                {
                    //此时是否表明已经生产结束?
                    printf("in customer %u,sem_post(empty) error!\n",getpid());
                    perror(err_desc);

                }

                if(sem_post(mutex)!=0)
                {
                    printf("in customer %u,sem_post(empty) error!\n",getpid());
                    perror(err_desc);
                    break;
                }
            }

            //close(of);
            exit(0);
        }
    }

    printf("now, main process exit!\n");
    return 0;
}

最后的补充:在我机器上(版本14.04 - 3.16)显示正常,但在实验楼的虚拟机中却会有多余的显示出现经查看版本为3.13,原因不明。


信号量实现方法2:

一.在内核中实现信号量

1.在POSIX标准中,sem_open()和sem_unlink()都是以字符串作为参数的,想要根据这个参数快速定位信号量在内核中的位置,自然就会想到用哈希表.我的想法是在OS启动后就初始化好一个全局的信号量的哈希表,表中的元素为struct sem_t *,初始化为NULL.哈希表的容量随便取了一个对本实验来说足够大素数.用最简单"线性探测法"来处理冲突,于是必须"懒惰删除"哈希表元素,详见注释

2.对于struct sem_t的内容,详见semaphore.h,我就说说挂起队列BlockQueue的实现:我没有选择指导书中方法一一一通过不同进程的内核栈上的局部变量形成的隐式链表,由sched.c中的sleep_on()和wake_up()来完成挂起和唤醒.我的方法比较笨,就是一个由链表实现的FIFO队列,写的很罗嗦,运行时开销也比较大一一一因为在一个信号量上阻塞或者唤醒进程会有很多对malloc()和free()的调用.

3.也因为2中的原因,我无法调用sleep_on()和wake_up()来改变当前进程current的状态(state).另一方面若直接对current用赋值语句,则要把struct task_struct的定义包进来,即还要include sched.h,太麻烦了,所以我在sched.c中写了个调用change_state()来专门做这件事.另外,为了操作方便,还在sem.c中写了一个系统调用void getsemval(struct sem_t * sem)来获取参数信号量当前的Value.

4.关于对error的处理,几乎是没有处理,反正指导书上也说这是一套缩水山寨版的POSIX信号量,就没有把那些过于庞杂的关于出错后擦屁股的东西加进来(而且我也不太懂),能把实验过了就行.

/*放在sched.c中*/
void change_state(struct task_struct * tmp,int new_state)
{
    tmp->state=new_state;
}

5.semaphore.h(内含本实验的山寨信号量所需的所有数据结构)

#ifndef SEMAPHORE_H_INCLUDED
#define SEMAPHORE_H_INCLUDED

/*Hash表的大小取一个质数*/
#define TABLE_SIZE 17

#ifndef NULL
#define NULL ((void *) 0)
#endif

/**
    链表的一个结点的定义如下:一个域存放指向PCB的指针,
    一个域指向下一个链表结点
**/
typedef struct LNode * PtrToLNode;
struct LNode{
    struct task_struct * Process;
    PtrToLNode Next;
};

struct BQNode{
    PtrToLNode Head;
    PtrToLNode Rear;
};
typedef struct BQNode * BlockQueue;
/**
    (1)信号量的定义,包括名字(Name),当前的值(Value),阻塞在该信号量上的队列.
    对于这个队列BlockQueue,我打算做成一个FIFO链表:其中BQ是个指向结构体BQNode
    的指针,存放指向FIFO链表的头结点的指针Head,以及末尾节点的指针Rear.

    (2)因为用的是线性探测法处理哈希表的地址冲突,所以为sem_t分配了一个域Info,
    用来记录当前哈希表中该信号量的状态,当调用sem_unlink()删除信号量时进行"懒惰删除".
**/

typedef enum {Free,IsUsed,DeletedSameName} Situation;
struct sem_t{
    Situation Info;
    char * Name;
    int Value;
    BlockQueue BQ;
};

/*模仿sched.h中的宏展开INIT_TASK,写一个用来初始化Hash表的宏*/

#define INIT_SEMHASH \
{NULL,NULL,NULL,NULL,NULL,NULL,\
NULL,NULL,NULL,NULL,NULL,NULL,\
NULL,NULL,NULL,NULL,NULL}

#endif

6.sem.c(内含本实验要实现的那几个山寨POSIX信号量的系统调用)

#include <semaphore.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/segment.h>

extern void schedule(void);
extern void change_state(struct task_struct * tmp,int new_state);
extern struct task_struct * current;

/**
    因为是以信号量的名字来索引信号量,所以我打算用一个全局Hash表all_the_sem来存放信号量.
    Hash表中的元素为指向sem_t的指针,用一个宏INIT_SEMHASH来初始化
**/
struct sem_t * all_the_sem[TABLE_SIZE]=INIT_SEMHASH;


PtrToLNode NewLNode(struct task_struct * Process,PtrToLNode Next)
{
    PtrToLNode newnode=(PtrToLNode)malloc(sizeof(struct LNode));
    newnode->Process=Process;
    newnode->Next=Next;

    return newnode;
}

/**
    以信号量的名字一一一字符串来索引信号量在Hash表中的位置
    用的是"移位法"一一一把字符串视为一个"32进制"的数,再将其转换成十进制,
    最后还要对Hash表的容量取模
**/
int SemHash(const char * Key,int TableSize)
{
    unsigned int H=0;
    char ch;
    int i=0;
/**
    一开始我直接用对Key解引用,即*Key来获得字符,结果发生Segment Fault,
    后来想起系统调用的那个实验中在内核中是不能直接取用户态空间中的数据的,
    必须通过asm/segment.h中的几个函数(包括get_fs_byte())来实现.
**/
    while ((ch=get_fs_byte(Key+i))!='\0')
    {
        H=(H<<5)+ch;
        i++;
    }

    return H%TableSize;
}

int FindSem(const char * name,int TableSize)
{
    int position=SemHash(name,TABLE_SIZE);
    char ch;
    int i;

/**
    使用线性探测法来解决哈希地址冲突:
    (1)这里要说一下struct sem_t中的Info域.于使用"开放定址法"的哈希表都是用的"懒惰删除",
    即仅仅把sem->Info置为Free,而不是真的去删除它(这就是所谓的关闭信号量吗?).
    所以当有all_the_sem[position]->Info==Free,直接返回就行了
    (2)当sem->Info!=DeletedSameName时(即sem->Info==Free或者IsUsed),说明有信号量正在占用哈希表中的这个位置,而且这个信号量可能被"懒惰删除"过.
    那么就用一段功能类似于strcmp()的代码判断这个信号量的Name与参数name是否相同,如果相同,而且其Info为Free,说明这是一个被"懒惰删除"过而且其Name
    与参数name相同,所以将其Info设为DeletedSameName.如果不同,就看看其Info是否为IsUsed,若是,说明有别的
    名字的信号量正在占着这个坑,就去找下一个位置呗;否则,即Info为Free,就选择该位置.
**/
    while (all_the_sem[position] && all_the_sem[position]->Info!=DeletedSameName)
    {
        /**
            指导书上说使用string.h中的内嵌汇编处理字符串会破坏参数,为了保险起见就没去用,
            就自己实现了一段和strcmp()类似功能的代码:ch存放当前从地址(name+i)中取出的字符,
            和all_the_sem[position]->Name[i]对比.参数name和all_the_sem[position]->Name能够
            完全相同的唯一情况就是ch=='\0' && all_the_sem[position]->Name[i]=='\0'
        **/
        for  (i=0;(ch=get_fs_byte(name+i))!='\0' && all_the_sem[position]->Name[i]!='\0' ;i++)
        {
            if (all_the_sem[position]->Name[i]!=ch)
                break;
        }
        if (ch=='\0' && all_the_sem[position]->Name[i]=='\0')
        {
            if (all_the_sem[position]->Info==Free)
                all_the_sem[position]->Info=DeletedSameName;
            break;
        }
        else
        {
            if (all_the_sem[position]->Info==Free)
                break;
            else
                position=(position+1)%TableSize;
        }
    }

    return position;
}

/**
    创建信号量,或者打开一个已经存在的信号量的系统调用
**/
struct sem_t * sys_semopen(const char * name,int value)
{
    int position=FindSem(name,TABLE_SIZE);
    int Size=0;
    char ch;

    //Size中记录name中有几个字符(不包括结尾的'\0'),也是用get_fs_byte()
    while ((ch=get_fs_byte(name+Size))!='\0')
        Size++;

    //如果该信号量还不存在,就创建它
    if (all_the_sem[position]==NULL)
    {
        /**
            在这里我是选择把用户态中的字符串name中的内容通通复制到all_the_sem[position]->Name中,
            我也想过能不能直接令all_the_sem[position]->Name存放指向用户态字符串name的指针?感觉
            可行,不过就要对FindSem()进行改动了,以后再说吧.
        **/
        //先为struct sem_t分配所需的内存空间
        if ((all_the_sem[position]=(struct sem_t *)malloc(sizeof(struct sem_t)))==NULL)
            return NULL;
        all_the_sem[position]->Name=NULL;
        all_the_sem[position]->Info=Free;
        /**
            初始时,为挂起队列BQ分配一个无意义的头结点,令BQ->Head和BQ->Rear都指向它,
            为了方便FIFO链表的插入和删除.
        **/
        all_the_sem[position]->BQ=(BlockQueue)malloc(sizeof(struct BQNode));
        all_the_sem[position]->BQ->Head=NewLNode(NULL,NULL);
        all_the_sem[position]->BQ->Rear=all_the_sem[position]->BQ->Head;
    }
    /**
            Info==Free有两层含义:
            (1)这是一个新建的信号量,还没有Name.
            (2)它是一个曾经被关闭(懒惰删除)过的信号量,但它的Name和当前sys_semopen()的参数name不同.
            无论怎样,都要把它的名字变成参数name,只不过(2)中的情况还要把原来的Name释放掉
    **/
    if (all_the_sem[position]->Info==Free)
    {
        if (all_the_sem[position]->Name)
            free(all_the_sem[position]->Name);
        all_the_sem[position]->Name=(char *)malloc((Size+1)*sizeof(char));
        for (;(int)Size>=0;Size--)
        {
            ch=get_fs_byte(name+Size);
            all_the_sem[position]->Name[Size]=ch;
        }
        all_the_sem[position]->Info=DeletedSameName;
    }
    /**
        如果Info==DeletedSameName,这说明all_the_sem[position]曾经被删除过,而且它的Name和参数的name相同,
        所以要设置好Info和Value.
    **/
    if (all_the_sem[position]->Info==DeletedSameName)
    {
        all_the_sem[position]->Value=value;
        all_the_sem[position]->Info=IsUsed;
    }
    /**
        最后一种结果,就是Info==IsUsed,这说明all_the_sem[position]->Name和参数的name相同,而且它正在
        被使用着,所以什么都不做,直接返回
    **/
    return all_the_sem[position];
}
/**
    P原子操作的实现
**/
int sys_semwait(struct sem_t * sem)
{
/**
    sem_wait()何时会出错?我觉得是参数sem不存在的时候,
    即参数sem==NULL时,
**/

    if (sem==NULL)
        return -1;

    cli();

    (sem->Value)--;

    if (sem->Value<0)
    {
    /**
        首先,把当前进程current加入挂起队列.这是个FIFO的链表,在尾部(Rear)插入,
        在头部(Head)删除.
        然后调用change_state()改变当前进程的状态.
        最后调用schedule()切出去.
    **/
        sem->BQ->Rear->Next=NewLNode(current,NULL);
        sem->BQ->Rear=sem->BQ->Rear->Next;
        change_state(sem->BQ->Rear->Process,2);
        schedule();
    }

    sti();
    return 0;
}

int sys_sempost(struct sem_t * sem)
{
    if (sem==NULL)
        return -1;
    cli();

    PtrToLNode tmp;

    (sem->Value)++;
    /**
        这里我直接通过判断挂起队列是否为空来决定是否要唤醒进程,而不是
        if (sem->Value<=0)
    **/
    if ((tmp=sem->BQ->Head->Next)!=NULL)
    {
        change_state(tmp->Process,0);
    /**
        这里有个特殊情况要处理,就是挂起队列中只有一个进程的时候,当删除这个进程
        时,要把挂起队列恢复成初始化时的样子一一一重点是把sem->BQ->Rear重新指回
        那个无意义的头结点.我一开始没有进行这个处理,导致再往队列中添加进程时出错
        (因为是在尾部Rear添加新进程的)
    **/
        if ((sem->BQ->Head->Next=tmp->Next)==NULL)
            sem->BQ->Rear=sem->BQ->Head;
        free(tmp);
    }

    sti();
    return 0;
}

/**
    并没有研读过《UNIX环境高级编程》和POSIX标准,暂时无法理解
    为什么sem_unlink()要以信号量的名字,即字符串作参数.直接用struct sem_t * sem
    作参数不是更快更直接吗?
**/
int sys_semunlink(const char * name)
{
    int position=FindSem(name,TABLE_SIZE);
    PtrToLNode tmp,next;

    if (all_the_sem[position]==NULL)
        return -1;

/**
    对all_the_sem[position]进行懒惰删除,仅仅把Info置为Free,
    但也要把阻塞在该信号量上的进程都唤醒
**/
    all_the_sem[position]->Info=Free;
    for (tmp=all_the_sem[position]->BQ->Head->Next;tmp;tmp=next)
    {
        next=tmp->Next;
        change_state(tmp->Process,0);
        free(tmp);
    }
    all_the_sem[position]->BQ->Head->Process=NULL;
    all_the_sem[position]->BQ->Head->Next=NULL;
    all_the_sem[position]->BQ->Rear=all_the_sem[position]->BQ->Head;

    return 0;
}

/**
    为了操作方便,还写了一个系统调用getsemval()来获取参数信号量当前的Value
**/
int sys_getsemval(struct sem_t * sem)
{
    return sem->Value;
}

二.pc.c的实现

0.本人觉得pc.c的实现是本实验中最难的部分,整整花了我3天时间(整个实验我做了两个周末一共4天),究其原因,就是我在进程同步这一块掌握不牢,以及几个操作文件的POSIX接口用的太烂一一一说白了还是自己水平太垃圾.奉劝想入坑的同学先把这两块知识学扎实了再做实验.

1.pc.c的难点在于怎么在文件这个共享缓冲区中实现实现produce和consume这两个动作.以下是我的尝试:

  • 看到"共享",我一开始就想当然的只用一个文件描述符来打开这个文件,即consumer和producer都用同一个文件描述符来处理缓冲区,结果输出的消费序列是乱的(比如011234465这种).
  • 于是拍脑门一想,"嗨呀既然使用同一个文件指针,那consumer和producer各自的文件偏移量肯定是一样的嘛,所以才会出错"于是就想通过信号量FreeBuffer(标示空闲的缓冲区的量)+ProductQuant(标示缓冲区中的产品数量)+lseek()来计算出consumer获得产品的正确位置,结果自然还是错的:由于OS对进程难以预测的调度,当有多个consumer时,是很有可能在ProductQuant上阻塞了多个进程的,即ProductQuant可能变成负数.于是,就算ProductQuant恢复回了正值,其值也无法正确反应当前缓冲区中产品的数量,它仅仅反映了执行sem_wait()而不被挂起的进程的数量而已.FreeBuffer也是同理.
  • 最后就百度了一下,"多个进程共享一个文件"的正确方法,才想到让consumer和producer各自支配一个指向同一个文件的文件描述符,才实验成功.具体的做法见pc.c

2.因为产品的序列较大(>=500),所以决定把进程ID+产品号输出到一个文件Output中,而不是直接输出到Bochs的屏幕上.

3.同样的,没有对error进行处理.

4.我的这个pc.c有时会发生死锁,不知是程序逻辑的问题还是说这本来就是普遍现象?但无论死锁与否,都可以保证输出结果的正确,我也懒得去找原因了.

5.代码如下

#define __LIBRARY__
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define CONSUMER_QUANT 3
#define BUFFER_QUANT 10
#define PRODUCT_RANGE 501
#define CONSUME_RANGE 500
_syscall2(struct sem_t *,sem_open,const char *,name,int,value);/*sem_open的接口*/
_syscall1(int,sem_wait,struct sem_t *,sem);/*sem_wait的接口*/
_syscall1(int,sem_post,struct sem_t *,sem);/*sem_post的接口*/
_syscall1(int,sem_unlink,const char *,name);/*sem_unlink的接口*/
_syscall1(int,getsemval,struct sem_t *,sem);/*getsemval的接口*/


/**
    关于上面的那几个宏,从上往下分别表示:消费者的数量,缓冲区的数量,
    生产的产品的取值范围,消费的产品的取值范围
**/
void Producer(int BufferI,struct sem_t *Mutex,struct sem_t * FreeBuffer,struct sem_t *ProductQuant)
{
    int Product=0;

    while (Product<=PRODUCT_RANGE)
    {
        sem_wait(FreeBuffer);
        sem_wait(Mutex);
        /**
            为了保证Producer和Consumer可以在限定大小的缓冲区中活动,
            每当产品Product为BUFFER_QUANT的倍数时(说明Producer已经到了
            缓冲区的末尾了),就用lseek()把文件偏移量拨回到文件起始处
        **/
        if (Product%BUFFER_QUANT==0)
            lseek(BufferI,0,SEEK_SET);
        write(BufferI,(void *)(&Product),sizeof(int));
        Product++;
        sem_post(Mutex);
        sem_post(ProductQuant);
    }
}

void Consumer(int BufferO,int Output,struct sem_t *Mutex,struct sem_t *FreeBuffer,struct sem_t *ProductQuant,struct sem_t * ConsumeTimes )
{
    int Product,Length;
    char * Tmp=(char *)malloc(20*sizeof(char));

    while (1)
    {
        sem_wait(ProductQuant);
        /**
            把循环终止的判断放在这里是为了和
            结尾的while循环配套
        **/
        if (getsemval(ConsumeTimes)>CONSUME_RANGE)
            break;
        sem_wait(Mutex);
        /**
            请联系Producer()中对文件偏移量的处理.对于Consumer,
            当读到文件尾时,就用lseek()把文件偏移量拨回到文件起始处.
        **/
        if (read(BufferO,(void *)(&Product),sizeof(int))==0)
        {
            lseek(BufferO,0,SEEK_SET);
            read(BufferO,(void *)(&Product),sizeof(int));
        }
        /**
            在缓冲区中Producer写入以及Consumer读入的都是int值,
            而在文件Output中打印出的消费记录自然是根据ASCII码
            显示,所以Consumer要往Output中写入char,这需要
            一个字符串Tmp做中转站.
            但这样做有一个问题:不知为何,把Tmp的内容用write()写入
            Output的话,字符串中的'\n'在Output中会变成奇怪的符号,
            用Vim打开会显示成'换行^@'
        **/
        sprintf(Tmp,"%d: %d\n",getpid(),Product);
        Length=strlen(Tmp);
        write(Output,(void *)Tmp,(Length+1)*sizeof(char));
        sem_post(ConsumeTimes);
        sem_post(Mutex);
        sem_post(FreeBuffer);
    }

    /**
        关于这个while循环,是用来处理ConsumeTimes已经超过
        CONSUME_RANGE了还有Consumer阻塞在ProductQuant
        中出不来的情况,不过感觉好像没什么作用,该死锁时还是会死锁
    **/
    while (getsemval(ProductQuant)<0)
        sem_post(ProductQuant);
}
int main(void)
{
    struct sem_t * Mutex;
    struct sem_t * FreeBuffer;
    struct sem_t * ProductQuant;
    /**
        信号量ConsumeTimes不是用来互斥,把它视为一个记录当前总的消费
        次数的全局变量就行了,用它来通知消费者是否要退出循环,于是对
        ConsumeTimes也不会有sem_wait()操作
    **/
    struct sem_t * ConsumeTimes;
    pid_t CurrentID;
    int i,BufferI,BufferO,Output;

    if ((Mutex=sem_open("Mutex",1))==NULL)
        exit(0);
    if ((FreeBuffer=sem_open("FreeBuffer",BUFFER_QUANT))==NULL)
        exit(0);
    if ((ProductQuant=sem_open("ProductQuant",0))==NULL)
        exit(0);
    if ((ConsumeTimes=sem_open("ConsumeTimes",0))==NULL)
        exit(0);

    /*建立缓冲区和输出的终端*/
    BufferI=open("/usr/root/buffer",O_WRONLY|O_TRUNC|O_CREAT);
    BufferO=open("/usr/root/buffer",O_RDONLY|O_TRUNC);
    Output=open("/usr/root/output.log",O_RDWR|O_TRUNC|O_CREAT);

    /**
        fork()出CONSUMER_QUANT个子进程作为消费者,
        父进程作为生产者.
    **/
    for (i=0;i<CONSUMER_QUANT;i++)
    {
        if ((CurrentID=fork())==0)
            break;
    }

    if (CurrentID==0)
        Consumer(BufferO,Output,Mutex,FreeBuffer,ProductQuant,ConsumeTimes);
    else
    {
        Producer(BufferI,Mutex,FreeBuffer,ProductQuant);
        /**
            用一个循环来让父进程回收子进程
        **/
        while (wait(NULL)!=-1)
            continue;
        sem_unlink("Mutex");
        sem_unlink("FreeBuffer");
        sem_unlink("ProductQuant");
        sem_unlink("ConsumeTimes");
        close(BufferI);
        close(BufferO);
        close(Output);
    }

    return 0;
}

三.实验报告

1.执行效果变化很大,打印出的消费序列完全是乱的.因为没有信号量对临界区进行保护,加上OS对进程调度的不确定性,导致生产者和消费者们的动作都无法做到互斥.也就是说,在临界区中完成一个动作之前,它们都有可能被另一个进程抢占,于是,在缓冲区中的取出的数据自然都是错的.比如,消费者C准备准备在缓冲区的起始处取出产品0时,就被生产者P抢占,而P已经完成了一轮的生产,也处于缓冲区的起始处,它往这里写入一个11.如果有轮到C执行,那么C取出的产品就是11,而不是0.

2.不可行.因为这会导致死锁的发生.假如Producer调用P(Mutex),成功进入临界区,但被阻塞在Empty上,此时,它需要一个Consumer执行V(Empty)将其唤醒.然而,此时临界区中已经存在了一个Producer了,如果Consumer调用P(Mutex),就会阻塞在Mutex上,即其无法进入临界区.结果就是,两者都拥有对方需要的资源,但都被阻塞,无法释放自己拥有的资源,形成死锁.



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值