linux进程的地址空间
Ⅰ linux进程地址空间划分
Linux 64位系统在理论上拥有16位十六进制表示的巨大内存地址空间,即从0x0000000000000000到0xFFFFFFFFFFFFFFFF。然而,Linux仅实际使用了其中的256T空间,其余部分未被使用。
在Linux 64位操作中,实际使用的是低47位地址,高17位用于扩展,只能取全0或全1值。这样,可用的地址空间被分为两部分:用户空间(0x0000000000000000至0x00007FFFFFFFFFFF)和内核空间(0xFFFF800000000000至0xFFFFFFFFFFFFFFFF),剩余部分未被利用。
用户空间主要包含以下部分:代码段、数据段、BSS段、堆和栈。
代码段用于存放程序执行代码,即CPU执行的机器指令。
数据段存放已初始化且初值不为0的全局变量和静态局部变量,属于静态内存分配,可读可写。
BSS段包括未初始化全局变量和静态局部变量的空间。
堆(heap)是动态分配内存的区域,当进程读取文件时,若文件未在内存中,会通过缺页中断获取物理内存,通过磁盘调页将文件数据读入内存,实现文件的读取。
文件在两个进程间共享时,即使它们映射到同一文件,虚拟地址空间也可能不同。若进程A先读取文件,则会获取物理内存,通过磁盘调页将文件数据读入内存。进程B在访问文件时,若文件数据不在内存中,则会查找缓存区,如果缓存中有文件数据,则建立映射关系,实现进程间通信。
栈(stack)用于存储函数调用时的局部变量。
以数组s和指针p3为例,数组s的内容是在运行时赋值,而指针p3指向的常量区字符串内容在编译时已赋值。
使用malloc函数分配内存时,虚拟内存的分配情况如下:
当malloc分配的内存小于128k时,使用brk分配内存,将_edata向高地址移动,只分配虚拟空间,不对应物理内存。第一次读/写数据时,会触发内核缺页中断,内核分配物理内存,建立虚拟地址空间映射关系。若分配的内存不被访问,对应的物理内存不会被分配。
brk分配的内存需要等待高地址内存释放后才能释放,可能导致内存碎片。
当malloc分配的内存大于128k时,使用mmap分配内存,在堆和栈之间寻找空闲内存分配,对应独立内存且初始化为0。mmap分配的内存可以直接通过free释放。
当最高地址空间的空闲内存超过128k时,Linux执行内存紧缩操作,释放部分内存。
当进程访问未建立映射关系的虚拟内存时,逻辑地址转换为物理地址,发现当前页不在内存中,处理器自动触发缺页异常。
Ⅱ Linux关于地址空间和MMAP映射有何特点
Linux采用
虚拟
内存技术,系统中的所有进程之间以虚拟方式共享内存。对每个进程来说,它们好像都可以访问整个系统的所有物理内存。更重要的是,即使单独一个进程,它拥有的地址空间也可以远远大于系统物理内存。
进程地址空间由每个进程中的线性地址区组成,每个进程都有一个32位或64位的平坦(flat)空间,空间的具体大小取决于体系结构。“平坦”指地址空间范围是一个独立的连续区间。通常情况下,每个进程都有唯一的这种平坦空间,而且每个进程的地址空间之间彼此互不相干。两个不同的进程可以在它们各自地址空间的相同地址内存存放不同的数据。但是进程之间也可以选择共享地址空间,我们称这样的进程为线程。
在地址空间中,我们更为关心的是进程有权访问的虚拟内存地址区间,比如08048000~0804c000。这些可被访问的合法地址区间被成为内存区域(memory area),通过内核,进程可以给自己的地址空间动态地添加或减少内存区域。
进程只能访问有效范围内的内存地址。每个内存区域也具有相应进程必须遵循的特定访问属性,如只读、只写、可执行等属性。如果一个进程访问了不在有效范围中的地址,或以不正确的方式访问有效地址,那么内核就会终止该进程,并返回“段错误”信息。
?
内存区域可以包含各种内存对象,如下:
?
可执行文件代码的内存映射,成为代码段(text section)。
?
可执行文件的已初始化全局变量的内存映射,成为数据段(data section)。
?
包含未初始化全局变量的零页(也就是bss段)的内存映射。零页是指页面中的数据全部为0。
?
用于进程用户空间栈的零页的内存映射。
?
每一个诸如C库或动态链接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间。
?
任何内存映射文件。
?
任何共享内存段。
?
任何匿名的内存映射,比如由malloc()分配的内存。
进程地址空间的任何有效地址都只能位于唯一的区域,这些内存区域不能相互覆盖。可以看到,在执行的进程中,每个不同的内存片断都对应一个独立的内存区域:栈、对象代码、全局变量、被映射的文件等等。
内核使用内存描述符表示进程的地址空间。内存描述符由mm_struct结构体表示,定义在文件中,该结构包含了和进程地址空间有关的全部信息。
VMA
内存区域由vm_area_struct结构体描述,定义在文件中,内存区域在内核中也经常被称作虚拟内存区域或者VMA。
VMA标志是一种位标志,它定义在vm_area_struct结构中(该结构中的vm_flags子域)。和物理页的访问权限不同,VMA标志反映了内核处理页面索需要遵守的行为准则,而不是硬件要求。VM_IO标志内存区域中包含对设备I/O空间的映射。该标志通常在设备驱动程序执行 mmap()函数进行I/O空间映射时才被设置,同时该标志也表示该内存区域不能被包含在任何进程的存放转存(core mp)中。VM_RESERVED标志内存区域不能被换出,它也是在设备驱动程序进行映射时被设置。
vm_area_struct结构体中的vm_ops域指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。
mmap()和do_mmap():创建地址区间
内核使用do_mmap()函数创建一个新的线性地址区间。但是说给函数创建一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况,do_mmap()函数都会将一个地址区间加入到进程的地址空间中——无论是扩展已经存在的内存区域还是创建一个新的区域。
do_mmap()函数声明在文件中,原型如下:
unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
在用户空间可以通过mmap()函数调用获取内核函数do_mmap()的功能。mmap()系统调用原型如下:
void *mmap2(void *start, size_t length,
int prot, int flags,
int fd, off_t pgoff)
do_munmap()函数从特定的进程地址空间中删除指定地址区间,该函数在文件中声明:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
系统调用munmap()给用户空间程序提供了一种从自身地址空间中删除指定地址区间的方法,它和系统调用mmap()的作用相反:
int munmap(void *start, size_t length)
mmap设备操作
对于驱动程序来说,内存映射可以提供给用户程序直接访问设备内存的能力。映射一个设备,意味着使用户空间的一段地址关联到设备内存上。无论何时,只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。
并不是所有的设备都能进行mmap抽象。例如,串口设备和其他面向流的设备就无法实现这种抽象。mmap的另一个限制是映射都是以 PAGE_SIZE为单位的。内核只能在页表一级处理虚拟地址;因此,被映射的区域必须是PAGE_SIZE的整数倍,而且必须位于起始于 PAGE_SIZE整数倍地址的物理内存内。如果区域的大小不是页大小的整数倍,内核就通过生成一个稍微大一些的区域来容纳它。
mmap方法是file_operations结构中的一员,并且在执行mmap系统调用时就会调用该方法。在调用实际方法之前,内核会完成很多工作,而且该方法的原型与系统调用的原型由很大区别。关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html
文件操作声明如下:
int (*mmap) (struct file * filp, struct vm_area_struct *vma);
其中vma参数包含了用于访问设备的虚拟地址区间的信息。大部分工作已经由内核完成了,要实现mmap,驱动程序只要为这一地址范围构造合适的页表即可,如果需要的话,就用一个新的操作集替换vma->vm_ops。
有两种建立页表的方法:使用remap_page_range函数可一次建立所有的页表,或者通过nopage VMA方法每次建立一个页表。
构造用于映射一段物理地址的新页表的工作是由remap_page_range完
Ⅲ 如何查看Linux内存中的程序所有堆的地址
linux 下面查看内存有多种渠道,比如通过命令 ps ,top,free 等,比如通过/proc系统,一般需要比较详细和精确地知道整机内存/某个进程内存的使用情况,最好通过/proc 系统,下面介绍/proc系统下内存相关的几个文件
单个进程的内存查看 cat /proc/[pid] 下面有几个文件: maps , smaps, status
maps 文件可以查看某个进程的代码段、栈区、堆区、动态库、内核区对应的虚拟地址,如果你还不了解linux进程的内存空间,可以参考这里。
下图是maps文件内存示例
Develop>cat /proc/self/maps
00400000-0040b000 r-xp 00000000 fd:00 48 /mnt/cf/orig/root/bin/cat
0060a000-0060b000 r--p 0000a000 fd:00 48 /mnt/cf/orig/root/bin/cat
0060b000-0060c000 rw-p 0000b000 fd:00 48 /mnt/cf/orig/root/bin/cat 代码段
0060c000-0062d000 rw-p 00000000 00:00 0 [heap] 堆区
7f1fff43b000-7f1fff5d4000 r-xp 00000000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so
7f1fff5d4000-7f1fff7d3000 ---p 00199000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so
7f1fff7d3000-7f1fff7d7000 r--p 00198000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so
7f1fff7d7000-7f1fff7d9000 rw-p 0019c000 fd:00 861 /mnt/cf/orig/root/lib64/libc-2.15.so
7f1fff7d9000-7f1fff7dd000 rw-p 00000000 00:00 0
7f1fff7dd000-7f1fff7fe000 r-xp 00000000 fd:00 2554 /mnt/cf/orig/root/lib64/ld-2.15.so
7f1fff9f9000-7f1fff9fd000 rw-p 00000000 00:00 0
7f1fff9fd000-7f1fff9fe000 r--p 00020000 fd:00 2554 /mnt/cf/orig/root/lib64/ld-2.15.so
7f1fff9fe000-7f1fff9ff000 rw-p 00021000 fd:00 2554 /mnt/cf/orig/root/lib64/ld-2.15.so
7f1fff9ff000-7f1fffa00000 rw-p 00000000 00:00 0
7fff443de000-7fff443ff000 rw-p 00000000 00:00 0 [stack] 用户态栈区
7fff443ff000-7fff44400000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 内核区
有时候可以通过不断查看某个进程的maps文件,通过查看其虚拟内存(堆区)是否不停增长来简单判断进程是否发生了内存溢出。
maps文件只能显示简单的分区,smap文件可以显示每个分区的更详细的内存占用数据
下图是smaps文件内存示例, 实际显示内容会将每一个区都显示出来,下面我只拷贝了代码段和堆区,
每一个区显示的内容项目是一样的,smaps文件各项含义可以参考这里
Develop>cat /proc/self/smaps
00400000-0040b000 r-xp 00000000 fd:00 48 /mnt/cf/orig/root/bin/cat
Size: 44 kB 虚拟内存大小
Rss: 28 kB 实际使用物理内存大小
Pss: 28 kB
Shared_Clean: 0 kB 页面被改,则是dirty,否则是clean,页面引用计数>1,是shared,否则是private
Shared_Dirty: 0 kB
Private_Clean: 28 kB
Private_Dirty: 0 kB
Referenced: 28 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB 处于交换区的页面大小
KernelPageSize: 4 kB 操作系统一个页面大小
MMUPageSize: 4 kB 体系结构MMU一个页面大小
Locked: 0 kB
0060c000-0062d000 rw-p 00000000 00:00 0 [heap]
Size: 132 kB
Rss: 8 kB
Pss: 8 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 8 kB
Referenced: 8 kB
Anonymous: 8 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
下图是status文件内存示例, 加粗部分是内存相关的统计,
Develop>cat /proc/24475/status
Name: netio 可执行程序的名字
State: R (running) 任务状态,运行/睡眠/僵死
Tgid: 24475 线程组号
Pid: 24475 进程id
PPid: 19635 父进程id
TracerPid: 0
Uid: 0 0 0 0
Gid: 0 0 0 0
FDSize: 256 该进程最大文件描述符个数
Groups: 0
VmPeak: 6330708 kB 内存使用峰值
VmSize: 268876 kB 进程虚拟地址空间大小
VmLck: 0 kB 进程锁住的物理内存大小,锁住的物理内存无法交换到硬盘
VmHWM: 16656 kB
VmRSS: 11420 kB 进程正在使用的物理内存大小
VmData: 230844 kB 进程数据段大小
VmStk: 136 kB 进程用户态栈大小
VmExe: 760 kB 进程代码段大小
VmLib: 7772 kB 进程使用的库映射到虚拟内存空间的大小
VmPTE: 120 kB 进程页表大小
VmSwap: 0 kB
Threads: 5
SigQ: 0/63346
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001000000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: ffffffffffffffff
CapEff: ffffffffffffffff
CapBnd: ffffffffffffffff
Cpus_allowed: 01
Cpus_allowed_list: 0
Mems_allowed: 01
Mems_allowed_list: 0
voluntary_ctxt_switches: 201
nonvoluntary_ctxt_switches: 909
可以看到,linux下内存占用是一个比较复杂的概念,不能
简单通过一个单一指标就判断某个程序“内存消耗”大小,原因有下面2点:
进程所申请的内存不一定真正会被用到(malloc或mmap的实现)
真正用到了的内存也不一定是只有该进程自己在用 (比如动态共享库)
关于内存的使用分析及本文几个命令的说明也可以参考这里
下面是查看整机内存使用情况的文件 /proc/meminfo
Develop>cat /proc/meminfo
MemTotal: 8112280 kB 所有可用RAM大小 (即物理内存减去一些预留位和内核的二进制代码大小)
MemFree: 4188636 kB LowFree与HighFree的总和,被系统留着未使用的内存
Buffers: 34728 kB 用来给文件做缓冲大小
Cached: 289740 kB 被高速缓冲存储器(cache memory)用的内存的大小
(等于 diskcache minus SwapCache )
SwapCached: 0 kB 被高速缓冲存储器(cache memory)用的交换空间的大小
已经被交换出来的内存,但仍然被存放在swapfile中。
用来在需要的时候很快的被替换而不需要再次打开I/O端口
Active: 435240 kB 在活跃使用中的缓冲或高速缓冲存储器页面文件的大小,
除非非常必要否则不会被移作他用
Inactive: 231512 kB 在不经常使用中的缓冲或高速缓冲存储器页面文件的大小,可能被用于其他途径.
Active(anon): 361252 kB
Inactive(anon): 120688 kB
Active(file): 73988 kB
Inactive(file): 110824 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB 交换空间的总大小
SwapFree: 0 kB 未被使用交换空间的大小
Dirty: 0 kB 等待被写回到磁盘的内存大小
Writeback: 0 kB 正在被写回到磁盘的内存大小
AnonPages: 348408 kB 未映射页的内存大小
Mapped: 33600 kB 已经被设备和文件等映射的大小
Shmem: 133536 kB
Slab: 55984 kB 内核数据结构缓存的大小,可以减少申请和释放内存带来的消耗
SReclaimable: 25028 kB 可收回Slab的大小
SUnreclaim: 30956 kB 不可收回Slab的大小(SUnreclaim+SReclaimable=Slab)
KernelStack: 1896 kB 内核栈区大小
PageTables: 8156 kB 管理内存分页页面的索引表的大小
NFS_Unstable: 0 kB 不稳定页表的大小
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 2483276 kB
Committed_AS: 1804104 kB
VmallocTotal: 34359738367 kB 可以vmalloc虚拟内存大小
VmallocUsed: 565680 kB 已经被使用的虚拟内存大小
VmallocChunk: 34359162876 kB
HardwareCorrupted: 0 kB
HugePages_Total: 1536 大页面数目
HugePages_Free: 0 空闲大页面数目
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB 大页面一页大小
DirectMap4k: 10240 kB
DirectMap2M: 8302592 kB