watchmen linux高级程序设计 02进程内存管理

来源:互联网 发布:淘宝联盟修改导购名称 编辑:程序博客网 时间:2024/06/05 08:20
Linux 高级程序设计_02 进程内存管理与 valgrind 的使用    守望者成才网(http://watchmen.cn/)
本课目标
(1) 知识目标:理解 Linux 进程结构及内存申请与释放原理。
(2) 编程目标:熟练掌握 brk/sbrk/malloc/reall/free/memmove/memcpy 系列函数的使用。
主要知识点
(1)Linux 可执行文件与进程内存结构, Linux 进程内存加载过程。
(2) Linux 进程的堆与栈的区别与使用。
(3)内存管理在编程开发中的应用及注意事项。
(4)内存管理工具 valgrind 的使用。
课程内容
问题引入:各个变量具体存放在哪个地方?为什么会有段错误?
一、程序的结构与进程的结构
yangzd@ubuntu:~/watchmen$ size test
text data bss dec hex filename
1061 264 8 1333 535 test
一个可执行程序包含三个部分
代码段:主要存放指令,操作以及只读的(常量)数据(例如字符串常量)。
数据段:全局或者静态的已经初始化的变量。
BSS 段:全局或者静态的未初始化的变量。

执行一个程序(存放在磁盘上),我们系统如何为其申请内存空间?
要运行,则需要把程序加载到内存中?(磁盘是专门的读写驱动,慢,内存可由 CPU 直接访问读
写,比磁盘快)。
什么是进程:进程是 Linux 操作系统最小的资源管理单元。一个程序运行,必然首先为其创建一个
进程,进程是有生命周期(在第 8 章会介绍执行一个程序中可以创建多个进程)。一个进程是执行的程
序段。资源有哪些?
因为在程序的执行时,会动态的申请空间,执行子函数,因此 Linux 对一个进程的管理采用以下方
式:

在执行程序时,系统首先在内核空间中创建一个进程,为这个进程申请一个 PCB(进程控制块
task_struct),用于管理整个进程的所有资源。其中 mm_struct 成员用来管理与当前进程相关的所有内存
资源。
(1)代码段,数据段,BSS 段,直接从磁盘拷贝到当前内存空间,大小相等。
(2)动态的空间。
堆 ,栈空间,mmap 段(映射其它的库相关的信息。)
一个进程的内存地址信息列表(或者使用 pmap 来查看)

yangzd@ubuntu:~$ cat /proc/3579/maps
008ba000-008bb000 r-xp 00000000 00:00 0
[vdso]
009e1000-00b3b000 r-xp 00000000 08:01 263070 /lib/i386-linux-gnu/libc-2.13.so
00b3b000-00b3c000 ---p 0015a000 08:01 263070 /lib/i386-linux-gnu/libc-2.13.so
00b3c000-00b3e000 r--p 0015a000 08:01 263070 /lib/i386-linux-gnu/libc-2.13.so
00b3e000-00b3f000 rw-p 0015c000 08:01 263070 /lib/i386-linux-gnu/libc-2.13.so
00b3f000-00b42000 rw-p 00000000 00:00 0
00dd8000-00df4000 r-xp 00000000 08:01 263057 /lib/i386-linux-gnu/ld-2.13.so
00df4000-00df5000 r--p 0001b000 08:01 263057 /lib/i386-linux-gnu/ld-2.13.so
00df5000-00df6000 rw-p 0001c000 08:01 263057 /lib/i386-linux-gnu/ld-2.13.so
08048000-08049000 r-xp 00000000 08:01 540695 /home/yangzd/watchmen/test
08049000-0804a000 r--p 00000000 08:01 540695 /home/yangzd/watchmen/test
0804a000-0804b000 rw-p 00001000 08:01 540695 /home/yangzd/watchmen/test
b776b000-b776c000 rw-p 00000000 00:00 0
b777b000-b777e000 rw-p 00000000 00:00 0
bfabb000-bfadc000 rw-p 00000000 00:00 0
[stack]
地址实际并不是真正的物理地址,而是虚拟地址空间。为什么要用虚拟地址,出于对资源的保护,
对系统来说,内存资源是宝贵的,而一个程序执行并不需要立即将所有的资源全部加载到内存,而实际
上可采用写时申请的方法。
好处
(1)保护系统,很多用户程序非法访问不能去造成内核的崩溃,因此段错误出现,使执行这个非
法访问的进程自动退出。


(2)节约资源。采用内存映射的方法,一个程序执行时并不是立即将所有代码和空间都空间到,
而是使用时全用缺页的方法真正申请物理空间,一是提前效率,另外也节约物理内存空间。
32 位平台,一个进程拥有自己的 4G 的虚拟地址空间,与其它进程无关。因此,进程与进程之间是
独立的。

二、进程地址空间的申请
32 位平台下,一个进程拥有 4G 虚拟地址空间,这个地址空间怎么分配和使用。这些空间怎么来映
射:
(1)代码段,数据段,BSS 段,这三个部分直接从磁盘拷贝到内存。起始地址在当前的 32 位平台
linux 下为 0x08048000 地址。
(2)堆栈。动态变化 。malloc 系列。
(3)mmap 映射的文件(普通文件,也可以是其它类型的文件)。库。用户自己调用 mmap 函数。
(4)栈段。
(5)高地址 1G 空间供内核映射处理的。用户空间不能直接处理。
堆和栈的起始地址默认是随机产生的,其目的是避免安全漏洞。但是可以指定在堆中申请空间的地
始地址。brk/sbrk 函数。

void *sbrk(intptr_t increment);
//在当前的地址位置后移 increment 字节。如果为 0,返回当前
program break 值
int brk(void *addr);//指定下次申请堆空间的起始地直来 addr。
系统执行一个进程,到底怎么来加载这些空间。strace 工具可以查看。
堆空间的起始地址是随机的。可以设置为不随机,大小也可以设置为固定大小。
(1)代码段,数据段,BSS 段的地址是已经在编译链接时固定。
(2)在 BSS 结束与堆起始地址有间隙。这个大小是随机的。
(3)brk 函数仅仅是调整在堆中申请空间的起始值。
使用以下指令可以指定堆的起始地址固定。

yangzd@ubuntu:~/watchmen$ sudo sysctl -w kernel/randomize_va_space=0
(4)系统默认为每个进程分配的堆空间大小是固定的。使用 sbrk(0)得到的是我们堆空间的结束
值。第一次使用 malloc 申请资源返回的地址接近于堆空间的起始值。全用 brk(addr)改变的是新申请数据
的堆空间起始值。
(5)在真正编程中,很少全用 brk/sbrk,使用 malloc 函数来新申请堆空间,效率更高。部分时间使
用 mmap 来映射 mmap 区,
栈:栈从高地址向低地址增长,栈的起始值也是随机的。栈中主要存放的是局部变量,新调用子函
数时函数的参数及返回值。由 OS 自动管理。
三、编程中的内存空间的申请与释放
(1)代码段中:由只读数据和代码组成:const,字符串常量,这些内容的空间在编译链接时申请好
且指定存储地址。
(2)数据段 BSS 段申请:定义的全局的或者是静态的变量。已经初始化的在数据段,未初始化的
在 BSS 段中。因此也是在编译链接时已经申请且指定的地址。
以上空间的申请在运行之时就加载到内存中,直到程序结束。不再变化(除 execX 函数替换外)。
因此,这些变量申请的空间生存周期就是整个程序。因此,如果在相应的作用域中,可一直访问;另一
个方式,如果你知道这个变量的地址,也可以一直访问。
{
通过符号访问一个变量,要在他的作用域中。但是,对于以上三个段中的数据,只要知道地址,其
实可以通过地址间接访问这个空间,且在整个程序执行期间都可以。
}
在编程中,以上空间的申请就是定义相应的变量或者常量。
(3)堆:由程序员自己动态管理的。
C:malloc/calloc/realloc/free
c++ :new /delete
动态申请。堆的起始位置由 brk 函数来指定,但具体编程中, 一般不会自己使用 brk 函数,而是使
用 malloc 库函数。内核对内存的管理是页式管理,因此在 malloc 申请空间时,使用链式结构来管理已经
申请的堆空间。
void *calloc(size_t nmemb, size_t size);
void *malloc(size_t size); //申请指定大小的内存空间。虚拟地址空间。
void free(void *ptr);
void *realloc(void *ptr, size_t size);
char *ptr = malloc(100 * sizeof(char));
if(NULL == ptr)
{
}
Watchmen 成才网(版权所有)http://www.watchmen.cnLinux 高级程序设计 配套视频
Watchmen 出品@版权所有
进程内存管理与 valgrind 的使用
修订时间:2014-05-10
V0.9
页码 5/8
realloc 函数用于调整已经申请的堆中的数据空间。
ptr = malloc(100);
ptr_new = realloc(ptr, 200);
增加:在真正实现上:如果 Ptr 后有空闲的空间,直接扩展,但是如果没有,会重新查找一个可用
(>=200)大小空间,将原来的数据(ptr 100)复制到新空间中,然后释放掉原来空间。如果查找不到,返回
空。因此不能写成这样:
ptr = realloc(ptr, 200);
//这样子写的问题是如果失败,ptr 返回空,丢失了指向的空间。
在堆中申请的空间,如果不再使用,一定要释放,free(ptr);编程中还要加上一句。
free(ptr);
ptr = NULL;
避免后面的双重释放,但 free(NULL)没有任何影响。
另外申请与释放一定要匹配,避免不必要的内存泄露。
(4)栈:由 OS 动态管理的。
栈空间在加载程序,创建进程是就申请了一个范围,栈是由 OS 来主要管理。
为什么不能返回局部变量的地址?
局部变量的生存周期是怎么样?
概念:普通局部变量,函数的参数,返回值,函数调用相关信息都要使用栈空间。
对于未申请的堆空间地址访问,一般会出现段错误。
对已经释放的函数栈空间的访问,一般不会出现段错误,但是是非法的访问,是不允许的。
在编程中,对任何空间的访问一定要保证这个空间已经申请且在控制范围内。
Watchmen 成才网(版权所有)http://www.watchmen.cnLinux 高级程序设计 配套视频
Watchmen 出品@版权所有
进程内存管理与 valgrind 的使用
修订时间:2014-05-10
V0.9
页码 6/8
对任何空间地址值的操作仅仅是数据的操作,没有任何的问题。
(5)mmap 的库以及相关文件。
将一个文件的内容全部或者部分的映射到虚拟地址空间中,后面释放时操作这段内存空间可以被同
步到磁盘文件的操作,效率比较高。
#include <sys/mman.h>
void *mmap(void *addr,
size_t length, //映射到哪个虚拟地址空间,一般为 NULL,让系统选择
//如果自己设置,最好是页的整数倍,getpagesize
//大小
int prot,
int flags,
int fd,
off_t offset); //加载的文件对应打开的文件描述符
//偏移,从文件哪个位置开始映射
int munmap(void *addr, size_t length);
prot:
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
flags
MAP_SHARED Share this mapping. Updates to the mapping are visible to other processes that map
this file, and are carried through to the underlying file. The file may not actually be updated until msync(2)
or munmap() is called.
共享映射,其它进程如果也映射这个文件,可以立即看到这个更改,但并不是立即更新磁盘文件的
内容,如果要更新到磁盘,必须使用 msync 以及 munmap 时。
MAP_PRIVATE
Create a private copy-on- write mapping. Updates to the mapping are not visible to other proâ€
cesses mapping the same file, and are not carried through to the underlying file. It is
unspecified whether changes made to the file after the mmap() call are visible in the mapped
region.
内存段是私有的,对它的修改仅仅是局部有效,其它进程不可见的。
int munmap(void *addr, size_t length);
int msync(void *addr, size_t length, int flags);
第一个参数是映射文件的起始地址,第二个参数为 长度,第三个参数:

MS_ASYNC:期望尽快写入文件。
MS_SYNC:在函数结束时必须写入文件。
MS_INVALIDATE:让内核自己决定。
文件的内容要在中间插入新内容比较麻烦的。磁盘是按块存储的。要中间添加内容,必须导致后面
所有内容的移动。用 mmap 来实现/模拟文件操作工具是比较麻烦的,因为涉及到文件长度的增加,数据
的移动。在书上 5.2.7 有关于 mmap 来调整文件内容的示例。
04 常见内存错误以及 valgrind 使用
代码段:只读数据。因此对这一部分的数据,试图写只读数据。这个在编译的时候基本可以检测。
数据段/BSS 段:未初始化直接访问。即使没有显式初始化,仍然会初始化为 0。
栈空间数据:
(1)局部变量,未初始化这类变量会给随机的初值,出现异常情况更诡异。
(2) 栈溢出,在栈中申请过大的局部变量。
堆空间数据:
内存泄漏,(1)申请未释放(2)申请后,双重释放。
对于所有的地址空间:
(1)野指针的问题。未初始化指针。去访问这个指针指向的空间。
(2)越界访问,例如一个数组 a[10],试图访问 a[10]以及以后。
(3)非法的越权访问。例如 mmap 的空间只读,但试图写。
(4)空间不在控制范围仍然去访问空间,例如返回局部变量地址,且后面访问这个空间。
使用工具,来检测常见的内存错误。valgrind 工具。
1041 gcc -o valgrind_example01 valgrind_example01.c -g
1042 valgrind --tool=memcheck --show-reachable=yes --read-var-info=yes --verbose --time-stamp=yes --
leak-check=full --log-file=mycode.log ./valgrind_example01
1043 less mycode.log
如果要使用图形化的工具,要安装 QT,这个工具名字叫 valkyrie。
编程作业
(1)编程验证各段虚拟地址信息。
(2)熟悉使用 sbrk/brk/malloc 等系列函数。

0 0
原创粉丝点击