Android的存儲系統—Vold與MountService分析(三)

来源:http://www.cnblogs.com/pepsimaxin/archive/2016/02/17/5195842.html
-Advertisement-
Play Games

Android的存儲系統(三) 回顧:前帖分析了Vold的main()函數和NetlinkManager的函數調用流程,截止到NetlinkHandler的創建和start()調用,本帖繼續分析源碼 1、處理block類型的uevent main()函數創建了CommandListener對象,Ne


Android的存儲系統(三)

回顧:前帖分析了Vold的main()函數和NetlinkManager的函數調用流程,截止到NetlinkHandler的創建和start()調用,本帖繼續分析源碼

 

 

1、處理block類型的uevent

  main()函數創建了CommandListener對象,NetlinkManager的start()函數又創建了NetlinkHandler對象,如果將CommandListener類和NetlinkHandler類的繼承關係圖畫出來,會發現它們都是從SocketListener類派生出來的,如下圖所示:

 

圖1 NetlinkHandler和CommandListener的繼承關係

  原理:處於最底層的SocketListener類的作用是監聽socket的數據,接收到數據後分別交給FrameworkListener類和NetlinkListener類的函數,並分別對來自Framework和驅動的數據進行分析,分析後根據命令再分別調用CommandListener和NetlinkHandler中的函數。

  觀察NetlinkHandler類的構造方法,代碼如下:

NetlinkHandler::NetlinkHandler(int listenerSocket) :
                NetlinkListener(listenerSocket) {
}

  這個構造方法很簡單,再看看它的start()方法,代碼如下:

int NetlinkHandler::start() {
    return this->startListener();
}

  可以發現,start()方法調用了SocketListener的startListener()函數,代碼如下:

 1 int SocketListener::startListener(int backlog) {
 2      if (!mSocketName && mSock == -1) {
 3          SLOGE("Failed to start unbound listener");
 4          errno = EINVAL;
 5          return -1;
 6      } else if (mSocketName) {            // 只有CommandListener中會設置mSocketName
 7            if ((mSock = android_get_control_socket(mSocketName)) < 0) {
 8                SLOGE("Obtaining file descriptor socket '%s' failed: %s",mSocketName, strerror(errno));
 9                return -1;
10            }
11            SLOGV("got mSock = %d for %s", mSock, mSocketName);
12      }
13 
14      if (mListen && listen(mSock, backlog) < 0) {
15          SLOGE("Unable to listen on socket (%s)", strerror(errno));
16          return -1;
17      } else if (!mListen)
18           mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));
19 
20      if (pipe(mCtrlPipe)) {                          // 創建管道,用於退出監聽線程
21          SLOGE("pipe failed (%s)", strerror(errno));
22          return -1;
23      }
24 
25      if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {         // 創建一個監聽線程
26          SLOGE("pthread_create (%s)", strerror(errno));
27          return -1;
28      }
29 
30      return 0;
31 }

  startListener()函數開始監聽socket,這個函數在NetlinkHandler中會被調用,在CommandListener也會被調用。

  startListener()函數首先判斷變數mSocketName是否有值,只有CommandListener對象會對這個變數賦值,它的值就是在init.rc中定義的socket字元串。

      調用函數 android_get_control_socket()的目的是從環境變數中取得socket的值,這樣CommandListener對象得到了它需要監聽的socket,

  而對於NetlinkHandler對象而言,它的mSocket不為NULL,前面已經創建了socket。

 

  startListener()函數接下來會根據成員變數mListener的值來判斷是否需要調用Listen()函數來監聽socket。這個mListen的值在對象構造時根據參數來初始化。

  對於CommandListener對象,mListener的值為ture,對於NetlinkHandler對象,mListener的值為false,這是因為CommandListener對象和SystemServer通信,需要監聽socket連接,而NetlinkHandler對象則不用。

  

  接下來startListener()函數會創建一個管道,這個管道的作用是通知線程停止監聽,這個線程就是startListener()函數最後創建的監聽線程,它的運行函數是threadStart(),在前貼的NetlinkManager家族圖系中我們可以清晰的發現,其代碼如下:

void *SocketListener::threadStart(void *obj) {
     SocketListener *me = reinterpret_cast<SocketListener *>(obj);
     me->runListener();                                             // 調用runListener()方法
     pthread_exit(NULL);
     return NULL;
}

  threadStart()中又調用了runListener()函數,代碼如下:

 1 void SocketListener::runListener() {
 2 
 3       SocketClientCollection pendingList;
 4 
 5       while(1) {             // 無限迴圈,一直監聽
 6             SocketClientCollection::iterator it;
 7             fd_set read_fds;
 8             int rc = 0;
 9             int max = -1;
10 
11             FD_ZERO(&read_fds);       // 清空文件描述符集read_fds
12 
13             if (mListen) {            // 如果需要監聽
14                 max = mSock;
15                 FD_SET(mSock, &read_fds);               // 把mSock加入到read_fds
16             }
17 
18             FD_SET(mCtrlPipe[0], &read_fds);                 // 把管道mCtrlPipe[0]也加入到read_fds
19             if (mCtrlPipe[0] > max)
20                 max = mCtrlPipe[0];
21 
22             pthread_mutex_lock(&mClientsLock);               // 對容器mClients的操作需要加鎖
23             for (it = mClients->begin(); it != mClients->end(); ++it) {    // mClient中保存的是NetlinkHandler對象的socket,或者CommandListener接入的socket
24                   int fd = (*it)->getSocket();
25                   FD_SET(fd, &read_fds);      // 遍歷容器mClients的所有成員,調用內聯函數getSocket()獲取文件描述符,並添加到文件描述符集read_fds
26                   if (fd > max) {                                     // 也加入到read_fds
27                       max = fd;
28                   }
29             }
30             pthread_mutex_unlock(&mClientsLock);
31             SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName);
32             if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {              // 執行select調用,開始等待socket上的數據到來
33                  if (errno == EINTR)                                              // 因為中斷退出select,繼續
34                      continue;
35                  SLOGE("select failed (%s) mListen=%d, max=%d", strerror(errno), mListen, max);
36                  sleep(1);                               // select出錯,休眠1秒後繼續
37                  continue;
38             } else if (!rc)
39                  continue;                           // 如果fd上沒有數據到達,繼續
40 
41             if (FD_ISSET(mCtrlPipe[0], &read_fds)) {
42                 char c = CtrlPipe_Shutdown;
43                 TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
44                 if (c == CtrlPipe_Shutdown) {
45                     break;
46                 }
47                 continue;
48             }
49             if (mListen && FD_ISSET(mSock, &read_fds)) {           // 如果是CommandListener對象上有連接請求
50                 struct sockaddr addr;
51                 socklen_t alen;
52                 int c;
53 
54                 do {
55                       alen = sizeof(addr);
56                       c = accept(mSock, &addr, &alen);             // 接入連接請求
57                       SLOGV("%s got %d from accept", mSocketName, c);
58                 } while (c < 0 && errno == EINTR);                 // 如果是中斷導致失敗,重新接入
59                 if (c < 0) {
60                     SLOGE("accept failed (%s)", strerror(errno));
61                     sleep(1);
62                     continue;                                      // 接入發生錯誤,繼續迴圈
63                 }
64                 pthread_mutex_lock(&mClientsLock);
65                 mClients->push_back(new SocketClient(c, true, mUseCmdNum));   // 把接入的socket連接加入到mClients,這樣再迴圈時就會監聽到它的數據到達
66                 pthread_mutex_unlock(&mClientsLock);
67             }
68             
69             /* Add all active clients to the pending list first */
70             pendingList.clear();
71             pthread_mutex_lock(&mClientsLock);
72             for (it = mClients->begin(); it != mClients->end(); ++it) {
73                  SocketClient* c = *it;
74                  int fd = c->getSocket();
75                  if (FD_ISSET(fd, &read_fds)) {
76                      pendingList.push_back(c);             // 如果mClients中的某個socket上有數據了,把它加入到pendingList列表中
77                      c->incRef();
78                  }
79             }
80             pthread_mutex_unlock(&mClientsLock);
81 
82             /* Process the pending list, since it is owned by the thread,* there is no need to lock it */
83             while (!pendingList.empty()) {                 // 處理pendingList列表
84                   /* Pop the first item from the list */
85                   it = pendingList.begin();
86                   SocketClient* c = *it;
87                   pendingList.erase(it);                   // 把處理了的socket從pendingList列表中刪除
88                   /* Process it, if false is returned, remove from list */
89                   if (!onDataAvailable(c)) {
90                       release(c, false);   // 調用release()函數-->調用onDataAvailable()方法
91                   }
92                   c->decRef();
93             }
94       }
95 }

  SocketListener::runListener是線程真正執行的函數。

  以上runListener()函數雖然比較長,但這是一段標準的處理混合socket連接的代碼,對於我們編寫socket的程式大有幫助,這裡先做簡單瞭解。

 

  <--------接下來,我們繼續分析......-------->

 

  runListener()函數收到從驅動傳遞的數據或者MountService傳遞的數據後,調用onDataAvailable()函數來處理,FrameworkListener類和NetlinkListener類都會重載這個函數。

  首先來分析一下NetlinkListener類的onDataAvailable()函數是如何實現的!

  直接上代碼:

 1 bool NetlinkListener::onDataAvailable(SocketClient *cli)
 2 {
 3       int socket = cli->getSocket();
 4       ssize_t count;
 5       uid_t uid = -1;
 6       /*從socket中讀取kernel發送來的uevent消息*/
 7       count = TEMP_FAILURE_RETRY(uevent_kernel_multicast_uid_recv(socket, mBuffer, sizeof(mBuffer), &uid));
 8       if (count < 0) {                     // 如果count<0,進行錯誤處理
 9           if (uid > 0)
10               LOG_EVENT_INT(65537, uid);
11           return false;
12       }
13 
14       NetlinkEvent *evt = new NetlinkEvent();     // 創建NetlinkEvent對象
15       if (evt->decode(mBuffer, count, mFormat)) {     // 調用decode()函數
16           onEvent(evt);                           // 在NetlinkHandler中實現17       } else if (mFormat != NETLINK_FORMAT_BINARY) {
18           SLOGE("Error decoding NetlinkEvent");
19       }
20       delete evt;
21       return true;
22 }

  NetlinkListener類的onDataAvailable()函數首先調用uevent_kernel_multicast_uid_recv()函數來接收uevent消息。

  接收到消息後,會創建NetlinkEvent對象,然後調用它的decode()函數對消息進行解碼,然後用得到的消息數據給NetlinkEvent對象的成員變數賦值。

  最後onDataAvailable()函數調用了onEvent()函數繼續處理消息,onEvent()函數的代碼如下:

void NetlinkHandler::onEvent(NetlinkEvent *evt) {
      VolumeManager *vm = VolumeManager::Instance();
      const char *subsys = evt->getSubsystem();

      if (!subsys) {
          SLOGW("No subsystem found in netlink event");
          return;
      }

      if (!strcmp(subsys, "block")) {
          vm->handleBlockEvent(evt);           // 調用VolumeManager的handleBlockEvent()函數來處理
      }
}

  NetlinkHandler的onEvent()函數中會判斷event屬於哪個子系統的,如果屬於“block”(SD熱插拔),則調用VolumeManager的handleBlockEvent()函數來處理,代碼如下:

void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
      const char *devpath = evt->findParam("DEVPATH");
      VolumeCollection::iterator it;
      bool hit = false;
      for (it = mVolumes->begin(); it != mVolumes->end(); ++it) {
           if (!(*it)->handleBlockEvent(evt)) {        // 對每個DirectVolume對象,調用它handleBlockEvent來處理這個event
               hit = true;                             // 如果某個Volume對象處理了Event,則返回
               break;
           }
      }
.....
}

總結:本帖的源碼分析先到這裡為止,下一貼再分析DirectVolume對象的handleBlockEvent()函數以及CommandListener對象如何處理從MountService發送的命令數據,即我們之前還沒有討論的關於FrameworkListener的onDataAvailable()函數的代碼!

PS:希望對Android手機開發、IOS、以及游戲(純興趣,白菜)和Java EE感興趣的碼友們互粉,這樣我也能及時的看到你們的大神之作和經驗之貼,感謝感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 接觸 DeviceOne 要從15年11月開始說起了,因項目和產品時間需求接觸了快速開發平臺,DeviceOne是非常棒的一個平臺,雙向數據綁定,可以自定義指令,過濾器等等。總之非常好用完全超出了我們功能需求。之後我們使用了混合型開發平臺,沒有達到原生App的體驗和流暢,在頁面切換以及頁面滾動的時候
  • 查看效果:http://hovertree.com/code/javascript/pwl4bhoi.htm 代碼如下: 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html;
  • 這是一款基於jQuery和CSS3的登錄表單,登錄表單的界面整體呈現扁平化風格,非常簡潔和清新。當焦點在表單的輸入欄位上時,表單提示文字會出現一個小動畫,同時這款CSS3登錄表單的最大特點是其個性化的登錄按鈕,滑鼠滑過也有不錯的效果。 線上預覽 源碼下載 實現的代碼。 html代碼: <div cl
  • 1.0 作者:cloudgamer http://www.cnblogs.com/cloudgamer/archive/2010/04/01/ImageZoom.html
  • Web技術的發展速度太快了,如果你不與時俱進,就會被淘汰。因此,為了應對即將到來的HTML5,本文總結了22個HTML5的初級技巧,希望能對你進一步學習好HTML5會有所幫助。 1. 新的Doctype聲明 XHTML的聲明太長了,我相信很少會有前端開發人員能手寫出這個Doctype聲明。 1 <!
  • 1.不論滑鼠指針離開被選元素還是任何子元素,都會觸發 mouseout 事件。 2.只有在滑鼠指針離開被選元素時,才會觸發 mouseleave 事件。 <div class="sel_box"> <input type="button" value="請選擇所屬部門" id="sel_dept"
  • 一、首先需要明白的幾個概念 1、屏幕尺寸:也就是我們平常所說的某某手機幾寸屏。比如蘋果的4.7寸, 榮耀6的5.5寸。這裡說的寸是英寸(1 英寸 = 2.54 釐米)。 計算方法:屏幕尺寸=對角先尺寸(cm) / 2.54(cm) 一般用英寸表示。1英寸=2.54cm 2、解析度:屏幕上顯示的像素個
  • # built application files *.apk *.ap_ # files for the dex VM *.dex # Intellij project files .idea/ .gradle/ *.iml # Java class files *.class # Local c
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...