jake1036

linux内核学习之八 内核体系结构

                                                                                     linux内核体系结构
       1 操作系统整体架构
          分为四个部分: 用户应用程序 , 操作系统服务部分 , 操作系统内核部分, 硬件系统部分。
       2  linux内核模式
           应用程序使用指定的参数值执行系统调用(int x80) ,使CPU从用户态切换到核心态,然后操作系统根据具体的参数值调用特定的系统调用服务程序,
           这些服务程序则根据需要再调用底层的一些支持函数以完成特定的功能。
          在完成了应用程序所要求的服务程序之后,操作系统从核心态回到了用户态,返回到应用程序中执行后面的指令。
          

3 内核体系结构
  内核体系结构主要由5部分组成 :
   (1) 进程调度 (2) 内存管理 (3) 进程间通信 (4) 文件系统 (5) 网络接口
 





下图为 物理内存使用的功能分布图 
           

内核模块占用了 物理内存的最开始的位置,紧接着是 高速缓冲区 ,系统从外部读取数据的时候,先把数据读取到高速缓冲区中,然后再从高速缓冲区中
 将数据移动到主内存区中。

   4  内存地址的概念
       存在以下三种不同的地址需要予以区别:
       (1) 程序的虚拟地址 
            是指由段选择符和段内偏移地址两个部分组成的地址。这两部分组成的地址并没有直接用来访问内存地址,而是通过分段机制地址变化处理和映射之后,才能找到对应的物理内存中。虚拟地址空间由GDT映射的全局地址空间和LDT映射成的局部地址空间组成。 

      (2) 线性地址 是虚拟地址到物理地址变换之间的中间层,是处理器可以寻址的内存空间中的地址。段中的偏移地址再加上相应段的基址,就生成了一个物理地址。

      (3)物理地址 是指出现在CPU外部地址上的寻址内存的地址信号,是地址变换的最终结果地址。
 
   5 虚拟地址 线性地址 和 物理地址 之间的关系 
     
      5.1内核代码和数据的地址
           对于linux0.11内核来说,在head.s中已经把内核代码段和数据段设置为长度为16MB的段,在线性地址空间中,这两个段的范围重叠,都是从线性地址空间0开始到地址0xFFFFFF共16MB地址范围。      在该范围中含有内核所有的代码,内核段表(GDT , IDT , TSS) ,页目录表和内核的二级页表,内核局部数据以及内核临时堆栈。(该临时堆栈将被用作第一个任务即任务0 的用户堆栈) ,  所以内核代码段和数据段在三种地址空间上的关系。
                      


   通过上述分析得到 :
        (1)内核代码段和数据段区域 在线性地址空间和物理地址空间中是一样的。
        (2)GDT和IDT在内核数据段中,因此它们的线性地址空间也同样等同于它们的物理地址地址。
        (3) 除任务0以外,所有的其他任务所需要的物理内存页面与线性地址中的不同或部分不同,因此内核 需要动态地在主内存区中为它们作映射操作,
              动态地建立页目录项和页表项。


    5.2任务0的地址对应关系
任务0是系统中一个人工启动的第一个任务,它的代码段和数据段长度被设置为640kb ,该任务的代码和数据直接包含在内核代码和数据段中,是从线性地址0开始的640kb内容,因此它可以使用内核代码已经设置好的页目录和页表进行分页地址变换。同样它的代码段和数据段在内存中也是重叠的。





 5.3  任务1的地址对应关系
         任务1 也是一个特殊的任务。它的代码也在内核代码区域中。与任务0不同的是在线性地址空间中,系统在使用fork 创建任务时,为存放任务1 的二级页表而在主内存区申请了一页内存来存放,并复制了任务0 的页目录和二级页表项。



 5.4其他任务的地址对应关系
       
     对于从任务2开始的进程来说,它们的父进程都是init进程(任务1) 。从任务2开始,如果任务号以nr来表示,那么任务nr在线性地址空间中的起始位置将是 nr * 64MB 。
      

5.4 用户申请内存的动态分配
      当用C函数库中的malloc函数进行内存动态分配的时候,内核并不会插手管理,因为每个进程的拥有64MB的内存空间。但是内核会为进程使用的代码和数据维护一个
当前位置值brk ,这个值保存在每个进程的数据结构中。它指出了进程代码和数据在进程地址空间中的末端位置,当malloc 函数为程序分配内存时,它会通过系统调用
brk() 把程序要求新增加的空间长度通知内核,内核代码可以根据malloc()函数所提供的信息来更新brk的值。但是此时并不会为新申请的空间映射物理内存页面。只有当程序寻址到某个不存在对应的物理页面的时候,内核才会进行物理内存页面的映射操作。
 

6  Linux 系统的中断系统

           CPU根据中断号获取中断向量值,即对应中断服务程序的入口地址值。为了让CPU由中断号查到对应的中断向量,就需要在内存中建立一张查询表。
     即中断向量表(32位模式下称为中断描述符表), 80x86支持256个中断,对应每个中断要安排一个中断服务程序。对应一个中断号,它的中断服务程序的入口地址就在
     保存在0x0000:N x 4 处。

        对于Linux 系统,在刚开始启动的时候,它会使用BIOS提供的显示和磁盘读取中断功能。 在内核正常运行之前则会在setup.s程序中重新初始化8259A芯片在head.s程序中重新设置的一张中断向量表。
    
 7 linux 系统调用
      系统调用是linux内核与上层应用程序进行交互通信的唯一接口。
      用户通过直接或者间接地调用中断int 0x80 ,并在eax寄存器中指定系统调用功能号,即可以使用内核资源,包括系统硬件资源。 
     在Linux 内核中,每个系统调用都具有唯一的一个系统调用功能号。 这些功能号 实际上对应于 系统调用处理程序指针数组表 sys_call_table[] 中的项的索引值。 
     
  7.1系统调用处理过程
      当应用程序经过库函数向内核发出一个中断可调用int 0x80时 ,就可以执行一个系统调用。
      其中寄存器eax中存储着中断调用号 ,而寄存器中携带的参数可以依次存放在ebx  , ecx , edx中。因此Linux 0.11中最多可以向内核中传递三个参数。


  8 Linux进程控制 
      对于0.11来说,系统最多同时运行64个进程。处理第一个进程使用“手工”创建以外,其余的进程都是通过fork系统调用产生。内核使用进程标识号来标识进程。
       进程由可执行的指令代码 数据 和堆栈区组成。进程中的代码和数据部分 分别对应一个执行文件中的代码段、数据段。每个进程只能访问自己的代码,数据和堆栈。
       linux 系统中一个进程可以在内核态或者是在用户态下执行,并且分别使用各自独立的内核态堆栈 和 用户态堆栈。  用户堆栈用于进程在用户态下临时保存调用函
数的参数、局部变量等数据。内核堆栈则含有内核程序执行函数调用时的信息。

     在linux内核中,进程通常叫做任务,而把运行在用户空间的程序称作 进程。
     

   8.1   任务数据结构 
             内核程序通过使用进程表对进程进行管理,每个进程在进程表中占有一项。 在Linux系统中,进程表项是一个task_struct 任务结构指针。或者叫做进程控制块,
       其中保存着控制和管理进程的所有信息。
            
   8.2  进程初始化
           系统进入保护模式之后,就开始执行系统初始化程序init/main.c 。该程序首先确定如何分配使用系统物理内存,然后调用内核各个部分的初始化函数,
       分别对内存管理、中断处理、块设备和字符设备、进程管理以及硬盘和软盘进行初始化处理。此后程序把自己“手工”移动到任务0中运行,并使用fork系统调用首先创建出进程1 。 在进程1中将继续执行应用环境的初始化并执行shell登录程序。而原进程则会在系统空闲的时候被调度执行,此时任务0 仅执行pause()系统调用,其中又回去执行调度函数。
  
  8.3 CPU允许低级别的代码通过调用门或者中断、陷阱门来调用和转移到高级别的代码中去。 但是 ,反之则不行。所以内核采用这种中断返回指令iret来实现,
        由高级别到低级别的跳转。

  9 创建新进程
     linux系统中创建新进程都是使用fork() 系统调用。所有的进程都是通过复制进程0而得到的,都是进程0的子进程。
      随后对复制的任务数据结构,进行修改。把当前进程设置为新进程的父进程,清除信号位图,并复位新进程的各统计值。
     接着根据当前进程设置任务状态段(TSS)中各个寄存器的值。
     
     此后系统设置新任务的代码和数据段基地址、限长、并复制当前进程内存分页管理的页表。此时,系统并不为新的进程分配实际的物理内存页面,
     而是让它共享父进程的内存页面。只有当父进程或新进程中任意的一个有写内存的操作时,系统才会为执行写操作的进程分配相关的独自使用的内存页面。
     这种处理方式叫做 写实复制技术。

     创建一个新的进程和加载一个执行程序文件时两个不同的概念。当创建子进程的时候,它完全复制了父进程的代码和数据区,并会在其中执行子进程部分的代码。
        
     9.1进程调度
          调度程序可以看在所有处于运行状态的进程之间分配CPU运行时间的管理代码。
          linux的进程是抢占式的,但是被抢占的进程仍然处于TASK_RUNNING 状态,只是暂时没有被CPU运行。进程的抢占发生在进程处于用户态执行阶段,
          在内核态执行时是不能被抢占的。
           
     9.2 调度程序
           schedule()函数首先扫描任务数组。通过比较每个就绪态(TASK_RUNNING)的运行时间递减滴答计数counter 的值来 确定当前哪个进程运行的时间最少。
           哪一个值越大,就表示运行时间还不长,于是就选择该进程,并使用任务切换宏函数切换到该进程执行。
            
            如果没有其他进程执行,系统就会选择进程0运行,进程0会调用pause()函数把自己置位可中断的睡眠状态,并再次使用schedule()函数。只要系统空闲就
           调用进程0运行。 
          
      9.3进程切换
            每当选择出一个新的可以运行的进程时,schedule()函数就会调用定义在include/asm/system.h中的switch_to()宏执行实际进程切换操作。该宏会把CPU的当前进程状态
            替换为新进程的状态。.
 
      9.4 终止进程
            当一个进程结束了运行或在半途中终止了运行,那么内核就需要释放该进程所占用的系统资源。这包括进程运行时打开的文件、申请的内存等。
          









           
               








        









     




 

        


    


  



























posted on 2010-10-08 19:49 kahn 阅读(1479) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理