<Android开发> Android vold - 第四篇 vold 的NetlinkHandler类简介

本系列主要介绍 Android vold,分为以下篇章
<Android开发> Android vold - 第一篇 vold前言简介
<Android开发> Android vold - 第二篇 vold 的main()函数简介
<Android开发> Android vold - 第三篇 vold 的NetLinkManager类简介
<Android开发> Android vold - 第四篇 vold 的NetlinkHandler类简介

继第三篇vold 的NetLinkManager类简介后,我们来看看NetlinkHandler类的内容。

6 NetlinkHandler类

在第三篇介绍NetLinkManager类是,提到了 NetLinkManager.start()函数,在该函数的:
第33行:mHandler = new NetlinkHandler(mSock);,给mHandler 申请对象;
第34行:mHandler->start() ,启动监听内核uevent事件。
由此进入 NetlinkHandler类方法中。

6.1 NetlinkHandler类定义
NetlinkHandler类定义内容如下:

路径:LINUX/android/system/vold/NetlinkHandler.h
class NetlinkHandler: public NetlinkListener {
public:
    explicit NetlinkHandler(int listenerSocket);
    virtual ~NetlinkHandler();
    int start(void);
    int stop(void);
protected:
    virtual void onEvent(NetlinkEvent *evt);
};

该类继承自父类NetlinkListener;NetlinkHandler类实现了start、stop、onEvent三个方法,以及构造和析构函数。

路径:LINUX/android/system/vold/NetlinkHandler.cpp
NetlinkHandler::NetlinkHandler(int listenerSocket) :
                NetlinkListener(listenerSocket) {
}

NetlinkHandler::~NetlinkHandler() {
}

int NetlinkHandler::start() {
    return this->startListener();   //NetlinkHandler的start函数调用的startListener函数正是 NetlinkListener 的父类 SocketListener 类中的实现
}

int NetlinkHandler::stop() {
    return this->stopListener();
}
// 获取到 kernel 事件 后调用这个函数进行处理
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);
    }
}

可以看到,构造函数将new NetlinkHandler时传入的socket直接传给了父类NetlinkListener,所以socket是NetlinkListener类里使用的,后面会继续看该类。
start函数也是直接调用了父类NetlinkListener的start函数。
stop函数也是直接调用了父类NetlinkListener的stop函数。
onEvent函数是虚函数,父类NetlinkListener没有实现,所以NetlinkHandler类自己实现该方法。

6.2 new NetlinkHandler类

从6.1节可知构造函数将new NetlinkHandler时传入的socket直接传给了父类NetlinkListener,所以socket是NetlinkListener类里使用的。

6.3 NetlinkListener 类定义
看看NetlinkHandler的父类NetlinkListener的定义

路径:LINUX/android/system/core/libsysutils/include/sysutils/NetlinkListener.h
class NetlinkEvent;

class NetlinkListener : public SocketListener {
    char mBuffer[64 * 1024] __attribute__((aligned(4)));
    int mFormat;

public:
    static const int NETLINK_FORMAT_ASCII = 0;
    static const int NETLINK_FORMAT_BINARY = 1;
    static const int NETLINK_FORMAT_BINARY_UNICAST = 2;

#if 1
    /* temporary version until we can get Motorola to update their
     * ril.so.  Their prebuilt ril.so is using this private class
     * so changing the NetlinkListener() constructor breaks their ril.
     */
    NetlinkListener(int socket);
    NetlinkListener(int socket, int format);
#else
    NetlinkListener(int socket, int format = NETLINK_FORMAT_ASCII);
#endif
    virtual ~NetlinkListener() {}

protected:
    virtual bool onDataAvailable(SocketClient *cli);
    virtual void onEvent(NetlinkEvent *evt) = 0;
};

首先我们又看到 NetlinkListener类又进一步集成其父类SocketListener;
以及定义了一些变量,暂不解释变量,用到在说明;
实现了NetlinkListener的两种方法;还有虚函数onEvent,在NetlinkListener的子类NetlinkHandler中实现了。虚函数onDataAvailable则是继承自其父类SocketListener,并实现。

路径:LINUX/android/system/core/libsysutils/src/NetlinkListener.cpp
NetlinkListener::NetlinkListener(int socket) :
                            SocketListener(socket, false) {
    mFormat = NETLINK_FORMAT_ASCII;
}

NetlinkListener类的构造函数只是初始化了编码类型,然后又进入其父类SocketListener了。

6.4 SocketListener类定义
看看NetlinkListener的父类SocketListener的定义;

路径:LINUX/android/system/core/libsysutils/include/sysutils/SocketListener.h
class SocketListener {
    bool                    mListen;
    const char              *mSocketName;
    int                     mSock;
    SocketClientCollection  *mClients;
    pthread_mutex_t         mClientsLock;
    int                     mCtrlPipe[2];
    pthread_t               mThread;
    bool                    mUseCmdNum;

public:
    SocketListener(const char *socketName, bool listen);
    SocketListener(const char *socketName, bool listen, bool useCmdNum);
    SocketListener(int socketFd, bool listen);

    virtual ~SocketListener();
    int startListener();
    int startListener(int backlog);
    int stopListener();

    void sendBroadcast(int code, const char *msg, bool addErrno);
    void runOnEachSocket(SocketClientCommand *command);
    bool release(SocketClient *c) { return release(c, true); }

protected:
    virtual bool onDataAvailable(SocketClient *c) = 0;

private:
    bool release(SocketClient *c, bool wakeup);
    static void *threadStart(void *obj);
    void runListener();
    void init(const char *socketName, int socketFd, bool listen, bool useCmdNum);
};

SocketListener类的内容挺多的,使用时在一一解释吧。

所以我们可以看到当new NetlinkHandler时传入的socket,第一步是调用NetlinkHandler的构造函数,在函数中将socket传给了NetlinkListener类;进一步,NetlinkListener类的构造函数又将socket传给了SocketListener类。并调用SocketListener类的init函数。

6.5 SocketListener::init()
SocketListener::init函数是在SocketListener类的构造函数调用的,所以new类时就对调用到。构造函数内容如下:

SocketListener::SocketListener(const char *socketName, bool listen) {
    init(socketName, -1, listen, false);
}
SocketListener::SocketListener(int socketFd, bool listen) {
    init(NULL, socketFd, listen, false);
}

我们可以看到SocketListener构造函数调用init时传入了NULL、socketFd、 listen和false。
socketFd:是从NetLinkManager.start()函数申请的socket描述符经过NetlinkHandler类->NetlinkListener 类->SocketListener 类,一直传入的。
listen:由NetlinkListener 类构造函数传入,也是固定为false。
我们接着看init函数的内容,如下:

路径:LINUX/android/system/core/libsysutils/src/SocketListener.cpp
void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
	mListen = listen;
	mSocketName = socketName;
	mSock = socketFd;
	mUseCmdNum = useCmdNum;
	pthread_mutex_init(&mClientsLock, NULL);
	mClients = new SocketClientCollection();
}

从SocketListener构造函数调用可知:
mListen : mListen =listen=false;
mSocketName :mSocketName = NULL
mSock :mSock = socketFd = NetlinkManager.mSock;
mUseCmdNum : mUseCmdNum = useCmdNum = false;
pthread_mutex_init(&mClientsLock, NULL):初始化锁;
SocketListener::init又new了一个SocketClientCollection类。 接着继续看该类定义。

6.6 SocketClientCollection定义
看下SocketClientCollection的定义,如下:

路径:LINUX/android/system/core/libsysutils/include/SocketClientCollection.h
class SocketClient {
    int             mSocket;
    bool            mSocketOwned;
    pthread_mutex_t mWriteMutex;
    ....}
typedef android::sysutils::List<SocketClient *> SocketClientCollection;

可以看到是以SocketClient 类为成员的集合List。list类的定义在“LINUX/android/system/core/libsysutils/include/sysutils/List.h”中;
这个后续又多个socket客户端时使用存放socket的。

前面我们分析了NetlinkHandler类申请对象涉及到的层层父类;
这里初步整理一个NetLinkManager类的关系。如下图:
在这里插入图片描述

6.7 NetlinkHandler.start()
在第三章的5.3节中我们知道NetlinkManager::start()在为NetlinkHandler new了一个对象之后调用其start()函数,我们看下该函数的内容,如下:

路径:LINUX/android/system/vold/NetlinkHandler.cpp
int NetlinkHandler::start() {
	//NetlinkHandler的start函数调用的startListener函数正是 NetlinkListener 的父类 SocketListener 类中的实现
    return this->startListener();   
}

我们可以看到NetlinkHandler的start函数调用的是NetlinkHandler的父类NetlinkListener 的startListener函数,这也正是 NetlinkListener 的父类 SocketListener 类中的实现方法。
NetlinkHandler->NetlinkListener ->SocketListener::startListener()

6.8 SocketListener.startListener()
接着看看该函数的内容。

路径:LINUX/android/system/core/libsysutils/src/SocketListener.cpp
int SocketListener::startListener() {
	return startListener(4);//进一步调用带参函数,带入参数4
}

int SocketListener::startListener(int backlog) {
	//此函数的 mSock 是在 NetlinkManager::start 中  mHandler = new NetlinkHandler(mSock);  传入的
	if (!mSocketName && mSock == -1) {              
		SLOGE("Failed to start unbound listener");
		errno = EINVAL;
		return -1;
	} else if (mSocketName) {
		if ((mSock = android_get_control_socket(mSocketName)) < 0) 
		{
			SLOGE("Obtaining file descriptor socket '%s' failed: %s", mSocketName, strerror(errno));
			return -1;
		}
		SLOGV("got mSock = %d for %s", mSock, mSocketName);
		fcntl(mSock, F_SETFD, FD_CLOEXEC);
	}

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

	if (pipe(mCtrlPipe)) {
		SLOGE("pipe failed (%s)", strerror(errno));
		return -1;
	}

	if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {
		SLOGE("pthread_create (%s)", strerror(errno));
		return -1;
	}

	return 0;
}

第3行:进一步调用带参函数,并带入参数4;
第8行:在6.5节时解释了各个变量的值如下:
mListen =false;
mSocketName = null
mSock = NetlinkManager::mSock;
mUseCmdNum = false;
所以第6行的判断不进入。
第12行:因为mSocketName = null 所以也不进入;因此if里面的android_get_control_socket()函数和fcntl()函数先不解释,后续有用到在解释。
第22行:listen()将sockfd引用的套接字标记为被动套接字,也就是说,标记为将使用accept()函数接受传入连接请求的套接符。

sockfd参数:是一个文件描述符,它引用SOCK_STREAM或SOCK_SEQPACKET类型的套接字。
backlog参数:定义了sockfd的挂起连接队列可能增长的最大长度。如果连接请求在队列已满时到达,则客户端可能会收到一个带有ECONREFUSED指示的错误,或者,如果底层协议支持重传,则可以忽略该请求,以便稍后重新尝试连接成功。
返回值:成功后,返回零。出错时,返回-1,并适当设置errno。
这里sockfd = NetlinkManager::mSock,backlog=4。
正常情况下listen()返回大于0的值,不会进入该if判断。

第25行:由前面分析知mListen =false,所以这个判断会进入。
第26行:以mSock为参数,new了一个SocketClient,并放入到mClients,这个mClients是6.6节解释的一个list。后面分析SocketClient类。

路径:LINUX/android/system/core/libsysutils/include/sysutils/List.h
void push_back(const T& val) { insert(end(), val); }

由 push_back可知,是将new的SocketClient到mClients的list的最后。

第28行:mCtrlPipe是类里的定义的“int mCtrlPipe[2]”,pipe()创建一个管道,一个可用于进程间通信的单向数据通道。数组pipefd用于返回引用管道末端的两个文件描述符。pipefd[0]表示管道的读取端。pipefd[1]表示管道的写入端。写入管道写入端的数据由内核缓冲,直到从管道读取端读取。

第33行:pthread_create创建线程,用来创建socket的监听线程。
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
描述:pthread_create()函数在调用进程中启动一个新线程。新线程通过调用start_routine()开始执行;arg作为start_routine()的唯一参数传递。
由传入的参数可知 调用的新线程为SocketListener::threadStart();传入的参数是this,则是当前这个SocketListener类对象的引用。

6.9 SocketListener类定义
在6.8节我们分析到,new了个SocketClient。来看看这个SocketClient有啥东西。

路径:LINUX/android/system/core/libsysutils/include/sysutils/SocketClient.h
class SocketClient {
    int             mSocket;
    bool            mSocketOwned;
    pthread_mutex_t mWriteMutex;
    pid_t mPid; //对等进程ID
    uid_t mUid; //对等用户ID
    gid_t mGid; //对等组ID
    pthread_mutex_t mRefCountMutex; //引用计数(从1开始)
    int mRefCount;
    int mCmdNum;
    bool mUseCmdNum;

public:
    SocketClient(int sock, bool owned);
    SocketClient(int sock, bool owned, bool useCmdNum);
    virtual ~SocketClient();

    int getSocket() { return mSocket; }
    pid_t getPid() const { return mPid; }
    uid_t getUid() const { return mUid; }
    gid_t getGid() const { return mGid; }
    void setCmdNum(int cmdNum) {
        android_atomic_release_store(cmdNum, &mCmdNum);
    }
    int getCmdNum() { return mCmdNum; }
    //发送以空结尾的C字符串:
    int sendMsg(int code, const char *msg, bool addErrno);
    int sendMsg(int code, const char *msg, bool addErrno, bool useCmdNum);
    int sendMsg(const char *msg);
    //提供向客户端发送响应代码的机制。发送代码和空字符。
    int sendCode(int code);
    //提供向客户端发送二进制数据的机制。发送代码和一个空字符,后跟4字节的大端长度和数据。
    int sendBinaryMsg(int code, const void *data, int len);
    //发送二进制数据:
    int sendData(const void *data, int len);
    //iovec内容未通过调用保存
    int sendDatav(struct iovec *iov, int iovcnt);
    //可选引用计数。引用计数从1开始。如果它减到0,它会删除自身。
    //SocketListener创建一个SocketClient(在refcount 1),并在客户端完成后调用decRef()。
    void incRef();
    //在0处返回true(但注意:SocketClient已删除)
    bool decRef(); 
    //为“my arg”传输返回带“\\”和“\”转义的引号的新字符串
    static char *quoteArg(const char *arg);

private:
    void init(int socket, bool owned, bool useCmdNum);
    //正在发送二进制数据。调用者应该确保这一点不受多个线程同时进入的影响。
    //如果成功,则返回0;如果存在0字节写入或发生任何其他错误,则返回-1(使用errno获取错误)
    int sendDataLockedv(struct iovec *iov, int iovcnt);
};

typedef android::sysutils::List<SocketClient *> SocketClientCollection;

我们可以看到该类定义了很对发送data的操作函数 以及一些变量。
该类的构造函数如下:

路径:
LINUX/android/system/core/libsysutils/src/SocketClient.cpp
SocketClient::SocketClient(int socket, bool owned) {
    init(socket, owned, false);
}

SocketClient::SocketClient(int socket, bool owned, bool useCmdNum) {
    init(socket, owned, useCmdNum);
}

根据SocketListener.startListener()中new时带入的参数可知,这里调用的构造函数时带3个参数的。
socket=mSock
owned= false
useCmdNum=mUseCmdNum=false

构造函数调用的SocketClient::init(),函数内容如下:

路径:LINUX/android/system/core/libsysutils/src/SocketClient.cpp
void SocketClient::init(int socket, bool owned, bool useCmdNum) {
  mSocket = socket;
  mSocketOwned = owned;
  mUseCmdNum = useCmdNum;
  pthread_mutex_init(&mWriteMutex, NULL);
  pthread_mutex_init(&mRefCountMutex, NULL);
  mPid = -1;
  mUid = -1;
  mGid = -1;
  mRefCount = 1;
  mCmdNum = 0;

  struct ucred creds;
  socklen_t szCreds = sizeof(creds);
  memset(&creds, 0, szCreds);

  int err = getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
  if (err == 0) {
    mPid = creds.pid;
    mUid = creds.uid;
    mGid = creds.gid;
  }
}

第3行:mSocket =socket = NetlinkManager::mSock;
第4行:owned= false
第5行:useCmdNum=mUseCmdNum=false
第6行:初始化mWriteMutex,线程写锁;
第7行:初始化mRefCountMutex,线程计数锁;
第8~12行,初始化相关变量值。
第14行:定义一个 struct ucred 结构体变量 creds;
结构体如下:

路径: usr/include/x86_64-linux-gnu/bits/socket.h
struct ucred
{
  pid_t pid;			/* PID of sending process.  */
  uid_t uid;			/* UID of sending process.  */
  gid_t gid;			/* GID of sending process.  */
};

第15行:定义一个socklen_t类型的 szCreds,值为sizeof(creds);socklen_t的类型是unsigned int;pid_t的类型是int;uid_t的类型是unsigned int;unsigned int的类型是int;所以szCreds的值为12;
第16行:creds清0;
第18行:获取socket的描述符引用的套接字的选项;

函数原型:
int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
参数optval和optlen用于访问setsockopt()的选项值。对于getsockopt(),它们标识一个缓冲区,请求的选项的值将在该缓冲区中返回。optlen是一个值结果参数,最初包含optval指向的缓冲区的大小,并在返回时进行修改以指示返回值的实际大小。如果未提供或返回任何选项值,optval可能为NULL。
Optname和任何指定的选项都会无解释地传递给相应的协议模块进行解释。
参数level:在6.1节有解释,在这个环境下固定使用SOL_SOCKET;
参数optval:也是在6.1节有解释,SO_PEERCRED仅适用于由sockpair创建的AF_UNIX流套接字或AF_UNIX流/数据报套接字。
返回值:成功后,标准选项返回0。出错时,返回-1,并适当设置errno。

第19~22行:由前面函数的分析可知,getsockopt()获取到对应的pid、uid、gid,并提取赋值给到SocketClient类的成员变量中。

6.10 SocketListener::threadStart()

前面讲到NetlinkManager::start() 调用了NetlinkHandler::start(),进一步调用了SocketListener::startListener();在startListener中又调用了pthread_create()函数创建线程SocketListener::threadStart();
前面对NetlinkManager的前期准备工作基本都做完了,剩下的就是具体的监听Kernel的uevent的事了。
我们接着看。
SocketListener::threadStart()函数内容如下:

路径:LINUX/android/system/core/libsysutils/src/SocketListener.cpp
void *SocketListener::threadStart(void *obj) {
    SocketListener *me = reinterpret_cast<SocketListener *>(obj);
    me->runListener();
    pthread_exit(NULL);
    return NULL;
}

第3行:SocketListener::startListener()调用创建这个线程是传入的参数this,表示的是SocketListener这个类对象。
第4行:调用的SocketListener::runListener()方法;
第5行:线程退出。

6.11 SocketListener::runListener()
接着继续看runListener函数的内容,这个函数有点长,不过没关系,我们来一一分析。这个函数的主要内容是一直循环监测所有的SocketClient;

路径:LINUX/android/system/core/libsysutils/src/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) {
			//注意:在保持mClientsLock的情况下调用其他对象(安全)
			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)) {
			int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
			if (c < 0) {
				SLOGE("accept failed (%s)", strerror(errno));
				sleep(1);
				continue;
			}
			pthread_mutex_lock(&mClientsLock);
			mClients->push_back(new SocketClient(c, true, mUseCmdNum));
			pthread_mutex_unlock(&mClientsLock);
		}

		/*首先将所有活动客户端添加到挂起列表*/
		pendingList.clear();
		pthread_mutex_lock(&mClientsLock);
		for (it = mClients->begin(); it != mClients->end(); ++it) {
			SocketClient* c = *it;
			//注意:在保持mClientsLock的情况下调用其他对象(安全)
			int fd = c->getSocket();
			if (FD_ISSET(fd, &read_fds)) {
				pendingList.push_back(c);
				c->incRef();
			}
		}
		pthread_mutex_unlock(&mClientsLock);

		/*处理挂起的列表,因为它属于线程,所以不需要锁定它*/
		while (!pendingList.empty()) {
			/*从列表中弹出第一项*/
			it = pendingList.begin();
			SocketClient* c = *it;
			pendingList.erase(it);
			/*处理它,如果返回false,则从列表中删除*/
			if (!onDataAvailable(c)) {      //将收到信息的SocketClient对象指针传递到了onDataAvailable函数中
				release(c, false);
			}
			c->decRef();
		}
	}
}

第3行:由6.6节分析知,SocketClientCollection是一个以SocketClient为成员节点的list。
第6行:以LIST类里的_ListIterator子类定义变量it;
所以it是一个_ListIterator类,类定义在LINUX/android/system/core/libsysutils/include/sysutils/List.h中。
第7行:定义fd_set类型的变量 read_fds。fd_setfd_set用于select和pselect。定义在“usr/include/x86_64-linux-gnu/sys/select.h”; fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。
第10行:将read_fds清零使集合中不含任何fd(描述符/句柄);
第12行:在6.5节介绍SocketListener的构造函数时解释了mListen=false;所以第10、11行不会执行到;
第17行:在6.8节定义中知道mCtrlPipe[0]表示管道的读取端、mCtrlPipe[1]表示管道的写入端。这里是将mCtrlPipe[0]读取端管道加入到read_fds集合。
第18~19行:有前面几句分析可知mCtrlPipe[0] > max。所以会执行第15行。
第21行:使用mClientsLock锁,在6.5分析SocketListener构造函数时执行了mClientsLock的初始化;
第22~29行:遍历mClients这个List,获取对应节点后将其fd描述符 加入到read_fds集合;并将最大的fd描述符赋值给max变量。
//注意:在保持mClientsLock的情况下调用其他对象(安全)
第30行:释放mClientsLock锁。
第32行:首先先来看看select()这个函数的定义与解释;

函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
参数解释:
nfds:是⼀个整数值,是指集合中所有⽂件描述符的范围,即所有⽂件描述符的最⼤值加1,不能错。所以我们看到前面为什么要获取max这个值了。
readfds:是指向 fd_set 结构的指针,这个集合中包括⽂件描述符,是要监视这些⽂件描述符的读变化的,即关⼼是否可以从这些⽂件中读取数据。所以看到前面把mCtrlPipe[0]读取端管道和mClients的描述符都放进这个read_fds中。如果这个集合中有⼀个⽂件可读,select就会返回⼀个⼤于0的值,表⽰有⽂件可读(前提没有超时); 如果没有可读的⽂件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发⽣错误返回负值。 可以传⼊NULL值,表⽰不关⼼任何⽂件的读变化。
writefds:是指向 fd_set 结构的指针,这个集合中包括⽂件描述符,是要监视这些⽂件描述符的写变化的,即关⼼是否可以向这些⽂件中写⼊数据。 如果这个集合中有⼀个⽂件可写,select就会返回⼀个⼤于0的值,表⽰有⽂件可写; 如果没有可写的⽂件,则根据timeout再判断是否超时,若超出timeout的时间,select返回0,若发⽣错误返回负值。可以传⼊NULL值,表⽰不关⼼任何⽂件的写变化。
exceptfds:⽤来监视⽂件错误异常。这个通常是:OOB同步标记未处理。

timeout:是select的超时时间,这个参数⾄关重要,它可以使select处于三种状态。
状态1:若将NULL以形参传⼊,即不传⼊时间结构,就是将select置于阻塞状态,⼀定等到监视⽂件描述符集合中某个⽂件描述符发 ⽣变化为⽌;
状态2:若将时间值设为00毫秒,就变成⼀个纯粹的⾮阻塞函数,不管⽂件描述符是否有变化,都⽴刻返回继续执⾏,⽂件⽆变化 返回0,有变化返回⼀个正值;
状态3:timeout的值⼤于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在 超时后不管怎样⼀定返回,若超出timeout的时间,select返回0,若发⽣错误返回负值。
struct timeval结构体定义如下:
struct timeval{
long tv_sec;   /*秒 */
long tv_usec;  /*微秒 */
}
所以我们可以知道第27行是以阻塞的形式监控read_fds存放的所有mCtrlPipe[0]读取端管道和mClients的描述符是否发生变化的。

第33~37行:如果监控的描述符发生错误返回负值,则进入这部分执行,主要是输出Log。
第38行:select()超时返回0,但由于select()的参数是NULL,表示阻塞,所以不会有超时的情况;第39行也就执行不到。
第41行:检测mCtrlPipe[0]读取端管道是否在read_fds集合中,不在则返回0。前面我们分析知道,mCtrlPipe[0]读取端管道是加入到这个集合中的。所以会执行第42~47行。
第42行:定义一个char类的变量c,并赋值CtrlPipe_Shutdown,CtrlPipe_Shutdown为0。
第43行:先分析read()函数,如下

函数原型:ssize_t read(int fd, void *buf, size_t count)
参数解析:
fd:文件描述符,用来指向要操作的文件的文件结构体
    buf:一块内存空间
    count:希望读取的字节数
由此可知,这里的read是读取mCtrlPipe[0]管道中的1字节,并放到c中;
TEMP_FAILURE_RETRY()又是什么呢?
TEMP_FAILURE_RETRY()是个宏定义,内容如下:
路径:usr/include/unistd.h
# define TEMP_FAILURE_RETRY(expression) \
  (__extension__							      \
    ({ long int __result;						      \
       do __result = (long int) (expression);				      \
       while (__result == -1L && errno == EINTR);			      \
       __result; }))
该宏的作用是如果返回指定错误,那么进行重试;可以看到如果表达式返回错误是EINTR,那么就进行重试,其中 :
#define    EINTR    4    /* Interrupted system call */
错误4代表什么情景可以发生呢?
如果一个进程在一个慢系统调用中阻塞时,当进程捕获到某个信号且相应从信号 处理函数返回时,这个系统调用被中断,然后返回错误,并且设置errno为EINTR。
什么是为慢系统调用呢?
  (1) 读写一个设备时,当缓存满时,需要阻塞等待资源,或者其他原因需要阻塞.
   (2) 访问一些互斥资源时,当前条件不满足时
   (3) waitpid, accept(阻塞),ioctl等等,只要引起阻塞的系统调用,那么就可以称为慢系统调用.
而上面的这个表达式就是为了解决EINTR返回而设计的,但也有其局限性,有些调用不能简单的重新调用来避免这个问题.
 比如connect函数返回一个EINTR错误的时候,不能再次调用它,否则会马上返回一个错误,错对这 种的解决方法是用select(connect_fd)来做。
防止EINTR还有一种方法,那就是直接屏蔽此信号,不让系统产生信号中断。

第44行:如果上一句read()函数没有读到数据,那么c依旧=CtrlPipe_Shutdown;
第45行: 这里就直接break。表示mCtrlPipe[0]读管道关闭,就读不了数据了。监听kernel 的线程也就结束了。

第49行:由6.5节的init函数可知,mListen为false,所以这个判断里的内容不会执行。不执行就先不分析了。
第62行:清理用于挂起的LIST,这个在函数开头就定义了这个列表。
第63行: 加锁;
第64-72行:将mClients这里LIST中一个一个取出Socket,并判断该Socket是否在read_fds这个集合中,如果在,则放到pendingList这个LIST中,然后该SocketClient的引用计数+1。
第73行:释放锁;
第76-86行:取出pendingList这个LIST中的一个it,并赋给 c,然后删除it,接着调用onDataAvailable©对c 这个SocketClient进行处理,处理完的SocketClient *c 需要release调,然后SocketClient *c的引用计数-1。
这样则对所有的 SocketClient 都会处理到。具体的处理函数数onDataAvailable(),接着后面看看这个函数。

6.12 NetlinkListener::onDataAvailable()
在6.11节分析中,知道onDataAvailable()函数是在SocketListener 中调用的,也从前面的分析知道 NetlinkListener类继承了SocketListener 类,所以SocketListener 中的虚函数onDataAvailable在NetlinkListener类中实现了方法;所以6.11节中提到的调用的onDataAvailable()函数,实际是NetlinkListener类中的onDataAvailable()函数;因此我们要分析的就是NetlinkListener类中的onDataAvailable()函数。
看看该函数的内容如下:

路径:LINUX/android/system/core/libsysutils/src/NetlinkListener.cpp
bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
	int socket = cli->getSocket();
	ssize_t count;
	uid_t uid = -1;

	bool require_group = true;
	if (mFormat == NETLINK_FORMAT_BINARY_UNICAST) {
		require_group = false;
	}
	//根据socket通过uevent_kernel_recv获取底层Uevent事件,并将其存储到mBuffer中
	//socket创建的时候就确认了有哪些uevent事件需要关注
	count = TEMP_FAILURE_RETRY(uevent_kernel_recv(socket,
	mBuffer, sizeof(mBuffer), require_group, &uid));
	if (count < 0) {
		SLOGE("recvmsg failed (%s)", strerror(errno));
		return false;
	}

	NetlinkEvent *evt = new NetlinkEvent();
	if (evt->decode(mBuffer, count, mFormat)) {
		onEvent(evt);   //将消息传递到onEvent函数
	} else if (mFormat != NETLINK_FORMAT_BINARY) {
		//如果parseBinaryNetlinkMessage返回false,不要抱怨。这可能意味着缓冲区中没有我们感兴趣的消息。
		SLOGE("Error decoding NetlinkEvent");
	}

	delete evt;
	return true;
}

第4行:获取SocketClient的Socket套节字;
第5-8行:定义一些变量;
第9行:判断格式是否是 网络链接格式二进制单播 ;是则require_group=false,非组播的意思;
第14行: TEMP_FAILURE_RETRY 是一个宏,用于忽略系统中断造成的错误。常用于系统调用。具体内容如下:

/*对EXPRESSION求值,并重复,只要它返回-1并带有“errno”设置为EINTR*/
# define TEMP_FAILURE_RETRY(expression) \
(__extension__ \
({ long int __result; \
do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \
__result; }))

uevent_kernel_recv()函数主要是用来接收kernel的uevent,属于系统调用的一个接口。看看具体内容。

路径:LINUX/android/system/core/libcutils/uevent.c
ssize_t uevent_kernel_recv(int socket, void *buffer, size_t length, bool require_group, uid_t *uid)
{
    struct iovec iov = { buffer, length };
    struct sockaddr_nl addr;
    char control[CMSG_SPACE(sizeof(struct ucred))];
    struct msghdr hdr = {
        &addr,
        sizeof(addr),
        &iov,
        1,
        control,
        sizeof(control),
        0,
    };

    *uid = -1;
    ssize_t n = recvmsg(socket, &hdr, 0);
    if (n <= 0) {
        return n;
    }

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
    if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
        /*忽略没有发件人凭据的netlink消息*/
        goto out;
    }

    struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg);
    *uid = cred->uid;
    if (cred->uid != 0) {
        /*忽略来自非root用户的netlink消息*/
        goto out;
    }

    if (addr.nl_pid != 0) {
        /* ignore non-kernel */
        /*忽略非内核*/
        goto out;
    }
    if (require_group && addr.nl_groups == 0) {
        /*请求时忽略单播消息*/
        goto out;
    }

    return n;

out:
    /*清除残留的潜在恶意数据*/
    bzero(buffer, length);
    errno = EIO;
    return -1;
}

从该函数看主要是调用 recvmsg()函数 读取对应socket数据。放到了struct msghdr hdr 这个结构体中,主要的数据是放在 struct iovec iov 中,而 struct iovec iov 中主要存放数据的则是 uevent_kernel_recv()函数的参数void *buffer。所以在 onDataAvailable()函数中调用uevent_kernel_recv()时传入的mBuffer。
针对recvmsg()函数 具体的通信,这里不再深入分析,后续有时间在详细分析 这一部分内容。

这里我们值要知道 是去读取kernel的数据即可。

第16行:在第14行读取错误返回负数进入判断,输出错误Log,并返回false。
第21行:new一个 NetlinkEvent类;
NetlinkEvent类的内容如下:

路径:LINUX/android/system/core/libsysutils/include/sysutils/NetlinkEvent.h
class NetlinkEvent {
public:
    enum class Action {
        kUnknown = 0,
        kAdd = 1,
        kRemove = 2,
        kChange = 3,
        kLinkUp = 4,
        kLinkDown = 5,
        kAddressUpdated = 6,
        kAddressRemoved = 7,
        kRdnss = 8,
        kRouteUpdated = 9,
        kRouteRemoved = 10,
    };

private:
    int  mSeq;
    char *mPath;
    Action mAction;
    char *mSubsystem;
    char *mParams[NL_PARAMS_MAX];

public:
    NetlinkEvent();
    virtual ~NetlinkEvent();

    bool decode(char *buffer, int size, int format = NetlinkListener::NETLINK_FORMAT_ASCII);
    const char *findParam(const char *paramName);

    const char *getSubsystem() { return mSubsystem; }
    Action getAction() { return mAction; }

    void dump();

 protected:
    bool parseBinaryNetlinkMessage(char *buffer, int size);
    bool parseAsciiNetlinkMessage(char *buffer, int size);
    bool parseIfInfoMessage(const struct nlmsghdr *nh);
    bool parseIfAddrMessage(const struct nlmsghdr *nh);
    bool parseUlogPacketMessage(const struct nlmsghdr *nh);
    bool parseNfPacketMessage(struct nlmsghdr *nh);
    bool parseRtMessage(const struct nlmsghdr *nh);
    bool parseNdUserOptMessage(const struct nlmsghdr *nh);
};

从该类的定义大致可看出, NetlinkEvent类 主要是用来解析 前面uevent_kernel_recv()接收到的uevent 的。具体处理接收的数据相关的函数,后面用到时再解释。
第22行:调用NetlinkEvent类的decode()方法,对接收数据进行解析;解析下一节单独解释;
第23行:解析完后调用onEvent()进行对解析后的uevent进行处理。
第24-27行:判断编码格式数据log;
第29-30行:最后释放evt后返回。

6.13 NetlinkEvent::decode()
在6.12节中newl 一个NetlinkEvent类;在前面已列出来。看看new后执行的构造函数,函数如下:

路径:LINUX/android/system/core/libsysutils/src/NetlinkEvent.cpp
NetlinkEvent::NetlinkEvent() {
  mAction = Action::kUnknown;
  memset(mParams, 0, sizeof(mParams));
  mPath = NULL;
  mSubsystem = NULL;
}

第3行:mAction 是用来标记将uevent的Action 的,初始设为kUnknown;
第4行:数组mParams清0;
第5-6行:清空指针。

看完NetlinkEvent类的初始化后,接着具体看一下decode()方法;

路径:LINUX/android/system/core/libsysutils/src/NetlinkEvent.cpp
bool NetlinkEvent::decode(char *buffer, int size, int format) {
    if (format == NetlinkListener::NETLINK_FORMAT_BINARY
            || format == NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST) {
        return parseBinaryNetlinkMessage(buffer, size);
    } else {
        return parseAsciiNetlinkMessage(buffer, size);
    }
}

可已看到,根据不同的编码格式,解析也分两种;在6.3节,我们可知编码格式是 mFormat = NETLINK_FORMAT_ASCII;所以我们这里主要分析parseAsciiNetlinkMessage()这个解析函数。

这里对源码填加了Log进行测试验证,内容如下:

bool NetlinkEvent::decode(char *buffer, int size, int format) {
    if (format == NetlinkListener::NETLINK_FORMAT_BINARY
            || format == NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST) {
				SLOGE("[watr] enter parseBinaryNetlinkMessage \n");
        return parseBinaryNetlinkMessage(buffer, size);
    } else {
			SLOGE("[watr] enter parseAsciiNetlinkMessage \n");
        return parseAsciiNetlinkMessage(buffer, size);
    }
}

输出Log如下:
在这里插入图片描述
有此可确认采用的是ascii 编码,跟前面分析的编码类型一致。所以调用的解析函数也就是parseAsciiNetlinkMessage()了。

接着看看parseAsciiNetlinkMessage()函数的内容。

路径:LINUX/android/system/core/libsysutils/src/NetlinkEvent.cpp
/* 分析NETLINK_KOBJECT_UEVENT网络链接套接字中的ASCII格式消息。*/
bool NetlinkEvent::parseAsciiNetlinkMessage(char *buffer, int size) {
  const char *s = buffer;
  const char *end;
  int param_idx = 0;
  int first = 1;

  if (size == 0)
    return false;

  /*确保缓冲区以零结尾,下面的代码取决于此*/
  buffer[size-1] = '\0';

  end = s + size;
  while (s < end) {
    if (first) {
      const char *p;
      /*缓冲区以0结尾,无需检查p<end*/
      for (p = s; *p != '@'; p++) {
        if (!*p) { /*没有“@”,不应该发生*/
          return false;
        }
      }
      mPath = strdup(p+1);//拷贝字符串 p+1 到mPath中,会申请内存,字符串跳过@
      first = 0; //取得一个字串后跑else部分
    } else {
      const char* a;
      if ((a = HAS_CONST_PREFIX(s, end, "ACTION=")) != NULL) { //检查以指定的字串开头
        if (!strcmp(a, "add"))      //比较字串是不是 add
          mAction = Action::kAdd; //设置动作 kAdd
        else if (!strcmp(a, "remove"))//比较字串是不是 remove
          mAction = Action::kRemove;//设置动作 kRemove
        else if (!strcmp(a, "change"))//比较字串是不是 change
          mAction = Action::kChange;//设置动作 kChange
      } else if ((a = HAS_CONST_PREFIX(s, end, "SEQNUM=")) != NULL) {
         mSeq = atoi(a);
      } else if ((a = HAS_CONST_PREFIX(s, end, "SUBSYSTEM=")) != NULL) {
        mSubsystem = strdup(a);
      } else if (param_idx < NL_PARAMS_MAX) {
        mParams[param_idx++] = strdup(s);
      }
    }
    s += strlen(s) + 1;
  }
  return true;
}

第4行:定义一个const char *s 用来指向kernel上报的event buffer;
第6行:定义一个param_idx用以计数;
第9行:判断上报的数据长度是否=0,是则立即返回false;
第13行:给接收的数据字符串添加结束符;
第15行:end是字符串结束地址,由起始地址s+数据长度size;
第17~26行:判断buffer中是否存在字符@,不存在则返回false;存在,则拷贝字符串 p+1 到mPath中,会申请内存,字符串跳过@;并且将first清0;之后第16行的while循环就会进入第27行的else部分了;
第27行:检查以指定的字串"ACTION="开头,存在则a不为NULL,进入第30~35行的判断;
第30行:比较第29行返回的地址之后是否存在“add”,是则设置mAction = Action::kAdd;
第32行:比较第29行返回的地址之后是否存在“remove”,是则设置mAction = Action::kRemove;
第34行:比较第29行返回的地址之后是否存在“change”,是则设置mAction = Action::kChange;
第36行:检查以指定的字串"SEQNUM="开头,存在则a不为NULL
第37行: 字符转数字,SEQNUM数字。
第38行:检查以指定的字串"SUBSYSTEM="开头,存在则a不为NULL
第39行: 截取a之后的字符串给到mSubsystem。
第40~41:判断获取的属性个数数否超阀值,并将获取的字符串放到参数列表里。
第44行:跳转到下一个属性地址。
第46行,解析完成后返回true。
由6.13节分析可知,当正常接收到uevent的属性数据后,

NetlinkListener::onDataAvailable()中调用的evt->decode(mBuffer, count, mFormat)会返回true,因此调用onEvent(evt); 将消息传递到onEvent函数。
此时的onEvent函数是“LINUX/android/system/vold/NetlinkHandler.cpp”文件中的void NetlinkHandler::onEvent(NetlinkEvent *evt)函数,而传入的参数正是NetlinkListener::onDataAvailable()中new的NetlinkEvent()对象的 evt。

6.14 NetlinkHandler::onEvent()
onEvent()函数是用来处理 NetlinkListener类处理uevent数据后的数据的。接下来看下该函数的内容。

路径:LINUX/android/system/vold/NetlinkHandler.cpp
// 获取到 kernel 事件 后调用这个函数进行处理
void NetlinkHandler::onEvent(NetlinkEvent *evt) {
    VolumeManager *vm = VolumeManager::Instance(); //获取一个VolumeManager 对象
    const char *subsys = evt->getSubsystem(); //获取子系统

    if (!subsys) {  //无子系统
        SLOGW("No subsystem found in netlink event");
        return;
    }

    if (!strcmp(subsys, "block")) { //判断是块设备子系统
        vm->handleBlockEvent(evt); //执行 VolumeManager 中的块设备event
    }
}

以U盘插入为例,U盘为存储设备,在kernel中是分类为块设备的。所以当U盘插入后,在onEvent()中判断为block ,所以会执行vm->handleBlockEvent(evt)。

至此vold获取kernel的uevent数据接收部分已分析完,后续就是VolumeManager对uevent的处理了。

通过VolumeManager::handleBlockEvent()函数运行到VolumeManager类中。

VolumeManager类将在下一篇讲解。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

waterfxw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值