Linux設備驅動開發詳解筆記二

第11章 內存與I/O訪問

每個進程的用戶空間都是完全獨立、 互不相干的, 用戶進程各自有不同的頁表。 而內核空間是由內核
負責映射, 它並不會跟着進程改變, 是固定的。 內核空間的虛擬地址到物理地址映射是被所有進程共享
的, 內核的虛擬空間獨立於其他程序
11.3 內存存取
11.3.1 用戶空間內存動態申請
在用戶空間中動態申請內存的函數爲malloc() , 這個函數在各種操作系統上的使用都是一致的,
malloc() 申請的內存的釋放函數爲free() 。 對於Linux而言, C庫的malloc() 函數一般通過brk() 和
mmap() 兩個系統調用從內核申請內存。
由於用戶空間C庫的malloc算法實際上具備一個二次管理能力, 所以並不是每次申請和釋放內存都一
定伴隨着對內核的系統調用。 比如, 代碼清單11.2的應用程序可以從內核拿到內存後, 立即調用
free() , 由於free() 之前調用了mallopt(M_TRIM_THRESHOLD, -1) 和
mallopt(M_MMAP_MAX, 0) , 這個free() 並不會把內存還給內核, 而只是還給了C庫的分配算法(內
存仍然屬於這個進程) , 因此之後所有的動態內存申請和釋放都在用戶態下進行。
代碼清單11.2 用戶空間內存申請以及mallopt

#include <malloc.h>
#include <sys/mman.h>
#define SOMESIZE (100*1024*1024) // 100MB

int main(int argc, char *argv[])
{
 unsigned char *buffer;
 int i;

 if (mlockall(MCL_CURRENT | MCL_FUTURE))
 mallopt(M_TRIM_THRESHOLD, -1);
 mallopt(M_MMAP_MAX, 0);

 buffer = malloc(SOMESIZE);
 if (!buffer)
 exit(-1);

 /*
 * Touch each page in this piece of memory to get it
 * mapped into RAM
 */
 for (i = 0; i < SOMESIZE; i += page_size)
 buffer[i] = 0;
 free(buffer);
 /* <do your RT-thing> */

 return 0;
}


另外, Linux內核總是採用按需調頁(Demand Paging) , 因此當malloc() 返回的時候, 雖然是成功
返回, 但是內核並沒有真正給這個進程內存, 這個時候如果去讀申請的內存, 內容全部是0, 這個頁面的
映射是隻讀的。 只有當寫到某個頁面的時候, 內核纔在頁錯誤後, 真正把這個頁面給這個進程。

11.4 設備I/O端口和I/O內存的訪問
設備通常會提供一組寄存器來控制設備、 讀寫設備和獲取設備狀態, 即控制寄存器、 數據寄存器和狀
態寄存器。 這些寄存器可能位於I/O空間中, 也可能位於內存空間中。 當位於I/O空間時, 通常被稱爲I/O端
口; 當位於內存空間時, 對應的內存空間被稱爲I/O內存。

11.4.1 Linux I/O端口和I/O內存訪問接口
1.I/O端口
在Linux設備驅動中, 應使用Linux內核提供的函數來訪問定位於I/O空間的端口, 這些函數包括如下幾
種。
1) 讀寫字節端口(8位寬) 。
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
2) 讀寫字端口(16位寬) 。
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
3) 讀寫長字端口(32位寬) 。
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
4) 讀寫一串字節。
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
5) insb() 從端口port開始讀count個字節端口, 並將讀取結果寫入addr指向的內存; outsb() 將addr
指向的內存中的count個字節連續寫入以port開始的端口。
6) 讀寫一串字。
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
7) 讀寫一串長字。
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
上述各函數中I/O端口號port的類型高度依賴於具體的硬件平臺, 因此, 這裏只是寫出了unsigned。
2.I/O內存
在內核中訪問I/O內存(通常是芯片內部的各個I2C、 SPI、 USB等控制器的寄存器或者外部內存總線上的設備) 之前, 需首先使用ioremap() 函數將設備所處的物理地址映射到虛擬地址上。 ioremap() 的
原型如下:
void *ioremap(unsigned long offset, unsigned long size);
ioremap() 與vmalloc() 類似, 也需要建立新的頁表, 但是它並不進行vmalloc() 中所執行的內存
分配行爲。 ioremap() 返回一個特殊的虛擬地址, 該地址可用來存取特定的物理地址範圍, 這個虛擬地
址位於vmalloc映射區域。 通過ioremap() 獲得的虛擬地址應該被iounmap() 函數釋放, 其原型如下:
void iounmap(void * addr);
 

11.4.4 將設備地址映射到用戶空間
1.內存映射與VMA
用戶空間是不可能也不應該直接訪問設備的, 但是, 設備驅動程序中可實現mmap()
函數, 這個函數可使得用戶空間能直接訪問設備的物理地址。 將用戶空間的一段內存與設備內存關聯, 當用戶訪問用戶空間的這段地址範圍時, 
實際上會轉化爲對設備的訪問。
這種能力對於顯示適配器一類的設備非常有意義, 如果用戶空間可直接通過內存映射訪問顯存的話,
屏幕幀的各點像素將不再需要一個從用戶空間到內核空間的複製的過程。
mmap() 必須以PAGE_SIZE爲單位進行映射, 實際上, 內存只能以頁爲單位進行映射, 若要映射非
PAGE_SIZE整數倍的地址範圍, 要先進行頁對齊, 強行以PAGE_SIZE的倍數大小進行映射。
從file_operations文件操作結構體可以看出, 驅動中mmap() 函數的原型如下:
int(*mmap)(struct file *, struct vm_area_struct*);
驅動中的mmap() 函數將在用戶進行mmap() 系統調用時最終被調用, mmap() 系統調用的原型
與file_operations中mmap() 的原型區別很大, 如下所示:
caddr_t mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);

當用戶調用mmap() 的時候, 內核會進行如下處理。
1) 在進程的虛擬空間查找一塊VMA。2) 將這塊VMA進行映射。
3) 如果設備驅動程序或者文件系統的file_operations定義了mmap( ) 操作, 則調用它。
4) 將這個VMA插入進程的VMA鏈表中。
file_operations中mmap( ) 函數的第一個參數就是步驟1) 找到的VMA。
由mmap( ) 系統調用映射的內存可由munmap( ) 解除映射, 這個函數的原型如下:
int munmap(caddr_t addr, size_t len );

大多數設備驅動都不需要提供設備內存到用戶空間的映射能力, 因爲, 對於串口等面向流的設
備而言, 實現這種映射毫無意義。 而對於顯示、 視頻等設備, 建立映射可減少用戶空間和內核空間之間的
內存複製

 

11.5 I/O內存靜態映射

建立外設I/O內存物理地址到虛擬地址的靜態映射  map_desc
map_desc結構體
struct map_desc {
 unsigned long virtual; /* 虛擬地址 */
 unsigned long pfn ; /* __phys_to_pfn(phy_addr) */
 unsigned long length; /* 大小 */
 unsigned int type; /* 類型 */
};

static struct map_desc ixdp2x01_io_desc _ _initdata = {
 .virtual = IXDP2X01_VIRT_CPLD_BASE,
 .pfn = _ _phys_to_pfn(IXDP2X01_PHYS_CPLD_BASE),
 .length = IXDP2X01_CPLD_REGION_SIZE,
 .type = MT_DEVICE
};

static void _ _init ixdp2x01_map_io(void)
{
 ixp2000_map_io();
 iotable_init(&ixdp2x01_io_desc, 1);//建立頁映射

}

11.6 DMA

DMA是一種無須CPU的參與就可以讓外設與系統內存之間進行雙向數據傳輸的硬件機制。 使用DMA
可以使系統CPU從實際的I/O數據傳輸過程中擺脫出來, 從而大大提高系統的吞吐率。

DMA方式的數據傳輸由DMA控制器(DMAC) 控制, 在傳輸期間, CPU可以併發地執行其他任務。
當DMA結束後, DMAC通過中斷通知CPU數據傳輸已經結束, 然後由CPU執行相應的中斷服務程序進行
後處理。
 

第12章 Linux設備驅動的軟件架構思想
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章