经过一个学期的课程学习,一个精简的Linux操作系统应该具有以下部分:
一个可以运行的精简Linux操作系统,应该由上述的5个部分紧密结合形成。其中中断处理是核心,无论是时钟系统、文件系统、驱动程序还是进程调度,都与
中断处理有着紧密的联系,下面就对这5个部分分别进行介绍。
中断处理和异常处理是操作系统必不可少的部分,使用中断和异常处理有两个重大意义:
每个能发出中断请求的硬件控制设备都有一个指派为IRQ的输出线。所有IRQ都与中断控制器的输入引脚相连。
中断控制器(PIC)通常会执行以下动作:
在允许中断前,内核必须建立IRQ号与I/O设备相对应,在新的I/O设备加入时,内核会初始化相应的驱动程序,此时会检索到设备挑选的IRQ线。
当执行了一条指令,cs和eip寄存器包含了下一条指令的逻辑地址。在执行这条指令之前,控制单元会检查在运行上一条指令时是否发生了一个中断或异常,如果发证了,控制单元将进行以下处理:
中断或异常处理完成后,相应的处理程序必须产生一条iret指令,把控制权交给被中断的进程,控制单元进行以下操作:
文件系统,就是操作系统中实现文件统一管理的一组软件、被管理的文件以及为实施文件管理所需要的一些数据结构的总称。
实现操作系统对其它各种不同文件系统的支持,就要将对各种不同文件系统的操作和管理纳入到一个统一的框架中。对用户程序隐去各种不同文件系统的实现细节,为用户程序提供一个统一的、抽象的、虚拟的文件系统界面,这就是所谓的虚拟文件系统(Virtual File System Switch,VFS)。
虚拟文件系统通常分为三个层次:
第一层为文件系统接口层,如open、write、close等系统调用接口。
第二层为VFS (Virtual File System)接口层。该层有两个接口:一个是与用户的接口;一个是与特定文件系统的接口。VFS与用户的接口将所有对文件的操作定向到相应的特定文件系统函数上。VFS与特定文件系统的接口主要是通过vfs-operations来实现的。
第三层是具体文件系统层,提供具体文件系统的结构和实现,包括网络文件系统,如NFS (network file system)。
VFS的基本思想是引入一个通用文件模型,这个模型能够表示所有支持的文件系统。
通用模型通常由下列对象组成:
通用模型中的每个对象,都对应一个操作对象,它描述了内核针对主要对象的可以使用的方法。
当用户进程打开一个文件时(如使用open系统调用),会在该进程的task_struct中进程当前工作目录和根目录相关信息以及当前打开的文件信息。
文件描述符fd用来描述打开的文件,每个进程都用一个files_struct来记录文件描述符的使用情况,这个结构称为用户打开文件表。
系统打开文件表是由file对象组成的链表,它在内核态由内核控制。用户打开文件表,是进程的私有数据,因而应用程序只能使用文件描述符作为参数的系统调用才能访问系统打开文件表。
Linux中的每个进程都有两个数据结构来描述进程与文件的相关信息,一个进程所处的位置由fs_struct结构来描述,它包含了两个指向VFS dentry的指针,分别指向root和pwd。进程打开的文件是由files_struct结构来描述,最多能同时打开的文件为OPEN_MAX个,每当打开一个文件时,就从files_struct结构中找一个空闲的文件描述符,使它指向打开文件的描述结构file,对文件的操作通过file结构中定义的操作函数的VFS inode的信息来完成。
驱动是应用软件和硬件得桥梁,它使得应用软件只需要调用系统软件得API就可以让硬件去完成要求得工作。
Linux设备驱动包括字符设备、块设备和网络设备。
Linux的内核模块机制给内核提供了很强的灵活性,用户通过加载内核模块方便的给内核添加功能,用户同样也可以将内核不需要用的功能卸载。用户通过内核模块机制可以动态的把需要用到的驱动程序动态地加入内核。
内核模块在内核态运行,并且只能调用和使用内核提供地函数。
Linux将硬件设备看作是一个特殊的文件来操作,该文件被称为设备文件,系统通过对设备文件的读写等操作,实现对外设的读写等操作。
驱动程序是设备文件与直接外设间的桥梁。
Linux驱动的层次结构如下图所示:
字符设备驱动与字符设备之间的关系如下图所示:
Linux必须完成两种主要的定时测量,具体区别如下:
所有的PC都包含一个叫RTC的时钟,它是独立于CPU和所有其他芯片的,即时PC被切断电源,RTC还继续工作,因为它靠一个小电视或蓄电池供电。
RTC能在IRQ8上发出周期性的中断,Linux只用RTC来获取时间和日期。不过,通过对/dev/rtc设备文件进行操作,也允许进程对RTC变成,内核通过0x70和0x71 I/O端口访问RTC。
所有x86微处理器都包含一条CLK输入引线,它接收外部振荡器的时钟信号。x86包含一个计数器,在每个时钟信号到来时加1,该计数器利用64位的TSC寄存器实现,可以通过汇编rdtsc读取该寄存器。
PIT通过编程可以以内核确定的固定频率不断地通过IRQ0来发出时钟中断,来通知内核又过去一个时间间隔。
PIT每次发出时钟中断地间隔叫做一个tick,它地长度以纳秒地单位存放在tick_nsec变量中。tick如果设置地短,则需要CPU在内核态花费更多地时间,因而用户程序运行地稍慢。
Linux必定执行与定时相关的操作,例如,内核会周期性地:
在单处理器系统中,所有定时活动都由IRQ0上的时钟中断触发。
内核初始化时期,time_init()函数被调用来建立计时体系结构。具体进行一下操作:
time_interrupt()是PIT或HPET的中断服务例程,它执行以下步骤:
Linux中一个任务(task)就是一个进程,每个进程都具有一定的功能和权限,它们都运行在各自独立的虚拟地址空间。在Linux中,进程是系统资源分配的基本单位,也是使用CPU运行的基本调度单位。存放在磁盘上的可执行文件的代码和数据的集合称为可执行映像,当一个可执行映像装入系统中运行时,它就形成了一个进程。
进程控制块PCB是名字为task_struct的数据结构,它称为任务结构体。任务结构体中容纳了一个进程的所有信息,是系统进行进程管理的有效手段。当一个进程被创建时,系统就为该进程建立一个task_struct任务结构体,当进程运行结束时,系统撤销该进程的任务结构体。
进程的任务结构体是进程存在的唯一标志,Linux在内存空间中开辟了一个专门的区域存放进程的任务结构体。
进程结构体放在动态内存中而且和内核态的进程栈放在一个独立的8KB的内存区中。
Linux系统为每个用户进程分配了两个栈,用户栈和内核栈。当一个进程在用户空间执行时,系统使用用户栈,当在内核空间执行时,系统使用内核栈。内核进程只有内核栈,没有用户栈。
当进程从用户空间陷入到内核空间时,首先,操作系统在内核栈中记录用户栈的当前位置,然后将栈寄存器指向内核栈。内核空间的程序执行完毕后,操作系统根据内核栈中记录的用户栈位置,重新将栈寄存器指向用户栈。
Linux使用PID进程标识符来唯一标识一个进程,每个进程的PID号都存放在进程描述符的pid域中,新创建的进程通常是前一个进程的PID加1。Linux内核通过管理一个pidmap-array位图来标识当前已分配的pid号和闲置的pid号。
Linux将可运行状态的进程统一放置在运行队列中,并按照不同的优先级将可运行状态的进程放置在不同的链表中,来加速内核调度程序寻找进程的扫描效率,这种可运行状态的双向循环链表,也叫做运行队列。在多处理系统中,每个CPU都有它自己的运行队列,即自己的进程链表集,所有这些链表都由一个单独的prio_array_t数据结构来实现。
从本质上说,进程切换由两步组成:
尽管每个进程可以有自己的地址空间,但所有的进程只能共享CPU的寄存器。因此,在恢复一个进程执行之前,内核必须确保每个寄存器装入了挂起进程时的值,这样才能正确的恢复一个进程的执行。
硬件上下文指得时进程恢复执行前必须装入寄存器得一组数据,包括通用寄存器得值以及一些系统寄存器。
如果prev标识切换出得进程描述符,next标识切换进得进程描述符,进程切换可以理解为,保存priv上下文,用next上下文代替prev。
switch_to宏执行进程切换,schedule()函数会调用这个宏。进程切换得函数调用关系如下:schedule()->context_switch()->switch_to()->__switch_to()。
当schedule()需要暂停A进程切换到B进程时,就会由context_switch()完成全局页表项切换,由switch_to()和__switch_to()完成内核堆栈和硬件上下文切换。
在以下情况时,通常会发生进程调度:
Linux的进程调度是基于优先级的调度,Linux的进程分为普通进程和实时进程,实时进程的优先级要高于普通进程。Linux中进程的优先级是动态的,避免进程饥饿。
Linux对实时进程和普通进程采用不同的调度策略。
下面以I/O设备为例,描述一个精简的Linux操作系统在I/O驱动时是如何运作的。
在Linux系统中,设备驱动程序以静态方式或动态的方式注册到内核中,假设这里再内核编译时以静态方式注册到内核中,设备程序驱动程序根据编写的驱动代码自动创建
设备文件,并将设备文件与设备驱动程序相连,这一步骤也是Linux文件系统与设备驱动程序之间进行了结合,同时设备驱动程序中页定义了对于设备文件的各种操作访问函数。
设备驱动程序为了能成功运行,还需要向Linux操作系统申请中断,申请中断号一般有两种方式,一种方式是指定中断号,另一种方式是使用自动探测的方式来决定中断号。
确定中断号后,我们就紧接着需要确定中断处理函数,也就是中断服务例程ISR,通过调用request_irq来向指定中断号注册中断服务例程,注册完成后,操作系统便可以执行do_irq函数
来根据执行的IRQ来执行相应的中断服务例程对应的中断处理函数。这一步就是设备驱动程序和中断系统的相结合。
至此,一个I/O设备的驱动程序应该已经成功注册完毕,用户可以进行相应的设备操作。
假设用户态开启了一个进程来访问I/O设备对应的设备文件,则会触发系统调用,从用户态陷入到内核态,并执行由设备驱动程序注册设备文件相应的文件处理函数。加入该I/O设备是一个
可插拔的闹钟,我们通过访问其设备文件来设置闹钟到时时间。设置成功时间后,该设备驱动程序应该会创建一个动态定时器,该动态定时器。Linux在触发IRQ0引起的PIT时钟中断时,
会更新jiffies的计数值,同时在do_IRQ完成该时钟中断处理时,会检查软中断,此时会发现动态定时器注册的TIMER_SOFRIRQ,会执行该软中断处理函数,并讲动态定时器的值与当前
jiffies值进行比较,判断是否超时,从而执行设备驱动程序注册的超时函数,让闹钟开始响。
原文:https://www.cnblogs.com/cavegf/p/13274454.html