哈工大-操作系统-李治军-实验5-信号量的实现和应用

本文主要讲述哈工大操作系统实验5信号量的实现和应用的修改过程。作者参考文章调试后得出结果,介绍了在linux/kernel建立sem.c、编写pc.c、修改Makefile等七个步骤,最后进行测试,虽存在运行到200停止、文件结尾有奇怪符号等问题,但整体代码能运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我参考了下面的文章,下面的文章有一些错误,经过不断的调试,终于将结果弄出来了。 但是应该还有一些小问题,实在是弄了好几天不再想看了,要是有后来者继续完善了可以在评论区留下文章地址。 这篇文章原理我就不讲了,有许多文章都说了,我就只说说哪里修改了。

哈工大-操作系统-HitOSlab-李治军-实验5-信号量的实现和应用哈工大操作系统实验五garbage_man的博客-优快云博客

第一步

在linux/kernel中建立sem.c

#include <unistd.h> /* NULL */
//#include <string.h> /* strcmp */
#include <linux/sem.h> /* sem_t */
#include <asm/segment.h> /* get_fs_byte */
#include <asm/system.h> /* cli, sti */
#include <linux/kernel.h> /* printk */
​
//信号量最大数量
#define SEM_LIST_LENGTH 5
​
//信号量数组(都初始化为没有的状态)
sem_t sem_list[SEM_LIST_LENGTH] = {
    {"\0",0,NULL}, {"\0",0,NULL},{"\0",0,NULL},{"\0",0,NULL},{"\0",0,NULL}
};
​
/*
sem_open()的功能是创建一个信号量,或打开一个已经存在的信号量。
*/
​
sem_t *sys_sem_open(const char *name,unsigned int value)
{
​
    if (name == NULL)
    {
        printk("name == NULL\n");
        return NULL;
    }
    /* 首先将信号量的名称赋值到新建的缓冲区中 */
    char nbuf[20];
    int i = 0;
    for(; i< 20; i++)
    {
        nbuf[i] = get_fs_byte(name+i);
    }
    nbuf[i]='\0';
    /* 然后开始遍历已有的信号量数组,如果有该名字的信号量,直接返回信号量的地址 */
    sem_t *result;
    i = 0;
    int k=0;
    int tmp=-1;
    for(; i < SEM_LIST_LENGTH; i++)
    {
        if(sem_list[i].name[0] == '\0'){
            tmp=i;
            continue;   
        }   
        for(k=0;nbuf[k]!='\0';k++)
        {
            if(sem_list[i].name[k]!=nbuf[k])
                break;
        }
        if(nbuf[k]!='\0')
            continue;
        if(sem_list[i].name[k]=='\0'){
            result=&sem_list[i];
            return result;
        }
            
/*        if(!strcmp(sem_list[i].name, nbuf))
        {
            result = &sem_list[i];
            printk("sem %s is found\n",result->name);
            return result;
        }
*/
    }
    if(tmp==-1)
        printk("sem_list is full");
    /* 如果找不到信号量,就开始新建一个名字为name的信号量,值=value,队列指针=NULL,然后返回信号量的地址 */
//    strcpy(sem_list[tmp].name, nbuf);
    for(k=0;nbuf[k]!='\0';k++)
        sem_list[tmp].name[k]=nbuf[k];
    sem_list[tmp].name[k]='\0';
    sem_list[tmp].value = value;
    sem_list[tmp].queue = NULL;
    result = &sem_list[tmp];
    printk("sem %s is created , value = %d\n",sem_list[tmp].name,sem_list[tmp].value);
    printk("sem %s is created , value = %d\n",result->name,result->value);
    return result;
}
​
/*
 sem_wait()就是信号量的P原子操作。
 如果继续运行的条件不满足,则令调用进程等待在信号量sem上。
 返回0表示成功,返回-1表示失败。
 */
int sys_sem_wait(sem_t * sem)
{
    /* 判断:如果传入的信号量是无效信号量,P操作失败,返回-1 */
    if(sem == NULL || sem < sem_list || sem > sem_list + SEM_LIST_LENGTH)
    {
        printk("P(sem) error\n");
        return -1;
    }
    /* 关中断 */
    cli();
    while(sem->value == 0)
    {
        sleep_on(&(sem->queue));
    }
    sem->value--; 
    /* 开中断 */
    sti();
    return 0;
}
/*
sem_post()就是信号量的V原子操作。
如果有等待sem的进程,它会唤醒其中的一个。
返回0表示成功,返回-1表示失败。
*/
int sys_sem_post(sem_t * sem)
{
    /* 判断:如果传入的信号量是无效信号量,V操作失败,返回-1 */
    if(sem == NULL || sem < sem_list || sem > sem_list + SEM_LIST_LENGTH)
    {
        printk("V(sem) error\n");
        return -1;
    }
    /* 关中断 */
    cli();
    sem->value++;
    /* 如果有等待sem的进程,它会唤醒其中的一个。 */
    if(sem->value > 0)
    {
        wake_up(&(sem->queue));
    }
    /* 开中断 */
    sti();
    return 0;
}
​
/*
sem_unlink()的功能是删除名为name的信号量。
返回0表示成功,返回-1表示失败。
*/
int sys_sem_unlink(const char *name)
{
    if (name == NULL)
        return -1;
    /* 首先将信号量的名称赋值到新建的缓冲区中 */
    char nbuf[20];
    int i = 0;
    int k=0;
    for (; i < 20; i++)
    {
        nbuf[i] = get_fs_byte(name + i);
        if (nbuf[i] == '\0')
            break;
    }
    nbuf[i]='\0';
    i = 0;
    for (; i < SEM_LIST_LENGTH; i++)
    {
        if(sem_list[i].name[0] == '\0'){
            continue;   
        }   
        for(k=0;nbuf[k]!='\0';k++)
        {
            if(sem_list[i].name[k]!=nbuf[k])
                break;
        }
        if(nbuf[k]!='\0')
            continue;
        if(sem_list[i].name[k]=='\0'){
            sem_list[i].name[0] = '\0';
            sem_list[i].value = 0;
            sem_list[i].queue = NULL;
            break;
        }
 /*       if (strcmp(sem_list[i].name, nbuf))
        {
            sem_list[i].name[0] = '\0';
            sem_list[i].value = 0;
            sem_list[i].queue = NULL;
        }
*/
    }
    /* 没找到该名字的信号量,删除失败,返回-1 */
    if (i == SEM_LIST_LENGTH)
        return -1;
​
    return 0;
}
​
​

我参考的文章中对于字符串的拷贝和比较都是使用#include <string.h>下的strcmp()和strcpy(),就如老师给的实验指导书中说的一般,真的会出现问题。 就比如在sys_sem_open函数中,我把nbuf中的数据使用strcpy复制到sem_list[].name以后,想打印一下sem_list[].name结果发现打不出来,后来我全换成一个字节一个字节的拷贝就好了。

我还修改了一下通过信号量名字在信号量数组中匹配的规则,原来的规则有点问题。

第二步

编写pc.c

/*
建立一个生产者进程,N个消费者进程(N>1);
用文件建立一个共享缓冲区;
生产者进程依次向缓冲区写入整数0,1,2,...,M,M>=500;
消费者进程从缓冲区读数,每次读一个,并将读出的数字从缓冲区删除,然后将本进程ID和数字输出到标准输出;
缓冲区同时最多只能保存10个数。
提示:建议直接使用系统调用进行文件操作,使用标准C的fopen等文件读写函数需要额外的操作,稍显麻烦
*/
#define __LIBRARY__
#include <stdio.h>
#include <linux/sem.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
​
_syscall2(int, 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);
​
int consumerNum = 5;    /* 消费者个数 */
const int maxNum = 500; /* 写入的数据量 */
const int bufSize = 10; /* 缓冲区大小 */
int data = -404;        /* 存放读取的数据*/
pid_t p_pid[5];         /* 进程号数组*/
​
sem_t *empty, *full, *mutex; /*三个信号量 */
​
void producer()
{
    int i, fo,fx;
    char buf[4];
    int endpos_produce = 0; /* 记录消费者进程的消费次数*/
    fo = open("report.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);
    fx = open("out.txt", O_RDWR | O_CREAT | O_APPEND);
    if (fo==-1||fx==-1)
    {
        perror("打开文件失败!\n");
        return;
    }
    i = 0;
​
    for (i = 1; i <= maxNum; i++)
    {
        sem_wait(empty); /* 如果empty为0,意味着缓冲区已满,阻塞生产者*/
        sem_wait(mutex); /* 互斥控制 */
​
        lseek(fo, endpos_produce * sizeof(int), SEEK_SET);
        sprintf(buf, "%d", i);
        write(fo, buf, sizeof(int));
​
        /*将生产信息写到out文件*/
        write(fx, "p", 1);
        write(fx, ":", 1);
        write(fx, buf, sizeof(int));
        write(fx, "\n", 1);
        fflush(stdout);
​
        endpos_produce = (endpos_produce + 1) % bufSize;
​
        sem_post(mutex); /* 出了临界区,需要mutex++,以便下一次可以进入 */
        sem_post(full);  /* 看是不是需要唤醒阻塞 */
    }
    close(fo);
}
void consumer()
{
    int cnt, endpos_consumer = 0, n;
    char buf[4];
    char pid[4];
    int id;
    int fi = open("report.txt", O_RDONLY);
    int fx = open("out.txt", O_RDWR | O_CREAT | O_APPEND);
    int fb = open("endpos.txt", O_RDWR | O_CREAT);
​
    if (fi == -1 || fx == -1 || fb == -1) {
        perror("创建文件缓冲区失败!\n");
        return;
    }
    for (cnt = 0; cnt < maxNum / 5; ++cnt)
    {
​
        sem_wait(full);
        sem_wait(mutex);
​
        /* 从文件中读取消费者进程的文件指针位置 */
        n=read(fb, buf, 4);
        if(n==0)
            endpos_consumer=0;
        sscanf(buf, "%d", &endpos_consumer);
        printf("%d", endpos_consumer);
​
​
        /* 将文件中对应位置的数据读取出来 */
        lseek(fi, endpos_consumer * sizeof(int), SEEK_SET);
        read(fi, buf, 4);
​
        /*将pid写到输出信息文件*/
        write(fx, "c,", 1);
        id = getpid();
        sprintf(pid, "%d", id);
        write(fx, pid, sizeof(int));
        write(fx, ":", 1);
​
        /*将消费的数字写到输出文件*/
        write(fx, buf, sizeof(int));
        write(fx, "\n", 1);
​
        /* 移动endpos_consumer的位置,下一次的消费者进程将读取新的数*/
        endpos_consumer = (endpos_consumer + 1) % bufSize;
        lseek(fb, 0, SEEK_SET);
        sprintf(buf, "%d", endpos_consumer);
        write(fb, buf, sizeof(int));
​
        fflush(stdout);
​
        sem_post(mutex);
        sem_post(empty);
    }
    close(fi);
    close(fx);
    close(fb);
}
​
int main()
{
    /* 建立这三个信号量 */
    empty = sem_open("empty", bufSize);
    if (empty == NULL)
    {
        perror("empty create falied!\n");
        return -1;
    }
    full = sem_open("full", 0);
    if (full == NULL)
    {
        perror("full create failed!\n");
        return -1;
    }
    mutex = sem_open("mutex", 1);
    if (mutex == NULL)
    {
        perror("mutex create failed!\n");
        return -1;
    }
    if (empty && full && mutex)
    {
        printf("create semphore successed!\n");
    }
    /* 调用生产者进程,往文件缓冲区中写入数字 */
    if (!fork())
    {
        printf("producer is running!\n");
        producer();
        exit(0); /* 生产者任务完成后,杀死该子进程 */
    }
​
    while (consumerNum--)
    {
        p_pid[consumerNum] = fork();
​
        if (!p_pid[consumerNum])
        {
            printf("consumer %d is running!\n", getpid());
            consumer();
            exit(0);
        }
    }
​
    wait(NULL);
    /* 关闭信号量 */
    sem_unlink("empty");
    sem_unlink("full");
    sem_unlink("mutex");
    return 0;
}
​
​

我这里的做法是三个文件: report文件时缓冲区文件,最多只能放10个数字。 out文件是输出文件,里面记录进程的行动。(c,4:20 含义就是消费者进程4消费数字20)(p:20 生产者进程生产数字20) endpos文件是记录消费者进程消费指针。(文件内只有一个数字,记录消费者进程消费到何处,所有消费者进程共享这一个文件,当一个消费者进程醒来,都会从这个文件中读入这个数字,从这里开始消费)

endpos_produce是生产者的生产指针,endpos_consumer是消费者指针,缓冲区就像一个环形数组一样,两个指针在上面你追我赶,缓冲区一共有10个位置,两个指针的值也总是在0到9之间。

第三步

修改linux0.11/kernel/Makefile

两处地方

 

sem.s sem.o: sem.c ../include/linux/kernel.h ../include/unistd.h \
  ../include/linux/sem.h ../include/linux/sched.h

第四步

建立sem.h,放在 linux0.11/include/linux/ 下和hdc/usr/include/linux/中

/* 定义的信号量数据结构: */
# 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
​

第五步

在include/linux/sys.h中添加系统调用的定义

#include<linux/sem.h>
extern sem_t * sys_sem_open();
extern int sys_sem_wait();
extern int sys_sem_post();
extern int sys_sem_unlink();
, sys_sem_wait, sys_sem_post, sys_sem_unlink

下面也要添加别忘了哦。

第六步

在hdc/usr/include/unistd.h中添加系统调用号 也要在linux-0.11/include/unistd.h中添加

/* ... */
#define __NR_setregid 71
/* 添加的系统调用号 */
#define __NR_sem_open 72
#define __NR_sem_wait 73
#define __NR_sem_post 74
#define __NR_sem_unlink 75

第七步

修改linux0.11/kernel/system_call.s

nr_system_calls=76

把这个值修改一下

测试

这就修改完成了,挂载hdc将pc.c复制到root目录下

sudo ./mount-hdc
cp pc.c hdc/usr/root/
sudo umount hdc

运行虚拟机

./run

编译pc.c

gcc -o 111 pc.c

运行111

./111

 如果看到上图这样,就说明运行对了,这些信息都是我测试时候看的,想删就删。

查看一下report.txt

可以看到此时缓冲区内是从191到200,我也不知道为什么他每次到200就停了,运行不到500,也有点懒得找bug了。

再查看一下

endpos.txt 数字是00,有可能上面为什么只能到200的原因是因为这。

再看一下out.txt

vi out.txt

每行的结尾为什么多了个这个奇怪的符号。。。我也不知道

做实验的时候看到这,真的是很开心。我本身也不是主写c语言的,写这些c语言的代码真的是步步维艰。所以里面有一些c语言的问题多多包涵,也有一些不完美,比如申请了什么变量,结果一会没用到,我也没删什么的。

哈哈,总的来说,代码和人有一个能跑的就行,既然他能跑,跑的也不错,我就不改了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值