xinhe

I like open and free

  DonewsBlog  |  Donews首页  |  Donews社区  |  Donews邮箱  |  我的首页  |  联系作者  |  聚合   |  登录
  73篇文章 :: 13篇收藏:: 0篇评论:: 3个Trackbacks

文章

收藏

相册

Linux/UNIX技术站点

安全站点

编程资料站点

好友Blog

软件下载站点

存档


正在读取评论……


6.4 任务队列Task Queue
任务队列是与Bottom Half机制紧密相连的。因为Bottom Half机制只有有限的32个函数指针,而且大部分都已被系统预定义使用,所以早期版本的Linux内核为了扩展Bottom Half机制,就设计了任务队列机制。
所谓任务队列就是指以双向队列形式连接起来的任务链表,每一个链表元数都描述了一个可执行的任务(以函数的形式表现)。如下图所示:

任务队列机制实现在include/linux/tqueue.h头文件中。

6.4.1 数据结构的定义
Linux用数据结构tq_struct来描述任务队列中的每一个链表元数(即一个可执行的任务):
struct tq_struct {
struct list_head list; /* linked list of active bh's */
unsigned long sync; /* must be initialized to zero */
void (*routine)(void *); /* function to call */
void *data; /* argument to function */
};
这个数据结构很简单,在此就不详述。
然后,Linux定义了数据结构task_queue来描述任务队列的头部,其实task_queue就是结构类型list_head,如下:
typedef struct list_head task_queue;

但是Linux又定义了一个宏DECLARE_TASK_QUEUE()来辅助我们更方便地定义任务队列的链表表头:
#define DECLARE_TASK_QUEUE(q) LIST_HEAD(q)

一个任务队列是否处于active状态主要取决于其链表表头(即task_queue结构)是否为空,因此Linux定义宏TQ_ACTIVE()来判断一个任务队列是否有效:
#define TQ_ACTIVE(q) (!list_empty(&q))
显然,只要任务队列表头q不为空,该任务队列就是有效的。

6.4.2 向任务队列中插入一个新任务
(1)保护自旋锁
由于任务队列是系统全局的共享资源,所以面临竞争的问题。为了实现对任务队列链表的互斥访问,Linux在kernel/timer.c文件中定义了一个任务队列保护自旋锁tqueue_lock,如下:
spinlock_t tqueue_lock = SPIN_LOCK_UNLOCKED;
该自旋锁在tqueue.h头文件中也有原型声明:
extern spinlock_t tqueue_lock;
任何想要访问任务队列的代码都首先必须先持有该自旋锁。

(2)queue_task()函数
实现在tqueue.h头文件中的内联函数queue_task()用来将一个指定的任务添加到某指定的任务队列的尾部,如下:
/*
* Queue a task on a tq. Return non-zero if it was successfully
* added.
*/
static inline int queue_task(struct tq_struct *bh_pointer, task_queue *bh_list)
{
int ret = 0;
if (!test_and_set_bit(0,&bh_pointer->sync)) {
unsigned long flags;
spin_lock_irqsave(&tqueue_lock, flags);
list_add_tail(&bh_pointer->list, bh_list);
spin_unlock_irqrestore(&tqueue_lock, flags);
ret = 1;
}
return ret;
}

6.4.3 运行任务队列
函数run_task_queue()用于实现指定的任务队列。它只有一个参数:指针list——指向待运行的任务队列头部task_queue结构变量。该函数实现在tqueue.h头文件中:
static inline void run_task_queue(task_queue *list)
{
if (TQ_ACTIVE(*list))
__run_task_queue(list);
}
显然,函数首先调用宏TQ_ACTIVE()来判断参数list指定的待运行任务队列是否为空。如果不为空,则调用__run_task_queue()函数来实际运行这个有效的任务队列。
函数__run_task_queue()实现在kernel/softirq.c文件中。该函数将依次遍历任务队列中的每一个元数,并调用执行每一个元数的可执行函数。其源码如下:
void __run_task_queue(task_queue *list)
{
struct list_head head, *next;
unsigned long flags;

spin_lock_irqsave(&tqueue_lock, flags);
list_add(&head, list);
list_del_init(list);
spin_unlock_irqrestore(&tqueue_lock, flags);

next = head.next;
while (next != &head) {
void (*f) (void *);
struct tq_struct *p;
void *data;

p = list_entry(next, struct tq_struct, list);
next = next->next;
f = p->routine;
data = p->data;
wmb();
p->sync = 0;
if (f)
f(data);
}
}
对该函数的注释如下:
(1)首先,用一个局部的表头head来代替参数list所指向的表头。这是因为:在__run_task_queue()函数的运行期间可能还会有新的任务加入到list任务队列中来,但是__run_task_queue()函数显然不想陷入无休止的不断增加的任务处理中,因此它用局部的表头head来代替参数list所指向的表头,以使要执行的任务个数固定化。为此:①先对全局的自旋锁tqueue_lock进行加锁,以实现对任务队列的互斥访问;②将局部的表头head加在表头(*list)和第一个元数之间。③将(*list)表头从队列中去除,并将其初始化为空。④解除自旋锁tqueue_lock。
(2)接下来,用一个while循环来遍历整个队列head,并调用执行每一个队列元素中的函数。注意!任务队列是一个双向循环队列。

6.4.4 内核预定义的任务队列
Bottom Half机制与任务队列是紧密相连的。大多数BH函数都是通过调用run_task_queue()函数来执行某个预定义好的任务队列。最常见的内核预定义任务队列有:
l tq_timer:对应于TQUEUE_BH。
l tq_immediate:对应于IMMEDIATE_BH。
l tq_disk:用于块设备任务。

任务队列tq_timer和tq_immediate都定义在kernel/timer.c文件中,如下所示:
DECLARE_TASK_QUEUE(tq_timer);
DECLARE_TASK_QUEUE(tq_immediate);

BH向量TQUEUE_BH和IMMEDIATE_BH的BH函数分别是:queue_bh()函数和immediate_bh()函数,它们都仅仅是简单地调用run_task_queue()函数来分别运行任务队列tq_timer和tq_immediate,如下所示(kernel/timer.c):
void tqueue_bh(void)
{
run_task_queue(&tq_timer);
}

void immediate_bh(void)
{
run_task_queue(&tq_immediate);
}


Trackback: http://tb.donews.net/TrackBack.aspx?PostId=210914


[点击此处收藏本文]  发表于2004年12月22日 2:47 PM




正在读取评论……