中断与异常、时钟源、文件系统、设备驱动

中断和异常

一、明确中断异常描述表的作用:它与每一个中断和异常相联系,每一个向量在表中都有相应的处理程序的入口地址。
二、明确中断控制器的作用,表述清楚中断如何产生。
  1. 中断控制器是对中断和异常进行调度的程序。
  2. 中断的产生过程:
    1. 在计算机设备中每一个能产生中断的设备都与一条IRQ输出线相连,相应的通过这条线与中断控制器的IRQ的输入引脚相连。当产生中断的设备将信号发送到中断控制器时,中断控制器通过INTR引脚通知CPU,当CPU接受到信号时,中断正式产生。
三、明确中断发生时,CPU硬件级的中断信号处理过程
  1. 确定与中断或异常相关联的向量i
  2. 读取idtr寄存器的值,找到IDT的基址,通过查询IDT,找到第i项对应的内容。
  3. 从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的段选择符所标识的段描述符。
  4. 确定中断是由授权的发生源发出的。
    1. 中断:需要比较CPL和GDT中的DPL。中断处理程序的特权不能低于引起中断的程序的特权。
    2. 编程异常:需要比较CPL和IDT中的DPL。
  5. 检查是否发生了特权级的变化,一般指的是用户态陷入内核态。
    1. 如果是用户态陷入内核态,控制单元要使用新的特权级堆栈。
    2. 保存ss和esp,并用新的堆栈的值填充。
  6. 如果是故障,用引起故障的指令修改cs和eip,以便异常处理后再次执行。
  7. 在堆栈中保存eflags/cs/eip的内容。
  8. 如果有硬件出错码,则保存。
  9. 使用IDT中第i项中的段描述符和偏移量填充cs和eip。
四、中断的返回过程
  1. 用保存在堆栈中的数值填充cs和eip、eflags,如果有硬件出错码则也弹出。
  2. 检查处理程序的特权级是否低于cs的低两位的特权级,若是则iret终止执行。
  3. 从栈中弹出ss和esp。返回到旧的堆栈。
  4. 比较ds、es、fs、gs段寄存器的内容,如果有一个寄存器的段选择符是段描述符且特权级比当前特权级高。那么就清空相应的寄存器。防止用户态访问内核空间信息。
五、中断和异常的初始化
  1. 首先在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
  2. Do_IRQ的功能
    1. 得到中断或者异常向量号
    2. 找到对应的处理句柄
    3. 对相应的IRQ线进行禁止或者其他操作。
    4. 执行中断服务函数。
    5. 恢复IRQ线的响应。
六、Tasklet的处理机制
  1. 通过open_softirq()进行初始化。
  2. 分配一个tasklet的数据结构,并初始化。
  3. 可以禁用或者允许这个tasklet。
  4. 通过激活tasklet_schedule和tasklet_hi_schedule激活这个tasklet。然后加入task_vec或者task_hi_vec链表中,等待合适的时机执行。
  5. 由task_action或者task_hi_action对链表进行遍历执行。

timer

一、时钟初始化过程
  1. 系统加载start_kernel开始,然后调用tick_init(),初始化tick相关的内容。将tick_notifier注册到clockevents_chain中。
  2. 接着初始化init_timer(),初始化定时相关的内容。
  3. 然后调用hrtimer_init(),高精度定时机制相关的初始化。
  4. 通过timekeeping_init()对xtime进行初始化。
  5. Time_init()在这里调用choose_time_init()选择一个时钟源,用hpet_time_int()调用set_pit_init()将pit_clockevent注册为global_clock_event以及time_init_hook()来设置时钟中断处理程序。
  6. 通过sched_cloak_init()对时钟调度进行初始化。
二、调用过程
  1. 我们知道在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.
三、软定时器的结构:
  1. Express:用来与jiffies进行比较。
  2. Data:定时器超时处理函数需要传入的参数。
  3. Function:该指针变量保存着中断处理的程序。
  4. Base:指定该定时器属于哪个处理器。
四、创建并激活一个定时器
  1. 创建一个新的time_list对象。
  2. 通过init_time()对结构体中的个元素进行赋值。
  3. 设置定时的时间。
  4. 用add_timer()加入到合适的链表中。
五、明确中断时钟源和定时时钟源的区别
  1. 定时时钟源是决定xtime一个增加多少的关键所在。
  2. 定时时钟源是决定xtime在何时增加的管家,不决定增加的多少。
  3. 如果两个时钟源采用统一时钟,那么每次xtime增加1tick。
  4. 否则由精度高的时钟源决定,而一般定时时钟源精度高于中断时钟源。
  5. Xtime的值在定时处理函数中更新。
六、定时处理函数的流程
  1. 改变xtime的值。
  2. 把当前的xtime的时间写回到cmos中。Cmos是精度最低的时钟源。
  3. 对CPU的节拍加1.
  4. 对当前的进程的时间片减1.以及挂起的sleep进程的时间也减1.
  5. 给CPU的资源统计计数。

文件系统

一、明确VFS文件系统的作用
  1. 屏蔽底层各个文件系统之间的实现差异,给用户提供统一的接口。
二、主要数据结构
  1. 超级块对象:保存文件系统信息
  2. 文件对象:保存已打开的文件和进程的交互信息。
  3. 目录项对象:存放目录项和文件链接的信息。
  4. 索引结点对象:存放具体文件的一般信息。
  5. 在磁盘中的数据结构:
    1. 引导控制块:操作系统的初始引导块
    2. 盘控制块:磁盘信息等
    3. 文件控制块:文件属性。
    4. 目录结构:组织管理文件。
  6. 在内存中的数据结构:
    1. 系统打开文件表
    2. 进程打开文件表
三、Ext2的初始化
  1. 初始化超级块和组描述符。
  2. 检查时候有怀块,如果有,创建坏块列表。
  3. 对每个块组,保留保存超级块、位图、列表的块。
  4. 初始化位图和列表。
  5. 创建/root目录。
  6. 创建lost+found目录。
  7. 为上面的两个目录更新位图等信息。
  8. 如果有坏块,则将其在lost+found目录中组织起来。
四、Open函数的调用
  1. 当我们使用open函数的时候,首先通过按名查找,看在高速缓存中有没有查找的inode,如果有那么对其进行引用并加1。如果没有将创建新的vfs的inode对象和目录项对象等。
  2. Open函数调用的系统服务例程是sys_open函数,其接受的参数为文件目录和访问的模式。如果文件存在则返回一个文件描述符fd,如果不存在则返回-1.
  3. Read和write函数分调用的服务例程是sys_read和sys_write,接受三个参数。分别是一个文件描述符fd,一个对数据缓存的地方buf,和需要传输的数据的多少count。Read对将文件读入缓存区。Write则相反。
五、根文件系统的挂载
  1. 准备一个虚拟的文件系统rootfs,将其初始化为根文件系统
    1. Start_kernel后调用vfs_caches_init()初始化vfs。调用mnt_init()创建一个虚拟的rootfs,调用init_rootfs将其向内核注册。通过init_mount_tree挂载根文件系统。./挂载点就形成了。
  2. 将initrd文件加载到内存。在这里分两种情况。
    1. 格式为cpio_initrd。
      1. 直接将其解压到根文件下。
      2. 执行/init,如果执行成功则加载成功。如果失败则执行3。如果没有/init那么执行用户定义的根文件系统。然后执行3。
    2. 格式为image_initrd。
      1. 将其释放到/image_initrd文件下。
      2. 在内核下将其复制到/dev/ram0文件下。
      3. 如果/dev/ram0是根文件系统,那么挂载根文件系统,然后执行3。
      4. 否则执行Linuxrc,加载根文件系统的一些驱动等。而后挂载根文件系统,然后执行3。
    3. 执行/sbin/init,/bin/int,/etc/init等,如果成功则挂载根文件系统成功。

设备驱动

一、字符设备驱动程序的组成
  1. File_operation:成员大部分是指针,如果设备不支持则值为null。其中有一下操作:read、write、open、release。
  2. File:打开的文件, 每open一个文件就建立一个file结构体。
  3. Inode:关于文件的大量内部信息。
  4. 设备号:表示设备号和处理程序的对应关系。
  5. Chrdev[]:存放注册的设备驱动程序。
二、字符设备的访问过程一定要与文件系统结合
  1. 根据我们的设备号通过kobj_lookup函数找到对应的设备驱动程序。
  2. 将inode->i_dev指向查找到的dev。
  3. 将inode添加到cdev_list的链表中。
  4. 使用cdev的ops来设置文件的f_op。
  5. 如果ops中存在open函数,则执行open函数。
  6. 返回。
  7. 返回后对于其他操作read、write等也是通过这个cdev的ops中的函数进行的。
三、注册设备驱动程序
  1. 首先申请一个cdev的结构体。其中包含驱动模块、设备号范围、cdev_list、ops等。自己实现file_operations函数等。
  2. 字符设备的初始化。
    1. 静态的初始化,通过调用已用的函数进行初始化。
    2. 动态的初始化,主要用过malloc等方式,创建指针等来进行初始化。
  3. 在cdev初始化结束后,我们可以通过register_chrdev_region和alloc_register_chrdev_region两种方式进行申请注册设备号。第一种方式是用指定的设备号向系统进行申请。第二种是没有指定设备号由系统进行分配然后返回。把实现的file_operations和cdev进行绑定。申请得到设备号后,用cdev_add将设备注册。
  4. 在释放设备的时候,首先通过unregister_chrdev_region释放原来申请的设备号。然后调用cdev_del来注销设备。

参考资料:
深入理解Linux内核
备注:
转载请注明出处:http://blog.csdn.net/wsyw126/article/details/51803834
作者:WSYW126

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值