A. linux關於地址空間和MMAP映射有何特點
Linux採用
虛擬
內存技術,系統中的所有進程之間以虛擬方式共享內存。對每個進程來說,它們好像都可以訪問整個系統的所有物理內存。更重要的是,即使單獨一個進程,它擁有的地址空間也可以遠遠大於系統物理內存。
進程地址空間由每個進程中的線性地址區組成,每個進程都有一個32位或64位的平坦(flat)空間,空間的具體大小取決於體系結構。「平坦」指地址空間范圍是一個獨立的連續區間。通常情況下,每個進程都有唯一的這種平坦空間,而且每個進程的地址空間之間彼此互不相干。兩個不同的進程可以在它們各自地址空間的相同地址內存存放不同的數據。但是進程之間也可以選擇共享地址空間,我們稱這樣的進程為線程。
在地址空間中,我們更為關心的是進程有權訪問的虛擬內存地址區間,比如08048000~0804c000。這些可被訪問的合法地址區間被成為內存區域(memory area),通過內核,進程可以給自己的地址空間動態地添加或減少內存區域。
進程只能訪問有效范圍內的內存地址。每個內存區域也具有相應進程必須遵循的特定訪問屬性,如只讀、只寫、可執行等屬性。如果一個進程訪問了不在有效范圍中的地址,或以不正確的方式訪問有效地址,那麼內核就會終止該進程,並返回「段錯誤」信息。
?
內存區域可以包含各種內存對象,如下:
?
可執行文件代碼的內存映射,成為代碼段(text section)。
?
可執行文件的已初始化全局變數的內存映射,成為數據段(data section)。
?
包含未初始化全局變數的零頁(也就是bss段)的內存映射。零頁是指頁面中的數據全部為0。
?
用於進程用戶空間棧的零頁的內存映射。
?
每一個諸如C庫或動態鏈接程序等共享庫的代碼段、數據段和bss也會被載入進程的地址空間。
?
任何內存映射文件。
?
任何共享內存段。
?
任何匿名的內存映射,比如由malloc()分配的內存。
進程地址空間的任何有效地址都只能位於唯一的區域,這些內存區域不能相互覆蓋。可以看到,在執行的進程中,每個不同的內存片斷都對應一個獨立的內存區域:棧、對象代碼、全局變數、被映射的文件等等。
內核使用內存描述符表示進程的地址空間。內存描述符由mm_struct結構體表示,定義在文件中,該結構包含了和進程地址空間有關的全部信息。
VMA
內存區域由vm_area_struct結構體描述,定義在文件中,內存區域在內核中也經常被稱作虛擬內存區域或者VMA。
VMA標志是一種位標志,它定義在vm_area_struct結構中(該結構中的vm_flags子域)。和物理頁的訪問許可權不同,VMA標志反映了內核處理頁面索需要遵守的行為准則,而不是硬體要求。VM_IO標志內存區域中包含對設備I/O空間的映射。該標志通常在設備驅動程序執行 mmap()函數進行I/O空間映射時才被設置,同時該標志也表示該內存區域不能被包含在任何進程的存放轉存(core mp)中。VM_RESERVED標志內存區域不能被換出,它也是在設備驅動程序進行映射時被設置。
vm_area_struct結構體中的vm_ops域指向與指定內存區域相關的操作函數表,內核使用表中的方法操作VMA。
mmap()和do_mmap():創建地址區間
內核使用do_mmap()函數創建一個新的線性地址區間。但是說給函數創建一個新VMA並不非常准確,因為如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問許可權的話,那麼兩個區間將合並為一個。如果不能合並,那麼就確實需要創建一個新的VMA了。但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中——無論是擴展已經存在的內存區域還是創建一個新的區域。
do_mmap()函數聲明在文件中,原型如下:
unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
在用戶空間可以通過mmap()函數調用獲取內核函數do_mmap()的功能。mmap()系統調用原型如下:
void *mmap2(void *start, size_t length,
int prot, int flags,
int fd, off_t pgoff)
do_munmap()函數從特定的進程地址空間中刪除指定地址區間,該函數在文件中聲明:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
系統調用munmap()給用戶空間程序提供了一種從自身地址空間中刪除指定地址區間的方法,它和系統調用mmap()的作用相反:
int munmap(void *start, size_t length)
mmap設備操作
對於驅動程序來說,內存映射可以提供給用戶程序直接訪問設備內存的能力。映射一個設備,意味著使用戶空間的一段地址關聯到設備內存上。無論何時,只要程序在分配的地址范圍內進行讀取或者寫入,實際上就是對設備的訪問。
並不是所有的設備都能進行mmap抽象。例如,串口設備和其他面向流的設備就無法實現這種抽象。mmap的另一個限制是映射都是以 PAGE_SIZE為單位的。內核只能在頁表一級處理虛擬地址;因此,被映射的區域必須是PAGE_SIZE的整數倍,而且必須位於起始於 PAGE_SIZE整數倍地址的物理內存內。如果區域的大小不是頁大小的整數倍,內核就通過生成一個稍微大一些的區域來容納它。
mmap方法是file_operations結構中的一員,並且在執行mmap系統調用時就會調用該方法。在調用實際方法之前,內核會完成很多工作,而且該方法的原型與系統調用的原型由很大區別。關於Linux命令的介紹,看看《linux就該這么學》,具體關於這一章地址3w(dot)linuxprobe/chapter-02(dot)html
文件操作聲明如下:
int (*mmap) (struct file * filp, struct vm_area_struct *vma);
其中vma參數包含了用於訪問設備的虛擬地址區間的信息。大部分工作已經由內核完成了,要實現mmap,驅動程序只要為這一地址范圍構造合適的頁表即可,如果需要的話,就用一個新的操作集替換vma->vm_ops。
有兩種建立頁表的方法:使用remap_page_range函數可一次建立所有的頁表,或者通過nopage VMA方法每次建立一個頁表。
構造用於映射一段物理地址的新頁表的工作是由remap_page_range完
B. linux內核 io.h中的outb是哪裡的用法
.h文件是函數的說明,你得去定義這個函數的文件里找,一般來說在同名的c或cpp文件里。
C. 楂樻ц兘寮傛io鏈哄埗錛歩o_uring
楂樻ц兘寮傛I/O澶勭悊鏈哄埗錛歩o_uring鐨勯潻鍛芥х獊鐮闅忕潃Linux 5.10鐗堟湰鐨勯潻鏂幫紝io_uring寮傛IO鎺ュ彛搴旇繍鑰岀敓錛屽畠閫氳繃宸у欑殑鐢ㄦ埛絀洪棿鍐呭瓨鏄犲皠鍜屾棤閿佺幆褰㈤槦鍒楄捐★紝鏋佸ぇ鍦版彁鍗囦簡鏁版嵁澶勭悊鐨勬晥鐜囥俰o_uring浠ュ叾鐙鐗圭殑璁捐★紝灝嗕換鍔℃彁浜や笌緇撴灉榪斿洖鏃犵紳鏁村悎錛屽噺灝戜簡鍐呭瓨鎷瘋礉鐨勫紑閿錛屽睍鐜板嚭鍗撹秺鐨勬ц兘銆
io_uring鐨勬牳蹇冩満鍒跺湪浜庡叾鍙屽悜闃熷垪緇撴瀯錛屽寘鎷琒Q錛圫ubmit Queue錛夊拰CQ錛圕ompletion Queue錛夈係Q璐熻矗鎺ユ敹鐢ㄦ埛鐨処O璇鋒眰錛岃孋Q鍒欒礋璐i氱煡鐢ㄦ埛璇鋒眰鐨勫畬鎴愮姸鎬併傚畠浠閫氳繃鍐呭瓨灞忛殰鎿嶄綔淇濇寔鍚屾ワ紝鏃犻渶閿佹満鍒訛紝浠庤岄伩鍏嶄簡甯歌佺殑絝炴佹潯浠躲
緋葷粺璋冪敤鐨勫叧閿鐜鑺
涓轟簡鐩磋傚湴灞曠ずio_uring鐨勫▉鍔涳紝鎴戜滑閫氳繃瀹炴垬婕旂ず錛氫緥濡傦紝uring_cat紼嬪簭錛屽畠鍒╃敤io_uring灝佽呬簡鏂囦歡鎿嶄綔錛屽疄鐜板湪紓佺洏I/O鏂歸潰鐨勬樉钁楁ц兘鎻愬崌銆傚湪瀹為檯嫻嬭瘯涓錛屽規瘮寮傛ユā寮忥紙io_uring錛岃揪鍒版儕浜虹殑19.0k IOPS錛夊拰鍚屾ユā寮忥紙8k IOPS錛夛紝io_uring鍦ㄧ佺洏I/O鎬ц兘涓婂崰鎹浜嗘樉钁椾紭鍔褲
姝ゅ栵紝rust_echo_bench鏈嶅姟鍣ㄦ祴璇曡繘涓姝ヨ瘉瀹炰簡io_uring鐨勪紭瓚婃э紝瀹冨湪澶勭悊澶ч噺騫跺彂璇鋒眰鏃訛紝灞曠幇鍑哄崜瓚婄殑鍚炲悙閲忓拰鍝嶅簲閫熷害銆傛繁鍏ョ悊瑙io_uring鐨勬牳蹇冪郴緇熻皟鐢ㄦ帴鍙o紝鑳藉熷府鍔╁紑鍙戣呮洿鏈夋晥鍦板埄鐢ㄨ繖涓寮哄ぇ鐨勫伐鍏楓
瀹炶返搴旂敤涓庡︿範璧勬簮
瑕佹繁鍏ユ帉鎻io_uring錛孌PDK鏁欑▼鏄涓涓鏋佸ソ鐨勫︿範璧風偣銆傞氳繃瀹冿紝寮鍙戣呭彲浠ヤ簡瑙e備綍鍒╃敤io_uring榪涜岄珮鏁堢殑緗戠粶鍜孖/O鎿嶄綔錛屼互鍙婂備綍浼樺寲緋葷粺鎬ц兘銆傜珛鍗寵㈤槄錛岃笍涓奿o_uring鐨勯珮鎬ц兘涔嬫棶錛
灝界io_uring鎻愪緵浜嗗己澶х殑鎬ц兘鎻愬崌錛屼絾鍏朵嬌鐢ㄤ篃瑕佹眰寮鍙戣呭叿澶囦竴瀹氱殑緋葷粺緙栫▼鐭ヨ瘑鍜屽唴瀛樼$悊鎶宸с傞氳繃緇嗚嚧鐨勪唬鐮佺ず渚嬪拰瀹炶返錛屼綘灝嗚兘澶熼嗙暐io_uring鐨勫唴鍦ㄩ瓍鍔涳紝涓哄簲鐢ㄧ▼搴忓甫鏉ュ墠鎵鏈鏈夌殑閫熷害涓庢晥鐜囥
D. Linux磁碟IO流程
文件IO的分層設計
先看圖:
malloc的buf對應application buffer,用戶空間;
fwrite是系統提供的最上層介面,也是最常用的接纖嘩口。它在用戶進程空間開辟一個CLib buffer,將多次小數據量相鄰寫操作(application buffer)先緩存起來,合並,最終調用write函數一次性寫入(或者將大塊數據分解多次write調用);
write函數通過調用系統調用介面,將數據從應用層到內核慎巧層,所以write會觸發內核態/用戶態切換。當數據到達page cache後,內核並不會立即把數據往下傳遞。而是返回用戶空間。數據什麼時候寫入硬碟,有內核IO調度決定,所以write是一個非同步調用;
read調用是先檢查page cache裡面是否有數據,如果有,就取出來返回用戶,如果沒有,就同步傳遞寬豎鍵下去並等待有數據,再返回用戶,所以read是一個同步過程;
fclose隱含fflush函數,fflush只負責把數據從Clibbuffer拷貝到pagecache中返回,並沒有刷新到磁碟上,刷新到磁碟上可以使用fsync函數;
即便fsync仍有可能沒寫到磁碟上,一是磁碟有緩存,二是即便關閉緩存也可能為了跑分沒有真正關閉;
** 一致性
fwrite使用用戶進程私有空間,多線程必然需要做同步。write如果寫大小小於PIPE_BUF,是原子操作。根據已知信息,內核所做僅限於此,如果兩個進程同時寫文件,可能出現錯亂,需要實測。
** 安全性
從前面的分層設計來看,使用fsync函數可以最大限度保障安全寫入,但仍然沒有絕對的安全性。
另外一張圖
E. 宓屽叆寮廘inux寮鍙戜腑鐨勬枃浠禝/O鏄浠涔
銆銆宓屽叆寮弆inux寮鍙戜腑鐨勬枃浠禝/O灝辨槸瀵瑰栬捐繘琛屾枃浠舵娊璞★紝涔熷氨鏄璁や負涓鍒囧栭儴璁懼囬兘鏄鏂囦歡錛屾墍鏈夊瑰栬劇殑璁塊棶閮介氳繃鏂囦歡鏂瑰紡銆傚氫換鍔″氨鏄鐢ㄤ竴涓狢PU鎵ц屽氫釜浠誨姟錛岃繖涓浠誨姟鍙鍋氳繘紼嬫垨綰跨▼銆
銆銆I/O杈撳叆/杈撳嚭(Input/Output),鍒嗕負IO璁懼囧拰IO鎺ュ彛涓や釜閮ㄥ垎銆
銆銆鍦≒OSIX鍏煎圭殑緋葷粺涓婏紝渚嬪侺inux緋葷粺錛孖/O鎿嶄綔鍙浠ユ湁澶氱嶆柟寮忥紝姣斿侱IO(Direct I/O),AIO(Asynchronous I/O 寮傛I/O),Memory-Mapped I/O(鍐呭瓨鏄犺綢/O)絳夛紝涓嶅悓鐨処/O鏂瑰紡鏈変笉鍚岀殑鏂瑰紡鍜屾ц兘錛屽湪涓嶅悓鐨勫簲鐢ㄤ腑鍙浠ユ寜鎯呭喌閫夋嫨涓嶅悓鐨処/O鏂瑰紡銆
銆銆杈撳叆杈撳嚭I/O嫻佸彲浠ョ湅鎴愬瑰瓧鑺傛垨鑰呭寘瑁呭悗鐨勫瓧鑺傜殑璇誨彇灝辨槸鎷垮嚭鏉ユ斁榪涘幓鍙岃礬鍒囨崲錛涘疄鐜拌仈鍔ㄦ帶鍒剁郴緇熺殑寮辯數綰胯礬涓庤鎺ц懼囩殑寮虹數綰胯礬涔嬮棿鐨勮漿鎺ャ侀殧紱伙紝浠ラ槻姝㈠己鐢電獪鍏ョ郴緇燂紝淇濋殰緋葷粺鐨勫畨鍏錛