您的位置:首页 > 移动开发 > Android开发

Android磁盘管理

2013-07-15 10:49 323 查看

Android磁盘管理

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

TAG: 磁盘管理
系统源码分析

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源码分析(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文件的源代码:
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函数的入口点深入分析流程。。

vold源码分析(3)

NetlinkManager类负责管理捕获内核的uevent事件,这里使用了Netlink套接字。

Netlink的概念:

Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。Netlink套接字可以使用标准的套接字APIs来创建。socket(), bind(), sendmsg(), recvmsg() 和 close()很容易地应用到 netlink socket。netlink包含于头文件linux/netlink.h中。

平时的应用层一般都不会用到Netlink这个套接字,了解就行。

在Main.cpp文件中的main函数里面,有一个准备工作是用来开启监听内核uevent事件的线程,源码如下:
if (nm->start()) {
SLOGE("Unable to start NetlinkManager (%s)", strerror(errno));
exit(1);
}


nm是NetlinkManager类实例化的一个对象,以下是start()函数的源码:
/************************************
**file:system/vold/NetlinkManager.cpp
**以下一些socket的初始化,跟linux的应用层的tcp/udp使用差不多。
************************************/
int NetlinkManager::start() {
struct sockaddr_nl nladdr;
int sz = 64 * 1024;

memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid();
nladdr.nl_groups = 0xffffffff;

if ((mSock = socket(PF_NETLINK, SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) {
SLOGE("Unable to create uevent socket: %s", strerror(errno));
return -1;
}

if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
SLOGE("Unable to set uevent socket options: %s", strerror(errno));
return -1;
}

if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
SLOGE("Unable to bind uevent socket: %s", strerror(errno));
return -1;
}
/*************************************
**这里先说明NetlinkHandler类的继承关系:
**NetlinkHandler --> NetlinkListener --> SocketListener(父类)
**NetlinkHandler类的start()函数调用了SocketListener::startListener()函数,源码如下。
*************************************/
mHandler = new NetlinkHandler(mSock);
if (mHandler->start()) {
SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));
return -1;
}
return 0;
}
/******************************************
**file:system/vold/NetlinkHandler.cpp
**该函数使用this指针调用自身的startListener函数,可以发现,在NetlinkHandler没有
**startListener()这个函数,这函数是它的父类的函数SocketListener::startListener;
******************************************/
int NetlinkHandler::start() {
return this->startListener();
}
/******************************************
**file:system/core/libsysutils/src/SocketListener.cpp
**以下这个函数就涉及到其他方面的内容了,不在vold部分。
******************************************/
int SocketListener::startListener() {
if (!mSocketName && mSock == -1) {
SLOGE("Failed to start unbound listener");
errno = EINVAL;
return -1;
} else if (mSocketName) {
if ((mSock = android_get_control_socket(mSocketName)) < 0) {
SLOGE("Obtaining file descriptor socket '%s' failed: %s",
mSocketName, strerror(errno));
return -1;
}
}

if (mListen && listen(mSock, 4) < 0) {
SLOGE("Unable to listen on socket (%s)", strerror(errno));
return -1;
} else if (!mListen)
mClients->push_back(new SocketClient(mSock));

if (pipe(mCtrlPipe)) {
SLOGE("pipe failed (%s)", strerror(errno));
return -1;
}
/************************************
**该函数开启了一个监听线程。
************************************/
if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {
SLOGE("pthread_create (%s)", strerror(errno));
return -1;
}

return 0;
}

/***********************************************
**该线程函数在类里面声明为静态函数:static void *threadStart(void *obj);
**所以不能调用this指针来指向自身的函数,所以通过pthread_create线程的创建函数来传递
**一个参数,将this指针传递给它;
**这里使用reinterpret_cast运算符是用来处理无关类型之间的转换,
**它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。
***********************************************/
void *SocketListener::threadStart(void *obj) {
SocketListener *me = reinterpret_cast<SocketListener *>(obj);

me->runListener();
pthread_exit(NULL);
return NULL;
}
/***************************************************
**该函数才是真正的处理函数了,使用select集合,结合fd_set结构体,可以判断套接字有无
**信息可读,如果没有,立即返回,不阻塞;
**还使用了管道,仅仅判断该套接字的读端是否有数据可读。
****************************************************/
void SocketListener::runListener() {
while(1) {
SocketClientCollection::iterator it;
fd_set read_fds;
int rc = 0;
int max = 0;

FD_ZERO(&read_fds);

if (mListen) {
max = mSock;
FD_SET(mSock, &read_fds);
}

FD_SET(mCtrlPipe[0], &read_fds);
if (mCtrlPipe[0] > max)
max = mCtrlPipe[0];

pthread_mutex_lock(&mClientsLock);
for (it = mClients->begin(); it != mClients->end(); ++it) {
FD_SET((*it)->getSocket(), &read_fds);
if ((*it)->getSocket() > max)
max = (*it)->getSocket();
}
pthread_mutex_unlock(&mClientsLock);

if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {
SLOGE("select failed (%s)", strerror(errno));
sleep(1);
continue;
} else if (!rc)
continue;

if (FD_ISSET(mCtrlPipe[0], &read_fds))
break;
if (mListen && FD_ISSET(mSock, &read_fds)) {
struct sockaddr addr;
socklen_t alen = sizeof(addr);
int c;

if ((c = accept(mSock, &addr, &alen)) < 0) {
SLOGE("accept failed (%s)", strerror(errno));
sleep(1);
continue;
}
pthread_mutex_lock(&mClientsLock);
mClients->push_back(new SocketClient(c));
pthread_mutex_unlock(&mClientsLock);
}

do {
pthread_mutex_lock(&mClientsLock);
for (it = mClients->begin(); it != mClients->end(); ++it) {
int fd = (*it)->getSocket();
if (FD_ISSET(fd, &read_fds)) {
pthread_mutex_unlock(&mClientsLock);
/*********************************************
**onDataAvailable是SocketListener类声明的一个纯虚函数,在其子类NetlinkListener实现
**onDataAvailable函数,函数里面调用了NetlinkHandler类的onEvent函数,该函数是
**在NetlinkListener类中定义的纯虚函数,在vold中的NetlinkHandler类中实现。
*********************************************/
if (!onDataAvailable(*it)) {
close(fd);
pthread_mutex_lock(&mClientsLock);
delete *it;
it = mClients->erase(it);
pthread_mutex_unlock(&mClientsLock);
}
FD_CLR(fd, &read_fds);
continue;
}
}
pthread_mutex_unlock(&mClientsLock);
} while (0);
}
}

/*****************************************************
**file:system/core/libsysutils/src/NetlinkListener.cpp
**该函数用来处理内核的uevent事件,然后调用onEvent函数,让onEvent函数去捕获这些事件
**的信息。
******************************************************/
bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
int socket = cli->getSocket();
int count;

if ((count = recv(socket, mBuffer, sizeof(mBuffer), 0)) < 0) {
SLOGE("recv failed (%s)", strerror(errno));
return false;
}

NetlinkEvent *evt = new NetlinkEvent();
if (!evt->decode(mBuffer, count)) {
SLOGE("Error decoding NetlinkEvent");
goto out;
}
/*下一篇文章介绍该函数*/
onEvent(evt);
out:
delete evt;
return true;
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 磁盘管理