Operating system organization

这一章仍然是总体介绍xv6-OS,但是会稍微深入一点点细节。

操作系统需要让所有的进分时(time-shared) 来是用来硬件资源。即时进程数目比cpu数要多,仍然能够给用户提供所有进程都在同时运行的“假象”。操作系统同时也需要维持不同进程之间的隔离关系(isolation),这个也是非常重要的功能,不能因为一个进程出错,而让其他的进程受到影响。此外为了进行一些特点的操作,我们还需要打破这个隔离,使得操作系统可以让不同的进程进行交互。简单来说OS必须满足这三个特点:硬件多路复用,隔离以及交互。本章主要是围绕这三个特点来介绍。

Abstract physical resource

这一节课本介绍了一些硬件资源的抽象以及这样抽象的原因。在第一章的时候已经简单介绍过了。这里就不赘述

User mode,supervisor mode,and system calls

在OS中我们为了实现隔离性,用户的代码肯定不能直接访问内核的数据,也不能对硬件直接进行读写,这些敏感的操作只能通过操作系统来完成。cpu只是一个 “取址执行”的机器罢了,他又如何知道现在这个代码能不能被执行呢?所以这里os就引入了三种模式:machine mode, supervisor mode, and user mode(其中supervisor mode 也可以说是kernel mode)。不同的地址有着不同的运行模式,只有在目前权限大于等于要求的模式的情况下才能够正常的让cpu执行这个指令。

machine mode 比较少见,他用处也比较少,只是在开机的时候会配置计算机,我们就不细看。

kernel mode 下 cpu可以执行很多敏感操作,例如启动/禁止中断等。(后面会具体介绍中断)

user mode 只能执行用户态的代码,如果想要执行内核态的代码,例如磁盘写入数据等,就需要利用系统调用,陷入内核态后才能进行敏感操作。而内核态的代码都是事先写好的,会对用户的传入的参数进行验证,只有满足要求才会执行,防止用户对内核或其他进程进行破坏。注意从用户态跳转到内核态只有一个入口! 所有的系统调用都是通过这个入口进入的。后面我们会具体来说这个入口。

Kernel organization

这里文章主要介绍了两个内核的组织模式,跟主题内容没啥关系,不用看了。

Process overview

进程为我们的程序提供了这样的一种抽象:每一个程序都好像运行在一个单独的机器上,有自己独自的内存地址,有独自的cpu等。而其他的程序无法干扰。而页表(page table)就是实现这种抽象的一种手段(页表是后面的最重要的内容之一)。每一个进程都有自己的页表,页表提供一种虚拟地址 到 物理地址的映射。有了这一层映射后,无论虚拟地址怎么操作,怎么蹦跶,都只是在物理地址规定的大小内蹦跶,不会影响其他进程的内容。而xv6的进程的虚拟地址分布如下:

从低地址到高地址依次是 代码区,数据区,栈区,和堆区。上面两个trapframe 与 trapoline是 后面讲trap时用到的关键两个区域(trapframe 用来存数据,而trapline是一段固定代码)。其中MAXVA 是 2^38 -1 ,这也意味着xv6中有效的虚拟地址是38位的。在内核中每一个进程都会维护一个结构体proc,里面有会有 pagetable就是指向该进程页表的指针。每一个进程都有一个执行线程来执行指令,当然xv6中有且只有一个。(这里注意理解进程与线程之间的关系,进程是一种抽象,一个资源集合的抽象,包括代码段,堆区等等资源等并不会执行指令。而实际执行指令的抽象是线程,不同的线程有自己的栈空间,但是共用一个堆空间以及数据区和代码区),此外每一个进程都会有自己的内核栈(内核线程切换就是通过这个栈来保存相应的数据)。

starting xv6 and the first process

这里会具体介绍内核是如何启动,以及第一个进程是如何运行的。首先是在配置好硬件后,又一段汇编程序开始:

# 此时不存在什么页表,直接都是用的物理地址。在开始前内核代码被加载到物理内存的0x80000000开始,而从0到该地址都是表示的硬件。真正的物理内存从0x80000000开始。
_entry:
    # set up a stack for C.
        # stack0 is declared in start.c,
        # with a 4096-byte stack per CPU.
        # sp = stack0 + (hartid * 4096)
        la sp, stack0 # 
        li a0, 1024*4
    csrr a1, mhartid
        addi a1, a1, 1
        mul a0, a0, a1
        add sp, sp, a0 # 上面汇编简单来说就是设置了一个4k大小的栈空间 用来执行c代码  
    # jump to start() in start.c 
        call start # 执行start
spin:
        j spin

具体的start 代码就不看了不是重点。只需要知道在start中将 机器模式 改变成了 内核模式。 并且开启了时钟中断。然后将指针指向了一个main函数。 这个main函数就是万物的开始

void
main()
{//进入内核线程
  if(cpuid() == 0){
    consoleinit();
    printfinit();
    printf("\n");
    printf("xv6 kernel is booting\n");
    printf("\n");
    kinit();         // physical page allocator
    kvminit();       // create kernel page table
    kvminithart();   // turn on paging
    procinit();      // process table
    trapinit();      // trap vectors
    trapinithart();  // install kernel trap vector
    plicinit();      // set up interrupt controller
    plicinithart();  // ask PLIC for device interrupts
    binit();         // buffer cache
    iinit();         // inode cache
    fileinit();      // file table
    virtio_disk_init(); // emulated hard disk //前面都是执行的一些初始化 暂时不用管
    userinit();      // first user process//从这个开始 开启了第一个进程。
    __sync_synchronize();
    started = 1;
  } else {
    while(started == 0)
      ;
    __sync_synchronize();
    printf("hart %d starting\n", cpuid());
    kvminithart();    // turn on paging
    trapinithart();   // install kernel trap vector
    plicinithart();   // ask PLIC for device interrupts
  }
​
  scheduler();        
}
​
void
userinit(void)
{
  struct proc *p;
​
  p = allocproc();//创建了第一个进程
  initproc = p;
 
  // allocate one user page and copy init's instructions
  // and data into it.
  uvminit(p->pagetable, initcode, sizeof(initcode)); //将一段硬编码的 initcode塞进这个进程的代码段。
                                                 //也就是initcode.S汇编程序
  p->sz = PGSIZE;
​
  // prepare for the very first "return" from kernel to user.
  p->trapframe->epc = 0;      // user program counter
  p->trapframe->sp = PGSIZE;  // user stack pointer
​
  safestrcpy(p->name, "initcode", sizeof(p->name));
  p->cwd = namei("/");
​
  p->state = RUNNABLE;//这个进程可以被调度了 
​
  release(&p->lock);
}
//执行下面的汇编
# Initial process that execs /init.
# This code runs in user space.
​
#include "syscall.h"
​
# exec(init, argv)
.globl start
start:
        la a0, init
        la a1, argv
        li a7, SYS_exec #系统调用进入sys_exec 代码 ,执用户态的init程序 也就开启了第一个用户进程
        ecall
​
# for(;;) exit();
exit:
        li a7, SYS_exit
        ecall
        jal exit
​
# char init[] = "/init\0";
init:
  .string "/init\0"
​
# char *argv[] = { init, 0 };
.p2align 2
argv:
  .long init
  .long 0

上面的看个大概就行,不用特别懂。我也没系统学过riscv-V的汇编,不过无所谓。后面会经常进入这个main函数来看看的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值