Android內卡掛載之FUSE文件系統
原創作者 | 陳豪
審校 | 孫淑娟
一、簡介

FUSE(Filesystem in Userspace),是一種用戶空間文件系統。用戶可以通過FUSE文件系統操作內卡。FUSE主要實現代碼位于用戶空間中,而不需要重新編譯到內核,用戶空間開發者可以通過FUSE的接口直接訪問內核空間,不需要了解文件系統的內幕和內核模塊編程的知識,這給用戶空間開發者帶來了眾多便利。
二、FUSE文件系統架構

1.FUSE內核模塊(內核態)實現VFS 接口(FUSE文件驅動注冊、supper block、dentry、inode的維護),接收請求傳遞給LibFUSE,LibFUSE 再傳遞給用戶程序的接口進行操作。
2.LibFUSE模塊(用戶態)實現文件系統主要框架,比如對實現的文件系統操作進行封裝、mount管理、通過設備/dev/fuse與內核模塊通信。
3.用戶程序模塊(用戶態)當內卡掛載成功后,對內卡進行讀寫操作。
這種架構的設計可以讓用戶通過FUSE在用戶空間來定制自己的文件系統,將文件系統從內核剝離出來,大大縮減了開發的難度。本文將著重介紹libfuse如何掛載內卡。
三、內卡的掛載
3.1 內卡掛載與分區掛載的不同
分區掛載是掛載到內核實地文件系統,例如userdata分區掛載f2fs到 /data目錄下。內卡掛載是掛載用戶空間文件系統,如dev/fuse 掛載fuse到mnt/user/0/emulated目錄下。


上圖mnt/user/0/emulated和/data/media/0下的內容是一樣的。原因是這兩個目錄是綁定的關系,說明內卡是userdata的一部分。這部分空間是用戶可以直接操作的。
在手機的文件管理器中也可以看到同樣的目錄:

3.2 內卡掛載和綁定

VoldNativeService::mount接收到framwork層發送的mount請求后調用vol->mount,從而執行VolumeBase::mount這個父類。真正的實現是在子類內卡會調用EmulatedVolume::doMount執行掛載。
1.VoldNativeService::mount
mountFlags決定掛載的是內卡還是SD卡,為3時掛載內卡,為2時掛載SD卡。內卡的mountUserId為0,SD卡的mountUserId是卡本身的guid。最終會執行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的實例,VolumeBase的mount方法由具體的子類EmulatedVolume、PublicVolume、PrivateVolume等實現。執行操作之后會發送應答消息給MountService。將掛載的結果上報給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 ()
內卡會走到EmulatedVolume這個子類進行掛載,SD卡則會走PublicVolume掛載。在EmulatedVolume函數里建立了四個/mnt/runtime路徑并設置了不同的權限,原因是控制不同權限APP訪問。然后利用掛載命名空間實現了掛載點的隔離,用戶在不同掛載命名空間的進程,看到的目錄層次不同。MountUserFuse是掛載FUSE的實現,內卡和SD卡都會走這個流程。著重看一下MountUserFuse函數的實參,如果掛載的是內卡,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();
如下函數中只粘貼了重要的部分。fuse_path是掛載路徑mnt/user/0/emulated。隨后調用mount函數調用內核接口進行掛載,將/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命令去查看,截圖如下:

四、總結
本文介紹了內卡對FUSE的掛載,將創建好的FUSE設備掛載到內置存儲空間關聯目錄。對于內置存儲空間的訪問變成了先訪問FUSE文件系統,再訪問f2fs文件系統。對于FUSE而言,在內核空間和用戶空間來回切換會增加性能開銷,所以對FUSE的性能優化至關重要。
作者介紹
陳豪,51CTO社區編輯,具有6年工作經驗的高級系統工程師。擅長技能有Linux內嵌匯編語言,Python,C,C++,Java,Linux內核分析,智能機器人軟件設計等。
參考鏈接
??https://blog.csdn.net/kongxinsun/article/details/79587305??
??https://blog.csdn.net/bob_fly1984/article/details/80720807??
























