1 總體介紹
在Android 中,當SD卡插入系統之後,系統會自動掛載。Vold 就是負責掛載SD卡的,vold 的全稱是volume daemon。實際上是負責完成系統的CDROM,USB 大容量存儲,MMC 卡等擴展存儲的掛載任務自動完成的守護進程。它提供的主要特點是支持這些存儲外設的熱插拔。
1.1總體流程圖

O 綠色箭頭:表示插入SD卡後事件傳遞以及SD卡掛載 O 紅色箭頭:表示掛載成功後的消息傳遞流程 O 黃色箭頭:表示MountService發出掛載/卸載SD卡的命令
1.2總體類圖
main.cpp,vold的入口函數,系統起來會只執行vold的可執行文件,調到這個main函數中。 NetlinkManager.cpp位於源碼位置/system/vold/NetlinkManager.cpp。該類的主要通過引用NetlinkHandler類中的onEvent()方法來接收來自內核的事件消息,NetlinkHandler位於/system/vold/NetlinkHandler.cpp。 VolumeManager:位於源碼位置/system/vold/VolumeManager.cpp。該類的主要作用是接收經過NetlinkManager處理過後的事件消息。 DirectVolume:位於/system/vold/DirectVolume.cpp。該類的是一個工具類,主要負責對傳入的事件進行進一步的處理,block事件又可以分為:Add,Removed,Change,Noaction這四種。 Volume:Volume.cpp位於/system/vold/Volume.cpp,該類是負責SD卡掛載的主要類。Volume.cpp主要負責檢查SD卡格式,以及對復合要求的SD卡進行掛載,並通過Socket將消息SD卡掛載的消息傳遞給NativeDaemonConnector。
總的講,vold程序需要分層三部分,第一部分為NetlinkManager,管理接受來自kernel的UEvent消息,第二部分為VolumeManager,主要負責處理來自NetlinkManager的消息和來自java層的消息,之後真正的掛載卸載動作就需要volume負責了。
2 初始化流程2.1 時序圖2.2 代碼分析在Android 系統啟動的時候,init進程會去解析init.rc文件,在該文件中,有如下代碼: service vold /system/bin/vold
class core
socket vold stream 0660 root mount
ioprio be 2
|
定義了一個vold的service,去執行vold程序,並創建了一個名字為vold的socket,init進程解析完後就去執行vold程序,創建與java層通信的Socket。 在Android 源碼/system/vold路徑下的main.cpp,這個就是vold程序的入口,我們看看起main函數,代碼如下: - int main() {
- VolumeManager *vm;
- CommandListener *cl;
- NetlinkManager *nm;
- if (!(vm = VolumeManager::Instance())) {//創建VolumeManager實例
- };
-
- if (!(nm = NetlinkManager::Instance())) {//創建NelinkManager實例
- };
- cl = new CommandListener(); //創建與java層socket通信的接口
- vm->setBroadcaster((SocketListener *) cl);
- nm->setBroadcaster((SocketListener *) cl);
- if (vm->start()) { //什麼都沒做
- }
- if (process_config(vm)) {//初始化fstab
- SLOGE("Error reading configuration (%s)... continuing anyways", strerror(errno));
- }
- if (nm->start()) {//開始監聽kernel上報的vold消息
- }
- ……
- if (cl->startListener()) {//開始監聽來自java層的socket消息
- SLOGE("Unable to start CommandListener (%s)", strerror(errno));
- exit(1);
- }
- while(1) {
- sleep(1000);
- }
- exit(0);
- }
複製代碼 首先,在main函數中,需要創建VolumeManager和NetlinkManager的實例,裡面就做了一些初始化的動作,這裡就不多說了。
接著,則是初始化vold與java層的socket通信接口。創建了的CommandListener實例。在上面的類圖關係中,我們知道,CommandListener繼承於FrameworkListener,而FrameworkListener有繼承於SocketListener。先看看CommandListener的初始化代碼: - CommandListener::CommandListener() :
- FrameworkListener("vold") {
- registerCmd(new DumpCmd());
- registerCmd(new VolumeCmd()); //處理volume事件
- registerCmd(new AsecCmd());
- registerCmd(new ObbCmd());
- registerCmd(new StorageCmd());
- registerCmd(new XwarpCmd());
- registerCmd(new CryptfsCmd());
- }
複製代碼 在上面代碼中我們看到,先以「vold」為參數構造FrameworkListener類,完成之後,則調用FrameworkListener類中的registerCmd()方法,註冊一些處理方法類,而對於sd卡掛載的事件,我們先關注VolumeCmd類,它是FrameworkListener的內部類,用於處理Volume事件。接下來,看FrameworkListener的構造函數:- FrameworkListener::FrameworkListener(const char *socketName) :SocketListener (socketName, true) {
- mCommands = new FrameworkCommandCollection();
- }
複製代碼
以之前傳進來的「vold」參數構造SocketListener類,然後在FrameworkListener構造函數中,創建FrameworkCommandCollection的實例,其實它就是一個容器,用於存儲之前調用的registerCmd()註冊的處理方法類。下面就看SocketListener的構造函數:- SocketListener::SocketListener(const char *socketName, bool listen) {
- mListen = listen;
- mSocketName = socketName; /將vold字符串存儲在mSocketName變量中
- mSock = -1;
- pthread_mutex_init(&mClientsLock, NULL);
- mClients = new SocketClientCollection(); //創建socket客戶端容器
- }
複製代碼 其實很簡單,就是做了一些變量的初始化工作,用mSocketName變量存儲「vold」字符串,這個vold是很有講究的,因為是init.rc定義的vold Service中,就創建了一個名字為vold的socket端口,後面將通過「vold」獲取到該 socket端口。
到此,CommandListener的初始化就完成了的。 我們回到main函數中,創建了CommandListener實例之後,然後調用VolumeManger的setBroadcaster方法,將CommandListener的實例存儲在mBroadcaster變量中,代碼如下: void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }
|
其實NetlinkManager也做了同樣的設置,但我還沒發現它有什麼用,所以就不再關注了。 接下來就開始調用了main.cpp的process_config()方法了,在介紹之前,我必須先介紹下vold.fstab配置文件,這個配置文件就是在process_config()中被解析的,而vold.fstab配置文件,就是用於描述vold的掛載動作的,其配置例子如下: dev_mount sdcard /mnt/sdcard
auto /devices/platform/goldfish_mmc.0
掛載命令 標籤 掛載點 子分區個數 掛載路徑
|
我們就以上面例子來說明,意思就是將/devices/platform/goldfish_mmc.0掛載到/mnt/sdcard中,/devices/platform/goldfish_mmc.0可以認為是kernel上報上來的路徑。子分區個數如果為auto則表示只有1個子分區,也可以為任何不為0的整數。如果vold.fstab解析無誤,VolueManager將創建DirectVolume。 好了,下面可以看 process_config()方法了,代碼如下: - static int process_config(VolumeManager *vm) {
- FILE *fp;
- int n = 0;
- char line[255];
- if (!(fp = fopen("/etc/vold.fstab", "r"))) {
- return -1;
- }
- while(fgets(line, sizeof(line), fp)) {
- const char *delim = " \t";
- char *save_ptr;
- char *type, *label, *mount_point, *mount_flags, *sysfs_path;
- int flags;
- n++;
- line[strlen(line)-1] = '\0';
-
- if (line[0] == '#' || line[0] == '\0')
- continue;
- if (!(type = strtok_r(line, delim, &save_ptr))) {
- goto out_syntax;
- }
- if (!(label = strtok_r(NULL, delim, &save_ptr))) {
- goto out_syntax;
- }
- if (!(mount_point = strtok_r(NULL, delim, &save_ptr))) {
- goto out_syntax;
- }
- if (!strcmp(type, "dev_mount")) {
- DirectVolume *dv = NULL;
- char *part;
- if (!(part = strtok_r(NULL, delim, &save_ptr))) {
- goto out_syntax;
- }
- if (strcmp(part, "auto") && atoi(part) == 0) {
- goto out_syntax;
- }
- if (!strcmp(part, "auto")) {//如果解析沒有錯,那麼就將創建DirectVolume
- dv = new DirectVolume(vm, label, mount_point, -1);
- } else {
- dv = new DirectVolume(vm, label, mount_point, atoi(part));
- }
- while ((sysfs_path = strtok_r(NULL, delim, &save_ptr))) {
- if (*sysfs_path != '/') {
- break;
- }
- if (dv->addPath(sysfs_path)) {
- goto out_fail;
- }
- }
- if (sysfs_path)
- flags = parse_mount_flags(sysfs_path);
- else
- flags = 0;
- dv->setFlags(flags);
- vm->addVolume(dv); //將創建的DirectVolume添加到VolumeManager中。
- } else if (!strcmp(type, "map_mount")) {
- } else {
- }
- }
- fclose(fp);
- return 0;
- }
複製代碼
該方法,通過一個wihle方法,逐行進行解析,如果認為合理,那麼將拿到的信息用於創建DirectVolume實例,然後調用VolumeManager的addVolume方法,存儲在mVolumes變量中。
好了,下面就開始看註冊監聽kernel的sockect端口了。就是NetLinkManager的start方法,代碼如下: int NetlinkManager::start() {
struct sockaddr_nl nladdr;
int sz = 64 * 1024;
int on = 1;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid();
nladdr.nl_groups = 0xffffffff;
if ((mSock = socket(PF_NETLINK,//創建socket,返回文件描述符
SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) {
SLOGE("Unable to create uevent socket: %s", strerror(errno));
return -1;
}
if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
SLOGE("Unable to set uevent socket SO_RECBUFFORCE option: %s", strerror(errno));
return -1;
}
if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
return -1;
}
if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
SLOGE("Unable to bind uevent socket: %s", strerror(errno));
return -1;
}
mHandler = new NetlinkHandler(mSock);
if (mHandler->start()) {
return -1;
}
return 0;
}
|
其實就是調用socket()創建socket端口,返回描述符,經過一些設置,然後就描述符作為參數,創建的NetlinkHandler實例,然後就直接調用起start方法。看NetLinkHandler構造函數: NetlinkHandler::NetlinkHandler(int listenerSocket) :
NetlinkListener(listenerSocket) {
}
|
構造函數里什麼都沒做,NetlinkHandler繼承於NetlinkListener,然後講socket端口的描述符傳進去。 NetlinkListener::NetlinkListener(int socket) :
SocketListener(socket, false) {
mFormat = NETLINK_FORMAT_ASCII;
}
|
又是這麼幾句代碼,NetlinkListener也是繼承於SocketListener,所以還將socket描述符傳進去,再次創建了SocketListener的實例,所以,在vold系統中,有兩個SocketListener的實例。看其構造函數,這裡的構造函數與之前的是不一樣的,代碼如下: SocketListener::SocketListener(int socketFd, bool listen) {
mListen = listen;
mSocketName = NULL;
mSock = socketFd;
pthread_mutex_init(&mClientsLock, NULL);
mClients = new SocketClientCollection();
}
|
其實,與上面的構造函數,還是差不多的,只是傳進來的參數不一樣而已,之前的是一個「vold」字符串,而這裡是一個socket的描述符。 好了,構造函數創建好了,那麼接著看NetlinkHandler->start()方法: int NetlinkHandler::start() {
return this->startListener();//指到了socketListener中了
}
|
這裡的startListener方法是SocketListener中的,代碼如下: int SocketListener::startListener() {
if (!mSocketName && mSock == -1) {
return -1;
} else if (mSocketName) {
if ((mSock = android_get_control_socket(mSocketName)) < 0) {
return -1;
}
}
if (mListen && listen(mSock, 4) < 0) {
return -1;
} else if (!mListen)
mClients->push_back(new SocketClient(mSock, false));//創建socket客戶端,並添加到mClients容器中。
if (pipe(mCtrlPipe)) {
return -1;
}
if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {//創建新線程
return -1;
}
return 0;
}
|
此時的條件下,mSocketName=null,mSock!=0,繼續往下看,創建了SocketClient實例,並添加到mClients容器中,用於接收客戶端發過來的消息。 接著創建新的一個線程,用於讀取socket客戶端發過來的消息,線程執行的方法如下: void *SocketListener::threadStart(void *obj) {
SocketListener *me = reinterpret_cast<SocketListener *>(obj);
me->runListener();
pthread_exit(NULL);
return NULL;
}
|
看runListener()方法: void SocketListener::runListener() {
SocketClientCollection *pendingList = new SocketClientCollection();
while(1) {
……
for (it = mClients->begin(); it != mClients->end(); ++it) {
int fd = (*it)->getSocket();
if (FD_ISSET(fd, &read_fds)) {
pendingList->push_back(*it);
}
}
pthread_mutex_unlock(&mClientsLock);
while (!pendingList->empty()) {//客戶端有消息
it = pendingList->begin();
SocketClient* c = *it;
pendingList->erase(it);
if (!onDataAvailable(c) && mListen) {// 處理消息
}
}
}
delete pendingList;
}
|
在該方法中,一個while循環,不斷讀取socket消息,如果發現有socket消息,那麼就調用方法onDataAvailable處理,該方法是在NetlinkListener方法實現的,其代碼如下: bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
int socket = cli->getSocket();
ssize_t count;
count = TEMP_FAILURE_RETRY(uevent_kernel_multicast_recv(socket, mBuffer, sizeof(mBuffer)));
if (count < 0) {
SLOGE("recvmsg failed (%s)", strerror(errno));
return false;
}
NetlinkEvent *evt = new NetlinkEvent();
if (!evt->decode(mBuffer, count, mFormat)) {
SLOGE("Error decoding NetlinkEvent");
} else {
onEvent(evt);//在NetlinkHandler被實現
}
return true;
|
就是經過了處理,跳轉到了NetlinkHandler的onEvent()方法處理。好了,註冊kernel監聽就到此先搞一段落了。 回到了main函數中,最後,看到調用了CommandListener->startListener(),其實就是調用了SocketListener中的startListener方法。代碼就不再次貼出來了,同樣也是創建了一個新的線程讀取socket消息,只是,發現有消息後,調用的是FrameworkListener中的onDataAvailable方法處理。 好了,到此,vold的初始化已經完成了。下面看看sd的mount流程吧。
3 SD卡mount流程 
3.3 代碼分析經過前面的介紹,我們知道了,在NetlinkHandler的onEvent方法中,收到了kernel的消息。其代碼如下: void NetlinkHandler::onEvent(NetlinkEvent *evt) {
VolumeManager *vm = VolumeManager::Instance();
const char *subsys = evt->getSubsystem();
if (!subsys) {
return;
}
if (!strcmp(subsys, "block")) {
vm->handleBlockEvent(evt);//進一步處理
}
}
|
這裡就只處理block消息了,看看VolumeManager的handleBlockEvent方法吧: void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
const char *devpath = evt->findParam("DEVPATH");
VolumeCollection::iterator it;
bool hit = false;
for (it = mVolumes->begin(); it != mVolumes->end(); ++it) {
if (!(*it)->handleBlockEvent(evt)) {//到DirectVolume處理
hit = true;
break;
}
}
}
|
這裡的for循環遍歷mVolumes,它其實是DirectVolume實例列表,在解析vold.fstab中,創建的DirectVolume實例並添加到mVolumes列表中。然後再調用DirectVolume的handleBlockEvent方法嘗試處理該消息,看是否能匹配,起代碼如下:
int DirectVolume::handleBlockEvent(NetlinkEvent *evt) {
const char *dp = evt->findParam("DEVPATH");
PathCollection::iterator it;
for (it = mPaths->begin(); it != mPaths->end(); ++it) {//遍歷vold.fstab定義的路徑
if (!strncmp(dp, *it, strlen(*it))) {//kernel上報上來的路徑與vold.fstab中定義的匹配
int action = evt->getAction();
const char *devtype = evt->findParam("DEVTYPE");
if (action == NetlinkEvent::NlActionAdd) {
int major = atoi(evt->findParam("MAJOR"));
int minor = atoi(evt->findParam("MINOR"));
char nodepath[255];
snprintf(nodepath,
sizeof(nodepath), "/dev/block/vold/%d:%d",
major, minor);
if (createDeviceNode(nodepath, major, minor)) {
}
if (!strcmp(devtype, "disk")) {//插入設備消息
handleDiskAdded(dp, evt);//上報一個物理分區
} else {
handlePartitionAdded(dp, evt);//上報一個邏輯分區
}
} else if (action == NetlinkEvent::NlActionRemove) {//拔出設備消息
if (!strcmp(devtype, "disk")) {
handleDiskRemoved(dp, evt);
} else {
handlePartitionRemoved(dp, evt);
}
} else if (action == NetlinkEvent::NlActionChange) {//設備狀態改變消息
if (!strcmp(devtype, "disk")) {
handleDiskChanged(dp, evt);
} else {
handlePartitionChanged(dp, evt);
}
} else {
SLOGW("Ignoring non add/remove/change event");
}
return 0;
}
}
errno = ENODEV;
return -1;
}
|
Kernel上報上來的消息中,有一個路徑的消息,將與vold.fstab中定義的路徑進行匹配,如果匹配,那麼說明這個消息是有效的,那麼就繼續處理。 那麼,kernel上報的消息也分為三類,分別是設備插入、拔出、狀態改變。我們這裡就先關注插入的消息吧。 那麼,插入的消息,又分是物理分區還是一個邏輯分區。假如插入一個sd卡,它只有一個分區,那麼上報的就是Disk消息。假如插入一個sd卡,該卡有內部又被分成多個分區,那麼就先上報的是一個Dist消息,用於描述這個sd卡,後面還會上報多個消息,每個消息對應sd卡中的一個分區,也就是partition消息。 在這裡,我們關注Dist消息吧,看看handleDiskAdded()方法,代碼如下; void DirectVolume::handleDiskAdded(const char *devpath, NetlinkEvent *evt) {
mDiskMajor = atoi(evt->findParam("MAJOR"));
mDiskMinor = atoi(evt->findParam("MINOR"));
const char *tmp = evt->findParam("NPARTS");
if (tmp) {
mDiskNumParts = atoi(tmp);//如果上報的是只有一個分區的sd,該變量為0
} else {
mDiskNumParts = 1;
}
mPartsEventCnt = 0;
char msg[255];
int partmask = 0;
int i;
for (i = 1; i <= mDiskNumParts; i++) {
partmask |= (1 << i);
}
mPendingPartMap = partmask;
if (mDiskNumParts == 0) {
setState(Volume::State_Idle);//設置初始狀態
} else {
setState(Volume::State_Pending);
}
snprintf(msg, sizeof(msg), "Volume %s %s disk inserted (%d:%d)",
getLabel(), getMountpoint(), mDiskMajor, mDiskMinor);//構造消息
mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted,msg, false);//發socket消息到java層
}
|
如果是Disk消息,那麼上報的sd卡只有一個分區,所以上面的mDiskNumParts=0。看下面,調用snprintf()構造msg消息,然後調用mVm->getBroadcaster()->sendBroadcast發送到java層。其實mVm->getBroadcaster()就是放回CommandListener的實例變量,sendBroadcast就是在SocketListener中,代碼如下: void SocketListener::sendBroadcast(const char *msg) {
pthread_mutex_lock(&mClientsLock);
SocketClientCollection::iterator i;
for (i = mClients->begin(); i != mClients->end(); ++i) {
if ((*i)->sendMsg(msg)) {//發送socket消息
}
}
pthread_mutex_unlock(&mClientsLock);
}
|
Ok,看到了吧,這裡就發送了一個VolumeDiskInserted的消息到java層。 但如果是系統改啟動的話,kernel早早就發來了消息,但是java層還沒起來呢。所以,等到mountService起來之後,就收到了socket消息了。 我們直接看mountService的onEvent()方法吧代碼如下: public boolean onEvent(int code, String raw, String[] cooked) {
…….
if (code == VoldResponseCode.VolumeDiskInserted) {
new Thread() {
public void run() {
try {
int rc;
if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
}
} catch (Exception ex) {
}
}
}.start();
} else if (code == VoldResponseCode.VolumeDiskRemoved) {
}
}
|
這裡我們只看onEvent的處理VoldResponseCode.VolumeDiskInserted消息,我們看到,對於VolumeDiskInserted消息,mountService立刻調用了方法doMountVolume(path),其實就是通過socket對vold發送了一個條mount的命令。 所以對與java層來講,可以發mount、unmount消息到vold中。那麼現在,就看vold處理吧。 前面也介紹過,java層發送的socket消息,vold層在SocketListener中讀取到,然後會在FrameworkListener的onDataAvailable()方法中處理,代碼如下: bool FrameworkListener::onDataAvailable(SocketClient *c) {
char buffer[255];
int len;
len = TEMP_FAILURE_RETRY(read(c->getSocket(), buffer, sizeof(buffer)));
if (len < 0) {
return false;
} else if (!len)
return false;
int offset = 0;
int i;
for (i = 0; i < len; i++) {
if (buffer == '\0') {
dispatchCommand(c, buffer + offset);//開始派發消息
offset = i + 1;
}
}
return true;
}
|
調用dispatchCommand()派發消息了,代碼如下: void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {
……
for (i = mCommands->begin(); i != mCommands->end(); ++i) {
FrameworkCommand *c = *i;
if (!strcmp(argv[0], c->getCommand())) {
if (c->runCommand(cli, argc, argv)) {
SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));
}
}
}
|
一堆的處理,代碼也就不貼出來了,直接看關鍵的部分吧。記得在CommandListener的構造函數中嗎,裡面調用了FrameworkListener的registerCmd()方法,註冊了一些處理方法類,其實就是添加到了mCommands容器中了,這裡當然需要遍歷咯,找到其合適的處理方法類,然後調用其runComand()方法,看看其代碼吧: int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
dumpArgs(argc, argv, -1);
…….
VolumeManager *vm = VolumeManager::Instance();
int rc = 0;
if (!strcmp(argv[1], "list")) {
return vm->listVolumes(cli);
} else if (!strcmp(argv[1], "debug")) {
} else if (!strcmp(argv[1], "mount")) {//處理mount消息
rc = vm->mountVolume(argv[2]);
} else if (!strcmp(argv[1], "unmount")) {
rc = vm->unmountVolume(argv[2], force, revert);
} else if (!strcmp(argv[1], "format")) { //處理格式化消息 rc = vm->formatVolume(argv[2]);
} else if (!strcmp(argv[1], "share")) { //處理掛載到pc消息
rc = vm->shareVolume(argv[2], argv[3]);
} else if (!strcmp(argv[1], "unshare")) {
rc = vm->unshareVolume(argv[2], argv[3]);
} else if (!strcmp(argv[1], "shared")) {
bool enabled = false;
if (vm->shareEnabled(argv[2], argv[3], &enabled)) {
}
return 0;
}
|
在這裡處理Volume消息,我們就只看mount消息吧,就調用了VolumeManager的mountVolume方法,代碼如下: int VolumeManager::mountVolume(const char *label) {
Volume *v = lookupVolume(label);//找到該掛載點的Volume的實例
if (!v) {
return -1;
}
return v->mountVol();//去掛載啦
}
|
到Volume的mountVol()中掛載,代碼如下: int Volume::mountVol() {
…..
}
|
這個方法代碼量比較大,就不貼出來了,但是完成mount的動作就是在該方法中,然後呢,Volume中還包含了其他的功能方法,比如unmount、share、unshare。 好了,花了一個下午的時間整理出來,vold的初始化即sd卡的掛載流程就講解到這吧,我這裡講流程的比較多,很多細節問題也沒有講,其實我寫的文檔,還是比較註冊流程,消息是怎麼傳遞的,至於細節,用到的時候,再詳細看!
|