<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Linux Kernel文件系統寫I/O流程代碼分析(一)

      Linux Kernel文件系統寫I/O流程代碼分析(一)

      Linux VFS機制簡析(二)這篇博客上介紹了struct address_space_operations里底層文件系統需要實現的操作,實際編碼過程中發現不是那么清楚的知道這里面的函數具體是干啥,在什么時候調用。尤其是寫IO相關的操作,包括write_begin, write_end, writepage, writepages, direct_IO以及set_page_dirty等函數指針。
      要搞清楚這些函數指針,就需要縱觀整個寫流程里這些函數指針的調用位置。因此本文重點分析和梳理了Linux文件系統寫I/O的代碼流程,以幫助實現底層文件系統的讀寫接口。

      概覽

      先放一張圖鎮貼,該流程圖沒有包括bdi_writeback回寫機制(將在下一篇中展示):
      Linux Kernel文件系統寫I/O代碼流程

      VFS流程

      sys_write()

      Glibc提供的write()函數調用由內核的write系統調用實現,對應的系統調用函數為sys_write()定義如下:

      asmlinkage long sys_write(unsigned int fd, const char __user *buf,
      			  size_t count);
      

      sys_write()的實現在fs/read_write.c里:

      SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
      		size_t, count)
      {
      	struct fd f = fdget_pos(fd);
      	ssize_t ret = -EBADF;
      
      	if (f.file) {
      		loff_t pos = file_pos_read(f.file);
      		ret = vfs_write(f.file, buf, count, &pos);
      		file_pos_write(f.file, pos);
      		fdput_pos(f);
      	}
      
      	return ret;
      }
      

      該函數獲取struct fd引用計數和pos鎖定,獲取pos并主要通過調用vfs_write()實現數據寫入。

      vfs_write()

      vfs_write()函數定義如下:

      ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
      {
      	ssize_t ret;
      
      	if (!(file->f_mode & FMODE_WRITE))
      		return -EBADF;
      	if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write))
      		return -EINVAL;
      	if (unlikely(!access_ok(VERIFY_READ, buf, count)))
      		return -EFAULT;
      
      	ret = rw_verify_area(WRITE, file, pos, count);
      	if (ret >= 0) {
      		count = ret;
      		file_start_write(file);
      		if (file->f_op->write)
      			ret = file->f_op->write(file, buf, count, pos);
      		else
      			ret = do_sync_write(file, buf, count, pos);
      		if (ret > 0) {
      			fsnotify_modify(file);
      			add_wchar(current, ret);
      		}
      		inc_syscw(current);
      		file_end_write(file);
      	}
      
      	return ret;
      }
      

      該函數首先調用rw_verify_area()檢查pos和count對應的區域是否可以寫入(如是否獲取寫鎖等)。然后如果底層文件系統指定了struct file_operations里的write()函數指針,則調用file->f_op->write()函數,否則直接調用VFS的通用寫入函數do_sync_write()。

      do_sync_write()

      VFS的do_sync_write()函數在底層文件系統沒有指定f_op->write()函數指針時默認調用,它也被很多底層系統直接指定為f_op->write()。其定義如下所示:

      ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
      {
      	struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
      	struct kiocb kiocb;
      	ssize_t ret;
      
      	init_sync_kiocb(&kiocb, filp);
      	kiocb.ki_pos = *ppos;
      	kiocb.ki_left = len;
      	kiocb.ki_nbytes = len;
      
      	ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
      	if (-EIOCBQUEUED == ret)
      		ret = wait_on_sync_kiocb(&kiocb);
      	*ppos = kiocb.ki_pos;
      	return ret;
      }
      

      通過時上面的代碼可知,該函數主要生成struct kiocb,將其提交給f_op->aio_write()函數,并等待該kiocb的完成。所以底層文件系統必須實現f_op->aio_write()函數指針。
      底層文件系統大部分實現了自己的f_op->aio_write(),也有部分文件系統(如ext4, nfs等)直接指向了通用的寫入方法:generic_file_aio_write()。我們通過該函數代碼來分析寫入的大致流程。

      generic_file_aio_write()

      VFS(其實是mm模塊)提供了通用的aio_write()函數,其定義如下:

      ssize_t generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
      		unsigned long nr_segs, loff_t pos)
      {
      	struct file *file = iocb->ki_filp;
      	struct inode *inode = file->f_mapping->host;
      	ssize_t ret;
      
      	BUG_ON(iocb->ki_pos != pos);
      
      	mutex_lock(&inode->i_mutex);
      	ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);
      	mutex_unlock(&inode->i_mutex);
      
      	if (ret > 0) {
      		ssize_t err;
      
      		err = generic_write_sync(file, pos, ret);
      		if (err < 0 && ret > 0)
      			ret = err;
      	}
      	return ret;
      }
      

      該函數對inode加鎖之后,調用__generic_file_aio_write()函數將數據寫入。如果ret > 0即數據寫入成功,并且寫操作需要同步到磁盤(如設置了O_SYNC),則調用generic_write_sync(),這里面將調用f_op->fsync()函數指針將數據寫盤。

      函數__generic_file_aio_write()的代碼略多,這里貼出主要的片段如下:

      ssize_t __generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
      				 unsigned long nr_segs, loff_t *ppos)
      {
      	...
      	if (io_is_direct(file)) {
      		loff_t endbyte;
      		ssize_t written_buffered;
      
      		written = generic_file_direct_write(iocb, iov, &nr_segs, pos,
      							ppos, count, ocount);
      		...
      	} else {
      		written = generic_file_buffered_write(iocb, iov, nr_segs,
      				pos, ppos, count, written);
      	}
      	...
      

      從上面代碼可以看到,如果是Direct IO,則調用generic_file_direct_write(),不經過page cache直接寫入磁盤;如果不是Direct IO,則調用generic_file_buffered_write()寫入page cache。

      Direct IO實現

      generic_file_direct_write()

      函數generic_file_direct_write()的主要代碼如下所示:

      ssize_t
      generic_file_direct_write(struct kiocb *iocb, const struct iovec *iov,
      		unsigned long *nr_segs, loff_t pos, loff_t *ppos,
      		size_t count, size_t ocount)
      {
      	...
      
      	if (count != ocount)
      		*nr_segs = iov_shorten((struct iovec *)iov, *nr_segs, count);
      
      	write_len = iov_length(iov, *nr_segs);
      	end = (pos + write_len - 1) >> PAGE_CACHE_SHIFT;
      
      	written = filemap_write_and_wait_range(mapping, pos, pos + write_len - 1);
      	if (written)
      		goto out;
      
      	if (mapping->nrpages) {
      		written = invalidate_inode_pages2_range(mapping,
      					pos >> PAGE_CACHE_SHIFT, end);
      		if (written) {
      			if (written == -EBUSY)
      				return 0;
      			goto out;
      		}
      	}
      
      	written = mapping->a_ops->direct_IO(WRITE, iocb, iov, pos, *nr_segs);
      
      	if (mapping->nrpages) {
      		invalidate_inode_pages2_range(mapping,
      					      pos >> PAGE_CACHE_SHIFT, end);
      	}
      
      	if (written > 0) {
      		pos += written;
      		if (pos > i_size_read(inode) && !S_ISBLK(inode->i_mode)) {
      			i_size_write(inode, pos);
      			mark_inode_dirty(inode);
      		}
      		*ppos = pos;
      	}
      out:
      	return written;
      }
      

      由于是Direct IO,在寫入之前需要調用filemap_write_and_wait_range()將page cache里的對應臟數據刷盤,以保障正確的寫入順序。filemap_write_and_wait_range()函數最終通過調用do_writepages()函數將臟頁刷盤(參見后面)。
      然后調用invalidate_inode_pages2_range()函數將要寫入的區域在page cache里失效,以保證讀操作必須經過磁盤讀到最新寫入的數據。在本次寫操作完成后再次調用invalidate_inode_pages2_range()函數將page cache失效,避免寫入磁盤的過程中有新的讀取操作將過期數據讀到了cache里。
      最終通過調用a_ops->dierct_IO()將數據Direct IO方式寫入磁盤。a_ops即struct address_operations,由底層文件系統實現。

      Buffered IO實現

      generic_file_buffered_write()

      函數generic_file_buffered_write()的主要代碼如下所示:

      ssize_t
      generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
      		unsigned long nr_segs, loff_t pos, loff_t *ppos,
      		size_t count, ssize_t written)
      {
      	struct file *file = iocb->ki_filp;
      	ssize_t status;
      	struct iov_iter i;
      
      	iov_iter_init(&i, iov, nr_segs, count, written);
      	status = generic_perform_write(file, &i, pos);
      
      	if (likely(status >= 0)) {
      		written += status;
      		*ppos = pos + status;
        	}
      	
      	return written ? written : status;
      }
      

      該函數初始化一個struct iov_iter,然后主要通過調用generic_perform_write()函數寫入page cache。

      generic_perform_write()

      函數generic_perform_write()主要代碼如下所示:

      static ssize_t generic_perform_write(struct file *file,
      				struct iov_iter *i, loff_t pos)
      {
      	...
      
      	if (segment_eq(get_fs(), KERNEL_DS))
      		flags |= AOP_FLAG_UNINTERRUPTIBLE;
      
      	do {
      		...
      
      		offset = (pos & (PAGE_CACHE_SIZE - 1));
      		bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
      						iov_iter_count(i));
      
      again:
      		if (unlikely(iov_iter_fault_in_readable(i, bytes))) {
      			status = -EFAULT;
      			break;
      		}
      
      		status = a_ops->write_begin(file, mapping, pos, bytes, flags,
      						&page, &fsdata);
      		if (unlikely(status))
      			break;
      
      		if (mapping_writably_mapped(mapping))
      			flush_dcache_page(page);
      
      		pagefault_disable();
      		copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
      		pagefault_enable();
      		flush_dcache_page(page);
      
      		mark_page_accessed(page);
      		status = a_ops->write_end(file, mapping, pos, bytes, copied,
      						page, fsdata);
      		if (unlikely(status < 0))
      			break;
      		copied = status;
      
      		cond_resched();
      
      		iov_iter_advance(i, copied);
      		if (unlikely(copied == 0)) {
      			bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
      						iov_iter_single_seg_count(i));
      			goto again;
      		}
      		pos += copied;
      		written += copied;
      
      		balance_dirty_pages_ratelimited(mapping);
      		if (fatal_signal_pending(current)) {
      			status = -EINTR;
      			break;
      		}
      	} while (iov_iter_count(i));
      
      	return written ? written : status;
      }
      
      

      該函數包括如下幾個步驟:
      1.通過調用a_ops->write_begin()進行數據寫入前的處理,由底層文件系統實現,主要處理需要申請額外的存儲空間,以及從后端存儲(磁盤或者網絡)讀取不在緩存里的page數據。該函數返回locked的page。
      2.從用戶空間拷貝數據到步驟1返回的page里。訪問用戶態內存時可能觸發缺頁異常,為避免陷入缺頁異常處理從而導致重入和死鎖(如mmap文件系統的內存),拷貝之前,通過pagefault_disable()將缺頁異常處理關閉,當發生缺頁異常時不進行異常處理。
      3.通過調用底層文件系統的a_ops->write_end()將page這是為dirty并unlock。
      4.循環步驟1-3,直到所有iov都得到處理,每次循環只處理一個page里的數據。
      5.調用balance_dirty_pages_ratelimited()平衡內存中的臟頁,需要時將臟頁刷盤。

      后記

      從上可知,對于Buffered IO,并不一定有將數據寫入磁盤的操作,這就是延遲寫技術。數據寫入內核的page cache緩存后,后續由bdi_writeback機制負責臟頁的數據刷盤回寫。

      posted @ 2019-02-26 11:33  艦隊  閱讀(8600)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 精品无码一区二区三区水蜜桃| 精品久久久久无码| 国产小受被做到哭咬床单GV| 亚洲精品福利一区二区三区蜜桃 | 久久婷婷五月综合色国产免费观看| 亚洲第四色在线中文字幕| 欧美日产国产精品日产| 在线观看国产成人av天堂| 最新国产精品精品视频| 深夜福利资源在线观看| 欧美国产精品啪啪| 亚洲成av人片在线观看www| 国产高清av首播原创麻豆| 热久在线免费观看视频| 亚洲第一福利网站在线观看| 日本大片在线看黄a∨免费| 日韩熟女熟妇久久精品综合 | 99热精品毛片全部国产无缓冲| 日本一区二区三本视频在线观看| 亚洲+成人+国产| 亚洲中文字幕精品无人区| 欧美牲交a欧美在线| 国产精品不卡一区二区久久| 中国熟女仑乱hd| 亚洲色最新高清AV网站| 日韩一区二区三区在线观院 | 国产精品一区在线蜜臀| 欧美精品高清在线观看| 国产精品中文字幕在线看| 久久天天躁狠狠躁夜夜躁2o2o| 久久久成人毛片无码| 亚洲精品一区二区三区小| 国产精品天天看天天狠| 国产极品视频一区二区三区 | 精品一区二区免费不卡| 亚洲午夜无码久久久久小说| 妇女自拍偷自拍亚洲精品| 精品 无码 国产观看| 宜君县| 人妻少妇偷人一区二区| 亚欧美闷骚院|