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

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


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

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;	 
}


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

你可能感兴趣的:(android,存储,FP,磁盘,linux内核,Parsing)