前言
最近在看《Linux 内核设计与实现》的时候,就想着要不把常识串联一下吧。
聊什么呢?明天先来聊聊 Android IO 的调用链路。
说起 IO,这可真是一个很简单的过程,外面波及了很多内容,先是软件,最初到硬件,用一张图来示意一下吧:
本文打算简略得和大伙讨论一下 IO 的流程。
一、应用层
作为利用开发者,咱们通常是 IO 发动点,比方用户说这本小说很难看,我要下载到本地,或者,这张图拍的不错,分享给你看一下。
尽管这些都是常见的 IO 场景,然而你晓得有哪些 IO 吗?
1. IO 的分类
通常去应用 IO 的时候,咱们会有很多种抉择,常见的有:
- 缓冲与非缓冲 IO
- 间接与非间接 IO
- 阻塞与非阻塞 IO
- 同步与异步 IO
大家平时可能也就听过缓冲 IO 和 阻塞 IO,这些可能是咱们平时开发可能波及到的。
1.1 缓冲和间接
前两种分类都是应用缓存的。
缓冲是针对规范库的 。
Linux 规范库定义了很多操作系统的根底服务,比方输出 / 输入、字符串解决等等。Android 操作系统的规范库是 Bionic,它可是应用层分割内核的桥梁,咱们也能够通过 NDK 拜访 Bionic。
应用规范库进行 IO 咱们称为缓冲 IO,咱们读文件的时候,常常遇到,读完一行才会让输入,在 Android 外部也做了相似的解决。
间接是针对内核的 。
应用 Binder 跨过程传递数据的时候,须要将数据从用户空间传递到内核空间,非间接 IO 也这样,内核空间会多做一层页缓存,如果做间接 IO,应用程序会间接调用文件系统。
缓冲和非间接 IO 就像 IO 调度的一级和二级缓存,为什么要做这么多缓存呢?因为操作磁盘自身就是耗费资源的,不加缓存频繁 IO 不仅会消耗资源也会耗时。
1.2 阻塞和异步
同步和异步我想大家都理解什么意思。
阻塞 IO 指的是当用户执行读写的时候,线程会始终阻塞, 数据筹备和将数据拷贝到用户过程都是阻塞的 :
Java 中的 NIO 是非阻塞 IO,当用户发动读写的时候,线程不会阻塞,之后,用户能够通过轮询或者承受告诉的形式,获取以后 IO 调度的后果:
即便是非阻塞 IO,对于读数据来说,也只有筹备数据的过程是异步, 将数据从内核拷贝到用户过程这个过程还是同步的。所以非阻塞 IO 不能算是真正意义上的异步 IO。
真正的异步 IO 应该是这样的:
筹备数据和将数据拷贝从内核到用户过程都应该是异步的,当收到告诉的的时候,咱们曾经能够在利用过程应用数据了。
2. IO 流程
作为应用层开发,大家做 IO 的场景并不多,最多也就是应用 BufferedInputStream
和 BufferedOutputStream
读写文件,至于 NIO,那就更少见了。
咱们理解一下阻塞 IO 的读调用流程。
二、sysCall 零碎调用
应用层调完了,上面会间接进入内核吗?
除去间接 IO,大部分都不会!用户空间和内核之间隔着一个零碎调用(sysCall),它的作用如下:
- 给用户空间提供形象的拜访硬件的接口 :比方申请系统资源、操作设施读写等
- 保证系统的平安和稳固 :内核能够对用户过程的拜访做出一些裁决,避免用户过程做出一些危害零碎的事件
毕竟内核很简单,形象出通用的接口,能够避免用户空间的过程僭越,获取到它不该获取的内容。
为了可能让利用过程分割上内核,它会通过一个软中断,告诉内核,我想调用内核中 sysCall 中的读接口。
对于读 IO,零碎调用中有一个 sys_read
办法与之对应,内核收到告诉执行该办法的时候,就会执行虚构文件系统的 read
办法。
三、虚构文件系统
文件系统切实是太多了,比方我手机用户空间的文件系统是 f2fs,零碎空间的文件系统是 ext4。对于应用程序来说,它就想调用个读办法,不想管你手机的底层文件系统是什么!
虚构文件系统就是来干这活的,它能够屏蔽具体的文件系统,定义了一组所有文件系统都反对的数据结构和标准接口。这样,应用层的程序员只需理解 VFS 提供的对立接口就行。
虚构文件系统常被称为 VFS(Virtual File System),下称 VFS。
1. VFS 构造
VFS 采纳的是面向对象的设计思路,它经常有下列的对象(C 语言中的构造体)形成:
这些对象形成了根本的虚构文件系统。
不过,光有这些对象可不行,VFS 还得晓得如何操作它们,所以,每个对象中还存在对应的操作对象:
super_operation
对象:内核针对超级块所能调用的办法inode_operation
对象:内核针对索引结点所能调用的办法dentry_operation
对象:内核针对目录项所能操作的办法file_operation
对象:内核针对过程中关上的文件所能操作的办法
大伙最相熟的应该是文件,这是咱们可能在过程中实实在在可能操作的,比方,在文件的 file_operation
中,就有咱们相熟的读、写、拷贝、关上、写入磁盘等办法。
不晓得大伙儿有没留神到,我特意标注了超级块和索引节点存在于内存和磁盘,而目录项和文件只存在于内存。
我的了解是对于磁盘,索引节点曾经足够记录文件信息,并不需要目录项再来记录层级关系;而对于内存来说,为了节俭内存,只会把须要用到的文件和目录项所用到的索引节点退出内存,文件系统只有被挂载的时候超级块才会被退出到内存中。
目录项、索引节点、文件和超级块结构图:
下面的结构图还有几点要留神一下:
- 目录项不等于目录这个概念,对于 /home/pic/a.jpg 来说,根目录 /、home 目录、pic 目录 和 a.jpg 都属于目录项
- 每个目录项都会持有索引节点的指针
- 索引节点蕴含内核在操作文件须要的全副信息,比方存在磁盘的地位等
- 过程中关上的文件持有目录项
2. VFS 中的缓存
联合本文中的第一张图,咱们会发现,VFS 有目录项缓存、索引节点缓存和页缓存,目录项和索引节点咱们都晓得什么意思,那页缓存呢?
页缓存是由 RAM 中的物理页组成的,对应着 ROM 上的物理地址。咱们都晓得,当初支流 Android 的 RAM 访问速度高达是 8.5 GB/S,而 ROM 的访问速度最高只有 6400 MB/S,所以拜访 RAM 的速度要远远快于 ROM,页缓存的目标也在于此。
当发动一个读操作的时候,内核会首先查看须要的数据是否在页缓存,如果在,间接从内存中读取,咱们称之为缓存命中;如果不在,那么内核在读取数据的时候,将读到的数据放入页缓存,须要留神的是,页缓存能够存入全副文件内容,也能够仅仅存几页。
3. IO 流程
通过零碎调用,读 IO 进入了 VFS。
就去找文件对象(VFS 中的),通过文件对象的 file_operation
对象,调用 read
办法,传入读取的数据量。不过 read
办法也是找到文件对象对应的目录项,目录项又找到索引节点,毕竟,只有索引节点晓得文件存在哪儿?
通过索引节点,内核就能惟一确定一个文件,而后在页缓存中寻找是否有本人须要的数据,找到就间接返回。
没找到就去进行下一步的操作。
四、文件系统
VFS 定义了文件系统的对立接口,具体的实现了交给了文件系统,超级块外面的数据如何组织、目录和索引构造如何设计、怎么调配和清理数据,这都是设计一个文件系统必须思考的!
说白了,文件系统就是用来治理磁盘里的长久化的数据的,对于 Android 来说,最常见的就是 ext4 和 f2fs。
1. 文件系统构造
因为文件系统是 VFS 的具体实现,所以同样有目录项、索引节点和超级块,下面的图片用来形容文件系统也同样适宜。
拿早起 ext2 的系统结构来讲:
每一个 ext2 都由大量的块组组成,每个块组的构造就跟下面的目录项和索引节点中的图一样。能够看到,在 inode 列表,存在着很多数据块,块是内存中最小的寻址单元,见于磁盘中的章节,个别能够设置带大小为 2kb – 64kb 之间。
2. 文件系统的不同点
尽管大部分的文件系统也都有超级块、索引节点和数据块,然而各个文件系统的实现却大不相同,这就导致了他们的侧重点也不一样。拿 ext4 和 f2fs 来讲:
- ext4 间断读取大文件更强,占用的空间更小
- f2fs 随机 IO 更快
说白了,也就是它们对于闲暇空间调配和已有的数据管理形式不统一,不同的数据结构和算法导致了不同的后果。
3. IO 流程
这里的 IO 流程其实跟 VFS 差不多,毕竟文件系统是 VFS 的具体实现。
五、块 IO 层
Linux 上面有两大根本设施类型:
- 块设施: 可能随机拜访固定大小数据片的硬件设施 ,硬盘和闪存(上面介绍)就是常见的块设施
- 字符设施: 字符设施只能依照字符流的形式被有序拜访 ,比方键盘和串口
这两个设施的区别就是是否可能随机拜访。拿属于字符设施的键盘来说,当咱们输出 Hello World
的时候,零碎必定不能够先失去失去 eholl wrodl
,这样的话,输入就乱套了。而对于闪存来说,经常是看完这个这些数据库组成的图片,又要读距离很远的数组块的小说内容,所以读取的块在磁盘上必定不是间断的。
因为内核治理块设施切实太简单了,所以就呈现了治理块设施的子系统,就是下面说的文件系统。
1. 块设施构造
块设施中罕用的数据管理单位:
- 扇区:设施的最小寻址单元
- 块:文件系统的最小寻址单元,数倍大于扇区
- 片段:由数百至数千的块组成
因为 Linux 中经常用的硬盘,这里我有点疑难,这里的治理单位是否和上面闪存治理单位统一?
2. IO 过程
如果以后有 IO 操作,内核会建设一个 bio 构造体的根本容器,它是由多个片段组成,每一个片段都是一小块间断的内存缓冲区。
之后,内核会将这些 IO 申请保留在一个 request_queue 的申请队列中。
如果依照 IO 申请产生的程序发向块设施,性能必定难以承受,所以内核会依照磁盘地址对进入队列之前提交的 IO 申请做合并与排序的预操作。
六、磁盘
挪动设施中罕用的长久化存储是 Nand 闪存,UFS 又是 Nand 闪存中的佼佼者,其特点是速度更快、体积小和更省电。
当今 Android 旗舰机基本上标配 UFS 3.1,它们只是一块儿很小的芯片:
闪存是一种非易失性存储器,即便掉电了,数据也不会丢。闪存的存储单元从小到大有:
- Cell(单元):是闪存存储的最小单位,依据存储的数量能够分为 SLC(1bit/Cell)、MLC(2bit/Cell)、TLC(3bit/Cell)和 QLC(4bit/Cell)
- Page(页):由大量的 Cell 形成,每个 Page 的大小通常是 16 kb,它是闪存可能读取的和写入的最小单位
- Block(块):每个块由数百至数千的 Page 组成
- Plane(面):Plane 由数百至数千的 Black 组成
- Die(逻辑单元):每个 Die 由一个至多个 Plane,是闪存中能够执行命令或者回报状态的最小单元
对于每个 Cell 来说,是由一品种 NMOS 的双层浮栅 MOS 管组成,大略是这样:
对于 SLC(存储 1bit)来说:
- 如果须要 1,在 P 极施加一个电压,将电子吸出贮存单元
- 如果须要 0,须要在顶层的管制极施加一个电压,让电子吸回存储单元
这就形成了数据存储的最小单位,0 和 1!
总结
整个流程简要的用一张图来示意:
因为我对内核也不是特地熟,文中不免有不对的中央,欢送在评论区斧正,如果感觉本文不错,「点赞」是最好的必定!
文章参考:
《一口气搞懂「文件系统」,就靠这 25 张图了》
《Android 开发高手课》
本文由博客一文多发平台 OpenWrite 公布!