【哈工大_操作系统实验】Lab6 信号量的实现和应用

本节将更新哈工大《操作系统》课程第六个 Lab 实验 信号量的实现和应用。按照实验书要求,介绍了非常详细的实验操作流程,并提供了超级无敌详细的代码注释

实验目的:

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

实验任务:

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

文件名介绍
hit-操作系统实验指导书.pdf哈工大OS实验指导书
Linux内核完全注释(修正版v3.0).pdf赵博士对Linux v0.11 OS进行了详细全面的注释和说明
file1615.pdfBIOS 涉及的中断数据手册
hit-oslab-linux-20110823.tar.gzhit-oslab 实验环境
gcc-3.4-ubuntu.tar.gzLinux v0.11 所使用的编译器
Bochs 汇编级调试指令bochs 基本调试指令大全
最全ASCII码对照表0-255屏幕输出字符对照的 ASCII 码
x86_64 常用寄存器大全x86_64 常用寄存器大全

一、编写生产者-消费者模型

1、在编写生产者-消费者模型之前,先进行一些知识准备:

lseek() 函数可以改变文件的文件偏移量,所有打开的文件都有一个当前文件偏移量,用于表明文件开始处到文件当前位置的字节数。成功返回新的偏移量,失败返回-1。

off_t lseek(int filedes, off_t offset, int whence);
// 参数 filedes:  文件描述符
// 参数 offset:   位移量
// 参数 whence:   指定初始位置
	// 	SEEK_SET 将读写位置指向文件头后再增加offset个位移量。
	//	SEEK_CUR 以目前的读写位置往后增加offset个位移量。
	//	SEEK_END 将读写位置指向文件尾后再增加offset个位移量。

2、实现生产者、消费者模型,编写pc.c文件:

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.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;  /*3个信号量*/
	int fd;                       /*共享缓冲区文件描述符*/
    int  i,j,k,child;
    int  data;                    /*写入的数据*/
    pid_t pid;
    int  buf_out = 0;             /*记录上次从缓冲区读取位置*/
    int  buf_in = 0;              /*记录上次写入缓冲区位置*/
	/*创建信号量*/
	empty = sem_open("empty", O_CREAT|O_EXCL, 0644, BUFSIZE); /*剩余资源,初始化为size*/
    full = sem_open("full", O_CREAT|O_EXCL, 0644, 0);         /*已生产资源,初始化为0*/
	mutex = sem_open("mutex", O_CREAT|O_EXCL, 0644, 1);       /*互斥量,初始化为1*/
    /* 在文件中存储buf_out(因此生产者只有一个进程,所以buf_in不用存在文件中)*/
    fd = open("buffer.txt", O_CREAT|O_TRUNC|O_RDWR,0666); 
    lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);/*修改地址偏移在10个数字之后,即40字节位置*/
    write(fd,(char *)&buf_out,sizeof(int));/*将上次读取位置buf_out存入buffer后的一个位置,每次从该位置获取上次位置,以便子进程之间通信*/
    
    /*生产者进程*/
    if((pid=fork())==0)
    {
		printf("I'm producer. pid = %d\n", getpid());
		/*生产多少个产品就循环几次*/
        for( i = 0 ; i < M; i++)
        {
			/*empty大于0,才能生产*/
            sem_wait(empty);  /* empty-- */
            sem_wait(mutex);  /* mutex-- */
            
            /*从上次位置继续向文件缓冲区写入一个字符*/
            lseek(fd, buf_in*sizeof(int), SEEK_SET); 
            write(fd,(char *)&i,sizeof(int)); 
            /*更新写入缓冲区位置,保证在0-9之间,缓冲区最大为10*/
            buf_in = (buf_in + 1) % BUFSIZE;
            
            sem_post(mutex);  /* mutex++ */
            sem_post(full);   /* full++,唤醒消费者线程 */
        }
        printf("producer end.\n");
        fflush(stdout);/*确保将输出立刻输出到标准输出。*/
        return 0;
    }
	else if(pid < 0)
    {
        perror("Fail to fork!\n");
        return -1;
    }
    
	/*消费者进程*/
    for( j = 0; j < N ; j++ )
    {
        if((pid=fork())==0)
        {
	        /* 每个进程读取 M/N 个数字 */
			for( k = 0; k < M/N; k++ )
            {
	            /* full大于0,才能消费 */
                sem_wait(full);  /* full-- */
                sem_wait(mutex); /* mutex-- */
				
                /*从文件第11个位置获得上次读取位置*/
                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));
                /*在文件第11个位置写入下次应读取位置*/
                buf_out = (buf_out + 1) % BUFSIZE;
                lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);
                write(fd,(char *)&buf_out,sizeof(int));

                sem_post(mutex);  /* mutex++ */
                sem_post(empty);  /* empty++,唤醒生产者进程 */
                /*消费资源*/
                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;
}

3、在Ubuntu下编译执行

  • 注意:此处代码头文件使用的是Ubuntu系统下的semaphore.h中的信号量,故无需下文定义的信号量即可编译通过。
gcc -o pc pc.c -lpthread
./pc > 1.txt

打开1.txt文件即可见到输出如下:
在这里插入图片描述在这里插入图片描述

二、信号量的实现

1、新建头文件include/linux/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、新建文件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;
    // 计算信号量名字长度,需要通过get_fs_byte从用户空间获取数据
    while( get_fs_byte(name+name_cnt) != '\0')
	    name_cnt++;
	// 大于则退出
    if(name_cnt>SEM_NAME_LEN)
	    return NULL;
	// 将信号量名字从用户态复制到内核态,存入kernelname
    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!\n");
    }
    else   // 不存在,则新建
    {
        i=0;
        // 赋值信号名
        for(i=0;i<name_len;i++)
        {
            semtable[cnt].name[i]=kernelname[i];
        }
        // 赋值信号值
        semtable[cnt].value = value;
        // 往信号量表cnt位置记录
        p=(sem_t*)(&semtable[cnt]);
         //printk("creat name!\n");
        cnt++;
     }
    return p;
}

// 信号量P原子操作
int sys_sem_wait(sem_t *sem)
{
	// 关中断,阻止调度,实现临界区保护
    cli(); 
    // 使得所有小于0的进程阻塞(生产者没有缓冲可用empty<=0,则进入睡眠)
    while( sem->value <= 0 )        
        sleep_on(&(sem->queue));    // 睡眠当前进程
    sem->value--;
    // 开中断
    sti();
    return 0;
}

// 信号量V原子操作
int sys_sem_post(sem_t *sem)
{
	// 关中断,阻止调度,实现临界区保护
    cli();
    sem->value++;
    // 加完后小于等于1,说明加之前是没有可消费的,则唤醒一个进程
    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;
    // 计算信号量名字长度,需要通过get_fs_byte从用户空间获取数据
    while( get_fs_byte(name+name_cnt) != '\0')
            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;
    // 判断是否存在,并定位赋值给cnt
    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;
        // 从定位到的cnt位置往后开始向前覆盖,实现删除
        for(tmp=i;tmp<=cnt;tmp++)
        {
            semtable[tmp]=semtable[tmp+1];
        }
        cnt = cnt-1;  // 信号量个数减一
        return 0;
    }
    else
        return -1;  // 不存在则返回
}

3、新增上述四个系统调用

  1. 修改/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
  1. 修改/kernel/system_call.s,需要修改总的系统调用数
nr_system_calls = 76
  1. 修改/include/linux/sys.h,声明全局新增函数
extern int sys_sem_open();
extern int sys_sem_wait();
extern int sys_sem_post();
extern int sys_sem_unlink();

fn_ptr sys_call_table[] = {
//...sys_setreuid,sys_setregid,sys_whoami,sys_iam,
sys_sem_open,sys_sem_wait,sys_sem_post,sys_sem_unlink
};

  1. 修改 linux-0.11/kernel/Makefile,添加sem.c编译规则
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 who.o sem.o
// ...
### Dependencies:
sem.s sem.o: sem.c ../include/linux/sem.h ../include/linux/kernel.h \
../include/unistd.h

三、在linux0.11环境下编译运行

前面编写的pc.c能够在Ubuntu下运行通过,但在linux0.11系统下不行,需要进行修改,我们复制pc.c,创建一个适合在linux0.11环境下编译的`pc2.c’

  1. 在linux0.11系统的应用程序中,注释不能写//,必须要写/* */
  2. 修改头文件,包含上面新建的sem.h
  3. 修改sem_open()的创建方法
#define   __LIBRARY__
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/sem.h>

#define M 530            /*打出数字总数*/
#define N 5               /*消费者进程数*/
#define BUFSIZE 10      /*缓冲区大小*/

_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);

int main()
{
    sem_t *empty, *full, *mutex;  /*3个信号量*/
    int fd;                       /*共享缓冲区文件描述符*/
    int  i,j,k,child;
    int  data;                    /*写入的数据*/
    pid_t pid;
    int  buf_out = 0;             /*记录上次从缓冲区读取位置*/
    int  buf_in = 0;              /*记录上次写入缓冲区位置*/

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

    /* 在文件中存储buf_out(因此生产者只有一个进程,所以buf_in不用存在文件中)*/
    fd = open("buffer.txt", O_CREAT|O_TRUNC|O_RDWR,0666); 
    lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);/*修改地址偏移在10个数字之后,即40字节位置*/

//  .....(以下内容不变)

注意:本人实验过程中发现,如果几个头文件前后放的顺序不同,将导致找不到四个sem函数,血的教训,具体原因不知。

将已经修改的pc.cunistd.hsem.h文件拷贝到linux-0.11系统中

cd oslab_Lab5
sudo ./mount-hdc
cp ./pc.c ./hdc/usr/root/
cp ./linux-0.11/include/unistd.h ./hdc/usr/include/ 
cp ./linux-0.11/include/linux/sem.h ./hdc/usr/include/linux/
sudo umount hdc

编译及运行Bochs

cd oslab_Lab5/linux-0.11
make all
../run

在linux0.11的Bochs中编译生产者-消费者程序

gcc -o pc pc.c
./pc > 1.txt
sync

在Ubuntu中挂载hdc,将linux0.11输出的1.txt文件移动到Ubuntu中

sudo ./mount-hdc
sudo cp ./hdc/usr/root/1.txt ./

打开1.txt文件即可见到输出如下:
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/894821.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp x 样式 uvue css 样式节选

uniapp的下一版本uniapp x已经发布&#xff0c;uniapp x支持的样式为uvue css。 1、css模块 模块App支持情况备注背景与边框√不支持背景图盒子模型√Flex 布局√Inline 布局Inline-Block 布局Block 布局字体√详见Positioned 布局√CSS AnimationxCSS Transition√CSS Varia…

汇编实现逆序复制数据

一.实验目的 使其可以将10000H &#xff5e; 1000FH中的8个字&#xff0c;逆序复制到20000H &#xff5e; 2000FH中。 二.实验过程表示 三.部分汇编实现代码 mov ax,1000H ;将1000H放入AX寄存器中 mov ds,ax ;将AX寄存器中的内容放入DS寄存器中&#xff0c;这时候DS中存…

Ubuntu里彻底卸载UHD

查看已经安装的UHD版本uhd_find_devices&#xff0c;展示的是当前安装的 UHD 库版本所支持的设备信息&#xff0c;下载了多个版本的uhd但是又记不住安装的位置&#xff0c;想要把所有的uhd相关环境全都删掉&#xff0c;用下边这个命令看一下所有的uhd信息&#xff1a; apt lis…

如何设计开发RTSP直播播放器?

技术背景 我们在对接RTSP直播播放器相关技术诉求的时候&#xff0c;好多开发者&#xff0c;除了选用成熟的RTSP播放器外&#xff0c;还想知其然知其所以然&#xff0c;对RTSP播放器的整体开发有个基础的了解&#xff0c;方便方案之作和技术延伸。本文抛砖引玉&#xff0c;做个…

万户ezEIP企业管理系统 productlist.aspx SQL注入漏洞复现

0x01 产品简介 万户ezEIP是一种企业资源规划软件,旨在帮助企业管理其各个方面的业务流程。它提供了一套集成的解决方案,涵盖了财务、供应链管理、销售和市场营销、人力资源等各个领域。 0x02 漏洞概述 万户ezEIP企业管理系统 productlist.aspx 接口存在SQL注入漏洞,未经身…

【学术会议-6】激发灵感-计算机科学与技术学术会议邀您参与,共享学术盛宴,塑造明天的科技梦想!

【学术会议-6】激发灵感-计算机科学与技术学术会议邀您参与&#xff0c;共享学术盛宴&#xff0c;塑造明天的科技梦想&#xff01; 【学术会议-6】激发灵感-计算机科学与技术学术会议邀您参与&#xff0c;共享学术盛宴&#xff0c;塑造明天的科技梦想&#xff01; 文章目录 【…

【Linux】进程ID和线程ID在日志中的体现

在分析内核打印日志流程的时候&#xff0c;发现有时候同一个进程函数调用关系比较混乱&#xff08;因为只打印了进程号&#xff09;&#xff0c;现象就是一个函数走着走着不知道走哪里去了。 另一个现象是&#xff0c;在Linux启动Firefox的时候&#xff0c;启了大概80个进程&…

Rocky linux 修改ip地址, rocky服务器修改静态地址, rocky虚拟机修改ip

1. 更新yum yum update 2. 安装ifconfig yum install net-tools 3. 修改配置 vi /etc/NetworkManager/system-connections/ens33.nmconnection 将ipv4内容修改如下&#xff1a; # 自动改为手动 methodmanual # 网关为vm ware 查看网关地址 address你想改为的ip/24,网关 #dns不…

Linux基础-shell的简单实现

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 Linux基础-shell的简单实现 收录于专栏[Linux学习] 本专栏旨在分享学习Linux的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1, 全局变…

MatLab Desired port was :31515解决方案

前言&#xff1a;使用的MatLabR2022b今天突然出现了错误&#xff0c;在程序中打不开文件。后尝试了下面的方法&#xff0c;可以解决。 解决方法一&#xff1a; 搜索栏输入&#xff1a;firewall.cpl 找到相关项&#xff0c;右键属性&#xff0c;设置为允许。 之后就可以了…

超声波清洗机双十一值得买吗?2024四款顶级超声波清洗机推荐!

许多朋友们可能还在犹豫不决&#xff0c;思考着在双十一这个购物狂欢节里&#xff0c;究竟应该选择哪一款品牌的超声波清洗机来购买呢&#xff1f;不用担心&#xff0c;今天我将为大家详细解读2024年四款备受瞩目的顶级超声波清洗机&#xff0c;让你在双十一的购物狂欢中不再感…

地理空间与交通流量数据集:TaxiNYC、TaxiBJ、BikeDC和BikeNYC

目录 TaxiNYC数据集TaxiBJ数据集BikeDC数据集1. **数据来源与时间范围**2. **数据内容**3. **区域划分与站点处理**4. **图结构构建**5. **人群流动计算**6. **数据集的应用场景**7. **预测任务设置**8. **图的构建** BikeNYC数据集1. **数据来源与时间范围**2. **数据内容**3.…

单位评职称需要在指定媒体上投稿发表文章看我如何轻松应对

在职场中,晋升与评职称是一项不可或缺的任务,而在这个过程中,完成相关的投稿更是至关重要。作为单位的一名员工,当我得知自己需要在指定的媒体上发表文章以满足职称评审要求时,心中既期待又忐忑。起初,我选择了传统的邮箱投稿方式,然而却没想到,这条路竟让我倍感挫折。 刚开始,…

FlexMatch: Boosting Semi-Supervised Learning with Curriculum Pseudo Labeling

FlexMatch: Boosting Semi-Supervised Learning with Curriculum Pseudo Labeling 摘要:引言:背景3 flexMatch3.1 Curriculum Pseudo Labeling3.2 阈值预热3.3非线性映射函数实验4.1 主要结果4.2 ImageNet上的结果4.3收敛速度加速4.4 消融研究5 相关工作摘要: 最近提出的Fi…

◇【论文_20150225】 DQN_2015(nature) 〔Google DeepMind〕

整理代码 1&#xff1a;DQN CartPole_v1.ipynb https://www.nature.com/articles/nature14236 Human-level control through deep reinforcement learning 文章目录 摘要主体&#xff1a;要做什么 如何做的 要点keypoints实验 与 评估2 个指标和 各游戏的最好方法比较t-S…

数据湖新突破:Hudi让实时数据分析更高效!

开源数据湖对比 Hudi的使用收益 Hudi使用成效 Hudi内部机制 增量摄入与更新 Hudi使用一种混合日志存储模式(称为Copy-on-Write),可以同时处理基础数据文件(Parquet)和增量日志(HoodieLogFile)。以 MergeOnReadTable 的 upsert 操作为例,当有新数据到来时,Hudi会先将数据以行…

【OpenMMLab】MMagic入门

1. 概述 OpenMMLab 概述&#xff1a;OpenMMLab 是上海人工智能实验室的计算机视觉算法开源体系&#xff0c;是深度学习时代全球领域最全面、最具影响力的视觉算法开源项目&#xff0c;也是全球最大最全的开源计算机视觉算法库。特点&#xff1a; 丰富的算法库&#xff1a;已累…

第三天-128.最长连续序列

这道题我完全没有思路&#xff0c;求助gpt&#xff0c;让它给我思路&#xff1a; 这个问题要求找出数组中数字连续的最长序列&#xff0c;并且时间复杂度必须是 O(n)&#xff0c;可以采用 哈希集&#xff08;HashSet&#xff09;来帮助我们高效地判断数字是否存在。以下是解决…

AI周报(10.13-10.19)

AI应用-清华校友用AI破解162个高数定理 加州理工、斯坦福和威大的研究人员提出了LeanAgent——一个终身学习&#xff0c;并能证明定理的AI智能体。LeanAgent会根据数学难度优化的学习轨迹课程&#xff0c;来提高学习策略。并且&#xff0c;它还有一个动态数据库&#xff0c;有效…

Ubuntu如何显示pcl版本

终端输入&#xff1a; apt-cache show libpcl-dev可以看到&#xff0c;Ubuntu20.04&#xff0c;下载的pcl&#xff0c;应该都是1.10版本的