Android磁盘管理

转自: http://www.eefocus.com/bbs/article_867_194001.html


本系列文章,将会详细讲解Android系统的磁盘管理部分,可以通过这个突破口,熟悉整个Android的系统架构与实现原理。

Android磁盘管理总共涉及到四大部分源码:
1.Linux kernel: Android建立在Linux内核的基础上,最底层的部分是由Linux kernel来负责的,用于检测热插拔事件;
2.Vold:Android没有使用Linux平台下的udev来处理,于是Google写了一个类似udev功能的vold,充当了kernel与framework之间的桥梁;


3.Framework:Android的核心框架,(仅仅磁盘管理这部分)负责操作vold,给vold下发操作命令;
4.UI:Androidd的系统应用,与Framework进行交互,用于挂载/卸载SD卡。

Android挂载SD卡,流程如下:
1.用户在“设置”页面的“SD卡和手机内存”中,点击“挂载”;
2.UI从Framework获取操作磁盘的函数(向Framework注册,才能使用的函数),然后调用挂载的处理函数;
3.该处理函数通过广播机制发送挂载命令“volume mount sdcard”,vold接受命令并挂载SD卡后,用广播通知Framework;
4.Framework收到挂载SD卡的回复,通知UI的处理结果;
5.界面显示挂载成功/挂载失败。

从这里可以看出,Android的磁盘管理涉及到整个系统框架,这是Android系统很重要的一个子系统,通过磁盘管理的子系统来熟悉Android系统架构,能够了解到Android的多方面知识。从SD卡的挂载流程可以看出,Android系统大量地使用IPC,从而做到了模块独立的效果。从磁盘管理的那四大部分来看,四个部分之间的相互联系均是使用socket进行通信,没有使用到传统的API调用,整个系统就显得非常的独立。

源码的位置:
Vold:system/vold
Framework: frameworks/base/services/java/com/Android/server
UI: Android-2.2r2/packages/apps/Settings/src/com/android/settings/deviceinfo/

 

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函数的入口点深入分析流程。



 
RE:Android磁盘管理
 
上篇文章分析到了NetlinkHandler类中的onEvent函数,该函数由NetlinkListener::onDataAvailable函数调用,当SocketListener类监听到内核的uevent事件,调用该函数,之后的事情交给onEvent来负责。
file:system/vold/NetlinkHandler.cpp
现在来说onEvent函数,在vold中,磁盘的热插拔事件都是通过上述那些间接的过程来调用到 
该函数,该函数再调用vold中的处理事件的函数,这样vold就能得到最新的磁盘热插拔事件; 
void NetlinkHandler::onEvent(NetlinkEvent *evt) { 
VolumeManager *vm = VolumeManager::Instance(); 
const char *subsys = evt->getSubsystem(); 

if (!subsys) { 
SLOGW("No subsystem found in netlink event"); 
return; 


if (!strcmp(subsys, "block")) { 
vm->handleBlockEvent(evt); 
} else if (!strcmp(subsys, "switch")) { 
vm->handleSwitchEvent(evt); 
} else if (!strcmp(subsys, "battery")) { 
} else if (!strcmp(subsys, "power_supply")) { 


file:system/vold/VolumeManager.cpp
vm->handleSwitchEvent(evt)函数涉及的比较少,先来分析;
该函数是用来处理大容量存储设备,也就是otg功能,NetlinkEvent类提供的findParam函数
可以获取到该事件的具体信息,比如设备路径,设备名称,状态,分区数量等等。。
如果判断是“online”状态,那么就向framework发送状态消息。使用notifyUmsConnected函数
进行广播。 
void VolumeManager::handleSwitchEvent(NetlinkEvent *evt) { 
const char *devpath = evt->findParam("DEVPATH"); 
const char *name = evt->findParam("SWITCH_NAME"); 
const char *state = evt->findParam("SWITCH_STATE"); 

if (!name || !state) { 
SLOGW("Switch %s event missing name/state info", devpath); 
return; 


if (!strcmp(name, "usb_mass_storage")) { 
if (!strcmp(state, "online")) { 
notifyUmsConnected(true); 
} else { 
notifyUmsConnected(false); 

} else { 
SLOGW("Ignoring unknown switch '%s'", name); 


notifyUmsConnected函数将otg的状态发送给framework,
命令为:Share method ums now available/unavailable;
getBroadcaster()->sendBroadcast()广播函数,这在main函数分析中就涉及到了,
源码:
cl = new CommandListener();
vm->setBroadcaster((SocketListener *) cl);
nm->setBroadcaster((SocketListener *) cl);
将CommandListener对象强制转换成SocketListener类型,这样cl对象就能够调用SocketListener
类的setBroadcaster广播函数;
getBroadcaster()的源码:
SocketListener *getBroadcaster() { return mBroadcaster; }
直接将刚才被强制转换成SocketListener类型返回,相当于如下:
(SocketListener *)cl->sendBroadcast(xxx); 
void VolumeManager::notifyUmsConnected(bool connected) { 
char msg[255]; 

if (connected) { 
mUsbMassStorageConnected = true; 
} else { 
mUsbMassStorageConnected = false; 

snprintf(msg, sizeof(msg), "Share method ums now %s", (connected ? "available" : "unavailable")); 

getBroadcaster()->sendBroadcast(ResponseCode::ShareAvailabilityChange, msg, false); 

file:system/vold/ResponseCode.cpp
该类没做什么事,也就是提供一些出错标志,下面将写出出错原因;
这些标志都会在不同的操作(命令)反馈不同的值。 
class ResponseCode { 
public: 
// 100 series - Requestion action was initiated; expect another reply 
// before proceeding with a new command. 
static const int ActionInitiated = 100; 

static const int VolumeListResult = 110; 
static const int AsecListResult = 111; 
static const int StorageUsersListResult = 112; 

// 200 series - Requested action has been successfully completed 
static const int CommandOkay = 200; 
static const int ShareStatusResult = 210; 
static const int AsecPathResult = 211; 
static const int ShareEnabledResult = 212; 
static const int XwarpStatusResult = 213; 

// 400 series - The command was accepted but the requested action 
// did not take place.//这几个标志有点重复性,其实vold里面也没有这几个标志。 
static const int OperationFailed = 400; 
static const int OpFailedNoMedia = 401; 
static const int OpFailedMediaBlank = 402; 
static const int OpFailedMediaCorrupt = 403; 
static const int OpFailedVolNotMounted = 404; 
static const int OpFailedStorageBusy = 405; 
static const int OpFailedStorageNotFound = 406; 

// 500 series - The command was not accepted and the requested 
// action did not take place. 
static const int CommandSyntaxError = 500;// 
static const int CommandParameterError = 501;// 

// 600 series - Unsolicited broadcasts 
static const int UnsolicitedInformational = 600; 
static const int VolumeStateChange = 605; 
static const int VolumeMountFailedBlank = 610; 
static const int VolumeMountFailedDamaged = 611; 
static const int VolumeMountFailedNoMedia = 612; 

static const int ShareAvailabilityChange = 620; 

static const int VolumeDiskInserted = 630; 
static const int VolumeDiskRemoved = 631; 
static const int VolumeBadRemoval = 632; 

static int convertFromErrno(); 
}; 
file:system/vold/VolumeManager.cpp
该函数用来捕获磁盘的热插拔事件信息。 
void VolumeManager::handleBlockEvent(NetlinkEvent *evt) { 
const char *devpath = evt->findParam("DEVPATH"); 

VolumeCollection::iterator it; 
bool hit = false; 
/********************************************************************************** 
**mVolumes是一个存放volume*的容器,类型如下: 
**typedef Android::List VolumeCollection; 
**mVolumes是在main函数中添加进磁盘的,在mian函数中,使用process_config函数分析 
**/etc/vold.fstab配置文件,然后将磁盘的信息添加进该容器,以便后续的操作。 
**********************************************************************************/ 
for (it = mVolumes->begin(); it != mVolumes->end(); ++it) { 
if (!(*it)->handleBlockEvent(evt)) { 
#ifdef NETLINK_DEBUG 
SLOGD("Device '%s' event handled by volume %s\n", devpath, (*it)->getLabel()); 
#endif 
hit = true; 
break; 


if (!hit) { 
#ifdef NETLINK_DEBUG 
SLOGW("No volumes handled block event for '%s'", devpath); 
#endif 


这里使用(*it)调用handleBlockEvent函数,可以看出handleBlockEvent在Volume类对象实现,
源码在system/vold/Volume.cpp
可以发现,该函数并没有做什么事情,其实handleBlockEvent函数在Volume的子类DirectVolume
中重写了。 
int Volume::handleBlockEvent(NetlinkEvent *evt) { 
errno = ENOSYS; 
return -1; 

file:system/vold/DirectVolume.cpp
这函数是处理热插拔事件最重要的函数,Volume若要操作磁盘,肯定先要从这个函数获取到
磁盘事件和信息。 
int DirectVolume::handleBlockEvent(NetlinkEvent *evt) { 
const char *dp = evt->findParam("DEVPATH"); 

PathCollection::iterator it; 
for (it = mPaths->begin(); it != mPaths->end(); ++it) { 
size_t len = strlen(*it); 
if (!strncmp(dp, *it, len) && (dp[len] == '\0' || dp[len] == '/')) { 
/* We can handle this disk */ 
int action = evt->getAction(); 
const char *devtype = evt->findParam("DEVTYPE"); 
/********************************************************************************** 
**NetlinkEvent提供4个uevent状态,代码: 
**const int NetlinkEvent::NlActionUnknown = 0; 
const int NetlinkEvent::NlActionAdd = 1; //增加硬盘或分区的事件 
const int NetlinkEvent::NlActionRemove = 2;//移除硬盘或分区的事件 
const int NetlinkEvent::NlActionChange = 3;//改变硬盘或分区的事件 
**这里的事件是增加存储设备 
**********************************************************************************/ 
if (action == NetlinkEvent::NlActionAdd) { 
/*从这里获取到主次设备号*/ 
int major = atoi(evt->findParam("MAJOR")); 
int minor = atoi(evt->findParam("MINOR")); 
char nodepath[255]; 

/********************************************************************************** 
**之前有提到过,vold在/dev/block/vold创建了相应的设备节点,诸如8:0形式的节点,这里 
**就是创建节点的位置,createDeviceNode函数。 
**********************************************************************************/ 
snprintf(nodepath, sizeof(nodepath), "/dev/block/vold/%d:%d", major, minor); 
if (createDeviceNode(nodepath, major, minor)) { 
SLOGE("Error making device node '%s' (%s)", nodepath, strerror(errno)); 

/*磁盘类型,指的就是一块存储设备*/ 
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); 

} else { 
SLOGW("Ignoring non add/remove/change event"); 

return 0; 


errno = ENODEV; 
return -1; 

该函数主要对存储设备或分区处理,每个存储设备或分区都有增加、删除和改变的事件,下一篇文章介绍
每个不同事件和函数的处理,有以下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); 
下一篇文章继续。
 
 
RE:Android磁盘管理
 
上篇文章分析到了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也就根本不理会也不知道底层发生了什么事情。
 



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值