Android磁盘管理系列之vold源码分析(2)

 

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文件的源代码:

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

原文:http://blog.csdn.net/gzshun/article/details/7096802

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/jasontome/article/details/7172618
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞