天天看點

Vold分析

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

轉載于:https://www.cnblogs.com/yuanfang/archive/2010/12/23/1914909.html

繼續閱讀