1.nfs_file_write
NFS文件系统中write操作过程和read操作过程类似,区别在于read操作从服务器读取数据到客户端,write操作将数据从客户端写到服务器中。NFS文件系统中write操作的处理函数是nfs_file_write()。
ssize_t nfs_file_write(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct dentry * dentry = iocb->ki_filp->f_path.dentry; // 找出文件的目录项结构
struct inode * inode = dentry->d_inode; // 这是文件的索引节点
unsigned long written = 0;
ssize_t result;
size_t count = iov_length(iov, nr_segs); // 计算用户态缓存的总长度
struct nfs_inode *nfsi = NFS_I(inode);
// 步骤1 判断是否是直接IO
if (iocb->ki_filp->f_flags & O_DIRECT) // 这是直接写
return nfs_file_direct_write(iocb, iov, nr_segs, pos, true);
// 下面就是缓存写了
dprintk("NFS: write(%s/%s, %lu@%Ld)\n",
dentry->d_parent->d_name.name, dentry->d_name.name, // 父目录名称,文件名称
(unsigned long) count, (long long) pos); // 需要写的数据量,数据在文件中的位置
result = -EBUSY;
if (IS_SWAPFILE(inode))
goto out_swapfile;
/*
* O_APPEND implies that we must revalidate the file length.
*/
// 步骤2 追加写时需要先更新文件长度
if (iocb->ki_filp->f_flags & O_APPEND) { // 追加写
// 追加写操作之前需要先获取文件长度,因为文件长度已经发生了变化,
// 需要重新确定文件长度。这个函数向服务器发起了GETATTR请求。
result = nfs_revalidate_file_size(inode, iocb->ki_filp);
if (result)
goto out; // 获取文件属性过程出错了
}
result = count;
if (!count) // 没有需要写的数据,不处理了,直接返回.
goto out;
// 步骤3 这是缓存写的处理函数
// 这就是通用的文件写操作方法,这个函数会调用write_begin和write_end。
result = generic_file_aio_write(iocb, iov, nr_segs, pos);
if (result > 0)
written = result; // 这是写入的数据量
/* Return error values for O_DSYNC and IS_SYNC() */
// 步骤4 将数据刷新到服务器中
if (result >= 0 && nfs_need_sync_write(iocb->ki_filp, inode)) {
int err = vfs_fsync(iocb->ki_filp, 0); // 同步数据,并且同步元数据
if (err < 0)
result = err;
}
// 步骤5 更新统计信息
if (result > 0)
nfs_add_stats(inode, NFSIOS_NORMALWRITTENBYTES, written);
out:
return result;
out_swapfile:
printk(KERN_INFO "NFS: attempt to write to active swap file!\n");
goto out;
}
这个函数的总体流程还是比较清晰的,分为下面5个步骤
步骤1:判断是否是直接IO,如果是直接IO则调用nfs_file_direct_write()处理。
步骤2:追加写时先更新文件长度。因为多个客户端可以挂载同一个文件系统,其他客户端可能已经修改了文件,追加写时需要先确定现在文件结尾的位置,从这个位置写入数据。更新文件长度是由nfs_revalidate_file_size()完成的,这个函数最终发起了GETATTR请求,不仅更新了文件长度,还更新了文件其他的属性信息。写操作中只有追加写之前更新了文件属性,如果不是追加写就没有更新文件属性。估计这样设计的初衷是本地可能已经修改了文件数据,但是还没有提交到服务器中。如果这时更新文件属性的话,以前修改的数据就丢失了。
步骤3:调用generic_file_aio_write()将数据写入缓存页中。这是VFS层的函数,调用了文件系统的write_begin和write_end函数,后面会分析这两个函数。
步骤4:将数据刷新到服务器中。步骤3只是将数据写入到了客户端的缓存页中,并没有传输到服务器中。步骤4中检查是否需要马上就数据传输到服务器中,如果需要马上传送则向服务器发起WRITE请求,否则由客户端缓存管理进程定期刷新脏的缓存页。
步骤5:更新统计信息,这就没什么说的了。
2.nfs_write_begin
generic_file_aio_write()的作用是将用户态缓冲区中的数据写入到内核态缓存页中,这是VFS层的通用函数,这个函数包含三个步骤:
步骤1:准备内核态缓存页,比如分配新的缓存页,这是由文件系统的write_begin函数完成的,NFS中这个函数是nfs_write_begin()。
步骤2:将数据从用户态缓冲区中拷贝到内核态缓存页中,这是由函数iov_iter_copy_from_user_atomic()实现的,这是一个通用函数,就不讲了。
步骤3:进行一些后续处理,比如将缓存页标记为脏,这是由文件系统的write_end函数完成的,NFS中这个函数是nfs_write_end()。