9.2 Vold的原理与机制分析

Vold是Volume Daemon的缩写,它是Android平台中外部存储系统的管控中心,是一个比较重要的进程。虽然它的地位很重要,但其代码结构却远没有前面的Audio和Surface系统复杂。欣赏完Audio和Surface的大气磅礴后,再来感受一下Vold的小巧玲珑也会别有一番情趣。Vold的架构可用图9-1来表示:

9.2 Vold的原理与机制分析 - 图1

图 9-1 Vold架构图

从上图中可知:

Vold中的NetlinkManager模块(简称NM)接收来自Linux内核的uevent消息。例如SD卡的插拔等动作都会引起Kernel向NM发送uevent消息。

NM将这些消息转发给VolumeManager模块(简称VM)。VM会对应做一些操作,然后把相关信息通过CommandListener(简称CL)发送给MountService,MountService根据收到的消息会发送相关的处理命令给VM做进一步的处理。例如待SD卡插入后,VM会将来自NM的“Disk Insert”消息发送给MountService,而后MountService则发送“Mount”指令给Vold,指示它挂载这个SD卡。

CL模块内部封装了一个Socket用于跨进程通信。它在Vold进程中属于监听端(即服务端),而它的连接端(即客户端)则是MountService。它一方面接收来自MountService的控制命令(例如卸载存储卡、格式化存储卡等),另一方面VM和NM模块又会通过它将一些信息发送给MountService。

相比于Audio和Surface系统,Vold的架构确实比较简单,并且Vold和MountService所在的进程(这个进程其实就是system_server)在进行进程间通信时,也没有利用Binder机制,而是直接使用了Socket,这样,在代码量和程序中类的派生关系上就会简单不少。

9.2.1 Netlink和Uevent介绍

在分析Vold的代码前,先介绍一下Linux系统中的Netlink和Uevent。

1.Netlink介绍

Netlink是Linux系统中用户空间进程和Kernel进行通信的一种机制,通过这种机制,位于用户空间的进程可以接收来自Kernel的一些信息(例如Vold中用到的USB或SD的插拔消息),同时应用层也可通过Netlink向Kernel发送一些控制命令。

目前,Linux系统并没有为Netlink单独设计一套系统调用,而是复用了Socket的操作接口,只是在创建Socket时会有一些特殊的地方。Netlink的具体使用方法在进行代码分析时再来了解,读者目前只需知道,通过Netlink机制应用层,可接收来自Kernel的消息即可。

2.Uevent介绍

Uevent和Linux的Udev设备文件系统及设备模型有关系,它实际上就是一串字符串,字符串的内容可告知发生了什么事情。下面通过一个实例来直观感受Uevent。

在SD卡插入手机后(我们这里以SD卡为例),系统会检测到这个设备的插入,然后内核会通过Netlink发送一个消息给Vold,Vold将根据接收到的消息进行处理,例如挂载这个SD卡。内核发送的这个消息,就是Uevent,其中U代表User space(应用层空间)。下面看SD卡插入时Vold截获到的Uevent消息。在我的G7手机上,Uevent的内容如下,注意,其中//号或/**/号中的内容是为方便读者理解而加的注释:


[—>SD卡插入的Uevent消息]

//mmc表示MultiMedia Card,这里统称为SD卡。

add@/devices/platform/msm_sdcc.2/mmc_host/mmc1/mmc1:c9f2/block/mmcblk0

ACTION=add//add表示设备插入,另外还有remove和change等动作。

//DEVPATH表示该设备位于/sys目录中的设备路径。

DEVPATH=/devices/platform/msm_sdcc.2/mmc_host/mmc1/mmc1:c9f2/block/mmcblk0

/*

SUBSYSTEM表示该设备属于哪一类设备,block为块设备,磁盘也属于这一类设备,另外还有

character(字符)设备等类型。

*/

SUBSYSTEM=block

MAJOR=179//MAJOR和MINOR分别表示该设备的主次设备号,二者联合起来可以标识一个设备。

MINOR=0

DEVNAME=mmcblk0

DEVTYPE=disk//设备Type为disk。

NPARTS=3//这个表示该SD卡上的分区,我的SD卡上有三块分区。

SEQNUM=1357//序号


由于我的SD卡上有分区,所以还会接收到和分区相关的Uevent。简单看一下:


[—>SD卡插入后和分区相关的Uevent消息]

add@/devices/platform/msm_sdcc.2/mmc_host/mmc1/mmc1:c9f2/block/mmcblk0/mmcbl k0p1

ACTION=add

//比上面那个DEVPATH多了一个mmcblk0p1。

DEVPATH=/devices/platform/msm_sdcc.2/mmc_host/mmc1/mmc1:c9f2/block/mmcblk0/mmcblk0p1

SUBSYSTEM=block

MAJOR=179

MINOR=1

DEVNAME=mmcblk0p1

DEVTYPE=partition//设备类型变为partition,表示分区。

PARTN=1

SEQNUM=1358


通过上面的实例,我们和Uevent来了一次亲密接触,具体到Vold,也就是内核通过Uevent告知外部存储系统发生了哪些事情,那么Uevent在什么情况下会由Kernel发出呢?

当设备发生变化时,这会引起Kernel发送Uevent消息,例如设备的插入和拔出等。如果Vold在设备发生变化之前已经建立了Netlink IPC通信,那么Vold可以接收到这些Uevent消息。这种情况是由设备发生变化而触发的。

设备一般在/sys对应的目录下有一个叫uevent的文件,往该文件中写入指定的数据,也会触发Kernel发送和该设备相关的Uevent消息,这是由应用层触发的。例如Vold启动时,会往这些uevent文件中写数据,通过这种方式促使内核发送Uevent消息,这样Vold就能得到这些设备的当前信息了。

根据上面的介绍可知,Netlink和Uevent的目的,就是让Vold随时获悉外部存储系统的信息,这至关重要。我们总不会希望发生诸如SD卡都被拔了,而Vold却一无所知的情况吧?