Android磁盘管理-系统源码分析(1)

来源:互联网 发布:linux 壁纸 编辑:程序博客网 时间:2024/05/23 13:39

作者:gzshun. 原创作品,转载请标明出处!

http://blog.csdn.net/kenny_wju/article/details/8924528

Vold是Android系统处理磁盘的核心部分,取代了原来Linux系统中的udev,主要用来处理Android系统的热插拔存储设备。在Android2.2以后的系统中,vold源码已经移到了system目录下,vold目录包含以下源码:
├── Android.mk
├── Asec.h
├── CleanSpec.mk
├── CommandListener.cpp
├── CommandListener.h
├── Devmapper.cpp
├── Devmapper.h
├── DirectVolume.cpp
├── DirectVolume.h
├── Fat.cpp
├── Fat.h
├── hash.h
├── logwrapper.c
├── Loop.cpp
├── Loop.h
├── main.cpp
├── NetlinkHandler.cpp
├── NetlinkHandler.h
├── NetlinkManager.cpp
├── NetlinkManager.h
├── Process.cpp
├── Process.h
├── ResponseCode.cpp
├── ResponseCode.h
├── vdc.c
├── VoldCommand.cpp
├── VoldCommand.h
├── Volume.cpp
├── Volume.h
├── VolumeManager.cpp
├── VolumeManager.h
├── Xwarp.cpp
└── Xwarp.h


先简要说明一下类的继承关系,vold中比较重要的有以下几个类:
三大管理类:VolumeManager,CommandListener,NetlinkManager
其他处理类:Volume,DirectVolume,NetlinkHandler,Fat,ResponseCode
其他相关的类:NetlinkListener,SocketListener

1.VolumeManager管理Volume类;
2.DirectVolume类继承于Volume类,保存着磁盘信息与操作函数;
3.NetlinkManager类负责与内核uevent事件通信,期间,使用到了NetlinkListener和SocketListener类的函数;
4.Fat是格式化sd卡的函数;
5.ResponseCode保存着vold向framework反馈的值。

本文讲解main.cpp文件的源代码:

[cpp] view plaincopy
  1. int main() {  
  2.     /********************************************************************************** 
  3.     **以下三个类声明三个指针对象: 
  4.     **VolumeManager     :管理所有存储设备(volume对象); 
  5.     **CommandListener   :监听Framework下发的消息,并分析命令,调用响应的操作函数; 
  6.     **NetlinkManager  :监听Linux内核的热插拔事件,uevent事件 
  7.     **********************************************************************************/  
  8.     VolumeManager *vm;  
  9.     CommandListener *cl;  
  10.     NetlinkManager *nm;  
  11.   
  12.     SLOGI("Vold 2.1 (the revenge) firing up");  
  13.     /********************************************************************************** 
  14.     **在Linux系统,如scsi硬盘,U盘的设备节点默认生成在/dev/目录下,Android把这些设备 
  15.     **节点改到了/dev/block/目录下。但随着热插拔事件的产生,设备节点(如sda,sdb)经常变换, 
  16.     **对于vold来说,可能有点麻烦,所以在/dev/block/下新建了一个名为vold的目录,存放sda, 
  17.     **sdb对应的设备节点,形如"8:0"。 
  18.     **eg:sda 的主次设备号分别为8,0,于是vold就会在vold目录下创建名为"8:0"的节点,基于主次设备号 
  19.     **命名,便于程序操作,增加了灵活性。 
  20.     **********************************************************************************/  
  21.     mkdir("/dev/block/vold", 0755);  
  22.   
  23.     /********************************************************************************** 
  24.     **实例化vm对象,VolumeManager类调用自身的Instance函数,new了一个对象给vm。 
  25.     **源码: 
  26.     VolumeManager *VolumeManager::Instance() { 
  27.         if (!sInstance) 
  28.             sInstance = new VolumeManager(); 
  29.         return sInstance; 
  30.     } 
  31.     **********************************************************************************/  
  32.     if (!(vm = VolumeManager::Instance())) {  
  33.         SLOGE("Unable to create VolumeManager");  
  34.         exit(1);  
  35.     };  
  36.       
  37.     /********************************************************************************** 
  38.     **实例化nm对象,NetlinkManager类调用自身的Instance函数,new了一个对象给nm。 
  39.     **源码: 
  40.     NetlinkManager *NetlinkManager::Instance() { 
  41.         if (!sInstance) 
  42.             sInstance = new NetlinkManager(); 
  43.         return sInstance; 
  44.     } 
  45.     **********************************************************************************/  
  46.     if (!(nm = NetlinkManager::Instance())) {  
  47.         SLOGE("Unable to create NetlinkManager");  
  48.         exit(1);  
  49.     };  
  50.   
  51.     /********************************************************************************** 
  52.     **实例化cl对象; 
  53.     **vm->setBroadcaster((SocketListener *) cl); 
  54.     **setBroadcaster函数将VolumeManager的成员变量mBroadcaster设置成cl,这两个变量都是 
  55.     **SocketListener的指针类型,命令执行状态广播函数就会调用这个SocketListener指针来调用 
  56.     **SocketListener类的广播函数; 
  57.     **为什么SocketListener类能强制转换CommandListener类呢? 
  58.     **原因:继承关系:CommandListener(子类) --> FrameworkListener(子类) --> SocketListener(父类) 
  59.     **将子类强制转换为父类是没错的。 
  60.     **********************************************************************************/  
  61.     cl = new CommandListener();  
  62.     vm->setBroadcaster((SocketListener *) cl);  
  63.     nm->setBroadcaster((SocketListener *) cl);  
  64.       
  65.     /********************************************************************************** 
  66.     **调用start函数启动存储设备的管理类,看了源码,这函数没干什么事,估计去哪打酱油了。 
  67.     **源码: 
  68.     int VolumeManager::start() { 
  69.         return 0; 
  70.     } 
  71.     **********************************************************************************/  
  72.     if (vm->start()) {  
  73.         SLOGE("Unable to start VolumeManager (%s)", strerror(errno));  
  74.         exit(1);  
  75.     }  
  76.   
  77.     /********************************************************************************** 
  78.     **process_config函数用来解析/etc/vold.fstab的配置文件,从代码可以看出,配置文件的参数 
  79.     **以空格和制表格(Tab键)分隔;系统启动起来,分析该配置文件,挂载相应的分区,相当于 
  80.     **Linux系统的/etc/fstab文件。 
  81.     **********************************************************************************/  
  82.     if (process_config(vm)) {  
  83.         SLOGE("Error reading configuration (%s)... continuing anyways", strerror(errno));  
  84.     }  
  85.       
  86.     /********************************************************************************** 
  87.     **nm对象调用start函数开启了一个线程,用来监听底层的uevent事件;这start函数干的事就 
  88.     **多了,主要是打开一个udp套接字,循环监听底层事件。线程里面使用了Select函数来处理 
  89.     **套接字,这设计到fd_set结构体等等的使用; 
  90.     **当捕获到uevent事件,vold会将该事件通知给Framework层,Framework进行判断,然后再 
  91.     **下发操作命令。 
  92.     **********************************************************************************/  
  93.     if (nm->start()) {  
  94.         SLOGE("Unable to start NetlinkManager (%s)", strerror(errno));  
  95.         exit(1);  
  96.     }  
  97.       
  98.     coldboot("/sys/block");  
  99.   
  100.     /********************************************************************************** 
  101.     **下面是判断Android系统是否处于ums状态,ums是大容量存储的意思,这是Android系统 
  102.     **的OTG功能。OTG是on-the-go的简称,主要提供与pc机的连接; 
  103.     **notifyUmsConnected函数将ums的状态通知给Framework层,于是Framework与UI配合,弹出 
  104.     **一个与pc机连接的交互界面。 
  105.     **********************************************************************************/  
  106.     {  
  107.         FILE *fp;  
  108.         char state[255];  
  109.           
  110.         if ((fp = fopen("/sys/devices/virtual/switch/usb_mass_storage/state","r"))) {  
  111.             if (fgets(state, sizeof(state), fp)) {  
  112.                 if (!strncmp(state, "online", 6)) {  
  113.                     vm->notifyUmsConnected(true);  
  114.                 } else {  
  115.                     vm->notifyUmsConnected(false);  
  116.                 }  
  117.                 } else {  
  118.                     SLOGE("Failed to read switch state (%s)", strerror(errno));  
  119.                 }  
  120.           
  121.             fclose(fp);  
  122.         } else {  
  123.             SLOGW("No UMS switch available");  
  124.         }  
  125.     }  
  126.   
  127.     /********************************************************************************** 
  128.     **上面的准备工作已做完,现在是vold比较重要的一个处理线程; 
  129.     **startListener是CommandListener类的父类的函数,该函数用于开启监听线程,监听 
  130.     **Framework层下发给vold的命令,然后调用相应的命令操作存储设备。 
  131.     **********************************************************************************/  
  132.     if (cl->startListener()) {  
  133.         SLOGE("Unable to start CommandListener (%s)", strerror(errno));  
  134.         exit(1);  
  135.     }  
  136.   
  137.     /********************************************************************************** 
  138.     **进入一个循环,让vold保持守护进程的状态; 
  139.     **vold的主要工作是由:nm->start()和cl->startListener()两个线程共同完成;这两个处理线程 
  140.     **中间需要Framework来充当桥梁与boss的身份,Framework是管理这些磁盘的boss。 
  141.     **********************************************************************************/  
  142.     while(1) {  
  143.         sleep(1000);  
  144.     }  
  145.       
  146.     SLOGI("Vold exiting");  
  147.     exit(0);  
  148.     }  
  149.   
  150. /********************************************************************************** 
  151. **以下这两个函数不重要,也就是打开/sys/block目录处理一些事情;这俩函数用来给vold打杂, 
  152. **社会阶级比较低,o(∩_∩)o 哈哈。 
  153. **里面有几个函数是bionic库提供的,用得比较少。 
  154. **********************************************************************************/  
  155. static void do_coldboot(DIR *d, int lvl)  
  156. {  
  157.     struct dirent *de;  
  158.     int dfd, fd;  
  159.   
  160.     dfd = dirfd(d);  
  161.   
  162.     fd = openat(dfd, "uevent", O_WRONLY);  
  163.     if(fd >= 0) {  
  164.             write(fd, "add\n", 4);  
  165.             close(fd);  
  166.     }  
  167.   
  168.     while((de = readdir(d))) {  
  169.         DIR *d2;  
  170.   
  171.         if (de->d_name[0] == '.')  
  172.                 continue;  
  173.   
  174.         if (de->d_type != DT_DIR && lvl > 0)  
  175.                 continue;  
  176.   
  177.         fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);  
  178.         if(fd < 0)  
  179.                 continue;  
  180.   
  181.         d2 = fdopendir(fd);  
  182.         if(d2 == 0)  
  183.                 close(fd);  
  184.         else {  
  185.                 do_coldboot(d2, lvl + 1);  
  186.                 closedir(d2);  
  187.         }  
  188.     }  
  189. }  
  190.   
  191. static void coldboot(const char *path)  
  192. {  
  193.         DIR *d = opendir(path);  
  194.         if(d) {  
  195.                 do_coldboot(d, 0);  
  196.                 closedir(d);  
  197.         }  
  198. }  
  199.   
  200. /********************************************************************************** 
  201. **该函数用来解析/etc/vold.fstab配置文件,文本的处理; 
  202. **可能不同的源码版本,有点差异; 
  203. **strsep是字符串的分割函数,可以看出该函数是以" \t"来分割(\t前面有一空格),分割空格 
  204. **或制表格,所以配置文件里面空格与tab键来分割都行; 
  205. **strsep不是ANSI C的函数,但它用来取代strtok函数,strtok是线程不安全的函数。 
  206. **********************************************************************************/  
  207. static int process_config(VolumeManager *vm) {  
  208.     FILE *fp;  
  209.     int n = 0;  
  210.     char line[255];  
  211.   
  212.     if (!(fp = fopen("/etc/vold.fstab""r"))) {  
  213.             return -1;  
  214.     }  
  215.   
  216.     while(fgets(line, sizeof(line), fp)) {  
  217.         char *next = line;  
  218.         char *type, *label, *mount_point;  
  219.   
  220.         n++;  
  221.         line[strlen(line)-1] = '\0';  
  222.   
  223.         if (line[0] == '#' || line[0] == '\0')  
  224.                 continue;  
  225.   
  226.         if (!(type = strsep(&next, " \t"))) {  
  227.                 SLOGE("Error parsing type");  
  228.                 goto out_syntax;  
  229.         }  
  230.         if (!(label = strsep(&next, " \t"))) {  
  231.                 SLOGE("Error parsing label");  
  232.                 goto out_syntax;  
  233.         }  
  234.         if (!(mount_point = strsep(&next, " \t"))) {  
  235.                 SLOGE("Error parsing mount point");  
  236.                 goto out_syntax;  
  237.         }  
  238.   
  239.         if (!strcmp(type, "dev_mount")) {  
  240.             DirectVolume *dv = NULL;  
  241.             char *part, *sysfs_path;  
  242.   
  243.             if (!(part = strsep(&next, " \t"))) {  
  244.                     SLOGE("Error parsing partition");  
  245.                     goto out_syntax;  
  246.             }  
  247.             if (strcmp(part, "auto") && atoi(part) == 0) {  
  248.                     SLOGE("Partition must either be 'auto' or 1 based index instead of '%s'", part);  
  249.                     goto out_syntax;  
  250.             }  
  251.             /********************************************************************************** 
  252.             **如果配置文件指定为auto,则为自动挂载存储设备,在实例化DirectVolume的对象,传递-1 
  253.             **进去,否则将分区序数part传进去; 
  254.             **********************************************************************************/  
  255.             if (!strcmp(part, "auto")) {  
  256.                     dv = new DirectVolume(vm, label, mount_point, -1);  
  257.             } else {  
  258.                     dv = new DirectVolume(vm, label, mount_point, atoi(part));  
  259.             }  
  260.   
  261.             while((sysfs_path = strsep(&next, " \t"))) {  
  262.                 /********************************************************************************** 
  263.                 **将存储设备在/sys/对应的路径添加进PathCollection容器,该容器为“char *”类型; 
  264.                 **在/sys/里面可以获取到存储设备的热插拔事件,所以DirectVolume类的主要工作就是针对 
  265.                 **这里去获取uevent事件的; 
  266.                 **DirectVolume::handleBlockEvent(NetlinkEvent *evt)函数去得到这些事件,主要还是 
  267.                 **NetlinkListener类从内核捕获到的。 
  268.                 **********************************************************************************/  
  269.                 if (dv->addPath(sysfs_path)) {  
  270.                         SLOGE("Failed to add devpath %s to volume %s", sysfs_path,  
  271.                                  label);  
  272.                         goto out_fail;  
  273.                 }  
  274.             }  
  275.             /********************************************************************************** 
  276.             **如果在配置文件有找到正确的挂载参数,那么就会将DirectVolume的对象添加到VolumeCollection 
  277.             **容器中,该容器存放着Volume*类型的数据,VolumeManager的对象vm是用来管理这些存储设备的; 
  278.             **一块存储设备就会实例化一个Volume对象,但对于手机来说,一般只能识别到一张SD卡。 
  279.             **********************************************************************************/  
  280.             vm->addVolume(dv);  
  281.         } else if (!strcmp(type, "map_mount")) {  
  282.         } else {  
  283.                 SLOGE("Unknown type '%s'", type);  
  284.                 goto out_syntax;  
  285.         }  
  286.     }  
  287.   
  288.     fclose(fp);  
  289.     return 0;  
  290.   
  291. /********************************************************************************** 
  292. **从这个main函数的出错处理可以看出,系统源码经常使用到这种高效性的goto技巧,goto在 
  293. **系统中的出错处理用得很频繁,可以说几乎每个文件都使用到了goto跳转函数; 
  294. **很多文章或者教材,经常反面性的批判goto的不规则,但从这些外国的开源代码可以看出, 
  295. **那些牛人都很喜欢用goto,利用了goto来处理出错情况的技巧,显得很漂亮; 
  296. **我觉得,要从实用性的角度来评论这些语言的优缺点,并不能用否认的说法来解释,这样才能 
  297. **不断地进步; 
  298. **所以,如果在出错处理非常多的情况下,使用goto是使代码更可读,减少重复的出错判断的 
  299. **代码量。 
  300. **********************************************************************************/  
  301. out_syntax:  
  302.         SLOGE("Syntax error on config line %d", n);  
  303.         errno = -EINVAL;  
  304. out_fail:  
  305.         fclose(fp);  
  306.         return -1;     
  307. }  


下篇文章开始从main函数的入口点深入分析流程。。
原创粉丝点击