hello_world-3.1之增加操作函数fops(一)概念介绍

来源:互联网 发布:ppt数据分析模板 编辑:程序博客网 时间:2024/05/22 00:14

                    hello_world-3.1之增加操作函数fops(一)概念介绍


1.我们对常见的file_operations 进行讨论,下面是file_operations的结构体


struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);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 **);long (*fallocate)(struct file *file, int mode, loff_t offset,  loff_t len);};

在hello_world-3.0中,我们只是定义了一个空的file_operations,这样的设备不能进行任何的文件操作。

下面我们来介绍,比较重点的几个文件操作:

1.1  open方法

这个方法是在用户空间首先被调用的,一般在用户空间先open一个设备,这样会通过系统调用,调用底层的open,我们来看看open接口

int (*open) (struct inode *, struct file *);

首先,引入眼帘的是这个inode,这个是个比较奇怪的东西。
这里我想引用fudan_abc大牛们的解说http://blog.csdn.net/fudan_abc/article/details/1775313。
     所谓"文件", 就是按一定的形式存储在介质上的信息,所以一个文件其实包含了两方面的信息,一是存储的数据本身,二是有关该文件的组织和管理的信息。在内存中, 每个文件都有一个dentry(目录项)和inode(索引节点)结构,dentry记录着文件名,上级目录等信息,正是它形成了我们所看到的树状结构;而有关该文件的组织和管理的信息主要存放inode里面,它记录着文件在存储介质上的位置与分布。同时dentry->d_inode指向相应的inode结构。dentry与inode是多对一的关系,因为有可能一个文件有好几个文件名(硬链接, hard link, )

      所有的dentry用d_parent和d_child连接起来,就形成了我们熟悉的树状结构。

inode代表的是物理意义上的文件,通过inode可以得到一个数组,这个数组记录了文件内容的位置,如该文件位于硬盘的第3,8,10块,那么这个数组的内容就是3,8,10。其索引节点号inode->i_ino,在同一个文件系统中是唯一的,内核只要根据i_ino,就可以计算出它对应的inode在介质上的位置。就硬盘来说,根据i_ino就可以计算出它对应的inode属于哪个块(block),从而找到相应的inode结构。但仅仅用inode还是无法描述出所有的文件系统,对于某一种特定的文件系统而言,比如ext3,在内存中用ext3_inode_info描述。他是一个包含inode的"容器"。

dentry和inode终究都是在内存中的,它们的原始信息必须要有一个载体。否则断电之后岂不是玩完了?且听我慢慢道来。

文件可以分为磁盘文件,设备文件,和特殊文件三种。

磁盘文件
就磁盘文件而言,dentry和inode的载体在存储介质(磁盘)上。对于像ext3这样的磁盘文件来说,存储介质中的目录项和索引节点载体如下,
struct ext3_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */

__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
......
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
......
}
struct ext3_dir_entry_2 {
__u32 inode; /* Inode number */
__u16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[EXT3_NAME_LEN]; /* File name */
};

le32 i block[EXT2 N BLOCKS];/* Pointers to blocks */
i_block数组指示了文件的内容所存放的地点(在硬盘上的位置)。

ext3_inode是放在索引节点区,而ext3_dir_entry_2是以文件内容的形式存放在数据区。我们只要知道了ino,由于ext3_inode大小已知,我们就可以计算出ext3_inode在索引节点区的位置( ino * sizeof(ext3_inode) ),而得到了ext3_inode,我们根据i_block就可以知道这个文件的数据存放的地点。将磁盘上ext3_inode的内容读入到ext3_inode_info中的函数是ext3_read_inode()。以一个有100 block的硬盘为例,一个文件系统的组织布局大致如下图。位图区中的每一位表示每一个相应的对象有没有被使用。


特殊文件
特殊文件在内存中有inode和dentry数据结构,但是不一定在存储介质上有"索引节点",它断电之后的确就玩完了,所以不需要什么载体。当从一个特殊文件读时,所读出的数据是由系统内部按一定的规则临时生成的,或从内存中收集,加工出来的。sysfs里面就是典型的特殊文件。它存储的信息都是由系统动态的生成的,它动态的包含了整个机器的硬件资源情况。从sysfs读写就相当于向kobject层次结构提取数据。

还请注意, 我们谈到目录项和索引节点时,有两种含义。一种是在存储介质(硬盘)中的(如ext3_inode),一种是在内存中的,后者是根据在前者生成的。内存中的表示就是dentry和inode,它是VFS中的一层,不管什么样的文件系统,最后在内存中描述它的都是dentry和inode结构。我们使用不同的文件系统,就是将它们各自的文件信息都抽象到dentry和inode中去。这样对于高层来说,我们就可以不关心底层的实现,我们使用的都是一系列标准的函数调用。这就是VFS的精髓,实际上就是面向对象。

我们在进程中打开一个文件F,实际上就是要在内存中建立F的dentry,和inode结构,并让它们与进程结构联系来,把VFS中定义的接口给接起来。我们来看一看这个经典的图。这张图之于文件系统,就像每天爱你多一些之于张学友,番茄炒蛋之于复旦南区食堂,刻骨铭心。


前面说过,只要知道文件的索引节点号,就可以得到那个文件。但是我们在操作文件时,从没听说谁会拿着索引节点号来操作文件,我们只知道文件名而已。它们是如何"和谐"起来的呢?linux把目录也看成一种文件,里面记录着文件名与索引节点号的对应关系。比如在ext3文件系统中,如果文件是一个目录,那么它的内容就是一系列ext3_dir_entry_2的结构


struct ext3_dir_entry_2 {
__u32 inode; /* Inode number */
__u16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[EXT3_NAME_LEN]; /* File name */
};


举个例子,比如要打开/home/test/hello.c。首先,找到‘/’,读入其内容,找到名为"home"的文件的索引节点号,打开/home这个"文件",读入内容,找到名为 "test" 的的文件的索引节点号,同理,再打开文件"/home/test",找到找到名为"hello.c”的文件的索引节点号,最后就得到/home/test/hello.c了。这就是path_walk()函数的原理。

其中,根据一个文件夹的inode,和一个文件名来获取该文件的inode结构的函数,就叫lookup,它是inode_operations里面的函数。
struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
lookup,顾名思义,就是查找,比如查查在test这个文件夹下,有没有叫hello.c的文件,有的话,就从存储介质中读取其inode结构。并用dentry->d_inode指向它。所以,我们只要知道了文件的路径和名字,总可以从根目录开始,一层一层的往下走,定位到某一个文件。

好吧,就贴到这里了,我们一般写上层的C调用文件时,open("/home/thiz/helloworldmem.c",O_RDONLY);这样,然后调用系统调用,系统调用帮你完成pathwalk,就是根据所给的文件名字和路径来找到他的inode,那么,再将inode传给内核层的open(struct inode *inode,file *filp);

@file *flip,因为文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数(包括open,release,read,write,ioctl)。在文件的所有实例都关闭后,内核释放这个数据结构。在内核创建和驱动源码中,struct file的指针通常被命名为file或filp.


1.2 读方法 

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

1.3  写方法

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

1.4 ioctl 方法


long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

1.5 release 方法

int (*release) (struct inode *, struct file *);

当然release和open是相对应的,就像在上层C中调用close(fd),这个在底层中就是调用release来释放文件,以及资源

注意:设备驱动中没有flush函数,说明没有缓冲

2.系统调用的文件操作

当一个进程在运行时,一般已经打开的3个文件描述符:
0:标准输入
1:标准输出
2:标准错误

2.1 open系统调用原型


        #include <fcntl.h>        #include <sys/types.h>        #include <sys/stat.h>        int open(const char *path, int oflags);        int open(const char *path, int oflags, mode_t mode);

open建立了一条到文件或者设备访问的路径,如果调用成功,将会成功返回一个文件描述符,该文件描述符是唯一的,不会

和任何其他运行中的进程共享。如果两个程序同时打开一个文件,他们会分别得到两个不同的文件描述符。如果他们都对文件

进行写操作,那么他们会各写各的(这也是FIFO必须考虑的同步问题)。他们会接着上次离开的位置继续写。不会交织在一起

而是相互覆盖。

open调用在成功时候返回一个新的文件描述符,他总是一个非负整数,失败是返回-1.


2.2 write 系统调用,原型为

#include <unistd.h>

        size_t write(int fildes, const void *buf, size_t nbytes);

作用是把缓冲区buf前nbytes字节写入文件描述符fildes关联的文件中,返回实际写入的字节数。如果返回为0,表示未写入任何数据

-1,表述write调用中出现错误。错误代码在errno中。

2.3  read 系统调用,原型为

 #include <unistd.h>        size_t read(int fildes, void *buf, size_t nbytes)
作用是,从与文件描述符 fildes 相关联的文件里读入 nbytes 个字节的数据,并存放到数据区 buf 中,返回实际读入的字节数。如果返回 0,就表示未读入任何数据,如果返回 -1,就表示 read 调用出现错误。此外,该返回值可能小于请求的字节数。











原创粉丝点击