您的位置:首页 > 运维架构 > Linux

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

2016-07-01 19:17 351 查看

中断和异常

一、明确中断异常描述表的作用:它与每一个中断和异常相联系,每一个向量在表中都有相应的处理程序的入口地址。

二、明确中断控制器的作用,表述清楚中断如何产生。

中断控制器是对中断和异常进行调度的程序。

中断的产生过程:

在计算机设备中每一个能产生中断的设备都与一条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等,如果成功则挂载根文件系统成功。

设备驱动

一、字符设备驱动程序的组成

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息