分享

Linux内核模式

 春华_秋实 2012-06-03

Linux内核模式

Linux内核模式
  目前,操作系统内核的结构模式主要可分为整体式的单内核模式和层次式的微内核模式。而Linux0.11是 采用了单内核模式。单内核模式的主要优点是内核代码结构紧凑,执行速度快,不足之处主要是层次结构性不强。

  在单内核模式的系统中,操作系统所提供服务的流程为:应用主程序使用指定的参数值执行系统调用指令(init x80),使CPU从用户态(User Mode)切换到核心态(Kernel Model),然后操作系统根据具体的参数值调用特定的系统调用服务程序,而这些服务程序则根据需要在底层的一些支持函数以完成特定的功能。在完成了应用程序所需要的服务后,操作系统又从核心态切换回应用态,返回到应用程序中继续执行后面的指令。因此概要地讲,单内核模式的内核也可以粗略地分为三个层次:调用服务的主程序层,执行系统调用的服务层和支持系统调用的底层函数。

2.2 Linux内核系统体系结构

  Linux 内核主要由5个模块构成,它们分别是:进程调度模块,内存管理模块,文件系统模块,进程间通信模块和网络接口模块。

  进程调度模块用来负责控制进程对CPU资源的使用。所采取的调度策略是各进程能够公平合理地访问CPU,同时保证内核能及时地执行硬件操作。内存管理模块用于确保所有进程能够安全地共享机器主内存区,同时,内存管理模块还支持虚拟内存管理方式,使得Linux支持进程使用比实际内存空间更多的内存容量。并可以利用文件系统把暂时不用的内存数据块会被交换到存储设备上去,当需要时再换回来。文件系统的模块用于支持对外部设备的驱动和存储。虚拟文件系统模块通过向所有的外部存储设备提供一个通用的文件接口,隐藏了各种硬件设备的不同细节。从而提供并支持与其他操作系统兼容的多种文件系统格式。进程间通信模块子系统用于支持多种进程间的信息交换方式。网络接口模块提供对多种网络通信标准的访问并支持许多网络硬件。

  这几个模块之间的依赖关系如下图

  由图可以看出,所有的模块都与进程调度模块存在依赖关系。因为它们都需要依赖进程调度程序来挂起(暂停)或重新运行它们的进程。通常,一个模块会在等待硬件操作期间被挂起,而在操作作完后才继续运行。

2.3 中断机制

  在使用80x86组成的PC机种,采用了两片8259A可编程中断控制芯片。每片可以管理8个中断源。通过多片级联方式,能构成最多管理64个中断向量的系统。在PC/AT系列兼容机中,使用了两片8259A芯片,共可管理15级中断向量。其级连示意图见下面的图。其中从芯片的INT引脚连接到主芯片的IR2引脚上。主8259A芯片的端口基地址是0x20,从芯片是0xA0。

  

  在总线控制器控制下,8259A芯片可以处于编程状态和操作状态。编程状态是CPU使用INT或OUT指令对8259A芯片进行初始化编程的状态。一旦完成了初始化编程,芯片即进入操作状态,此时芯片即可随时相应外部设备提出的中断请求(IRQ0-IRQ15)。通过中断判优选择,芯片可将选中当前最高优先级的中断请求作为中断服务对象,并通过CPU引脚INT通知CPU外中断请求的到来,CPU响应后,芯片从数据总线D7-D0将编程设定的当前服务对象的中断号送出,CPU由此获取对应的中断向量值,并执行中断服务程序。

  对于Linux内核来说,中断信号通常分为两类:硬件中断和软件中断(异常)。每个中断是由0-255之间的一个数组来标识。对于中断int0--int31,每个中断的的功能由Intel公司固定设定或者保留用,属于软件中断,但Intel公司称之为异常。因为这些中断是在CPU执行指令时探测到异常情况而引起的。通常还可分为故障(Fault)和陷阱(traps)两类。中断int32--int255可以由用户自己设定。在Linux系统中,则将int32--int47对应于8259A中断控制芯片发出的硬件中断请求信号IRQ0-IRQ15,并把程序编程发出的系统调用(system_call)中断设置为int128。

2.4 系统定时

  在Linux0.11内核中,PC机的可编程定时芯片Intel8253被设置成每隔10ms就发出一个时钟中断(IRQ0)信号。这个时间节拍就是系统运行的脉搏,我们称之为1个系统滴答。因此每经过一个滴答就会被调用一次时钟中断处理程序(timer_interrupt)。该处理程序主要用来通过jiffies变量来累计自系统启动以来经过的滴答数。每当发生一次时钟中断该值就增1。然后从被中断程序的段选择符中取得当前特权纪CPL作为参数调用do_timer()函数。

  do_timer()函数则根据特权级队当前进程运行时间做累计。如果CPL=0,则表示进程是运行在内核态时被中断,因此把进程的内核运行时间统计值stime增1,否则把进程用户态运行时间统计值增1。如果程序添加过定时器,则对定时器链表进行处理。若某个定时器时间到(递减后等于0),则调用该定时器的处理函数。然后对当前进程运行时间进行处理,把当前进程运行时间减1。如果此时当前进程时间片并还大于0,表示其时间片还没有用完,于是就推出do_timer()继续运行当前进程。如果此时进程时间片已经递减为0,表示该进程已经用完了此次使用CPU的时间片,于是程序就会根据被中断程序的级别来确定进一步处理的方法。若被中断的当前进程是工作的用户态的(特权级别大于0),则do_timer()就会调用调度程序schedule()切换到其他进程去运行。如果被中断的当前进程工作在内核态,也即在内核程序中运行时被中断,则do_timer()会立即退出。因此这样的处理方式决定了Linux系统在内核态运行时不会被调度程序切换。内核态程序是不可抢占的,但当处于用户态程序中运行时则是可以被抢占的。

  2.5 Linux进程控制

  程序是一个可执行的问题建,而进程(process)是一个执行中的程序实例。利用分时技术,在Linux操作系统上同时可以运行多个进程。分时技术的基本原理是把CPU的运行时间划分成一个个规定长度的时间片,让每个进程在一个时间片内运行。当进程的时间片用完时系统就利用调度程序切换到另一个进程去运行。因此实际上对于具体单个CPU的机器来说某一个时刻只能运行一个进程。但由于每个进程运行的时间片很短(例如15个系统滴答=150ms),所以表面看起来好像所有进程子阿同时运行着。

  对于Linux0.11内核来讲,系统最多可由64个进程同时存在。除了第一个进程是"手工"建立以外,其余的都是进程使用系统调用fork创建的新进程,被创建的进程成为子进程(Child Process),创建者,则称为父进程(parent process)。内核程序使用进程标识号(process ID,pid)来标识每个进程。进程由可执行的指令代码,数据和堆栈区组成。进程中的代码和数据部分分别对应一个可执行文件中的代码段,数据段。每个进程只能执行自己的代码和访问自己的数据及堆栈区。进程之间相互之间的通信需要通过系统调用来进行。对于只有一个CPU的系统,在某一个时刻只能有一个进程正在运行。内核通过进程调度程序分时调度各个进程运行。

  Linux系统中,一个进程可以在内核态(Kerneo mode)或者用户态(user mode)下执行,因此Linux内核堆栈和用于堆栈是分开的。用户堆栈用于进程在用户态下临时保存调用函数的参数,局部变量等数据。内核堆栈则含有内核程序执行函数调用时的信息。

  2.5.1任务数据结构

  内核程序通过进程表对进程进行管理,每个进程在进程表中占有一项。在Linux系统中,进程表项是一个task_struct任务结构指针。任务数据结构定义在头文件include/linux/sched.h中。有些书上称其为进程控制块PCB(Process Control Block)或者进程描述符PD(Processor Descriptor)。其中保存着用于控制和管理进程的所有信息。主要包括进程当前运行的状态信息,信号,进程号,父进程号,运行时间累计值,正在使用的文件和本任务的局部描述符以及任务状态段信息。该结构每个字段的含义如下所示。

当一个进程在执行时,CPU的所有寄存器中的值,进城的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换(switch)至另一个进程时,它就需要保存当前进程的所有状态,也即保存当前进程的上下文,以便在再次执行该进程时,能够恢复到切换时的状态执行下去。在Linux中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核状态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中断服务结束时能恢复被中断进程的执行。

2.5.2进程运行状态

  一个进程在其生存期内,可处于一组不同的状态下,称为进程状态。见下图2-6所示。进程状态保存在进程任务结构的state字段中。当进程正在等待系统中的资源而处于等待状态时,则称奇处于睡眠等待状态。在Linux系统中,睡眠等待状态被分为可中断的和不可中断的等待状态。

  运行状态(TASK_RUNNING)

  当进程正在被CPU执行,或已经准备就绪随时可以由调度程序执行,则称该进程为处于运行状态(running)。进程可以在内核态运行,也可以在用户态运行。当系统资源已经可用时,进程就被唤醒而进入准备运行状态,该状态称为就绪态。这些状态在内核中表示方法相同,都被称为处于TASK_RUNNING状态。

  可中断睡眠状态(TASK_INTERRUPTIBLE)

  当进程处于可中断等待状态时,系统不会调度该进程执行。当系统产生一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,都可以唤醒进程转换到就绪状态(运行状态)。

  不可中断睡眠状态(TASK_UNINTERRUPTIBLE)

  与可中断睡眠状态类似。但处于该状态的进程只有被使用wake_up()函数明确唤醒时才能被转换到可运行就绪状态。

  暂停状态(TASK_STOPPED)

  当进程收到信号SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。在Linux0.11中,还为实现对该状态的转换处理。处于该状态的进程将被作为进程终止来处理。

  僵死状态(TASK_ZOMBIE)

  当进程已停止运行,但其父进程还没有询问其状态时,则称该进城处于僵死状态。

当一个进程的运行时间片用完,系统就会使用调度程序强制切换到其他的进程去执行。另外,如果进程在内核态执行时需要等待系统的某个资源,此时该进城就会调用sleep_on()或者sleep_on_interruptible()自愿放弃CPU使用权,而让调度程序去执行其他程序。进程则进入睡眠状态(TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE)。

  只有当进程从"内核运行态"转移到"睡眠状态"时,内核才会进行进城切换操作。在内核态下运行的进程不能被其他进程抢占,而且一个进程不能改变另一个进程的状态。为了避免进程切换时造成内核数据错误,内核在执行临街区代码时禁止一切中断。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多