linux内核学习5:虚拟文件系统(VFS)

参考https://blog.csdn.net/qq_34556414/article/details/109007718
https://blog.csdn.net/u012319493/article/details/85016519

VFS

  1. VFS支持的文件系统

  2. VFS 的数据结构 : 进程-》(fd)文件对象-》(f_dentry)目录项对象-》(d_inode)索引节点对象-》(i_sb)超级块对象-》磁盘文件

  3. 软硬链接方式

  4. 进程的命名空间

  5. 文件系统安装和卸载

  6. VFS 系统调用的实现(open(), write(), read(), close())

  7. Linux 文件锁

一、虚拟文件系统(VFS)的作用

Linux的成功因素之一是它具有与其它操作系统和谐共存的能力,其中代表作之一就是虚拟文件系统(VFS)。

VFS的思想是把不同类型文件的共同信息放入内核,具体思路是通过Linux在用户进程(或C库)和文件系统之间引入了一个抽象层,该抽象层称之为虚拟文件系统(VFS)。

虚拟文件系统也可称为虚拟文件转换,是一个内核软件层,用来处理与 Unix 标准文件系统相关的所有系统调用。
VFS 能为各种文件系统提供一个通用的接口。
在这里插入图片描述
VFS 支持三种主要类型的文件系统:

  • 磁盘文件系统。如 Ext2、Ext3、System V 等。这些文件系统管理本地磁盘分区中可用的存储空间或者其他可起到磁盘作用的设备(USB 闪存)。
  • 网络文件系统。如 NFS、Coda 等。这些文件系统允许轻易地访问属于其他计算机的文件系统所包含的文件。
  • 特殊文件系统。如 /proc 文件系统等。这些文件系统不管理本地或者远程磁盘空间。

Unix 的目录建立了一棵目录为“/”的树。
根目录包含在根文件系统中,Linux 通常为 Ext2 或 Ext3。
其他文件系统可以被“安装”在根文件系统的子目录中。

基于磁盘的文件系统通常存放在硬件块设备中,如磁盘、软盘或 CD-ROM。
Linux VFS 能处理如 /dev/loop0 这样的虚拟块设备,这种设备可用来安装普通文件所在的文件系统。
在这里插入图片描述

通用文件模型

VFS 所隐含的主要思想在于引入了一个通用的文件模型,该模型能表示所有支持的文件系统。要实现每个具体的文件系统,必须将其物理组织结构转换为虚拟文件系统的通用文件模型。

本质上,Linux 内核不能对一个特定的函数进行硬编码来执行如 read() 或 ioctl() 这样的操作,而是对每个操作都必须使用一个指针,指向要访问的具体文件系统的函数。

可将通用文件模型看作是面向对象的,出于效率考虑,采用普通的 C 数据结构实现。

普通文件模型由下列对象类型组成:
1)超级快对象(superblock object):存放已安装文件系统有关的信息
superblock的数据结构
在这里插入图片描述
2)索引节点对象(inode object):存放具体文件的一般信息(内核在操作文件或目录时需要的全部信息)。一个索引节点代表文件系统中的一个文件,但是索引节点仅当文件被访问时,才在内存中创建。

inode的数据结构
在这里插入图片描述
3)文件对象(file object):它代表由进程打开的文件。存放打开文件与进程之间进行交互的有关信息。这些信息仅当进程访问文件期间存放在内核中。
这类信息仅当进程访问期间存在于内核内存中。文件对象(不是物理文件)由相应的open()系统调用创建,由close()系统调用撤销。
file的数据结构
在这里插入图片描述
4)目录项对象(dentry object):不是指目录,VFS把目录作为一个文件看待,这里的目录项对象指的是路径的一个组成部分,与对应文件进行链接的有关信息。

dentry的数据结构在这里插入图片描述
VFS对象之间的关系如下:
在这里插入图片描述
进程与VFS对象之间的交互如下
在这里插入图片描述
在 Linux 系统中 ,inode 号才是文件的唯一标识而非文件名。文件名只是为了方便人们的记忆和适用。

ls -li
total xx
533124 drwxr-xr-x 9 apple apple 4096 May  4 10:22 apache-tomcat-8.5.41
360908 -rw-rw-r-- 1 apple apple   18 Jun  1 23:50 helloWolrd
360974 drwxr-xr-x 4 apple apple 4096 Jun  3 10:15 hi-cat
393217 drwxrwxr-x 6 apple apple 4096 May 28 09:37 program
518079 drwxrwxr-x 3 apple apple 4096 Mar  4 11:31 project

如上述命令 “ls -li” 结果中的第一列就是文件的 inode 号。系统是通过 inode 号寻找正确的文件数据块。

文件的实际内容则保存在 block 中(大小可以是 1KB、2KB 或 4KB),一个 inode 的默认大小为 128B (在 Ext3 文件系统中),记录一个 block 则消耗 4B 。当文件的 inode 被写满后,Linux 系统会自动分配出一个 Block 块,专门用于像 innode 那样记录其他 block 块的信息,这样能把各个 block 块的内容串到一起,就能够让用户读到完整的文件内容了。

对于存储文件内容的的 Block 块,有以下两种常见情况,以 4KB 的 block 大小为例说明情况 :

文件很小(1KB) , 但依然会占用一个 block ,因此会潜在占用 3kb。
文件很大(5kb) , 那么会占用两个 block。

总结 :
superBlock : 存储整个文件系统的信息。
inode : 存储文件的权限与属性。
data block : 真正存储文件内容。

软、硬链接方式

参考:https://blog.csdn.net/stupid56862/article/details/90785420
在 Windows 系统中,快捷方式是指向原始文件的一个链接文件。可以让用户从不同的位置来访问原始的文件;原文件一旦被删除或剪切到其他地方后,会导致链接文件失效。

但是在 Linux 系统中,"快捷方式"就不太一样 。在 Linux 系统存在硬链接和软链接两种文件。

硬链接: “指向原始文件 inode 的指针”,系统不为它分配独立的 inode 和 文件,硬链接文件与原始文件其实是同一个文件,只是名字不同。我们每添加一个硬链接,该文件的 innode 连接数就会增加 1 ; 而且只有当该文件的 inode 连接数为 0 时,才算彻底被将它删除。因此即便删除原始文件,依然可以通过硬链接文件来访问。需要注意的是,我们不能跨分区对文件进行链接。
在这里插入图片描述

我们可以这样理解,在 Linux 系统中,文件名本身就相当于硬链接,文件名仅仅是一个指针。

软链接:等同于 Windows 系统下的快捷方式。仅仅包括所含链接文件的路径名字。因此能链接目录,也能跨文件系统链接。但是,当删除原始文件后,链接文件也将失效。

如上面的图12-2 进程与VFS对象之间的交友
在这里插入图片描述

图中,三个进程打开同一个文件,其中两个进程使用同一个硬链接。
每个进程都使用自己的文件对象,但只需要两个目录项对象,每个硬链接对应一个目录对象。
这两个目录对象指向同一个索引节点对象,该索引节点对象标识超级块对象,以及随后的普通磁盘文件。

最近最常使用的目录项对象被放在称为目录高速缓存的磁盘高速缓存中,以加速从文件路径名到最后一个路径分量的索引节点的转换过程。

一般,磁盘高速缓存属于软件机制,它允许内核将原本存在磁盘上的某些信息保存在 RAM 中。

磁盘高速缓存不同于硬件高速缓存或内存高速缓存,后两者都与磁盘或其他设备无关。
硬件高速缓存是一个快速 RAM,加快了直接对慢速动态 RAM 的请求。
内存高速缓存是一种软件机制,引入它是为了绕过内核内存分配器。

除了目录项高速缓存和索引节点高速缓存,还有其他磁盘高速缓存,如页高速缓存。

VFS 处理的系统调用

在这里插入图片描述
在这里插入图片描述
某些情况下,一个文件操作可能由 VFS 本身去执行,无需调用底层函数。
如,当某个进程关闭一个打开的文件时,不需要涉及磁盘上的相应文件,VFS 只需释放对应的文件对象即可。

目录树

综合来说,Linux 的 根文件系统(system’s root filessystem) 是内核启动mount的第一个文件系统。内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后,从中把一些基本的初始化脚本和服务等加载到内存中去运行(文件系统和内核是完全独立的两个部分)。其他文件系统,则后续通过脚本或命令作为子文件系统安装在已安装文件系统的目录上,最终形成整个目录树。

start_kernel 
  vfs_caches_init 
    mnt_init 
      init_rootfs     // 注册rootfs文件系统
      init_mount_tree // 挂载rootfs文件系统 
  … 
  rest_init 
  kernel_thread(kernel_init, NULL, CLONE_FS);
复制代码

就单个文件系统而言,在文件系统安装时,创建超级块对象;沿树查找文件时,总是首先从初识目录的中查找匹配的目录项,以便获取相应的索引节点,然后读取索引节点的目录文件,转化为dentry对象,再检查匹配的目录项,反复执行以上过程,直至找到对应的文件的索引节点,并创建索引节点对象。加粗样式

二、VFS 的数据结构

1.超级块对象
2.索引节点对象
3.文件对象
4.目录项对象
这4个的结构在上面有讲过,这里不做过多详细

目录项高速缓存

由于从磁盘读入一个目录项并构造响应的目录项对象需要花费大量的时间,所以,在完成对目录项对象的操作后,后面可能还要使用它,因此仍在内存中保留它有重要的意义。
为了最大限度地提高处理目录对象的效率,Linux 使用目录项高速缓存,它由两种类型的数据结构组成:

  • 一个处于正在使用、未使用或负状态的目录项对象的集合。
  • 一个散列表,从中能快速获取与给定的文件名和目录名对应的目录项对象。不在目录项高速缓存中时,返回空值。

目录项高速缓存还相当于索引节点高速缓存的控制器。
在内核中,并不丢弃与目录项相关的索引节点,因为目录项高速缓存仍在使用它们。
因此,这些索引节点对象保存在 RAM 中,并能够借助相应的目录项快速索引。

所有“未使用”目录项对象都存放在一个“最近最少使用”的双向链表中,该链表按照插入的时间排序。
即最后释放的目录对象放在链表的首部,所以最近最少使用的目录项对象总是靠近链表的尾部。
一旦目录项高速缓存的空间开始变小,内核就从链表的尾部删除元素,使得最近最常使用的对象得以保留。

与进程相关的文件

每个进程都有自己当前工作的目录和它的根目录,这仅仅是内核用来表示进程与文件系统相互作用所必须维护的数据中两个例子。类型为fs_struct的整个数据结构就用于此目的,且每个进程描述符的fs字段就指向进程的fs_struct结构。
在这里插入图片描述
每个进程都有自己的根目录和当前工作目录,内核使用struct fs_struct来记录这些信息,进程描述符的fs字段便是指向该进程的fs_struct结构。

fs_struct 定义于 include/linux/fs_struct.h 。

struct fs_struct {
	int users;
	spinlock_t lock;
	seqcount_t seq;
	int umask;
	int in_exec;
	struct path root, pwd;
};

除了根目录和当前工作目录,进程还需要记录自己打开的文件。进程已经打开的所有文件使用struct files_struct来记录,进程描述符的files字段便指向该进程的files_struct结构。
files_struct 定义于 include/linux/fdtable.h

struct files_struct {
    /*
     * read mostly part
     */
    atomic_t count;
    bool resize_in_progress;
    wait_queue_head_t resize_wait;
    struct fdtable __rcu *fdt;
    struct fdtable fdtab;
    /*
     * written part on a separate cache line in SMP
     */
    spinlock_t file_lock ____cacheline_aligned_in_smp;
    int next_fd;
    unsigned long close_on_exec_init[1];
    unsigned long open_fds_init[1];
    struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};

由于NR_OPEN_DEFAULT在x86结构下的定义为32,在ia64结构下的定义为64,所以说,fd_array最多只能存储32或64个文件对象,远远小于进程被允许打开的最大文件数NR_OPEN, 如果超过了NR_OPEN_DEFAULT, 多出来的文件对象将如何存储呢?

显然,内核必须分配新的存储空间存放这些文件对象的指针。旧版本的内核中,struct file_struct中有一个fd字段,指向文件对象的指针数组。通常fd指向fd_array,如果进程打开的文件数目多于32个,内核就分配一个新的更大的文件对象的指针数组,并将其地址存放在fd字段中,这个数组所包含的元素数目存放在max_fds字段。

新版本的内核将fd,max_fds以及其他几个相关字段组织在一起,增加一个新的独立数据结构struct fdtable,成为文件描述符表,其主要数据结构定义如下所示:

fdtable 定义于 include/linux/fdtable.h 。

struct fdtable {
    unsigned int max_fds;
    struct file __rcu **fd;      /* current fd array */
    unsigned long *close_on_exec;
    unsigned long *open_fds;
    struct rcu_head rcu;
};

一般的,文件描述符有如下意思:
0 stdin 标准输入
1 stdout 标准输出
2 stderr 标准错误

三、文件系统类型

文件系统注册:

通常在系统初始化器间并且使用文件系统类型前必须执行的基本操作。
一旦文件系统被注册,其特定的函数对内核就是可用的,因此文件系统类型可安装在系统的目录树上。

特殊文件系统(简单了解就好)

当网络和磁盘文件系统能够使用用户处理存放在内核之外的信息时,特殊文件系可以为系统程序员和管理员提供一种容易的方式来操作内核的数据结构并实现操作系统的特殊特征。

特殊文件系统不限于物理块设备。
然而,内核给每个安装的特殊文件系统分配一个虚拟的块设备,让其主设备号为 0 而次设备号具有任意值。
在这里插入图片描述

文件系统注册类型(了解就好)

VFS 必须对代码目前已在内核中的所有文件系统的类型进行跟踪。
这通过文件系统类型注册实现。
每个注册的文件系统都用一个类型为 file_system_type 的对象表示。

所有文件系统类型的对象都插入一个单向链表,变量 file_systems 指向链表的第一个元素。
file_systems_lock 读/写自旋锁保护整个链表免受同时访问。

file_system_type 的一些字段:

fs_supers,表示给定类型的已安装文件系统所对应的超级块链表的头。
链表元素的向后和向前链接存放在超级块对象的 s_instances 字段。
get_sb,指向依赖于文件系统类型的函数,该函数分配一个新的超级块对象并初始化它。
kill_sb,指向删除超级块的函数。
fs_flags,存放几个标志。
在系统初始化器间,register_filesystem() 注册编译时指定的每个文件系统:
该函数把相应的 file_system_type 对象插入到文件系统类型的链表。

当文件系统的模块被装入时,也要调用 register_filesystem()。
当该模块被卸载时,对应的文件系统也可以被注销。

get_fs_type() 扫描已注册的文件系统链表以查找文件系统类型的 name 字段,并返回指向相应的 file_system_type 对象的指针。

四、文件系统处理

Linux 使用系统的根文件系统:它由内核在引导阶段直接安装,并拥有系统初始化脚本以及最基本的系统程序。

其他文件系统要么由初始化脚本安装,要么由用户直接安装在已安装文件系统的目录上。
作为一个目录树,每个文件系统都拥有自己的根目录。例如,/proc虚拟文件系统是系统的根文件系统的孩子。
安装文件系统的目录称为安装点。
已安装文件系统属于安装点目录的一个子文件系统。
在这里插入图片描述

4.1 进程的命名空间

参考:https://www.cnblogs.com/x_wukong/p/8496896.html
参考:https://blog.csdn.net/lalalafishleong123/article/details/100072379
每个进程可拥有自己的安装文件系统树—叫做进程的命名空间。

传统上Linux及其衍生版的UNIX变体中,许多资源都是全局管理的。例如进程PID和用户的UID等全局ID。为了节约成本并且能够保证用户之间的权限不受影响,命名空间提供了一种与KVMhe VMare不同的解决方案。内核通过命名空间将全局资源进行抽象,使得各个进程组分别放到不同的容器,彼此隔离,但是可以允许提供一些接口使得其可以相互通信来降低容器间的间隔。
命名空间给系统建立了不同的视图,如下图所示
在这里插入图片描述
新的命名空间的创建方法:
1)在用fork和clone系统调用创建进程时,可以选特定的选项控制是否与父进程共享命名空间。
2)unshare系统调用可将某些部分,如命名空间等从父进程分离。

  • Linux查看进程的命名空间
    在这里插入图片描述

目前,Linux内核里面实现了7种不同类型的namespace。
名称 宏定义 隔离内容
Cgroup CLONE_NEWCGROUP Cgroup root directory (since Linux 4.6)
IPC CLONE_NEWIPC System V IPC, POSIX message queues (since Linux 2.6.19)
Network CLONE_NEWNET Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount CLONE_NEWNS Mount points (since Linux 2.4.19)
PID CLONE_NEWPID Process IDs (since Linux 2.6.24)
User CLONE_NEWUSER User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS CLONE_NEWUTS Hostname and NIS domain name (since Linux 2.6.19)

下面简要介绍一个以上不同类型的命名空间的作用:

  • IPC:用于隔离进程间通讯所需的资源( System V IPC, POSIX message queues),PID命名空间和IPC命名空间可以组合起来用,同一个IPC名字空间内的进程可以彼此看见,允许进行交互,不同空间进程无法交互

  • Network:Network Namespace为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPv4和IPv6协议栈,IP路由表,防火墙规则,sockets等等。一个Network Namespace提供了一份独立的网络环境,就跟一个独立的系统一样。

  • Mount:每个进程都存在于一个mount Namespace里面,mount Namespace为进程提供了一个文件层次视图。如果不设定这个flag,子进程和父进程将共享一个mount Namespace,其后子进程调用mount或umount将会影响到所有该Namespace内的进程。如果子进程在一个独立的mount Namespace里面,就可以调用mount或umount建立一份新的文件层次视图。

  • PID:linux通过命名空间管理进程号,同一个进程,在不同的命名空间进程号不同!进程命名空间是一个父子结构,子空间对于父空间可见。

  • User:用于隔离用户

  • UTS:用于隔离主机名

与命名空间相关的API主要有三个:clone,setns和unshare,这三个API都是针对一个进程来操作的。

总结: namespace的本质就是把原来所有进程全局共享的资源拆分成了很多个一组一组进程共享的资源 当一个namespace里面的所有进程都退出时,namespace也会被销毁,所以抛开进程谈namespace没有意义 UTS
namespace就是进程的一个属性,属性值相同的一组进程就属于同一个namespace,跟这组进程之间有没有亲戚关系无关 UTS
namespace没有嵌套关系,即不存在说一个namespace是另一个namespace的父namespace

4.2 文件系统安装

在 Linux 中,同一个文件系统可被多次安装。
如果一个文件系统被安装了 n 次,那么它的根目录就可通过 n 个安装点访问。
但文件系统的确是唯一的,因此,不管一个文件系统被安装了多少次,都仅有一个超级块对象。

安装的文件系统形成一个层次:一个文件系统的安装点可能成为第二个文件系统的目录。
而第三个文件系统又安装在第二个文件系统之上,等等。

安装普通文件系统

mount() 被用来安装一个普通文件系统

大内核锁: 它在早期linux版本里的广泛使用,大内核锁(BKL)的设计是在kernel hacker们对多处理器的同步还没有十足把握时,引入的大粒度锁。他的设计思想是,一旦某个内核路径获取了这把锁,那么其他所有的内核路径都不能再获取到这把锁。
目前是用自旋锁等其他锁
在这里插入图片描述

安装根文件系统

安装根文件系统是系统初始化的关键部分。
安装根文件系统分两个阶段:

  • 内核安装特殊 rootfs 文件系统,该文件系统仅提供一个作为初始安装点的空目录。
  • 内核在空目录上安装实际根文件系统。

rootfs 文件系统使得内核较容易地改变实际根文件系统。
阶段1:安装 rootfs 文件系统
第一阶段由 init_rootfs() 和 init_mount_tree() 完成,它们在系统初始化过程中执行。
init_rootfs() 注册特殊文件系统类型 rootfs:

struct file_system_type rootfs_fs_type 
{
	.name = "rootfs";
	.get_sb = rootfs_get_sb;
	.kill_sb = kill_litter_super;
};
register_filesystem(&rootfs_fs_type);

init_mount_tree() 执行如下操作:

  1. do_kern_mount() ,参数为文件系统类型参数–字符串 “rootfs” ,该函数把返回的新安装文件系统描述符的地址保存在 mnt 局部变量中。
    do_kern_mount() 调用 rootfs 文件系统的 get_sb 方法,即 rootfs_get_sb():
struct superbolck *rootfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
	return get_sb_nodev(fs_type, flags|MS_NOUSESR, data, ramfs_file_super);
}

  • get_sb_nodev() 执行下述步骤:
    a. sget() 分配新对超级块,传递 set_anon_super() 的地址作为参数。
    用合适的方式设置超级块的 s_dev 字段:主设备号为 0,次设备号不同于其他已安装的特殊文件系统的次设备号。
    b. 超级块的 s_flags = flags 参数的值。
    c. ramfs_fill_super() 分配索引节点对象和对应的目录项对象,并填充超级块字段。
    由于 rootfs 是一种特殊文件系统,没有磁盘超级块,因此只需执行两个超级块操作。
    d. 返回新超级块的地址。
  1. 为进程 0 的命名空间分配一个 namespace 对象,并将它插入到由 do_kern_mount() 返回的已安装文件系统描述符中:
namespace = kmalloc(sizeof(*namespace), GFP_KERNEL);
list_add(&mnt->mnt_list, &namespace->list);

namespace->root = mnt;
mnt->mnt_namespace = init_task.namesapce = namespace;

  1. 系统中其他每个进程的 namespace = namespace 对象的地址;同时初始化引用计数器 namespace->count。
  2. 将进程 0 的根目录和当前工作目录设置为根文件系统。

阶段2:安装实际根文件系统
简单起见,只考虑磁盘文件系统的情况,它的设备文件名已通过“root”启动参数传递给内核。
同时假定除了 rootfs 文件系统外,没有使用其他初始特殊文件系统。

prepare_namespace() 执行如下操作:

  • root_device_name = 从启动参数“root”中获取的设备文件名。
    ROOT_DEV = 同一设备文件的主设备号和次设备号。
  • mount_root() 执行下述操作:
    a. sys_mknod() 在 rootfs 初始根文件系统中创建设备文件 /dev/root,其主、次设备号与存放在 ROOT_DEV 中的一样。
    b. 分配一个缓冲区并用文件系统类型名链表填充。
    该链表要么通过启动参数“rootstype”传递给内核,要么通过扫描文件系统类型单向链表建立。
    c. 扫描上一步建立的文件系统类型名链表。
    对每个名字,调用 sys_mount() 在根设备上安装给定的文件系统类型。
    由于每个特定于文件系统的方法使用不同的魔数,因此,对 get_sb() 的调用大都会失败,但有一个例外,那就是用根设备上实际使用过的文件系统的函数来填充超级块的那个调用,该文件系统被安装在 rootfs 文件系统的 /root 目录上。
    d. sys_chdir(”/root“)改变进程的当前目录。
  • 移动 rootfs 文件系统根目录上的已安装文件系统的安装点。
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");

注意,rootfs 特殊文件系统没有被卸载,只是隐藏在基于磁盘的根文件系统下了。

4.3 卸载文件系统

umount() 系统调用卸载一个文件系统。然后调用sys_umount() 服务

sys_umount() 服务例程作用于两个参数:

  • 文件名(多是安装点目录或块设备文件名)
  • 一组标志
    sys_umount() 执行下列步骤:
  1. path_lookup() 查找安装点路径名;把返回的查找结果存放在 nameidata 类型的局部变量 nd 中。
  2. 如果查找的最终目录不是文件系统的安装点,则设置 retval 返回码为 -EINVAL 并跳到第 6 步。
    该检查通过验证 nd->mnt->mnt_root 进行。
  3. 如果要卸载的文件系统还没有安装在命名空间中,则设置 retval 返回码为 -EINVAL 并跳到第 6 步。
    这种检查通过在 nd->mnt 上调用 check_mnt() 进行。
  4. 如果用户不具有卸载文件系统的特权,则设置 retval 返回码为 -EPERM 并跳到第 6 步。
  5. do_umount(),参数为 nd.mnt(已安装文件系统对象)和 flags(一组标志)。执行下述步骤:
    a. 从已安装文件系统对象的 mnt_sb 字段检索超级块对象 sb 的地址。
    b. 如果用户要求强制卸载操作,则调用 umount_begin 超级块操作中断任何正在进行的安装操作。
    c. 如果要卸载的文件系统是根文件系统,且用户并不要求真正卸载,则调用 do_remount_sb() 重新安装根文件系统为只读并终止。
    d. 为进行写操作获取当前进程的 namespace->sem 读/写信号量和 vfsmount_lock 自旋锁。
    e. 如果已安装文件系统不包含任何子安装文件系统的安装点,或者用户要求强制卸载文件系统,则调用 umount_tree() 卸载文件系统。
    f. 释放 vfsmount_lock 自旋锁和当前进程的 namespace->sem 读/写信号量。
  6. 减少相应文件系统根目录的目录项对象和已安装文件系统描述符的应用计数器值;
    这些计数器值由 path_lookup() 增加。
  7. 返回 retval 值。

五、路径名查找(有印象就行)

路径查找是VFS的一个主要操作:给定一个文件名,获取该文件名的inode。路径查找是VFS中相当繁琐的一部分,主要是符号链接,文件系统装载点,以及. …和//等奇怪路径 引入了复杂性。

后面的参考https://blog.csdn.net/Fybon/article/details/30504803

六、VFS 系统调用的实现

主要对open(), write(), read(), close()等调用的系统代码实现流程的介绍

用户发出了一条shell命令:把/floppy/TEST 中的MS-DOS 文件拷贝到/tmp/test中的Ext2 文件中。命令shell 调用一个外部程序(如cp),我们假定cp执行下列代码片段:

inf = open("/floppy/TEST", O_RDONLY,0);
outf = open("/tmp/test", O_WRONLY |O_CREAT |O_TRUNC,0600);
do {
len = read(inf, buf, 4096);
write(outf, buf,len);
) while (len);
close(outf);
close(inf);

实际上,真正的cp程序的代码要更复杂些,因为它还必须检查由每个系统调用返回的可能的出错码。在我们的例子中,我们只把注意力集中在拷贝操作的“正常”行为上。

详细的open(), write(), read(), close()等调用的系统代码实现流程参考:https://blog.csdn.net/weixin_48101150/article/details/109990746

七、Linux 文件锁

7.1 基本概念

Linux中软件、硬件资源都是文件(一切皆文件),文件在多用户环境中是可共享的。
文件锁是用于解决资源的共享使用的一种机制:当多个用户需要共享一个文件时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。

文件锁包括建议性锁和强制性锁:

  • 建议性锁:要求每个使用上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁,它们依靠程序员遵守这个规定。
  • 强制性锁:是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。

在Linux中,实现文件上锁的函数有lockf()和fcntl()

  • lockf()用于对文件施加建议性锁

  • fcntl()不仅可以施加建议性锁,还可以施加强制锁。

  • fcntl()还能对文件的某一记录上锁,也就是记录锁。

  • 记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。

  • 写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分建立写入锁。

  • 在文件的同一部分不能同时建立读取锁和写入。

对lockf()和fcntl()的详细说明和用例参考:https://blog.csdn.net/kunkliu/article/details/78710273

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值