为什么要有文件系统?
操作系统能够更方便的操作硬盘 同时给用户提供一层抽象,方便用户操作
为什么要有文件系统?
操作系统能够更方便的操作硬盘 同时给用户提供一层抽象,方便用户操作
VFS与文件系统
- 为什么要引入虚拟文件系统?
假设我们的硬盘用的文件系统和U盘用的文件系统不同,在这两个文件系统之间拷贝文件是很复杂的
引入虚拟文件系统就是为了方便操作
VFS中的数据结构
超级块对象—对应具体的文件系统的操作
从硬盘中读出来的 来初始化super_block
超级块是一个真实的在硬盘存在的东西,存储了文件系统的核心信息,试想我们格式化硬盘的时候是在做什么?
struct super_block
- 超级块操作 super_operations
索引节点对象–inode
inode是Linux文件系统中用于存储文件和目录元数据的数据结构
对于Unix风格的好说 直接读硬盘
非Unix风格的 想办法转换
struct inode 描述了文件相关的信息
这个结构体还挺有意思的,一个大的文件在物理硬盘上,可能并不是连续在数据块上的- 索引节点操作 inode_operation
- 软链接和硬链接的区别?
- 硬链接:
- 硬链接可以认为是一个指针,指向文件索引节点inode的指针,系统并不为它重新分配inode。每添加一个一个硬链接,文件的链接数就加1。
- 硬连接之间没有主次之分,删除某个硬链接,只是将其从目录的数据块中删除相关信息,并且文件链接数减一。不会从inode表中删除inode,除非只剩下一个链接数。
- 硬链接无法跨分区 跨设备 也不能创建对文件夹的硬链接
ln file.txt hardlink.txt
- 软链接:
- 软链接是真的又创建了一个inode节点 但这个文件只是存储了路径信息
- 删除源文件会导致软链接的失效
- 软链接可以跨分区 跨设备 应可以创建对文件夹的
ln -s /path/to/target.txt mylink.txt
- 硬链接:
目录项对象–查找相关的时候有用
struct dentry
- 目录项状态
- 目录项缓存
- 目录项相关操作
文件对象—用户角度查看文件时的结构体
struct file- 文件相关的操作 file_operation
其他相关的
- struct file_system_type : 描述相关的结构体
- struct vfsmount: 挂载点
进程相关的结构体
我们常交互的fd—就在这里
通过fd 找到对应的struct file, 再找到对应的file_operation
- fd与inode的关系?
每个进程会维护自己的fd数组 同一个文件(inode)在不同的进程都被打开的时候fd可能不一样
此时为了防止并发访问带来的问题 可以使用文件锁等操作
- fd与inode的关系?
read/write系统调用的处理过程
read调用
read()–>sys_read()–>vfs_read()–>file.f_op.read()(此时vfs的任务结束 交给了具体文件系统去操作)
这里的文件系统会查看页缓存中是否有最新的 如果有就返回页缓存数据,如果没有那就得靠驱动程序了,驱动程序申请页缓存并读取数据放进去—>最终 copy_to_user()拷贝到内存空间- 如果数据在页缓存中,read系统调用只需要1次拷贝。
- 如果数据不在页缓存中,read系统调用需要2次拷贝。
write调用
write调用和read调用的过程基本一致
write()–>sys_write()–>vfs_write()–>file.f_op.write()–>数据写入页缓存–>在合适的时机写入硬盘mmap为什么比read/write要性能更好?
read()/write()频繁地系统调用还是会带来一定的性能开销。系统调用会不停地切换CPU和操作系统的工作模式,数据也在用户空间和内核空间之间不停地复制,而mmap减少了复制的次数,也不再经过系统调用
r当我们完成映射后,我们向虚拟地址写入–>触发缺页异常–>分配物理页–>radix树记录页缓存–>合适实际把内容写回硬盘
常用的命令
- ls
- cat
- mount —挂载文件系统 嵌入式的话挂载nfs文件系统常用
- sudo chmod --这这太常用了
- touch
- mv / rm / cp
- grep / find 指令 这俩指令是用来搜索的 具体想要了解可以搜搜
grep “pattern” file.txt
grep -r “pattern” /path
find /path -name “*.txt” # 按名称查找
IO多路复用
五个IO模型
阻塞IO
进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据
非阻塞IO
进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞;进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。
信号驱动IO
当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。
异步IO
当进程发起一个IO操作,进程返回(不阻塞),但也不能返回果结;内核把整个IO处理完后,会通知进程结果。如果IO操作成功则进程直接获取到数据
和信号驱动IO的区别在于:信号驱动IO是线程处理IO操作的 但是异步IO内核全做了
IO多路复用
允许一个进程同时监控多个文件描述符(如套接字、管道等),并在其中任何一个文件描述符就绪时进行处理。
IO多路复用在等待数据准备好 和 把数据从内核态拷贝到用户态这俩个阶段都是阻塞的
select / poll / epoll的区别
IO多路复用的目的就是减少创建线程/进程的开销select
假设监视的IO口有数据可读/可写了需要遍历fd_set找到到底是谁 可能是一个可能是多个
- 缺点: 需要遍历fd_set 这开销不小
能监听的数量有上限 fd_set集合的大小是1024
需要把fd_set 集合从用户态拷贝到内核态
select的底层是维护了一个数组
selct最大的毛病就是虽然返回值是就绪的文件描述符的数量,但是还是要遍历所有的文件描述符才能找到是哪些
- 缺点: 需要遍历fd_set 这开销不小
poll
poll的底层是链表 所以没有和监听数量的限制
但是本质上还是要遍历所有文件描述符 而且还是需要把数据从用户态拷贝到内核态epoll—epoll的高效体现在哪里
- epoll使用了mmap(events结构体) 减少了拷贝带来的开销,而每次调用select/poll都需要拷贝数据到内核
- epoll支持水平触发和边缘触发
- epoll的底层用到了红黑树机制
- 事件驱动机制:epoll可以只遍历就绪文件符所在的链表 但是poll/select必须遍历所有查找
epoll_wait的返回值为当前就绪的文件描述符的数量;而且把就绪的放在了events中,要遍历的就少很多
水平触发与边缘触发
- 水平触发:如果就绪了就会一直通知程序处理(select/poll/epoll)
如果它返回了某个文件描述符(fd)可读,但你没有及时处理,下次调用 select 时,它仍然会立即返回这个文件描述符 - 边缘触发(epoll)
只有状态变化时通知一次。不处理拉到
- 水平触发:如果就绪了就会一直通知程序处理(select/poll/epoll)
RTT的文件系统
RTT文件系统和Linux的文件系统思路还是类似的有时间就研究一下