Android storage system source code walk (a) : StorageManagerService
preface
The main logic of the startup part of StorageManagerService has been read above, and the vOLD code will be analyzed in this paper. The code involved in this article:
- system/vold/Android.bp
- system/vold/vold.rc
- system/vold/main.cpp
- system/vold/VoldNativeService.cpp
- system/vold/NetlinkManager.cpp
- system/vold/NetlinkHandler.cpp
- system/vold/VolumeManager.cpp
- system/vold/fs/Ext4.cpp
- system/vold/model/PublicVolume.cpp
A code framework for storage services
Before we do that, let’s take a look at the code framework for the current version of the storage service (Android11). Compared with previous versions of AndroidO, the StorageManagerService is mainly optimized to communicate with Vold, and CommandListener is removed to communicate with Binder. Older versions of the code framework can be found here: gityuan.com/2016/07/23/…
Android does not use Linux’s Udev to handle disks, so Google wrote a vold similar to udev to act as a bridge between the kernel and the framework.
The start of the vold
Android.bp packages vold.rc into init.rc
init_rc: [
"vold.rc",
"wait_for_keymaster.rc",
],
Copy the code
Vold. Rc start/system/bin/vold
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
ioprio be 2
writepid /dev/cpuset/foreground/tasks
shutdown critical
group root reserved_disk
Copy the code
The main program of Vold is in /system/ Vold. Vold (main.cpp) will start VoldNativeService and create VoldManager and NetlinkManager
- VolumeManager implements the VoldNativeService function
- NetLinkManager is used to listen for hot plug events in the kernel, notifying the Vold process that a USB device has been connected
int main(int argc, char** argv) { ... VolumeManager* vm; NetlinkManager* nm; // Initialize VolumeManager, VoldNativeService function executor if (! (vm = VolumeManager::Instance())) { LOG(ERROR) << "Unable to create VolumeManager"; exit(1); } // Initialize NetLinkManager to listen for hot plug events in the kernel and notify vold that the USB device has been connected. If (! (nm = NetlinkManager::Instance())) { LOG(ERROR) << "Unable to create NetlinkManager"; exit(1); } if (vm->start()) { PLOG(ERROR) << "Unable to start VolumeManager"; exit(1); } / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * The **process_config function parses the /etc/vald. fstab configuration file. As you can see from the code, the parameters in the configuration file are separated by Spaces and tab-based tables. The system boots up, analyzes the configuration file, mounts the corresponding partition, Equivalent to a Linux system/etc/fstab file * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / bool has_adoptable; bool has_quota; bool has_reserved; if (process_config(vm, &has_adoptable, &has_quota, &has_reserved)) { PLOG(ERROR) << "Error reading configuration... continuing anyways"; } ATRACE_BEGIN("VoldNativeService::start"); if (android::vold::VoldNativeService::start() ! = android::OK) { LOG(ERROR) << "Unable to start VoldNativeService"; exit(1); } ATRACE_END(); // Start VoldNativeService ATRACE_BEGIN("NetlinkManager::start"); if (nm->start()) { PLOG(ERROR) << "Unable to start NetlinkManager"; exit(1); } ATRACE_END(); // The application layer writes the "add\n" command to the uevent file in the /sys/block directory to trigger the kernel to send uEvent messages to obtain the current device information coldboot("/sys/block"). . }Copy the code
StorageManager communicates with Vold
The StorageManagerService obtained in connect is VoldNativeService’s binder proxy StorageManager establishes communication with Vold through IVoldListener
MVold = ivold.stub.asInterface (binder); mVold = ivold.stub.asinterface (binder); // Set Vold Listener(mListener); } catch (RemoteException e) { mVold = null; Slog.w(TAG, "vold listener rejected; trying again", e); }Copy the code
In VoldNativeService. CPP is implemented through the setListener function
binder::Status VoldNativeService::setListener(
const android::sp<android::os::IVoldListener>& listener) {
ENFORCE_SYSTEM_OR_ROOT;
ACQUIRE_LOCK;
VolumeManager::Instance()->setListener(listener);
return Ok();
}
Copy the code
At this point the Binder callback is set up and VoldNativeService can call VoldListener to notify the Java layer just like a normal callback.
Communication between vold and the kernel
Vold establishes socket channels through NetLinkManager to listen for UEvent events reported by the kernel.
NetLink is a mechanism for user processes to interact with the kernel in Linux. With this mechanism, user processes (such as Vold/Netd) can receive messages from the kernel and send control commands to the kernel. NetlinkManager is designed based on this. Uevent is also associated with Linux systems, having some relationship to the Linux device file system; Here, we can simply think of Uevent as a string, which describes the status of the external storage device insertion/removal, mount/unmount information. Using the Netlink mechanism, Vold obtains these information and manages and controls external storage devices.
int NetlinkManager::start() { struct sockaddr_nl nladdr; int sz = 64 * 1024; int on = 1; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = getpid(); nladdr.nl_groups = 0xffffffff; // Key code: Create a socket with address family PF_NETLINK. Communicate with the Kernel if ((mSock = socket (PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) < 0) { PLOG(ERROR) << "Unable to create uevent socket"; return -1; } // When running in a net/user namespace, SO_RCVBUFFORCE will fail because // it will check for the CAP_NET_ADMIN capability in the root namespace. // Try using SO_RCVBUF if that fails. if ((setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) && (setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) < 0)) { PLOG(ERROR) << "Unable to set uevent socket SO_RCVBUF/SO_RCVBUFFORCE option"; goto out; } if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) { PLOG(ERROR) << "Unable to set uevent socket SO_PASSCRED option"; goto out; } if (bind(mSock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) { PLOG(ERROR) << "Unable to bind uevent socket"; goto out; } // Handle socket callback through NetlinkerHandler mHandler = new NetlinkHandler(mSock); if (mHandler->start()) { PLOG(ERROR) << "Unable to start NetlinkHandler"; goto out; } return 0; out: close(mSock); return -1; }Copy the code
The NetlinkerHandler class handles socket callbacks
void NetlinkHandler::onEvent(NetlinkEvent* evt) {
VolumeManager* vm = VolumeManager::Instance();
const char* subsys = evt->getSubsystem();
if (!subsys) {
LOG(WARNING) << "No subsystem found in netlink event";
return;
}
if (std::string(subsys) == "block") {
vm->handleBlockEvent(evt);
}
}
Copy the code
The VolumeManager handles block-related events.
The driver device is divided into character device, block device and network device. Character devices are accessed sequentially in the form of character streams. A character device is also called a raw device. It can read the physical disk directly without going through the system cache. Block devices refer to devices in the system that can access fixed-size chunks randomly (not sequentially), such as hard disks. Block devices read from the system cache.
Events reported by the kernel consist of strings, as shown in the following example:
ACTION=add
DEVPATH=/devices/platform/msm_sdcc.2/mmc_host/mmc1/mmc1:c9f2/block/mmcblk0
SUBSYSTEM=block
MAJOR=179
MINOR=0
DEVNAME=mmcblk0
DEVTYPE=disk
NPARTS=3
SEQNUM=1357
Copy the code
void VolumeManager::handleBlockEvent(NetlinkEvent* evt) { std::lock_guard<std::mutex> lock(mLock); if (mDebug) { LOG(DEBUG) << "----------------"; LOG(DEBUG) << "handleBlockEvent with action " << (int)evt->getAction(); evt->dump(); } // Read several parameters from NetlinkEvent including DEVPATH, DEVTYPE, MAJOR, MINOR STD ::string eventPath(evt->findParam("DEVPATH")? evt->findParam("DEVPATH") : ""); STD ::string devType(evt->findParam(" devType ")? evt->findParam("DEVTYPE") : ""); // If (devType! = "disk") return; Int major = STD ::stoi(evt->findParam(" major ")); int minor = std::stoi(evt->findParam("MINOR")); Dev_t device = makedev(major, minor); The switch (evt - > getAction ()) {/ / device inserted into the event case NetlinkEvent: : Action: : kAdd: {the for (const auto & source: MDiskSources) {if (source->matches(eventPath)) {For now, assume that MMC and virtio-blk (the latter is // specific to virtual platforms; see Utils.cpp for details) // devices are SD, and that everything else is USB int flags = source->getFlags(); if (major == kMajorBlockMmc || IsVirtioBlkDevice(major)) { flags |= android::vold::Disk::Flags::kSd; } else { flags |= android::vold::Disk::Flags::kUsb; } auto disk = new android::vold::Disk(eventPath, device, source->getNickname(), flags); HandleDiskAdded (STD ::shared_ptr< Android ::vold::Disk>(Disk)); break; } } break; } case NetlinkEvent::Action::kChange: { LOG(DEBUG) << "Disk at " << major << ":" << minor << " changed"; handleDiskChanged(device); break; } / / equipment to remove event case NetlinkEvent: : Action: : kRemove: {handleDiskRemoved (device); break; } default: { LOG(WARNING) << "Unexpected block event action " << (int)evt->getAction(); break; }}}Copy the code
After identifying whether it is a USB disk or SD card, call handleDiskAdded for mounting. There are two scenarios that need to wait temporarily:
- The lock screen is not unlocked
- We need to wait for user0 to start because we need to start the user before mount Fuse Daemon processes the disk.
Void VolumeManager: : handleDiskAdded (const STD: : from the android: : vold: : Disk > & Disk) {/ / 1, the lock screen is not unlocked, 2. Wait for user0 to start because we need to start the user before mounting Fuse daemon to process the disk. // For security reasons, if secure keyguard is showing, "Wait until the user unlocks the device to actually touch it" wait until user 0 is actually started, since we need // the user to be up before we can mount a FUSE daemon to handle the disk. bool userZeroStarted = mStartedUsers.find(0) ! = mStartedUsers.end(); if (mSecureKeyguardShowing) { LOG(INFO) << "Found disk at " << disk->getEventPath() << " but delaying scan due to secure keyguard"; mPendingDisks.push_back(disk); } else if (! userZeroStarted) { LOG(INFO) << "Found disk at " << disk->getEventPath() << " but delaying scan due to user zero not having started"; mPendingDisks.push_back(disk); } else { disk->create(); mDisks.push_back(disk); }}Copy the code
Disk::create()
status_t Disk::create() { CHECK(! mCreated); mCreated = true; Auto Listener = VolumeManager::Instance()->getListener(); // Callback VolumeManager::Instance()->getListener(); if (listener) listener->onDiskCreated(getId(), mFlags); if (isStub()) { createStubVolume(); return OK; } // Read the disk metadata readMetadata(); ReadPartitions (); return OK; }Copy the code
Disk::readPartitions()
This is to call the /system/bin/sgdisk tool to read partition information
Sgdisk is the tool for operating GPT partitions under Linux, just as fdisk is the tool for operating MBR partitions.
After the disk object is created, the volume object is created. The SD card USb device is a publicVolume and the built-in storage is privateVolume
static const char* kSgdiskPath = "/system/bin/sgdisk"; status_t Disk::readPartitions() { int maxMinors = getMaxMinors(); if (maxMinors < 0) { return -ENOTSUP; } destroyAllVolumes(); // Parse partition table std::vector<std::string> cmd; cmd.push_back(kSgdiskPath); cmd.push_back("--android-dump"); cmd.push_back(mDevPath); std::vector<std::string> output; Status_t res = ForkExecvp(CMD, &output); status_t res = ForkExecvp(CMD, &output); if (res ! = OK) { LOG(WARNING) << "sgdisk failed to scan " << mDevPath; auto listener = VolumeManager::Instance()->getListener(); if (listener) listener->onDiskScanned(getId()); mJustPartitioned = false; return res; } Table table = Table::kUnknown; bool foundParts = false; for (const auto& line : output) { auto split = android::base::Split(line, kSgdiskToken); auto it = split.begin(); if (it == split.end()) continue; if (*it == "DISK") { if (++it == split.end()) continue; If (*it == "> < span style =" max-width: 100%; clear: both; } else if (*it == "gpt") { table = Table::kGpt; } else { LOG(WARNING) << "Invalid partition table " << *it; continue; } } else if (*it == "PART") { foundParts = true; if (++it == split.end()) continue; int i = 0; if (! android::base::ParseInt(*it, &i, 1, maxMinors)) { LOG(WARNING) << "Invalid partition number " << *it; continue; } dev_t partDevice = makedev(major(mDevice), minor(mDevice) + i); if (table == Table::kMbr) { if (++it == split.end()) continue; int type = 0; if (! android::base::ParseInt("0x" + *it, &type)) { LOG(WARNING) << "Invalid partition type " << *it; continue; } switch (type) { case 0x06: // FAT16 case 0x07: // HPFS/NTFS/exFAT case 0x0b: // W95 FAT32 (LBA) case 0x0c: // W95 FAT32 (LBA) case 0x0e: // W95 FAT16 (LBA) case 0x83: // Linux EXT4/F2FS/... createPublicVolume(partDevice); break; } } else if (table == Table::kGpt) { if (++it == split.end()) continue; auto typeGuid = *it; if (++it == split.end()) continue; auto partGuid = *it; if (android::base::EqualsIgnoreCase(typeGuid, kGptBasicData)) { createPublicVolume(partDevice); } else if (android::base::EqualsIgnoreCase(typeGuid, kGptAndroidExpand)) { createPrivateVolume(partDevice, partGuid); } } } } // Ugly last ditch effort, treat entire disk as partition if (table == Table::kUnknown || ! foundParts) { LOG(WARNING) << mId << " has unknown partition table; trying entire device"; std::string fsType; std::string unused; if (ReadMetadataUntrusted(mDevPath, &fsType, &unused, &unused) == OK) { createPublicVolume(mDevice); } else { LOG(WARNING) << mId << " failed to identify, giving up"; } } auto listener = VolumeManager::Instance()->getListener(); If (listener) listener->onDiskScanned(getId()); mJustPartitioned = false; return OK; }Copy the code
Disk::createPublicVolume
void Disk::createPublicVolume(dev_t device) {
auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device));
if (mJustPartitioned) {
LOG(DEBUG) << "Device just partitioned; silently formatting";
vol->setSilent(true);
vol->create();
vol->format("auto");
vol->destroy();
vol->setSilent(false);
}
mVolumes.push_back(vol);
vol->setDiskId(getId());
vol->create();
}
Copy the code
After receiving the onVolumeCreated callback, the StorageManagerService calls VoldNativeService::mount to mount it. Before mounting, the FSCK tool corresponding to different file systems is invoked to verify disks.