Linux內核中內存相關的操作函數

1、kmalloc()/kfree()


  static __always_inline void *kmalloc(size_t size, gfp_t flags)


  內核空間申請指定大小的內存區域,返回內核空間虛擬地址。在函數實現中,如果申請的內存空間較大的話,會從buddy系統申請若干內存頁面,如果申請的內存空間大小較小的話,會從slab系統中申請內存空間。


  gfp_t flags 的選項較多。參考內核文件gfp.h.


  在函數kmalloc()實現中,如果申請的空間較小,會根據申請空間的大小從slab中獲取;如果申請的空間較大,如超過一個頁面,會直接從buddy系統中獲取。


  2、vmalloc()/vfree()


  void *vmalloc(unsigned long size)


  函數作用:從高端(如果存在,優先從高端)申請內存頁面,並把申請的內存頁面映射到內核的動態映射空間。vmalloc()函數的功能和alloc_pages(_GFP_HIGHMEM)+kmap() 的功能相似,只所以說是相似而不是相同,原因在於用vmalloc()申請的物理內存頁面映射到內核的動態映射區(見下圖),並且,用vmalloc()申請的頁面的物理地址可能是不連續的。而alloc_pages(_GFP_HIGHMEM)+kmap()申請的頁面的物理地址是連續的,被映射到內核的KMAP區。


  vmalloc分配的地址則限於vmalloc_start與vmalloc_end之間。每一塊vmalloc分配的內核虛擬內存都對應一個vm_struct結構體(可別和vm_area_struct搞混,那可是進程虛擬內存區域的結構),不同的內核虛擬地址被4k大小的空閒區間隔,以防止越界--見下圖)。與進程虛擬地址的特性一樣,這些虛擬地址與物理內存沒有簡單的位移關係,必須通過內核頁表纔可轉換爲物理地址或物理頁。它們有可能尚未被映射,在發生缺頁時才真正分配物理頁面。


  如果內存緊張,連續區域無法滿足,調用vmalloc分配是必須的,因爲它可以將物理不連續的空間組合後分配,所以更能滿足分配要求。vmalloc可以映射高端頁框,也可以映射底端頁框。vmalloc的作用只是爲了提供邏輯上連續的地址…


  注意:在申請頁面時,如果註明_GFP_HIGHMEM,即從高端申請。則實際是優先從高端內存申請,順序爲(分配順序是HIGH, NORMAL, DMA )。


  3、alloc_pages()/free_pages()


  內核空間申請指定個數的內存頁,內存頁數必須是2^order個頁。


  alloc_pages(gfp_mask, order) 中,gfp_mask 是flag標誌,其中可以爲_ _GFP_DMA、_GFP_HIGHMEM 分別對應DMA和高端內存。


  注:該函數基於buddy系統申請內存,申請的內存空間大小爲2^order個內存頁面。


  參見《linux內核之內存管理。doc》


  通過函數alloc_pages()申請的內存,需要使用kmap()函數分配內核的虛擬地址。


  4、__get_free_pages()/__free_pages()


  unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)


  作用相當於alloc_pages(NORMAL)+kmap(),但不能申請高端內存頁面。


  __get_free_page()只申請一個頁面。


  5、kmap()/kunmap()


  返回指定頁面對應內核空間的虛擬地址。


  #include 


  void *kmap(struct page *page);


  void kunmap(struct page *page);


  kmap 爲系統中的任何頁返回一個內核虛擬地址。


  對於低端內存頁,它只返回頁的邏輯地址;


  對於高端內存頁, kmap在"內核永久映射空間"中創建一個特殊的映射。 這樣的映射數目是有限, 因此最好不要持有過長的時間。


  使用 kmap 創建的映射應當使用 kunmap 來釋放;


  kmap 調用維護一個計數器, 因此若2個或多個函數都在同一個頁上調用kmap也是允許的。


  通常情況下,"內核永久映射空間"是 4M 大小,因此僅僅需要一個頁表即可,內核通過來 pkmap_page_table 尋找這個頁表。


  注意:不用時及時釋放。


  kmalloc()和vmalloc()相比,kmalloc()總是從ZONE_NORMAL(下圖中的直接映射區)申請內存。kmalloc()分配的內存空間通常用於linux內核的系統數據結構和鏈表。因內核需要經常訪問其數據結構和鏈表,使用固定映射的ZONE_NORMAL空間的內存有利於提高效率。


  使用vmalloc()可以申請非連續的物理內存頁,並組成虛擬連續內存空間。vmalloc()優先從高端內存(下圖中的動態映射區)申請。內核在分配那些不經常使用的內存時,都用高端內存空間(如果有),所謂不經常使用是相對來說的,比如內核的一些數據結構就屬於經常使用的,而用戶的一些數據就屬於不經常使用的。


  alloc_pages(_GFP_HIGHMEM)+kmap() 方式申請的內存使用內核永久映射空間(下圖中的KMAP區),空間較小(通常4M線性空間),不用時需要及時釋放。另外,可以指定alloc_pages()從直接映射區申請內存,需要使用_GFP_NORMAL屬性指定。


  __get_free_pages()/__free_pages() 不能申請高端內存頁面,操作區域和kmalloc()相同(下圖中的動態映射區)。






  6、virt_to_page()


  其作用是由內核空間的虛擬地址得到頁結構。見下面的宏定義。


  #define virt_to_pfn(kaddr) (__pa(kaddr) 》 PAGE_SHIFT)


  #define pfn_to_virt(pfn) __va((pfn) 《 PAGE_SHIFT)


  #define virt_to_page(addr) pfn_to_page(virt_to_pfn(addr))


  #define page_to_virt(page) pfn_to_virt(page_to_pfn(page))


  #define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))


  #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + \


  ARCH_PFN_OFFSET)


  7、物理地址和虛擬地址之間轉換


  #ifdef CONFIG_BOOKE


  #define __va(x) ((void *)(unsigned long)((phys_addr_t)(x) + VIRT_PHYS_OFFSET))


  #define __pa(x) ((unsigned long)(x) - VIRT_PHYS_OFFSET)


  #else


  #define __va(x) ((void *)(unsigned long)((phys_addr_t)(x) + PAGE_OFFSET - MEMORY_START))


  #define __pa(x) ((unsigned long)(x) - PAGE_OFFSET + MEMORY_START)


  #endif


  8、ioremap()/iounmap()


  ioremap()的作用是把device寄存器和內存的物理地址區域映射到內核虛擬區域,返回值爲內核的虛擬地址。


  註明:在內核中操作內存空間時使用的都是內核虛擬地址,必須把device的空間映射到內核虛擬空間。


  #include 


  void *ioremap(unsigned long phys_addr, unsigned long size);


  void *ioremap_nocache(unsigned long phys_addr, unsigned long size); 映射非cache的io內存區域


  void iounmap(void * addr);


  爲了增加可移植性,最好使用下面的接口函數讀寫io內存區域,


  unsigned int ioread8(void *addr);


  unsigned int ioread16(void *addr);


  unsigned int ioread32(void *addr);


  void iowrite8(u8 value, void *addr);


  void iowrite16(u16 value, void *addr);


  void iowrite32(u32 value, void *addr);


  如果你必須讀和寫一系列值到一個給定的 I/O 內存地址, 你可以使用這些函數的重複版本:


  void ioread8_rep(void *addr, void *buf, unsigned long count);


  void ioread16_rep(void *addr, void *buf, unsigned long count);


  void ioread32_rep(void *addr, void *buf, unsigned long count);


  void iowrite8_rep(void *addr, const void *buf, unsigned long count);


  void iowrite16_rep(void *addr, const void *buf, unsigned long count);


  void iowrite32_rep(void *addr, const void *buf, unsigned long count);


  這些函數讀或寫 count 值從給定的 buf 到 給定的 addr. 注意 count 表達爲在被寫入的數據大小; ioread32_rep 讀取 count 32-位值從 buf 開始。


  9、request_mem_region()


  本函數的作用是:外設的io端口映射到io memory region中。在本函數實現中會檢查輸入到本函數的參數所描述的空間(下面成爲本io空間)是否和io memory region中已存在的空間衝突等,並設置本io空間的parent字段等(把本io空間插入到io 空間樹種)。


  註明:io memory region 空間中是以樹形結構組織的,默認的根爲iomem_resource描述的io空間,其name爲"PCI mem".


  request_mem_region(start,n,name) 輸入的參數依次是設備的物理地址,字節長度,設備名字。函數返回類型如下


  struct resource {


  resource_size_t start;


  resource_size_t end;


  const char *name;


  unsigned long flags;


  struct resource *parent, *sibling, *child;


  };


  10、SetPageReserved()


  隨着linux的長時間運行,空閒頁面會越來越少,爲了防止linux內核進入請求頁面的僵局中,Linux內核採用頁面回收算法(PFRA)從用戶進程和內核高速緩存中回收內存頁框,並根據需要把要回收頁框的內容交換到磁盤上的交換區。調用該函數可以使頁面不被交換。


  #define SetPageReserved(page) set_bit(PG_reserved, &(page)->flags)


  PG_reserved 的標誌說明如下。


  * PG_reserved is set for special pages, which can never be swapped out. Some


  * of them might not even exist (eg empty_bad_page)…


  11、do_mmap()/do_ummap()


  內核使用do_mmap()函數爲進程創建一個新的線性地址區間。但是說該函數創建了一個新VMA並不非常準確,因爲如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問權限的話,那麼兩個區間將合併爲一個。如果不能合併,那麼就確實需要創建一個新的VMA了。但無論哪種情況, do_mmap()函數都會將一個地址區間加入到進程的地址空間中--無論是擴展已存在的內存區域還是創建一個新的區域。


  同樣,釋放一個內存區域應使用函數do_ummap(),它會銷燬對應的內存區域。


  12、get_user_pages()


  作用是在內核空間獲取用戶空間內存的page 描述,之後可以通過函數kmap() 獲取page 對應到內核的虛擬地址。


  int get_user_pages(struct task_struct *tsk, struct mm_struct *mm,


  unsigned long start, int len, int write, int force,


  struct page **pages, struct vm_area_struct **vmas)


  參數說明


  參數tsk:指示用戶空間對應進程的task_struct數據結構。只是爲了記錄錯誤信息用,該參數可以爲空。


  參數mm:從該mm struct中獲取start 指示的若干頁面。


  參數start:參數mm空間的起始地址,即用戶空間的虛擬地址。


  參數len:需要映射的頁數。


  參數write:可以寫標誌。


  參數force:強制可以寫標誌。


  參數pages:輸出的頁數據結構。


  參數vmas:對應的需要存儲區,(沒有看明白對應的代碼)


  返回值:數返回實際獲取的頁數,貌似對每個實際獲取的頁都是給頁計數值增1,如果實際獲取的頁不等於請求的頁,要放棄操作則必須對已獲取的頁計數值減1.


  13、copy_from_user()和copy_to_user()


  主要應用於設備驅動中讀寫函數中,通過系統調用觸發,在當前進程上下文內核態運行(即當前進程通過系統調用觸發)。


  copy_from_user的目的是防止用戶程序欺騙內核,將一個非法的地址傳進去,如果沒有它,這一非法地址就檢測不到,內和就會訪問這個地址指向的數據。因爲在內核中訪問任何地址都沒有保護,如果不幸訪問一個錯誤的內存地址會搞死內核或發生更嚴重的問題


  copy_from_user調用了access_ok,所以纔有"自己判斷功能"


  access_ok(),可以檢查訪問的空間是否合法。


  注意:中斷代碼時不能用copy_from_user,因爲其調用了might_sleep()函數,會導致睡眠。


  unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)


  通常用在設備讀函數或ioctl 中獲取參數的函數中:其中"to"是用戶空間的buffer地址,在本函數中將內核buffer"from"除的n個字節拷貝到用戶空間的"to"buffer.


  unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)


  通常用在設備寫函數或ioctl中設置參數的函數中:"to"是內核空間的buffer指針,要寫入的buffer;"from"是用戶空間的指針,數據源buffer.


  14、get_user(x, ptr)


  本函數的作用是獲取用戶空間指定地址的數值並保存到內核變量x中,ptr爲用戶空間的地址。用法舉例如下。


  get_user(val, (int __user *)arg)


  註明:函數用戶進程上下文內核態,即通常在系統調用函數中使用該函數。


  15、put_user(x, ptr)


  本函數的作用是將內核空間的變量x的數值保存到用戶空間指定地址處,prt爲用戶空間地址。用法舉例如下。


  put_user(val, (int __user *)arg)


  註明:函數用戶進程上下文內核態,即通常在系統調用函數中使用該函數。




原文出自【比特網】,轉載請保留原文鏈接:http://soft.chinabyte.com/os/333/12370833.shtml
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章