XV6源代碼閱讀--進程與內存管理

由於工作和興趣愛好的關係,接觸了不少實時操作系統, 一般來說實時操作系統基本沒有進程的概念了,無非是任務堆棧的切換。

一直對Linux,Windows這種帶有進程的OS,很好奇,無奈,LINUX代碼很龐大,很難整體把握。

所以去年一直在尋找帶支持進程的OS, 要求簡單,易懂,確實真找不到。最後找到了MIT教學用VX6,便深深的着迷了。

自從調試了VX6的源代碼,發現用MMU來管理進程真是複雜,怪不得很少能找到支持進程的OS了。

通過參考了很多關於XV6的中文的blog,發現幾乎沒有對進程管理和MMU做闡述的。

於是我將去年的筆記部分公開了出來了(直接從word中黏貼出來的)。

 

進程

 

 

 

進程的線程  線程堆棧/用戶堆棧 

進程的內核線程 內核堆棧

一個進程可能在內核線程中等待IO而導致block

P->pgdir 指向進程的頁表

 

 

 

爲什麼不加載到物理的0地址而是0x100000處,因爲0xa0000:0x100000IO設備區

 

 

第一個進程initcode創建

main

         callkvmalloc 設置CPU內核線程的頁表kpgdir

         Calluserinit

                   Callallocproc 初始化 用於進程內核線程執行的 PCB部分數據結構

                   調用setupkvm 給進程分配頁表pgdir

                   調用inituvm

調用kalloc分配一個物理頁mem

將虛擬地址0映射到該物理頁 mappages(0, v2p(mem));爲什麼V2P請看內存管理

initcode拷貝該物理頁

                   設置PCB

Tf->eip=0,表示從0地址開始執行

tf->cs 設置爲SEG_UCODEDPL_USER. tf->ds ES SS設置爲 SEG_UDATA DPL_USER

         設置的cs會使代碼段的CPL3,這樣用戶代碼段只可以訪問PTE_U的頁

tf->eflags = FL_IF允許中斷

tf->esp = PGSIZE  4K  

用戶堆棧的空間最大隻有4K??? 由於堆棧由高地址向地址(即由4K0地址), 用戶代碼和數據都需要佔據空間呀,(代碼從0地址開始)

allocproc        

allocproc 創建進程都會調用allocproc

1          ptable.proc[NPROC]找到一個可用的PCB p

2          調用kalloc給進程內核線程分配物理頁作爲內核堆棧p->kstack,爲什麼是物理內存?此時和邏輯地址什麼關係?

3          設置pid

4          設置內核堆棧

4.1         sp = p->kstack + KSTACKSIZE- sizeof (struct trapframe);      p->tf= sp 設置tf的地址

4.2         sp -= 4;  *(uint*)sp = trapret 設置堆棧內容

4.3         sp -= sizeof *p->context;   p->context= sp 設置context的地址

4.4         p->context->eip = forkret設置堆棧內容

 

注意整個過程沒有記錄內核的ESP?

從下圖可以看到p->context就是指向ESP,而且 swtch(&cpu->scheduler,proc->context); 參數就是進程ESP

 

 

核堆

-----------  <-- p->kstack  低地址

|           |

|  (empty) |

|           |

|    edi   | <-- 第一次p->context (核寄存器)第一元素

|    esi   |

|    ebx    |

|    ebp   |

|    eip   | 第一次forkret函數地址, p->context的最後一元素

|           | 第一次此處爲trapret(fortret調束後的返回的地址).如果是alltraps,處壓ESP

|    edi   |<--p->tf(寄存器).alltraps壓棧結(pushal的最後一reg).trapret手動開始彈出

|    esi   |

|    ebp   |

|    oesp  |

|    ebx   |

|    edx   |

|    ecx   |

|    eax   | 由於pushal入的第一寄存器

|    GS    |

|    FS    |

|    ES    |

|    DS    | 處開始由alltraps  

|   trapno | vectorX入的X編號,比如由vector64入的是64

|    errno | vectorX入的錯誤號0    trapret手動結束彈出

|    EIP   | 產生trap CPU自動壓入     trapretiret彈出

|    CS    | 產生trap CPU自動壓入     trapretiret彈出

|   EFLAGS | 產生trapCPU自動壓入     trapretiret彈出

|    ESP   | 模式的ESP.產生trapCPU自動壓入 如果已經在內核模式,就不會壓入

|    SS    |<-- p->tf結構體最後.模式的SS.產生trapCPU自動壓入,如果已經在內核模式,就不會壓入

|---------- | 地址p->kstack +KSTACKSIZE  高地址 

 

問題:p->context中爲什麼要保存這些寄存器? 注意這些寄存器是calleesave register? 被調用函數需要保存這些寄存器,所以需要壓棧

第一個進程initcode運行

main

         Userinit

         mpmain

                   Scheduler

                            switchuvm(p);

                            swtch(&cpu->scheduler,proc->context); 切換到用戶進程的內核堆棧堆棧

 

                            forkret

                   trapret

         用戶空間

 

調度器scheduler()調用的swtch函數首先esp=p->context,然後從進程內核堆棧恢復進程的內核寄存器,即返回到forkret 函數(p->context->eip)forkret函數返回到trapret (注意fortret函數中沒有局部變量,即沒有堆棧操作,所以allocproc在設置內核堆棧時候將trapret放在forkret的後面)Trapret模擬trap(中斷/異常/系統調用)的返回,進而返回到用戶模式(trapret中會彈出EIP)0地址,寄存器值如下


eax            0x0 

ecx            0x0 

edx            0x0 

ebx            0x0 

esp            0x1000  

ebp            0x0 

esi            0x0 

edi            0x0 

eip            0x0 

eflags         0x202   

cs             0x23

ss             0x2b

ds             0x2b

es             0x2b

fs             0x0 

gs             0x0 


可見代碼段,數據段和堆棧都位於4K之內

第一次系統調用exec

Initcode.s 首先會系統調用exec加載init程序來替換initcode進程的內存,代碼

char init[] = "/init\0";

char *argv[] = { init, 0 };

start:

Push argv

Push init

Push 0  應該是argv的結束符

Eax = 7 (exec系統調用號)

int 64 EXEC系統調用執行init程序

 

執行int 64之前


eax            0x7    

ecx            0x0     

edx            0x0    

ebx            0x0    

esp            0xff4  

ebp            0x0    

esi            0x0      

edi            0x0     

eip            0x11   

eflags         0x202  

cs             0x23  

ss             0x2b  

ds             0x2b  

es             0x2b  

fs             0x0     

gs             0x0    


0xff4處內存爲:       0x00000000     0x0000001c     0x00000024    

print cpus[0].proc->kstack 0x8dfff000

可見內核堆棧使用的ESP=proc->kstack + KSTACKSIZE=0x8dfff000+0x1000=0x8e000000

 

 

 

 

 

執行int 64之後(導致進入vector64,建立trapframe,gdb調試已經執行完push 0)


eax            0x7    

ecx            0x0     

edx            0x0    

ebx            0x0    

esp            0x8dffffe8 

ebp            0x0    

esi            0x0      

edi            0x0     

eip            0x80106c10        

eflags         0x202  

cs             0x8     

ss             0x10  

ds             0x2b  

es             0x2b  

fs             0x0     

gs             0x0    


內核堆棧trapframe的內容如下

0x8dffffe8:        0x00000000  (push 0) errorno

                            0x00000013  eip

                            0x00000023  CS

                            0x00000202  eflags

0x8dfffff8:         0x00000ff4       用戶空間esp

0x0000002b  用戶空間ss

0x8e000000:

 

接下來的sys_execexec 都是在這個內核堆棧上。

 

exec

exec (char *path, char **argv)

主要執行以下內容

1.      ELF文件頭,檢測是否有效

2.      調用setupkvm分配頁表pgdir 爲什麼要重新分配一個頁表呢?

3.      分析elf各個段

a)        根據段大小,調用allocuvm爲這些段分配頁,注意仔細看代碼

b)        調用loaduvm加載ELF的段到該段的虛擬地址對應的內核地址(虛擬地址轉換成物理地址,p2v轉換成內核地址) ,爲什麼不直接放到進程虛擬地址(對應的頁已經分配了啦??),因爲頁表還沒有加載呢

4.      調用allocuvm分配2個頁,這兩個頁的虛擬地址接着之前的虛擬地址。

5.      調用clearpteu 清除第一個頁的PTE_U標誌(進程空間無法訪問,應該作爲guard page)

6.      Sp =sz sz爲整個程序空間的長度包括堆棧,並根據argv 設置當前的堆棧

 

Init堆棧要如下:

低地址Y:  ustack[0] PC=0xffffffff

       ustack[1]字符串的個數N

            ustack[2] 內容爲char *argv[]的地址X

         X:  ustack[3+0] 內容爲argv[0]地址

            ustack[3+1] 內容爲argv[1]地址

       ustack[3+2] 內容爲argv[2]地址

       ustack[3+N] 內容爲argv[3+N]地址

            ustack[3+N+1 ] 內容爲0

argv[N]整個字符串可能好多字節 4字節對齊

argv[2]整個字符串可能好多字節 4字節對齊

argv[1]整個字符串可能好多字節 4字節對齊

高地址argv[0]整個字符串 可能好多字節 4字節對齊

可以的得知initcodeinitargv都被拷貝到堆棧了。 X處應該不會反回的吧,這些都浪費了。。

 

但是由於新的頁表還沒有加載,當前堆棧是無法直接訪問的,如何將進程空間的initargv數據拷貝到堆棧中的呢,請參考 copyout函數

7.      設置path

8.      oldpgdir = proc->pgdir;  保存原來的頁表

9.      proc->pgdir = pgdir; 更新進程的頁表

10.  proc->sz = sz; 重新設置進程整個空間的大小

11.  proc->tf->eip =elf.entry;  設置eip的入口

12.    proc->tf->esp = sp; 設置堆棧spsp的值爲地址Y

13.    switchuvm(proc);

14.    freevm(oldpgdir); 並返回

 

 

進程調度

上下文切換

從進程的內核線程 切換到 CPU的調度線程/scheduler thread

CPU的調度線程切換到 新進程的內核線程

 

CPU的調度線程/CPU內核線程 用於執行調度器

 

Scheduler

         得到狀態RUNABLE的進程proc

Switchuvm(proc) 加載進程的頁表

         設置狀態爲RUN

         Swtch(&cpu->scheduler,proc->context) CPU內核線程切換到進程的內核線程          

         Switchkvm

 

 

swtch

進程的內核線程會調用swtch來保存上下文(內核堆棧和內核寄存器) 返回到 調度器的上下文。

swtch用於切換內核堆棧和內核寄存器

Swtch調用代碼如下:

extern struct cpu *cpuasm("%gs:0");       // Thiscpu.

extern struct proc *procasm("%gs:4");     // Currentproc on this cpu.

swtch(&cpu->scheduler,proc->context);

返彙編如下:

mov %gs:0x4,%eax      proc的內容eax

mov 0x1c(%eax),%eax   proc->context -> EAX

mov %eax,0x4(%esp)    proc->context 壓入堆棧

mov %gs:0x0,%eax

add $0x4,%eax

mov %eax,(%esp)      &cpu->scheduler 壓入堆棧

call swtch

 

swtch: // swtch(struct context **old,struct context *new);

 movl 4(%esp), %eax  ;eax內容爲&cpu->scheduler

 movl 8(%esp), %edx  ;edx 內容爲proc->context

 

 pushl %ebp

 pushl %ebx

 pushl %esi

 pushl %edi

 

 movl %esp, (%eax)  ;將當前內核堆棧ESP壓入 cpu->scheduler  cpu->scheduler=esp

 movl %edx, %esp   ;proc->context 賦值 當前內核堆棧ESP esp = proc->context

 

 popl %edi

  popl%esi

 popl %ebx

 popl %ebp

  ret

 

問題:

Swtch 保存cpu內核線程的寄存器,然後就沒有了。

如果下次切換到CPU內核線程,會出現什麼情況呢?

         因爲剛開始CPU內核線程使用的堆棧爲stack, swtch只是將一些寄存器保存到cpu->scheduler沒有保存ESP.

         CPU內核線程恢復時候,直接從cpu->scheduler上恢復寄存器,還能正確找到原來的堆棧stack嗎?

回答:

         其實swtch已經保存了esp: cpu->scheduler =esp, cpu->scheduler指向CPU內核線程的內核堆棧棧頂

         下次CPU內核線程恢復運行時,esp=cpu->cheduler,即可從CPU內核堆棧上恢復寄存器

 

 

 

總結

CPU內核線程/調度線程

執行 scheduler()

1.      選擇就緒的進程p, proc =p

2.      switchuvm(proc)

3.      swtch(&cpu->scheduler,proc->context)

4.      switchkvm

5.      proc = 0, 1開始重新執行

永遠不會返回

可見proc表示當前進程的PCB

 

第一個進程的運行

mainc

調用mpmain

                   調用scheduler

                            調用swtch 保存CPU內核線程的ESP和寄存器,並切換到一個進程的的內核線程

                   返回到forkret

         返回到trapret

返回到用戶進程

 

時間片輪轉調度

 

定時中斷 T_IRQ0 + IRQ_TIMER=32

 

情況1:普通情況,進程運行過程中產生定時中斷

vector32 jmp alltraps      

         trap

                   walkup

yield

sched

swtch 切換到CPU的內核線程

返回到scheduler()switchkvm,選擇kpgdir作爲頁表,

                            繼續執行的scheduler()的第5步驟,又開始執行scheduler()的第1,2,3步驟(選擇新的進程)

                            執行scheduler():swtch 切換到新進程的內核寄存器和內核堆棧

                   返回到新進程sched():swtch函數調用的下條指令

         返回到yield

返回到trap

         返回到alltraps

返回到新進程用戶空間

 

進程調用Sched(),sched調用swtch切換到CPU調度線程

(先保存進程的寄存器和swtch函數的返回地址)

CPU調度線程調用Sched(),sched調用swtch切換到進程

(先保存CPU調度進程的寄存器和swtch函數的返回地址)

 

其實可以看到scheduler函數和sched函數是互相切換運行,又稱co-routines

 

CPU處於IDLE狀態(沒有可運行的進程), IDLE狀態的CPUscheduler函數一開始就上鎖, 正在運行進程的其他cpu無法實施上下文切換或者進程相關的系統調用,也無法設置一個進程爲可運行以至於讓IDLECPU跳出調度循環。

接着關於lock,沒看懂

 

 

情況2:在 cpu內核線程運行時(在執行scheduler()函數),產生定時中斷

vector32

         Alltraps

                   Trap但是,proc0

         直接退出中斷

 

額外說明:

比如定時中斷髮生在 情況1某個步驟,會不會產生中斷嵌套呢?

應該不會,因爲執行了swtch之後,內核堆棧切換到CPU內核堆棧上了,而中斷使用的堆棧是什麼?

經過調試發現,進入中斷時候ESP有時0XFFFFB4(B4+4C(trapframe結構體的長度,由中斷自行壓入)),應該打斷CPU內核線程

 有時0XFF1D60,應該是打斷了進程內核線程 ,第一個進程初始化的時 kstack0xff1000,內核SP0Xff2000,那爲什麼不是0XFF1FB4呢?感覺是嵌套了,估計: 在內核模式下運行(中斷,系統調用,異常,反正都是trap)trap模式,ESP已經從TSS中取出,如果在內核模式下再出現中斷,那麼不會重新從TSSESP

估計正確,根據某次調試結果:

進入vector32 esp0XFF0D60,強制跳過yield,vector32返回後,ESP0xff0D70,即壓入16byte4個寄存器

(根據文檔是errno EIP CS EFLAGS ,但是errorno不是由VECTOR32壓入的嗎?

調試:

某次ESP0xff0dc0

0xff0dc0: 0x103cf0(EIP) 0X08(CS)0X202(EFLAGS) 0X10140C()

向量返回後 ESP0xff0dcc  正好是彈出了3個寄存器

 

 

 

LOCKING

內存管理

X86頁式管理

頁表/PAGE TABLE

存放頁表項, 2^20=1M個頁表項/PAGE TABLEENTRY/PTE,

每個頁表項指向物理頁面4K        所以一個頁表能表示4K*1M=4G

在頁表中每個頁表項佔用4BYTE,所以一個頁表佔用4*1M=4M byte

 

PTE

一個PTE 中,PHYSICAL PAGE NUMBER /PPN佔用20bit

線性地址的高20bit,低12bit作爲頁內偏移地址

 

X86的頁表具體如下:

一個頁表Page DirectoryTable/PDT 包好 1024 Page Table Page/PTP/Page Directory Entry

每個PDP 包含1024 32bitPTE

分頁硬件將虛擬地址 10bit 用於指定PDE,如果該PDEPTE_P置位,繼續,否則fault

分頁硬件將虛擬地址中間10bit 用於指定PTE,如果該PTEPTE_P置位,繼續,否則fault

 

 

 

線性地址轉換原理,

CPU得到一個虛擬地址後,首先通過段式管理得到線性地址

取出線性地址的高20bits後,

CR3 頁表寄存器

         頁表項/PTE=頁表基地址/PT+(線性地址>>20)

         物理頁面=頁表項的高20bit

         物理地址 = 物理頁面+線性地址的低12bits

 

每個PTE中含有標誌位來表示對應的線性地址的訪問權限

PTE_P  表示當前PTE是否有效,如果訪問無效的PTE會導致fault

PTE_W 可寫,不可寫的話只能讀和執行

PTE_U  用戶程序可以訪問,否則只能內核程序訪問

 

每個進程都有一個獨立的頁表,進程切換時候需要切換頁表.

 

 

VX6內存管理

不同進程將用戶空間轉換到不同的物理頁,所以進程有私有的用戶空間,進程空間從0地址開始

每個進程的頁表都映射內核空間(KERNBASE以上的地址),內核空間的PTE不會設置PTE_U

即虛擬地址XUKERNBASE~KERNBASE+PHYSTOP映射到0~PHYSTOP。理由是

1.      內核可以使用自己的代碼和數據,不明白什麼意思?

2.      內核有時會寫物理page. 比如分配頁表時,物理地址如果映射可以預測的虛擬地址,就很方便了。不明白?

猜想: 假設內核給某進程的虛擬地址0X0分配的物理頁肯定在0~PHYSTOP範圍內,那麼內核可以直接訪問該地址(位於XUKERNBASE~KERNBASE+PHYSTOP範圍), 也可以該進程空間訪問0x0(實際是通過頁表通過訪問該物理頁)

缺點:

Xv6不能使用超過2G的物理內存. WINXP好像也不能使用2G的內存的。

 

每個進程的頁表包含的用戶空間(進程空間)和內核空間的映射。這樣系統調用和中斷就不用切換頁表。

Xv6保證每個進程使用獨立的用戶空間(利用PTE_U實現), 保證每個進程虛擬地址連續並且開始於0地址.(利用頁表將連續的虛擬地址映射到不連續的物理頁上)

 

地址空間

PA物理地址地址範圍 0~PHYSTOP

UVA進程空間虛擬地址 0~KERNBASE

KA內核空間虛擬地址 KERNBASE~ KERNBASE+PHYSTOP其中

         KERNBASE~KERNBASE+EXTMEMBIOS /IO

         KERNBASE+EXTMEM~KERNBASE+PHYSTOP 映射在 0~PHYSTOP

                  KERNBASE+EXTMEM ~ data xv6內核的text .rodata

                  data ~ end  xv6內核的dataBSS

                  end~ KERNBASE+PHYSTOP 分配其他用(如用戶空間)

 

注意不管怎麼樣,進程空間虛擬地址和內核空間虛擬地址都是應設在物理地址0~PHYSTOP

 

進程空間虛擬地址轉換爲內核空間虛擬地址方法:

進程空間虛擬地址->進程頁表->物理地址

內核空間虛擬地址=p2v(物理地址) ,當然內核空間也可以用進程空間虛擬地址來訪問

 

物理頁分配

內核從end~ KERNBASE+PHYSTOP這個內核空間虛擬地址範圍內來分配頁,所以這個頁地址還是虛擬地址.

一般來說,這個頁可以拿來直接使用了。但是什麼情況下需要轉換爲物理地址:

當要生成頁表的物理映射的時候,就需要v2p轉換爲物理地址

mem = kalloc();分配頁

if(mappages(頁表, 虛擬地址, 地址範圍, v2p(mem)物理地址) 轉換爲物理地址

   

關於entry

所有的物理地址映射好之後, 分配器纔可以初始化了freelist。但是創建一個頁表(並初始化頁表內的映射)需要分配器來分配物理頁。 ---太費解了。。。

Xv6entry是利用獨立的分配器來解決這個問題,儘管不支持釋放頁,受限於4M,但是足夠分配一個內核頁表kpgdir

 

 

Entry 使用的頁表是entrypgdir,頁大小爲4M (cr4|=CR4_PSE)

問題1:進入main程序特別是kvmalloc之後,使用的4K, 這是如何區分的呢?

         CR4.PSE 置爲 1 ,將開啓 4M page,但在具體由相應的 page-translation tables 的屬性來決定哪個是 4M pages,哪個是 4K pages

         查看 entrypgdir 數組一個元素 [0] = (0)| PTE_P | PTE_W | PTE_PS, PTE_PS應該就表示4MPAGE

 

Main

         kinit1(end,P2V(4*1024*1024)); end.text.data.bss之後的地址, P2V(4*1024*1024)0X80400000

         kvmalloc

         kinit2(P2V(4*1024*1024),P2V(PHYSTOP)); 0X80400000

 

爲什麼要兩次調用?

Kinit2 使能locking

物理內存管理
kinit1/ kinit2

kinit1/ kinit2(void *vstart, void *vend)

         freerange(vstart,vend)

freerange

freerange(void *vstart, void *vend)

將內核空間虛擬地址從vstartvend這個範圍以4K爲單位連接起來 kmem.freelist

 

Kalloc

kmem.freelist分配一個頁, 注意該頁使用的是內核空間虛擬地址,要使用V2P才能轉換成真正的物理地址。

沒有特殊說明的話,就直接說明用kalloc函數分配一個物理頁

 

虛擬內存管理/用戶空間

Heapstack之上,可以使用sbrk擴展

Stack佔用一個page,內容如下圖.

         Xv6stack下面有一個guard page(未映射),stack溢出時(堆棧向下增長,使用guard page),導致頁未映射異常。

allocuvm

allocuvm(pde_t *pgdir, uint oldsz, uintnewsz) 將進程空間內存由oldsz擴展到newsz

即需要額外分配 newsz-oldsz 空間

基本原理

1.      調用kalloc分配4K物理頁, oldsz開始的4K映射到該物理頁(這樣進程的虛擬地址就連續了)

2.      oldsz+=4K

3.      oldsz < newsz? ,1,否則退出

loaduvm

loaduvm(pde_t *pgdir, char *addr, structinode *ip, uint offset, uint sz) 加載elf段到用戶空間

1.      判斷addr是否4k對齊

2.      根據addr調用walkpgdir尋找對應頁的pte

3.      根據pte計算物理地址pa

4.      根據ip,offset(段在elf文件中的偏移),長度調用readi讀取信息到 內核空間虛擬地址p2v(pa)

爲什麼不直接讀到addr,請看exec

copyout

copyout(pde_t *pgdir, uint va, void *p,uint len) 從地址p處拷貝數據到va(包含va地址映射的頁表pgdir非當前進程頁表)

1.      pa =uva2ka(pgdir, (char*)va);根據pgdir計算vapa(其實是內核空間虛擬地址)

2.      p處數據拷貝到pa(因爲是內核空間虛擬地址,所以內核可以訪問)

 

uva2ka(pde_t *pgdir, char *uva)

1.      調用walkpgdir得到uvapte

2.      pte PTE_PPTE_U必須存在

3.      p2v(PTE_ADDR(*pte)) 根據PTE計算得到物理地址,在轉換內核空間虛擬地址

虛擬內存管理/內核

鏈接腳本中定義了鏈接地址data (text段結束地址) end(bss段結束)

Kpgdir CPU內核線程使用的頁表

 

 

setupkvm

         調用kalloc分配一個物理頁

         調用mappages 設置頁表,利用kmap映射區域如下

                   virt;     phys_start;    phys_end;  perm; kmap[] = {

KERNBASE, 0,             EXTMEM,    PTE_W // I/O space

KERNLINK, V2P(KERNLINK), V2P(data),0},     // kern text+rodata

data,     V2P(data),     PHYSTOP,  PTE_W}, // kern data+memory

DEVSPACE, DEVSPACE,      0,        PTE_W}, // more devices

虛擬地址到物理地址

虛擬地址0~KERNBASE即用戶空間(text+data+stack+heap),對應的物理內存有內核來分配

虛擬地址KERNBASE~KERNBASE+EXTMEM映射到0~EXTMEMI/O地址空間 BIOS

虛擬地址KERNBASE+EXTMEM~data映射到EXTMEM~V2P(data) 內核textro

                 虛擬地址data~KERNBASE+PHYSTOP映射到V2P(data)~PHYSTOP,內核dataBSS段和可供內核分配的內存

        虛擬地址0xfe000000~0直接映射 (devices such as ioapic)

       

        物理地址到虛擬地址

        0~EXTMEMI/O地址空間 對應虛擬地址KERNBASE~KERNBASE+EXTMEM

        EXTMEM~V2P(data) 對應虛擬地址KERNBASE~KERNBASE+EXTMEM內核textro

        V2P(data)~PHYSTOP對應虛擬地址data~KERNBASE+PHYSTOP內核data,BSS段和可供內核分配的內存

        0xfe000000~0 對應虛擬地址0xfe000000~0 (devices such as ioapic)

 

物理地址V2P(end)~PHYSTOP(對應虛擬地址end~P2V(PHYSTOP)),這段內存內核用於分配內存等(包括用戶空間)

 

Kvmalloc

kpgdir =setupkvm();

       switchkvm();

 

switchkvm

        使用v2p(kpgdir)作爲CPU內核線程頁表

 

Switchuvm

設置TSSss寄存器

設置TSSespproc->kstack +KSTACKSIZE 即設置內核堆棧esp

加載該TSS

加載進程頁表pgdir

Trap處理

中斷由內核來處理

進入ISR要保存寄存器, 從用戶模式切換內核模式

退出ISR要恢復寄存器, 從內核模式切換用戶模式

 

4級保存0~3, 內核模式使用0,用戶模式使用3,當前的保護級別位於CSCPL字段內

中斷子程序定義在IDT中,IDT256的條目,每條包含CSeip信息

0-31爲軟件中斷 

32-63 硬件中斷 INTERRUPT GATE 清除EFLAGFL標誌  32 定時中斷

64 系統調用   TRAP GATE,即不會清除EFLAGFL標誌, 系統調用中允許中斷。DPL_USER

不允許app調用其他中斷(比如 int 非系統調用號),否則會進入異常 VECTOR[13].

 

 

 

Int n調用系統調用,n表示中斷子程序在IDT的索引。運行系統調用指令,CPU的步驟如下:

1.      IDT中取出第n個索引的描述符

2.      檢查CS中的CPL <= DPL, DPL在描述符中

3.      如果目標段選擇字的PL <CPL,保存ESPss CPU內部寄存器中

4.      TSS/任務段描述符加載ssESP(內核模式的)

5.      壓棧原來的ss,原來的ESP (即用戶模式的ESP,SS)

6.      壓棧EFLAGS,CS,EIP,error(視情況)

7.      清空eflags的某些位,根據IDT描述符來設置CS,EIP

 

如果CPU在運行內核模式(已經在特權模式)不會做5步驟中壓棧SS,ESP

 

中斷使用的堆棧是內核堆棧。 TSS 在哪裏設置的?switchuvm中,

每個進程都會公用TSS段,但是不同進程時,設置該TSS段的爲進程的內核堆棧

 

Trap發生, CPU如果在內核模式,不需要做4,5步驟

 

 

系統調用

系統調用函數的代碼如下:

exec:

mov   $0x1,%eax

int   64

ret

 

Vector64 jmp alltraps

         Trap

                   Syscall

                            sys_exec

                                     獲得系統調用參數

                                     exec

 

alltraps中保存完用戶寄存器後,開始設置DS,eS爲內核數據段,之後都是內核操作啦,壓入esp(指向當前的trap frame)作爲C函數trap的參數,然後調用trap

 

trap

         如果是系統調用,首先從proc->tf->eax獲得系統調用號 (EAX=7exec)

執行系統調用(首先獲得系統調用的參數)

         將系統調用的返回值賦值給proc->tf->eax,這樣作爲系統調用函數的返回值返回給用戶空間

 

Fork

*np->tf = *proc->tf; 拷貝整個trapframe

 

中斷

設置eflagFL會允許中斷

CLI 會清空EFLAGSFL

STI會設置EFLAGSFL

 

定時中斷IRQ 0 使用vector32向量

Vector32 jmp alltrap

         Trap

                   wakeup

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章