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

课程链接: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;
}

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

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

相关文章

开源图床Qchan本地部署远程访问,轻松打造个人专属轻量级图床

文章目录 前言1. Qchan网站搭建1.1 Qchan下载和安装1.2 Qchan网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar云端设置2.2 Cpolar本地设置 3. 公网访问测试总结 前言 图床作为云存储的一项重要应用场景&#xff0c;在大量开发人员的努力下&#xff0c;已经开发出大…

基于机器深度学习的交通标志目标识别

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 智能交通系统&#xff08;ITS&#xff09;&#xff0c;包括无人驾驶车辆&#xff0c;尽管在道路…

最受好评的 5 款最佳安卓手机数据恢复工具

您想从 Android 手机中恢复永久删除或丢失的数据和文件吗&#xff1f;您可以借助顶级 Android 数据恢复软件来完成此操作。如果您的数据已被删除、损坏或格式化&#xff0c;则可以将已删除的文件恢复到您的 Android 手机中。Android 设备丢失文件的原因可能有多种。 不管你的手…

非全自研可视规则引擎RuleLinK可视化之路

导读 上一篇《非全自研可视化表达引擎-RuleLinK》介绍了RuleLink的V1.0版本&#xff0c;虽说一定程度上消除了一些配置相关的样板式代码&#xff0c;也肉眼可见的消除了一些研发资源的浪费&#xff1b;RuleLink的初衷是让业务配置变得简单&#xff0c;是面向运营同学。要真正面…

聚观早报 |国行PS5轻薄版开售;岚图汽车11月交付7006辆

【聚观365】12月2日消息 国行PS5轻薄版开售 岚图汽车11月交付7006辆 比亚迪推出12月限时优惠 特斯拉正式交付首批Cybertruck 昆仑万维发布「天工 SkyAgents」平台 国行PS5轻薄版开售 索尼最新的PlayStation5主机&#xff08;CFI-2000型号组-轻薄版&#xff09;国行版本正…

OpenHarmony 关闭息屏方式总结

前言 OpenHarmony源码版本&#xff1a;4.0release 开发板&#xff1a;DAYU / rk3568 一、通过修改系统源码实现不息屏 修改目录&#xff1a;base/powermgr/power_manager/services/native/profile/power_mode_config.xml 通过文件中的提示可以知道DisplayOffTime表示息屏的…

EasyExcel两行表头

例子&#xff1a; 代码&#xff1a; StorageService localStorageService storageFactory.getLocalStorageService();String path "";// 文件信息String dateTime DateUtils.formatTimestampToString(new Date());String title "xxx统计";String fil…

前端学习系列之CSS

目录 CSS 简介 发展史 优势 基本语法 引用方式 内部样式 行内样式 外部样式 选择器 id选择器 class选择器 标签选择器 子代选择器 后代选择器 相邻兄弟选择器 后续兄弟选择器 交集选择器 并集选择器 通配符选择器 伪类选择器 属性选择器 CSS基本属性 优…

七、ZooKeeper选举机制

目录 1、概念 2、全新集群选举 3、非全新集群选举 zookeeper默认的算法是FastLeaderElection,采用投票数大于半数则胜出

接口测试基础知识

一、接口测试简介 什么是接口测试&#xff1f; 接口测试是测试系统组件间接口的一种测试&#xff0c;主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。 测试的重点&#xff1a; 检查数据的交换&#xff0c;传递和控制管理过程&#xff1b;检查系统间的相互…

OpenTelemetry系列 - 第1篇 相关概念

目录 一、背景二、概念2.1 Traces & Span2.2 Metrics2.3 Logs2.4 Baggage2.5 OTel2.6 OTLP2.7 Resources2.8 Instrumentation Scope2.9 Sampling 三、核心组件 一、背景 OpenTelemetry是一个可观察性框架和工具包&#xff0c;旨在创建和管理遥测数据&#xff0c;如跟踪、指…

一个用c#瞎写的sftp工具

0.下载地址 https://wwus.lanzouj.com/iOZUv1gkgpze 密码:123456 1.能进行单个和批量下载, 没有弄上传 2.速度奇差,可能是某些地方没弄好.有一定的进度显示,但是不太准. 3.很多地方没弄好,有能力的自己弄一下 4.在app.config文件配置sftp

深度学习第4天:感知机模型

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 ​ 文章目录 感知机模型介绍 神经网络搭建感知机 结构 准备训练数据 感知机的损失函数与优化方法 测试结果 完整代码 多层感知机 结语 感知机模…

火车头插件-最全火车头伪原创图片存储等插件

火车头插件。作为一个功能强大的工具&#xff0c;火车头插件以其众多特色引起了广大用户的关注。而其中&#xff0c;火车头采集器更是备受瞩目。我们将分享火车头插件的安装教程&#xff0c;还会深入了解火车头伪原创插件的应用。 火车头插件安装教程 我们来安装火车头插件&a…

JavaScript 内存泄漏的检测与防范:让你的程序更稳定

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

HT7713 3A同步降压变换器 快速瞬态响应

HT7713 是一款 3A 降压转换器&#xff0c;具有Z少的外部元件和低关断电流。HT7713具有快速瞬态响应的特点&#xff0c;输出电容器采用低 ESR &#xff08;聚合物&#xff09;或超低 ESR&#xff08;陶瓷&#xff09;&#xff0c;无需外部补偿。 HT7713在轻载时以脉冲跳跃模式工…

Spring Security 6.x 系列(8)—— 源码分析之配置器SecurityConfigurer接口及其分支实现

一、前言 本章主要内容是关于配置器的接口架构设计&#xff0c;任意找一个配置器一直往上找&#xff0c;就会找到配置器的顶级接口&#xff1a;SecurityConfigurer。 查看SecurityConfigurer接口的实现类情况&#xff1a; 在 AbstractHttpConfigurer 抽象类的下面可以看到所有…

HT81298 集成免滤波器调制D类音频功放

HT81298是一款内置升压的立体声D类音频功率放大器&#xff0c;HT81298内部集成免滤波器调制技术&#xff0c; 能够直接驱动扬声器&#xff0c;内置的关断功能使待机 电流Z小化&#xff0c;还集成了输出端过流保护、片内 过温保护、输入电源欠压异常保护、升压电压 过压保护等功…

Docker 环境中 Spring Boot 应用的 Arthas 故障排查与性能优化实战

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

Guns快速开发平台 Shiro反序列化漏洞复现

0x01 产品简介 Guns是一个现代化的 Java 应用开发框架&#xff0c;基于主流技术Spring Boot 2 Vue3&#xff0c;Guns的核心理念是提高开发人员开发效率&#xff0c;降低企业信息化系统的开发成本。 0x02 漏洞概述 Guns v5.1 及之前的版本存在 shiro 反序列化漏洞&#xff0c;…