Chinaunix首页 | 论坛 | 博客
  • 博客访问: 936289
  • 博文数量: 403
  • 博客积分: 27
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-25 22:20
文章分类

全部博文(403)

文章存档

2016年(3)

2015年(16)

2014年(163)

2013年(222)

分类: LINUX

2013-05-12 13:13:22

原文地址:linux内核阅读总结 作者:liujunwei1234

    接着上篇CFS学习总结,下面对很久前看的一些Linux内核的主要模块写了个简单的总结,本总结个人针对某个模块的回忆,并不针对源码,主要目的是一方面加强自己的记忆,另一方面提炼出一些东西与大家分享(有时候代码看多了,对某个模块反而没有了一个整体的概念)。

一、Linux 启动过程分析

Linux的启动过程可以分为四个阶段:系统上电阶段, BIOS阶段,引导程序阶段,Linux内核阶段。

1)系统上电阶段

对于x86体系结构来说,CPU上电后,eip = 0xffff fff0, CPU执行eip指向的指令,通常这是条跳转指令,即跳转到BIOS的入口。

2BIOS阶段

BIOS主要完成两个功能:加电自检,即POSTPower On Self Test)和加载内核引导程序,这里特指MBRMaster Boot Record主引导记录区512字节),POST过程主要完成系统硬件的检测,比如内存检测,系统总线检测等。

3)引导程序阶段

这里说的内核引导程序包括两部分:MBR中的主引导程序和活动分区中的次引导程序。MBR中的主引导程序包含446字节的程序和64字节的磁盘分区表,主引导程序会扫描磁盘分区表,寻找活动的磁盘分区,将活动分区中的次引导程序加载到内存执行,次引导程序负责完成加载内核的任务。

4Linux内核阶段

内核被加载到内存后,首先为内核的运行做前期的准备,主要包括以下几个主要步骤:

  • 设置c程序运行的堆栈
  • 清空BSS
  • 设置中断描述符表(IDT
  • 设置全局描述符表(GDT
  • 置位CR0PE位,开启保护模式(保护模式开启后必须紧跟一条长跳转指令,用来清空指令预取队列,并重新设置%CS段寄存器
  • 调用内核解压函数 – decompress_kernel(),并跳转到解压内核的入口
  • 设置CR0PG位,开启分页机制

基本的思想就是在内存中找一块安全的内存区,通常在内核数据段或堆结束的地方,在这段内存中将物理地址0开始的一段内存同时映射虚拟地址空间的0xC000 00000x0000 0000 开始的地址处。

  • 跳转到start_kernel
二、Linux系统调用执行流程

Linux系统调用的执行流程可以描述为:

(1)       应用程序调用C库中的函数

(2)       C库函数的实现为触发int 0x80系统调用中断,系统调用号在%eax

(3)       操作系统拿中断向量号0x80查询中断向量表,执行对应的中断处理函数

(4)       中断处理函数拿系统调用号%eax查询系统调用表,执行相应的系统调用

(5)       系统调用执行完成后返回应用程序


三、Linux中断分类和中断处理过程

中断的初始化主要包括:中断向量表的初始化,中断数组的初始化,中断处理函数的注册等。


中断产生和处理过程中硬件执行的操作:

设备通过中断请求线(IRQ线)向中断控制器发送一个中断信号,中断控制器接收到中断信号后,一方面经过译码电路将中断信号转化为中断号,保存在指定的IO ports中,另一方面通过与CPU的INTR管脚直接相连的INT线向CPU发送中断信号;

CPU收到中断信号后,等执行完当前指令,在执行下一条指令前,去检查有无中断处于Pending状态,如果有,通过INTA向中断控制器发送响应信号,并将中断号通过Data Line取回,然后读取IDT表,获得该中断号对应的IDT表中的第i项,然后根据IDT[i]描述符中指定的段选择子,去GDT表中查找,获得中断处理程序的基址,然后用基址+IDT[i]中指定的中断处理程序的偏移地址,来获得中断处理程序的地址,当然,在读取IDT和GDT表时还会有一些安全性检查。

CPU去判断有没有发生特权级的变化(用户态陷入内核态),如果有,通过tr寄存器获得TSS段的基址,从中读取内核态堆栈的ss和esp,此时就将用户态的堆栈切换到了内核态的堆栈,接着将用户态的ss和esp保存在内核堆栈中,如果此中断的类型是故障(fault),则重新修改cs和eip的值为产生中断的这条指令的地址,以便中断处理完成后,重新执行这条指令。然后,在内核栈中保存eflags,cs和eip的值,如果有硬件出错码,也将其保存到堆栈上,最后将cs:eip的值赋值为中断处理函数的基址+IDT[i]的偏移。

从用户态进入中断与从内核态进入中断的堆栈示意图如下。


IDT表的初始化:通过set_intr_gate()trap_init(),后者用于初始化20个异常和一个系统调用相关的中断0x80

init_IRQ()中通过循环调用set_intr_gate(vector,interrupt[i])来初始化vector对应的IDT表项,这里的interrupt[256]数组,通过下面的代码把所有中断的处理函数都定义为统一的入口:common_interrupt

 

体系结构无关的中断处理过程: do_IRQ()

irq_desc数据包含了224irq_desc_t描述符,每一个中断号对应一个描述符,这个描述符中的action链表,它指向irqaction的数据结构,代表这个中断对应的处理函数,共享同一中断向量的所有的处理函数都挂在这个链表上,通过遍历这个链表,并查是否要是设备注册的处理函数(dev_id)。

 

软中断(softirq)由内核静态分配,在编译时就确定了,个数有限,目前内核支持8/9个,具有优先级,主要与定时器,网络包的收发,块设备,调度,Tasklet等有关。在特定的点检查有无可执行的软中断:

(1)       local_bh_enable重新激活软中断时

(2)       do_IRQ完成中断处理过程后

(3)       ksoftirqd被唤醒时

TaskletI/O驱动程序中实现可延迟函数的首选,建立在软中断HI_SOFTIRQTASKLET_SOFTIRQ之上,二者的区别是优先级不同,这两种Tasklet由对应的数组tasklet_vectasklet_hi_vec来管理,数组的每一项对应一个CPU,即tasklet是与CPU绑定的。

Workqueue由工作队列和工作者线程两部分构成,工作者线程会定期的扫描工作队列有无可处理的任务,软中断和tasklet运行在中断上下文,所以不可睡眠,workquue运行在进程上下文,可睡眠。



阅读(286) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~