XV6源代码阅读-中断与系统调用

Exercise1 源代码阅读

1.启动部分: bootasm.S bootmain.c 和xv6初始化模块:main.c

  • bootasm.S 由16位和32位汇编混合编写成的XV6引导加载器。bootasm.S内的汇编代码会调用bootmain.c中的void bootmain(void);main.c主函数内部初始化各模块;
  • 当x86 PC启动时,它执行的是一个叫BIOS的程序。BIOS存放在非易失存储器中,BIOS的作用是在启动时进行硬件的准备工作,接着把控制权交给操作系统。具体来说,BIOS会把控制权交给从磁盘第0块引导扇区(用于引导的磁盘的第一个512字节的数据区)加载的代码。引导扇区中包含引导加载器——负责内核加载到内存中。BIOS 会把引导扇区加载到内存 0x7c00 处,接着(通过设置寄存器 %ip)跳转至该地址。引导加载器开始执行后,处理器处于模拟Intel 8088处理器的模式下。而接下来的工作就是把处理器设置为现代的操作模式,并从磁盘中把 xv6内核载入到内存中,然后将控制权交给内核。
# Start the first CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00..code16                       # Assemble for 16-bit mode
.globl start
start:cli                         # BIOS enabled interrupts; disable# Zero data segment registers DS, ES, and SS.xorw    %ax,%ax             # Set %ax to zeromovw    %ax,%ds             # -> Data Segmentmovw    %ax,%es             # -> Extra Segmentmovw    %ax,%ss             # -> Stack Segment

2.中断与系统调用部分: trap.c trapasm.S vectors.S & vectors.pl syscall.c sysproc.c proc.c 以及相关其他文件代码

  • trap.c 陷入指令c语言处理接口,trapasm.S陷入指令的汇编逻辑;
  • vector.S由vector.pl生成,中断描述符256个;
  • proc.c 内部主要接口:static struct proc * allocproc(void)、void userinit(void)、int growproc(int n)、int fork(void)、void exit(void)、int wait(void)、void scheduler(void)、void yield(void);
  • syscall.c 内部定义了各种类型的系统调用函数,sysproc.c内部是与进程创建、退出等相关的系统调用函数的实现。
// syscall.h  System call numbers
……
#define SYS_fork    1
#define SYS_exit    2
#define SYS_wait    3
#define SYS_pipe    4
#define SYS_read    5
#define SYS_kill    6
#define SYS_exec    7
……// syscall.c 声明系统调用
……
extern int sys_chdir(void);
extern int sys_close(void);
extern int sys_dup(void);
extern int sys_exec(void);
extern int sys_exit(void);
extern int sys_fork(void);
extern int sys_fstat(void);
extern int sys_getpid(void);
extern int sys_kill(void);
extern int sys_link(void);
extern int sys_mkdir(void);
extern int sys_mknod(void);
extern int sys_open(void);
……// sysproc.c 定义前面声明的系统调用接口
int sys_fork(void)
{return fork();
}int sys_exit(void)
{exit();return 0;  // not reached
}int sys_wait(void)
{return wait();
}int sys_kill(void)
{int pid;if(argint(0, &pid) < 0)return -1;return kill(pid);
}
……

Exercise2 带着问题阅读

3.什么是用户态和内核态,两者有何区别? 什么是中断和系统调用,两者有何区别? 计算机在运行时,是如何确定当前处于用户态还是内核态的?

  • 当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。Ring3状态不能访问Ring0的地址空间,包括代码和数据;当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。用户运行一个程序,该程序创建的进程开始时运行自己的代码,处于用户态。如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用,这些系统调用会调用内核的代码。进程会切换到Ring0,从而进入内核地址空间去执行内核代码来完成相应的操作。内核态的进程执行完后又会切换到Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。这说的保护模式是指通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程地址空间中的数据;
  • 系统调用需要借助于中断机制来实现。两者都是从同一个异常处理入口开始,但是系统调用会一开始让CPU进入内核模式且使能中断,然后从系统调用表中取得相应的注册函数调用之;而中断处理则让CPU进入内核模式且disable中断。所以系统调用的真实处理(系统调用表中的注册函数执行)中可以阻塞,而中断处理的上半部不可以。所以在写驱动代码如字符设备驱动,实现读操作时是可以让其sleep的(比如没有数据时候,用户设置读模式是阻塞型的)。另一方面,如果该驱动读操作过于耗时也是不可取的,它在内核态中执行,这个时候只有中断的优先级比它高,其它的高优先级线程将不能得到及时调度执行;
  • 用户态和内核态的特权级不同,因此可以通过特全级判断当前处于用户态还是内核态。

4.计算机开始运行阶段就有中断吗? XV6 的中断管理是如何初始化的? XV6 是如何实现内核态到用户态的转变的? XV6 中的硬件中断是如何开关的? 实际的计算机里,中断有哪几种?

  • 计算机开始运行阶段就有BIOS支持的中断;
  • 由于xv6在开始运行阶段没有初始化中断处理程序,于是xv6在bootasm.S中用cli命令禁止中断发生。xv6的终端管理初始化各部分通过main.c中的main()函数调用。picinit()和oapicinit()初始化可编程中断控制器,consoleinit()和uartinit()设置了I/O、设备端口的中断。接着,tvinit()调用trap.c中的代码初始化中断描述符表,关联vectors.S中的中断IDT表项,在调度开始前调用idtinit()设置32号时钟中断,最后在scheduler()中调用sti()开中断,完成中断管理初始化;
  • xv6在proc.c中的userinit()函数中,通过设置第一个进程的tf(trap frame)中cs ds es ss处于DPL_USER(用户模式) 完成第一个用户态进程的设置,然后在scheduler中进行初始化该进程页表、切换上下文等操作,最终第一个进程调用trapret,而此时第一个进程构造的tf中保存的寄存器转移到CPU中,设置了 %cs 的低位,使得进程的用户代码运行在 CPL = 3 的情况下,完成内核态到用户态的转变;
  • xv6的硬件中断由picirq.c ioapic.c timer.c中的代码对可编程中断控制器进行设置和管理,比如通过调用ioapicenable控制IOAPIC中断。处理器可以通过设置 eflags 寄存器中的 IF 位来控制自己是否想要收到中断,xv6中通过命令cli关中断,sti开中断;
  • 中断的种类有:程序性中断:程序性质的错误等,如用户态下直接使用特权指令;外中断: 中央处理的外部装置引发,如时钟中断;I/O中断: 输入输出设备正常结束或发生错误时引发,如读取磁盘完成;硬件故障中断: 机器发生故障时引发,如电源故障;访管中断: 对操作系统提出请求时引发,如读写文件。

5.什么是中断描述符,中断描述符表(IDT)? 在XV6里是用什么数据结构表示的?

  • 中断描述符表的每一项是一个中断描述符,在x86系统中,中断处理程序定义存储在IDT中。XV6的IDT有256个入口点,每个入口点中对应的处理程序不同,在出发trap时,只要找到对应编号的入口,就能得到对应的处理程序;
  • XV6中的数据结构中中断描述符用struct gatedesc表示:
// trap.c
# generated by vectors.pl - do not edit
# handlers
.globl alltraps
.globl vector0
vector0:pushl $0pushl $0jmp alltraps
.globl vector1
vector1:pushl $0pushl $1jmp alltraps
.globl vector2
……
  • alltraps继续保存处理器的寄存器,设置数据和CPU段,然后压入 %esp,调用trap,到此时已完成用户态到内核态的转变;
// trapasm.S# vectors.S sends all traps here.
.globl alltraps
alltraps:# Build trap frame.pushl %dspushl %espushl %fspushl %gspushal# Set up data and per-cpu segments. 设置数据和CPU段movw $(SEG_KDATA<<3), %axmovw %ax, %dsmovw %ax, %esmovw $(SEG_KCPU<<3), %axmovw %ax, %fsmovw %ax, %gs# Call trap(tf), where tf=%esp 压入 %esppushl %esp  # 调用trapcall trapaddl $4, %esp
  • trap会根据%esp指向对应的tf,首先根据trapno判断该中断是否是系统调用,之后判断硬件中断,由于除零不是以上两种,于是判断为代码错误中断,并且是发生在用户空间的。接着处理程序将该进程标记为killed,并退出,继续下一个进程的调度;
// trap.c
//PAGEBREAK: 41
void trap(struct trapframe *tf)
{if(tf->trapno == T_SYSCALL){ // 判断该中断是否为系统调用if(proc->killed)exit();proc->tf = tf;syscall();if(proc->killed)exit();return;}switch(tf->trapno){……// PAGEBREAK: 13  // tf->trapno与其他case语句对不上,除零被视为代码错误中断,进入这里杀掉进程default: if(proc == 0 || (tf->cs&3) == 0){// In kernel, it must be our mistake.cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n",tf->trapno, cpu->id, tf->eip, rcr2());panic("trap");}// In user space, assume process misbehaved.  cprintf("pid %d %s: trap %d err %d on cpu %d ""eip 0x%x addr 0x%x--kill proc\n",proc->pid, proc->name, tf->trapno, tf->err, cpu->id, tf->eip, rcr2());proc->killed = 1;}……
}
  • 涉及到的主要数据结构:中断描述符表IDT(trap.c +12)、(vi x86.h +150)、(vi vector.S)。
// trap.c
// Interrupt descriptor table (shared by all CPUs).
struct gatedesc idt[256];
extern uint vectors[];  // in vectors.S: array of 256 entry pointers
……// x86.h
//PAGEBREAK: 36
// Layout of the trap frame built on the stack by the
// hardware and by trapasm.S, and passed to trap().
struct trapframe {// registers as pushed by pushauint edi;uint esi;uint ebp;uint oesp;      // useless & ignoreduint ebx;uint edx;uint ecx;uint eax;……
};// vector.S  0~255共256个
vectors:.long vector0.long vector1.long vector2.long vector3.long vector4.long vector5.long vector6.long vector7.long vector8.long vector9……

6.请以系统调用setrlimit(该系统调用的作用是设置资源使用限制)为例,叙述如何在XV6中实现一个系统调用。(提示:需要添加系统调用号,系统调用函数,用户接口等等)。

  • 在syscall.h中添加系统调用号 #define SYS_setrlimit 22;
// syscall.h
……
#define SYS_mkdir  20
#define SYS_close  21
#define  SYS_setrlimit  22 // add by yangyu
  • 在syscall.c中添加对应的处理程序的调用接口
// syscall.c
……
static int (*syscalls[])(void) = {
……
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
[SYS_setrlimit]   SYS_setrlimit, // add by yangyu
};
  • 在sysproc.c中添加系统调用函数int sys_setrlimit(void),具体实现对于进程资源使用限制的设置;
// syspro.c
……
int sys_uptime(void)
{uint xticks;acquire(&tickslock);xticks = ticks;release(&tickslock);return xticks;
}// 在这里面写逻辑,限制进程资源的使用
int sys_setrlimit(void)
{// to do
}
  • 在user.h中声明系统调用接口int setrlimit(int resource, const struct rlimit * rlim);
// syspro.c
……
// system calls
int fork(void);
int exit(void) __attribute__((noreturn));
…… // 调用该接口陷入内核执行系统调用
int setrlimit(int resource, const struct rlimit *rlim); 
  • 在usys.S添加SYSCALL(setrlimit)。
// usys.S
……
SYSCALL(sleep)
SYSCALL(uptime)
SYSCALL(setrlimit)  // add by yangyu

参考文献

[1] xv6 idt初始化
[2] xv6中文文档
[3] xv6 alltraps
[4] [xv6 trap/interrupt](

posted on 2019-06-09 16:17 荒野之萍 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/icoty23/p/10993829.html

XV6源代码阅读-中断与系统调用相关推荐

  1. XV6源代码阅读-虚拟内存管理

    Exercise1 源代码阅读 内存管理部分: kalloc.c vm.c 以及相关其他文件代码 kalloc.c:char * kalloc(void)负责在需要的时候为用户空间.内核栈.页表页以及 ...

  2. XV6源代码阅读-文件系统

    Exercise1 源代码阅读 文件系统部分 buf.h fcntl.h stat.h fs.h file.h ide.c bio.c log.c fs.c file.c sysfile.c exec ...

  3. XV6源代码阅读-进程线程

    文章目录 Exercise1 源代码阅读 Exercise2 带着问题阅读 参考文献 Exercise1 源代码阅读 基本头文件:types.h param.h memlayout.h defs.h ...

  4. Linux 源代码阅读知识点及要求

    说明:1.本次源代码阅读,以Linux 最新的稳定版本(2.6)为主: 2.源代码下载地址: 在官方站点 www.kernel.org 上最新稳定版本是 2.6.13.2: 在清华的 ftp 上随时都 ...

  5. 非常好!!!Linux源代码阅读——内核引导【转】

    Linux源代码阅读--内核引导 转自:http://home.ustc.edu.cn/~boj/courses/linux_kernel/1_boot.html 目录 Linux 引导过程综述 BI ...

  6. XV6陷入,中断和驱动程序

    陷入,中断和驱动程序 运行进程时,cpu 一直处于一个大循环中:取指,更新 PC,执行,取指--.但有些情况下用户程序需要进入内核,而不是执行下一条用户指令.这些情况包括设备信号的发出.用户程序的非法 ...

  7. [置顶] 为什么要阅读源代码?如何有效的阅读源代码? 选一些比较优秀的开源产品作为源代码阅读对象?...

    盛大TeamHost上有个关于学习开源项目的wiki :http://www.teamhost.org/projects/learn-with-open-source/wiki/Wiki 一.为什么要 ...

  8. 为什么要阅读源代码?如何有效的阅读源代码? 选一些比较优秀的开源产品作为源代码阅读对象?

    一.为什么要阅读源代码? 很多作家成名之前都阅读过大量的优秀文学作品,经过长期的阅读和写作积累,慢慢的才有可能写出一些好的.甚至是优秀的文学作品. 而程序员与此类似,很多程序员也需要阅读大量的优秀程序 ...

  9. 非常好!!!Linux源代码阅读——环境准备【转】

    Linux源代码阅读--环境准备 转自:http://home.ustc.edu.cn/~boj/courses/linux_kernel/0_prepare.html 目录 Linux 系统环境准备 ...

最新文章

  1. 2014百度面试题目---“求比指定整数大且最小的不重复数”解答
  2. java形状_形状等于()
  3. 人工智能行业有哪些岗位_建筑行业“七大员”是哪些岗位?职责是什么?
  4. 云计算与 Cloud Native | 数人云CEO王璞@KVM分享实录
  5. Ajax-图书管理系统数据提交
  6. pandas基础知识--1
  7. 华为电脑管家PcManager多屏协同功能破解
  8. 9008刷机模式写入超时刷机帮_【转】高通9008模式刷机,让小米刷机不再畏惧
  9. 网站制作教程:如何建设自己的网站?
  10. 如何使用视频转换器将kux格式转换成mp4格式
  11. Eoapi — 一个可拓展的开源 API 工具
  12. Calendar根据输入的年份和周数计算该周的开始日期和结束日期
  13. SK 注意力模块 原理分析与代码实现
  14. map的基本使用-go篇
  15. 成为会带团队的技术人 大项目:把握关键点,谋定而后动
  16. 英语背单词有用吗_对于大学生英语背单词软件哪个好可以用_最好的背单词
  17. 零中频数字接收机原理
  18. C\C ++语言 文件备份实验
  19. swag_ios安卓 testlight /apps/android官方开发包安装
  20. 去香港读研——申请全过程

热门文章

  1. 用户表单事件(focus事件)
  2. 棋盘型动态规划 之 CODE[VS] 1169 传纸条 2008年NOIP全国联赛提高组
  3. 【Nginx】epoll事件驱动模块
  4. HDU 2704 Bulletin Board
  5. 让Updatepanel中的控件触发整个页面Postback
  6. Rstudio连接spark失败
  7. 计算机的发展英语600词,急求一份有关计算机“存储器”的英语作文,600词左右可以多加分!...
  8. Abbirb120型工业机器人_工业机器人市场深度调研及投资前景预测报告2020-2024年
  9. influxdb tsm文件_利用InfluxDB+Grafana搭建Flink on YARN作业监控大屏
  10. Kanzi常用操作2