由于网络上的资料非常多, 使得学习linux 的难度降低了许多. 踏着前人的足迹更快地体验linux内部的实现.
感谢这些人能将他们学习中的心得体会记录下来并公开. 有了源码再加上这些宝贵的资料, 我想唯一需要做
的就是克服浮躁, 静下心来好好学习.
参考:
1. 嵌入式Linux应用开发完全手册 (这里面已经讲的非常详细, 且条理清晰)
3.
start_kernel // init/main.c
+--------> setup_arch(char **cmdline_p) // arch/arm/kernel/setup.c
| ->early_trap_init(void) // arch/arm/kernel/traps.c
|
+-------> early_irq_init(); // kernel/irq/handle.c
|
+-------> init_IRQ(); // arch/arm/kernel/irq.c
1. early_trap_init
1). 复制中断向量表及处理部分代码到运行时的位置
vectors = 0xffff0000
__vectors_start 异常向量表起始地址
...
... 异常向量表, 每个中断向量为一条跳转指令, 其跳入
... __stubs_start~__stubs_end 之间的某个地址, 进行更为复杂
... 的处理工作.
...
__vectors_end 中断向量表终址地址
void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
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);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
/*
* 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);
}
2). 异常向量表
arch/arm/kernel/entry-armv.S
// stubs_offset 是经过计算后的偏移地址. 对于vector_irq, 这个标号的地址在编译后
// 就已经为固定了, 但是在运行时, 代码会从存储器中的image状态复制到sdram中, 成
// 为运行状态. 这个过程打乱了代码间的相对位置, 所以下面用 b 跳转, 如果还按照
// 编译时的地址来跳, 肯定不能跳到正确的位置.
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__vectors_start:
ARM( swi SYS_ERROR0 )
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:
3). __stubs_start 与 __stubs_end
这两个地址中间包含着几段代码, 分别是几种异常的处理程序
__stubs_start:
[vector_irq 处理代码]
[irq跳转地址表]
[vector_dabt 处理代码]
[dabt跳转地址表]
[vector_pabt 处理代码]
[pabt跳转地址表]
[vector_und 处理代码]
[und跳转地址表]
[vector_fiq 处理代码]
[vector_addrexcptn 处理代码]
[swi 地址]
__stubs_end:
4). 拿 vector_irq 来分析
vector_stub irq, IRQ_MODE, 4
vector_stub 是一个宏, 这一句会展开一段代码, 这里展开后如下
vector_irq:
@ 计算返回地址
sub lr, lr, #4
@ 将 r0和r14(lr) 入栈保存起来, sp的位置没有改变
@ stmia 表示栈操作为事后递增
stmia sp, {r0, lr} @ save r0, lr
@ 将 spsr 也入栈保存起来
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@ 置为svc模式, 此时 cspr 寄存器中断位为禁止状态
mrs r0, cpsr
eor r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
@ 进入中断时会将中断前的 cpsr 保存在 spsr 中, 其[4:0]表示当时cpu的工作
@ 模式(而[4]始终为1), 所以可以利用此时的spsr[3:0]来标志中断前的工作模
@ 式, 进而根据不同的工作模式索引到对应的子处理部分.
@ 上面将 spsr 赋值给 lr, 这里取lr的低八位在下面的表中索引.
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.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
假设中断前cpu为usr状态, 则运行完上面的代码后就跳入 __irq_usr 中执行.
5). 代码重定位
分析了代码的作用, 但代码复制到sdram后, 又是如何能够找到正确的地址的呢?
如下图,
image 这条线表示编译后生成的image, 线上有代码间的相对位置
exec 表示在sdram中运行时的这段代码间的相对位置
假设此时, 在image 中有t1 位置, 该位置的代码为 b vector_irq, 运行时, 该点的代码
复制到了exec 中的 t1 点, 此时要如何才能够正确跳到 exec 中的 vector_irq 位置呢?
即跳到exec线上的E(vector_irq).
[exec]
|
+ E(__vectors_start) -+-
/| |
/ | |
/ | |
/ | |
/ | |
/ | |
/ | |
[image] / | |
| / | |
__stubs_start + / | |
|\ / + E(t1) --------------+--------+-
| \ / | | |
| \ / + E(__vectors_end) V |
vector_irq + \ / /| |
| \ / / | 0x200 |
| X / | |
| / \ / | ^ |
| / \ / | | |
| / \ / | | V
| / \ / | |
|/ \ / | | L1
__stubs_end X \/ | |
__vectors_start |\ /\ | | ^
| \ / \ | | |
| \ / \ | | |
| \ / \ | | |
| \ / \ | | |
| \/ \ | | |
| /\ \ | | |
| / \ \ | | |
| / \ \| | |
| / \ + E(__stubs_start) ---+--------+-
b "irq":t1 + / \ | V
|/ \ | L2
__vectors_end + \ | ^
| \ + E(vector_irq) ---------------+-
\ |
\ |
\ |
\ |
\ |
\ |
\|
+ E(__stubs_end)
|
在exec 线中, 假设t1 与 E(vector_irq) 相对位置偏移 offset, 则有:
offset = L1 + L2
= 0x200 - (E(t1) - E(__vectors_start)) + E(vector_irq) - E(__stubs_start)
= 0x200 - (t1 - __vectors_start) + vector_irq - __stubs_start
= vector_irq + (__vectors_start + 0x200 - __stubs_start) - t1
令 stubs_offset = __vectors_start + 0x200 - __stubs_start (这些为固定值)
则 offset = vector_irq + stubs_offset - t1
又 执行 b 跳转命令时PC就在 t1 位置,
=> b vector_irq + stubs_offset 就可以跳到运行时的vector_irq地址去
所以会看到这一行的存在
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
2. early_irq_init
kernel/irq/handle.c
在这个函数的上面, 可以看到下面的代码, 定义了一个名为irq_desc的数组, 其中每个成
员都是一个 irq_desc 结构体. NR_IRQS 为中断源个数.
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),
}
};
int __init early_irq_init(void)
{
struct irq_desc *desc;
int count;
int i;
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
desc = irq_desc;
count = ARRAY_SIZE(irq_desc);
for (i = 0; i < count; i++) {
desc[i].irq = i;
alloc_desc_masks(&desc[i], 0, true);
init_desc_masks(&desc[i]);
desc[i].kstat_irqs = kstat_irqs_all[i];
}
return arch_early_irq_init();
}
early_irq_init 则对数组中每个成员结构进行初始化, 例如, 初始每个中断源的中断号.
其他的函数基本为空.
3. init_IRQ()
进一步初始化 irq_desc 数组中的成员
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
init_arch_irq();
}
init_arch_irq 是一个函数指针:
void (*init_arch_irq)(void) __initdata = NULL;
void __init setup_arch(char **cmdline_p)
{
...
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
...
/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
...
early_trap_init();
}
在 arch/arm/mach-s3c2410/mach-smdk2410.c 中有
(至于mdesc是怎么与这个文件相联系起来的不在这里跟踪)
MACHINE_START(SMDK2410, "SMDK2410")
...
.init_irq = s3c24xx_init_irq,
...
MACHINE_END
所以最终的执行是: init_IRQ() -> s3c24xx_init_irq(),
s3c24xx_init_irq 中进行一系列的工作
至此, 关于中断的工作就初始完了
由于排版问题, 上面用字符画的图错位了. 附件为此篇笔记, 推荐用vim打开.其中的图应该没错.
|
文件: | linux_kernel_irq.tar.bz2 |
大小: | 3KB |
下载: | 下载 |
|