亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频

蟲蟲首頁| 資源下載| 資源專輯| 精品軟件
登錄| 注冊

您現在的位置是:首頁 > 技術閱讀 >  深入理解mmap

深入理解mmap

時間:2024-02-12

1.開場白

  • 環境:
    處理器架構:arm64
    內核源碼:linux-5.11
    ubuntu版本:20.04.1
    代碼閱讀工具:vim+ctags+cscope

我們知道,linux系統中用戶空間和內核空間是隔離的,用戶空間程序不能隨意的訪問內核空間數據,只能通過中斷或者異常的方式進入內核態,一般情況下,我們使用copy_to_user和copy_from_user等內核api來實現用戶空間和內核空間的數據拷貝,但是像顯存這樣的設備如果也采用這樣的方式就顯的效率非常底下,因為用戶經常需要在屏幕上進行繪制,要消除這種復制的操作就需要應用程序直接能夠訪問顯存,但是顯存被映射到內核空間,應用程序是沒有訪問權限的,如果顯存也能同時映射到用戶空間那就不需要拷貝操作了,于是字符設備中提供了mmap接口,可以將內核空間映射的那塊物理內存再次映射到用戶空間,這樣用戶空間就可以直接訪問不需要任何拷貝操作,這就是我們今天要說的0拷貝技術。

下面是正常情況下用戶空間和內核空間數據訪問圖示:

2. 體驗一下

首先我們通過一個例子來感受一下:

驅動代碼:

注:驅動代碼中使用misc框架來實現字符設備,misc框架會處理如創建字符設備,創建設備等通用的字符設備處理,我們只需要關心我們的實際的邏輯即可(內核中大量使用misc設備框架來使用字符設備操作集如ioctl接口,像實現系統虛擬化kvm模塊,實現安卓進程間通信的binder模塊等)。

0copy_demo.c


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>


#define MISC_DEV_MINOR 5

static char *kbuff;


static ssize_t misc_dev_read(struct file *filep, char __user *buf, size_t count, loff_t *offset)
{

 int ret;

 size_t len = (count > PAGE_SIZE ? PAGE_SIZE : count);

 pr_info("###### %s:%d kbuff:%s ######\n", __func__, __LINE__, kbuff);
 
 ret = copy_to_user(buf, kbuff, len);  //這里使用copy_to_user  來進程內核空間到用戶空間拷貝

 return len - ret;
}

static ssize_t misc_dev_write(struct file *filep, const char __user *buf, size_t count, loff_t *offset)
{
 pr_info("###### %s:%d ######\n", __func__, __LINE__);
 return 0;
}

static int misc_dev_mmap(struct file *filep, struct vm_area_struct *vma)
{
 int ret;
 unsigned long start;

 start = vma->vm_start;
 
 ret =  remap_pfn_range(vma, start, virt_to_phys(kbuff) >> PAGE_SHIFT,
   PAGE_SIZE, vma->vm_page_prot); //使用remap_pfn_range來映射物理頁面到進程的虛擬內存中  virt_to_phys(kbuff) >> PAGE_SHIFT作用是將內核的虛擬地址轉化為實際的物理地址頁幀號  創建頁表的權限為通過mmap傳遞的 vma->vm_page_prot   映射大小為1頁

 return ret;
}

static long misc_dev_ioctl(struct file *filep, unsigned int cmd, unsigned long args)
{
 pr_info("###### %s:%d ######\n", __func__, __LINE__);
 return 0;
}



static int misc_dev_open(struct inode *inodep, struct file *filep)
{
 pr_info("###### %s:%d ######\n", __func__, __LINE__);
 return 0;
}

static int misc_dev_release(struct inode *inodep, struct file *filep)
{
 pr_info("###### %s:%d ######\n", __func__, __LINE__);
 return 0;
}


static struct file_operations misc_dev_fops = {
 .open = misc_dev_open,
 .release = misc_dev_release,
 .read = misc_dev_read,
 .write = misc_dev_write,
 .unlocked_ioctl = misc_dev_ioctl,
 .mmap = misc_dev_mmap,
};

static struct miscdevice misc_dev = {
 MISC_DEV_MINOR,
 "misc_dev",
 &misc_dev_fops,
};

static int __init misc_demo_init(void)
{
 misc_register(&misc_dev);  //注冊misc設備 (讓misc來幫我們處理創建字符設備的通用代碼,這樣我們就不需要在去做這些和我們的實際邏輯無關的代碼處理了)

 
 kbuff = (char *)__get_free_page(GFP_KERNEL);  //申請一個物理頁面(返回對應的內核虛擬地址,內核初始化的時候會做線性映射,將整個ddr內存映射到線性映射區,所以我們不需要做頁表映射)
 if (NULL == kbuff)
  return -ENOMEM;

 pr_info("###### %s:%d ######\n", __func__, __LINE__);
 return 0;
}

static void __exit misc_demo_exit(void)
{
 free_page((unsigned long)kbuff);

 misc_deregister(&misc_dev);
 pr_info("###### %s:%d ######\n", __func__, __LINE__);
}

module_init(misc_demo_init);
module_exit(misc_demo_exit);
MODULE_LICENSE("GPL");

應用代碼:test.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>



int main(int argc, char **argv)
{
 
 int fd;
 char *ptr;
 char buff[32];

 fd = open("/dev/misc_dev", O_RDWR);  //打開字符設備
 if (fd < 0) {
  perror("fail to open");
  return -1;
 }
  
 ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); //映射字符設備到進程的地址空間  權限為可讀可寫 映射為共享  大小為一個頁面
 if (ptr == MAP_FAILED) {
  perror("fail to mmap");
  return -1;
 }


 memcpy(ptr, "hello world!!!", 15);   //寫mmap映射的內存  直接操作,不需要進行特權級別的陷入!


 if(read(fd, buff, 15) == -1) {  //讀接口  來讀取映射的內存,這里會進行內核空間到用戶空間的數據拷貝 (需要調用系統調用 在內核空間進行拷貝,然后才能訪問)
  perror("fail to read");
  return -1;
 }
 puts(buff);  

 pause();
 return 0;
}

Makefile文件:

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

KERNEL_DIR ?= ~/kernel/linux-5.11
obj-m := 0copy_demo.o

modules:
 $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules

app:
 aarch64-linux-gnu-gcc test.c -o test
 cp test $(KERNEL_DIR)/kmodules

clean:
 $(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean

install:
 cp *.ko $(KERNEL_DIR)/kmodules

編譯驅動代碼和應用代碼,然后拷貝到qemu中運行:

編譯驅動模塊代碼:
$ make modules

編譯并拷貝應用:
$ make app

拷貝驅動模塊到qemu:
$ make install 

加載驅動代碼:
# insmod 0copy_demo.ko
[23328.532194] ###### misc_demo_init:91 ######

查看生成的設備節點:
# ls -l /dev/misc_dev 
crw-rw----    1 0        0          10,   5 Apr  7 19:26 /dev/misc_dev

后臺運行應用程序:
# ./test&
# [23415.280501] ###### misc_dev_open:56 ######
[23415.281052] ###### misc_dev_read:20 kbuff:hello world!!! ######
hello world!!!

查看test的pid:
# pidof test
1768


查看內存映射:
# cat /proc/1768/maps 
aaaabc5a0000-aaaabc5a1000 r-xp 00000000 00:19 8666193                    /mnt/test
aaaabc5b0000-aaaabc5b1000 r--p 00000000 00:19 8666193                    /mnt/test
aaaabc5b1000-aaaabc5b2000 rw-p 00001000 00:19 8666193                    /mnt/test
aaaacf033000-aaaacf054000 rw-p 00000000 00:00 0                          [heap]
ffff8a911000-ffff8aa52000 r-xp 00000000 fe:00 152                        /lib/libc-2.27.so
ffff8aa52000-ffff8aa61000 ---p 00141000 fe:00 152                        /lib/libc-2.27.so
ffff8aa61000-ffff8aa65000 r--p 00140000 fe:00 152                        /lib/libc-2.27.so
ffff8aa65000-ffff8aa67000 rw-p 00144000 fe:00 152                        /lib/libc-2.27.so
ffff8aa67000-ffff8aa6b000 rw-p 00000000 00:00 0 
ffff8aa6b000-ffff8aa88000 r-xp 00000000 fe:00 129                        /lib/ld-2.27.so
ffff8aa91000-ffff8aa92000 rw-s 00000000 00:05 152                        /dev/misc_dev      //映射設備文件到用戶空間
ffff8aa92000-ffff8aa94000 rw-p 00000000 00:00 0 
ffff8aa94000-ffff8aa96000 r--p 00000000 00:00 0                          [vvar]
ffff8aa96000-ffff8aa97000 r-xp 00000000 00:00 0                          [vdso]
ffff8aa97000-ffff8aa98000 r--p 0001c000 fe:00 129                        /lib/ld-2.27.so
ffff8aa98000-ffff8aa9a000 rw-p 0001d000 fe:00 129                        /lib/ld-2.27.so
ffffecb5a000-ffffecb7b000 rw-p 00000000 00:00 0                          [stack]

執行了以上步驟可以發現最終內核中出現了我在應用程序中寫入的“hello world!!!“  字符串,應用程序也能成功讀取到(當然本文講解的0拷貝實現的驅動接口是mmap,而我們讀取使用的是read接口,里面我們用copy_to_user來實現的,當然我們可以直接操作mmap映射的內存不需要任何拷貝操作)。

查看應用程序的內存映射發現,/dev/misc_dev設備被映射到了ffff8aa91000-ffff8aa92000這段用戶空間地址范圍,而且權限為rw-s(可讀可寫共享)。

寫到這里可能大家還是有點不明白那我來解釋下:

1.用戶空間不能直接訪問內核空間數據(不能直接讀寫),一旦訪問發生缺頁異常,產生段錯誤,必須通過read這樣的接口來訪問,而read這樣的接口會通過系統調用的方式寫入到內核態,然后通過copy_to_user這樣的內核api來拷貝內核空間數據到用戶空間之后才能正常訪問。

2.通過mmap這種方式之后,用戶進程可以直接訪問這塊內存,memcpy訪問的也只不過是用戶空間地址,由于訪問的時候已經分配好了物理頁面和建立好了物理頁到虛擬頁的映射,所有不會發生缺頁異常,也不會發生用戶態到內核態的陷入動作。

3.用戶態進程正常訪問內核態數據需要首先通過系統調用等方式陷入內核,進行數據拷貝,然后再次回到用戶態,用戶態和內核態直接的進出需要進行上下文切換,需要2次上下文切換,需要一定的開銷,而mmap映射好之后以后訪問都不需要進行上下文切換。

4.mmap映射這種方法由于物理頁面通過頁面共享更加節省內存,而用戶態和內核態內存拷貝需要兩份物理頁面。

3.實現原理

我們發現通過mmap映射之后,我們在應用程序中可以直接讀寫這段內存,不需要任何用戶空間和內核空間的拷貝動作,大大提高了內存訪問效率,那么就是是如何實現的呢?下面我們來揭開它神秘的面紗:

實現0拷貝功不可沒的是mmap接口中的remap_pfn_range內核api,它將內核空間映射的物理內存重新映射到了用戶空間,下面我們來看這個函數的實現:remap_pfn_range函數參數如下:

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,         
                |   unsigned long pfn, unsigned long size, pgprot_t prot)   

vma為需要映射的進程的vma(進程調用mmap的時候內核會找到一個合適的vma), addr為vma中的一個起始映射地址(這是用戶空間的一個虛擬地址),pfn為頁幀號(在驅動的mmap接口中會將內核空間的地址轉化為物理地址的頁幀號),size為需要映射的大小,prot為映射的權限(一般取mmap時傳遞的權限如rw)

remap_pfn_range實現主要如下代碼段:

remap_pfn_range
    ...
    pgd = pgd_offset(mm, addr);                                    
     flush_cache_range(vma, addr, end);                             
     do {                                                           
             next = pgd_addr_end(addr, end);                        
             err = remap_p4d_range(mm, pgd, addr, next,             
                             pfn + (addr >> PAGE_SHIFT), prot);     
             if (err)                                               
                     break;                                         
     } while (pgd++, addr = next, addr != end);                     

解釋下:remap_pfn_range函數會查找進程的頁表,然后填寫頁表,會將映射的物理頁幀號和訪問權限填寫到進程的對應頁表中,這會遍歷進程的各級頁表找到最終的頁表項然后進行填寫,具體過程自行查看代碼。

我們需要注意的是:

1.一般情況下,用戶程序調用mmap只是申請虛擬內存(即是獲得一塊沒有使用用戶空間內存,使用vma描述),實際的物理頁表都是通過進程訪問的時候缺頁異常的方式來申請的,但是本場景中是物理頁面已經申請好了,進程訪問時不會再發生缺頁異常,不會申請物理頁面。

2.同樣,物理頁面到用戶空間虛擬頁面的映射也在調用mmap的時候,驅動調用mmap接口的remap_pfn_range映射好了,也不需要在訪問的時候發生缺頁異常來建立映射。所以,只要用戶進程通過mmap映射之后就可以正常訪問,訪問過程中不會發生缺頁異常,映射虛擬頁對應的物理頁面已經在驅動中申請好映射好。

下面給出mmap映射原理的圖示:



4.應用場景

最后,我們來看下使用framebuffer的lcd對0拷貝的使用情況

fbmem_init    //drivers/video/fbdev/core/fbmem.c
->register_chrdev(FB_MAJOR, "fb", &fb_fops)  //注冊framebuffer字符設備
   
   -> struct file_operations fb_fops = {
   ->.mmap =         fb_mmap    
        -> fb_mmap    //framebuffer的實現
            ->vm_iomap_memory
                ->io_remap_pfn_range
                    ->remap_pfn_range
                    
->  fb_class = class_create(THIS_MODULE, "graphics")  //創建設備類

lcd驅動代碼中會設置好最終注冊framebuffer:

xxxfb_probe
->register_framebuffer
    ->do_register_framebuffer
        -> fb_info->dev = device_create(fb_class, fb_info->device,
                         |    MKDEV(FB_MAJOR, i), NULL, "fb%d", i);  //創建設備  會出現/dev/fdx 設備節點

可以看到當系統支持framebuffer設備時,在fbmem_init中會創建framebuffer設備類關聯字符設備操作集fb_fops,lcd的驅動代碼中會調用register_framebuffer創建framebuffer設備(就會創建出了/dev/fdx 設備節點),應用程序就可以通過mmap來映射framebuffer設備到用戶空間,然后進行屏幕繪制操作,不需要任何數據拷貝。

5.總結

可以看的出,通過mmap實現0拷貝非常簡單,只需要在驅動的mmap接口中調用remap_pfn_range來將內核空間映射的那塊物理頁再次映射到用戶空間即可,這就實現了用戶空間和內核空間的數據共享,這和用戶進程之間的共享內存機制非常相似,都需要操作進程的頁表將這段物理內存映射到進程虛擬地址空間。


亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频
亚洲色在线视频| 免费观看欧美在线视频的网站| 国产精品第十页| 国产日韩在线一区| 亚洲国产欧美一区| 国产综合久久| 久久久久综合网| 欧美亚一区二区| 亚洲福利视频免费观看| 免费观看在线综合| 国产性做久久久久久| 久久久久久久999| 有坂深雪在线一区| 亚洲免费在线观看视频| 亚洲午夜未删减在线观看| 国产精品初高中精品久久| 激情久久一区| 香蕉久久精品日日躁夜夜躁| 国产伦精品一区二区三区四区免费| 最新日韩av| 久久另类ts人妖一区二区| 亚洲成人在线观看视频| 欧美影院一区| 国产精品亚洲一区二区三区在线| 久久久久久噜噜噜久久久精品| 国产精品欧美日韩| 亚洲一卡久久| 激情久久综合| 久久亚洲不卡| 欧美日韩一区在线播放| 久久精品国产久精国产思思| 国产情侣一区| 亚洲毛片在线观看| 国产精品专区h在线观看| 亚洲一区在线免费| 欧美午夜激情视频| 免费在线观看精品| 最新日韩在线| 久久久久久电影| 亚洲网站在线播放| 欧美性感一类影片在线播放 | 亚洲欧洲av一区二区三区久久| 久久婷婷综合激情| 午夜精彩国产免费不卡不顿大片| 国产伦精品一区二区三区高清 | 欧美久久综合| 99xxxx成人网| 国产麻豆日韩欧美久久| 久久午夜视频| 亚洲日本中文字幕区| 99天天综合性| 亚洲免费成人| 久久裸体艺术| 尤物99国产成人精品视频| 国产欧美一区二区三区视频| 久久精品导航| 99av国产精品欲麻豆| 国产女精品视频网站免费| 久久欧美肥婆一二区| 久久久久久久综合| 99re国产精品| 亚洲国产婷婷| 国产精品网站在线| 小嫩嫩精品导航| 亚洲欧美在线视频观看| 狠狠噜噜久久| 欧美日韩国产一区精品一区| 老司机精品导航| 99re6热只有精品免费观看| 国产精品免费观看在线| 欧美人与禽猛交乱配| 欧美在线日韩精品| 午夜精品在线看| 亚洲毛片在线观看| 国产精品久久国产精麻豆99网站| 欧美午夜a级限制福利片| 亚洲欧美日韩精品久久久| 亚洲国产精品毛片| 亚洲国产精品一区二区www| 国产精品美女久久久| 久久天天躁夜夜躁狠狠躁2022| 久久成人国产精品| 亚洲视频1区2区| 狠狠色综合日日| 狠狠色伊人亚洲综合成人| 欧美精品二区| 亚洲一区免费看| 亚洲无线视频| 亚洲美女91| 亚洲精品视频一区二区三区| 激情综合久久| 国产日韩在线亚洲字幕中文| 极品少妇一区二区三区| 国产在线不卡视频| 国产欧美日韩一区二区三区在线| 国产欧美日韩综合| 欧美三级视频在线观看| 亚洲网站视频福利| 久久精品亚洲一区二区| 欧美不卡在线视频| 国产精品久久久久999| 欧美日韩在线精品| 欧美日韩国产专区| 亚洲免费视频一区二区| 中文av字幕一区| 亚洲一区综合| 香蕉久久夜色精品国产| 亚洲乱码精品一二三四区日韩在线| 国产综合香蕉五月婷在线| 国产精品亚洲激情| 国产精品日韩在线| 国产精品视频999| 在线欧美日韩精品| 国产老女人精品毛片久久| 狂野欧美激情性xxxx欧美| 欧美日韩理论| 欧美成人一品| 亚洲欧美高清| 亚洲欧美日韩国产一区二区| 国产精品99久久久久久久vr| 久久精品国产77777蜜臀| 久久精品官网| 男人的天堂亚洲在线| 国产亚洲人成网站在线观看| 国产一区自拍视频| 国产精品自拍网站| 亚洲校园激情| 亚洲天堂网站在线观看视频| 在线观看国产精品网站| 亚洲人成在线观看| 亚洲午夜精品国产| 久久精品国产综合| 免费日韩av片| 欧美精品一区二区久久婷婷| 欧美国产综合视频| 欧美午夜精品久久久久久久| 黄色av成人| 在线看片欧美| 尤妮丝一区二区裸体视频| 欧美精品久久天天躁| 欧美午夜性色大片在线观看| 久久精品国产综合精品| 国产精品美女久久久久久免费| 国产精品日韩欧美一区二区三区| 亚洲国产老妈| 99综合在线| 亚洲精品日韩精品| 欧美成人午夜激情在线| 欧美日韩国产黄| 久久综合色8888| 国产亚洲人成网站在线观看| 亚洲美女黄网| 欧美黑人多人双交| 韩国自拍一区| 亚洲激情另类| 久久亚洲精选| 欧美人成在线| 久久夜精品va视频免费观看| 欧美日韩不卡在线| 欧美人交a欧美精品| 欧美日韩亚洲综合在线| 每日更新成人在线视频| 欧美精品99| 精品91视频| 欧美一区三区二区在线观看| 在线亚洲欧美视频| 欧美一二区视频| 欧美高清在线视频| 欧美午夜在线| 久久综合九色综合欧美狠狠| 欧美色道久久88综合亚洲精品| 欧美激情视频一区二区三区免费 | 嫩草成人www欧美| 国内外成人在线| 亚洲激情av在线| 母乳一区在线观看| 欧美色播在线播放| 欧美日韩国产一区精品一区| 这里只有精品视频| 久久久五月婷婷| 国产一区二区三区久久悠悠色av | 在线亚洲欧美| 欧美中文字幕不卡| 好看的日韩视频| 久久精品1区| 国产精品久久久久久亚洲调教| 亚洲欧美大片| 欧美特黄a级高清免费大片a级| 久久精品理论片| 精东粉嫩av免费一区二区三区| 亚洲女人小视频在线观看| 国产综合激情| 久久久噜噜噜久久久| 亚洲线精品一区二区三区八戒| 欧美日韩国产免费| 久久亚洲国产精品一区二区| 国产欧美一区二区精品婷婷| 欧美精品成人一区二区在线观看| 亚洲国产精品久久久| 国产欧美日韩综合一区在线播放|