精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Linux的直接I/O機(jī)制

系統(tǒng) Linux
Linux 中提供了一種不經(jīng)過操作系統(tǒng)內(nèi)核的緩存文件訪問機(jī)制,這樣一種文件訪問機(jī)制對(duì)于那種將 I/O 緩存存放在用戶地址空間的應(yīng)用程序來說,是非常高效的。本文將基于 2.6.18 版本的內(nèi)核來討論 Linux 中直接 I/O 的技術(shù)的設(shè)計(jì)與實(shí)現(xiàn)。

對(duì)于傳統(tǒng)的操作系統(tǒng)來說,普通的 I/O 操作一般會(huì)被內(nèi)核緩存,這種 I/O 被稱作緩存 I/O。本文所介紹的文件訪問機(jī)制不經(jīng)過操作系統(tǒng)內(nèi)核的緩存,數(shù)據(jù)直接在磁盤和應(yīng)用程序地址空間進(jìn)行傳輸,所以該文件訪問的機(jī)制稱作為直接 I/O。Linux 中就提供了這樣一種文件訪問機(jī)制,對(duì)于那種將 I/O 緩存存放在用戶地址空間的應(yīng)用程序來說,直接 I/O 是一種非常高效的手段。本文將基于 2.6.18 版本的內(nèi)核來討論 Linux 中直接 I/O 的技術(shù)的設(shè)計(jì)與實(shí)現(xiàn)。

直接 I/O 的動(dòng)機(jī)

在介紹直接 I/O 之前,這一小節(jié)先介紹一下為什么會(huì)出現(xiàn)直接 I/O 這種機(jī)制,即傳統(tǒng)的 I/O 操作存在哪些缺點(diǎn)。

什么是緩存 I/O (Buffered I/O)

緩存 I/O 又被稱作標(biāo)準(zhǔn) I/O,大多數(shù)文件系統(tǒng)的默認(rèn) I/O 操作都是緩存 I/O。在 Linux 的緩存 I/O 機(jī)制中,操作系統(tǒng)會(huì)將 I/O 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存( page cache )中,也就是說,數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。緩存 I/O 有以下這些優(yōu)點(diǎn):

·緩存 I/O 使用了操作系統(tǒng)內(nèi)核緩沖區(qū),在一定程度上分離了應(yīng)用程序空間和實(shí)際的物理設(shè)備。

·緩存 I/O 可以減少讀盤的次數(shù),從而提高性能。

當(dāng)應(yīng)用程序嘗試讀取某塊數(shù)據(jù)的時(shí)候,如果這塊數(shù)據(jù)已經(jīng)存放在了頁緩存中,那么這塊數(shù)據(jù)就可以立即返回給應(yīng)用程序,而不需要經(jīng)過實(shí)際的物理讀盤操作。當(dāng)然,如果數(shù)據(jù)在應(yīng)用程序讀取之前并未被存放在頁緩存中,那么就需要先將數(shù)據(jù)從磁盤讀到頁緩存中去。對(duì)于寫操作來說,應(yīng)用程序也會(huì)將數(shù)據(jù)先寫到頁緩存中去,數(shù)據(jù)是否被立即寫到磁盤上去取決于應(yīng)用程序所采用的寫操作機(jī)制:如果用戶采用的是同步寫機(jī)制( synchronous writes ), 那么數(shù)據(jù)會(huì)立即被寫回到磁盤上,應(yīng)用程序會(huì)一直等到數(shù)據(jù)被寫完為止;如果用戶采用的是延遲寫機(jī)制( deferred writes ),那么應(yīng)用程序就完全不需要等到數(shù)據(jù)全部被寫回到磁盤,數(shù)據(jù)只要被寫到頁緩存中去就可以了。在延遲寫機(jī)制的情況下,操作系統(tǒng)會(huì)定期地將放在頁緩存中的數(shù)據(jù)刷到磁盤上。與異步寫機(jī)制( asynchronous writes )不同的是,延遲寫機(jī)制在數(shù)據(jù)完全寫到磁盤上的時(shí)候不會(huì)通知應(yīng)用程序,而異步寫機(jī)制在數(shù)據(jù)完全寫到磁盤上的時(shí)候是會(huì)返回給應(yīng)用程序的。所以延遲寫機(jī)制本身是存在數(shù)據(jù)丟失的風(fēng)險(xiǎn)的,而異步寫機(jī)制則不會(huì)有這方面的擔(dān)心。

緩存 I/O 的缺點(diǎn)

在緩存 I/O 機(jī)制中,DMA 方式可以將數(shù)據(jù)直接從磁盤讀到頁緩存中,或者將數(shù)據(jù)從頁緩存直接寫回到磁盤上,而不能直接在應(yīng)用程序地址空間和磁盤之間進(jìn)行數(shù)據(jù)傳輸,這樣的話,數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間和頁緩存之間進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來的 CPU 以及內(nèi)存開銷是非常大的。

對(duì)于某些特殊的應(yīng)用程序來說,避開操作系統(tǒng)內(nèi)核緩沖區(qū)而直接在應(yīng)用程序地址空間和磁盤之間傳輸數(shù)據(jù)會(huì)比使用操作系統(tǒng)內(nèi)核緩沖區(qū)獲取更好的性能,下邊這一小節(jié)中提到的自緩存應(yīng)用程序就是其中的一種。

自緩存應(yīng)用程序( self-caching applications)

對(duì)于某些應(yīng)用程序來說,它會(huì)有它自己的數(shù)據(jù)緩存機(jī)制,比如,它會(huì)將數(shù)據(jù)緩存在應(yīng)用程序地址空間,這類應(yīng)用程序完全不需要使用操作系統(tǒng)內(nèi)核中的高速緩沖存儲(chǔ)器,這類應(yīng)用程序就被稱作是自緩存應(yīng)用程序( self-caching applications )。數(shù)據(jù)庫管理系統(tǒng)是這類應(yīng)用程序的一個(gè)代表。自緩存應(yīng)用程序傾向于使用數(shù)據(jù)的邏輯表達(dá)方式,而非物理表達(dá)方式;當(dāng)系統(tǒng)內(nèi)存較低的時(shí)候,自緩存應(yīng)用程序會(huì)讓這種數(shù)據(jù)的邏輯緩存被換出,而并非是磁盤上實(shí)際的數(shù)據(jù)被換出。自緩存應(yīng)用程序?qū)σ僮鞯臄?shù)據(jù)的語義了如指掌,所以它可以采用更加高效的緩存替換算法。自緩存應(yīng)用程序有可能會(huì)在多臺(tái)主機(jī)之間共享一塊內(nèi)存,那么自緩存應(yīng)用程序就需要提供一種能夠有效地將用戶地址空間的緩存數(shù)據(jù)置為無效的機(jī)制,從而確保應(yīng)用程序地址空間緩存數(shù)據(jù)的一致性。

對(duì)于自緩存應(yīng)用程序來說,緩存 I/O 明顯不是一個(gè)好的選擇。由此引出我們這篇文章著重要介紹的 Linux 中的直接 I/O 技術(shù)。Linux 中的直接 I/O 技術(shù)非常適用于自緩存這類應(yīng)用程序,該技術(shù)省略掉緩存 I/O 技術(shù)中操作系統(tǒng)內(nèi)核緩沖區(qū)的使用,數(shù)據(jù)直接在應(yīng)用程序地址空間和磁盤之間進(jìn)行傳輸,從而使得自緩存應(yīng)用程序可以省略掉復(fù)雜的系統(tǒng)級(jí)別的緩存結(jié)構(gòu),而執(zhí)行程序自己定義的數(shù)據(jù)讀寫管理,從而降低系統(tǒng)級(jí)別的管理對(duì)應(yīng)用程序訪問數(shù)據(jù)的影響。在下面一節(jié)中,我們會(huì)著重介紹 Linux 中提供的直接 I/O 機(jī)制的設(shè)計(jì)與實(shí)現(xiàn),該機(jī)制為自緩存應(yīng)用程序提供了很好的支持。#p#

Linux 2.6 中的直接 I/O 技術(shù)

Linux 2.6 中提供的幾種文件訪問方式

所有的 I/O 操作都是通過讀文件或者寫文件來完成的。在這里,我們把所有的外圍設(shè)備,包括鍵盤和顯示器,都看成是文件系統(tǒng)中的文件。訪問文件的方法多種多樣,這里列出下邊這幾種 Linux 2.6 中支持的文件訪問方式。

標(biāo)準(zhǔn)訪問文件的方式

在 Linux 中,這種訪問文件的方式是通過兩個(gè)系統(tǒng)調(diào)用實(shí)現(xiàn)的:read() 和 write()。當(dāng)應(yīng)用程序調(diào)用 read() 系統(tǒng)調(diào)用讀取一塊數(shù)據(jù)的時(shí)候,如果該塊數(shù)據(jù)已經(jīng)在內(nèi)存中了,那么就直接從內(nèi)存中讀出該數(shù)據(jù)并返回給應(yīng)用程序;如果該塊數(shù)據(jù)不在內(nèi)存中,那么數(shù)據(jù)會(huì)被從磁盤上讀到頁高緩存中去,然后再從頁緩存中拷貝到用戶地址空間中去。如果一個(gè)進(jìn)程讀取某個(gè)文件,那么其他進(jìn)程就都不可以讀取或者更改該文件;對(duì)于寫數(shù)據(jù)操作來說,當(dāng)一個(gè)進(jìn)程調(diào)用了 write() 系統(tǒng)調(diào)用往某個(gè)文件中寫數(shù)據(jù)的時(shí)候,數(shù)據(jù)會(huì)先從用戶地址空間拷貝到操作系統(tǒng)內(nèi)核地址空間的頁緩存中去,然后才被寫到磁盤上。但是對(duì)于這種標(biāo)準(zhǔn)的訪問文件的方式來說,在數(shù)據(jù)被寫到頁緩存中的時(shí)候,write() 系統(tǒng)調(diào)用就算執(zhí)行完成,并不會(huì)等數(shù)據(jù)完全寫入到磁盤上。Linux 在這里采用的是我們前邊提到的延遲寫機(jī)制( deferred writes )。

以標(biāo)準(zhǔn)的方式對(duì)文件進(jìn)行讀寫 

圖 1. 以標(biāo)準(zhǔn)的方式對(duì)文件進(jìn)行讀寫

同步訪問文件的方式

同步訪問文件的方式與上邊這種標(biāo)準(zhǔn)的訪問文件的方式比較類似,這兩種方法一個(gè)很關(guān)鍵的區(qū)別就是:同步訪問文件的時(shí)候,寫數(shù)據(jù)的操作是在數(shù)據(jù)完全被寫回磁盤上才算完成的;而標(biāo)準(zhǔn)訪問文件方式的寫數(shù)據(jù)操作是在數(shù)據(jù)被寫到頁高速緩沖存儲(chǔ)器中的時(shí)候就算執(zhí)行完成了。

數(shù)據(jù)同步寫回磁盤 

圖 2. 數(shù)據(jù)同步寫回磁盤

 

內(nèi)存映射方式

在很多操作系統(tǒng)包括 Linux 中,內(nèi)存區(qū)域( memory region )是可以跟一個(gè)普通的文件或者塊設(shè)備文件的某一個(gè)部分關(guān)聯(lián)起來的,若進(jìn)程要訪問內(nèi)存頁中某個(gè)字節(jié)的數(shù)據(jù),操作系統(tǒng)就會(huì)將訪問該內(nèi)存區(qū)域的操作轉(zhuǎn)換為相應(yīng)的訪問文件的某個(gè)字節(jié)的操作。Linux 中提供了系統(tǒng)調(diào)用 mmap() 來實(shí)現(xiàn)這種文件訪問方式。與標(biāo)準(zhǔn)的訪問文件的方式相比,內(nèi)存映射方式可以減少標(biāo)準(zhǔn)訪問文件方式中 read() 系統(tǒng)調(diào)用所帶來的數(shù)據(jù)拷貝操作,即減少數(shù)據(jù)在用戶地址空間和操作系統(tǒng)內(nèi)核地址空間之間的拷貝操作。映射通常適用于較大范圍,對(duì)于相同長(zhǎng)度的數(shù)據(jù)來講,映射所帶來的開銷遠(yuǎn)遠(yuǎn)低于 CPU 拷貝所帶來的開銷。當(dāng)大量數(shù)據(jù)需要傳輸?shù)臅r(shí)候,采用內(nèi)存映射方式去訪問文件會(huì)獲得比較好的效率。

利用 mmap 代替 read 

圖 3. 利用 mmap 代替 read

 

直接 I/O 方式

凡是通過直接 I/O 方式進(jìn)行數(shù)據(jù)傳輸,數(shù)據(jù)均直接在用戶地址空間的緩沖區(qū)和磁盤之間直接進(jìn)行傳輸,完全不需要頁緩存的支持。操作系統(tǒng)層提供的緩存往往會(huì)使應(yīng)用程序在讀寫數(shù)據(jù)的時(shí)候獲得更好的性能,但是對(duì)于某些特殊的應(yīng)用程序,比如說數(shù)據(jù)庫管理系統(tǒng)這類應(yīng)用,他們更傾向于選擇他們自己的緩存機(jī)制,因?yàn)閿?shù)據(jù)庫管理系統(tǒng)往往比操作系統(tǒng)更了解數(shù)據(jù)庫中存放的數(shù)據(jù),數(shù)據(jù)庫管理系統(tǒng)可以提供一種更加有效的緩存機(jī)制來提高數(shù)據(jù)庫中數(shù)據(jù)的存取性能。

數(shù)據(jù)傳輸不經(jīng)過操作系統(tǒng)內(nèi)核緩沖區(qū) 

圖 4. 數(shù)據(jù)傳輸不經(jīng)過操作系統(tǒng)內(nèi)核緩沖區(qū)

 

異步訪問文件的方式

Linux 異步 I/O 是 Linux 2.6 中的一個(gè)標(biāo)準(zhǔn)特性,其本質(zhì)思想就是進(jìn)程發(fā)出數(shù)據(jù)傳輸請(qǐng)求之后,進(jìn)程不會(huì)被阻塞,也不用等待任何操作完成,進(jìn)程可以在數(shù)據(jù)傳輸?shù)臅r(shí)候繼續(xù)執(zhí)行其他的操作。相對(duì)于同步訪問文件的方式來說,異步訪問文件的方式可以提高應(yīng)用程序的效率,并且提高系統(tǒng)資源利用率。直接 I/O 經(jīng)常會(huì)和異步訪問文件的方式結(jié)合在一起使用。

CPU 處理其他任務(wù)和 I/O 操作可以重疊執(zhí)行 

圖 5.CPU 處理其他任務(wù)和 I/O 操作可以重疊執(zhí)行

 #p#

在下邊這一小節(jié)中,我們會(huì)重點(diǎn)介紹 Linux 2.6 內(nèi)核中直接 I/O 的設(shè)計(jì)與實(shí)現(xiàn)。

Linux 2.6 中直接 I/O 的設(shè)計(jì)與實(shí)現(xiàn)

在塊設(shè)備或者網(wǎng)絡(luò)設(shè)備中執(zhí)行直接 I/O 完全不用擔(dān)心實(shí)現(xiàn)直接 I/O 的問題,Linux 2.6 操作系統(tǒng)內(nèi)核中高層代碼已經(jīng)設(shè)置和使用了直接 I/O,驅(qū)動(dòng)程序級(jí)別的代碼甚至不需要知道已經(jīng)執(zhí)行了直接 I/O;但是對(duì)于字符設(shè)備來說,執(zhí)行直接 I/O 是不可行的,Linux 2.6 提供了函數(shù) get_user_pages() 用于實(shí)現(xiàn)直接 I/O。本小節(jié)會(huì)分別對(duì)這兩種情況進(jìn)行介紹。

內(nèi)核為塊設(shè)備執(zhí)行直接 I/O 提供的支持

要在塊設(shè)備中執(zhí)行直接 I/O,進(jìn)程必須在打開文件的時(shí)候設(shè)置對(duì)文件的訪問模式為 O_DIRECT,這樣就等于告訴操作系統(tǒng)進(jìn)程在接下來使用 read() 或者 write() 系統(tǒng)調(diào)用去讀寫文件的時(shí)候使用的是直接 I/O 方式,所傳輸?shù)臄?shù)據(jù)均不經(jīng)過操作系統(tǒng)內(nèi)核緩存空間。使用直接 I/O 讀寫數(shù)據(jù)必須要注意緩沖區(qū)對(duì)齊( buffer alignment )以及緩沖區(qū)的大小的問題,即對(duì)應(yīng) read() 以及 write() 系統(tǒng)調(diào)用的第二個(gè)和第三個(gè)參數(shù)。這里邊說的對(duì)齊指的是文件系統(tǒng)塊大小的對(duì)齊,緩沖區(qū)的大小也必須是該塊大小的整數(shù)倍。

這一節(jié)主要介紹三個(gè)函數(shù):open(),read() 以及 write()。Linux 中訪問文件具有多樣性,所以這三個(gè)函數(shù)對(duì)于處理不同的文件訪問方式定義了不同的處理方法,本文主要介紹其與直接 I/O 方式相關(guān)的函數(shù)與功能.首先,先來看 open() 系統(tǒng)調(diào)用,其函數(shù)原型如下所示:

 

  1. int open(const char *pathname, int oflag, … /*, mode_t mode * / ) ;  
  2. |-------10--------20--------30--------40--------50--------60--------70--------80--------9|  
  3. |-------- XML error:  The previous line is longer than the max of 90 characters ---------|  

以下列出了 Linux 2.6 內(nèi)核定義的系統(tǒng)調(diào)用 open() 所使用的標(biāo)識(shí)符宏定義:

表 1. open() 系統(tǒng)調(diào)用提供的標(biāo)識(shí)符  

標(biāo)識(shí)符名

 標(biāo)識(shí)符描述

O_RDONLY

以只讀的方式打開文件

O_WRONLY

以只寫的方式打開文件

O_RDWR

 

以讀寫的方式打開文件

O_CREAT

若文件不存在,則創(chuàng)建該文件

O_EXCL

以獨(dú)占模式打開文件;若同時(shí)設(shè)置 O_EXCL 和 O_CREATE, 那么若文件已經(jīng)存在,則打開操作會(huì)失敗

O_NOCTTY

若設(shè)置該描述符,則該文件不可以被當(dāng)成終端處理

O_TRUNC 

截?cái)辔募粑募嬖冢瑒t刪除該文件

O_APPEND

若設(shè)置了該描述符,則在寫文件之前,文件指針會(huì)被設(shè)置到文件的底部

O_NONBLOCK

以非阻塞的方式打開文件

O_NELAY

同 O_NELAY,若同時(shí)設(shè)置 O_NELAY 和 O_NONBLOCK,O_NONBLOCK 優(yōu)先起作用

O_SYNC 該描述符會(huì)對(duì)普通文件的寫操作產(chǎn)生影響,若設(shè)置了該描述符,則對(duì)該文件的寫操作會(huì)等到數(shù)據(jù)被寫到磁盤上才算結(jié)束
 FASYNC

 若設(shè)置該描述符,則 I/O 事件通知是通過信號(hào)發(fā)出的

O_DIRECT 該描述符提供對(duì)直接 I/O 的支持
O_LARGEFILE  該描述符提供對(duì)超過 2GB 大文件的支持
O_DIRECTORY 該描述符表明所打開的文件必須是目錄,否則打開操作失敗
O_NOFOLLOW 若設(shè)置該描述符,則不解析路徑名尾部的符號(hào)鏈接

 當(dāng)應(yīng)用程序需要直接訪問文件而不經(jīng)過操作系統(tǒng)頁高速緩沖存儲(chǔ)器的時(shí)候,它打開文件的時(shí)候需要指定 O_DIRECT 標(biāo)識(shí)符。

操作系統(tǒng)內(nèi)核中處理 open() 系統(tǒng)調(diào)用的內(nèi)核函數(shù)是 sys_open(),sys_open() 會(huì)調(diào)用 do_sys_open() 去處理主要的打開操作。它主要做了三件事情:首先, 它調(diào)用 getname() 從進(jìn)程地址空間中讀取文件的路徑名;接著,do_sys_open() 調(diào)用 get_unused_fd() 從進(jìn)程的文件表中找到一個(gè)空閑的文件表指針,相應(yīng)的新文件描述符就存放在本地變量 fd 中;之后,函數(shù) do_filp_open() 會(huì)根據(jù)傳入的參數(shù)去執(zhí)行相應(yīng)的打開操作。清單 1 列出了操作系統(tǒng)內(nèi)核中處理 open() 系統(tǒng)調(diào)用的一個(gè)主要函數(shù)關(guān)系圖。

清單 1. 主要調(diào)用函數(shù)關(guān)系圖

  1. sys_open()  
  2.   |-----do_sys_open()  
  3.   |---------getname()  
  4.   |---------get_unused_fd()  
  5.   |---------do_filp_open()  
  6.   |--------nameidata_to_filp()  
  7.   |----------__dentry_open()  

函數(shù) do_flip_open() 在執(zhí)行的過程中會(huì)調(diào)用函數(shù) nameidata_to_filp(),而 nameidata_to_filp() 最終會(huì)調(diào)用 __dentry_open() 函數(shù),若進(jìn)程指定了 O_DIRECT 標(biāo)識(shí)符,則該函數(shù)會(huì)檢查直接 I./O 操作是否可以作用于該文件。清單 2 列出了 __dentry_open() 函數(shù)中與直接 I/O 操作相關(guān)的代碼。

清單 2. 函數(shù) dentry_open() 中與直接 I/O 相關(guān)的代碼

  1. if (f->f_flags & O_DIRECT) {  
  2.  
  3.          if (!f->f_mapping->a_ops ||  
  4.  
  5.             ((!f->f_mapping->a_ops->direct_IO) &&  
  6.  
  7.             (!f->f_mapping->a_ops->get_xip_page))) {  
  8.  
  9.                   fput(f);  
  10.  
  11.                   f = ERR_PTR(-EINVAL);  
  12.  
  13.           }  
  14.  
  15. }  
  16.  

當(dāng)文件打開時(shí)指定了 O_DIRECT 標(biāo)識(shí)符,那么操作系統(tǒng)就會(huì)知道接下來對(duì)文件的讀或者寫操作都是要使用直接 I/O 方式的。

下邊我們來看一下當(dāng)進(jìn)程通過 read() 系統(tǒng)調(diào)用讀取一個(gè)已經(jīng)設(shè)置了 O_DIRECT 標(biāo)識(shí)符的文件的時(shí)候,系統(tǒng)都做了哪些處理。 函數(shù) read() 的原型如下所示:

ssize_t read(int feledes, void *buff, size_t nbytes) ;

操作系統(tǒng)中處理 read() 函數(shù)的入口函數(shù)是 sys_read(),其主要的調(diào)用函數(shù)關(guān)系圖如下清單 3 所示:

清單 3. 主調(diào)用函數(shù)關(guān)系圖

  1. sys_read()  
  2.     |-----vfs_read()  
  3.           |----generic_file_read()  
  4.                |----generic_file_aio_read()  
  5.                     |--------- generic_file_direct_IO()  
  6.  

函數(shù) sys_read() 從進(jìn)程中獲取文件描述符以及文件當(dāng)前的操作位置后會(huì)調(diào)用 vfs_read() 函數(shù)去執(zhí)行具體的操作過程,而 vfs_read() 函數(shù)最終是調(diào)用了 file 結(jié)構(gòu)中的相關(guān)操作去完成文件的讀操作,即調(diào)用了 generic_file_read() 函數(shù),其代碼如下所示:

清單 4. 函數(shù) generic_file_read()

  1. ssize_t  
  2. generic_file_read(struct file *filp,  
  3. char __user *buf, size_t count, loff_t *ppos)  
  4. {  
  5.        struct iovec local_iov = { .iov_base = buf, .iov_len = count };  
  6.        struct kiocb kiocb;  
  7.        ssize_t ret;  
  8.        init_sync_kiocb(&kiocb, filp);  
  9.        ret = __generic_file_aio_read(&kiocb, &local_iov, 1, ppos);  
  10.        if (-EIOCBQUEUED == ret)  
  11.        ret = wait_on_sync_kiocb(&kiocb);  
  12.        return ret;  

函數(shù) generic_file_read() 初始化了 iovec 以及 kiocb 描述符。描述符 iovec 主要是用于存放兩個(gè)內(nèi)容:用來接收所讀取數(shù)據(jù)的用戶地址空間緩沖區(qū)的地址和緩沖區(qū)的大小;描述符 kiocb 用來跟蹤 I/O 操作的完成狀態(tài)。之后,函數(shù) generic_file_read() 凋用函數(shù) __generic_file_aio_read()。該函數(shù)檢查 iovec 中描述的用戶地址空間緩沖區(qū)是否可用,接著檢查訪問模式,若訪問模式描述符設(shè)置了 O_DIRECT,則執(zhí)行與直接 I/O 相關(guān)的代碼。函數(shù) __generic_file_aio_read() 中與直接 I/O 有關(guān)的代碼如下所示:

清單 5. 函數(shù) __generic_file_aio_read() 中與直接 I/O 有關(guān)的代碼

  1. if (filp->f_flags & O_DIRECT) {  
  2.           loff_t pos = *ppos, size;  
  3.           struct address_space *mapping;  
  4.           struct inode *inode;  
  5.           mapping = filp->f_mapping;  
  6.           inode = mapping->host;   
  7.           retval = 0;  
  8.           if (!count)  
  9.              goto out;  
  10.           size = i_size_read(inode);  
  11.           if (pos < size) {  
  12.              retval = generic_file_direct_IO(READ, iocb,  
  13. iov, pos, nr_segs);  
  14.           if (retval > 0 && !is_sync_kiocb(iocb))  
  15.              retval = -EIOCBQUEUED;  
  16.           if (retval > 0)  
  17.              *ppos = pos + retval;  
  18.           }  
  19. file_accessed(filp);  
  20. goto out;  

上邊的代碼段主要是檢查了文件指針的值,文件的大小以及所請(qǐng)求讀取的字節(jié)數(shù)目等,之后,該函數(shù)調(diào)用 generic_file_direct_io(),并將操作類型 READ,描述符 iocb,描述符 iovec,當(dāng)前文件指針的值以及在描述符 io_vec  中指定的用戶地址空間緩沖區(qū)的個(gè)數(shù)等值作為參數(shù)傳給它。當(dāng) generic_file_direct_io() 函數(shù)執(zhí)行完成,函數(shù) __generic_file_aio_read()會(huì)繼續(xù)執(zhí)行去完成后續(xù)操作:更新文件指針,設(shè)置訪問文件 i 節(jié)點(diǎn)的時(shí)間戳;這些操作全部執(zhí)行完成以后,函數(shù)返回。 函數(shù) generic_file_direct_IO() 會(huì)用到五個(gè)參數(shù),各參數(shù)的含義如下所示:

·rw:操作類型,可以是 READ 或者 WRITE

·iocb:指針,指向 kiocb 描述符

·iov:指針,指向 iovec 描述符數(shù)組

·offset:file 結(jié)構(gòu)偏移量

·nr_segs:iov 數(shù)組中 iovec 的個(gè)數(shù)

函數(shù) generic_file_direct_IO() 代碼如下所示:

清單 6. 函數(shù) generic_file_direct_IO()

  1. static ssize_t  
  2. generic_file_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov,loff_t offset, unsigned long nr_segs)  
  3. {  
  4.          struct file *file = iocb->ki_filp;  
  5.          struct address_space *mapping = file->f_mapping;  
  6.          ssize_t retval;  
  7.          size_t write_len = 0;  
  8.          if (rw == WRITE) {  
  9.                 write_len = iov_length(iov, nr_segs);  
  10.                 if (mapping_mapped(mapping))  
  11.                            unmap_mapping_range(mapping, offset, write_len, 0);  
  12.          }  
  13.          retval = filemap_write_and_wait(mapping);  
  14.          if (retval == 0) {  
  15.                     retval = mapping->a_ops->direct_IO(rw, iocb, iov,offset, nr_segs);  
  16.                     if (rw == WRITE && mapping->nrpages) {  
  17.                             pgoff_t end = (offset + write_len - 1)  
  18.                                         >> PAGE_CACHE_SHIFT;  
  19.                             int err = invalidate_inode_pages2_range(mapping,  
  20.                                 offset >> PAGE_CACHE_SHIFT, end);  
  21.                             if (err)  
  22.                                 retval = err;  
  23.                        }  
  24.  
  25.           }  
  26.           return retval;  

函數(shù) generic_file_direct_IO() 對(duì) WRITE 操作類型進(jìn)行了一些特殊處理,這在下邊介紹 write() 系統(tǒng)調(diào)用的時(shí)候再做說明。除此之外,它主要是調(diào)用了 direct_IO 方法去執(zhí)行直接 I/O 的讀或者寫操作。在進(jìn)行直接  I/O  讀操作之前,先將頁緩存中的相關(guān)臟數(shù)據(jù)刷回到磁盤上去,這樣做可以確保從磁盤上讀到的是***的數(shù)據(jù)。這里的 direct_IO 方法最終會(huì)對(duì)應(yīng)到 __blockdev_direct_IO() 函數(shù)上去。__blockdev_direct_IO() 函數(shù)的代碼如下所示:

清單 7. 函數(shù) __blockdev_direct_IO()

  1. ssize_t  
  2. __blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,struct block_device *bdev, const struct iovec *iov, loff_t offset,unsigned long nr_segs, get_block_t get_block, dio_iodone_t end_io,int dio_lock_type)  
  3. {  
  4.            int seg;  
  5.            size_t size;  
  6.            unsigned long addr;  
  7.            unsigned blkbits = inode->i_blkbits;  
  8.            unsigned bdev_blkbits = 0;  
  9.            unsigned blocksize_mask = (1 << blkbits) - 1;  
  10.            ssize_t retval = -EINVAL;  
  11.            loff_t end = offset;  
  12.            struct dio *dio;  
  13.            int release_i_mutex = 0;  
  14.            int acquire_i_mutex = 0;  
  15.            if (rw & WRITE)  
  16.                   rw = WRITE_SYNC;  
  17.            if (bdev)  
  18.                   bdev_blkbits = blksize_bits(bdev_hardsect_size(bdev));  
  19.            if (offset & blocksize_mask) {  
  20.                       if (bdev)  
  21.                           blkbits = bdev_blkbits;  
  22.                           blocksize_mask = (1 << blkbits) - 1;  
  23.                       if (offset & blocksize_mask)  
  24.                                  goto out;  
  25.  
  26.             }  
  27.            for (seg = 0; seg < nr_segs; seg++) {  
  28.                     addr = (unsigned long)iov[seg].iov_base;  
  29.                     size = iov[seg].iov_len;  
  30.                     end += size;  
  31.                     if ((addr & blocksize_mask) || (size & blocksize_mask)) {  
  32.                                if (bdev)  
  33.                                   blkbits = bdev_blkbits;  
  34.                                   blocksize_mask = (1 << blkbits) - 1;  
  35.                                if ((addr & blocksize_mask) || (size & blocksize_mask))  
  36.                                   goto out;  
  37.  
  38.                                  }  
  39.  
  40.                      }  
  41.             dio = kmalloc(sizeof(*dio), GFP_KERNEL);  
  42.             retval = -ENOMEM;  
  43.             if (!dio)  
  44.             goto out;  
  45.             dio->lock_type = dio_lock_type;  
  46.             if (dio_lock_type != DIO_NO_LOCKING) {  
  47.                    if (rw == READ && end > offset) {  
  48.                           struct address_space *mapping;  
  49.                           mapping = iocb->ki_filp->f_mapping;  
  50.                           if (dio_lock_type != DIO_OWN_LOCKING) {  
  51.                                  mutex_lock(&inode->i_mutex);  
  52.                                  release_i_mutex = 1;  
  53.                   }  
  54.  
  55.                   retval = filemap_write_and_wait_range(mapping, offset,end - 1);  
  56.                   if (retval) {  
  57.                         kfree(dio);  
  58.                         goto out;  
  59.  
  60.                   }  
  61.  
  62.                   if (dio_lock_type == DIO_OWN_LOCKING){        
  63.                   mutex_unlock(&inode->i_mutex);  
  64.                   acquire_i_mutex = 1;  
  65.                   }  
  66.             }  
  67.                   if (dio_lock_type == DIO_LOCKING)  
  68.                          down_read_non_owner(&inode->i_alloc_sem);  
  69.                   }  
  70.                   dio->is_async = !is_sync_kiocb(iocb) && !((rw & WRITE) &&(end > i_size_read(inode)));  
  71.                   retval = direct_io_worker(rw, iocb, inode, iov, offset,nr_segs, blkbits, get_block, end_io, dio);  
  72.                   if (rw == READ && dio_lock_type == DIO_LOCKING)  
  73.                   release_i_mutex = 0;  
  74. out:  
  75.  
  76.             if (release_i_mutex)  
  77.                       mutex_unlock(&inode->i_mutex);  
  78.             else if (acquire_i_mutex)  
  79.                       mutex_lock(&inode->i_mutex);  
  80.             return retval;  

該函數(shù)將要讀或者要寫的數(shù)據(jù)進(jìn)行拆分,并檢查緩沖區(qū)對(duì)齊的情況。本文在前邊介紹 open() 函數(shù)的時(shí)候指出,使用直接 I/O 讀寫數(shù)據(jù)的時(shí)候必須要注意緩沖區(qū)對(duì)齊的問題,從上邊的代碼可以看出,緩沖區(qū)對(duì)齊的檢查是在 __blockdev_direct_IO() 函數(shù)里邊進(jìn)行的。用戶地址空間的緩沖區(qū)可以通過 iov 數(shù)組中的 iovec 描述符確定。直接 I/O 的讀操作或者寫操作都是同步進(jìn)行的,也就是說,函數(shù) __blockdev_direct_IO() 會(huì)一直等到所有的 I/O 操作都結(jié)束才會(huì)返回,因此,一旦應(yīng)用程序 read() 系統(tǒng)調(diào)用返回,應(yīng)用程序就可以訪問用戶地址空間中含有相應(yīng)數(shù)據(jù)的緩沖區(qū)。但是,這種方法在應(yīng)用程序讀操作完成之前不能關(guān)閉應(yīng)用程序,這將會(huì)導(dǎo)致關(guān)閉應(yīng)用程序緩慢。

接下來我們看一下 write() 系統(tǒng)調(diào)用中與直接 I/O 相關(guān)的處理實(shí)現(xiàn)過程。函數(shù) write() 的原型如下所示:

ssize_t write(int filedes, const void * buff, size_t nbytes) ;

操作系統(tǒng)中處理 write() 系統(tǒng)調(diào)用的入口函數(shù)是 sys_write()。其主要的調(diào)用函數(shù)關(guān)系如下所示:

清單 8. 主調(diào)用函數(shù)關(guān)系圖

  1. sys_write()  
  2.           |-----vfs_write()  
  3.                |----generic_file_write()  
  4.                     |----generic_file_aio_read()  
  5.                          |---- __generic_file_write_nolock()  
  6.                                 |-- __generic_file_aio_write_nolock  
  7.                                      |-- generic_file_direct_write()  
  8.                                          |-- generic_file_direct_IO()  
  9.  

函數(shù) sys_write() 幾乎與 sys_read() 執(zhí)行相同的步驟,它從進(jìn)程中獲取文件描述符以及文件當(dāng)前的操作位置后即調(diào)用 vfs_write() 函數(shù)去執(zhí)行具體的操作過程,而 vfs_write() 函數(shù)最終是調(diào)用了 file 結(jié)構(gòu)中的相關(guān)操作完成文件的寫操作,即調(diào)用了 generic_file_write() 函數(shù)。在函數(shù) generic_file_write() 中, 函數(shù) generic_file_write_nolock() 最終調(diào)用 generic_file_aio_write_nolock() 函數(shù)去檢查 O_DIRECT 的設(shè)置,并且調(diào)用  generic_file_direct_write() 函數(shù)去執(zhí)行直接 I/O 寫操作。

函數(shù) generic_file_aio_write_nolock() 中與直接 I/O 相關(guān)的代碼如下所示:

清單 9. 函數(shù) generic_file_aio_write_nolock() 中與直接 I/O 相關(guān)的代碼

  1. if (unlikely(file->f_flags & O_DIRECT)) {  
  2.             written = generic_file_direct_write(iocb, iov,&nr_segs, pos, ppos, count, ocount);  
  3.             if (written < 0 || written == count)  
  4.                        goto out;  
  5.                        pos += written;  
  6.                        count -= written;  

從上邊代碼可以看出, generic_file_aio_write_nolock() 調(diào)用了 generic_file_direct_write() 函數(shù)去執(zhí)行直接 I/O 操作;而在 generic_file_direct_write() 函數(shù)中,跟讀操作過程類似,它最終也是調(diào)用了 generic_file_direct_IO() 函數(shù)去執(zhí)行直接 I/O 寫操作。與直接 I/O 讀操作不同的是,這次需要將操作類型 WRITE 作為參數(shù)傳給函數(shù) generic_file_direct_IO()。

前邊介紹了 generic_file_direct_IO() 的主體 direct_IO 方法:__blockdev_direct_IO()。函數(shù) generic_file_direct_IO() 對(duì) WRITE 操作類型進(jìn)行了一些額外的處理。當(dāng)操作類型是 WRITE 的時(shí)候,若發(fā)現(xiàn)該使用直接 I/O 的文件已經(jīng)與其他一個(gè)或者多個(gè)進(jìn)程存在關(guān)聯(lián)的內(nèi)存映射,那么就調(diào)用 unmap_mapping_range() 函數(shù)去取消建立在該文件上的所有的內(nèi)存映射,并將頁緩存中相關(guān)的所有 dirty 位被置位的臟頁面刷回到磁盤上去。對(duì)于直接  I/O  寫操作來說,這樣做可以保證寫到磁盤上的數(shù)據(jù)是***的,否則,即將用直接  I/O  方式寫入到磁盤上的數(shù)據(jù)很可能會(huì)因?yàn)轫摼彺嬷幸呀?jīng)存在的臟數(shù)據(jù)而失效。在直接  I/O  寫操作完成之后,在頁緩存中相關(guān)的臟數(shù)據(jù)就都已經(jīng)失效了,磁盤與頁緩存中的數(shù)據(jù)內(nèi)容必須保持同步。#p#

如何在字符設(shè)備中執(zhí)行直接 I/O

在字符設(shè)備中執(zhí)行直接 I/O 可能是有害的,只有在確定了設(shè)置緩沖 I/O 的開銷非常巨大的時(shí)候才建議使用直接 I/O。在 Linux 2.6 的內(nèi)核中,實(shí)現(xiàn)直接 I/O 的關(guān)鍵是函數(shù) get_user_pages() 函數(shù)。其函數(shù)原型如下所示:

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);

該函數(shù)的參數(shù)含義如下所示:

·tsk:指向執(zhí)行映射的進(jìn)程的指針;該參數(shù)的主要用途是用來告訴操作系統(tǒng)內(nèi)核,映射頁面所產(chǎn)生的頁錯(cuò)誤由誰來負(fù)責(zé),該參數(shù)幾乎總是 current。

·mm:指向被映射的用戶地址空間的內(nèi)存管理結(jié)構(gòu)的指針,該參數(shù)通常是 current->mm 。

·start: 需要映射的用戶地址空間的地址。

·len:頁內(nèi)緩沖區(qū)的長(zhǎng)度。

·write:如果需要對(duì)所映射的頁面有寫權(quán)限,該參數(shù)的設(shè)置得是非零。

·force:該參數(shù)的設(shè)置通知 get_user_pages() 函數(shù)無需考慮對(duì)指定內(nèi)存頁的保護(hù),直接提供所請(qǐng)求的讀或者寫訪問。

·page:輸出參數(shù)。調(diào)用成功后,該參數(shù)中包含一個(gè)描述用戶空間頁面的 page 結(jié)構(gòu)的指針列表。

·vmas:輸出參數(shù)。若該參數(shù)非空,則該參數(shù)包含一個(gè)指向 vm_area_struct 結(jié)構(gòu)的指針,該 vm_area_struct 結(jié)構(gòu)包含了每一個(gè)所映射的頁面。

在使用 get_user_pages() 函數(shù)的時(shí)候,往往還需要配合使用以下這些函數(shù):

  1. void down_read(struct rw_semaphore *sem);  
  2. void up_read(struct rw_semaphore *sem);  
  3. void SetPageDirty(struct page *page);  
  4. void page_cache_release(struct page *page); 

首先,在使用 get_user_pages() 函數(shù)之前,需要先調(diào)用 down_read() 函數(shù)將 mmap 為獲得用戶地址空間的讀取者 / 寫入者信號(hào)量設(shè)置為讀模式;在調(diào)用完 get_user_pages() 函數(shù)之后,再調(diào)用配對(duì)函數(shù) up_read() 釋放信號(hào)量 sem。若 get_user_pages() 調(diào)用失敗,則返回錯(cuò)誤代碼;若調(diào)用成功,則返回實(shí)際被映射的頁面數(shù),該數(shù)目有可能比請(qǐng)求的數(shù)量少。調(diào)用成功后所映射的用戶頁面被鎖在內(nèi)存中,調(diào)用者可以通過 page 結(jié)構(gòu)的指針去訪問這些用戶頁面。

直接 I/O 的調(diào)用者必須進(jìn)行善后工作,一旦直接 I/O 操作完成,用戶內(nèi)存頁面必須從頁緩存中釋放。在用戶內(nèi)存頁被釋放之前,如果這些頁面中的內(nèi)容改變了,那么調(diào)用者必須要通知操作系統(tǒng)內(nèi)核,否則虛擬存儲(chǔ)子系統(tǒng)會(huì)認(rèn)為這些頁面是干凈的,從而導(dǎo)致這些數(shù)據(jù)被修改了的頁面在被釋放之前無法被寫回到***存儲(chǔ)中去。因此,如果改變了頁中的數(shù)據(jù),那么就必須使用 SetPageDirty() 函數(shù)標(biāo)記出每個(gè)被改變的頁。對(duì)于 Linux 2.6.18.1,該宏定義在 /include/linux/page_flags.h 中。執(zhí)行該操作的代碼一般需要先檢查頁,以確保該頁不在內(nèi)存映射的保留區(qū)域內(nèi),因?yàn)檫@個(gè)區(qū)的頁是不會(huì)被交換出去的,其代碼如下所示:

if (!PageReserved(page)) 
SetPageDirty(page);

但是,由于用戶空間所映射的頁面通常不會(huì)被標(biāo)記為保留,所以上述代碼中的檢查并不是嚴(yán)格要求的。

最終,在直接 I/O 操作完成之后,不管頁面是否被改變,它們都必須從頁緩存中釋放,否則那些頁面永遠(yuǎn)都會(huì)存在在那里。函數(shù) page_cache_release() 就是用于釋放這些頁的。頁面被釋放之后,調(diào)用者就不能再次訪問它們。

關(guān)于如何在字符設(shè)備驅(qū)動(dòng)程序中加入對(duì)直接 I/O 的支持,Linux 2.6.18.1 源代碼中 /drivers/scsi/st.c 給出了一個(gè)完整的例子。其中,函數(shù) sgl_map_user_pages()和 sgl_map_user_pages()幾乎涵蓋了本節(jié)中介紹的所有內(nèi)容。

直接 I/O 技術(shù)的特點(diǎn)

直接 I/O 的優(yōu)點(diǎn)

直接 I/O 最主要的優(yōu)點(diǎn)就是通過減少操作系統(tǒng)內(nèi)核緩沖區(qū)和應(yīng)用程序地址空間的數(shù)據(jù)拷貝次數(shù),降低了對(duì)文件讀取和寫入時(shí)所帶來的 CPU 的使用以及內(nèi)存帶寬的占用。這對(duì)于某些特殊的應(yīng)用程序,比如自緩存應(yīng)用程序來說,不失為一種好的選擇。如果要傳輸?shù)臄?shù)據(jù)量很大,使用直接 I/O 的方式進(jìn)行數(shù)據(jù)傳輸,而不需要操作系統(tǒng)內(nèi)核地址空間拷貝數(shù)據(jù)操作的參與,這將會(huì)大大提高性能。

直接 I/O 潛在可能存在的問題

直接 I/O 并不一定總能提供令人滿意的性能上的飛躍。設(shè)置直接 I/O 的開銷非常大,而直接 I/O 又不能提供緩存 I/O 的優(yōu)勢(shì)。緩存 I/O 的讀操作可以從高速緩沖存儲(chǔ)器中獲取數(shù)據(jù),而直接 I/O 的讀數(shù)據(jù)操作會(huì)造成磁盤的同步讀,這會(huì)帶來性能上的差異 , 并且導(dǎo)致進(jìn)程需要較長(zhǎng)的時(shí)間才能執(zhí)行完;對(duì)于寫數(shù)據(jù)操作來說,使用直接 I/O 需要 write() 系統(tǒng)調(diào)用同步執(zhí)行,否則應(yīng)用程序?qū)?huì)不知道什么時(shí)候才能夠再次使用它的 I/O 緩沖區(qū)。與直接 I/O 讀操作類似的是,直接 I/O 寫操作也會(huì)導(dǎo)致應(yīng)用程序關(guān)閉緩慢。所以,應(yīng)用程序使用直接 I/O 進(jìn)行數(shù)據(jù)傳輸?shù)臅r(shí)候通常會(huì)和使用異步 I/O 結(jié)合使用。

總結(jié)

Linux 中的直接 I/O 訪問文件方式可以減少 CPU 的使用率以及內(nèi)存帶寬的占用,但是直接 I/O 有時(shí)候也會(huì)對(duì)性能產(chǎn)生負(fù)面影響。所以在使用直接 I/O 之前一定要對(duì)應(yīng)用程序有一個(gè)很清醒的認(rèn)識(shí),只有在確定了設(shè)置緩沖 I/O 的開銷非常巨大的情況下,才考慮使用直接 I/O。直接 I/O 經(jīng)常需要跟異步 I/O 結(jié)合起來使用,本文對(duì)異步 I/O 沒有作詳細(xì)介紹,有興趣的讀者可以參看 Linux 2.6 中相關(guān)的文檔介紹。

原文:http://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html?ca=drs

【編輯推薦】

  1. Unix標(biāo)準(zhǔn)輸入/輸出 (I/O) 流知識(shí)講解
  2. Linux Kernel IOPERM系統(tǒng)調(diào)用I/O端口訪問漏洞
  3. 技巧:部署I/O虛擬化技術(shù)的方法和時(shí)機(jī)
責(zé)任編輯:黃丹 來源: IBMDW
相關(guān)推薦

2019-06-14 09:34:59

Linux 系統(tǒng) 數(shù)據(jù)

2017-01-19 19:14:20

Linux重定向命令

2021-02-22 17:06:58

Linux改動(dòng)代碼

2020-06-03 17:30:42

LinuxIO

2017-03-25 21:33:33

Linux調(diào)度器

2022-04-23 16:30:22

Linux磁盤性能

2025-11-03 01:00:00

2023-05-08 00:06:45

Go語言機(jī)制

2020-11-09 14:30:28

Linux多線程數(shù)據(jù)

2025-06-13 01:22:00

2019-09-12 08:58:26

LinuxUNIX操作系統(tǒng)

2017-03-01 12:36:15

Linux驅(qū)動(dòng)技術(shù)內(nèi)存

2018-11-05 11:20:54

緩沖IO

2019-02-25 08:40:28

Linux磁盤IO

2010-06-25 09:47:29

Linux系統(tǒng)監(jiān)控

2017-01-19 19:24:29

Linux重定向

2022-07-28 11:09:44

Linux優(yōu)化IO

2020-12-01 07:08:23

Linux網(wǎng)絡(luò)I

2014-07-28 16:47:41

linux性能

2020-06-10 08:28:51

Kata容器I
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

国产麻豆日韩| 欧美区一区二区三区| 99视频日韩| 天堂网一区二区三区| 首页亚洲中字| 色婷婷久久一区二区三区麻豆| 日韩高清av| 91麻豆成人精品国产| 欧美网站在线| 亚洲欧美日韩天堂一区二区| 成人日韩在线视频| 国产亚洲成人精品| 婷婷综合福利| 欧美一区二区三区白人| 日韩人妻精品无码一区二区三区| 第九色区av在线| 国产在线不卡视频| 91成人天堂久久成人| av资源在线免费观看| 日韩影院在线| 亚洲欧美综合网| 国产一区二区自拍| 91丨九色丨丰满| 国产伦理一区| 美女性感视频久久久| 国精品无码人妻一区二区三区| 亚洲毛片在线免费| 欧美视频在线视频| www.一区二区.com| 色影视在线观看| 三级在线观看一区二区| 欧美日本国产在线| 蜜桃av免费在线观看| 欧美性生活一级片| 日韩欧美精品在线| 黄色手机在线视频| 在线人成日本视频| 亚洲成av人在线观看| 精品国产一区二区三区在线| 国产女人在线观看| 久久久久高清精品| 精品亚洲欧美日韩| 黄色福利在线观看| 国产麻豆精品theporn| 国产精品亚洲综合天堂夜夜| 欧美精品韩国精品| 一本久道久久综合婷婷鲸鱼| 欧美激情国产高清| 黄色一级视频免费| 粉嫩的18在线观看极品精品| 欧美三级韩国三级日本三斤| 久久综合久久色| 蜜芽在线免费观看| 国产精品久久久久久久久图文区| 欧美日韩一区二区视频在线观看| 深夜福利在线看| 国产一区二区三区电影在线观看| 亚洲精品一区二区精华| 亚洲美女高潮久久久| 亚洲va欧美va人人爽成人影院| 在线综合亚洲欧美在线视频| 国产亚洲视频一区| www.91精品| 日韩欧美国产1| 久久精品无码专区| 久久97久久97精品免视看秋霞| 亚洲精品一区在线观看| 理论片大全免费理伦片| 欧美电影完整版在线观看| 亚洲大胆人体视频| 丰满少妇一区二区三区| 精品中文一区| 在线观看国产精品91| 日日噜噜夜夜狠狠| 久久日本片精品aaaaa国产| 亚洲免费视频中文字幕| 男女h黄动漫啪啪无遮挡软件| 国产黄a三级三级三级av在线看 | 欧美综合一区二区三区| 看欧美ab黄色大片视频免费| 久久麻豆视频| 精品免费99久久| 三级视频网站在线观看| 久草成人在线| 久久精品亚洲精品| 亚洲一区二区91| 久久激情视频| 成人欧美在线观看| 日本少妇bbwbbw精品| 国产深夜精品| 国产精品视频成人| 国产99视频在线| 91视频免费播放| 一本久道久久综合狠狠爱亚洲精品| 精产国品自在线www| 亚洲一区二区三区四区在线免费观看 | 国产精品久久久久久免费观看 | 久久久久久久久伊人| 亚洲精品一区二区三| 91在线中文| 一本到三区不卡视频| 日日干日日操日日射| 九九热播视频在线精品6| 中文字幕国产亚洲| 好吊操这里只有精品| 麻豆高清免费国产一区| 国产一区二区在线网站| 国产网站在线免费观看| 色综合中文字幕| 日韩精品xxx| 精品理论电影在线| 国语对白做受69| 国产精品国产精品88| 亚洲精选久久| 成人高h视频在线| 男人天堂亚洲二区| 一区二区成人在线| www.夜夜爽| 亚洲国产合集| 久久久久久97| 免费观看成人高| 九九九国产视频| 日本麻豆一区二区三区视频| 久久久久久美女| 中文字幕精品一区二区精| av在线不卡网| 欧美狂野激情性xxxx在线观| 亚洲欧洲专区| 在线不卡国产精品| youjizz在线视频| 99视频一区二区| 国产在线视频在线| 国产一区二区视频在线看| 中文精品99久久国产香蕉| 天堂网免费视频| 91免费版在线看| 国产一线二线三线女| 精品国产亚洲一区二区三区大结局| 亚洲图片制服诱惑| 手机在线看片1024| 91色九色蝌蚪| 国产超级av在线| 奇米777国产一区国产二区| 久久久久久亚洲精品中文字幕| hs视频在线观看| 亚洲色图欧美在线| 欧美激情第一区| 91欧美大片| 成人福利视频网| 伦xxxx在线| 69av一区二区三区| 亚洲综合网在线| 国产一区二区三区国产| 看一级黄色录像| 久久免费福利| 欧美高清在线观看| 空姐吹箫视频大全| 亚瑟在线精品视频| 亚洲av网址在线| 久久免费高清| 五码日韩精品一区二区三区视频| 巨胸喷奶水www久久久免费动漫| 正在播放一区二区| avtt天堂在线| 丁香激情综合五月| 日韩欧美视频网站| 精品美女在线视频| 成人激情春色网| 天堂av中文在线| 亚洲国产成人精品女人久久久| 日韩三级免费看| 国产亚洲欧美在线| 中文字幕色网站| 伊人影院久久| 日本不卡在线播放| 国产高清日韩| 91精品国产91久久久久久吃药| 日本国产在线| 欧美日韩综合在线免费观看| 少妇人妻丰满做爰xxx| 成人午夜av影视| 能在线观看的av网站| 偷偷www综合久久久久久久| 高清欧美一区二区三区| 深夜福利视频在线免费观看| 欧美日韩中文另类| 久久久久香蕉视频| 久久精品男人的天堂| 欧美成人乱码一二三四区免费| 欧美极品一区二区三区| 久久精品国产精品青草色艺| 欧美视频在线视频精品| 欧美激情在线有限公司| 国产高清自拍视频在线观看| 欧美一区二区女人| 人妻丰满熟妇av无码区| 亚洲毛片av在线| www.色天使| 国产精品一区二区视频| 116极品美女午夜一级| 久久久久久久久久久妇女| 国产精品成人va在线观看| 日韩免费网站| 亚洲国产欧美一区| 亚洲一卡二卡在线观看| 欧美国产日本视频| 日本精品一二三区| 久久99国内精品| 波多野结衣家庭教师在线| 天天插综合网| 日本最新一区二区三区视频观看| 深夜激情久久| 欧美中文字幕在线视频| 91亚洲天堂| 深夜福利亚洲导航| 人妻丰满熟妇av无码区| 一区二区三区不卡视频在线观看| 国产在线观看h| 成人精品视频.| 中文字幕在线视频精品| 久久在线91| 无码人妻丰满熟妇区96| 欧美久久一区| 国产精品夜夜夜爽张柏芝| 免费成人av| 久久精品人人做人人爽电影| 亚洲国产欧美国产第一区| 国产欧美日韩中文字幕在线| 欧美日韩电影免费看| 97视频网站入口| 四虎电影院在线观看| 欧美一二区视频| 一级做a爰片久久毛片16| 91激情在线视频| 亚洲免费在线视频观看| 亚瑟在线精品视频| 国产精品99无码一区二区| 一区二区三区在线观看动漫| 影音先锋男人资源在线观看| 国产欧美一区视频| 久久午夜福利电影| 国产亚洲欧洲一区高清在线观看| 搡老熟女老女人一区二区| fc2成人免费人成在线观看播放 | 国产精品一区二区不卡| 午夜不卡福利视频| 激情欧美一区二区| 九九九九九伊人| 国产一二三精品| 樱花草www在线| 国产一区二区三区香蕉| 欧美高清精品一区二区| 国产精品主播直播| 午夜性福利视频| 成人激情午夜影院| 91黄色免费视频| 91美女精品福利| 最近中文字幕在线mv视频在线| 久久久夜色精品亚洲| 韩国三级hd中文字幕| 国产欧美va欧美不卡在线 | 亚洲精品国产精品乱码不99 | 高清av在线| 日韩一区av在线| 东京干手机福利视频| 精品欧美一区二区在线观看| 蜜桃av鲁一鲁一鲁一鲁俄罗斯的 | 超鹏97在线| 欧美黑人国产人伦爽爽爽| aaa在线播放视频| 中文字幕亚洲无线码在线一区| a天堂中文在线| 亚洲国产精品成人av| 日本一区二区三区在线观看视频| 亚洲色图15p| 麻豆av免费在线观看| 欧美精品福利在线| 大胆人体一区二区| 国产专区精品视频| 国产欧美一区二区三区米奇| 欧美一区1区三区3区公司 | 2019精品视频| 日韩精品影片| 亚洲最大激情中文字幕| 琪琪久久久久日韩精品| 亚洲国产欧美不卡在线观看| 亚洲精品国产首次亮相| 国产黄页在线观看| 久久99深爱久久99精品| 国产综合内射日韩久| 国产亚洲成av人在线观看导航| 亚洲一二三四五六区| 午夜精品123| 91丨九色丨丰满| 日韩久久精品成人| av网站大全在线| 日韩av电影在线播放| 欧州一区二区三区| 欧美在线激情| 国产精品激情| 国产精品jizz在线观看老狼| 亚洲高清在线| av免费一区二区| 97精品国产露脸对白| 日本黄色免费片| 日韩欧美aaa| 国产高清在线观看视频| 国产亚洲一区精品| 17videosex性欧美| 成人日韩av在线| 深爱激情综合网| 国产一级爱c视频| 九色porny丨国产精品| 草草影院第一页| 亚洲成人久久影院| 99热这里只有精品99| 伊人久久精品视频| 在线看片国产福利你懂的| aaa级精品久久久国产片| 日韩美女一区二区三区在线观看| 乱妇乱女熟妇熟女网站| 成人自拍视频在线观看| 我要看一级黄色录像| 色老综合老女人久久久| 午夜视频免费看| 久久久久亚洲精品国产| 久久视频社区| 国产精品无码乱伦| 蜜臀国产一区二区三区在线播放| 免费看黄色aaaaaa 片| 午夜成人在线视频| 亚洲精品.www| 欧美成aaa人片免费看| 亚洲国产精选| 亚洲免费视频一区| 奇米精品一区二区三区四区| 青青草视频成人| 精品久久久久久久久久久久| 后入内射欧美99二区视频| 欧美日韩aaaa| jizz国产精品| bt天堂新版中文在线地址| 国产精品综合二区| 欧美成人综合色| 日韩一区二区在线看| av香蕉成人| 成人免费看片网站| 亚洲私人影院| 在线观看免费视频黄| 亚洲国产美女搞黄色| 欧美性受xxxx狂喷水| 97国产精品久久| 欧美一级全黄| 久久久久久久久久久久久国产精品| 日韩国产欧美视频| 亚洲а∨天堂久久精品2021| 欧美性猛交xxxx黑人交| 天堂а√在线官网| 91精品入口蜜桃| 极品日韩av| 亚洲熟妇无码av| 欧美在线一区二区三区| 午夜视频在线观看网站| 老司机精品视频网站| 免费黄频在线观看| 亚洲另类在线一区| 欧美一级特黄aaaaaa大片在线观看| 国产做受69高潮| 欧美美女在线观看| 亚洲xxx在线观看| 亚洲综合一区二区三区| 三级在线观看网站| 日韩免费黄色av| 99re久久最新地址获取| 人妻体体内射精一区二区| 欧美日韩国产精品一区| ,一级淫片a看免费| 操人视频在线观看欧美| 巨胸喷奶水www久久久免费动漫| 亚洲精品在线免费看| 国产精品综合在线视频| 国产九色在线播放九色| 色偷偷av一区二区三区乱| 777久久精品| 中文字幕永久视频| 亚洲精品乱码久久久久久黑人 | 亚洲人成电影网| 天堂综合在线播放| 久久久久99精品成人片| 国产无一区二区| 成人av手机在线| 国产91色在线|免| 欧美精品aa| 久久久视频6r| 精品日韩在线观看| 日韩一级二级| 国产无限制自拍| 一色屋精品亚洲香蕉网站| 熟妇高潮一区二区高潮| 成人免费xxxxx在线观看|