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()函数,代码如下:

 int SocketListener::startListener(int backlog) {
      ) {
          SLOGE("Failed to start unbound listener");
          errno = EINVAL;
          ;
      } else if (mSocketName) {            // 只有CommandListener中会设置mSocketName
            ) {
                SLOGE("Obtaining file descriptor socket '%s' failed: %s",mSocketName, strerror(errno));
                ;
            }
            SLOGV("got mSock = %d for %s", mSock, mSocketName);
      }

      ) {
          SLOGE("Unable to listen on socket (%s)", strerror(errno));
          ;
      } else if (!mListen)
           mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));

      if (pipe(mCtrlPipe)) {                          // 创建管道,用于退出监听线程
          SLOGE("pipe failed (%s)", strerror(errno));
          ;
      }

      if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {         // 创建一个监听线程
          SLOGE("pthread_create (%s)", strerror(errno));
          ;
      }

      ;
 }

  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()函数,代码如下:

 void SocketListener::runListener() {

       SocketClientCollection pendingList;

       ) {             // 无限循环,一直监听
             SocketClientCollection::iterator it;
             fd_set read_fds;
             ;
             ;

             FD_ZERO(&read_fds);       // 清空文件描述符集read_fds

             if (mListen) {            // 如果需要监听
                 max = mSock;
                 FD_SET(mSock, &read_fds);               // 把mSock加入到read_fds
             }

             FD_SET(mCtrlPipe[], &read_fds);                 // 把管道mCtrlPipe[0]也加入到read_fds
             ] > max)
                 max = mCtrlPipe[];

             pthread_mutex_lock(&mClientsLock);               // 对容器mClients的操作需要加锁
             for (it = mClients->begin(); it != mClients->end(); ++it) {    // mClient中保存的是NetlinkHandler对象的socket,或者CommandListener接入的socket
                   int fd = (*it)->getSocket();
                   FD_SET(fd, &read_fds);      // 遍历容器mClients的所有成员,调用内联函数getSocket()获取文件描述符,并添加到文件描述符集read_fds
                   if (fd > max) {                                     // 也加入到read_fds
                       max = fd;
                   }
             }
             pthread_mutex_unlock(&mClientsLock);
             SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName);
             , &read_fds, NULL, NULL, NULL)) < ) {              // 执行select调用,开始等待socket上的数据到来
                  if (errno == EINTR)                                              // 因为中断退出select,继续
                      continue;
                  SLOGE("select failed (%s) mListen=%d, max=%d", strerror(errno), mListen, max);
                  sleep();                               // select出错,休眠1秒后继续
                  continue;
             } else if (!rc)
                  continue;                           // 如果fd上没有数据到达,继续

             ], &read_fds)) {
                 char c = CtrlPipe_Shutdown;
                 TEMP_FAILURE_RETRY(read(mCtrlPipe[], &c, ));
                 if (c == CtrlPipe_Shutdown) {
                     break;
                 }
                 continue;
             }
             if (mListen && FD_ISSET(mSock, &read_fds)) {           // 如果是CommandListener对象上有连接请求
                 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);
                 }  && errno == EINTR);                 // 如果是中断导致失败,重新接入
                 ) {
                     SLOGE("accept failed (%s)", strerror(errno));
                     sleep();
                     continue;                                      // 接入发生错误,继续循环
                 }
                 pthread_mutex_lock(&mClientsLock);
                 mClients->push_back(new SocketClient(c, true, mUseCmdNum));   // 把接入的socket连接加入到mClients,这样再循环时就会监听到它的数据到达
                 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;
                  int fd = c->getSocket();
                  if (FD_ISSET(fd, &read_fds)) {
                      pendingList.push_back(c);             // 如果mClients中的某个socket上有数据了,把它加入到pendingList列表中
                      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()) {                 // 处理pendingList列表
                   /* Pop the first item from the list */
                   it = pendingList.begin();
                   SocketClient* c = *it;
                   pendingList.erase(it);                   // 把处理了的socket从pendingList列表中删除
                   /* Process it, if false is returned, remove from list */
                   if (!onDataAvailable(c)) {
                       release(c, false);   // 调用release()函数-->调用onDataAvailable()方法
                   }
                   c->decRef();
             }
       }
 }

  SocketListener::runListener是线程真正执行的函数。

  以上runListener()函数虽然比较长,但这是一段标准的处理混合socket连接的代码,对于我们编写socket的程序大有帮助,这里先做简单了解。

  <--------接下来,我们继续分析......-------->

  runListener()函数收到从驱动传递的数据或者MountService传递的数据后,调用onDataAvailable()函数来处理,FrameworkListener类和NetlinkListener类都会重载这个函数。

  首先来分析一下NetlinkListener类的onDataAvailable()函数是如何实现的!

  直接上代码:

 bool NetlinkListener::onDataAvailable(SocketClient *cli)
 {
       int socket = cli->getSocket();
       ssize_t count;
       uid_t uid = -;
       /*从socket中读取kernel发送来的uevent消息*/
       count = TEMP_FAILURE_RETRY(uevent_kernel_multicast_uid_recv(socket, mBuffer, sizeof(mBuffer), &uid));
       ) {                     // 如果count<0,进行错误处理
           )
               LOG_EVENT_INT(, uid);
           return false;
       }

       NetlinkEvent *evt = new NetlinkEvent();     // 创建NetlinkEvent对象
       if (evt->decode(mBuffer, count, mFormat)) {     // 调用decode()函数
        } else if (mFormat != NETLINK_FORMAT_BINARY) {
           SLOGE("Error decoding NetlinkEvent");
       }
       delete evt;
       return true;
 }

  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感兴趣的码友们互粉,这样我也能及时的看到你们的大神之作和经验之贴,感谢感谢!

Android 7.0 存储系统—Vold与MountService分析(三)(转 Android 9.0 分析)的更多相关文章

  1. Android 7.0 存储系统—Vold与MountService分析(二)(转 Android 9.0 分析)

    Android的存储系统(二) 回顾:前贴主要分析了Android存储系统的架构和原理图,简要的介绍了整个从Kernel-->Vold-->上层MountService之间的数据传输流程, ...

  2. Android 7.0 存储系统—Vold与MountService分析(一)(转 Android 9.0 分析)

    Android的存储系统(一) 看了很长时间Vold存储模块的相关知识,也死扣了一段时间的Android源码,发现Android存储系统所涉及的函数调用,以及Kernel与上层之间的Socket传输真 ...

  3. Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】

    本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...

  4. 【Android开发日记】之入门篇(三)——Android目录结构

    本来的话,这一章想要介绍的是Android的系统架构,毕竟有了这些知识的储备,再去看实际的项目时才会更清楚地理解为什么要这样设计,同时在开发中遇到难题,也可以凭借着对Android的了解,尽快找出哪些 ...

  5. Spring5深度源码分析(三)之AnnotationConfigApplicationContext启动原理分析

    代码地址:https://github.com/showkawa/spring-annotation/tree/master/src/main/java/com/brian AnnotationCon ...

  6. Activity的绘制流程简单分析(基于android 4.0源码进行分析)

    要明白这个流程,我们还得从第一部开始,大家都知道 在activity里面 setcontentview 调用结束以后 就可以看到程序加载好我们的布局文件了,从而让我们在手机上看到这个画面. 那么我们来 ...

  7. Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案

    我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如L ...

  8. Android 核心分析之十三Android GWES之Android窗口管理

    Android GWES之Android窗口管理1基本构架原理 Android的窗口管理是C/S模式的.Android中的Window是表示Top Level等顶级窗口的概念.DecorView是Wi ...

  9. Android内存机制分析1——了解Android堆和栈

    //----------------------------------------------------------------------------------- Android内存机制分析1 ...

随机推荐

  1. Netstat状态分类

    用netstat -an命令查看!再stat下面有一些英文,简单说一下这些英文具体都代表什么: LISTEN:(Listening for a connection.)侦听来自远方的TCP端口的连接请 ...

  2. Java自学开发编程路线图(文中有资源福利)

    Java 语言入门 免费视频资源<毕向东Java基础教程>:http://yun.itheima.com/course/7.html JavaEE 学习大纲 所处阶段 主讲内容 技术要点 ...

  3. JSF-使用JSF标记

    使用JSF标记 基于Facelets技术的JSF页面是一个 XHTML页面,文件扩展名为 .xhtml 1)JSF页面可用html标记,但必须满足: ①所有标记都必须闭合.如<p>开始,& ...

  4. MySQL中的外键约束

  5. MySQL技术内幕 InnoDB存储引擎(笔记)

    1. InnoDB 体系架构 其中,后台程序主要负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据. 此外将已经修改的数据刷新到磁盘文件,同时保证在数据库发生异常的时候Innodb能恢复正常 ...

  6. 运算符优先级--C

    优先口决 括号成员第一; //括号运算符[]() 成员运算符. -> 全体单目第二; //所有的单目运算符比如++ -- +(正) -(负) 指针运算*& 乘除余三,加减四; //这个& ...

  7. ASP.NET中直接用C# 动态修改CSS样式

    ASP.NET中直接用C# 动态修改CSS样式  wonsoft (wonsoft@163.com) 使用JavaScript控制CSS样式有点麻烦,还是觉得直接使用C#操作更方便快捷,本文通过两个B ...

  8. 【转】javascript 作用域链

    JavaScript函数的作用域链分为定义时作用域链和运行时作用域链: 函数被定义的时候,它有一个属性[[scope]]标明它的定义作用域链,定义时作用域链[[scope]]遵守这样的规则:一个函数的 ...

  9. numpy用法归纳

    1.生成数组 import numpy as np 把python列表转换为数组 >>> np.array([1, 2, 3]) array([1, 2, 3]) 把python的r ...

  10. CSS学习笔记二:css 画立体图形

    继上一次学了如何去运用css画平面图形,这一次学如何去画正方体,从2D向着3D学习,虽然有点满,但总是一个过程,一点一点积累,然后记录起来. Transfrom3D 在这一次中运用到了一下几种属性: ...