中断和异常
一、明确中断异常描述表的作用:它与每一个中断和异常相联系,每一个向量在表中都有相应的处理程序的入口地址。
二、明确中断控制器的作用,表述清楚中断如何产生。
- 中断控制器是对中断和异常进行调度的程序。
- 中断的产生过程:
- 在计算机设备中每一个能产生中断的设备都与一条IRQ输出线相连,相应的通过这条线与中断控制器的IRQ的输入引脚相连。当产生中断的设备将信号发送到中断控制器时,中断控制器通过INTR引脚通知CPU,当CPU接受到信号时,中断正式产生。
三、明确中断发生时,CPU硬件级的中断信号处理过程
- 确定与中断或异常相关联的向量i
- 读取idtr寄存器的值,找到IDT的基址,通过查询IDT,找到第i项对应的内容。
- 从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的段选择符所标识的段描述符。
- 确定中断是由授权的发生源发出的。
- 中断:需要比较CPL和GDT中的DPL。中断处理程序的特权不能低于引起中断的程序的特权。
- 编程异常:需要比较CPL和IDT中的DPL。
- 检查是否发生了特权级的变化,一般指的是用户态陷入内核态。
- 如果是用户态陷入内核态,控制单元要使用新的特权级堆栈。
- 保存ss和esp,并用新的堆栈的值填充。
- 如果是故障,用引起故障的指令修改cs和eip,以便异常处理后再次执行。
- 在堆栈中保存eflags/cs/eip的内容。
- 如果有硬件出错码,则保存。
- 使用IDT中第i项中的段描述符和偏移量填充cs和eip。
四、中断的返回过程
- 用保存在堆栈中的数值填充cs和eip、eflags,如果有硬件出错码则也弹出。
- 检查处理程序的特权级是否低于cs的低两位的特权级,若是则iret终止执行。
- 从栈中弹出ss和esp。返回到旧的堆栈。
- 比较ds、es、fs、gs段寄存器的内容,如果有一个寄存器的段选择符是段描述符且特权级比当前特权级高。那么就清空相应的寄存器。防止用户态访问内核空间信息。
五、中断和异常的初始化
- 首先在start_kernel后,调用了trap.c中的trap_init()函数对中断进行初始化。并且调用同一文件下的set_trap_gate()/set_system_gate()/set_intr_gate()等对中断描述符进行初始化。在进入保护模式之前,IDT再次通过setup_idt()函数进行初始化,在这里使用了ignore_int()函数,是为了保护未初始化完成是发生异常不出错。然后调用init_IRQ()函数使用新的中断函数代替原来的函数,使其指向interrupt数组中。而该数组都指向common_interrupt()。该函数的功能是:SAVE_ALL movl %esp %eax call do_IRQ jump ret_from_intr
- Do_IRQ的功能
- 得到中断或者异常向量号
- 找到对应的处理句柄
- 对相应的IRQ线进行禁止或者其他操作。
- 执行中断服务函数。
- 恢复IRQ线的响应。
六、Tasklet的处理机制
- 通过open_softirq()进行初始化。
- 分配一个tasklet的数据结构,并初始化。
- 可以禁用或者允许这个tasklet。
- 通过激活tasklet_schedule和tasklet_hi_schedule激活这个tasklet。然后加入task_vec或者task_hi_vec链表中,等待合适的时机执行。
- 由task_action或者task_hi_action对链表进行遍历执行。
timer
一、时钟初始化过程
- 系统加载start_kernel开始,然后调用tick_init(),初始化tick相关的内容。将tick_notifier注册到clockevents_chain中。
- 接着初始化init_timer(),初始化定时相关的内容。
- 然后调用hrtimer_init(),高精度定时机制相关的初始化。
- 通过timekeeping_init()对xtime进行初始化。
- Time_init()在这里调用choose_time_init()选择一个时钟源,用hpet_time_int()调用set_pit_init()将pit_clockevent注册为global_clock_event以及time_init_hook()来设置时钟中断处理程序。
- 通过sched_cloak_init()对时钟调度进行初始化。
二、调用过程
- 我们知道在set_pit_init()的时候,将pit_clockevents注册为了global_clock_event。其中event_handle被初始化为tick_handle_periodic(),其中关键的函数时tick_periodic(),该函数调用了do_time()完成xtime的更新和update_process_time完成raise_softirq()激活时钟中断队列和scheduler_tick对当前进程的时间片减1.
三、软定时器的结构:
- Express:用来与jiffies进行比较。
- Data:定时器超时处理函数需要传入的参数。
- Function:该指针变量保存着中断处理的程序。
- Base:指定该定时器属于哪个处理器。
四、创建并激活一个定时器
- 创建一个新的time_list对象。
- 通过init_time()对结构体中的个元素进行赋值。
- 设置定时的时间。
- 用add_timer()加入到合适的链表中。
五、明确中断时钟源和定时时钟源的区别
- 定时时钟源是决定xtime一个增加多少的关键所在。
- 定时时钟源是决定xtime在何时增加的管家,不决定增加的多少。
- 如果两个时钟源采用统一时钟,那么每次xtime增加1tick。
- 否则由精度高的时钟源决定,而一般定时时钟源精度高于中断时钟源。
- Xtime的值在定时处理函数中更新。
六、定时处理函数的流程
- 改变xtime的值。
- 把当前的xtime的时间写回到cmos中。Cmos是精度最低的时钟源。
- 对CPU的节拍加1.
- 对当前的进程的时间片减1.以及挂起的sleep进程的时间也减1.
- 给CPU的资源统计计数。
文件系统
一、明确VFS文件系统的作用
- 屏蔽底层各个文件系统之间的实现差异,给用户提供统一的接口。
二、主要数据结构
- 超级块对象:保存文件系统信息
- 文件对象:保存已打开的文件和进程的交互信息。
- 目录项对象:存放目录项和文件链接的信息。
- 索引结点对象:存放具体文件的一般信息。
- 在磁盘中的数据结构:
- 引导控制块:操作系统的初始引导块
- 盘控制块:磁盘信息等
- 文件控制块:文件属性。
- 目录结构:组织管理文件。
- 在内存中的数据结构:
- 系统打开文件表
- 进程打开文件表
三、Ext2的初始化
- 初始化超级块和组描述符。
- 检查时候有怀块,如果有,创建坏块列表。
- 对每个块组,保留保存超级块、位图、列表的块。
- 初始化位图和列表。
- 创建/root目录。
- 创建lost+found目录。
- 为上面的两个目录更新位图等信息。
- 如果有坏块,则将其在lost+found目录中组织起来。
四、Open函数的调用
- 当我们使用open函数的时候,首先通过按名查找,看在高速缓存中有没有查找的inode,如果有那么对其进行引用并加1。如果没有将创建新的vfs的inode对象和目录项对象等。
- Open函数调用的系统服务例程是sys_open函数,其接受的参数为文件目录和访问的模式。如果文件存在则返回一个文件描述符fd,如果不存在则返回-1.
- Read和write函数分调用的服务例程是sys_read和sys_write,接受三个参数。分别是一个文件描述符fd,一个对数据缓存的地方buf,和需要传输的数据的多少count。Read对将文件读入缓存区。Write则相反。
五、根文件系统的挂载
- 准备一个虚拟的文件系统rootfs,将其初始化为根文件系统
- Start_kernel后调用vfs_caches_init()初始化vfs。调用mnt_init()创建一个虚拟的rootfs,调用init_rootfs将其向内核注册。通过init_mount_tree挂载根文件系统。./挂载点就形成了。
- 将initrd文件加载到内存。在这里分两种情况。
- 格式为cpio_initrd。
- 直接将其解压到根文件下。
- 执行/init,如果执行成功则加载成功。如果失败则执行3。如果没有/init那么执行用户定义的根文件系统。然后执行3。
- 格式为image_initrd。
- 将其释放到/image_initrd文件下。
- 在内核下将其复制到/dev/ram0文件下。
- 如果/dev/ram0是根文件系统,那么挂载根文件系统,然后执行3。
- 否则执行Linuxrc,加载根文件系统的一些驱动等。而后挂载根文件系统,然后执行3。
- 执行/sbin/init,/bin/int,/etc/init等,如果成功则挂载根文件系统成功。
- 格式为cpio_initrd。
设备驱动
一、字符设备驱动程序的组成
- File_operation:成员大部分是指针,如果设备不支持则值为null。其中有一下操作:read、write、open、release。
- File:打开的文件, 每open一个文件就建立一个file结构体。
- Inode:关于文件的大量内部信息。
- 设备号:表示设备号和处理程序的对应关系。
- Chrdev[]:存放注册的设备驱动程序。
二、字符设备的访问过程一定要与文件系统结合
- 根据我们的设备号通过kobj_lookup函数找到对应的设备驱动程序。
- 将inode->i_dev指向查找到的dev。
- 将inode添加到cdev_list的链表中。
- 使用cdev的ops来设置文件的f_op。
- 如果ops中存在open函数,则执行open函数。
- 返回。
- 返回后对于其他操作read、write等也是通过这个cdev的ops中的函数进行的。
三、注册设备驱动程序
- 首先申请一个cdev的结构体。其中包含驱动模块、设备号范围、cdev_list、ops等。自己实现file_operations函数等。
- 字符设备的初始化。
- 静态的初始化,通过调用已用的函数进行初始化。
- 动态的初始化,主要用过malloc等方式,创建指针等来进行初始化。
- 在cdev初始化结束后,我们可以通过register_chrdev_region和alloc_register_chrdev_region两种方式进行申请注册设备号。第一种方式是用指定的设备号向系统进行申请。第二种是没有指定设备号由系统进行分配然后返回。把实现的file_operations和cdev进行绑定。申请得到设备号后,用cdev_add将设备注册。
- 在释放设备的时候,首先通过unregister_chrdev_region释放原来申请的设备号。然后调用cdev_del来注销设备。
参考资料:
深入理解Linux内核
备注:
转载请注明出处:http://blog.csdn.net/wsyw126/article/details/51803834
作者:WSYW126