vold 的全称是volume daemon。实际上是负责完成系统 的CDROM, USB大容量存储,MMC卡等扩展存储的挂载任务自动完成的守护进程。它提供的主要特点是支持这些存储外设的热插拔。这里有GNU/Linux vold的介绍[http://vold.sourceforge.net/]。在Android上的这个vold系统和GNU/Linux的之间存在很 大的差异,这里我们主要是分析Android上的vold系统的处理过程。

Vold处理过程大致分为三步:
1.创建链接:
在vold作为一个守护进程,一方面接受驱动的信 息,并把信息传给应用 层;另一方面接受上层的命令 并完成相应。所以这里的链接一共有两条:
(1)vold socket: 负责vold与应用层的信息传递;
(2)访问udev的socket: 负责vold与底层的信息传递;
这两个链接都是在进程的一开始完成创建的。

2.引导:
这里主要是在vold启动时,对现有外设存储设备的处理。首先,要加载并解析vold.conf,
并检查挂载点是否已经被挂载(注:这里检查挂载点的用意不是很清楚!); 其次,执行MMC卡挂载; 最后,处理USB大容量存储。

3.事件处理:
这里通 过对两个链接的监听,完成对动态事件的处理,以及对上层应用操作的响应。

我们来具体分析一下代码过程。我们以带着mmc卡开机这种情况为例,看看vold的启动和处 理过程。我们从vold的主函数 的部分实现开始:

首先是创建两个 socket。首先创建的是vold与上层应用程序的链接。

// 
Socket to listen on for incomming framework connections

    if ((door_sock = 
android_get_control_socket(VOLD_SOCKET)) < 0) {

        LOGE("Obtaining file descriptor 
socket '%s' failed: %s",

             
VOLD_SOCKET, strerror(errno));

        
exit(1);

    }

door_sock就是对VOLD_SOCKET监听的文件 描述符。这里VOLD_SOCKET就是在 init.rc中启动vold服务的时候创建的 socket设备文件。上层的应用层——MountListener,这个服务会连接vold socket——作为客户端,然后通过这个连接和vold之间进行信息和命令的交互。当有client连接进这个vold socket时,监听door_sock的vold就会生成自己的socket描述符fw_sock,并通过它来传送和接受上层信息。
然后,创建vold与底层的信息交互的socket。

if 
((uevent_sock = socket(PF_NETLINK,

                            
 SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) {

        LOGE("Unable to create uevent socket: %s", 
strerror(errno));

        exit(1);

    }

这里使用 NETLINK_KOBJECT_UEVENT的socket去访问并获得udev的信息。关于udev,这是一个Linux 内核的设备管理 模块,网上有详细资料。这里我们只要知道设备的热插 拔信息是从这个模块中获得的。

下边介绍引导(Boot Strap)过程。 首先,执行volmgr_bootstrap(),对vold.conf文件进行解析。这个文件主要包含了如下的信息,我们使用模拟器中的默认的 vold.conf为例。

volume_sdcard {

    ## This is the direct uevent device 
path to the SD slot on the device

    
emu_media_path /devices/platform/goldfish_mmc.0/mmc_host/mmc0


    
media_type     mmc

    mount_point    
/sdcard

    ums_path       
/devices/platform/usb_mass_storage/lun0

}



然后把解析之后的信 息保存在一个全局的结构变量里,并读取/proc/mounts信息,检查挂载点是否已经被挂载。这里只有一个mmc卡槽,所以只定义了一个挂载点。

接着是 mmc_bootstrap()函数。这里有一连串调用和处理,主要是针对目录来作的。调用关系如下,
mmc_bootstrap() → mmc_bootstrap_controller() → mmc_bootstrap_card()
经过这几步之后,在 mmc_bootstrap_card()调用中,我们已经进入文件夹 /sys/devices/platform/goldfish_mmc.0/mmc_host/mmc0/mmc0:e118下边了。这里,就是我们当 前插槽中的mmc卡存放设备信息的地方,然后获得一些相关的信息,这些信息为之后的uevent的生成作准备。我们简单说一下uevent事件处理系统。 下面是代码中的dispatch_table全局变量。

static struct uevent_dispatch 
dispatch_table[] = {

    { "switch", 
handle_switch_event },

    { "battery", 
handle_battery_event },

    { "mmc", 
handle_mmc_event },

    { "block", 
handle_block_event },

    { "bdi", 
handle_bdi_event },

    { 
"power_supply", handle_powersupply_event },

    { NULL, NULL }

};


这里主要存储了不同的 设备的uevent的不同的处理方式。通过dispatch_event()函数,来获得相应事件的名字,并找到,执行相应的操作。 dispatch_table全局变量保存了所有事件与处理句柄的对应。dispatch_event()中,正是遍历这个全局表来完成相应的调用。而 dispatch_event(),需要一个struct uevent*,也就是uevent指针作为参数。这里,uevent就是事件的结构体,定义如下。

struct uevent {

    char *path;

    enum uevent_action action;

   
 char *subsystem;

    char 
*param[UEVENT_PARAMS_MAX];

    unsigned 
int seqnum;

};



对dispatch_event()的调用在vold主要有两种 方式,一种是通过捕捉udev的底层消息,然后执行 process_uevent_message()来执行dispatch_event();另一种就是在Boot strap期间调用的simulate_uevent()函数,开辟内存并通过参数生成一个uevent,然后执行dispatch_event()。在 我们的mmc卡的引导过程中,一共需要调用若干次的simulate_uevent()函数。这个函数中,会根据参数,生成并初始化一个uevent实 例,再把这个实例 作为参数传给dispatch_event()函 数,来完成事件的执行过程。下面我们回到mmc卡的引导过程,结合这个过程看看 simulate_uevent()函数的工作。

我们把虚拟机的文件目 录下的配置看一下。

# pwd

/sys/devices/platform/goldfish_mmc.0/mmc_host/mmc0/mmc0:e118

# ls -l

-rw-r--r--
 root     root         4096 2009-06-09 11:01 uevent

-r--r--r-- root     root         4096 
2009-06-09 11:10 cid

-r--r--r-- root    
 root         4096 2009-06-09 11:10 csd

-r--r--r--
 root     root         4096 2009-06-09 11:10 scr

-r--r--r-- root     root         4096 2009-06-09 11:10 date

-r--r--r-- root     root         4096 
2009-06-09 11:10 fwrev

-r--r--r-- 
root     root         4096 2009-06-09 11:10 hwrev

-r--r--r-- root     root         4096 2009-06-09 11:10 manfid

-r--r--r-- root     root         4096 
2009-06-09 11:01 name

-r--r--r-- 
root     root         4096 2009-06-09 11:10 oemid

-r--r--r-- root     root         4096 2009-06-09 11:01 serial

-r--r--r-- root     root         4096 
2009-06-09 11:01 type

lrwxrwxrwx 
root     root              2009-06-09 11:10 subsystem -> 
http://www.cnblogs.com/http://www.cnblogs.com/http://www.cnblogs.com/bus/mmc

drwxr-xr-x 
root     root              2009-06-09 11:01 power

lrwxrwxrwx root     root              2009-06-09 11:10 driver 
-> http://www.cnblogs.com/http://www.cnblogs.com/http://www.cnblogs.com/bus/mmc/drivers/mmcblk

drwxr-xr-x root     root              2009-06-09 11:01 block


这里边的文件存放的 各种mmc卡的类型,名字等信息。我们再看一段mmc_bootstrap_card()函数的一段代码:

// file: mmc.c

static int mmc_bootstrap_card(char *sysfs_path)

{

    … ...

    sprintf(tmp, "DEVPATH=%s", devpath);

    uevent_params[0] = (char *) 
strdup(tmp);


    sprintf(filename, "/sys%s/type", devpath);

    p = read_file(filename, &sz);

    p[strlen(p) - 1] = '/0';

    sprintf(tmp, "MMC_TYPE=%s", p);

    free(p);

    uevent_params[1] = (char *) strdup(tmp);


    
sprintf(filename, "/sys%s/name", devpath);

    p = read_file(filename, &sz);

    p[strlen(p) - 1] = '/0';

   
 sprintf(tmp, "MMC_NAME=%s", p);

    
free(p);

    uevent_params[2] = (char *)
 strdup(tmp);


    uevent_params[3] = (char *) NULL;


    if (simulate_uevent("mmc",
 devpath, "add", uevent_params) < 0) {

        LOGE("Error simulating uevent (%m)");

        return -errno;

    }

    …
 …

}


通过simulate_uevent()的参数,传入uevent结构体中。这里,执行的 是一个mmc卡的加载信息。我们看到char* uevent_params[4]中存储了mmc卡的路径,类型,名字等信息。
并将其传入simulate_uevent()。我们进入 simulate_uevent()函数看看。

int simulate_uevent(char *subsys, char 
*path, char *action, char **params)

{

    struct uevent *event;

    char tmp[255];

    int i, rc;


    if (!(event = malloc(sizeof(struct 
uevent)))) {

        LOGE("Error 
allocating memory (%s)", strerror(errno));

        return -errno;

    }


    
memset(event, 0, sizeof(struct uevent));


    event->subsystem = strdup(subsys);


    if 
(!strcmp(action, "add"))

        
event->action = action_add;

    else 
if (!strcmp(action, "change"))

        
event->action = action_change;

    
else if (!strcmp(action, "remove"))

       
 event->action = action_remove;

    
else {

        LOGE("Invalid action 
'%s'", action);

        return -1;

    }


    event->path = strdup(path);


    for (i
 = 0; i < UEVENT_PARAMS_MAX; i++) {

       
 if (!params)

            break;

        event->param = strdup(params);

    }


    rc = dispatch_uevent(event);

    free_uevent(event);

    return rc;

}


我们看到,simulate_uevent()函数生成并根据参数初始化,最后,调用dispatch_uevent()去执行这个模 拟事件。

处理 函数dispatch_uevent()调用会根据名字,这里会调用 handle_mmc_event()进行处理。实际上,这个处理过程并没有加载mmc卡到/sdcard挂载点上。而挂载过程,还在下边。:-) 我们继续分析。

处 理完这里之后,mmc_bootstrap_card()过程继续往下走,
mmc_bootstrap_card() → mmc_bootstrap_block() → mmc_bootstrap_mmcblk() → mmc_bootstrap_mmcblk_partition()
这是执行过程。 mmc_bootstrap_mmcblk_partition()函数总共执行了两次。两次的差别主要是参数上的不同,第一次的调用参数是:
/sys/devices/platform/goldfish_mmc.0/mmc_host/mmc0/mmc0:e118/block/mmcblk0
执行一次simulate_uevent(),添加block的信息; 第二次调用的参数是:
/sys/devices/platform/goldfish_mmc.0/mmc_host/mmc0/mmc0:e118/block/mmcblk0/mmcblk0p1
我们来看一下这个函数的实现:

static int mmc_bootstrap_mmcblk(char *devpath)

{

… ...

    if ((rc = 
mmc_bootstrap_mmcblk_partition(devpath))) {

    … ...

    }

    … ...

   
 for (part_no = 0; part_no < 4; part_no++) {

    … ...

            if 
(mmc_bootstrap_mmcblk_partition(part_devpath))

    … ...

        }

    }

… ...

}

两次调用的路径参 数,我们已经给出了。 mmc_bootstrap_mmcblk_partition()函数第一次调用会添加mmc卡的DISK信息进去,然后创建一个block device把些信息记录下来,一个主要的信息是这个DISK的Partition信息,这个对后来的挂载起着决定的作用。挂载实际上只是对 Partition才进行的。mmc_bootstrap_mmcblk_partition()通过调用simulate_uevent()函数进行事 件的模拟,最后完成操作。值得一说的是,挂载是通过单独线程异步执行的。到这里,关于启动时的分析就介绍到这儿,具体的代码调用,比较复杂,大家可以追踪 代码来分析具体的实现。

由于USB大容量存储的挂载还没有实现,ums_bootstrap()是个空函数,所以这一部分可以跳过。还有就是 switch_bootstrap(),这个似乎也是处理USB存储方面的东西,具体代码,还没有仔细的阅读,以后有更新了,我会继续update。

这样我们再次回到 vold主函数内部。接下来就是进入while循环阻塞,要对两个链接描述符进行监听,并执行各自的请求了,这里使用了我们熟悉的 select系统调用。当应用层有链接vold socket的请求进来时,这个应用层和vold之间的链接描述符fw_sock就会获得值

==========
在init.rc中启动VOLD这个守护线程和创建socket的命令如下:
service vold /system/bin/vold
    socket vold stream 0660 root mount

================
有一点需要注意,以前使用mountd的时候,也采用了同样的原理 ,也建立了一个socket
现在这些在init.rc中已经被注释掉了。
#service mountd /system/bin/mountd
#    socket mountd stream 0660 root mount