linux kernel 文件系统编程接口

1 打开文件

进程读写文件之前需要打开文件,得到文件描述符,然后通过文件描述符读写文件.

1.1 编程接口

内核提供了两个打开文件的系统调用openopenat.

1.2 技术原理

打开文件的主要步骤如下:
(1)需要在父目录的数据中查找文件对应的目录项,从目录项得到索引节点的编号,然后在内存中创建索引节点的副本.因为各种文件系统类型的物理结构不同,所以需要提供索引节点操作集合的lookup方法和文件操作集合的open方法.
(2)需要分配文件的一个打开实例--file结构体,关联到文件的索引节点.
(3)在进程的打开文件表中分配一个文件描述符,把文件描述符和打开实例的映射添加到进程的打开文件表中.

2 关闭文件

进程可通过使用系统调用close关闭文件.
系统调用close的执行流程如下:
(1)解除打开文件表和file实例的关联.
(2)在close_on_exec位图中清楚文件描述符对应的位.
(3)释放文件描述符,在文件描述符位图中清除文件描述符对应的位.
(4)调用函数fput释放file实例:把引用计数减1,如果引用计数是0,那么把file实例添加到链表delayed_fput_list中,然后调用延迟工作项delayed_fput_work.
延迟工作项delayed_fput_work的处理函数是flush_delayed_fput,遍历链表delayed_fput_list,针对每个file实例,调用函数__fput来加以释放.

3 创建文件

3.1 使用方法

创建不同类型的文件,需要使用不同的命令.
(1)普通文件:touch FILE,这条命令本来用来更新文件的访问时间和修改时间,如果文件不存在,创建文件.
(2)目录:mkdir DIRECTORY.
(3)符号链接(软链接):ln -s TARGET LINK_NAME或ln --symbolic TARGET LINK_NAME.
(4)字符或块设备文件:mknod NAME TYPE [MAJOR MINOR].
(5)命名管道:mkpipe NAME.
(6)硬连接:命令"ln TARGET LINK_NAME".给已经存在的文件增加新的名称,文件的索引节点有一个硬链接计数,如果文件有n个名称,那么硬链接计数是n.

3.2 技术原理

创建文件需要在文件系统中分配一个索引节点,然后在父目录的数据中增加一个目录项来保存文件的名称和索引节点编号.

4 删除文件

4.1 使用方法

删除文件的命令如下:
(1)删除任何类型文件:unlink FILE.
(2)rm FILE,默认不删除目录,如果使用"-r""-R"或"-recursive",可以删除目录和目录的内容.
(3)删除目录:rmdir DICTIONARY.
内核提供了unlink,unlinkat用来删除文件的名称,如果文件的硬链接计数变成0,并且没有进程打开这个文件,那么删除文件.提供了rmdir删除目录.

4.2 技术原理

删除文件需要从父目录的数据中删除文件对应的目录项,把文件的索引节点的硬链接计数减1(一个文件可以有多个名称,Linux把文件名称称为硬链接),如果索引节点的硬链接计数变成0,那么释放索引节点.因为各种文件系统的物理结构不同,所以需要提供索引节点操作集合的unlink方法.

5 设置文件权限

5.1 使用方法

设置文件权限的命令如下:
(1)chmod [OPTION]... MODE[, MODE]... FILE...
mode : 权限设定字串,格式[ugoa...][[+-=][rwxX]...][,...]
其中:

  • u 表示该文件的拥有者,g 表示与该文件的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示这三者皆是.
  • +表示增加权限、- 表示取消权限、= 表示唯一设定权限.
  • r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行, s表示当文件被执行时,根据who参数指定的用户类型设置文件的setuid或者setgid权限。

(2)chmod [OPTION]... OCTAL-MODE FILE...
参数OCTAL-MODE是八进制数值.
系统调用chmod负责修改文件权限.

5.2 技术原理

修改文件权限需要修改文件的索引节点的文件模式字段,文件模式字段包含文件类型和访问权限.因为各种文件系统类型的索引节点不同,所以需要提供索引节点操作集合的setattr方法.

6 页缓存

访问外部存储设备的速度很慢,为了避免每次读写文件时访问外部存储设备,文件系统模块为每个文件在内存中创建一个缓存,因为缓存的单位是页,所以称为页缓存.
(1)索引节点的成员i_mapping指向地址空间结构体(address_space).进程在打开文件的时候,文件打开实例(file结构体)的成员f_mapping也会指向文件的地址空间.
(2)每个文件有一个地址空间结构体address_space,成员page_tree的类型是结构体radix_tree_root:成员gfp_mask是分配内存页的掩码,成员rnode指向基数树的根节点.
(3)使用基数树管理页缓存,把文件的页索引映射到内存页的页描述符.

6.1 地址空间

每个文件都有一个地址空间结构体address_space,用来建立数据缓存(在内存中为某种数据创建的缓存)和数据来源(即存储设备)之间的关联.结构体address_space如下:

struct address_space {
    struct inode        *host;      /* owner: inode, block_device 指向索引节点*/
    struct radix_tree_root  page_tree;  /* radix tree of all pages */
    spinlock_t      tree_lock;  /* and lock protecting it */
    atomic_t        i_mmap_writable;/* count VM_SHARED mappings */
    struct rb_root      i_mmap;     /* tree of private and shared mappings */
    struct rw_semaphore i_mmap_rwsem;   /* protect tree, count, list */
    /* Protected by tree_lock together with the radix tree */
    unsigned long       nrpages;    /* number of total pages */
    unsigned long       nrshadows;  /* number of shadow entries */
    pgoff_t         writeback_index;/* writeback starts here */
    const struct address_space_operations *a_ops;   /* methods 指向地址空间操作集合*/
    unsigned long       flags;      /* error bits/gfp mask */
    spinlock_t      private_lock;   /* for use by the address_space */
    struct list_head    private_list;   /* ditto */
    void            *private_data;  /* ditto */
} __attribute__((aligned(sizeof(long))));

struct radix_tree_root {
    unsigned int        height;
    gfp_t           gfp_mask;/*分配内存页的掩码*/
    struct radix_tree_node  __rcu *rnode; /*rnode指向基数树的根节点*/;
};

地址空间操作结合address_space_operations的主要成员如下:

struct address_space_operations {
        /* 用来把文件的一页写到存储设备 */
    int (*writepage)(struct page *page, struct writeback_control *wbc);

        /* 用来把文件的一页从存储设备读到内存 */
    int (*readpage)(struct file *, struct page *);

    /* Write back some dirty pages from this mapping. 用来把文件的多个脏页写到储存设备 */
    int (*writepages)(struct address_space *, struct writeback_control *);

    /* Set a page dirty.  Return true if this dirtied it. 用来给文件的一页设置脏标记,表示数据被修改过, 还没写回到设备*/
    int (*set_page_dirty)(struct page *page);

    int (*readpages)(struct file *filp, struct address_space *mapping,
            struct list_head *pages, unsigned nr_pages);

    int (*write_begin)(struct file *, struct address_space *mapping,
                loff_t pos, unsigned len, unsigned flags,
                struct page **pagep, void **fsdata);
    int (*write_end)(struct file *, struct address_space *mapping,
                loff_t pos, unsigned len, unsigned copied,
                struct page *page, void *fsdata);

    /* Unfortunately this kludge is needed for FIBMAP. Don't use it */
    sector_t (*bmap)(struct address_space *, sector_t);
    void (*invalidatepage) (struct page *, unsigned int, unsigned int);
    int (*releasepage) (struct page *, gfp_t);
    void (*freepage)(struct page *);
    ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter, loff_t offset);
    /*
     * migrate the contents of a page to the specified target. If
     * migrate_mode is MIGRATE_ASYNC, it must not block.
     */
    int (*migratepage) (struct address_space *,
            struct page *, struct page *, enum migrate_mode);
    int (*launder_page) (struct page *);
    int (*is_partially_uptodate) (struct page *, unsigned long,
                    unsigned long);
    void (*is_dirty_writeback) (struct page *, bool *, bool *);
    int (*error_remove_page)(struct address_space *, struct page *);

    /* swapfile support */
    int (*swap_activate)(struct swap_info_struct *sis, struct file *file,
                sector_t *span);
    void (*swap_deactivate)(struct file *file);
};

6.2 编程接口

页缓存的常用操作函数如下:
(1)函数find_get_page根据文件的页索引在页缓存中查找内存页.

static inline struct page *find_get_page(struct address_space *mapping,
                    pgoff_t offset);

(2)函数find_or_create_page根据文件的页索引在页缓存中查找内存页,如果没有找到内存页,那么分配一个内存页,然后添加到页缓存中.

static inline struct page *find_or_create_page(struct address_space *mapping,
                    pgoff_t offset, gfp_t gfp_mask);

(3)函数add_to_page_cache_lru把一个内存页添加到页缓存和LRU链表中.

int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
                pgoff_t index, gfp_t gfp_mask);

(4)函数delete_from_page_cache从页缓存中删除一个内存页.

void delete_from_page_cache(struct page *page);

7 读文件

7.1 编程接口

进程读文件的方式有3种:
(1)调用内核提供的读文件的系统调用.
(2)调用glibc库封装的读文件的标准I/O流函数.
(3)创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟地址空间,然后直接读内存.
第2种方式在用户空间创建了缓冲区,能减少系统调用的次数,提高性能.第3种方式可以避免系统调用,性能最高.

7.2 技术原理

读文件的主要步骤如下:
(1)调用具体文件系统类型提供的文件操作集合的read和read_iter方法来读文件.
(2)read或read_iter方法根据页索引在文件的页缓存中查找页,如果没有找到,那么调用具体文件系统类型提供的地址空间集合的readpage方法来从存储设备读取文件页到内存中.
为了提高读文件的速度,从存储设备读取文件页到内存中的时候,除了读取请求的文件页,还会预读后面的文件页.如果进程按顺序读文件,预读文件页可以提高读文件的速度;如果进程随机读文件,预读文件页对提高读文件的速度帮助不大.

8 写文件

8.1 编程接口

进程写文件的方式有3种:
(1)调用内核提供的写文件的系统调用.
(2)调用glibc库封装的写文件的标准I/O流函数.
(3)创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟空间,然后直接写内存.
第2种方式在用户空间创建了缓冲区,能够减少系统调用的次数,提高性能.第3种方式可以避免系统调用,性能最高.

8.2 技术原理

写文件的主要步骤如下:
(1)调用具体文件系统类型提供的文件操作集合的write或write_iter方法来写文件.
(2)write或write_iter方法调用文件的地址空间操作集合的write_begin方法,在页缓存查找页,如果页不存在就分配页;然后把数据从用户缓冲区复制到页缓存的页中;最后调用文件的地址空间操作集合的write_end方法.

9 文件回写

进程写文件时,内核的文件系统模块把数据写到文件的页缓存,没有立即写回到存储设备.文件系统模块会定期把脏页写回到存储设备,进程也可以调用系统调用把脏页强制写回到存储设备.

9.1 编程接口

管理员可以执行命令"sync",把内存中所有修改过的文件元数据和文件数据写回到存储设备.
内核提供了sync,syncfs,fsync,fdatasync,sync_file_range等系统调用用于文件写回.

9.2 技术原理

把文件写回到存储设备的时机如下:
(1)周期回写.
(2)当脏页的数量达到限制的时候,强制回写.
(3)进程调用sync和syncfs等系统调用.

10 DAX

对于类似内存的块设备,例如NVDIMM设备,不需要把文件从存储设备复制到页缓存.DAX绕过页缓存,直接访问存储设备,对于基于文件的内存映射,直接把存储设备映射到进程的虚拟地址空间.
调用系统调用mmap创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟地址空间,这会调用具体文件系统类型提供的文件操作集合的mmap方法.mmap方法针对设置了标志位S_DAX的索引节点,处理方法如下:
(1)给虚拟内存区域设置标志位VM_MIXEDMAP和VM_HUGEPAGE.
(2)设置虚拟内存操作集合,提供fault,huge_fault,page_mkwrite和pfn_mkwrite方法.

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

推荐阅读更多精彩内容

  • 1 概述 在Linux系统中,一切皆文件,除了通常所说的狭义的文件(文本文件和二进制文件)以外;目录,设备,套接字...
    CHCD阅读 1,165评论 0 0
  • linux 文件系统工作原理简介 文件系统时对存储设备上的文件进行组织管理的机制,组织方式不同就形成了不同的文件系...
    Vackine阅读 912评论 0 3
  • 同 CPU、内存一样,磁盘和文件系统的管理,也是操作系统最核心的功能。 磁盘为系统提供了最基本的持久化存储。 文件...
    tracy_668阅读 472评论 0 5
  • 第12章:文件系统 文件系统概念文件系统和文件文件描述符目录文件别名文件系统种类 虚拟文件系统 文件缓存和打开文件...
    liuzhangjie阅读 1,400评论 0 0
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,536评论 28 53