深入探析Linux 64位进程的地址空间 (linux 64位进程地址空间)

随着计算机技术的不断发展,64位操作系统已经逐渐成为新时代的主流。与此同时,Linux作为一款强大的开源操作系统,也在不断地更新和发展。Linux 64位操作系统自然而然成为了当今计算机操作系统的重要代表之一。在这个系统中,进程的地址空间是一个非常重要的概念。在本文中,我们将,帮助大家更好地理解其中的原理和机制。

1. 什么是进程的地址空间?

在操作系统中,每个进程都有自己的地址空间。地址空间简单来说就是一块用于存储进程数据和代码的连续内存区域。在Linux 64位操作系统中,每个进程的地址空间大小可以达到2的64次方,也就是18,446,744,073,709,551,616字节(即16EB)。进程的地址空间可以被分成多个区域,每个区域用于存储不同类型的数据和代码,如图所示:

![地址空间](https://img-blog.csdn.net/20230326171259950)

其中,可执行区域(text)用于存放可执行代码,包括函数代码和指令代码。数据区(data)用于存放全局变量和静态变量的初始化值,BSS区则用于存放未初始化的全局变量和静态变量。堆区(heap)用于存放由应用程序动态申请的内存,而栈区(stack)则用于存放函数调用时的参数、返回值和局部变量。

2. 进程地址空间的分配

在Linux 64位操作系统中,进程在运行前需要先分配地址空间。当内核发现一个进程需要分配地址空间时,会为其分配一个长度为2的64次方的虚拟地址空间,但这个虚拟地址空间并不会直接映射到物理内存中。只有当进程真正需要访问该内存地址时,内核才会将其映射到实际物理内存中。

当进程访问地址超出了当前地址空间的限制时,就会产生一个缺页异常(page fault),内核会为其分配一个新的物理页,并将其映射到进程的地址空间中。

3. 地址空间的操作

进程在运行过程中,需要不断地申请、释放内存空间,或者改变地址空间中不同区域的大小。在Linux 64位操作系统中,有一些特殊的系统调用可以实现对地址空间的操作,包括:

* mmap: 申请虚拟地址空间

“`c

void * mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

“`

这个函数用于向操作系统申请一段连续的虚拟地址空间,参数length表示所需的地址空间大小。返回值是指向映射区的指针,如果映射失败,返回MAP_FLED。

* munmap: 释放虚拟地址空间

“`c

int munmap(void *addr, size_t length);

“`

这个函数用于释放先前申请的虚拟地址空间,参数addr表示需要释放的地址空间的起始地址,length表示需要释放的地址空间大小。如果释放成功,函数返回0,否则返回-1。

* mprotect: 修改地址空间的保护属性

“`c

int mprotect(void *addr, size_t len, int prot);

“`

这个函数用于在地址空间中修改某个区域的保护属性,参数addr表示起始地址,len表示需要修改的长度,prot表示新的保护属性。保护属性有以下几种:

* PROT_READ:可读

* PROT_WRITE:可写

* PROT_EXEC:可执行

* PROT_NONE:不可访问

当一个进程需要修改地址空间时,它可以使用上述系统调用来实现。但是,这些系统调用是由内核提供的,它们和内核紧密关联,因此应该谨慎地使用。如果使用不当,可能会引起严重的错误或安全问题。

4. 地址空间的布局

在Linux 64位操作系统中,进程的地址空间经过精心设计,以更大限度地利用可用的内存空间。具体来说,进程的地址空间可以被划分为以下几个区域:

* 在地址0处对应的页表,用于映射进程地址空间中的虚拟地址和物理地址

* 栈区,用于存放函数调用时的参数、返回值和局部变量,通常从高地址向低地址增长

* 静态执行区,用于存放二进制可执行文件中的指令代码和只读数据

* 动态执行区,用于存放动态链接库中的代码和数据,在进程运行时动态加载

* 数据区,用于存放全局变量和静态变量的初始化值

* BSS区,用于存放未初始化的全局变量和静态变量

* 堆区,用于存放由应用程序动态申请的内存,从低地址向高地址增长

不同区域的地址范围可以使用命令”pmap -x PID”查看。例如,下面是一个样例输出:

“`

Address Size RSS AnonHugePages Dirty Referenced Shmem Swap KernelPageSize MMUPageSize Locked VmFlags

7f9bfefd7000 4K 4K 0K 0K 0K 0K 0K 4K 4K 0 r–

7f9bfefd8000 44K 4K 4K 0K 0K 0K 0K 4K 4K 0 rw-

7f9bfefe1000 8K 8K 0K 0K 0K 0K 0K 4K 4K 0 rw-

7f9bfefe3000 4K 4K 0K 0K 0K 0K 0K 4K 4K 0 r–

7f9bfefe4000 4K 4K 4K 0K 0K 0K 0K 4K 4K 0 rw-

“`

这里,”Address”列显示了地址范围,”Size”列显示了占用的内存大小,”RSS”列显示了实际使用的物理内存大小,”AnonHugePages”列显示了使用的大页数,”Dirty”列和”Referenced”列分别表示了脏页和引用页的个数,”Shmem”列和”Swap”列分别表示在内存和磁盘上的共享内存和交换区的大小,”KernelPageSize”列和”MMUPageSize”列分别表示内核页大小和MMU页大小,最后一列”VmFlags”则表示了不同区域的内存属性。

5. 结语

在Linux 64位操作系统中,进程的地址空间是一个非常重要的概念。它为各种不同类型的数据和代码提供了安全的存储区域,同时也为进程的动态加载和卸载提供了便利。通过,我们可以更好地理解其中的原理和机制,为开发和调试应用程序提供帮助。希望本文对读者有所帮助。

相关问题拓展阅读:

Linux 虚拟地址空间如何分布

一个进程的虚拟地址空间主要由两个数据结来描述。一个是更高层次的:mm_struct,一个是较高层次的:vm_area_structs。更高层次的mm_struct结构描述了一个进程的整个虚拟地址空间。较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间(简称虚拟区)。

1. MM_STRUCT结构

mm_strcut 用来描述一个进程的虚拟地址空间,在/include/linux/sched.h 中描述如下:

struct mm_struct {

struct vm_area_struct * mmap;/* 指向虚拟区间(VMA)链表 */

rb_root_t mm_rb;/*指向red_black树*/

struct vm_area_struct * mmap_cache; /* 指向最近找到的虚拟区间*/

pgd_t * pgd;/*指向进程的页目录*/

atomic_t mm_users; /* 用户空间中的有多少用户*/

atomic_t mm_count; /* 对”struct mm_struct”有多少引用*/

int map_count;/* 虚拟区间的个数*/

struct rw_semaphore mmap_sem;

spinlock_t page_table_lock;/* 保护任务页表和 mm->rss */

struct list_head mmlist;/*所有活动(active)mm的链表 */

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, total_vm, locked_vm;

unsigned long def_flags;

unsigned long cpu_vm_mask;

unsigned long swap_address;

unsigned dumpable:1;

/* Architecture-specific MM context */

mm_context_t context;

};

对该结构进一步说明如下:

在内核代码中,指向这个数据结构的变量常常是mm。

每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。可以说,mm_struct结构是对整个用户空间的描述。

一个进程的虚拟空间中可能有多个虚拟区间(参见下面对vm_area_struct描述),对这些虚拟区间的组织方式有两种,当虚拟区较少时采用单链表,由mmap指针指向这个链表,当虚拟区间多时采用“红黑树(red_black

tree)”结构,由mm_rb指向这颗树。在2.4.10以前的版本中,采用的是AVL树,因为与AVL树基罩册相比,对红黑树进行操作的效率更高。

因为程序中用到的地址常常具有局部性,因此,最近一次用到的虚拟区间很可能下一次还要用到,因此,把最近用到的虚拟区间结构应当放入高速缓存,这个虚拟区间闷岩就由mmap_cache指向。

指针pgt指向该进程的页目录(每个进程都有自己的页目录,注意同内核页目录的区别),当调度程序调度一个程序运行时,就将这个地址转成物理地址,并写入控制寄存器(CR3)。

由于进程的虚拟空间及其下属的虚拟区间有可能在不同的上下文中受到访问,而这些访问又必须互斥,所以在该结构中设置了用于P、V操作的信号量mmap_sem。此外,page_table_lock也是为类似的目的而设置。

虽然每个进程只有一个虚拟地址空间,但这个地址空间可以被别的进程来共享,如,子进程共享父进程的地址空间(也即共享mm_struct结构)。所以,用mm_user和mm_count进行计数。类搏宏型atomic_t实际上就是整数,但对这种整数的操作必须是“原子”的。

另外,还描述了代码段、数据段、堆栈段、参数段以及环境段的起始地址和结束地址。这里的段是对程序的逻辑划分,与我们前面所描述的段机制是不同的。

mm_context_t是与平台相关的一个结构,对i386 几乎用处不大。

在后面对代码的分析中对有些域给予进一步说明。

2. VM_AREA_STRUCT 结构

vm_area_struct描述进程的一个虚拟地址区间,在/include/linux/mm.h中描述如下:

struct vm_area_struct

struct mm_struct * vm_mm;/* 虚拟区间所在的地址空间*/

unsigned long vm_start;/* 在vm_mm中的起始地址*/

unsigned long vm_end;/*在vm_mm中的结束地址 */

/* linked list of VM areas per task, sorted by address */

struct vm_area_struct *vm_next;

pgprot_t vm_page_prot;/* 对这个虚拟区间的存取权限 */

unsigned long vm_flags;/* 虚拟区间的标志. */

rb_node_t vm_rb;

/*

* For areas with an address space and backing store,

* one of the address_space->i_mmap{,shared} lists,

* for shm areas, the list of attaches, otherwise unused.

*/

struct vm_area_struct *vm_next_share;

struct vm_area_struct **vm_pprev_share;

/*对这个区间进行操作的函数 */

struct vm_operations_struct * vm_ops;

/* Information about our backing store: */

unsigned long vm_pgoff;/* Offset (within vm_file) in PAGE_SIZE

units, *not* PAGE_CACHE_SIZE */

struct file * vm_file;/* File we map to (can be NULL). */

unsigned long vm_raend;/* XXX: put full readahead info here. */

void * vm_private_data;/* was vm_pte (shared mem) */

};

vm_flag是描述对虚拟区间的操作的标志,其定义和描述如下

标志名 描述

VM_DENYWRITE在这个区间映射一个打开后不能用来写的文件。

VM_EXEC 页可以被执行。

VM_EXECUTABLE页含有可执行代码。

VM_GROWSDOWN这个区间可以向低地址扩展。

VM_GROWSUP这个区间可以向高地址扩展。

VM_IO这个区间映射一个设备的I/O地址空间。

VM_LOCKED页被锁住不能被交换出去。

VM_MAYEXEC VM_EXEC 标志可以被设置。

VM_MAYREAD VM_READ 标志可以被设置。

VM_MAYSHARE VM_SHARE 标志可以被设置。

VM_MAYWRITE VM_WRITE 标志可以被设置。

VM_READ页是可读的。

VM_SHARED页可以被多个进程共享。

VM_SHM页用于IPC共享内存。

VM_WRITE页是可写的。

较高层次的结构vm_area_structs是由双向链表连接起来的,它们是按虚地址的降顺序来排列的,每个这样的结构都对应描述一个相邻的地址空间范围。之所以这样分割,是因为每个虚拟区间可能来源不同,有的可能来自可执行映象,有的可能来自共享库,而有的则可能是动态分配的内存区,所以对每一个由vm_area_structs结构所描述的区间的处理操作和它前后范围的处理操作不同。因此Linux

把虚拟内存分割管理,并利用了虚拟内存处理例程(vm_ops)来抽象对不同来源虚拟内存的处理方法。不同的虚拟区间其处理操作可能不同,Linux在这里利用了面向对象的思想,即把一个虚拟区间看成一个对象,用vm_area_structs描述了这个对象的属性,其中的vm_operation结构描述了在这个对象上的操作,其定义在/include/linux/mm.h中:

/*

* These are the virtual MM functions – opening of an area, closing and

* unmapping it (needed to keep files on disk up-to-date etc), pointer

* to the functions called when a no-page or a wp-page exception occurs.

*/

struct vm_operations_struct {

void (*open)(struct vm_area_struct * area);

void (*close)(struct vm_area_struct * area);

struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);

};

vm_operations结构中包含的是函数指针;其中,open、close分别用于虚拟区间的打开、关闭,而nopage用于当虚存页面不在物理内存而引起的“缺页异常”时所应该调用的函数。

3.红黑树结构

Linux内核从2.4.10开始,对虚拟区的组织不再采用AVL树,而是采用红黑树,这也是出于效率的考虑,虽然AVL树和红黑树很类似,但在插入和删除节点方面,采用红黑树的性能更好一些,下面对红黑树给予简单介绍。

一颗红黑树是具有以下特点的二叉树:

每个节点着有颜色,或者为红,或者为黑

根节点为黑色

如果一个节点为红色,那么它的子节点必须为黑色

从一个节点到叶子节点上的所有路径都包含有相同的黑色节点数

关于linux 64位进程地址空间的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。


数据运维技术 » 深入探析Linux 64位进程的地址空间 (linux 64位进程地址空间)