找工作的时候大体看了一下中断、软中断以及工作队列的一些相关的知识,其实关于中断和软中断的相关源码早就在《情景分析》和ULK3中看过了,但是对于设计的意图一点都不了解,只是囫囵吞枣的看了个源码的大概。其中一个重点的区别是在中断和软中断中不能sleep,所以才在2.5版本中引入了工作队列来解决这一问题。
但是不禁要问为什么在中断及软中断中不能sleep呢?书中给出的答案是中断处于中断上下文,而可以调度的进程处于进程上下文,在中断上下文中不能调度。
今天在CU上看到了一个讨论帖《关于LINUX在中断(硬软)中不能睡眠的真正原因 》,19L网友xiaozhaoz给出了详细的说明来说明中断中不能sleep,其实并不是中断中sleep不能实现,而是出于各种方面的考虑而未被采用而已。后来在网上看到了一个函数
- int __must_check request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name , void *dev)
可以用来实现中断的线程化。
根据xiaozhaoz网友的描述,中断可以是存在上下文的,而这个上下文就是被中断的task的上下文,如果中断并不是采用独立的内核堆栈而是与被中断进程共享堆栈的话,那么可以很容易的通过current宏找到相应的task的上下文。但是因为中断是随意的,即其上下文是随意的,所以这样的随机性会给内核实时性等等带来问题。出于考虑,可以给中断一个固定的上下文现场。也就是request_threaded_irq函数中的参数3的函数irq_handler_t thread_fn。
- static inline int __must_checkrequest_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
- {
- return request_threaded_irq(irq, handler, NULL, flags, name, dev);
- }
这里面函数request_threaded_irq被封装为request_irq.
其实关于request_threaded_irq我不准备贴源码介绍,因为整体上跟原来的request_irq差不多。这里面我要提到__setup_irq中的一段代码。
- if (new->thread_fn && !nested) {
- struct task_struct *t;
- t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
- new->name);
- if (IS_ERR(t))
- return PTR_ERR(t);
- /*
- * We keep the reference to the task struct even if
- * the thread dies to avoid that the interrupt code
- * references an already freed task_struct.
- */
- get_task_struct(t);
- new->thread = t;
- }
其实关于irqaction结构与以前相比也添加了不少元素,主要是与进程相关的内容。在上面我们提到了,中断支持睡眠是可以实现的,但是由于没有固定的上下文,使得中断睡眠的代价很大甚至会出现问题,比如被中断的进程握有锁或者信号量,但是随眠被重新调度的进程也会用到这个锁或者信号量,可能会出现死锁的情况。所以说中断不能睡眠是因为没有固定的上下文。
那么这儿我们就给中断一个固定的上下文,即irq_thread线程。
为了支持中断睡眠,显然do_IRQ函数必须被重写。
- irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
- {
- irqreturn_t ret, retval = IRQ_NONE;
- unsigned int status = 0;
- if (!(action->flags & IRQF_DISABLED))
- local_irq_enable_in_hardirq();
- do {
- trace_irq_handler_entry(irq, action);
- ret = action->handler(irq, action->dev_id);
- trace_irq_handler_exit(irq, action, ret);
- switch (ret) {
- case IRQ_WAKE_THREAD:
- /*
- * Set result to handled so the spurious check
- * does not trigger.
- */
- ret = IRQ_HANDLED;
- /*
- * Catch drivers which return WAKE_THREAD but
- * did not set up a thread function
- */
- if (unlikely(!action->thread_fn)) {
- warn_no_thread(irq, action);
- break;
- }
- /*
- * Wake up the handler thread for this
- * action. In case the thread crashed and was
- * killed we just pretend that we handled the
- * interrupt. The hardirq handler above has
- * disabled the device interrupt, so no irq
- * storm is lurking.
- */
- if (likely(!test_bit(IRQTF_DIED,
- &action->thread_flags))) {
- set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
- wake_up_process(action->thread);
- }
- /* Fall through to add to randomness */
- case IRQ_HANDLED:
- status |= action->flags;
- break;
- default:
- break;
- }
- retval |= ret;
- action = action->next;
- } while (action);
- if (status & IRQF_SAMPLE_RANDOM)
- add_interrupt_randomness(irq);
- local_irq_disable();
- return retval;
- }
上面的代码是2.6.32中的源码,为了对比,我贴出2.6.16中该部分的源码
- fastcall int handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
- struct irqaction *action)
- {
- int ret, retval = 0, status = 0;
- if (!(action->flags & SA_INTERRUPT))
- local_irq_enable();
- do {
- ret = action->handler(irq, action->dev_id, regs);
- if (ret == IRQ_HANDLED)
- status |= action->flags;
- retval |= ret;
- action = action->next;
- } while (action);
- if (status & SA_SAMPLE_RANDOM)
- add_interrupt_randomness(irq);
- local_irq_disable();
- return retval;
- }
从这个函数可以看出新的handle_IRQ_event为了支持中断睡眠,而将中断处理函数一分为二,将不会产生睡眠的部分仍然放在handle_IRQ_event中去做,而将会引起睡眠的部分放到action->thread中,也就是irq_thread线程中的action->thread_fn去中,因为这儿已经处于了进程上下文,睡眠不会引起问题。
那么在handle_IRQ_event中调用wake_up_process会不会产生问题呢。
wake_up_process->try_to_wake_up.try_to_wake_up只是将一个进程设置为TASK_RUNNING状态,然后移到调度链表中去等待调度,并没有立即唤醒。
其实这里面并没有真正的实现在中断中睡眠,而是将睡眠的部分让给中断处理线程irq_thread来做。所以这里面仍然没有实现中断睡眠,只是它的采用中断处理线程的做法给用户一种在中断睡眠的假象。
阅读(5618) | 评论(0) | 转发(4) |