[Linux内核]软中断与硬中断
转自:http://blog.csdn.net/zhangskd/article/details/21992933
本文主要内容:硬中断 / 软中断的原理和实现
内核版本:2.6.37
Author:zhangskd @ csdn blog
概述
从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。
如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,
跳到中断处理程序的入口点,进行中断处理。
(1) 硬中断
由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统系统外设状态的变化。比如当网卡收到数据包
的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq)。
(2) 软中断
为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间
就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq)来完成。
(3) 中断嵌套
Linux下硬中断是可以嵌套的,但是没有优先级的概念,也就是说任何一个新的中断都可以打断正在执行的中断,但同种中断
除外。软中断不能嵌套,但相同类型的软中断可以在不同CPU上并行执行。
(4) 软中断指令
int是软中断指令。
中断向量表是中断号和中断处理函数地址的对应表。
int n - 触发软中断n。相应的中断处理函数的地址为:中断向量表地址 + 4 * n。
(5)硬中断和软中断的区别
软中断是执行中断指令产生的,而硬中断是由外设引发的。
硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。
硬中断是可屏蔽的,软中断不可屏蔽。
硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。
软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。
开关
(1) 硬中断的开关
简单禁止和激活当前处理器上的本地中断:
local_irq_disable();
local_irq_enable();
保存本地中断系统状态下的禁止和激活:
unsigned long flags;
local_irq_save(flags);
local_irq_restore(flags);
(2) 软中断的开关
禁止下半部,如softirq、tasklet和workqueue等:
local_bh_disable();
local_bh_enable();
需要注意的是,禁止下半部时仍然可以被硬中断抢占。
(3) 判断中断状态
#define in_interrupt() (irq_count()) // 是否处于中断状态(硬中断或软中断)
#define in_irq() (hardirq_count()) // 是否处于硬中断
#define in_softirq() (softirq_count()) // 是否处于软中断
硬中断
(1) 注册中断处理函数
注册中断处理函数:
1 /** 2 * irq: 要分配的中断号 3 * handler: 要注册的中断处理函数 4 * flags: 标志(一般为0) 5 * name: 设备名(dev->name) 6 * dev: 设备(struct net_device *dev),作为中断处理函数的参数 7 * 成功返回0 8 */ 9 10 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, 11 const char *name, void *dev);
中断处理函数本身:
(2) 注销中断处理函数
1 /** 2 * free_irq - free an interrupt allocated with request_irq 3 * @irq: Interrupt line to free 4 * @dev_id: Device identity to free 5 * 6 * Remove an interrupt handler. The handler is removed and if the 7 * interrupt line is no longer in use by any driver it is disabled. 8 * On a shared IRQ the caller must ensure the interrupt is disabled 9 * on the card it drives before calling this function. The function does 10 * not return until any executing interrupts for this IRQ have completed. 11 * This function must not be called from interrupt context. 12 */ 13 14 void free_irq(unsigned int irq, void *dev_id);
软中断
(1) 定义
软中断是一组静态定义的下半部接口,可以在所有处理器上同时执行,即使两个类型相同也可以。
但一个软中断不会抢占另一个软中断,唯一可以抢占软中断的是硬中断。
软中断由softirq_action结构体表示:
1 struct softirq_action { 2 void (*action) (struct softirq_action *); /* 软中断的处理函数 */ 3 };
目前已注册的软中断有10种,定义为一个全局数组:
1 static struct softirq_action softirq_vec[NR_SOFTIRQS]; 2 3 enum { 4 HI_SOFTIRQ = 0, /* 优先级高的tasklets */ 5 TIMER_SOFTIRQ, /* 定时器的下半部 */ 6 NET_TX_SOFTIRQ, /* 发送网络数据包 */ 7 NET_RX_SOFTIRQ, /* 接收网络数据包 */ 8 BLOCK_SOFTIRQ, /* BLOCK装置 */ 9 BLOCK_IOPOLL_SOFTIRQ, 10 TASKLET_SOFTIRQ, /* 正常优先级的tasklets */ 11 SCHED_SOFTIRQ, /* 调度程序 */ 12 HRTIMER_SOFTIRQ, /* 高分辨率定时器 */ 13 RCU_SOFTIRQ, /* RCU锁定 */ 14 NR_SOFTIRQS /* 10 */ 15 };
(2) 注册软中断处理函数
例如:
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
(3) 触发软中断
调用raise_softirq()来触发软中断。
1 void raise_softirq(unsigned int nr) 2 { 3 unsigned long flags; 4 local_irq_save(flags); 5 raise_softirq_irqoff(nr); 6 local_irq_restore(flags); 7 } 8 9 /* This function must run with irqs disabled */ 10 inline void rasie_softirq_irqsoff(unsigned int nr) 11 { 12 __raise_softirq_irqoff(nr); 13 14 /* If we're in an interrupt or softirq, we're done 15 * (this also catches softirq-disabled code). We will 16 * actually run the softirq once we return from the irq 17 * or softirq. 18 * Otherwise we wake up ksoftirqd to make sure we 19 * schedule the softirq soon. 20 */ 21 if (! in_interrupt()) /* 如果不处于硬中断或软中断 */ 22 wakeup_softirqd(void); /* 唤醒ksoftirqd/n进程 */ 23 }
Percpu变量irq_cpustat_t中的__softirq_pending是等待处理的软中断的位图,通过设置此变量
即可告诉内核该执行哪些软中断。
1 static inline void __rasie_softirq_irqoff(unsigned int nr) 2 { 3 trace_softirq_raise(nr); 4 or_softirq_pending(1UL << nr); 5 } 6 7 typedef struct { 8 unsigned int __softirq_pending; 9 unsigned int __nmi_count; /* arch dependent */ 10 } irq_cpustat_t; 11 12 irq_cpustat_t irq_stat[]; 13 #define __IRQ_STAT(cpu, member) (irq_stat[cpu].member) 14 #define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x)) 15 #define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)
唤醒ksoftirqd内核线程处理软中断。
1 static void wakeup_softirqd(void) 2 { 3 /* Interrupts are disabled: no need to stop preemption */ 4 struct task_struct *tsk = __get_cpu_var(ksoftirqd); 5 6 if (tsk && tsk->state != TASK_RUNNING) 7 wake_up_process(tsk); 8 }
在下列地方,待处理的软中断会被检查和执行:
1. 从一个硬件中断代码处返回时
2. 在ksoftirqd内核线程中
3. 在那些显示检查和执行待处理的软中断的代码中,如网络子系统中
而不管是用什么方法唤起,软中断都要在do_softirq()中执行。如果有待处理的软中断,
do_softirq()会循环遍历每一个,调用它们的相应的处理程序。
在中断处理程序中触发软中断是最常见的形式。中断处理程序执行硬件设备的相关操作,
然后触发相应的软中断,最后退出。内核在执行完中断处理程序以后,马上就会调用
do_softirq(),于是软中断开始执行中断处理程序完成剩余的任务。
下面来看下do_softirq()的具体实现。
1 asmlinkage void do_softirq(void) 2 { 3 __u32 pending; 4 unsigned long flags; 5 6 /* 如果当前已处于硬中断或软中断中,直接返回 */ 7 if (in_interrupt()) 8 return; 9 10 local_irq_save(flags); 11 pending = local_softirq_pending(); 12 if (pending) /* 如果有激活的软中断 */ 13 __do_softirq(); /* 处理函数 */ 14 local_irq_restore(flags); 15 }
1 /* We restart softirq processing MAX_SOFTIRQ_RESTART times, 2 * and we fall back to softirqd after that. 3 * This number has been established via experimentation. 4 * The two things to balance is latency against fairness - we want 5 * to handle softirqs as soon as possible, but they should not be 6 * able to lock up the box. 7 */ 8 asmlinkage void __do_softirq(void) 9 { 10 struct softirq_action *h; 11 __u32 pending; 12 /* 本函数能重复触发执行的次数,防止占用过多的cpu时间 */ 13 int max_restart = MAX_SOFTIRQ_RESTART; 14 int cpu; 15 16 pending = local_softirq_pending(); /* 激活的软中断位图 */ 17 account_system_vtime(current); 18 /* 本地禁止当前的软中断 */ 19 __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET); 20 lockdep_softirq_enter(); /* current->softirq_context++ */ 21 cpu = smp_processor_id(); /* 当前cpu编号 */ 22 23 restart: 24 /* Reset the pending bitmask before enabling irqs */ 25 set_softirq_pending(0); /* 重置位图 */ 26 local_irq_enable(); 27 h = softirq_vec; 28 do { 29 if (pending & 1) { 30 unsigned int vec_nr = h - softirq_vec; /* 软中断索引 */ 31 int prev_count = preempt_count(); 32 kstat_incr_softirqs_this_cpu(vec_nr); 33 34 trace_softirq_entry(vec_nr); 35 h->action(h); /* 调用软中断的处理函数 */ 36 trace_softirq_exit(vec_nr); 37 38 if (unlikely(prev_count != preempt_count())) { 39 printk(KERN_ERR "huh, entered softirq %u %s %p" "with preempt_count %08x," 40 "exited with %08x?\n", vec_nr, softirq_to_name[vec_nr], h->action, prev_count, 41 preempt_count()); 42 } 43 rcu_bh_qs(cpu); 44 } 45 h++; 46 pending >>= 1; 47 } while(pending); 48 49 local_irq_disable(); 50 pending = local_softirq_pending(); 51 if (pending & --max_restart) /* 重复触发 */ 52 goto restart; 53 54 /* 如果重复触发了10次了,接下来唤醒ksoftirqd/n内核线程来处理 */ 55 if (pending) 56 wakeup_softirqd(); 57 58 lockdep_softirq_exit(); 59 account_system_vtime(current); 60 __local_bh_enable(SOFTIRQ_OFFSET); 61 }
(4) ksoftirqd内核线程
内核不会立即处理重新触发的软中断。
当大量软中断出现的时候,内核会唤醒一组内核线程来处理。
这些线程的优先级最低(nice值为19),这能避免它们跟其它重要的任务抢夺资源。
但它们最终肯定会被执行,所以这个折中的方案能够保证在软中断很多时用户程序不会
因为得不到处理时间而处于饥饿状态,同时也保证过量的软中断最终会得到处理。
每个处理器都有一个这样的线程,名字为ksoftirqd/n,n为处理器的编号。
1 static int run_ksoftirqd(void *__bind_cpu) 2 { 3 set_current_state(TASK_INTERRUPTIBLE); 4 current->flags |= PF_KSOFTIRQD; /* I am ksoftirqd */ 5 6 while(! kthread_should_stop()) { 7 preempt_disable(); 8 9 if (! local_softirq_pending()) { /* 如果没有要处理的软中断 */ 10 preempt_enable_no_resched(); 11 schedule(); 12 preempt_disable(): 13 } 14 15 __set_current_state(TASK_RUNNING); 16 17 while(local_softirq_pending()) { 18 /* Preempt disable stops cpu going offline. 19 * If already offline, we'll be on wrong CPU: don't process. 20 */ 21 if (cpu_is_offline(long)__bind_cpu))/* 被要求释放cpu */ 22 goto wait_to_die; 23 24 do_softirq(); /* 软中断的统一处理函数 */ 25 26 preempt_enable_no_resched(); 27 cond_resched(); 28 preempt_disable(); 29 rcu_note_context_switch((long)__bind_cpu); 30 } 31 32 preempt_enable(); 33 set_current_state(TASK_INTERRUPTIBLE); 34 } 35 36 __set_current_state(TASK_RUNNING); 37 return 0; 38 39 wait_to_die: 40 preempt_enable(); 41 /* Wait for kthread_stop */ 42 set_current_state(TASK_INTERRUPTIBLE); 43 while(! kthread_should_stop()) { 44 schedule(); 45 set_current_state(TASK_INTERRUPTIBLE); 46 } 47 48 __set_current_state(TASK_RUNNING); 49 return 0; 50 }