惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 SpinalHDL上板过程记录 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 自定义CUDA实现PyTorch算子的四种简单方法 游戏玩后感:星空的记忆 游戏玩后感:9nine 游戏玩后感:AtriMyDearMoments 游戏玩后感:极限脱出 游戏玩后感:魔女的夜宴 SSH实现多跳代理 动漫观后感:向山进发 flv重封装H264、AAC流 动漫观后感:夏日重现 CSP模板 游戏玩后感:海沙风云 动漫观后感:灵能百分百 游戏玩后感:交响乐之雨 游戏玩后感:爱上火车LastRun 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 Ultra96V2开发板简单使用 SpinalWorkshop实验笔记(三) SpinalWorkshop实验笔记(二) SpinalWorkshop实验笔记(一) PYNQ开发板上使用USB声卡+OSS兼容层播放音频 TestOS移植K210开发板 rCore-Tutorial-Book-v3学习笔记(七) 动漫观后感:凉宫春日的忧郁 rCore-Tutorial-Book-v3学习笔记(♭七) rCore-Tutorial-Book-v3学习笔记(六) rCore-Tutorial-Book-v3学习笔记(五) rCore-Tutorial-Book-v3学习笔记(四) rCore-Tutorial-Book-v3学习笔记(三) rCore-Tutorial-Book-v3学习笔记(一) 游戏玩后感:RewritePlus MIT-6.S081-2020实验(xv6-riscv64)十一:net MIT-6.S081-2020实验(xv6-riscv64)十:mmap MIT-6.S081-2020实验(xv6-riscv64)九:fs MIT-6.S081-2020实验(xv6-riscv64)八:lock MIT-6.S081-2020实验(xv6-riscv64)七:thread MIT-6.S081-2020实验(xv6-riscv64)六:cow MIT-6.S081-2020实验(xv6-riscv64)五:lazy MIT-6.S081-2020实验(xv6-riscv64)四:traps MIT-6.S081-2020实验(xv6-riscv64)三:pgtbl MIT-6.S081-2020实验(xv6-riscv64)二:syscall 动漫观后感:吹响吧上低音号 MIT-6.S081-2020实验(xv6-riscv64)一:util 快速生成网络mp4视频缩略图技术 使用OpenCV对图片进行特征点检测和匹配 git基本操作
rCore-Tutorial-Book-v3学习笔记(二)
VnYzm · 2021-02-25 · via

概述

第二部分是实现一个批处理系统。批处理系统顾名思义就是能输入好几个程序,然后对这些程序依次执行的操作系统。重点不是在批处理,而是在输入用户程序,这就要求用户程序和我们的系统有一种隔离,所以需要在这一部分的系统实现用户态和内核态的切换。

内容

这一部分的代码文件比上一部分多了好几个,大致说明一下功能:

  • 用户态文件包括hello_world.clib.cuser.h
    • hello_world.c是用户写的程序,和普通程序一样,主函数是int main() { ... },因为支持的系统调用有限,目前只有往标准输出打印一个Hello word!然后返回退出的功能。
    • lib.c是包含用户程序的入口函数,在函数中执行main函数,然后调用exit()系统调用退出,另外还包含了清零bss段和系统调用函数在用户态的实现(即设置对应寄存器然后ecall的过程)。
    • user.h包含一些类型、宏定义和函数头。
  • 内核态文件包括batch.centry.Skernel.hsbicall.cprintf.csyscall.clink_app.Smod.ctrap.S
    • batch.c是批处理系统的核心,包含初始化、加载程序(由于目前没有文件系统,所以用户程序都是和内核程序一起加载到内存中,等到要执行时将用户程序复制到对应的位置,从那个位置开始执行)、程序管理等功能。
    • entry.S和实验一一样,是内核的入口点。
    • kernel.h包含一些类型、宏定义和函数头。
    • sbicall.h包含了对SBI的调用。
    • printf.c和实验一一样,实现了输出函数和panic函数。
    • syscall.c包含了对各类系统调用的实际处理。
    • link_app.S将用户程序加载到内核程序的数据段中,并定义了一些符号供C语言调用。
    • mod.c包含了对trap的处理,如果是系统调用则执行syscall.c里那些函数,否则说明是其他异常,直接panic。
    • trap.S包含了进出内核态时保存现场的汇编代码。

为了写起来简单,代码基本没怎么对错误情况和异常进行处理,因此可能存在不少漏洞。下面是一些注意点:

首先是lib.c中的入口函数:

int main();
__attribute__((section(".text.entry")))
void _start() {
    clear_bss();
    exit(main());
}

注意第二行,它等价于Rust里的#[link_section = ".text.entry"],作用是把下面的函数放到字符串指定的段里,这有什么作用呢,看下用户态的链接脚本:

OUTPUT_ARCH(riscv)
ENTRY(_start)

BASE_ADDRESS = 0x80400000;

SECTIONS
{
    . = BASE_ADDRESS;
    .text : {
        *(.text.entry)
        *(.text .text.*)
    }
    ...

注意到代码段专门把.text.entry这一段列出来放在最前面,所以如果指定了某函数是在这个段里的,那么链接器就会把这个函数放在最前面,也就是BASE_ADDRESS的位置。事实上,链接脚本里的ENTRY在内核编译中是没有意义的,这个“入口点”的定义放在ELF文件的元信息里,而我们在使用objcopy的过程中会把ELF的元信息去掉,那么ENTRY指定的东西也就被去掉了。

在内核编译中,指令的地址才是最重要的,如果指定用户程序从0x80400000开始执行,那么用户程序0x80400000位置的第一条指令就是其入口点。而链接脚本的段定义配合语言中的段名指定正是决定变量和函数放在哪个内存位置的方法。如果不加段名指定,像上面的程序就可能把其他的函数放在0x80400000这个位置,那么内核在执行用户程序时就会先执行这个函数,然后当这个函数运行结束后返回时内核就傻了,不知道该返回到哪,只有_start函数最后有系统调用exit,能告诉内核运行下一个用户程序。

然后是batch.c里的app_init_context函数:

TrapContext *app_init_context(usize entry, usize sp, TrapContext *cx) {
    usize sstatus; asm volatile("csrr %0, sstatus":"=r"(sstatus));
    sstatus &= ~(1L << 8);
    for (int i = 0; i < 32; i++) cx->x[i] = 0;
    cx->sepc = entry; cx->sstatus = sstatus;
    cx->x[2] = sp; return cx;
}

C语言调库麻烦,所以这里就硬编码了,sstatus的第8位(从0开始)指定程序所处的态,0为用户态,1为内核态。

然后是run_next_app,虽然app_init_context的参数和教程不太一样,但思想都是仿照教程来的:

void run_next_app() {
    load_app(current_app); current_app++;
    __restore((usize)app_init_context(
                APP_BASE_ADDRESS,
                (usize)USER_TOP,
                (TrapContext *)(KERNEL_TOP - sizeof(TrapContext))
                ));
}

结合教程提出的问题:

有兴趣的读者可以思考: sscratch 是何时被设置为内核栈顶的?

这里谈一下这一部分栈是怎么切换的。首先明确一点,目前用到的地址全是物理地址,没有用户态和内核态之间的页表切换,riscv和x86不一样,也没有段寄存器这些东西,所以在本部分中sp寄存器指向哪哪就是当前的栈。KERNEL_TOP和USER_TOP虽然为了反映程序中栈从高向低增长的特性,用的都是“TOP”,但是为了贴合现实生活中的栈以方便理解,下面都用“内核栈底”、“用户栈底”来描述。

在程序启动时,和实验一一样,sp指向了boot_stack,那么那里就是入口函数load_all所用的栈。然后load_all调用run_next_app,后者调用app_init_context,这里对TrapContext的操作修改的都是内核栈的栈底,把USER_TOP传给了TrapContext里的sp寄存器,并返回了TrapContext的指针。

然后进入restore,第一句mv sp, a0,将TrapContext的指针传给了sp,这一步就相当于重置了内核栈,把内核栈重置为栈底只有一个TrapContext的状态,同时此处也将程序当前的栈从boot_stack转到内核栈了。然后各条指令都是在处理内核栈里的那一个TrapContext。之后csrw sscratch, t2将刚才传进x[2]的用户栈栈底传给sscratch寄存器,最后一句csrrw sp, sscratch, sp,sscratch被设置为了内核栈底,sp也指向了用户栈底。这就是上面问题的解答。比较有趣的地方是每次run_next_app的时候都会重置一下内核栈,这一方面是节省空间,另一方面也是为了写起来便捷,不过在后面的程序中应该会改掉。

trap.S有个小细节,就是因为代码里用到了宏,如果要用符号常量调用宏,须在代码最前面加上.altmacro,原因我也不知道,这方面资料太少了,如果不加,宏就会直接把参数替换成符号常量的名字,而不是其代表的值。

最后是trap_handler函数:

TrapContext *trap_handler(TrapContext *cx) {
    usize scause, stval;
    asm volatile (
            "csrr %0, scause\n"
            "csrr %1, stval\n"
            :"=r"(scause), "=r"(stval)
            );
    switch (scause & (~(1L << 63))) {
        case 8:
            cx->sepc += 4;
            cx->x[10] = syscall(cx->x[17], cx->x[10], cx->x[11], cx->x[12]);
            break;
        default:
            panic("Other trap");
    }
    return cx;
}

同样的,scause的处理我也进行了硬编码,scause的第0到62位存储trap的原因,8表示是来自用户态的系统调用。