Android 儲存系統之原始碼篇
基於Android 6.0原始碼, 來分析儲存相關架構,涉及原始碼:
/framework/base/services/java/com/android/server/SystemServer.java /framework/base/services/core/java/com/android/server/MountService.java /framework/base/services/core/java/com/android/server/NativeDaemonConnector.java /framework/base/services/core/java/com/android/server/NativeDaemonEvent.java /framework/base/core/java/android/os/storage/IMountService.java /framework/base/core/java/android/os/storage/IMountServiceListener.java /framework/base/core/java/android/os/storage/StorageManager.java /system/vold/Main.cpp /system/vold/VolumeManager.cpp /system/vold/NetlinkManager.cpp /system/vold/NetlinkHandler.cpp /system/vold/CommandListener.cpp /system/vold/VoldCommand.cpp /system/vold/VolumeBase.cpp /system/vold/PublicVolume.cpp /system/vold/EmulatedVolume.cpp /system/vold/PublicVolume.cpp /system/vold/Disk.cpp /system/core/libsysutils/src/NetlinkListener.cpp /system/core/libsysutils/src/SocketListener.cpp /system/core/libsysutils/src/FrameworkListener.cpp /system/core/libsysutils/src/FrameworkCommand.cpp /system/core/include/sysutils/NetlinkListener.h /system/core/include/sysutils/SocketListener.h /system/core/include/sysutils/FrameworkListener.h /system/core/include/sysutils/FrameworkCommand.h
一、概述
本文主要介紹跟儲存相關的模組MountService和Vold的整體流程與架構設計.
- MountService:Android Binder服務,執行在system_server程式,用於跟Vold進行訊息通訊,比如
MountService
向Vold
傳送掛載SD卡的命令,或者接收到來自Vold
的外設熱插拔事件。 - Vold:全稱為Volume Daemon,用於管理外部儲存裝置的Native守護程式,這是一個非常重要的守護程式,由NetlinkManager,VolumeManager,CommandListener這3部分組成。
二、MountService
MountService執行在system_server
程式,在系統啟動到階段PHASE_WAIT_FOR_DEFAULT_DISPLAY後,進入startOtherServices
會啟動MountService.
2.1 啟動
[-> SystemServer.java]
private void startOtherServices() { ... IMountService mountService = null; //啟動MountService服務,【見小節2.2】 mSystemServiceManager.startService(MOUNT_SERVICE_CLASS); //等價new IMountService.Stub.Proxy(),即獲取MountService的proxy物件 mountService = IMountService.Stub.asInterface( ServiceManager.getService("mount")); ... mActivityManagerService.systemReady(new Runnable() { public void run() { //啟動到階段550【見小節2.7】 mSystemServiceManager.startBootPhase( SystemService.PHASE_ACTIVITY_MANAGER_READY); ... }); }
NotificationManagerService依賴於MountService,比如media/usb通知事件,所以需要先啟動MountService。此處MOUNT_SERVICE_CLASS=com.android.server.MountService$Lifecycle
.
2.2 startService
mSystemServiceManager.startService(MOUNT_SERVICE_CLASS)主要完成3件事:
- 建立MOUNT_SERVICE_CLASS所指類的Lifecycle物件;
- 將該物件新增SystemServiceManager的
mServices
服務列表; - 最後呼叫Lifecycle的onStart()方法,主要工作量這個過程,如下:
[-> MountService.java]
class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks, Watchdog.Monitor { public static class Lifecycle extends SystemService { public void onStart() { //建立MountService物件【見小節2.3】 mMountService = new MountService(getContext()); //登記Binder服務 publishBinderService("mount", mMountService); } ... } ... }
建立MountService物件,並向Binder服務的大管家ServiceManager登記,該服務名為“mount”,對應服務物件為mMountService。登記之後,其他地方當需要MountService的服務時便可以通過服務名來向ServiceManager來查詢具體的MountService服務。
2.3 MountService
[-> MountService.java]
public MountService(Context context) { sSelf = this; mContext = context; //FgThread執行緒名為“"android.fg",建立IMountServiceListener回撥方法【見小節2.4】 mCallbacks = new Callbacks(FgThread.get().getLooper()); //獲取PKMS的Client端物件 mPms = (PackageManagerService) ServiceManager.getService("package"); //建立“MountService”執行緒 HandlerThread hthread = new HandlerThread(TAG); hthread.start(); mHandler = new MountServiceHandler(hthread.getLooper()); //IoThread執行緒名為"android.io",建立OBB操作的handler mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper()); File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); mLastMaintenanceFile = new File(systemDir, LAST_FSTRIM_FILE); //判斷/data/system/last-fstrim檔案,不存在則建立,存在則更新最後修改時間 if (!mLastMaintenanceFile.exists()) { (new FileOutputStream(mLastMaintenanceFile)).close(); ... } else { mLastMaintenance = mLastMaintenanceFile.lastModified(); } ... //將MountServiceInternalImpl登記到sLocalServiceObjects LocalServices.addService(MountServiceInternal.class, mMountServiceInternal); //建立用於VoldConnector的NDC物件【見小節2.5】 mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25, null); mConnector.setDebug(true); //建立執行緒名為"VoldConnector"的執行緒,用於跟vold通訊【見小節2.6】 Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); //建立用於CryptdConnector工作的NDC物件 mCryptConnector = new NativeDaemonConnector(this, "cryptd", MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null); mCryptConnector.setDebug(true); //建立執行緒名為"CryptdConnector"的執行緒,用於加密 Thread crypt_thread = new Thread(mCryptConnector, CRYPTD_TAG); crypt_thread.start(); //註冊監聽使用者新增、刪除的廣播 final IntentFilter userFilter = new IntentFilter(); userFilter.addAction(Intent.ACTION_USER_ADDED); userFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); //內部私有volume的路徑為/data,該volume通過dumpsys mount是不會顯示的 addInternalVolume(); //預設為false if (WATCHDOG_ENABLE) { Watchdog.getInstance().addMonitor(this); } }
其主要功能依次是:
- 建立ICallbacks回撥方法,FgThread執行緒名為”android.fg”,此處用到的Looper便是執行緒”android.fg”中的Looper;
- 建立並啟動執行緒名為”MountService”的handlerThread;
- 建立OBB操作的handler,IoThread執行緒名為”android.io”,此處用到的的Looper便是執行緒”android.io”中的Looper;
- 建立NativeDaemonConnector物件
- 建立並啟動執行緒名為”VoldConnector”的執行緒;
- 建立並啟動執行緒名為”CryptdConnector”的執行緒;
- 註冊監聽使用者新增、刪除的廣播;
從這裡便可知道共建立了3個執行緒:”MountService”,”VoldConnector”,”CryptdConnector”,另外還會使用到系統程式中的兩個執行緒”android.fg”和”android.io”. 這便是在文章開頭程式架構圖中Java framework層程式的建立情況.
接下來再分別看看MountService建立過程中的Callbacks例項化, NativeDaemonConnector例項化,以及”vold”執行緒的執行.
2.4 Callbacks
class MountService { ... private static class Callbacks extends Handler { private final RemoteCallbackList<IMountServiceListener> mCallbacks = new RemoteCallbackList<>(); public Callbacks(Looper looper) { super(looper); } ... } }
建立Callbacks時的Looper為FgThread.get().getLooper(),其中FgThread
採用單例模式,是一個執行緒名為”android.fg”的HandlerThread。另外,Callbacks物件有一個成員變數mCallbacks
,如下:
[-> RemoteCallbackList.java]
public class RemoteCallbackList<E extends IInterface> { ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); //Binder死亡通知 private final class Callback implements IBinder.DeathRecipient { public void binderDied() { ... } } //註冊死亡回撥 public boolean register(E callback, Object cookie) { synchronized (mCallbacks) { ... IBinder binder = callback.asBinder(); Callback cb = new Callback(callback, cookie); binder.linkToDeath(cb, 0); mCallbacks.put(binder, cb); ... } } ... }
通過register()
方法新增IMountServiceListener物件資訊到mCallbacks
成員變數。RemoteCallbackList的內部類Callback繼承於IBinder.DeathRecipient,很顯然這是死亡通知,當binder服務端程式死亡後,回撥binderDied方法通知binder客戶端進行相應地處理。
2.5 NativeDaemonConnector
[-> NativeDaemonConnector.java]
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) { this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl, FgThread.get().getLooper()); } NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl, Looper looper) { mCallbacks = callbacks; //socket名為"vold" mSocket = socket; //物件響應個數為500 mResponseQueue = new ResponseQueue(responseQueueSize); mWakeLock = wl; if (mWakeLock != null) { mWakeLock.setReferenceCounted(true); } mLooper = looper; mSequenceNumber = new AtomicInteger(0); //TAG為"VoldConnector" TAG = logTag != null ? logTag : "NativeDaemonConnector"; mLocalLog = new LocalLog(maxLogSize); }
- mLooper為FgThread.get().getLooper(),即執行在”android.fg”執行緒;
- mResponseQueue物件中成員變數
mPendingCmds
資料型別為LinkedList,記錄著vold程式上報的響應事件,事件個數上限為500。
2.6 NDC.run
[-> NativeDaemonConnector.java]
final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor { public void run() { mCallbackHandler = new Handler(mLooper, this); while (true) { try { //監聽vold的socket【見小節2.13】 listenToSocket(); } catch (Exception e) { loge("Error in NativeDaemonConnector: " + e); SystemClock.sleep(5000); } } } }
線上程VoldConnector
中建立了名為vold
的socket的客戶端,通過迴圈方式不斷監聽Vold服務端傳送過來的訊息。 另外,同理還有一個執行緒CryptdConnector也採用類似的方式,建立了
cryptd`的socket客戶端,監聽Vold中另個執行緒傳送過來的訊息。到此,MountService與NativeDaemonConnector都已經啟動,那麼接下來到系統啟動到達階段PHASE_ACTIVITY_MANAGER_READY,則呼叫到onBootPhase方法。
2.7 onBootPhase
[-> MountService.java ::Lifecycle]
由於MountService的內部Lifecycle已新增SystemServiceManager的mServices
服務列表;系統啟動到PHASE_ACTIVITY_MANAGER_READY
時會回撥mServices
中的onBootPhase
方法
public static class Lifecycle extends SystemService { public void onBootPhase(int phase) { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mMountService.systemReady(); } } }
再呼叫MountService.systemReady方法,該方法主要是通過mHandler
傳送訊息。
private void systemReady() { mSystemReady = true; mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); }
此處mHandler = new MountServiceHandler(hthread.getLooper())
,採用的是執行緒”MountService”中的Looper。到此system_server主執行緒通過handler向執行緒”MountService”傳送H_SYSTEM_READY
訊息,接下來進入執行緒”MountService”的MountServiceHandler物件(簡稱MSH)的handleMessage()來處理相關的訊息。
2.8 MSH.handleMessage
[-> MountService.java ::MountServiceHandler]
class MountServiceHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case H_SYSTEM_READY: { handleSystemReady(); //【見小節2.9】 break; } ... } } }
2.9 handleSystemReady
[-> MountService.java]
private void handleSystemReady() { synchronized (mLock) { //【見小節2.10】 resetIfReadyAndConnectedLocked(); } //計劃執行日常的fstrim操作【】 MountServiceIdler.scheduleIdlePass(mContext); }
2.10 resetIfReadyAndConnectedLocked
[-> MountService.java]
private void resetIfReadyAndConnectedLocked() { Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady + ", mDaemonConnected=" + mDaemonConnected); //當系統啟動到階段550,並且已經與vold守護程式建立連線,則執行reset if (mSystemReady && mDaemonConnected) { killMediaProvider(); mDisks.clear(); mVolumes.clear(); //將/data為路徑的private volume新增到mVolumes addInternalVolume(); try { //【見小節2.11】 mConnector.execute("volume", "reset"); //告知所有已經存在和啟動的users final UserManager um = mContext.getSystemService(UserManager.class); final List<UserInfo> users = um.getUsers(); for (UserInfo user : users) { mConnector.execute("volume", "user_added", user.id, user.serialNumber); } for (int userId : mStartedUsers) { mConnector.execute("volume", "user_started", userId); } } catch (NativeDaemonConnectorException e) { Slog.w(TAG, "Failed to reset vold", e); } } }
2.11 NDC.execute
[-> NativeDaemonConnector.java]
public NativeDaemonEvent execute(String cmd, Object... args) throws NativeDaemonConnectorException { return execute(DEFAULT_TIMEOUT, cmd, args); }
其中DEFAULT_TIMEOUT=1min
,即命令執行超時時長為1分鐘。經過層層呼叫,executeForList()
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args) throws NativeDaemonConnectorException { final long startTime = SystemClock.elapsedRealtime(); final ArrayList<NativeDaemonEvent> events = Lists.newArrayList(); final StringBuilder rawBuilder = new StringBuilder(); final StringBuilder logBuilder = new StringBuilder(); //mSequenceNumber初始化值為0,每執行一次該方法則進行加1操作 final int sequenceNumber = mSequenceNumber.incrementAndGet(); makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args); //例如:“3 volume reset” final String rawCmd = rawBuilder.toString(); final String logCmd = logBuilder.toString(); log("SND -> {" + logCmd + "}"); synchronized (mDaemonLock) { //將cmd寫入到socket的輸出流 mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8)); ... } NativeDaemonEvent event = null; do { //【見小節2.12】 event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd); events.add(event); //當收到的事件響應碼屬於[100,200)區間,則繼續等待後續事件上報 } while (event.isClassContinue()); final long endTime = SystemClock.elapsedRealtime(); //對於執行時間超過500ms則會記錄到log if (endTime - startTime > WARN_EXECUTE_DELAY_MS) { loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)"); } ... return events.toArray(new NativeDaemonEvent[events.size()]); }
首先,將帶執行的命令mSequenceNumber執行加1操作,再將cmd(例如3 volume reset
)寫入到socket的輸出流,通過迴圈與poll機制等待執行底層響應該操作結果,否則直到1分鐘超時才結束該方法。即便收到底層的響應碼,如果響應碼屬於[100,200)區間,則繼續阻塞等待後續事件上報。
2.12 ResponseQueue.remove
[-> MountService.java ::ResponseQueue]
private static class ResponseQueue { public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) { PendingCmd found = null; synchronized (mPendingCmds) { //從mPendingCmds查詢cmdNum for (PendingCmd pendingCmd : mPendingCmds) { if (pendingCmd.cmdNum == cmdNum) { found = pendingCmd; break; } } //如果已有的mPendingCmds中查詢不到,則建立一個新的PendingCmd if (found == null) { found = new PendingCmd(cmdNum, logCmd); mPendingCmds.add(found); } found.availableResponseCount--; if (found.availableResponseCount == 0) mPendingCmds.remove(found); } NativeDaemonEvent result = null; try { //採用poll輪詢方式等待底層上報該事件,直到1分鐘超時 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS); } catch (InterruptedException e) {} return result; } }
這裡用到poll,先來看看responses = new ArrayBlockingQueue<NativeDaemonEvent>(10)
,這是一個長度為10的可阻塞佇列。 這裡的poll也是阻塞的方式來輪詢事件。
responses.poll
[-> ArrayBlockingQueue.java]
public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; //可中斷的鎖等待 lock.lockInterruptibly(); try { //當佇列長度為空,迴圈等待 while (count == 0) { if (nanos <= 0) return null; nanos = notEmpty.awaitNanos(nanos); } return extract(); } finally { lock.unlock(); } }
小知識:這裡用到了ReentrantLock同步鎖,該鎖跟synchronized有功能有很相似,用於多執行緒併發訪問。那麼ReentrantLock與synchronized相比,
ReentrantLock優勢:
- ReentrantLock功能更為強大,比如有時間鎖等候,可中斷鎖等候(lockInterruptibly),鎖投票等功能;
- ReentrantLock效能更好些;
- ReentrantLock提供可輪詢的鎖請求(tryLock),相對不容易產生死鎖;而synchronized只要進入,要麼成功獲取,要麼一直阻塞等待。
ReentrantLock的劣勢:
- lock必須在finally塊顯式地釋放,否則如果程式碼丟擲Exception,鎖將一直得不到釋放;對於synchronized而言,JVM或者ART虛擬機器都會確保該鎖能自動釋放。
- synchronized鎖,在dump執行緒轉儲時會記錄鎖資訊,對於分析除錯大有裨益;對於Lock來說,只是普通類,虛擬機器無法識別。
再回到ResponseQueue.remove(),該方法中mPendingCmds中的內容是哪裡新增的呢?其實是在NDC.listenToSocket迴圈監聽到訊息時新增的,則接下來看看監聽過程。
2.13 listenToSocket
[-> NativeDaemonConnector.java]
private void listenToSocket() throws IOException { LocalSocket socket = null; try { socket = new LocalSocket(); LocalSocketAddress address = determineSocketAddress(); //建立與"/dev/socket/vold"的socket連線 socket.connect(address); InputStream inputStream = socket.getInputStream(); synchronized (mDaemonLock) { mOutputStream = socket.getOutputStream(); } //建立連線後,回撥MS.onDaemonConnected【見小節2.15】 mCallbacks.onDaemonConnected(); byte[] buffer = new byte[BUFFER_SIZE]; int start = 0; while (true) { int count = inputStream.read(buffer, start, BUFFER_SIZE - start); ... for (int i = 0; i < count; i++) { if (buffer[i] == 0) { final String rawEvent = new String( buffer, start, i - start, StandardCharsets.UTF_8); boolean releaseWl = false; try { //解析socket服務端傳送的event final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent( rawEvent); log("RCV <- {" + event + "}"); //當事件的響應碼區間為[600,700),則傳送訊息交由mCallbackHandler處理 if (event.isClassUnsolicited()) { if (mCallbacks.onCheckHoldWakeLock(event.getCode()) && mWakeLock != null) { mWakeLock.acquire(); releaseWl = true; } if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage( event.getCode(), event.getRawEvent()))) { releaseWl = false; } } else { //對於其他的響應碼則新增到mResponseQueue佇列【見小節2.14】 mResponseQueue.add(event.getCmdNumber(), event); } } catch (IllegalArgumentException e) { log("Problem parsing message " + e); } finally { if (releaseWl) { mWakeLock.acquire(); } } start = i + 1; } } ... } } catch (IOException ex) { throw ex; } finally { //收尾清理類工作,關閉mOutputStream, socket ... } }
這裡有一個動作是mResponseQueue.add(),通過該方法便能觸發ResponseQueue.poll阻塞操作繼續往下執行。
2.14 ResponseQueue.add
[-> NativeDaemonConnector.java]
private static class ResponseQueue { public void add(int cmdNum, NativeDaemonEvent response) { PendingCmd found = null; synchronized (mPendingCmds) { for (PendingCmd pendingCmd : mPendingCmds) { if (pendingCmd.cmdNum == cmdNum) { found = pendingCmd; break; } } //沒有找到則建立相應的PendingCmd if (found == null) { while (mPendingCmds.size() >= mMaxCount) { PendingCmd pendingCmd = mPendingCmds.remove(); } found = new PendingCmd(cmdNum, null); mPendingCmds.add(found); } found.availableResponseCount++; if (found.availableResponseCount == 0) mPendingCmds.remove(found); } try { found.responses.put(response); } catch (InterruptedException e) { } } }
responses.put
[-> ArrayBlockingQueue.java]
public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { //當佇列滿了則等待 while (count == items.length) notFull.await(); insert(e); } finally { lock.unlock(); } }
看完了如何向mPendingCmds中增加待處理的命令,再來回過來看看,噹噹listenToSocket剛開始監聽前,收到Native的Daemon連線後的執行操作.
2.15 MS.onDaemonConnected
[-> MountService.java]
public void onDaemonConnected() { mDaemonConnected = true; mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget(); }
當前主執行緒傳送訊息H_DAEMON_CONNECTED
給執行緒MountService`,該執行緒收到訊息後呼叫MountServiceHandler的handleMessage()相應分支後,進而呼叫handleDaemonConnected()方法。
private void handleDaemonConnected() { synchronized (mLock) { resetIfReadyAndConnectedLocked(); } //型別為CountDownLatch,用於多執行緒同步,阻塞await()直到計數器為零 mConnectedSignal.countDown(); if (mConnectedSignal.getCount() != 0) { return; } //呼叫PMS來載入ASECs mPms.scanAvailableAsecs(); //用於通知ASEC掃描已完成 mAsecsScanned.countDown(); }
這裡的PMS.scanAvailableAsecs()經過層層呼叫,最終核心工作還是通過MountService.getSecureContainerList。
[-> MountService.java]
public String[] getSecureContainerList() { enforcePermission(android.Manifest.permission.ASEC_ACCESS); //等待mConnectedSignal計數鎖達到零 waitForReady(); //當沒有掛載Primary卷裝置,則彈出警告 warnOnNotMounted(); try { //向vold程式傳送asec list命令 return NativeDaemonEvent.filterMessageList( mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult); } catch (NativeDaemonConnectorException e) { return new String[0]; } }
2.16 小節
這裡以一張簡單的流程圖來說明上述過程:
三、Vold
介紹完了Java framework層的MountService以及NativeDaemonConnector,往下走來到了Vold的世界.Vold是由開機過程中解析init.rc時啟動:
on post-fs-data start vold //啟動vold服務
Vold的service定義如下:
service vold /system/bin/vold class core socket vold stream 0660 root mount socket cryptd stream 0660 root mount ioprio be 2
接下來便進入Vold的main(),在開啟新的征途之前,為了不被程式碼弄暈,先來用一幅圖來介紹下這些核心類之間的關係以及主要方法,以方便更好的往下閱讀.
3.1 main
[-> 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)); VolumeManager *vm; CommandListener *cl; CryptCommandListener *ccl; NetlinkManager *nm; //解析引數,設定contenxt parse_args(argc, argv); ... 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檢查,並mount加密的檔案系統 klog_set_level(6); //建立單例物件VolumeManager 【見小節3.2.1】 if (!(vm = VolumeManager::Instance())) { exit(1); } //建立單例物件NetlinkManager 【見小節3.3.1】 if (!(nm = NetlinkManager::Instance())) { exit(1); } if (property_get_bool("vold.debug", false)) { vm->setDebug(true); } // 建立CommandListener物件 【見小節3.4.1】 cl = new CommandListener(); // 建立CryptCommandListener物件 【見小節3.5.1】 ccl = new CryptCommandListener(); //【見小節3.2.2】 vm->setBroadcaster((SocketListener *) cl); //【見小節3.3.2】 nm->setBroadcaster((SocketListener *) cl); if (vm->start()) { //【見小節3.2.3】 exit(1); } process_config(vm); //【見小節3.2.4】 if (nm->start()) { //【見小節3.3.3】 exit(1); } coldboot("/sys/block"); //啟動響應命令的監聽器 //【見小節3.4.2】 if (cl->startListener()) { exit(1); } if (ccl->startListener()) { exit(1); } //Vold成為監聽執行緒 while(1) { sleep(1000); } exit(0); }
該方法的主要功能是建立下面4個物件並啟動
- VolumeManager
- NetlinkManager (NetlinkHandler)
- CommandListener
- CryptCommandListener
接下來分別說說幾個類:
3.2 VolumeManager
3.2.1 建立
[-> VolumeManager.cpp]
VolumeManager *VolumeManager::Instance() { if (!sInstance) sInstance = new VolumeManager(); return sInstance; }
建立單例模式的VolumeManager物件
VolumeManager::VolumeManager() { mDebug = false; mActiveContainers = new AsecIdCollection(); mBroadcaster = NULL; mUmsSharingCount = 0; mSavedDirtyRatio = -1; //當UMS獲取時,則設定dirty ratio為0 mUmsDirtyRatio = 0; }
3.2.2 vm->setBroadcaster
void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }
將新建立的CommandListener
物件sl賦值給vm物件的成員變數mBroadcaster
3.2.3 vm->start
int VolumeManager::start() { //解除安裝所有裝置,以提供最乾淨的環境 unmountAll(); //建立Emulated內部儲存 mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>( new android::vold::EmulatedVolume("/data/media")); mInternalEmulated->create(); return 0; }
mInternalEmulated的據型別為EmulatedVolume
,裝置路徑為/data/media
,id和label為“emulated”,mMountFlags=0。EmulatedVolume
繼承於VolumeBase
3.2.3.1 unmountAll
int VolumeManager::unmountAll() { std::lock_guard<std::mutex> lock(mLock); //解除安裝內部儲存 if (mInternalEmulated != nullptr) { mInternalEmulated->unmount(); } //解除安裝外部儲存 for (auto disk : mDisks) { disk->unmountAll(); } FILE* fp = setmntent("/proc/mounts", "r"); if (fp == NULL) { return -errno; } std::list<std::string> toUnmount; mntent* mentry; while ((mentry = getmntent(fp)) != NULL) { if (strncmp(mentry->mnt_dir, "/mnt/", 5) == 0 || strncmp(mentry->mnt_dir, "/storage/", 9) == 0) { toUnmount.push_front(std::string(mentry->mnt_dir)); } } endmntent(fp); for (auto path : toUnmount) { //強制解除安裝 android::vold::ForceUnmount(path); } return 0; }
此處開啟的”/proc/mounts”每一行內容依次是檔名,目錄,型別,操作。例如:
/dev/fuse /mnt/runtime/default/emulated fuse rw,nosuid,nodev,...
該方法的主要工作是解除安裝:
- 內部儲存mInternalEmulated;
- 外部儲存mDisks,比如sdcard等;
- “/proc/mounts”中目錄包含mnt或者storage的路徑;
解除安裝內部儲存:
status_t EmulatedVolume::doUnmount() { if (mFusePid > 0) { kill(mFusePid, SIGTERM); TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0)); mFusePid = 0; } KillProcessesUsingPath(getPath()); //強制解除安裝fuse路徑 ForceUnmount(mFuseDefault); ForceUnmount(mFuseRead); ForceUnmount(mFuseWrite); rmdir(mFuseDefault.c_str()); rmdir(mFuseRead.c_str()); rmdir(mFuseWrite.c_str()); mFuseDefault.clear(); mFuseRead.clear(); mFuseWrite.clear(); return OK; }
KillProcessesUsingPath
的功能很強大,通過檔案path來檢視其所在程式,並殺掉相應程式。當以下5處任意一處存在與path相同的地方,則會殺掉相應的程式:
proc/<pid>/fd
,開啟檔案;proc/<pid>/maps
開啟檔案對映;proc/<pid>/cwd
連結檔案;proc/<pid>/root
連結檔案;proc/<pid>/exe
連結檔案;
3.2.3.2 EV.create
[-> VolumeBase.cpp]
status_t VolumeBase::create() { mCreated = true; status_t res = doCreate(); //通知VolumeCreated事件 notifyEvent(ResponseCode::VolumeCreated, StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str())); //設定為非掛載狀態 setState(State::kUnmounted); return res; } void VolumeBase::notifyEvent(int event, const std::string& value) { if (mSilent) return; //通過socket向MountService傳送建立volume的命令(650) VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event, StringPrintf("%s %s", getId().c_str(), value.c_str()).c_str(), false); }
3.2.4 process_config(vm)
[-> system/vold/Main.cpp]
static int process_config(VolumeManager *vm) { //獲取Fstab路徑 std::string path(android::vold::DefaultFstabPath()); fstab = fs_mgr_read_fstab(path.c_str()); ... bool has_adoptable = false; for (int i = 0; i < fstab->num_entries; i++) { 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))); } } property_set("vold.has_adoptable", has_adoptable ? "1" : "0"); return 0; }
Fstab路徑:首先通過getprop ro.hardware
,比如高通晶片則為qcom
那麼Fstab路徑就是/fstab.qcom
,那麼該檔案的具體內容,例如(當然這個不同手機會有所不同):
src | mnt_point | type | mnt_flags and options | fs_mgr_flags |
---|---|---|---|---|
/dev/block/bootdevice/by-name/system | /system | ext4 | ro,barrier=1,discard | wait,verify |
/dev/block/bootdevice/by-name/userdata | /data | ext4 | nosuid,nodev,barrier=1,noauto_da_alloc,discard | wait,check,forceencrypt=footer |
/dev/block/bootdevice/by-name/cust | /cust | ext4 | nosuid,nodev,barrier=1 | wait,check |
/devices/soc.0/7864900.sdhci/mmc_host* | /storage/sdcard1 | vfat | nosuid,nodev | wait,voldmanaged=sdcard1:auto,noemulatedsd,encryptable=footer |
/dev/block/bootdevice/by-name/config | /frp | emmc | defaults defaults | |
/devices/platform/msm_hsusb* | /storage/usbotg | vfat | nosuid,nodev | wait,voldmanaged=usbotg:auto,encryptable=footer |
3.3 NetlinkManager
3.3.1 建立
[-> NetlinkManager.cpp]
NetlinkManager *NetlinkManager::Instance() { if (!sInstance) sInstance = new NetlinkManager(); return sInstance; }
3.3.2 nm->setBroadcaster
void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }
3.3.3 nm->start
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(); //記錄當前程式的pid nladdr.nl_groups = 0xffffffff; //建立event socket if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) < 0) { return -1; } //設定uevent的SO_RCVBUFFORCE選項 if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) { goto out; } //設定uevent的SO_PASSCRED選項 if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) { goto out; } //繫結uevent socket if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) { goto out; } //建立NetlinkHandler【見小節3.3.4】 mHandler = new NetlinkHandler(mSock); //啟動NetlinkHandler【見小節3.3.5】 if (mHandler->start()) { goto out; } return 0; out: close(mSock); return -1; }
3.3.4 NetlinkHandler
NetlinkHandler繼承於NetlinkListener
,NetlinkListener
繼承於SocketListener
。new NetlinkHandler(mSock)中引數mSock是用於與Kernel進行通訊的socket物件。由於這個繼承關係,當NetlinkHandler初始化時會呼叫基類的初始化,最終呼叫到:
[-> SocketListener.cpp]
SocketListener::SocketListener(int socketFd, bool listen) { //listen=false init(NULL, socketFd, listen, false); } void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) { mListen = listen; mSocketName = socketName; //用於監聽Kernel傳送過程的uevent事件 mSock = socketFd; mUseCmdNum = useCmdNum; //初始化同步鎖 pthread_mutex_init(&mClientsLock, NULL); //建立socket通訊的client端 mClients = new SocketClientCollection(); }
到此,mListen = false; mSocketName = NULL; mUseCmdNum = false。 另外,這裡用到的同步鎖,用於控制多執行緒併發訪問。 接著在來看看start過程:
3.3.5 NH->start
[-> NetlinkHandler.cpp]
int NetlinkHandler::start() { return this->startListener(); }
[-> SocketListener.cpp]
int SocketListener::startListener() { return startListener(4); } int SocketListener::startListener(int backlog) { ... //mListen =false if (mListen && listen(mSock, backlog) < 0) { return -1; } else if (!mListen) //建立SocketClient物件,並加入到mClients佇列 mClients->push_back(new SocketClient(mSock, false, mUseCmdNum)); //建立匿名管道 if (pipe(mCtrlPipe)) { return -1; } //建立工作執行緒,執行緒執行函式threadStart【見小節3.3.6】 if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) { return -1; } return 0; }
mCtrlPipe是匿名管道,這是一個二元陣列,mCtrlPipe[0]從管道讀資料,mCtrlPipe[1]從管道寫資料。
3.3.6 threadStart
[-> SocketListener.cpp]
void *SocketListener::threadStart(void *obj) { SocketListener *me = reinterpret_cast<SocketListener *>(obj); //【見小節3.3.7】 me->runListener(); pthread_exit(NULL); //執行緒退出 return NULL; }
3.3.7 SL->runListener
[-> SocketListener.cpp]
void SocketListener::runListener() { SocketClientCollection pendingList; while(1) { SocketClientCollection::iterator it; fd_set read_fds; int rc = 0; int max = -1; FD_ZERO(&read_fds); if (mListen) { max = mSock; FD_SET(mSock, &read_fds); } FD_SET(mCtrlPipe[0], &read_fds); if (mCtrlPipe[0] > max) max = mCtrlPipe[0]; pthread_mutex_lock(&mClientsLock); for (it = mClients->begin(); it != mClients->end(); ++it) { // NB: calling out to an other object with mClientsLock held (safe) int fd = (*it)->getSocket(); FD_SET(fd, &read_fds); if (fd > max) { max = fd; } } pthread_mutex_unlock(&mClientsLock); SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName); if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) { if (errno == EINTR) continue; SLOGE("select failed (%s) mListen=%d, max=%d", strerror(errno), mListen, max); sleep(1); continue; } else if (!rc) continue; if (FD_ISSET(mCtrlPipe[0], &read_fds)) { char c = CtrlPipe_Shutdown; TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1)); if (c == CtrlPipe_Shutdown) { break; } continue; } if (mListen && FD_ISSET(mSock, &read_fds)) { struct sockaddr addr; socklen_t alen; int c; do { alen = sizeof(addr); c = accept(mSock, &addr, &alen); SLOGV("%s got %d from accept", mSocketName, c); } while (c < 0 && errno == EINTR); if (c < 0) { SLOGE("accept failed (%s)", strerror(errno)); sleep(1); continue; } fcntl(c, F_SETFD, FD_CLOEXEC); pthread_mutex_lock(&mClientsLock); mClients->push_back(new SocketClient(c, true, mUseCmdNum)); pthread_mutex_unlock(&mClientsLock); } /* Add all active clients to the pending list first */ pendingList.clear(); pthread_mutex_lock(&mClientsLock); for (it = mClients->begin(); it != mClients->end(); ++it) { SocketClient* c = *it; // NB: calling out to an other object with mClientsLock held (safe) int fd = c->getSocket(); if (FD_ISSET(fd, &read_fds)) { pendingList.push_back(c); c->incRef(); } } pthread_mutex_unlock(&mClientsLock); /* Process the pending list, since it is owned by the thread, * there is no need to lock it */ while (!pendingList.empty()) { /* Pop the first item from the list */ it = pendingList.begin(); SocketClient* c = *it; pendingList.erase(it); /* Process it, if false is returned, remove from list */ if (!onDataAvailable(c)) { release(c, false); } c->decRef(); } } }
3.4 CommandListener
3.4.1 建立
[-> CommandListener.cpp]
CommandListener::CommandListener() : FrameworkListener("vold", true) { registerCmd(new DumpCmd()); registerCmd(new VolumeCmd()); registerCmd(new AsecCmd()); registerCmd(new ObbCmd()); registerCmd(new StorageCmd()); registerCmd(new FstrimCmd()); }
3.4.1.1 FrameworkListener
[-> FrameworkListener.cpp]
FrameworkListener::FrameworkListener(const char *socketName, bool withSeq) : SocketListener(socketName, true, withSeq) { init(socketName, withSeq); } void FrameworkListener::init(const char *socketName UNUSED, bool withSeq) { mCommands = new FrameworkCommandCollection(); errorRate = 0; mCommandCount = 0; mWithSeq = withSeq; //true }
3.4.1.2 SocketListener
[-> SocketListener.cpp]
SocketListener::SocketListener(const char *socketName, bool listen, bool useCmdNum) { init(socketName, -1, listen, useCmdNum); } void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) { mListen = listen; //true mSocketName = socketName; //"vold" mSock = socketFd; // -1 mUseCmdNum = useCmdNum; //true pthread_mutex_init(&mClientsLock, NULL); mClients = new SocketClientCollection(); }
socket名為“vold”
3.4.1.3 registerCmd
void FrameworkListener::registerCmd(FrameworkCommand *cmd) { mCommands->push_back(cmd); } CommandListener::VolumeCmd::VolumeCmd() : VoldCommand("volume") { }
建立這些物件 DumpCmd,VolumeCmd,AsecCmd,ObbCmd,StorageCmd,FstrimCmd,並都加入到mCommands佇列。
3.4.2 cl->startListener
int SocketListener::startListener() { return startListener(4); } int SocketListener::startListener(int backlog) { if (!mSocketName && mSock == -1) { ... } else if (mSocketName) { //獲取“vold”所對應的控制程式碼 if ((mSock = android_get_control_socket(mSocketName)) < 0) { return -1; } fcntl(mSock, F_SETFD, FD_CLOEXEC); } //CL開始監聽 if (mListen && listen(mSock, backlog) < 0) { return -1; } ... //建立匿名管道 if (pipe(mCtrlPipe)) { return -1; } //建立工作執行緒 if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) { return -1; } return 0; }
四、小結
- Linux Kernel:通過
uevent
向Vold的NetlinkManager傳送Uevent事件; - NetlinkManager:接收來自Kernel的
Uevent
事件,再轉發給VolumeManager; - VolumeManager:接收來自NetlinkManager的事件,再轉發給CommandListener進行處理;
- CommandListener:接收來自VolumeManager的事件,通過
socket
通訊方式傳送給MountService; - MountService:接收來自CommandListener的事件。
本文從原始碼視角主要介紹了相關模組的建立與啟動過程以及部分流程的介紹。要想更進一步瞭解,Android儲存系統之架構篇。
相關文章
- Android系統原始碼分析之-ContentProviderAndroid原始碼IDE
- spark 原始碼分析之十八 -- Spark儲存體系剖析Spark原始碼
- Android 系統原始碼-1:Android 系統啟動流程原始碼分析Android原始碼
- Simpleperf分析之Android系統篇Android
- android 系統原始碼挖掘之Animator效能優化Android原始碼優化
- ClickHouse原始碼筆記6:探究列式儲存系統的排序原始碼筆記排序
- Android中的資料儲存之檔案儲存Android
- OceanBase 原始碼解讀(九):儲存層程式碼解讀之「巨集塊儲存格式」原始碼
- 理解作業系統之儲存管理作業系統
- 儲存系統
- 談談系統密碼儲存策略密碼
- opentracing-go原始碼閱讀——Log儲存(完結篇)Go原始碼
- [原始碼分析] Dynomite 分散式儲存引擎 之 DynoJedisClient(1)原始碼MIT分散式儲存引擎client
- [原始碼分析] Dynomite 分散式儲存引擎 之 DynoJedisClient(2)原始碼MIT分散式儲存引擎client
- Android系統原始碼分析-事件收集Android原始碼事件
- Android系統原始碼目錄解析Android原始碼
- Laravel 儲存 (Storage) 原始碼分析Laravel原始碼
- Redis常用資料型別及其儲存結構(原始碼篇)Redis資料型別原始碼
- 超市管理系統原始碼 超市進銷存管理系統原始碼 (CS架構)原始碼架構
- 分散式kv儲存系統之Etcd叢集分散式
- Android系統架構與系統原始碼目錄Android架構原始碼
- spark 原始碼分析之十六 -- Spark記憶體儲存剖析Spark原始碼記憶體
- Android中的資料儲存之SharedPreferencesAndroid
- Android Room 之儲存 Objects 中的 ListAndroidOOMObject
- Android系統原始碼剖析-事件分發Android原始碼事件
- Android系統原始碼分析-Broadcast傳送Android原始碼AST
- Appdash原始碼閱讀——Store儲存APP原始碼
- Android FrameWork學習(二)Android系統原始碼除錯AndroidFramework原始碼除錯
- Android 外部儲存Android
- 帶你瞭解遊戲陪玩系統原始碼前端常用的儲存方式遊戲原始碼前端
- Android儲存(2)– 介面卡儲存Android
- Android系統開篇Android
- 分散式系統技術:儲存之資料庫分散式資料庫
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- 儲存篇(1)
- 儲存篇(2)
- 儲存篇(3)
- 【學習筆記之作業系統原理篇】儲存管理筆記作業系統
- app直播原始碼,Android 設定系統亮度APP原始碼Android