之前看adb_main的loop循环,总是很迷糊,后来发现,这货得先从其全局变量看起。。。。。
几个结构体下来,瞬间感觉豁然开朗可有木有。。。。
1.首先是结构体FH及其相关全局变量_win32_fhs
static FHRec _win32_fhs[ WIN32_MAX_FHS ]; //这个表是用来记录系统所有的FH结构。
static int _win32_fh_count; //记录此表中已有fh的数目。
typedef struct FHRec_
{
FHClass clazz; //这货记录了此FH具体的操作函数
int used; //此FH是否已使用
int eof;
union { //FH的具体内容,可以使一个句柄,一个socket,或者一个socketpair
HANDLE handle;
SOCKET socket;
SocketPair pair;
} u;
HANDLE event; //读还是写事件
int mask;
char name[32]; //用来唯一匹配fh的
} FHRec;
其中:
typedef const struct FHClassRec_* FHClass;
typedef struct FHClassRec_
{
void (*_fh_init) ( FH f );
int (*_fh_close)( FH f );
int (*_fh_lseek)( FH f, int pos, int origin );
int (*_fh_read) ( FH f, void* buf, int len );
int (*_fh_write)( FH f, const void* buf, int len );
void (*_fh_hook) ( FH f, int events, EventHook hook );
} FHClassRec;
FH可以说代表的是一种抽象的数据通信句柄,在程序中的一个socket,一个文件句柄,一个SocketPair都统一用一个FH结构体来表示,对三种数据的接口函数也是相同的,但调用时具体调用的是哪个函数,是通过FH的分配函数_fh_alloc中通过clazz设定的。
Adb在全局变量中针对socket,handle和socketpair初始化了三个接口的实例,_fh_file_class,_fh_socket_class,_fh_socket_pair_class如图
这里以socket为例,如果要将一个socket 转换为FH,其大体步骤如下:
FH f = _fh_alloc( &_fh_socket_class );
SOCKET s;
bind(s)
listen(s, 1);
return _fh_to_int(f);
先看一下FH的空间分配函数_fh_alloc,此函数的主要操作可以简写为:
static FH _fh_alloc( FHClass clazz )
{
FH f = &_win32_fhs[ _win32_fh_count++ ]; //在全局_win32_fhs数组中的末尾为FH指定空间
f->clazz = clazz; //设置此FH具体操作函数
clazz->_fh_init(f); //调用初始化函数,这里调用的就是socket的初始化函数_fh_socket_init
return f;
}
再看_fh_to_int函数,此函数主要是将fh指针转换为一个整数fd,
_fh_to_int( FH f )
{
if (f && f->used && f >= _win32_fhs && f < _win32_fhs + WIN32_MAX_FHS)
return (int)(f - _win32_fhs) + WIN32_FH_BASE;
return -1;
}
对应还有一个_fh_from_int,此函数就是将一个整数转换为对应的fd指针,二者就跟句柄和指针的关系一样。
好了,综上所述,我们可知,一个FH内部其实是封装了一个socket或一个handle或一个socketpair,然后通过fh->clazz对应的init,read,write等函数可以对这三者进行操作(比如说如果一个socket,调用read函数最终调用的是_fh_socket_read,一个handle最终调用的是_fh_file_read函数)。然后fh还被封装成了一个句柄,叫fd,一个fd对应一个fh。
好吧。。。到这里我想说,后面还有好几个这样的东西呢,为嘛这么麻烦,因为最终一个消息循环fdevent_loop处理了所有的消息啊 ,各种回调,各种参数,各种读写函数都不一样,不这么封装没法实现啊。
2.接着是结构体fdevent及其相关全局变量fd_table:
static fdevent **fd_table = 0; //这个表示用来记录fdevent结构的指针
static int fd_table_max = 0; //表中元素个数
struct fdevent
{
fdevent *next; //这货用来连自身的,这里先不用管
fdevent *prev;
int fd; //对应fd
int force_eof;
unsigned short state;
unsigned short events;
fd_func func; //回调函数
void *arg; //回调函数参数
};
前面墨迹一大堆,主要是想说这种封装的思想,后面用一句话解释fdevent,就是说fh封装了socket,handle,socketpair,提供了其读写函数,但异步通信中这三者都是需要有回调的,fdevent封装了fh,主要是让一个fh对应上一个回调函数,和相应的参数,为嘛回调函数和参数不卸载fh里面?因为不同socket,handle可能对应不同回调,没法封装在fh里面,具体的实现细节就写来浪费大家眼神了。
Fdevent有一个重要函数叫fdevent_register(fdevent *fde),此函数在fd_table中记录此fde的指针,程序中的所有fdevent结构都存在全局变量_fd_table中。
下面这肯定是目前最后一个结构体了。。。。
3. 结构体EventHook及其相关全局变量win32_looper:
typedef struct EventHookRec_* EventHook;
static EventLooperRec win32_looper; //最终的事件检测,检测的就是这个表
typedef struct EventHookRec_
{
EventHook next;
FH fh; //socket有关的
HANDLE h; //异步通信中与socket绑定的触发事件的句柄
int wanted; /* wanted event flags */
int ready; /* ready event flags */
void* aux;
void (*prepare)( EventHook hook );
int (*start) ( EventHook hook );
void (*stop) ( EventHook hook );
int (*check) ( EventHook hook );
int (*peek) ( EventHook hook );
} EventHookRec;
typedef struct EventHookRec_* EventHook;
typedef struct EventLooperRec_
{
EventHook hooks;
HANDLE htab[ MAX_LOOPER_HANDLES ]; //这货是个句柄表,记录每个hook对应fde里面的一个句柄
int htab_count; //如果没有事件发生,就通过这个句柄表,等待相应事件
} EventLooperRec;
好吧,这个结构体有点长,还是一句话简言之,一个EventHook封装了一个fh的状态监测函数,和fh的状态,最终也是存在一个表中,这个表就是win32_looper。还是同样的问题,为嘛这么封装,如果说一个fh封装的是三种不同的句柄(socket,handle,socketpair),一个fdevent对每一种类型的fh设置回调,那么一个EventHook就是制定某一个特定的fh目前的状态怎么样了,是否ready,比如后面listen后accept的socket,这些socket有同样的回调函数,但具体是哪个socket事件触发了,就需要调用EventHook中的函数来检测了。
好吧 接下来进入正题,消息循环:
首先是一个fh怎么进入消息循环链表win32_looper的,看这个函数
void fdevent_install( //此函数将一个fd指定的fh记录到win32_looper和fd_table中
fdevent *fde, //当前fde的指针
int fd, //fh对应的fd
fd_func func, //fde的回调函数
void *arg //回调函数的参数
)
{
memset(fde, 0, sizeof(fdevent)); //初始化fde结构
fde->state = FDE_ACTIVE;
fde->fd = fd;
fde->func = func;
fde->arg = arg;
fdevent_register(fde); //在fd_table中记录fde的指针
fdevent_connect(fde); //在win_looper.hook链表中为fd创建一个hook
fde->state |= FDE_ACTIVE;
}
此函数输入的fde指针指向的应该是fd_table中的一个位置,然后fd指定是对哪个fh的注册,func,arg是回调函数和参数。此函数通过fdevent_register函数在fd_table中注册fde和对应回调,参数,通过fdevent_connect函数在win_looper.hook链表中为fd创建一个hook(这个名字起得太怪异了吧。。。。)。ok,注册完了之后前面提到的fdevent_loop()循环中就能扫到此fh的状态了,如果ready,就可以调用相应的回调函数并传入对应参数了。
其实这里才是正题。。。。。消息循环的主要代码:
void fdevent_loop()
{
fdevent *fde;
for(;;) {
fdevent_process();
while((fde = fdevent_plist_dequeue()))
{
unsigned events = fde->events;
fde->func(fde->fd, events, fde->arg);
}
}
Fdevent_process这货比较长,大概描述一下吧
1) 循环所有looper_hook[i](后面称为hook),并调用其函数hook.prepare,查看是否有事件出发了,如果有则把所有这样的hook加入list_pending列表,退出。这个list_pending是fdevent_process已经发现的,有消息需要处理的fh所在的列表。
2) 如果一个hook也没有加入到list_pending列表,则循环所有looper_hook[i](后面称为hook),把所有hook.h有效地(就是说等待的事件句柄有效地)加入到win32_loop.htab_count表中,然后调用WaitForMultipleObjects等待其中的一个事件触发。这个htab_cout记录的是,程序中所有有等待句柄的fh的句柄表。
3) 有事件触发后,再执行第一步,循环列表找到所有prepare成功的,加入list_pending列表中
4) 最终清空htab_cout,返回
再简言之,fdevent_process就是去找那些fh有事件需要处理,只要有一个fh有事件需要处理就返回,否则就一直等到有一个fh有事件,此时再扫一遍,看看哪些fh需要处理(因为可能等待到了多个需要处理的fh),然后返回。
之后就比较好理解了,while循环处理所有的可处理消息,fdevent_plist_dequeue是从list_pending中取出一个消息,然后内部调用其回调函数fde->func,传入fd,传入event(read/write),传入参数。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课