您的位置:首页 > 运维架构 > Linux

linux文件打开和读写流程代码解析

2017-03-27 09:44 525 查看
打开文件流程:系统调用

fd=open("/dev/pcie_ssd",O_RDWR);

代码定位fs: open.c文件里

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)

{
if (force_o_largefile())
flags |= O_LARGEFILE;

return do_sys_open(AT_FDCWD, filename, flags, mode);

}

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)

{
struct open_flags op;
int fd = build_open_flags(flags, mode, &op);
struct filename *tmp;

if (fd)
return fd;
tmp = getname(filename);
//获取文件名,其内部先创建存取文件名称的空间,然后从用户空间将文件名拷贝过来
if (IS_ERR(tmp))
return PTR_ERR(tmp);

fd = get_unused_fd_flags(flags);
/*获取一个可用的fd,调用alloc_fd从fd_table中获取一个可用的fd并简单初始化,注意对于fd,
它只对本进程有效,也就是它只在该进程中可见而在其他进程中代表完全不同的文件*/

if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &op);//创建file对象,进行路径解析
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
/*文件已经被打开了,调用它,根据inode所指定的信息进行打开函数,
将该文件加入到文件监控的系统中,该系统是用来监控文件被打开、创建、读写、关闭、修改等操作的*/
fd_install(fd, f);
/*将文件指针安装到fd数组,将f加入到fd索引位置的数组中,
如果后续过程有对f继续操作的话,就会通过查找该数组得到对应的文件结构,进行操作*/
}
}
putname(tmp);
return fd;

}

struct file *do_filp_open(int dfd, struct filename *pathname,
const struct open_flags *op)

{
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp;

set_nameidata(&nd, dfd, pathname);
filp = path_openat(&nd, op, flags | LOOKUP_RCU);/*根据op的look_up方法解析路路路径*/
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;

}

int vfs_open(const struct path *path, struct file *file,
    const struct cred *cred)

{
struct dentry *dentry = path->dentry;/*根据路径名解析dentry*/
struct inode *inode = dentry->d_inode;/*根据dentry找到inode*/

file->f_path = *path;
if (dentry->d_flags & DCACHE_OP_SELECT_INODE) {
inode = dentry->d_op->d_select_inode(dentry, file->f_flags);
if (IS_ERR(inode))
return PTR_ERR(inode);
}

return do_dentry_open(file, inode, NULL, cred);

}

static int do_dentry_open(struct file *f, struct inode *inode,int (*open)(struct inode *, struct file *),
 const struct cred *cred)

{

........

f->f_op = fops_get(inode->i_fop);//关键,根据inode的i_fop找到文件的f_op

if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
i_readcount_inc(inode);
if ((f->f_mode & FMODE_READ) &&
    likely(f->f_op->read || f->f_op->read_iter))
f->f_mode |= FMODE_CAN_READ;
if ((f->f_mode & FMODE_WRITE) &&
    likely(f->f_op->write || f->f_op->write_iter))
f->f_mode |= FMODE_CAN_WRITE;

}

路径查找 path_lookup  见链接 http://blog.csdn.net/kickxxx/article/details/9529961
nameidata数据结构
查找过程涉及到很多函数调用,在这些调用过程中,nameidata起到了很重要的作用:1. 向查找函数传递参数;2. 保存查找结果。

[html] view
plain copy

struct nameidata {  

        struct dentry   *dentry;  

        struct vfsmount *mnt;  

        struct qstr     last;  

        unsigned int    flags;  

        int             last_type;  

        unsigned        depth;  

        char *saved_names[MAX_NESTED_LINKS + 1];  

  

        /* Intent data */  

        union {  

                struct open_intent open;  

        } intent;  

};  

VFS读写流程:系统调用

retval1=read(fd,buf,4096);retval1=write(fd,buf,4096);

代码定位fs: read_write.c文件里

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,

size_t, count)

{
struct fd f = fdget_pos(fd);//获取file
ssize_t ret = -EBADF;

if (f.file) {
loff_t pos = file_pos_read(f.file); //读取文件读写位置
ret = vfs_write(f.file, buf, count, &pos);//VFS 读文件
if (ret >= 0)
file_pos_write(f.file, pos);  //回写文件读写位置
fdput_pos(f);
}

return ret;
}

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);
ret = __vfs_write(file, buf, count, pos);//调用文件写操作方法
if (ret > 0) {
fsnotify_modify(file);//更改文件位置
add_wchar(current, ret);
}
inc_syscw(current);
file_end_write(file);
}

return ret;

}

ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,
   loff_t *pos)

{
if (file->f_op->write)
return file->f_op->write(file, p, count, pos);//调用文件写操作方法
  //注:从这可以看出vfs没有对写入文件内容进程缓存
else if (file->f_op->write_iter)
return new_sync_write(file, p, count, pos);//通用文件模型写方法
else
return -EINVAL;

}

static ssize_t new_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;
struct iov_iter iter;
ssize_t ret;

init_sync_kiocb(&kiocb, filp);//初始化异步I/O的结构体

     /*struct kiocb {
struct file
*ki_filp;  //文件指针
loff_t
ki_pos;//文件位置
void (*ki_complete)(struct kiocb *iocb, long ret, long ret2);//回调,表示异步I/O完成
void *private;
int ki_flags;

       ;*/
kiocb.ki_pos = *ppos;
iov_iter_init(&iter, WRITE, &iov, 1, len);

ret = filp->f_op->write_iter(&kiocb, &iter);
BUG_ON(ret == -EIOCBQUEUED);
if (ret > 0)
*ppos = kiocb.ki_pos;
return ret;

}

struct file_operations {
struct module *owner;、//第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <Linux/module.h> 中定义的宏.
loff_t (*llseek) (struct file *, loff_t, int);//llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);//对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
//同步读写:应用程序发起读写后,等到读写函数完成返回,才能继续跑后面的代码。 

//异步读写:应用程序发起读写后,将读写注册到队列,然后立马返回,应用程序继续跑后面的代码,速度非常快,当读写动作完成后,系统发消息通知应用程序,然后应用程序接收读写结果。根据场景选择,驱动可以同时都实现同步和异步的接口。
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);//poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll
方法为 NULL, 设备假定为不阻塞地可读可写.
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);//mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
int (*open) (struct inode *, struct file *);//尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
int (*flush) (struct file *, fl_owner_t id);//flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.
int (*release) (struct inode *, struct file *);//在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
int (*fsync) (struct file *, loff_t, loff_t, int datasync);//这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是 NULL, 系统调用返回 -EINVAL.
int (*aio_fsync) (struct kiocb *, int datasync);//这是 fsync 方法的异步版本.
int (*fasync) (int, struct file *, int);//这个操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.
int (*lock) (struct file *, int, struct file_lock *);//lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);//sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);//这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
 loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);

#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);

#endif

};

ext4: file.c文件里

const struct file_operations ext4_file_operations = {
.llseek
= ext4_llseek,
.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,

};

static ssize_t ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from)->ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)

iocb->ki_flags & IOCB_DIRECT判断是否进入直接IO

Direct IO是write函数的一个选项,用来确定数据内容直接写到磁盘上,而非缓存中,保证即使系统异常了,也能保证紧要数据写到磁盘上,Direct IO流程也是接续着写文件流程而来的。内核走到__generic_file_aio_write函数时,系统根据file->f_flags & O_DIRECT判断进入DirectIO处理的分支,依次先看generic_file_direct_write函数,主要有filemap_write_and_wait_range,invalidate_inode_pages2_range和mapping->a_ops->direct_IO起作用。

filemap_write_and_wait_range主要用来刷mapping下的脏页,filemap_write_and_wait_range如果有写入量则返回,后续的两个函数则不执行。作用就是检查当前内存中是否由对应将要direct_IO的缓存页,如果有,则将其缓存标记为无效。目的是,因为direct_IO写入的数据并不缓存,如果direct_IO写入数据之前有对应缓存,而且是clean的,direct_IO完成之后,缓存和磁盘数据就不一致了,读取缓存的时候,如果没有保护,获取的数据就不是磁盘上的数据。如果的确有对应缓存标记为无效,则返回不执行后面的函数。mapping->a_ops->direct_IO通过__blockdev_direct_IO实现,在direct_io_worker中组装了dio结构,然后通过dio_bio_submit,本质就是通过submit_bio(dio->rw,
bio)提交到io层。所谓direct_io和其他读写比较就是跨过了buffer层,不要中间线程pdflush和kjournald定期刷盘到IO层。这个时候也不一定数据就在磁盘上了,direct_IO就是先假定IO的设备驱动没有较大延时的。

generic_file_direct_write(iocb, from, iocb->ki_pos);->ssize_t generic_perform_write(struct file *file,struct iov_iter *i, loff_t pos)

->a_ops>write_begin(file, mapping, pos, bytes, flags,&page, &fsdata);

首先通过调用函数a_ops->write_begin为数据写入分配页缓存并为这个page准备一组buffer_head结构用于描述组成这个page的数据块,在函数 iov_iter_copy_from_user_atomic中将用户空间数据拷入该页缓存,然后调用函数a_ops->write_end将page中的每一个buffer_head标记为dirty。

 

file->f_mapping是从对应的inode->f_mapping而来,inode->f_mapping->a_ops是由对应的文件系统类型在生成这个inode时赋予的。在ext3文件系统中file->f_mapping->a_ops->write_begin和file->f_mapping->a_ops->write_end

inode.c里

static const struct address_space_operations ext4_da_aops = {
.readpage
= ext4_readpage,
.readpages
= ext4_readpages,
.writepage
= ext4_writepage,
.writepages
= ext4_writepages,
.write_begin
= ext4_da_write_begin,
.write_end
= ext4_da_write_end,
.bmap = ext4_bmap,
.invalidatepage
= ext4_da_invalidatepage,
.releasepage
= ext4_releasepage,
.direct_IO
= ext4_direct_IO,
.migratepage
= buffer_migrate_page,
.is_partially_uptodate  = block_is_partially_uptodate,
.error_remove_page
= generic_error_remove_page,

};

grab_cache_page_write_begin->pagecache_get_page->ext4_block_write_begin
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: