当前位置: 首页 > news >正文

操作系统-System Call

操作系统有三大职责:硬件资源抽象管理、系统资源调度、提供用户接口

xv6系统的启动流程

1. 进行引导加载:RISC-V上电之后,首先运行只读内存中的引导加载程序(Boot Loader),会将xv6内核加载到物理地址0x80000000(避开0x0-0x80000000的I/O设备地址区间),进入机器模式;

2. 初始化:此时在机器模式下(操作系统三种模式分别是Machine mode(机器模式)、supervisor mode(特权模式)、user mode(用户模式)),内核从kernel/entry.s 中的_entry 开始执行,初始化栈区,然后调用 kernel/start.c 中的 start;

3. 切换到特权模式:start 在机器模式下完成配置后,会按照下面的步骤切换到特权模式:

a.首先修改 mstatus(Machine Status Registers)寄存器,将运行模式改为管理模式

b. 将 main函数地址写入 mepc(Machine Exception Program Counter Registers)寄存器,指定返回地址

c. 向页表寄存器 satp(Supervisor Address Translation and Protection Registers)写入0,禁用
特权模式下的虚拟地址转换

d. 执行 mret 指令返回到特权模式,此时程序计数器PC跳转到 main函数

4. 创建进程与初始化:

a. 在跳转到main 函数之后,kernel/main.c 对设备和子系统完成初始化后,调用kernel/proc.c 中的userinit 创建第一个进程,这个进程会去执行 initcode.S

b.initcode.S 会进行 exec 系统调用加载/init 程序,替换自身内存和寄存器,进入用户空间

c./init(user/init.c)创建控制台设备文件,打开标准输入、输出、错误描述符(fd 0、1、2),
最终启动 shell,完成系统启动

用户态调用的发起与内核处理方式

在用户态中会通过“寄存器传参+ecall 指令“发起系统调用,内核会通过陷阱处理和函数表分发执行系统调用。

1. 用户态将系统调用的参数存入RISC-V寄存器 a0,a1,然后将系统调用号(SYS_exec)存入寄存器 a7;执行ecall 指令,触发陷阱 trap(可以理解为trap就是用户态与内核态切换的入口),从用户态陷入内核态

2.触发 trap后,内核执行usertrap(kernel/trap.c),将用户态寄存器保存到陷阱帧(trapframe)中;内核函数 syscall(kernel/syscall.c)会从 trapframe 中读取寄存器 a7的系统调用号,索引syscalls数组(在kernel/syscall.c 中108-130行处,大家可自行查看源码)调用对应的接口函数(如sys_exec ).

static uint64 (*syscalls[])(void) = { [SYS_fork] sys_fork, [SYS_exit] sys_exit, [SYS_wait] sys_wait, [SYS_pipe] sys_pipe, [SYS_read] sys_read, [SYS_kill] sys_kill, [SYS_exec] sys_exec, [SYS_fstat] sys_fstat, [SYS_chdir] sys_chdir, [SYS_dup] sys_dup, [SYS_getpid] sys_getpid, [SYS_sbrk] sys_sbrk, [SYS_sleep] sys_sleep, [SYS_uptime] sys_uptime, [SYS_open] sys_open, [SYS_write] sys_write, [SYS_mknod] sys_mknod, [SYS_unlink] sys_unlink, [SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_close] sys_close, [SYS_trace] sys_trace, [SYS_sysinfo] sys_sysinfo, };

3. 第2步中系统调用接口函数执行完毕会将返回值存入trapframe的a0 寄存器中,内核执行sret 指令
(sret 全称是Supervisor Return,监管者返回,它是内核处理完异常或中断后,用来恢复用户程序运行
的最后一道指令。)返回用户态,用户态从a0 获取返回值(负数表示错误,非负表示成功;若系统调用号无效,syscall 返回-1并打印错误)。

系统调用参数

用户态进行系统调用时,首先把调用参数放在RISC-V的a0,a1寄存器中,trap时又将参数保存到trapframe帧中,那么参数是怎样从trapframe中找到,内核提供了几个参数,argraw从陷阱帧trapframe中获取第n个参数,artint、artaddr等函数分别以整数、指针的形式返回。

static uint64 argraw(int n) { struct proc *p = myproc(); switch (n) { case 0: return p->trapframe->a0; case 1: return p->trapframe->a1; case 2: return p->trapframe->a2; case 3: return p->trapframe->a3; case 4: return p->trapframe->a4; case 5: return p->trapframe->a5; } panic("argraw"); return -1; } int argint(int n, int *ip) { *ip = argraw(n); return 0; } int argaddr(int n, uint64 *ip) { *ip = argraw(n); return 0; }

内核态陷阱处理

上面说了用户系统调用会触发trap,从用户态变为内核态,这一部分我们之后会有一个单独的章节来讲这个,我们先大概了解一个内核态陷阱处理流程:

1.当内核执行时,tvec 会指向 kernel/kernelvec.S 中的kernelvec,用于处理内核自身的陷阱(设备终
端或者异常等)

2. 保存寄存器与分发陷阱

kernelvec 将所有寄存器保存到被中断的内核线程的栈上(保证线程切换时寄存器的安全性),然后跳转到 kernel/trap.c 中的 kerneltrap。

kerneltrap 处理两类陷阱:设备中断:调用kernel/trap.c 中的devintr 处理;异常:内核直接panic(致命错误,停止执行)。

若为计时器中断且当前是进程的内核线程在运行(非调度程序线程)程调度,kerneltrap 会调用 yield 进行线程调度。

3.kerneltrap 处理完成后,恢复 sepc和 sstatus 寄存器,返回 kernelvec;kernelvec 从栈中弹出保存的寄存器,执行sret 指令,回到被中断的内核代码。

ECALL指令

ECALL(Environment Call)是RISC-V指令集中的同步异常触发指令,用于在不同特权级之间切换(如用户态→内核态),ECALL 执行时会触发同步陷阱(Trap),CPU会根据当前执行模式(用户态/内核态)和陷阱向量寄存器 stvec 的配置,跳转到对应的陷阱处理程序。

例如,从用户态切换到内核态的过程:

1. 首先用户态会进行基础准备:

。将系统调用参数存入RISC-V寄存器(如a0、a1);

。将系统调用号(如 SYS_exec)存入寄存器 a7;

。执行ecall指令,触发陷阱,从用户态陷入内核态

2.配置陷阱向量以此确定内核入口

XV6 在用户态运行时,会配置 陷阱向量寄存器 stvec 指向用户态陷阱处理的入口 usertrap(位于
kernel/trap.c)。当ecal1触发陷阱时,CPU会跳转到 usertrap 执行。

3. 保存用户态寄存器上下文

usertrap 执行时,会将用户态的所有寄存器(如a0-a7、sp、pc等)保存到 陷阱帧(trapframe)中
(trapframe 是内核为每个进程维护的结构体,用于记录陷阱发生时的用户态上下文)。这一步确保用户态的执行状态不会因内核态执行而丢失。

4.切换特权级别(从用户态到内核态)

ecall 触发的陷阱会使CPU从用户态(U模式)切换到内核态(S模式)。内核获得特权级权限,可执行用
户态无法执行的操作(如访问硬件、修改页表等)。

5. 陷阱处理(执行系统调用逻辑)

内核在 usertrap 中判断陷阱类型为“系统调用”后,会调用 syscall 函数(kernel/syscall.c):

。syscall 从陷阱帧中读取 a7 的系统调用号;

。通过系统调用号索引 syscalls 数组(函数指针表),调用对应的系统调用处理函数(如sys_exec)

6. 返回用户态

系统调用处理完成后:

。内核将返回值存入陷阱帧的a0 寄存器;

。执行sret 指令(特权级返回指令),恢复之前保存的用户态上下文(如pc、sstatus 等),从内核态切换回用户态;

。用户程序从 a0 寄存器读取返回值,继续执行后续逻辑

System call tracing

第一步

我们先把准备工作做好(接口)

// 在 user/user.h 中添加函数原型 int trace(int); // 在user/usys.pl 中注册系统调用 entry("trace"); // 在kernel/syscall.h 中为 trace 分配唯一系统调用号,确保与现有的不冲突 #define SYS_trace 22 // 声明函数原型 extern uint64 sys_trace(void); // 在kernel/syscall.c中在syscalls[]系统调用表中添加系统调用函数 [SYS_trace] sys_trace,

第二步

因为要追踪系统调用,所以在进程控制块(kernel/proc.h)中添加一个成员变量,用来记录这个进程中要追踪哪些系统调用

第三步

我们要实现系统调用追踪结构体,从当前进程中获取进程的追踪掩码,在kernel/sysproc.c中添加

第四步

因为父进程可能创建一个子进程,在子进程中,也要继续跟踪,所以要修改kernel/proc.c的fork函数

第五步

系统调用执行后,要检查调用的内核函数是否是我们需要追踪的函数,所以要修改kernel/syscall.c中syscall函数

第六步

提供用户态函数,在user目录下添加trace.c函数

Sysinfo

空闲内存的统计原理

物理内存主要有一个空闲链表进行管理,这个kfreelists空闲链表在kernel/kalloc中。

我们只要添加一个统计空闲链表的系统调用函数,然后调用这个函数就可以了。

非UNUSED进程数统计原理

进程的状态在kernel/proc.h中的进程控制块结构体中包含,我们只要判断state成员变量是否为UNUSED就行

然后我们写一个统计非UNused状态的进程函数就行,然后调用这个函数

用户-内核态的数据交互安全机制(copyout)

由于用户态传递的结构体指针可能会被指向非法/恶意地址,直接访问会导致内核崩溃或者安全漏洞。XV6通过copyout 函数解决这个问题。

copyout 定义在 kernel/vm.c 中,核心思想是”逐页复制,逐页验证”,会先验证用户提供的虚拟地址是否属于用户地址空间(通过遍历页表确认物理地址有效性);通过将整个复制过程分解为多个页面大小的块,copyout确保了每一次内存写入都是安全的。在写入每个块之前,它都会通过 walkaddr 函数查询页表,确认目标虚拟地址确实映射到了一个合法的物理地址。如果发现任何无效地址,函数会立即中止并返回错误,从而保护了内核的稳定性和安全性。

示例代码

第一步

首先我们现在kernel/sysinfo.h中定义一个结构体,用来保存我们收集到的结果

第二步

还是定义接口,系统调用号,把系统调用函数添加到系统调用表中

// user/user.h 中声明: int sysinfo(struct sysinfo *); // 在usys.pl注册 sysinfo 系统调用 entry("sysinfo"); // kernel/syscall.h 中给 sysinfo 分配一个系统调用号 #define SYS_sysinfo 23 // 声明函数原型 extern uint64 sys_sysinfo(void); // 在kernel/syscall.c中系统调用表syscalls[]中添加系统调用 [SYS_sysinfo] sys_sysinfo,

第三步

在kernel/kalloc中通过遍历freelist空闲链表,实现统计空闲内存的功能

第四步

在kernel/proc.c中,通过遍历proc进程控制块的成员变量state,统计非UNUSED进程数量

第五步

在kernel/sysproc.c实现sys_info的系统调用(这次没有用户态接口需要咱们实现,仓库里都设置好了)

http://www.jsqmd.com/news/551141/

相关文章:

  • 004、网络性能关键指标:带宽、延迟、吞吐量与拥塞
  • Python遥感采集效率提升300%的底层逻辑(Rasterio+GDAL内核级调优实录)
  • 基于ResNet50与CBAM注意力机制的鸟类识别模型开发
  • 飞书文档+Markmap联动指南:用AI自动生成会议纪要思维导图
  • 子串—最小覆盖子串
  • 2026年3月进口木地板漆厂家推荐,进口实木地板漆、进口软木地板漆、进口彩色木地板漆、进口耐磨木地板漆、进口体育木地板漆、进口户外木地板漆,高环保耐候性实力源头厂商 - 品牌企业推荐师(官方)
  • 避坑指南:BH1750光照传感器I2C通信的5个常见问题与解决方案(STM32版)
  • Educational Codeforces Round 2 复盘
  • 路沿模板,北海水泥路路沿模板,25公分路面钢模哪家质量好
  • 3步高效获取无水印视频:TikTokDownload新手实用指南
  • Python原生AOT编译报错解决率98.7%的黄金组合:1个自检脚本+2个环境隔离容器+1份2026 Q2已验证的ABI白名单(限本周内开放下载)
  • switch case加宏实现协程类似的操作
  • 从零开始的web开发03
  • Asian Beauty Z-Image Turbo 建筑概念设计展:未来都市与奇幻场景生成
  • 2026年3月进口水性漆厂家推荐,进口环保水性漆、进口水性油漆、进口水性金属漆,低VOC高附着力源头厂商精选 - 品牌企业推荐师(官方)
  • GHelper:华硕笔记本的轻量级性能管家,告别臃肿拥抱自由
  • 保姆级教程:从下载到汉化,手把手搞定AD20安装与激活(附资源包)
  • 告别爆显存!WuliArt Qwen-Image Turbo优化实测:小显卡也能畅玩文生图
  • 拼多多API授权实战:从OAuth2.0到access_token的全流程解析
  • 阿里CosyVoice3声音克隆实战:从部署到生成,小白也能轻松上手
  • HsMod炉石传说插件:让游戏体验个性化的实用指南
  • Magisk终极实战指南:Android系统Root权限的完整解决方案
  • Audacity:音频创作的颠覆性工具与效率革命
  • Qt6开发环境搭建避坑指南:为什么你的Kit里没有MSVC2019?两种情况的解决方案都在这
  • 3个技巧让Mac窗口管理效率翻倍:Loop终极指南
  • 3分钟零配置:在浏览器中直接查看SQLite数据库的纯前端方案
  • PhysX源码分析——RigidBody:刚体的数据结构
  • Java基于微信小程序的学生签到系统,附源码+文档说明
  • 数据集资源
  • AI视频增强技术全解析:从问题诊断到实战优化的完整指南