6.1810: Operating System Engineering <Lab2 syscall: System calls>

news2024/11/20 12:35:14

课程链接:6.1810 / Fall 2023

一、本节任务 

二、要点

操作系统要满足三要素:并发、隔离、交互(multiplexing, isolation, and interaction)。

宏内核(monolithic kernel:是操作系统核心架构的一种,此架构的特性是整个核心程序都是以核心空间(Kernel Space)的身份及监管者模式(Supervisor Mode)来运行。宏内核中各个部分通信十分容易,缺点就是一旦操作系统某个部分出问题,整个内核都可能直接崩溃。

为了减少内核中出现错误的风险,操作系统设计者减少在 Supervisor Mode 下运行的操作系统代码,并在用户模式运行大部分操作系统模块。这种内核组织被称为微内核(microkernel

IPC(inter-process communication):进程间通信

xv6使用硬件实现的页表(page table)来给每个进程提供自己的地址空间。riscv页表把虚拟地址(riscv指令操作的地址)转换成物理地址(cpu发送到主存储器的地址)。

xv6为每个进程的地址空间维护一个单独的地址空间,包括从虚拟地址零开始的进程的用户内存。首先是指令,然后是全局变量,然后是堆栈,最后是一个“堆”区域(对于malloc),进程可以根据需要进行扩展。

At the top of the address space xv6 reserves a page for a trampoline and a page mapping the process’s trapframe . Xv6 uses these two pages to transition into the kernel and back; the trampoline page contains the code to transition in and out of the kernel and mapping the  trapframe is necessary to save/restore the state of the user process.

在结构体 struct proc(kernel/proc.h)中保存了进程的各种状态。一个进程最重要的内核状态包括它的页表,它的内核栈,和它的运行状态。

每个进程有两个栈:用户栈和内核栈,当进程执行用户指令时,只会使用用户栈,此时内核栈是空的;当进程进入内核空间时(系统调用或中断),内核代码(如系统函数 sys_open())在进程的内核栈里面执行。当进程在内核态时,它的用户栈仍然包含之前保存的数据,内核栈是独立的,所以即使进程破坏了其用户堆栈,内核也可以执行。

在 riscv 中,进程可以通过 ecall 指令来进行系统调用,ecall 指令会提高硬件的特权级别,并且跳转到内核定义的入口点。入口点上的代码会切换到一个内核堆栈,并执行实现系统调用的内核代码。当系统调用完成,内核会切回用户栈并且通过 sret 指令返回用户空间,sret 指令降低硬件的特权级别,并且返回到用户进行系统调用的下一条指令继续执行。

总之,进程有两个设计思想:一个是地址空间,给每个进程都拥有自己的内存空间的错觉,另一个是线程,给每个进程都拥有自己的 CPU 的错觉。

xv6 如何启动

当机器上电,它会运行一个存储在只读内存中的引导程序(boot loader),引导程序会把 xv6 内核搬运到内存中,然后,在机器模式下,cpu 执行 xv6 的 _entry(kernel/entry.S),在开始时,riscv 会禁用分页硬件,虚拟地址直接映射到物理地址。

引导程序会把 xv6 内核搬运到内存物理地址 0x80000000 处,因为 0x0 到 0x80000000 之间包含 I/O 设备。

在 entry.S 中,会先初始化对应 cpu hart 的栈指针,然后跳转到 start(kernel/start.c)处执行。

在 start() 中,先将 mstatus 寄存器的 MPP(Previous Privilege mode)位设置成 Supervisor,然后将 mepc 寄存器设置为 main(kernel/main.c)函数的地址。这样的话在使用 mret 指令就可以将特权级别切换为 Supervisor,并且跳转到 main() 处执行。最后 start 中还会配置时钟中断,配置 machine-mode 的 mtvec寄存器。

在 main() 中,初始化许多设备和子系统后,将会调用 userinit(kernel/proc.c)来创建第一个进程。

在 userinit() 中,创建的进程代码为 initcode 里面的内容(user/initcode.S),在 initcode 中会请求 exec() 系统调用创建 init(user/init.c)进程,在 init.c 中会先创建 fd 0、1、2,然后 fork() 一个子进程来执行 shell,至此,整个系统启动完成。

三种IO

BIO(阻塞IO):线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成。
NIO(非阻塞IO):线程发起IO请求,立即返回;内核在做好IO操作的准备之后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。
AIO(异步非阻塞IO):线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做IO操作完成或者失败。


同步与异步

这两个概念与消息的通知机制有关。 

同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。比如,调用readfrom系统调用时,必须等待IO操作完成才返回。

异步:当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。比如:调用aio_read系统调用时,不必等IO操作完成就直接返回,调用结果通过信号来通知调用者。

阻塞和非阻塞

阻塞与非阻塞与等待消息通知时的状态有关。

阻塞:阻塞是指调用结果返回前,进程会被挂起,直到调用结束得到结果再唤醒进程。

非阻塞:非阻塞指在不能立刻获得返回结果之前,不会阻塞进程,进程可以立即返回,并且设置相应的 erron。

三、Lab:System calls

切换到 syscall 分支: 

git fetch
git checkout syscall
make clean

3.1 Using gdb

该部分主要教你怎么使用 gdb 来调试 xv6。

第一步

准备两个 shell 窗口。

第二步

在一个 shell 窗口内,运行如下指令(在 xv6 仓库里面运行):

make qemu-gdb

运行后最下方会出现 tcp::26000 的字样,记住这个端口号 26000。 

第三步

在另外一个 shell 运行如下命令(也要在 xv6 仓库里面运行):

gdb-multiarch

然后在 gdb 命令窗口输入如下命令:

target remote localhost:26000

接下来就可以开始调试了,使用 file 命令可以指定调试的文件:

file kernel/kernel

使用 b 命令设置断点:

b syscall

使用 c 让程序执行,直到断点处停下:

使用 layout src/asm 查看程序当前位置的源码或者汇编:

layout src

使用 backtrace 打印函数栈,如下,可以看到我们设置断点的 syscall() 在栈顶,usertrap() 则在其下,说明在 usertrap() 函数里面调用了 syscall():

使用 n 命令单步执行,跨过 struct proc *p = myproc() 这一行后,然后执行如下命令查看 p 指针指向的内容:

p /x *p

3.2 System call tracing (moderate)

这部分要实现 trace 命令,该命令能够追踪某条命令所执行的系统调用,并且打印出来,入参是一个 mask,指定要追踪哪些系统调用。

首先在 user/user.h 中定义系统调用,该文件中的定义是提供给用户调用的: 

// user/user.h
int trace(int);

其对应实现在 usys.S 中,在执行 make 后,usys.S 会由 usys.pl 脚本生成,这个汇编函数首先将系统调用号 SYS_trace 放入寄存器 a7 中,然后执行 ecall 指令请求系统调用:

.global trace
trace:
 li a7, SYS_trace
 ecall
 ret

执行 ecall 指令后,系统会进入内核态,此时即可执行真正的系统函数,先到 syscall.h 中定义 trace 的系统调用号:

然后在 syscall.c 中加入 trace:

然后到 sysproc.c 定义系统函数 sys_trace():

uint64
sys_trace()
{
        int mask;
        argint(0, &mask);
        myproc()->trace_mask = mask;
        if(((1 << SYS_trace) & mask) == (1 << SYS_trace))
        {
                printf("%d: syscall trace -> 0\n", myproc()->pid);
        }
        return 0;
}

最后修改 syscall.c 的 syscall() 函数即可:

void
syscall(void)
{
  int num, mask;
  struct proc *p = myproc();

  num = p->trapframe->a7;
  mask = p->trace_mask;

  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    // Use num to lookup the system call function for num, call it,
    // and store its return value in p->trapframe->a0
    p->trapframe->a0 = syscalls[num]();
    if(((1 << num) & mask) == (1 << num)){
        printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

3.3Sysinfo (moderate)

这部分也要实现一个系统调用,可以返回一个结构体给用户,结构体里面包含了正在使用的进程个数,以及当前的空闲内存,这部分主要注意的地方就是内核空间的内存用户是访问不了的,所以需要使用 copyout 函数将用户空间的结构体拷贝到用户空间上,然后把结构体在用户空间上的地址返回即可。

系统调用的添加和上面一样。系统函数在 sysfile.c 里面声明:

uint64
sys_sysinfo(void)
{
        uint64 si_p; // user pointer to struct sysinfo
        struct sysinfo si;
        struct proc *p = myproc();

        argaddr(0, &si_p);
        si.freemem = get_free_memory();
        si.nproc = get_nproc();

        if(copyout(p->pagetable, si_p, (char *)&si, sizeof(si)) < 0)
                return -1;

        return 0;
}

获取正在使用的进程个数:

/* get the number of processes whose state is not UNUSED */
uint64 get_nproc(void)
{
        struct proc *p;
        uint64 num = 0;
        for(p = proc; p < proc + NPROC; p++)
        {
                if(p->state != UNUSED)
                {
                        num++;
                }
        }
        return num;
}

获取空闲内存:

/* collect the amount of free memory */
uint64
get_free_memory(void)
{
        struct run *r;
        uint64 num = 0;
        acquire(&kmem.lock);
        r = kmem.freelist;
        while(r)
        {
                num++;
                r = r->next;
        }
        release(&kmem.lock);
        return num * PGSIZE;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker部署kerberos,群晖nas中nfs开启kerberos校验

背景 nas开启nfs存储共享&#xff0c;默认情况下只能给IP/24做限制, 达不到安全效果 需要增加kerberos策略校验&#xff0c;并且持久化kerberos数据&#xff0c;避免容器重启丢失数据 环境描述 宿主机系统&#xff1a;CentOS Linux release 7.9.2009 (Core) Docker版本&#xf…

CentOS7根分区扩容之一

Centos默认根分区50G&#xff0c;很快接近100%&#xff0c;如果你的系统使用了全部磁盘&#xff0c;文件系统是xfs&#xff0c;根分区和/home都是逻辑卷&#xff0c;那么在没有额外的磁盘增加情况下&#xff0c;可以从/home卷中切分一部分空间增加到根分区空间。 1.由于xfs格式…

【Android】MotionLayout实现动画

MotionLayout不断地更新&#xff0c;文章并不适用全部最近的更新内容。 文章目录 引入 ConstraintSetTransitionManager和MotionLayout有什么区别&#xff1f; 使用ConstrainSet(属性类似于ConstrainLayout) Transition属性OnClickOnSwipeKeyFrameSetKeyPositionKeyAttribute C…

220V转12V60MA红外雷达降压芯片:节能、高效、多功能的解决方案

220V转12V60MA红外雷达降压芯片&#xff1a;节能、高效、多功能的解决方案 在我国&#xff0c;红外雷达技术已广泛应用于各种小型家用电器中&#xff0c;如遥控器、智能家居等。为了满足这些设备对电源电压的需求&#xff0c;推出了一款220V转12V60MA红外雷达降压芯片&#xf…

免费通配符证书

通配符SSL证书&#xff0c;也称为泛域名证书&#xff0c;能够在一个证书中保护一个主域名及其所有下一级子域名。例如&#xff0c;如果你拥有一个名为example.com的主域名和多个子域名如mail.example.com、blog.example.com等&#xff0c;只需要一个通配符SSL证书就可以覆盖所有…

kkFileView 从源码编译最新安装包

目录 一、前言二、拉取 kkFileView 最新代码三、kkFileView 打包 一、前言 kkFileView 是一个开源的附件在线预览项目&#xff0c;可以让你的项目方便的在线预览附件&#xff0c;包括比如&#xff1a;doc、docx、pdf、xml、xls、xlsx、ppt、pptx、zip、png、jpg、txt、mp4等常…

Windows C++ VS2022 OpenVINO 实例分割 Demo

目录 效果 模型信息 项目 代码 下载 其他 Windows C VS2022 OpenVINO 实例分割 Demo 效果 模型信息 Model Properties ------------------------- date&#xff1a;2023-09-07T17:11:46.798385 description&#xff1a;Ultralytics YOLOv8n-seg model trained on coco.…

STM32 定时器TIM

单片机学习 目录 文章目录 前言 一、TIM简介 二、STM32的三种定时器 2.1基本定时器 2.1.1定时中断功能 1. 时钟源 2. 预分频器 3. 计数器 4. 自动重装寄存器 5.更新中断和更新事件 2.1.2主模式触发DAC功能 2.2 计数模式 2.2通用定时器 2.2.1 时钟源 外部时钟模式2 外部时钟模式…

人才缺口达150万!云计算凭什么这么火?

《中国互联网发展报告2022》指出&#xff0c;2021年&#xff0c;我国云计算市场规模达到3229亿元&#xff0c;增速为54.4%。未来5年内&#xff0c;我国云计算产业将面临高达近150万的人才缺口&#xff0c;预计未来市场仍将保持30%的增速。与此同时&#xff0c;随着大数据、人工…

C/C++11 语法/概念易错总结(1)

文章目录 缺省参数函数重载引用引用和指针内联宏的优缺点auto范围forNULL和nullptr 缺省参数 半缺省参数必须从右往左依次来给出&#xff0c;不能间隔着给 void Func(int a, int b 10, int c 20){cout<<"a "<<a<<endl;cout<<"b &…

SQL 算术运算符:加法、减法、乘法、除法和取模的用法

SQL Server中的存储过程 什么是存储过程&#xff1f; 存储过程是一段预先编写好的 SQL 代码&#xff0c;可以保存在数据库中以供反复使用。它允许将一系列 SQL 语句组合成一个逻辑单元&#xff0c;并为其分配一个名称&#xff0c;以便在需要时调用执行。存储过程可以接受参数…

《C++PrimerPlus》第11章 使用类

11.1 运算符重载 11.2 计算时间&#xff1a;一个运算符重载示例 运算符重载示例&#xff08;计算时间&#xff09; 头文件mytime0.h #ifndef __MYTIME0__H__ #define __MYTIME0__H__ #include <iostream> using namespace std;class Time {private:int hours;int minu…

【ECCV 2022】《Transformers as Meta-learners for Implicit Neural Representations》

文章目录 一、动机二、相关工作三、方法四、实验部分五、Does the INR Exploit Data Structures?六、结论 一、动机 \quad 与像素、体素和网格等离散数据表示相比&#xff0c;INRs不需要依赖于分辨率的二次或三次存储。它们的表示能力并不依赖于网格分辨率&#xff0c;而是依赖…

《ChatGPT实操应用大全》探索无限可能

&#x1f5e3;️探索ChatGPT&#xff0c;开启无限可能&#x1f680; 文末有免费送书福利&#xff01;&#xff01;&#xff01; ChatGPT是人类有史以来最伟大的发明。他能写作、绘画、翻译、看病、做菜、编程、数据分析、制作视频、解高等数学题…&#xff0c;他会的技能…

网站上https协议,nginx配置SSL,443端口

nginx配置ssl 要给自己的网站上ssl证书&#xff0c;使用https协议。首先你需要有证书文件&#xff0c;这个文件是你买的服务&#xff0c;买过之后别人会给你。 就是这样的文件&#xff1a; 然后你就把文件上传到服务器的一个位置&#xff0c;你记住这个位置&#xff0c;后面配…

树莓派3B+ PCB叠层设计

板子废了&#xff0c;用电磨切了下&#xff0c;看看是什么叠层。 由于有BCM43455 WIFI芯片&#xff0c;这个是0.3ball 0.4pitch&#xff0c;肯定是要用盲孔布线的。 然后根据这个切面看&#xff0c;板子是6层的&#xff0c;外层内层铜厚应该是一样的 1-2层介质特别薄竟然<1o…

unicloud云函数url化后,客户端通过url地址向云函数发送数据流并传递到云存储中

在不久前录制过这样一门课程&#xff0c;使用uniapp生态开发API接口&#xff0c;通过这套课程&#xff0c;你不需要后后端Java、Python、PHP等后端语言&#xff0c;你只需要用前端的知识就可以构建这样一套API接口&#xff0c;而且使用uniapp生态开发接口更简单高效&#xff0c…

共享单车停放(简单的struct结构运用)

本来不想写这题的&#xff0c;但是想想最近沉迷玩雨世界&#xff0c;班长又问我这题&#xff0c;就草草写了一下 代码如下&#xff1a; #include<stdio.h> #include<math.h> struct parking{int distance;int remain;int speed;int time;int jud; }parking[50]; …

2022年1月14日 Go生态洞察:Go 1.18 新教程探索

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

java+springboot停车场小区车库租赁预订系统ssm+jsp

该平台为客户和业主提供等信息服务平台的运营方&#xff0c;管理方&#xff0c;如何通过车库平台建立实现优化管理的方法提供参考。能够实现在一个相对广阔的地域内&#xff08;例如一座城市)的多个停车场的随意停车。管理平台会统一调度车位资源&#xff0c;自动进行交易结算。…