轉自:https://blog.csdn.net/qq_32473685/article/details/103494398
目錄
2.3 sys_write( ) 的核心部分 vfs_write( )
2.5 generic_file_write_iter( )
2.6 __generic_file_write_iter( )
2.7.1 ext4文件系統address_space_operations
2.7.2 ext4文件系統delay allocation機制
2.7.3 執行完 generate_write_back( )后
1 概述
用戶進程通過系統調用write()往磁盤上寫數據,但write()執行結束后,數據是否 立即寫到磁盤上?內核讀文件數據時,使用到了“提前讀”;寫數據時,則使用了“延遲寫”, 即write()執行結束后,數據並沒有立即立即將請求放入塊設備驅動請求隊列,然后寫到 硬盤上。
跟蹤的時候通過
dump_stack
重新編譯linux內核,跟蹤函數執行過程。
2 虛擬文件系統 與 Ext4 文件系統
首先文件系統在內核中的讀寫過程是在 sys_write( ) 中定義的。
2.1 sys_write( ) 代碼跟蹤
sys_write( ) 定義在 include/linux/syscalls.h 中:
asmlinkage long sys_write(unsigned int fd, const char __user *buf, 568 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);
-
if (ret >= 0)
-
file_pos_write(f.file, pos);
-
fdput_pos(f);
-
}
-
return ret;
-
}
2.2 sys_write( ) 過程分析
可以看出在實現 sys_write( ) 的時候,分為如下幾步:
1) 根據打開文件號 fd找到該已打開文件file結構:
struct fd f = fdget_pos(fd);
2) 讀取當前文件的讀寫位置:
loff_t pos = file_pos_read(f.file);
3) 寫入:
ret = vfs_write(f.file, buf, count, &pos);
4) 根據讀文件結果,更新文件讀寫位置 :
file_pos_write(f.file, pos);
2)和 4)可以作為寫入之前和之后的對應操作來看,一個是讀取當前文件的位置,一個是根據寫文件的結果,更新文件的讀寫位置,主要代碼還是在 fs/read_write.c 中:
-
static inline loff_t file_pos_read(struct file *file)
-
{
-
return file->f_pos;
-
}
-
-
static inline void file_pos_write(struct file *file, loff_t pos)
-
{
-
file->f_pos = pos;
-
}
3) 是整個 sys_write( ) 中最為重要的一部分,下面我們仔細分析一下這個函數。
2.3 sys_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_mode & FMODE_CAN_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 if (file->f_op->aio_write)
-
ret = do_sync_write(file, buf, count, pos);
-
else
-
ret = new_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(WRITE, file, pos, count); 檢查文件是否從當前位置 pos 開始的 count 字節是否對寫操作加上了 “強制鎖”,這是通過調用函數完成的。
通過合法性檢查后,就調用具體文件系統 file_operations中 write 的方法。對於ext4文件系統,file_operations方法定義在 fs/ext4/file.c 中。從定義中可知 write 方法實現函數為 do_sync_write( )。
下面是ext4文件系統操作的數據結構:
-
const struct file_operations ext4_file_operations = {
-
.llseek = ext4_llseek,
-
.read = new_sync_read,
-
.write = new_sync_write,
-
.read_iter = generic_file_read_iter,
-
.write_iter = ext4_file_write_iter,
-
.unlocked_ioctl = ext4_ioctl,
-
#ifdef CONFIG_COMPAT
-
.compat_ioctl = ext4_compat_ioctl,
-
#endif
-
.mmap = ext4_file_mmap,
-
.open = ext4_file_open,
-
.release = ext4_release_file,
-
.fsync = ext4_sync_file,
-
.splice_read = generic_file_splice_read,
-
.splice_write = iter_file_splice_write,
-
.fallocate = ext4_fallocate,
-
};
下面是do_sync_write( )的具體代碼,也在fs/read_write.c中:
-
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_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;
-
}
-
EXPORT_SYMBOL(do_sync_write);
異步I/O允許用戶空間來初始化操作而不必等待它們的完成,因此,一個應用程序可以在他的I/O處理進行中做其他的處理。
塊和網絡驅動在整個時間是完全異步的,因此只有字符驅動對於明確的異步I/O支持是候選的。實現異步I/O操作的file_operations方法,都使用I/O Control Block,其定義在 include/linux/aio.h中
定義了一個臨時變量iov,這個變量記錄了用戶空間緩沖區地址buf和所要寫的字節數len,用戶空間的緩沖區地址buf是保存在iov中的。初始化異步I/O數據結構后,就用file_operations 中的aio_write方法。拓展到ext4文件中的時,該方法就是ext4_file_operations結構體中的ext4_file_write( )。
下面就具體到ext4的文件系統,這個函數也是aio_write( ) 的延展。
2.4 ext4_file_write( )
2.4.1 ext4文件系統的extent
Ext2/3等老Linux文件系統使用間接映射模式 (block mapping), 文件的每一個塊都要被記錄下來,這使得大文件操作(刪除)效率低下。Ext4 引入extents這一概念來代替 Ext2/3 使用的傳統的塊映射方式。ext4中一個extent最大可以映射128MB的連續物理存儲空間。
Ext3采用間接塊映射,當操作大文件的時候,效率極其低下,比如一個100MB大小的文件,在Ext3中要建立25600個數據塊的映射表,每個數據塊大小為4KB,而Ext4引入了extents,每個extent為一組連續的數據塊,上述文件表示為,該文件數據保存在接下來的25600個數據塊中,提高了不少效率。
Extent模式主要數據結構包括ext4_extent, ext4_extent_idx, ext4_extent_header,均定義在文件fs/ext4/ext4_extents.h文件中。
-
/*
-
* This is the extent on-disk structure.
-
* It's used at the bottom of the tree.
-
*/
-
struct ext4_extent {
-
__le32 ee_block; /* first logical block extent covers */
-
__le16 ee_len; /* number of blocks covered by extent */
-
__le16 ee_start_hi; /* high 16 bits of physical block */
-
__le32 ee_start_lo; /* low 32 bits of physical block */
-
};
-
-
/*
-
* This is index on-disk structure.
-
* It's used at all the levels except the bottom.
-
*/
-
struct ext4_extent_idx {
-
__le32 ei_block; /* index covers logical blocks from 'block' */
-
__le32 ei_leaf_lo; /* pointer to the physical block of the next *
-
* level. leaf or next index could be there */
-
__le16 ei_leaf_hi; /* high 16 bits of physical block */
-
__u16 ei_unused;
-
};
-
-
/*
-
* Each block (leaves and indexes), even inode-stored has header.
-
*/
-
struct ext4_extent_header {
-
__le16 eh_magic; /* probably will support different formats */
-
__le16 eh_entries; /* number of valid entries */
-
__le16 eh_max; /* capacity of store in entries */
-
__le16 eh_depth; /* has tree real underlying blocks? */
-
__le32 eh_generation; /* generation of the tree */
-
};
2.4.2 ext4_file_write( )
-
static ssize_t
-
ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
-
{
-
struct file *file = iocb->ki_filp;
-
struct inode *inode = file_inode(iocb->ki_filp);
-
struct mutex *aio_mutex = NULL;
-
struct blk_plug plug;
-
int o_direct = io_is_direct(file);
-
int overwrite = 0;
-
size_t length = iov_iter_count(from);
-
ssize_t ret;
-
loff_t pos = iocb->ki_pos;
-
-
/*
-
* Unaligned direct AIO must be serialized; see comment above
-
* In the case of O_APPEND, assume that we must always serialize
-
*/
-
if (o_direct &&
-
ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS) &&
-
!is_sync_kiocb(iocb) &&
-
(file->f_flags & O_APPEND ||
-
ext4_unaligned_aio(inode, from, pos))) {
-
aio_mutex = ext4_aio_mutex(inode);
-
mutex_lock(aio_mutex);
-
ext4_unwritten_wait(inode);
-
}
-
mutex_lock(&inode->i_mutex);
-
if (file->f_flags & O_APPEND)
-
iocb->ki_pos = pos = i_size_read(inode);
-
-
/*
-
* If we have encountered a bitmap-format file, the size limit
-
* is smaller than s_maxbytes, which is for extent-mapped files.
-
*/
-
if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) {
-
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
-
-
if ((pos > sbi->s_bitmap_maxbytes) ||
-
(pos == sbi->s_bitmap_maxbytes && length > 0)) {
-
mutex_unlock(&inode->i_mutex);
-
ret = -EFBIG;
-
goto errout;
-
}
-
-
if (pos + length > sbi->s_bitmap_maxbytes)
-
iov_iter_truncate(from, sbi->s_bitmap_maxbytes - pos);
-
}
-
-
iocb-> private = &overwrite;
-
if (o_direct) {
-
blk_start_plug(&plug);
-
/* check whether we do a DIO overwrite or not */
-
if (ext4_should_dioread_nolock(inode) && !aio_mutex &&
-
!file->f_mapping->nrpages && pos + length <= i_size_read(inode)) {
-
struct ext4_map_blocks map;
-
unsigned int blkbits = inode->i_blkbits;
-
int err, len;
-
-
map.m_lblk = pos >> blkbits;
-
map.m_len = (EXT4_BLOCK_ALIGN(pos + length, blkbits) >> blkbits)
-
- map.m_lblk;
-
len = map.m_len;
-
-
err = ext4_map_blocks( NULL, inode, &map, 0);
-
/*
-
* 'err==len' means that all of blocks has
-
* been preallocated no matter they are
-
* initialized or not. For excluding
-
* unwritten extents, we need to check
-
* m_flags. There are two conditions that
-
* indicate for initialized extents. 1) If we
-
* hit extent cache, EXT4_MAP_MAPPED flag is
-
* returned; 2) If we do a real lookup,
-
* non-flags are returned. So we should check
-
* these two conditions.
-
*/
-
if (err == len && (map.m_flags & EXT4_MAP_MAPPED))
-
overwrite = 1;
-
}
-
}
-
-
ret = __generic_file_write_iter(iocb, from);
-
mutex_unlock(&inode->i_mutex);
-
-
if (ret > 0) {
-
ssize_t err;
-
err = generic_write_sync(file, iocb->ki_pos - ret, ret);
-
if (err < 0)
-
ret = err;
-
}
-
if (o_direct)
-
blk_finish_plug(&plug);
-
-
errout:
-
if (aio_mutex)
-
mutex_unlock(aio_mutex);
-
return ret;
-
}
首先檢查文件是否為ext4的extent模式,若為傳統的塊映射方式,先檢查文件是否過大。若當前文件位置加上待寫的數據長度,大小若超過最大文件限制,則要做相應的調整,最終文件大小不能超過sbi->s_bitmap_maxbytes。
-
if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) {
-
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
-
-
if ((pos > sbi->s_bitmap_maxbytes) ||
-
(pos == sbi->s_bitmap_maxbytes && length > 0)) {
-
mutex_unlock(&inode->i_mutex);
-
ret = -EFBIG;
-
goto errout;
-
}
-
-
if (pos + length > sbi->s_bitmap_maxbytes)
-
iov_iter_truncate(from, sbi->s_bitmap_maxbytes - pos);
-
}
generic_file_aio_write( ) 就是ext4_file_write( )的主體執行語句,若I/O不是塊對齊,寫操作完成后,還要對i_aio_mutex解鎖。
-
ret = __generic_file_write_iter(iocb, from);
-
mutex_unlock(&inode->i_mutex);
-
-
if (ret > 0) {
-
ssize_t err;
-
err = generic_write_sync(file, iocb->ki_pos - ret, ret);
-
if (err < 0)
-
ret = err;
-
}
-
if (o_direct)
-
blk_finish_plug(&plug);
2.5 generic_file_write_iter( )
generic_file_aio_write( )源碼如下:
-
/**
-
* generic_file_write_iter - write data to a file
-
* @iocb: IO state structure
-
* @from: iov_iter with data to write
-
*
-
* This is a wrapper around __generic_file_write_iter() to be used by most
-
* filesystems. It takes care of syncing the file in case of O_SYNC file
-
* and acquires i_mutex as needed.
-
*/
-
ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
-
{
-
struct file *file = iocb->ki_filp;
-
struct inode *inode = file->f_mapping->host;
-
ssize_t ret;
-
-
mutex_lock(&inode->i_mutex);
-
ret = __generic_file_write_iter(iocb, from);
-
mutex_unlock(&inode->i_mutex);
-
if (ret > 0) {
-
ssize_t err;
-
-
err = generic_write_sync(file, iocb->ki_pos - ret, ret);
-
if (err < 0)
-
ret = err;
-
}
-
return ret;
-
}
-
EXPORT_SYMBOL(generic_file_write_iter);
在do_sync_write()中已經將當前文件寫的起始位置記錄在iocb->ki_pos。接下來執行主體函數__generic_file_write_iter( ),執行寫操作前要加鎖,完成后解鎖,若寫操作成功,就返回寫完成的字節數,返回值大於0;寫操作出現錯誤,就返回相應的錯誤碼。接下來就要調用generic_write_sync()將數據刷新到硬盤上。
2.6 __generic_file_write_iter( )
-
/**
-
* __generic_file_write_iter - write data to a file
-
* @iocb: IO state structure (file, offset, etc.)
-
* @from: iov_iter with data to write
-
*
-
* This function does all the work needed for actually writing data to a
-
* file. It does all basic checks, removes SUID from the file, updates
-
* modification times and calls proper subroutines depending on whether we
-
* do direct IO or a standard buffered write.
-
*
-
* It expects i_mutex to be grabbed unless we work on a block device or similar
-
* object which does not need locking at all.
-
*
-
* This function does *not* take care of syncing data in case of O_SYNC write.
-
* A caller has to handle it. This is mainly due to the fact that we want to
-
* avoid syncing under i_mutex.
-
* 此功能完成了將數據實際寫入文件所需的所有工作。它會進行所有基本檢查,從文件中刪除SUID,更新修改
-
* 時間並根據我們執行直接I/O還是標准緩沖寫入來調用適當的子例程。除非我們在完全不需要鎖定的塊設備或
-
* 類似對象上工作,否則它預計將獲取i_mutex。如果是O_SYNC寫操作,此功能不會負責同步數據。呼叫者必
-
* 須處理它。這主要是由於我們要避免在i_mutex下進行同步。
-
*/
-
-
ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
-
{
-
struct file *file = iocb->ki_filp;
-
struct address_space * mapping = file->f_mapping;
-
struct inode *inode = mapping->host;
-
loff_t pos = iocb->ki_pos;
-
ssize_t written = 0;
-
ssize_t err;
-
ssize_t status;
-
size_t count = iov_iter_count(from);
-
-
/* We can write back this queue in page reclaim */
-
current->backing_dev_info = inode_to_bdi(inode);
-
err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
-
if (err)
-
goto out;
-
-
if (count == 0)
-
goto out;
-
iov_iter_truncate(from, count);
-
-
err = file_remove_suid(file);
-
if (err)
-
goto out;
-
-
err = file_update_time(file);
-
if (err)
-
goto out;
-
-
if (io_is_direct(file)) {
-
loff_t endbyte;
-
-
written = generic_file_direct_write(iocb, from, pos);
-
/*
-
* If the write stopped short of completing, fall back to
-
* buffered writes. Some filesystems do this for writes to
-
* holes, for example. For DAX files, a buffered write will
-
* not succeed (even if it did, DAX does not handle dirty
-
* page-cache pages correctly).
-
*/
-
if (written < 0 || written == count || IS_DAX(inode))
-
goto out;
-
-
pos += written;
-
count -= written;
-
-
status = generic_perform_write(file, from, pos);
-
/*
-
* If generic_perform_write() returned a synchronous error
-
* then we want to return the number of bytes which were
-
* direct-written, or the error code if that was zero. Note
-
* that this differs from normal direct-io semantics, which
-
* will return -EFOO even if some bytes were written.
-
*/
-
if (unlikely(status < 0)) {
-
err = status;
-
goto out;
-
}
-
iocb->ki_pos = pos + status;
-
/*
-
* We need to ensure that the page cache pages are written to
-
* disk and invalidated to preserve the expected O_DIRECT
-
* semantics.
-
*/
-
endbyte = pos + status - 1;
-
err = filemap_write_and_wait_range(file->f_mapping, pos, endbyte);
-
if (err == 0) {
-
written += status;
-
invalidate_mapping_pages(mapping,
-
pos >> PAGE_CACHE_SHIFT,
-
endbyte >> PAGE_CACHE_SHIFT);
-
} else {
-
/*
-
* We don't know how much we wrote, so just return
-
* the number of bytes which were direct-written
-
*/
-
}
-
} else {
-
written = generic_perform_write(file, from, pos);
-
if (likely(written >= 0))
-
iocb->ki_pos = pos + written;
-
}
-
out:
-
current->backing_dev_info = NULL;
-
return written ? written : err;
-
}
-
EXPORT_SYMBOL(__generic_file_write_iter);
更新檢查后的實際可寫入數據大小(大多數情況下不變,只有待寫的數據超出文件大小限制,count值才會變化)。
generic_write_checks( )來檢查對該文件的是否有相應的寫權限,這個和系統中是否對文件大小有限制有關,將文件的suid標志清0,而且如果是可執行文件的話,就將sgid標志也清0,既然寫文件,那么文件就會被修改(或創建),修改文件的時間是要記錄在inode中的,並且將inode標記為臟(回寫到磁盤上)。
若寫方式為Direct IO,前面的工作都是一些合法性檢查、記錄文件改變、修改時間。而寫文件的主要工作是調用函數 generic_perform_write( ) 來完成。
2.7 generic_perform_write( )
-
ssize_t generic_perform_write(struct file *file,
-
struct iov_iter *i, loff_t pos)
-
{
-
struct address_space *mapping = file->f_mapping;
-
const struct address_space_operations *a_ops = mapping->a_ops;
-
long status = 0;
-
ssize_t written = 0;
-
unsigned int flags = 0;
-
-
/*
-
* Copies from kernel address space cannot fail (NFSD is a big user).
-
*/
-
if (!iter_is_iovec(i))
-
flags |= AOP_FLAG_UNINTERRUPTIBLE;
-
-
// 若當前I/O操作是屬於在內核中進行,顯然是不能被中斷的(用戶態的I/O操作可以被中斷),就要設置AOP_FLAG_UNINTERRUPTIBLE標志
-
do {
-
struct page *page;
-
unsigned long offset; /* Offset into pagecache page */
-
unsigned long bytes; /* Bytes to write to page */
-
size_t copied; /* Bytes copied from user */
-
void *fsdata;
-
-
offset = (pos & (PAGE_CACHE_SIZE - 1));
-
bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
-
iov_iter_count(i));
-
// index:當前pos位置在pagecache的索引(以頁面大小為單位)
-
// offset:為在頁面內的偏移
-
// bytes:要從用戶空間拷貝的數據大小
-
again:
-
/*
-
* Bring in the user page that we will copy from _first_.
-
* Otherwise there's a nasty deadlock on copying from the
-
* same page as we're writing to, without it being marked
-
* up-to-date.
-
*
-
* Not only is this an optimisation, but it is also required
-
* to check that the address is actually valid, when atomic
-
* usercopies are used, below.
-
*/
-
if (unlikely(iov_iter_fault_in_readable(i, bytes))) {
-
status = -EFAULT;
-
break;
-
}
-
-
// 調用索引節點(file->f_mapping)中address_space對象的write_begin方法,write_begin方法會為該頁分配和初始化緩沖區首部,稍后,我們會詳細分析ext4文件 系統實現的write_begin方法ext4_da_write_begin()。
-
-
status = a_ops->write_begin(file, mapping, pos, bytes, flags,
-
&page, &fsdata);
-
if (unlikely(status < 0))
-
break;
-
-
if (mapping_writably_mapped(mapping))
-
flush_dcache_page(page);
-
-
// mapping->i_mmap_writable 記錄 VM_SHAREE 共享映射數。若mapping_writably_mapped()不等於0,則說明該頁面被多個共享使用,調用flush_dcache_page()。flush_dcache_page()將dcache相應的page里的數據寫到memory里去,以保證dcache內的數據與memory內的數據的一致性。但在x86架構中,flush_dcache_page() 的實現為空,不做任何操作。
-
-
copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
-
flush_dcache_page(page);
-
-
status = a_ops->write_end(file, mapping, pos, bytes, copied,
-
page, fsdata);
-
if (unlikely(status < 0))
-
break;
-
copied = status;
-
-
cond_resched();
-
-
//將待寫的數據拷貝到內核空間后,調用ext4文件系統的address_space_operations的 write_end方法。前面看到ext4文件系統有4種模式:writeback、ordered、journalled和delay allocation。加載ext4分區時,默認方式為delay allocation。對應的write_end方法為 ext4_da_write_end()。
-
cond_resched()檢查當前進程的TIF_NEED_RESCHED標志,若該標志為設置,則 調用schedule函數,調度一個新程序投入運行。
-
-
iov_iter_advance(i, copied);
-
if (unlikely(copied == 0)) {
-
/*
-
* If we were unable to copy any data at all, we must
-
* fall back to a single segment length write.
-
*
-
* If we didn't fallback here, we could livelock
-
* because not all segments in the iov can be copied at
-
* once without a pagefault.
-
*/
-
bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
-
iov_iter_single_seg_count(i));
-
goto again;
-
}
-
//當a_ops->write_end()執行完成后,寫數據操作完成了(注意,此時數據不一定真正寫到磁盤上,因為大多數數據寫為異步I/O)。接下來就要更新iov_iter結構體里的信息,包括文件的位置、寫數據大小、數據所在位置。若copied值為0,說明沒能將數據從用戶態拷貝到內核態,就要再次嘗試寫操作。
-
pos += copied;
-
written += copied;
-
//更新文件位置pos和已完成寫的數據大小
-
balance_dirty_pages_ratelimited(mapping);
-
if (fatal_signal_pending(current)) {
-
status = -EINTR;
-
break;
-
}
-
} while (iov_iter_count(i));
-
//調用 balance_dirty_pages_ratelimited() 來檢查頁面Cache中的臟頁比例是否超過一 個閥值(通常為系統中頁的40%)。若超過閥值,就調用 writeback_inodes() 來刷新幾十頁到磁盤上
-
return written ? written : status;
-
}
-
EXPORT_SYMBOL(generic_perform_write);
2.7.1 ext4文件系統address_space_operations
2.7.2 ext4文件系統delay allocation機制
延時分配(Delayed allocation)該技術也稱為allocate-on-flush,可以提升文件系統的性能。只有buffer I/O中每次寫操作都會涉及的磁盤塊分配過程推遲到數據回寫時再進行,即數據將要被真正寫入磁盤時,文件系統才為其分配塊,這與其它文件系統在早期就分配好必要的塊是不同的。另外,由於ext4的這種做法可以根據真實的文件大小做塊分配決策,它還減少了碎片的產生。
通常在進行Buffer Write時,系統的實際操作僅僅是為這些數據在操作系統內分配內存頁(page cache)並保存這些數據,等待用戶調用fsync等操作強制刷新或者等待系統觸發定時回寫過程。在數據拷貝到page cache這一過程中,系統會為這些數據在磁盤上分配對應的磁盤塊。
而在使用delalloc(delay allocation)后,上面的流程會略有不同,在每次buffer Write時,數據會被保存到page cache中,但是系統並不會為這些數據分配相應的磁盤塊,僅僅會查詢是否有已經為這些數據分配過磁盤塊,以便決定后面是否需要為這些數據分配磁盤 塊。在用戶調用fsync或者系統觸發回寫過程時,系統會嘗試為標記需要分配磁盤塊的這些 數據分配磁盤塊。這樣文件系統可以為這些屬於同一個文件的數據分配盡量連續的磁盤空間,從而優化后續文件的訪問性能。
2.7.3 執行完 generate_write_back( )后
在generic_perform_write()函數執行完成后,我們應知道以下兩點:
(1) 寫數據已從用戶空間拷貝到頁面Cache中(內核空間);
(2) 數據頁面標記為臟;
(3) 數據還未寫到磁盤上去,這就是“延遲寫”技術。后面我們會分析何時、在哪里、怎樣將數據寫到磁盤上的