Sdcard vs External storage
在Android手机的早期,几乎所有设备都依赖于使用microSD卡进行存储。这是由于当时的手机出厂时内部存储容量很小。但是,至少与内部闪存可以读取/写入数据的速度相比,用于存储应用程序的SD卡通常无法提供出色的用户体验。因此,越来越多地将SD卡用于外部数据存储,
由于SD卡作为外部存储设备的泛滥,Android的存储命名约定基于以下事实:每个设备都有一个实际的物理microSD卡插槽。但是,即使在不包含SD卡插槽的设备上,/ sdcard标签仍用于指向实际的内部存储芯片。更令人困惑的事实是,既使用物理SD卡又使用大容量存储芯片进行存储的设备通常会根据SD卡来命名其分区。例如,在这些设备中,/ sdcard挂载点将引用实际的内部存储芯片,而类似/ storage / sdcard1的引用将引用物理外部卡。因此,即使实际上将microSD卡视为外部存储设备,命名约定也导致“ SDCard”在任何实际使用物理卡的情况下都存在很长时间。由于将应用程序数据及其媒体隔离在两个分区之间,因此与存储的混淆也使应用程序开发人员感到头疼。
早期内部存储芯片的存储空间不足,导致用户沮丧地发现他们无法再安装应用程序(由于/ data分区已满)。同时,其更大容量的microSD卡被降级为仅保留媒体(例如照片,音乐和电影)。
Nexus One仍然是唯一带有microSD卡插槽的Nexus设备。,现在只有一个统一的分区来存储所有应用程序数据和媒体,即/ data分区。现在曾经被称为/ sdcard挂载点的现在只是指位于数据分区-/ data / media / 0中的虚拟文件系统。
为了保持兼容性并减少混乱,Google仍然使用此虚拟的“ sdcard”分区来保存媒体。但是,既然此“ sdcard”虚拟分区实际上位于/ data中,则其中存储的所有内容都将计入内部存储芯片的存储空间。因此,由OEM来考虑分配给应用程序(/ data)与媒体(/ data / media)多少空间。
Google希望制造商能效仿他们,并摆脱SD卡。幸运的是,随着时间的流逝,电话制造商能够以更高的容量采购这些组件,同时又保持了成本效益,因此对SD卡的需求开始减少。但是命名约定一直在坚持,以减少开发人员和OEM不得不做出调整的工作量。当前,当我们提到“外部存储”时,我们指的是以下两种情况之一:实际的可移动microSD卡或/ data / media中的虚拟“ SDCard”分区。
实际上
,其中的后者
实际上是内部存储
,但Google的命名约定因用户可以访问此数据(例如插入计算机时)而有所区别。
Android虚拟文件系统的历史
现在,“ sdcard”被视为虚拟文件系统,这意味着它可以被格式化为Google想要的任何文件系统。从Nexus S和Android 2.3开始,Google选择将“ sdcard”格式化为VFAT(虚拟FAT)。当时这样做很有意义,因为安装VFAT将使几乎所有计算机都可以访问手机中存储的数据。但是,此初始实施存在两个主要问题。
第一个主要涉及最终用户。为了将设备连接到计算机,您将使用USB Mass Storage Mode传输数据。但是,这要求Android设备先卸载虚拟分区,然后计算机才能访问数据。如果用户想在插入电源的情况下使用其设备,则许多东西将显示为不可用。
MTP的引入解决了第一个问题。插入电源后,计算机会将您的设备视为“媒体存储”设备。它从您的手机请求文件列表,并且MTP返回计算机可以从设备下载的文件列表。当请求删除文件时,MTP发送命令从存储中删除请求的文件。与实际安装“ sdcard”的USB大容量存储模式不同,MTP允许用户在插入电源后继续使用其设备。此外,Android手机上的文件系统对于计算机识别设备上的文件不再重要。
其次,事实是VFAT没有提供Google所需的那种强大的权限管理。早期,许多应用程序开发人员会将“ sdcard”视为其应用程序数据的转储场,而对存储文件的位置没有统一的认识。许多应用程序会简单地使用应用程序名称创建一个文件夹并将其文件存储在该文件夹中。
当时几乎所有的应用程序都需要WRITE_EXTERNAL_STORAGE权限才能将其应用程序文件写入外部存储。但是,更麻烦的是,几乎每个应用程序都还需要READ_EXTERNAL_STORAGE权限-仅读取它们自己的数据文件!这意味着应用程序可以轻松访问存储在外部存储中任何位置的数据,并且这种权限通常由用户授予,因为许多应用程序都需要它才能正常运行。
Google显然认为这是有问题的。权限管理的整个思想是隔离应用程序可以访问和不能访问的内容。如果几乎每个应用程序都被授予对潜在敏感用户数据的读取访问权限,则该权限毫无意义。因此,谷歌认为他们需要一种新的方法。因此FUSE coming.在andorid4.4 中引入了 FUSE。
Google开始使用FUSE在“ sdcard”虚拟分区上模拟FAT32。通过sdcard程序调用FUSE以模拟FAT-on-sdcard样式的目录权限,应用程序可以开始访问其存储在外部存储中的数据,
而无需任何权限
。实际上,从API级别19开始,不再需要READ_EXTERNAL_STORAGE来访问位于外部存储器上的文件-只要FUSE守护程序创建的数据文件夹与应用程序的软件包名称匹配即可。安装应用程序时, FUSE可以处理外部存储上文件的所有者,组和模式。
FUSE与内核模块不同,因为它允许非特权用户编写虚拟文件系统。Google实施FUSE的原因很简单-它做了他们想要的事情,但是,很明显,FUSE的开销正在导致性能下降等问题。
然而由于大量的性能问题,在Android O上,FUSE替换为“ SDCardFS ”。SDCardFS 是三星提出并开发的,其SDCardFS基于WrapFS。此内核解决方案像FUSE一样模拟FAT32,但是减少了I / O开销,双重缓存以及提到的其他问题。
实现内核内解决方案所面临的最大挑战是如何将包名称映射到应用程序ID,这对于应用程序包在不需要任何权限的情况下访问外部存储中自己的数据是必需的。
用SDCardFS 替换FUSE将减少大量的I / O开销,消除双重缓存,并解决一些与其FUSE仿真FAT32有关的晦涩问题
然而在android 11 上为了更好的权限控制,为了更好的支持 Scoped Storage Android 11 又用FUSE 替换了SDCardFS。只是有了一些新的变化。
Android 11 中的FUSE
在 /sdcard 目录中的FS 大多用FUSE. 如:
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 Alarms
drwxrwx— 5 root everybody 3488 2020-07-08 17:28 Android
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 Audiobooks
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 DCIM
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 Documents
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 Download
drwxrwx— 3 root everybody 3488 2020-07-08 17:28 Movies
drwxrwx— 3 root everybody 3488 2020-07-08 17:28 Music
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 Notifications
drwxrwx— 3 root everybody 3488 2020-07-08 17:28 Pictures
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 Podcasts
drwxrwx— 2 root everybody 3488 2020-07-08 17:28 Ringtones
除了,
/sdcard/Android/data/
/sdcard/Android/obb/
Google 建议用MediaProvider来进行相关文件的操作如果APP大量用到相关的文件并且对性能敏感。 APP 改!!!!!!
/dev/fuse on /mnt/user/0/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
/dev/fuse on /mnt/installer/0/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
/dev/fuse on /mnt/androidwritable/0/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
/dev/fuse on /storage/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
(/storage/emulated/0 ) sdcard -> /storage/self/primary| primary ->/storage/emulated/0
/data/media on /storage/emulated/0/Android/data type sdcardfs
/data/media on /storage/emulated/0/Android/obb type sdcardfs
FUSE 问题
在Android中,“ sdcard”用户空间守护程序在启动时利用FUSE将/ dev / fuse挂载到模拟的外部存储目录。此后,sdcard守护程序将轮询FUSE设备以查找来自内核的任何未决消息。
Problem #1 – I/O Overhead
假设我们创建了一个名为“ test.txt”的简单文本文件,并将其存储在/sdcard/test.txt中(让我提醒您,实际上是/data/media/0/test.txt,当前用户是设备上的主要用户)。如果我们想读取(命令目录)此文件,我们希望系统发出3条命令:打开,读取然后关闭。
但是,由于文件位于由sdcard守护程序管理的外部存储上,因此需要执行许多其他操作。
这3个单独命令中的每个命令
实际上都需要执行8个附加步骤:
-
用户空间应用程序发出系统调用,该调用将由内核中的FUSE驱动程序处理(我们在第一个strace输出中看到它)
-
内核中的FUSE驱动程序向用户空间守护程序(sdcard)通知新请求
-
用户空间守护程序读取/ dev / fuse
-
用户空间守护程序解析命令并识别文件操作(例如,打开)
-
用户空间守护程序向实际文件系统(EXT4)发出系统调用
-
内核处理物理数据访问并将数据发送回用户空间
-
用户空间修改(或不修改)数据,并将其通过/ dev / fuse再次传递给内核
-
内核完成了原始的系统调用并将数据移动到实际的用户空间应用程序(在我们的示例cat中)
仅运行一个I / O命令就好像
有很多
开销。进行I / O测试:一种涉及复制大文件,另一种涉及复制许多小文件。比较处理这些操作的FUSE(在安装为FAT32的虚拟分区上)与内核(在格式化为EXT4的数据分区上)的速度,并发现FUSE确实造成了巨大的开销。
复制了725MB的文件。FUSE实施将大文件传输的速度降低了17%。
在第二项测试中,复制了10,000个文件-每个文件5KB。在这种情况下,FUSE实现的速度要慢40秒钟以上才能复制基本上50MB的数据。
在现实世界中,这种性能下降会影响存储在外部存储中的
所有
文件。这意味着诸如Maps在/ sdcard上存储大文件的Music应用程序,存储大量音乐文件的Music应用程序,Camera应用程序和照片等应用程序。正在执行的涉及外部存储的任何I / O操作都将受到FUSE开销的影响。但是I / O开销并不是FUSE唯一的问题。
Problem #2 – Double Caching
数据缓存对于提高数据访问性能非常重要。通过将必要的数据片段存储在内存中,Linux内核能够在需要时快速调用该数据。但是由于实现FUSE的方式,Android可以存储所需缓存的两倍。
预计将在缓存中保存一个10MB的文件,恰好为10MB,但是将缓存大小增加了约20MB。这在具有较少RAM的设备上是有问题的,因为Linux内核存储使用页面缓存将数据存储在内存中。用以下方法测试了此双重缓存问题:
-
创建一个已知大小的文件(用于测试,大小为10MB)
-
将其复制到/ sdcard
-
删除页面缓存
-
快照页面缓存的使用
-
读取测试文件
-
拍摄页面缓存使用情况的另一个快照
在测试之前,内核正在使用241MB的内存来进行页面缓存。读取测试文件后,预计将看到251MB用于页缓存。可是,发现该内核使用了263MB的页面缓存-大约是预期的两倍。发生这种情况的原因是,首先由最初发出I / O调用(FUSE)的用户应用程序缓存数据,然后由sdcard守护程序(EXT4 FS)缓存数据。
Problem #3 – Incomplete Implementation of FAT32
由于使用FUSE模拟FAT32而引起的另外两个问题在Android社区中鲜为人知。
第一个涉及不正确的时间戳记。如果您曾经传输过文件(例如照片),并且发现时间戳不正确,那是因为Android实现了FUSE。这个问题已经存在多年了。更具体地说,问题涉及
utime()
系统调用,该调用允许您更改文件的访问和修改时间。不幸的是,以标准用户身份对sdcard守护程序进行的调用没有执行此系统调用的适当权限。有一些解决方法,但是它们要求您具有root访问权限。
对于使用诸如smartSD卡之类的业务的企业,下一个问题更值得关注。在FUSE之前,应用程序制造商可以监控O_DIRECT标志,以便与卡中的嵌入式微控制器通信。使用FUSE,开发人员只能访问文件的缓存版本,而看不到微控制器发送的任何命令。