其他分享
首页 > 其他分享> > 6.s081 : 系统调用

6.s081 : 系统调用

作者:互联网

Syscall

CH2 Operating system organization

os的任务是:

CPU周围由RAM, ROM(保存着启动时代码), 串行连接到用户的键盘/显示屏和一个用来存储的磁盘.

User mode, supervisor mode, and system calls

隔离需要:

CPU可以在三中模式下运行指令:

  1. machine mode: 在这个模式下的指令由最高的特权. CPU开始于这个模式, machine mode用作配置计算机, 在machine mode下运行几行指令后, CPU进入supervisor mode.
  2. supervisor mode: CPU被允许运行特权指令(打开关闭中断, 读写寄存器等).
  3. user mode: 只能运行user mode指令, 如果在user mode 运行特权指令的话, CPU不会运行该指令, 而是切换到supervisor mode, 在supervisor mode下运行指令来终止程序.

应用程序调用kernel函数步骤:

  1. 应用程序调用read系统调用.
  2. CPU中有一个特殊指令, 将CPU从user mode切换到supervisor mode, 并进入由内核制定的entry point(ecall指令).
  3. CPU切换到supervisor mode, 内核检查系统调用参数, 决定应用是否被允许执行.

Process overview

xv6中隔离的最小单元是进程. 进程之间内存, CPU, 文件描述符都不共享. 进程和内核之间也不共享. 内核通过user/supervisor模式标志, 地址空间和线程的time-slicing.

xv6使用页表, RISC-V页表将虚拟地址(RISC-V指令操作的地址)翻译成物理地址(CPU发送给主存).

每个进程都有独立的页表

QQ20210705-193611@2x.png

RISC-V的指针是64 bits, 硬件使用低39 bits来寻找虚拟地址, xv6使用39中的38 bits, 所以最大地址是(2^38 - 1 = 0x3fffffffff, MAXVA).

每个进程保存着许多状态.

QQ20210705-194105@2x.png

每个进程有一个线程, 来运行进程的指令. 线程可以被挂起恢复. 内核通过挂起当前运行进程的线程, 恢复其他进程的线程来切换进程. 线程的状态(局部变量, 函数调用的返回地址)存储在线程栈. 进程有两个栈, user栈和kernel栈. 当进程在运行指令时, 只有user栈被使用, kernel栈为空. 当进程进入内核, user栈仍然保存数据.

ecall指令: 提高特权等级, 并将pc变为内核定义的entry point.

p->state表明进程的状态.

p->pagetable保存进程页表.

Code: starting xv6 and the first process

内核启动并运行第一个进程:

  1. RISC-V硬件启动, 初始化并运行一个存储在只读内存中的boot loader. boot loader将xv6内核加载进内存. loader把内核加载进0x80000000的物理地址(0x0: 0x80000000包含I/O设备).

  2. 在machine mode下, 从_entry运行xv6, 这是MMU还不能用(虚拟地址直接映射到物理地址). _entry的指令设置栈, 这样xv6可以运行C代码. _entry代码把stack0+4096加载进sp, 栈是向下生长的. 这时, 内核有一个栈stack0, 这样 _entry可以调用start.

    QQ20210705-203833@2x.png

  3. start执行了一些只能在machine mode下的配置. start先开启时钟来产生时钟中断.

    QQ20210705-211219@2x.png
    之后会在寄存器mstatus中设置之前的模式为supervisor mode

    QQ20210705-211500@2x.png
    通过将main的地址写入mepc来将返回地址设置为main.

    QQ20210705-211709@2x.png
    这时还不能开启虚拟地址翻译, 所以把0写入satp中

    QQ20210705-212018@2x.png

    最后调用mret从machine mode 返回到supervisor mode

    QQ20210705-212355@2x.png

  4. main会初始化几个设备

    QQ20210705-213528@2x.png
    调用userinit来创建第一个进程

    QQ20210705-213606@2x.png

    QQ20210705-213658@2x.png
    第一个程序会运行一个小程序initcode, 通过调用exec重新进入内核. initcode会用一个新的程序/init来替换当前程序.

    QQ20210705-214034@2x.png
    init创建一个新的控制台设备文件并开启shell.

    QQ20210705-214249@2x.png

Lec3

编译运行内核

QQ20210705-214445@2x.png

xv6代码由三个部分组成:

编译过程:

几个参数:

QQ20210705-215218@2x.png

xv6启动过程

  1. 设置断点在_entry

    QQ20210705-215624@2x.png
    继续运行到_entry, 发现第一条指令读取了控制系统寄存器mhartid, 并将结果加载到a1寄存器

    QQ20210705-215856@2x.png

  2. xv6从entry.s启动, 这时没有内存分页, 没有隔离性, 并运行在machine mode下, xv6要跳到supervisor mode下, 给main函数设置一个断点, 这是main已经运行在supervisor mode下了.

    QQ20210705-220218@2x.png

  3. 先是调用consoleinit, 设置好console, 一旦console设置好, 就可以向console打印输出.

    QQ20210705-220432@2x.png

  4. 之后还会初始化一系列

    QQ20210705-220540@2x.png

    • kinit: 设置好页表分配器(page allocator)
    • kvminit: 设置好虚拟内存
    • kvminithart: 打开页表
    • processinit: 设置好初始进程
    • trapinit/ trapinithart: 设置好user/kernel mode转换代码
    • plicinit/plicinithart: 设置好中断控制器PLIC(platform level interrupt controller)
    • binit: 分配buffer cache
    • iinit: 初始化inode缓存
    • fileinit: 初始化文件系统
    • virtio_disk_init: 初始化磁盘
    • userinit: 以上所有设置完成, os系统开始运行, 会通过userinit 运行第一个进程.
  5. userinit启动了第一个用户程序(总是需要一个用户程序运行).

    QQ20210705-221144@2x.png
    userinit需要一个小程序initcode来初始化第一个用户进程.

    QQ20210705-221254@2x.png
    initcode对应汇编代码如下

    QQ20210705-221355@2x.png

    首先将init地址加载到a0, argv中的地址加载到a1, exec系统调用对应的数字加载到a7, 最后调用ECALL.

  6. 在syscall上设置一个断点, 继续运行程序, userint会创建进程, 返回到用户空间, 执行3条指令, 再回到内核空间.

    QQ20210705-221841@2x.png
    num = p->trapframe->a7会读取系统调用对应的整数

    p->trapframe->a0 = syscall [num] ()实际调用系统调用, 由于传入的是init程序, initcode通过exec调用init程序, init程序会为用户空间设置好一些东西, 调用fork, 并在fork的子程序执行shell.

    QQ20210705-222206@2x.png

  7. 这是shell正常运行

    QQ20210705-222351@2x.png

标签:调用,系统,mode,进程,s081,CPU,运行,内核
来源: https://www.cnblogs.com/rainbowg0/p/15082657.html