`
love19820823
  • 浏览: 933816 次
文章分类
社区版块
存档分类
最新评论

《深入理解Linux内核》--第四章 中断和异常:读书笔记

 
阅读更多

同步中断:只有在一条指令终止执行后CPU才会发出中断;
异步中断:由其它硬件设备依照CPU时钟信号随机产生的中断。
一般中断是指 异步中断。异常: 同步中断。

中断处理与进程切换的区别:中断或异常处理程序执行的代码不是一个进程,它是一个内核控制内经,代表中断发生时正在运行的进程执行。作为一个内核控制路径,中断处理程序比一个进程要“轻”。(没有进程要求那么多资源)

一、中断
分为:1)可屏蔽中断(maskable Interrupt);2)非屏蔽中断(nonmaskable Interrupt)
1)IRQ
IRQ线(即 外部中断 线):每个能够发出中断请求的硬件设备都有一条IRQ输出线,所有IRQ Line都和PIC硬件电路的输入引脚相连。PIC主要是用来监控IRQ线的信号。如果有信号产生则把信号向量发送到处理器的INTR引脚,产生一个中断。
IRQ分配给I/O时,一个IRQ的序号对应一个INT序号,INT序号即为CPU的INTR引脚序号。
IRQ从0开始编号,IRQn一般对应于Intel的缺省向量n+32。(注意LInux的128号向量用于系统调用的可编程的异常,32~127,129~238都用于IRQ)
中断请求在多处理器系统中分发方式:静态分发,动态分发。
数据结构式:irq_desc_t[NR_CPU],每个CPU对应一个irp_desc_t描述符

在多处理器系统中,kirqd()内核线程周期性执行do_irq_balance()函数,追踪最近时间间隔内每个CPU接收的中断次数。如果负荷不均匀,要么把一个IRQ从一个CPU转移到另一个CPU,要么让所有的IRQ在所有CPU之间“轮转”。

thread_info进程描述符:
union thread_union{
struct thread_info thread_info;
unsigned long stack[2048]; /*对4k的栈数组下表是1024 ,long是4byte */
}
如果为thread_union为8kB,则进程的内核栈被用于所有的内核控制路径:异常、中断和可延迟函数。(总共<=8K)
如果thread_union为4KB,则内核有三种类型的内核:异常栈、硬件请求栈(中断)、软中断请求栈(可延迟),它们是4KB对齐的,但是如果8KB的又都可以容下。

do_IRQ()函数执行与一个中断相关的所有中断服务例程,_ _do_IRQ()函数用于禁止本地中断运行。

I/O中断处理,两种不同方式:IRQ共享,IRQ动态分配
2)IDT
中断描述符表是一个系统表,内核在允许中断前必须初始化IDT,用lidt汇编指令初始化idtr。idtr CPU寄存器使 IDT可以位于内存的任何地方,idtr指定IDT的线性基地址及其限制(最大长度)。
描述符类型:task gate(任务门)、Interrupt gate、Trap gate
在初始化中断描述符表时,需要防止用户(用户态) int 指令(软件中断)模拟非法的中断和异常,通过吧中断或陷阱门描述符的DPL字段设置成0来实现。控制单元通过检查CPL 与 DPL的字段,如果不都为0(内核态)则有冲突,产生异常。

二、异常 (Divide error,Overflow,Double fault之类)
分为:1)故障(fault); 2)陷阱(trap);3)abort(异常中止);4)programmed exception(编程异常)
fault 和 trap 的区别:fault只要处理程序能纠正引起异常的反常条件,重新执行同一指令时必要的;
trap只有当没有必要重新执行已终止的指令时,才触发trap。

三、中断和异常的硬件处理
注意的地方:
1)需要比较CPL和DPL,如果CPL(0,内核态)小于DPL(3,用户态),则发生异常
2)在处理完后,相应的处理程序需要产生一条iret指令(非ret指令,ret用于用户程序)
在返回后,需要检查ds、es、fs和gs段寄存器的内容,如果其中一个包含的选择符是一个段描述符,并且DPL(0)值小于CPL(3)(也就是说当前的为用户态),需要清除相应的段寄存器。为了禁止用户态程序利用以前所用的段寄存器(DPL=0),恶意利用它们来访问内核地址空间。
3)内核控制路径嵌套执行。中断处理程序运行期间不允许进程切换。
异常处理程序引发 相关的 至多两个内核控制路径(第一个由系统调用引起,第二个由(缺页)异常)。

四、软中断(softirq) 和 tasklet
软中断可以并发地运行在多个CPU上。是可重入函数,必须明确地使用自旋锁保护其数据结构。
tasklet:同类型的tasklet总是被串行地执行,不能再两个CPU上同时运行相同类型的tasklet(类型不同的tasklet可以,分为普通的TASTKLET_SOFTIQT和HI_SOFTIRQ两种,即普通的和高级的)。是可重入的函数。
1)软中断
软中断对应的处理函数是:do_softirq(),使用的数据结构:softirq_vec[n].
preempt_count的字段中各位意思:抢占计数器(0~7)、软中断计数器(8~15)、硬中断计数器(16~27)、PREEMPT_ACTIVE标志(28)
in_interrupt()检查current_thread_info()->preempt_count字段的硬中断计数器和软中断计数器,只要这两个计数器中的一个值为正数,该宏就产生一个非零值。
raise_softirq()激活软中断,wakeup_softiqrd()唤醒本地CPU的ksoftirqd内核线程,do_softirq()来处理检测到的被挂起的软中断,_ _do_softirq()读取本地CPU的 软中断掩码 并执行与每个设置位相关的可延迟函数。
ksoftirq内核线程 解决了软中断的连续高流量(如网络中断)可能产生的问题。其代码如下:
for(::){
set_current_state(TASK_INTERRUPTIBLE);
schedule();
while(local_softirq_pending()){
preempt_disable();
do_softirq();
preempt_enable();
cond_resched();
}
}

2)tasklet
TASKLET_SOFTIRQ软中断类型对应的处理函数是:tasklet_action(),数据结构:tasklet_vec[n]
HI_SOFTIRQ软中断对应的处理函数:tasklet_hi_action(),数据结构:tasklet_hi_vec[n]
I/O驱动程序实现可延迟函数的首选方法:tasklet。
在tasklet_vec[n]或tasklet_hi_vec[n]指向的链表起始处增加taskl描述符(n表示本地CPU的逻辑号)
调用raise_softirq_irqoff()激活TASKLET_SOFTIRQ或HI_SOFTIRQ类型的软中断。

五、工作队列
描述符:workqueue_struct,数组元素个数:NR_CPUS
工作队列和可延迟函数(软中断)的区别:可延迟函数运行在中断上下文中,工作队列中的函数运行在进程上下文中。
相同:他们都不能访问进程的用户态地址空间。
工作队列的函数是有内核线程来执行的。

六、从中断和异常返回
1) ret_from_intr() 和 ret_from_exception() 入口点相当于:
ret_from_exception:
cli; missing if kernel preemption is not supported
ret_from_intr:
movl $-8192, %ebp ; -4096 if multiple Kernel Mode Stacks are used;8192(8K)
andl %esp, %ebp
movl 0x30(%esp), %eax
movb 0x2c(%esp), %a1
test1 $0x00020003, %eax
jnz resume_userspace
jpm resume_kernel
如果被恢复的程序在内核态,就需要resum_kernel,检查thread_info描述符的preempt_count字段,若为0则内核跳到need_resched检查内核抢占,所有没有执行完的内核控制路径都不是中断处理程序,否则preempt_count字段的值就会大于0。若preempt_count为不为0,则被中断的程序重新开始执行(直接restore_all)
在need_resched中,如果current->thread_info的flags字段中的TIF_NEED_RESCHED标志为0,说明没有需要切换进程,直接跳到restore_all恢复用户态程序。如果TIF_NEED_RESCHED不为0,则调用preempt_schedule_irq(),设置preempt_count字段的PREEMPT_ACTIVE标志,把大内核锁计数器暂时设置为-1,打开本地中断调用schedule()选择另一个进程运行;当前面的进程要恢复是,preempt_schedule_irq()使大内核计数器的值恢复为以前的值,清除PREEMPT_ACTIVE标志并且禁用本地中断。
resume_userspace,检查cureent->thread_info的flags字段的值,如果只设置了TIF_SYSCALL_TRACE、TIF_SYSCALL_AUDIT或TIF_SINGLESTEP标志,就不做任何其他事情,直接跳到restore_all,恢复用户程序。

七、附录

常见缩写:
IRQ(Interrupt ReQuest)中断请求
PIC(Programmable Interrupt Controller)可编程中断控制器
cli(clear Interrupt)、sti(set Interrupt)汇编指令分别清除和设置eflags寄存器的IF(Interrupt Flag)标志。
APIC(I/O Advanced Programmable Interrupt Controller)I/O高级可编程控制器
IRT(Interrupt Redirection Table)中断重定向表
IDT (Interrupt Descriptor Table)中断描述符表
DPL (Descriptor Priviledge Level) 描述符特权等级

内核中比较重要的函数、宏及变量
local_irq_save()用来保存IF标志的状态并禁用本地中断
local_irq_restore()恢复IF标志的状态
do_IRQ()
_ _do_IRQ()
set_task_gate(8,31);表示存放IDT的第8想中的任务门描述符,指向存放在GDT表中第32项的TSS段描述符。
preempt_count抢占计数字段,preempt中文意思:抢占
do_softirq(),检测到挂起的软件中断,内核调用该函数来处理它们。
_ _do_softirq()
ksoftirq/n内核线程(n为CPU的逻辑号)

注:这书里面挺侧重分析 多处理器 架构,Linux内核对多处理器的支持做了很多工作。
PS:这一章后面一些看的比较快,比较草率~

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics