只讲VolumeCmd类的runCommand函数的实现。
VolumeCmd可以说是最重要的,该类的实现处理了list,debug,mount,unmount,format,unshare,shared等等操作,详细说明:
list: 在启动vold之后,接收的第一条命令就是list,这里是获取系统的所有磁盘对象,一般只有sd卡。
debug: 设置USB的调试模式
mount: 挂载磁盘
unmount: 卸载磁盘
format: 格式化磁盘
unshare: 关闭USB的大容量存储模式,相当于断开手机与电脑的连接
shared: 开启USB的大容量存储模式,相当于手机与电脑连接,并挂载在电脑
每次操作完成后,都要将操作结果汇报给framework,阶级负责工作,一定要把工作结果汇报给领导,这里使用了sendMsg函数。
1.搜索需要挂载的磁盘对象信息列表。先从listVolumes函数开始,贴源码:
这函数扫描了mVolumes容器,里面保存着从配置文件/etc/vold.fstab解析出来的磁盘。
该容器是在main函数中添加的:vm->addVolume(dv);
这函数先将每个磁盘的标签,挂载点,状态汇报给framework,然后再循环工作结束后,再次向领导汇报工作结束。
2.设置调试模式。贴源码:
每个Volume对象都有一个setDebug函数设置调试状态:
VolumeCmd可以说是最重要的,该类的实现处理了list,debug,mount,unmount,format,unshare,shared等等操作,详细说明:
list: 在启动vold之后,接收的第一条命令就是list,这里是获取系统的所有磁盘对象,一般只有sd卡。
debug: 设置USB的调试模式
mount: 挂载磁盘
unmount: 卸载磁盘
format: 格式化磁盘
unshare: 关闭USB的大容量存储模式,相当于断开手机与电脑的连接
shared: 开启USB的大容量存储模式,相当于手机与电脑连接,并挂载在电脑
下面把该函数的源码贴出来,一目了然,程序员,代码说话:
- int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
- int argc, char **argv) {
- dumpArgs(argc, argv, -1);
- if (argc < 2) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing Argument", false);
- return 0;
- }
- VolumeManager *vm = VolumeManager::Instance();
- int rc = 0;
- if (!strcmp(argv[1], "list")) {
- return vm->listVolumes(cli);
- } else if (!strcmp(argv[1], "debug")) {
- if (argc != 3 || (argc == 3 && (strcmp(argv[2], "off") && strcmp(argv[2], "on")))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume debug <off/on>", false);
- return 0;
- }
- vm->setDebug(!strcmp(argv[2], "on") ? true : false);
- } else if (!strcmp(argv[1], "mount")) {
- if (argc != 3) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume mount <path>", false);
- return 0;
- }
- rc = vm->mountVolume(argv[2]);
- } else if (!strcmp(argv[1], "unmount")) {
- if (argc < 3 || argc > 4 || (argc == 4 && strcmp(argv[3], "force"))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume unmount <path> [force]", false);
- return 0;
- }
- bool force = false;
- if (argc >= 4 && !strcmp(argv[3], "force")) {
- force = true;
- }
- rc = vm->unmountVolume(argv[2], force);
- } else if (!strcmp(argv[1], "format")) {
- if (argc != 3) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume format <path>", false);
- return 0;
- }
- rc = vm->formatVolume(argv[2]);
- } else if (!strcmp(argv[1], "share")) {
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume share <path> <method>", false);
- return 0;
- }
- rc = vm->shareVolume(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "unshare")) {
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume unshare <path> <method>", false);
- return 0;
- }
- rc = vm->unshareVolume(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "shared")) {
- bool enabled = false;
- if (argc != 4) {
- cli->sendMsg(ResponseCode::CommandSyntaxError,
- "Usage: volume shared <path> <method>", false);
- return 0;
- }
- if (vm->shareEnabled(argv[2], argv[3], &enabled)) {
- cli->sendMsg(
- ResponseCode::OperationFailed, "Failed to determine share enable state", true);
- } else {
- cli->sendMsg(ResponseCode::ShareEnabledResult,
- (enabled ? "Share enabled" : "Share disabled"), false);
- }
- return 0;
- } else {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume cmd", false);
- }
- if (!rc) {
- cli->sendMsg(ResponseCode::CommandOkay, "volume operation succeeded", false);
- } else {
- int erno = errno;
- rc = ResponseCode::convertFromErrno();
- cli->sendMsg(rc, "volume operation failed", true);
- }
- return 0;
- }
每次操作完成后,都要将操作结果汇报给framework,阶级负责工作,一定要把工作结果汇报给领导,这里使用了sendMsg函数。
1.搜索需要挂载的磁盘对象信息列表。先从listVolumes函数开始,贴源码:
- int VolumeManager::listVolumes(SocketClient *cli) {
- VolumeCollection::iterator i;
- for (i = mVolumes->begin(); i != mVolumes->end(); ++i) {
- char *buffer;
- asprintf(&buffer, "%s %s %d",
- (*i)->getLabel(), (*i)->getMountpoint(),
- (*i)->getState());
- cli->sendMsg(ResponseCode::VolumeListResult, buffer, false);
- free(buffer);
- }
- cli->sendMsg(ResponseCode::CommandOkay, "Volumes listed.", false);
- return 0;
- }
这函数扫描了mVolumes容器,里面保存着从配置文件/etc/vold.fstab解析出来的磁盘。
该容器是在main函数中添加的:vm->addVolume(dv);
这函数先将每个磁盘的标签,挂载点,状态汇报给framework,然后再循环工作结束后,再次向领导汇报工作结束。
2.设置调试模式。贴源码:
- void VolumeManager::setDebug(bool enable) {
- mDebug = enable;
- VolumeCollection::iterator it;
- for (it = mVolumes->begin(); it != mVolumes->end(); ++it) {
- (*it)->setDebug(enable);
- }
- }
每个Volume对象都有一个setDebug函数设置调试状态:
- void Volume::setDebug(bool enable) {
- mDebug = enable;
- }
-
3.挂载磁盘。这里都有一个const char *类型的参数,这参数保存着每个磁盘的标签信息,比如sd卡的label是sdcard。
- int VolumeManager::mountVolume(const char *label) {
- Volume *v = lookupVolume(label);
- if (!v) {
- errno = ENOENT;
- return -1;
- }
- return v->mountVol();
- }
lookupVolume函数寻找与label匹配的对象:
- Volume *VolumeManager::lookupVolume(const char *label) {
- VolumeCollection::iterator i;
- for (i = mVolumes->begin(); i != mVolumes->end(); ++i) {
- if (label[0] == '/') {
- if (!strcmp(label, (*i)->getMountpoint()))
- return (*i);
- } else {
- if (!strcmp(label, (*i)->getLabel()))
- return (*i);
- }
- }
- return NULL;
- }
如果找到,直接返回磁盘对象Volume*,挂载操作在mountVol函数里面,该函数内容有点多,贴源码:
- int Volume::mountVol() {
- dev_t deviceNodes[4];
- int n, i, rc = 0;
- char errmsg[255];
- if (getState() == Volume::State_NoMedia) {
- snprintf(errmsg, sizeof(errmsg),
- "Volume %s %s mount failed - no media",
- getLabel(), getMountpoint());
- mVm->getBroadcaster()->sendBroadcast(
- ResponseCode::VolumeMountFailedNoMedia,
- errmsg, false);
- errno = ENODEV;
- return -1;
- } else if (getState() != Volume::State_Idle) {
- errno = EBUSY;
- return -1;
- }
- /*判断该挂载点是否已经挂载,若已经挂载,则直接返回。*/
- if (isMountpointMounted(getMountpoint())) {
- SLOGW("Volume is idle but appears to be mounted - fixing");
- /*这里的setState函数用得很频繁,这函数就是将状态通知给framework*/
- setState(Volume::State_Mounted);
- // mCurrentlyMountedKdev = XXX
- return 0;
- }
- /*获取磁盘的设备号与分区数量,在下面说明*/
- n = getDeviceNodes((dev_t *) &deviceNodes, 4);
- if (!n) {
- SLOGE("Failed to get device nodes (%s)\n", strerror(errno));
- return -1;
- }
- /*将循环挂载n个分区,但从代码上看,只适用于挂载一个分区*/
- for (i = 0; i < n; i++) {
- char devicePath[255];
- /*这里看到了吧,用这种方式来使用磁盘的设备节点很方便,直接用主次设备号来命令*/
- sprintf(devicePath, "/dev/block/vold/%d:%d", MAJOR(deviceNodes[i]),
- MINOR(deviceNodes[i]));
- SLOGI("%s being considered for volume %s\n", devicePath, getLabel());
- errno = 0;
- setState(Volume::State_Checking);
- /*挂载之前先检测一下该分区是否是fat分区,如果不是fat格式,返回-1*/
- if (Fat::check(devicePath)) {
- if (errno == ENODATA) {
- SLOGW("%s does not contain a FAT filesystem\n", devicePath);
- continue;
- }
- errno = EIO;
- /* Badness - abort the mount */
- SLOGE("%s failed FS checks (%s)", devicePath, strerror(errno));
- setState(Volume::State_Idle);
- return -1;
- }
- /*
- * Mount the device on our internal staging mountpoint so we can
- * muck with it before exposing it to non priviledged users.
- */
- errno = 0;
- /*将设备节点挂载在/mnt/secure/staging目录*/
- if (Fat::doMount(devicePath, "/mnt/secure/staging", false, false, 1000, 1015, 0702, true)) {
- SLOGE("%s failed to mount via VFAT (%s)\n", devicePath, strerror(errno));
- continue;
- }
- SLOGI("Device %s, target %s mounted @ /mnt/secure/staging", devicePath, getMountpoint());
- protectFromAutorunStupidity();
- /*挂载一个只有root用户能够访问的目录,这函数挂载了两次
- 将/mnt/secure/staging/.android_secure 挂载在 /mnt/secure/asec,
- 将tmpfs 挂载在 /mnt/secure/staging/.android_secure*/
- if (createBindMounts()) {
- SLOGE("Failed to create bindmounts (%s)", strerror(errno));
- umount("/mnt/secure/staging");
- setState(Volume::State_Idle);
- return -1;
- }
- /*
- * Now that the bindmount trickery is done, atomically move the
- * whole subtree to expose it to non priviledged users.
- */
- /*将挂载点挂载的目录再挂载到最终的目录/mnt/sdcard*/
- if (doMoveMount("/mnt/secure/staging", getMountpoint(), false)) {
- SLOGE("Failed to move mount (%s)", strerror(errno));
- umount("/mnt/secure/staging");
- setState(Volume::State_Idle);
- return -1;
- }
- setState(Volume::State_Mounted);
- mLastMountedKdev = mCurrentlyMountedKdev = deviceNodes[i];
- return 0;
- }
- SLOGE("Volume %s found no suitable devices for mounting :(\n", getLabel());
- setState(Volume::State_Idle);
- return -1;
- }
这个挂载函数看起来,会发现很繁琐,好几个目录的挂载关系,有以下挂载目录:
/dev/block/vold/8:1 挂载在-> /mnt/secure/staging
/mnt/secure/staging/.android_secure 挂载在-> /mnt/secure/asec
tmpfs 挂载在-> /mnt/secure/staging/.android_secure
/mnt/secure/staging 挂载在-> /mnt/sdcard
从程序的注释看,这样的目的是挂载一个只有root用户能查看的目录,具体还是没搞清楚谷歌为什么要这样挂载,
还是有疑问,希望有清楚的高手指点一下。
sd卡的挂载比较清楚,中间多了一个中介,将设备节点8:1挂载在/mnt/secure/staging,最后又将该目录挂载在/mnt/sdcard,
这目录就是最终用户能够看到文件的目录。
函数里面涉及到几个函数:
getDeviceNodes函数获取挂载设备的设备号与分区数量,是Volume类的一个纯虚函数,在子类DirectVolume中实现,源码:
- int DirectVolume::getDeviceNodes(dev_t *devs, int max) {
- if (mPartIdx == -1) {
- // If the disk has no partitions, try the disk itself
- if (!mDiskNumParts) {
- devs[0] = MKDEV(mDiskMajor, mDiskMinor);
- return 1;
- }
- int i;
- for (i = 0; i < mDiskNumParts; i++) {
- if (i == max)
- break;
- devs[i] = MKDEV(mDiskMajor, mPartMinors[i]);
- }
- return mDiskNumParts;
- }
- devs[0] = MKDEV(mDiskMajor, mPartMinors[mPartIdx -1]);
- return 1;
- }
下面贴一些mountVol里面挂载的源码:
- int Fat::doMount(const char *fsPath, const char *mountPoint,
- bool ro, bool remount, int ownerUid, int ownerGid,
- int permMask, bool createLost) {
- int rc;
- unsigned long flags;
- char mountData[255];
- flags = MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_DIRSYNC;
- flags |= (ro ? MS_RDONLY : 0);
- flags |= (remount ? MS_REMOUNT : 0);
- /*
- * Note: This is a temporary hack. If the sampling profiler is enabled,
- * we make the SD card world-writable so any process can write snapshots.
- *
- * TODO: Remove this code once we have a drop box in system_server.
- */
- char value[PROPERTY_VALUE_MAX];
- property_get("persist.sampling_profiler", value, "");
- if (value[0] == '1') {
- SLOGW("The SD card is world-writable because the"
- " 'persist.sampling_profiler' system property is set to '1'.");
- permMask = 0;
- }
- sprintf(mountData,
- "utf8,uid=%d,gid=%d,fmask=%o,dmask=%o,shortname=mixed",
- ownerUid, ownerGid, permMask, permMask);
- rc = mount(fsPath, mountPoint, "vfat", flags, mountData);
- if (rc && errno == EROFS) {
- SLOGE("%s appears to be a read only filesystem - retrying mount RO", fsPath);
- flags |= MS_RDONLY;
- rc = mount(fsPath, mountPoint, "vfat", flags, mountData);
- }
- if (rc == 0 && createLost) {
- char *lost_path;
- asprintf(&lost_path, "%s/LOST.DIR", mountPoint);
- if (access(lost_path, F_OK)) {
- /*
- * Create a LOST.DIR in the root so we have somewhere to put
- * lost cluster chains (fsck_msdos doesn't currently do this)
- */
- if (mkdir(lost_path, 0755)) {
- SLOGE("Unable to create LOST.DIR (%s)", strerror(errno));
- }
- }
- free(lost_path);
- }
- return rc;
- }
- int Volume::createBindMounts() {
- unsigned long flags;
- /*
- * Rename old /android_secure -> /.android_secure
- */
- if (!access("/mnt/secure/staging/android_secure", R_OK | X_OK) &&
- access(SEC_STG_SECIMGDIR, R_OK | X_OK)) {
- if (rename("/mnt/secure/staging/android_secure", SEC_STG_SECIMGDIR)) {
- SLOGE("Failed to rename legacy asec dir (%s)", strerror(errno));
- }
- }
- /*
- * Ensure that /android_secure exists and is a directory
- */
- if (access(SEC_STG_SECIMGDIR, R_OK | X_OK)) {
- if (errno == ENOENT) {
- if (mkdir(SEC_STG_SECIMGDIR, 0777)) {
- SLOGE("Failed to create %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
- return -1;
- }
- } else {
- SLOGE("Failed to access %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
- return -1;
- }
- } else {
- struct stat sbuf;
- if (stat(SEC_STG_SECIMGDIR, &sbuf)) {
- SLOGE("Failed to stat %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
- return -1;
- }
- if (!S_ISDIR(sbuf.st_mode)) {
- SLOGE("%s is not a directory", SEC_STG_SECIMGDIR);
- errno = ENOTDIR;
- return -1;
- }
- }
- /*
- * Bind mount /mnt/secure/staging/android_secure -> /mnt/secure/asec so we'll
- * have a root only accessable mountpoint for it.
- */
- if (mount(SEC_STG_SECIMGDIR, SEC_ASECDIR, "", MS_BIND, NULL)) {
- SLOGE("Failed to bind mount points %s -> %s (%s)",
- SEC_STG_SECIMGDIR, SEC_ASECDIR, strerror(errno));
- return -1;
- }
- /*
- * Mount a read-only, zero-sized tmpfs on <mountpoint>/android_secure to
- * obscure the underlying directory from everybody - sneaky eh? ;)
- */
- if (mount("tmpfs", SEC_STG_SECIMGDIR, "tmpfs", MS_RDONLY, "size=0,mode=000,uid=0,gid=0")) {
- SLOGE("Failed to obscure %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
- umount("/mnt/asec_secure");
- return -1;
- }
- return 0;
- }
- int Volume::doMoveMount(const char *src, const char *dst, bool force) {
- unsigned int flags = MS_MOVE;
- int retries = 5;
- while(retries--) {
- if (!mount(src, dst, "", flags, NULL)) {
- if (mDebug) {
- SLOGD("Moved mount %s -> %s sucessfully", src, dst);
- }
- return 0;
- } else if (errno != EBUSY) {
- SLOGE("Failed to move mount %s -> %s (%s)", src, dst, strerror(errno));
- return -1;
- }
- int action = 0;
- if (force) {
- if (retries == 1) {
- action = 2; // SIGKILL
- } else if (retries == 2) {
- action = 1; // SIGHUP
- }
- }
- SLOGW("Failed to move %s -> %s (%s, retries %d, action %d)",
- src, dst, strerror(errno), retries, action);
- Process::killProcessesWithOpenFiles(src, action);
- usleep(1000*250);
- }
- errno = EBUSY;
- SLOGE("Giving up on move %s -> %s (%s)", src, dst, strerror(errno));
- return -1;
- }
篇幅有点长了,挂载点也挂载了好几个,mountVol处理的事情最多的,也最繁琐,但都是简单的,主要是有点摸不着头脑,
- 提供了挂载,卸载与格式化的功能。在这里,我们可以回忆一下,Android在挂载sd卡有几个途径,
卸载又是有几种方法?
一、挂载sd卡,有2种方式:
1.用户自己挂载sd卡;
2.自动挂载sd卡,这时候是在Android系统启动完成后挂载的,所以可以看成是自动挂载;
二、卸载sd卡,有2种方式:
1.用户自己卸载sd卡;
2.用户移除sd卡;
卸载sd卡,1和2看起来有点矛盾,但这相当于平时我们在电脑使用U盘一样,
一种是先安全删除再移除U盘;
一种是不安全删除直接移除U盘;
所以在Android系统中,卸载sd卡有两个途径:
1.Framework下发卸载命令(volume unmount sdcard force),卸载sd卡;
2.vold捕获到移除事件,如果是非安全删除sd卡,那么就卸载sd卡。
所以挂载与卸载sd卡的两种方法不同,挂载sd卡都是由Framework下发命令,而卸载sd卡则不同。
一、先来分析Framework下发卸载命令:
截取CommandListener类其中的源码:
这条卸载命令的格式是:volume unmount sdcard true/false.- else if (!strcmp(argv[1], "unmount")) {
- if (argc < 3 || argc > 4 || (argc == 4 && strcmp(argv[3], "force"))) {
- cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: volume unmount <path> [force]", false);
- return 0;
- }
- bool force = false;
- if (argc >= 4 && !strcmp(argv[3], "force")) {
- force = true;
- }
- rc = vm->unmountVolume(argv[2], force);
- }
这里有一个强制卸载的标志,也就是force参数,如果命令包含“force”字符串,那么就强制
卸载sd卡,接下来看unmountVolume卸载函数:- int VolumeManager::unmountVolume(const char *label, bool force) {
- Volume *v = lookupVolume(label);
- ......
- /*这函数清理了/mnt/asec目录的一些内容,其实看不见*/
- cleanupAsec(v, force);
- return v->unmountVol(force);
- }
开始看卸载函数,以下函数均做简化,不再贴出大量源码,Android系统的源码实在太多:- int Volume::unmountVol(bool force) {
- /*广播sd卡正在卸载的状态*/
- setState(Volume::State_Unmounting);
- if (doMoveMount(getMountpoint(), SEC_STGDIR, force)) {
- SLOGE("Failed to move mount %s => %s (%s)", getMountpoint(), SEC_STGDIR, strerror(errno));
- setState(Volume::State_Mounted);
- return -1;
- }
- if (doUnmount(Volume::SEC_STG_SECIMGDIR, force)) {
- SLOGE("Failed to unmount tmpfs on %s (%s)", SEC_STG_SECIMGDIR, strerror(errno));
- goto fail_republish;
- }
- if (doUnmount(Volume::SEC_ASECDIR, force)) {
- SLOGE("Failed to remove bindmount on %s (%s)", SEC_ASECDIR, strerror(errno));
- goto fail_remount_tmpfs;
- }
- if (doUnmount(Volume::SEC_STGDIR, force)) {
- SLOGE("Failed to unmount %s (%s)", SEC_STGDIR, strerror(errno));
- goto fail_recreate_bindmount;
- }
- /*以上doMoveMount, doUnmount等函数做些清理工作,就是之前挂载到系统的目录
- 清理干净*/
- /*卸载工作做完,告诉framework,该磁盘处于空闲状态*/
- setState(Volume::State_Idle);
- /*用来保存正在挂载的设备号的全局变量,现在赋为-1*/
- mCurrentlyMountedKdev = -1;
- return 0;
- }
二、vold捕获移除事件,调用卸载函数:
- 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);
- /*
- * The framework doesn't need to get notified of
- * partition removal unless it's mounted. Otherwise
- * the removal notification will be sent on the Disk
- * itself
- */
- state = getState();
- if (state != Volume::State_Mounted && state != Volume::State_Shared) {
- return;
- }
- /*判断,如果移除事件的设备号与正在挂载系统的设备号相等,
- 说明此时是没有安全删除,所以下面会广播VolumeBadRemoval反馈码*/
- if ((dev_t) MKDEV(major, minor) == mCurrentlyMountedKdev) {
- /*
- * Yikes, our mounted partition is going away!
- */
- snprintf(msg, sizeof(msg), "Volume %s %s bad removal (%d:%d)",
- getLabel(), getMountpoint(), major, minor);
- mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeBadRemoval,
- msg, false);
- /*清理asec目录*/
- if (mVm->cleanupAsec(this, true)) {
- SLOGE("Failed to cleanup ASEC - unmount will probably fail!");
- }
- /*在这里调用了卸载函数,跟framework下发命令卸载一样。*/
- if (Volume::unmountVol(true)) {
- SLOGE("Failed to unmount volume on bad removal (%s)",
- strerror(errno));
- // XXX: At this point we're screwed for now
- } else {
- SLOGD("Crisis averted");
- }
- } else if (state == Volume::State_Shared) {
- /*这里的判断是,如果此时手机与电脑正在连接着使用时,移除事件的情况*/
- /* removed during mass storage */
- snprintf(msg, sizeof(msg), "Volume %s bad removal (%d:%d)",
- getLabel(), major, minor);
- mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeBadRemoval,
- msg, false);
- /*卸载大容量存储*/
- if (mVm->unshareVolume(getLabel(), "ums")) {
- SLOGE("Failed to unshare volume on bad removal (%s)",
- strerror(errno));
- } else {
- SLOGD("Crisis averted");
- }
- }
- }
- 操作有这几步:
1.将分区信息写到硬盘的第一个设备节点的MBR结构中的分区表;
2.格式化分区到指定的文件系统类型。
MBR中存放分区表的位置在446-509,占用了64字节,MBR结构只支持4个主分区,所以
有4个16字节的区域,先简要说明一下MBR的分区表的结构:
从这个表格可以看出,相对于446-509的分区表区域,每个主分区的第5个字节存放的是文件
系统标志位,用来识别什么分区,用fdisk工具查看一下,有如下文件系统对应的十六进制标志:
若需要读取这些文件系统标志,只需读取MBR的450个位置,占用一个字节大小。
扯得太远了,回到正题,本文是要分析Android格式化sd卡的功能,在格式化部分,涉及到
了系统的一些函数,与vold无关,简单的说明一下即可。
Android系统在格式化sd卡的时候,首先会判断sd卡是否存在分区,如果sd卡不存在分区,
那么需要重新初始化MBR区域,所以上面简要的介绍了MBR结构中分区表的区域。
- int Volume::formatVol() {
- if (getState() == Volume::State_NoMedia) {
- errno = ENODEV;
- return -1;
- } else if (getState() != Volume::State_Idle) {
- errno = EBUSY;
- return -1;
- }
- /*如果该分区为卸载,那么格式化失败,返回错误*/
- if (isMountpointMounted(getMountpoint())) {
- SLOGW("Volume is idle but appears to be mounted - fixing");
- setState(Volume::State_Mounted);
- // mCurrentlyMountedKdev = XXX
- errno = EBUSY;
- return -1;
- }
- char devicePath[255];
- dev_t diskNode = getDiskDevice();
- dev_t partNode;
- if (mDebug) {
- SLOGI("Formatting volume %s (%s)", getLabel(), devicePath);
- }
- setState(Volume::State_Formatting);
- if (!mLastMountedKdev) {
- dev_t deviceNodes[2];
- int n = getDeviceNodes(deviceNodes, 2);
- // initialize MBR if no partition, or has multiple partitions
- // but none is selected
- if ((diskNode == deviceNodes[0]) || (n > 1)) {
- sprintf(devicePath, "/dev/block/vold/%d:%d",
- MAJOR(diskNode), MINOR(diskNode));
- if (initializeMbr(devicePath)) {
- SLOGE("Failed to initialize MBR (%s)", strerror(errno));
- partNode = diskNode; // try to use whole disk
- } else {
- partNode = MKDEV(MAJOR(diskNode), MINOR(diskNode) + 1);
- }
- } else {
- partNode = deviceNodes[0];
- }
- } else {
- partNode = mLastMountedKdev;
- }
- sprintf(devicePath, "/dev/block/vold/%d:%d",
- MAJOR(partNode), MINOR(partNode));
- int ret = Fat::format(devicePath, 0);
- SLOGE_IF(ret, "Failed to format (%s)", strerror(errno));
- setState(Volume::State_Idle);
- return ret;
- }
格式化函数两个主要工作交给了initializeMbr和Fat::format函数:
1.initializeMbr函数负责初始化MBR;
2.Fat::format函数负责格式化分区。
先来看initializeMbr函数的初始化工作:
- int Volume::initializeMbr(const char *deviceNode) {
- struct disk_info dinfo;
- memset(&dinfo, 0, sizeof(dinfo));
- if (!(dinfo.part_lst = (struct part_info *) malloc(MAX_NUM_PARTS * sizeof(struct part_info)))) {
- SLOGE("Failed to malloc prt_lst");
- return -1;
- }
- memset(dinfo.part_lst, 0, MAX_NUM_PARTS * sizeof(struct part_info));
- dinfo.device = strdup(deviceNode);
- dinfo.scheme = PART_SCHEME_MBR;
- dinfo.sect_size = 512;
- dinfo.skip_lba = 2048;
- dinfo.num_lba = 0;
- dinfo.num_parts = 1;
- struct part_info *pinfo = &dinfo.part_lst[0];
- pinfo->name = strdup("android_sdcard");
- pinfo->flags |= PART_ACTIVE_FLAG;
- pinfo->type = PC_PART_TYPE_FAT32;
- pinfo->len_kb = -1;
- int rc = apply_disk_config(&dinfo, 0);
- if (rc) {
- SLOGE("Failed to apply disk configuration (%d)", rc);
- goto out;
- }
- out:
- free(pinfo->name);
- free(dinfo.device);
- free(dinfo.part_lst);
- return rc;
- }
这里贴出一些重要的结构体:
- struct part_info {
- char *name;
- uint8_t flags;
- uint8_t type;
- uint32_t len_kb; /* in 1K-bytes */
- uint32_t start_lba; /* the LBA where this partition begins */
- };
- struct disk_info {
- char *device;
- uint8_t scheme;
- int sect_size; /* expected sector size in bytes. MUST BE POWER OF 2 */
- uint32_t skip_lba; /* in sectors (1 unit of LBA) */
- uint32_t num_lba; /* the size of the disk in LBA units */
- struct part_info *part_lst;
- int num_parts;
- };
初始化完成后,将该结构体变量pinfo通过apply_disk_config函数进行设置:
- int apply_disk_config(struct disk_info *dinfo, int test)
- {
- int fd;
- struct write_list *wr_lst = NULL;
- int rv;
- if (validate_and_config(dinfo, &fd, &wr_lst) != 0) {
- LOGE("Configuration is invalid.");
- goto fail;
- }
- if ((rv = wlist_commit(fd, wr_lst, test)) >= 0)
- rv = test ? 0 : sync_ptable(fd);
- close(fd);
- wlist_free(wr_lst);
- return rv;
- fail:
- close(fd);
- if (wr_lst)
- wlist_free(wr_lst);
- return 1;
- }
该函数先打开sd卡的设备节点,然后将MBR的初始化信息写到第一个block(512B)中,
这里涉及到非常多函数,不在vold的讨论范围。
写完MBR后,就要对分区进行格式化,要格式化成FAT32格式,Fat::format函数直接调用
系统命令newfs_msdos来格式化新分区,检测磁盘是,Fat::check函数直接调用系统命令
fsck_msdos来检测分区。
最后格式化完成功,通知Framework,sd卡处于空闲状态:
- setState(Volume::State_Idle);
然后,Framework又要重复挂载和卸载的操作了。