linux内核:从0开始(1)基本操作+文件系统

目录

内核源码树

向内核插入驱动模块

向内核删除驱动模块

查看内核驱动模块

插入到内核中的驱动在哪里

使用驱动模块

挂载文件系统

最基础的文件系统

file_system_type

inode_operations   

file_operations

三者关系

代码

现在开始正式介绍文件系统

虚拟文件系统

特定文件系统

实际的操作流程

文件系统对象

超级块对象

超级块操作

超级块和文件系统的关系

索引节点对象

索引节点操作

索引节点和文件系统的关系

目录项对象

目录项操作

目录项和文件系统的关系

文件对象

文件操作

文件对象和文件系统的关系

总结

打开路径为 /home/user/document.txt 的文件

1. 用户空间请求打开文件

2. VFS接收请求

3. 路径遍历与目录项的查找

4.索引节点 inode的加载

5. 文件对象的创建

6. 文件读取准备

7. 从存储设备读取数据


内核源码树

COPYING文件是内核许可证,CREDITS是开发了很多内核代码的开发者列表,MAINTAINERS是维护者列表,它们负责维护内核子系统和驱动程序,makefile是基本内核的makefile

向内核插入驱动模块

命令:insmod xxx.ko

ko文件由.c文件编译得来,该.c没有main函数

向内核删除驱动模块

rmmod xxx.ko

查看内核驱动模块

lsmod

插入到内核中的驱动在哪里

在/dev/xxx里,这个是由mknod /dev/xxx创建的,然后插入驱动就会自动进入这里,为什么呢

因为xxx.c文件里会定义一个主设备号,/dev/xxx文件也有一个主设备号,一一对应的,查看文件的这设备号可以用 ls -l /dev/xxx来查看,crw-r--r-- 1 root root 96,0 jul ....,其中96,0就是主设备号和次设备号

简单来说就是:/dev/xxx与xxx.ko的联系就是由主设备号联系,可以互相找到

使用驱动模块

如果是对驱动文件进行读写打开关闭操作,操作的函数都是执行xxx.c里的函数,比如read驱动文件,就是调用xxx.c里的read函数,"file_operations" 通常是指 Linux 内核中的一个结构体,用于定义文件操作的函数指针集合。这个结构体通常用于设备驱动程序中,其中的函数指针会指向设备驱动程序实现的特定功能,比如读取文件、写入文件、定位文件指针等等

挂载文件系统

文件系统的类型可以在根目录下的fs文件夹下查看,想使用文件系统,就要将文件系统挂载到文件树下面,mount -t 文件类型 none 挂载到的目的目录,none表示不需要起始化的目录,如果想用图形化界面查看可以输入命令make mcnuconfig,选择File systems----Pseudo filesystems,就可以查看有什么文件系统了,

添加文件系统,在根目录中的fs文件夹里,vim Kconfig,添加source "fs/xxxx/Kconfig"语句,前提是已经拥有并设置好fs/xxxx/文件夹下的内容,里面有Makefile(编译原则)、Kconfig()、

最基础的文件系统

在这个文件系统中可以mount挂载,可以卸载rmmod,可以cd进入这个挂载后的文件夹,可以写入信息echo 1234 >> a.txt,写完可以用cat a.txt来查看是否写入,当编写完.c就将其编译成ko文件,加入到内核中就可以使用了,因为我们没有添加ls类似的函数,所有ls这些功能是用不了的

在fs文件夹下先创建xxxx.c,会定义几个结构体

file_system_type

主要用于存放文件系统名字和所有者和mount(挂载)和kill_sb(卸载)两个函数

  • 先定义file_system_type结构体,并定义文件系统名字和所有者
  • 定义xxxx_mount函数来赋值到结构体中的mount函数指针(挂载   执行mount -t 文件类型 none 挂载到的目的目录命令时调用)
    • 在执行挂载命令时,有一个参数none,就要在xxxx_mount函数中实现mount_nodev()函数,该函数有四个参数,其中最后一个参数是一个函数指针,就还需要再定义一个函数来当作参数,这个函数的作用就是填充挂载的目标目录上要放的东西和操作
  • 定义xxxx_keill_sb函数来赋值到结构体中的kill_sb函数指针(卸载)

inode_operations   

主要是用于存放文件具体信息内容的,例如一个txt里面的内容

  • 定义xxxx_create函数来赋值到结构体中的create函数指针

file_operations

主要是用于存放文件的属性信息,例如权限,创建时间,所有者,所有组等等,有一系列文件操作的函数指针

  • 定义xxxx_read函数来赋值到结构体中的read函数指针
  • 定义xxxx_write函数来赋值到结构体中的write函数指针

三者关系

file_system_type是形容描述这个文件系统来做什么的

file_operations是对于这个文件系统操作的时候执行的函数

inode_operations是每个文件里面的内容怎么存

代码

#include <linux/fs.h>

// 定义文件系统类型
struct file_system_type xxxx_fs_type;

// 文件操作函数:读取文件内容
ssize_t xxxx_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
    // 实现读取文件内容的逻辑
    return 0; // 返回实际读取的字节数
}
// 文件操作函数:写入文件内容
ssize_t xxxx_write(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
    // 实现读取文件内容的逻辑
    return 0; // 返回实际读取的字节数
}
// 超级块填充函数
int xxxx_fill_super(struct super_block *sb, void *data)
{
    // 实现填充超级块的逻辑
    return 0; // 返回填充结果,通常为0表示成功,-errno表示失败
}

// 挂载文件系统
struct dentry *xxxx_mount(struct file_system_type *fs_type, int flags, const char *devname, void *data)
{
    return mount_nodev(fs_type, flags, data, xxxx_fill_super);
}

// 卸载文件系统
void xxxx_kill_sb(struct super_block *sb)
{
    // 实现卸载文件系统的逻辑
}

// 文件系统类型结构体
struct file_system_type xxxx_fs_type = {
    .owner = THIS_MODULE,
    .name = "xxxx",
    .mount = xxxx_mount,
    .kill_sb = xxxx_kill_sb,
};

// 文件操作函数集
struct file_operations xxxx_file_ops = {
    .read = xxxx_read,
    .write = xxxx_write,
};

// inode 操作函数集
struct inode_operations xxxx_inode_ops = {
    .create = xxxx_create,
};

现在开始正式介绍文件系统

文件系统分为两类,一是虚拟文件系统,二是特定文件系统,我们上面实现的是虚拟文件系统

虚拟文件系统

简称VFS,作为内核子系统,提供了一个抽象层,使得用户空间程序可以通过统一的接口与不同类型的文件系统进行交互。VFS 提供了一组标准的文件系统操作接口,包括打开文件、关闭文件、读取文件、写入文件等等。这些操作会被映射到相应的文件系统实现上

简单来说就是每有一种类型的文件,只需要编写一个对应独立的文件系统模块,加载到内核中,当要访问这一类型的文件,就会自动的调用对应的代码,根据文件的类型来调用不同的代码

特定文件系统

指的是针对特定需求或特定硬件设计的文件系统,如 ext4、NTFS、FAT32 等。这些文件系统的实现与 VFS 有关,但它们提供了各自独特的特性和优化,以满足不同的使用场景和需求。特定文件系统的实现通常依赖于 VFS 提供的抽象接口,并在此基础上进行扩展和优化

实际的操作流程

当用户调用write(),首先会被一个通用系统调用sys_write()处理,sys_write()函数要找到fd所在的文件系统实际给出的是那个写操作,然后再执行该操作,

文件系统对象

  • 超级块对象,代表一个具体的已安装文件系统
  • 索引节点对象,他代表一个具体文件
  • 目录项对象,他代表一个目录项,是路径的一个组成部分
  • 文件对象,他代表由进程打开的文件

每个对象都包含一个操作对象

  • supere_operations对象
    • 包含内核针对特定文件系统所能调用的方法,比如write_inode()和sync_fs()等方法
  • inode_operations对象
    • 包括内核针对特定文件所能调用的方法,比如create()he link()等方法
  • dentry_operations对象
    • 包括内核针对特定目录所能调用的方法,比如d_compare()和d_delete()等方法
  • file_operations对象
    • 包括进程针对一打开文件所能调用的方法,比如read()和write()等方法

如果通用函数提供的基本功能无法满足需求,那么就必须使用特定文件系统的独有方法填充这些函数指针,十七指向文件系统实例

超级块对象

各种文件都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应基于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块,对于并非基于磁盘的文件系统,它们会在使用现场创建超级块并将其把保存到内存中

虚拟文件系统提供了一个抽象层,使得用户空间程序可以通过统一的接口与不同类型的文件系统进行交互,而超级块则是文件系统的关键数据结构之一,包含了文件系统的元数据信息

其实就是一个结构体 super_block

超级块操作

上面超级块对象中的s_op是超级块操作也是一个结构体,描述了vfs操作超级块对象的所有方法

该结构体中的每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的低层操作

超级块和文件系统的关系

打个比喻,超级块是路的基础设施,它定义了路的类型、规则和特征,而虚拟文件系统就像是车辆,它们在这些路上行驶,通过超级块来找到目的地,即文件系统中的特定文件或目录。就像车辆可以在不同类型的路上行驶一样,虚拟文件系统可以操作各种不同类型的文件系统,但都是依靠超级块提供的信息来导航和管理

举个例子

  1. 调用层次开始: 比如你运行了一个命令 mount /dev/sdb1 /mnt/usb,这个命令的目的是将 /dev/sdb1 这个设备挂载到 /mnt/usb 这个目录上。

  2. VFS 接管: 这个命令会调用操作系统提供的挂载命令,这个挂载命令实际上是一个用户空间的程序,会使用 VFS 提供的接口来完成挂载操作。

  3. 超级块检索: 在挂载之前,VFS 首先要确定 /dev/sdb1 对应的文件系统类型,它会检索已知的文件系统列表,并找到与 /dev/sdb1 相关的超级块。

  4. 超级块解析: VFS 从 /dev/sdb1 对应的超级块中获取文件系统的元数据信息,比如文件系统的类型、块大小、inode 结构等。

  5. 文件系统操作对象: 在执行挂载操作之前,VFS 还需要操作设备文件 /dev/sdb1 对应的设备驱动程序,这个驱动程序负责与实际的硬件设备进行通信,

    1. 超级块操作对象在这里的作用是指挂载操作所涉及的底层设备操作,这些操作包括了读取设备的分区表、读取超级块信息等。通过这些操作对象,VFS 可以与设备驱动程序交互,从而正确地执行文件系统挂载操作。

  6. 文件系统挂载: VFS 使用超级块中的信息来执行文件系统挂载操作,同时调用设备驱动程序来访问 /dev/sdb1 上的数据。

  7. 返回结果: 挂载操作完成后,VFS 将根据操作的结果向用户返回相应的信息,比如挂载成功或者挂载失败的提示。

索引节点对象

索引节点对象包含了内核在操作文件或目录时需要的全部信息,用于存储文件或目录的元数据信息,比如文件的大小、权限、所有者等

作用

  1. 存储文件元数据:索引节点中包含了文件的各种元数据信息,如文件大小、所有者、权限、创建时间、修改时间等。这些信息对于操作系统来说非常重要,因为它们决定了文件的访问方式和属性。

  2. 文件定位:索引节点中存储了文件的物理位置信息,比如文件数据块的地址或者间接指针等。这些信息帮助操作系统定位文件在存储介质上的具体位置,从而能够读取或写入文件的内容。

  3. 提高访问速度:通过索引节点,操作系统可以直接访问文件的元数据信息,而不需要遍历整个文件系统来查找。这样可以大大提高文件访问的速度和效率。

  4. 支持文件系统的逻辑结构:索引节点是文件系统的核心组成部分之一,它与其他数据结构(如目录项、数据块等)共同构成了文件系统的逻辑结构,从而实现了文件的组织和管理。

由inode结构体表示

一个索引节点代表文件系统中的一个文件,并且只有在访问的时候才会创建,还可以是块和字符设备、管道,在结构体中有一个共用体,所以只能在三者选一个

索引节点操作

在索引节点结构体中也有一个结构体描述了vfs操作索引节点对象的所有方法

索引节点和文件系统的关系

索引节点存储了文件的元数据信息,并指向文件的实际数据块,通过这些信息,文件系统可以管理和操作文件

假设要创建一个example.txt,并且向里面写入东西,然后保存

  1. 文件系统中创建索引节点: 当你创建 example.txt 文件时,文件系统会为这个文件创建一个对应的索引节点。这个索引节点包含了文件的元数据信息,比如文件大小、所有者、权限等,以及指向文件数据块的指针。

  2. 分配存储空间: 文件系统会根据文件大小为 example.txt 分配相应的存储空间,这些存储空间可以是连续的或者分散的,取决于文件系统的实现。

  3. 写入文件内容: 当你向 example.txt 文件中写入文本内容时,操作系统会将这些内容写入文件系统分配的数据块中。索引节点中的指针会指向这些数据块,以便操作系统可以在需要时读取文件内容。

  4. 更新索引节点: 每当你修改 example.txt 文件的元数据(比如修改文件权限或者所有者)时,文件系统会更新相应的索引节点。这样,文件系统可以保持文件的元数据与索引节点中的信息同步。

目录项对象

每个dentry代表路径中的一个特定部分,例如/bin/vi  ,/、bin、vi都属于目录项对象,前两个是目录,最后一个是文件

目录项也可包括安装点,在路径/mnt/cdrom/foo中,构成元素 /、mnt、cdrom、foo都属于目录项对象,vfs在执行目录操作时,会现场创建目录项对象

目录项对象由dentry结构体表示

其中目录对象有三种有效状态,被使用,未被使用,负状态

  • 一个被使用的目录项对应一个有效的索引节点(d_inode)并且表明该对象存在一个或者多个使用者(d_count>0),此时不能被丢弃
  • 一个未被使用的目录项对应一个有效的索引节点(d_inode),此时d_count为0,但目录项对象还是指向一个有效对象,会存放在缓存,下次使用方便,但也可以回收
  • 一个负状态的目录项没有对应的有效的索引节点(d_inode=null),因为索引节点已被删除或路径不正确,但目录项还是存在,就比如快捷方式,但目标路径下的文件不存在就没有作用,但如果把目标路径下的文件放回去又可以使用,有作用,但作用不大

目录项缓存

简单来说就是等于网页的缓存,第一次进入会久一点,后面就快了很多,就是因为他会将网页放入缓存,下一次可以快速进入,只是我们存放的不是网页,是目录项,这个缓存简称dcache

包括三个部分:被使用的目录项链表,最近被使用的双向链表,散列表和相应的散列函数

  • 被使用的目录项链表
    • 这个链表表示的是某个路径下的所有文件和目录,包括被使用过的和未被使用过的。它是对整个路径下文件和目录的完整描述,无论这些文件和目录是最近被使用过还是很久没有被使用过
  • 最近被使用的双向链表
    • 这个链表则表示的是某个路径下最近被访问或使用的文件和目录。它只包含了最近被使用过的文件和目录的信息,而不考虑其他未被使用过的文件和目录
    • 最新使用的目录项从链表头插入,所以越靠近头代表越新,
    • 该链表含有未被使用和负状态的目录项对象
  • 散列表和相应的散列函数
    • 散列表和相应的散列函数可以用于快速检索文件或目录
    • 将文件名作为键,将文件的存储位置(磁盘上的路径或索引)作为值,然后使用散列函数将文件名映射到散列表的某个槽位上
    • 当需要查找某个文件时,可以通过散列表快速定位到该文件的存储位置,而不需要遍历整个文件系统

目录项操作

dentry_operations结构体指明了VFS操作目录项的所有方法

目录项和文件系统的关系

目录项是文件系统中的一个组成部分,用于管理文件和目录的结构和属性

文件和目录被组织成一个层次结构,类似于树状结构。每个节点代表一个目录或文件,节点之间的关系由父目录和子目录/文件的关系构成。目录项则是用来描述每个文件或目录的信息的数据结构,通常包括文件名、大小、权限、创建时间、修改时间等属性

举例来说,假设有一个简单的文件系统,其中包含一个根目录和两个子目录以及一些文件

  • 根目录包含子目录 A 和 B,以及文件 X 和 Y。
  • 子目录 A 包含文件 A1 和 A2。
  • 子目录 B 包含文件 B1。

在这个文件系统中,每个文件和目录都有相应的目录项来描述它们的属性和位置。比如,根目录的目录项会包含指向子目录 A 和 B,以及文件 X 和 Y 的信息;子目录 A 的目录项则包含指向文件 A1 和 A2 的信息,以此类推

文件对象

文件对象代表进程已经打开的文件,是已打开文件在内存中的表示

由open创建,close销毁,因为多个进程可以打开同一个文件,所以存在多个对应的文件对象,但是对应的索引节点和目录项是唯一的

由file结构体表示

文件操作

由file_operations结构体表示

文件对象和文件系统的关系

文件对象是操作系统中用来表示打开的文件的数据结构,而文件系统是操作系统中用来管理存储在存储设备上的文件和目录的软件系统。

它们之间的关系可以理解为文件对象是文件系统的一个抽象,用于在程序执行时对文件进行操作和管理。

当程序需要对文件进行读取、写入或其他操作时,通常会通过操作系统的文件系统接口打开文件,这时操作系统会为该文件创建一个文件对象。文件对象包含了文件的相关信息,比如文件描述符、文件指针、文件状态等,以及与文件相关的缓冲区等。

举例来说,假设有一个简单的文本编辑器程序,它需要打开一个名为 "example.txt" 的文件进行编辑。当用户在程序中打开这个文件时,操作系统会为 "example.txt" 创建一个文件对象。这个文件对象会包含文件的描述符(用来标识文件)、文件指针(表示当前读写位置)、文件状态(比如是否可读、是否可写)、文件缓冲区等信息。

程序在编辑文件时,可以通过文件对象来进行读取、写入等操作,而文件系统负责管理文件的物理存储、访问权限等问题

总结

举例子来说明各个模块的作用和文件系统步骤

打开路径为 /home/user/document.txt 的文件

假设操作系统是基于Linux,文件系统类型为EXT4

1. 用户空间请求打开文件
  • 用户程序调用如 open("/home/user/document.txt", O_RDONLY) 的系统调用来请求读取文件。
2. VFS接收请求
  • VFS 角色:VFS 接收到打开文件的请求,并开始处理这个路径。
  • 操作:VFS 首先解析路径字符串,从根目录 / 开始,逐级解析直到 document.txt
3. 路径遍历与目录项的查找
  • 超级块的使用:VFS 从根文件系统的超级块中获取根目录的inode号。
  • 目录项角色:使用根目录的inode,系统读取根目录文件,查找 home 目录的目录项。
  • 操作:读取 home 的目录项获取到 home 目录的inode号,接着读取 home 目录,重复此过程找到 user,最后找到 document.txt 的inode号。
  • 超级块包括以下类型的信息
    • 文件系统类型:例如,EXT4、XFS等。
    • 文件系统大小:文件系统总体大小,通常以块的数量表示。
    • 块大小:文件系统中单个块的大小,如1KB、2KB、4KB等。
    • 空闲块和空闲inode列表:管理文件系统空间的数据结构,记录了哪些块和inodes是空闲的,可用于新文件或目录的创建。
    • 总块数和空闲块数:文件系统中块的总数及其空闲块的数量。
    • 总inode数和空闲inode数:inode的总数及其空闲inode的数量。
    • 挂载时间和最后写入时间:文件系统最后一次挂载和修改的时间。
    • 文件系统状态:如是否处于一致状态,是否需要检查等。
    • 超级块的备份:为了防止数据损坏,超级块的备份通常存储在文件系统的多个位置
  • 每个目录项一般包括以下信息
    • 文件名:目录中的文件或目录的名称。
    • inode号:文件名所对应的inode的编号。通过这个编号,系统可以找到存储文件元数据的inode结构。
    • 记录长度:目录项的长度,用于在目录文件中定位下一个目录项。
    • 文件类型:指示目录项代表的是常规文件、目录、符号链接还是其他类型的文件
4.索引节点 inode的加载
  • inode索引节点的角色:每个文件或目录都有一个inode索引节点,包含了文件的元数据(如权限、所有者、文件大小、数据块位置等)。
  • 操作:系统加载 document.txt 的inode。此inode包含文件的所有元数据,包括指向文件实际数据块的指针。
  • 这些元数据包括但不限于
    • 文件类型:例如,普通文件、目录、符号链接等。
    • 文件权限:文件的访问权限,如读、写和执行权限。
    • 所有者和组信息:文件的所有者ID和组ID。
    • 文件大小:文件的字节数。
    • 时间戳:文件的创建时间、最后访问时间和最后修改时间。
    • 链接计数:指向文件的硬链接数量。
    • 数据块指针:指向存储文件内容的数据块的指针或位置信息。
5. 文件对象的创建
  • 文件对象的角色:当文件被打开时,操作系统创建一个文件对象,该对象包括文件的当前状态(如文件指针的位置和打开模式)。
  • 操作:操作系统基于inode的信息和调用 open 的进程创建一个文件对象,并将此对象与进程的文件描述符表相关联。
  • 文件对象通常包含以下信息
    • 当前文件偏移量:标记在文件中的当前位置,用于读写操作。
    • 打开模式:文件是以只读、只写还是读写方式打开。
    • 引用的inode:包含了上述提到的各种文件元数据。
    • 安全和访问控制信息:确保文件操作符合安全政策。
6. 文件读取准备
  • 操作:用户程序可能接下来调用 read 系统调用,指定文件描述符和缓冲区地址。
  • 文件对象的使用:系统利用文件对象中的信息(如文件指针和inode索引节点信息)来定位文件数据,并开始实际的读取操作。
7. 从存储设备读取数据
  • 特定文件系统的角色:EXT4 文件系统负责处理具体的数据块读取请求,包括定位数据块和将数据块内容读入内存。
  • 操作:根据文件inode中记录的数据块地址,文件系统将数据从硬盘读入到指定的内存缓冲区。
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值