Linux学习总结—启动、内存结构和管理

   系统引导过程主要由以下几个步骤组成(以硬盘启动为例)
  1 开机;
   2  BIOS 加电自检( POST——Power On Self Test ),包括检查 RAM keyboard ,显示器,软硬磁盘等等。 Intel 系列的 CPU 首先进入的是实模式,并开始执行位于地址 0xFFFF0 处的代码,也就是 ROM-BIOS 起始位置的代码;
3 搜索启动的操作系统,根据 BIOS 设置,可能会依次访问每个软盘的第一个扇区、硬盘、 CD-ROW 等;一旦找到有效的启动设备,将第一个扇区( 0 0 1 扇区,也就是 Boot Sector )的内容读入内存地址 0x7c00 处;
   4 检查( WORD 0000 7dfe 是否等于 0xaa55. 若不等于则转去尝试其他介质;如果没有其他启动介质,则显示  “No ROM BASIC”  ,然后死机;
   5 跳转到 0000 7c 00 处执行 MBR 中的程序 bootsect.S
6  MBR 先将自己复制到 0x90000 处,然后将紧接其后的 setup 部分(第二扇区)拷贝到 0x90200 ,将真正的内核代码拷贝到 0x100000 。以上这些拷贝动作都是以 bootsect.S setup.S 以及 vmlinux 在磁盘上连续存放为前提的;
7 bootsect.S 完成加载动作后,就直接跳转到 0x90200 ,这里正是 setup.S 的程序入口。  setup.S 的主要功能就是将系统参数(包括内存、磁盘等,由 BIOS 返回)拷贝到  0x90000-0x901FF 内存中,这个地方正是 bootsect.S 存放的地方,这时它将被系统参数覆盖。以后这些参数将由保护模式下的代码来读取。
   8  setup.S 还将 video.S 中的代码包含进来,检测和设置显示器和显示模式。最 后, setup.S 将系统转换到保护模式,并跳转到 0x100000 (对于 bzImage 格式的大内核是  0x100000 ,对于 zImage 格式的是 0x1000 )的内核引导代码, Bootloader 过程结束;
   9 Bootloader 跳转到 0x100000,  此处为 "arch/I386/init/head.S" 中的 startup_32  startup_32 的代码只需要设置一下全局变量,然后就跳转到 start_kernel 去了; start_kernel() "init/main.c" 中的 asmlinkage 函数,至此,启动过程转入体系结构无关的通用 C 代码中;
   10 start_kernel() 中设置与体系结构相关的环境、页表结构初始化、 Trap/IRQ 初始化、核心进程调度器初始化、时间 / 定时器初始化、控制台初始化、核心 Cache 初始化、内存初始化、内部及通用等各种 Cache 初始化、信号量初始化、其他部分初始化 (Init() smp_init())
   11 、启动 Init() 过程,创建第一个进程; Init() 中,取得  run-level  信息 执行  /etc/rc.d/rc.sysinit  脚本 激活核心的外挂式模块 (/etc/modules.conf),  然后 init  执行  run-level  的各个脚本 接着执行  /etc/rc.d/rc.local 脚本 最后执行  /bin/login 程序 登入之后开始以 Shell  控管主机;
   12 、启动完成。  
3Linux的内存结构和管理
Linux  内核按照  3:1  的比率来划分虚拟内存: 3 GB  的虚拟内存用于用户空间, 1 GB  的内存用于内核空间。内核代码及其数据结构都必须位于这  1 GB  的地址空间中,但是对于此地址空间而言,更大的消费者是物理地址的虚拟映射。
为了迎合大量用户的需要,支持更多内存、提高性能,建立一种独立于架构的内存描述方法, Linux  内存模型将内存划分成分配给每个  CPU  的空间。每个空间都称为一个 节点 ;每个节点都被划分成一些 区域 。区域(表示内存中的范围)可以进一步划分为以下类型:
  • ZONE_DMA0-16 MB):包含 ISA/PCI 设备需要的低端物理内存区域中的内存范围。
  • ZONE_NORMAL16-896 MB):由内核直接映射到高端范围的物理内存的内存范围。所有的内核操作都只能使用这个内存区域来进行,因此这是对性能至关重要的区域。
  • ZONE_HIGHMEM896 MB 以及更高的内存):系统中内核不能映像到的其他可用内存。
节点的概念在内核中是使用 struct pglist_data 结构来实现的。区域是使用 struct zone_struct 结构来描述的。物理页框是使用 struct Page 结构来表示的,所有这些 Struct 都保存在全局结构数组 struct mem_map 中,这个数组存储在 NORMAL_ZONE 的开头。节点、区域和页框之间的基本关系如下图所示。
 7.  节点、区域和页框之间的关系
 
对于 4 GB  的内存可以通过使用 kmap() ZONE_HIGHMEM 映射到 ZONE_NORMAL 来进行访问。
物理内存区域的管理是通过一个 区域分配器(zone allocator 实现的。它负责将内存划分为很多区域;它可以将每个区域作为一个分配单元使用。每个特定的分配请求都利用了一组区域,内核可以从这些位置按照从高到低的顺序来进行分配。
例如:
  • 对于某个用户页面的请求可以首先从普通区域中来满足(ZONE_NORMAL);
  • 如果失败,就从ZONE_HIGHMEM开始尝试;
  • 如果这也失败了,就从ZONE_DMA开始尝试。
这种分配的区域列表依次包括 ZONE_NORMAL ZONE_HIGHMEM ZONE_DMA 区域。另一方面,对于  DMA  页的请求可能只能从  DMA  区域中得到满足,因此这种请求的区域列表就只包含  DMA  区域。
 
Linux物理内存的管理
在物理页面管理上实现了基于区的伙伴系统( zone based buddy system )。对不同区的内存使用单独的伙伴系统 (buddy system) 管理 , 而且独立地监控空闲页。相应接口 alloc_pages(gfp_mask, order) _ _get_free_pages(gfp_mask, order) 等。
单单分配页面的分配器肯定是不能满足要求的。内核中大量使用各种数据结构,大小从几个字节到几十上百 k 不等,都取整到 2 的幂次个页面那是完全不现实的。 Linux 提供了 Cache Slab 分配算法,提供大小为 2,4,8,16,...,131056 字节的内存对象管理。对象的要素有大小、结构、构造和析构函数。每个 Cache 有若干 Slab 组成, Slab 2 的整数次幂为单位向分区申请页面。每个 Slab 被划分成若干个 size 大小的对象,每个对象之间可能需要 Cache 对齐。为了防止对象频繁的分配释放, Slab 并不物理上释放已经分配不使用的对象,当下次再申请对象时就不需要经过初始化直接把对象分配使用。 Cache Slab 都满时, Slab 像分区申请一个 Slab 大小需要的页面,并进行 Slab 和对象的初始化划分。
8 Slab 的对象结构
 
Cache kmem_cache_t 结构表示,主要包括 array_cache[] 每个 CPU 的本地对象 Cache slabs_full slabs_partial slabs_free 三个 Slab 双向链表, gfporder 每个 Slab 所需页面的次幂, colour colour_off 表示 Slab Cache 对齐参数及一些状态参数。
Slab 描述符用 slab 结构表示, list 指向 Slab 所在链表, colouroff 表示对象在 Slab 中的偏移, s_mem 表示第一个对象的地址, inuse 表示当前使用的对象数, free 表示第一个空闲的对象索引。每个 Slab 描述符后面放着一个 kmem_bufctl_t 数组,用来描述 Slab 中的空闲对象。
Slab 的管理结构既可以放在每个 Slab 页面上也可以集中放在其他位置,这取解决对象大小等因素。 Slab 结构如下图所示:
9 Slab 对象结构
管理区的初始化
管理区的初始化在函数start_kernel()->  setup_arch() ->zone_sizes_init()—>…—>  free_area_init_node() 中进行。该函数在setup_memory()建立引导内存分配器和paging_init()建立内核页表后调用。传递参数有管理区结点标志符nid,初始化的pg_data_t,管理区大小zones_size,第一个管理区的起始物理地址node_start_pfn等。函数原型:
void __meminit free_area_init_node(int nid, struct pglist_data *pgdat,
unsigned long *zones_size, unsigned long node_start_pfn, unsigned long *zholes_size)
alloc_node_mem_map() 用于为节点分配 mem_map 数组, free_area_init_core() 用于向每个 zone_t 填充相关信息,标记所有页保留,标记所有内存队列为空,清空内存位图;并初始化区的 mem_map
mem_map 的初始化:在 NUMA 系统中全局 mem_map 被处理成一个起始于 PAGE_OFFSET 的虚拟数组,全局 mem_map 从未被明确的申明国,取而代之被处理成起始于 PAGE_OFFSET 的虚拟数组。局部映射地址存储在 pg_data_t >node_mem_map 中,也存在于虚拟 mem_map 中。对于节点中的每个管理区,虚拟 mem_map 中表示管理区的地址存储在 zone >zone_mem_map 中。余下的节点都把 mem_map 作为真实的数组,因为只有有效的管理区会被节点所使用。
 
4Linux的内存初始化
引导内存分配器
由于硬件配置的多样性,在编译时静态初始化所有的内核存储结构是不现实的。物理页面分配器是如何分配内存完成自身的初始化的呢? Linux 是通过引导内存分配器 boot memory allocator 来完成的,该机制基于大部分分配器的原理,用位图代替空闲链表结构表示存储空间,位图中某位置 1 表示该页面已被分配,否则表示未被占有。该机制通过记录上一次分配页面帧号及结束时的偏移量实现小于一页的内存分配。该分配器也是基于 NUMA 上的节点分配的。
初始化引导内存分配器
每种体系结构都提供了 setup_arch() 函数,用于获取初始化引导内存分配器时所必须的参数信息。 setup_arch() 中调用 setup_memory() 初始化内存。 setup_memory() 中首先找出低端内存的 PFN 起点和终点和高端内存 PFN 的起点和终点,然后调用 setup_bootmem_allocator() 初始化引导内存分配器。
setup_bootmem_allocator() 处理流程如下:
1)         调用init_bootmem()->init_bootmem_core()用contig_page_data及低端内存起点和终点初始化对应的bootmem_data_t结构,并插入pgdata_list节点列表中。
2)         调用register_bootmem_low_pages()函数通过检测e820映射图,并在每一个可用页面上调用free_bootmem()函数,将其位设为1。
3)         依次调用reserve_bootmem()分别为bootmem保存实际位图所需的页面预留空间、预留BIOS使用的第一个物理页面0、为EBDA区预留4K区、为AMD 768MPX芯片预留1个页面。
4)         如果配置了CONFIG_SMP则为trampoline跳转预留4K空间;
5)         如果加入了睡眠机制,则调用acpi_reserve_bootmem()为之保留内存;
6)         调用find_smp_config()读取SMP配置信息并为之保留内存;
7)         如果配置了CONFIG_BLK_DEV_INITRD或者CONFIG_KEXEC则为它们保留内存;
内存分配和释放
引导内存的分配接口有: alloc_bootmem_node(pgdat, x) alloc_bootmem_pages_node(pgdat, x) alloc_bootmem_low_pages_node(pgdat, x) ,最终它们都调用一个核心函数 __alloc_bootmem_core (pg_data_t *pgdat, unsigned long size, unsigned long align, unsigned long goal) 。该函数主要处理流程:
1)         函数开始处保证所有参数正确;
2)         以goal参数为基础计算开始扫描的起始地址;
3)         检查本次分配是否可以使用上次分配的页面以节省内存;
4)         在位图中标记已分配为1,并将页中内容清0;
     内存释放函数只有一个free_bootmem_node(),它调用free_bootmem_core().该函数比较简单,对于受释放影响的每个完整页面的相应位设为0,如果原来就是0则调用BUG()提示重复释放错误。对于释放函数只有完整的页面才可释放,不能部分释放一个页面。
释放引导内存分配器
系统启动后,引导内存分配器就不在需要,内核提供mem_init()负责释放引导内存分配器并把其余的页面传人到普通的页面分配器中。
mem_init() 的流程如下:
1)         调用ppro_with_ram_bug ()检查奔腾Pro版本中是否存在一个bug,该bug阻止高端内存的某些页被使用;
2)         调用free_all_bootmem()释放引导内存分配器,并把低端地址页面转移到伙伴分配器中管理;
3)         遍历所有内存计算保留内存的页面数;
4)         调用set_highmem_pages_init()并逐页初始化高端内存;
5)         计算用于初始化的代码和数据的代码段、数据段和内存大小并打印内存信息;
6)         打印内核虚拟内存布局并进行检测;
7)         如果配置了CONFIG_X86_PAE当CPU不支持,则使系统瘫痪;
8)         测试WP位是否可用;
9)         如果配置了CONFIG_SMP,调用zap_low_mappings()填充swapper_pg_dir的PGD用户空间部分的表项,将这些页面都映射到0,这是因为在后面SMP辅助处理器启动时它需要为进入保护模式进行地址映射;
free_all_bootmem() ->  free_all_bootmem_core() 执行以下操作:
1)         对于该节点上分配器可以识别的所有未分配的页面:
l           将它们结构页面上的PG_reserved标志清0;
l           将计数器置为1;
l           调用__free_pages()以使伙伴系统分配器能建立free空闲列表;
2)         释放位图使用的所有页面,并将之交给伙伴分配器。
这样,伙伴系统就控制了所有的低端内存页面。
对于高端内存,由set_highmem_pages_init()进行初始化,该函数对highstart_pfn和highend_pfn之间的页面分别调用add_one_highpage_init()该函数将PG_reserved标志清0,初始化计数器为1,调用__free_page()将自己释放到伙伴分配器中。
初始化页表
前面已经分析过在启动时startup_32函数为系统8 MB 的物理内存设置页表。在setup_arch()调用setup_memory()初始化引导内存分配器后,需要完成对其余所有物理地址的映射。这里是通过paging_init()完成的。处理流程为:
1)         调用pagetable_init()初始化对应于ZONE_DMA、ZONE_NORMAL的所有物理内存必须要的页表;它对从FIXADDR_START开始的高端内存调用permanent_kmaps_init()进行初始化;
2)         将初始化后的swapper_pg_dir页表载入CR3寄存器中,以供换页单元使用;
3)         如果配置了CONFIG_X86_PAE且CPU支持,则设置CR4寄存器中的相应位;
4)         调用kmap_init()初始化带有PAGE_KERNEL标志位的每个PTE;
 
5Linux的进程地址空间
Linux 管理系统中的进程用一个task_struct数据结构来表示,Linux地址空间如下所示,对用户空间来说,它只能访问0~3G空间的范围。
10  内核地址空间
Linux 进程地址空间有mm_struct管理,task_struct中的mm成员表示。mm_struct数据结构包含了已加载可执行映象的信息和指向进程页表的指针,它还包含了一个指向vm_area_struct链表的指针,每个指针代表进程内的一个虚拟内存区域。vm_area_struct表示的内存区域是一个页面对齐的、并且相互之间不会重叠,它可能是一个malloc使用的进程堆或者是一个内存映射文件,也可以是mmap()分配的匿名内存区域。如果该区域是一个文件的映像,则vm_file字段被设置,通过vm_file可以找到该区域代表的地址空间内容。
图11 进程地址空间的数据结构
 
mm_struct结构的初始化和释放
mm_init() 初始化一个 mm 结构, allocate_mm() 用于从 slab 分配器分配一个 mm_struct 结构。系统中第一个 mm_struct 通过 init_mm() 初始化,后继的子 mm_struct 都通过 copy_mm() 进行复制得到。第一个 mm_struct 结构在编译时静态配置。 do_munmap() 负载删除一个 VMA 区域,释放相关页面,并修复区域。当进程退出时,必须删除与 mm_struct 相关的所有 VMA ,由函数 exit_mmap() 负责操作。该函数首先刷新 CPU 高速缓存,依次删除每一个 VMA 并释放相关页面,然后刷新 TLB 和删除页表项。  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值