程序是存在文件中的(硬盘),一个运行的程序是需要加载到内存中的,加载到内存中的程序叫进程。
STL --> 内存是自动分配和回收
|
C++ --> new/delete,会调用malloc和free
|
C语言 --> malloc/free
|
Unix/Linux系统调用 -> brk/sbrk
|
内存映射 -> mmap
| (用户层)
------------------------------------
kmalloc/vmalloc (内核层)
|
get_free_page()
虚拟内存技术:
在32位的Unix/Linux中,用虚拟内存技术管理内存。
每个进程都有0-4G的虚拟地址空间,虚拟地址必须映射到物理内存后才能使用,否则引发段错误。所谓的内存分配其实就是映射物理内存。程序员所接触到只能是虚拟内存,无法直接接触物理内存。
0-3G是用户层使用的,叫用户空间,3G-4G是内核使用的,叫内核空间。用户程序只能直接访问用户空间,无法直接访问内核空间,但可以通过Unix/Linux系统提供的系统调用(一系列的函数)进入内核空间。内存管理的最小单位是一个内存页,大小4096(4k)。
进程内存的分区:
代码区:程序代码(函数)放入代码区,只读区
全局区:保存全局变量/static变量
BSS段: 保存未初始化的全局变量
注:全局区和bss段 在main执行之前都会分配内存,但bss段会自动清0。
栈区:保存局部变量(非malloc分配),包括函数的参数,内存分配和回收自动进行
堆区:也叫自由区,内存管理是程序员执行。new/malloc分配的内存。
注:在代码区附近有一个只读变量区,一般和代码区合并。
段错误的原因:
1 虚拟内存没有映射物理内存就使用
2 操作某些没有权限的内存区域(修改只读区)
3 释放内存时缺少必要的附加信息
malloc的特点:
1 申请小内存时(不足33个内存页),默认映射33个内存页。用完后再申请不一再给33个内存页。
2 申请大内存时,映射稍多一点的内存页。
3 申请内存时,会额外多分配一点内存,用于记录相关信息。
注:malloc不一定映射新的物理内存,free也不一定真正释放物理内存。
进程之间同样的虚拟地址对应 不同的物理内存。
存管理API
包含头文件#include <unistd.h>
1、函数void *sbrk(intptr_t increment/*内存增量,单位字节*/);它返回上一次调用sbrk/brk函数后的末尾指针,失败返回-1;
increment取值:
如果内存增量为0,则返回当前位置指针;内存增量可正可负,如果为正数,指针向后移动,增加内存空间;如果为负数,指针向前移动,并释放内存空间。
函数工作原理:内部维护一个指针,指向当前堆内存最后一个字节的下一个位置。sbrk函数根据增量参数调整该指针的位置,同时返回该指针被调整之前的位置。若发现页耗尽或空闲,则会自动追加或解除页映射。
2、函数int brk(void *end_data_segment/*内存块尾地址*/);成功返回0,失败返回-1。
函数工作原理:内部维护一个指针,指向当前堆内存最后一个字节的下一个位置。brk函数根据指针参数设置该指针的位置。若发现页耗尽或空闲,则自动追加或解除页映射。
brk/sbrk 函数是系统提供分配内存/回收内存的。
一般用sbrk分配内存,用brk回收。
void* sbrk(int size)
size == 0 取当前位置
size >0 分配内存,并返回之前的位置
size <0 释放内存
brk/sbrk 底层需要维护一个位置
实例:
(1)malloc函数演示:
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *p1 = (int *)malloc(sizeof(int));
int *p2 = (int *)malloc(sizeof(int));
printf("%p\n",p1); //0x8491008
printf("%p\n",p2); //0x8491018
//malloc分配内存时会有多余的空间存储一些额外的信息,在使用free释放时会使用.如果该信息被破坏,将导致free失败
printf("每页%d个字节\n",getpagesize()); //每页4096个字节
free(p2);
// p1[3] = 0; //破坏存储的额外信息
free(p1);
p1 = NULL;
p2 = NULL;
return 0;
}
(2)malloc函数的实现:
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
// 内存控制块
typedef struct mem_control_block {
bool free; // 自由标志
struct mem_control_block* prev; // 前块指针
size_t size; // 本块大小
} MCB;
// 桟顶指针
MCB* g_top = NULL;
// 分配内存
void* mymalloc (size_t size) {
MCB* mcb;
for (mcb = g_top; mcb; mcb = mcb->prev)
if (mcb->free && mcb->size >= size)
break;
if (! mcb) {
mcb = sbrk (sizeof (MCB) + size);
if (mcb == (void*)-1)
return NULL;
mcb->prev = g_top;
mcb->size = size;
g_top = mcb;
}
mcb->free = false;
return mcb + 1;
}
// 释放内存
void myfree (void* ptr) {
if (! ptr)
return;
MCB* mcb = (MCB*)ptr - 1;
mcb->free = true;
for (mcb = g_top; mcb->prev; mcb=mcb->prev)
if (! mcb->free)
break;
if (mcb->free) {
g_top = mcb->prev;
brk (mcb);
}
else if (mcb != g_top) {
g_top = mcb;
brk ((void*)mcb + sizeof (MCB) + mcb->size);
}
}
typedef struct student{
int id;
char name[30];
char gender[10];
int age;
}Student;
int main(void){
Student* stu = (Student*)mymalloc(sizeof(Student));
printf("Please input the id of student:");
scanf("%d",&(stu->id));
printf("Please input the name of student:");
scanf("%s",stu->name);
printf("Please input the gender of student:");
scanf("%s",stu->gender);
printf("PLease input the age of student:");
scanf("%d",&(stu->age));
printf("ID:%d, Name:%s, Gender:%s, Age:%d.\n",stu->id,stu->name,stu->gender,stu->age);
myfree(stu);
return 0;
}
#include <sys/mman.h>
void* mmap (
void* start, // 映射区首地址,NULL系统自动选择
size_t length, // 映射区长度(字节),按页取整
int prot, // 映射权限
int flags, // 映射标志
int fd, // 文件描述符
off_t offset // 文件偏移量
);
成功返回映射区首地址,
失败返回MAP_FAILED(-1)。
prot取值:
PROT_EXEC - 映射区可执行
PROT_READ - 映射区可读
PROT_WRITE - 映射区可写
PROT_NONE - 映射区不可访问
flags取值:
MAP_FIXED - 若在start上无法创建映射, 返回失败,无此标志则自动调整
MAP_SHARED - 对映射区的写操作直接反映到文件中
MAP_PRIVATE - 对映射区的写操作只反映到内存中,不进文件
MAP_ANONYMOUS - 内存映射,将虚拟地址映射到物理内存中
MAP_DENYWRITE - 拒绝其它对文件的写入
MAP_LOCKED - 锁定映射区域
void* start, // 映射区首地址
size_t length // 映射区长度(字节)按页取整
);
成功返回0,失败返回-1。
实例:
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
struct Emp{
int id;
char name[20];
double sal;
};
int main(){
int fd=open("emp.dat",O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd==-1) perror("open"),exit(-1);
ftruncate(fd,sizeof(struct Emp)*3);//指定文件大小
void* p = mmap(0,sizeof(struct Emp)*3,
PROT_READ|PROT_WRITE,
MAP_SHARED,//写入文件
//MAP_PRIVATE,//不会把内容写入文件,只有本进程使用
fd,0);
struct Emp* pe = p;
pe[0].id = 100;
strcpy(pe[0].name,"liubei");
pe[0].sal = 200000;
pe[1].id = 200;
strcpy(pe[1].name,"guanyu");
pe[1].sal = 150000;
pe[2].id = 300;
strcpy(pe[2].name,"zhangfei");
pe[2].sal = 120000;
munmap(p,sizeof(struct Emp)*3);
close(fd);
return 0;
}
几个动态内存分配函数区别:
brk/sbrk:底层维护一个指针,记录堆尾。
malloc/free:底层维护一个双向链表,存储内存块的控制信息。