本文为速成教材,非精细研究,适合想了解linux中断机制,和从事linux驱动开发或者调式IRQ相关的驱动的工程师使用。该文章也没经过osboy的review过,属于一遍速成之回忆录,所以会存在问题,请跟帖指出,我会一一回复,并整理出最终版本。注意转贴的时候你可以不加上作者的名字,但是最好加上我的这些话,因为我们要对读文章的人负责人,谢谢。部分语言描述来源于网络拷贝。
1 Linux 中断机制
1.1 认识三个重要的数据结构
1.1.1 中断描述符结构 irq_desc
irq_desc:中断全局描述符
struct irq_desc {
unsigned int
irq; 注释: 表示此中断描述符的中断号
struct timer_rand_state *timer_rand_state; 注释: timer 伪随机数状态结构指针
unsigned int
*kstat_irqs;
#ifdef CONFIG_INTR_REMAP
struct irq_2_iommu
*irq_2_iommu;
#endif
irq_flow_handler_t handle_irq; 注释: 当前中断的内核中断处理函数入口(非用户定义)
struct irq_chip *chip; 注释: 中断的硬件 chip 级描述符,提供底层的硬件访问。具体见下面结构描述
struct msi_desc
*msi_desc;
void
*handler_data;
void
*chip_data;
struct irqaction
*action;
/* IRQ action list */
注释 :标识当出现 IRQ 时要调用的中断服务例程。该字段指向 IRQ 的 irqaction 链表的第一个元素。
unsigned int
status;
/* IRQ status */
注释 :描述 IRQ 线状态的一组标志
unsigned int
depth;
/* nested irq disables */
注释 :如果 IRQ 线被激活,则显示 0 ,如果 IRQ 线被禁止了不止一次,则显示一个正数。
unsigned int
wake_depth;
/* nested wake enables */
unsigned int
irq_count;
/* For detecting broken IRQs */
unsigned long
last_unhandled;
/* Aging timer for unhandled count */
unsigned int
irqs_unhandled;
raw_spinlock_t
lock;
#ifdef CONFIG_SMP
cpumask_var_t
affinity;
const struct cpumask
*affinity_hint;
unsigned int
node;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t
pending_mask;
#endif
#endif
atomic_t
threads_active;
wait_queue_head_t
wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry
*dir;
#endif
const char
*name;
} ____cacheline_internodealigned_in_smp;
Linux 内核将所有的中断统一编号,使用一个 irq_desc 结构数组来描述这些中断 ; 每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。
1.1.2
中断服务描述符 irqaction irqaction:每中断服务描述符
struct irqaction {
irq_handler_t handler; 注释: 用户定义的当前中断的驱动中断处理函数
unsigned long flags; 注释:中断标志
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};
irq_desc 结构中的 irqaction 结构类型在 include/linux/iterrupt.h 中定义。用户注册的每个中断
处理函数用一个 irqaction 结构来表示,一个中断比如共享中断可以有多个处理函数,它们的 irqaction 结构链接成一个链表,以 action 为表头。他是在用户 request_irq 的时候第一次创建并被初始化。
1.1.3
中断硬件描述符 irq_chip irq_chip 结构类型是在 include/linux/irq.h 中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。所以我在这里称他为中断硬件描述符,主要是提供硬件操作的 API 。
irq_chip:
struct irq_chip {
const char
*name; 注释 :定义名字,通过 /proc/interrupts 可以查看这个名字
unsigned int
(*startup)(unsigned int irq);
void
(*shutdown)(unsigned int irq);
void
(*enable)(unsigned int irq); ; 注释 :使能 IRQ, 默认使能
void
(*disable)(unsigned int irq);
void
(*ack)(unsigned int irq); 注释 : irq 的全局 ack ,需要根据硬件设置必须实现 通常是清除当前中断使得可以接收下一个中断。
void
(*mask)(unsigned int irq); 注释 :屏蔽中断
void
(*mask_ack)(unsigned int irq);
void
(*unmask)(unsigned int irq); 注释 :非屏蔽中断
void
(*eoi)(unsigned int irq);
void
(*end)(unsigned int irq);
int
(*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int
(*retrigger)(unsigned int irq);
int
(*set_type)(unsigned int irq, unsigned int flow_type);
int
(*set_wake)(unsigned int irq, unsigned int on);
void
(*bus_lock)(unsigned int irq);
void
(*bus_sync_unlock)(unsigned int irq);
/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void
(*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char
*typename;
};
1.2
Request_irq开始说起 我们知道用户驱动程序通过 request_irq 函数向内核注册中断处理函数, request_irq 函数根据中断号找到 irq_desc 数组项,然后在它的 action 链表添加一个表项。
error = request_irq(keypad->irq, keypad_irq_handler,
IRQF_DISABLED, pdev->name, keypad);
if (error) {
dev_err(&pdev->dev, "failed to request IRQ\n");
goto failed_put_clk;
}
看上面这段代码是我们在写驱动的时候需要申请 IRQ 号的时候需要写的一段标准 code 。 request_irq 的原型为:
static inline int __must_check
request_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);
}
所以我们写驱动的时候对应的各个参数:
keypad->irq à unsigned int irq, 中断号
keypad_irq_handler à irq_handler_t handler 用户自定义的中断处理函数
IRQF_DISABLED à IRQF_DISABLED, linux 定义的中断 flags 如下:
#define IRQF_DISABLED
0x00000020 注释: 当前 irq 处理的时候保持 irq 禁止
#define IRQF_SAMPLE_RANDOM
0x00000040
#define IRQF_SHARED
0x00000080 注释: 允许在多个设备之间共享 irq 号
#define IRQF_PROBE_SHARED
0x00000100
#define IRQF_TIMER
0x00000200 注释: 标志该中断是 timer interrupt
#define IRQF_PERCPU
0x00000400
#define IRQF_NOBALANCING
0x00000800
#define IRQF_IRQPOLL
0x00001000
#define IRQF_ONESHOT
0x00002000
pdev->name à const char *name
keypad à void *dev
request_irq 调用了函数 request_threaded_irq ,他的原型为:
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*/
if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;
注释: 前面的 log 已经说的很明确,当多个设备共享 irq 的时候,我们必须通过 dev_id 来识别是哪一个设备来的中断,所以当 flag 被标志为: RQF_SHARED 共享的时候,我们必须确保 dev_id 非空。
desc = irq_to_desc(irq);
irq_to_desc 的原型如下:
注释:
struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
是通过 irq 号查找 irq_desc 数组,来确定对应 irq 的 irq_desc[irq].
irq_desc 全局数组对应的定义如下:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.chip = &no_irq_chip,
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
NR_IRQS 一般会重定义在 arch./arm/mach-xxxx/include/mach/irqs.h 下面。
if (!desc)
return -EINVAL; 注释: 如果没找到相应的 irq_desc 表示出错了。
if (desc->status & IRQ_NOREQUEST)
return -EINVAL;
注释 : irq_desc.status 初始化的时候设置为: IRQ_NOREQUEST 不可被 request 状态,则这里做判断确保不能被 request 。
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
注释 :如果在驱动中 request irq 的时候没定义自己的 handler ,则至少 thread_fn 应该被定义否则报错返回 EINVAL 。同时这段 code 还赋值一个某人 handler 给驱动程序。
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
注释 : kzalloc 一个 irqaction 结构体,并用驱动调用 request_irq 时候传进去的 handler , irqflags , devname 和 dev_id 初始化这个结构体。
chip_bus_lock(irq, desc);
注释:
这个函数的原型:
/* Inline functions for support of irq chips on slow busses */
static inline void chip_bus_lock(unsigned int irq, struct irq_desc *desc)
{
if (unlikely(desc->chip->bus_lock))
desc->chip->bus_lock(irq);
}
从 linux 给出的解释看出是为了在低速设备上的 irq 用的,就是实现了 mutex 。具体实现是在
在定义 arch/arm/mach-xxx/irq.c 中定义:
static struct irq_chip xxx_irq_chip = {
.ack
= xxx_irq_ack,
.mask
= xxx_irq_mask,
.unmask
= xxx_irq_unmask,
};
的时候有:
void
(*bus_lock)(unsigned int irq);
void
(*bus_sync_unlock)(unsigned int irq);
这两个函数的实现。
retval = __setup_irq(irq, desc, action);
注释:setup irq函数是是一个重要的函数,我们将在下面进行具体讲解。
chip_bus_sync_unlock(irq, desc);
if (retval)
kfree(action);
#ifdef CONFIG_DEBUG_SHIRQ
if (!retval && (irqflags & IRQF_SHARED)) {
/*
* It's a shared IRQ -- the driver ought to be prepared for it
* to happen immediately, so let's make sure....
* We disable the irq to make sure that a 'real' IRQ doesn't
* run in parallel with our fake.
*/
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval;
}
EXPORT_SYMBOL(request_threaded_irq);
__setup_irq函数:
/*
* Internal function to register an irqaction - typically used to
* allocate special interrupts that are part of the architecture.
*/
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
const char *old_name = NULL;
unsigned long flags;
int nested, shared = 0;
int ret;
if (!desc)
return -EINVAL;
if (desc->chip == &no_irq_chip)
return -ENOSYS;
注释 :这里判断 chip 是否被初始化,一般在 arch/arm/mach-xxx/irq.c 的 xxx_init_irq set_irq_chip(irqno, &xxx_irq_chip); 中初始化这个 desc->chip 指针, xxx_irq_chip 定义的结构类似:
static struct irq_chip xxx_irq_chip = {
.ack
= xxx_irq_ack,
.mask
= xxx_irq_mask,
.unmask
= xxx_irq_unmask,
};
/*
* 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 & IRQF_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);
}
/* Oneshot interrupts are not allowed with shared */
if ((new->flags & IRQF_ONESHOT) && (new->flags & IRQF_SHARED))
return -EINVAL;
注释: Oneshot 类型的中断不允许共享
/*
* Check whether the interrupt nests into another interrupt
* thread.
*/
nested = desc->status & IRQ_NESTED_THREAD;
if (nested) {
if (!new->thread_fn)
return -EINVAL;
/*
* Replace the primary handler which was provided from
* the driver for non nested interrupt handling by the
* dummy function which warns when called.
*/
new->handler = irq_nested_primary_handler;
}
/*
* Create a handler thread when a thread function is supplied
* and the interrupt does not nest into another interrupt
* thread.
*/
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;
}
注释 :由于 new->thread_fn 在驱动调用的 request irq 函数中设置为 NULL ,所以以上代码执行不到,我们暂时不做解释。
/*
* The following block of code has to be executed atomically
*/
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
注释: old_ptr 是 irqaction 结构类型指针的指针。这里把 old_ptr 指向当前 irq 的 irq_desc 成员 action 的指针的地址。
old = *old_ptr;
old 是 irqaction 结构类型指针,这里是把 old 指向当前 irq 的 irq_desc 成员 action 的指针。
if (old) {
/*
* Can't share interrupts unless both agree to and are
* the same type (level, edge, polarity). So both flag
* fields must have IRQF_SHARED set and the bits which
* set the trigger type must match.
*/
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch;
}
#if defined(CONFIG_IRQ_PER_CPU)
/* All handlers must agree on per-cpuness */
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;
#endif
/* add new interrupt at end of irq queue */
do {
old_ptr = &old->next;
old = *old_ptr;
} while (old);
注释: 将新建的 irqaction 结构链入 irq_desc[irq] 结构的 action 链表中,这有两种可能。
如果 action 链表为空,则直接链入 , 否则先判断新建的 irqaction 结构和链表中的 irqaction 结构所表示的中断类型是否一致,即是否都声明为 " 可共享的 " ( IRQF_SHARED) 、是否都使用相同的触发方式,如果一致,则将新建的 irqation 结构链入。
shared = 1;
}
if (!shared) {
irq_chip_set_defaults(desc->chip);
注释 :初始化一些默认的 chip 配置
init_waitqueue_head(&desc->wait_for_threads);
/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc, irq,
new->flags & IRQF_TRIGGER_MASK);
if (ret)
goto out_thread;
} else
compat_irq_chip_set_default_handler(desc);
#if defined(CONFIG_IRQ_PER_CPU)
if (new->flags & IRQF_PERCPU)
desc->status |= IRQ_PER_CPU;
#endif
desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_ONESHOT |
IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);
if (new->flags & IRQF_ONESHOT)
desc->status |= IRQ_ONESHOT;
if (!(desc->status & IRQ_NOAUTOEN)) {
desc->depth = 0;
desc->status &= ~IRQ_DISABLED;
desc->chip->startup(irq);
} else
/* Undo nested disables: */
desc->depth = 1;
/* Exclude IRQ from balancing if requested */
if (new->flags & IRQF_NOBALANCING)
desc->status |= IRQ_NO_BALANCING;
/* Set default affinity mask once everything is setup */
setup_affinity(irq, desc);
} else if ((new->flags & IRQF_TRIGGER_MASK)
&& (new->flags & IRQF_TRIGGER_MASK)
!= (desc->status & IRQ_TYPE_SENSE_MASK)) {
/* hope the handler works with the actual trigger mode... */
pr_warning("IRQ %d uses trigger mode %d; requested %d\n",
irq, (int)(desc->status & IRQ_TYPE_SENSE_MASK),
(int)(new->flags & IRQF_TRIGGER_MASK));
}
new->irq = irq;
*old_ptr = new;
注释 :把新建的 irqation 结构链入 irq_desc 中的 IRQ action list ( struct irqaction
*action; )中。
/* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;
注释 :如果一个中断内核没有处理,那么这个中断就是意外中断,也就是说,与某个 IRQ 线相关的中断处理例程( ISR )不存在,或者与某个中断线相关的所有例程都识别不出是否是自己的硬件设备发出的中断。通常,内核检查从 IRQ 线接收的意外中断的数量,当这条 IRQ 线的有故障设备没完没了的发中断时,就禁用这条 IRQ 线,内核不会在每监测到一个意外中断时就立刻禁用 IRQ 线。由于几个设备可能共享 IRQ 线,更合适的办法是:内核把中断和意外中断的总次数分别放在 irq_desc 描述符的 irq_count 和 irqs_unhandled 字段中,当第 100000 次中断产生时,如果意外中断的次数超过 99900 次内核才禁用这条 IRQ 线。
/*
* Check whether we disabled the irq via the spurious handler
* before. Reenable it and give it another chance.
*/
if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
desc->status &= ~IRQ_SPURIOUS_DISABLED;
__enable_irq(desc, irq, false);
}
raw_spin_unlock_irqrestore(&desc->lock, flags);
/*
* Strictly no need to wake it up, but hung_task complains
* when no hard interrupt wakes the thread up.
*/
if (new->thread)
wake_up_process(new->thread);
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);
return 0;
mismatch:
#ifdef CONFIG_DEBUG_SHIRQ
if (!(new->flags & IRQF_PROBE_SHARED)) {
printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", irq);
if (old_name)
printk(KERN_ERR "current handler: %s\n", old_name);
dump_stack();
}
#endif
ret = -EBUSY;
out_thread:
raw_spin_unlock_irqrestore(&desc->lock, flags);
if (new->thread) {
struct task_struct *t = new->thread;
new->thread = NULL;
if (likely(!test_bit(IRQTF_DIED, &new->thread_flags)))
kthread_stop(t);
put_task_struct(t);
}
return ret;
}
1.3
IRQ 相关数据结构初始化 硬件相关的操作接口的实现是在 arch/arm/mach-xxx/irq.c 中进行,类似于下面的例子,他一般初始化 irq_chip , irq_desc 结构体的一些变量,并具体实现了操作硬件部分的函数。
static void xxx _irq_mask(unsigned int irq)
{
__raw_writel(1 << irq, REG_AIC_MDCR);
}
static void xxx _irq_ack(unsigned int irq)
{
__raw_writel(0x01, REG_AIC_EOSCR);
}
static void xxx _irq_unmask(unsigned int irq)
{
__raw_writel(1 << irq, REG_AIC_MECR);
}
static struct irq_chip xxx _irq_chip = {
.ack
= xxx _irq_ack,
.mask
= xxx _irq_mask,
.unmask
= xxx _irq_unmask,
};
注释 :
通过 for 循环遍历整理系统支持的 irq 中断号, NR_IRQS 一般是定义最后一个 irq 号 +1 。
Kernel/irq/chip.c 中定义了一些函数用来初始化 irq_chip , irq_desc 中的成员变量,例如设置 irq chip , irq 触发类型和根据触发类型设置的 irq handler 。
典型的使用方法是:
handle_level_irq— 电平触发
handle_edge_irq— 边沿触发
void __init xxx _init_irq(void)
{
int irqno;
for (irqno = IRQ_WDT; irqno < NR_IRQS; irqno++) {
set_irq_chip(irqno, &xxx_irq_chip); 注释 :主要做的工作就是初始化 desc->chip = chip;
set_irq_handler(irqno, handle_level_irq); 注释 :主要做的工作就是初始化 desc->handle_irq = handle; desc->name = name;
set_irq_flags(irqno, IRQF_VALID); 注释 :主要做的工作就是初始化 desc->status
}
}
那么 xxx _init_irq(void) 函数什么时候调用呢?
这个还得看一个数据结构:
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int
nr;
/* architecture number
*/
unsigned int
phys_io;
/* start of physical io
*/
unsigned int
io_pg_offst;
/* byte offset for io
* page tabe entry
*/
const char
*name;
/* architecture name
*/
unsigned long
boot_params;
/* tagged list
*/
unsigned int
video_start;
/* start of video RAM
*/
unsigned int
video_end;
/* end of video RAM
*/
unsigned int
reserve_lp0 :1;
/* never has lp0
*/
unsigned int
reserve_lp1 :1;
/* never has lp1
*/
unsigned int
reserve_lp2 :1;
/* never has lp2
*/
unsigned int
soft_reboot :1;
/* soft reboot
*/
void
(*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void
(*map_io)(void);/* IO mapping function
*/
void
(*init_irq)(void);
struct sys_timer
*timer;
/* system tick timer
*/
void
(*init_machine)(void);
};
其中蓝色加粗函数就是定义了一个函数指针,一般来说我们在移植 linux 内核到某款 arm 板的时候我们需要实现一个针对板子初始化的第一个 c 文件 mach-xxxboard.c ,在这个 c 文件中我们需要实现以下 code :
MACHINE_START(XXXEVB, "XXXEVB")
/* Maintainer: Wan ZongShun */
.phys_io
= XXX_PA_UART,
.io_pg_offst
= (((u32)XXX_VA_UART) >> 18) & 0xfffc,
.boot_params
= 0,
.map_io
=xxxevb_map_io,
.init_irq
= xxx_init_irq,
.init_machine
= xxxevb_init,
.timer
= &xxx_timer,
MACHINE_END
我们知道 linux 的 c 语言第一个入口函数为 asmlinkage void __init start_kernel(void) ,
在这个函数中有调用函数:
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
注释 : linux 首先设置 irq_desc[irq].status 此时的状态为不可探测和不可 request 的。这个字段当然会被后续我们 user 的初始化重载的。
init_arch_irq();
该函数是在start_kernel(void)函数中的setup_arch(char **cmdline_p) 函数中有调用一段code:init_arch_irq = mdesc->init_irq;,从而把我们的xxx_init_irq函数地址赋值给init_arch_irq,从而在这里开始就直接执行我们自己的xxx_init_irq函数了。
}
1.4
IRQ 中断向量底层实现
异常,就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义指令、试图修改只读的数据、执行swi指令(Software Interrupt Instruction ,软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操作系统中经常通过异常来完成一些特定的功能。其中的中断也占有很大的一部分。例如下面的这几种情况:
当CPU执行未定义的机器指令时将触发“未定义指令异常”,操作系统可以利用这个特点使用一些自定义的机器指令,它们在异常处理函数中实现。
当用户程序试图读写的数据或执行的指令不在内存中时,也会触发一个“数据访问中止异常”或“指令预取中止异常”,在异常处理函数中将这些数据或指令读入内存,然后重新执行被中断的程序,这样可以节省内存,还使得操作系统可以运行这类程序,它们使用的内存远大于实际的物理内存。
在原先的内核版本中,内核在start_kernel函数(源码在init/main.c中)中调用trap_init、init_IRQ两个函数来设置异常和处理函数。在Linux最新的内核版本中,trap_init函数的内容发生了变化,在trap.c中:
void __init trap_init(void)
{
return;
}
可见这个函数已经不起作用了,换成了early_trap_init();函数并且调用时间提前到了start kernel函数的setup函数里面执行。
void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
注释:#define CONFIG_VECTORS_BASE 0xffff0000
首先理解下arm的中断向量,所谓“向量”,就是一些被安放在固定位置的代码,当发生异常时,CPU会自动执行这些固定位置上的指令。ARM架构的CPU的异常向量基址可以是0x00000000,也可以是0xffff0000,Linux内核使用后者。
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
注释:在entry-armv.S中:有code:
.globl
__vectors_start
__vectors_start:
ARM(
swi
SYS_ERROR0
)
THUMB(
svc
#0
)
THUMB(
nop
)
W(b)
vector_und + stubs_offset
W(ldr)
pc, .LCvswi + stubs_offset
W(b)
vector_pabt + stubs_offset
W(b)
vector_dabt + stubs_offset
W(b)
vector_addrexcptn + stubs_offset
W(b)
vector_irq + stubs_offset
W(b)
vector_fiq + stubs_offset
.globl
__vectors_end
__vectors_end:
.globl
__vectors_start,export一个外部标号链接。
从上面这段code可以看出,memcpy就是把上面这段code完整的拷贝到内存地址为:
0xffff0000的地址处。
而这里:
.equ
stubs_offset, __vectors_start + 0x200 - __stubs_start,
所以典型的irq为例:
vector_irq + stubs_offset = (__vectors_start + 0x200)+ (vector_irq - __stubs_start)
__vectors_start这个标号应该是和pc相关的,所以他就表示当前的0xffff0000的地址。
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
__stubs_start, __stubs_end之间的代码是需要跳转的代码,他们被拷贝到vectors + 0x200地址处,也就是说(__vectors_start + 0x200)表示的地址处。(vector_irq - __stubs_start)表示的是从vector_irq到__stubs_start的相对地址,也就是偏移量。所以:
vector_irq + stubs_offset = (__vectors_start + 0x200)+ (vector_irq - __stubs_start)
表示从vectors + 0x200开始加上vector_irq相对于vectors + 0x200开始的偏移量,就是当前
vector_irq的实际地址。
这段跳转代码,我附在了下面:
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
memcpy((void *)KERN_RESTART_CODE, syscall_restart_code,
sizeof(syscall_restart_code));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
中断跳转代码:
.globl
__stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub
irq, IRQ_MODE, 4
注释:vector_stub的宏定义为:
.macro
vector_stub, name, mode, correction=0
.align
5
vector_\name:
.if \correction
sub
lr, lr, #\correction
.endif
……………………………….
………………………………..
所以:vector_stub irq, IRQ_MODE,4相当于:
vector_irq:
.if 4
Sub lr, lr, #4
.endif
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia
sp, {r0, lr}
@ save r0, lr
mrs
lr, spsr
str
lr, [sp, #8]
@ save spsr
@
@ Prepare for SVC32 mode.
IRQs remain disabled.
@
mrs
r0, cpsr
eor
r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr
spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and
lr, lr, #0x0f
THUMB(
adr
r0, 1f
)
THUMB(
ldr
lr, [r0, lr, lsl #2]
)
mov
r0, sp
ARM(
ldr
lr, [pc, lr, lsl #2]
)
movs
pc, lr
@ branch to handler in SVC mode
ENDPROC(vector_\name)
.long
__irq_usr
@
0
(USR_26 / USR_32)
.long
__irq_invalid
@
1
(FIQ_26 / FIQ_32)
.long
__irq_invalid
@
2
(IRQ_26 / IRQ_32)
.long
__irq_svc
@
3
(SVC_26 / SVC_32)
.long
__irq_invalid
@
4
.long
__irq_invalid
@
5
.long
__irq_invalid
@
6
.long
__irq_invalid
@
7
.long
__irq_invalid
@
8
.long
__irq_invalid
@
9
.long
__irq_invalid
@
a
.long
__irq_invalid
@
b
.long
__irq_invalid
@
c
.long
__irq_invalid
@
d
.long
__irq_invalid
@
e
.long
__irq_invalid
@
f
/*
* Data abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stub
dabt, ABT_MODE, 8
.long
__dabt_usr
@
0
(USR_26 / USR_32)
.long
__dabt_invalid
@
1
(FIQ_26 / FIQ_32)
.long
__dabt_invalid
@
2
(IRQ_26 / IRQ_32)
.long
__dabt_svc
@
3
(SVC_26 / SVC_32)
.long
__dabt_invalid
@
4
.long
__dabt_invalid
@
5
.long
__dabt_invalid
@
6
.long
__dabt_invalid
@
7
.long
__dabt_invalid
@
8
.long
__dabt_invalid
@
9
.long
__dabt_invalid
@
a
.long
__dabt_invalid
@
b
.long
__dabt_invalid
@
c
.long
__dabt_invalid
@
d
.long
__dabt_invalid
@
e
.long
__dabt_invalid
@
f
/*
* Prefetch abort dispatcher
* Enter in ABT mode, spsr = USR CPSR, lr = USR PC
*/
vector_stub
pabt, ABT_MODE, 4
.long
__pabt_usr
@
0 (USR_26 / USR_32)
.long
__pabt_invalid
@
1 (FIQ_26 / FIQ_32)
.long
__pabt_invalid
@
2 (IRQ_26 / IRQ_32)
.long
__pabt_svc
@
3 (SVC_26 / SVC_32)
.long
__pabt_invalid
@
4
.long
__pabt_invalid
@
5
.long
__pabt_invalid
@
6
.long
__pabt_invalid
@
7
.long
__pabt_invalid
@
8
.long
__pabt_invalid
@
9
.long
__pabt_invalid
@
a
.long
__pabt_invalid
@
b
.long
__pabt_invalid
@
c
.long
__pabt_invalid
@
d
.long
__pabt_invalid
@
e
.long
__pabt_invalid
@
f
/*
* Undef instr entry dispatcher
* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/
vector_stub
und, UND_MODE
.long
__und_usr
@
0 (USR_26 / USR_32)
.long
__und_invalid
@
1 (FIQ_26 / FIQ_32)
.long
__und_invalid
@
2 (IRQ_26 / IRQ_32)
.long
__und_svc
@
3 (SVC_26 / SVC_32)
.long
__und_invalid
@
4
.long
__und_invalid
@
5
.long
__und_invalid
@
6
.long
__und_invalid
@
7
.long
__und_invalid
@
8
.long
__und_invalid
@
9
.long
__und_invalid
@
a
.long
__und_invalid
@
b
.long
__und_invalid
@
c
.long
__und_invalid
@
d
.long
__und_invalid
@
e
.long
__und_invalid
@
f
.align
5
/*=============================================================================
* Undefined FIQs
*-----------------------------------------------------------------------------
* Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
* MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
* Basically to switch modes, we *HAVE* to clobber one register...
brain
* damage alert!
I don't think that we can execute any code in here in any
* other mode than FIQ...
Ok you can switch to another mode, but you can't
* get out of that mode without clobbering one register.
*/
vector_fiq:
disable_fiq
subs
pc, lr, #4
/*=============================================================================
* Address exception handler
*-----------------------------------------------------------------------------
* These aren't too critical.
* (they're not supposed to happen, and won't happen in 32-bit data mode).
*/
vector_addrexcptn:
b
vector_addrexcptn
/*
* We group all the following data together to optimise
* for CPUs with separate I & D caches.
*/
.align
5
.LCvswi:
.word
vector_swi
.globl
__stubs_end
__stubs_end:
.equ
stubs_offset, __vectors_start + 0x200 - __stubs_start
__irq_svc:
svc_entry
#ifdef CONFIG_TRACE_IRQFLAGS
bl
trace_hardirqs_off
#endif
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr
r8, [tsk, #TI_PREEMPT]
@ get preempt count
add
r7, r8, #1
@ increment it
str
r7, [tsk, #TI_PREEMPT]
#endif
irq_handler
#ifdef CONFIG_PREEMPT
str
r8, [tsk, #TI_PREEMPT]
@ restore preempt count
ldr
r0, [tsk, #TI_FLAGS]
@ get flags
teq
r8, #0
@ if preempt count != 0
movne
r0, #0
@ force flags to 0
tst
r0, #_TIF_NEED_RESCHED
blne
svc_preempt
#endif
ldr
r4, [sp, #S_PSR]
@ irqs are already disabled
#ifdef CONFIG_TRACE_IRQFLAGS
tst
r4, #PSR_I_BIT
bleq
trace_hardirqs_on
#endif
svc_exit r4
@ return from exception
UNWIND(.fnend
)
ENDPROC(__irq_svc)
.macro
irq_handler
get_irqnr_preamble r5, lr
1:
get_irqnr_and_base r0, r6, r5, lr
注释:该宏定义在:entry-macro.S中,给r0赋值当前发生中断的中断号。
.macro
get_irqnr_and_base, irqnr, irqstat, base, tmp
mov
\base, #AIC_BA
ldr
\irqnr, [ \base, #AIC_IPER]
ldr
\irqnr, [ \base, #AIC_ISNR]
cmp
\irqnr, #0
.endm
movne
r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne
lr, BSYM(1b)
bne
asm_do_IRQ
注释:当调用asm_do_IRQ的时候,传进去的参数为r0 = irq number, r1 = struct pt_regs *
R0就是刚才从get_irqnr_and_base得到的,r1为sp。
asm_do_IRQ定义在:linux/arch/arm/kernel/irq.c中。
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
irq_enter();
/*
* Some hardware gives randomly wrong interrupts.
Rather
* than crashing, do something sensible.
*/
if (unlikely(irq >= NR_IRQS)) {
if (printk_ratelimit())
printk(KERN_WARNING "Bad IRQ%u\n", irq);
ack_bad_irq(irq);
} else {
generic_handle_irq(irq);
注释:这里最终会调用到desc->handle_irq(irq, desc);,也就是我们在arch/arm/mach-xxx/irq,c中初始化的handle_level_irq。
通过这句set_irq_handler(irqno, handle_level_irq);来初始化的。
handle_level_irq最终又会调用到ret = action->handler(irq, action->dev_id);从而调用真正的用户注册的中断处理函数,该中断处理函数是通过request_irq函数注册的。
}
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r6) and base (r5) are
* preserved from get_irqnr_and_base above
*/
test_for_ipi r0, r6, r5, lr
movne
r0, sp
adrne
lr, BSYM(1b)
bne
do_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movne
r0, sp
adrne
lr, BSYM(1b)
bne
do_local_timer
#endif
#endif
.endm