现在我在板子上想读写一个文件。这个动作好像极其苦难。
一: Linux kernel
Linux Kernel 只是Linux系统的一部分,它管理所有的系统硬件设备,通过系统调用 向其他程序提供interface。
Linux kernel 主要分为了五大子系统:
分别是
1. Process Scheduler : 进程管理
2. Memory Manager:内存管理
3. VFS(VirtualFile System):虚拟文件系统
4. Network:网络子系统
5. IPC(Inter-Process Communication):进程间通信。
Linux下一切解文件
而对于一个文件的IO操作也是属于VFS的一部分。
二:VFS
VFS 介绍
VFS(Virtual File System,虚拟文件系统)的目的是为了在Linux系统中引入一套通用的文件模型,使其能够表示并操作所有支持的文件系统。
在Linux中,我们不能使用一个特定的函数来对所有设备执行相同的操作,比如read()和ioctl()。相反,我们可以使用指向特定对象的函数指针来执行这些操作。举个例子,如果我有两本书,一本是《哈利·波特》,另一本是《猎人笔记》,它们分别拥有自己的write操作。
当我调用write的时候内核会去钩住 猎人笔记的 write 来实现这个操作,他们都会有各自的write。
当用户层调用write()时,内核会根据文件描述符索引到相应的文件对象,然后执行相应的write操作。这可能涉及到设备驱动程序中的钩子函数,它们会执行特定设备的相关操作,比如read()和write()。
因此,Linux并不是使用一个特定的read函数来完成所有设备的读取,而是通过系统调用和文件对象中的函数指针来调用特定设备的函数,以实现对不同设备的操作。
所以并不是linux的一个特定的read函数能完成所有的设备的读取,而是系统调用会去查询到特定的read,让read能调到特定的函数。
其实通用模型就是面向对象编程的思路,上层通过钩子函数调用底层的各个对象的类来调用类中的成员函数。每个设备都被抽象为一个对象,而每个对象都有其特定的成员函数来处理对应的操作。这种设计使得Linux系统能够灵活地适配不同的设备,并利用对象的多态性来执行特定设备的操作。
虽然它是使用C实现的,但是仍然不妨碍它是面向对象的编程思路。
在VFS中,如果通用的函数无法满足特定文件系统的需求,那么就需要由实际文件系统提供的独特方法来填充这些函数指针。这就涉及到编写特定文件系统的驱动程序。
对于特定的设备或文件系统,需要编写专门的驱动程序来提供其所需的功能。这些驱动程序会实现特定的文件操作函数,比如read()、write()、ioctl()等,以及其他必要的功能。这些函数会被赋值给VFS中对应文件对象的函数指针,从而在文件操作时调用到特定文件系统的实现。
因此,驱动程序的编写就是为了填充VFS中的函数指针,以便实现特定设备或文件系统的功能,确保Linux系统能够灵活地适配各种设备和文件系统。
还好我只是想使用通用的write 和read来对文件进行操作不然,还需要去编写驱动,来填充特定的程序指针。
VFS组成:
VFS一共有4个主要的对象类型:
超级块对象:代表具体的已安装的文件系统。
索引节点对象:代表具体的文件。
目录对象:代表的目录项,是路径的组成。
文件对象:代表进程中打开的文件。
这张图展示了 3个进程来调用文件对象的一个过程。三个进程打开一个文件,每个进程都使用自己的文件对象,但是需要目录对象,目录对象指向索引对象,而索引对象表示超级块对象。如果我们站在,用户层的角度来看,我们首先看到的就是file_operations(文件对象)。下面这个类会表明一些最基本的文件对象的操作。
struct file_operations {
struct module *owner;
...
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
....
....
int (*open) (struct inode *, struct file *);
....
int (*release) (struct inode *, struct file *);
....
} __randomize_layout;
当然为了,我更好的去理解,我删去了很多其他的成员函数。比如
int (*mmap) (struct file *, struct vm_area_struct *);
作用用户的我来说我最先看到的是open,所以以open为例,所谓开始
int (*open) (struct inode *, struct file *);
调用关系如下
open() -> do_sys_open() -> file_operation.open()
当然比如我们编写驱动的时候,不需要实现的成员函数我们可以设置成空。不过常见的open() read() write()这样的操作,我们总是需要实现的用来满足用户的请求。
对于open来说我们所需要的就是索引节点对象还有 file打开文件对象。
下篇文章会用以描述open的过程。希望自己能学明白。