本系列主要介绍 Android vold,分为以下篇章
<Android开发> Android vold - 第一篇 vold前言简介
<Android开发> Android vold - 第二篇 vold 的main()函数简介
<Android开发> Android vold - 第三篇 vold 的NetLinkManager类简介
继前一边vold简介后,我们来看看具体的代码内容。
4 vold的main()函数
要了解vold服务是干什么的,对直接的方法就是看你源码到底写了什么内容。而vold服务的入口当然就是main()函数了。先把main()的内容贴出来,再对其内容进行讲解。vold服务的main()函数路径和内容如下。
路径:LINUX/android/system/vold/main.cpp
int main(int argc, char** argv) {
setenv("ANDROID_LOG_TAGS", "*:v", 1); //设置环境变量
android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM)); //初始化log系统
LOG(INFO) << "Vold 3.0 (the awakening) firing up";
LOG(VERBOSE) << "Detected support for:"
<< (android::vold::IsFilesystemSupported("ext4") ? " ext4" : "")
<< (android::vold::IsFilesystemSupported("f2fs") ? " f2fs" : "")
<< (android::vold::IsFilesystemSupported("vfat") ? " vfat" : ""); //输出支持的文件系统类型
VolumeManager *vm; //管理volume
CommandListener *cl; //和Framework进行通讯
CryptCommandListener *ccl; //运行 和Framework进行通讯 有关命令
NetlinkManager *nm; //接收内核消息
parse_args(argc, argv); //解析主函数参数
sehandle = selinux_android_file_context_handle(); //获取selinux,权限有关
if (sehandle) {
selinux_android_set_sehandle(sehandle); //设置selinux
}
//在我们刚刚从init继承的套接字上快速抛出一个CLOEXEC
fcntl(android_get_control_socket("vold"), F_SETFD, FD_CLOEXEC);
fcntl(android_get_control_socket("cryptd"), F_SETFD, FD_CLOEXEC);
mkdir("/dev/block/vold", 0755); //创建设备节点
/*当cryptfs检查并装载加密文件系统时*/
klog_set_level(6);
/* Create our singleton managers */
/*创建我们的单例 管理器*/
if (!(vm = VolumeManager::Instance())) {
LOG(ERROR) << "Unable to create VolumeManager";
exit(1);
}
if (!(nm = NetlinkManager::Instance())) {
LOG(ERROR) << "Unable to create NetlinkManager";
exit(1);
}
//获取属性vold.debug
if (property_get_bool("vold.debug", false)) {
vm->setDebug(true);
}
cl = new CommandListener();
ccl = new CryptCommandListener();
vm->setBroadcaster((SocketListener *) cl);
nm->setBroadcaster((SocketListener *) cl);
if (vm->start()) { //卸载原有可能存在的所有磁盘。并新建 "/data/media"/Disk
PLOG(ERROR) << "Unable to start VolumeManager"; //卷管理器
exit(1);
}
bool has_adoptable;
bool has_quota;
//根据配置文件/etc/vold.fstab,初始化VolumeManager
if (process_config(vm, &has_adoptable, &has_quota)) {
PLOG(ERROR) << "Error reading configuration... continuing anyways";
}
//启动内核监听
if (nm->start()) {
PLOG(ERROR) << "Unable to start NetlinkManager";
exit(1);
}
/*现在我们起床了,我们可以响应命令了*/
if (cl->startListener()) {
PLOG(ERROR) << "Unable to start CommandListener";
exit(1);
}
if (ccl->startListener()) {
PLOG(ERROR) << "Unable to start CryptCommandListener";
exit(1);
}
//此调用应在侦听器启动后进行,以避免vold和init之间的死锁(有关详细信息,请参阅b/34278978)
property_set("vold.has_adoptable", has_adoptable ? "1" : "0");
property_set("vold.has_quota", has_quota ? "1" : "0");
//在这里进行冷启动,这样就不会阻止启动,如果我们在Vold启动之前连接了闪存驱动器,也需要冷启动
coldboot("/sys/block"); //触发内核sysfs发送uevent事件
//最终我们将成为监控线程
while(1) {
pause();
}
LOG(ERROR) << "Vold exiting";
exit(0);
}
上面是vold服务最开始运行的代码内容,我们来一一看看。
第3行:设置环境变量,其中"ANDROID_LOG_TAGS"环境变量名;“:v" 表示环境变量的值;1表示替换,即将”ANDROID_LOG_TAGS”环境变量的值替换为":v”。
第4行:初始化log系统,这样在Vold中可输出log;
该函数在“LINUX/android/system/core/base/logging.cpp”中实现,这里不在深入讨论log系统。
第9~11行:通过IsFilesystemSupported()函数判断支持的文件系统类型,并输出;该函数在”LINUX/android/system/vold/Utils.cpp”中实现;
static const char* kProcFilesystems = "/proc/filesystems";
bool IsFilesystemSupported(const std::string& fsType) {
std::string supported;
if (!ReadFileToString(kProcFilesystems, &supported)) {
PLOG(ERROR) << "Failed to read supported filesystems";
return false;
}
return supported.find(fsType + "\n") != std::string::npos;
}
从该函数可看到, kProcFilesystems 是操作读取的文件,该函数主要是读取文件filesystems,并匹配字符串。
文件内容如下:

第13行:定义了一个VolumeManager类vm,用来管理volume的;后面会对这个类进行详细解释。
第14行:定义了一个CommandListener类cl,用来同Framework进行通讯的;后面会对这个类进行详细解释。
第15行:定义了一个CryptCommandListener类cll,用来同Framework进行通讯,处理有关cmd的;后面会对这个类进行详细解释。
第16行:定义了一个NetlinkManager类nm,用来接收内核消息的;后面会对这个类进行详细解释。
第18行:函数parse_args()解析主函数的参数,vold.rc文件启动vold服务时传入了参数;内容如下:
路径:LINUX/android/system/vold/main.cpp
static void parse_args(int argc, char** argv) {
static struct option opts[] = {
{"blkid_context", required_argument, 0, 'b' },
{"blkid_untrusted_context", required_argument, 0, 'B' },
{"fsck_context", required_argument, 0, 'f' },
{"fsck_untrusted_context", required_argument, 0, 'F' },
};
int c;
while ((c = getopt_long(argc, argv, "", opts, nullptr)) != -1) {
switch (c) {
case 'b': android::vold::sBlkidContext = optarg; break;
case 'B': android::vold::sBlkidUntrustedContext = optarg; break;
case 'f': android::vold::sFsckContext = optarg; break;
case 'F': android::vold::sFsckUntrustedContext = optarg; break;
}
}
CHECK(android::vold::sBlkidContext != nullptr);
CHECK(android::vold::sBlkidUntrustedContext != nullptr);
CHECK(android::vold::sFsckContext != nullptr);
CHECK(android::vold::sFsckUntrustedContext != nullptr);
}
路径:LINUX/android/system/vold/vold.rc
service vold /system/bin/vold \
--blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
--fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
class core
socket vold stream 0660 root mount
socket cryptd stream 0660 root mount
ioprio be 2
writepid /dev/cpuset/foreground/tasks
shutdown critical
option的结构体如下:
struct option
{
const char *name;
/*has_arg不能是枚举,因为一些编译器抱怨所有假定它是int的代码中的类型不匹配*/
int has_arg;
int *flag;
int val;
};
这参数与selinux有关。
第20行,selinux_android_file_context_handle()获取selinux的handle;
路径:
LINUX/android/external/selinux/libselinux/src/android/android_platform.c
static const struct selinux_opt seopts_file_split[] = {
{ SELABEL_OPT_PATH, "/system/etc/selinux/plat_file_contexts" },
{ SELABEL_OPT_PATH, "/vendor/etc/selinux/nonplat_file_contexts" }
};
static const struct selinux_opt seopts_file_rootfs[] = {
{ SELABEL_OPT_PATH, "/plat_file_contexts" },
{ SELABEL_OPT_PATH, "/nonplat_file_contexts" }
};
struct selabel_handle* selinux_android_file_context_handle(void)
{
if (selinux_android_opts_file_exists(seopts_file_split)) {
return selinux_android_file_context(seopts_file_split,
ARRAY_SIZE(seopts_file_split));
} else {
return selinux_android_file_context(seopts_file_rootfs,
ARRAY_SIZE(seopts_file_rootfs));
}
}
static struct selabel_handle* selinux_android_file_context(const struct selinux_opt *opts, unsigned nopts)
{
struct selabel_handle *sehandle;
struct selinux_opt fc_opts[nopts + 1];
memcpy(fc_opts, opts, nopts*sizeof(struct selinux_opt));
fc_opts[nopts].type = SELABEL_OPT_BASEONLY;
fc_opts[nopts].value = (char *)1;
sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, ARRAY_SIZE(fc_opts));
if (!sehandle) {
selinux_log(SELINUX_ERROR, "%s: Error getting file context handle (%s)\n",
__FUNCTION__, strerror(errno));
return NULL;
}
if (!compute_file_contexts_hash(fc_digest, opts, nopts)) { //
selabel_close(sehandle);
return NULL;
}
selinux_log(SELINUX_INFO, "SELinux: Loaded file_contexts\n");
return sehandle;
}
设备log如下图:

第21~24行: 在前一句获取到selinux的handle后,则设置android的selinux;
路径:LINUX/android/external/selinux/libselinux/src/android/android_platform.c
void selinux_android_set_sehandle(const struct selabel_handle *hndl)
{
fc_sehandle = (struct selabel_handle *) hndl;
}
因为selinux要详细讲解的话 能单独出一篇文章了。这里就不深入探索selinux,这里我们只需知道,vold根据vold.rc传入的参数可获得对应的权限,然后在selinux的handle里能使用对应权限的资源即可。
第26~27行: fcntl()针对(文件)描述符提供控制。参数fd是被参数cmd操作(如下面的描述)的描述符。针对cmd的值,fcntl能够接受第三个参数int arg;
第1个参数:其中android_get_control_socket(“vold”):获取对应socket的fd;F_SETFD
第2个参数:对描述符操作功能
第3个参数:cmd值的F_GETFD和F_SETFD:
F_GETFD: 取得与文件描述符fd联合的close-on-exec标志,类似FD_CLOEXEC。如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg 被忽略)
F_SETFD: 设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定,应当了解很多现存的涉及文件描述符标志的程序并不使用常数 FD_CLOEXEC,而是将此标志设置为0(系统默认,在exec时不关闭)或1(在exec时关闭)
第28行:创建目录文件“/dev/block/vold”,并给予0755的权限;
第30行:设置klog的等级,即kernel的log等级。
路径:LINUX/android/system/core/libcutils/klog.cpp
static int klog_level = KLOG_INFO_LEVEL;
//设置klog的级别
void klog_set_level(int level) {
klog_level = level;
}
static int __open_klog(void) {
static const char kmsg_device[] = "/dev/kmsg";
int ret = android_get_control_file(kmsg_device);
if (ret >= 0) return ret;
return TEMP_FAILURE_RETRY(open(kmsg_device, O_WRONLY | O_CLOEXEC));
}
#define LOG_BUF_MAX 512
void klog_writev(int level, const struct iovec* iov, int iov_count) {
if (level > klog_level) return;
static int klog_fd = __open_klog();
if (klog_fd == -1) return;
TEMP_FAILURE_RETRY(writev(klog_fd, iov, iov_count));
}
void klog_write(int level, const char* fmt, ...) {
if (level > klog_level) return;
char buf[LOG_BUF_MAX];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
buf[LOG_BUF_MAX - 1] = 0;
struct iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = strlen(buf);
klog_writev(level, iov, 1);
}
klog_write(0函数调用时,打开(创建文件) /dev/kmsg,并写入log内容。
第34行:函数VolumeManager::Instance()的内容可知,new VolumeManager()并返回给到vm;路径:LINUX/android/system/vold/VolumeManager.cpp;后面会对这个类进行详解;
如果new不成功对报如下错误Log:
LOG(ERROR) << “Unable to create VolumeManager”;
第39行:函数NetlinkManager::Instance()的内容可知,new NetlinkManager()并返回给到nm;路径:LINUX/android/system/vold/NetlinkManager.cpp;后面会对这个类进行详解;
如果new不成功对报如下错误Log:
LOG(ERROR) << “Unable to create NetlinkManager”;
第44行:函数property_get_bool()是用来获取属性值的,这里是要获取"vold.debug"这个属性值,且期待的返回值是false;如果返回true,则调用vm->setDebug(true);设置为Debug。
路径:LINUX/android/system/core/libcutils/properties.cpp
int8_t property_get_bool(const char *key, int8_t default_value) {
if (!key) {
return default_value;
}
int8_t result = default_value;
char buf[PROPERTY_VALUE_MAX] = {'\0'};
int len = property_get(key, buf, "");
if (len == 1) {
char ch = buf[0];
if (ch == '0' || ch == 'n') {
result = false;
} else if (ch == '1' || ch == 'y') {
result = true;
}
} else if (len > 1) {
if (!strcmp(buf, "no") || !strcmp(buf, "false") || !strcmp(buf, "off")) {
result = false;
} else if (!strcmp(buf, "yes") || !strcmp(buf, "true") || !strcmp(buf, "on")) {
result = true;
}
}
return result;
}
路径:LINUX/android/system/vold/VolumeManager.cpp
int VolumeManager::setDebug(bool enable) {
mDebug = enable;
return 0;
}
第48行:new 一个 CommandListener()类给cl,后续分析该类;
第49行:new 一个 CryptCommandListener()类给cll,后续分析该类;
第50行:设置vm的广播为cl;在类定义里只有
void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }
第51行:设置nm的广播为cl;在类定义里只有
void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }
第54行:vm start,开始管理volume。后面详解。
第61行:process_config函数是根据配置文件/etc/vold.fstab 或/vendor/etc/fstab.xx 配置vm ;
路径:LINUX/android/system/vold/main.cpp
static int process_config(VolumeManager *vm, bool* has_adoptable, bool* has_quota) {
fstab = fs_mgr_read_fstab_default(); //读配置文件
if (!fstab) {
PLOG(ERROR) << "Failed to open default fstab";
return -1;
}
/*遍历条目,查找vold管理的条目*/
*has_adoptable = false;
*has_quota = false;
for (int i = 0; i < fstab->num_entries; i++) {
if (fs_mgr_is_quota(&fstab->recs[i])) {
*has_quota = true;
}
if (fs_mgr_is_voldmanaged(&fstab->recs[i])) {
if (fs_mgr_is_nonremovable(&fstab->recs[i])) {
LOG(WARNING) << "nonremovable no longer supported; ignoring volume";
continue;
}
std::string sysPattern(fstab->recs[i].blk_device);
std::string nickname(fstab->recs[i].label);
int flags = 0;
if (fs_mgr_is_encryptable(&fstab->recs[i])) {
flags |= android::vold::Disk::Flags::kAdoptable;
*has_adoptable = true;
}
if (fs_mgr_is_noemulatedsd(&fstab->recs[i])
|| property_get_bool("vold.debug.default_primary", false)) {
flags |= android::vold::Disk::Flags::kDefaultPrimary;
}
vm->addDiskSource(std::shared_ptr<VolumeManager::DiskSource>(
new VolumeManager::DiskSource(sysPattern, nickname, flags)));
}
}
return 0;
}
路径:LINUX/android/system/core/fs_mgr/fs_mgr_fstab.cpp
/*尝试加载默认fstab 来自/odm/etc、/vendor/etc或/ <的硬件>文件。加载找到的第一个条目,并合并从设备树传入的fstab条目*/
struct fstab *fs_mgr_read_fstab_default()
{
std::string hw;
std::string default_fstab;
//分别使用不同的fstab路径进行正常引导和恢复引导
if (access("/sbin/recovery", F_OK) == 0) {
default_fstab = "/etc/recovery.fstab";
} else if (fs_mgr_get_boot_config("hardware", &hw)) { // normal boot
for (const char *prefix : {"/odm/etc/fstab.","/vendor/etc/fstab.", "/fstab."}) {
default_fstab = prefix + hw;
if (access(default_fstab.c_str(), F_OK) == 0) break;
}
} else {
LWARNING << __FUNCTION__ << "(): failed to find device hardware name";
}
//将从设备树传入的fstab条目与从defaultfstab文件中找到的条目进行组合
struct fstab *fstab_dt = fs_mgr_read_fstab_dt();
struct fstab *fstab = fs_mgr_read_fstab(default_fstab.c_str());
return in_place_merge(fstab_dt, fstab);
}
加载fstab文件,并与dts中的fstab进行合并,最终保存到fstab,这其中主要遍历几个fstab的位置,包括:/etc/recovery.fstab,/odm/etc/fstab., /vendor/etc/fstab., /fstab.,我们的方案中实际会使用/vendor/etc/fstab.
fstab是fstab类型,即std::vector,fstab文件最终解析结果会存放到fstab;
我们可以看到fstab_rec的结构如下,它用于保存fstab中的一条记录
路径:LINUX/android/system/core/fs_mgr/include_fstab/fstab/fstab.h
/*条目必须按照在fstab中看到的相同顺序保存。除非明确要求,否则装载点上的查找应始终返回第一个*/
struct fstab {
int num_entries;
struct fstab_rec* recs;
char* fstab_filename;
};
struct fstab_rec {
char* blk_device;
char* mount_point;
char* fs_type;
unsigned long flags;
char* fs_options;
int fs_mgr_flags;
char* key_loc;
char* key_dir;
char* verity_loc;
long long length;
char* label;
int partnum;
int swap_prio;
int max_comp_streams;
unsigned int zram_size;
uint64_t reserved_size;
unsigned int file_contents_mode;
unsigned int file_names_mode;
unsigned int erase_blk_size;
unsigned int logical_blk_size;
};
process_config函数中for循环将遍历fstab中被解析的每一条记录,为每条记录创建DiskSource,通过vm->addDiskSource存放到mDiskSources。

第65行:nm start,开始启动监听kernel的uevent事件。后面详解。
第71行:cl的startListener()函数是创建监听FW的socket线程的;后续详解;
第76行:ccl的startListener()函数也是创建监听FW的socket线程的;后续详解;
第82~83行:设置属性"vold.has_adoptable" 和 “vold.has_quota”;
对于has_adoptable 和has_quota 的值,取决于获取fatab配置;
-> process_config(vm, &has_adoptable, &has_quota)
-> if (fs_mgr_is_quota(&fstab->recs[i])) {
*has_quota = true;
}
-> return fstab->fs_mgr_flags & MF_QUOTA;
Quota是Linux的一个重要工具,使用Quota能对某一分区下指定用户或用户组进行磁盘限额。这里要说明的是,限额不是针对用户主目录,而是针对这个分区下的用户或用户组。Quota通过限制用户的blocks或者inodes起到限额的作用。
->if (fs_mgr_is_encryptable(&fstab->recs[i])) {
flags |= android::vold::Disk::Flags::kAdoptable;
*has_adoptable = true;
}
->return fstab->fs_mgr_flags & (MF_CRYPT | MF_FORCECRYPT | MF_FORCEFDEORFBE);
LINUX/android/system/core/fs_mgr/fs_mgr_priv.h
该 .h文件对fstab的格式内容进行说明,如下:
/*fstab具有以下格式:
*任何以#开头的行都是注释,将被忽略 *忽略任何空行
*所有其他行必须采用以下格式:
*
*
*<mount_flags>是一个逗号分隔的标志列表,可以传递给mount命令。该列表包括noatime、nosuid、nodev、nodiratime、ro、rw、remount和defaults。
*
*
*
*
*当fs_mgr被请求挂载所有文件系统时,它将首先挂载所有没有_NOT_指定check的文件系统(包括只读文件系统并指定check,因为在这种情况下会忽略check),然后它将检查并挂载标记为check的文件系统。
*/
第86行:触发内核sysfs发送uevent事件;在这里进行冷启动,这样就不会阻止启动,如果我们在Vold启动之前连接了闪存驱动器(如U盘等),也需要冷启动;
路径:LINUX/android/system/vold/main.cpp
static void coldboot(const char *path) {
DIR *d = opendir(path); //打开目录
if(d) {
do_coldboot(d, 0); //冷启动该目录
closedir(d); //关闭目录
}
}
递归扫描/sys目录下的uevent节点,然后写入字符串“add”,强制触发内核uevent事件
static void do_coldboot(DIR *d, int lvl) {
struct dirent *de;
int dfd, fd;
dfd = dirfd(d);
fd = openat(dfd, "uevent", O_WRONLY | O_CLOEXEC);
if(fd >= 0) {
//往/sys/block目录写入add\n事件
//往/sys/block发送add uevent事件
write(fd, "add\n", 4);
close(fd);
}
// 递归调用do_coldboot(),扫描uevent节点
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 | O_CLOEXEC);
if(fd < 0)
continue;
d2 = fdopendir(fd);
if(d2 == 0)
close(fd);
else {
do_coldboot(d2, lvl + 1);
closedir(d2);
}
}
}
第88~90行:至此,当前main函数的功能就完成,剩下的或都交给了vm、nm、cl、cll了,main()则转为监控线程,就是不干活,但又不能退出;除非异常。
接下来将会以 U盘设备插入、读取数据、拔出等内容,对vold的流程进行详细的解析。
vold 模块的介绍的第二篇主要讲解了 vold模块 main()函数具体实现了哪些内容。后续我们再对具体的借口、功能、实现类方法进行详细解析。