9.2 Vold的原理与机制分析
Vold是Volume Daemon的缩写,它是Android平台中外部存储系统的管控中心,是一个比较重要的进程。虽然它的地位很重要,但其代码结构却远没有前面的Audio和Surface系统复杂。欣赏完Audio和Surface的大气磅礴后,再来感受一下Vold的小巧玲珑也会别有一番情趣。Vold的架构可用图9-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却一无所知的情况吧?