天天看點

Linux 核心源代碼情景分析 chap 1 預備知識1.1 Linux核心簡介1.2 Intel X86 CPU 系列的尋址方式2.1 i386 的頁式記憶體管理機制1.4 Linux 核心中的C語言代碼1.5 Linux 核心源碼中的彙編語言

最近一段時間面試了一些公司後發現, 自己對作業系統的一些概念還是了解的不夠深刻, 之前看的是《作業系統概念 第六版》, 這次覺得應該加點難度, 正好就開始看這本《Linux 核心源代碼情景分析》好了。

1.1 Linux核心簡介

1. 微核心和宏核心的差別

核心中提供各種服務的成分和使用這些服務的程序之間形成了一種典型的Client/Server 關系, 由于有些服務提供者并不是一定非得留在核心中, 他們僅僅隻是服務一部分對象, 這些服務提供者完全可以從核心中轉移出來到程序的層次上, 這樣就可以讓核心得到精簡。最終核心中僅剩下程序間通信的服務了。此時的核心, 我們稱為微核心。 與之相對應的核心稱為 宏核心。

2. linux 源碼的安裝位置

一般在 /usr/src/linux 目錄下。

像我的ubuntu 是在如圖所示的位置中, (終于知道源碼的位置了, 想想有些小高興=_=~~)

Linux 核心源代碼情景分析 chap 1 預備知識1.1 Linux核心簡介1.2 Intel X86 CPU 系列的尋址方式2.1 i386 的頁式記憶體管理機制1.4 Linux 核心中的C語言代碼1.5 Linux 核心源碼中的彙編語言

3. linux 版本号

linux 版本号 x.yy.zz, x 表示核心在涉及或者實作上的重大改變, yy 表示版本變遷, 偶數為穩定版, 奇數為開發版

4. gpl 協定

如果一個軟體是在gnu源代碼的基礎上修改擴充來的, 那麼這個軟體的源代碼也必須對使用者公開, 但可以收費

1.2 Intel X86 CPU 系列的尋址方式

1. 實模式

為處理位址總線和資料總線數目不比對的問題(20 位址總線寬度, 16 資料總線寬度), intel 在8086cpu 中提出了 用段位址 + 偏移的尋址方式。在這種模式下, 記憶體是完全暴露的, 很容易被攻擊

2. 保護模式

保護模式還是建立在段寄存器的基礎上的, 通過添加兩個新的段寄存器 FS, GS實作。

他的基本思路是, 在保護模式下改變段寄存器的功能, 使其從一個單純的基位址(變相的基位址)程式設計一個指針。

如果說實模式是直接通過段寄存器擷取目标位置的位址, 保護模式實際上是在這個過程中間加了一層間接性, 他需要通過通路相應的 位址段描述結構 才能擷取基位址

為實作這個效果80386cpu 增加了兩個寄存器, GDTR 全局性的段描述表寄存器, 和 LDTR 局部性的段描述表寄存器, 通路這兩個寄存器的指令被設計為特權指令。

同時, 段寄存器的定義也被相應修改了

Linux 核心源代碼情景分析 chap 1 預備知識1.1 Linux核心簡介1.2 Intel X86 CPU 系列的尋址方式2.1 i386 的頁式記憶體管理機制1.4 Linux 核心中的C語言代碼1.5 Linux 核心源碼中的彙編語言

隻有将段寄存器中的低 3 位屏蔽後, 與GDTR 或者 LDTR 中的基位址相加 才能得到描述表項的起始位址

3. intel 的平面位址

這其實是将段寄存器設定為0 的一種特例情形

4. 80386 虛存管理

當需要通路一段記憶體的時候, 先通路到他的描述表項, cpu 會檢查描述表項中的 p 标志位, (p 标記目前描述表項所訓示的内容是否在記憶體中), 如果 p 為0, 内容不在記憶體中, cpu 執行一次中斷, 将相應内容載入記憶體空間

5. 80386 權限管理

當需要改變一個段寄存器的内容的時候, cpu 會檢查, 確定該段程式的目前執行權限和段寄存器所指定的要求的權限均不低于所要通路的那一段記憶體的權限dpl

2.1 i386 的頁式記憶體管理機制

1. 段式管理和頁式管理

頁式管理相對段式管理更加先進。 段式存儲管理機制的靈活性和效率都比較差。 一方面是 ‘ 段’ 是可變長度的, 這就給盤區的交換操作帶來不便, 另一方面, 如果未來增加靈活性而将程序的空間劃分成很多的小段的時候, 就需要在程式中頻繁的改變段寄存器的内容。 因而, 段式管理相對而言是比較差的。

2. 80386 的頁式管理

由于80386 是通過段式存儲來實作保護模式的, 因而, 80386 的頁式管理就必須建立在段式存儲管理的基礎上。

ie, 80386中, 頁式存儲管理是通過在段式存儲管理所映射的位址上再加一層位址映射得到的。

線性記憶體, 由段式存儲管理所映射得到的位址。

總結一下: 段式存儲管理先将邏輯位址映射成線性位址, 然後再由頁式存儲管理将線性位址映射成實體位址, 或者, 當不使用頁式存儲管理的時候, 就将線性位址直接用作實體位址。

3. 線性位址到實體位址的映射過程

  1. 從CR3取得頁面目錄的基位址
  2. 以線性位址中的dir 位段為下标, 在目錄中擷取相應頁面表的基位址
  3. 以線性位址中的page位段為下标, 找到相應的頁面描述項
  4. 将頁面描述向中給出的頁面基位址與線性位址中的offset 位段 相加得到實體位址
    Linux 核心源代碼情景分析 chap 1 預備知識1.1 Linux核心簡介1.2 Intel X86 CPU 系列的尋址方式2.1 i386 的頁式記憶體管理機制1.4 Linux 核心中的C語言代碼1.5 Linux 核心源碼中的彙編語言
    Linux 核心源代碼情景分析 chap 1 預備知識1.1 Linux核心簡介1.2 Intel X86 CPU 系列的尋址方式2.1 i386 的頁式記憶體管理機制1.4 Linux 核心中的C語言代碼1.5 Linux 核心源碼中的彙編語言

4. 為什麼使用兩個層次結構呢

出于空間效率的考慮, 如果隻是單個層次的話, 由于很難有程式會用到4G的全部空間, 大部分情況下表項是空的, 造成浪費。

然而使用兩個層次的結構, 頁表可以根據需要來設定,可以不必為空的目錄表項設定相應的頁表, 節省空間

32bit cpu 頁表項最多1024 個, ie, 一個頁面 4KB, 而 64bit 中 一個頁面時 8KB

1.4 Linux 核心中的C語言代碼

1. 單次執行宏

#define DUMP_WRITE(addr, nr) do {memcpy(bufp, addr, nr); bufp += nr;} while ()
           

2. 空操作宏

#define prepare_to_switch() do {} while ()
           

3. Linux 核心中的雙鍊隊列管理

底層是一個list 連結清單

struct list_head{
    struct list_head * next, * prev;
}
           

通過将這個部分加入到需要管理的對象中, 可以實作對相應對象的管理

typedef struct page{
    struct list_head list;
.........
    struct page * next_hash;
.........
    struct list_head lru;
.........
} mem_map_t;
           

那麼如何擷取一個page的位址呢?

#define memlist_entry list_entry

#define list_entry(ptr, type, member) \ 
    ((type *)((char *)(ptr) - (unsigned long)(&((type *))->member)))
           

而page位址的擷取方式如下:

page = memlist_entry(curr, struct page, list)
           

經過C 預處理器的文字替換之後得到:

ie, 通俗的來講:

已知 結構體中 list 項的位址資訊, 隻要減去這個list項在這個結構體中的偏移, 就可以得到這個結構體的位址了

1.5 Linux 核心源碼中的彙編語言

1. 使用彙編的重要性

  1. 有些需要和硬體打交道的代碼, C語言沒有相應相應的指令
  2. CPU 的一些特殊指令也沒有C 語言部分
  3. 有些函數,程式, 過程會被頻繁的調用, 時間效率非常重要, 需要使用高效率的彙編
  4. 有些空間效率有要求的, 也必須使用彙編

2. GUN 的386 彙編語言

GNU 中采用的是 AT&T 的彙編語言, 這和intel 的彙編語言的差别是相當大的

3. 嵌入C代碼中的386彙編語言程式段

  1. 這裡的代碼都是AT&T 風格的, 有不明白的需要對照 intel 的才能看懂
  2. 在C 中插入彙編比較複雜, 會涉及如何配置設定寄存器, 以及如何與C 代碼中的變量相結合的問題。

    一般的代碼片表示形式:

指令部 : 輸出部 : 輸入部 : 損壞部
           

4. memcpy 的底層實作:

Linux 核心源代碼情景分析 chap 1 預備知識1.1 Linux核心簡介1.2 Intel X86 CPU 系列的尋址方式2.1 i386 的頁式記憶體管理機制1.4 Linux 核心中的C語言代碼1.5 Linux 核心源碼中的彙編語言

繼續閱讀