Android IO流程你真的清楚了吗|硬核科普

前言

最近在看《Linux内核设计与实现》的时候,就想着要不把知识串联一下吧。

聊什么呢?今天先来聊聊 Android IO 的调用链路。

说起 IO,这可真是一个很复杂的过程,里面涉及了很多内容,先是软件,最后到硬件,用一张图来表示一下吧:

IO结构

本文打算简单得和大伙讨论一下 IO 的流程。

一、应用层

作为应用开发者,我们通常是 IO 发起点,比如用户说这本小说很好看,我要下载到本地,或者,这张图拍的不错,分享给你看一下。

虽然这些都是常见的 IO 场景,但是你知道有哪些 IO 吗?

1. IO的分类

通常去使用 IO 的时候,我们会有很多种选择,常见的有:

  1. 缓冲与非缓冲 IO
  2. 直接与非直接 IO
  3. 阻塞与非阻塞 IO
  4. 同步与异步 IO

大家平时可能也就听过缓冲 IO 和 阻塞 IO,这些可能是我们平时开发可能涉及到的。

1.1 缓冲和直接

前两种分类都是使用缓存的。

缓冲是针对标准库的

Linux 标准库定义了很多操作系统的基础服务,比如输入/输出、字符串处理等等。Android 操作系统的标准库是 Bionic,它可是应用层联系内核的桥梁,我们也可以通过 NDK 访问 Bionic。

使用标准库进行 IO 我们称为缓冲 IO,我们读文件的时候,经常遇到,读完一行才会让输出,在 Android 内部也做了类似的处理。

直接是针对内核的

使用 Binder 跨进程传递数据的时候,需要将数据从用户空间传递到内核空间,非直接 IO 也这样,内核空间会多做一层页缓存,如果做直接 IO,应用程序会直接调用文件系统。

缓冲和非直接 IO 就像 IO 调度的一级和二级缓存,为什么要做这么多缓存呢?因为操作磁盘本身就是消耗资源的,不加缓存频繁 IO 不仅会耗费资源也会耗时。

1.2 阻塞和异步

同步和异步我想大家都了解什么意思。

阻塞 IO指的是当用户执行读写的时候,线程会一直阻塞,数据准备和将数据拷贝到用户进程都是阻塞的

阻塞IO

Java 中的 NIO 是非阻塞 IO,当用户发起读写的时候,线程不会阻塞,之后,用户可以通过轮询或者接受通知的方式,获取当前 IO 调度的结果:

非阻塞IO

即使是非阻塞 IO,对于读数据来说,也只有准备数据的过程是异步,将数据从内核拷贝到用户进程这个过程还是同步的。所以非阻塞 IO 不能算是真正意义上的异步 IO

真正的异步 IO 应该是这样的:

异步IO

准备数据和将数据拷贝从内核到用户进程都应该是异步的,当收到通知的的时候,我们已经可以在应用进程使用数据了。

2. IO流程

作为应用层开发,大家做 IO 的场景并不多,最多也就是使用 BufferedInputStreamBufferedOutputStream 读写文件,至于 NIO ,那就更少见了。

我们了解一下阻塞 IO 的读调用流程。

二、sysCall系统调用

应用层调完了,下面会直接进入内核吗?

除去直接 IO,大部分都不会!用户空间和内核之间隔着一个系统调用(sysCall),它的作用如下:

  1. 给用户空间提供抽象的访问硬件的接口:比如申请系统资源、操作设备读写等
  2. 保证系统的安全和稳定:内核可以对用户进程的访问做出一些裁决,防止用户进程做出一些危害系统的事情

毕竟内核很复杂,抽象出通用的接口,可以防止用户空间的进程僭越,获取到它不该获取的内容。

为了能够让应用进程联系上内核,它会通过一个软中断,通知内核,我想调用内核中 sysCall 中的读接口。

对于读 IO,系统调用中有一个 sys_read 方法与之对应,内核收到通知执行该方法的时候,就会执行虚拟文件系统的 read 方法。

三、虚拟文件系统

文件系统实在是太多了,比如我手机用户空间的文件系统是 f2fs,系统空间的文件系统是 ext4。对于应用程序来说,它就想调用个读方法,不想管你手机的底层文件系统是什么!

虚拟文件系统就是来干这活的,它可以屏蔽具体的文件系统,定义了一组所有文件系统都支持的数据结构和标准接口。这样,应用层的程序员只需了解 VFS 提供的统一接口就行。

虚拟文件系统常被称为 VFS(Virtual File System),下称 VFS。

1. VFS结构

VFS 采用的是面向对象的设计思路,它常常有下列的对象(C语言中的结构体)构成:

VFS结构

这些对象构成了基本的虚拟文件系统。

不过,光有这些对象可不行,VFS 还得知道如何操作它们,所以,每个对象中还存在对应的操作对象:

  • super_operation 对象:内核针对超级块所能调用的方法
  • inode_operation 对象:内核针对索引结点所能调用的方法
  • dentry_operation 对象:内核针对目录项所能操作的方法
  • file_operation 对象:内核针对进程中打开的文件所能操作的方法

大伙最熟悉的应该是文件,这是我们能够在进程中实实在在能够操作的,比如,在文件的 file_operation 中,就有我们熟悉的读、写、拷贝、打开、写入磁盘等方法。

不知道大伙儿有没注意到,我特意标注了超级块和索引节点存在于内存和磁盘,而目录项和文件只存在于内存。

我的理解是对于磁盘,索引节点已经足够记录文件信息,并不需要目录项再来记录层级关系;而对于内存来说,为了节省内存,只会把需要用到的文件和目录项所用到的索引节点加入内存,文件系统只有被挂载的时候超级块才会被加入到内存中。

目录项、索引节点、文件和超级块结构图:

VFS关系

上面的结构图还有几点要注意一下:

  1. 目录项不等于目录这个概念,对于 /home/pic/a.jpg 来说,根目录 / 、home 目录、pic 目录 和 a.jpg 都属于目录项
  2. 每个目录项都会持有索引节点的指针
  3. 索引节点包含内核在操作文件需要的全部信息,比如存在磁盘的位置等
  4. 进程中打开的文件持有目录项

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系统

每一个 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,它们只是一块儿很小的芯片:

UFS3.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!

总结

整个流程简要的用一张图来表示:

IO流程

因为我对内核也不是特别熟,文中难免有不对的地方,欢迎在评论区指正,如果觉得本文不错,「点赞」是最好的肯定!

文章参考:

《一口气搞懂「文件系统」,就靠这 25 张图了》
《Android开发高手课》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 转载于浅谈Linux内核IO体系之磁盘IO[https://zhuanlan.zhihu.com/p/963915...
    爱健身的兔子阅读 637评论 0 4
  • 本文从应用开发者角度给予一些IO知识介绍,因篇幅问题,很多内容只做了粗略介绍,因IO涉及知识体系众多,读者若感兴趣...
    Anderson大码渣阅读 1,948评论 0 4
  • IO 优化不就是不在主线程读写大文件吗,真的只有这么简单吗? IO 基础 IO流程:应用程序 发送逻辑IO命令给文...
    今阳说阅读 1,714评论 0 7
  • 计算机主要由CPU、总线、I/O设备、内存、硬盘等组成,见下图: cpu由控制器(CU)和运算器(ALU)组成,相...
    舒小贱阅读 2,025评论 0 1
  • 硬盘物理结构 硬盘内部主要部件为磁盘盘片、传动手臂、读写磁头和主轴马达。实际数据都是写在盘片上,读写主要是通过传动...
    滩主阅读 3,607评论 0 5