Linux内核主要由以下几个功能:进程管理、文件系统、IO体系结构和设备驱动程序、内存管理等等。
从这张图中,我们可以看到linux操作系统的架构。
对底层来说,Linux系统与硬件交互,管理所有资源,对上层来说,通过系统调用为系统程序和应用程序提供执行环境。有了整体的把握,下面对Linux的各个功能进行简单的介绍。
进程管理:进程管理是linux内核中最重要的部分,它保证了程序的正常执行。 在Linux中, 进程是系统资源分配的基本单位,也是使用CPU运行的基本调度单位。它实现了对进程的控制和调度。
文件系统:在Linux中,一切都是文件,通过对文件的定义和操作来控制设备的执行和数据的存储。并且使用VFS虚拟文件系统,实现对多种文件系统的兼容。
IO体系结构和设备驱动程序:在Linux中,驱动是应用软件和硬件的桥梁,应用程序只需要调用系统软件的应用编程接口,就可以让相应的硬件完成工作,通过设备驱动程序的定义和运行实现了对IO设备操作的控制。
内存管理:内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射、页面分配、页面回收、页面交换等,而且对性能也有很高的要求。
除去上面的这些功能,Linux内核还提供了内核同步、时间测量和进程间通信等功能。这些功能每一步的实现都非常复杂。
所以我用这学期学习的知识,准备以运行一个IO设备的驱动程序为例,通过对一开始的初始化定义和后面的运行过程这整个流程的介绍,来分析Linux内核相应功能的具体实现过程。
Linux文件系统
首先是文件系统,Linux系统的一切操作都是建立在文件上的。
对于Linux来说,字符设备和块设备都被映射到Linux文件系统的文件和目录,通过文件系统对系统调用接口的调用,运行中断处理程序和设备服务程序,即可访问设备,让设备完成相应的操作。
所以要想研究驱动设备的过程,必须对Linux的文件系统有所了解。
Linux支持多种文件系统,包括ext2、ext3、vfat、ntfs、iso9660、jffs、romfs和nfs等,为了实现这种支持,Linux采用虚拟文件系统VFS。
VFS对用户程序隐去各种不同文件系统的实现细节,为用户程序提供一个统一的、抽象的、虚拟的文件系统界面。
第一层为文件系统接口层,如open、write、close等系统调用接口。
第二层为VFS (Virtual File System)接口层。该层有两个接口:一个是与用户的接口;一个是与特定文件系统的接口。VFS与用户的接口将所有对文件的操作定向到相应的特定文件系统函数上。VFS与特定文件系统的接口主要是通过vfs-operations来实现的。
第三层是具体文件系统层,提供具体文件系统的结构和实现,包括网络文件系统,如NFS (network file system)。
Linux驱动程序
有了设备文件,我们需要将编写好的驱动程序与设备关联。
驱动程序注册
注册时机
如果设备驱动程序被静态编译进内核, 则注册发生在内核初始化阶段
如果作为一个内核模块来编译,则在装入模块的时候注册(并在卸载模块时注销)
注册过程
(1) 编译好的模块使用insmod加载到内核中
(2) 调用module_init()进行初始化,register_chrdev_regoion 进行申请设备号,运行设备号注册程序,如果注册成功,就分配内核内存,如果申请的设备号被注册,则返回注册失败。
(3) 内核中每个字符设备对应一个cdev结构的变量,在注册设备号后,运行cdev_init()对字符设备初始化,初始化有动态和静态两种,功能一样,只是使用的内存区不一样。
(4) Cdev结构初始化后,调用cdev.add()函数把他添加到系统。Cdev.add的函数中kobj_map将以dev的设备号为索引注册到cdev_map中,当后续需要打开字符设备时,通过调用kobj_lookup()函数,
根据设备号就可以找到cdev结构变量,从而取出其中的op字段。Cedv结构的file_operation结构包含一组函数操作,函数用用户编写的驱动程序里的函数赋值,对设备的操作,会转成对file_operation结构里函数的操作。
要想内核能够调用注册好的驱动程序,还需要对中断程序进行注册。
中断程序的注册
外部设备通过中断线将产生的中断通知处理器,在驱动程序采用中断模式工作前必须要申请注册一个中断号。再编写相关的中断服务例程ISR。
(1) 申请中断号:在interrupt.h文件中声明了自动检测IRQ的函数。调用probe_irq_on()函数保存返回值,将probo_irq_on的返回值作为参数传递给probe_irq_off,即可获得设备使用的IRQ。
(2) 中断程序的注册,在interrupt.h文件中声明了中断注册的函数。Request_irq函数用于安装ISR。
某个进程如果想要调用设备,对设备进行操作,就必须进入中断,通过注册好的中断程序来调用驱动程序。下面介绍Linux系统的中断。
中断
中断是操作系统最重要的部分。在内核完成初始化后,cpu开始执行指令,在执行下一条指令之前,CPU控制单元会检查在
当前指令时是否发生了一个中断或者异常。若发生,则进入中断处理过程。
中断过程
(1) 确定中断向量
(2) 根据中断向量读IDT表的第i项,并根据段描述符找到GDT表中相应的项。
(3) 确定中断的发生源和优先级
(4) 判断特权级是否发生变化,如果由用户级陷入内核级,则需要开辟新的栈段。
(5) 在栈中保存eflags、cs、eip等内容
(6) 装载cs、eip寄存器,进入中断处理程序。
(7) 中断处理完成后,执行iret指令退出,用保存在栈中的值恢复现场,装载cs、eip、和eflags等寄存器。
中断处理
上面介绍了中断的过程,下面详细介绍一下中断处理的过程。在这里,将会运行前面驱动程序为设备注册了中断函数。设备在IRQ线上发出中断信号,内核读取到中断信号,便开始处理中断。
系统初始化后,init_IRQ()函数用新的中断门来更新IDT,此时在interrupt数组中注册好的中断程序与IDT中某项关联,在保存好现场后,调用do_IRQ()对中断程序进行处理。do_IRQ()中完成相应对中断的处理,调用handle_IRQ_event()执行中断服务例程。
最后总结一下:IO设备是通过文件系统中的设备文件完成了定义和初始化,并且将程序员编写的驱动程序和中断程序注册到内核中。
在cpu运行指令时,如果进程中出现了对设备的操作,就会产生系统调用,进入中断,并处理之前初始化时注册的中断处理例程,完成对IO设备的操作。
课程总结和心得体会
这学期对Linux内核的学习,自己收获了很多,一开始虽然知道一个操作系统大致有哪些功能和一些粗略的运行过程,但通过这门课的学习,自己对操作系统的运行过程有了深入的了解,知道了许多细节。
而且老师在上课过程中通过对源码的分析,和课后的实验也让我对Linux内核的具体实现有了详细的了解,在此非常感谢李老师和孟老师的细心教导。唯一有点遗憾的是课时太短了,还有一些Linux内核中重要的内容没有学习。
希望这门课能越办越好。