程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> memory 編程接口

memory 編程接口

編輯:C++入門知識

在Linux系統中,一般有32位(4GB)的地址空間,進程的4GB內存空間被分為兩個部分 — 用戶空間與內核空間,用戶空間地址一般分布為0 ~ 3GB(即PAGE_OFFSET,一般等於0xc0000000),剩下的3 ~ 4GB則為內核空間。進程與內核不各自使用4GB的空間,好處就是進程進入內核不需要切換頁表,降低了進出內核的消耗。內核空間從低地址到高地址又分為:物理內存映射區、vmalloc 虛擬內存分配區、高端內存映射區、保留區。每個進程的用戶空間都是完全獨立的,用戶進程各自有不同的頁表。而內核空間是由內核負責映射,它並不會跟著進程改變,是固定的。內核空間地址有自己的頁表,內核的虛擬地址空間獨立於其他用戶進程。
一、數據結構
用戶進程創建後可以訪問整個用戶空間的虛擬地址,這段空間是未分段的線性地址范圍,在內核中進程地址空間以及與之相關的所有信息都保存在 mm_struct 中,該結構出現在進程控制結構 task_struct 中。進程用到的每段連續有效地址范圍稱為內存區,一個內存區由 vm_area_struct 描述符表示,每個內存區描述符都描述它所表示的一段連續地址區間。不同的內存區有不同的保護方案和特點,比如程序代碼段的某些部分標記為只讀,而其他部分標記為可寫或可執行。
1、mm_struct
[cpp]
struct mm_struct { 
    struct vm_area_struct * mmap;       /* list of VMAs */ 
    struct rb_root mm_rb; 
    struct vm_area_struct * mmap_cache; /* last find_vma result */ 
... 
    pgd_t * pgd; 
    atomic_t mm_users;          /* How many users with user space? */ 
    atomic_t mm_count;          /* How many references to "struct mm_struct" (users count as 1) */ 
    int map_count;              /* number of VMAs */ 
    struct rw_semaphore mmap_sem; 
    spinlock_t page_table_lock;     /* Protects page tables and some counters */ 
 
    struct list_head mmlist;        /* List of maybe swapped mm's.  These are globally strung
                                     * together off init_mm.mmlist, and are protected
                                     * by mmlist_lock
                                     */ 
... 
    unsigned long total_vm, locked_vm, shared_vm, exec_vm; 
    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes; 
    unsigned long start_code, end_code, start_data, end_data;  // 進程在內存中代碼段、數據段的起始和結束地址 
    unsigned long start_brk, brk, start_stack;                 // 進程堆的起始和結束地址、棧的起始地址 
    unsigned long arg_start, arg_end, env_start, env_end;      // 參數、環境段的起始和結束地址 
... 
}; 
其中 mmap 參數表示進程的所有內存區描述符組成鏈表的頭節點地址,mm_struct 通過 mmap訪問該鏈表,而 vm_area_struct 中的 vm_next 指針將各個內存區鏈接起來;mmap_cache 指向進程最後一次訪問的內存區描述符指針,用於提高訪問效率;mm_users 存放訪問該進程地址空間的進程數量;mm_count 是對 mm_struct 的使用統計,其值為0時說明沒有進程使用則將其回收;map_count 存在在進程地址空間中的內存區數量,即 vm_area_struct 描述符數量。
2、vm_area_struct
[cpp]
struct vm_area_struct { 
    struct mm_struct * vm_mm;   /* The address space we belong to. */ 
    unsigned long vm_start;     /* Our start address within vm_mm. */ 
    unsigned long vm_end;       /* The first byte after our end address
                       within vm_mm. */ 
 
    /* linked list of VM areas per task, sorted by address */ 
    struct vm_area_struct *vm_next; 
 
    pgprot_t vm_page_prot;      /* Access permissions of this VMA. */ 
    unsigned long vm_flags;     /* Flags, see mm.h. */ 
 
    struct rb_node vm_rb; 
... 
    /* Function pointers to deal with this struct. */ 
    const struct vm_operations_struct *vm_ops; 
... 
}; 
其中 vm_mm 指向該內存區所屬的進程地址空間;vm_start 和 vm_end 表示該虛擬內存區的起始和結束地址,考慮到性能問題,內存區的起始地址必須是頁面大小的整數倍;vm_next 指向該進程的下一個虛擬內存區;vm_ops 用於操作特定的虛擬內存區,包括打開、關閉、反映射內存區等操作。
二、編程接口
在用戶空間動態申請內存的函數為 malloc,釋放函數為 free。內核空間申請內存涉及的函數主要包括 kmalloc、__get_free_pages 和 vmalloc 等。kmalloc 和 __get_free_pages 申請的內存位於物理內存映射區域,而且在物理也是連續的,它們與真實的物理地址只有一個固定的偏移,因此存在較簡單的轉換關系。而 vmalloc 在虛擬內存空間給出一塊連續的內存區,實質上,這片連續的虛擬內存在物理內存中並不一定連續,其申請的虛擬內存和物理內存之間也沒有簡單的換算關系。
1、kmalloc
[cpp] 
/**
 * kmalloc - 分配一塊指定大小的內存
 * @size:    內存區的大小
 * @flags:   分配標志,可能值有 GFP_ATOMIC、GFP_KERNEL 等
 *
 * Note: 當標志為 GFP_KERNEL 的時候可能會引起睡眠
 */ 
 
void *kmalloc(size_t size, int flags); 
使用 kmalloc 分配的內存應該用 kfree 釋放。
2、mmap
[cpp]
/**
 * mmap   -  將物理地址映射至用戶空間
 * @addr:    指定文件應被映射到進程空間的起始地址,一般被指定為NULL,此時選擇起始地址的任務留給內核來完成
 * @len:     映射到用戶空間的字節數
 * @prot:    指定被映射空間的訪問權限,可取如下幾個值的或:
 *           PROT_READ(可讀),PROT_WRITE (可寫),PROT_EXEC (可執行),PROT_NONE(不可訪問)
 * @flags:   由以下幾個常值指定:MAP_SHARED,MAP_PRIVATE,MAP_FIXED,MAP_ANON
 * @fd:      映射到用戶空間的文件的描述符,一般由open()返回
 *           同時,fd可以指定為-1,此時須指定flags參數中的MAP_ANON,表明進行的是匿名映射
 * @offset:  被映射內存區在文件中的偏移值
 */ 
 
void* mmap(void * addr, size_t len, int prot, int flags, int fd, off_t offset); 
該函數映射文件描述符 fd 指定文件的 [offset, offset + len] 物理內存區至調用進程的 [addr, addr + len] 的用戶空間虛擬內存區,通常用於內存的共享或者用戶空間程序控制硬件設備,函數的返回值為最後文件映射到用戶空間的地址,進程可直接操作該地址。當用戶調用 mmap 的時候,內核會根據 fd 找到相對應設備驅動或者文件系統的file_operations,比如在 camera 裡面就是 v4l2_file_operations,然後調用裡面定義的 mmap 操作,一個常見的處理流程如下:
[cpp] 
static int xxx_mmap(struct file *file, struct vm_area_struct *vma) 

    int ret; 
    unsigned long size = vma->vm_end - vma->vm_start;      // 計算將要映射的內存大小 
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;    // 計算映射內存區的偏移值 
    struct xxx_info *info = file->private_data; 
 
    mutex_lock(&info->lock); 
 
    if (size > info->gbuffers * info->gbufsize - offset) {  // 如果要映射的區域過大則返回錯誤值 
        mutex_unlock(&info->lock); 
        return -EINVAL; 
    } 
 
    if (!info->mmap_start_base) {                           // 檢測物理地址是否有效 
        mutex_unlock(&info->lock); 
        return -EIO; 
    } 
 
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);// 對映射區域增加 nocache 屬性 
    ret = remap_pfn_range(vma, vma->vm_start,               // 調用 remap_pfn_range 函數創建頁表 
        (uint32_t)(info->mmap_start_base + offset) >> PAGE_SHIFT, 
        size, vma->vm_page_prot); 
    if(ret) { 
        mutex_unlock(&info->lock); 
        return -EAGAIN; 
    } 
 
    mutex_unlock(&info->lock); 
    return 0; 

在驅動程序中,我們能使用 remap_pfn_range 映射內存中的保留頁和設備 I/O 內存,另外,kmalloc 申請的的內存若要被映射到用戶空間可以通過mem_map_reserve 設置為保留頁後進行,示例代碼如下:
[cpp] 
buffer = kmalloc(size, GFP_KERNEL);  // 申請buffer 
 
for(page = virt_to_page(buffer); page < virt_to_page(buffer + size); page++) 
    mem_map_reserve(page);           // 設置頁為保留頁 
我們再看一下 remap_pfn_range 的說明:
[cpp] 
/**
 * remap_pfn_range - 映射物理地址到用戶空間
 * @vma:   虛擬內存區域指針,由內核根據用戶請求自動填充,函數將物理地址區域映射至該虛擬內存區
 * @addr:  虛擬內存區的起始地址,函數將為 addr ~ addr + size 的虛擬地址區域構造頁表
 * @pfn:   被映射物理地址的頁幀號,即將物理地址右移PAGE_SHIFT(12位,一般PAGE_SIZE為4KB)
 * @size:  被映射內存區域大小
 * @prot:  頁表的保護屬性
 *
 * Note:   調用者需要持有 mm 信號量
 */ 
 
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, 
                        unsigned long pfn, unsigned long size, pgprot_t prot); 
總結 mmap 的執行順序如下:
(1)在用戶進程創建一個虛擬內存區 - vma
(2)驅動程序調用 ramap_pfn_range 為被映射的物理內存區構造頁表
(3)將頁表分配給虛擬內存區 - vma
3、ioremap_nocache
[cpp]
/**
 * ioremap_nocache - 將物理地址映射至內核空間
 * @phys_addr:       物理地址起始值
 * @size:            要映射的空間大小
 *
 * Note:             必須由 iounmap() 釋放
 */ 
 
void __iomem *ioremap_nocache (unsigned long phys_addr, unsigned long size) 

        return __ioremap(phys_addr | MEM_NON_CACHEABLE, size, 0); 

在驅動中申請到一片連續的物理內存後,通常需要將物理地址映射到內核的虛擬地址空間,然後就可以在驅動代碼裡面直接使用虛擬地址訪問那段物理內存了,用法如下:
[cpp] 
vbase = ioremap_nocache((unsigned long)phys_start_base, cam_total_buf_size); 
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved