EdmondFrank's 时光足迹

この先は暗い夜道だけかもしれない それでも信じて進むんだ。星がその道を少しでも照らしてくれるのを。
或许前路永夜,即便如此我也要前进,因为星光即使微弱也会我为照亮前途。
——《四月は君の嘘》

Linux进程内存管理

Linux进程内存管理

1.程序的结构与进程结构

$ size test

text data bss dec hex filename
181617 432 18844 200893 310bd ./book

一个可执行程序包括三个部分
代码段:存放指令,操作以及只读的常量
数据段:全局或者静态的已经初始化的变量
BSS段 :全局或者静态的未初始化的变量

2.进程管理的过程

在执行程序时,系统首先在内核空间中创建一个进程,为这个进程申请一个PCB (进程控制块task_struct),用于管理整个进程的所有资源,其中mm_struct成员用来管理与当前 进程相关的所有内存资源.

(1)代码段,数据段,BSS段,直接从磁盘拷贝到当前内存空间,大小相等.
(2)动态的空间:堆,栈空间,mmap段(用于映射其他的库的相关信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ cat /proc/7450/maps
08048000-08152000 r-xp 00000000 08:01 36175926   /bin/bash
08152000-08153000 r--p 00109000 08:01 36175926   /bin/bash
08153000-08158000 rw-p 0010a000 08:01 36175926   /bin/bash
08158000-0815d000 rw-p 00000000 00:00 0 
084cf000-08994000 rw-p 00000000 00:00 0          [heap]
b710d000-b7118000 r-xp 00000000 08:01 21496849   /lib/i386-linux-gnu/libnss_files-2.23.so
b7118000-b7119000 r--p 0000a000 08:01 21496849   /lib/i386-linux-gnu/libnss_files-2.23.so
b7119000-b711a000 rw-p 0000b000 08:01 21496849   /lib/i386-linux-gnu/libnss_files-2.23.so
b711a000-b7120000 rw-p 00000000 00:00 0 
b7120000-b712b000 r-xp 00000000 08:01 21496770   /lib/i386-linux-gnu/libnss_nis-2.23.so
b712b000-b712c000 r--p 0000a000 08:01 21496770   /lib/i386-linux-gnu/libnss_nis-2.23.so
b712c000-b712d000 rw-p 0000b000 08:01 21496770   /lib/i386-linux-gnu/libnss_nis-2.23.so
b7151000-b7158000 r--s 00000000 08:01 48245384   /usr/lib/i386-linux-gnu/gconv/gconv-modules.cache
b7158000-b730b000 r--p 002d7000 08:01 48234512   /usr/lib/locale/locale-archive
b730b000-b750b000 r--p 00000000 08:01 48234512   /usr/lib/locale/locale-archive
b750b000-b750c000 rw-p 00000000 00:00 0 
b750c000-b76bb000 r-xp 00000000 08:01 21497064   /lib/i386-linux-gnu/libc-2.23.so
b76bb000-b76bc000 ---p 001af000 08:01 21497064   /lib/i386-linux-gnu/libc-2.23.so
b76bc000-b76be000 r--p 001af000 08:01 21497064   /lib/i386-linux-gnu/libc-2.23.so
b76be000-b76bf000 rw-p 001b1000 08:01 21497064   /lib/i386-linux-gnu/libc-2.23.so
b76bf000-b76c2000 rw-p 00000000 00:00 0 
b76c2000-b76c5000 r-xp 00000000 08:01 21497066   /lib/i386-linux-gnu/libdl-2.23.so
b76c5000-b76c6000 r--p 00002000 08:01 21497066   /lib/i386-linux-gnu/libdl-2.23.so
b76c6000-b76c7000 rw-p 00003000 08:01 21497066   /lib/i386-linux-gnu/libdl-2.23.so
b76c7000-b76e7000 r-xp 00000000 08:01 21496816   /lib/i386-linux-gnu/libtinfo.so.5.9
b76e7000-b76e9000 r--p 0001f000 08:01 21496816   /lib/i386-linux-gnu/libtinfo.so.5.9
b76e9000-b76ea000 rw-p 00021000 08:01 21496816   /lib/i386-linux-gnu/libtinfo.so.5.9
b76ef000-b7706000 r-xp 00000000 08:01 21496768   /lib/i386-linux-gnu/libnsl-2.23.so
b7706000-b7707000 r--p 00016000 08:01 21496768   /lib/i386-linux-gnu/libnsl-2.23.so
b7707000-b7708000 rw-p 00017000 08:01 21496768   /lib/i386-linux-gnu/libnsl-2.23.so
b7708000-b770a000 rw-p 00000000 00:00 0 
b770a000-b7712000 r-xp 00000000 08:01 21496134   /lib/i386-linux-gnu/libnss_compat-2.23.so
b7712000-b7713000 r--p 00007000 08:01 21496134   /lib/i386-linux-gnu/libnss_compat-2.23.so
b7713000-b7714000 rw-p 00008000 08:01 21496134   /lib/i386-linux-gnu/libnss_compat-2.23.so
b7714000-b7715000 r--p 002d4000 08:01 48234512   /usr/lib/locale/locale-archive
b7715000-b7717000 rw-p 00000000 00:00 0 
b7717000-b7719000 r--p 00000000 00:00 0          [vvar]
b7719000-b771a000 r-xp 00000000 00:00 0          [vdso]
b771a000-b773c000 r-xp 00000000 08:01 21497056   /lib/i386-linux-gnu/ld-2.23.so
b773c000-b773d000 rw-p 00000000 00:00 0 
b773d000-b773e000 r--p 00022000 08:01 21497056   /lib/i386-linux-gnu/ld-2.23.so
b773e000-b773f000 rw-p 00023000 08:01 21497056   /lib/i386-linux-gnu/ld-2.23.so
bff73000-bff94000 rw-p 00000000 00:00 0          [stack]

所示地址并不是真正的物理地址,而是虚拟地址.使用虚拟地址是出于对资源的保护 对系统来说,内存资源是宝贵的,而一个程序执行并不需要立即将所有资源加载到内存, 而实际上可采用写时申请的方法

虚拟地址的优点:

  1. 保护系统,避免用户程序非法访问内存造成内核崩溃.
  2. 节约资源,采用映射方法,使用时全用缺页的方法申请物理空间,即提高了效率也节约物理空间

3.进程地址空间的申请

32bit平台下,一个进程拥有4G的虚拟地址空间

映射规则
1. 代码段,数据段,BSS段,直接从磁盘拷贝至内存.在linux 32bit平台上起始地址一般为 0x08048000
2. 堆栈,动态变化.
3. mmap映射的文件(普通文件以及库),用户需自行调用mmap函数
4. 高地址1G空间:供内核映射处理,用户空间不能直接访问

堆和栈的起始地址是随机产生的,其目的是避免产生安全漏洞,但是可以使用brk/sbrk函数指定在堆中申请空间的起始地址

brk/sbrk函数声明

1
2
3
4
5
   #include <unistd.h>

   int brk(void *addr);  //指定下次申请堆空间的起始地址

   void *sbrk(intptr_t increment);  //在当前地址位置后移increament字节,为0时返回当前地址

代码示例:

1
2
3
4
5
6
7
8
9
10
11
 #include<stdlib.h>
 #include<stdio.h>
 int i = 10;
 int main(int argc,char *argv[])
 {
     brk(0x09e50000);
     printf("brk return:%p\n",sbrk(0));
     char *ptr = malloc(102400);
     printf("new malloc:%p \n",ptr);
     return 0;
 }

输出结果
brk return:0x9e50000
new malloc:0x9e51010

strace工具可以追踪进程空间的加载

4.空间分配规则

  1. 代码段,数据段,BSS段的地址在编译时已经固定.
  2. BSS的结束段与堆的起始地址之间有一定间隙,间隙大小随机.
  3. brk调整的仅仅是堆中申请空间的起始值
  4. 系统默认认为每个进程分配的堆空间大小是固定的.使用sbrk(0)得到的是我们堆空间的结束值.
  5. 真正编程中,很少使用brk/sbrk调整位置

5.栈的说明

栈:从高地址向低地址增长,起始值也是随机的.主要存放局部变量, 新调用子函数时函数的参数以及返回值,由OS自动管理.