当前位置: 首页 > ai >正文

用户虚拟地址空间布局

虚拟地址空间划分

因为目前应用程序没有那么大的内存需求,所以ARM64处理器不支持完全的64位虚拟地址。 虚拟地址的最大宽度一般是48位(ARMv8.2+支持52位)。

ARM64架构内核/用户虚拟地址空间划分如下图(假设虚拟地址的宽度是48位):

在编译ARM64架构的Linux内核时,可以选择虚拟地址宽度。

(1)如果选择页长度4KB,默认的虚拟地址宽度是39位。

(2)如果选择页长度16KB,默认的虚拟地址宽度是47位。

(3)如果选择页长度64KB,默认的虚拟地址宽度是42位。

(4)可以选择48位虚拟地址。

通过getconf PAGESIZE或者grep "KernelPageSize" /proc/self/smaps查询页大小。

通过zcat /proc/config.gz | grep CONFIG_ARM64_VA_BITS查询虚拟地址宽度。

所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟地址空间,同一个线程组的用户线程共享用户虚拟地址空间,内核线程没有用户虚拟地址空间。

用户虚拟地址空间布局

进程的用户虚拟地址空间的起始地址是0,长度是`**TASK_SIZE**`,由每种处理器架构定义自己的宏`TASK_SIZE`。ARM64架构定义的宏`TASK_SIZE`如下所示。
/** PAGE_OFFSET - the virtual address of the start of the linear map (top*		 (VA_BITS - 1))* KIMAGE_VADDR - the virtual address of the start of the kernel image* VA_BITS - the maximum number of bits for virtual addresses.* VA_START - the first kernel virtual address.* TASK_SIZE - the maximum size of a user space task.* TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area.*/
#define VA_BITS			(CONFIG_ARM64_VA_BITS)#define TASK_SIZE_64		(UL(1) << VA_BITS)#ifdef CONFIG_COMPAT    /* 支持执行32位用户空间程序 */
#define TASK_SIZE_32		UL(0x100000000)
#define TASK_SIZE		(test_thread_flag(TIF_32BIT) ? \TASK_SIZE_32 : TASK_SIZE_64)
#define TASK_SIZE_OF(tsk)	(test_tsk_thread_flag(tsk, TIF_32BIT) ? \TASK_SIZE_32 : TASK_SIZE_64)
#else
#define TASK_SIZE		TASK_SIZE_64
#endif /* CONFIG_COMPAT */

编译内核的时候,指定虚拟地址位数CONFIG_ARM64_VA_BITS

如果CONFIG_ARM64_VA_BITS为39,64位用户空间程序TASK_SIZE2<sup>39</sup>


用户空间详细布局

典型 ARM64 Linux 进程地址空间(以 48 位为例):

高地址
+--------------------------------+ ← 0x0000_FFFF_FFFF_FFFF (256TB)
|            [vdso]/[vvar]       |   // 内核接口 (加速系统调用)
+--------------------------------+ ← 约 0x0000_FFFF_FFFE_FFFF
|(Stack)            |   // 主线程栈 (默认8MB)
|                                |   // 向低地址增长
+--------------------------------+ ← 随机化起始
|           内存映射区            |   // 文件/共享内存映射 (libc.so等)
|         (Memory Mapping)       |   // 向低地址增长
+--------------------------------+ ← 随机化起始
|(Heap)           |   // 动态内存 (brk/sbrk管理)
|                                |   // 向高地址增长
+--------------------------------+ ← 程序中断点 (brk)
| 未初始化数据 (.bss)             |   // 初始化为0
+--------------------------------+
| 已初始化数据 (.data)            |   // 全局/静态变量
+--------------------------------+
| 只读数据 (.rodata)              |   // 常量字符串等
+--------------------------------+
| 程序代码 (.text)                |   // 可执行指令
+--------------------------------+ ← 0x400000 (4MB) 
|       ELF 头部 / 程序头         | 
+--------------------------------+ ← 0x400000 以下保留
|           保留区                |   // 空指针访问保护
+--------------------------------+ ← 0x0
低地址

程序映像(ELF segments)

  • 包含 .text(代码段)、.rodata(只读数据)、.data(初始化数据)、.bss(未初始化数据)
  • 非PIE程序加载地址通常固定为 0x400000,PIE 程序(Position Independent Executable)起始地址随机

Heap(堆)

  • 通过 brk()sbrk() 系统调用增长
  • malloc 默认使用堆
  • 对应于 mm_struct 中的 start_brkbrk 字段。

mmap 区域

  • mmap、动态库加载都发生在这
  • PIE 程序的代码段也会加载在这里
  • 动态地址由内核随机决定(ASLR)

Stack(栈)

  • 向下增长,每个线程有独立栈空间(默认 8MB,**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">ulimit -s</font>**
  • 通常位于靠近 TASK_SIZE 的高地址
  • 命令行参数环境变量等在栈顶位置,通过/proc/<pid>/cmdline/proc/<pid>/environ查询

vdso / vvar

  • vdso:内核提供的用户态共享库,加速系统调用,如 gettimeofday
  • vvar:提供 vdso 所需的数据支持
  • 映射在栈附近的高地址区域

地址随机化(Address Space Layout Randomization)

为了使缓冲区溢出攻击更加困难,内核支持为内存映射区域、栈和堆选择随机的起始地址。进程是否使用虚拟地址空间随机化的功能,由以下两个因素共同决定。

(1)进程描述符的成员personality(个性化)是否设置ADDR_NO_RANDOMIZE。可以通过/proc/<pid>/personality查看。

ADDR_NO_RANDOMIZE值定义如下:

/** Flags for bug emulation.** These occupy the top three bytes.*/
enum {UNAME26	=               0x0020000,ADDR_NO_RANDOMIZE = 	0x0040000,	/* disable randomization of VA space */FDPIC_FUNCPTRS =	0x0080000,	/* userspace function ptrs point to descriptors* (signal handling)*/MMAP_PAGE_ZERO =	0x0100000,ADDR_COMPAT_LAYOUT =	0x0200000,READ_IMPLIES_EXEC =	0x0400000,ADDR_LIMIT_32BIT =	0x0800000,SHORT_INODE =		0x1000000,WHOLE_SECONDS =		0x2000000,STICKY_TIMEOUTS	=	0x4000000,ADDR_LIMIT_3GB = 	0x8000000,
};

(2)全局变量randomize_va_space:0 表示关闭虚拟地址空间随机化,1表示使内存映射区域和栈的起始地址随机化,2 表示使内存映射区域、栈和堆的起始地址随机化。 可以通过文件proc/sys/kernel/randomize_va_space查看和修改。

/** Randomize the address space (stacks, mmaps, brk, etc.).** ( When CONFIG_COMPAT_BRK=y we exclude brk from randomization,*   as ancient (libc5 based) binaries can segfault. )*/
int randomize_va_space __read_mostly =
#ifdef CONFIG_COMPAT_BRK1;
#else2;
#endif

PIE(Position Independent Executable,位置无关可执行文件)

PIE 是一种可执行文件格式,它支持在 任意虚拟内存地址 加载运行,而不依赖固定的起始地址。

这类程序在运行时可以被操作系统加载到 任意地址,而不会因为代码中的绝对地址而出错。


一句话解释:

PIE 是一种可以在任意内存地址运行的可执行程序,配合地址空间布局随机化(ASLR)增强系统安全性


PIE 的关键特性:
特性描述
地址无关性程序中使用相对地址,不依赖固定内存地址。
启用 ASLR支持 Address Space Layout Randomization(地址空间随机化),增加攻击者猜测地址的难度。
默认在现代系统开启如 Ubuntu 18.04+ 默认使用 gcc 编译的程序是 PIE。
代码重定位加载时动态链接器可以把程序加载到任意可用地址。

为什么需要 PIE?

安全性 是主要原因,特别是为了启用 ASLR

  • 在非-PIE 程序中,代码段是固定地址(如 0x400000)。
  • 攻击者可以根据这一点构造攻击(如 ROP)。
  • PIE 程序的代码地址每次运行都变,攻击者无法预测地址。

测试验证

gcc编译默认是pie程序。如果编译非pie程序,需要加上-no-pie参数。


PIE 与共享库的关系
  • 所有 共享库(.so) 本质上都是 position-independent code(PIC)
  • PIE 程序类似共享库,编译器生成的机器码使用相对地址跳转。

struct mm_struct

内核使用内存描述符 mm_struct 描述进程的用户虚拟地址空间。

struct mm_struct {struct vm_area_struct *mmap;		/* list of VMAs */struct rb_root mm_rb;u32 vmacache_seqnum;                   /* per-thread vmacache */
#ifdef CONFIG_MMUunsigned long (*get_unmapped_area) (struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);
#endifunsigned long mmap_base;		/* base of mmap area */unsigned long mmap_legacy_base;         /* base of mmap area in bottom-up allocations */
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES/* Base adresses for compatible mmap() */unsigned long mmap_compat_base;unsigned long mmap_compat_legacy_base;
#endifunsigned long task_size;		/* size of task vm space */unsigned long highest_vm_end;		/* highest vma end address */pgd_t * pgd;/*** @mm_users: The number of users including userspace.** Use mmget()/mmget_not_zero()/mmput() to modify. When this drops* to 0 (i.e. when the task exits and there are no other temporary* reference holders), we also release a reference on @mm_count* (which may then free the &struct mm_struct if @mm_count also* drops to 0).*/atomic_t mm_users;/*** @mm_count: The number of references to &struct mm_struct* (@mm_users count as 1).** Use mmgrab()/mmdrop() to modify. When this drops to 0, the* &struct mm_struct is freed.*/atomic_t mm_count;atomic_long_t nr_ptes;			/* PTE page table pages */
#if CONFIG_PGTABLE_LEVELS > 2atomic_long_t nr_pmds;			/* PMD page table pages */
#endifint map_count;				/* number of VMAs */spinlock_t page_table_lock;		/* Protects page tables and some counters */struct rw_semaphore mmap_sem;struct list_head mmlist;		/* List of maybe swapped mm's.	These are globally strung* together off init_mm.mmlist, and are protected* by mmlist_lock*/unsigned long hiwater_rss;	/* High-watermark of RSS usage */unsigned long hiwater_vm;	/* High-water virtual memory usage */unsigned long total_vm;		/* Total pages mapped */unsigned long locked_vm;	/* Pages that have PG_mlocked set */unsigned long pinned_vm;	/* Refcount permanently increased */unsigned long data_vm;		/* VM_WRITE & ~VM_SHARED & ~VM_STACK */unsigned long exec_vm;		/* VM_EXEC & ~VM_WRITE & ~VM_STACK */unsigned long stack_vm;		/* VM_STACK */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;unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv *//** Special counters, in some configurations protected by the* page_table_lock, in other configurations by being atomic.*/struct mm_rss_stat rss_stat;struct linux_binfmt *binfmt;cpumask_var_t cpu_vm_mask_var;/* Architecture-specific MM context */mm_context_t context;unsigned long flags; /* Must use atomic bitops to access the bits */struct core_state *core_state; /* coredumping support */
#ifdef CONFIG_AIOspinlock_t			ioctx_lock;struct kioctx_table __rcu	*ioctx_table;
#endif
#ifdef CONFIG_MEMCG/** "owner" points to a task that is regarded as the canonical* user/owner of this mm. All of the following must be true in* order for it to be changed:** current == mm->owner* current->mm != mm* new_owner->mm == mm* new_owner->alloc_lock is held*/struct task_struct __rcu *owner;
#endifstruct user_namespace *user_ns;/* store ref to file /proc/<pid>/exe symlink points to */struct file __rcu *exe_file;
#ifdef CONFIG_MMU_NOTIFIERstruct mmu_notifier_mm *mmu_notifier_mm;
#endif
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKSpgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
#ifdef CONFIG_CPUMASK_OFFSTACKstruct cpumask cpumask_allocation;
#endif
#ifdef CONFIG_NUMA_BALANCING/** numa_next_scan is the next time that the PTEs will be marked* pte_numa. NUMA hinting faults will gather statistics and migrate* pages to new nodes if necessary.*/unsigned long numa_next_scan;/* Restart point for scanning and setting pte_numa */unsigned long numa_scan_offset;/* numa_scan_seq prevents two threads setting pte_numa */int numa_scan_seq;
#endif
#if defined(CONFIG_NUMA_BALANCING) || defined(CONFIG_COMPACTION)/** An operation with batched TLB flushing is going on. Anything that* can move process memory needs to flush the TLB when moving a* PROT_NONE or PROT_NUMA mapped page.*/bool tlb_flush_pending;
#endifstruct uprobes_state uprobes_state;
#ifdef CONFIG_HUGETLB_PAGEatomic_long_t hugetlb_usage;
#endifstruct work_struct async_put_work;
};

内存描述符的主要成员如下:

成员说明
atomic_t mm_users共享同一个用户虚拟地址空间的进程数量,即线程组包含的进程数量
atomic_t mm_count内存描述符的引用计数
struct vm_area_struct *mmap虚拟内存区域链表
struct rb_root mm_rb虚拟内存区域红黑树
unsigned long (*get_unmapped_area)(...)在内存映射区域中寻找未映射区域
pgd_t *pgd指向页全局目录,即第一级页表
unsigned long mmap_base内存映射区域的起始地址
unsigned long task_size用户虚拟地址空间的长度
unsigned long start_code, end_code代码段的起始地址和结束地址
unsigned long start_data, end_data数据段的起始地址和结束地址
unsigned long start_brk, brk堆的起始地址和结束地址
unsigned long start_stack栈的起始地址
unsigned long arg_start, arg_end参数字符串的起始地址和结束地址
unsigned long env_start, env_end环境变量的起始地址和结束地址
mm_context_t context处理器架构特定的内存管理上下文

task_structmm_structvm_area_struct关系

一个进程的虚拟地址空间主要由两个数据结构进行描述。一个是最高层次的mm_struct,较高层次的vm_area_structmm_struct描述一个进程整个虚拟地址空间。vm_area_struct描述虚拟地址空间的一个区间(称为虚拟区)。每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个专门用来指向该mm_struct的成员。mm_struct结构是对整个用户空间的描述。

task_structmm_structvm_area_struct关系如下图:

查询实际布局

cat /proc/<pid>/cmdline  # 命令行参数
cat /proc/<pid>/environ  # 环境变量cat /proc/<pid>/maps     #用户空间虚拟内存布局
cat /proc/<pid>/smaps    #比maps更详细
pmap <pid>

cmdlineenviron

maps

每一行格式如下,每行对应一个vm_area_sturct

地址范围           权限   偏移量   设备号  inode     映射文件名或区域名
start-end          perms   offset  dev     inode     pathname

pmap

每行格式如下:

<起始虚拟地址>   <大小> <权限> <映射对象>

smaps

参考资料

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
  4. linux kernel 4.12
http://www.xdnf.cn/news/17520.html

相关文章:

  • JVM管理数据的方式
  • 剧本杀小程序系统开发:推动行业数字化转型新动力
  • Linux中DNS系统搭建与配置指南(配实验步骤与注释)
  • 在 .NET Core 5.0 中启用 Gzip 压缩 Response
  • Tricentis Tosca:现代软件测试的自动化利器
  • 企业级 IT 运维服务平台数据备份方案:基于 rsync 的自动化实现
  • AI生成代码时代的商业模式重构:从“软件即产品”到“价值即服务”
  • 云原生环境Prometheus企业级监控
  • Notepad++ 插件开发实战:从理念到落地的探索
  • 嵌入式第二十五天(基于Linux操作系统的编程-文件操作)
  • 大模型提示词工程实践:大语言模型文本转换实践
  • 【读代码】微软开源Agentic-RAG深度解析
  • execjs执行js报错, subprocess.py编码问题
  • Ignite端口管理组件GridPortProcessor全解析
  • Linux系统编程——基础IO
  • 《录井管理与工程》书籍第一章要点及相应思考
  • 虚幻GAS底层原理解剖十 (网络)
  • 深度剖析 Linux 信号:从基础概念到高级应用,全面解析其在进程管理与系统交互中的核心作用与底层运行机制
  • Orange的运维学习日记--39.Nginx详解与服务部署
  • 【liunx】web高可用---nginx
  • GSON 框架下百度天气 JSON 数据转 JavaBean 的实战攻略
  • ZooKeeper和Reids做分布式锁的区别?
  • Notepad--:国产跨平台文本编辑器,Notepad++ 的理想替代方案
  • 车载软件架构 --- 车辆量产后怎么刷写Flash Bootloader
  • 【数据结构入门】二叉树(1)
  • Redis7 GEO功能介绍与电商场景案例解析
  • Android模块化架构深度解析:从设计到实践
  • HTML5中华美食网站源码
  • (Arxiv-2025)Phantom-Data:迈向通用的主体一致性视频生成数据集
  • LangChain框架之 invoke() 方法