0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux进程地址空间详解

马哥Linux运维 来源:稀土掘金技术社区 2023-12-18 09:45 次阅读

RAM 的某些部分永久地分配给内核, 并用来存放内核代码以及静态内核数据结构. RAM 的其余部分称为动态内存 (dynamic memory). 动态内存不仅是进程所需的宝贵资源, 也是内核本身所需的宝贵资源. 实际上,整个系统的性能取决于如何有效地管理动态内存. 因此, 现在所有多任务操作系统都在尽力优化对动态内存的使用, 也就是说, 尽可能做到当需要时分配, 不需要时释放.

0bfcda9a-9ccd-11ee-8b88-92fbcf53809c.jpg

当给内核分配动态内存时, 是相对容易的, 有如下两点原因:

内核是操作系统中优先级最高的成分. 如果某个内核函数请求动态内存, 那么, 必定有正当的理由发出这个请求, 因此, 没有道理试图推迟这个请求.

内核信任自己. 所有的内核函数都被假定是没有错误的, 因此内核函数不必针对程序错误施加任何保护措施.

而当给用户态进程分配内存时, 情况完全不同:

进程对动态内存的请求被认为是不紧急的. 例如, 当进程对应在磁盘上所存储的可执行文件被装入内存时, 进程并不一定会立即对所有的代码和数据进行访问. 类似地, 当进程调用malloc()以请求获得额外的动态内存时, 也并不意味着进程很快就会访问所获得的额外的动态内存.因此, 一般来说, 内核总是尽量推迟给用户态进程分配动态内存.

由于用户进程是不可信任的, 因此, 内核必须能随时准备捕获用户态进程引起的所有寻址错误.

为了使得动态内存得到最大限度的使用, 内核使用一种新的资源成功实现了对进程动态内存的推迟分配. 当用户态进程请求动态内存时, 并没有获得请求的动态内存, 而仅仅得到了对一个新的线性地址区间的使用权, 这样的线性地址区间有很多, 由允许进程使用的全部线性地址区间所组成的集合就叫做进程地址空间.

与进程地址空间有关的全部信息都包含在一个叫做内存描述符的数据结构中 (实际上就是描述进程虚拟内存的数据结构), 这个结构的类型为mm_struct, 进程描述符的mm字段就指向这个结构.


struct mm_struct *mm;

如下为Linux 2.6.11版的内核中mm_struct的实现.

struct mm_struct {
  struct vm_area_struct * mmap;    
  struct rb_root mm_rb;
  struct vm_area_struct * mmap_cache;  
  unsigned long (*get_unmapped_area) (struct file *filp,
        unsigned long addr, unsigned long len,
        unsigned long pgoff, unsigned long flags);
  void (*unmap_area) (struct vm_area_struct *area);
  unsigned long mmap_base;    
  unsigned long free_area_cache;    
  pgd_t * pgd;
  atomic_t mm_users;      
  atomic_t mm_count;      
  int map_count;        
  struct rw_semaphore mmap_sem;
  spinlock_t page_table_lock;    


  struct list_head mmlist;    
             * together off init_mm.mmlist, and are protected
             * by mmlist_lock
             */


  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;
  unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;
  unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;


  unsigned long saved_auxv[42]; 


  unsigned dumpable:1;
  cpumask_t cpu_vm_mask;


  
  mm_context_t context;


  
  unsigned long swap_token_time;
  char recent_pagein;


  
  int core_waiters;
  struct completion *core_startup_done, core_done;


  
  rwlock_t    ioctx_list_lock;
  struct kioctx    *ioctx_list;


  struct kioctx    default_kioctx;


  unsigned long hiwater_rss;  
  unsigned long hiwater_vm;  
};

其中用来标识相应进程特定线性区的字段如下:

0c149d2e-9ccd-11ee-8b88-92fbcf53809c.jpg

start_code, end_code

正文代码的起始地址和终止地址.

start_data, end_data

已初始化数据的起始地址和终止地址.

start brk, brk

堆的起始地址和当前终止地址.

start_stack

用户态堆栈的起始地址.

arg_start, arg_end

命令行参数的起始地址和终止地址.

env_start, env_end

环境变量的起始地址和终止地址.

如下为进程地址空间的布局, 由一个一个的线性地址区间组成, 线性地址 (linear address), 也称虚拟地址 (virtual address) 是一个 32 位无符号整数 (unsigned long), 可以用来表示数值高达 4GB 的地址, 也就是 4,294,967,296 个内存单元.线性地址通常用十六进制数字表示, 值的范围从 0x00000000 到 0xffffffff.

0c2a070e-9ccd-11ee-8b88-92fbcf53809c.jpg

0x00000000 ~ 0xbfffffff 这一线性地址区间被称为用户空间, 大小为 3GB; 而0xc0000000 ~ 0xffffffff 这一线性地址区间被称为内核空间, 大小为 1GB.

可以通过以下代码对进程地址空间的布局图进行验证.


#include 
#include 


int uninitialized_global_var;
int initialized_global_var = 100;


int main(int argc, char *argv[], char *envp[])
{
    printf("Code address:%p
", main);  
    printf("Initialized Data address:%p
", &initialized_global_var);  
    printf("Uninitialized Data address:%p
", &uninitialized_global_var);  
    int *p = (int*)malloc(sizeof(int));
    printf("Heap address:%p
", p);  
    printf("Stack address:%p
", &p);  


    for (int i = 0; i < argc; i++) {
        printf("Command-line Arguments address:%p
", argv[i]);  
    }
    
    for (int i = 0; envp[i]; i++) {
        printf("Environment Variables address:%p
", envp[i]);  
    }
    return 0;
}

运行结果如下, 与进程地址空间的布局相吻合.

0c3a7792-9ccd-11ee-8b88-92fbcf53809c.jpg

线性地址(虚拟地址)的集合称为虚拟内存, 物理地址的集合称为物理内存, 进程对于内存访问的终点是物理内存而不是虚拟内存,所以必然存在一种将虚拟内存转化为物理内存的结构, 这种结构被称为页表.

页 (Page) && 页帧 (Page Frame)

内核使用struct page作为基本单位来管理物理内存, 在内核看来,所有的 RAM 都被划分成了固定长度的页帧 (页帧也叫页框, 通常大小为4KB). 每一个页帧包含了一个页, 也就是说一个页帧的长度和一个页的长度相同.页和页帧的区别在于, 页是抽象的数据结构, 可以存放在任意地方, 而页帧是真实的存储区域, 属于主存的一部分.

如下为Linux 2.6.11版的内核中struct page的实现.


struct page {
  page_flags_t flags;    
           * updated asynchronously */
  atomic_t _count;    
  atomic_t _mapcount;    
           * to show when page is mapped
           * & limit reverse map searches.
           */
  unsigned long private;    
           * usually used for buffer_heads
           * if PagePrivate set; used for
           * swp_entry_t if PageSwapCache
           * When page is free, this indicates
           * order in the buddy system.
           */
  struct address_space *mapping;  
           * inode address_space, or NULL.
           * If page mapped as anonymous
           * memory, low bit is set, and
           * it points to anon_vma object:
           * see PAGE_MAPPING_ANON below.
           */
  pgoff_t index;      
  struct list_head lru;    
           * protected by zone->lru_lock !
           */
  
   * On machines where all RAM is mapped into kernel address space,
   * we can simply calculate the virtual address. On machines with
   * highmem some memory is mapped into kernel virtual memory
   * dynamically, so we need a place to store that address.
   * Note that this field could be 16 bits on x86 ... ;)
   *
   * Architectures with slow multiplication can define
   * WANT_PAGE_VIRTUAL in asm/page.h
   */
#if defined(WANT_PAGE_VIRTUAL)
  void *virtual;      
             not kmapped, ie. highmem) */
#endif 
};

CPU 管理物理地址, 因而虚拟地址需要转化为物理地址才能给 CPU 使用.用于将进程(虚拟)地址空间映射成物理地址空间的数据结构称为页表.

0c5ca876-9ccd-11ee-8b88-92fbcf53809c.jpg

进程地址空间, 页表的存在有什么意义?

让所有进程以统一的视角看待内存,进程地址空间的存在让我们在编写程序的时候只需关注虚拟地址, 而无需关注数据在物理内存当中实际的存储位置.

页表的存在让进程在间接访问内存的时候, 增加一个转换的过程, 在这个转换的过程中, 内核对进程的寻址请求进行检查, 如果该进程的寻址请求异常, 则该请求被操作系统拦截, 从而实现对物理内存的保护.

进程地址空间与页表的存在, 让内核对于进程管理模块与内存管理模块进行了解耦.

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • RAM
    RAM
    +关注

    关注

    7

    文章

    1322

    浏览量

    113706
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206738
  • 操作系统
    +关注

    关注

    37

    文章

    6288

    浏览量

    121886
  • 动态内存
    +关注

    关注

    1

    文章

    24

    浏览量

    7918
  • 进程
    +关注

    关注

    0

    文章

    193

    浏览量

    13876

原文标题:Linux - 进程 - 进程地址空间

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux 内核的角度谈线程栈和进程

    1. 进程进程栈是属于用户态栈,和进程 虚拟地址空间(Virtual Address Space) 密切相关。那我们先了解下什么是虚拟
    的头像 发表于 09-25 15:23 2296次阅读
    从 <b class='flag-5'>Linux</b> 内核的角度谈线程栈和<b class='flag-5'>进程</b>栈

    Linux如何证明线程共享进程地址空间

    所有的书上都说,进程中的所有线程共享进程地址空间,如上图中的蓝框都在一个进程中。那么该如何证明这个结论呢?
    发表于 08-25 16:22 343次阅读
    <b class='flag-5'>Linux</b>如何证明线程共享<b class='flag-5'>进程</b>的<b class='flag-5'>地址</b><b class='flag-5'>空间</b>

    Linux内核地址映射模型与Linux内核高端内存详解

    的数据可能不在内存中。 Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑
    发表于 05-08 10:33 3329次阅读
    <b class='flag-5'>Linux</b>内核<b class='flag-5'>地址</b>映射模型与<b class='flag-5'>Linux</b>内核高端内存<b class='flag-5'>详解</b>

    Linux守护进程详解

    分享到:标签:进程控制 Linux 守护进程进程 7.3 Linux守护进程 7.3.1 守
    发表于 10-18 14:24 0次下载
    <b class='flag-5'>Linux</b>守护<b class='flag-5'>进程</b><b class='flag-5'>详解</b>

    linux进程的深入理解

    每个进程都有自己的堆栈,内核在创建一个新的进程时,在创建进程控制块 task struct 的同时,也为进程创建堆栈。 一个进程有 2个堆栈
    发表于 01-16 14:43 2次下载

    Linux用户空间与内核空间

    对内核进行操作,因此必须使用一个叫做系统调用的方法来实现从用户空间陷入到内核空间,这样才能实现对底层驱动的操作。 os分配给每个进程一个独立的、连续的、虚拟的地址内存
    发表于 05-20 10:58 890次阅读
    <b class='flag-5'>Linux</b>用户<b class='flag-5'>空间</b>与内核<b class='flag-5'>空间</b>

    Linux进程的内存结构

    Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该地址
    发表于 06-01 09:17 1353次阅读
    <b class='flag-5'>Linux</b>下<b class='flag-5'>进程</b>的内存结构

    深入浅出Linux进程地址空间

    我们知道,在32位机器上linux操作系统中的进程地址空间大小是4G,其中0-3G是用户空间,3G-4G是内核
    的头像 发表于 06-20 09:57 1778次阅读

    Linux进程

    内核通过轻量级进程 (lightweight process) 来支持多线程。1个轻量级进程就对应1个线程,轻量级进程之间可以共享打开的文件、地址
    的头像 发表于 11-29 09:51 1811次阅读
    <b class='flag-5'>Linux</b>的<b class='flag-5'>进程</b>

    mlock如何锁住进程地址空间关联的物理内存

    的应用),Linux中提供了mlock相关的系统调用供用户空间使用来锁住部分或全部的地址空间关联的物理页面。 本文的分析基于arm64处理器架构,内核版本为
    的头像 发表于 03-14 09:36 847次阅读

    Linux进程的内存消耗和泄漏详解

    当我们评估进程消耗多少内存时,就是指在用户空间消耗的内存,即虚拟地址在0~3G的部分,对应的物理地址内存。内核空间的内存消耗属于内核,系统调
    的头像 发表于 05-14 10:07 2433次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>进程</b>的内存消耗和泄漏<b class='flag-5'>详解</b>

    Linux程序地址空间详解

    在正式讲程序[地址空间]前我们先来看一段简单的代码来分析分析。
    的头像 发表于 03-26 10:39 456次阅读

    为什么进程地址空间中要包括操作系统(内核)呢?

    这张图就是Linux程序运行起来后所谓的进程地址空间,这里包括我们熟悉的代码区、数据区、以及堆区和栈区。
    的头像 发表于 04-18 09:09 782次阅读

    Linux系统为什么需要引入虚拟地址

    Linux 系统中,采用了虚拟内存管理技术,事实上大多数现在操作系统都是如此!在 Linux 系统中,每一个进程都在自己独立的地址空间
    的头像 发表于 10-07 17:28 543次阅读
    <b class='flag-5'>Linux</b>系统为什么需要引入虚拟<b class='flag-5'>地址</b>

    Linux虚拟地址空间和物理地址空间的关系

    过程,这其实也是MMU的工作原理。 我们知道,在Linux中,每个进程都有自己独立的地址空间,且互不干扰。每个进程
    的头像 发表于 10-08 11:40 506次阅读
    <b class='flag-5'>Linux</b>虚拟<b class='flag-5'>地址</b><b class='flag-5'>空间</b>和物理<b class='flag-5'>地址</b><b class='flag-5'>空间</b>的关系