首页 > 其他分享 >一文带你了解Android IO的底层原理

一文带你了解Android IO的底层原理

时间:2023-06-19 11:07:30浏览次数:41  
标签:缓存 文件系统 索引 内核 IO Android VFS 底层


前言

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

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

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

一文带你了解Android 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指的是当用户执行读写的时候,线程会一直阻塞,数据准备和将数据拷贝到用户进程都是阻塞的

一文带你了解Android IO的底层原理_android_02

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xgMThsLu-1659597815636)(https://upload-images.jianshu.io/upload_images/25149744-592c9a6d9c60508a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

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

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

一文带你了解Android IO的底层原理_java_03

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

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语言中的结构体)构成:

一文带你了解Android IO的底层原理_文件系统_04

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

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

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

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KuNFEWmB-1659597815637)(https://upload-images.jianshu.io/upload_images/25149744-116d0d3981c48b19.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

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

  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 的系统结构来讲:

一文带你了解Android IO的底层原理_java_05

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

一文带你了解Android IO的底层原理_Android_06

闪存是一种非易失性存储器,即使掉电了,数据也不会丢。闪存的存储单元从小到大有:

  • 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 管组成,大概是这样:

一文带你了解Android IO的底层原理_Android_07

对于 SLC(存储1bit)来说:

  • 如果需要1,在 P 极施加一个电压,将电子吸出储存单元
  • 如果需要0,需要在顶层的控制极施加一个电压,让电子吸回存储单元

这就构成了数据存储的最小单位,0和1!

总结

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

一文带你了解Android IO的底层原理_开发语言_08

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

文章参考:

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

作者:九心

标签:缓存,文件系统,索引,内核,IO,Android,VFS,底层
From: https://blog.51cto.com/u_16163480/6511601

相关文章

  • Android 架构之 MVI 完全体 | 重新审视 MVVM 之殇,PartialChange & Reducer 来拯救
    作者:唐子玄MVI架构有三大关键词:“唯一可信数据源”+“单向数据流”+“响应式编程”,以及一些关键概念,比如Intent,State。理解这些概念之后,能更轻松地阅读本文。(强烈建议从第一篇开始阅读)引子在上一篇中,用MVI重构了“新闻流”这个业务场景。本篇在此基础上进一步拓展,引入MVI中......
  • 干了8年Android开发熬到年薪40万,突然接到被辞退消息,应该怎么办?
    0136岁Android开发,为公司工作8年,昨天HR说公司不准备续约前天晚上,有个读者给我留言,讲述了他自己比较气愤的一件事,感觉自己委屈又不值。这位朋友不愿意透露姓名,就叫他H先生吧。H先生是典型的学霸,大学也是211的牌子,又是计算机专业。研究生毕业以后就进入了现在的公司当了一名Android......
  • 方法对了,你做1年Android开发能顶别人做10年
    前几天后台有读者问我这样的问题。他在一家互联网公司工作3年了,每天都很忙,事情又多又杂。本想着学习多一些东西也不是坏事,可到头来一无所获,什么都没学会,满腔的热情也被消磨得差不多。三天两头动辞职的念头,但又不知道自己还能做什么,甚至开始后悔:如果当初选择另一个行业,是不是就会好......
  • 做Android开发,你后悔过吗?
    有同学跟我说,编程太难了,总是有学不完的技术、框架,新技术也层出不穷,马上三十了,还有各种学不完的东西,后悔做程序员了编程对我来讲,还难吗我主业是做Android的。我刚学编程的时候,觉得难点在于众多语法/API的学习,以及抓耳挠腮、苦思冥想捉bug的无奈。但无论语法多么复杂难用,终究会有学会......
  • 阿里钉钉Android实习面试也太太太太难了吧,对算法的要求堪比字节
    本人研究生在读,在2月26日找了师兄内推阿里钉钉团队,28号接到了约1面的电话。幸好我提前准备了一个多月的样子,刷面试题、刷LeetCode(面了之后才觉得自己刷少了),对于我这样一个实习生来说题目还是有些偏难,不过在4月20号终于拿到意向书了,听内推人说阿里实习面试没有rank,可能单纯就是流程......
  • 大厂技术总监总结的Android Framework开发笔记火了!知乎已1.7k赞!不吃透都对不起他
    为什么要学AndroidFramework?想要成为一名优秀的Android开发,就需要有一个完备的知识体系,AndroidFramework的知识是很重要的一个组成部分,他广泛的应用在各个领域。像掉帧监控,函数插装,慢函数检测,ANR监控,启动监控,都需要对Framework有比较深入的了解。只有这样才能知道怎么去做监......
  • 又一开源项目爆火于GitHub,Android高级插件化强化实战
    一、插件化起源插件化技术最初源于免安装运行Apk的想法,这个免安装的Apk就可以理解为插件,而支持插件的app我们一般叫宿主。想必大家都知道,在Android系统中,应用是以Apk的形式存在的,应用都需要安装才能使用。但实际上Android系统安装应用的方式相当简单,其实就是把应用Apk......
  • Kotlin版本的WanAndroid项目实战(三):Kotlin的集合
    集合概述学习方法:扫描一遍下述表格中集合相关的有哪些操作,具体使用时再可以详细去查构造集合Kotlin里面添加了可变集合的概念,目前只是一种接口级别的限制,底层实现还是可变的集合,不是线程安全的,还是可以通过Java代码修改内部元素迭代器(1)Iterable接口的继承者(包括Set与......
  • 从入门到精通,Android Jetpack 架构实战教程合集
    Jetpack是Google推出的一些库的集合,包含组件、工具、架构方案等,其优势众多:可以减少空指针异常崩溃、内存泄漏,为开发出健壮且流畅的程序提供强力保障;可以消除大量重复样板式的代码,加速Android的开发进程;可以统一开发模式,抛弃传统的MVC,MVP…对于谷歌而言,AndroidJetpack是他......
  • 阿里P7架构师整理:最新Android 开发源码精编内核解析
    做Android开发多年,我们都深知阅读源码的重要性,阅读源码可以帮助我们:①在通用型基础技术中提高技术能力,凸显出自己的技术实力;②在重点领域打造自己的亮点,参与技术栈的运维,积累丰富的使用经验,成为团队的核心骨干;③从优秀的源码中学习设计模式的应用,和有用的编码技巧。但是平时读源码......