JOS学习记录
JOS是MIT操作系統對應的課程設計
其課程地址在http://pdos.csail.mit.edu/6.828/2014/index.html
LAB1:系統的啟動
這里主要講了兩個關鍵的點
操作系統的啟動
程序的之間的調用關系
1.操作系統的啟動的過程主要通過以下幾個步驟
首先運行BIOS,這里BIOS完成一些簡單設置,比如VGA的顯示之類
然后加載Boot loader。就是通過BIOS搜索Boot loader.
Boot loader將內核調入
boot load因為歷史原因一般會被加載到內存的0x7c00-0x7dff中,其中加載的內核也是一個ELF文件。通過將各個字段加載到內存當中,最后通過ELFHDR->e_entry()來運行。這里值得一說的是Boot loader。因為Boot loader完成了實模式到保護模式的切換(通過加載GD)。
其中保護模式的切換具有很重要的意義,不僅僅是為了提供更大的地址訪問空間,而且后邊我們也可以看到通過中間增加了一層的訪問的方法,可以實現對內存的讀寫保護.
2程序之間的調用關系
很基礎的東西,就是程序相互調用時。把棧底壓棧,然后返回時彈出
LAB2:內存管理模塊
這個實驗主要實現了操作系統的內存管理模塊。這里實現主要分為兩部分
物理內存的管理
虛擬地址到物理內存的映射
1.物理內存通過鏈表的方式進行管理,定義一個
1 Struct PageInfo{
2 Struct PageInfo * next;
3 size_t ref;
4 }
結構體進行控制,這里next如果是空閑表,指向下個地址,如果不是則為NULL,而ref則主要標記這個物理內存地址被引用的次數,如果等于0,則表示沒有被使用。對于初始化這個控制物理內存的頁表時要注意把那些IO端口映射為已用。
2.虛擬地址到物理內存的映射
Jos利用了一個雙層的頁表項進行管理虛擬地址。這里的虛擬地址空間的管理,主要通過查找這兩個的頁表項進行。其實每個頁表項又可以設置相應的控制位,也就是利用這些控制位從而實現了內存的讀寫保護。其虛擬地址到物理的轉換可以由下圖說明(選自intel手冊 http://pdosnew.csail.mit.edu/6.828/2014/readings/i386/toc.htm)
這里需要提到的地址的轉換過程,以及虛擬地址到物理地址的映射。另外part3部分提及了內核和用戶空間的區分。ULIM之上的是內核空間,一般而言內核和用戶空間之間還有一段空間是內核和用戶都是只能讀取不能修改的,那段空間存放著管理內存的Pagetable以及虛擬內存表,還有環境空間變量。
LAB3 進程以及中斷的切換
這部分實驗主要分為兩個部分進行,第一部分主要是進程的創建和初始化。
1 struct Env {
2 struct Trapframe env_tf; // Saved registers
3 struct Env *env_link; // Next free Env
4 envid_t env_id; // Unique environment identifier
5 envid_t env_parent_id; // env_id of this env's parent
6 enum EnvType env_type; // Indicates special system environments
7 unsigned env_status; // Status of the environment
8 uint32_t env_runs; // Number of times environment has run
9
10 // Address space
11 pde_t *env_pgdir; // Kernel virtual address of page dir
12 };
由PCB可以看出進程中存放的那些變量。其中env_pgdir指的是內存中保存的文件表,而env_tf則是保存各個進程的寄存器數據,可以用以cpu切換時使用。
進程創建過程要求實現的幾個函數。
Exercise 2. In the file env.c, finish coding the following functions:
env_init()
Initialize all of the Env structures in the envs array and add them to the env_free_list. Also calls env_init_percpu, which configuresthe segmentation hardware with separate segments for privilege level 0 (kernel) and privilege level 3 (user).
env_setup_vm()
Allocate a page directory for a new environment and initialize the kernel portion of the new environment's address space.
region_alloc()
Allocates and maps physical memory for an environment
load_icode()
You will need to parse an ELF binary image, much like the boot loader already does, and load its contents into the user address space of a new environment.
env_create()
Allocate an environment with env_alloc and call load_icode load an ELF binary into it.
env_run()
Start a given environment running in user mode.
As you write these functions, you might find the new cprintf verb %e useful -- it prints a description corresponding to an error code. For example,
r = -E_NO_MEM;
panic("env_alloc: %e", r);
will panic with the message "env_alloc: out of memory".
這里分別講一下各個函數的作用以及實現思路:
env_init()主要對進程列表進行初始化,因為進程列表也是放在進程空間當中的。剛開始很明顯要對其進行初始化,比如全部放置在free list里等操作。
env_setup_vm():每個進程很顯然由有自己一個頁表項,這里就是建立進程自己的頁表項。建立頁表項目要注意,這里還沒有涉及到fork指令,所以可以直接對上個實驗的文件目錄進行復制。這樣也就保證了之后的所有進程總體上的結構都與剛開始初始化的頁表項相似。
region_alloc():這個函數主要實現是給定虛擬地址,然后分配相應的物理內存給它。用LAB2中的page_insert實現
*load_icode():加載ELF,把IP寄存器指向ELF中的entry()
env_create():這里需要注意的,創建一個進程并不是真的“創建”,而是對于進程列表中的某個進程項目進行status為free初始化為RUNNABLE
env_run():切換進程,這里有個進行curenv指帶當前運行的進程,切換進程只需要把當前的進程賦值給它。再讀入相關的寄存器的值以及頁表目錄。
中斷切換當中有兩點需要注意的,分別是
1.調用中斷處理程序的過程
2.中斷處理的中的寄存器切換
以下這個圖可以較好的解釋中斷的切換過程
其中通過一個中斷號在IDT(中斷描述符表中進行查找),然后確定相應的中斷處理程序。然后進行中斷切換的工作,這里中斷切換需要先將當前進程的寄存器地址壓棧。然后調用相應的中斷處理程序對其進行處理,處理完成后pop出相應的寄存器的值。這樣也就完成了相應的中斷處理過程。這里有幾點需要注意的
中斷處理過程中需要注意當中斷發生在內核中時,不需要保存ss以及esp寄存器。
系統調用也是一個中斷,通過傳給它相關的參數決定調用哪一個系統調用以及函數的相關傳入參數。
pagefault 不能發生在內核當中,因為如果發生在內核當中會導致內核處理這個pagefault再次出錯,所以處理pagefault處理時要判斷是不是內核發生這種情形了,如果是則panic
TSS段的作用指定保存切換時寄存器的地址,這里指定的地址是內核棧以及內核數據段。
SYSCALL系統調用通過eax觸發,然后通過其它的寄存器進行參數傳遞。
LAB4 多核處理器以及多任務切換
多核處理器的支持。多核處理器有兩個方面的需要注意的。
1.多核處理器的啟動
2.多核處理器之間的通信
先說多核處理器的啟動問題,啟動時都是先單核啟動,由BSP(bootstarp processor)啟動然后通知其它處理器也啟動 ,這里就涉及到了第二個方面的問題,那就是多核處理器之間的通信問題。首先我們要知道對于每個處理器都有一個APIC,要與多核處理器進行通信,首先就要通過APIC。而怎么向APIC中傳遞數據,這里就將內存中的一塊映射為通信區域。對這個區域內進行讀寫就相當于向相應的APIC發送數據。
Per-CPU kernel stack. Per-CPU TSS and TSS descriptor. Per-CPU current environment pointer. Per-CPU system registers.
這里每個cpu需要保存的數據如上所示。
問題 2.為什么要有cpu棧
對于多處理器問題,要實現內核的加鎖功能,從而實現只有一個進程陷入內核的功能。
再實現了內核加鎖功能后,下一步就是要實現內核切換任務的功能,這里的基于LAB3中的env_run來實現的。總體思想就是從內核的就緒態中選取一個處于Runnable的進行,然后調用。這里需要注意如果沒有就緒進程,就調用原先運行的進程,切不可調用其它運行的進行,因為它們可能正在其它cpu上運行。
然后讓實現一個簡略版的fork:
sys_exofork:實現思路就是從envfree表中選取一個,設置其PCB表,這里要注意設置其e->env_tf = curenv->env_tf,也就是設置了eip的值,并且設置其eax為0.那樣也是fork返回0的原因。
sys_env_set_status:設置相應的進程狀態,沒啥好說的
sys_page_map:利用page_insert實現插入頁表
sys_page_unmap:刪除頁表利用page_remove
PART B
fork的實現:
fork()實現思路是這樣,從進程FREE列表里選取一個進程進行初始化設置其為RUNNABLE那么它就可以執行了,這也是fork可以一次執行返回兩次的原因。
fork()是操作系統中非常常用的系統調用中,這里實現了fork函數使用了copy_on_write的機制。這種機制的思想就是當fork時并不復制父進程的內存空間,只有當子進程寫入時才進行復制。這里的處理方法是在PCB中加入page_fault的函數指針,當觸發page_fault時看看進程中的page_fault指針是否為NULL,如果不是則調用該指針。否則調用默認處理。這里之所以不使用內核的默認page_fault函數是因為,內核的page_fault默認處理并不是僅僅復制一份,可能會將磁盤中的數據調入內存,但是這里僅僅需要復制一份,與默認的處理不相符。因為要在用戶空間處理中斷,所以這里也需要在用戶空間開辟一個堆棧UXSTACK用以模擬內核處理中斷的過程。這里中斷的處理首先通過將tf中寄存器內容保存UXSTACK中,然后再將設置esp eip到相應的位置。
copy_on_write的實現思路是這樣的。首先在fork中通過子程序將父程序頁表的地址復制一份,然后利用設置相應的寄存器為不能寫,這樣一旦發生寫操作時候,就會導致page_fault操作,而對于page_fault操作當中想進行壓棧保存工作,然后進行中斷處理。這里很顯然就是想到將發生寫操作的頁copy一份,然后映射到相應pte當中,這樣子進程中發生page_fault處地址的值與父進程并不相同了。
PARTC
這部分實現了兩個方面的功能:
1. IRQ中斷
2. IPC 進程間通信
IRQ中斷與中斷類似不過其執行的是硬件中斷,實驗中利用了IRQ中斷實現了處理器的時間片管理/
如何查找相應的進程?
IPC進程間通信,實驗中通過實現了兩種信息的傳遞,一個int類型的數值以及內存中Page的傳遞。其中int類型傳遞過程比較簡單就是通過虛擬地址中所共有的進程列表,找出所要傳遞的進程號,寫入相應的PCB當中即可。而Page的傳遞稍微復雜些。這個傳遞過程分為傳遞和接收兩部分進行,傳遞方首先要通過page_lookup找出所要傳遞的page,接收放在PCB中有個值是保存用來接收的page地址的,當進程要接收相應的page時,作為接收方首先要將接收的地址初始化,確保其有足夠的空間可以接收這些地址。當然作為接收方和發送方,其發送的數據都要位于UTOP之下,不能發送內核的數據。這里還要注意一個進程切換的機制,發送的先運行,然后切換為接受的運行。
LAB5 文件管理系統、
實驗首先讓你確定一個進程是否可以訪問文件系統,即是否可以進行IO OUT操作,這個在創建進程時就要實現。這里通過FLAG寄存器來實現這個功能。緊接著讓你實現一個block buffer功能的模塊,這個功能的模塊的原理就是在內存中開辟一個區域,用以對硬盤數據進行緩存(文中是0X1000000 - 0xD0000000)。利用LAB4中的映射,首先映射一個頁到這個虛擬地址當中,然后調用ide_read讀入數據。而flush功能與其相反,是寫入到內存當中,這里需要注意的內存中是不是存在要寫入的頁,這里通過PTE_D這個位置來判斷。
通過IPC進行文件系統的讀?。?/p>
并不是所有的進程都可以進行文件系統的讀取的,ex1中已經完成這部分的功能了。通過FLAG寄存器來判斷。很顯然就會聯想到使用IPC來進行操作文件系統。以下給出讀寫文件的框架圖。
Regular env FS env
+---------------+ +---------------+
| read | | file_read |
| (lib/fd.c) | | (fs/fs.c) |
...|.......|.......|...|.......^.......|...............
| v | | | | RPC mechanism
| devfile_read | | serve_read |
| (lib/file.c) | | (fs/serv.c) |
| | | | ^ |
| v | | | |
| fsipc | | serve |
| (lib/file.c) | | (fs/serv.c) |
| | | | ^ |
| v | | | |
| ipc_send | | ipc_recv |
| | | | ^ |
+-------|-------+ +-------|-------+
| |
+-------------------+
這里分別解釋下上圖中的一些函數的作用
read():也就是平常我們所謂的read函數的系統調用
devfile_read():這里把需要讀去的文件描述符添加到相應的進程通信結構體中,并且將其讀取的n。以及文件描述符傳遞給它
fsipc():給相應的fs env進行發送相應的通信結構體,并且信號中包含是讀還是寫。這里查找是否是文件進程,通過搜索進程表。查看其是否是ENV_TYPE
ipc_send():
可以看出對文件系統的操作是通過IPC交給相應的文件系統操作進程進行的。以下再給出對于每個打開文件的說明結構體
1 // The file system server maintains three structures
2 // for each open file.
3 //
4 // 1. The on-disk 'struct File' is mapped into the part of memory
5 // that maps the disk. This memory is kept private to the file
6 // server.
7 // 2. Each open file has a 'struct Fd' as well, which sort of
8 // corresponds to a Unix file descriptor. This 'struct Fd' is kept
9 // on *its own page* in memory, and it is shared with any
10 // environments that have the file open.
11 // 3. 'struct OpenFile' links these other two structures, and is kept
12 // private to the file server. The server maintains an array of
13 // all open files, indexed by "file ID". (There can be at most
14 // MAXOPEN files open concurrently.) The client uses file IDs to
15 // communicate with the server. File IDs are a lot like
16 // environment IDs in the kernel. Use openfile_lookup to translate
17 // file IDs to struct OpenFile.
18
19 struct OpenFile {
20 uint32_t o_fileid; // file id
21 struct File *o_file; // mapped descriptor for open file
22 int o_mode; // open mode
23 struct Fd *o_fd; // Fd page
24 };
如注釋所描述的struct File 是描述文件的物理結構,struct Fd是文件描述符,里邊是文件讀取位置,文件設備以及文件打開模式等的數據。這里的結構體需要注意進程之間
傳送只傳送文件描述符,這個結構體將文件描述符與實際存放的地址結合起來。也就是struct File中的數據。
1 struct Fd {
2 int fd_dev_id;
3 off_t fd_offset;
4 int fd_omode;
5 union {
6 // File server files
7 struct FdFile fd_file;
8 };
9 };
ex5,ex6要求實現文件的讀取與寫入操作。
這里文件的讀取與寫入操作的過程如下(這里的fs env可能有多個)
1.首先通過IPC將要打開的文件id以及讀取的byte傳遞給 fs env
2. fs env通過id轉換為相應的打開文件,并且通過OpenFile這個結構體中相應的文件描述符等信息讀取文件
3. 讀取文件后傳入到IPC的buffer當中,再利用IPC傳送回去
最后附上一張虛擬地址的分布圖,可以說這幾個實驗就是一步步解釋這個內存分布中各個區域的作用。
總結
- 上一篇: 哪些钱借了可以不还?
- 下一篇: 法宣在线自动考试