<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux 直接I/O 原理與實(shí)現(xiàn)

          共 6928字,需瀏覽 14分鐘

           ·

          2020-11-21 23:20

          緩存I/O

          一般來說,當(dāng)調(diào)用?open()?系統(tǒng)調(diào)用打開文件時(shí),如果不指定?O_DIRECT?標(biāo)志,那么就是使用緩存I/O來對文件進(jìn)行讀寫操作。我們先來看看?open()?系統(tǒng)調(diào)用的定義:

          int open(const char *pathname, int flags, ... /*, mode_t mode */ );

          下面說明一下各個(gè)參數(shù)的作用:

          • pathname:指定要打開的文件路徑。

          • flags:指定打開文件的標(biāo)志。

          • mode:可選,指定打開文件的權(quán)限。

          其中?flags?參數(shù)可選值如下表:

          標(biāo)志說明
          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)辔募?,若文件存在,則刪除該文件
          O_APPEND若設(shè)置了該描述符,則在寫文件之前,文件指針會(huì)被設(shè)置到文件的底部
          O_NONBLOCK以非阻塞的方式打開文件
          O_NELAY同 O_NELAY,若同時(shí)設(shè)置 O_NELAY 和 O_NONBLOCK,O_NONBLOCK 優(yōu)先起作用
          FASYNC若設(shè)置該描述符,則 I/O 事件通知是通過信號發(fā)出的
          O_SYNC該描述符會(huì)對普通文件的寫操作產(chǎn)生影響,若設(shè)置了該描述符,則對該文件的寫操作會(huì)等到數(shù)據(jù)被寫到磁盤上才算結(jié)束
          O_DIRECT該描述符提供對直接 I/O 的支持
          O_LARGEFILE該描述符提供對超過 2GB 大文件的支持
          O_DIRECTORY該描述符表明所打開的文件必須是目錄,否則打開操作失敗
          O_NOFOLLOW若設(shè)置該描述符,則不解析路徑名尾部的符號鏈接

          flags?參數(shù)用于指定打開文件的標(biāo)志,比如指定?O_RDONLY,那么就只能以只讀方式對文件進(jìn)行讀寫。這些標(biāo)志都能通過?位或 (|)?操作來設(shè)置多個(gè)標(biāo)志如:

          open("/path/to/file", O_RDONLY|O_APPEND|O_DIRECT);

          但?O_RDONLY、O_WRONLY?和?O_RDWR?這三個(gè)標(biāo)志是互斥的,也就是說這三個(gè)標(biāo)志不能同時(shí)設(shè)置,只能設(shè)置其中一個(gè)。

          當(dāng)打開文件不指定?O_DIRECT?標(biāo)志時(shí),那么就默認(rèn)使用?緩存I/O?方式打開。我們可以通過下圖來了解?緩存I/O?處于文件系統(tǒng)的什么位置:

          上圖中紅色框部分就是?緩存I/O?所在位置,位于?虛擬文件系統(tǒng)?與?真實(shí)文件系統(tǒng)?中間。

          也就是說,當(dāng)虛擬文件系統(tǒng)讀文件時(shí),首先從緩存中查找要讀取的文件內(nèi)容是否存在緩存中,如果存在就直接從緩存中讀取。對文件進(jìn)行寫操作時(shí)也一樣,首先寫入到緩存中,然后由操作系統(tǒng)同步到塊設(shè)備(如磁盤)中。

          緩存I/O 的優(yōu)缺點(diǎn)

          緩存I/O?的引入是為了減少對塊設(shè)備的 I/O 操作,但是由于讀寫操作都先要經(jīng)過緩存,然后再從緩存復(fù)制到用戶空間,所以多了一次內(nèi)存復(fù)制操作。如下圖所示:

          所以?緩存I/O?的優(yōu)點(diǎn)是減少對塊設(shè)備的 I/O 操作,而缺點(diǎn)就是需要多一次的內(nèi)存復(fù)制。另外,有些應(yīng)用程序需要自己管理 I/O 緩存的(如數(shù)據(jù)庫系統(tǒng)),那么就需要使用?直接I/O?了。

          直接I/O

          直接I/O?就是對用戶進(jìn)行的 I/O 操作直接與塊設(shè)備進(jìn)行交互,而不進(jìn)行緩存。

          • 直接I/O?的優(yōu)點(diǎn)是:由于不對 I/O 數(shù)據(jù)塊進(jìn)行緩存,所以可以直接跟用戶數(shù)據(jù)進(jìn)行交互,減少一次內(nèi)存的拷貝。

          • 直接I/O?的缺點(diǎn)是:每次 I/O 操作都直接與塊設(shè)備進(jìn)行交互,增加了對塊設(shè)備的讀寫操作。

          但由于應(yīng)用程序可以自行對數(shù)據(jù)塊進(jìn)行緩存,所以更加靈活,適合一些對 I/O 操作比較敏感的應(yīng)用,如數(shù)據(jù)庫系統(tǒng)。

          直接I/O 實(shí)現(xiàn)

          當(dāng)調(diào)用?open()?系統(tǒng)調(diào)用時(shí),在?flags?參數(shù)指定?O_DIRECT?標(biāo)志即可使用?直接I/O。我們從?虛擬文件系統(tǒng)?開始跟蹤 Linux 對?直接I/O?的處理過程。

          當(dāng)調(diào)用?open()?系統(tǒng)調(diào)用時(shí),會(huì)觸發(fā)調(diào)用?sys_open()?系統(tǒng)調(diào)用,我們先來看看?sys_open()?函數(shù)的實(shí)現(xiàn):

          asmlinkage long sys_open(const char *filename, int flags, int mode){    char *tmp;    int fd, error;    ...    tmp = getname(filename); // 把文件名從用戶空間拷貝到內(nèi)核空間    fd = PTR_ERR(tmp);    if (!IS_ERR(tmp)) {        fd = get_unused_fd(); // 申請一個(gè)還沒有使用的文件描述符        if (fd >= 0) {            // 根據(jù)文件路徑打開文件, 并獲取文件對象            struct file *f = filp_open(tmp, flags, mode);            error = PTR_ERR(f);            if (IS_ERR(f))                goto out_error;            fd_install(fd, f); // 把文件對象與文件描述符關(guān)聯(lián)起來        }out:        putname(tmp);    }    return fd;    ...}

          打開文件的整個(gè)流程比較復(fù)雜,但對我們分析?直接I/O?并沒有太大關(guān)系,之前在虛擬文件系統(tǒng)一章已經(jīng)分析過,這里就不再重復(fù)了,可以參考之前的文章:虛擬文件系統(tǒng)

          我們主要關(guān)注的是,sys_open()?函數(shù)最后會(huì)調(diào)用?dentry_open()?把?flags?參數(shù)保存到文件對象的?f_flags?字段中,調(diào)用鏈:sys_open() -> filp_open() -> dentry_open()

          struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags){    struct file *f;    ...    f = get_empty_filp();    f->f_flags = flags;    ...}

          也就是說,sys_open()?函數(shù)會(huì)打開文件,然后把?flags?參數(shù)保存到文件對象的?f_flgas?字段中。接下來,我們分析一下讀文件操作時(shí),是怎么對?直接I/O?進(jìn)行處理的。讀文件操作使用?read()?系統(tǒng)調(diào)用,而?read()?最終會(huì)調(diào)用內(nèi)核的?sys_read()?函數(shù),代碼如下:

          asmlinkage ssize_t sys_read(unsigned int fd, char *buf, size_t count){    ssize_t ret;    struct file *file;
          file = fget(fd); if (file) { ... if (!ret) { ssize_t (*read)(struct file *, char *, size_t, loff_t *); ret = -EINVAL; // ext2文件系統(tǒng)對應(yīng)的是: generic_file_read() 函數(shù) if (file->f_op && (read = file->f_op->read) != NULL) ret = read(file, buf, count, &file->f_pos); } ... } return ret;}

          由于?sys_read()?函數(shù)屬于虛擬文件系統(tǒng)范疇,所以其最終會(huì)調(diào)用真實(shí)文件系統(tǒng)的?file->f_op->read()?函數(shù),ext2文件系統(tǒng)?對應(yīng)的是?generic_file_read()?函數(shù),我們來分析下?generic_file_read()?函數(shù):

          ssize_t generic_file_read(struct file *filp, char * buf, size_t count, loff_t *ppos){    ssize_t retval;    ...    if (filp->f_flags & O_DIRECT) // 如果標(biāo)記了使用直接IO        goto o_direct;    ... o_direct:    {        loff_t pos = *ppos, size;        struct address_space *mapping = filp->f_dentry->d_inode->i_mapping;        struct inode *inode = mapping->host;        ...        size = inode->i_size;        if (pos < size) {            if (pos + count > size)                count = size - pos;            retval = generic_file_direct_IO(READ, filp, buf, count, pos);            if (retval > 0)                *ppos = pos + retval;        }        UPDATE_ATIME(filp->f_dentry->d_inode);        goto out;    }}

          從上面代碼可以看出,如果在調(diào)用?open()?時(shí)指定了?O_DIRECT?標(biāo)志,那么?generic_file_read()?函數(shù)就會(huì)調(diào)用?generic_file_direct_IO()?函數(shù)對 I/O 操作進(jìn)行處理。由于?generic_file_direct_IO()?函數(shù)的實(shí)現(xiàn)曲折迂回,所以下面主要分析重要部分:

          static ssize_t generic_file_direct_IO(int rw, struct file *filp, char *buf, size_t count, loff_t offset){    ...    while (count > 0) {        iosize = count;        if (iosize > chunk_size)            iosize = chunk_size;
          // 為用戶虛擬內(nèi)存空間申請物理內(nèi)存頁 retval = map_user_kiobuf(rw, iobuf, (unsigned long)buf, iosize); if (retval) break;
          // ext2 文件系統(tǒng)對應(yīng) ext2_direct_IO() 函數(shù), // 而 ext2_direct_IO() 函數(shù)直接調(diào)用了 generic_direct_IO() 函數(shù) retval = mapping->a_ops->direct_IO(rw, inode, iobuf, (offset+progress) >> blocksize_bits, blocksize); ... } ...}

          generic_file_direct_IO()?函數(shù)主要的處理有兩部分:

          • 調(diào)用?map_user_kiobuf()?函數(shù)為用戶虛擬內(nèi)存空間申請物理內(nèi)存頁。

          • 調(diào)用真實(shí)文件系統(tǒng)的?direct_IO()?接口對?直接I/O?進(jìn)行處理。

          map_user_kiobuf()?函數(shù)屬于內(nèi)存管理部分,可以參考之前的?內(nèi)存管理?相關(guān)的文章進(jìn)行分析,這里就不重復(fù)了。

          generic_file_direct_IO()?函數(shù)最終會(huì)調(diào)用真實(shí)文件系統(tǒng)的?direct_IO()?接口,對于?ext2文件系統(tǒng),direct_IO()?接口對應(yīng)的是?ext2_direct_IO()?函數(shù),而?ext2_direct_IO()?函數(shù)只是簡單的封裝了?generic_direct_IO()?函數(shù),所以我們來分析下?generic_direct_IO()?函數(shù)的實(shí)現(xiàn):

          int generic_direct_IO(int rw, struct inode *inode, struct kiobuf *iobuf,        unsigned long blocknr, int blocksize, get_block_t *get_block){    int i, nr_blocks, retval;    unsigned long *blocks = iobuf->blocks;
          nr_blocks = iobuf->length / blocksize; // 獲取要讀取的數(shù)據(jù)塊號列表 for (i = 0; i < nr_blocks; i++, blocknr++) { struct buffer_head bh;
          bh.b_state = 0; bh.b_dev = inode->i_dev; bh.b_size = blocksize;
          retval = get_block(inode, blocknr, &bh, rw == READ ? 0 : 1); ... blocks[i] = bh.b_blocknr; }
          // 開始進(jìn)行I/O操作 retval = brw_kiovec(rw, 1, &iobuf, inode->i_dev, iobuf->blocks, blocksize);
          out: return retval;}

          generic_direct_IO()?函數(shù)的邏輯也比較簡單,首先調(diào)用?get_block()?獲取要讀取的數(shù)據(jù)塊號列表,然后調(diào)用?brw_kiovec()?函數(shù)進(jìn)行 I/O 操作。所以?brw_kiovec()?函數(shù)才是 I/O 操作的最終觸發(fā)點(diǎn)。我們繼續(xù)分析:

          int brw_kiovec(int rw, int nr, struct kiobuf *iovec[],           kdev_t dev, unsigned long b[], int size){    ...    for (i = 0; i < nr; i++) {        ...        for (pageind = 0; pageind < iobuf->nr_pages; pageind++) {            map  = iobuf->maplist[pageind];            ...            while (length > 0) {                blocknr = b[bufind++];                ...                tmp = bhs[bhind++];
          tmp->b_size = size; set_bh_page(tmp, map, offset); // 設(shè)置保存I/O操作后的數(shù)據(jù)的內(nèi)存地址 (用戶空間的內(nèi)存) tmp->b_this_page = tmp;
          init_buffer(tmp, end_buffer_io_kiobuf, iobuf); // 設(shè)置完成I/O后的收尾工作回調(diào)函數(shù)為: end_buffer_io_kiobuf() tmp->b_dev = dev; tmp->b_blocknr = blocknr; tmp->b_state = (1 << BH_Mapped) | (1 << BH_Lock) | (1 << BH_Req); ... submit_bh(rw, tmp); // 提交 I/O 操作 (通用塊I/O層)
          if (bhind >= KIO_MAX_SECTORS) { kiobuf_wait_for_io(iobuf); err = wait_kio(rw, bhind, bhs, size); ... }
          skip_block: length -= size; offset += size;
          if (offset >= PAGE_SIZE) { offset = 0; break; } } /* End of block loop */ } /* End of page loop */ } /* End of iovec loop */ ... return err;}

          brw_kiovec()?函數(shù)主要完成 3 個(gè)工作:

          • 設(shè)置用于保存 I/O 操作后的數(shù)據(jù)的內(nèi)存地址 (用戶申請的內(nèi)存)。

          • 設(shè)置 I/O 操作完成后的收尾回調(diào)函數(shù)為: end_buffer_io_kiobuf()。

          • 提交 I/O 操作到通用塊層。

          可以看出,對于 I/O 操作后的數(shù)據(jù)會(huì)直接保存到用戶空間的內(nèi)存,而沒有通過內(nèi)核緩存作為中轉(zhuǎn),從而達(dá)到?直接I/O?的目的。


          瀏覽 54
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91丨九色丨蝌蚪丨对白 | 一级福利在线播放 | 国语操逼 | 哪里可以免费看A片 | 国产真实露脸乱子伦对白高清视频 |