linux kernel 下半部 软中断 tasklet
2015-07-20 10:14
603 查看
为了使中断能快速的返回,linux kernel 将中断分成两部分,上半部(中断处理程序)和下半部(现在linux内核用软中断,tasklet, 工作队列来实现)
其中,tasklet 是基于软中断来实现的,属于软中断的一种类型。工作队列比较熟悉了,不再赘述。
在linux 内核中中断处理的流程一般是这样的:
中断
->中断服务程序(tasklet_schedule()你的tasklet 下半部)
->下半部(tasklet/softirq)
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 }
tasklet_schedule()
->raise_softirq_irqoff()
->wakeup_softirqd
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 回调函数。
{
tasklet_init(&iop_chan->irq_tasklet, iop_adma_tasklet, (unsigned long)
iop_chan);
{
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 在下文中详述。
其中,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.c2.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 在下文中详述。
相关文章推荐
- Linux 每天自动备份mysql数据库的方法
- Linux加密类型及相关算法
- Linux正则表达式awk讲解
- Windows/Linux环境下模拟服务端口方法
- linux scp命令(主机,服务器间复制文件)
- PuTTY + Xming 远程使用 Linux GUI
- PuTTY + Xming 远程使用 Linux GUI
- Linux C函数使用记录
- Mac/Windows/Linux安装MYSQL
- Linux下使用多个不同版本的R
- Linux命令学习之路---输入输出状态(iostat)命令
- linux 下的日志相关
- linux系统用户下的crontab任务不执行问题处理
- linux系统用户下的crontab任务不执行问题处理
- Linux多线程——使用信号量同步线程
- Linux应用总结(1):自动删除n天前日志
- linux安装IPython四种方法
- linux下找到程序运行的位置
- linux安装ftp
- Linux命令学习之路---虚拟内存统计(vmstat)命令