您的位置:首页 > 运维架构 > Linux

Linux 2.4中下半部机制(BottomHalf)的基本原理与使用(简单分析)

2009-03-10 23:24 429 查看
Linux 2.4中下半部机制(BottomHalf)的基本原理与使用

1.介绍
下半部是为了解决内核中驱动程序响应中断的效率问题而设置的机制。通常在上半部响应中断,将关键数据保存下来。而下半部负责在内核空间作进一步的数据处理,以便最终交付给用户使用。但在Linux2.4中有了新的tasklet机制来替代bottomhalf机制,bottomhalf机制存在的意义主要是兼容和移植了。

2.基本原理
上半部响应中断-->收取数据-->标示下半部可启动;
时钟中断-->检查下半部任务-->完成任务;
第一,在include/linux/interrupt.h中,为大家所公认的下半部机制以枚举的方式被定义。代码如下所示。
enum {
TIMER_BH = 0,
TQUEUE_BH,
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
SCSI_BH,
IMMEDIATE_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};
第二,这些机制相关的最重要的数据结构,下半部执行例程的指针bh_base[]在kernel/softirq.c中得到静态定义。代码如下所示:
static void (*bh_base[32])(void);

第三,就是将具体的下半部例程填写到指针数组中的初始化过程。init_bh()定义在kernel/softirq.c中,代码如下所示:
void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}

第四,就是真正完成任务的执行下半部例程bh_action。定义在kernel/softirq.c中的代码如下所示:
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();

if (!spin_trylock(&global_bh_lock))
goto resched;

if (!hardirq_trylock(cpu))
goto resched_unlock;

if (bh_base[nr])
bh_base[nr]();

hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;

resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}

3.实例
这里用一个典型的串行口驱动程序drivers/char/serial.c来说明主要流程;总的流程是这样的:
中断到达-->收中断-->状态统计-->数据暂存-->将读操作行规处理任务挂接到tq_timer的任务队列上(TQUEUE_BH);
中断到达-->发中断-->发送数据-->将唤醒上层写操作的任务排到tq_serial的任务队列上(SERIAL_BH);

3.1 TQUEUE_BH
tq_timer的任务队列初始化在kernel/timer.c中实现,这是一个可以提供给大家公用的队列。每次时钟中断时,会启动这个队列上的操作。代码如下所示:
DECLARE_TASK_QUEUE(tq_timer);
执行队列中的任务的代码如下所示:
void tqueue_bh(void)
{
run_task_queue(&tq_timer);
}
由时钟中断标志该队列可执行;
void do_timer(struct pt_regs *regs)
{
(*(unsigned long *)&jiffies)++;
#ifndef CONFIG_SMP
/* SMP process accounting uses the local APIC timer */

update_process_times(user_mode(regs));
#endif
mark_bh(TIMER_BH);
if (TQ_ACTIVE(tq_timer))
mark_bh(TQUEUE_BH);
}

3.2 SERIAL_BH
tq_serial的任务队列初始化在drivers/char/serial.c中,这是串行口驱动专用的队列。初始化代码如下所示:
static DECLARE_TASK_QUEUE(tq_serial);
static int __init rs_init(void)
{
int i;
struct serial_state * state;

init_bh(SERIAL_BH, do_serial_bh);
......
}
static void do_serial_bh(void)
{
run_task_queue(&tq_serial);
}
任务排队,调度的代码如下所示:
static _INLINE_ void transmit_chars(struct async_struct *info, int *intr_done)
{
......
if (CIRC_CNT(info->xmit.head,
info->xmit.tail,
SERIAL_XMIT_SIZE) < WAKEUP_CHARS)
rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
......
}
static _INLINE_ void rs_sched_event(struct async_struct *info,
int event)
{
info->event |= 1 << event;
queue_task(&info->tqueue, &tq_serial);
mark_bh(SERIAL_BH);
}
static int __init rs_init(void)
{
......
info->tqueue.routine = do_softint;
info->tqueue.data = info;
......
}

3.3 应用队列的注意事项
虽然只定义了32个队列,但其中的大部分可以供驱动开发者重新利用。
依据LDD2的定义,bottomhalf代码应当遵守如下规则:
.不允许访问用户空间。(不存在进程上下文)
.不可执行睡眠或调度;
.不可使用信号量和内存申请;

{zhuan}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: