Linux中断初始化init_IRQ的实现
中断初始化init_IRQ
void __init init_IRQ(void)
{int i;/* all the set up before the call gates are initialised */pre_intr_init_hook();/** Cover the whole vector space, no vector can escape* us. (some of these will be overridden and become* 'special' SMP interrupts)*/for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {int vector = FIRST_EXTERNAL_VECTOR + i;if (i >= NR_IRQS)break;if (vector != SYSCALL_VECTOR)set_intr_gate(vector, interrupt[i]);}/* setup after call gates are initialised (usually add in* the architecture specific gates)*/intr_init_hook();/** Set the clock to HZ Hz, we already have a valid* vector now:*/setup_pit_timer();/** External FPU? Set up irq13 if so, for* original braindamaged IBM FERR coupling.*/if (boot_cpu_data.hard_math && !cpu_has_fpu)setup_irq(FPU_IRQ, &fpu_irq);irq_ctx_init(smp_processor_id());
}
1. 函数功能
初始化x86系统的中断处理机制,设置中断描述符表,配置硬件中断控制器,为系统中断处理建立基础设施
2. 代码详细解释
2.1. 函数定义和变量声明
void __init init_IRQ(void)
{int i;
void __init
:没有返回值,__init
表示该函数只在系统初始化阶段使用init_IRQ
:函数名,初始化中断请求int i
:循环计数器,用于遍历中断向量
2.2. 前置中断初始化钩子
/* all the set up before the call gates are initialised */pre_intr_init_hook();
- 在调用门初始化之前的所有设置
pre_intr_init_hook()
:架构特定的前置中断初始化钩子函数- 通常用于在设置中断门之前进行必要的架构特定准备工作
2.3. 设置中断向量门
/** Cover the whole vector space, no vector can escape* us. (some of these will be overridden and become* 'special' SMP interrupts)*/for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
- 覆盖整个向量空间
循环控制:
i = 0
:从第一个外部向量开始i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR)
:遍历所有外部向量NR_VECTORS
:总向量数量(通常256)FIRST_EXTERNAL_VECTOR
:第一个外部向量号(通常32)
2.4. 计算向量号和边界检查
int vector = FIRST_EXTERNAL_VECTOR + i;if (i >= NR_IRQS)break;
vector = FIRST_EXTERNAL_VECTOR + i
:计算实际的中断向量号if (i >= NR_IRQS)
:如果超过支持的IRQ数量则退出循环NR_IRQS
:系统支持的IRQ总数
2.5. 设置中断门(排除系统调用向量)
if (vector != SYSCALL_VECTOR)set_intr_gate(vector, interrupt[i]);}
if (vector != SYSCALL_VECTOR)
:排除系统调用向量SYSCALL_VECTOR
:系统调用中断向量(通常0x80)set_intr_gate(vector, interrupt[i])
:设置中断门vector
:中断向量号interrupt[i]
:对应的中断处理函数地址
2.6. 后置中断初始化钩子
/* setup after call gates are initialised (usually add in* the architecture specific gates)*/intr_init_hook();
- 在调用门初始化之后的设置
intr_init_hook()
:架构特定的后置中断初始化钩子函数- 用于设置架构特定的中断门或进行后续初始化
2.7. 配置PIT定时器
/** Set the clock to HZ Hz, we already have a valid* vector now:*/setup_pit_timer();
- 将时钟设置为HZ,现在已经有了有效的向量
setup_pit_timer()
:设置可编程间隔定时器(PIT)- 配置定时器中断频率(通常100Hz或1000Hz)
- 为系统提供时间滴答和调度时间片
2.8. 设置FPU中断
/** External FPU? Set up irq13 if so, for* original braindamaged IBM FERR coupling.*/if (boot_cpu_data.hard_math && !cpu_has_fpu)setup_irq(FPU_IRQ, &fpu_irq);
boot_cpu_data.hard_math
:CPU支持数学协处理器!cpu_has_fpu
:但没有内置FPU(使用外部协处理器)setup_irq(FPU_IRQ, &fpu_irq)
:设置FPU中断FPU_IRQ
:FPU中断号(通常13)&fpu_irq
:FPU中断处理结构
2.9. 初始化中断上下文
irq_ctx_init(smp_processor_id());
}
irq_ctx_init(smp_processor_id())
:初始化当前CPU的中断上下文smp_processor_id()
:获取当前CPU的ID- 为中断处理准备每CPU的堆栈和上下文信息
3. 中断向量空间布局
x86中断向量分配:
0-31: 处理器异常(除零、页错误等)
32: 可编程中断控制器(PIC)IRQ0 - 定时器
33: PIC IRQ1 - 键盘
34-47: PIC IRQ2-15 - 其他设备
48-255: 其他用途(APIC、MSI、系统调用等)
4. 关键配置详解
中断门设置:
// set_intr_gate 设置的中断门特性:
- 特权级0:只有内核可以调用
- 中断禁用:进入处理程序时自动禁用中断
- 使用内核代码段
PIT定时器配置:
// setup_pit_timer() 配置:
- 中断频率:HZ(通常100或1000Hz)
- 通道0:系统定时器
- 模式3:方波发生器
- 为系统调度提供时间基准
配置PIT定时器setup_pit_timer
void setup_pit_timer(void)
{extern spinlock_t i8253_lock;unsigned long flags;spin_lock_irqsave(&i8253_lock, flags);outb_p(0x34,PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */udelay(10);outb_p(LATCH & 0xff , PIT_CH0); /* LSB */udelay(10);outb(LATCH >> 8 , PIT_CH0); /* MSB */spin_unlock_irqrestore(&i8253_lock, flags);
}
1. 函数功能
配置PIT定时器,设置定时器的工作模式和频率,为系统提供时间滴答中断
2. 代码详细解释
2.1. 函数定义
void setup_pit_timer(void)
void
:没有返回值setup_pit_timer
:函数名,设置PIT定时器
2.2. 外部变量声明
extern spinlock_t i8253_lock;
extern spinlock_t i8253_lock
:声明外部定义的自旋锁变量i8253_lock
是8253/8254 PIT芯片的访问锁,防止并发访问- 8253是8254的前身,软件兼容
2.3. 局部变量声明
unsigned long flags;
unsigned long flags
:用于保存中断状态的变量- 在锁操作中用于保存和恢复中断使能状态
2.4. 获取锁并保存中断状态
spin_lock_irqsave(&i8253_lock, flags);
spin_lock_irqsave(&i8253_lock, flags)
:获取自旋锁并保存当前中断状态&i8253_lock
:指向PIT锁的指针flags
:用于保存中断状态的变量- 作用:防止其他CPU或中断上下文同时访问PIT寄存器
2.5. 设置定时器模式
outb_p(0x34,PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */
命令字 0x34 的二进制分解:
0x34 = 00110100 (二进制)
位7-6: 00 = 选择通道0
位5-4: 11 = 读写先低字节后高字节
位3-1: 010 = 模式2 (速率发生器)
位0: 0 = 二进制计数
参数:
outb_p
:带暂停的端口输出(确保I/O操作完成)0x34
:模式控制字PIT_MODE
:控制字寄存器端口(通常0x43)
2.6. 短暂延迟
udelay(10);
udelay(10)
:延迟10微秒- 确保PIC有足够时间处理上一个命令
- 避免背靠背的I/O操作导致问题
2.7. 写入定时器值的低字节
outb_p(LATCH & 0xff , PIT_CH0); /* LSB */
outb_p
:带暂停的端口输出LATCH & 0xff
:定时器值的低8位PIT_CH0
:通道0数据端口(通常0x40)LATCH
:预定义的定时器计数值,决定中断频率
2.8. 再次延迟
udelay(10);
- 再次延迟10微秒,确保低字节写入完成
2.9. 写入定时器值的高字节
outb(LATCH >> 8 , PIT_CH0); /* MSB */
outb
:普通端口输出(不需要暂停,因为后面就是解锁)LATCH >> 8
:定时器值的高8位PIT_CH0
:通道0数据端口(通常0x40)
2.10. 释放锁并恢复中断状态
spin_unlock_irqrestore(&i8253_lock, flags);
}
spin_unlock_irqrestore(&i8253_lock, flags)
:释放锁并恢复中断状态&i8253_lock
:指向PIT锁的指针flags
:之前保存的中断状态- 作用:恢复系统到锁获取之前的状态
3. 定时器模式详解
模式2(速率发生器):
// 工作方式:
1. 从初始值开始递减计数
2. 达到1时输出一个时钟宽度的低脉冲
3. 自动重新加载初始值
4. 产生周期性的中断// 特点:
- 自动重载,无需软件干预
- 产生稳定的周期性中断
- 适合系统定时器用途
4. 系统影响
配置后的效果:
// 定时器开始工作后:
1. 每1/HZ秒产生一次中断(IRQ0)
2. 内核定时器中断处理程序被调用
3. 更新系统时间(jiffies)
4. 执行定时器回调
5. 进行进程调度
中断上下文栈初始化irq_ctx_init
void irq_ctx_init(int cpu)
{union irq_ctx *irqctx;if (hardirq_ctx[cpu])return;irqctx = (union irq_ctx*) &hardirq_stack[cpu*THREAD_SIZE];irqctx->tinfo.task = NULL;irqctx->tinfo.exec_domain = NULL;irqctx->tinfo.cpu = cpu;irqctx->tinfo.preempt_count = HARDIRQ_OFFSET;irqctx->tinfo.addr_limit = MAKE_MM_SEG(0);hardirq_ctx[cpu] = irqctx;irqctx = (union irq_ctx*) &softirq_stack[cpu*THREAD_SIZE];irqctx->tinfo.task = NULL;irqctx->tinfo.exec_domain = NULL;irqctx->tinfo.cpu = cpu;irqctx->tinfo.preempt_count = SOFTIRQ_OFFSET;irqctx->tinfo.addr_limit = MAKE_MM_SEG(0);softirq_ctx[cpu] = irqctx;printk("CPU %u irqstacks, hard=%p soft=%p\n",cpu,hardirq_ctx[cpu],softirq_ctx[cpu]);
}
1. 函数功能
为指定CPU初始化硬中断和软中断的专用堆栈,建立中断处理的基础设施
2. 代码详细解释
2.1. 函数定义
void irq_ctx_init(int cpu)
void
:没有返回值irq_ctx_init
:函数名,中断上下文初始化int cpu
:要初始化中断堆栈的CPU编号
2.2. 局部变量声明
union irq_ctx *irqctx;
union irq_ctx *irqctx
:中断上下文联合体指针- 这个联合体包含线程信息和其他上下文数据
2.3. 检查是否已初始化
if (hardirq_ctx[cpu])return;
if (hardirq_ctx[cpu])
:检查该CPU的硬中断上下文是否已存在- 如果已经初始化过,直接返回,避免重复初始化
hardirq_ctx[]
:全局数组,存储每个CPU的硬中断上下文指针
2.4. 获取硬中断堆栈地址
irqctx = (union irq_ctx*) &hardirq_stack[cpu*THREAD_SIZE];
hardirq_stack
:硬中断堆栈数组cpu*THREAD_SIZE
:计算该CPU在堆栈数组中的偏移THREAD_SIZE
:每个线程堆栈的大小(union irq_ctx*)
:将堆栈地址转换为中断上下文指针
2.5. 初始化硬中断上下文
irqctx->tinfo.task = NULL;irqctx->tinfo.exec_domain = NULL;irqctx->tinfo.cpu = cpu;irqctx->tinfo.preempt_count = HARDIRQ_OFFSET;irqctx->tinfo.addr_limit = MAKE_MM_SEG(0);
task = NULL
- 中断上下文不属于任何具体的任务
- 表示这是中断处理堆栈,不是进程堆栈
exec_domain = NULL
- 执行域为空,中断处理使用内核默认执行域
cpu = cpu
- 记录这个中断上下文属于哪个CPU
preempt_count = HARDIRQ_OFFSET
- 设置抢占计数为硬中断偏移量
- 表示当前处于硬中断上下文,禁止抢占
addr_limit = MAKE_MM_SEG(0)
- 设置地址限制为内核空间(0)
- 中断处理程序只能访问内核地址空间
2.6. 保存硬中断上下文指针
hardirq_ctx[cpu] = irqctx;
- 将初始化好的硬中断上下文指针保存到全局数组中
- 后续中断处理时可以通过这个指针快速访问
2.7. 获取软中断堆栈地址
irqctx = (union irq_ctx*) &softirq_stack[cpu*THREAD_SIZE];
softirq_stack
:软中断堆栈数组- 同样计算该CPU在软中断堆栈数组中的位置
- 准备初始化软中断上下文
2.8. 初始化软中断上下文
irqctx->tinfo.task = NULL;irqctx->tinfo.exec_domain = NULL;irqctx->tinfo.cpu = cpu;irqctx->tinfo.preempt_count = SOFTIRQ_OFFSET;irqctx->tinfo.addr_limit = MAKE_MM_SEG(0);
preempt_count = SOFTIRQ_OFFSET
:软中断偏移量- 表示处于软中断上下文,有不同于硬中断的抢占规则
2.9. 保存软中断上下文指针
softirq_ctx[cpu] = irqctx;
- 保存软中断上下文指针到全局数组
- 软中断处理时使用这个堆栈
2.10. 打印调试信息
printk("CPU %u irqstacks, hard=%p soft=%p\n",cpu,hardirq_ctx[cpu],softirq_ctx[cpu]);
}
- 输出CPU的中断堆栈信息
CPU %u
:CPU编号hard=%p
:硬中断堆栈地址soft=%p
:软中断堆栈地址- 用于调试和验证初始化结果
3. 为什么需要独立的中断堆栈?
堆栈隔离
// 不使用独立堆栈的问题:
- 中断可能发生在任何进程的堆栈上
- 中断处理可能耗尽进程堆栈空间
- 导致堆栈溢出或数据损坏// 使用独立堆栈:
- 中断有专用的堆栈空间
- 不会影响进程堆栈
- 提高系统可靠性
性能优化
// 独立堆栈的好处:
- 更好的缓存局部性
- 减少堆栈切换开销
- 中断处理更高效
调试支持
// 通过堆栈区分:
- 可以清晰识别中断上下文
- 堆栈跟踪更清晰
- 便于问题诊断
4. 实际使用场景
硬中断处理:
// 当硬件中断发生时:
1. CPU自动切换到硬中断堆栈
2. preempt_count设置为HARDIRQ_OFFSET
3. 执行中断处理程序
4. 完成后恢复原堆栈
软中断处理:
// 当处理软中断时:
1. 切换到软中断堆栈
2. preempt_count设置为SOFTIRQ_OFFSET
3. 执行软中断处理程序
4. 完成后恢复
设置中断处理程序setup_irq
int setup_irq(unsigned int irq, struct irqaction * new)
{struct irq_desc *desc = irq_desc + irq;struct irqaction *old, **p;unsigned long flags;int shared = 0;if (desc->handler == &no_irq_type)return -ENOSYS;/** Some drivers like serial.c use request_irq() heavily,* so we have to be careful not to interfere with a* running system.*/if (new->flags & SA_SAMPLE_RANDOM) {/** This function might sleep, we want to call it first,* outside of the atomic block.* Yes, this might clear the entropy pool if the wrong* driver is attempted to be loaded, without actually* installing a new handler, but is this really a problem,* only the sysadmin is able to do this.*/rand_initialize_irq(irq);}/** The following block of code has to be executed atomically*/spin_lock_irqsave(&desc->lock,flags);p = &desc->action;if ((old = *p) != NULL) {/* Can't share interrupts unless both agree to */if (!(old->flags & new->flags & SA_SHIRQ)) {spin_unlock_irqrestore(&desc->lock,flags);return -EBUSY;}/* add new interrupt at end of irq queue */do {p = &old->next;old = *p;} while (old);shared = 1;}*p = new;if (!shared) {desc->depth = 0;desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT |IRQ_WAITING | IRQ_INPROGRESS);if (desc->handler->startup)desc->handler->startup(irq);elsedesc->handler->enable(irq);}spin_unlock_irqrestore(&desc->lock,flags);new->irq = irq;register_irq_proc(irq);new->dir = NULL;register_handler_proc(irq, new);return 0;
}
1. 函数功能
注册中断处理程序到指定的IRQ线,支持中断共享,管理中断处理链
2. 代码详细解释
2.1. 函数定义和变量声明
int setup_irq(unsigned int irq, struct irqaction * new)
{struct irq_desc *desc = irq_desc + irq;struct irqaction *old, **p;unsigned long flags;int shared = 0;
int
:返回错误码,0表示成功setup_irq
:函数名,设置中断处理程序unsigned int irq
:中断号struct irqaction * new
:新的中断处理动作结构desc = irq_desc + irq
:获取该IRQ的描述符指针old, **p
:用于遍历现有中断处理链的指针flags
:保存中断状态的变量shared
:共享中断标志,初始为0(非共享)
2.2. 检查IRQ是否可用
if (desc->handler == &no_irq_type)return -ENOSYS;
desc->handler == &no_irq_type
:检查该IRQ是否有有效的中断控制器处理no_irq_type
:表示该IRQ不可用的虚拟处理程序return -ENOSYS
:如果IRQ不可用,返回"功能未实现"错误
2.3. 随机数熵池初始化
if (new->flags & SA_SAMPLE_RANDOM) {rand_initialize_irq(irq);}
new->flags & SA_SAMPLE_RANDOM
:检查是否使用该中断作为随机数源rand_initialize_irq(irq)
:初始化该IRQ的随机数熵池- 在锁外调用:因为这个函数可能睡眠
2.4. 开始原子操作
/** The following block of code has to be executed atomically*/spin_lock_irqsave(&desc->lock,flags);
spin_lock_irqsave(&desc->lock,flags)
:获取IRQ描述符锁并保存中断状态- 确保中断处理链的修改是原子的
2.5. 遍历中断处理链
p = &desc->action;if ((old = *p) != NULL) {
p = &desc->action
:指向IRQ处理链的头指针(old = *p) != NULL
:检查是否已有处理程序注册- 如果
old
不为NULL,表示该IRQ已经有处理程序
2.6. 检查中断共享兼容性
/* Can't share interrupts unless both agree to */if (!(old->flags & new->flags & SA_SHIRQ)) {spin_unlock_irqrestore(&desc->lock,flags);return -EBUSY;}
!(old->flags & new->flags & SA_SHIRQ)
:检查新旧处理程序是否都支持共享- 如果任一不支持共享,返回
-EBUSY
(设备忙错误) - 先释放锁再返回,因为这是错误路径
2.7. 找到处理链末尾
/* add new interrupt at end of irq queue */do {p = &old->next;old = *p;} while (old);shared = 1;}
循环操作:
p = &old->next
:移动到下一个节点的指针地址old = *p
:获取下一个节点while (old)
:循环直到找到链表末尾(NULL)shared = 1
:设置共享标志为真
2.8. 插入新的处理程序
*p = new;
*p = new
:在链表末尾插入新的中断处理程序- 此时
p
指向最后一个节点的next
指针地址
2.9. 非共享中断的初始化
if (!shared) {desc->depth = 0;desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT |IRQ_WAITING | IRQ_INPROGRESS);if (desc->handler->startup)desc->handler->startup(irq);elsedesc->handler->enable(irq);}
非共享中断的特殊处理:
desc->depth = 0
:重置禁用深度计数器desc->status &= ~(...)
:清除各种状态标志IRQ_DISABLED
:中断禁用IRQ_AUTODETECT
:自动检测IRQ_WAITING
:等待状态IRQ_INPROGRESS
:处理中状态
- 调用中断控制器的启动或启用函数
2.10. 释放锁
spin_unlock_irqrestore(&desc->lock,flags);
spin_unlock_irqrestore(&desc->lock,flags)
:释放锁并恢复中断状态- 原子操作区域结束
2.11. 设置IRQ编号和注册proc文件
new->irq = irq;register_irq_proc(irq);new->dir = NULL;register_handler_proc(irq, new);return 0;
}
new->irq = irq
:在irqaction
结构中记录IRQ编号register_irq_proc(irq)
:在/proc文件系统中注册IRQ信息new->dir = NULL
:初始化目录指针register_handler_proc(irq, new)
:注册处理程序到proc文件系统return 0
:成功返回
3. 总结
setup_irq
函数的作用是:
- 中断处理程序注册:将驱动提供的中断处理函数注册到指定IRQ
- 共享中断管理:支持多个设备共享同一IRQ线
- 原子性操作:确保中断处理链的修改是线程安全的
- 硬件协调:与中断控制器交互启用/禁用中断
- 系统集成:注册proc文件系统接口用于调试和管理
- 随机数支持:为需要熵源的IRQ初始化随机数池