Linux之中断
简单梳理下Linux中的中断机制。
硬件中断和软件中断¶
中断分为硬件中断和软件中断
- 硬件中断:由硬件引发的中断,例如外部io设备引发的中断、CPU内部异常,区别于硬中断
- 软件中断:由软件引发的中断,例如int指令,区别于软中断
硬中断和软中断¶
中断执行过程通常分为上下半,以节省硬中断资源(也有简单的中断没有下半)
- 上半 硬中断,由硬件实现的中断机制,中断资源是有限的,需要尽快处理完,因此硬中断主要将软中断标志位置位,然后返回
- 下半 软中断,由软件实现的中断机制,不同的软中断标志由对应的守护程序进行轮询
硬中断¶
简单来说,硬中断的过程如下:
可以看出,硬中断的资源和硬件资源相关,是十分宝贵的,但是其响应也相对快。
软中断¶
看看Linux 2.6.0代码:
// 直接在init/main.c找softirq相关的初始化函数
static void do_pre_smp_initcalls(void)
{
extern int spawn_ksoftirqd(void);
node_nr_running_init();
spawn_ksoftirqd(); // <----- Here
}
// 注册了个回调函数
__init int spawn_ksoftirqd(void)
{
cpu_callback(&cpu_nfb, CPU_ONLINE, (void *)(long)smp_processor_id()); // <-- Here
register_cpu_notifier(&cpu_nfb);
return 0;
}
// 当某个软中断触发,就通知
static int __devinit cpu_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
{
int hotcpu = (unsigned long)hcpu;
if (action == CPU_ONLINE) {
if (kernel_thread(ksoftirqd, hcpu, CLONE_KERNEL) < 0) { // <---- Here
printk("ksoftirqd for %i failed\n", hotcpu);
return NOTIFY_BAD;
}
while (!per_cpu(ksoftirqd, hotcpu))
yield();
}
return NOTIFY_OK;
}
// 怎么触发?
// 主要是一个循环进行do_softirq(),再看看 do_softirq()
static int ksoftirqd(void * __bind_cpu)
{
int cpu = (int) (long) __bind_cpu;
daemonize("ksoftirqd/%d", cpu);
set_user_nice(current, 19);
current->flags |= PF_IOTHREAD;
/* Migrate to the right CPU */
set_cpus_allowed(current, cpumask_of_cpu(cpu));
BUG_ON(smp_processor_id() != cpu);
__set_current_state(TASK_INTERRUPTIBLE);
mb();
__get_cpu_var(ksoftirqd) = current;
for (;;) {
if (!local_softirq_pending())
schedule();
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) {
do_softirq();
cond_resched();
}
__set_current_state(TASK_INTERRUPTIBLE);
}
}
// 主要是对软中断向量表的各个位进行判断,并选择是否执行对应处理函数
asmlinkage void do_softirq(void)
{
int max_restart = MAX_SOFTIRQ_RESTART;
__u32 pending;
unsigned long flags;
if (in_interrupt())
return;
local_irq_save(flags);
pending = local_softirq_pending();
if (pending) {
struct softirq_action *h;
local_bh_disable();
restart:
/* Reset the pending bitmask before enabling irqs */
local_softirq_pending() = 0;
local_irq_enable();
h = softirq_vec; // 顾名思义:软中断向量表
do {
if (pending & 1) // 如果中断标志位置位
h->action(h); // 调用处理函数
h++; // 下一个中断处理函数
pending >>= 1; // 下一个中断标志位
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
__local_bh_enable();
}
local_irq_restore(flags);
}
// 看看软中断标志相关
typedef struct {
unsigned int __softirq_pending;
unsigned long idle_timestamp;
unsigned int __nmi_count; /* arch dependent */
unsigned int apic_timer_irqs; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) ((void)(cpu), irq_stat[0].member)
#define softirq_pending(cpu) __IRQ_STAT((cpu), __softirq_pending)
#define local_softirq_pending() softirq_pending(smp_processor_id())
// 看看软中断向量表相关结构
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
精简一下
// 直接在init/main.c找softirq相关的初始化函数
static void do_pre_smp_initcalls(void)
{
spawn_ksoftirqd(); // <------ Here
}
// 注册了个回调函数
__init int spawn_ksoftirqd(void)
{
cpu_callback(&cpu_nfb, CPU_ONLINE, (void *)(long)smp_processor_id()); // <--- Here
register_cpu_notifier(&cpu_nfb);
return 0;
}
// 当某个软中断触发,就通知
static int __devinit cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu)
{
kernel_thread(ksoftirqd, hcpu, CLONE_KERNEL); // <------- Here
return NOTIFY_OK;
}
// 怎么触发?
// 主要是一个循环进行do_softirq(),再看看 do_softirq()
static int ksoftirqd(void * __bind_cpu)
{
for (;;) {
while (local_softirq_pending()) {
do_softirq();
cond_resched();
}
}
}
// 主要是对软中断向量表的各个位进行判断,并选择是否执行对应处理函数
asmlinkage void do_softirq(void)
{
__u32 pending = local_softirq_pending();
struct softirq_action *h = softirq_vec; // 故名思意:软中断向量表
do {
if (pending & 1) // 如果中断标志位置位
h->action(h); // 调用处理函数
h++; // 下一个中断处理函数
pending >>= 1; // 下一个中断标志位
} while (pending);
}
// 看看软中断标志相关
typedef struct {
unsigned int __softirq_pending;
unsigned long idle_timestamp;
unsigned int __nmi_count; /* arch dependent */
unsigned int apic_timer_irqs; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
#define local_softirq_pending() irq_stat[0].__softirq_pending // 展开相关宏
// 看看软中断向量表相关结构
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
可以发现,软中断标志位及向量表最多同时支持32项。下面是已有的:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
SCSI_SOFTIRQ,
TASKLET_SOFTIRQ
};
软中断系统提供下面的接口:
asmlinkage void do_softirq(void);
// 注册中断
extern void open_softirq(int nr, void (*action)(struct softirq_action*), void *data);
extern void softirq_init(void);
// 触发中断:实际上就是将对应的中断标志位置位
#define __raise_softirq_irqoff(nr) do { local_softirq_pending() |= 1UL << (nr); } while (0)
extern void FASTCALL(raise_softirq_irqoff(unsigned int nr));
extern void FASTCALL(raise_softirq(unsigned int nr));
#ifndef invoke_softirq
#define invoke_softirq() do_softirq()
#endif