原文地址::http://www.chineselinuxuniversity.net/freesky/viewthread.php?tid=41
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 -> ../../../../../../bus/mmc
drwxr-xr-x root root 2009-06-09 11:01 power
lrwxrwxrwx root root 2009-06-09 11:10 driver -> ../../../../../../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