精华内容
下载资源
问答
  • Linux虚拟地址空间

    千次阅读 2018-11-02 21:18:47
    Linux虚拟地址空间 注:本文来自多篇博客整理,具体博客链接在博客下方 在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的...

    Linux虚拟地址空间

    注:本文来自多篇博客整理,具体博客链接在博客下方

    在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存地址块,这篇博客均是X86架构的

    1. 地址空间分布

    在这里插入图片描述

    2. 内核地址空间

    其中高1G为内核空间,所有进程共享内核地址空间,内核空间分三部分:DMA映射区,一致映射区、高端内存区一致映射区的虚拟地址均一一对应了物理页框,因此此区间虚拟地址的访问可以直接通过偏移量得到物理内存而不需进行页表的转换,其中的高128M空间为高端内存,当物理内存大于4G时内核用128M的地址空间作为高端内存,扮演着临时映射的作用。我的能力现在有限主要解释用户地址空间,以后阅读内核代码会对内核空间进行解释。
    Temporary Kernel Mapping 为固定映射空间
    在这个空间中,有一部分用于高端内存的临时映射。当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。
    Persistent Kernel Mapping 永久映射空间
    从PKMAP_BASE 到 FIXADDR_START用于映射高端内存
    Vmalloc Area loremap Area 动态映射空间

    3. 用户地址空间

    Envioment Variables 为环境变量
    Command line arguments 为命令行参数
    Random stack offset和Random mmap offset等随机值意在防止恶意程序。Linux通过对栈、内存映射段、堆的起始地址加上随机偏移量来打乱布局,以免恶意程序通过计算访问栈、库函数等

    (1). 栈

    栈可以被称为堆栈,由编译器进行管理(自动释放和分配)并且是先入后出,这里说的堆栈是进程栈(栈分为进程栈,内核栈,线程栈, 中断栈)
    进程栈的初始化大小是由编译器和链接器计算出来的,但是栈的实时大小并不是固定的,Linux 内核会根据入栈情况对栈区进行动态增长(其实也就是添加新的页表)。但是并不是说栈区可以无限增长,它也有最大限制 RLIMIT_STACK (一般为 8M)。
    栈的特点和用途:
    1. 进程调用函数产生的栈帧建立在栈区(首先压入主调函数中下条指令(函数调用语句的下条可执行语句)的地址,然后是函数实参,然后是被调函数的局部变量。本次调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的指令地址,程序由该点继续运行下条可执行语句)
    2. 临时存储区,用于暂存长算术表达式部分计算结果或alloca()函数分配的栈内内存,或者c++的临时对象。
    3. 栈由计算机底层提供支持:分配专门的寄存器存放栈地址,压栈出栈由专门的指令执行, 其相比堆效率较高

    进程栈的动态增长实现
    进程在运行的过程中,通过不断向栈区压入数据,当超出栈区容量时,就会耗尽栈所对应的内存区域,这将触发一个 缺页异常 (page fault)。通过异常陷入内核态后,异常会被内核的 expand_stack() 函数处理,进而调用 acct_stack_growth() 来检查是否还有合适的地方用于栈的增长。
    如果栈的大小低于 RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情,这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会发生 栈溢出(stack overflow),进程将会收到内核发出的 段错误(segmentation fault) 信号。
    动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误。

    (2). 内存映射段(mmap)

    内存映射是一种方便高效的文件I/O方式(内核直接将硬盘文件映射到虚拟内存中), 因而被用于装载动态共享库, 同时可以用于映射可执行文件用到的动态链接库。
    在内存映射段, 进程可以不使用read() ,write()等函数对文件操作,像访问普通内存对文件进行访问,可通过Linux的mmap()系统调用函数将一个普通文件映射到此段,然后进行操作,另外mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉虚拟内存地址)可以通过munmap()来取消内存映射。
    注意:在 Linux中,若通过malloc()请求一大块内存,C运行库将创建一个匿名内存映射,而不使用堆内存。”大块” 意味着比阈值 MMAP_THRESHOLD还大,缺省为128KB,可通过mallopt()调整
    另外线程栈是也是在内存映射段
    因为线程和进程共享同一地址空间,如果线程和进行共用同一栈会造成调用栈混乱,因此在线程创建的时候使用mmap 系统调用为线程在内存映射段映射一块固定大小空间作为线程栈,因此线程栈不能动态增长。

    (3). 堆

    堆用于存放进程运行时动态分配的空间,可动态扩张或者缩减,堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。当进程调用malloc©/new(C++)等函数分配内存时,新分配的内存动态添加到堆上(扩张);当调用free©/delete(C++)等函数释放内存时,被释放的内存从堆中剔除(缩减)

    堆的特点:
    1> 堆管理器通过链表管理堆,由于申请和释放堆内存是无序的因此会产生内存碎片,堆的释放由程序员完成,回收的内存可以重新使用, 如果不释放只有在程序结束时才会统一释放。
    2> 堆的末端由break指针标识,当堆管理器需要更多内存时,可通过系统调用brk()和sbrk()来移动break指针以扩张堆(向上生长并且32位Linux系统中堆内存理论上可达2.9G空间),一般由系统自动调用。
    3> 操作系统为堆维护一个记录空闲内存地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点空间分配给程序。若无足够大小的空间(可能由于内存碎片太多),有可能调用系统功能去增加程序数据段的内存空间,以便有机会分到足够大小的内存,然后进行返回。,大多数系统会在该内存空间首地址处记录本次分配的内存大小,供后续的释放函数(如free/delete)正确释放本内存空间。此外,由于找到的堆结点大小不一定正好等于申请的大小,系统会自动将多余的部分重新放入空闲链表中。

    使用堆时经常出现两种问题
    1> 释放或改写仍在使用的内存(“内存破坏”);
    2> 未释放不再使用的内存(“内存泄漏”)。当释放次数少于申请次数时,可能已造成内存泄漏。泄漏的内存往往比忘记释放的数据结构更大,因为所分配的内存通常会圆整为下个大于申请数量的2的幂次(如申请212B,会圆整为256B)。

    (4). BSS段

    BSS段用于存放程序的以下符号:

    • 程序中的未初始化的全局变量和静态局部变量
    • 初始值为0的全局变量和静态局部变量(依赖于编译器实现)
    • 未定义且初值不为0的符号

    由于程序加载时,BSS会被操作系统清零,所以未赋初值或初值为0的全局变量都在BSS中。BSS段仅为未初始化的静态分配变量预留位置,在目标文件中并不占据空间,这样可减少目标文件体积。但程序运行时需为变量分配内存空间,故目标文件必须记录所有未初始化的静态分配变量大小总和(通过start_bss和end_bss地址写入机器代码)。当加载器(loader)加载程序时,将为BSS段分配的内存初始化为0

    尽管均放置于BSS段,但初值为0的全局变量是强符号,而未初始化的全局变量是弱符号。若其他地方已定义同名的强符号(初值可能非0),则弱符号与之链接时不会引起重定义错误,但运行时的初值可能并非期望值(会被强符号覆盖)。因此,定义全局变量时,若只有本文件使用,则尽量使用static关键字修饰;否则需要为全局变量定义赋初值(哪怕0值),保证该变量为强符号,以便链接时发现变量名冲突,而不是被未知值覆盖。
    gcc将未初始化的全局变量保存在common段,链接时再将其放入BSS段。在编译阶段可通过-fno-common选项来禁止将未初始化的全局变量放入common段。

    (5). 数据段

    数据段通常用于存放程序中已初始化且初值不为0的全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),可读可写。数据段保存在目标文件中,其内容由程序初始化。例如,对于全局变量int gVar = 10,必须在目标文件数据段中保存10这个数据,然后在程序加载时复制到相应的内存。

    数据段与BSS段的区别如下:
    1> BSS段不占用物理文件尺寸,但占用内存空间;数据段占用物理文件,也占用内存空间。
    对于大型数组如int ar0[1000] = {1, 2, 3, …}和int ar1[1000],ar1放在BSS段,只记录共有1000*4个字节需要初始化为0,而不是像ar0那样记录每个数据1、2、3…,此时BSS为目标文件所节省的磁盘空间相当可观。
    2) 当程序读取数据段的数据时,系统会出发缺页故障,从而分配相应的物理内存;当程序读取BSS段的数据时,内核会将其转到一个全零页面,不会发生缺页故障,也不会为其分配相应的物理内存。

    (6). 代码段

    代码段也称正文段或文本段,通常用于存放程序执行代码(即CPU执行的机器指令)。代码段通常属于只读,以防止其他程序意外地修改其指令(对该段的写操作将导致段错误)。
    代码段还存放一些只读数据如字符串常量。

    代码段指令中包括操作码和操作对象(或对象地址引用)。若操作对象是立即数(具体数值),将直接包含在代码中;若是局部数据,将在栈区分配空间,然后引用该数据地址;若位于BSS段和数据段,同样引用该数据地址

    (7) 保留区

    位于虚拟地址空间的最低部分,未赋予物理地址。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况。
    它并不是一个单一的内存区域,而是对地址空间中受到操作系统保护而禁止用户进程访问的地址区域的总称。大多数操作系统中,极小的地址通常都是不允许访问的,如NULL。C语言将无效指针赋值为0也是出于这种考虑,因为0地址上正常情况下不会存放有效的可访问数据。

    在32位X86架构的Linux系统中,用户进程可执行程序一般从虚拟地址空间0x08048000开始加载。该加载地址由ELF文件头决定,可通过自定义链接器脚本覆盖链接器默认配置,进而修改加载地址。0x08048000以下的地址空间通常由C动态链接库、动态加载器ld.so和内核VDSO(内核提供的虚拟共享库)等占用。通过使用mmap系统调用,可访问0x08048000以下的地址空间。

    4. 内存描述符对虚拟地址空间的定义

    struct mm_struct {
        struct vm_area_struct *mmap;           /* 内存区域链表 */
        struct rb_root mm_rb;                  /* VMA 形成的红黑树 */
        ...
        struct list_head mmlist;               /* 所有 mm_struct 形成的链表 */
        ...
        unsigned long total_vm;                /* 全部页面数目 */
        unsigned long locked_vm;               /* 上锁的页面数据 */
        unsigned long pinned_vm;               /* Refcount permanently increased */
        unsigned long shared_vm;               /* 共享页面数目 Shared pages (files) */
        unsigned long exec_vm;                 /* 可执行页面数目 VM_EXEC & ~VM_WRITE */
        unsigned long stack_vm;                /* 栈区页面数目 VM_GROWSUP/DOWN */
        unsigned long def_flags;
        unsigned long start_code, end_code, start_data, end_data;    /* 代码段、数据段 起始地址和结束地址 */
        unsigned long start_brk, brk, start_stack;                   /* 栈区 的起始地址,堆区 起始地址和结束地址 */
        unsigned long arg_start, arg_end, env_start, env_end;        /* 命令行参数 和 环境变量的 起始地址和结束地址 */
        ...
        /* Architecture-specific MM context */
        mm_context_t context;                  /* 体系结构特殊数据 */
     
        /* Must use atomic bitops to access the bits */
        unsigned long flags;                   /* 状态标志位 */
        ...
        /* Coredumping and NUMA and HugePage 相关结构体 */
    };
    

    在这里插入图片描述

    来源博客
    Linux虚拟地址空间布局
    Linux用户空间与内核地址空间
    linux内存映射mmap原理分析

    展开全文
  • Linux虚拟地址空间分布

    千次阅读 2017-06-16 23:35:40
    Linux虚拟地址空间分布

    什么是虚拟内存?
    IBM在解释虚拟内存的概念时用了四句话:
    如果它存在,而且你能看到它,它是真实的;
    如果它不存在,但是你能看到它,它是虚拟的;
    如果它存在,但是你看不到它,它是透明的;
    如果它不存在,而且你也看不到它,那肯定是你把它擦掉了。

    在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存地址块。在Linux系统中, 内核进程和用户进程所占的虚拟内存比例是1:3,0~3G为用户空间,3G~4G为内核空间。而Windows系统为2:2,0~2G为用户空间,2G~4G为内核空间(通过设置Large-Address-Aware Executables标志也可为1:3)。这并不意味着内核使用那么多物理内存,仅表示它可支配这部分地址空间,根据需要将其映射到物理内存。

    虚拟地址通过页表(Page Table)映射到物理内存,页表由操作系统维护并被处理器引用。内核空间在页表中拥有较高特权级,因此用户态程序试图访问这些页时会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存。内核代码和数据总是可寻址,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化,操作系统会为每个进程分配4G的虚拟地址空间。

    Linux虚拟地址空间的分布如下图:
    这里写图片描述
    现在来分别介绍各段:

    .text段

    代码段也称正文段或文本段,通常用于存放程序执行代码(即CPU执行的机器指令)。一般C语言执行语句都编译成机器代码保存在代码段。通常代码段是可共享的,因此频繁执行的程序只需要在内存中拥有一份拷贝即可。代码段通常属于只读,以防止其他程序意外地修改其指令(对该段的写操作将导致段错误)。某些架构也允许代码段为可写,即允许修改程序。

    .data段

    数据段通常用于存放程序中已初始化且初值不为0的全局变量、静态全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),可读可写。其中有一个.rodata段,一般用于存放常量字符串和只读变量

    .bss段

    BSS(Block Started by Symbol)段中通常存放程序中以下符号:
    1.未初始化的全局变量和静态局部变量
    2.初始值为0的全局变量和静态局部变量(依赖于编译器实现)
    bss可以理解为better save space,请思考:节省的这个空间是什么空间?

    C语言中,未显式初始化的静态变量被初始化为0(算术类型)或空指针(指针类型)。由于程序加载时,bss会被操作系统清零,所以未赋初值或初值为0的全局变量都在bss段中。bss段仅为这些变量预留位置,它们在目标文件中并不占据空间,这样可减少目标文件体积。但程序运行时需为变量分配内存空间,故目标文件必须记录所有未初始化的变量大小总和)。当加载器(loader)加载程序时,将为bss段分配的内存初始化为0。

    注意:尽管未初始化和初始化为0的全局变量和静态局部变量均在bss段,但初始值为0的全局变量是强符号,而未初始化的全局变量是弱符号。若其他文件已定义同名的强符号(初值可能非0),则弱符号与之链接时不会引起重定义错误,但运行时的初值可能并非期望值(弱符号会被强符号覆盖)。因此,定义全局变量时,若只有本文件使用,则尽量使用static关键字修饰;否则需要为全局变量定义赋初值(哪怕0值),保证该变量为强符号,以便链接时发现变量名冲突,而不是被未知值覆盖。
    某些编译器将未初始化的全局变量保存在common段,链接时,等符号确定后,再放入.bss段。在编译阶段可通过-fno-common选项来禁止将未初始化的全局变量放入common段。

    下面看一个例子:

    main.c
    #include <stdio.h>
    
    int x;
    
    int func()
    {
        x = 20;
    }
    test.c
    #include <stdio.h>
    int func();
    short x = 10;
    short y = 20;
    int main()
    {
        func();
        printf("x=%d, y=%d\n", x, y);
        return 0;
    }

    答案是:x=20, y=0。你答对了吗?
    编译时,各文件是分开编译的,此时main.c里面的x是弱符号,func函数里x=20被编译成: mov dword ptr[x], 14h。往x的内存上写14h,x写4字节。在链接时,发现test.c里面有一个同名的强符号x,在调用func函数的时候取了short x的地址。因为指令已经在编译阶段编译,所以,这个14h就写在了short x的地址上。但是short x只有两个字节,其他的部分就写在了内存紧挨着的short y的内存上,覆盖了y原有的数据。小端模式,14h在内存上的保存方式是:00010100 00000000 00000000 00000000。因此,x=20,y=0。

    bss段和data段有什么区别?
    1.bss段不占用物理文件尺寸,但占用内存空间;数据段占用物理文件,也占用内存空间。
    对于大型数组如int arr1[10000] = {1, 2, 3, …}和int arr2[10000],arr1要记录每个数据1,2,3…,位于.data段。arr2位于.bss段,只需要记录共有10000*4个字节需要初始化为0。此时bss为目标文件所节省的磁盘空间相当可观。
    2.当程序读取数据段的数据时,系统会出发缺页故障,从而分配相应的物理内存;当程序读取.bss段的数据时,内核会将其转到一个全零页面,不会发生缺页故障,也不会为其分配相应的物理内存。

    堆用于存放进程运行时动态分配的内存,可动态扩张或缩减。堆中内容是匿名的,不能按名字直接访问,只能通过指针解引用间接访问。当进程调用malloc(C)/new(C++)等函数分配内存时,新分配的内存动态添加到堆上;当调用free(C)/delete(C++)等函数释放内存时,被释放的内存从堆中剔除。
    分配的堆内存是经过字节对齐的空间,以适合原子操作。堆管理器通过链表管理每个申请的内存,由于堆申请和释放是无序的,最终会产生内存碎片。堆内存一般由应用程序分配释放,回收的内存可供重新使用。若程序员不释放,程序结束时操作系统可能会自动回收。
    堆的末端由break指针标识,当堆管理器需要更多内存时,可通过系统调用brk()和sbrk()来移动break指针以扩张堆,一般由系统自动调用。
    使用堆时经常出现两种问题:
    1.释放或改写仍在使用的内存(“内存破坏”);
    2.未释放不再使用的内存(“内存泄漏”)。
    当释放次数少于申请次数时,可能已造成内存泄漏。泄漏的内存往往比忘记释放的数据结构更大,因为实际分配的内存通常会为下个大于申请数量的2的幂次(如申12B,实际申请256B)。
    堆向高地址方向增长

    栈又称堆栈,由编译器自动分配释放,行为类似数据结构中的栈(先进后出)。
    堆栈主要有三个用途:
    1.为函数内部声明的非静态局部变量(C语言中称“自动变量”)提供存储空间。
    2.记录函数调用过程相关的维护性信息,称为栈帧(Stack Frame))。它包括函数返回地址,不适合装入寄存器的函数参数及一些寄存器值的保存。除递归调用外,堆栈并非必需。因为编译时可获知局部变量,参数和返回地址所需空间,并将其分配于bss段。
    3.临时存储区,用于暂存长算术表达式部分计算结果或alloca()函数分配的栈内内存。
    Linux中ulimit -s命令可查看和设置堆栈最大值,当程序使用的堆栈超过该值时, 发生栈溢出(Stack Overflow),程序收到一个段错误(Segmentation Fault)。注意,调高堆栈容量可能会增加内存开销和启动时间。
    栈向低地址方向增长

    共享库

    程序运行时,编译器会自动链接libc.so/libc++.so动态链接库,程序中用到的库函数就被加载到共享库这部分内存上。共享库位于栈和堆之间。

    内核空间

    内核空间分为3部分,ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEME。
    ZONE_DMA,0-16M,直接内存访问。该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。这部分的数据可以直接访问,目的在于加快磁盘和内存之间交换数据,数据不需要经过总线流向CPU的PC寄存器,再流向物理内存,直接通过总线就可到达物理内存
    ZONR_NORMAL,16M-896M,内核最重要的部分,该区域的物理页面是内核能够直接使用的。
    ZONE_HIGHMEM,896M-结束,共128M,高端内存。主要用于32位Linux系统中,映射高于1G的物理内存。64位不需要高端内存。
    这部分内容作为了解知识,如果你对操作系统内核特别感兴趣,推荐《Linux内核源代码情景分析》,此处不做详细介绍。

    展开全文
  • 》,我们知道了CPU是如何访问内存的,本篇文章我们来讲下虚拟地址空间和物理地址空间的映射。 通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间...

    在之前的文章中《CPU是如何访问内存的?》,我们知道了CPU是如何访问内存的,本篇文章我们来讲下虚拟地址空间和物理地址空间的映射。

    通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。下面以X86为例。

    物理地址空间布局

    Linux虚拟地址空间和物理地址空间怎么映射的?

     

     

    1. 物理地址空间的顶部以下一段空间,被PCI设备的I/O内存映射占据,它们的大小和布局由PCI规范所决定。
    2. 640K~1M这段地址空间被BIOS和VGA适配器所占据。

     

    Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。

     

    进一步,针对不同的用途,Linux内核将所有的物理页面划分到3类内存管理区中,如图,分别为:

    • ZONE_DMA:范围是0~16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
    • ZONE_NORMAL:范围是16M~896M,该区域的物理页面是内核能够直接使用的。
    • ZONE_HIGHMEM:围是896M~结束,该区域即为高端内存,内核不能直接使用。

    linux虚拟地址内核空间分布

     

    Linux虚拟地址空间和物理地址空间怎么映射的?

     

     

    在kernel image下面有16M的内核空间用于DMA操作。位于内核空间高端的128M地址主要由3部分组成,分别为vmalloc area,持久化内核映射区,临时内核映射区。

     

    由于ZONE_NORMAL和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT、IDT、PGD、mem_map数组等放在ZONE_NORMAL里。而将用户数据、页表(PT)等不常用数据放在ZONE_ HIGHMEM里,只在要访问这些数据时才建立映射关系(kmap())。比如,当内核要访问I/O设备存储空间时,就使用ioremap()将位于物理地址高端的mmio区内存映射到内核空间的vmalloc area中,在使用完之后便断开映射关系。

    linux虚拟地址用户空间分布

     

    Linux虚拟地址空间和物理地址空间怎么映射的?

     

     

    用户进程的代码区一般从虚拟地址空间的0x08048000开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。

    linux虚拟地址与物理地址映射的关系

     

    Linux虚拟地址空间和物理地址空间怎么映射的?

     

     

    Linux将4G的线性地址空间分为2部分,0~3G为user space,3G~4G为kernel space。

     

    由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将全部物理地址空间映射到1G的内核线性空间中,这显然不可能。于是,内核将0~896M的物理地址空间一对一映射到自己的线性地址空间中,这样它便可以随时访问ZONE_DMA和ZONE_NORMAL里的物理页面;此时内核剩下的128M线性地址空间不足以完全映射所有的ZONE_HIGHMEM,Linux采取了动态映射的方法,即按需的将ZONE_HIGHMEM里的物理页面映射到kernel space的最后128M线性地址空间里,使用完之后释放映射关系,以供其它物理页面映射。虽然这样存在效率的问题,但是内核毕竟可以正常的访问所有的物理地址空间了。

    参考

    https://www.cnblogs.com/wuchanming/p/4756911.html

    展开全文
  • Linux虚拟地址空间概述

    千次阅读 2015-11-10 00:12:43
     Linux进程虚拟地址空间linux内存管理一个重要的部分,我们知道,在IA-32系统上地址空间的范围可达2的32次幂=4G,总的地址空间通常按3:1的比例划分,用户态占用了3G,内核占用了1G。 各进程的用户态虚拟地址...

    原文:

    http://my.oschina.net/u/1770090/blog/263326



    1 虚拟地址空间概述 

        Linux进程虚拟地址空间是linux内存管理一个重要的部分,我们知道,在IA-32系统上地址空间的范围可达2的32次幂=4G,总的地址空间通常按3:1的比例划分,用户态占用了3G,内核占用了1G。

    各进程的用户态虚拟地址空间起始于地址0,延伸到TASK_SIZE -1的位置,其上是内核的地址空间。

        无论当前的那个用户进程处于运行状态,虚拟地址空间的内核部分总是相同的。如下图所示:

    2 用户态地址空间

        虚拟地址空间有不同的段组成,分布方式是特定于体系结构的,但所有方法都有下列共同成分。

    在IA-32架构中,Linux传统内存空间布局如下:

    --芯片寄存器的虚拟地址也应该在内核地址1GB范围内吧,是不是还应该在0XF8000000之上?(我注释)

    --内核有stack,用户也有stack

    2.1 各段的说明

    TEXT:代码段,映射程序的二进制代码, 该区域为私有区域;在代码段中,也会包含一些只读的常数变量,比如字符串常量等。

        一般来说,IA-32体系结构中进程空间的代码段从0x08048000开始,这与最低可用地址(0x00000000)有128M的间距,按照linux内核架构一书介绍是为了捕获NULL指针(具体不详)。该值是在编译阶段就已经设定好的,其他体系结构也有类似的缺口,比如mips(), ppc() x86-64使用的是0x000000000400000(只4MB了)

    DATA:数据段,映射程序中已经初始化的全局变量。

    BSS段:存放程序中未初始化的全局变量,在ELF文件中,该区域的变量仅仅是个符号,并不占用文件空间,但程序加载后,会占用内存空间,并初始化为0。

    HEAP:运行时的堆,在程序运行中使用malloc申请的内存区域。

        该区域的起始地址受start_brk影响,和BSS段之间会有一个随机值的空洞;该区域的内存增长方式是由低地址向高地址增长。

    MMAP:共享库及匿名文件的映射区域;该区域中会包含共享库的代码段和数据段。其中代码段为共享的,但数据段为私有的,写时拷贝。

        该区域起始地址也会有一个随机值,该区域的增长方式是从高地址向低地址增长(Linux经典布局中不同,稍后讨论)

    STACK:用户进程栈;

        该区域起始地址也存在一个随机值,通过PF_RANDOMIZE来设置。栈的增长方向是由高地址向低地址增长,并且若设置了RLIMIT_STACK即规定了栈的大小。

    最后,是命令行参数和环境变量。

    2.2 布局说明

        Linux经典内存布局和新布局不同

        从以上图对比可以发现,不同之处在于MMAP区域的增长方向,新布局导致了栈空间的固定,而堆区域和MMAP区域公用一个空间,这在很大程度上增长了堆区域的大小。

        那么为什么这么做呢?使用经典布局在32位计算机上,TASK_UNMMAPPED_BASE在IA-32中只有0x40000000(固定了害死人呢),也就是说对空间只有1G空间可用(包括malloc),堆超过了可用空间,继续增长会进入MMAP区域。在2.6.7内核开始,为IA-32引入了新的虚拟地址空间的布局(经典布局仍然可用)。

        布局的选择,如果用户通过/proc/sys/kernel/legacy_va_layout给出明确指示,或者要执行的二进制文件为unix变体编译需要旧的布局,或者栈可以无限制增长,则系统就会选择旧的布局。

        如果选择新的布局时,内存增长是有高地址向低地址,那MMAP的起始地址从什么地方开始的?可以使用栈的最大长度来计算栈最低的可能位置,作为MMAP的基址。但内核也会确保栈至少跨越128M空间。如果指定了栈的界限非常大,那内核也会至少保留一部分地址空间不会被栈占据。通常要求地址空间的随机化,上述位置会再减去一个偏移量,最大为1M。

        64位系统上堆虚拟地址空间总是使用经典布局。

    2.3 随机化问题

        在以上的描述中提到堆/栈/MMAP的随机化问题,那么为什么需要随机化呢? 如果所有的进程都按照设定的默认值,攻击者很容易就知道栈和库函数的地址。

    3 内核态地址空间

    3.1 内核在物理内存的布局

        Linux的内核在初始化时会加载到内存区的固定位置(在此我们不讨论可重定位内核的情况),而内核所占用的内存区域的布局是固定的,如图所示:

        内存的第一个页不使用,通常保留给BIOS使用,接下来的640K原则上是可用的,但也不会加载内核代码,因为系统用于各种ROM(BIOS和显卡ROM)的映射。再之后的空间也是闲置的,原因是内核需要被放到连续的内存中;所以从0x100000(1MB)作为内核代码的起始地址。

    3.2 各段的说明

    内核占据的内存也分为几个段:

    _text和_etext是代码段的起始和终止位置

    _etext到_edata之间是数据段,保存了大部分的内核变量

    _edata到_end之间,是初始化为0的所有静态全局变量的BSS段。

    以上的信息可以通过cat /proc/iomem来查看,不同cpu体系有不同的情况。

    3.3 由物理地址到虚拟地址的转换

        IA-32架构可以访问4G的地址空间,通常会将线性地址空间划分为3:1的两个部分:用户态使用3G,内核态使用1G,即内核空间从0xC0000000开始,每个虚拟地址x都对应于物理地址x-0xC0000000。这样的设计可以加快内核空间的寻址速度(简单的减法操作)。在进程切换的过程中,只有用户态对应的页表被切换,高地址空间会公用内核页表。


    3.4 高端内存的情况

        IA-32架构的这种设计也存在这样的一个问题:既然内核只能处理1G的空间(实际上内核直接处理的空间还不足1G),那么物理内存大于1G,剩下的内存将如何处理呢?这种情况下内核无法直接映射全部的物理内存,这样就会用到高端内存区域的概念了(ZONE_HIGHMEM)。具体的内存分配如下图:

        由以上的图可以看到,内核区域的映射从_PAGE_OFFSE(0xC0000000)开始,即3G的位置开始映射到4G的区域,开始的一段用于直接映射,后而的128M为vmalloc使用的空间,再之后会有永久映射和固定映射区。所以事实上,物理内存能够直接映射到内核的空间=1G-vmalloc-永久映射-固定映射,真正的空间大约只剩下850M,也就是说,物理内存中只有850M是可以映射到内核空间的,对于超过的部分来说,作为高端内存使用。

    3.5 疑问一

        这里会存在一个疑问:如果内存只能处理896M的空间,那么如果内存很大比如(3G),那剩下的内存怎么办?对于这个问题,我们需要注意的是,1.这里讲述的内存是内核直接映射的物理内存,对于高地址的内存内核还有另外一套机制来管理;2.用户空间对物理内存的访问不是通过直接映射,还有另外一套机制。

    3.6 疑问二

        还有一个疑问:既然内核直接映射了将近1G的内存,那么如果我们有3G的物理内存,是不是只有2G多一点的内存可使用了呢?这个说法是错误的,上图所描述的只是内核在线性地址空间的分布情况,物理内存分配的过程中(用户态进程申请的内存,vmalloc部分的内存等),更倾向于先分配高地址的内存,在高地址内存耗尽的情况下,才会使用低850M的内存。

    4 虚拟地址空间的访问权限

    1. 用户空间禁止访问内核空间。

    2. 用户态通过系统调用切换到内核空间后,该进程处于内核态上下文,可以访问用户空间的内存。

    3. 中断抢占进程后,处于中断上下文时,不能访问用户空间。

    4. 内核线程不能访问用户空间。

    第一篇就先写到这里,可能也会有理解不太准确的地方,后续也会据此继续更新,希望使该篇文章保持准确性。


    展开全文
  • Linux虚拟地址空间布局以及进程栈和线程栈总结

    万次阅读 多人点赞 2016-12-21 18:55:46
    一:Linux虚拟地址空间布局 (转自:Linux虚拟地址空间布局)  在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存...
  • Linux虚拟内存管理的几个基础概念 (1)每个进程都有自己独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址。 (2)虚拟地址通过页表的映射,...Linux中采用虚拟地址空间,大大增加了进程的寻址空间
  • linux虚拟地址空间你真的理解了吗?

    千次阅读 2015-10-20 17:19:41
    学了很久linux内核了,但对于linux内核对于内存管理这块始终有许多疑问,学而不思则罔!看了许多书籍和博客但还是没有搞得很明白。今天仔细思考了为什么需要虚拟内存的问题,突然觉得很有收获,所以记下来和大家分享...
  • Linux虚拟内存空间分布

    千次阅读 2019-03-03 16:38:03
    程序只是一段可以执行的代码文件,通俗讲在 linux 上就是一个可执行文件。当一个程序运 行时就被称为进程,即进程是运行状态的程序。 程序存储了一系列文件信息,这些信息描述了如何在运行时创建一个进程,包含...
  • 4.9 Linux64位内核地址空间分布图,各个关键宏的分布位置。
  • x+PAGE_OFFSET x+PAGE_OFFSET o 对于用户空间而言 它与物理内存 Linux 进程的虚拟地址空间 在 x86 体系结构中分段机制是必选的 而分页机制则可 由具体的操作系统而选择 Linux 通过让段的基地址为 0 而 巧妙的绕过了...
  • 1. 用户空间虚拟地址vaddr通过MMU(pgd,pmd,pte)找到对应的页表项x(即为物理地址) 2. 页表项x的高20位是物理也好,物理页号index = x >> PAGE_SHIFT, 同理,index后面补上12个0就是物理页表的首地址。 3. ...
  • 【Linux】Linux虚拟内存空间描述

    千次阅读 2018-07-19 20:51:27
    由于虚拟空间是程序员使用的空间,在程序员的头脑中并不需要页的概念,因此也不需要页的描述,在虚拟空间Linux描述的是分区。所谓分区,就是按照虚存的代码或数据的属性分成的段。描述虚拟分区的结构定义在文件...
  • Linux中,每个进程通过一个task_struct结构体描述,每个进程地址虚拟空间通过一个mm_struct描述,c语言中每个段空间通过vm_area_struct描述,关系如下, 当执行一个程序时,linux创建一个进程,通过...
  • Linux 内核物理地址空间虚拟地址空间布局(IA-32体系结构) 1. 杂言: 最近比较忙,故先放出一个自己总结的图。详细的解说分析待有时间时会给出。有些基础的人,看此图即可理解。 2. 正言: 在学习linux内核...
  • 作者简介:本文由西邮陈莉君教授研一学生贺东升编辑,梁金荣、张孝家校对建议结合之前的《linux的内存寻址方式》看。Linux可执行文件与进程的虚拟地址空间一个可执行文件被执行的同时也伴随...
  • linux虚拟地址转物理地址

    千次阅读 2018-06-21 07:06:00
    80386虚拟地址和物理地址转换CPU的发展之前在看malloc内存分配函数的原理时,有涉及到分配虚拟内存,然后再映射到物理内存,当初也是看得一头雾水,因为对虚拟内存和物...
  • Linux虚拟地址布局x64 layout在x86_64下面,其实虚拟地址只使用了48位。所以C语言里,输出地址都是12位16进制的地址。48位地址长度也就是对应了256TB的地址空间。而在Linux下有效的地址区间是从0x0 ~ 0x00007FFF ...
  • 要查看一个进程的虚拟地址空间的内存布局,需要设置阻塞。如果设置阻塞,当./a.out按下去后,程序执行的速度非常快以至于来不及查看,所以需要设置阻塞。 #include&amp;amp;lt;stdio.h&amp;amp;gt; int a...
  • 有助于大家理解目前的64位Android地址空间布局 ------------------------------------------------------------ Saturday, February 15, 201...
  • Linux的物理地址虚拟地址空间布局,说的比较明白,通俗易懂
  • Linux——环境变量、虚拟地址空间

    千次阅读 2021-03-21 17:35:14
    文章目录一、环境变量1.1 常见的环境变量1.2 与环境变量相关的命令1.3 环境变量的组织方式二、虚拟地址空间2.2 虚拟地址空间的访问过程三、虚拟地址空间的管理方式3.1 页式管理3.2 段式管理3.3 段页式管理 ...
  • 进程的虚拟地址在内核中通过三/四级页表到达物理地址。而内核的虚拟地址在NORMAL...内核的逻辑地址 与 用户空间逻辑地址 (逻辑地址有时也被叫虚拟地址) 都是位于 0x00000000~0xFFFFFFFF 这段虚拟地址空间 ,其中用户
  • Linux内核地址空间分布

    千次阅读 2017-09-04 11:23:15
    在32位系统里,Linux内核地址空间是指0xC0000000开始到0xFFFFFFFF总量为1G的高端内存地址空间,而用户空间是0x00000000至0xBFFFFFFF的3G虚拟存储空间。操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。 ...
  • 个人学习笔记,可能会有错误之处,敬请谅解。 一直以来感觉虚拟地址和物理地址之间的相互转换非常麻烦,虚拟地址...今天看了下Linux代码,才发现内核地址空间地址转换其实非常简单,总结如下(Kernel: 4.5, x86_64):
  • 虚拟地址空间

    万次阅读 多人点赞 2019-03-13 03:32:18
    对于每一个进程都会对应一个虚拟地址空间,对于32位的操作系统(其指令的位数最大为32位,因此地址码最多32位),虚拟地址空间的大小为B即0~4GB的虚拟地址空间,其中内核空间为1GB,如下所示: 每一个进程的进程...
  • 这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 218,462
精华内容 87,384
关键字:

linux虚拟地址空间

linux 订阅