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

linux kernel 下半部 软中断 tasklet

2015-07-20 10:14 603 查看
为了使中断能快速的返回,linux kernel 将中断分成两部分,上半部(中断处理程序)和下半部(现在linux内核用软中断,tasklet, 工作队列来实现)

其中,tasklet 是基于软中断来实现的,属于软中断的一种类型。工作队列比较熟悉了,不再赘述。

在linux 内核中中断处理的流程一般是这样的:

中断

->中断服务程序(tasklet_schedule()你的tasklet 下半部)

->下半部(tasklet/softirq)

1. tasklet

1.1 创建tasklet

创建一个tasklet,设置回调函数

void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

1.2. 触发/调度你的tasklet

在中断服务程序里面调用 tasklet_schedule() 将会触发调度你的tasklet.

tasklet_schedule()

->raise_softirq_irqoff()

->wakeup_softirqd

1.3. 你的下半部究竟什么时候才会得到执行? ksoftirqd/N 内核线程运行的时候

linux 内核会为每个CPU 创建一个叫做ksoftirqd/N 的内核线程,用于处理该CPU 上的软中断。

p = kthread_create_on_node(run_ksoftirqd,
hcpu,
cpu_to_node(hotcpu),
"ksoftirqd/%d", hotcpu);

可以看到ksoftirqd/N 内核线程是非实时进程,优先级p->prio = 120,因此,内核并不保证中断下半部的代码能够很快执行,这点要注意。

USER     PID   PPID  VSIZE  RSS   PRIO  NICE  RTPRI SCHED  PCY  WCHAN    PC        NAME

root      3     2     0      0     20    0     0     0     fg  c00a1e00 00000000 S ksoftirqd/0

root      29957 2     0      0     20    0     0     0     fg  c00a1e00 00000000 S ksoftirqd/1

root      29981 2     0      0     20    0     0     0     fg  c00a1e00 00000000 S ksoftirqd/2

这时你可能会问,tasklet 和软中断究竟是怎么联系起来的呢? 为什么说tasklet 是软中断的一种特殊实现?

start_kernel()

->softirq_init()

看下sotfirq_init() 初始化软中断的时候都做了什么操作:
void __init softirq_init(void)
{
int cpu;

for_each_possible_cpu(cpu) {
int i;

per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
for (i = 0; i < NR_SOFTIRQS; i++)
INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));//维护一个软中断链表
}

register_hotcpu_notifier(&remote_softirq_cpu_notifier);

open_softirq(TASKLET_SOFTIRQ, tasklet_action);//这里是关键,调用open_softirq()设置 TASKLET_SOFTIRQ 类型的软中断的action 回调函数
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}


看下include/linux/interrupt.h 中定义的softirq 的类型:
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
frequency threaded job scheduling. For almost all the purposes
tasklets are more than enough. F.e. all serial device BHs et
al. should be converted to tasklets, not to softirqs.
*/

enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,//tasklet !!!
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS
};


在这段代码上面的注释中你也可以知道,一般的下半部实现你应该做的是使用tasklet 而不是添加新的软中断!

接着看open_softirq()的实现:
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}


tasklet_action() 被设置成了TASKLET_SOFTIRQ 类型的软中断的action。

在看ksoftirqd/N 内核线程的循环体:

->run_ksoftirqd()

->__do_softirq()

trace_softirq_entry(vec_nr);
h->action(h);//执行的就是相应类型软中断对应的action 函数,对于tasklet 来说也就是 tasklet_action()
trace_softirq_exit(vec_nr);

可以看下tasklet_action()
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;

local_irq_disable();
list = __this_cpu_read(tasklet_vec.head);
__this_cpu_write(tasklet_vec.head, NULL);
__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
local_irq_enable();

while (list) {
struct tasklet_struct *t = list;

list = list->next;

if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data);// 最终调用到的就是创建tasklet的时候传入的func 回调函数。

2. 下面看一个tasklet 的例子

drivers/dma/iop-adma.c

2.1 创建tasklet

static int __devinit iop_adma_probe(struct platform_device *pdev)
{
tasklet_init(&iop_chan->irq_tasklet, iop_adma_tasklet, (unsigned long)
iop_chan);

2.2 调度tasklet

static irqreturn_t iop_adma_eot_handler(int irq, void *data)
{
struct iop_adma_chan *chan = data;

dev_dbg(chan->device->common.dev, "%s\n", __func__);

tasklet_schedule(&chan->irq_tasklet);//调度tasklet, 这个函数执行在iop 的中断服务程序中

iop_adma_device_clear_eot_status(chan);

return IRQ_HANDLED;
}

static int __devinit iop_adma_probe(struct platform_device *pdev)
{

for (i = 0; i < 3; i++) {
irq_handler_t handler[] = { iop_adma_eot_handler,
iop_adma_eoc_handler,
iop_adma_err_handler };
int irq = platform_get_irq(pdev, i);
if (irq < 0) {
ret = -ENXIO;
goto err_free_iop_chan;
} else {
ret = devm_request_irq(&pdev->dev, irq,
handler[i], 0, pdev->name, iop_chan);//中断服务程序handler 设置为 iop_adma_eot_handler()
if (ret)
goto err_free_iop_chan;
}
}

至于中断的handler 中断服务程序在调用流程以及 由devm_request_irq()申请中断中引发的threaded irq 在下文中详述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: