国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

15.2. mmap 設(shè)備操作

2018-02-24 15:50 更新

15.2.?mmap 設(shè)備操作

內(nèi)存映射是現(xiàn)代 Unix 系統(tǒng)最有趣的特性之一. 至于驅(qū)動, 內(nèi)存映射可被實現(xiàn)來提供用戶程序?qū)υO(shè)備內(nèi)存的直接存取.

一個 mmap 用法的明確的例子可由查看給 X Windows 系統(tǒng)服務(wù)器的虛擬內(nèi)存區(qū)的一個子集來見到:


cat /proc/731/maps 
000a0000-000c0000 rwxs 000a0000 03:01 282652 /dev/mem
000f0000-00100000 r-xs 000f0000 03:01 282652 /dev/mem
00400000-005c0000 r-xp 00000000 03:01 1366927 /usr/X11R6/bin/Xorg
006bf000-006f7000 rw-p 001bf000 03:01 1366927 /usr/X11R6/bin/Xorg
2a95828000-2a958a8000 rw-s fcc00000 03:01 282652 /dev/mem
2a958a8000-2a9d8a8000 rw-s e8000000 03:01 282652 /dev/mem
...

X 服務(wù)器的 VMA 的完整列表很長, 但是大部分此處不感興趣. 我們確實見到, 但是, /dev/mm 的 4 個不同映射, 它給出一些關(guān)于 X 服務(wù)器如何使用視頻卡的內(nèi)幕. 第一個映射在 a0000, 它是視頻內(nèi)存的在 640-KB ISA 孔里的標準位置. 再往下, 我們見到了大映射在 e8000000, 這個地址在系統(tǒng)中最高的 RAM 地址之上. 這是一個在適配器上的視頻內(nèi)存的直接映射.

這些區(qū)也可在 /proc/iomem 中見到:


000a0000-000bffff : Video RAM area
000c0000-000ccfff : Video ROM
000d1000-000d1fff : Adapter ROM
000f0000-000fffff : System ROM
d7f00000-f7efffff : PCI Bus #01

 e8000000-efffffff : 0000:01:00.0
fc700000-fccfffff : PCI Bus #01

 fcc00000-fcc0ffff : 0000:01:00.0 

映射一個設(shè)備意味著關(guān)聯(lián)一些用戶空間地址到設(shè)備內(nèi)存. 無論何時程序在給定范圍內(nèi)讀或?qū)? 它實際上是在存取設(shè)備. 在 X 服務(wù)器例子里, 使用 mmap 允許快速和容易地存取視頻卡內(nèi)存. 對于一個象這樣的性能關(guān)鍵的應(yīng)用, 直接存取有很大不同.

如你可能期望的, 不是每個設(shè)備都出借自己給 mmap 抽象; 這樣沒有意義, 例如, 對串口或其他面向流的設(shè)備. mmap 的另一個限制是映射粒度是 PAGE_SIZE. 內(nèi)核可以管理虛擬地址只在頁表一級; 因此, 被映射區(qū)必須是 PAGE_SIZE 的整數(shù)倍并且必須位于是 PAGE_SIZE 整數(shù)倍開始的物理地址. 內(nèi)核強制 size 的粒度通過做一個稍微大些的區(qū)域, 如果它的大小不是頁大小的整數(shù)倍.

這些限制對驅(qū)動不是大的限制, 因為存取設(shè)備的程序是設(shè)備依賴的. 因為程序必須知道設(shè)備如何工作的, 程序員不會太煩于需要知道如頁對齊這樣的細節(jié). 一個更大的限制存在當 ISA 設(shè)備被用在非 x86 平臺時, 因為它們的 ISA 硬件視圖可能不連續(xù). 例如, 一些 Alpha 計算機將 ISA 內(nèi)存看作一個分散的 8 位, 16 位, 32 位項的集合, 沒有直接映射. 這種情況下, 你根本無法使用 mmap. 對不能進行直接映射 ISA 地址到 Alph 地址可能只發(fā)生在 32-位 和 64-位內(nèi)存存取, ISA 可只做 8-位 和 16-位 發(fā)送, 并且沒有辦法來透明映射一個協(xié)議到另一個.

使用 mmap 有相當?shù)貎?yōu)勢當這樣做可行的時候. 例如, 我們已經(jīng)看到 X 服務(wù)器, 它傳送大量數(shù)據(jù)到和從視頻內(nèi)存; 動態(tài)映射圖形顯示到用戶空間提高了吞吐量, 如同一個 lseek/write 實現(xiàn)相反. 另一個典型例子是一個控制一個 PCI 設(shè)備的程序. 大部分 PCI 外設(shè)映射它們的控制寄存器到一個內(nèi)存地址, 并且一個高性能應(yīng)用程序可能首選對寄存器的直接存取來代替反復(fù)地調(diào)用 ioctl 來完成它的工作.

mmap 方法是 file_operation 結(jié)構(gòu)的一部分, 當發(fā)出 mmap 系統(tǒng)調(diào)用時被引用. 用了 mmap, 內(nèi)核進行大量工作在調(diào)用實際的方法之前, 并且, 因此, 方法的原型非常不同于系統(tǒng)調(diào)用的原型. 這不象 ioctl 和 poll 等調(diào)用, 內(nèi)核不會在調(diào)用這些方法之前做太多.

系統(tǒng)調(diào)用如下一樣被聲明(如在 mmap(2) 手冊頁中描述的 );


mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset) 

另一方面, 文件操作聲明如下:


int (*mmap) (struct file *filp, struct vm_area_struct *vma);

方法中的 filp 參數(shù)象在第 3 章介紹的那樣, 而 vma 包含關(guān)于用來存取設(shè)備的虛擬地址范圍的信息. 因此, 大量工作被內(nèi)核完成; 為實現(xiàn) mmap, 驅(qū)動只要建立合適的頁表給這個地址范圍, 并且, 如果需要, 用新的操作集合替換 vma->vm_ops.

有 2 個建立頁表的方法:調(diào)用 remap_pfn_range 一次完成全部, 或者一次一頁通過 nopage VMA 方法. 每個方法有它的優(yōu)點和限制. 我們從"一次全部"方法開始, 它更簡單. 從這里, 我們增加一個真實世界中的實現(xiàn)需要的復(fù)雜性.

15.2.1.?使用 remap_pfn_range

建立新頁來映射物理地址的工作由 remap_pfn_range 和 io_remap_page_range 來處理, 它們有下面的原型:


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

由這個函數(shù)返回的值常常是 0 或者一個負的錯誤值. 讓我們看看這些函數(shù)參數(shù)的確切含義:

vma
頁范圍被映射到的虛擬內(nèi)存區(qū)

virt_addr
重新映射應(yīng)當開始的用戶虛擬地址. 這個函數(shù)建立頁表為這個虛擬地址范圍從 virt_addr 到 virt_addr_size.

pfn
頁幀號, 對應(yīng)虛擬地址應(yīng)當被映射的物理地址. 這個頁幀號簡單地是物理地址右移 PAGE_SHIFT 位. 對大部分使用, VMA 結(jié)構(gòu)的 vm_paoff 成員正好包含你需要的值. 這個函數(shù)影響物理地址從 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size.

size
正在被重新映射的區(qū)的大小, 以字節(jié).

prot
給新 VMA 要求的"protection". 驅(qū)動可(并且應(yīng)當)使用在 vma->vm_page_prot 中找到的值.

給 remap_fpn_range 的參數(shù)是相當直接的, 并且它們大部分是已經(jīng)在 VMA 中提供給你, 當你的 mmap 方法被調(diào)用時. 你可能好奇為什么有 2 個函數(shù), 但是. 第一個 (remap_pfn_range)意圖用在 pfn 指向?qū)嶋H的系統(tǒng) RAM 的情況下, 而 io_remap_page_range 應(yīng)當用在 phys_addr 指向 I/O 內(nèi)存時. 實際上, 這 2 個函數(shù)在每個體系上是一致的, 除了 SPARC, 并且你在大部分情況下被使用看到 remap_pfn_range . 為編寫可移植的驅(qū)動, 但是, 你應(yīng)當使用 remap_pfn_range 的適合你的特殊情況的變體.

另一種復(fù)雜性不得不處理緩存: 常常地, 引用設(shè)備內(nèi)存不應(yīng)當被處理器緩存. 常常系統(tǒng) BIOS 做了正確設(shè)置, 但是它也可能通過保護字段關(guān)閉特定 VMA 的緩存. 不幸的是, 在這個級別上關(guān)閉緩存是高度處理器依賴的. 好奇的讀者想看看來自 drivers/char/mem.c 的 pgprot_noncached 函數(shù)來找到包含什么. 我們這里不進一步討論這個主題.

15.2.2.?一個簡單的實現(xiàn)

如果你的驅(qū)動需要做一個簡單的線性的設(shè)備內(nèi)存映射, 到一個用戶地址空間, remap_pfn_range 幾乎是所有你做這個工作真正需要做的. 下列的代碼從 drivers/char/mem.c 中得來, 并且顯示了這個任務(wù)如何在一個稱為 simple ( Simple Implementation Mapping Pages with Little Enthusiasm)的典型模塊中進行的.


static int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma) 
{
 if (remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff,
 vma->vm_end - vma->vm_start,
 vma->vm_page_prot))
 return -EAGAIN;
 vma->vm_ops = &simple_remap_vm_ops;
 simple_vma_open(vma);
 return 0; 
} 

如你所見, 重新映射內(nèi)存只不過是調(diào)用 remap_pfn_rage 來創(chuàng)建必要的頁表.

15.2.3.?添加 VMA 的操作

如我們所見, vm_area_struct 結(jié)構(gòu)包含一套操作可以用到 VMA. 現(xiàn)在我們看看以一個簡單的方式提供這些操作. 特別地, 我們?yōu)?VMA 提供 open 和 close 操作. 這些操作被調(diào)用無論何時一個進程打開或關(guān)閉 VMA; 特別地, open 方法被調(diào)用任何時候一個進程產(chǎn)生和創(chuàng)建一個對 VMA 的新引用. open 和 close VMA 方法被調(diào)用加上內(nèi)核進行的處理, 因此它們不需要重新實現(xiàn)任何那里完成的工作. 它們對于驅(qū)動存在作為一個方法來做任何它們可能要求的附加處理.

如同它所證明的, 一個簡單的驅(qū)動例如 simple 不需要做任何額外的特殊處理. 我們已創(chuàng)建了 open 和 close 方法, 它打印一個信息到系統(tǒng)日志來通知大家它們已被調(diào)用. 不是特別有用, 但是它確實允許我們來顯示這些方法如何被提供, 并且見到當它們被調(diào)用時.

到此, 我們忽略了缺省的 vma->vm_ops 使用調(diào)用 printk 的操作:


void simple_vma_open(struct vm_area_struct *vma)
{
    printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx\n", vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);
} 

void simple_vma_close(struct vm_area_struct *vma)
{
 printk(KERN_NOTICE "Simple VMA close.\n");
}

static struct vm_operations_struct simple_remap_vm_ops = {
 .open = simple_vma_open,
 .close = simple_vma_close,
};

為使這些操作為一個特定的映射激活, 有必要存儲一個指向 simple_remap_um_ops 指針在相關(guān) VMA 的 vm_ops 成員中. 這常常在 mmap 方法中完成. 如果你回看 simple_remap_mmap 例子, 你見到這些代碼行:


vma->vm_ops = &simple_remap_vm_ops;
simple_vma_open(vma);

注意對 simple_vma_open 的明確調(diào)用. 因為 open 方法不在初始化 mmap 時調(diào)用, 我們必須明確調(diào)用它如果我們要它運行.

15.2.4.?使用 nopage 映射內(nèi)存

盡管 remap_pfn_range 對許多人工作得不錯, 如果不是大部分人, 驅(qū)動 mmap 的實現(xiàn)有時有點更大的靈活性是必要的. 在這樣的情況下, 一個使用 nopage VMA 方法的實現(xiàn)可被調(diào)用.

一種 nopage 方法有用的情況可由 mremap 系統(tǒng)調(diào)用引起, 它被應(yīng)用程序用來改變一個被映射區(qū)的綁定地址. 如它所發(fā)生的, 當一個被映射的 VMA 被 mremap 改變時內(nèi)核不直接通知驅(qū)動. 如果這個 VMA 的大小被縮減, 內(nèi)核可靜靜地刷出不需要的頁, 而不必告訴驅(qū)動. 相反, 如果這個 VMA 被擴大, 當映射必須為新頁建立時, 驅(qū)動最終通過對 nopage 的調(diào)用發(fā)現(xiàn), 因此沒有必要進行特殊的通知. nopage 方法, 因此, 如果你想支持 mremap 系統(tǒng)調(diào)用必須實現(xiàn). 這里, 我們展示一個簡單的 nopage 實現(xiàn)給 simple 設(shè)備.

nopage 方法, 記住, 有下列原型:


struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type);

當一個用戶進程試圖存取在一個不在內(nèi)存中的 VMA 中的一個頁, 相關(guān)的 nopage 函數(shù)被調(diào)用. 地址參數(shù)包含導(dǎo)致出錯的虛擬地址, 向下圓整到頁的開始. nopage 函數(shù)必須定位并返回用戶需要的頁的 struct page 指針. 這個函數(shù)必須也負責遞增它通過調(diào)用 get_page 宏返回的頁的使用計數(shù).


 get_page(struct page *pageptr); 

這一步是需要的來保持在被映射頁的引用計數(shù)正確. 內(nèi)核為每個頁維護這個計數(shù); 當計數(shù)到 0, 內(nèi)核知道這個頁可被放置在空閑列表了. 當一個 VMA 被去映射, 內(nèi)核遞減使用計數(shù)給區(qū)中每個頁. 如果你的驅(qū)動在添加一個頁到區(qū)時不遞增計數(shù), 使用計數(shù)過早地成為 0, 系統(tǒng)的整體性被破壞了.

nopage 方法也應(yīng)當存儲錯誤類型在由 type 參數(shù)指向的位置 -- 但是只當那個參數(shù)不為 NULL. 在設(shè)備驅(qū)動中, 類型的正確值將總是 VM_FAULT_MINOR.

如果你使用 nopage, 當調(diào)用 mmap 常常很少有工作來做; 我們的版本看來象這樣:


static int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma)
{
 unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;

    if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
 vma->vm_flags |= VM_IO;
 vma->vm_flags |= VM_RESERVED;

 vma->vm_ops = &simple_nopage_vm_ops;
 simple_vma_open(vma);
 return 0;

}

mmap 必須做的主要的事情是用我們自己的操作來替換缺省的(NULL)vm_ops 指針. nopage 方法接著進行一次重新映射一頁并且返回它的 struct page 結(jié)構(gòu)的地址. 因為我們這里只實現(xiàn)一個到物理內(nèi)存的窗口, 重新映射的步驟是簡單的: 我們只需要定位并返回一個指向 struct page 的指針給需要的地址. 我們的 nopage 方法看來如下:


struct page *simple_vma_nopage(struct vm_area_struct *vma, unsigned long address, int *type) 
{
 struct page *pageptr;
 unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
 unsigned long physaddr = address - vma->vm_start + offset;
 unsigned long pageframe = physaddr >> PAGE_SHIFT;

 if (!pfn_valid(pageframe))
 return NOPAGE_SIGBUS;
 pageptr = pfn_to_page(pageframe);
 get_page(pageptr);
 if (type)

 *type = VM_FAULT_MINOR;
 return pageptr;
}

因為, 再一次, 在這里我們簡單地映射主內(nèi)存, nopage 函數(shù)只需要找到正確的 struct page 給出錯地址并且遞增它的引用計數(shù). 因此, 事件的請求序列是計算需要地物理地址, 并且通過右移它 PAGE_SHIFT 位轉(zhuǎn)換它為以頁幀號. 因為用戶空間可以給我們?nèi)魏嗡矚g的地址, 我們必須確保我們有一個有效的頁幀; pfn_valid 函數(shù)為我們做這些. 如果地址超范圍, 我們返回 NOPAGE_SIGBUS, 它產(chǎn)生一個總線信號被遞交給調(diào)用進程.

否則, pfn_to_page 獲得必要的 struct page 指針; 我們可遞增它的引用計數(shù)(使用調(diào)用 get_page)并且返回它.

nopage 方法正常地返回一個指向 struct page 的指針. 如果, 由于某些原因, 一個正常的頁不能返回(即, 請求的地址超出驅(qū)動的內(nèi)存區(qū)), NOPAGE_SIGBUS 可被返回來指示錯誤; 這是上的簡單代碼所做的. nopage 也可以返回 NOPAGE_OOM 來指示由于資源限制導(dǎo)致的失敗.

注意, 這個實現(xiàn)對 ISA 內(nèi)存區(qū)起作用, 但是對那些在 PCI 總線上的不行. PCI 內(nèi)存被映射在最高的系統(tǒng)內(nèi)存之上, 并且在系統(tǒng)內(nèi)存中沒有這些地址的入口. 因為沒有 struct page 來返回一個指向的指針, nopage 不能在這些情況下使用; 你必須使用 remap_pfn_range 代替.

如果 nopage 方法被留置為 NULL, 處理頁出錯的內(nèi)核代碼映射零頁到出錯的虛擬地址. 零頁是一個寫時拷貝的頁, 它讀作為0, 并且被用來, 例如, 映射 BSS 段. 任何引用零頁的進程都看到: 一個填滿 0 的頁. 如果進程寫到這個頁, 它最終修改一個私有頁. 因此, 如果一個進程擴展一個映射的頁通過調(diào)用 mremap, 并且驅(qū)動還沒有實現(xiàn) nopage, 進程結(jié)束以零填充的內(nèi)存代替一個段錯誤.

15.2.5.?重新映射特定 I/O 區(qū)

所有的我們至今所見的例子是 /dev/mem 的重新實現(xiàn); 它們重新映射物理地址到用戶空間. 典型的驅(qū)動, 但是, 想只映射應(yīng)用到它的外設(shè)設(shè)備的小的地址范圍, 不是全部內(nèi)存. 為了映射到用戶空間只一個整個內(nèi)存范圍的子集, 驅(qū)動只需要使用偏移. 下面為一個驅(qū)動做這個技巧來映射一個 simple_region_size 字節(jié)的區(qū)域, 在物理地址 simple_region_start(應(yīng)當是頁對齊的) 開始:


unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
unsigned long physical = simple_region_start + off;
unsigned long vsize = vma->vm_end - vma->vm_start;
unsigned long psize = simple_region_size - off;

if (vsize > psize)
 return -EINVAL; /* spans too high */
remap_pfn_range(vma, vma_>vm_start, physical, vsize, vma->vm_page_prot);

除了計算偏移, 這個代碼引入了一個檢查來報告一個錯誤當程序試圖映射超過在目標設(shè)備的 I/O 區(qū)可用的內(nèi)存. 在這個代碼中, psize 是已指定了偏移后剩下的物理 I/O 大小, 并且 vsize 是虛擬內(nèi)存請求的大小; 這個函數(shù)拒絕映射超出允許的內(nèi)存范圍的地址.

注意, 用戶空間可一直使用 mremap 來擴展它的映射, 可能超過物理設(shè)備區(qū)的結(jié)尾. 如果你的驅(qū)動不能定義一個 nopage method, 它從不會得到這個擴展的通知, 并且額外的區(qū)映射到零頁. 作為一個驅(qū)動編寫者, 你可能很想阻止這種行為; 映射理由到你的區(qū)的結(jié)尾不是一個明顯的壞事情, 但是很不可能程序員希望它發(fā)生.

最簡單的方法來阻止映射擴展是實現(xiàn)一個簡單的 nopage 方法, 它一直導(dǎo)致一個總線信號被發(fā)送給出錯進程. 這樣的一個方法可能看來如此:


struct page *simple_nopage(struct vm_area_struct *vma,
 unsigned long address, int *type);
{ return NOPAGE_SIGBUS; /* send a SIGBUS */}

如我們已見到的, nopage 方法只當進程解引用一個地址時被調(diào)用, 這個地址在一個已知的 VMA 中但是當前沒有有效的頁表入口給這個 VMA. 如果有已使用 remap_pfn_range 來映射全部設(shè)備區(qū), 這里展示的 nopage 方法只被調(diào)用來引用那個區(qū)外部. 因此, 它能夠安全地返回 NOPAGE_SIGBUS 來指示一個錯誤. 當然, 一個更加完整的 nopage 實現(xiàn)可以檢查是否出錯地址在設(shè)備區(qū)內(nèi), 并且如果是這樣進行重新映射. 但是, 再一次, nopage 無法在 PCI 內(nèi)存區(qū)工作, 因此 PCI 映射的擴展是不可能的.

15.2.6.?重新映射 RAM

remap_pfn_range 的一個有趣的限制是它只存取保留頁和在物理內(nèi)存頂之上的物理地址. 在 Linux, 一個物理地址頁被標志為"保留的"在內(nèi)存映射中來指示它對內(nèi)存管理是不可用的. 在 PC 上, 例如, 640 KB 和 1MB 之間被標記為保留的, 如同駐留內(nèi)核代碼自身的頁. 保留頁被鎖定在內(nèi)存并且是唯一可被安全映射到用戶空間的; 這個限制是系統(tǒng)穩(wěn)定的一個基本要求.

因此, remap_pfn_range 不允許你重新映射傳統(tǒng)地址, 這包括你通過調(diào)用 get_free_page 獲得的. 相反, 它映射在零頁. 所有都看來正常, 除了進程見到私有的, 零填充的頁而不是它在期望的被重新映射的 RAM. 這個函數(shù)做了大部分硬件驅(qū)動需要來做的所有事情, 因為它能夠重新映射高端 PCI 緩沖和 ISA 內(nèi)存.

remap_pfn_range 的限制可通過運行 mapper 見到, 其中一個例子程序在 misc-progs 在 O'Reilly 的 FTP 網(wǎng)站提供的文件. mapper 是一個簡單的工具可用來快速測試 mmap 系統(tǒng)調(diào)用; 它映射由命令行選項指定的一個文件的只讀部分, 并且輸出被映射的區(qū)到標準輸出. 下面的部分, 例如, 顯示 /dev/mem 沒有映射位于地址 64 KB的物理頁 --相反, 我們看到一個頁充滿 0 (例子中的主機是一臺 PC, 但是結(jié)果應(yīng)該在其他平臺上相同).


morgana.root# ./mapper /dev/mem 0x10000 0x1000 | od -Ax -t x1
mapped "/dev/mem" from 65536 to 69632
000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
001000

remap_pfn_range 處理 RAM 的不能之處說明基于內(nèi)存的設(shè)備如 scull 不能輕易實現(xiàn) mmap, 因為它的設(shè)備內(nèi)存是傳統(tǒng)內(nèi)存, 不是 I/O 內(nèi)存. 幸運的是, 一個相對容易的方法對任何需要映射 RAM 到用戶空間的驅(qū)動都可用; 它使用我們前面已見過的 nopage 方法.

15.2.6.1.?使用 nopage 方法重新映射 RAM

映射真實內(nèi)存到用戶空間的方法是使用 vm_ops-<nopage 來一次一個地處理頁錯. 一個簡單的實現(xiàn)是 scullp 模塊的一部分, 在第 8 張介紹.

scullp 是一個面向頁的字符設(shè)備. 因為它是面向頁的, 它可以在它的內(nèi)存上實現(xiàn) mmap. 實現(xiàn)內(nèi)存映射的代碼使用一些在"Linux 中的內(nèi)存管理"一節(jié)中介紹的概念.

在檢查代碼前, 讓我們查看影響在 scullp 中的 mmap 實現(xiàn)的設(shè)計選擇.

  • scullp 只要設(shè)備被映射就不會釋放設(shè)備內(nèi)存. 這是策略問題而非一個需求, 并且它不同于 scull 和類似設(shè)備的行為, 它們被截短為 0 當為寫而打開時. 對釋放一個映射的 scullp 設(shè)備的拒絕, 允許一個進程覆蓋被其他進程映射的區(qū)., 因此你可以測試并且看進程和設(shè)備內(nèi)存如何交互. 為避免釋放一個映射設(shè)備, 驅(qū)動必須保持一個激活映射的計數(shù); 在設(shè)備結(jié)構(gòu)中的 vmas 成員被用來作此目的.

  • 內(nèi)存映射僅當 scullp 的 order 參數(shù)(在模塊加載時間設(shè)置)是 0 時進行. 這個參數(shù)控制 get_free_pages 如何被調(diào)用( 見第 8 章"get_free_page 及其友" 一節(jié)). 0 order 的限制( 這強制一次分配一頁, 而不是以大的組)被 get_free_pages 的內(nèi)部所規(guī)定, 它是 scullp 所使用的分配函數(shù). 為最大化分配性能, Linux 內(nèi)核維護一個空閑頁列表給每個分配級別, 并且只有在一個簇中第一頁的引用計數(shù)被 get_free_pages 遞增以及被 free_pages 遞減. mmap 方法對一個 scullp 設(shè)備被禁止, 如果分配級大于 0, 因為 nopage 處理單個頁而不是一簇頁. scullp 不知道如何正確管理是高級別分配的一部分的頁的引用計數(shù).(如果你需要重新回顧 scullp 和 內(nèi)存分配級別值, 返回第 8 章的"一個使用整頁的 scull: scullp"一節(jié).)

0-級的限制大部分是用來保持代碼簡單. 它可能正確實現(xiàn) mmap 給多頁分配, 通過使用頁的使用計數(shù), 但是它可能只增加了例子的復(fù)雜性而沒有介紹任何有趣的信息.

打算根據(jù)剛剛概括的規(guī)則來映射 RAM 的代碼, 需要實現(xiàn) open, close, 和 nopage VMA 方法; 它還需要存取內(nèi)存映射來調(diào)整頁使用計數(shù).

這個 scullp_mmap 的實現(xiàn)非常短, 因為它依賴 nopage 函數(shù)來做所有的感興趣的工作:


int scullp_mmap(struct file *filp, struct vm_area_struct *vma)
{

 struct inode *inode = filp->f_dentry->d_inode;
 /* refuse to map if order is not 0 */
 if (scullp_devices[iminor(inode)].order)
 return -ENODEV;

 /* don't do anything here: "nopage" will fill the holes */
 vma->vm_ops = &scullp_vm_ops;
 vma->vm_flags |= VM_RESERVED;
 vma->vm_private_data = filp->private_data;
 scullp_vma_open(vma);
 return 0;
}

if 語句的目的是避免映射分配級別不是 0 的設(shè)備. scullp 的操作存儲在 vm_ops 成員, 并且一個指向設(shè)備結(jié)構(gòu)的指針藏于 vm_private_data 成員. 最后, vm_ops->open 被調(diào)用來更新設(shè)備的激活映射的計數(shù).

open 和 close 簡單地跟蹤映射計數(shù)并如下定義:


void scullp_vma_open(struct vm_area_struct *vma)
{
 struct scullp_dev *dev = vma->vm_private_data;
 dev->vmas++;
}

void scullp_vma_close(struct vm_area_struct *vma)
{
 struct scullp_dev *dev = vma->vm_private_data;
 dev->vmas--;
}

大部分地工作接下來由 nopage 進行. 在 scullp 實現(xiàn)中, 給 nopage 的地址參數(shù)被用來計算設(shè)備中的偏移; 這個偏移接著被用來在 scullp 內(nèi)存樹中查找正確的頁.


struct page *scullp_vma_nopage(struct vm_area_struct *vma, unsigned long address, int *type)
{
        unsigned long offset;
        struct scullp_dev *ptr, *dev = vma->vm_private_data;
        struct page *page = NOPAGE_SIGBUS;
        void *pageptr = NULL; /* default to "missing" */

        down(&dev->sem);
        offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT);
        if (offset >= dev->size)
                goto out; /* out of range */

        /*
        * Now retrieve the scullp device from the list,then the page.
        * If the device has holes, the process receives a SIGBUS when
        * accessing the hole.
        */
        offset >>= PAGE_SHIFT; /* offset is a number of pages */
        for (ptr = dev; ptr && offset >= dev->qset;)
        {
                ptr = ptr->next;
                offset -= dev->qset;
        }
        if (ptr && ptr->data)
                pageptr = ptr->data[offset];
        if (!pageptr)
                goto out; /* hole or end-of-file */
        page = virt_to_page(pageptr);

        /* got it, now increment the count */
        get_page(page);
        if (type)
                *type = VM_FAULT_MINOR;
out:
        up(&dev->sem);
        return page;
}

scullp 使用由 get_free_pages 獲取的內(nèi)存. 那個內(nèi)存使用邏輯地址尋址, 因此所有的 scullp_nopage 為獲得一個 struct page 指針不得不做的是調(diào)用 virt_to_page.

現(xiàn)在 scullp 設(shè)備如同期望般工作了, 就象你在這個從 mapper 工具中的例子輸出能見到的. 這里, 我們發(fā)送一個 /dev 的目錄列表(一個長的)到 scullp 設(shè)備并且接著使用 mapper 工具來查看這個列表的各個部分連同 mmap.


morgana% ls -l /dev > /dev/scullp
morgana% ./mapper /dev/scullp 0 140
mapped "/dev/scullp" from 0 (0x00000000) to 140 (0x0000008c)
total 232
crw-------1 root root 10, 10 Sep 15 07:40 adbmouse
crw-r--r--1 root root 10, 175 Sep 15 07:40 agpgart
morgana% ./mapper /dev/scullp 8192 200 mapped "/dev/scullp" from 8192 (0x00002000) to 8392 (0x000020c8) 
d0h1494  
brw-rw---- 1 root  floppy  2,  92 Sep 15 07:40 fd0h1660  
brw-rw---- 1 root  floppy  2,  20 Sep 15 07:40 fd0h360  
brw-rw---- 1 root  floppy  2,  12 Sep 15 07:40 fd0H360  

15.2.7.?重映射內(nèi)核虛擬地址

盡管它極少需要, 看一個驅(qū)動如何使用 mmap 映射一個內(nèi)核虛擬地址到用戶空間是有趣的. 記住, 一個真正的內(nèi)核虛擬地址, 是一個由諸如 vmalloc 的函數(shù)返回的地址 -- 就是, 一個映射到內(nèi)核頁表中的虛擬地址. 本節(jié)的代碼來自 scullv, 這是如同 scullp 但是通過 vmalloc 分配它的存儲的模塊.

大部分的 scullv 實現(xiàn)如同我們剛剛見到的 scullp, 除了沒有必要檢查控制內(nèi)存分配的 order 參數(shù). 這個的原因是 vmalloc 分配它的頁一次一個, 因為單頁分配比多頁分配更加可能成功. 因此, 分配級別問題不適用 vmalloc 分配的空間.

此外, 在由 scullp 和 scullv 使用的 nopage 實現(xiàn)中只有一個不同. 記住, scullp 一旦它發(fā)現(xiàn)感興趣的頁, 將使用 virt_to_page 來獲得對應(yīng)的 struct page 指針. 那個函數(shù)不使用內(nèi)核虛擬地址, 但是. 相反, 你必須使用 mvalloc_to_page. 因此 scullv 版本的 nopage 的最后部分看來如此:


/*
* After scullv lookup, "page" is now the address of the page
* needed by the current process. Since it's a vmalloc address,
* turn it into a struct page.
*/
page = vmalloc_to_page(pageptr);

/* got it, now increment the count */
get_page(page);
if (type)
        *type = VM_FAULT_MINOR;
out:
up(&dev->sem);
return page;

基于這個討論, 你可能也想映射由 ioremap 返回的地址到用戶空間. 但是, 那可能是一個錯誤; 來自 ioremap 的地址是特殊的并且不能作為正常的內(nèi)核虛擬地址對待. 相反, 你應(yīng)當使用 remap_pfn_range 來重新映射 I/O 內(nèi)存區(qū)到用戶空間.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號