xv6源码阅读·第一章

第一章·主函数

主函数:

\kernel\main.c
void
main()
{
  if(cpuid() == 0){   // 主CPU
    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();     // GCC 提供的一个内建函数,确保在它之前的所有内存操作(读/写)都在它之后的操作开始之前完成
    started = 1;
  } else {      // 非主CPU
    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();        //  负责选择下一个要运行的进程,并切换到该进程
}

系统加电后,主CPU(xv6最多支持8核心)开始执行(cpuid() == 0),对系统初始化,分别初始化控制台、printf()函数、分页、进程……,然后启动第一个程序,将started更改为1

非主CPU不断查询started变量的值,直到started=1,则开始执行相关初始化操作

__sync_synchronize():GCC 提供的一个内建函数,确保在它之前的所有内存操作(读/写)都在它之后的操作开始之前完成

然后,所有的CPU开始并行运行。

主CPU和非主CPU的功能性区别:

  • 主 CPU非主 CPU 在功能上是相同的,都是执行相同的操作系统代码和调度任务,但在 系统启动初始化阶段 有不同的职责。
  • 主 CPU 负责初始化系统、启动第一个进程,并协调其他 CPU 核心的启动过程。
  • 非主 CPU 在主 CPU 完成初始化后,执行自己的初始化工作,并加入进程调度,实现并行执行任务。

这种设计确保了系统初始化时的有序性,同时为多核并行执行提供了支持。

现在假设所有的初始化任务都已经完成了,并且已经创建了第一个用户进程,CPU开始调用 scheduler() 函数, scheduler() 位于 \kernel\proc.c 中,负责选择下一个要运行的进程,并切换到该进程。下面分析下 scheduler() 源码:

\kernel\proc.c
void
scheduler(void)
{
  struct proc *p;
  struct cpu *c = mycpu();
  
  c->proc = 0;
  for(;;){			// 一个for无限循环,不断运行查询可调度的进程
    
    intr_on();		//  打开中断,确保设备能够通过中断打断当前的进程
    
    int nproc = 0;
    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state != UNUSED) {
        nproc++;
      }
      if(p->state == RUNNABLE) {
        p->state = RUNNING;
        c->proc = p;
        swtch(&c->context, &p->context);
        c->proc = 0;
      }
      release(&p->lock);
    }
      
      //如果系统中可运行的进程数小于等于 2
      //(意味着只有 init 和 sh 进程存在),调度器会调用 wfi 指令(Wait For Interrupt)让 CPU 进入空闲状态,等待中断。
    if(nproc <= 2) {  
      intr_on();
      asm volatile("wfi");
    }
  }
}

proc 数组保存了所有的进程。对于每个进程,首先尝试获取该进程的锁,确保在访问该进程时没有其他线程在修改它。接着检查进程的状态,如果进程不是 UNUSED(未使用状态),则增加 nproc 计数器。

wfi 是一种让 CPU 进入低功耗空闲状态的指令,直到有外部中断(例如硬件中断)打断 CPU

intr_on() 的主要作用是使当前 CPU 可以响应中断。当中断被启用时,CPU 会中断当前正在执行的指令,跳转到中断处理程序(中断向量)来处理外部事件。

swtch() 负责切换上下文,在切换上下文时会切换PC的值,然后CPU就会从新的PC地址(即新进程的地址)开始运行。

swtch.S

.globl swtch
swtch:
        sd ra, 0(a0)		# pc的值
        sd sp, 8(a0)		# 栈
       ...

        ld ra, 0(a1)
        ld sp, 8(a1)
       ...
        
        ret

另外,关于锁的使用,主要目的就是实现对进程的保护,具体实现在下文详细说明。

至此,所有的CPU就开始并行的实现对进程的调度,当进程数量较少时,就会执行 wfi 指令,有可执行的指令时,继续执行进程,计算机的工作,就是无休止的执行 for(;;) 代码。

接下来我们按照系统初始化的过程,依次分析相关代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值