Heja Heja
首页
  • 本科相关(软院)
  • 硕士相关(计院&国卓院)
  • 嵌入式
  • 大模型
  • 网站搭建
首页
  • 本科相关(软院)
  • 硕士相关(计院&国卓院)
  • 嵌入式
  • 大模型
  • 网站搭建
  • 大模型

  • 嵌入式

    • makefile入门
    • 32标准库说明
    • xv6笔记
      • 1.1 进程与内存
      • 1.2 IO与文件描述符
      • 1.3 管道
      • 1.4 文件系统
      • 2.1 系统调用具体过程
      • 3.1 硬件相关
      • 3.2 内核空间
    • stm32配置高速时钟
  • 计算机
  • 嵌入式
2025-10-30
目录

xv6笔记

# xv6笔记

教程:https://xv6.dgs.zone/

实验代码:https://github.com/Wcacciatori/xv6-labs

# 第一章 操作系统接口

# 1.1 进程与内存

fork复制创建新进程

exec为替换进程内存,即fork复制过来的进程和父进程是完全一样,这样没有意义,所以需要exec来为新进程指定运行内容。

fork和exec分开的原因是中间可以进行io的重定向

# 1.2 IO与文件描述符

文件描述符是一个小整数,可以代指文件,设备,管道等等。是这些东西的抽象。进程可以通过打开一个文件,目录,设备,或创建一个管道或复制现有描述符来获取文件描述符。

每个进程都有一个文件描述符的私有空间

文件描述符“0”一般指标准输入(键盘)

文件描述符“1”一般指标准输出(屏幕)

文件描述符“3”一般指标准错误输出(屏幕)

和io相关的系统调用:

int read(int fd, char *buf, int n)//从文件描述符fd最多读取n字节存到buf中
int write(int fd, char *buf, int n)//从buf中写n字节到文件描述符fd中
dup(int oldfd)//创建一个现有文件描述符的副本指向相同的文件,拥有相同的偏移量,访问权限。
1
2
3

# 1.3 管道

相关系统调用:

int pipe(int p[])//创建一个管道,把read/write文件描述符放在p[0]和p[1]中
1

管道读端作为标准输入执行程序wc

int p[2];//声明管道读端和写端文件描述符存放的数组
char *argv[2];//存参数,一会重新给子进程指定程序用
argv[0] = "wc";
argv[1] = 0;
pipe(p);//创建一个管道,读端文件描述符为p[0]写端文件描述符为p[1]
if (fork() == 0) {//创建子进程,返回值为0进入子进程,为1继续父进程。在这里紧跟着if后面的是子进程内容,else里的是父进程要执行的
    close(0);//关闭标准输入,给等下的管道读端腾地方
    dup(p[0]);//复制一个管道描述符,新分配的文件描述符总是当前进程中编号最小的未使用描述符。故管道的读端替代了标准输入的位置
    close(p[0]);//关掉管道的读端文件描述符
    close(p[1]);//关掉管道的写端文件描述符
    exec("/bin/wc", argv);//参数1:程序所在地址  参数2:程序所需参数
} else {//父进程fork完返回1,故进入else
    close(p[0]);//关闭管道的读端因为用不到(父发子读)
    write(p[1], "hello world\n", 12);//往管道写端写内容
    close(p[1]);//写完关掉    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 1.4 文件系统

相关的系统调用:

int mkdir(char *dir)//创建一个新目录
int chdir(char *dir)//改变当前的工作目录,改到dir去
int mknod(char *file, int, int)//创建一个设备文件,参数1是文件地址,参数23是主设备号和次设备号
int fstat(int fd, struct stat *st)//将打开文件fd的信息放入*st
int stat(char *file, struct stat *st)//将指定名称的文件信息放入*st
    //结构体的内容:
    struct stat {
    int dev;     // 文件系统的磁盘设备
    uint ino;    // Inode编号
    short type;  // 文件类型
    short nlink; // 指向文件的链接数
    uint64 size; // 文件字节数
    };
int link(char *file1, char *file2)//为文件file1创建另一个名称(file2)
int unlink(char *file)//删除一个文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Linux系统调用之xargs:

通用命令格式:somecommand |xargs -item command

来个例子:echo "--help" | xargs cat

他等同于:cat --help

于是我们知道,xargs的作用是将管道前面的输出作为管道后面命令的参数。

# 第二章 系统调用

Lab 2-1思路:

添加一个系统调用的一般步骤

  1. 添加系统调用的原型,添加用户存根
  2. 添加系统调用编号
  3. 在内核空间写系统调用的功能代码

# 2.1 系统调用具体过程

通过中断实现

xv6系统调用由用户空间到内核空间的关键:entry(“trace”)相当于中断服务程序

# 第三章 页表

用户指令和内核指令使用的都是虚拟地址(页表形式)

# 3.1 硬件相关

虚拟地址和物理地址之间的转换是通过硬件(寄存器)来建立联系的。

虚拟地址64位,但是只使用低39位,39位中,高27位为页表索引值,低12位为偏移值。

通过页表索引值在页表中找到对应条目。页表条目包括44位物理地址和一些标志位。

最终的物理地址由条目中的44位做高位,虚拟地址中的偏移值做低位形成56位物理地址。

图示:

由此,每一页的大小就是虚拟地址中的偏移位形成的4096bit。索引值相当于页号。

实际的转换是通过三级页表实现的,如图:

分成三级的好处是:三级页表结构允许高效地映射大范围的虚拟地址空间到物理内存,同时在内存中只需要维护实际使用到的页表部分,节省了内存资源。

每个PTE包含标志位,这些标志位告诉分页硬件允许如何使用关联的虚拟地址。PTE_V​指示PTE是否存在:如果它没有被设置,对页面的引用会导致异常(即不允许)。PTE_R​控制是否允许指令读取到页面。PTE_W​控制是否允许指令写入到页面。PTE_X​控制CPU是否可以将页面内容解释为指令并执行它们。PTE_U​控制用户模式下的指令是否被允许访问页面;如果没有设置PTE_U,PTE只能在管理模式下使用。

# 3.2 内核空间

蹦床页面,相当于中断函数处理界面前的统一处理(保存现场,切换空间,执行代码)

lab3-2

  • 在​​struct proc​​中为进程的内核页表增加一个字段​,添加了K_pagetable.
  • 为一个新进程生成一个内核页表的合理方案是实现一个修改版的​​kvminit​​ ,这个版本中应当创造一个新的页表而不是修改​​kernel_pagetable​​。你将会考虑在​​allocproc​​中调用这个函数。
  • 确保每一个进程的内核页表都关于该进程的内核栈有一个映射。在未修改的XV6中,所有的内核栈都在​​procinit​​ 中设置。你将要把这个功能部分或全部的迁移到​​allocproc​​中。
  • 修改​​scheduler()​ ​ 来加载进程的内核页表到核心的​​satp​​ 寄存器(参阅​​kvminithart​​ 来获取启发)。不要忘记在调用完​​w_satp()​ ​ 后调用​​sfence_vma()​
  • 没有进程运行时​​scheduler()​ ​ 应当使用​​kernel_pagetable​
  • 在​​freeproc​​中释放一个进程的内核页表。待办,没有做好。
  • 你需要一种方法来释放页表,而不必释放叶子物理内存页面。
  • 调式页表时,也许vmprint能派上用场
  • 修改XV6本来的函数或新增函数都是允许的;你或许至少需要在***kernel/vm.c***和***kernel/proc.c***中这样做(但不要修改​***kernel/vmcopyin.c***​, ​***kernel/stats.c***​, ​***user/usertests.c***​, 和​***user/stats.c***)
  • 页表映射丢失很可能导致内核遭遇页面错误。这将导致打印一段包含sepc=0x00000000XXXXXXXX​的错误提示。你可以在***kernel/kernel.asm***通过查询XXXXXXXX来定位错误。

lab3-3

# lab中遇到的一些问题

  • lab1-5问题:忘记排除 . 目录,导致一直循环遍历根目录

  • lab1-6 make: *** No rule to make target 'user/_xargs', needed by 'fs.img'. Stop.
    ​

    • 原因,把args.c目录不小心放在了user目录之外。
  • 内核页表和内核栈的关系?栈需要空间,这个空间正是和页表对应。

  • 释放进程内核页面时,为什么不用把叶子物理页面释放了?

    • 因为你的叶子物理内存中内核区的会被其他内核页表使用。(每个进程都建立了到设备地址的映射) 程序独有的物理页面可以用之前的代码释放
    • 叶子???:第三级页表的页表项
  • 物理地址>>12=第三级页表的物理地址[55-12](0-11为0)=第二级页表项[53-10]

  • 第二级页表项>>9=页表项的索引

  • lab3-2

    • 当vm.c顶部头文件包含顺序如下时
    #include "param.h"
    #include "types.h"
    #include "memlayout.h"
    #include "elf.h"
    #include "riscv.h"
    #include "defs.h"
    #include "fs.h"
    #include "proc.h"
    #include "spinlock.h"
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

有如下报错:

当vm.c顶部头文件包含顺序如下时:

#include "param.h"
#include "types.h"
#include "memlayout.h"
#include "elf.h"
#include "riscv.h"
#include "defs.h"
#include "fs.h"
#include "spinlock.h"//把自旋锁结构定义的头文件置于proc.h之前
#include "proc.h"
1
2
3
4
5
6
7
8
9

报错解决。

defs.h中是对自旋锁结构的一个向前声明,spinlock.h中是自旋锁结构的定义,proc.h中使用了自旋锁结构。第一次的尝试中,先包含proc.h,也就是先使用了一下自旋锁结构,此时编译器还不知道自旋锁结构的具体定义是什么,所以会产生报错:lock是不完整的类型。第二次尝试中,自旋锁的定义放在了使用前,就没问题了。

上次更新: 2025/11/2 06:53:08
32标准库说明
stm32配置高速时钟

← 32标准库说明 stm32配置高速时钟→

最近更新
01
Transformer
11-21
02
PINN
11-07
03
GPT
11-03
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Heja
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式