MPoooooo

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

  首先,Linux内核主要由五个部分组成,他们分别是:进程调度模块、内存管理模块、文件系统模块、进程间通信模块和网络接口模块。

本部分所讲的内存是内存管理模块,其主要的作用是有以下几点:

  1:内存管理模块还支持虚拟内存的管理方式,使得Linux支持的进程使用比实际的内存空间有更多的内存容量。

  2:保证所有的进程都能够安全的共享机器的主内存区;

  3:利用文件系统把暂时不用的内存数据转移到外部存储设备上去,需要的时候交换回来。

 

  我们在下面的叙述中逐条的剖析Linux是如何完成上面的工作的。

  首先,我们能够使用的地址空间要比实际的空间地址大小要大的多。我们首先认识几个概念:

  虚拟地址:直接又程序产生的地址空间,由段选择符(说白了就是说在哪一个段上)和段内偏移量组成;

  逻辑地址:由程序产生的,与段相关的偏移地址。应用程序员可以与逻辑地址打交道,但是分段和分页对他们来说是完全透明的;

  线性地址:是由虚拟地址到物理地址的中间层,是处理器可寻址的内存空间,程序代码产生的段选择符(可以知道其段的基址)然后加上段内偏移量就是其线性地址。如果启用了分页机制,那么线性地址要在经过变换变成物理地址,如果没有启用分页机制,那么他的线性地址就是物理地址(Intel 80386 线性地址为4G)。

  物理地址:是指出现在CPU外部总线上的寻找物理内存的地址信号,是最终的地址。

  虚拟内存:是指计算机所呈现出来的内存量远大于他的实际拥有的内存空间大小。

 

  内存—分段实现机制:

  在内存分段机制中,一个程序的逻辑地址通过分段机制自动映射到中间层的4G的线性地址中。程序每次的引用都是对内存段的中内存的引用。当程序进行引用时,其地址就是计算好的物理地址。

  但是,我们经过计算得到的物理地址,可能是一块根本就不存在的内存区域(内存的页表中标识出相应的页面不在内存中),当一个进程引用这样的一个地址的时候,就是发生缺页中断,并将引起中断的线性地址放入到CR2寄存器中。因此在处理中断的时候,就能够从寄存器中得到该页面的确定地址,这样就可以将页面从二级存储空间中(比如硬盘上)加载到物理内存中。如果此时物理内存已经被全部占用,那么可以借助二级存储空间的一部分作为交换区(这就是在安装Linux的时候预留的swap区域的作用),将一部分不使用的页面放入到交换区中,然后将要求的页面放入内存。

  在Linux 0.11系统中,内核设置设置全局描述符表GDT中的段描述符项数最大为256,其中两项空闲,两项系统使用,每个进程使用两项。因此系统最多容纳(256-2)/2 个任务,并且虚拟地址的范围126*64MB约等于8G,但是0.11内核中人工定义的任务数量为64个,每个任务逻辑地址范围为64MB,各个任务地址的起始地址为任务号码*64MB,因此全部任务使用的地址空间约为4GB。图中显示的是有四个任务的情况,内核的代码段和数据段被映射到内存开始16MB的部分,并且代码和数据在一个区域,完全相互重叠。第一个任务(任务0)比较特别,它是由内核启动,他的代码和数据包含在内核的代码和数据中,他的代码和数据段是从0-640KB的区域,其代码段与数据段也是完全重叠,并且与内核的代码数据段有重叠的部分。

                   

进程0与进程1:

    在这里大家可能会对进程0和进程1存在疑问,为什么这两个进程和别的进程是不一样的,在这里简单的对其进行介绍,在以后的学习中,会相信的分析。

进程0的介绍:

    1. 进程0是所有其他进程的祖先, 也称作idle进程或swapper进程.

    2. 进程0是在系统初始化时由kernel自身从无到有创建.

    3. 进程0的数据成员大部分是静态定义的,即由预先定义好的INIT_TASK, INIT_MM等宏初始化.

    进程0的描述符init_task定义在arch/arm/kernel/init_task.c,由INIT_TASK宏初始化。 init_mm等结构体定义在include/linux/init_task.h内,为init_task成员的初始值,分别由对应的初始化宏如INIT_MM等初始化

    进程1的介绍:

进程0最终会通过调用kernel_thread创建一个内核线程去执行init函数,这个新创建的内核线程即Process 1(这时还是共享着内核线程0的资源属性如地址空间等)。init函数继续完成剩余的内核初始化,并在函数的最后调用execve系统调用装入用户空间的可执行程序/sbin/init,这时进程1就拥有了自己的属性资源,成为一个普通进程(init进程)。至此,内核初始化和启动过程结束。下面就进入了用户空间的初始化,最后运行shell登陆界面。(注:Init进程一直存活,因为它创建和监控在操作系统外层执行的所有进程的活动。)

                   

 

  在实际写代码的过程中,我们经常使用动态的对内存进行分配和回收,比如malloc和free(new与delete的底层实现也是malloc和free),那么Linux是怎么样进行管理的呢?

  首先要说明的一点就是,我们所申请的内存容量和大小是由高层次的C函数进行管理,内核本身无权进行插手管理,因为内核已经为每个进程分配了64M的线性空间,所以进程的寻址空间只是在他的64M的空间中进行寻址。

  但是内核在这中间并不是无所作为的,内核会为进程使用的代码和数据空间维护一个当前位置值brk,这个值保存在每个进程的数据结构中。他指出进程代码和数据(包括动态分配的数据空间)在进程地址中末端的位置,当malloc函数为程序分配内存时候,他会调用brk()把程序新增长的长度通知内核,然后内核代码更新进程数据结构中的brk值,但是,这个时候并不为新申请的空间映射物理内存页面。只有当程序寻找不存在的物理地址的时候,内核才会进行相应的物理地址映射工作。(当进程代码寻找的某个页面不存在,并且该页面处于某个进程堆的范围内,那么CPU产生一个缺页异常,建立映射关系)。

 

 

  上面所说的就是解释的Linux内存管理的第一个功能,下面主要解释一下Linux是如何保证所有的进程都能够安全的共享机器的主内存区。

  首先Intel 80X86的CPU有四个保护级别,0具有最高的保护级别,而3的级别最低。Linux适用0和3两个保护级别。内核的代码和数据本身会由系统中所有的进程共享,因此他保存在全局地址空间中。而每个任务进程有着自己的局部地址空间。每个进程的地址空间是别的任务看不到的。

  当一个任务记性系统调用而陷入内核代码执行中,我们就称进程进入到内核运行态。此时处理器处于特权级别最高的0级内核代码的执行中。当进程处于内核态的时候执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程执行用户自己的代码时,则称其为用户运行态,即此时的处理器在特权3上。当用户执行程序突然被中断程序中断的时候,此时用户程序也可以象征性的称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。用户态与内核态将在后面进行详细的分析。

 

posted on 2015-04-23 17:14  MPoooooo  阅读(634)  评论(0编辑  收藏  举报