日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

xv6源码阅读——xv6的启动,进程初识

發布時間:2023/12/9 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 xv6源码阅读——xv6的启动,进程初识 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 說明
  • 1.xv6的啟動
    • 1.1.kernel/entry.S
    • 1.2.kernel/start.c
    • 1.3.kernel/main.c
    • 1.4.kernel/proc.c
  • 2.進程
    • 2.1.進程管理
    • 2.2 進程狀態
  • 參考資料

說明

  • 閱讀的代碼是 xv6-riscv 版本的
    涉及到的文件如下
  • kernel
    entry.S、start.c、main.c、kalloc.c、vm.c、proc.c、swtch.S、proc.h、printf.c、trap.c
  • user
    initcode.S

1.xv6的啟動

  • 這一部分講述xv6 在啟動過程中的配置以及 xv6 中第一個 shell 進程的創建過程

1.1.kernel/entry.S

  • 當 xv6 的系統啟動的時候,首先會啟動一個引導加載程序(存在 ROM 里面),之后裝載內核程序進內存
    注意由于只有一個內核棧,內核棧部分的地址空間可以是固定,因此 xv6 啟動的時候并沒有開啟硬件支持的 paging 策略,也就是說,對于內核棧而言,它的物理地址和虛擬地址是一樣的

  • 在機器模式下,CPU是從_entry開始執行的

# kernel/entry.S _entry:# 設置一個內核棧# stack0 在 start.c 中聲明, 每個內核棧的大小為 4096 byte# 以下的代碼表示將 sp 指向某個 CPU 對應的內核棧的起始地址# 也就是說, 進行如下設置: sp = stack0 + (hartid + 1) * 4096la sp, stack0 # sp = stack0li a0, 1024*4 # a0 = 4096csrr a1, mhartid # 從寄存器 mhartid 中讀取出當前對應的 CPU 號# a1 = hartidaddi a1, a1, 1 # 地址空間向下增長, 因此將起始地址設置為最大mul a0, a0, a1 # a0 = 4096 * (hartid + 1)add sp, sp, a0 # sp = stack0 + (hartid + 1) * 4096# 跳轉到 kernel/start.c 執行內核代碼call start

1.2.kernel/start.c

  • 函數start執行一些僅在機器模式下允許的配置,然后切換到管理模式。RISC-V提供指令mret以進入管理模式,該指令最常用于將管理模式切換到機器模式的調用中返回。而start并非從這樣的調用返回,而是執行以下操作:它在寄存器mstatus中將先前的運行模式改為管理模式,它通過將main函數的地址寫入寄存器mepc將返回地址設為main,它通過向頁表寄存器satp寫入0來在管理模式下禁用虛擬地址轉換,并將所有的中斷和異常委托給管理模式。
  • strart()函數的調用
    • 函數start執行一些僅在機器模式下允許的配置,然后切換到管理模式。
      • 它在寄存器mstatus中將先前的運行模式改為管理模式
      • 它通過將main函數的地址寫入寄存器mepc將返回地址設為main
      • 它通過向頁表寄存器satp寫入0來在管理模式下禁用虛擬地址轉換,并將所有的中斷和異常委托給管理模式。
      • 對時鐘芯片進行編程以產生計時器中斷。
    • start通過調用mret“返回”到管理模式。
void start() {// set M Previous Privilege mode to Supervisor, for mret.unsigned long x = r_mstatus();x &= ~MSTATUS_MPP_MASK;x |= MSTATUS_MPP_S;w_mstatus(x);// set M Exception Program Counter to main, for mret.// requires gcc -mcmodel=medanyw_mepc((uint64)main);// disable paging for now.w_satp(0);// delegate all interrupts and exceptions to supervisor mode.w_medeleg(0xffff);w_mideleg(0xffff);w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);// ask for clock interrupts.timerinit();// keep each CPU's hartid in its tp register, for cpuid().int id = r_mhartid();w_tp(id);// switch to supervisor mode and jump to main().asm volatile("mret"); }

1.3.kernel/main.c

  • 主要工作就是初始化一些配置
void main() {if(cpuid() == 0){consoleinit(); // 配置控制臺屬性(鎖, uart寄存器配置)printfinit(); // 配置 printf 屬性(鎖)printf("\n");printf("xv6 kernel is booting\n");printf("\n");kinit(); //物理頁分配器kvminit(); // 創建內核頁表kvminithart(); // 開啟分頁機制procinit(); // 初始化進程表(最多支持 64 個進程)trapinit(); // 初始化中斷異常處理程序的一些配置(鎖)trapinithart(); // 設置內核異常plicinit(); // 設置中斷控制器plicinithart(); // 請求PLIC設備中斷binit(); // 初始化高速緩沖存儲器iinit(); // 初始化inode緩存fileinit(); // 初始化文件表virtio_disk_init(); // emulated hard diskuserinit(); //創建第一個進程__sync_synchronize();started = 1;} else {while(started == 0);__sync_synchronize();printf("hart %d starting\n", cpuid());kvminithart(); // turn on pagingtrapinithart(); // install kernel trap vectorplicinithart(); // ask PLIC for device interrupts}scheduler(); }

1.4.kernel/proc.c

  • 下面我們看一看userinit()函數具體干了些什么
// Set up first user process. 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));p->sz = PGSIZE;// prepare for the very first "return" from kernel to user.p->trapframe->epc = 0; // user program counterp->trapframe->sp = PGSIZE; // user stack pointersafestrcpy(p->name, "initcode", sizeof(p->name));p->cwd = namei("/");p->state = RUNNABLE;release(&p->lock); }

調用邏輯

  • 調用allocproc()函數來獲取一個空閑進程,及狀態為 UNUSED 的進程
    • 在proc[NPROC]中尋找一個 狀態為 UNUSED 的進程
      • 找不到返回0
      • 找到了對該進程進行一些初始化配置,然后返回一個struct proc
        • 計算 pid
        • 調用 kalloc() 分配一個 trapframe 。
          • 從空閑鏈表中分配一塊空閑頁
        • 分配失敗則調用freeproc(p)釋放
        • 調用函數proc_pagetable(p)為用戶態分配一個頁表
        • 設置 context 寄存器 ra、sp(進程切換)
          • ra:用戶態執行的上下文
          • sp:棧指針
    • 把初始化代碼(一段機器代碼)放入進程的頁表中(只是加載進去,并沒有執行)
    • 準備從內核到用戶的第一次“返回”。
    • epc = 0 用戶程序計數器
    • sp = PGSIZE用戶棧指針
    • 設置進程名稱為 initcode,進程工作目錄為 /
    • 設置進程狀態為 RUNNABLE
  • 最后返回 kernel/main.c 中執行進程調度程序 scheduler(),然后經調度后才開始執行那一段機器代碼。

2.進程

2.1.進程管理

  • proc結構體
// kernel/proc.h struct proc {struct spinlock lock; // 當前進程的鎖// 以下內容如果需要修改的話, 必須持有當前進程的鎖 lockenum procstate state; // 當前進程所處的狀態void *chan; // 非 0 表示當前進程處于 sleep 狀態(睡眠地址)int killed; // 非 0 則表示當前進程被 killedint xstate; // 退出狀態, 可以被父進程的 wait() 檢查int pid; // 進程 ID 號, pid// 如果需要修改父進程指針的話, 需要持有整個進程樹的鎖// kernel/proc.c: pid_lockstruct proc *parent; // 父進程指針// 這些變量對于一個進程來說是私有的, 修改的時候不需要加鎖uint64 kstack; // 內核棧的虛擬地址uint64 sz; // 進程所占的內存大小pagetable_t pagetable; // 用戶頁表struct trapframe *trapframe; // 當進程在用戶態和內核態之間切換時// 用于保存/恢復進程的狀態// 用于保存寄存器struct context context; // 切換進程所需要保存的進程狀態struct file *ofile[NOFILE]; // 打開文件列表struct inode *cwd; // 當前工作目錄char name[16]; // 進程名稱 };
  • 用于管理進程的變量和函數
// kernel/proc.c // 變量 int nextpid = 1; // 用于進程號的編碼 struct proc proc[NPROC]; // 最多支持 64 個進程 struct spinlock pid_lock; // 當修改一些整個進程樹相關的內容的時候, 需要加的鎖// 例如新建一個進程的時候, 需要從 nextpid 中生成一個新的 pid struct spinlock wait_lock; // 輔助于 wait() 使用// 函數 // 創建一個新的進程并且初始化這個進程, 具體內容在上面已經提到過了 void allocproc(void){} // 釋放進程的內容空間 static void freeproc(struct proc *p){}

2.2 進程狀態

在xv6中進程會有5中狀態
UNUSED
SLEEPING
RUNNABLE
RUNNING
ZOMBIE

enum procstate {// 當前進程沒有被使用, 屬于空閑進程// (1) 系統啟動的時候, 所有的進程的狀態都被初始化 UNUSED// 當 shell 或者其他方式想要新建一個進程的時候, 會查詢是否存在狀態為 UNUSED 的進程// (2) 一個 ZOMBIE 進程被回收之后(wait()), 狀態會被修改為 UNUSEDUNUSED,// 處于睡眠狀態// 調用 sleep() 的時候會從 RUNNING 狀態進入 SLEEPINGSLEEPING,// 表示當前繼承處于可以被調度運行的狀態// (1) wakeup() 可以將一個進程從 SLEEPING 轉向 RUNNABLE// (2) kill() 會將 SLEEPING 進程狀態修改為 RUNNABLE// (3) yield() 會讓出當前進程的執行權, 讓 CPU 重新調度// 狀態: RUNNING -> RUNNABLERUNNABLE,// (1) userinit() 會將 USED 狀態修改為 RUNNING// 這個調用僅在初始化第一個進程的時候出現// (2) 在調用 fork() 的時候, 剛剛被 allocproc() 申請的進程在經過錯誤檢查之后,// USED 狀態會被修改為 RUNNABLE// (3) scheduler() 調度程序可以把 RUNNABLE 狀態的程序修改為 RUNNINGRUNNING,// 處于進程退出但是還沒有被回收的狀態(資源已經被回收, 但是還沒有被父進程發現)// (1) exit() 的調用會讓進程 從高 RUNNING 轉變為 ZOMBIEZOMBIE };

參考資料

  • http://xv6.dgs.zone/tranlate_books/book-riscv-rev1/c1/s0.html
  • xv6-riscv源碼

總結

以上是生活随笔為你收集整理的xv6源码阅读——xv6的启动,进程初识的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。