1. 程式人生 > >總結一下linux中的分段機制

總結一下linux中的分段機制

  這篇文章主要說一下linux對於分段機制的處理,雖然都說linux不使用分段機制,但是分段機制屬於CPU的一個功能,即使linux不使用,也要通過程式碼想辦法繞過它,況且linux也使用到了分段機制中的某些功能。

  分段機制主要功能只有兩點:

  • 將實體記憶體劃分為多個段,讓作業系統可以使用大於其地址線對應的實體記憶體(比如正常情況下32位地址線可以訪問4G大小的記憶體,但是有分段後則可訪問大於4G的記憶體)。
  • 許可權控制,將每個段設定許可權位,讓不同的程式訪問不同的段。

  對於linux核心來說,它僅僅只使用了分段機制中的許可權控制功能,具體我們可以一起看看是如何做的。

CPU的段暫存器

  在CPU中,跟段有關的CPU暫存器一共有6個:cs,ss,ds,es,fs,gs,它們儲存的是段選擇符。而同時這六個暫存器每個都有一個對應的非程式設計暫存器,它們對應的非程式設計暫存器中儲存的是段描述符。系統可以把同一個暫存器用於不同的目的,方法是先將其暫存器中的值儲存到記憶體中,之後恢復。而在系統中最主要的是cs,ds,ss這三個暫存器。

  • CS 程式碼段暫存器:指向包含程式指令的段,在CS暫存器中RPL用於表示當前CPU的特權級(CPL),CPL為0是最高許可權(核心態使用),CPL為3是使用者態使用。

  • SS棧段暫存器:指向當前程式的棧的段。

  • DS 資料段暫存器:指向儲存著靜態資料和全域性資料的段(靜態區)。

  在段暫存器中主要儲存的是段選擇符,它的長度是16位,具體如下:

  • 索引號(index):所對應的段描述符處於GDT或LDT中的索引。

  • TI:TI=0表示對應段描述符儲存在GDT(全域性描述符表)中,TI=1表示對應的段描述符儲存在LDT(區域性描述符表)中。

  • RPL:當此對應的段選擇符裝入cs暫存器時,設定CPU當前的特權級的值為RPL,也就是cs暫存器中的RPL就是CPL。

  段選擇符主要用途就是根據段索引號和TI標誌,去到GDT或者LDT中找到這個選擇符對應的段描述符,比如我們在核心程式碼中常見的__KERNEL_CS,__KERNEL_DS,__USER_CS,__USER_DS就是段選擇符,它們並不是段描述符。

全域性描述符表與區域性描述符表

  全域性描述符表和區域性描述符表儲存的都是段描述符,記住要把段描述符和段選擇符區別開來,儲存在暫存器中的是段選擇符,這個段選擇符會到描述符表中獲取對於的段描述符,然後將段描述符儲存到對應暫存器的非程式設計暫存器中。

  系統中每個CPU有屬於自己的一個全域性描述符表(GDT),其所在記憶體的基地址和其大小一起儲存在CPU的gdtr暫存器中。其大小為64K,一共可儲存8192個段描述符,不過第一個一般都會置空,也就是能儲存8191個段描述符。第一個置空的原因是防止加電後段暫存器未經初始化就進入保護模式而使用GDT。

  而對於區域性描述符表,CPU設定是每個程序可以建立屬於自己的區域性描述符表(LDT),當前被使用的LDT的基地址和大小一起儲存在ldtr暫存器中。不過大多數使用者態的liunx程式都不使用區域性描述符表,所以linux核心只定義了一個預設的LDT供大多數程序共享。描述這個區域性描述符表的區域性描述符表描述符儲存在GDT中。

  對於表中的段描述符我們簡單說幾個特別的:

  • TLS段描述符:中文名字是區域性執行緒儲存段,這個會允許執行緒擁有自己的段,不過一般程式不經常會用到的,系統呼叫set_thread_area()與get_thread_area()為當前程序建立和撤銷一個TLS段。

  • TSS段描述符:叫做任務狀態段,這個描述符非常重要,每個處理器包含一個自己的tss段,這個tss段中的主要資料是一個tss_struct結構體,linux會將所有CPU的tss_struct結構體以init_tss陣列的形式儲存起來,這個tss_struct結構體中儲存的時當前執行程序的核心態堆疊棧頂地址和當前程序的IO許可許可權位。當程序切換時就會設定CPU的tss_struct結構體,CPU就可以從tss_struct中獲取當前程序的核心棧和IO許可許可權。

  • kernel code,kernel data,user code,user data:分別是核心程式碼段描述符,核心資料段描述符,使用者程式碼段描述符,使用者資料段描述符,不同的程序會使用同一個使用者程式碼段/資料段描述符,這個也之後介紹。

段描述符

  段描述符就是儲存在全域性描述符表或者區域性描述符表中,當某個段暫存器試圖通過自己的段選擇符獲取對於的段描述符時,會將獲取到的段描述符放到自己的非程式設計暫存器中,這樣就不用每次訪問段都要跑到記憶體中的段描述符表中獲取。

  • BASE(32位):段首地址的線性地址。

  • G:為0代表此段長度以位元組為單位,為1代表此段長度以4K為單位。

  • LIMIT(20位):此最後一個地址的偏移量,也相當於長度,G=0,段大小在1~1MB,G=1,段大小為4KB~4GB。

  • S:為0表示是系統段,否則為程式碼段或資料段。

  • Type:描述段的型別和存取許可權。

  • DPL:描述符特權級,表示訪問這個段CPU要求的最小優先順序(儲存在cs暫存器的CPL特權級),當DPL為0時,只有CPL為0才能訪問,DPL為3時,CPL為0為3都可以訪問這個段。

  • P:表示此段是否被交換到磁碟,總是置為1,因為linux不會把一個段都交換到磁碟中。

  • D或B:如果段的LIMIT是32位長,則置1,如果是16位長,置0。(詳見intel手冊)

  • AVL:忽略。

資料段描述符:

  表示這個段描述符代表一個數據段,這種描述符可以放在GDT或者LDT。該描述符的S標誌位為1,也就是非系統段。需要注意核心資料段屬於資料段描述符,並不屬於系統段描述符。

程式碼段描述符:

  表示這個段描述符代表一個數據段,這種描述符可以放在GDT或者LDT。該描述符的S標誌位為1,也就是非系統段。需要注意核心程式碼段屬於程式碼段描述符,並不屬於系統段描述符。

系統段描述符:

  此描述符代表一個系統段,Type的值代表了是哪一種系統段,S標誌位為0。其中以下兩種都是系統段

  區域性描述符表描述符(LDTD,系統段描述符的一種):

    此種描述符代表一個包含有LDT的段,它只能儲存在GDT中,相應的Type為2,S為0。

  任務狀態段描述符(TSSD,系統段描述符的一種):

    這個描述符代表一個任務狀態段(TSS),這個段用於儲存部分處理器暫存器的內容(核心態棧地址和IO許可許可權位),它只儲存在GDT中,根據相應的程序是否正在CPU上執行,其Type欄位的值分別為11或9.這個描述符S標誌為0。

  在所有段描述符中可能大家最關心的就是核心程式碼段描述符和核心資料段描述符以及使用者程式碼段描述符和使用者資料段描述符了,這裡也具體說說這幾個描述符,它們的構成如下:

  可以看出來它們的S都是1,都是非系統段,注意並不是核心用的段就是系統段,這裡的系統段的區分不是我們使用者態和核心態的這種劃分。所有的使用者程序都是使用同一個使用者程式碼段描述符和使用者資料段描述符,它們是__USER_CS和__USER_DS,也就是每個程序處於使用者態時,它們的CS暫存器和DS暫存器中的值是相同的。當任何程序或者中斷異常進入核心後,都是使用相同的核心程式碼段描述符和核心資料段描述符,它們是__KERNEL_CS和__KERNEL_DS。這裡要明確記得,核心資料段實際上就是核心態堆疊段。

  還可以看出這幾個段的BASE都是0x00000000,LIMIT都是0xfffff,並且G為1。也就是說,使用者程式碼段,使用者資料段,核心程式碼段,核心資料段這四個段它們的定址地址都是0x00000000~0xffffffff。也就是地址0到4G的大小。這也形成了為什麼所有程序都可以使用同一個使用者程式碼段和使用者資料段的條件。並且很清楚地可以看出,核心程式碼段和核心資料段都需要CPL為0時才能訪問,而使用者程式碼段和使用者資料段在CPL為0或者3時都可以訪問。

  再看看這4個段描述符對應的段選擇符:

  可以看出來,它們的TI為0,表示都儲存在全域性段描述符表中。可能看到這裡大家會有個疑問,既然使用者段的RPL為3,那怎麼去訪問DPL為0的核心段呢,這就是linux精明的地方,它就是禁止使用者態訪問核心態的資料,但是核心為使用者態開了兩個小門,然使用者態能夠通過這兩個小門進入到核心態中,這兩個小門就是系統呼叫與中斷和異常。

快速訪問段描述符:

  先看一下系統是如何將邏輯地址轉換為線性地址的:

  邏輯地址是由段選擇符(16位) + 段內偏移量offset(32位)得來。之前也說到,只有處於使用者態,CS和DS暫存器中的值都是__USER_CS和__USER_DS。只要處於核心態,CS和DS暫存器中的值都是__KERNEL_CS和__KERNEL_DS。在我們程式設計過程中,實際上提供的地址都是一個偏移量,系統會自動將這個偏移量與CS中的段選擇符進行結合。也就是我們使用的邏輯地址實際上只使用了offset這一段,段選擇符都為空。之前也說了這四個段描述符的BASE都為0x00000000,也得出當邏輯地址通過這樣的分段機制轉為線性地址後,實際上並沒有變化,也就是邏輯地址=線性地址(其實這兩個地址都是offset的值)。

  也可以看出來,每次進行地址轉換時都要通過段描述符獲取段的基地址然後與偏移量運算得到線性地址,而段描述符是儲存在記憶體當中的,這樣每次轉換難道就要訪問一次記憶體或者cache嗎?當然不是,之前說到一共有6種段暫存器,它們每個都有屬於自己的一個非程式設計暫存器,專門用於存放現在的段描述符,比如拿cs段暫存器說,cs暫存器存放的是段選擇符,所以每次通過邏輯地址訪問這個段裡的內容時,都要通過這個段選擇符與gdtr(段描述符儲存在全域性描述符表中)或者ldtr(段描述符儲存在區域性描述符表中)結合然後從記憶體中得到對應的段描述符,然後根據段描述符的BASE和LIMIT將邏輯地址轉換為線性地址。如果進行連續訪問時(而且連續訪問的概率非常高),這樣的效率就非常低了,這個cs段暫存器對應的非程式設計暫存器就是用於儲存這個段描述符的,這樣就不用每次都從記憶體中獲取段描述符,而是直接從這個CS對應的非程式設計暫存器中獲取段描述符。

任務狀態段(TSS)

  任務狀態段的段選擇符儲存在tr暫存器中,核心為每個CPU準備了一個任務狀態段,其主要儲存的是當前程序的IO許可許可權位和棧頂指標,其作用主要有兩個:

  • 程序從使用者態切換到核心態時,系統會從該CPU的TSS中獲取該程序的核心態堆疊地址。
  • 當用戶態程序試圖通過in或out指令訪問一個IO埠時,CPU需要訪問存放在TSS中的IO許可許可權位以檢查該程序是否有許可權訪問該IO埠。

  TSS段的儲存形式是一個tss_struct結構體,系統會將所有CPU的tss_struct結構體組成一個init_tss陣列的形式進行儲存,我們具體看一下tss_struct結構體:

struct tss_struct {
    /*
     * The hardware state:
     */
    /* 存放暫存器的值的結構體,儲存有棧頂指標SP暫存器的值 */
    struct x86_hw_tss    x86_tss;

    /*
     * The extra 1 is there because the CPU will access an
     * additional byte beyond the end of the IO permission
     * bitmap. The extra byte must be all 1 bits, and must
     * be within the limit.
     */
    /* 當前程序的IO許可許可權位 */
    unsigned long        io_bitmap[IO_BITMAP_LONGS + 1];

    /*
     * .. and then another 0x100 bytes for the emergency kernel stack:
     */
    /* 緊急核心棧 */
    unsigned long        stack[64];

} ____cacheline_aligned;


struct x86_hw_tss {
    u32            reserved1;
    u64            sp0;
    u64            sp1;
    u64            sp2;
    u64            reserved2;
    u64            ist[7];
    u32            reserved3;
    u32            reserved4;
    u16            reserved5;
    u16            io_bitmap_base;

} __attribute__((packed)) ____cacheline_aligned;

中斷或異常發生時的段切換

  其實發生段的切換有兩種情況,一種是系統呼叫發生時,一種是中斷或異常發生時,但是這兩種情況都大同小異,這裡我們只拿中斷異常發生的情況進行說明。

  這裡只說明系統大多數發生的情況,不討論個例。假定當前系統處於使用者態執行程式碼中,這時候各個段暫存器的值應該是這樣的:

  • CS: __USER_CS
  • DS: __USER_DS
  • SS: 儲存著使用者態棧基地址
  • ESP: 儲存著使用者態棧頂地址
  • EIP: 儲存下條將要執行的指令地址

  當中斷或異常發生時,CPU會按照如下步驟進行執行:

  1. 讀取由idtr暫存器儲存的IDT(中斷向量表)中對應的門描述符。
  2. 根據對應的門描述符,獲取其中儲存的段選擇符。(門描述符中儲存有一個段選擇符和一個門的DPL,這兩個部分是段切換的重要部分。具體可看我的部落格:http://www.cnblogs.com/tolimit/p/4415348.html)
  3. 根據這個段選擇符獲取對於的段描述符(門描述符中儲存的段選擇符基本都是__KERNEL_CS)。
  4. 這時CPU會使用CS暫存器中的CPL特權級與獲取的段描述符的DPL特權級比較,如果DPL<=CPL,則通過,否則產生“通用保護”異常,我們也看到,我們CS儲存的是__USER_CS,其CPL為3,門描述符中儲存的是__KERNEL_CS,其DPL為0,;也就是會通過檢查。
  5. 如果是異常情況,這時還會多一步進行檢查,會檢查門描述符中的DPL特權級,當前特權級CPL的值 > DPL的值時,則通過檢查,否則不能通過檢查,而只有系統門和系統中斷門的DPL是3,其他的異常門的DPL都為0。這樣做的好處是避免了使用者程式訪問陷阱門、中斷門和任務門。 
  6. 到這裡檢查已經通過,如果特權級發生變化(使用者態產生的中斷和異常,肯定會發生特權級變化),則CPU會自動幫切換不同特權級使用的暫存器。
  7. 從tr暫存器中獲取CPU的TSS段,從TSS段中獲取當前程序的核心態堆疊指標和SS暫存器的值並將它們裝載到SS和EIP暫存器。
  8. 在當前程序的核心棧中儲存使用者態的SS暫存器和EIP暫存器的值。(注意,這裡是先裝載了SS和EIP暫存器,讓其指向核心棧,再在核心棧中儲存使用者態的SS和EIP暫存器值)
  9. 如果故障已經發生,用引起異常的指令地址裝載到CS和EIP暫存器,從而使這條指令再次被執行。
  10. 在核心棧中儲存使用者態的eflags、CS和EIP。CS和EIP的值就是返回後的下一條指令地址。如果有硬體出錯碼,也儲存到核心棧中。
  11. 從中斷向量表的門中獲取CS和EIP值並裝載到CS和EIP暫存器。門中儲存的CS和EIP合起來就會是中斷處理程式入口地址。

  這些步驟執行完後,暫存器變化為:

  • CS: __KERNEL_CS
  • DS: __USER_DS
  • SS: 儲存著核心態棧基地址
  • ESP: 儲存著核心態棧頂地址
  • EIP: 儲存著中斷處理程式入口地址

  而核心棧中儲存的值有:使用者態CS,使用者態SS,使用者態ESP,使用者態EIP,使用者態eflags。當系統從中斷返回使用者態時,就會從核心棧中將這些值還原,最後會回到進入時的情況。至於為什麼不用修改DS暫存器的值,我也不清楚。

相關推薦

總結一下linux分段機制

  這篇文章主要說一下linux對於分段機制的處理,雖然都說linux不使用分段機制,但是分段機制屬於CPU的一個功能,即使linux不使用,也要通過程式碼想辦法繞過它,況且linux也使用到了分段機制中的某些功能。   分段機制主要功能只有兩點: 將實體記憶體劃分為多個段,讓作業系統可以使用

深入理解計算機系統-之-記憶體定址(四)--linux分段機制的實現方式

linux中的分段機制 前面說了那麼多關於分段機制的實現,其實,Linux以非常有限的方式使用分段。因為,Linux基本不使用分段的機制(注:並不是不使用,使用分段方式還是必須的,會簡化程式的編寫和執行方式),或者說,Linux中的分段機制只是為了相容IA

[轉]總結一下CSS的定位 Position 屬性

pub pos solid 修改 static blog style 分享 正常的 在CSS中,Position 屬性經常會用到,主要是絕對定位和相對定位,簡單的使用都沒有問題,尤其嵌套起來,就會有些混亂,今記錄總結一下,防止久而忘之。 CSS positi

請教一下linux程序重定向輸出到文...[模式及實現]

zcl inux log www. tar xxd html lin scm 56e3xa陌緣唾詠爍蹤http://blog.sina.com.cn/s/blog_17da0698f0102xdu0.html8vbt4y轎匣彩凸莆庇http://blog.sina.com.

簡單總結一下Java的集合

今天面試了深圳遞四方,奇蹟般的過了,在此紀念一下哈哈 技術面有不少問題沒答好,其中一個就是集合,用了這麼久的集合,腦袋裡面還是沒有一個系統的概念,當然要痛定思痛啦! 上一個集合框架圖 然後這個看起來有點混亂,先來一個簡化的 這個圖大概要能回答的出來,常用的還要

總結一下面試遇到的問題,一共面試了4家公司。

基本情況:本科畢業一年,面試職位:北京的Android開發職位。         1.搜狗:             (1)5種基本的Layout:relative,linear,frame,absolute,table.               (2) wait和s

總結一下Android主題(Theme)的正確玩法

在AndroidManifest.xml檔案中有<application android:theme="@style/AppTheme">,其中的@style/AppTheme是引用的res/values/styles.xml 中的主題樣式,也有可能是引用的 res/values-v11/sty

文件總結linux裝置的訪問

1.裝置訪問 1.裝置識別 /dev/xdxn ##硬碟裝置/dev/sda1 /dev/cdrom ##光碟機 /dev/mapper/* ##虛擬裝置 2.裝置的使用 裝置的發現 fdisk -l

【搬運】總結一下面試被問到的jdk幾個版本之間的區別問題

1.5 1.自動裝箱與拆箱: 2.列舉(常用來設計單例模式) 3.靜態匯入 4.可變引數 5.內省 1.6 1.Web服務元資料 2.指令碼語言支援 3.JTable的排序和過濾 4.更簡單,更強大的JAX-WS 5.輕量級Http Server 6.嵌入式資料庫 Derby

Linux核心機制學習總結

一、驅動中的poll機制 1.簡介:select()和poll()系統呼叫的本質一樣,前者在 BSD UNIX 中引入的,後者在 System V 中引入的。 應用程式使用 select() 或 poll() 呼叫裝置驅動程式的 file_operations 的 poll() 函式。 2.實現(1).初

Linux內存尋址之分段機制

tel 寄存器 操作 caption img 代碼段 window linux內存 -1 http://blog.xiaohansong.com/2015/10/03/Linux內存尋址之分段機制/ 、段的起始地址、段的長度等等,而在保護模式下則復雜一些。IA32將它們結合

linux軟件安裝方法總結

linuxlinux中軟件安裝方法:1 rpm -ivh 包名.rpm 有依賴問題,安裝A,A需要先安裝B,B先安裝C等。2 yum安裝,yum包管理器,yum安裝解決rpm安裝的依賴問題,安裝更簡單化。 優點:簡單,易用,高效 缺點:不能定制 centos切換在線yum源地址為ali

Linux查詢當前用戶的命令總結

linux命令1、w命令查看當前用戶[[email protected] /]# w04:31:52 up 1:45, 2 users, load average: 0.00, 0.01, 0.05USER TTY FROM [email p

Linux符號總結

entos a-z each || 取反 結果 範圍 www. anti 常用符號~ 登陸用戶當前的家目錄 . 當前目錄.. 當前目錄的上一級目錄cd - 返回上一次的目錄; 命令分隔符# 表示註釋 ? 通配符中表示任意一個字符* 通配符中表

linux常用命令,稍微總結一下

快捷鍵 Linux Linux常用命令 系統 一.目錄介紹/boot 存放內核,系統啟動時所需文件存放點/bin存放了所有用戶可執行的常用命令/dev接口設備/etc有關系統設置及管理文件/home所有普通用戶的宿主目錄/sbin存放具有root用戶權限的管理命令/usr存放其他程序/var存

學習linux成果及命令,總結一下(二)

linux 命令 總結 目錄 自學 一、目錄/etc/crontab 計劃任務配置文件/etc/init.d/crond 計劃任務啟動結束腳本/var/spool/cron 用戶cron任務的配置文件存放目錄/etc/rsyslog.conf 決定將內核消息及各種系統程序消息記錄到什麽

Android的常見通信機制Linux的通信機制

另一個 mes TCP/IP 物理內存 ram 中一 最簡 雙工 erp Handler Handler是Android系統中的一種消息傳遞機制,起作用是應對多線程場景。將A進程的消息傳遞給B線程,實現異步消息處理。很多情況是將工作線程中需要更新UI的操作消息傳遞給UI主線

關於LinuxLVM的使用總結

總結 play http 利用 重啟 界面 分區類型 圖文 結構 首先借鑒一段圖文,如下:LVM是邏輯盤卷管理(LogicalVolumeManager)的簡稱,它是Linux環境下對磁盤分區進行管理的一種機制,LVM是建立在硬盤和 分區之上的一個邏輯層,來提高磁盤分區管理

Linux的的虛擬WEB主機的幾點總結

Linux中的虛擬WEB主機幾點注意與備忘 聽著鋼琴曲,第一次開始寫部落格。 其實有點疲憊,因為上課了一整天,晚上還搭建了一遍服務。但是難以掩住第一次寫部落格的心情。 還原虛擬機器的命令:rht-vmctl reset 主機名 一般搭建一個服務的基本思想:裝包,配置,起服務 為保證實驗,上來先把Fi

Linux 使用者和組的工作機制

我認為它們的關係是這樣的: 每個程序都屬於一個使用者(比如使用者 julia) 當這個程序試圖讀取一個被某個組所擁有的檔案時, Linux 會 a. 先檢查使用者julia 是否有許可權訪問檔案。(LCTT 譯註:此處應該是指檢查檔案的所有者是否就是 julia)