Xen的调度分析 (四) ——credit调度算法中关于链表的部分细节
上一节讲了credit调度算法中的一些细节,这里提一些细节中的细节。
首先是lish.h文件中的几个问题。linux通过结构体某个成员的地址,以及他的偏移量来获得该结构体的地址。
这里是如何进行的呢?
假设我们有以下代码:
struct csched_vcpu { struct list_head runq_elem; int credit; }; void main() { struct csched_vcpu svc; INIT_LIST_HEAD(&svc.runq_elem); svc.credit = 10; struct list_head *iter = &svc.runq_elem; while (1); }
vcpu结构体简化如上,main已经声明了一个vcpu svc,并且对该VCPU的链表进行了初始化。现在的问题是,假设我们只有iter这个指针,这个指针指向队列中一个队列元素,但是这个队列元素只是某个vcpu的结构体成员,我们如何通过这个队列元素获取该队列元素所属于的结构体呢?
struct csched_vcpu * iter_svc = list_entry(iter, struct csched_vcpu, runq_elem); printf("%d", iter_svc->credit);
如上,使用list_entry来获取该结构体的地址。该宏定义等同于container_of()。
#define container_of(ptr, type, member) ({ \ typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
该宏定义乍看起来比较复杂,分析起来倒是不难。主要还是实现了通过结构体某个成员的地址,以及他的偏移量来获得该结构体的地址。
第一句的作用是将该成员的ptr地址缓存一下。要缓存该地址,就要声明一个该指针同样类型的指针变量。typeof( ((type *)0)->member )就是获得了member的指针类型。为什么不直接使用typeof( member )是因为type结构体不一定用member成员,主要还是为了合法性检查。为什么不直接使用ptr是避免ptr的多次调用。比如ptr++之类的,就像使用min(x,y)可能导致的一些问题。
第二句的作用是,把该成员的地址减去该成员的地址在结构体里的偏移量,得到该结构体的地址。
最后,还有个问题,就是container_of是如何返回__mptr - offsetof()的值的呢?
GCC把包含在圆括号和大括号双层括号内的复合语句看作是一个表达式,它可以出现在任何允许表达式的地方,而复合语句中可以声明局部变量,以及循环条件判断等复杂处理。而表达式的最后一条语句必须是一个表达式,它的计算结果作为返回值。例如{(A;B;C)}返回C的值。
最后是分析offsetof()
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER);
这是假设有个地址为0的type类型的结构体,取出了该结构体的member成员,再获得该成员的地址。显然该地址就是member在结构体中的偏移。