请选择 进入手机版 | 继续访问电脑版

环信

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: mmap uikit gcc
查看: 498|回复: 4

Linux内核: 内存管理

  [复制链接]

42

主题

68

帖子

229

积分

中级会员

Rank: 3Rank: 3

积分
229
发表于 2015-10-4 03:55:47 | 显示全部楼层 |阅读模式
本帖最后由 master 于 2016-1-6 15:17 编辑

CPU利用段式内存管理单元(Segmented Unit),将逻辑地址转换成线性地址,再利用页式内存管理单元(Paging Unit),把线性地址最终转换为物理地址:
bianhuan.png

16位CPU段式内存管理单元(Segmented Unit):CPU内部拥有20位的地址线,寻址范围是2的20次方(1048576 = 1Mb内存空间),CPU用于存放地址的寄存器只有16位,所以只能访问65536(2的16次方)个存储单元(64Kb)。为了能访问1Mb的内存空间,CPU采用段式内存管理模式,并在CPU内部加入段基地址寄存器和段偏移地址寄存器(16位CPU有4个段寄存器),把1Mb的内存空间分为若干个逻辑段,段的起始地址(段地址)为16的倍数,使最后4个二进制位必须全为0。
逻辑地址等于: 段基地址 + 段内偏移量
所以物理地址等于:段基地址寄存器 x 16 + 逻辑地址的段偏移量

32位CPU段式管理,在实模式下32位CPU内存管理和16位CPU是一致的。
保护模式下,段基地址长达32位,每个段的最大容量可达4Gb,段寄存器的值是段地址选择器(Selector),该选择器从内存中得到一个32位的段地址,存储单元的物理地址就是该段地址加上段内偏移量。
selector_descriptor.jpg

分页管理:线性地址被分为固定长度的组(例如用4Kb为一个页)。
物理页,分页单元把所有的物理内存划分为固定长度的管理单元,长度一般和线性地址相同。

page.jpg

linearTOphysical.jpg
Linux内核所有段的基地址均为0,每个段的逻辑地址空间范围为0-4G,所以逻辑地址等于线性地址(逻辑地址的偏移量字段的值与线性地址的值总是相同的),在Linux中逻辑地址,线性地址(虚拟地址),全是一样的。

页表存在内存中,页的大小是4k,页表的大小是需要访问的所有物理地址除以4k。内存4Gb,页表大小为1M。

虚拟地址和物理地址转换:
arch/arm/include/asm/memory.h
  1. #define __virt_to_phys(x)        ((x) - PAGE_OFFSET + PHYS_OFFSET)
  2. #define __phys_to_virt(x)        ((x) - PHYS_OFFSET + PAGE_OFFSET)
复制代码
页表初始化:
init/main.c
  1. asmlinkage void __init start_kernel(void)
  2. static void __init mm_init(void)
复制代码
kernel/fork.c
  1. static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p)
复制代码

Linux采用虚拟内存管理技术:每个进程都有独立的进程地址空间,该空间大小为3G,用户空间进程看到的都是虚拟地址,无法看到实际的物理地址。
用户空间内存:0~0xbfffffff,内核空间3g~4g。

进程空间:用户空间对应进程,每当进程切换,用户空间就会跟着变化,实际物理内存只有当进程真的去访问新获取的虚拟地址是,才会有“请页机制”产生“缺页”异常,从而进入分配实际页框程序,该程序会告诉内核去为进程分配物理页,并建立对应的页表,虚拟内存就会映射到物理地址上,fork(),execve(),malloc()等进程相关操作不会获得物理地址。
每个进程的用户空间完全独立,把同一程序运行10次(为了能同时运行,可sleep 100s),会看到线程地址是一样的:
# ps aux
# cat /proc/<pid>/maps

内核动态内存分配:
#include <linux/slab.h>
void *kmalloc(size_t size, int flags)
size:要分配的内存大小
flags:分配标志 -> GFP_ATOMIC GFP_KERNEL __GFP_DMA __GFP_HIGHMEM
还有:
get_zeroed_page(unsigned int flags)
__get_free_page(unsigned int flags)
__get_free_pages(unsigned int flags, unsigned int order)
alloc_pages(gfp_mask, order)
vmalloc(unsigned long size)
释放内存:
void free_page(unsigned long addr)
void free_pages(unsigned long addr, unsigned long order)

设备寄存器映射到内存,重新建立页表:
ioremap
定义处
  1. arch\arm\include\asm\io.h
  2. #define ioremap(cookie,size)                __arm_ioremap((cookie), (size), MT_DEVICE)

  3. arch_ioremap_caller -> __arm_ioremap_caller -> __arm_ioremap_pfn_caller -> ioremap_page_range
复制代码

内存使用:
mem_usage.jpg

内核空间内存
由内核负责映射,不会跟随进程改变,固定的。
物理内存896MB以上的是高端内存。
空间分布:
kernel_mem.jpg
直接内存映射区(Direct Memory Region):
从3g开始,最大896M的线性地址区间,和物理地址存在线性转换关系:
线性地址 = 3g + 物理地址
例如物理地址区间0x100000~0x200000 映射到线性空间就是3g+0x100000-3g+0x200000。

动态内存映射区(vmalloc region)
由内核函数vmalloc进行分配,特点是线性空间连续,对应物理空间不一定连续,可能是高端内存也可能不是。

永久内存映射区(PKMap Region)
作用是用来访问高端内存的:
1. 使用alloc_page(__GFP_HIGHMEM)分配高端内存页。
2. 使用kmap函数将分配到的高端内存映射到永久内存映射区。

固定映射区(Fixing Mapping Region)
PKMap区上面有4M线性空间,和4G顶端只有4K隔离带,该区为固定映射区,服务于特定用途。


回复

使用道具 举报

21

主题

43

帖子

140

积分

注册会员

Rank: 2

积分
140
发表于 2015-10-10 01:13:48 | 显示全部楼层
本帖最后由 cat 于 2015-10-10 01:14 编辑


Linux内核数据结构


  1. 最基本的页目录表和页表
  2. 定义在include/asm-i386/page.h中:
  3. typedef struct { unsigned long pte_low; } pte_t;

  4. typedef struct { unsigned long pmd; } pmd_t;

  5. typedef struct { unsigned long pgd; } pgd_t;

  6. 其中,pte_t是页表项,pgd_t是页目录项,pmd_t是中间项(在此不用)。

  7. typedef struct { unsigned long pgprot; } pgprot_t;

  8. pgprot_t是用来说明保护结构的,只用到低12位,因为像pte_t和pgd_t的高20位存物理地址,低12位存储的是一些属性等,pgprot_t就是用来说明这些属性的,当把一个物理地址的低12位清0再与pgprot_t相“或”就得出pte_t或者pgd_t项的值。其中页表/页目录表中的低12位属性如下所示(include/asm-i386/pgtable.h):

  9. #define _PAGE_PRESENT 0x001

  10. #define _PAGE_RW 0x002

  11. #define _PAGE_USER 0x004

  12. #define _PAGE_PWT 0x008

  13. #define _PAGE_PCD 0x010

  14. #define _PAGE_ACCESSED 0x020

  15. #define _PAGE_DIRTY 0x040

  16. #define _PAGE_PSE 0x080

  17. #define _PAGE_GLOBAL 0x100



  18. #define _PAGE_PROTNONE 0x080

  19. 其中0x080位在此不用。
复制代码

回复 支持 反对

使用道具 举报

0

主题

5

帖子

71

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
71
发表于 2015-10-10 01:17:43 | 显示全部楼层
物理空间管理

在系统中,每个物理页面都有一个记录信息与之相对应,在内核中的数据结构就是page。

  1. 定义于include/linux/mm.h中:

  2. typedef struct page {

  3. struct list_head list;

  4. struct address_space *mapping;

  5. unsigned long index;

  6. struct page *next_hash;

  7. atomic_t count;

  8. unsigned long flags;

  9. struct list_head lru;

  10. unsigned long age;

  11. wait_queue_head_t wait;

  12. struct page **pprev_hash;

  13. struct buffer_head * buffers;

  14. void *virtual;

  15. struct zone_struct *zone;

  16. } mem_map_t;

  17. 在内存中,page使用list_head数据结构组成一个双向循环链表,list_head是一个通用的数据结构,贯穿在Linux的内核连接上,它只有两个指针定义如下:

  18. struct list_head{

  19. struct list_head *next,*prev;

  20. };
复制代码


在page数据结构中mapping和index都和文件和内存交换有关,在后面会详解。next_hash是自身指针,将自身连接成一个链表。atomic_t实际上就是int型,count可以理解为目前正在使用的本块内存的程序数量(实际上是用于页面交换的计数,若页面为空闲则为0,分配就赋值1,每建立或恢复一次映射就加1,断开映射就减一 )。flags是标志页面的状态的标志。

在page中有一个非常重要的struct zone_struct *zone,这是指向所属管理区的指针,在计算机中,整个内存区被分成ZONE_DMA和ZONE_NORMAL(或者还有ZONE_HIGHMEM,用于管理超过1GB的内存空间)。ZONE_DMA里面的页面是专供DMA使用的。DMA使用的内存区间是不需要经过内存映射的,而是外设直接访问内存地址,而且DMA要求地址连续并且地址数值较小,要单独加以管理。

在系统初始化的时候就根据系统内存大小建立了一个page类型的数组,每个page数据结构代表着一个页面,数组的下标就是实际物理内存的页面号。
回复 支持 反对

使用道具 举报

0

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
发表于 2015-10-10 01:28:49 | 显示全部楼层

虚拟空间管理

除了物理空间管理以外,Linux的一个强大之处还在于其虚拟空间的管理。前面也说过,Linux为每个进程分配了4GB的虚拟空间,其中,用户空间占了3GB。虽然当物理空间不连续的时候还是可以使用页表机制将其映射成连续的虚拟空间,但是实际上使用的时候虚拟空间也不一定是连续的。所以在Linux内核中还有一个数据结构来管理成块的虚拟空间。

  1. include/linux/mm.h

  2. struct vm_area_struct {

  3. struct mm_struct * vm_mm;

  4. unsigned long vm_start;

  5. unsigned long vm_end;





  6. struct vm_area_struct *vm_next;



  7. pgprot_t vm_page_prot;

  8. unsigned long vm_flags;





  9. short vm_avl_height;

  10. struct vm_area_struct * vm_avl_left;

  11. struct vm_area_struct * vm_avl_right;





  12. struct vm_area_struct *vm_next_share;

  13. struct vm_area_struct **vm_pprev_share;



  14. struct vm_operations_struct * vm_ops;

  15. unsigned long vm_pgoff;

  16. struct file * vm_file;

  17. unsigned long vm_raend;

  18. void * vm_private_data;

  19. };
复制代码


每一个vm_area_struct管理一块连续的虚拟空间(注意:不同进程的虚拟用户空间一般不互相影响),vm_start和vm_end分别表示虚拟空间的开始和结束地址,注意这里的开始地址vm_start是包含在自身的管理区间内的,而vm_end是不包含在内的。

vm_next是用来连接统一进程的所有vm_area_struct虚拟链,是按照高低次序进行连接的。

当一个进程的虚存块划分地较少的时候可以使用链式连接查询,但是如果太多的话这样查询效率会降低,所以在块数较多的时候采用AVL树进行存储,也就是平衡二叉树。vm_area_struct中的vm_avl_height、vm_avl_left和vm_avl_right都是和AVL树有关。

vm_next_share、vm_pprev_share、vm_ops等都和磁盘文件以及内存换出等有关,在以后会说到。

在vm_area_struct开头有一个定义:struct mm_struct *vm_mm这个是表明它属于哪个内存进程的内存管理。
回复 支持 反对

使用道具 举报

42

主题

68

帖子

229

积分

中级会员

Rank: 3Rank: 3

积分
229
 楼主| 发表于 2015-10-10 01:30:29 | 显示全部楼层
每个进程都有一个唯一的mm_struct数据结构,在进程的PCB中,有一个链接到其自己的mm_struct。mm_struct是整个用户空间的抽象,也是总的控制结构。其中:

mmap用来建立一个虚存空间的单链队列;

mmap_avl用来建立虚存空间的AVL树;

mmap_cache用来指向最近一次用到的虚存区间结构;

pgd用来指向该进程的页目录表;

一个进程的mm_struct可以为多个进程共享,mm_user和mm_count就是用来计数的;

map_count用来记录进程有几个虚存空间;

mmap_sem和page_table_lock是用来定义P、V操作的信号量。

另外start_code、end_code等就是该进程的代码等起始、结束地址。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|环信 Glofty.com ( 粤ICP备15084637号 )

GMT+8, 2017-4-27 23:01 , Processed in 0.260254 second(s), 24 queries .

快速回复 返回顶部 返回列表