关于android:Android-IO流程你真的清楚了吗|硬核科普

34次阅读

共计 4455 个字符,预计需要花费 12 分钟才能阅读完成。

前言

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

聊什么呢?明天先来聊聊 Android 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 指的是当用户执行读写的时候,线程会始终阻塞, 数据筹备和将数据拷贝到用户过程都是阻塞的

Java 中的 NIO 是非阻塞 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 还得晓得如何操作它们,所以,每个对象中还存在对应的操作对象:

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

大伙最相熟的应该是文件,这是咱们可能在过程中实实在在可能操作的,比方,在文件的 file_operation 中,就有咱们相熟的读、写、拷贝、关上、写入磁盘等办法。

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

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

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

下面的结构图还有几点要留神一下:

  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 都由大量的块组组成,每个块组的构造就跟下面的目录项和索引节点中的图一样。能够看到,在 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 公布!

正文完
 0