Android细数Linux和Android系统中的伪文件系统

文章目录

  • 前言
  • Linux伪文件系统
  • cgroupfs
    • Linux的cgroups
    • Android的cgroups
  • debugfs
  • functionfs(/dev/usb-ffs/adb)
    • functionfs 的引入
      • sysfs是什么
  • procfs(/proc)
  • pstore(/sys/fs/pstore)
  • selinuxfs(/sys/fs/selinux)
  • sysfs(/sys)
  • 参考

前言

做了好些年Android开发,你了解过Linux伪文件系统吗?
在 Linux 中,伪文件系统(Pseudo Filesystem)是一种特殊的文件系统,它不涉及具体的物理存储设备,而是提供了一种接口,允许用户和应用程序以文件和目录的形式访问内核和其他系统信息。这些文件和目录在运行时被内核动态地生成,因此被称为伪文件系统。
阅读本文,我们一起来学习Android中的伪文件系统的方方面面。

Linux伪文件系统

尽管不是严格意义上的Android 文件系统,但 Linux 内核中提供的其他三种值得注意的文件系统也在 Android 上被使用。

请注意术语“伪文件系统”, 这些文件系统没有一个会被存储到物理存储设备上去的,相反它们是直接由内核中的回调函数维护的。也就是说,当要访问其中的一个文件或目录时,某个对应的内核级处理函数就会被调用。

这也意味着,这些文件系统并不真正占用存储空间(在内核所用的内存中,用于存放对应的 inode 和目录数据结构的开销不算)。更进一步说,每次访问伪文件系统中的某个文件或目录都要去调用系统的回调函数,所以这些文件和目录反映出的总是最新的实时更新的数据。

由此可以推出,讨论(伪文件系统中) 文件的大小是没有意义的,这也就是为什么用“Is -l”命令看到的这些文件都是空的的原因。

注意,由于这些文件都是由内核代码(由整个内核,或者在某些情况下,由某些内核模块
导出的,所以根据内核版本的不同,这些文件有时也会有所不同,而且文件中的内容(特别是 sysfs 中文件的内容)也是和硬件紧密相关的。

大多数伪文件系统中的文件都是以只读权限创建的,因为其用途是提供实时的诊断信息,向用户态程序提供一种使之能够查看一些内核态中原本是不可访问的变量和结构的机制。

不过有些文件实际上是可写的,这提供了一种更有用的能力,使得我们在用户态中就能实时地对内核中的数据施加影响。

不像在某些基于注册表的系统中,修改配置需要对各种隐藏的而且通常是不公开文档的注册表键和值进行操作那样,对伪文件系统中文件进行的修改,会立即产生作用,而且还不需要系统重启。

cgroupfs

Linux的cgroups

Linux 内核提供了一种重要的资源控制机制,它就是 cgroups。

一个 cgroup 就是一个可容纳一个或多个线程的组容器 (container group),它能把组中的所有线程视为一个整体,统一进行操作和策略设置。

在 Linux 内核文档以中,提供了关于 groups 的相当详尽的文档。为了便于在用户态中能把各个线程放置到不同的组中,cgroups 通过伪文件系统把它们自己导出到用户态中,这使得只需对这些文件进行简单的“写”操作,就能把一个线程添加到某个组中。

Android的cgroups

尽管 cgroups 用途广泛,可以以各种不同方式使用,但是在 Android 中却是以一种非常受限的方式使用它的。

只用于 CPU 使用记时和线程调度,如图:
在这里插入图片描述

Bionic 在每个进程启动时,通过/acct 为其设置了CPU 使用记时器,因此它能应用到系统中所有的进程上。

/sys/fs/cgroup/memory 可以被 ActivityManager(通过 android.os.Process 及其JNI方法 setSwappiness)访问。

最后,是/dev/cpuctl 目录,尽管它位于/dev 目录下,但仍是个由 Android调度策略 (scheduling policy)安装的 cgroup 目录。

在/init 启动时,它会安装该目录并创建其下的各个子目录,即

  • dev/cpuctl/tasks 对应系统任务
  • /dev/cpuctl/apps/tasks 对应运行在前台的应用
  • /dev/cpuctl/apps/bg_non_interactive/tasks 对应运行在后台的应用。

并以此对各个组进行调度。

每个组都被分配到一个“共享”CPU 的数值,并被赋予了一个可以运行的时间上限。这样就能防止因有意或无意的错误操作,导致某个进程完全占据整个 CPU 运行时间的情况发生。

/dev/cpuctl的配置是在/init.rc 中完成的,下图是/init.rc脚本中安装cpuctl cgroups的部分代码:
在这里插入图片描述

debugfs

debugfs 文件系统是用于(输出)内核级的调试信息的。

驱动以及类似的子系统可以自由地把驱动的调试信息转储到这个文件系统中。和其他伪文件系统一样,如果文件系统已经被 mount了,大量的调试信息就能像读取其他文件那样被读取出来。

不过请注意,debugfs 没有必要一定要被 mount 到系统中,而且内核也可以被编译成不支持debugfs 的形式。如果内核支持 debugfs,它就可以用下面这行简单的命令行命令 (通常是在/init.hardware.rc中执行)mount 上来:

mount -t debugfs none /sys/kernel/debug

尽管理论上可以把它 mount 到任意一个 mount 点上,但是因为它实在是太有用了,所以通常都能在“/”目录中找到它的符号链接。

比如,在模拟器镜像中,就有一个“/d”符号链接指向 debugfs 的 mount 点。

debugfs 中的内容是完全由内核的版本以及内核中实现了哪些 debug 特性决定的。

下图给出的是在各个版本的Android 内核中常见的几个文件/目录,位于/sys/kernel/debug目录中:
在这里插入图片描述

functionfs(/dev/usb-ffs/adb)

在Android 系统中,USB 的功能经常会需要根据用户的选择 ,即是以

  • USB 调试、
  • 大容量存储介质
  • 还是以其他方式连接设备

用户的这一选择将通过 init动态地进行重新配置,它是由一个特定的“gadget”驱动进行控制的。

传统的驱动程序(Android L之前的内核版本)需要通过sysfs 导出它的重新配置参数。这一做法是它被认为非常臃肿,并需要改进的原因之一。

functionfs 的引入

在2010年某个时候,一个相对较新的特性被引入Linux 内核,由 Linux内核提供一个通用的文件系统,使驱动能够获取用户态空间中发出的对配置修改的请求。

这一文件系统可以被认为是对 sysfs 的一个补充,只不过设计后者的目的是向用户态输出一些内核中的变量和驱动信息,而前者的设计目的是从用户态获取输入。root 用户可以使用 mkdir(2)创建目录,这会引起内核创建相应的内核对象,这些对象可以在稍后由在用户态中发起的、对目录中的伪文件进行的 write(2)操作予以初始化。

在 Unix-like 操作系统中,系统调用 mkdir 是用于创建目录的。系统调用的编号(通常用括号中的数字表示,例如 mkdir(2))是指该系统调用在系统调用表中的编号。在这个上下文中,(2) 表示系统调用表中的编号,而不是 mkdir 函数本身的参数。

sysfs是什么

sysfs 是 Linux 内核中的一种虚拟文件系统,用于向用户空间提供有关内核、设备和驱动程序的信息。它通常被挂载到 /sys 目录下。

以下是 sysfs 的主要特点和用途:

  1. 提供内核参数和配置信息: /sys 目录下的文件和目录包含有关内核运行时参数、配置选项和其他内核信息的数据。

  2. 设备和驱动程序信息: sysfs 提供了有关系统上的设备和驱动程序的信息。例如,你可以在 /sys/class/sys/bus 下找到有关不同设备类别和总线的信息。

  3. 动态生成: sysfs 中的文件和目录是在运行时动态生成的,以反映系统的当前状态。这使得用户空间能够动态地获取有关系统状态的信息。

  4. 与用户空间的接口: 用户空间的应用程序和工具可以通过读写 /sys 下的文件来与内核进行通信和配置。这提供了一种方便的用户接口,用于访问和配置系统信息。

  5. 用于设备文件:/sys/class/sys/devices 下,你可以找到关于系统中设备的信息,这对于设备管理和识别非常有用。

在使用 sysfs 时,一些常见的目录结构包括:

  • /sys/class: 包含有关设备类别的信息,例如网络设备、块设备等。
  • /sys/devices: 包含有关系统中所有设备的信息。
  • /sys/bus: 包含有关总线的信息,如 USB 总线、PCI 总线等。
  • /sys/module: 包含有关内核模块的信息。

用户和开发者可以通过查看和操作 sysfs 中的文件来获取关于系统和设备的详细信息,这对于调试、配置和监控系统非常有用。

procfs(/proc)

procfs 文件系统名副其实,它提供了一个基于目录的观察系统中运行的进程的方式。这个想法最初是出现在 Plan 9 操作系统上的,随后 Linux 马上吸收并改造了它,使之能提供大量关于进程、线程以及其他全方位的系统诊断信息。

不论在/proc 中提供太多东西到底是不是件好事,这都不妨碍它成为一个极为重要的文件系统。许多 Linux 实用程序(如 top、netstat、lsof和ifconfig)以及许多 Android 工具(如 procrank、librank) 都把它作为诊断信息的来源,没有它就不能运行。Linux 在 pro(5)的手册页中记录了相当详细的相关信息,并保持它不断更新。

pstore(/sys/fs/pstore)

pstore机制是 Linux 的一个内核特性(在3.5版时引入),它允许内核把部分物理内存(RAM)单独划为 persistent store 区。它在需要应用抓取内核崩溃(panic)时的数据非常有用。

内核崩溃是指内核中出现了内存破坏的情况。由于这类情况发生之后可能会影响到文件系统的执行逻辑。所以,这时任何向文件系统写入数据的操作,都可能使情况进一步恶化,甚至导致文件系统也被破坏掉。

UNIX 系统通常会把内核崩溃时的数据转储到 swap 分区里去,但这也不能保证重启之后数据一定都还在。而且 Android 是没有 swap 分区的,因此,剩下的唯一靠谱的解决方案是专门为此留出一部分物理内存,即一个专用的内存区域persistent store,让内核把它崩溃时的数据,至少是 bare minimum转储到这里来。

随后内核自动执行一次热重启(也就是中间不切断电源的重启),这也就意味着物理内存中的信息不会在重启的过程中而丢失。在重启的过程中,内核会去检查 persistent store 区,看看里面是不是留有上一次系统运行遗留下来的数据。如果有的话,它就会通过/sys/fs/pstore,让我们能在用户态中读取到这些数据。

在较旧版本的Android 系统中,这一功能是利用了一个被称为“RAM console”的 Android特有的特性(也算是某种 Android 内核技巧)提供的。追溯它的实现代码,我们发现它还是位于/init.rc 中。这段代码会从/proc/apanic console 和/proc/apanic threads 中获取数据,并把它们写到/data/dontpanic 中(这一大圈绕的,简直就是“银河系漫游指南”)。随着 pstore 功能的到来,这一实现方式已经被放弃,转而使用/sys/fs/pstore。

selinuxfs(/sys/fs/selinux)

和debugfs 一样,SELinuxFS 传统上也是 mount 在/sys 下,但却不是 sysfs 文件系统的一部分。这个文件系统是专供 SELinux 使用的,其中存储了与安装策略 (installed policy) 相关的文件。

这个文件系统中最重要的文件是 policy 和伪文件 enable。

其中,policy 提供了加载(编译,可以使用的二进制可执行文件的格式)安全策略。

enable 则用来切换这些策略是否要被强制执行。

sysfs(/sys)

从重要性上说,它的重要性仅次于procfs。sysfs 是在 Linux 内核版本 2.6 中作为对 procfs 的补充而被引入的,为了能把/proc 里的东西整理得井井有条些,把与硬件和模块相关的配置文件移到一个单独的目录中去,并让目录的层次也更清晰些。
你会发现,/sys 是个“整洁的”目录,伪文件被分门别类地放在各自所属的子目录中的地方。/sys的子目录如图:

在这里插入图片描述

不同的设备之间硬件配置的差异非常大,所以各种不同设备中的 sysfs 里存放的文件间的差别也非常大。Android 框架之所以能够免于受到因不同厂商选用不同的五花八门的硬件而带来的麻烦,完全要感谢硬件抽象层(HAL,Hardware Abstraction Layer),它是由/system/lib/libhardware.so及其插件构成的,因为硬件抽象层把对不同特定文件的调用封装成了更为统一的 API。

参考

《最强Android书:架构大剖析》