《操作系统真象还原》-阅读笔记(下)
第十一章
任意進程的頁目錄表第0~767個頁目錄項屬于用戶空間,指向用戶頁表。第768~1023個頁目錄項指向內核頁表。每創建一個新的用戶進程,就將內核頁目錄項復制到用戶進程的頁目錄表,其次需要把用戶頁目錄表中最后一個頁目錄項更新為用戶進程自己的頁目錄表的物理地址。
每個進程有自己單獨的位圖,存儲在進程pcb中的userprog_vaddr中。
LDT
當前運行的任務,其LDT位于LDTR指向的地址(選擇子)。每切換一個任務時,需要lldt指令重新加載新任務的LDT到LDTR
TSS
CPU自動用此結構體變量保存任務的狀態(任務的上下文環境,寄存器的值)和自動從此結構體變量中載入任務的狀態。TR寄存器始終指向當前任務的TSS,這樣每個任務必須有單獨的TSS,但Linux并未這么做。Linux所有任務共享一個TSS。
除了中斷和調用門返回外,CPU不允許從高特權級到低特權級。CPU在不同的特權級下使用不同的棧。
TSS和LDT一樣,必須要在GDT中注冊。
現代操作系統采用的任務切換方式
我們使用TSS的唯一理由是為0特權級的任務提供棧。
Linux中為每個CPU創建一個TSS,在各個CPU上的所有任務共享同一個TSS,各CPU的TR寄存器保存各自的TSS,在用ltr指令加載TSS后,TR寄存器永遠指向同一個TSS,進程切換時只把這個TSS中的SS0和esp0更新為新任務的內核棧的段地址和棧指針。
第十二章
系統調用
Linux只占用一個中斷向量號0X80,在寄存器eax中寫入子功能號。所有系統調用都可以通過syscall函數(不是由系統提供的,是glibc提供的庫函數,直接系統調用為_syscall)完成。總之對用戶進程而言,在 Linux 上執行系統調用,只需要提供子功能號和參數就行了。
堆內存管理
arena
將一大塊內存劃分為無數小內存塊的內存倉庫。
arena 是個提供內在分配的數據結構,它分為兩部分,一部分是元信息,用來描述自己內存池中空閑內存塊數量,這其中包括內存塊描述符指針(后面介紹),通過它可以間接獲知本 arena 所包含內存塊的規格大小,此部分占用的空間是固定的,約為 12 字節。另一部分就是內存地區域,這里面有無數的內存塊,此部分占用 arena 大量的空間。
本書中針對小內存塊的arena占用1頁框內存。
每個內存塊命名為mem_block,分別為每一種規格的內存塊建立一個內存塊描述符即mem_bloc_desc。
struct mem_block_desc (
uint32 t block size //內存塊大小
uint32_t blocks_per_arena; //本arena中可容納此 mem_block 的數量
struct list free list //目前可用同類的mem_block鏈表
實現sys_malloc
傳入參數size,代表申請多少字節內存
1.首先判斷用哪個內存池,內核還是用戶
2.若申請的內存不在內存池容量范圍內,直接返回NULL
3.超過1024就直接分配頁框
4.若小于等于1024,循環各種規格,找到最合適的規格
5.判斷該規格free_list是否為空,若mem_block_desc的free_list中已經沒有可用的mem_block,就創建新的arena提供mem_block
6.從free_list中彈出一個內存塊,通過elem2entry宏轉換成mem_block的地址,返回內存塊地址
內存釋放(頁框級別)
分配內存的步驟
1.虛擬地址池中分配虛擬地址,操作位圖
2.物理內存池中分配物理地址,操作位圖
3.完成虛擬地址和物理地址的映射
釋放內存的步驟
1.釋放物理頁地址,操作位圖
2.在頁表中去掉虛擬地址的映射,將虛擬地址對應pte的P位置0
3.在虛擬地址中釋放虛擬地址,操作位圖
當物理內存不多時,就將其數據移到硬盤中,然后對應頁表項pte的P位置0,當CPU訪問時會引發pagefault中斷,中斷處理程序將數據物理頁更新到pte中,再將P位置1,CPU會再次訪問引起pagefault的虛擬地址。
實現sys_free
對于大內存,就是把頁框在虛擬內存池和物理內存池的位圖中相應位置0。
對于小內存,是將arena中的內存塊重新放回到內存塊描述符的空閑塊鏈表free_list。
第十三章
編寫硬盤驅動(略)
第十四章
硬盤的讀寫單位是扇區,數據一般積攢到足夠大小才一次性訪問硬盤,足夠大小的數據就是塊,一個塊由多個扇區構成。
FAT32
用鏈表的方式來連接每個數據塊,查詢某個數據塊很耗時
inode
控制,管理文件相關信息的數據結構是FCB,inode就是其中一種。
用索引來查找數據塊,在UNIX系統中,一個文件必須對應一個inode(索引表),磁盤中有多少文件就有多少個inode。inode的結構如下如圖,前12個索引為直接指針,后面的3個為間接索引的地址。每個間接索引表都可存256個塊。
在Linux中每分區inode數量是固定的,分區中所有文件的inode通過一個大表格來維護,此表格稱為inode_table。
目錄項
通過文件名找文件實體數據的流程是:
1.在目錄中找到文件名所在的目錄項
2.從目錄項中獲取inode編號
3.用inode編號作為inode數組的索引下標,找到inode
4.從該inode中獲取數據塊的地址,讀取數據塊
目錄項僅存在于inode指向的數據塊中,有目錄項的數據塊就是目錄,目錄項所屬的inode指向的所有數據塊便是目錄。
每個分區都有自己的根目錄,根目錄/的位置是固定不變的,查找任意文件時,都直接到根目錄的數據塊中找相關的目錄項,然后遞歸查找,最終可以找到任意子目錄中的文件。
超級塊
超級塊是保存文件系統元信息的元信息。它被固定在各分區的第2個扇區。
文件系統布局
Linux早期文件系統布局如下圖
第十五章
fork
fork()會產生一個和父進程完全相同的子進程,但子進程在此后多會exec系統調用,出于效率考慮,Linux中引入了“寫時復制“技術,也就是只有進程空間的各段的內容要發生變化時,才會將父進程的內容復制一份給子進程。在fork之后exec之前兩個進程用的是相同的物理空間(內存區),子進程的代碼段、數據段、堆棧都是指向父進程的物理空間,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。當父子進程中有更改相應段的行為發生時,再為子進程相應的段分配物理空間,如果不是因為exec,內核會給子進程的數據段、堆棧段分配相應的物理空間(至此兩者有各自的進程空間,互不影響),而代碼段繼續共享父進程的物理空間(兩者的代碼完全相同)。而如果是因為exec,由于兩者執行的代碼不同,子進程的代碼段也會分配單獨的物理空間。
為什么fork后父子進程同一個變量地址打印出來相同
假定父進程malloc的指針指向0x12345678, fork 后,子進程中的指針也是指向0x12345678,但是這兩個地址都是虛擬內存地址 (virtual memory),經過內存地址轉換后所對應的 物理地址是不一樣的。所以兩個進城中的這兩個地址相互之間沒有任何關系。
fork時子進程獲得父進程數據空間、堆和棧的復制,所以變量的地址(當然是虛擬地址)也是一樣的。
每個進程都有自己的虛擬地址空間,不同進程的相同的虛擬地址顯然可以對應不同的物理地址。因此地址相同(虛擬地址)而值不同沒什么奇怪。
具體過程是這樣的:
fork子進程完全復制父進程的棧空間,也復制了頁表,但沒有復制物理頁面,所以這時虛擬地址相同,物理地址也相同,但是會把父子共享的頁面標記為“只讀”(類似mmap的private的方式),如果父子進程一直對這個頁面是同一個頁面,知道其中任何一個進程要對共享的頁面“寫操作”,這時內核會復制一個物理頁面給這個進程使用,同時修改頁表。而把原來的只讀頁面標記為“可寫”,留給另外一個進程使用。
這就是所謂的“寫時復制”。正因為fork采用了這種寫時復制的機制,所以fork出來子進程之后,父子進程哪個先調度呢?內核一般會先調度子進程,因為很多情況下子進程是要馬上執行exec,會清空棧、堆。。這些和父進程共享的空間,加載新的代碼段。。。,這就避免了“寫時復制”拷貝共享頁面的機會。如果父進程先調度很可能寫共享頁面,會產生“寫時復制”的無用功。所以,一般是子進程先調度滴。
(注1:在理解時,你可以認為fork后,這兩個相同的虛擬地址指向的是不同的物理地址,這樣方便理解父子進程之間的獨立性)
(注2:但實際上,Linux為了提高 fork 的效率,采用了 copy-on-write 技術,fork后,這兩個虛擬地址實際上指向相同的物理地址(內存頁),只有任何一個進程試圖修改這個虛擬地址里的內容前,兩個虛擬地址才會指向不同的物理地址(新的物理地址的內容從原物理地址中復制得到))
wait和exit
進程間通信必須要借助內核(無論管道、消息隊列、還是共享內存等進程間通信形式),子進程的返回值肯定是先交給內核,然后父進程向內核要子進程的返回值。父進程調用pid_t wait(int *status)后,內核就把子進程的返回值存儲到status指向的內存空間。子進程的返回值存放在它的PCB中,調用exit后內核會把進程占用的大部分資源回收,比如內存、頁表等,但不能回收PCB,需要將其中的返回值交給父進程后才能回收。
exit調用表面上是結束子進程運行并傳遞返回值給內核,本質上是內核在幕后將進程除了PCB以外的所有資源回收。
孤兒進程
父進程提前退出,父進程的子進程會稱為孤兒進程,被init進程收養
僵尸進程
僵尸進程就是沒有父進程來給某進程收尸,也就是調用wait收取返回值,因此其PCB一直不能被回收。
管道
Linux中管道實現如下圖:
其實就是對一頁框大小的內存區域做讀寫操作
匿名管道:在內核中,父子進程因為有相同的管道描述符,所以都可以訪問這個管道。
有名管道:在文件系統中創建一個管道文件(FIFO,是一種特殊的文件類型),使得該管道對任何進程都可見,因此沒有父子關系也能通信。當一個進程以讀(r)的方式打開該文件,而另一個進程以寫(w)的方式打開該文件,那么內核就會在這兩個進程之間建立管道,所以FIFO實際上也由內核管理,不與硬盤打交道。
Linux共享內存,信號
http://blog.csdn.net/lqygame/article/details/71424917
http://blog.csdn.net/lqygame/article/details/73555430
寫在最后
每天晚上看一點,歷時接近1個月終于把這本書讀完,感謝鋼哥,這本書刷新了我對操作系統的認知,總算對操作系統有了個很全面的了解。這些筆記是針對我自己的一些感覺需要記錄下來的東西,OS還需要學習的東西還有很多啊。
--------------------- ?
作者:月黑風高云游詩人 ?
來源:CSDN ?
原文:https://blog.csdn.net/lqygame/article/details/73850249 ?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的《操作系统真象还原》-阅读笔记(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国内第四大运营商!山东广电192友好预约
- 下一篇: 【转】C#执行rar,zip文件压缩的几