精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

通過mmap實現零拷貝技術

Linux閱碼場 ? 來源:Linux內核遠航者 ? 作者:Linux內核遠航者 ? 2022-06-28 17:33 ? 次閱讀

1.開場白

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

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

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

d0997780-f677-11ec-ba43-dac502259ad0.png

2. 體驗一下

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

驅動代碼:

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

0copy_demo.c

#include
#include
#include
#include
#include


#defineMISC_DEV_MINOR5

staticchar*kbuff;


staticssize_tmisc_dev_read(structfile*filep,char__user*buf,size_tcount,loff_t*offset)
{

intret;

size_tlen=(count>PAGE_SIZE?PAGE_SIZE:count);

pr_info("######%s:%dkbuff:%s######
",__func__,__LINE__,kbuff);

ret=copy_to_user(buf,kbuff,len);//這里使用copy_to_user來進程內核空間到用戶空間拷貝

returnlen-ret;
}

staticssize_tmisc_dev_write(structfile*filep,constchar__user*buf,size_tcount,loff_t*offset)
{
pr_info("######%s:%d######
",__func__,__LINE__);
return0;
}

staticintmisc_dev_mmap(structfile*filep,structvm_area_struct*vma)
{
intret;
unsignedlongstart;

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頁

returnret;
}

staticlongmisc_dev_ioctl(structfile*filep,unsignedintcmd,unsignedlongargs)
{
pr_info("######%s:%d######
",__func__,__LINE__);
return0;
}



staticintmisc_dev_open(structinode*inodep,structfile*filep)
{
pr_info("######%s:%d######
",__func__,__LINE__);
return0;
}

staticintmisc_dev_release(structinode*inodep,structfile*filep)
{
pr_info("######%s:%d######
",__func__,__LINE__);
return0;
}


staticstructfile_operationsmisc_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,
};

staticstructmiscdevicemisc_dev={
MISC_DEV_MINOR,
"misc_dev",
&misc_dev_fops,
};

staticint__initmisc_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######
",__func__,__LINE__);
return0;
}

staticvoid__exitmisc_demo_exit(void)
{
free_page((unsignedlong)kbuff);

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

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

應用代碼:test.c

#include
#include
#include
#include
#include
#include
#include



intmain(intargc,char**argv)
{

intfd;
char*ptr;
charbuff[32];

fd=open("/dev/misc_dev",O_RDWR);//打開字符設備
if(fd"failtoopen");
return-1;
}

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


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


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

pause();
return0;
}

Makefile文件:

exportARCH=arm64
exportCROSS_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-gcctest.c-otest
cptest$(KERNEL_DIR)/kmodules

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

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

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

編譯驅動模塊代碼:
$makemodules

編譯并拷貝應用:
$makeapp

拷貝驅動模塊到qemu:
$makeinstall

加載驅動代碼:
#insmod0copy_demo.ko
[23328.532194]######misc_demo_init:91######

查看生成的設備節點:
#ls-l/dev/misc_dev
crw-rw----10010,5Apr719:26/dev/misc_dev

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

查看test的pid:
#pidoftest
1768


查看內存映射:
#cat/proc/1768/maps
aaaabc5a0000-aaaabc5a1000r-xp0000000000:198666193/mnt/test
aaaabc5b0000-aaaabc5b1000r--p0000000000:198666193/mnt/test
aaaabc5b1000-aaaabc5b2000rw-p0000100000:198666193/mnt/test
aaaacf033000-aaaacf054000rw-p0000000000:000[heap]
ffff8a911000-ffff8aa52000r-xp00000000fe:00152/lib/libc-2.27.so
ffff8aa52000-ffff8aa61000---p00141000fe:00152/lib/libc-2.27.so
ffff8aa61000-ffff8aa65000r--p00140000fe:00152/lib/libc-2.27.so
ffff8aa65000-ffff8aa67000rw-p00144000fe:00152/lib/libc-2.27.so
ffff8aa67000-ffff8aa6b000rw-p0000000000:000
ffff8aa6b000-ffff8aa88000r-xp00000000fe:00129/lib/ld-2.27.so
ffff8aa91000-ffff8aa92000rw-s0000000000:05152/dev/misc_dev//映射設備文件到用戶空間
ffff8aa92000-ffff8aa94000rw-p0000000000:000
ffff8aa94000-ffff8aa96000r--p0000000000:000[vvar]
ffff8aa96000-ffff8aa97000r-xp0000000000:000[vdso]
ffff8aa97000-ffff8aa98000r--p0001c000fe:00129/lib/ld-2.27.so
ffff8aa98000-ffff8aa9a000rw-p0001d000fe:00129/lib/ld-2.27.so
ffffecb5a000-ffffecb7b000rw-p0000000000:000[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函數參數如下:

intremap_pfn_range(structvm_area_struct*vma,unsignedlongaddr,
|unsignedlongpfn,unsignedlongsize,pgprot_tprot)

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映射原理的圖示:

d0adc1d6-f677-11ec-ba43-dac502259ad0.png


4.應用場景

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

fbmem_init//drivers/video/fbdev/core/fbmem.c
->register_chrdev(FB_MAJOR,"fb",&fb_fops)//注冊framebuffer字符設備

->structfile_operationsfb_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來將內核空間映射的那塊物理頁再次映射到用戶空間即可,這就實現了用戶空間和內核空間的數據共享,這和用戶進程之間的共享內存機制非常相似,都需要操作進程的頁表將這段物理內存映射到進程虛擬地址空間。

原文標題:5.總結

文章出處:【微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 處理器
    +關注

    關注

    68

    文章

    19160

    瀏覽量

    229115
  • 內核
    +關注

    關注

    3

    文章

    1363

    瀏覽量

    40228
  • Linux
    +關注

    關注

    87

    文章

    11227

    瀏覽量

    208920
  • 內存映射
    +關注

    關注

    0

    文章

    14

    瀏覽量

    7411

原文標題:5.總結

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    嵌入式學習-飛凌嵌入式ElfBoard ELF 1板卡-LCD顯示圖片編程示例之介紹mmap

    mmap概念存儲映射 I/O這種高級 I/O方式,它的一個非常經典的使用場景便是用在 Framebuffer 應用編程中。通過 mmap()將顯示器的顯示緩沖區(顯存)映射到進程的地址空間中,這樣
    發表于 11-23 17:00

    批量音頻檔案拷貝最佳方案:解決播放錯誤與拷貝不完全問題

    本文討論了在數字化時代,專業SD拷貝機在批量拷貝音頻檔案至MicroSD卡中的應用優勢。相較于傳統計算機拷貝,SD拷貝機能夠完整復制包括隱藏區域在內的所有數據,避免播放錯誤和
    的頭像 發表于 11-23 16:12 ?68次閱讀
    批量音頻檔案<b class='flag-5'>拷貝</b>最佳方案:解決播放錯誤與<b class='flag-5'>拷貝</b>不完全問題

    嵌入式學習-飛凌嵌入式ElfBoard ELF 1板卡-LCD顯示圖片編程示例之介紹mmap

    mmap概念 存儲映射 I/O這種高級 I/O方式,它的一個非常經典的使用場景便是用在 Framebuffer 應用編程中。通過 mmap()將顯示器的顯示緩沖區(顯存)映射到進程的地址空間中,這樣
    發表于 11-22 09:13

    飛凌嵌入式ElfBoard ELF 1板卡-LCD顯示圖片編程示例之介紹mmap

    mmap概念存儲映射 I/O這種高級 I/O方式,它的一個非常經典的使用場景便是用在 Framebuffer 應用編程中。通過 mmap()將顯示器的顯示緩沖區(顯存)映射到進程的地址空間中,這樣
    發表于 11-21 08:59

    磁盤拷貝機會拷貝刪除的內容嗎

    磁盤拷貝機,也稱為硬盤克隆器或磁盤復制器,是一種用于復制硬盤驅動器內容的設備。它可以將一個硬盤上的所有數據,包括操作系統、程序、文件和設置,復制到另一個硬盤上。這種設備在數據備份、系統遷移、硬盤
    的頭像 發表于 10-14 15:38 ?270次閱讀

    如何打造碳園區,盾華電子助力“雙碳”目標實現 碳公路 碳智慧校園

    如何打造碳園區,盾華電子助力“雙碳”目標實現 碳公路 碳智慧校園
    的頭像 發表于 10-08 15:52 ?145次閱讀
    如何打造<b class='flag-5'>零</b>碳園區,盾華電子助力“雙碳”目標<b class='flag-5'>實現</b>  <b class='flag-5'>零</b>碳公路 <b class='flag-5'>零</b>碳智慧校園

    編譯例程partition_mmap,報錯no such vaddr range怎么解決?

    內存映射問題:編譯例程partition_mmap,報錯no such vaddr range怎么解決?
    發表于 09-26 07:03

    無線室內定位系統是通過什么技術實現的呢?

    無線室內定位系統作為現代科技的重要組成部分,廣泛應用于智能家居、企業管理、醫療護理等多個領域。這些系統通過不同的技術手段實現高精度的室內定位,為用戶帶來便捷與高效。那么,無線室內定位系統究竟是
    的頭像 發表于 09-10 09:28 ?348次閱讀
    無線室內定位系統是<b class='flag-5'>通過</b>什么<b class='flag-5'>技術</b><b class='flag-5'>實現</b>的呢?

    拆解mmap內存映射的本質!

    mmap 內存映射里所謂的內存其實指的是虛擬內存,在調用 mmap 進行匿名映射的時候(比如進行堆內存的分配),是將進程虛擬內存空間中的某一段虛擬內存區域與物理內存中的匿名內存頁進行映射,當調用
    的頭像 發表于 01-24 14:30 ?1476次閱讀
    拆解<b class='flag-5'>mmap</b>內存映射的本質!

    如何評價跑汽車的NAC技術?NAC技術是怎么出現的?

    日前,跑汽車推出了一項讓駕駛員體驗“科目二”駕駛樂趣的技術,這項名為NAC的駕駛技術可以實現系統控制油門剎車,駕駛員控制方向。
    的頭像 發表于 01-16 16:53 ?1308次閱讀
    如何評價<b class='flag-5'>零</b>跑汽車的NAC<b class='flag-5'>技術</b>?NAC<b class='flag-5'>技術</b>是怎么出現的?

    如何通過Matlab進行極點求解?

     對于包含變量的傳遞函數,如果其是二階的,利用Matlab求極點表達式是容易實現的。但對于更高階的系統而言,想通過Matlab來求得解析解是極其困難的。
    的頭像 發表于 11-30 12:23 ?4180次閱讀
    如何<b class='flag-5'>通過</b>Matlab進行<b class='flag-5'>零</b>極點求解?

    機器人拷貝數據傳輸編程開發

    編程開發 為了方便大家使用,TogetherROS針對拷貝功能進行了封裝,風格類似ROS2中話題通信的接口,還是話題通信一樣的流程,我們只需要修改幾個函數就可以實現啦。 運行例程 $ source
    的頭像 發表于 11-27 16:55 ?479次閱讀
    機器人<b class='flag-5'>零</b><b class='flag-5'>拷貝</b>數據傳輸編程開發

    如何進行拷貝性能測試

    TogetherROS?·Bot拷貝性能測試 我們使用TogetherROS?·Bot系統內部集成的性能測試工具——performance_test,來評估下開啟拷貝前后的性能差異
    的頭像 發表于 11-27 16:51 ?408次閱讀
    如何進行<b class='flag-5'>零</b><b class='flag-5'>拷貝</b>性能測試

    什么是拷貝技術

    的應用中,經常會發生這種問題,導致CPU都在做數據拷貝,沒有時間處理其他的應用功能了,直接的感覺就是處理卡頓。 拷貝技術 針對這種問題,
    的頭像 發表于 11-27 16:20 ?435次閱讀
    什么是<b class='flag-5'>零</b><b class='flag-5'>拷貝</b><b class='flag-5'>技術</b>