Bind整体框架
bind是分单线程和多线程的方式
主要是从setup函数入手,要是定义了使用多线程的话,变量named_g_cpus等于cpu的个数,变量named_g_udpdisp(分发线程的个数)等于CPU个数减1(这个就是udp listener对个数),否则俩者都是1;调用isc_taskmgr_create创建一个task的管理器,初始化好该manger的一些变量:如果工作队列还是用多线程的方式的话,那就先申请named_g_cpus个线程,接下来初始化三个条件变量,初始化队列包括优先级队列普通队列。
多线程:
多线程是通过锁来获取ready_tasks队列中的task, 通过条件变量来实现整体的同步控制,比如ready_task中没有task了,那线程就会睡眠
开始创建named_g_cpus个线程,每个线程跑的是run函数,run函数它的作用就是分发,执行回调,它是一个大的while循环,只要manger中有task那么就一直在循环内,如果manger中的ready_task队列为空的话或者有俩个特殊的条件变量为true那多个线程就睡眠在条件变量work_available上。否则,每个线程会从ready_task中选择一个task循环的执行task中的event的回调,在循环task期间,一旦发现task中的事件为空或者执行的事件的数目超过一定的数值以后,就跳出task的循环,如果一个task中没有事件了那就释放 task的内存,然后会有俩个条件变量的处理,最后如果task还要事件未处理完的话就再次放入到ready_task队列中。然后再次返回到上面的while循环中
接下来调用isc_socketmgr_create2创建和socket有关的信息,同样是创建一个manger,同时配置它的一些参数,创建一对pipe,并且将pipe[0]添加到epoll中,监听它的读。然后创建一个watcher线程,该线程主要执行watcher函数,该函数进入死循环调用epoll_wait进行监听,如果有事件发生调用process_fds进行处理,该函数的作用是:如果不是pipe[0]的话,那就调用process_fd进行处理,该函数通过fds取出对应的socket信息,如果该socket为空,那就调用epoll_ctrl添加该fd进去,如果不为空,那如果是可读的话,就调用dispatch_recv(sock),如果可写的话就调用dispatch_send
如果是pipe[0]来信息的话,那就调用process_ctlfd,该函数的作用也是针对一个fd的增删
针对dispatch_recv的说明:
总共会有四种类型的事件,是从recv_list,send_list,accept_list,等拿下来的,要想为某个fd注册事件,方法就是从对应的事件中取出task,然后把socket信息封装进去,再下发事件到task。dispatch_recv就是从recv_list上拿下来的,它会封装事件的action为internal_recv.并且把该事件添加到task中,如果task为idle的话还会把task添加到ready_task中,这样的话阻塞的run线程发现task不为空了就会开始跑了。
针对internal_recv的说明:
该函数就是run线程跑的函数,run线程调用事件的action,
多个线程,通过竞争socket的锁,也就是说最终只有一个线程可以进入循环,调用recvmsg函数,它的流程是一次性的处理recv_list中的事件,调用doio_recv即recvmsg函数尽可能多的接收udp请求,并且取出事件的task,然后调用send_recvdone_event把该事件从task中移除,然后还要求对task进行一次处理,在处理完recv_list中的事件以后,如果发现recv_list事件不为空的话,则调用select_poke,它会通知watcher线程把fd再次添加到epoll中,触发处理。
对于其他线程来说,没有获取到锁就继续睡眠
针对多线程的处理,bind没有采用reuseport的方式,但是也通过task实现了
以前的方式是一个地址+端口一个listen task,多个listen on就对应多个listen task,它把udp的数据分发给worker工作线程处理,为了利用CPU缓存,往往socket还会和某个cpu核进行绑定,因此这种方式的弊端在于,某个CPU的使用率很有可能会很高,而其他的负载很低,因此考虑如果一个地址加端口可以对应多个socket的话,这样他就有多个listen task,那么就可以利用到cpu的多核了
针对udp的处理:
ns_interface_listenudp就是在给定的地址和端口上来监听udp事件的,会调用 ifp->nudpdispatch次(这个次数就是CPU个数-1)dns_dispatch_getudp_dup方法,该函数主要的作用就是调用open_socket方法来打开并且绑定端口,这里使用了useaddr选项,对其他socket采用复制第一个socket的方式,目的是充分利用多核,也就是说,针对同一个addr+port,一般会有CPU/2个udp listener,会为每一个dispatch创建64个task.每一个dispatch应该有不同的fd,然后调用ns_clientmgr_createclients来处理udp端口了,初始化好client的事件,刚开始比较重要的是一个client_start函数,通过isc_task_send放到工作队列中,会有CPU个worker线程睡眠在工作队列上,然后worker同样通过锁的方式从队列中获取task然后执行client_start。相当于有 ifp->nudpdispatch执行client_tart函数,该函数内部就是调用recvmsg,可以保证的是每一个线程可以负载的处理task,因为当一个线程处理的task个数超过一定的值以后,他就会让别的线程来接着处理该task.
当线程通过recvmsg函数接收到数据以后,就会将数据交给线程中的队列执行client_request函数来处理udp请求,如果没有接收到数据,就会把fd放入到watcher线程中监听了,因此当有信息来的时候,他就会调用internal_recv处理了
https://blog.csdn.net/tennysonsky/article/details/44062173