Linux文件操作源码分析记录(打开与读文件)

本文记录Linux 0.11的文件操作源码的学习过程,该文章参考了《Linux内核设计的艺术》中的内容,并在原文的基础上加以理解,并总结于此以便学习回顾。

基础篇

image.png

文件系统用来存储文件内容、文件属性、和目录。这些类型的数据如何存储在磁盘块上的呢?unix/linux使用了一个简单的方法。

它将磁盘块分为三个部分:

  1. 超级块,文件系统中第一个块被称为超级块。这个块存放文件系统本身的结构信息。比如,超级块记录了每个区域的大小,超级块也存放未被使用的磁盘块的信息。
  2. inode表。超级块的下一个部分就是i-节点表,每个文件都有一些属性,如文件的大小、文件所有者、和创建时间等,这些性质被记录在一个称为i-节点的结构中。所有i-节点都有相同的大小,并且i-节点表是这些结构的一个列表,文件系统中每个文件在该表中都有一个i-节点。
  3. 数据区。文件系统的第3个部分是数据区。文件的内容保存在这个区域。磁盘上所有块的大小都一样。如果文件包含了超过一个块的内容,则文件内容会存放在多个磁盘块中。一个较大的文件很容易分布上千个独立的磁盘块中.

打开文件操作

获取目标文件的inode

Linux 0.11 版本的文件系统中存在*filp[20]、file_table[64]、inode_table[32]

文件系统需要确定进程操作哪个文件:

“1)将用户进程task_struct中的*filp[20]与内核中的file_table[64]进行挂接。

2)将用户进程需要打开的文件对应的inode在file_table[64]中进行登记。”

其中“内核通过 *filp[20] 掌控一个进程可以打开的文件,既可以打开多个不同的文件,也可以同一个文件多次打开,每打开一次文件(不论是否是同一个文件),就要在*filp[20]中占用一个项(比如hello.txt文件被一个用户进程打开两次,就要在*filp[20]中占用两项)记录指针。

操作系统中file_table[64]是管理所有进程打开文件的数据结构,与*filp[20]类似,只要打开一次文件,就要在file_table[64]中记录。

image.png

第一步,首先需要将 *filp[20]与内核中的file_table[64]进行挂接以便

挂载的目的是方便用户进程操作文件。书中以“/mnt/user/user1/user2/hello.txt”为例进行讲解:

sys_open函数是系统

image.png

即寻找到两个数据结构中空余的部分,并进行对应挂接。当超过使用的极限后,内核报错,所以需要文件系统先检查后使用。

image.png

上图为查找hello.txt的流程图

image.png

在底层是调用“open_namei()函数实现的”。

image.png

之后依次调用-open_namei() - dir_namei() - get_dir()函数,用于分析用户给出的文件路径名,遍历路径所有目录文件i节点,目的是获取最后一个目录文件i节点。

get_dir()函数中,确定目录项对应的函数是find_entry();通过目录项获取i节点对应的函数是iget()

get_dir()核心代码如下:

image.png

dentry,即directory entry,目录项,就是多个文件或者目录的链接,通过这个链接可以找寻到目录之下的文件或者是目录项。

struct dentry {
    atomic_t d_count;
    unsigned int d_flags;       /* protected by d_lock */
    spinlock_t d_lock;      /* per dentry lock */
    struct inode *d_inode;      /* Where the name belongs to - NULL is
                     * negative */
    /*
     * The next three fields are touched by __d_lookup.  Place them here
     * so they all fit in a cache line.
     */
    struct hlist_node d_hash;   /* lookup hash list */
    struct dentry *d_parent;    /* parent directory */
    struct qstr d_name;
 
 
    struct list_head d_lru;     /* LRU list */
    /*
     * d_child and d_rcu can share memory
     */
    union {
        struct list_head d_child;   /* child of parent list */
        struct rcu_head d_rcu;
    } d_u;
    struct list_head d_subdirs; /* our children */
    struct list_head d_alias;   /* inode alias list */
    unsigned long d_time;       /* used by d_revalidate */
    struct dentry_operations *d_op;
    struct super_block *d_sb;   /* The root of the dentry tree */
    void *d_fsdata;         /* fs-specific data */
#ifdef CONFIG_PROFILING
    struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
    int d_mounted;
    unsigned char d_iname[DNAME_INLINE_LEN_MIN];    /* small names */
};

根据目录项我们要找到对应目录的inode(Linux中每一个目录均是一个文件),之后循环寻找(/mnt/user/user1/user2/hello.txt为例依次寻找/mnt、 /user、 /user1、 /user2、 /hello.txt)

其中find_entry()函数的任务是:先通过目录文件i节点,确定目录文件中有多少目录项,之后从目录文件对应的第一个逻辑块开始,不断将该文件的逻辑块从外设读入缓冲区,并从中查找指定目录项,直到找到指定的目录项为止。

iget()函数的任务是:根据目录项中提供的i节点号、设备号获取i节点。具体的获取方式是:先在inode_table[32]中搜索,如果指定的i节点已在其中,就直接使用;如果找不到,再加载。

image.png

同样使用find_entry()iget()函数获取hello.txt的inode。

到此,我们就找到了hello.txt的inode号,并且将inode放入到空闲的inode_table[32]中。

将inode与file_table[64]挂载

此时我们已经获得了目标文件对应的inode_table[32]。

现在要将该inode与file_table[64]进行挂接,目的是使file_table[64]通过inode_table[32]中hello.txt文件inode所在表项的指针,找到该inode。

image.png

到此为止,file_table[64]中的挂接点,一端与当前进程的*filp[20]指针绑定,另一端与inode_table[32]中hello.txt文件的i节点绑定。绑定关系建立后,操作系统把fd返给用户进程。这个fd是挂接点在file_table[64]中的偏移量,即“文件句柄”。进程此后只要把这个fd传递给操作系统,操作系统就可以判断出进程需要操作哪个文件。

image.png

这就是我们常见的c中的读操作代码,此时fd已经被赋予了open后的句柄,变可以进行读操作了。

读文件操作

read()函数最终映射到sys_read()系统调用函数去执行,而该函数最终调用file_read()。在执行主体内容之前,先对此次操作的可行性进行检查,包括用户进程传递的文件句柄、读取字节数是否在合理范围内,用户进程数据所在的页面能否被写入数据,等等。

file_read()函数如下:

image.png

其中最重要的是bmap函数,该函数将1KB的数据复制到缓冲区中。

inode在管理文件时使用的是i_zone结构,如下图:

image.png

image.png

当数据总量小于等于7 KB时,i_zone[9]的前7个成员已经足够用了,它们就直接记录该文件的这7个数据块在数据区的“块号(剩余两个存储其他系统信息)。

由于一个i_zone元素代表1kb数据,所以当数据>7时,就需要使用二级索引或者三级索引。即最多存储:(7+512+512×512)KB数据。

那我们如何将数据从数据块读入缓冲区呢?

即调用bread()函数。

image.png

当数据从设备被读入到缓冲区后,系统需要再将其从缓冲区中复制到用户的进程中(*buf)。

image.png

从hello.txt文件的起始位置读出了一个数据块(1 KB)的数据。通过while不断地循环,将指定数量的数据全部载入用户进程的*buf区域。

新建文件操作

新建文件就是根据用户进程要求,创建一个文件系统中不存在的文件。新建文件由creat()函数实现。

新建文件首先需要查找该文件是否存在,即调用sys_creat(),之后调用sys_open()函数,此时仍然调用open_namei()函数来获取inode节点,但是不同的地方是这次并不能获取到。

image.png

于是将bh赋值为null。

之后进行新建操作,但是此处注意,没有找到对应文件的目录项并不意味就必须要新建,有可能是用户输入错了。所以需要检查O_CREAT标志位。

image.png

在创建新文件时,需要创建文件的目录项:

hello.txt的目录项要载入user2目录文件中。add_entry()函数的任务是:只要在目录文件中寻找到空闲项,就在此位置处加载新目录项;如果确实找不到空闲项,就在外设上创建新的数据块来加载。

image.png

之后调用new_block()新建数据块。

下一期我们讲一下如何进行写文件操作。

本文许多资料来自书中,大家如果想了解更多可以去书中寻找,我只是把自己的一些看法抽象出来便于回顾。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容