系统调用与内存管理(sbrk、brk、mmap、munmap)

一、系统调用(System Call):

在Linux中,4G内存可分为两部分——内核空间1G(3 ~ 4G)与用户空间3G(0 ~ 3G),我们通常写的C代码都是在对用户空间即0 ~ 3G的内存进行操作。而且,用户空间的代码不能直接访问内核空间,因此内核空间提供了一系列的函数,实现用户空间进入内核空间的接口,这一系列的函数称为系统调用(System Call)。比如我们经常使用的open、close、read、write等函数都是系统级别的函数(man 2 function_name),而像fopen、fclose、fread、fwrite等都是用户级别的函数(man 3 function_name)。不同级别的函数能够操作的内存区域自然也就不同。

我们用一幅图来描述函数的调用过程:

对于C++中new与delete的底层则是用malloc和free实现。而我们所用的malloc()、free()与内核之间的接口(桥梁)就是sbrk()等系统函数;当然我们也可以直接调用系统调用(系统函数),达到同样的作用。我们可以用下面这幅图来描述基本内存相关操作之间的关系:

虽然使用系统调用会带来一定的好处,但是物极必反,系统调用并非能频繁使用。由于程序由用户进入内核层时,会将用户层的状态先封存起来,然后到内核层运行代码,运行结束以后,从内核层出来到用户层时,再把数据加载回来。因此,频繁的系统调用效率很低。今天我们就系统调用层面来对内存操作做进一步的了解。

二、内存管理(Memory Management)系统调用:

1、brk()与sbrk():

(1)、函数原型与实现:

//函数原型:
#include<unistd.h>
int brk(void * addr); 
void * sbrk(intptr_t increment);

由于sbrk()与brk()这两个系统函数有点所谓怪异,我们先来看看man手册对于sbrk()与brk()的描述:

DESCRIPTION

brk() and sbrk() change the location of the program break, which
defines the end of the process's data segment (i.e., the program
break is the first location after the end of the uninitialized data
segment). Increasing the program break has the effect of allocating
memory to the process; decreasing the break deallocates memory.

brk() sets the end of the data segment to the value specified by
addr, when that value is reasonable, the system has enough memory,
and the process does not exceed its maximum data size (see
setrlimit(2)).

sbrk() increments the program's data space by increment bytes.
Calling sbrk() with an increment of 0 can be used to find the current
location of the program break.

RETURN VALUE

On success, brk() returns zero. On error, -1 is returned, and errno
is set to ENOMEM.

On success, sbrk() returns the previous program break. (If the break
was increased, then this value is a pointer to the start of the newly
allocated memory). On error, (void *) -1 is returned, and errno is
set to ENOMEM.

描述:
brk()和sbrk()改变程序间断点的位置。程序间断点就是程序数据段的结尾。(程序间断点是为初始化数据段的起始位置).通过增加程序间断点进程可以更有效的申请内存 。当addr参数合理、系统有足够的内存并且不超过最大值时brk()函数将数据段结尾设置为addr,即间断点设置为addr。sbrk()将程序数据空间增加increment字节。当increment为0时则返回程序间断点的当前位置。

返回值:
brk()成功返回0,失败返回-1并且设置errno值为ENOMEM(注:在mmap中会提到)。
sbrk()成功返回之前的程序间断点地址。如果间断点值增加,那么这个指针(指的是返回的之前的间断点地址)是指向分配的新的内存的首地址。如果出错失败,就返回一个指针并设置errno全局变量的值为ENOMEM。

总结:
这两个函数都用来改变 “program break” (程序间断点)的位置,改变数据段长度(Change data segment size),实现虚拟内存到物理内存的映射。
brk()函数直接修改有效访问范围的末尾地址实现分配与回收。sbrk()参数函数中:当increment为正值时,间断点位置向后移动increment字节。同时返回移动之前的位置,相当于分配内存。当increment为负值时,位置向前移动increment字节,相当与于释放内存,其返回值没有实际意义。当increment为0时,不移动位置只返回当前位置。参数increment的符号决定了是分配还是回收内存。而关于program break的位置如图所示:

(2)、简单测试:

对于分配好的内存,我们只要有其首地址old与长度MAX*MAX即可不越界的准确使用(如下图所示),其效果与malloc相同,只不过sbrk()与brk()是C标准函数的底层实现而已,其机制较为复杂(测试中,死循环是为了查看maps文件,不至于进程消亡文件随之消失)。

虽然,sbrk()与brk()均可分配回收兼职,但是我们一般用sbrk()分配内存,而用brk()回收内存,上例中回收内存可以这样写:

int err = brk(old);
// 或者brk(p);效果与sbrk(-MAX*MAX);是一样的,但brk()更方便与清晰明了。
if(-1 == err){
    perror("brk");
    exit(EXIT_FAILURE);
}

2、mmap()与munmap():

mmap函数(地址映射):mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零(Linux堆空间未使用内存均清零)。这里我们只研究mmap的内存映射,而暂时不讨论文件方面的问题。关于mmap的文件映射的更详细的内容可参考:认真分析mmap:是什么 为什么 怎么用

//函数原型:
#incldue<sys/mman.h>
void * mmap(void * addr, size_t length,int prot,int flags,int fd,off_t offset);

参数:

(1)、addr:
起始地址,置零让系统自行选择并返回即可。
(2)、length:
长度,不够一页会自动凑够一页的整数倍,我们可以宏定义#define MIN_LENGTH_MMAP 4096为一页大小。
(3)、prot:
读写操作权限,PROT_READ可读、PROT_WRITE可写、PROT_EXEC可执行、PROT_NONE映射区域不能读取。(注意PROT_XXXXX与文件本身的权限不冲突,如果在程序中不设定任何权限,即使本身存在读写权限,该进程也不能对其操作)。
(4)、flags常用标志:
MAP_SHARED【share this mapping】、MAP_PRIVATE【Create a private copy-on-write mapping】
MAP_SHARED只能设置文件共享,不能地址共享,即使设置了共享,对于两个进程来说,也不会生效。而MAP_PRIVATE则对于文件与内存都可以设置为私有。
MAP_ANON【Deprecated】、MAP_ANONYMOUS:匿名映射,如果映射地址需要加该参数,如果不加默认映射文件。MAP_ANON已经过时,只需使用MAP_ANONYMOUS即可。
(5)、fd:文件描述符。
(6)、offset:文件描述符偏移量
(fd和offset对于一般性内存分配来说设置为0即可)

返回值:

失败返回MAP_FAILED,即(void * (-1))并设置errno全局变量。
成功返回指向mmap area的指针pointer。

常见errno错误:

ENOMEM:内存不足;
EAGAIN:文件被锁住或有太多内存被锁住;
EBADF:参数fd不是有效的文件描述符;
EACCES:存在权限错误,。如果是MAP_PRIVATE情况下文件必须可读;使用MAP_SHARED则文件必须能写入,且设置prot权限必须为PROT_WRITE。
EINVAL:参数addr、length或者offset中有不合法参数存在。

munmap函数:解除映射关系

// addr为mmap函数返回接收的地址,length为请求分配的长度。
int munmap(void * addr, size_t length);

这张图描述了mmap内存地址映射的位置关系(栈区以上为内核空间)。关于这一点我们可以作以简单的测试(我采用MIN_LENGTH_MMAP宏,当然你也可以用多少申请多少,系统总是以最小1页来映射的,关于内存分页与虚拟地址映射可参考:Linux系统内存管理与内存分页机制

mmap映射的地址处于堆区与栈区中间,malloc映射的堆区内存为33页(最小映射大小),而mmap映射的内存为3页,也是4096的整数倍。

作者:Apollon_krj
链接:https://blog.csdn.net/Apollon_krj/article/details/54565768
来源:CSDN
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容