內存重映射

文章目錄

  • 1 kmap
  • 2 映射內核內存到用戶空間
    • 使用remap_pfn_range
    • 使用io_remap_pfn_range
  • mmap文件操作
    • 建立VMA和實際物理地址的映射
    • mmap 之前分配 + 一次性映射
    • mmap 之前分配 + Page Fault
    • Page Fault 中分配 + 映射

內核內存有時需要重新映射,無論是從內核到用戶空間還是從內核空間到內核。常見的情況是將內核內存重新映射到用戶空間,但還有其他一些情況,例如需要訪問高內存的情況。

1 kmap

kmap()用于將指定的頁面映射到內核地址空間

Linux內核將其896MB地址空間永久映射到物理內存較低的896MB(低端內存)。在4GB系統上,內核僅剩下128MB用來映射剩余3.2GB物理內存(高端內存)。由于低端內存采用永久一對一映射,因此內核可以直接尋址。而對于高端內存(高于896MB的內存),內核必須將所請求的高端內存區域映射到其地址空間,前面提到的128MB就是專門為此保留。用于執行此操作的函數是kmap()。kmap()用于將指定的頁面映射到內核地址空間。

void *kmap(struct page *page);

當分配到高端內存頁時,它不能直接尋址,必須調用調用kmap()函數將高端內存映射到內存地址空間。該映射將持續到kunmap()位置:

void *kunmap(struct page *page);

所謂暫時,指的是映射應該在不需要的時候立即撤銷。請記住,128MB不足以映射3.2GB。最好的編程習慣是在不需要時取消高端內存映射。這就是必須在每次訪問高端內存頁面時輸入kmap()-kunmap序列的原因了,

該函數適用于高端內存和低端內存,也就是說,如果頁面結構駐留在低端內存中,那么返回的是頁面的虛擬地址(因為低端內存頁面已經有永久映射)。如果頁面屬于高端內存,則在內核頁表中創建永久映射,并返回地址。

2 映射內核內存到用戶空間

映射物理地址是其中一個有用的功能,特別是在嵌入式系統中。有時可能想要與用戶空間共享部分內核內存。如前所述,CPU在用戶空間時以非特權模式運行要讓進程訪問內核內存區域,需要將該區域映射到進程地址空間

使用remap_pfn_range

remap_pfn_range()將物理內存(通過內核邏輯地址)映射到用戶空間進程。它對于實現mmap()特別有用。
在文件上調用mmap()系統調用后,CPU切換到特權模式,運行相應的file_operations.mmap()內核函數,它反過來調用remap_pfn_range()。這將產生映射區域的PTE,將其賦給進程,當然還有不同的保護標志,進程的VMA列表更新為新的VMA項,這將使用PTE訪問相同的內存。

這樣,內核不是通過復制來浪費內存,而只是復制PTE,但是內核和用戶空間PTE具有不同的屬性。remap_pfn_range()原型如下:

int remap_pfn_range (	struct vm_area_struct * vma,unsigned long virt_addr,unsigned long pfn,unsigned long size,pgprot_t prot);
  • vma: 這個我們不用擔心,因為在調用file_operations.mmap函數時,mmap調用do_mmap()會創建一個新的VMA并初始化,此vma就是創建的新的VMA,加入進程的虛擬地址空間里,這個已經確定了。
  • virt_addr:VMA開始位置的用戶虛擬地址(vma->vm_start),這將導致映射的虛擬地址范圍位于virt_addr~virt_addr+size
  • pfn:所映射內核內存區域的頁面幀碼,它對應于通過PAGE_SHIFT位右移得到的物理地址。產生pfn時應應該考慮vma偏移量。由于vma結構的vm_pgoff字段在頁碼中包含偏移值,因此需要以字節形式精確提取偏移量:offset= vma->vm_pgoff<<PAGE_SHIFT.最后,pnf=virt_to_phys(buffer+offset)>>PAGE_SHIFT.
  • size:需要建立映射的VMA的大小,以字節為單位
  • prot:代表新VMA所要求的保護。驅動程序可以修改默認值,但應該使用OR運算符將vma->vm_page_port中的值作基礎,因為它的某些位已經由用戶空間設置,其中一些標志如下:
  1. VM_IO:指定設備內存映射I/O
  2. VM_DONTCOPY:告訴內核不要在分叉上復制該vma
  3. VM_DONTEXPAND:防止vma通過mremap擴展
  4. VM_DONTDUMP:禁止在核心轉儲內包含vma

使用io_remap_pfn_range

前面討論的remap_pfn_range()不適用于將I/O內存映射到用戶空間。在這種情況下,相應的函數是io_remap_pfn_range(),它們的參數相同,唯一改變的是PFN的來源,其原型如下:

  int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr,unsigned long phys_addr, unsigned long size, pgprot_t prot);

當試圖將I/O內存映射到用戶空間時,不需要使用ioremap(),ioremap()用于內核映射(將I/O內存映射到內核地址空間)。

只需要將真實的物理I/O地址(通過PAGE_SHIFT向下移位生成PFN)直接傳遞給io_remap_pfn_range()。即使有一些體系將io_remap_pfn_range()定義為remap_pfn_range(),但在其他體系結構中并非如此,考慮到移植能力,只有在PFN參數指向RAM的情況下,才使用remap_pfn_range(),在pys_addr指向I/O聶村的情況下,才使用io_remap_pfn_range()。

mmap文件操作

內核mmap函數是struct file_operations結構的一部分,當用戶執行系統調用mmap(2),把物理內存映射到用戶虛擬地址時才執行它。出于安全考慮,用戶空間進程不能直接訪問設備內存,因此,用于空間進程使用mmap()系統調用將該設備映射到調用進程的虛擬地址空間。在映射之后,用戶空間進程可以通過返回的地址直接寫入設備內存。

       #include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);int munmap(void *addr, size_t length);

用戶空間的mmap()會通過系統調用調用內核的do_mmap()函數。
do_mmap()函數會:

  1. 首先創建一個新的VMA并初始化,然后加入進程的虛擬地址空間里
  2. 調用底層的mmap函數建立VMA和實際物理地址的映射(建立頁表)

什么是底層的mmap函數呢?在不同的設備是不一樣的。比如說我們映射的是一個普通文件,底層文件系統已經幫我們實現了mmap,我們可以直接使用,但是如果我們新寫了一個驅動,我們想為驅動提供mmap的接口,那么就需要我們實現mmap的接口,設備驅動的mmap實現主要是將這個物理設備的可操作區域映射到一個進程的虛擬地址空間,這樣用戶空間就可以直接采用指針的方式訪問設備的可操作區域。在驅動中的mmap實現主要完成一件事,就是建立設備的可操作區域到進程虛擬空間地址的映射過程。

  • addr:映射開始的用戶空間虛擬地址,如果指定NULL,則自動確定正確的地址
  • length:指定映射長度
  • prot:指定VMA的權限
  • flags:決定映射類型(私有還是共享)
  • fd:設備文件描述符
  • offset:指定映射區的偏移量(在物理內存里面)

建立VMA和實際物理地址的映射

在 linux 驅動中建立映射關系的方法主要有如下兩種:

  1. 一次性映射 —— 在 mmap 回調函數中,一次性建立好整塊內存的映射關系,通常以 remap_pfn_range() 為代表 。
  2. Page Fault —— mmap 先不建立映射關系,等上層觸發缺頁異常時,在 fault 中斷處理函數中建立映射關系,缺哪塊補哪塊,通常以 vm_insert_page() 為代表。

而內存分配的時機也會影響驅動程序的設計,大致分為如下三種:

  • 在 mmap 系統調用之前分配
  • 在 mmap 系統調用過程中分配
  • 在 fault 中斷處理函數中分配

因此不同的分配時機 + 不同的映射機制,就會得到不同的 mmap 的實現策略。

下面就以示例代碼的形式為大家展示幾種典型的 mmap 驅動實現方式。

mmap 之前分配 + 一次性映射

請添加圖片描述

描述:

  1. 驅動初始化時先分配好 3 個 PAGE。
  2. 上層執行 mmap 系統調用時,在底層 mmap 回調函數中通過 remap_pfn_range() 一次性建立好所有的映射關系,并將映射后的起始虛擬地址返回給應用程序。
  3. 應用程序使用返回的虛擬地址進行內存讀寫操作。

驅動代碼:

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>static void *kaddr;static int my_mmap(struct file *file, struct vm_area_struct *vma)
{return remap_pfn_range(vma, vma->vm_start,(virt_to_phys(kaddr) >> PAGE_SHIFT) + vma->vm_pgoff,vma->vm_end - vma->vm_start, vma->vm_page_prot);
}static struct file_operations my_fops = {.owner	= THIS_MODULE,.mmap	= my_mmap,
};static struct miscdevice mdev = {.minor = MISC_DYNAMIC_MINOR,.name = "my_dev",.fops = &my_fops,
};static int __init my_init(void)
{kaddr = kzalloc(PAGE_SIZE * 3, GFP_KERNEL);return misc_register(&mdev);
}
module_init(my_init);

mmap 之前分配 + Page Fault

請添加圖片描述
描述:

  1. 驅動初始化時預先分配好 3 個 PAGE。
  2. 上層執行 mmap 系統調用,底層驅動在 mmap 回調函數中不建立映射關系,而是將本地實現的 vm_ops 掛接到進程的 vma->vm_ops 指針上,然后函數返回。
  3. 上層獲取到一個未經映射的進程地址空間,并對其進行內存讀寫操作,導致觸發缺頁異常。缺頁異常最終會調用前面掛接的 vm_ops->fault() 回調接口,在該接回調中通過 vm_insert_page() 建立物理內存與用戶地址空間的映射關系。
  4. 異常返回后,應用程序就可以繼續之前被中斷的讀寫操作了。

注意:這種情況每次 Page Fault 中斷只能映射一個 Page

驅動代碼:

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>static void *kaddr;static int my_fault(struct vm_fault *vmf)
{struct vm_area_struct *vma = vmf->vma;int offset, ret;offset = vmf->pgoff * PAGE_SIZE;ret = vm_insert_page(vma, vmf->address, virt_to_page(kaddr + offset));if (ret)return VM_FAULT_SIGBUS;return VM_FAULT_NOPAGE;
}static const struct vm_operations_struct vm_ops = {.fault = my_fault,
};static int my_mmap(struct file *file, struct vm_area_struct *vma)
{vma->vm_flags |= VM_MIXEDMAP;vma->vm_ops = &vm_ops;return 0;
}static struct file_operations my_fops = {.owner	= THIS_MODULE,.mmap	= my_mmap,
};static struct miscdevice mdev = {.minor = MISC_DYNAMIC_MINOR,.name = "my_dev",.fops = &my_fops,
};static int __init my_init(void)
{kaddr = kzalloc(PAGE_SIZE * 3, GFP_KERNEL);return misc_register(&mdev);
}
module_init(my_init);

Page Fault 中分配 + 映射

請添加圖片描述
映射的過程和示例二完全一樣,只是內存分配的時機是在 page fault 中斷處理函數中進行的。

這里為了簡化代碼,總共只分配一個 page,多個 page 可通過 vmf->pgoff 來進行區分。

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>static struct page *page;static int my_fault(struct vm_fault *vmf)
{struct vm_area_struct *vma = vmf->vma;int ret;if (!page)page = alloc_page(GFP_KERNEL);ret = vm_insert_page(vma, vmf->address, page);if (ret)return VM_FAULT_SIGBUS;return VM_FAULT_NOPAGE;
}static const struct vm_operations_struct vm_ops = {.fault = my_fault,
};static int my_mmap(struct file *file, struct vm_area_struct *vma)
{vma->vm_flags |= VM_MIXEDMAP;vma->vm_ops = &vm_ops;return 0;
}static struct file_operations my_fops = {.owner	= THIS_MODULE,.mmap	= my_mmap,
};static struct miscdevice mdev = {.minor = MISC_DYNAMIC_MINOR,.name = "my_dev",.fops = &my_fops,
};static int __init my_init(void)
{return misc_register(&mdev);
}
module_init(my_init);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/379519.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379519.shtml
英文地址,請注明出處:http://en.pswp.cn/news/379519.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

math.sqrt 有問題_JavaScript中帶有示例的Math.sqrt()方法

math.sqrt 有問題JavaScript | Math.sqrt()方法 (JavaScript | Math.sqrt() Method) The Math.sqrt() method is inbuilt in JavaScript to find the square root of a number. In this tutorial, we will learn about the sqrt() method with examples. JavaScript中內置了Mat…

標題:移動距離

標題&#xff1a;移動距離 X星球居民小區的樓房全是一樣的&#xff0c;并且按矩陣樣式排列。其樓房的編號為1,2,3… 當排滿一行時&#xff0c;從下一行相鄰的樓往反方向排號。 比如&#xff1a;當小區排號寬度為6時&#xff0c;開始情形如下&#xff1a; 1 2 3 4 5 6 12 11 1…

ISAPI Rewrite 實現簡單url重寫、二級域名重寫

實現步驟&#xff1a; 第一步&#xff1a;下載ISAPI_Rewrite.rar&#xff0c;將Rewrite文件夾和httpd.ini直接放在項目根目錄下面。 第二步&#xff1a;IIS配置&#xff0c;篩選Rewrite文件夾里面的Rewrite.dll文件&#xff0c;如圖&#xff1a; 第三步&#xff1a;在httpd.ini…

用戶登錄

用戶登錄 代碼namespace 用戶登錄 {public partial class Form1 : Form{public Form1(){InitializeComponent();}bool b1, b2, b3, b4, b5, b6;private void button1_Click(object sender, EventArgs e){try{if (b1 && b2 && b3 && b4 && b5 &…

進程上下文和中斷上下文

文章目錄進程的preempt_count變量thread_infopreempt_counthardirq相關softirq相關上下文原文鏈接&#xff1a; https://zhuanlan.zhihu.com/p/88883239進程的preempt_count變量 thread_info 在內核中&#xff0c;上下文的設置和判斷接口可以參考 include/linux/preempt.h 文…

標題:湊算式

標題&#xff1a;湊算式 這個算式中AI代表19的數字&#xff0c;不同的字母代表不同的數字。 比如&#xff1a; 68/3952/714 就是一種解法&#xff0c; 53/1972/486 是另一種解法。 這個算式一共有多少種解法&#xff1f; 注意&#xff1a;你提交應該是個整數&#xff0c;不要…

匯編中imul_JavaScript中帶有示例的Math.imul()方法

匯編中imulJavaScript | Math.imul()方法 (JavaScript | Math.imul() Method) Math.imul() is a function in math library of JavaScript that is used to the 32-bit multiplication of the two values passed to it. It uses C-like semantics to find the multiplication. …

AFTER觸發器與INSTEAD OF觸發器的區別

INSTEAD OF 觸發器用來代替通常的觸發動作&#xff0c;即當對表進行INSERT、UPDATE 或 DELETE 操作時&#xff0c;系統不是直接對表執行這些操作&#xff0c;而是把操作內容交給觸發器&#xff0c;讓觸發器檢查所進行的操作是否正確。如正確才進行相應的操作。因此&#xff0c;…

Linux內存地址管理

文章目錄系統內存布局內核地址的低端和高端內存概念低端內存高端內存地址轉換和MMULinux中的四級分頁模型虛擬地址字段頁表處理將虛擬地址轉換物理地址Linux系統中的每個內存地址都是虛擬的&#xff0c;它們不直接指向任何物理內存地址。每當訪問內存位置時&#xff0c;可以執行…

錄制caf 轉 mp3

編譯需要使用的 lame庫http://www.cocoachina.com/bbs/read.php?tid108237參考的文章http://blog.csdn.net/ysy441088327/article/details/7392842說起來&#xff0c;我一直在找一個音頻轉換成mp3的方法。一年前&#xff0c;我成功編譯出了一個lame for armv7的庫。苦于不會使…

杭電2012-素數判定(C)

Problem Description 對于表達式n^2n41&#xff0c;當n在&#xff08;x,y&#xff09;范圍內取整數值時&#xff08;包括x,y&#xff09;(-39<x<y<50)&#xff0c;判定該表達式的值是否都為素數。 Input 輸入數據有多組&#xff0c;每組占一行&#xff0c;由兩個整數…

math.ceil帶小數點_JavaScript中帶有示例的Math.ceil()方法

math.ceil帶小數點JavaScript | Math.ceil()方法 (JavaScript | Math.ceil() Method) Math.ceil() is a function in math library of JavaScript that is used to round up the number passed to the function. The method will return the nearest integer value indeed is g…

開發記要 詭異的變量

告別繁體文盲,從寫blog開始 Variable命名很重要,有多重要,看看.net和java的加密就知道, 都是把variable改到一塌糊塗,你想看看都沒門. 但是這幾天看遺留系統的代碼,真是大開眼界。 我一直以為別人寫a,b,c,d這些單字節variable已經很過分。直到我看到以下這幾個&#xff0…

排序算法---快速排序、堆排序、冒泡排序

排序算法1 快速排序代碼實現stdlib庫快排2 堆排序堆排序的基本思想如何構造一個大頂堆排序3 冒泡排序1 快速排序 文章原地址&#xff1a;https://blog.csdn.net/morewindows/article/details/6684558 快速排序的平均時間復雜度是0(NlogN)&#xff0c;它采用了一種分治的策略&a…

CSS Hack 匯總快查

*:lang(zh) select {font:12px !important;} /*FF的專用*/ select:empty {font:12px !important;} /*safari可見*/ 這里select是選擇符&#xff0c;根據情況更換。第二句是MAC上safari瀏覽器獨有的。 僅IE7識別 *html {…} 當面臨需要只針對IE7做樣式的時候就可以采用這個HACK…

杭電2013-蟠桃記(C++)

Problem Description 喜歡西游記的同學肯定都知道悟空偷吃蟠桃的故事&#xff0c;你們一定都覺得這猴子太鬧騰了&#xff0c;其實你們是有所不知&#xff1a;悟空是在研究一個數學問題&#xff01; 什么問題&#xff1f;他研究的問題是蟠桃一共有多少個&#xff01; 不過&#…

c#中重載單目運算符-_C#程序重載二進制運算符(-,*,/)

c#中重載單目運算符-Here, we will design overloaded methods for binary operators: minus, multiply and divide. In the below program, we will create a Calculator class with data member val. 在這里&#xff0c;我們將為二進制運算符設計重載方法&#xff1a;減&…

項目總結:華南師范大學校園開發教育android客戶端總結

忽略之前小打小鬧&#xff0c;這個項目算是我的第一個項目--SCNU的網絡公選課的android版本的客戶端。項目是從5月中旬開始的&#xff0c;中間經歷了幾個星期的復習考試時間&#xff0c;到現在可以說是完工了吧&#xff08;或許還有寫細節要修改&#xff09;。這個項目帶給我蠻…

火鳥字幕合并器

火鳥字幕合并器-區塊獨立勾選-保存。漢王 PDF OCR轉載于:https://www.cnblogs.com/hnytwn/archive/2009/10/31/1593395.html

Linux系統編程---守護進程

1 守護進程的概述 Daemon&#xff08;守護進程&#xff09;是運行在后臺的一種特殊進程。它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件。它不需要用戶輸入就能運行而且提供某種服務&#xff0c;不是對整個系統就是對某個用戶程序提供服務。Linux系統的大…