對zebra的一點思考 --- 002

原文鏈接:https://www.cnblogs.com/iplus/archive/2013/03/18/4467312.html

此文並不針對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。

/* Thread itself. */
struct thread
{
  unsigned char type;        /* thread type */
  struct thread *next;        /* next pointer of the thread */
  struct thread *prev;        /* previous pointer of the thread */
  struct thread_master *master;    /* pointer to the struct thread_master. */
  int (*func) (struct thread *); /* event function */
  void *arg;            /* event argument */
  union {
    int val;            /* second argument of the event. */
    int fd;            /* file descriptor in case of read/write. */
    struct timeval sands;    /* rest of time sands value. */
  } u;
  RUSAGE_T ru;            /* Indepth usage info. */
};

/* Linked list of thread. */
struct thread_list
{
  struct thread *head;
  struct thread *tail;
  int count;
};

/* Master of the theads. */
struct thread_master
{
  struct thread_list read;
  struct thread_list write;
  struct thread_list timer;
  struct thread_list event;
  struct thread_list ready;
  struct thread_list unuse;
  fd_set readfd;
  fd_set writefd;
  fd_set exceptfd;
  unsigned long alloc;
};

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隊列是不二選擇。下面是一個例子,這個例子將對標準輸入文件描述符進行處理:

static int do_accept (struct thread *thread)
{
char buf[1024] = "";
int len = 0;
    
len = read(THREAD_FD(thread), buf, 1024);    
printf("len:%d, %s", len, buf);
return 0;
}

int main()
{
    struct thread thread;

    // 創建線程管理者
    struct thread_master *master = thread_master_create();    

    // 創建讀線程,讀線程處理的描述符是標準輸入0,處理函數爲do_accept
    thread_add_read(master, do_accept, NULL, fileno(stdin));
    
    // 打印當前線程管理者中的所有線程
    thread_master_debug(master);
    
// thread_fetch select所有的描述符,一旦偵聽的描述符需要處理就將對應的”線程”        的地址通過thread返回
    while(thread_fetch(master, &thread))
    {
     // 執行處理函數
       thread_call(&thread);
       thread_master_debug(master);

      // 這裏爲什麼需要再次添加呢?
      thread_add_read(master, do_accept, NULL, fileno(stdin));
      thread_master_debug(master);
    }    
    
    return 0;
}

編譯執行,得到如下的結果:

// 這裏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是用來獲取需要執行的線程的,它是整個程序的核心。這裏需要對它進行重點的分析。

struct thread *thread_fetch(struct thread_master *m, struct thread *fetch)
{
  int num;
  int ready;
  struct thread *thread;
  fd_set readfd;
  fd_set writefd;
  fd_set exceptfd;
  struct timeval timer_now;
  struct timeval timer_val;
  struct timeval *timer_wait;
  struct timeval timer_nowait;

  timer_nowait.tv_sec = 0;
  timer_nowait.tv_usec = 0;

  while(1)
  {
    /* 最先處理event隊列 */
    if((thread = thread_trim_head(&m->event)) != NULL)
            return thread_run(m, thread, fetch);

    /* 接着處理timer隊列 */
    gettimeofday(&timer_now, NULL);
    for(thread = m->timer.head; thread; thread = thread->next)
    { 
       /* 所有到時間的線程均將被處理 */
        if(timeval_cmp(timer_now, thread->u.sands) >= 0)
         {
         thread_list_delete(&m->timer, thread);
         return thread_run(m, thread, fetch);
        }
    }

    /* 處理ready中的線程 */
    if((thread = thread_trim_head (&m->ready)) != NULL)
       return thread_run(m, thread, fetch);

    /* Structure copy. */
    readfd = m->readfd;
    writefd = m->writefd;
    exceptfd = m->exceptfd;

    /* Calculate select wait timer. */
    timer_wait = thread_timer_wait(m, &timer_val);
    
    /* 對所有描述符進行listen */
    num = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait);
        xprintf("select num:%d\n", num);
    if(num == 0)
            continue;

    if(num < 0)
    {
         if(errno == EINTR)
         continue;
         return NULL;
    }

      /* 處理read中線程 */
    ready = thread_process_fd(m, &m->read, &readfd, &m->readfd);

      /* 處理 write中線程 */ 
      ready = thread_process_fd(m, &m->write, &writefd, &m->writefd);

      if((thread = thread_trim_head(&m->ready)) != NULL)
       return thread_run(m, thread, fetch);
  }
}

顯然,Zebra中的thread機制並沒有真正的優先級,而只是在處理的時候有些處理一些隊列。他們的次序是:event、timer、 ready、 read和write。後面代碼分析會得出read和write並沒有明顯的先後,因爲它們最終都將被移入ready然後再被依次執行。而select同 時收到多個描述符事件的概率是很低的。

thread_process_fd對於read和write線程來說是另一個關鍵的函數。

Int thread_process_fd (struct thread_master *m, struct thread_list *list,
         fd_set *fdset, fd_set *mfdset)
{
  struct thread *thread;
  struct thread *next;
  int ready = 0;
  for (thread = list->head; thread; thread = next)
  {
      next = thread->next;
      if (FD_ISSET (THREAD_FD (thread), fdset))
     {
     assert (FD_ISSET (THREAD_FD (thread), mfdset));
     FD_CLR(THREAD_FD (thread), mfdset);
 // 將偵聽到的描述符對應的線程移到ready鏈表中
     thread_list_delete (list, thread);
     thread_list_add (&m->ready, thread);
     thread->type = THREAD_READY;
     ready++;
    }
    }
  return ready;
}

Thread_process_fd 將偵聽到的描述符對應的線程移到ready鏈表中,並且進行文件描述的清除操作,文件描述符的添加在thread_add_read和thread_add_write中進行。

1.5 thread中的其他鏈表

write鏈表的操作類似於read鏈表,而event鏈表是直接操作的。timer鏈表只是添加對時間的比對操作。
在加入對應的鏈表時,使用不同的添加函數。

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);

//直接創建並執行,m參數可以爲NULL
struct thread *thread_execute (struct thread_master *m,
int (*func)(struct thread *),  void *arg,  int val);

//取消一個線程,thread中的master指針不可爲空
void thread_cancel (struct thread *thread);

//取消所有event鏈表中的參數爲arg的線程
void thread_cancel_event (struct thread_master *m, void *arg);

//類似於thread_call,區別是thread_call只是執行,不將其加入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的應用的探討是最重要的,也是最根本的。ZEBRA的thread機制,模擬了線程,便於平臺間的移植,使流水線式的程序編碼模塊化,結構化。


線程列表間的組合很容易實現狀態機的功能。可以自定義應用層通信協議。比如我們定義一個sysstat的遠程監控協議。 Client請求Server,請求Code 可以爲SYS_MEM,SYS_RUNTIME,SYS_LOG等信息獲取動作,也可以是SYS_REBOOT,SYS_SETTIME等動作請求, Server迴應這個SYS_MEM等的結果。通常這很簡單,但是如果我們需要添加一些步驟,比如用戶驗證過程呢?


              Request Auth
Client-------------------------------->Server
              Response PWD?
Client<--------------------------------Server
              Provide PWD
Client-------------------------------->Server
              Auth Result
Client<--------------------------------Server
              SYS_LOG
Client-------------------------------->Server
              SYS_LOG_INFO
Client<--------------------------------Server

再考慮三次認證錯誤觸發黑名單事件!狀態機就是在處理完上一事件後,添加不同的事件線程。

 

3.對ZEBRA的思考

Zebra由Kunihiro Ishiguro開發於15年前,Kunihiro Ishiguro離開了Zebra,而後它的名字被改成了quagga,以至於在因特網上輸入Zebra後,你得到只有斑馬的註釋。Zebra提供了一整 套基於TCP/IP網絡的路由協議的支持,如RIPv1,RIPv2的,RIPng,OSPFv2,OSPFv3,BGP等,然而它的亮點並不在於此,而 在於它對程序架構的組織,你可以容易的剝離它,使他成爲專用的cli程序,也已可以輕易的提取其中的一類數據結構,也可以借用他的thread機制實現複雜的狀態機。

編碼的價值往往不在於寫了多少,而在於對他們的組織!好的組織體現美好的架構、設計的藝術,可以給人啓迪,並在此基礎上激發出更多的靈感。如果一個初學者想學習程序設計的架構,無疑選擇Zebra是一個明智的選擇,你不僅可以學到各種數據結構,基於C的面向對象設計,還有CLI,以及各種網絡路由協 議,最重要是的Zebra條理清晰,代碼緊湊,至少不會讓你焦頭爛額!

如果你不知道代碼中的xprintf是怎麼一回事,那麼看看另一篇文章《一個通用的debug系統 》!


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章