vold挂载管理

看了很长时间Vold存储模块的相关知识,也深入的研究一段时间的Android源码,打算把自己看过的经验之贴、参考资料和自己的一些见解,以帖子的形式发出来,供有兴趣的同仁们参考,有不对的地方请指正,我们相互交流。

1.1 vold的原理与机制分析

1.1.1 Vold 架构

vold挂载管理_第1张图片

从上图中可知:

·  Vold中的NetlinkManager模块接收来自Linux内核的uevent消息。例如SD卡的插拔等动作都会引起Kernel向NM发送uevent消息。

·  NetlinkManager将这些消息转发给VolumeManager模块。VolumeManager会对应做一些操作,然后把相关信息通过CommandListener发送给MountService,MountService根据收到的消息会发送相关的处理命令给VolumeManager做进一步的处理。例如待SD卡插入后,VolumeManager会将来自NetlinkManager的“Disk Insert”消息发送给MountService,而后MountService则发送“Mount”指令给Vold,指示它挂载这个SD卡。

·  CommandListener模块内部封装了一个Socket用于跨进程通信。它在Vold进程中属于监听端(即是服务端),而它的连接端(即客户端)则是MountService。它一方面接收来自MountService的控制命令(例如卸载存储卡、格式化存储卡等),另一方面VolumeManagerNetlinkManager模块又会通过它,将一些信息发送给MountService。

1.1.2初识Vold

下面来初步的认识Vold,代码在system\vold\main.cpp

[cpp] view plain copy
  1. int main() {  
  2.     mkdir("/dev/block/vold", 0755);  //创建一个目录/dev/block/vold  
  3.   
  4.   //创建一个VolumeManager对象,该对象为单例模式  
  5.     if (!(vm = VolumeManager::Instance())) {  
  6.         SLOGE("Unable to create VolumeManager");  
  7.         exit(1);  
  8.     };  
  9.   
  10.   //创建一个NetlinkManager对象,该对象为单例模式  
  11.     if (!(nm = NetlinkManager::Instance())) {  
  12.         SLOGE("Unable to create NetlinkManager");  
  13.         exit(1);  
  14.     };  
  15.   
  16.   //创建一个CommandListener对象  
  17.     cl = new CommandListener();  
  18.     vm->setBroadcaster((SocketListener *) cl);  
  19.     nm->setBroadcaster((SocketListener *) cl);  
  20.   
  21. //启动VolumeManager  
  22.     if (vm->start()) {  
  23.         SLOGE("Unable to start VolumeManager (%s)", strerror(errno));  
  24.         exit(1);  
  25.     }  
  26.   
  27. //根据fstab配置文件初始化VolumeManager  
  28.     if (process_config(vm)) {  
  29.         SLOGE("Error reading configuration (%s)... continuing anyways", strerror(errno));  
  30.     }  
  31.   
  32. //启动NetlinkManager对象  
  33.     if (nm->start()) {  
  34.         SLOGE("Unable to start NetlinkManager (%s)", strerror(errno));  
  35.         exit(1);  
  36.     }  
  37. //通过往/sys/block目录下对应的uevent文件写“add\n”来触发内核发送Uevent消息  
  38.     coldboot("/sys/block");  
  39.   
  40. //启动CommandListener  
  41.     if (cl->startListener()) {  
  42.         SLOGE("Unable to start CommandListener (%s)", strerror(errno));  
  43.         exit(1);  
  44.     }  
  45. }  

1.2 NetlinkManager模块的分析

1.2.1 NetlinkManager架构流程图



上图中的虚线为启动是的调用流程。
 (1) class NetlinkManager(在其start函数中创建了NetlinkHandler对象,并把创建的socket作为参数)
 (2)class NetlinkHandler: public NetlinkListener(实现了onEvent)
 (3) class NetlinkListener : public SocketListener (实现了onDataAvailable)
 (4) class SocketListener(实现了runListener,在一个线程中通过select查看哪些socket有数据,通过onDataAvailable来读取数据)

1.2.2 start的分析

NetlinkManager模块将使用Netlink套接字实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,一起看下面代码
[cpp] view plain copy
  1. int NetlinkManager::start() {    
  2.     struct sockaddr_nl nladdr;    
  3.     int sz = 64 * 1024;    
  4.     int on = 1;    
  5.     memset(&nladdr, 0, sizeof(nladdr));    
  6.     nladdr.nl_family = AF_NETLINK;    
  7.     nladdr.nl_pid = getpid();    
  8.     nladdr.nl_groups = 0xffffffff;    
  9.     // 创建一个socket用于内核空间和用户空间的异步通信,监控系统的hotplug事件    
  10.     if ((mSock = socket(PF_NETLINK,    
  11.                         SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) {    
  12.         SLOGE("Unable to create uevent socket: %s", strerror(errno));    
  13.         return -1;    
  14.     }    
  15.     if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {    
  16.         SLOGE("Unable to set uevent socket SO_RECBUFFORCE option: %s", strerror(errno));    
  17.         return -1;    
  18.     }    
  19.     if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {    
  20.         SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));    
  21.         return -1;    
  22.     }    
  23.     
  24.     if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {    
  25.         SLOGE("Unable to bind uevent socket: %s", strerror(errno));    
  26.         return -1;    
  27.     }    
  28.     // 利用新创建的socket实例化一个NetlinkHandler类对象,NetlinkHandler继承了类NetlinkListener,        
  29.     // NetlinkListener又继承了类SocketListener        
  30.     mHandler = new NetlinkHandler(mSock);    
  31.     if (mHandler->start()) {  //启动NetlinkHandler    
  32.         SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));    
  33.         return -1;    
  34.     }    
  35.     return 0;    
  36. }   
NetlinkHandler构造

[cpp] view plain copy
  1. NetlinkHandler::NetlinkHandler(int listenerSocket) :  
  2.                 NetlinkListener(listenerSocket) {  
  3. }  
继承父类
[cpp] view plain copy
  1. NetlinkListener::NetlinkListener(int socket) :  
  2.                             SocketListener(socket, false) {  
  3.     mFormat = NETLINK_FORMAT_ASCII;  
  4. }  

这里又是构造了一个SockListener的实例,传入了上面创建的socket标识。

接着调用的start()函数,也是最终实现在SockListener的startListener()。

[cpp] view plain copy
  1. int NetlinkHandler::start() {    
  2.     return this->startListener();    
  3. }    
  4.     
  5. int SocketListener::startListener() {    
  6.     
  7.     if (!mSocketName && mSock == -1) {    
  8.         SLOGE("Failed to start unbound listener");    
  9.         errno = EINVAL;    
  10.         return -1;    
  11.     } else if (mSocketName) {    
  12.         if ((mSock = android_get_control_socket(mSocketName)) < 0) {    
  13.             SLOGE("Obtaining file descriptor socket '%s' failed: %s",    
  14.                  mSocketName, strerror(errno));    
  15.             return -1;    
  16.         }    
  17.     }    
  18.     
  19.     if (mListen && listen(mSock, 4) < 0) {    
  20.         SLOGE("Unable to listen on socket (%s)", strerror(errno));    
  21.         return -1;    
  22.     } else if (!mListen)    
  23.         mClients->push_back(new SocketClient(mSock, false));    
  24.     
  25.     if (pipe(mCtrlPipe)) {    
  26.         SLOGE("pipe failed (%s)", strerror(errno));    
  27.         return -1;    
  28.     }    
  29.     
  30.     if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {    
  31.         SLOGE("pthread_create (%s)", strerror(errno));    
  32.         return -1;    
  33.     }    
  34.     
  35.     return 0;    
  36. }    
  37.     
  38. void *SocketListener::threadStart(void *obj) {    
  39.     SocketListener *me = reinterpret_cast(obj);    
  40.     
  41.     me->runListener();    
  42.     pthread_exit(NULL);    
  43.     return NULL;    
  44. }    
  45.     
  46. void SocketListener::runListener() {    
  47.     
  48.     SocketClientCollection *pendingList = new SocketClientCollection();    
  49.     
  50.     while(1) { // 死循环,一直监听    
  51.         SocketClientCollection::iterator it;    
  52.         fd_set read_fds;    
  53.         int rc = 0;    
  54.         int max = -1;    
  55.     
  56.         FD_ZERO(&read_fds); //清空文件描述符集read_fds     
  57.     
  58.         if (mListen) {    
  59.             max = mSock;    
  60.             FD_SET(mSock, &read_fds); //添加文件描述符到文件描述符集read_fds    
  61.         }    
  62.     
  63.         FD_SET(mCtrlPipe[0], &read_fds); //添加管道的读取端文件描述符到read_fds    
  64.         if (mCtrlPipe[0] > max)    
  65.             max = mCtrlPipe[0];    
  66.     
  67.         pthread_mutex_lock(&mClientsLock); //对容器mClients的操作需要加锁    
  68.         for (it = mClients->begin(); it != mClients->end(); ++it) {    
  69.             int fd = (*it)->getSocket();    
  70.             FD_SET(fd, &read_fds); ////遍历容器mClients的所有成员,调用内联函数getSocket()获取文件描述符,并添加到文件描述符集read_fds    
  71.             if (fd > max)    
  72.                 max = fd;    
  73.         }    
  74.         pthread_mutex_unlock(&mClientsLock);    
  75.         // 等待文件描述符中某一文件描述符或者说socket有数据到来    
  76.         if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {    
  77.             if (errno == EINTR)    
  78.                 continue;    
  79.             SLOGE("select failed (%s)", strerror(errno));    
  80.             sleep(1);    
  81.             continue;    
  82.         } else if (!rc)    
  83.             continue;    
  84.     
  85.         if (FD_ISSET(mCtrlPipe[0], &read_fds))    
  86.             break;    
  87.         if (mListen && FD_ISSET(mSock, &read_fds)) { //监听套接字处理    
  88.             struct sockaddr addr;    
  89.             socklen_t alen;    
  90.             int c;    
  91.     
  92.             do {    
  93.                 alen = sizeof(addr);    
  94.                 c = accept(mSock, &addr, &alen); //接收链接请求,建立连接,如果成功c即为建立链接后的数据交换套接字,将其添加到mClient容器    
  95.             } while (c < 0 && errno == EINTR);    
  96.             if (c < 0) {    
  97.                 SLOGE("accept failed (%s)", strerror(errno));    
  98.                 sleep(1);    
  99.                 continue;    
  100.             }    
  101.             pthread_mutex_lock(&mClientsLock);    
  102.             mClients->push_back(new SocketClient(c, true));    
  103.             pthread_mutex_unlock(&mClientsLock);    
  104.         }    
  105.     
  106.         /* Add all active clients to the pending list first */    
  107.         pendingList->clear();    
  108.         pthread_mutex_lock(&mClientsLock);    
  109.         for (it = mClients->begin(); it != mClients->end(); ++it) {    
  110.             int fd = (*it)->getSocket();    
  111.             if (FD_ISSET(fd, &read_fds)) {    
  112.                 pendingList->push_back(*it);    
  113.             }    
  114.         }    
  115.         pthread_mutex_unlock(&mClientsLock);    
  116.     
  117.         /* Process the pending list, since it is owned by the thread,  
  118.          * there is no need to lock it */    
  119.         while (!pendingList->empty()) { //非监听套接字处理    
  120.             /* Pop the first item from the list */    
  121.             it = pendingList->begin();    
  122.             SocketClient* c = *it;    
  123.             pendingList->erase(it);    
  124.             /* Process it, if false is returned and our sockets are  
  125.              * connection-based, remove and destroy it */    
  126.             // ****** onDataAvailable在NetlinkListener中实现*********    
  127.              if (!onDataAvailable(c) && mListen) {    
  128.                 /* Remove the client from our array */    
  129.                 pthread_mutex_lock(&mClientsLock);    
  130.                 for (it = mClients->begin(); it != mClients->end(); ++it) {    
  131.                     if (*it == c) {    
  132.                         mClients->erase(it);    
  133.                         break;    
  134.                     }    
  135.                 }    
  136.                 pthread_mutex_unlock(&mClientsLock);    
  137.                 /* Remove our reference to the client */    
  138.                 c->decRef();    
  139.             }    
  140.         }    
  141.     }    
  142.     delete pendingList;    
  143. }  
SocketListener::runListener是线程真正执行的函数:mListen成员用来判定是否监听套接字,Netlink套接字属于udp套接字,非监听套接字,该函数的主要功能体现在,如果该套接字有数据到来,就调用函数onDataAvailable读取数据
[cpp] view plain copy
  1. bool NetlinkListener::onDataAvailable(SocketClient *cli)  
  2. {  
  3.     int socket = cli->getSocket();  
  4.     ssize_t count;  
  5.     uid_t uid = -1;  
  6.   
  7.     count = TEMP_FAILURE_RETRY(uevent_kernel_multicast_uid_recv(  
  8.                                        socket, mBuffer, sizeof(mBuffer), &uid)); //从socket中读取event信息  
  9.     if (count < 0) {  
  10.         if (uid > 0)  
  11.             LOG_EVENT_INT(65537, uid);  
  12.         SLOGE("recvmsg failed (%s)", strerror(errno));  
  13.         return false;  
  14.     }  
  15.   
  16.     NetlinkEvent *evt = new NetlinkEvent();  
  17.     if (!evt->decode(mBuffer, count, mFormat)) {   //调用NetlinkEvent解析event  
  18.         SLOGE("Error decoding NetlinkEvent");  
  19.     } else {  
  20.         onEvent(evt);  //传递event给子类NetlinkHandler处理  
  21.     }  
  22.   
  23.     delete evt;  
  24.     return true;  
  25. }  

到NetlinkHandler.cpp中:
[cpp] view plain copy
  1. void NetlinkHandler::onEvent(NetlinkEvent *evt) {  
  2.     VolumeManager *vm = VolumeManager::Instance();  
  3.     const char *subsys = evt->getSubsystem();  
  4.   
  5.     if (!subsys) {  
  6.         SLOGW("No subsystem found in netlink event");  
  7.         return;  
  8.     }  
  9.     if (!strcmp(subsys, "block")) {   //EVENT为block类型  
  10.         int action = evt->getAction();  
  11.   
  12.         vm->updatePullOutState(evt);  
  13.         vm->setHotPlug(true);  
  14.         vm->handleBlockEvent(evt);  //把event交给VolumeManager处理  
  15.         vm->setHotPlug(false);  
  16.   
  17.     }  

1.2.3 NM模块的总结

NM模块的功能就是从Kernel接收Uevent消息,然后转换成一个NetlinkEvent对象,最后会调用VM的处理函数来处理这个NetlinkEvent对象。

1.3 VolumeManager模块的分析

1.3.1 Vold使用VM模块的流程

vold挂载管理_第2张图片

  调用Instance创建一个VM对象。

  调用setBroadcaster设置CL对象

  调用start启动VM。

  调用process_config配置VM。

1.3.2 VM的创建

[cpp] view plain copy
  1. VolumeManager *VolumeManager::Instance() {  
  2.     if (!sInstance)  
  3.         sInstance = new VolumeManager();  
  4.     return sInstance;  
  5. }  

[cpp] view plain copy
  1. VolumeManager::VolumeManager() {  
  2.     mDebug = false;  
  3.     mVolumes = new VolumeCollection();  //一个容器,保存Volume的集合  
  4.     mActiveContainers = new AsecIdCollection();  
  5.     mBroadcaster = NULL;   //指向socketlisten,用于发送挂载事件  
  6.     mUmsSharingCount = 0;  
  7.     mSavedDirtyRatio = -1;  
  8.     mUmsDirtyRatio = dirtyRatio();  
  9.     mVolManagerDisabled = 0;  
  10.     mIsHotPlug= false;  
  11.     mUseBackupContainers =false;  
  12.     mIsFirstBoot = false;  
  13.     mIpoState = State_Ipo_Start;  
  14. }  

1.3.3 VM的启动

[cpp] view plain copy
  1. int VolumeManager::start() {  
  2.     return 0;   //VM的启动什么也没有做  
  3. }  

1.3.4 process_config分析

这个函数用于解析指定的配置文件,根据内容构造DirectVolume以及父类然后保存进VolumeManager的容器中,供VolumeManager用于挂载事件的管理
[cpp] view plain copy
  1. static int process_config(VolumeManager *vm)  
  2. {  
  3.     char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)];  
  4.     fstab = fs_mgr_read_fstab(fstab_filename);   //读取fstab文件  
  5.     if (!fstab) {  
  6.         SLOGE("failed to open %s\n", fstab_filename);  
  7.         return -1;  
  8.     }  
  9.     /* Loop through entries looking for ones that vold manages */  
  10.     for (i = 0; i < fstab->num_entries; i++) {  
  11.         if (fs_mgr_is_voldmanaged(&fstab->recs[i])) {  
  12.             DirectVolume *dv = NULL;  
  13.             flags = 0;  
  14.    
  15.             /* Set any flags that might be set for this volume */  
  16.             if (fs_mgr_is_nonremovable(&fstab->recs[i])) {  
  17.                 flags |= VOL_NONREMOVABLE;  
  18.             }  
  19.             if (fs_mgr_is_encryptable(&fstab->recs[i])) {  
  20.                 flags |= VOL_ENCRYPTABLE;  
  21.             }  
  22.             /* Only set this flag if there is not an emulated sd card */  
  23.             if (fs_mgr_is_noemulatedsd(&fstab->recs[i]) &&  
  24.                 !strcmp(fstab->recs[i].fs_type, "vfat")) {  
  25.                 flags |= VOL_PROVIDES_ASEC;  
  26.             }  
  27.             dv = new DirectVolume(vm, &(fstab->recs[i]), flags);  
  28.             if (dv->addPath(fstab->recs[i].blk_device)) {  
  29.                 SLOGE("Failed to add devpath %s to volume %s",  
  30.                       fstab->recs[i].blk_device, fstab->recs[i].label);  
  31.                 goto out_fail;  
  32.             }  
  33.             vm->addVolume(dv);  //添加到volumemanager容器  
  34.         }  
  35.     }  
  36. }  
fstab配置文件
[cpp] view plain copy
  1. # Android fstab file.  
  2. #                                           
  3. # The filesystem that contains the filesystem checker binary (typically /system) cannot  
  4. # specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK  
  5.   
  6. /devices/platform/mtk-msdc.0/mmc_host   auto      vfat      defaults        voldmanaged=sdcard0:emmc@fat,noemulatedsd  
  7. /devices/platform/mtk-msdc.1/mmc_host   auto      vfat      defaults        voldmanaged=sdcard1:auto  


1.3.5 DirectVolume分析


DirectVolume从Volume类派生,可把它看成是一个外部存储卡(例如一张SD卡)在代码中的代表物。它封装了对外部存储卡的操作,例如加载/卸载存储卡、格式化存储卡等
DirectVolume构造
[cpp] view plain copy
  1. DirectVolume::DirectVolume(VolumeManager *vm,const char *label,  
  2.                            const char*mount_point, int partIdx) :  
  3.               Volume(vm, label, mount_point) {//初始化基类  
  4.    /* 
  5.       注意其中的参数: 
  6.      label为”sdcard”,mount_point为”/mnt/sdcard”,partIdx为1     
  7. */  
  8.    mPartIdx = partIdx;  
  9. //PathCollection定义为typedef android::List PathCollection  
  10. //其实就是一个字符串list  
  11.     mPaths= new PathCollection();  
  12.     for(int i = 0; i < MAX_PARTITIONS; i++)  
  13.        mPartMinors[i] = -1;  
  14.    mPendingPartMap = 0;  
  15.    mDiskMajor = -1;  //存储设备的主设备号  
  16.    mDiskMinor = -1;  //存储设备的次设备号,一个存储设备将由主次两个设备号标识。  
  17.    mDiskNumParts = 0;  
  18.    //设置状态为NoMedia  
  19.    setState(Volume::State_NoMedia);  
  20. }  
  21. //再来看addPath函数,它主要目的是添加设备在sysfs中的路径,  
  22. int DirectVolume::addPath(const char *path) {  
  23.    mPaths->push_back(strdup(path));  
  24.     return0;  
  25. }  
addPath把和某个存储卡接口相关的设备路径与这个DirectVolume绑定到一起,并且这个设备路径和Uevent中的DEVPATH是对应的,这样就可以根据Uevent的DEVPATH找到是哪个存储卡的DirectVolume发生了变动。当然手机上目前只有一个存储卡接口,所以Vold也只有一个DirectVolume。

1.3.6 VM与NM交互

在分析NM模块的数据处理时发现,NM模块接收到Uevent事件后,会调用VM模块进行处理,如果Uevent是block子系统,则调用handleBlockEvent
[cpp] view plain copy
  1. void VolumeManager::handleBlockEvent(NetlinkEvent*evt) {  
  2.     constchar *devpath = evt->findParam("DEVPATH");  
  3.    
  4. /* 
  5. 前面在process_config中构造的DirectVolume对象保存在了mVolumes中,它的定义如下: 
  6. typedef android::ListVolumeCollection,也是一个列表。 
  7.   注意它保存的是Volume指针,而我们的DirectVolume是从Volume派生的 
  8. */  
  9.    VolumeCollection::iterator it;  
  10.     boolhit = false;  
  11. for (it = mVolumes->begin(); it !=mVolumes->end(); ++it) {  
  12.         //调用每个Volume的handleBlockEvent事件,实际上将调用  
  13.         //DirectVolume的handleBlockEvent函数。  
  14.         if(!(*it)->handleBlockEvent(evt)) {  
  15.            hit = true;  
  16.            break;  
  17.         }  
  18.     }  
  19. }  
[cpp] view plain copy
  1. int DirectVolume::handleBlockEvent(NetlinkEvent*evt) {  
  2.     constchar *dp = evt->findParam("DEVPATH");  
  3.    
  4. PathCollection::iterator  it;  
  5. //将Uevent的DEVPATH和addPath添加的路径进行对比,判断属不属于自己管理的范围。  
  6.     for(it = mPaths->begin(); it != mPaths->end(); ++it) {  
  7.         if(!strncmp(dp, *it, strlen(*it))) {  
  8.            int action = evt->getAction();  
  9.            const char *devtype = evt->findParam("DEVTYPE");  
  10.    
  11.            if (action == NetlinkEvent::NlActionAdd) {  
  12.                int major = atoi(evt->findParam("MAJOR"));  
  13.                int minor = atoi(evt->findParam("MINOR"));  
  14.                char nodepath[255];  
  15.    
  16.                snprintf(nodepath,  
  17.                          sizeof(nodepath),"/dev/block/vold/%d:%d",  
  18.                          major, minor);  
  19.                  //创建设备节点  
  20.                if (createDeviceNode(nodepath, major, minor)) {  
  21.                     ......  
  22.                }  
  23.                if (!strcmp(devtype, "disk")) {  
  24.                     handleDiskAdded(dp, evt);//添加一个磁盘  
  25.                } else {  
  26.                     /* 
  27. 对于有分区的SD卡,先收到上面的“disk”消息,然后每个分区就会收到 
  28.                    一个分区添加消息。 
  29.                    */  
  30.                     handlePartitionAdded(dp,evt);  
  31.                }  
  32.            } else if (action == NetlinkEvent::NlActionRemove) {  
  33.                 ......  
  34.            } else if (action == NetlinkEvent::NlActionChange) {  
  35.               ......  
  36.            }  
  37.            ......  
  38.            return 0;  
  39.         }  
  40.     }  
  41.     errno= ENODEV;  
  42.     return-1;  
  43. }  

1.4 commandlisten模块分析

1.4.1 commandlisten构造

 /system/vold/main.cpp
[cpp] view plain copy
  1. cl = new CommandListener();    
  2. vm->setBroadcaster((SocketListener *) cl);    
  3. nm->setBroadcaster((SocketListener *) cl);    
  4.     
  5. ...    
  6.     
  7. /*  
  8. * Now that we're up, we can respond to commands  
  9. */    
  10. if (cl->startListener()) {    
  11.    SLOGE("Unable to start CommandListener (%s)", strerror(errno));    
  12.    exit(1);    
  13. }  
构造函数/system/vold/CommandListener.cpp
[cpp] view plain copy
  1. CommandListener::CommandListener() :  
  2.                  FrameworkListener("vold"true) {  
  3.     registerCmd(new DumpCmd());  
  4.     registerCmd(new VolumeCmd());  
  5.     registerCmd(new AsecCmd());  
  6.     registerCmd(new ObbCmd());  
  7.     registerCmd(new StorageCmd());  
  8.     registerCmd(new XwarpCmd());  
  9.     registerCmd(new CryptfsCmd());  
  10.     registerCmd(new FstrimCmd());  
  11.     //M{  
  12. #ifndef MTK_EMULATOR_SUPPORT  
  13.     registerCmd(new USBCmd());  
  14. #endif  
  15.     registerCmd(new CDROMCmd());  
  16.     //}M  
  17. #if defined (ENG_BUILD_ENG)      
  18.     registerCmd(new SilkRoad());  
  19. #endif  
  20. }  
构造一个父类FrameworkListener
[cpp] view plain copy
  1. FrameworkListener::FrameworkListener(const char *socketName, bool withSeq) :    
  2.                             SocketListener(socketName, true, withSeq) {    
  3.     init(socketName, withSeq);    
  4. }  
[cpp] view plain copy
  1. void FrameworkListener::init(const char *socketName, bool withSeq) {    
  2.     mCommands = new FrameworkCommandCollection();    
  3.     errorRate = 0;    
  4.     mCommandCount = 0;    
  5.     mWithSeq = withSeq;    
  6. }  
[cpp] view plain copy
  1. SocketListener::SocketListener(const char *socketName, bool listen, bool useCmdNum) {  
  2.     init(socketName, -1, listen, useCmdNum);  
  3. }  
[cpp] view plain copy
  1. void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {  
  2.     mListen = listen;  
  3.     mSocketName = socketName;  
  4.     mSock = socketFd;  
  5.     mUseCmdNum = useCmdNum;  
  6.     pthread_mutex_init(&mClientsLock, NULL);  
  7.     mClients = new SocketClientCollection();  
  8. }  

1.4.2 Command注册

CommandListener的构造函数中调用父类的注册函数register, 注册commad到FrameworkListener的mCommands容器中
[cpp] view plain copy
  1. void FrameworkListener::registerCmd(FrameworkCommand *cmd) {  
  2.     mCommands->push_back(cmd);  
  3. }  
[cpp] view plain copy
  1. CommandListener::VolumeCmd::VolumeCmd() :  
  2.                  VoldCommand("volume") {  
  3. }  
[cpp] view plain copy
  1. VoldCommand::VoldCommand(const char *cmd) :  
  2.               FrameworkCommand(cmd)  {  
  3. }  
[cpp] view plain copy
  1. FrameworkCommand::FrameworkCommand(const char *cmd) {  
  2.     mCommand = cmd;  
  3. }  

将volume这个command注册到mCommands这个容器中之后,目的是当FrameworkListerer从SlocketListener接收到command的时候,会依据mCommands 中的command进行解析筛选判断分发,调用对应的command执行类

1.4.3 startListener的分析和数据处理

[cpp] view plain copy
  1. int SocketListener::startListener() {    
  2.     
  3.     if (!mSocketName && mSock == -1) {    
  4.         SLOGE("Failed to start unbound listener");    
  5.         errno = EINVAL;    
  6.         return -1;    
  7.     } else if (mSocketName) {    
  8.         if ((mSock = android_get_control_socket(mSocketName)) < 0) {//获取socket的文件描述符,这里是获取Vold这个socket的    
  9.             SLOGE("Obtaining file descriptor socket '%s' failed: %s",    
  10.                  mSocketName, strerror(errno));    
  11.             return -1;    
  12.         }    
  13.         SLOGV("got mSock = %d for %s", mSock, mSocketName);    
  14.     }    
  15.     
  16.     if (mListen && listen(mSock, 4) < 0) {    
  17.         SLOGE("Unable to listen on socket (%s)", strerror(errno));    
  18.         return -1;    
  19.     } else if (!mListen)//是否正常监听socket    
  20.         mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));    
  21.     
  22.     if (pipe(mCtrlPipe)) {//新建管道,保存文件描述符到数组    
  23.         SLOGE("pipe failed (%s)", strerror(errno));    
  24.         return -1;    
  25.     }    
  26.     
  27.     if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {//开了一个线程来处理    
  28.         SLOGE("pthread_create (%s)", strerror(errno));    
  29.         return -1;    
  30.     }    
  31.     
  32.     return 0;    
  33. }   
开启线程用于监听
[cpp] view plain copy
  1. void *SocketListener::threadStart(void *obj) {    
  2.     SocketListener *me = reinterpret_cast(obj);//threadStart为static函数,上面开线程创建的时候传了this,这里需要转换一个一样bit位的SocketListener指针    
  3.     
  4.     me->runListener();//SocketListener真正的执行函数    
  5.     pthread_exit(NULL);    
  6.     return NULL;    
  7. }   
对socket监听处理
[cpp] view plain copy
  1. void SocketListener::runListener() {    
  2.     
  3.     SocketClientCollection *pendingList = new SocketClientCollection();//暂存mClients中的SocketClient    
  4.     
  5.     while(1) {    
  6.         SocketClientCollection::iterator it;    
  7.         fd_set read_fds;    
  8.         int rc = 0;    
  9.         int max = -1;    
  10.     
  11.         FD_ZERO(&read_fds);//清空文件描述符集read_fds     
  12.     
  13.         if (mListen) {    
  14.             max = mSock;    
  15.             FD_SET(mSock, &read_fds);//如果正常的监听,这里就把之前获得的vold的文件描述符添加进去    
  16.         }    
  17.     
  18.         FD_SET(mCtrlPipe[0], &read_fds);//添加管道读取端的文件描述符    
  19.         if (mCtrlPipe[0] > max)    
  20.             max = mCtrlPipe[0];    
  21.     
  22.         pthread_mutex_lock(&mClientsLock);//加锁操作,多线程安全    
  23.         for (it = mClients->begin(); it != mClients->end(); ++it) {//遍历mClients,获取fd 添加到read_fds    
  24.             int fd = (*it)->getSocket();    
  25.             FD_SET(fd, &read_fds);    
  26.             if (fd > max)    
  27.                 max = fd;    
  28.         }    
  29.         pthread_mutex_unlock(&mClientsLock);//解锁    
  30.         SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName);    
  31.     
  32. //linux下socket编程的select,这里检测read_fds集合里面是否有可读的,也就是有没有数据过来,没有数据的文件描述符会从read_fds中被剔除,这里的select设置time out为NULL,阻塞操作,直到read_fds集合中描述符有变化    
  33.         if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {    
  34.             if (errno == EINTR)    
  35.                 continue;    
  36.             SLOGE("select failed (%s) mListen=%d, max=%d", strerror(errno), mListen, max);    
  37.             sleep(1);    
  38.             continue;    
  39.         } else if (!rc)    
  40.             continue;    
  41.     
  42.         if (FD_ISSET(mCtrlPipe[0], &read_fds))//如果匿名管道有数据可读,就退出    
  43.             break;    
  44.         if (mListen && FD_ISSET(mSock, &read_fds)) {//如果是正常监听的 mListen 为true,然后mSock这个描述符有可读数据,就创建链接,新建异步处理的SocketClient加入到mClients容器,这里的mSock 是vold这个套接字的描述符    
  45.             struct sockaddr addr;    
  46.             socklen_t alen;    
  47.             int c;    
  48.     
  49.             do {    
  50.                 alen = sizeof(addr);    
  51.                 c = accept(mSock, &addr, &alen);    
  52.                 SLOGV("%s got %d from accept", mSocketName, c);    
  53.             } while (c < 0 && errno == EINTR);    
  54.             if (c < 0) {    
  55.                 SLOGE("accept failed (%s)", strerror(errno));    
  56.                 sleep(1);    
  57.                 continue;    
  58.             }    
  59.             pthread_mutex_lock(&mClientsLock);    
  60.             mClients->push_back(new SocketClient(c, true, mUseCmdNum));    
  61.             pthread_mutex_unlock(&mClientsLock);    
  62.         }    
  63.     
  64.         /* Add all active clients to the pending list first */    
  65.         pendingList->clear();    
  66.         pthread_mutex_lock(&mClientsLock);    
  67.         for (it = mClients->begin(); it != mClients->end(); ++it) {//把上面有请求建立链接的Client加入到pendingList 容器中,后面处理    
  68.             int fd = (*it)->getSocket();    
  69.             if (FD_ISSET(fd, &read_fds)) {    
  70.                 pendingList->push_back(*it);    
  71.             }    
  72.         }    
  73.         pthread_mutex_unlock(&mClientsLock);    
  74.     
  75.         /* Process the pending list, since it is owned by the thread,  
  76.          * there is no need to lock it */    
  77.         while (!pendingList->empty()) {//遍历处理    
  78.             /* Pop the first item from the list */    
  79.             it = pendingList->begin();    
  80.             SocketClient* c = *it;    
  81.             pendingList->erase(it);    
  82.             /* Process it, if false is returned and our sockets are  
  83.              * connection-based, remove and destroy it */    
  84.             if (!onDataAvailable(c) && mListen) {//调用到FrameworkListener 中onDataAvailable处理Socket事件    
  85.                 /* Remove the client from our array */    
  86.                 SLOGV("going to zap %d for %s", c->getSocket(), mSocketName);    
  87.                 pthread_mutex_lock(&mClientsLock);    
  88.                 for (it = mClients->begin(); it != mClients->end(); ++it) {    
  89.                     if (*it == c) {    
  90.                         mClients->erase(it);//处理完成之后,从容器中移除这次的监听到的SocketClient    
  91.                         break;    
  92.                     }    
  93.                 }    
  94.                 pthread_mutex_unlock(&mClientsLock);    
  95.                 /* Remove our reference to the client */    
  96.                 c->decRef();    
  97.             }    
  98.         }    
  99.     }    
  100.     delete pendingList;    
  101. }    
SocketListener的核心处理函数是onDataAvailable
[cpp] view plain copy
  1. bool FrameworkListener::onDataAvailable(SocketClient *c) {    
  2.     char buffer[255];    
  3.     int len;    
  4.     
  5.     len = TEMP_FAILURE_RETRY(read(c->getSocket(), buffer, sizeof(buffer)));//读Socket 内容保存到buffer    
  6.     if (len < 0) {    
  7.         SLOGE("read() failed (%s)", strerror(errno));    
  8.         return false;    
  9.     } else if (!len)    
  10.         return false;    
  11.     
  12.     int offset = 0;    
  13.     int i;    
  14.     
  15.     for (i = 0; i < len; i++) {    
  16.         if (buffer[i] == '\0') {//一次传入一个字符串    
  17.            "font-family: Arial; font-size: 14px; line-height: 26px; background-color: rgb(240, 247, 254);"//分发命令,最终会调用对应命令对象的runCommand进行函数处理。  
  18.             dispatchCommand(c, buffer + offset);    
  19.             offset = i + 1;    
  20.         }    
  21.     }    
  22.     return true;    
  23. }    
调用dispatchcommand
[cpp] view plain copy
  1. void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {    
  2.     FrameworkCommandCollection::iterator i;    
  3.     int argc = 0;    
  4.     char *argv[FrameworkListener::CMD_ARGS_MAX];    
  5.     char tmp[255];    
  6.     
  7. ...//解析判断command buffer    
  8.     
  9.     for (i = mCommands->begin(); i != mCommands->end(); ++i) {//遍历之前register到FrameworkListener中的FrameworkCommand容器    
  10.         FrameworkCommand *c = *i;    
  11.     
  12.         if (!strcmp(argv[0], c->getCommand())) {//其中有注册“Volume”,如果这里是Volume开头的command,那么就调用,Volume构造的时候所构造的父类FrameworkCommand中的runCommand函数    
  13.             if (c->runCommand(cli, argc, argv)) {    
  14.                 SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));    
  15.             }    
  16.             goto out;    
  17.         }    
  18.     }     
  19. }   
以Volume这个runcommand为例 /system/vold/CommandListener.cpp中

[cpp] view plain copy
  1. int CommandListener::VolumeCmd::runCommand(SocketClient *cli,    
  2.                                                       int argc, char **argv) {    
  3.     dumpArgs(argc, argv, -1);    
  4.     
  5.     if (argc < 2) {    
  6.         cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing Argument"false);    
  7.         return 0;    
  8.     }    
  9.     
  10.     VolumeManager *vm = VolumeManager::Instance();//获取已经存在的VolumeManager实例    
  11.     
  12. ...    
  13.     
  14. else if (!strcmp(argv[1], "mount")) {//判断command 内容    
  15.         if (argc != 3) {    
  16.             cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume mount "false);    
  17.             return 0;    
  18.         }    
  19.         rc = vm->mountVolume(argv[2]);//交给VolumeManager来对Volume进行操作    
  20.     }    
  21.     
  22. ...    
  23.     
  24. }   

1.4.4 commandlisten 数据处理流程图

vold挂载管理_第3张图片


1.5 mountservice模块的介绍

1.5.1 mountservice构造

[cpp] view plain copy
  1. public MountService(Context context) {    
  2.         mContext = context;    
  3.     
  4.         synchronized (mVolumesLock) {    
  5.             readStorageListLocked(); // 解析/frameworks/base/core/res/res/xml/storage_list.xml保存volume到 MountService的list :mVolumes中    
  6.         }    
  7.     
  8.         // XXX: This will go away soon in favor of IMountServiceObserver    
  9.         mPms = (PackageManagerService) ServiceManager.getService("package");    
  10.     
  11.         mHandlerThread = new HandlerThread("MountService");    
  12.         mHandlerThread.start();    
  13.         mHandler = new MountServiceHandler(mHandlerThread.getLooper());//新建消息处理handler    
  14.     
  15.         // Watch for user changes    
  16.         final IntentFilter userFilter = new IntentFilter();    
  17.         userFilter.addAction(Intent.ACTION_USER_ADDED);    
  18.         userFilter.addAction(Intent.ACTION_USER_REMOVED);    
  19.         mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);//注册广播接收    
  20.     
  21.         // Watch for USB changes on primary volume    
  22.         final StorageVolume primary = getPrimaryPhysicalVolume();    
  23.         if (primary != null && primary.allowMassStorage()) {    
  24.             mContext.registerReceiver(    
  25.                     mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);    
  26.         }    
  27.     
  28.         // Add OBB Action Handler to MountService thread.    
  29.         mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());    
  30.     
  31.         /*  
  32.          * Create the connection to vold with a maximum queue of twice the  
  33.          * amount of containers we'd ever expect to have. This keeps an  
  34.          * "asec list" from blocking a thread repeatedly.  
  35.          */    
  36.         mConnector = new NativeDaemonConnector(this"vold", MAX_CONTAINERS * 2, VOLD_TAG, 25);//创建 vold 的监听接收,用于接收system中Vold的socket消息    
  37.     
  38.         Thread thread = new Thread(mConnector, VOLD_TAG);    
  39.         thread.start();//启动线程,NativeDaemonConnector实现了Runnable接口,实现在 run中    
  40.     
  41.         // Add ourself to the Watchdog monitors if enabled.    
  42.         if (WATCHDOG_ENABLE) {    
  43.             Watchdog.getInstance().addMonitor(this);    
  44.         }    
  45.     }   

1.5.2构造了NativeDaemonConnector用来接收来自下层的socket消息

[cpp] view plain copy
  1. NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,    
  2.         int responseQueueSize, String logTag, int maxLogSize) {    
  3.     mCallbacks = callbacks; //回调    
  4.     mSocket = socket; // socket名称    
  5.     mResponseQueue = new ResponseQueue(responseQueueSize);//构建一个响应队列    
  6.     mSequenceNumber = new AtomicInteger(0);    
  7.     TAG = logTag != null ? logTag : "NativeDaemonConnector";    
  8.   }  
开启监测线程的run方法
[cpp] view plain copy
  1. public void run() {    
  2.     HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); //TAG 为 VoldConnector,这里新建一个名为VoldConnector.CallbackHandler的消息处理线程,用于下面接收到vold 的socket之后的处理    
  3.     thread.start();    
  4.     mCallbackHandler = new Handler(thread.getLooper(), this); //创建handler 用于分发消息    
  5.      while (true) {    
  6.         try {    
  7.             listenToSocket();// while 循环 监听socket    
  8.         } catch (Exception e) {    
  9.             loge("Error in NativeDaemonConnector: " + e);    
  10.             SystemClock.sleep(5000);    
  11.         }    
  12.     }    
  13. }   
[cpp] view plain copy
  1. private void listenToSocket() throws IOException {    
  2.     LocalSocket socket = null;    
  3.     
  4.     try {    
  5.         socket = new LocalSocket();  //创建本地socket    
  6.         LocalSocketAddress address = new LocalSocketAddress(mSocket,    
  7.                 LocalSocketAddress.Namespace.RESERVED);//获得服务端vold socket的地址    
  8.     
  9.         socket.connect(address);//连接    
  10.     
  11.         InputStream inputStream = socket.getInputStream();    
  12.         synchronized (mDaemonLock) {    
  13.             mOutputStream = socket.getOutputStream();    
  14.         }//获取输入输出流    
  15.     
  16.         mCallbacks.onDaemonConnected();//回调,在MountService中执行,初始化一些Volume状态信息    
  17.     
  18.         byte[] buffer = new byte[BUFFER_SIZE];    
  19.         int start = 0;    
  20.     
  21.         while (true) {    
  22.             int count = inputStream.read(buffer, start, BUFFER_SIZE - start);//读取数据到buffer    
  23.             if (count < 0) {//连接断开,跳出当前while ,外部while循环 重新调用该函数连接    
  24.                 loge("got " + count + " reading with start = " + start);    
  25.                 break;    
  26.             }    
  27.     
  28.     
  29.             // Add our starting point to the count and reset the start.    
  30.             count += start;    
  31.             start = 0;    
  32.     
  33.             for (int i = 0; i < count; i++) {    
  34.                 if (buffer[i] == 0) {    
  35.                     final String rawEvent = new String(    
  36.                             buffer, start, i - start, Charsets.UTF_8);    
  37.                     log("RCV <- {" + rawEvent + "}");    
  38.     
  39.                     try {    
  40.                         final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(    
  41.                                 rawEvent);   //解析成event 保存    
  42.                         if (event.isClassUnsolicited()) {    //判断event的code范围 code >= 600 && code < 700    
  43.                             // TODO: migrate to sending NativeDaemonEvent instances    
  44.                             mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage( //发送消息,把event交给handle来分发处理    
  45.                                     event.getCode(), event.getRawEvent()));    
  46.                         } else {    
  47.                             mResponseQueue.add(event.getCmdNumber(), event);//加入到响应队列    
  48.                         }    
  49.                     } catch (IllegalArgumentException e) {    
  50.                         log("Problem parsing message: " + rawEvent + " - " + e);    
  51.                     }    
  52.     
  53.                     start = i + 1;    
  54.                 }    
  55.             }  
在NativeDaemonConnector中的handle处理为:
[cpp] view plain copy
  1. public boolean handleMessage(Message msg) {    
  2.     String event = (String) msg.obj;    
  3.     try {    
  4.         if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) { //回调到MountService 的onEent函数    
  5.             log(String.format("Unhandled event '%s'", event));    
  6.         }    
  7.     } catch (Exception e) {    
  8.         loge("Error handling '" + event + "': " + e);    
  9.     }    
  10.     return true;    
  11. }   
MountService中onEvent
[cpp] view plain copy
  1. public boolean onEvent(int code, String raw, String[] cooked) {    
  2.         if (DEBUG_EVENTS) {    
  3.             StringBuilder builder = new StringBuilder();    
  4.             builder.append("onEvent::");    
  5.             builder.append(" raw= " + raw);    
  6.             if (cooked != null) {    
  7.                 builder.append(" cooked = " );    
  8.                 for (String str : cooked) {    
  9.                     builder.append(" " + str);    
  10.                 }    
  11.             }    
  12.             Slog.i(TAG, builder.toString());    
  13.         }    
  14.         if (code == VoldResponseCode.VolumeStateChange) { //根据 Vold的Code 执行    
  15.             /*  
  16.              * One of the volumes we're managing has changed state.  
  17.              * Format: "NNN Volume  
  18.              * from  () to  ()"  
  19.              */    
  20.             notifyVolumeStateChange(    
  21.                     cooked[2], cooked[3], Integer.parseInt(cooked[7]),    
  22.                             Integer.parseInt(cooked[10])); //更新状态    
  23.         } else if ((code == VoldResponseCode.VolumeDiskInserted) ||    
  24.                    (code == VoldResponseCode.VolumeDiskRemoved) ||    
  25.                    (code == VoldResponseCode.VolumeBadRemoval))    
  26.     ...    
  27.     
  28.                   if (code == VoldResponseCode.VolumeDiskInserted) { //如果接收到的是插入disk的消息,则执行挂载操作    
  29.                 new Thread() {    
  30.                     @Override    
  31.                     public void run() {    
  32.                         try {    
  33.                             int rc;    
  34.                             if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {    
  35.                                 Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));    
  36.                             }    
  37.                         } catch (Exception ex) {    
  38.                             Slog.w(TAG, "Failed to mount media on insertion", ex);    
  39.                         }    
  40.                     }    
  41.                 }.start();    
  42.             }    
  43. }  

1.5.3 MountService下发command

MountService中对Volume的各种操作都是需要转换成符合Vold中socket command,这样Vold中才能正确的解析识别调用
[cpp] view plain copy
  1. private int doMountVolume(String path) {    
  2.     int rc = StorageResultCode.OperationSucceeded;    
  3.     
  4.     final StorageVolume volume;    
  5.     synchronized (mVolumesLock) {    
  6.         volume = mVolumesByPath.get(path);    
  7.     }    
  8.     
  9.     if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);    
  10.     try {    
  11.         mConnector.execute("volume""mount", path);// 调用到NativeDaemonConnector中的execute    
  12.     }   
NativeDaemonConnector的execute
[cpp] view plain copy
  1. public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)    
  2.             throws NativeDaemonConnectorException {    
  3.         final ArrayList events = Lists.newArrayList();    
  4.     
  5.         final int sequenceNumber = mSequenceNumber.incrementAndGet();    
  6.         final StringBuilder cmdBuilder =    
  7.                 new StringBuilder(Integer.toString(sequenceNumber)).append(' ');    
  8.         final long startTime = SystemClock.elapsedRealtime();    
  9.     
  10.         makeCommand(cmdBuilder, cmd, args); //转换制作成标准的Command    
  11.     
  12.         final String logCmd = cmdBuilder.toString(); /* includes cmdNum, cmd, args */    
  13.         log("SND -> {" + logCmd + "}");    
  14.     
  15.         cmdBuilder.append('\0');    
  16.         final String sentCmd = cmdBuilder.toString(); /* logCmd + \0 */    
  17.     
  18.         synchronized (mDaemonLock) {    
  19.             if (mOutputStream == null) {    
  20.                 throw new NativeDaemonConnectorException("missing output stream");    
  21.             } else {    
  22.                 try {    
  23.                     mOutputStream.write(sentCmd.getBytes(Charsets.UTF_8)); //通过在listenToSocket中获取的输出流,写入转换好的sentCmd    
  24.                 } catch (IOException e) {    
  25.                     throw new NativeDaemonConnectorException("problem sending command", e);    
  26.                 }    
  27.             }    
  28.         }    
  29. }  

1.5.4 Mountservice 数据处理流程图

vold挂载管理_第4张图片



你可能感兴趣的:(Android之SD卡相关)