http://blog.sina.com.cn/s/blog_87bbe37e01015nlf.html
http://blog.sina.com.cn/s/blog_87bbe37e01015nly.html
标签: 杂谈 | 分类: Zebra/Zebos |
提起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,
//在指定的链表中删除制定的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
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
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
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 <</span> 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 *),
//取消一个线程,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,
// 释放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<</span>--------------------------------Server
-
Provide PWD
- Client-------------------------------->Server
-
Auth Result
- Client<</span>--------------------------------Server
-
SYS_LOG
- Client-------------------------------->Server
-
SYS_LOG_INFO
- Client<</span>--------------------------------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条理清晰,代码紧凑,至少不会让你焦头烂额
标签: 杂谈 | 分类: Zebra/Zebos |
1 zebra线程机制概述
zebra这个软件包整体结构大致可分为两大块:协议模块和守护进程模块。协议模块实现各协议的功能,各协议以子模块的形式加载到zebra中;守护进程模块的功能主要是管理各协议的信令传输、表项操作、系统操作调用等事务,为各协议提供底层信息以及相关的硬件处理等功能支持。Zebra与各协议的交互采用的是C-S模式,在每个协议子模块中均有一个Client端与守护进程模块中的Server端交互,它们所使用的socket为zebra内部使用的socket,不与外部交互。
zebra中的线程是分队列调度的,每个队列以一个链表的方式实现。线程队列可以分成五个队列:event、timer、ready、read、write。队列的优先级由高到低排列。但是,read和write队列并不参与到优先级的排列中,实际操作时,如果read和write队列中的线程就绪,就加入ready队列中,等待调度。调度时,首先进行event队列中线程的调度,其次是timer和ready。
实际上,zebra中的线程是“假线程”,它并没有实现线程中的优先级抢占问题。在zebra的线程管理中,有个虚拟的时间轴,必须等前一时间的线程处理完,才看下一时间的线程是否触发。由于电脑处理速度较快且处理每个线程的时间间隔较小,所以处理其多线程来可以打到类似“并行处理”的效果。
zebra源码中有关线程管理的各个函数放置于zebra-0.95a\lib文件夹的thread.h和thread.c两个文件中。
2 线程管理源码分析
2.1 重要结构体介绍
2.1.1 thread
这是线程队列中每一个单个线程的代码描述,线程队列被描述成双向链表的形式,thread结构体是双向链表的元素。共有六种线程:read、write、timer、event、ready、unused。因此,线程队列也有六种。
struct thread
{
unsigned char type;
struct thread *next;
struct thread *prev;
struct thread_master
int (*func) (struct thread *);
void *arg;
union {
} u;
RUSAGE_T ru;
};
2.1.2 thread_list
一个thread_list结构体描述一个thread双向链表,也即一个进程队列。
struct thread_list
{
struct thread *head;
struct thread *tail;
int count;
};
2.1.3 thread_master
总的线程管理结构体,里面存有六种线程队列,三种文件描述符以及占用空间信息。
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;
//该thread_master所占空间大小
unsigned long alloc;
};
标签: 杂谈 | 分类: Zebra/Zebos |
一个新的thread可以通过如下三种方式被创建,主要是看你需要创建的thread的类型:
1,
2,
3,
4,
上面这三个函数的处理过程都差不多:
1,
2,
3,
2. thread的调用
1,bgp daemon不断地从event queue中取出thread并且执行它。一旦该thread被执行了,将该thread的type设置为unuse。并且将该thread添加到unuse queue中。
2,如果event queue为空时,bgp daemon 通过select函数监控读、写、异常三个描述符集。一旦有某个描述符准备就绪,则将该描述符所对应的thread加入ready queue.
而对于timer queue中的thread,只有当select函数超时后才会进入ready queue.
3.zebrad端的thread
zebrad启动后会,在read queue中会出现两个thread,一个是等待来自local client端bgpd的连接,另一个是等待来自vty client端的连接。
第1个thread
zebra_init ( )-> zebra_serv_un ( )中创建一个thread,加入read queue。该thread的处理函数为zebra_accept,监听内部client的socket。
}
vty_accept,加入read queue。作为vty server监听internet的vty socket.。
vty_create(vty_sock, &su);
根据vty_sock和ip地址信息su,创建一个新的vty。
}
根据new client vty的vty_sock创建新的VTY_READ thread,加入read queue,该thread的处理函数为vty_read。
}
sockunion_bind(accept_sock, &su, port, NULL);
将accept_socket文件描述符与一个特定的逻辑网络连系起来。服务器端使用su->sin.sin_addr.s_addr = htonl (INADDR_ANY);表示接受任何一个主机网络接口上的连接请求。
将accept_sock套接口设置成被动监听状态,用于接受连接,只能在服务器端使用。
}
sockunion_bind操作
int sockunion_bind (int sock, union sockunion *su, unsigned short port,
{
#ifdef HAVE_SIN_LEN
#endif
服务器一般将sin_addr.s_addr 字段设置为INADDR_ANY表示套接字应接收任何一个主机网络接口上的连接请求。
客户端将sin_addr.s_addr字段设置为服务器主机的IP地址。
#ifdef HAVE_IPV6
#ifdef SIN6_LEN
#endif
#if defined(LINUX_IPV6) || defined(NRL)
#else
#endif
#endif
}
vty_event操作
static void vty_event (enum event event, int sock, struct vty *vty)
{
}
第2 个thread
bgp_serv_sock( )-> bgp_serv_sock_family( )中创建一个thread,不是通过event的方式添加的。该thread的处理函数为bgp_accept。作为bgp_server,接受来自 internet上的bgp连接。
bgp_serv_sock_family操作
port = 179
void bgp_serv_sock_family (unsigned short port, int family)
{
}
VTY server和BGP server 在使用accept操作的方法如下:
他们均是通过创建一个THREAD_READ 类型的thread,加到Master的readlist的队列后面,thread 的处理函数会执行accept操作。
VTY accept:
BGP server accept:
bgpd和zebrad间通信
bgp和zebra是通过zebra message进行通信,格式如下:
报文头3字节(前两字节length,后1字节为command type)
报文体长度不定。
#define ZEBRA_INTERFACE_ADD
#define ZEBRA_INTERFACE_DELETE
#define ZEBRA_INTERFACE_ADDRESS_ADD
#define ZEBRA_INTERFACE_ADDRESS_DELETE
#define ZEBRA_INTERFACE_UP
#define ZEBRA_INTERFACE_DOWN
#define ZEBRA_IPV4_ROUTE_ADD
#define ZEBRA_IPV4_ROUTE_DELETE
#define ZEBRA_IPV6_ROUTE_ADD
#define ZEBRA_IPV6_ROUTE_DELETE
#define ZEBRA_REDISTRIBUTE_ADD
#define ZEBRA_REDISTRIBUTE_DELETE
#define ZEBRA_REDISTRIBUTE_DEFAULT_ADD
#define ZEBRA_REDISTRIBUTE_DEFAULT_DELETE 14
#define ZEBRA_IPV4_NEXTHOP_LOOKUP
#define ZEBRA_IPV6_NEXTHOP_LOOKUP
bgpd和zebrad之间的api接口
bgp端接受到message后,会执行相应的bgp action:
bgp action func
int (*interface_add) (int, struct zclient *, zebra_size_t);
int (*interface_delete) (int, struct zclient *, zebra_size_t);
int (*interface_up) (int, struct zclient *, zebra_size_t);
int (*interface_down) (int, struct zclient *, zebra_size_t);
int (*interface_address_add) (int, struct zclient *, zebra_size_t);
int (*interface_address_delete) (int, struct zclient *, zebra_size_t);
int (*ipv4_route_add) (int, struct zclient *, zebra_size_t);
int (*ipv4_route_delete) (int, struct zclient *, zebra_size_t);
int (*ipv6_route_add) (int, struct zclient *, zebra_size_t);
int (*ipv6_route_delete) (int, struct zclient *, zebra_size_t);
zebra 端接受到message后,会执行相应的zebra action:
zebra action func
void zread_interface_add (struct zserv *client, u_short length)
void zread_interface_delete (struct zserv *client, u_short length)
void zread_ipv4_add (struct zserv *client, u_short length)
void zread_ipv4_delete (struct zserv *client, u_short length)
void zread_ipv6_add (struct zserv *client, u_short length)
void zread_ipv6_delete (struct zserv *client, u_short length)
void zebra_redistribute_add (int command, struct zserv *client, int length)
void zebra_redistribute_delete (int command, struct zserv *client, int length)
voidzebra_redistribute_default_add (int command,
struct zserv *client, int length)
void zebra_redistribute_default_delete (int command,
struct zserv *client, int length)
void zread_ipv4_nexthop_lookup (struct zserv *client, u_short length)
void zread_ipv6_nexthop_lookup (struct zserv *client, u_short length)
bgp action:将local_client_socket中数据,写入bgp数据库。
zebra action:将zebra数据库中的信息写入local_server_subsocket,让local client端进行读取。
bgp peer间通信
bgp_accept操作
int bgp_accept (struct thread *thread)
{
}
bgp_event操作
int bgp_event (struct thread *thread)
{
}