十、任务调度(二)

本文详细解析了Eb32tree的具体实现,包括其结构、关键函数的运作原理及任务唤醒和执行机制。Eb32tree作为Ebtree的32位版本,通过高效的二进制树结构管理数据,支持快速查找、插入和删除操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Eb32tree

Eb32treeEbtree32位版本的一个具体实现

[ebtree/eb32tree.h]
/* This structure carries a node, a leaf, and a key. It must start with the
 * eb_node so that it can be cast into an eb_node. We could also have put some
 * sort of transparent union here to reduce the indirection level, but the fact
 * is, the end user is not meant to manipulate internals, so this is pointless.
 */
struct eb32_node {
    struct eb_node node; /* the tree node, must be at the beginning */
    u32 key;
};

/*
 * Exported functions and macros. 
 * Many of them are always inlined because they are extremely small, and
 * are generally called at most once or twice in a program.
 */

/* Return leftmost node in the tree, or NULL if none */ 
static inline struct eb32_node *eb32_first(struct eb_root *root)
{
    return eb32_entry(eb_first(root), struct eb32_node, node);
}

/* Return rightmost node in the tree, or NULL if none */ 
static inline struct eb32_node *eb32_last(struct eb_root *root)
{
    return eb32_entry(eb_last(root), struct eb32_node, node);
}

/* Return next node in the tree, or NULL if none */
static inline struct eb32_node *eb32_next(struct eb32_node *eb32)
{
    return eb32_entry(eb_next(&eb32->node), struct eb32_node, node);
}

/* Return previous node in the tree, or NULL if none */
static inline struct eb32_node *eb32_prev(struct eb32_node *eb32)
{
    return eb32_entry(eb_prev(&eb32->node), struct eb32_node, node);
}

/* Return next node in the tree, skipping duplicates, or NULL if none */
static inline struct eb32_node *eb32_next_unique(struct eb32_node *eb32)
{
    return eb32_entry(eb_next_unique(&eb32->node), struct eb32_node, node);
}

/* Return previous node in the tree, skipping duplicates, or NULL if none */
static inline struct eb32_node *eb32_prev_unique(struct eb32_node *eb32)
{
    return eb32_entry(eb_prev_unique(&eb32->node), struct eb32_node, node);
}

/* Delete node from the tree if it was linked in. Mark the node unused. Note
 * that this function relies on a non-inlined generic function: eb_delete.
 */
static inline void eb32_delete(struct eb32_node *eb32)
{
    eb_delete(&eb32->node);
}

/* Delete node from the tree if it was linked in. Mark the node unused. */
static forceinline void __eb32_delete(struct eb32_node *eb32)
{
    __eb_delete(&eb32->node);
}

以上代码需要说明的是,eb32_entrycontainer_of的宏别名。为了说明清楚bit如何表示当前节点的值范围,现在将分析一下__eb32_insert()的代码,其他函数用到到时候再分析。

[ebtree/eb32tree.h]__eb32_insert()
/* Insert eb32_node <new> into subtree starting at node root <root>.
 * Only new->key needs be set with the key. The eb32_node is returned.
 * If root->b[EB_RGHT]==1, the tree may only contain unique keys.
 */
static forceinline struct eb32_node *
__eb32_insert(struct eb_root *root, struct eb32_node *new) {
    struct eb32_node *old;
    unsigned int side;
    eb_troot_t *troot, **up_ptr;
    u32 newkey; /* caching the key saves approximately one cycle */
    eb_troot_t *root_right;
    eb_troot_t *new_left, *new_rght;
    eb_troot_t *new_leaf;
    int old_node_bit;

    side = EB_LEFT;
    troot = root->b[EB_LEFT];
    root_right = root->b[EB_RGHT];
    if (unlikely(troot == NULL)) {
        /* Tree is empty, insert the leaf part below the left branch */
        root->b[EB_LEFT] = eb_dotag(&new->node.branches, EB_LEAF);
        new->node.leaf_p = eb_dotag(root, EB_LEFT);
        new->node.node_p = NULL; /* node part unused */
        return new;
}

如果root节点的左子树为空,那么表示这棵树是空的,将新插入节点作为叶子节点插入到root的左孩子上。此叶子节点是没有node节点的,这是作者在前面到描述中也提到过的。

[ebtree/eb32tree.h]__eb32_insert()
    /* The tree descent is fairly easy :
     *  - first, check if we have reached a leaf node
     *  - second, check if we have gone too far
     *  - third, reiterate
     * Everywhere, we use <new> for the node node we are inserting, <root>
     * for the node we attach it to, and <old> for the node we are
     * displacing below <new>. <troot> will always point to the future node
     * (tagged with its type). <side> carries the side the node <new> is
     * attached to below its parent, which is also where previous node
     * was attached. <newkey> carries the key being inserted.
     */
    newkey = new->key;

    while (1) {
        if (eb_gettag(troot) == EB_LEAF) {
            /* insert above a leaf */
            old = container_of(eb_untag(troot, EB_LEAF),
                        struct eb32_node, node.branches);
            new->node.node_p = old->node.leaf_p;
            up_ptr = &old->node.leaf_p;
            break;
        }

如果当前节点就是叶子节点,那么新节点将会插入在它之上。跳出循环,那么下面到代码就没必要执行了。

eb_untag(troot,EB_LEAF)得到的是struct eb_root结构体的实例,而struct eb_node中的branches成员就是struct eb_root类型,而struct eb_node结构体在struct eb32_node结构体中的成员名为node,因此通过eb_root_t的地址获取对应struct eb32_node的地址时需要将 eb_root_t转换成eb_root,然后使用node.branches成员与之对应,才能正确得到。

[ebtree/eb32tree.h]__eb32_insert()
        /* OK we're walking down this link */
        old = container_of(eb_untag(troot, EB_NODE),
                    struct eb32_node, node.branches);
        old_node_bit = old->node.bit;

        /* Stop going down when we don't have common bits anymore. We
         * also stop in front of a duplicates tree because it means we
         * have to insert above.
         */

        if ((old_node_bit < 0) || /* we're above a duplicate tree, stop here */
            (((new->key ^ old->key) >> old_node_bit) >= EB_NODE_BRANCHES)) {
            /* The tree did not contain the key, so we insert <new> before the node
             * <old>, and set ->bit to designate the lowest bit position in <new>
             * which applies to ->branches.b[].
             */
            new->node.node_p = old->node.node_p;
            up_ptr = &old->node.node_p;
            break;
        }

        /* walk down */
        root = &old->node.branches;
        side = (newkey >> old_node_bit) & EB_NODE_BRANCH_MASK;
        troot = root->b[side];
}

当前节点是链接节点,那么如果它到bit小于0,表示其下的子树是值与其相等的子树,因此,不再向下查找。

(new->key ^ old->key) >> old_node_bit,之前提示过说,bit表示的是当前节点的值二进制1出现到最高位。因此这个表达式到值就是得到新节点到值是否最高位1到出现位置比当前查找节点的最高位1还高,如果这个表达式的值大于等于EB_NODE_BRANCHES(2),那么表示,新节点插入应该在当前节点之上,因此不需要往下再查找。

如果新节点最高位1出现位置小于当前节点的话,新节点将会被插入到当前节点到左子树;若新节点最高位1出现位置等于当前节点的话,那么新节点将会被插入到当前节点到右子树。在进入子树之后由于前面while语句的存在,会继续查找子树,直到叶子节点或者遇到重复子树或者遇到当前节点与子树节点之间出现断层到现象,而待插入节点刚好处于断层范围内到值时而停止查找。

[ebtree/eb32tree.h]__eb32_insert()
    new_left = eb_dotag(&new->node.branches, EB_LEFT);
    new_rght = eb_dotag(&new->node.branches, EB_RGHT);
    new_leaf = eb_dotag(&new->node.branches, EB_LEAF);

    /* We need the common higher bits between new->key and old->key.
     * What differences are there between new->key and the node here ?
     * NOTE that bit(new) is always < bit(root) because highest
     * bit of new->key and old->key are identical here (otherwise they
     * would sit on different branches).
     */

    // note that if EB_NODE_BITS > 1, we should check that it's still >= 0
new->node.bit = flsnz(new->key ^ old->key) - EB_NODE_BITS;

对于eb_dotag()没什么好说的。接下来看看fldnz()函数是个什么意思。

[ebtree/ebtree.h]flsnz()
static inline int flsnz(int x)
{   
    int r;
    __asm__("bsrl %1,%0\n"
            : "=r" (r) : "rm" (x));
    return r+1;
}

bsrl指令是用于获取给定变量的二进制1出现的位置,范围0----31,此函数得到这个值之后,返回加一之后的值,也就是返回范围为1----32的值。

那么前面在赋给新节点之前,减去了一个EB_NODE_BITS,这是在ebtree/ebtree.h中定义为1的。因此结果就是bsrl返回到值,可是干什么要这么绕呢?我不懂。除了基于x86架构以及后续的x86_64的实现版本,在其他CPU上的版本如下

[ebtree/ebtree.h]flsnz()
#define flsnz(___a) ({ \
    register int ___x, ___bits = 0; \
    ___x = (___a); \
    if (___x & 0xffff0000) { ___x &= 0xffff0000; ___bits += 16;} \
    if (___x & 0xff00ff00) { ___x &= 0xff00ff00; ___bits +=  8;} \
    if (___x & 0xf0f0f0f0) { ___x &= 0xf0f0f0f0; ___bits +=  4;} \
    if (___x & 0xcccccccc) { ___x &= 0xcccccccc; ___bits +=  2;} \
    if (___x & 0xaaaaaaaa) { ___x &= 0xaaaaaaaa; ___bits +=  1;} \
	___bits + 1; \
})

代码解释如下,如果在高16有值,那么最高位1到出现位置应该大于16,因此需要将bit16,更新__x的值,再检查往上的8位、4位、2位、1位,最后还要将__bits+1。看起来似乎这儿的+1还是多余了,因为在前面赋值给新节点到时候又减1了。所以我不懂为何作者要这样做,可能唯一到解释是为了方便人们的思想,好多编号都是从1开始的。

[ebtree/eb32tree.h]__eb32_insert()
    if (new->key == old->key) {
        new->node.bit = -1; /* mark as new dup tree, just in case */

        if (likely(eb_gettag(root_right))) {
            /* we refuse to duplicate this key if the tree is
             * tagged as containing only unique keys.
             */
            return old;
        }

        if (eb_gettag(troot) != EB_LEAF) {
            /* there was already a dup tree below */
            struct eb_node *ret;
            ret = eb_insert_dup(&old->node, &new->node);
            return container_of(ret, struct eb32_node, node);
        }
        /* otherwise fall through */
    }

如果新节点与当前节点相同,那么将新节点的bit设置为-1,这只是为了以防这是一颗新产生的重复子树而已。因为若之前就已经存在重复子树,那么根据上一章对重复子树的插入可以知道,新节点到bit都会被重新设置。上一章描述的重复插入要求重复子树至少有两个节点。

设置好bit后,需要检查当前树是否允许存在重复值,上一章描述过,这是由root节点到右孩子指针上的一些附加信息来描述的。

如果不允许重复,那么直接返回当前节点。

允许重复,若当前节点不是叶子节点,也就是说之前在此处已经存在重复子树了,也就是之前在树中存在至少两个节点与新节点到值相同。若是叶子节点,那么插入操作与新节点值比当前节点大到情况相同。按照上一节对重复节点的插入操作可知,新节点总是插入在父节点的右子树,而把之前的右孩子节点作为新node节点的左孩子,自身的leaf放到新node节点的右孩子位置上;对于新节点值比当前节点到值大的情况,新节点(nodeleaf)也是插入在父节点的右孩子,(除了位于root节点下的叶子节点,每个叶子节点都存在nodeleaf节点,它们其实是同一个节点),而把之前的右孩子放到新node节点的左孩子,新的leaf则放到新node节点的右孩子上。

[ebtree/eb32tree.h]__eb32_insert()
    if (new->key >= old->key) {
        new->node.branches.b[EB_LEFT] = troot;
        new->node.branches.b[EB_RGHT] = new_leaf;
        new->node.leaf_p = new_rght;
        *up_ptr = new_left;
    }
    else {
        new->node.branches.b[EB_LEFT] = new_leaf;
        new->node.branches.b[EB_RGHT] = troot;
        new->node.leaf_p = new_left;
        *up_ptr = new_rght;
    }

    /* Ok, now we are inserting <new> between <root> and <old>. <old>'s
     * parent is already set to <new>, and the <root>'s branch is still in
     * <side>. Update the root's leaf till we have it. Note that we can also
     * find the side by checking the side of new->node.node_p.
     */

    root->b[side] = eb_dotag(&new->node.branches, EB_NODE);
    return new;
}

按照具体值插入到相应位置,最后将当前节点的父节点到对应孩子设置为新节点。root的值随着遍历而改变,但总是指向当前节点的父节点。

还需要说明的就是up_ptr,这是为了统一代码修改当前节点的父节点指针而设置的。

在此总结一下存储于eb32tree中数据的特点,从上至下,存储的数据值从大到小;从左到右,存储的数据值从小到大;根据root右孩子指针的附加信息决定是否允许含有重复值;root左孩子树用于数据存储,root本身不用于存储数据。

任务的唤醒

任务到唤醒除了之前在process_session中提到的与stream_interfacer相关的操作可能唤醒当前任务之外,在每一轮run_loop里面还有会一个专门用于唤醒超时任务的函数。

[ebtree/task.c]wake_expired_tasks()
/*
 * Extract all expired timers from the timer queue, and wakes up all
 * associated tasks. Returns the date of next event (or eternity) in <next>. 
 */
void wake_expired_tasks(int *next)
{
    struct task *task;
    struct eb32_node *eb;

eb = eb32_lookup_ge(&timers, now_ms - TIMER_LOOK_BACK);

通过eb32_lookup_ge()查找超时时间不比now_ms - TIMER_LOOK_BACK小的节点。本函数内是检查超时的任务,为什么要用一个比当前时间小的值作为参数来查找不比这个值小的第一个节点呢?直接使用eb32_lookup_le()然后以当前时间去查找最后一个比当前值小的节点呢?其实两种方法都可以,但是我想作者在此主要是想为以后对超时时间任务做一个控制而这样做的。比如当以后需要将程序改成只需要前面10ms内超时的任务的时候,那么就可以将TIMER_LOOK_BACK设置为10,那么得到的就是过去10ms内出现超时,但是还没移到可执行队列中的任务。

[ebtree/task.c]eb32_lookup_ge()
	/*
	 * Find the first occurrence of the lowest key in the tree <root>, which is
	 * equal to or greater than <x>. NULL is returned is no key matches.
	 */
	REGPRM2 struct eb32_node *eb32_lookup_ge(struct eb_root *root, u32 x)
	{
	    struct eb32_node *node;
	    eb_troot_t *troot;
	
	    troot = root->b[EB_LEFT];
	    if (unlikely(troot == NULL))
	        return NULL;
	
	    while (1) {
	        if ((eb_gettag(troot) == EB_LEAF)) {
	            /* We reached a leaf, which means that the whole upper
	             * parts were common. We will return either the current
	             * node or its next one if the former is too small.
	             */
	            node = container_of(eb_untag(troot, EB_LEAF),
	                        struct eb32_node, node.branches);
	            if (node->key >= x)
	                return node;
	            /* return next */
	            troot = node->node.leaf_p;
	            break;
        }

如果当前节点是叶子节点,那么查看其值是否不小于要查找的x,若是则返回,否则退回上一层。

	[ebtree/task.c]eb32_lookup_ge()
	        node = container_of(eb_untag(troot, EB_NODE),
	                    struct eb32_node, node.branches);
	
	        if (node->node.bit < 0) {
	            /* We're at the top of a dup tree. Either we got a
	             * matching value and we return the leftmost node, or
	             * we don't and we skip the whole subtree to return the
	             * next node after the subtree. Note that since we're
	             * at the top of the dup tree, we can simply return the
	             * next node without first trying to escape from the
	             * tree.
	             */
	            if (node->key >= x) {
	                troot = node->node.branches.b[EB_LEFT];
	                while (eb_gettag(troot) != EB_LEAF)
	                    troot = (eb_untag(troot, EB_NODE))->b[EB_LEFT];
	                return container_of(eb_untag(troot, EB_LEAF),
	                            struct eb32_node, node.branches);
	            }
	            /* return next */
	            troot = node->node.node_p;
	            break;
        }

如果当前节点为重复子树的入口点,若当前节点值不小于待查值,则返回重复子树中最左边的叶子节点,否则退回上一层。

	[ebtree/task.c]eb32_lookup_ge()
	        if (((x ^ node->key) >> node->node.bit) >= EB_NODE_BRANCHES) {
	            /* No more common bits at all. Either this node is too
	             * large and we need to get its lowest value, or it is too
	             * small, and we need to get the next value.
	             */
	            if ((node->key >> node->node.bit) > (x >> node->node.bit)) {
	                troot = node->node.branches.b[EB_LEFT];
	                return eb32_entry(eb_walk_down(troot, EB_LEFT), struct eb32_node, 								node);
	            }
	
	            /* Further values will be too low here, so return the next
	             * unique node (if it exists).
	             */
	            troot = node->node.node_p;
	            break;
	        }
	        troot = node->node.branches.b[(x >> node->node.bit) 
	& EB_NODE_BRANCH_MASK];
    }

当前节点不存在重复子树,那么检查当前节点值与待查值的大小;

如果待查值比当前节点值大得多,那么返回上一层。至于里面作者检查是否当前节点比待查值大很多,我觉得在前面的if成立的情况下不可能发生。

如果以上条件均不满足,那么根据其右移bit得到的分支获取下一个节点,然后循环之前的操作。

	[ebtree/task.c]eb32_lookup_ge()
	    /* If we get here, it means we want to report next node after the
	     * current one which is not below. <troot> is already initialised
	     * to the parent's branches.
	     */
	    while (eb_gettag(troot) != EB_LEFT)
	        /* Walking up from right branch, so we cannot be below root */
	        troot = (eb_root_to_node(eb_untag(troot, EB_RGHT)))->node_p;
	
	    /* Note that <troot> cannot be NULL at this stage */
	    troot = (eb_untag(troot, EB_LEFT))->b[EB_RGHT];
	    if (eb_clrtag(troot) == NULL)
	        return NULL;
	
	    node = eb32_entry(eb_walk_down(troot, EB_LEFT), struct eb32_node, node);
	    return node;
}

运行至此,表示之前检查的节点小于待查值,现在需要找到比当前节点值大的最小叶子节点,那么首先需要找到共同祖先。这个共同祖先满足,当前节点位于其左子树的最右边。找到祖先之后,先获取其右子树,然后一直往左找到其位于最左边的叶子节点,这个点就是比当前节点大并且比待查节点大的第一个叶子节点。这是为什么呢?因为在查找过程中,如果跑到了当前节点左子树,这表明待查节点是比当前节点小至少一个档次,而右子树与当前节点是同一个档次的,因此如果在其左子树中找到的最大值小于待查值,那么其右子树的最左边的叶子节点就必然不小于待查值。

[ebtree/task.c]wake_expired_tasks()
    while (1) {
        if (unlikely(!eb)) {
            /* we might have reached the end of the tree, typically because 
            * <now_ms> is in the first half and we're first scanning the last
            * half. Let's loop back to the beginning of the tree now.
            */      
            eb = eb32_first(&timers);
            if (likely(!eb))
                break;  
        }

如果之前等待队列中没有任务,之前查找返回的任务为空;或者由于之前返回的是等待队列中的最后一个,而且刚好超时时间和当前时间相等,那么在处理完之后,eb32_next()得到的也是空的。

[ebtree/task.c]wake_expired_tasks()
        if (likely(tick_is_lt(now_ms, eb->key))) {
            /* timer not expired yet, revisit it later */
            *next = eb->key;
            return; 
        }

        /* timer looks expired, detach it from the queue */
        task = eb32_entry(eb, struct task, wq);
        eb = eb32_next(eb);
        __task_unlink_wq(task);

如果当前节点的超时时间大于当前时间,那么将next设置为当前节点的时间,然后返回。否则获取当前节点关联的任务,将当前节点指向逻辑上的下一个节点,然后将当前节点关联任务从等待队列中移除。

[ebtree/task.c]wake_expired_tasks()
	/* It is possible that this task was left at an earlier place in the
         * tree because a recent call to task_queue() has not moved it. This
         * happens when the new expiration date is later than the old one.
         * Since it is very unlikely that we reach a timeout anyway, it's a
         * lot cheaper to proceed like this because we almost never update
         * the tree. We may also find disabled expiration dates there. Since
         * we have detached the task from the tree, we simply call task_queue
         * to take care of this. Note that we might occasionally requeue it at
         * the same place, before <eb>, so we have to check if this happens,
         * and adjust <eb>, otherwise we may skip it which is not what we want.
         * We may also not requeue the task (and not point eb at it) if its
         * expiration time is not set.
         */
        if (!tick_is_expired(task->expire, now_ms)) {
            if (!tick_isset(task->expire))
                continue;
            __task_queue(task);
            if (!eb || eb->key > task->wq.key)
                eb = &task->wq;
            continue;
        }
        task_wakeup(task, TASK_WOKEN_TIMER);
}

如果当前任务没有超时,若其超时时间不存在,那么转到下一个节点;并且其超时时间是存在的,那么将其重新入等待队列,并且若下一个节点不存在,或者下一个节点必当前节点还大,那么将下一次处理的节点设置为当前节点。这儿的处理是为了处理一些冤假错案,但是可以这么说,基本上不会发生。

任务已经超时,那么将其转到可执行队列中,唤醒机制属于定时器超时。

[ebtree/task.c]wake_expired_tasks()
    /* We have found no task to expire in any tree */
    *next = TICK_ETERNITY;
    return;
}

最后对于没有任务的队列,当然将*next设置为无限,表示下一个定时器超时时间是不存在的,因为没有定时器存在。

在前面用到了__task_queue()task_wakeup(),那么接下来先看下它们的实现。

附加函数

[ebtree/task.c]__task_queue()
/*
 * __task_queue()
 *
 * Inserts a task into the wait queue at the position given by its expiration
 * date. It does not matter if the task was already in the wait queue or not, 
 * as it will be unlinked. The task must not have an infinite expiration timer.
 * Last, tasks must not be queued further than the end of the tree, which is
 * between <now_ms> and <now_ms> + 2^31 ms (now+24days in 32bit). 
 *
 * This function should not be used directly, it is meant to be called by the
 * inline version of task_queue() which performs a few cheap preliminary tests
 * before deciding to call __task_queue().
 */
void __task_queue(struct task *task)
{
    if (likely(task_in_wq(task)))
        __task_unlink_wq(task);

如果当前task位于queue中,那么需要先将它删除掉,这是为什么呢?因为按照之前的流程来看,这个task对应的eb32_node的信息不是很正确,因此它如果还在队列中,那么需要将其移出队列,然后修正信息之后再重新链入队列。

[ebtree/task.c]task_in_wq()
/* return 0 if task is in wait queue, otherwise non-zero */
static inline int task_in_wq(struct task *t)
{
    return t->wq.node.leaf_p != NULL;
}

[ebtree/task.c]__task_unlink_wq()
static inline struct task *__task_unlink_wq(struct task *t)
{       
    eb32_delete(&t->wq);
    if (last_timer == &t->wq) 
        last_timer = NULL;
    return t;
}

last_timer表示位于定时器树中的超时时间最大的一个节点。

[ebtree/task.c]__task_queue()
    /* the task is not in the queue now */
    task->wq.key = task->expire;
#ifdef DEBUG_CHECK_INVALID_EXPIRATION_DATES
    if (tick_is_lt(task->wq.key, now_ms))
        /* we're queuing too far away or in the past (most likely) */
        return; 
#endif

eb32_nodekey值设置为相应任务的超时时间。

[ebtree/task.c]__task_queue()
    if (likely(last_timer &&
           last_timer->node.bit < 0 &&
           last_timer->key == task->wq.key &&
           last_timer->node.node_p)) {
         /* Most often, last queued timer has the same expiration date, so
         * if it's not queued at the root, let's queue a dup directly there.
         * Note that we can only use dups at the dup tree's root (most
         * negative bit).
         */
        eb_insert_dup(&last_timer->node, &task->wq.node);
        if (task->wq.node.bit < last_timer->node.bit)
            last_timer = &task->wq;
        return;
    }
    eb32_insert(&timers, &task->wq);

    /* Make sure we don't assign the last_timer to a node-less entry */
    if (task->wq.node.node_p && (!last_timer || (task->wq.node.bit < last_timer->node.bit)))
        last_timer = &task->wq;
    return;
}

如果last_timer存在,并且last_timer是一个重复子树的入口点,而且last_timer的超时时间与待插入节点超时时间相同,并且last_timer节点不是root节点,那么直接点调用eb_insert_dup()将待插入节点插入到以last_timer作为根的重复子树中。

如果条件不满足,那么用eb32_insert()将待插入节点插入到定时器树中。

最后还需要根据条件决定是否将刚插入的节点更新为last_timerlast_timer的优化仅仅是优化定时器入队时对于当前树中存在入口点为last_timer的重复子树的情况。那么为了在eb32_insert()之后,对于last_timer是否需要更新使用的判断条件仍然使用task->wq.node.bit < last_timer->node.bit?这个判断的依据是什么?

 

 

[ebtree/task.c]task_wakeup()
/* puts the task <t> in run queue with reason flags <f>, and returns <t> */
static inline struct task *task_wakeup(struct task *t, unsigned int f)
{   
    if (likely(!task_in_rq(t)))
        __task_wakeup(t);
    t->state |= f;
    return t;
}

如果待唤醒任务已经存在与可执行队列,那么将任务的状态加上相应的唤醒标志即可。否则调用__task_wakeup()将任务链入可执行队列中。

[ebtree/task.c]__task_wakeup()
/* Puts the task <t> in run queue at a position depending on t->nice. <t> is
 * returned. The nice value assigns boosts in 32th of the run queue size. A
 * nice value of -1024 sets the task to -run_queue*32, while a nice value of
 * 1024 sets the task to run_queue*32. The state flags are cleared, so the
 * caller will have to set its flags after this call.
 * The task must not already be in the run queue. If unsure, use the safer
 * task_wakeup() function.
 */ 
struct task *__task_wakeup(struct task *t)
{
    run_queue++;
    t->rq.key = ++rqueue_ticks;

    if (likely(t->nice)) {
        int offset;

        niced_tasks++;
        if (likely(t->nice > 0))
            offset = (unsigned)((run_queue * (unsigned int)t->nice) / 32U);
        else
            offset = -(unsigned)((run_queue * (unsigned int)-t->nice) / 32U);
        t->rq.key += offset;
    }

    /* clear state flags at the same time */
    t->state &= ~TASK_WOKEN_ANY;

    eb32_insert(&rqueue, &t->rq);
    return t;
}

可执行任务的key设置为对runqueue的当前插入次数,如果待插入任务含有优先级,那么更新优先任务的数量,然后根据任务的优先级是大于还是小于0来决定其相对key的位移。正和负运算的结果绝对值是一样的,不同的仅仅是符号。那么与t->rq.key相加之后产生的效果是将当前任务在可执行队列中提前或者推后。

对于任务的优先级是在event_accept函数中在创建task的时候继承自LISTENER

在此函数之中,任务的唤醒状态将会被全部清空,因此如果需要任务的唤醒状态的,则需要在此函数调用之后自己设置。

任务的执行

任务的执行函数为process_runnable_tasks(),入口位于run_poll_loop()中。

[ebtree/task.c]__task_wakeup()
/* The run queue is chronologically sorted in a tree. An insertion counter is
 * used to assign a position to each task. This counter may be combined with
 * other variables (eg: nice value) to set the final position in the tree. The
 * counter may wrap without a problem, of course. We then limit the number of
 * tasks processed at once to 1/4 of the number of tasks in the queue, and to
 * 200 max in any case, so that general latency remains low and so that task
 * positions have a chance to be considered.
 *
 * The function adjusts <next> if a new event is closer. 
 */

作者说明,任务在树中的位置由插入次数决定,当然可能在这个值之上用其他值修正(例如:nice)。每一次处理可执行队列时,至少保证执行可执行队列四分之一的任务,上限为200个,以保证延迟较低而且还能兼顾一些具有优先级的任务,这是因为event_accept是在POLLER中进行处理的,而此处主要是负责TASK的处理,实际上就是process_session()的执行。

[ebtree/task.c]__task_wakeup()
void process_runnable_tasks(int *next)
{
    struct task *t;
    struct eb32_node *eb;
    unsigned int max_processed;
    int expire; 

    run_queue_cur = run_queue; /* keep a copy for reporting */
    nb_tasks_cur = nb_tasks;
    max_processed = run_queue;
    if (max_processed > 200)
        max_processed = 200;

    if (likely(niced_tasks))
        max_processed = (max_processed + 3) / 4; 

    expire = *next;
	eb = eb32_lookup_ge(&rqueue, rqueue_ticks - TIMER_LOOK_BACK);
    while (max_processed--) {
        /* Note: this loop is one of the fastest code path in
         * the whole program. It should not be re-arranged
         * without a good reason.
         */

        if (unlikely(!eb)) {
            /* we might have reached the end of the tree, typically because
            * <rqueue_ticks> is in the first half and we're first scanning
            * the last half. Let's loop back to the beginning of the tree now.
            */
            eb = eb32_first(&rqueue);
            if (likely(!eb))
                break;
        }

        /* detach the task from the queue */
        t = eb32_entry(eb, struct task, rq);
        eb = eb32_next(eb);
        __task_unlink_rq(t);

        t->state |= TASK_RUNNING;
        /* This is an optimisation to help the processor's branch
         * predictor take this most common call.
         */
        t->calls++;
        if (likely(t->process == process_session))
            t = process_session(t);
        else
            t = t->process(t);

        if (likely(t != NULL)) {
            t->state &= ~TASK_RUNNING;
            if (t->expire) {
                task_queue(t);
                expire = tick_first_2nz(expire, t->expire);
            }

            /* if the task has put itself back into the run queue, we want to ensure
             * it will be served at the proper time, especially if it's reniced.
             */
            if (unlikely(task_in_rq(t)) && (!eb || tick_is_lt(t->rq.key, eb->key))) {
                eb = eb32_lookup_ge(&rqueue, rqueue_ticks - TIMER_LOOK_BACK);
            }
        }
    }
    *next = expire;
}

此函数的代码很简单,并且与之前的wake_expired_tasks()函数代码很相似。需要说明的就是在执行完process_session()之后,如果任务又出现在可执行队列中,那么如果当前任务的超时时间比下一次检查的任务的超时时间早,那么将会重新调用eb32_lookup_ge()来查找第一个可执行任务,那么很可能又找到当前任务,在此对其进行执行。

总结下任务状态链,任务的创建位于event_accept()函数中,然后以初始化状态被唤醒进入runqueue,然后被执行,执行完成之后有可能被销毁,也可能被移出到等待队列,还可能重新被唤醒进入可执行队列被再次执行。位于等待队列中的任务,在每一轮run_loop都会被检查是否超时时间已经到达,然后以超时状态将其唤醒转入可执行队列。任务处理过程其实是对相应SESSION的一个处理过程。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值