声明
- Android系统中有很多分区,每个分区内的文件系统一般都不同的,使用ADB进入系统/目录下可发现挂载这很多的目录,不同的目录中可来自不同的分区及文件系统;
- 此篇参考了一些书籍及论文,仅供学习使用。只介绍大概理论,不深究,就足够用了。
1 概述
“伪文件系统”:指的是这些文件系统没有一个会被存储到物理存储设备上去的,相反它们是直接由内核中的回调函数维护的。当要访问其中的一个文件或目录时,某个对应的内核级处理函数就会被调用。这也意味着,这些文件系统并不真正占用存储空间(在内核所用的内存中,用于存放对应的 inode 和目录数据结构的开销不算)。更进一步说,每次访问伪文件系统中的某个文件或目录都要去调用系统的回调函数,所以这些文件和目录反映出的总是最新的实时更新的数据。由此,讨论伪文件系统中文件的大小是没有意义的,这也就是为什么用 “ls -” 命令看到的这些文件都是空的(或者在旧版本的内核里,是 4KB(即一个内存页大小)的整数倍)的原因。
注意,由于这些文件都是由内核代码(由整个内核,或者在某些情况下,由某些内核模块) 导出的,所以根据内核版本的不同,这些文件有时也会有所不同,而且文件中的内容(特别是 sysfs 中文件的内容) 也是和硬件紧密相关的。
大多数伪文件系统中的文件都是以只读权限创建的,因为其用途是提供实时的诊断信息,向用户态程序提供一种使之能够查看一些内核态中原本是不可访问的变量和结构的机制。不过有些文件实际上是可写的,这提供了一种更有用的能力,使得我们在用户态中就能实时地对内核中的数据施加影响。不像在某些基于注册表的系统中,修改配置需要对各种隐藏的而且通常是不公开文档的注册表键和值进行操作那样,对伪文件系统中(允许修改的)文件进行的修改,会立即产生作用,而且还不需要系统重启。事实上这也就是Android 中的 init.rc 脚本的重要功能之一。
2. cgroupfs
Linux 内核提供了一种重要的资源控制机制,它就是 cgroups。一个 cgroup 就是一个可容纳一个或多个线程的组容器 (container group),它能把组中的所有线程视为一个整体,统一进行操作和策略设置。在 Linux 内核文档中,提供了关于 groups 的相当详尽的文档。为了便于在用户态中能把各个线程放置到不同的组中,cgroups 通过伪文件系统把它们自己导出到用户态中——这使得只需对这些文件进行简单的“写”操作,就能把一个线程添加到某个组中。
尽管 cgroups 用途广泛,可以以各种不同方式使用,但是在 Android 中却是以一种非常受限的方式使用它的——只用于 CPU 使用计时和线程调度(如下面所示):
hammerhead:/ # mount|grep cgroup
none on /acct type cgroup (rw,relatime,cpuacct)
none on /dev/cpuctl type cgroup (rw,relatime,cpu)
Bionic 在每个进程启动时,通过/acct 为其设置了 CPU 使用计时器(因此它能应用到系统中所有的进程上)。/sys/fs/cgroup/memory 可以被 ActivityManager(通过 android.os.Process 及其JNI方法 setSwappiness)访问。最后,是 /dev/cpuctl 目录,尽管它位于 dev 目录下,但仍是个由 Android 调度策略安装的 cgroup 目录。在 /init 启动时,它会安装该目录并创建其下的各个子目录—— /dev/cpuctl/tasks 对应系统任务,/dev/cpuctl/apps/tasks 对应运行在前台的应用,/dev/cpuctl/apps/bg_non_interactive/tasks 对应运行在后台的应用,并以此对各个组进行调度。每个组都被分配到一个“共享” CPU 的数值,并被赋予了一个可以运行的时间上限。这样就能防止因有意或无意的错误操作,导致某个进程完全占据整个 CPU 运行时间的情况发生。/dev/cpuctl 的配置是在 /init.rc 中完成的,相关代码如下代码所示:
# Create cgroup mount points for process groups
mkdir /dev/cpuctl
mount cgroup none /dev/cpuctl cpu
chown system system /dev/cpuctl
chown system system /dev/cpuctl/tasks
chmod 0666 /dev/cpuctl/tasks
write /dev/cpuctl/cpu.rt_period_us 1000000
write /dev/cpuctl/cpu.rt_runtime_us 950000
3. debugfs
debugfs 文件系统是用于(输出)内核级的调试信息的。驱动以及类似的子系统可以自由地把驱动的调试信息转储到这个文件系统中。和其他伪文件系统一样,如果文件系统已经被 mount了,大量的调试信息就能像读取其他文件那样被读取出来。
注意,debugfs 没有必要一定要被 mount 到系统中,而且内核也可以被编译成不支持debugfs 的形式。如果内核支持 debugfs,它就可以用下面这行简单的命令行命令 (通常是在/init.hardware.rc中执行) mount 上来:
mount -t debugfs none /sys/kernel/debug
hammerhead:/ # mount |grep debug
debugfs on /sys/kernel/debug type debugfs (rw,seclabel,relatime)
尽管理论上可以把它 mount 到任意一个 mount 点上,但是因为它实在是太有用了,所以通常都能在“/”目录中找到它的符号链接。比如,在模拟器镜像中,就有一个“/d”符号链接指向 debugfs的mount 点。
hammerhead:/ # ls d -al
lrwxrwxrwx 1 root root 17 1970-01-01 08:00 d -> /sys/kernel/debug
debugfs 中的内容是完全由内核的版本以及内核中实现了哪些 debug 特性决定的。下表给出的是在各个版本的 Android 内核中常见的几个文件/目录。
文件/目录 | 用途 |
---|---|
binder | 提供通过Android IPC(进程间通信)机制中的 binder 方式传递的大量数据 |
tracing | 提供由 Linux 内核的 ftrace 机制产生的海量的调试和跟踪信息 |
wakeup-sources | 内核级的定时器,用在Android 系统或驱动程序防止设备休眠 |
4. functionfs(/dev/usb-ffs/adb)
在 Android 系统中,USB 的功能经常会需要根据用户的选择 (是以USB 调试、大容量存储介质、以其他方式连接设备,用户的这一选择将通过 init 传递进来) 动态地进行重新配置,它是由一个特定的 “gadget” 驱动进行控制的。
传统的驱动程序(Android L之前的内核版本)需要通过 sysfs 出它的重新配置参数。这做法是它被认为非常臃肿,并需要改进的原因之一。
functionfs 的引入:在2010 年某个时候,一个相对较新的特性被引入 Linux内核一一由 Linux内核提供一个通用的文件系统,使驱动能够获取用户态空间中发出的对配置修改的请求。这一文件系统可以被认为是对 sysfs 的一个补充,只不过设计后者的目的是向用户态输出一些内核中的变量和驱动信息,而前者的设计目的是从用户态获取输入。root 用户可以使用 mkdir(2)创建目录,这会引起内核创建相应的内核对象,这些对象可以在稍后由在用户态中发起的、对目录中的伪文件进行的 write(2) 操作予以初始化。
我的Nexus 5 LineageOS 14.1手机中并未采用:
hammerhead:/ # mount|grep function
1
5. procfs(/proc)
procfs 文件系统名副其实一一它提供了一个基于目录的观察系统中运行的进程的方式。Linux实现它,使之能提供大量关于进程、线程以及其他全方位的系统诊断信息。
不论在 /proc 中提供太多东西到底是不是件好事,这都不妨碍它成为一个极为重要的文件系统。许多 Linux 实用程序(top、netstat、lsof 和 ifconfig)以及许多 Android 工具(procrank、librank)都把它作为诊断信息的来源,没有它就不能运行。
6. 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,让我们能在用户态中读取到这些数据。
我的Nexus 5 LineageOS 14.1手机中并未采用:
hammerhead:/ # mount|grep pstore
1
7. selinuxfs(/sys/fs/selinux)
和 debugfs 一样,SELinuxFS 传统上也是 mount 在/sys 下,但却不是 sysfs 文件系统的一部分。这个文件系统是专供 SELinux 使用的,其中存储了与安装策略 (installed policy) 相关的文件。关于SELinux可参考此篇:Android 系统的安全性分析(4)–Linux层面上的安全措施。
这个文件系统中最重要的文件是 policy 和伪文件 enable。其中,policy 提供了加载(编译,可以使用的二进制可执行文件的格式)安全策略,enable 则用来切换这些策略是否要被强制执行(其实 getenforce/setenforce 工具集也是通过它来实现相关功能)
hammerhead:/ # getenforce
Enforcing
hammerhead:/ # echo 0 > /sys/fs/selinux/enforce
hammerhead:/ # getenforce
Permissive
8. sysfs
从重要性上说,它的重要性仅次于procfs。sysfs 是在 Linux 内核版本 2.6 中作为对 procfs 的补充而被引入的,为了能把 /proc 里的东西整的有条理些,把与硬件和模块相关的配置文件移到一个单独的目录中去,并让目录的层次也更清晰。
/sys 是个整洁的目录,伪文件被分门别类地放在各自所属的子目录中,这些子目录如下表所示:
不同的设备之间硬件配置的差异非常大,所以各种不同设备中的 sysfs 里存放的文件间的差别也非常大。Android 框架之所以能够免于受到因不同厂商选用不同的五花八门的硬件而带来的麻烦,完全要感谢硬件抽象层(HAL,Hardware Abstraction Layer,它是由/system/lib/libhardware.so及其插件构成的),因为硬件抽象层把对不同特定文件的调用封装成了更为统一的 API。
其他一些设备的标准化程度要更高一些,比如,位于/sys/devices/system/cpu/cpu#/cpufireq目录中的 CPU 主频调节器(用来调整主频) 数据,以及供震动器器使用的/sys/class/timed output/vibrator 目录。
执行如下命令,手机震动器会持续震动3秒。
hammerhead:/ # echo 3000 > /sys/class/timed_output/vibrator/enable
hammerhead:/ #