Android內(nèi)卡掛載之FUSE文件系統(tǒng)
原創(chuàng)作者 | 陳豪
審校 | 孫淑娟
一、簡(jiǎn)介
FUSE(Filesystem in Userspace),是一種用戶空間文件系統(tǒng)。用戶可以通過(guò)FUSE文件系統(tǒng)操作內(nèi)卡。FUSE主要實(shí)現(xiàn)代碼位于用戶空間中,而不需要重新編譯到內(nèi)核,用戶空間開(kāi)發(fā)者可以通過(guò)FUSE的接口直接訪問(wèn)內(nèi)核空間,不需要了解文件系統(tǒng)的內(nèi)幕和內(nèi)核模塊編程的知識(shí),這給用戶空間開(kāi)發(fā)者帶來(lái)了眾多便利。
二、FUSE文件系統(tǒng)架構(gòu)
1.FUSE內(nèi)核模塊(內(nèi)核態(tài))實(shí)現(xiàn)VFS 接口(FUSE文件驅(qū)動(dòng)注冊(cè)、supper block、dentry、inode的維護(hù)),接收請(qǐng)求傳遞給LibFUSE,LibFUSE 再傳遞給用戶程序的接口進(jìn)行操作。
2.LibFUSE模塊(用戶態(tài))實(shí)現(xiàn)文件系統(tǒng)主要框架,比如對(duì)實(shí)現(xiàn)的文件系統(tǒng)操作進(jìn)行封裝、mount管理、通過(guò)設(shè)備/dev/fuse與內(nèi)核模塊通信。
3.用戶程序模塊(用戶態(tài))當(dāng)內(nèi)卡掛載成功后,對(duì)內(nèi)卡進(jìn)行讀寫操作。
這種架構(gòu)的設(shè)計(jì)可以讓用戶通過(guò)FUSE在用戶空間來(lái)定制自己的文件系統(tǒng),將文件系統(tǒng)從內(nèi)核剝離出來(lái),大大縮減了開(kāi)發(fā)的難度。本文將著重介紹libfuse如何掛載內(nèi)卡。
三、內(nèi)卡的掛載
3.1 內(nèi)卡掛載與分區(qū)掛載的不同
分區(qū)掛載是掛載到內(nèi)核實(shí)地文件系統(tǒng),例如userdata分區(qū)掛載f2fs到 /data目錄下。內(nèi)卡掛載是掛載用戶空間文件系統(tǒng),如dev/fuse 掛載fuse到mnt/user/0/emulated目錄下。
上圖mnt/user/0/emulated和/data/media/0下的內(nèi)容是一樣的。原因是這兩個(gè)目錄是綁定的關(guān)系,說(shuō)明內(nèi)卡是userdata的一部分。這部分空間是用戶可以直接操作的。
在手機(jī)的文件管理器中也可以看到同樣的目錄:
3.2 內(nèi)卡掛載和綁定
VoldNativeService::mount接收到framwork層發(fā)送的mount請(qǐng)求后調(diào)用vol->mount,從而執(zhí)行VolumeBase::mount這個(gè)父類。真正的實(shí)現(xiàn)是在子類內(nèi)卡會(huì)調(diào)用EmulatedVolume::doMount執(zhí)行掛載。
1.VoldNativeService::mount
mountFlags決定掛載的是內(nèi)卡還是SD卡,為3時(shí)掛載內(nèi)卡,為2時(shí)掛載SD卡。內(nèi)卡的mountUserId為0,SD卡的mountUserId是卡本身的guid。最終會(huì)執(zhí)行vol->mount()。
binder::Status VoldNativeService::mount(
const std::string& volId, int32_t mountFlags, int32_t mountUserId,
const android::sp<android::os::IVoldMountCallback>& callback) {
ENFORCE_SYSTEM_OR_ROOT;
CHECK_ARGUMENT_ID(volId);
ACQUIRE_LOCK;
auto vol = VolumeManager::Instance()->findVolume(volId);
if (vol == nullptr) {
return error("Failed to find volume " + volId);
}
vol->setMountFlags(mountFlags);
vol->setMountUserId(mountUserId);
vol->setMountCallback(callback);
int res = vol->mount();
vol->setMountCallback(nullptr);
if (res != OK) {
return translate(res);
}
return translate(OK);
}
2.vol->mount
vol是VolumeBase的實(shí)例,VolumeBase的mount方法由具體的子類EmulatedVolume、PublicVolume、PrivateVolume等實(shí)現(xiàn)。執(zhí)行操作之后會(huì)發(fā)送應(yīng)答消息給MountService。將掛載的結(jié)果上報(bào)給framwork層。
status_t VolumeBase::mount() {
if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
LOG(WARNING) << getId() << " mount requires state unmounted or unmountable";
return -EBUSY;
}
setState(State::kChecking);
status_t res = doMount();
setState(res == OK ? State::kMounted : State::kUnmountable);
if (res == OK) {
doPostMount();
}
return res;
}
3.EmulatedVolume::doMount ()
內(nèi)卡會(huì)走到EmulatedVolume這個(gè)子類進(jìn)行掛載,SD卡則會(huì)走PublicVolume掛載。在EmulatedVolume函數(shù)里建立了四個(gè)/mnt/runtime路徑并設(shè)置了不同的權(quán)限,原因是控制不同權(quán)限APP訪問(wèn)。然后利用掛載命名空間實(shí)現(xiàn)了掛載點(diǎn)的隔離,用戶在不同掛載命名空間的進(jìn)程,看到的目錄層次不同。MountUserFuse是掛載FUSE的實(shí)現(xiàn),內(nèi)卡和SD卡都會(huì)走這個(gè)流程。著重看一下MountUserFuse函數(shù)的實(shí)參,如果掛載的是內(nèi)卡,user_id則為0,getInternalPath()為/data/media,label為emulated。
status_t EmulatedVolume::doMount() {
std::string label = getLabel();
bool isVisible = getMountFlags() & MountFlags::kVisible;
mSdcardFsDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str());
mSdcardFsRead = StringPrintf("/mnt/runtime/read/%s", label.c_str());
mSdcardFsWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str());
mSdcardFsFull = StringPrintf("/mnt/runtime/full/%s", label.c_str());
setInternalPath(mRawPath);
setPath(StringPrintf("/storage/%s", label.c_str()));
………………………………
res = MountUserFuse(user_id, getInternalPath(), label, &fd);
…………………………..
}
4.MountUserFuse();
如下函數(shù)中只粘貼了重要的部分。fuse_path是掛載路徑mnt/user/0/emulated。隨后調(diào)用mount函數(shù)調(diào)用內(nèi)核接口進(jìn)行掛載,將/dev/fuse 掛載到/mnt/user/0/emulated。
status_t MountUserFuse(userid_t user_id, const std::string& absolute_lower_path,
const std::string& relative_upper_path, android::base::unique_fd* fuse_fd) {
std::string fuse_path(
StringPrintf("%s/%s", pre_fuse_path.c_str(), relative_upper_path.c_str()));
result = TEMP_FAILURE_RETRY(mount("/dev/fuse", fuse_path.c_str(), "fuse",
MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_LAZYTIME,
opts.c_str()));
}
掛載成功后可以用mount命令去查看,截圖如下:
四、總結(jié)
本文介紹了內(nèi)卡對(duì)FUSE的掛載,將創(chuàng)建好的FUSE設(shè)備掛載到內(nèi)置存儲(chǔ)空間關(guān)聯(lián)目錄。對(duì)于內(nèi)置存儲(chǔ)空間的訪問(wèn)變成了先訪問(wèn)FUSE文件系統(tǒng),再訪問(wèn)f2fs文件系統(tǒng)。對(duì)于FUSE而言,在內(nèi)核空間和用戶空間來(lái)回切換會(huì)增加性能開(kāi)銷,所以對(duì)FUSE的性能優(yōu)化至關(guān)重要。
作者介紹
陳豪,51CTO社區(qū)編輯,具有6年工作經(jīng)驗(yàn)的高級(jí)系統(tǒng)工程師。擅長(zhǎng)技能有Linux內(nèi)嵌匯編語(yǔ)言,Python,C,C++,Java,Linux內(nèi)核分析,智能機(jī)器人軟件設(shè)計(jì)等。
參考鏈接
??https://blog.csdn.net/kongxinsun/article/details/79587305??
??https://blog.csdn.net/bob_fly1984/article/details/80720807??