🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
在Vold代码中,使用NM模块的流程是: - 调用Instance创建一个NM对象。 - 调用setBroadcaster设置CL对象。 - 调用start启动NM。 接下来,按这三个步骤来分析NM模块。 1. 创建NM Vold调用Instance函数创建了一个NM对象。看到Instance这个函数,读者应能想到,这里可能是采用了单例模式。来看是否如此,代码如下所示。 **NetlinkManager.cpp** ~~~ NetlinkManager *NetlinkManager::Instance() { if(!sInstance) sInstance = new NetlinkManager();//果然是单例模式 returnsInstance; } ~~~ NM的创建真是非常简单。再看第二个被调用的函数setBroadcaster。 2. setBroadcaster的分析 setBroadcaster就更简单了,它的实现在NetlinkManger类的声明中,如下所示: **NetlinkManager.h** ~~~ void setBroadcaster(SocketListener *sl) {mBroadcaster = sl; } ~~~ setBroadcaster参数中的那个sl其实际类型为CommandListener。需要说明的是,虽然NM设置了CL对象,但Vold的NM并没有通过CL发送消息和接收命令,所以在图9-1中,NM模块和CL模块并没有连接线,这一点务请注意。 下面看最后一个函数start。 3. start的分析 前面说过,NM模块将使用Netlink和Kernel进行IPC通信,那么它是怎么做到的呢?来看代码,如下所示: **NetlinkManager.cpp** ~~~ int NetlinkManager::start() { //PF_NETLINK使用的socket地址结构是sockaddr_nl,而不是一般的sockaddr_in structsockaddr_nl nladdr; int sz= 64 * 1024; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = getpid(); //设置自己的进程pid nladdr.nl_groups = 0xffffffff; /* 创建PF_NETLINK地址簇的socket,目前只支持SOCK_DGRAM类型,第三个参数 NETLINK_KOBJECT_UEVENT表示要接收内核的Uevent事件。 */ if((mSock = socket(PF_NETLINK, SOCK_DGRAM,NETLINK_KOBJECT_UEVENT)) < 0) { ...... return -1; } //设置Socket接收缓冲区大小 if(setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) { ...... return -1; } //必须对该socket执行bind操作 if(bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) { ...... return -1; } //创建一个NetlinkHandler对象,并把创建好的Socket句柄传给它。 mHandler = new NetlinkHandler(mSock); //调用NetlinkHandler对象的start if(mHandler->start()) { SLOGE("Unable to start NetlinkHandler: %s", strerror(errno)); return -1; } return0; } ~~~ 从代码上看,NM的start函数分为两个步骤: - 创建地址簇为PF_NETLINK类型的socket并做一些设置,这样NM就能和Kernel通信了。关于Netlink的使用技巧网上有很多资料,读者可在Linux系统上通过man netlink命令来查询相关信息。 - 创建NetlinkHandler对象,并调用它的start。看来,后续工作都是由NetlinkHandler来完成的。 据上文分析可看出,NetlinkHandler才是真正的主角,下面就来分析它。为书写方便起见,NetlinkHandler简称为NLH。 4. NetlinkHandler的分析 (1)创建NLH 代码结构简单的Vold程序中,NetlinkHandler却有一个相对不简单的派生关系,如图9-2所示: :-: ![](http://img.blog.csdn.net/20150802164431558?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图9-2 NLH的派生关系图 直接看代码,来认识这个NLH: **NetlinkHandler.cpp** ~~~ NetlinkHandler::NetlinkHandler(int listenerSocket): NetlinkListener(listenerSocket) { //调用基类NetlinkListener的构造函数。注意传入的参数是和Kernel通信的socket //句柄。注意,文件描述符和句柄表示的是同一个东西,这里不再区分二者。 } ~~~ 再看基类NetlinkListener的构造函数: **NetlinkListener.cpp** ~~~ NetlinkListener::NetlinkListener(int socket) : SocketListener(socket, false) { //调用基类SocketListener的构造函数,第二个参数为false。 } ~~~ 基类SocketListener的构造函数是: **SocketListener.cpp** ~~~ SocketListener::SocketListener(int socketFd,bool listen) { mListen = listen; //这个参数是false mSocketName = NULL; mSock = socketFd;//保存和Kernel通信的socket描述符 //初始化一个mutex,看来会有多个线程存在 pthread_mutex_init(&mClientsLock, NULL); /* SocketClientCollection的声明如下,它是一个列表容器。 typedef android::List<SocketClient *>SocketClientCollection 其中,SocketClient代表和Socket服务端通信的客户端。 */ mClients = new SocketClientCollection(); } ~~~ NLH的创建分析完了。此过程中没有什么新鲜内容。下面看它的start函数。 本章内容会大量涉及Socket,所以读者应先了解与Socket有关的知识,如果需要深入研究,建议阅读《Unix NetworkingProgramming Volume I》[^write]一书。 (2)start的分析 在分析前面的代码时,曾看到NetlinkHandler会创建一个同步互斥对象,这表明NLH会在多线程环境中使用,那么这个线程会在哪里创建呢?来看start的代码,如下所示: **NetlinkHandler.cpp** ~~~ int NetlinkHandler::start() { returnthis->startListener();//startListener由SocketListener实现。 } ~~~ **SocketListener.cpp** ~~~ int SocketListener::startListener() { if(!mSocketName && mSock == -1) { errno = EINVAL; return -1; } elseif (mSocketName) { if((mSock = android_get_control_socket(mSocketName)) < 0) { return -1; } } /* 还记得构造NLH时的参数嘛?mListen为false,这表明NLH不是监听端(listen)。 这里为了代码和操作的统一,用mSock做参数构造了一个SocketClient对象, 并加入到mClients列表中,但这个SocketClient并不是真实客户端的代表。 */ if(mListen && listen(mSock, 4) < 0) { ...... return -1; } else if (!mListen)//以mSock为参数构造SocketClient对象,并加入到对应列表中 mClients->push_back(new SocketClient(mSock)); /* pipe系统调用将创建一个匿名管道,mCtrlPipe是一个int类型的二元数组。 其中mCtrlPipe[0]用于从管道读数据,mCtrlPipe[1]用于往管道写数据 */ if(pipe(mCtrlPipe)) { ...... return -1; } //创建一个工作线程,线程函数是threadStart。 if(pthread_create(&mThread, NULL, SocketListener::threadStart, this)) { ...... return -1; } return0; } ~~~ 如果熟悉Socket编程,理解上面的代码就非常容易了。下面来看NLH的工作线程。 (3)工作线程的分析 工作线程的线程函数threadStart的代码如下所示: **SocketListener.cpp** ~~~ void *SocketListener::threadStart(void *obj) { SocketListener *me = reinterpret_cast<SocketListener *>(obj); me->runListener();//调用runListener。 pthread_exit(NULL); returnNULL; } //直接分析runListener void SocketListener::runListener() { while(1) { SocketClientCollection::iterator it; fd_set read_fds; int rc = 0; int max = 0; FD_ZERO(&read_fds); if(mListen) {//mListen为false,所以不走这个if分支 max = mSock; FD_SET(mSock, &read_fds); } /* 计算max,为什么要有这个操作?这是由select函数决定的,它的第一个参数的取值 必须为它所监视的文件描述符集合中最大的文件描述符加1。 */ FD_SET(mCtrlPipe[0], &read_fds); if(mCtrlPipe[0] > max) max = mCtrlPipe[0]; //还是计算fd值最大的那个 pthread_mutex_lock(&mClientsLock); for (it = mClients->begin(); it != mClients->end(); ++it) { FD_SET((*it)->getSocket(), &read_fds); if ((*it)->getSocket() > max) max = (*it)->getSocket(); } pthread_mutex_unlock(&mClientsLock); /* 注意select函数的第一个参数,为max+1。读者可以通过man select来查询 select的用法,注意,在Windows平台上的select对第一个参数没有要求。 */ if((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) { sleep(1); continue; }else if (!rc) continue; //如果管道可读的话,表示需要退出工作线程。 if(FD_ISSET(mCtrlPipe[0], &read_fds)) break; if(mListen && FD_ISSET(mSock, &read_fds)) { //如果是listen端的话,mSock可读表示有客户端connect上 struct sockaddr addr; socklen_t alen = sizeof(addr); int c; //调用accept接受客户端的连接,返回用于和客户端通信的Socket描述符 if ((c = accept(mSock, &addr, &alen)) < 0) { SLOGE("accept failed (%s)", strerror(errno)); sleep(1); continue; } pthread_mutex_lock(&mClientsLock); //根据返回的客户端Socket描述符构造一个SocketClient对象,并加入到对应list mClients->push_back(new SocketClient(c)); pthread_mutex_unlock(&mClientsLock); } do{ pthread_mutex_lock(&mClientsLock); for (it = mClients->begin(); it !=mClients->end(); ++it) { int fd = (*it)->getSocket(); if (FD_ISSET(fd, &read_fds)) { pthread_mutex_unlock(&mClientsLock); /* 有数据通过Socket发送过来,所以调用onDataAvailable进行处理。 如果在onDataAvailable返回false,表示需要关闭该连接。 */ if (!onDataAvailable(*it)){ close(fd); pthread_mutex_lock(&mClientsLock); delete *it; it =mClients->erase(it); pthread_mutex_unlock(&mClientsLock); } FD_CLR(fd, &read_fds); continue; } } pthread_mutex_unlock(&mClientsLock); }while (0); } } ~~~ 从代码中可看到: - 工作线程退出的条件是匿名管道可读,但在一般情况下不需要它退出,所以可以忽略此项内容。 - 不论是服务端还是客户端,收到数据后都会调用onDataAvailable进行处理。 下面就来看NLH的数据处理。 (4)数据处理 根据前面的分析,收到数据后首先调用onDataAvailable函数进行处理,这个函数由NLH的基类NetlinkListener实现。代码如下所示: **NetlinkListener** ~~~ bool NetlinkListener::onDataAvailable(SocketClient*cli) { intsocket = cli->getSocket(); intcount; /* 调用recev接收数据,如果接收错误,则返回false,这样这个socket在 上面的工作线程中就会被close。 */ if((count = recv(socket, mBuffer, sizeof(mBuffer), 0)) < 0) { SLOGE("recv failed (%s)", strerror(errno)); return false; } //new一个NetlinkEvent,并调用decode来解析接收到的Uevent数据 NetlinkEvent *evt = new NetlinkEvent(); if(!evt->decode(mBuffer, count)) { goto out; } //调用onEvent,并传递NetlinkEvent对象。 onEvent(evt); out: deleteevt; return true; ~~~ decode函数就是将收到的Uevent信息填充到一个NetlinkEvent对象中,例如Action是什么,SUBSYSTEM是什么等,以后处理Uevent时就不用再解析字符串了。 看onEvent函数,此函数是由NLH自己实现的,代码如下所示: **NetlinkHandler.cpp** ~~~ void NetlinkHandler::onEvent(NetlinkEvent *evt){ VolumeManager *vm = VolumeManager::Instance(); constchar *subsys = evt->getSubsystem(); if(!subsys) { return; } if (!strcmp(subsys, "block")) { vm->handleBlockEvent(evt); //调用VM的handleBlockEvent } elseif (!strcmp(subsys, "switch")) { vm->handleSwitchEvent(evt);//调用VM的handleSwitchEvent } else if (!strcmp(subsys, "battery")){ //这两个事件和外部存储系统没有关系,所以不处理 } elseif (!strcmp(subsys, "power_supply")) { } } ~~~ NLH的工作已介绍完,下面总结一下NM模块的工作。 5. NM模块的总结 NM模块的功能就是从Kernel接收Uevent消息,然后转换成一个NetlinkEvent对象,最后会调用VM的处理函数来处理这个NetlinkEvent对象。 [^write]: 该书中文版名为《UNIX网络编程第3版.第1卷,套接字联网API》,人民邮电出版社,2009年版。