Android-vold源码分析之handleBlockEvent(5)

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

上篇文章分析到了handleBlockEvent函数,该函数包含了以下6个处理函数:

void handleDiskAdded(const char *devpath, NetlinkEvent *evt);
void handleDiskRemoved(const char *devpath, NetlinkEvent *evt);
void handleDiskChanged(const char *devpath, NetlinkEvent *evt);
void handlePartitionAdded(const char *devpath, NetlinkEvent *evt);
void handlePartitionRemoved(const char *devpath, NetlinkEvent *evt);
void handlePartitionChanged(const char *devpath, NetlinkEvent *evt);

以下是精简版的handleBlockEvent函数:

if (action == NetlinkEvent::NlActionAdd) {
	...
	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);
	}
}

这样看起来就比较清楚每个函数的作用了,贴源码其实是比较直接的方法,程序员对代码都比较敏感,一看就明白意思,好,开始分析。

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);
	} else {
		SLOGW("Kernel block uevent missing 'NPARTS'");
		mDiskNumParts = 0;
	}
	/**********************************************************************************
	**mPendingPartsCount是一个全局变量,用来保存该存储设备的分区数量;这里需要说明一个
	**存储设备识别的顺序:
	**当插入一块5个分区的硬盘,首先会调用handleDiskAdded函数获取该存储设备的事件信息,
	**随后会调用若干次handlePartitionAdded函数来识别该存储设备的多个分区的事件信息,
	**当然,一般5个分区的硬盘肯定有一个扩展节点(因为mbr最多支持4个主分区);
	**调用顺序是这样:
	**handleDiskAdded函数调用1次;
	**handlePartitionAdded函数调用6次;
	**该变量的作用是这样:插入一块硬盘,此时mPendingPartsCount变量为分区数量,
	**开始用handlePartitionAdded函数识别分区,每识别一个分区,
	**mPendingPartsCount自减一次,当mPendingPartsCount==0时,结束该存储设备事件的捕获。
	**********************************************************************************/
	mPendingPartsCount = mDiskNumParts;
	
	if (mDiskNumParts == 0) {
		/**********************************************************************************
		**broadcastDiskAdded函数的作用是通知framework,系统插入一块存储设备,源码如下:		
		void DirectVolume::broadcastDiskAdded()
		{
			setState(Volume::State_Idle);
			char msg[255];
			snprintf(msg, sizeof(msg), "Volume %s %s disk inserted (%d:%d)",
					getLabel(), getMountpoint(), mDiskMajor, mDiskMinor);
			mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted, msg, false);
		}
		**********************************************************************************/
		broadcastDiskAdded();
	} else {
		/**********************************************************************************
		**setState函数起到的作用很大,相当于一只信鸽,每次sd卡发现状态改变,该函数马上就将
		**最新的状态广播给framework,后面会详细介绍该函数与framework的通信。
		**********************************************************************************/
		setState(Volume::State_Pending);
	}
}

随后就是handlePartitionAdded函数了,这个函数要识别插入的设备的所有分区,源码如下:

void DirectVolume::handlePartitionAdded(const char *devpath, NetlinkEvent *evt) {
	int major = atoi(evt->findParam("MAJOR"));
	int minor = atoi(evt->findParam("MINOR"));
	int part_num;
	
	const char *tmp = evt->findParam("PARTN");
	
	if (tmp) {
		part_num = atoi(tmp);
	} else {
		SLOGW("Kernel block uevent missing 'PARTN'");
		part_num = 1;
	}
	
	if (part_num > mDiskNumParts) {
		mDiskNumParts = part_num;
	}
	
	if (major != mDiskMajor) {
		SLOGE("Partition '%s' has a different major than its disk!", devpath);
		return;
	}
	/**********************************************************************************
	**上面就是做一下其他的判断,不重要;
	**MAX_PARTITIONS定义在system/vold/DirectVolume.h文件中,声明如下:
	static const int MAX_PARTITIONS = 4;
	Android系统支持太有限,谷歌太懒,呵呵,最多就识别4个分区,当然如果有些厂商想多实现
	**分区的识别数量,需要修改源码;
	**我觉得,Android系统是做得不错,但磁盘管理方面不太完善,自从分析修改了vold源码,
	**vold支持得太少,也许谷歌以前只想到应用于手机,要是哥想识别一块10个分区的硬盘,
	**咋办?修改源码咯。。。
	**********************************************************************************/
	if (part_num > MAX_PARTITIONS) {
		SLOGE("Dv:partAdd: ignoring part_num = %d (max: %d)\n", part_num, MAX_PARTITIONS);
	} else {
		/*全局数组,用来存放磁盘分区的此设备号*/
		mPartMinors[part_num -1] = minor;
	}
	/*看到了吧,上面那个函数说到mPendingPartsCount每识别一个分区要自减一次,就在这里*/
	--mPendingPartsCount;
	/*这里就在判断mPendingPartsCount变量了,如果mPendingPartsCount==0时,再向framework广播
	一次该设备的插入,所以framework总共需要收到磁盘的插入广播2次,才会下发操作命令。*/
	if (!mPendingPartsCount) {
		/*判断了磁盘的状态,如果正在格式化,将不做操作*/
		if (getState() != Volume::State_Formatting) {
			broadcastDiskAdded();
		}
	} else {
	}
}

磁盘在被系统识别完后,可能发生改变,这种改变的例子如下:

在Linux系统,大家格式化硬盘就会使用到了,就是fdisk命令,该命令会修改磁盘的一些分区参数,

当然,fdisk只是把分区信息写到存储设备的第一个设备节点或扩展节点。

fdisk里面有一个操作是修改分区类型id,按“t”就能修改,当修改完成后,保存退出fdisk,磁盘的设备节点

将会重新生成。

这里是Android系统,也可能遇到这种情况,以下是源码:

void DirectVolume::handleDiskChanged(const char *devpath, NetlinkEvent *evt) {
	int major = atoi(evt->findParam("MAJOR"));
	int minor = atoi(evt->findParam("MINOR"));
	
	if ((major != mDiskMajor) || (minor != mDiskMinor)) {
		return;
	}
	
	SLOGI("Volume %s disk has changed", getLabel());
	const char *tmp = evt->findParam("NPARTS");
	if (tmp) {
		mDiskNumParts = atoi(tmp);
	} else {
		SLOGW("Kernel block uevent missing 'NPARTS'");
		mDiskNumParts = 0;
	}
	mPendingPartsCount = mDiskNumParts;
	
	if (getState() != Volume::State_Formatting) {
		if (mDiskNumParts == 0) {
			/*这里类似于fdisk将删除存储设备的所有分区,这样存储设备的分区数量mDiskNumParts
			就等于0,此时广播磁盘的空闲状态*/
			setState(Volume::State_Idle);
		} else {
			setState(Volume::State_Pending);
		}
	}
}

分区的改变:

void DirectVolume::handlePartitionChanged(const char *devpath, NetlinkEvent *evt) {
	int major = atoi(evt->findParam("MAJOR"));
	int minor = atoi(evt->findParam("MINOR"));
	SLOGD("Volume %s %s partition %d:%d changed\n", getLabel(), getMountpoint(), major, minor);
}

以上两个参数基本没涉及到什么重要的内容,看下源码就行。

刚才上面分析了handleDiskAdded和handlePartitionAdded,这两个增加磁盘或分区的函数,当然也需要

对应移除磁盘或分区的函数,是handleDiskRemoved和handlePartitionRemoved函数。

void DirectVolume::handleDiskRemoved(const char *devpath, NetlinkEvent *evt) {
	int major = atoi(evt->findParam("MAJOR"));
	int minor = atoi(evt->findParam("MINOR"));
	char msg[255];
	
	SLOGD("Volume %s %s disk %d:%d removed\n", getLabel(), getMountpoint(), major, minor);
	snprintf(msg, sizeof(msg), "Volume %s %s disk removed (%d:%d)",
				getLabel(), getMountpoint(), major, minor);
	mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskRemoved, msg, false);
	/*设备移除后,广播通知framework*/
	setState(Volume::State_NoMedia);
}

移除一块存储设备比较方便,当移除分区涉及的比较多,移除分区需要卸载分区,并且删除设备节点,以下是删除分区的源码:

void DirectVolume::handlePartitionRemoved(const char *devpath, NetlinkEvent *evt) {
	int major = atoi(evt->findParam("MAJOR"));
	int minor = atoi(evt->findParam("MINOR"));
	char msg[255];
	int state;
	
	SLOGD("Volume %s %s partition %d:%d removed\n", getLabel(), getMountpoint(), major, minor);
	
	state = getState();
	if (state != Volume::State_Mounted && state != Volume::State_Shared) {
		return;
	}
	
	if ((dev_t) MKDEV(major, minor) == mCurrentlyMountedKdev) {
		snprintf(msg, sizeof(msg), "Volume %s %s bad removal (%d:%d)",
				getLabel(), getMountpoint(), major, minor);
		/*mCurrentlyMountedKdev变量保存着目前正挂载在系统的存储设备的设备号,
		这里的判断是这样:如果目前正在移除的分区等于挂载的存储设备的设备号,说明
		该存储设备没有被安全删除,也就是没有先卸载后移除*/
		mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeBadRemoval, msg, false);
		
		/*卸载挂载在asec目录的分区*/
		if (mVm->cleanupAsec(this, true)) {
			SLOGE("Failed to cleanup ASEC - unmount will probably fail!");
		}
		/*卸载该分区挂载的所有挂载点,这里为什么用所有来形容了,因为Android
		系统挂载一个分区的期间,重复挂载在好几个目录,将分区挂载在/mnt/asec目录,也挂载
		在/mnt/secure/asec目录,也挂载在/mnt/sdcard目录下,总共三次挂载,谷歌不知为什么搞这么复杂?
		待深究。。*/
		if (Volume::unmountVol(true)) {
			SLOGE("Failed to unmount volume on bad removal (%s)", 
			strerror(errno));
		} else {
			SLOGD("Crisis averted");
		}
	} else if (state == Volume::State_Shared) {
		snprintf(msg, sizeof(msg), "Volume %s bad removal (%d:%d)",
		getLabel(), major, minor);
		mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeBadRemoval, msg, false);
		/*这种情况是这样:如果手机跟电脑连接在一起,电脑正在使用sd卡,你把sd卡取出,
		就会广播该错误信息给framework*/
		if (mVm->unshareVolume(getLabel(), "ums")) {
			SLOGE("Failed to unshare volume on bad removal (%s)",
			strerror(errno));
		} else {
			SLOGD("Crisis averted");
		}
	}
}

这几章介绍了磁盘事件的处理,总算可以告一段落,这些工作就是在main函数中的nm->start()函数负责的,下一篇文章可以分析其他处理函数了,但这些事件的处理起着至关重要的作用,如果没有做这些工作,framework也就根本不理会也不知道底层发生了什么事情。

下一篇继续磁盘操作部分。。

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