學(xué)習(xí)啦 > 學(xué)習(xí)電腦 > 操作系統(tǒng) > Linux教程 > Linux內(nèi)核訪問用戶空間內(nèi)存的方法是什么

Linux內(nèi)核訪問用戶空間內(nèi)存的方法是什么

時間: 曉斌668 分享

Linux內(nèi)核訪問用戶空間內(nèi)存的方法是什么

  今天學(xué)習(xí)啦小編就要跟大家講解下從Linux內(nèi)核訪問用戶空間內(nèi)存的方法~那么對此感興趣的網(wǎng)友可以多來了解了解下。下面就是具體內(nèi)容!!!

  Linux內(nèi)核訪問用戶空間內(nèi)存的方法

  首先學(xué)習(xí)啦小編必須要給大家科普下什么是Linux 內(nèi)存

  在 Linux 中,用戶內(nèi)存和內(nèi)核內(nèi)存是獨立的,在各自的地址空間實現(xiàn)。地址空間是虛擬的,就是說地址是從物理內(nèi)存中抽象出來的(通過一個簡短描述的過程)。由于地址空間是虛擬的,所以可以存在很多。事實上,內(nèi)核本身駐留在一個地址空間中,每個進程駐留在自己的地址空間。這些地址空間由虛擬內(nèi)存地址組成,允許一些帶有獨立地址空間的進程指向一個相對較小的物理地址空間(在機器的物理內(nèi)存中)。不僅僅是方便,而且更安全。因為每個地址空間是獨立且隔離的,因此很安全。

  但是與安全性相關(guān)聯(lián)的成本很高。因為每個進程(和內(nèi)核)會有相同地址指向不同的物理內(nèi)存區(qū)域,不可能立即共享內(nèi)存。幸運的是,有一些解決方案。用戶進程可以通過 Portable Operating System Interface for UNIX? (POSIX) 共享的內(nèi)存機制(shmem)共享內(nèi)存,但有一點要說明,每個進程可能有一個指向相同物理內(nèi)存區(qū)域的不同虛擬地址。

  虛擬內(nèi)存到物理內(nèi)存的映射通過頁表完成,這是在底層軟件中實現(xiàn)的(見圖 1)。硬件本身提供映射,但是內(nèi)核管理表及其配置。注意這里的顯示,進程可能有一個大的地址空間,但是很少見,就是說小的地址空間的區(qū)域(頁面)通過頁表指向物理內(nèi)存。這允許進程僅為隨時需要的網(wǎng)頁指定大的地址空間。

  圖 1. 頁表提供從虛擬地址到物理地址的映射

  由于缺乏為進程定義內(nèi)存的能力,底層物理內(nèi)存被過度使用。通過一個稱為 paging(然而,在 Linux 中通常稱為 swap)的進程,很少使用的頁面將自動移到一個速度較慢的存儲設(shè)備(比如磁盤),來容納需要被訪問的其它頁面(見圖 2 )。這一行為允許,在將很少使用的頁面遷移到磁盤來提高物理內(nèi)存使用的同時,計算機中的物理內(nèi)存為應(yīng)用程序更容易需要的頁面提供服務(wù)。注意,一些頁面可以指向文件,在這種情況下,如果頁面是臟(dirty)的,數(shù)據(jù)將被沖洗,如果頁面是干凈的(clean),直接丟掉。

  圖 2. 通過將很少使用的頁面遷移到速度慢且便宜的存儲器,交換使物理內(nèi)存空間得到了更好的利用

  MMU-less 架構(gòu)

  不是所有的處理器都有 MMU。因此,uClinux 發(fā)行版(微控制器 Linux)支持操作的一個地址空間。該架構(gòu)缺乏 MMU 提供的保護,但是允許 Linux 運行另一類處理器。

  選擇一個頁面來交換存儲的過程被稱為一個頁面置換算法,可以通過使用許多算法(至少是最近使用的)來實現(xiàn)。該進程在請求存儲位置時發(fā)生,存儲位置的頁面不在存儲器中(在存儲器管理單元 [MMU] 中無映射)。這個事件被稱為一個頁面錯誤 并被硬件(MMU)刪除,出現(xiàn)頁面錯誤中斷后該事件由防火墻管理。該棧的詳細說明見 圖 3。

  Linux 提供一個有趣的交換實現(xiàn),該實現(xiàn)提供許多有用的特性。Linux 交換系統(tǒng)允許創(chuàng)建和使用多個交換分區(qū)和優(yōu)先權(quán),這支持存儲設(shè)備上的交換層次結(jié)構(gòu),這些存儲設(shè)備提供不同的性能參數(shù)(例如,固態(tài)磁盤 [SSD] 上的一級交換和速度較慢的存儲設(shè)備上的較大的二級交換)。為 SSD 交換附加一個更高的優(yōu)先級使其可以使用直至耗盡;直到那時,頁面才能被寫入優(yōu)先級較低的交換分區(qū)。

  圖 3. 地址空間和虛擬 - 物理地址映射的元素

  并不是所有的頁面都適合交換。考慮到響應(yīng)中斷的內(nèi)核代碼或者管理頁表和交換邏輯的代碼,顯然,這些頁面決不能被換出,因此它們是固定的,或者是永久地駐留在內(nèi)存中。盡管內(nèi)核頁面不需要進行交換,然而用戶頁面需要,但是它們可以被固定,通過 mlock(或 mlockall)函數(shù)來鎖定頁面。這就是用戶空間內(nèi)存訪問函數(shù)的目的。如果內(nèi)核假設(shè)一個用戶傳遞的地址是有效的且是可訪問的,最終可能會出現(xiàn)內(nèi)核嚴重錯誤(kernel panic)(例如,因為用戶頁面被換出,而導(dǎo)致內(nèi)核中的頁面錯誤)。該應(yīng)用程序編程接口(API)確保這些邊界情況被妥善處理。

  內(nèi)核 API

  現(xiàn)在,讓我們來研究一下用戶操作用戶內(nèi)存的內(nèi)核 API。請注意,這涉及內(nèi)核和用戶空間接口,而下一部分將研究其他的一些內(nèi)存 API。用戶空間內(nèi)存訪問函數(shù)在表 1 中列出。

  表 1. 用戶空間內(nèi)存訪問 API

  正如您所期望的,這些函數(shù)的實現(xiàn)架構(gòu)是獨立的。例如在 x86 架構(gòu)中,您可以使用 ./linux/arch/x86/lib/usercopy_32.c 和 usercopy_64.c 中的源代碼找到這些函數(shù)以及在 ./linux/arch/x86/include/asm/uaccess.h 中定義的字符串。

  當(dāng)數(shù)據(jù)移動函數(shù)的規(guī)則涉及到復(fù)制調(diào)用的類型時(簡單 VS. 聚集),這些函數(shù)的作用如圖 4 所示。

  圖 4. 使用 User Space Memory Access API 進行數(shù)據(jù)移動

  access_ok 函數(shù)

  您可以使用 access_ok 函數(shù)在您想要訪問的用戶空間檢查指針的有效性。調(diào)用函數(shù)提供指向數(shù)據(jù)塊的開始的指針、塊大小和訪問類型(無論這個區(qū)域是用來讀還是寫的)。函數(shù)原型定義如下:

  access_ok( type, addr, size );

  type 參數(shù)可以被指定為 VERIFY_READ 或 VERIFY_WRITE。VERIFY_WRITE 也可以識別內(nèi)存區(qū)域是否可讀以及可寫(盡管訪問仍然會生成 -EFAULT)。該函數(shù)簡單檢查地址可能是在用戶空間,而不是內(nèi)核。

  get_user 函數(shù)

  要從用戶空間讀取一個簡單變量,可以使用 get_user 函數(shù),該函數(shù)適用于簡單數(shù)據(jù)類型,比如,char 和 int,但是像結(jié)構(gòu)體這類較大的數(shù)據(jù)類型,必須使用 copy_from_user 函數(shù)。該原型接受一個變量(存儲數(shù)據(jù))和一個用戶空間地址來進行 Read 操作:

  get_user( x, ptr );

  get_user 函數(shù)將映射到兩個內(nèi)部函數(shù)其中的一個。在系統(tǒng)內(nèi)部,這個函數(shù)決定被訪問變量的大小(根據(jù)提供的變量存儲結(jié)果)并通過 __get_user_x 形成一個內(nèi)部調(diào)用。成功時該函數(shù)返回 0,一般情況下,get_user 和 put_user 函數(shù)比它們的塊復(fù)制副本要快一些,如果是小類型被移動的話,應(yīng)該用它們。

  put_user 函數(shù)

  您可以使用 put_user 函數(shù)來將一個簡單變量從內(nèi)核寫入用戶空間。和 get_user 一樣,它接受一個變量(包含要寫的值)和一個用戶空間地址作為寫目標:

  put_user( x, ptr );

  和 get_user 一樣,put_user 函數(shù)被內(nèi)部映射到 put_user_x 函數(shù),成功時,返回 0,出現(xiàn)錯誤時,返回 -EFAULT。

  clear_user 函數(shù)

  clear_user 函數(shù)被用于將用戶空間的內(nèi)存塊清零。該函數(shù)采用一個指針(用戶空間中)和一個型號進行清零,這是以字節(jié)定義的:

  clear_user( ptr, n );

  在內(nèi)部,clear_user 函數(shù)首先檢查用戶空間指針是否可寫(通過 access_ok),然后調(diào)用內(nèi)部函數(shù)(通過內(nèi)聯(lián)組裝方式編碼)來執(zhí)行 Clear 操作。使用帶有 repeat 前綴的字符串指令將該函數(shù)優(yōu)化成一個非常緊密的循環(huán)。它將返回不可清除的字節(jié)數(shù),如果操作成功,則返回 0。

  copy_to_user 函數(shù)

  copy_to_user 函數(shù)將數(shù)據(jù)塊從內(nèi)核復(fù)制到用戶空間。該函數(shù)接受一個指向用戶空間緩沖區(qū)的指針、一個指向內(nèi)存緩沖區(qū)的指針、以及一個以字節(jié)定義的長度。該函數(shù)在成功時,返回 0,否則返回一個非零數(shù),指出不能發(fā)送的字節(jié)數(shù)。

  copy_to_user( to, from, n );

  檢查了向用戶緩沖區(qū)寫入的功能之后(通過 access_ok),內(nèi)部函數(shù) __copy_to_user 被調(diào)用,它反過來調(diào)用 __copy_from_user_inatomic(在 ./linux/arch/x86/include/asm/uaccess_XX.h 中。其中 XX 是 32 或者 64 ,具體取決于架構(gòu)。)在確定了是否執(zhí)行 1、2 或 4 字節(jié)復(fù)制之后,該函數(shù)調(diào)用 __copy_to_user_ll,這就是實際工作進行的地方。在損壞的硬件中(在 i486 之前,WP 位在管理模式下不可用),頁表可以隨時替換,需要將想要的頁面固定到內(nèi)存,使它們在處理時不被換出。i486 之后,該過程只不過是一個優(yōu)化的副本。

  copy_from_user 函數(shù)

  copy_from_user 函數(shù)將數(shù)據(jù)塊從用戶空間復(fù)制到內(nèi)核緩沖區(qū)。它接受一個目的緩沖區(qū)(在內(nèi)核空間)、一個源緩沖區(qū)(從用戶空間)和一個以字節(jié)定義的長度。和 copy_to_user 一樣,該函數(shù)在成功時,返回 0 ,否則返回一個非零數(shù),指出不能復(fù)制的字節(jié)數(shù)。

  copy_from_user( to, from, n );

  該函數(shù)首先檢查從用戶空間源緩沖區(qū)讀取的能力(通過 access_ok),然后調(diào)用 __copy_from_user,最后調(diào)用 __copy_from_user_ll。從此開始,根據(jù)構(gòu)架,為執(zhí)行從用戶緩沖區(qū)到內(nèi)核緩沖區(qū)的零拷貝(不可用字節(jié))而進行一個調(diào)用。優(yōu)化組裝函數(shù)包含管理功能。

  strnlen_user 函數(shù)

  strnlen_user 函數(shù)也能像 strnlen 那樣使用,但前提是緩沖區(qū)在用戶空間可用。strnlen_user 函數(shù)帶有兩個參數(shù):用戶空間緩沖區(qū)地址和要檢查的最大長度。

  strnlen_user( src, n );

  strnlen_user 函數(shù)首先通過調(diào)用 access_ok 檢查用戶緩沖區(qū)是否可讀。如果是 strlen 函數(shù)被調(diào)用,max length 參數(shù)則被忽略。

  strncpy_from_user 函數(shù)

  strncpy_from_user 函數(shù)將一個字符串從用戶空間復(fù)制到一個內(nèi)核緩沖區(qū),給定一個用戶空間源地址和最大長度。

  strncpy_from_user( dest, src, n );

  由于從用戶空間復(fù)制,該函數(shù)首先使用 access_ok 檢查緩沖區(qū)是否可讀。和 copy_from_user 一樣,該函數(shù)作為一個優(yōu)化組裝函數(shù)(在 ./linux/arch/x86/lib/usercopy_XX.c 中)實現(xiàn)。

  內(nèi)存映射的其他模式

  上面部分探討了在內(nèi)核和用戶空間之間移動數(shù)據(jù)的方法(使用內(nèi)核初始化操作)。Linux 還提供一些其他的方法,用于在內(nèi)核和用戶空間中移動數(shù)據(jù)。盡管這些方法未必能夠提供與用戶空間內(nèi)存訪問函數(shù)相同的功能,但是它們在地址空間之間映射內(nèi)存的功能是相似的。

  在用戶空間,注意,由于用戶進程出現(xiàn)在單獨的地址空間,在它們之間移動數(shù)據(jù)必須經(jīng)過某種進程間通信機制。Linux 提供各種模式(比如,消息隊列),但是最著名的是 POSIX 共享內(nèi)存(shmem)。該機制允許進程創(chuàng)建一個內(nèi)存區(qū)域,然后同一個或多個進程共享該區(qū)域。注意,每個進程可能在其各自的地址空間中映射共享內(nèi)存區(qū)域到不同地址。因此需要相對的尋址偏移(offset addressing)。

  mmap 函數(shù)允許一個用戶空間應(yīng)用程序在虛擬地址空間中創(chuàng)建一個映射,該功能在某個設(shè)備驅(qū)動程序類中是常見的,允許將物理設(shè)備內(nèi)存映射到進程的虛擬地址空間。在一個驅(qū)動程序中,mmap 函數(shù)通過 remap_pfn_range 內(nèi)核函數(shù)實現(xiàn),它提供設(shè)備內(nèi)存到用戶地址空間的線性映射。

  結(jié)束語

  本文討論了 Linux 中的內(nèi)存管理主題,然后討論了使用這些概念的用戶空間內(nèi)存訪問函數(shù)。在用戶空間和內(nèi)核空間之間移動數(shù)據(jù)并沒有表面上看起來那么簡單,但是 Linux 包含一個簡單的 API 集合,跨平臺為您管理這個復(fù)雜的任務(wù)。

375764