热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析Zebra中的线程机制及其应用

本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。
转自:http://blog.chinaunix.net/uid-20608849-id-2103544.html
------------------------------------------
本文系作者原创,欢迎转载!
转载请注明出处:netwalker.blog.chinaunix.net
------------------------------------------
此文并不针对zebra的应用,甚至不是一个架构的分析,只是对于Zebra的一点儿思考。
Zebra 设计得是如此简洁明快。每一种数据结构均对应于一定的应用,它们之间以一种松耦合的方式共存,而多种数据结构组成的功能模块几乎完美的结合在一起,完成了非常复杂的功能。它的设计思想就在于对C语言面向对象式的应用。
虽然很多程序均借鉴面向对象设计方式,但是Zebra的代码风格是易读的,非常易于理解和学习,与此同时,Zebra使用了丰富的数据结构,比如链表、向量、表和队列等,它的松耦合方式使得每一数据结构封装的功能模块很容易被精简剥离出来,以备我们特殊的应用。这就是我写下Think Of ZEBRA非常重要的原因!
1.ZEBRA中的thread

提起thread就会让人想起线程,Linux中的线程被称为pthread,这里的thread不是pthread,因为它只是对线程的应用层模拟。ZEBRA借助自己的thread结构,将所有的事件(比如文件描述的读写事件,定时事件等)和对应的处理函数封装起来,并取名为struct thread。然后这些threads又被装入不同的“线程“链表挂载到名为thread_master的结构中,这样所有的操作只需要面向thead_master。
  1. /* Thread itself. */
  2. struct thread
  3. {
  4.   unsigned char type;        /* thread type */
  5.   struct thread *next;        /* next pointer of the thread */
  6.   struct thread *prev;        /* previous pointer of the thread */
  7.   struct thread_master *master;    /* pointer to the struct thread_master. */
  8.   int (*func) (struct thread *); /* event function */
  9.   void *arg;            /* event argument */
  10.   union {
  11.     int val;            /* second argument of the event. */
  12.     int fd;            /* file descriptor in case of read/write. */
  13.     struct timeval sands;    /* rest of time sands value. */
  14.   } u;
  15.   RUSAGE_T ru;            /* Indepth usage info. */
  16. };

  17. /* Linked list of thread. */
  18. struct thread_list
  19. {
  20.   struct thread *head;
  21.   struct thread *tail;
  22.   int count;
  23. };

  24. /* Master of the theads. */
  25. struct thread_master
  26. {
  27.   struct thread_list read;
  28.   struct thread_list write;
  29.   struct thread_list timer;
  30.   struct thread_list event;
  31.   struct thread_list ready;
  32.   struct thread_list unuse;
  33.   fd_set readfd;
  34.   fd_set writefd;
  35.   fd_set exceptfd;
  36.   unsigned long alloc;
  37. };

thread_master线程管理者维护了6个“线程“队列:read、write、timer、event、ready和unuse。read队列对应于描述符的读事件,write队列对应于描述符的写事件,timer通常为定时事件,event为自定义事件,这些事件需要我们自己在适合的时候触发,并且这类事件不需要对描述符操作,也不需要延时。ready队列通常只是在内部使用,比如read,write或event队列中因事件触发,就会把该”线程”移入ready队列进行统一处理。unuse是在一个”线程”执行完毕后被移入此队列,并且在需要创建一个新的”线程”时,将从该队列中取壳资源,这样就避免了再次申请内存。只有再取不到的情况下才进行新”线程”的内存申请。

 

1.2 线程管理者中的"线程"链表函数


struct thread_list是一个双向链表,对应的操作有:
//添加thread到指定的链表中的尾部
static void thread_list_add (struct thread_list *list, struct thread *thread);
//添加thread到指定的链表中指定的point前部,它在需要对链表进行排序的时候很有用
static void thread_list_add_before (struct thread_list *list, 
   struct thread *point, 
   struct thread *thread);
//在指定的链表中删除制定的thread
static struct thread *thread_list_delete (struct thread_list *list, struct thread *thread);
//释放指定的链表list中所有的thread, m 中的alloc减去释放的"线程"个数
static void thread_list_free (struct thread_master *m, struct thread_list *list);
//移除list中的第一个thread 并返回
static struct thread *thread_trim_head (struct thread_list *list);

 

1.3 thread中的read队列


考虑这样的应用:创建一个socket,并且需要listen在该socket上,然后读取信息,那么使用read队列是不二选择。下面是一个例子,这个例子将对标准输入文件描述符进行处理:

  1. static int do_accept (struct thread *thread)
  2. {
  3. char buf[1024] = "";
  4. int len = 0;
  5.     
  6. len = read(THREAD_FD(thread), buf, 1024);    
  7. printf("len:%d, %s", len, buf);
  8. return 0;
  9. }

  10. int main()
  11. {
  12.     struct thread thread;

  13.     // 创建线程管理者
  14.     struct thread_master *master = thread_master_create();    

  15.     // 创建读线程,读线程处理的描述符是标准输入0,处理函数为do_accept
  16.     thread_add_read(master, do_accept, NULL, fileno(stdin));
  17.     
  18.     // 打印当前线程管理者中的所有线程
  19.     thread_master_debug(master);
  20.     
  21. // thread_fetch select所有的描述符,一旦侦听的描述符需要处理就将对应的”线程”        的地址通过thread返回
  22.     while(thread_fetch(master, &thread))
  23.     {
  24.      // 执行处理函数
  25.        thread_call(&thread);
  26.        thread_master_debug(master);

  27.       // 这里为什么需要再次添加呢?
  28.       thread_add_read(master, do_accept, NULL, fileno(stdin));
  29.       thread_master_debug(master);
  30.     }    
  31.     
  32.     return 0;
  33. }

 

编译执行,得到如下的结果:
// 这里readlist链表中加入了一个"线程",其他链表为空
-----------
readlist  : count [1] head [0x93241d8] tail [0x93241d8] 
writelist : count [0] head [(nil)] tail [(nil)]
timerlist : count [0] head [(nil)] tail [(nil)]
eventlist : count [0] head [(nil)] tail [(nil)]
unuselist : count [0] head [(nil)] tail [(nil)]
total alloc: [1]
-----------
// 输入hello,回车
Hello

// thread_call调用do_accept进行了操作
len:6, hello

// 发现“线程“被移入了unuselist
-----------
readlist  : count [0] head [(nil)] tail [(nil)]
writelist : count [0] head [(nil)] tail [(nil)]
timerlist : count [0] head [(nil)] tail [(nil)]
eventlist : count [0] head [(nil)] tail [(nil)]
unuselist : count [1] head [0x93241d8] tail [0x93241d8]
total alloc: [1]
-----------

//再次调用thread_add_read发现unuselist被清空,并且”线程“再次加入readlist
-----------
readlist  : count [1] head [0x93241d8] tail [0x93241d8]
writelist : count [0] head [(nil)] tail [(nil)]
timerlist : count [0] head [(nil)] tail [(nil)]
eventlist : count [0] head [(nil)] tail [(nil)]
unuselist : count [0] head [(nil)] tail [(nil)]
total alloc: [1]
-----------

1.4 thread_fetch 和thread_process_fd


顾名思义,thread_fetch是用来获取需要执行的线程的,它是整个程序的核心。这里需要对它进行重点的分析。

  1. struct thread *thread_fetch(struct thread_master *m, struct thread *fetch)
  2. {
  3.   int num;
  4.   int ready;
  5.   struct thread *thread;
  6.   fd_set readfd;
  7.   fd_set writefd;
  8.   fd_set exceptfd;
  9.   struct timeval timer_now;
  10.   struct timeval timer_val;
  11.   struct timeval *timer_wait;
  12.   struct timeval timer_nowait;

  13.   timer_nowait.tv_sec = 0;
  14.   timer_nowait.tv_usec = 0;

  15.   while(1)
  16.   {
  17.     /* 最先处理event队列 */
  18.     if((thread = thread_trim_head(&m->event)) != NULL)
  19.             return thread_run(m, thread, fetch);

  20.     /* 接着处理timer队列 */
  21.     gettimeofday(&timer_now, NULL);
  22.     for(thread = m->timer.head; thread; thread = thread->next)
  23.     { 
  24.        /* 所有到时间的线程均将被处理 */
  25.         if(timeval_cmp(timer_now, thread->u.sands) >= 0)
  26.          {
  27.          thread_list_delete(&m->timer, thread);
  28.          return thread_run(m, thread, fetch);
  29.         }
  30.     }

  31.     /* 处理ready中的线程 */
  32.     if((thread = thread_trim_head (&m->ready)) != NULL)
  33.        return thread_run(m, thread, fetch);

  34.     /* Structure copy. */
  35.     readfd = m->readfd;
  36.     writefd = m->writefd;
  37.     exceptfd = m->exceptfd;

  38.     /* Calculate select wait timer. */
  39.     timer_wait = thread_timer_wait(m, &timer_val);
  40.     
  41.     /* 对所有描述符进行listen */
  42.     num = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait);
  43.         xprintf("select num:%d\n", num);
  44.     if(num == 0)
  45.             continue;

  46.     if(num < 0)
  47.     {
  48.          if(errno &#61;&#61; EINTR)
  49.          continue;
  50.          return NULL;
  51.     }

  52.       /* 处理read中线程 */
  53.     ready &#61; thread_process_fd(m, &m->read, &readfd, &m->readfd);

  54.       /* 处理 write中线程 */ 
  55.       ready &#61; thread_process_fd(m, &m->write, &writefd, &m->writefd);

  56.       if((thread &#61; thread_trim_head(&m->ready)) !&#61; NULL)
  57.        return thread_run(m, thread, fetch);
  58.   }
  59. }

显然&#xff0c;Zebra中的thread机制并没有真正的优先级&#xff0c;而只是在处理的时候有些处理一些队列。他们的次序是&#xff1a;event、timer、 ready、 read和write。后面代码分析会得出read和write并没有明显的先后&#xff0c;因为它们最终都将被移入ready然后再被依次执行。而select同时收到多个描述符事件的概率是很低的。

 

thread_process_fd对于read和write线程来说是另一个关键的函数。

 

  1. Int thread_process_fd (struct thread_master *m, struct thread_list *list,
  2.          fd_set *fdset, fd_set *mfdset)
  3. {
  4.   struct thread *thread;
  5.   struct thread *next;
  6.   int ready &#61; 0;
  7.   for (thread &#61; list->head; thread; thread &#61; next)
  8.   {
  9.       next &#61; thread->next;
  10.       if (FD_ISSET (THREAD_FD (thread), fdset))
  11.      {
  12.      assert (FD_ISSET (THREAD_FD (thread), mfdset));
  13.      FD_CLR(THREAD_FD (thread), mfdset);
  14.  // 将侦听到的描述符对应的线程移到ready链表中
  15.      thread_list_delete (list, thread);
  16.      thread_list_add (&m->ready, thread);
  17.      thread->type &#61; THREAD_READY;
  18.      ready&#43;&#43;;
  19.     }
  20.     }
  21.   return ready;
  22. }

Thread_process_fd 将侦听到的描述符对应的线程移到ready链表中&#xff0c;并且进行文件描述的清除操作&#xff0c;文件描述符的添加在thread_add_read和thread_add_write中进行。

 

1.5 thread中的其他链表

 


write链表的操作类似于read链表&#xff0c;而event链表是直接操作的。timer链表只是添加对时间的比对操作。
在加入对应的链表时&#xff0c;使用不同的添加函数。
struct thread *
thread_add_read (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);
struct thread *thread_add_write (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);
struct thread *thread_add_event (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);
struct thread *thread_add_timer (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);

 

1.6 thread 机制中的其他函数

 

//执行thread
void thread_call (struct thread *thread);

//直接创建并执行&#xff0c;m参数可以为NULL
struct thread *thread_execute (struct thread_master *m,
int (*func)(struct thread *),  void *arg,  int val);

//取消一个线程&#xff0c;thread中的master指针不可为空
void thread_cancel (struct thread *thread);

//取消所有event链表中的参数为arg的线程
void thread_cancel_event (struct thread_master *m, void *arg);

//类似于thread_call&#xff0c;区别是thread_call只是执行&#xff0c;不将其加入unuse链表。thread_run执行后会将其加入unuse链表。
struct thread *thread_run (struct thread_master *m, struct thread *thread,  struct thread *fetch);

// 释放m及其中的线程链表
void thread_master_free (struct thread_master *m);

 

1.7 一些时间相关的函数

 

static struct timeval timeval_subtract (struct timeval a, struct timeval b);

static int timeval_cmp (struct timeval a, struct timeval b);
当然也提供了简单的DEBUG函数thread_master_debug。

 

2.对ZEBRA中thread的应用

 

对thread的应用的探讨是最重要的&#xff0c;也是最根本的。ZEBRA的thread机制&#xff0c;模拟了线程&#xff0c;便于平台间的移植&#xff0c;使流水线式的程序编码模块化&#xff0c;结构化。


线程列表间的组合很容易实现状态机的功能。可以自定义应用层通信协议。比如我们定义一个sysstat的远程监控协议。 Client请求Server&#xff0c;请求Code 可以为SYS_MEM,SYS_RUNTIME,SYS_LOG等信息获取动作&#xff0c;也可以是SYS_REBOOT&#xff0c;SYS_SETTIME等动作请求, Server回应这个SYS_MEM等的结果。通常这很简单&#xff0c;但是如果我们需要添加一些步骤&#xff0c;比如用户验证过程呢&#xff1f;


 

  1.               Request Auth
  2. Client-------------------------------->Server
  3.               Response PWD?
  4. Client<--------------------------------Server
  5.               Provide PWD
  6. Client-------------------------------->Server
  7.               Auth Result
  8. Client<--------------------------------Server
  9.               SYS_LOG
  10. Client-------------------------------->Server
  11.               SYS_LOG_INFO
  12. Client<--------------------------------Server

 

再考虑三次认证错误触发黑名单事件&#xff01;状态机就是在处理完上一事件后&#xff0c;添加不同的事件线程。


 

3.对ZEBRA的思考

 

Zebra由Kunihiro Ishiguro开发于15年前&#xff0c;Kunihiro Ishiguro离开了Zebra&#xff0c;而后它的名字被改成了quagga&#xff0c;以至于在因特网上输入Zebra后&#xff0c;你得到只有斑马的注释。Zebra提供了一整套基于TCP/IP网络的路由协议的支持&#xff0c;如RIPv1&#xff0c;RIPv2的&#xff0c;RIPng&#xff0c;OSPFv2&#xff0c;OSPFv3&#xff0c;BGP等&#xff0c;然而它的亮点并不在于此&#xff0c;而在于它对程序架构的组织&#xff0c;你可以容易的剥离它&#xff0c;使他成为专用的cli程序&#xff0c;也已可以轻易的提取其中的一类数据结构&#xff0c;也可以借用他的thread机制实现复杂的状态机。

编码的价值往往不在于写了多少&#xff0c;而在于对他们的组织&#xff01;好的组织体现美好的架构、设计的艺术&#xff0c;可以给人启迪&#xff0c;并在此基础上激发出更多的灵感。如果一个初学者想学习程序设计的架构&#xff0c;无疑选择Zebra是一个明智的选择&#xff0c;你不仅可以学到各种数据结构&#xff0c;基于C的面向对象设计&#xff0c;还有CLI&#xff0c;以及各种网络路由协议&#xff0c;最重要是的Zebra条理清晰&#xff0c;代码紧凑&#xff0c;至少不会让你焦头烂额&#xff01;

如果你不知道代码中的xprintf是怎么一回事&#xff0c;那么看看另一篇文章《一个通用的debug系统 》&#xff01;


推荐阅读
  • 本文探讨了利用Java实现WebSocket实时消息推送技术的方法。与传统的轮询、长连接或短连接等方案相比,WebSocket提供了一种更为高效和低延迟的双向通信机制。通过建立持久连接,服务器能够主动向客户端推送数据,从而实现真正的实时消息传递。此外,本文还介绍了WebSocket在实际应用中的优势和应用场景,并提供了详细的实现步骤和技术细节。 ... [详细]
  • Linux入门教程第七课:基础命令与操作详解
    在本课程中,我们将深入探讨 Linux 系统中的基础命令与操作,重点讲解网络配置的相关知识。首先,我们会介绍 IP 地址的概念及其在网络协议中的作用,特别是 IPv4(Internet Protocol Version 4)的具体应用和配置方法。通过实际操作和示例,帮助初学者更好地理解和掌握这些基本技能。 ... [详细]
  • 在Linux系统中,为了提高安全性,可以通过设置命令执行超时和用户超时注销来防止因用户长时间未操作而带来的安全隐患。具体而言,可以通过编辑 `/etc/profile` 文件,添加或修改相关参数,确保用户在指定时间内无操作后自动注销。此外,还可以利用 `timeout` 命令来限制特定命令的执行时间,进一步增强系统的稳定性和安全性。 ... [详细]
  • 在 Windows 10 环境中,通过配置 Visual Studio Code (VSCode) 实现基于 Windows Subsystem for Linux (WSL) 的 C++ 开发,并启用智能代码提示功能。具体步骤包括安装 VSCode 及其相关插件,如 CCIntelliSense、TabNine 和 BracketPairColorizer,确保在 WSL 中顺利进行开发工作。此外,还详细介绍了如何在 Windows 10 中启用和配置 WSL,以实现无缝的跨平台开发体验。 ... [详细]
  • Java中处理NullPointerException:getStackTrace()方法详解与实例代码 ... [详细]
  • 在CodeIgniter框架中集成新库文件的过程中,我遇到了一些困惑。具体来说,在跟随nettuts的认证教程时,对于在Welcome控制器中添加的构造函数代码,特别是关于Session的验证部分,我感到不太理解。这部分内容涉及如何确保Session已经初始化并具备相应的功能,这对于实现用户认证至关重要。为了更好地掌握这一知识点,我计划深入研究CodeIgniter的官方文档,并参考更多相关资源,以确保能够正确地集成和使用新库文件。 ... [详细]
  • 投融资周报 | Circle 达成 4 亿美元融资协议,唯一艺术平台 A 轮融资超千万美元 ... [详细]
  • 利用 Python 管道实现父子进程间高效通信 ... [详细]
  • FastDFS Nginx 扩展模块的源代码解析与技术剖析
    FastDFS Nginx 扩展模块的源代码解析与技术剖析 ... [详细]
  • 在HTML5应用中,Accordion(手风琴,又称抽屉)效果因其独特的展开和折叠样式而广泛使用。本文探讨了三种不同的Accordion交互效果,通过层次结构优化信息展示和页面布局,提升用户体验。这些效果不仅增强了视觉效果,还提高了内容的可访问性和互动性。 ... [详细]
  • 本文详细介绍了 jQuery 的入门知识与实战应用,首先讲解了如何引入 jQuery 库及入口函数的使用方法,为初学者提供了清晰的操作指南。此外,还深入探讨了 jQuery 在实际项目中的多种应用场景,包括 DOM 操作、事件处理和 AJAX 请求等,帮助读者全面掌握 jQuery 的核心功能与技巧。 ... [详细]
  • jQuery插件验证与屏幕键盘功能的集成解决方案
    本文介绍了一种集成了验证功能和屏幕键盘的jQuery插件解决方案。该插件不仅提供了强大的表单验证功能,还引入了一个高度可定制的屏幕键盘,以增强用户体验。通过这一集成方案,开发者可以轻松实现复杂的表单验证逻辑,并为用户提供便捷的输入方式,特别适用于移动设备或特殊输入场景。 ... [详细]
  • 技术分享:深入解析GestureDetector手势识别机制
    技术分享:深入解析GestureDetector手势识别机制 ... [详细]
  • 开发笔记:深入解析Android自定义控件——Button的72种变形技巧
    开发笔记:深入解析Android自定义控件——Button的72种变形技巧 ... [详细]
  • 如果程序使用Go语言编写并涉及单向或双向TLS认证,可能会遭受CPU拒绝服务攻击(DoS)。本文深入分析了CVE-2018-16875漏洞,探讨其成因、影响及防范措施,为开发者提供全面的安全指导。 ... [详细]
author-avatar
手机用户2502853847
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有