转让malloc()该功能后,发生了什么事内核?附malloc()和free()实现源
特此声明:在本文中,引用另一篇文章和帖子,结合的概括的理解malloc()函数的实现机制。
我们常常会在C程序中调用malloc()函数动态分配一块连续的内存空间并使用它们。那么,这些用户空间发生的事会引发内核空间什么样的反应呢?
malloc()是一个API,这个函数在库中封装了系统调用brk。因此假设调用malloc,那么首先会引发brk系统调用运行的过程。
brk()在内核中相应的系统调用服务例程为SYSCALL_DEFINE1(brk,
unsigned long, brk)。參数brk用来指定heap段新的结束地址。也就是又一次指定mm_struct结构中的brk字段。
brk系统调用服务例程首先会确定heap段的起始地址min_brk。然后再检查资源的限制问题。接着,将新老heap地址分别依照页大小对齐,对齐后的地址分别存储在newbrk和okdbrk中。
brk()系统调用本身既能够缩小堆大小。又能够扩大堆大小。缩小堆这个功能是通过调用do_munmap()完毕的。假设要扩大堆的大小。那么必须先通过find_vma_intersection()检查扩大以后的堆是否与已经存在的某个虚拟内存重合,怎样重合则直接退出。否则,调用do_brk()进行接下来扩大堆的各种工作。
<span style="font-size:18px;">SYSCALL_DEFINE1(brk, unsigned long, brk)
{
unsigned long rlim, retval;
unsigned long newbrk, oldbrk;
struct mm_struct *mm = current->mm;
unsigned long min_brk; down_write(&mm->mmap_sem); #ifdef CONFIG_COMPAT_BRK
min_brk = mm->end_code;
#else
min_brk = mm->start_brk;
#endif
if (brk < min_brk)
goto out; rlim = rlimit(RLIMIT_DATA);
if (rlim < RLIM_INFINITY && (brk - mm->start_brk) +
(mm->end_data - mm->start_data) > rlim) newbrk = PAGE_ALIGN(brk);
oldbrk = PAGE_ALIGN(mm->brk);
if (oldbrk == newbrk)
goto set_brk;
if (brk brk) {
if (!do_munmap(mm, newbrk, oldbrk-newbrk))
goto set_brk;
goto out;
} if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
goto out; if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
goto out;
set_brk:
mm->brk = brk;
out:
retval = mm->brk;
up_write(&mm->mmap_sem);
return retval;
}</span>
brk系统调用服务例程最后将返回堆的新结束地址。
用户进程调用malloc()会使得内核调用brk系统调用服务例程。由于malloc总是动态的分配内存空间,因此该服务例程此时会进入第二条运行路径中,即扩大堆。do_brk()主要完毕下面工作:
1.通过get_unmapped_area()在当前进程的地址空间中查找一个符合len大小的线性区间。而且该线性区间的必须在addr地址之后。假设找到了这个空暇的线性区间,则返回该区间的起始地址,否则返回错误代码-ENOMEM;
2.通过find_vma_prepare()在当前进程全部线性区组成的红黑树中依次遍历每一个vma。以确定上一步找到的新区间之前的线性区对象的位置。假设addr位于某个现存的vma中,则调用do_munmap()删除这个线性区。假设删除成功则继续查找,否则返回错误代码。
3.眼下已经找到了一个合适大小的空暇线性区,接下来通过vma_merge()去试着将当前的线性区与临近的线性区进行合并。假设合并成功。那么该函数将返回prev这个线性区的vm_area_struct结构指针。同一时候结束do_brk()。否则,继续分配新的线性区。
4.接下来通过kmem_cache_zalloc()在特定的slab快速缓存vm_area_cachep中为这个线性区分配vm_area_struct结构的描写叙述符。
5.初始化vma结构中的各个字段。
6.更新mm_struct结构中的vm_total字段,它用来同级当前进程所拥有的vma数量。
7.假设当前vma设置了VM_LOCKED字段。那么通过mlock_vma_pages_range()马上为这个线性区分配物理页框。
否则,do_brk()结束。
能够看到,do_brk()主要是为当前进程分配一个新的线性区。在没有设置VM_LOCKED标志的情况下,它不会立马为该线性区分配物理页框。而是通过vma一直将分配物理内存的工作进行延迟,直至发生缺页异常。
经过上面的过程,malloc()返回了线性地址,假设此时用户进程訪问这个线性地址,那么就会发生缺页异常(Page Fault)。整个缺页异常的处理过程很复杂,我们这里仅仅关注与malloc()有关的那一条运行路径。
当CPU产生一个异常时,将会跳转到异常处理的整个处理流程中。对于缺页异常,CPU将跳转到page_fault异常处理程序中。
异常处理程序会调用do_page_fault()函数,该函数通过读取CR2寄存器获得引起缺页的线性地址。通过各种条件推断以便确定一个合适的方案来处理这个异常。
do_page_fault()函数:
该函数通过各种条件来检測当前发生异常的情况,但至少do_page_fault()会区分出引发缺页的两种情况:由编程错误引发异常,以及由进程地址空间中还未分配物理内存的线性地址引发。
对于后一种情况,通常还分为用户空间所引发的缺页异常和内核空间引发的缺页异常。
内核引发的异常是由vmalloc()产生的,它仅仅用于内核空间内存的分配。
显然,我们这里须要关注的是用户空间所引发的异常情况。这部分工作从do_page_fault()中的good_area标号处開始运行,主要通过handle_mm_fault()完毕。
<span style="font-size:18px;">dotraplinkage void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
…… ……
good_area:
write = error_code & PF_WRITE; if (unlikely(access_error(error_code, write, vma))) {
bad_area_access_error(regs, error_code, address);
return;
}
fault = handle_mm_fault(mm, vma, address, write ? FAULT_FLAG_WRITE : 0);
}</span>
handle_mm_fault()函数:
该函数的主要功能是为引发缺页的进程分配一个物理页框,它先确定与引发缺页的线性地址相应的各级页文件夹项是否存在,怎样不存在则分进行分配。详细怎样分配这个页框是通过调用handle_pte_fault()完毕的。
<span style="font-size:18px;">int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, unsigned int flags)
{
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
…… ……
pgd = pgd_offset(mm, address);
pud = pud_alloc(mm, pgd, address);
if (!pud)
return VM_FAULT_OOM;
pmd = pmd_alloc(mm, pud, address);
if (!pmd)
return VM_FAULT_OOM;
pte = pte_alloc_map(mm, pmd, address);
if (!pte)
return VM_FAULT_OOM;
return handle_pte_fault(mm, vma, address, pte, pmd, flags);
}</span>
handle_pte_fault()函数:
该函数依据页表项pte所描写叙述的物理页框是否在物理内存中,分为两大类:
请求调页:被訪问的页框不再主存中,那么此时必须分配一个页框。
写时复制:被訪问的页存在,可是该页是仅仅读的。内核须要对该页进行写操作,此时内核将这个已存在的仅仅读页中的数据拷贝到一个新的页框中。
用户进程訪问由malloc()分配的内存空间属于第一种情况。对于请求调页。handle_pte_fault()仍然将其细分为三种情况:
1.假设页表项确实为空(pte_none(entry)),那么必须分配页框。
假设当前进程实现了vma操作函数集合中的fault钩子函数,那么这样的情况属于基于文件的内存映射。它调用do_linear_fault()进行分配物理页框。
否则。内核将调用针对匿名映射分配物理页框的函数do_anonymous_page()。
2.假设检測出该页表项为非线性映射(pte_file(entry)),则调用do_nonlinear_fault()分配物理页。
3.假设页框事先被分配,可是此刻已经由主存换出到了外存。则调用do_swap_page()完毕页框分配。
由malloc分配的内存将会调用do_anonymous_page()分配物理页框。
<span style="font-size:18px;">static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, pte_t *pte, pmd_t *pmd, unsigned int flags)
{
…… ……
if (!pte_present(entry)) {
if (pte_none(entry)) {
if (vma->vm_ops) {
if (likely(vma->vm_ops->fault))
return do_linear_fault(mm, vma, address,
pte, pmd, flags, entry);
}
return do_anonymous_page(mm, vma, address,
pte, pmd, flags);
}
if (pte_file(entry))
return do_nonlinear_fault(mm, vma, address,
pte, pmd, flags, entry);
return do_swap_page(mm, vma, address,
pte, pmd, flags, entry);
}
…… ……
}</span>
do_anonymous_page()函数:
此时,缺页异常处理程序最终要为当前进程分配物理页框了。它通过alloc_zeroed_user_highpage_movable()来完毕这个过程。
我们层层拨开这个函数的外衣,发现它最终调用了alloc_pages()。
<span style="font-size:18px;">static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd,
unsigned int flags)
{
…… ……
if (unlikely(anon_vma_prepare(vma)))
goto oom;
page = alloc_zeroed_user_highpage_movable(vma, address);
if (!page)
goto oom;
…… ……
}</span>
经过这样一个复杂的过程,用户进程所訪问的线性地址最终相应到了一块物理内存。
以下附上我自觉得比較完好的malloc()和free()函数源码:
<span style="font-size:18px;">#include <unistd.h>
#include <stdlib.h>
//块首
union header
{
struct{
union header *next;//指向下一空暇快的指针
unsigned int size;//空暇块的大小
}s;
long x;//对齐
};
typedef union header Header; #define NALLOC 1024;//请求的最小单位数,每页大小为1KB
static Header* moreSys(unsigned int num);//向系统申请一块内存
void* userMalloc(unsigned int nbytes);//从用户管理区申请内存
void userFree(void *ap);//释放内存,放入到用户管理区 static Header base;//定义空暇链表头
static Header *free_list = NULL;//空暇链表的起始查询指针 void* userMalloc(unsigned int nbytes)
{
Header *p;
Header *prev;
unsigned int unitNum;
//将申请的字节数nbytes转换成unitNum个块首单位,多计算一个作为管理块首
unitNum = (nbytes + sizeof(Header) - 1)/sizeof(Header) + 1;
if ((prev = free_list) == NULL)//假设无空暇链表,定义空暇链表
{
base.s.next = free_list = prev = &base;
base.s.size = 1;
}
for (p = prev->s.next; ; p = p->s.next, prev = p)
{
if (p->s.size >= unitNum)//空暇块足够大
{
if (p->s.size <= (unitNum + 1))
{
prev->s.next = p->s.next;
}
else//偏大,切出须要的一块
{
p->s.size = unitNum;
p += p->s.size;
p->s.size = unitNum;
}
free_list = prev;
return (void *)(p+1);
}
if (p == free_list)
{
if ((p = moreSys(unitNum)) == NULL)//无合适块。向系统申请
{
return NULL;
}
}
}
} static Header* moreSys(unsigned int num)
{
char *cp;
Header *up; if(num < NALLOC)
num = NALLOC;//向系统申请的最小量
cp = sbrk(num * sizeof(Header));
if (cp == (char *)-1)
{
return NULL;//无空暇页面。返回空地址
}
up = (Header *)cp;
up->s.size = num;
userFree(up + 1);
return free_list;
}</span>
<span style="font-size:18px;">//回收内存到空暇链上
void Free(void *ap)
{
Header *bp, *p;
bp = (Header *)ap - 1; //指向块首 for(p = free_list; !(bp>p && bp<p->s.next); p = p->s.next) //按地址定位空暇块在链表
//中的位置
if(p>=p->s.next && (bp>p || bp<p->s.next))
break; //空暇块在两端
if(bp + bp->s.size == p->s.next) { //看空暇块是否与已有的块相邻,相邻就合并
bp->s.size += p->s.next->s.size;
bp->s.next = p->s.next->s.next;
}
else
bp->s.next = p->s.next; if(p + p->s.size == bp) {
p->s.size += bp->s.size;
p->s.next = bp->s.next;
}
else
p->s.next = bp; free_list = p;
}</span>
版权声明:本文博主原创文章,博客,未经同意,不得转载。
转让malloc()该功能后,发生了什么事内核?附malloc()和free()实现源的更多相关文章
- 痞子衡嵌入式:IVT里的不同entry设置可能会造成i.MXRT1xxx系列启动App后发生异常跑飞
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IVT里的不同entry设置可能会造成i.MXRT1xxx系列启动App后发生异常跑飞问题的分析解决经验. 事情缘起恩智浦官方论坛上的一 ...
- 单片机main函数退出后发生什么——以stm32为例
STM32:main函数退出后发生什么? 我们都在说单片机要运行在无限循环里,不能退出,可退出之后会发生什么? 讨论STM32启动过程的文章数不胜数,可main函数结束之后会发生什么却少有讨论. 几日 ...
- 在浏览器中输入URL按下回车键后发生了什么
在浏览器中输入URL按下回车键后发生了什么 [1]解析URL[2]DNS查询,解析域名,将域名解析为IP地址[3]ARP广播,根据IP地址来解析MAC地址[4]分别从应用层到传输层.网络层和数据链路层 ...
- ASP.NET MVC 如何解决“上下文的模型已在数据库创建后发生更改”问题
问题描述:支持"XXContext"(泛指之类的数据库上下文模型)上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库. 问题解决:坑爹的MVC会 ...
- 支持“***Context”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。
在用VS进行MVC开发的过程中遇到如下问题: 支持“***Context”上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库(http://go.microsoft ...
- TProcedure,TMethod,TNotifyEvent,TWndMethod的区别,并模拟点击按钮后发生的动作
忽然发现TProcedure和TNotifEvent的区别还挺大的: procedure TForm1.Button2Click(Sender: TObject); begin ShowMessage ...
- "ApplicationDbContext"(泛指之类的数据库上下文模型)上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库。
一,在我使用自动生成数据库的时候,当你改变了数据库就会出现下面问题 "ApplicationDbContext"(泛指之类的数据库上下文模型)上下文的模型已在数据库创建后发生更改. ...
- System.InvalidOperationException: 支持“XXX”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。
System.InvalidOperationException: 支持“XXX”上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库(http://go.micro ...
- 支持“xxxContext”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库
将项目的数据库连接用户及密码修改后(切换用户,用户名与原来不一样,用户下对象结构一致),报以下错误: 支持“XXXDBContext”上下文的模型已在数据库创建后发生更改.请考虑使用 Code Fir ...
随机推荐
- PDF数据防扩散系统方案
在企业信息化过程中.大量的企业重要图纸和资料都是以电子文件的方式存在.为了避免内部关键数据的外泄,採取了多种方式:设计部门的门禁管制.防火墙.禁止计算机的USB接口等等. 可是泄密问题还是时有发生,原 ...
- Blob API及问题记录
接上一篇<js创建下载文件>, 记录核心部分 Blob 的API, >>传送门 , 同时说下使用过程中碰到的一个问题. 先说问题: 用Blob创建后缀为.sql的文件, 内容是 ...
- AS3.0下去除flash右键菜单
这两天工作中遇到一个问题,就是网页中内嵌的flash小游戏的用户体验,当鼠标在flash上点击右键时,出现的右键菜单中会有播放,停止等选项,虽然不会造成什么漏洞,但是体验非常差.在寻找解决方案的时候, ...
- 详细分析Java中断机制(转)
1. 引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任务.Java没有提供一种安全直接的方法 ...
- cocos2d-x坐标系
在cocos2d-x在,有几种不同的坐标系. 因为有好几个坐标系着一定的差异,他们需要明白,能力更精确的绘制各种图形画面上. 1.屏幕坐标系 只windows通过绘制图形上基本都知道.相应的坐标系统: ...
- 分解XML方法
分解XML方法 1.DOM生成和解析XML 2.SAX生成和解析XML 3.DOM4J生成和解析XML 4.JDOM生成和解析XML 版权声明:本文博主原创文章.博客,未经同意不得转载.
- Moran’s I空间统计中出现内存溢出的问题
在经济学.资源管理.生物地理学.政治地理学和人口统计等领域,经常会有如下的研究需求: 研究区域中的富裕区和贫困区之间的最清晰边界在哪里? 研究区域中存在可以找到异常消费模式的位置吗? 研究区域中意想不 ...
- Windows 8 应用开发 - 挂起与恢复
原文:Windows 8 应用开发 - 挂起与恢复 Windows 8 应用通常涉及到两种数据类型:应用数据与会话数据.在上一篇提到的本地数据存储就是应用层面的数据,包括应用参数设置.用户重 ...
- 【Swift】学习笔记(一)——熟知 基础数据类型,编码风格,元组,主张
自从苹果宣布swift之后,我一直想了解,他一直没有能够把它的正式学习,从今天开始,我会用我的博客来驱动swift得知,据我们了解还快. 1.定义变量和常量 var 定义变量,let定义常量. 比如 ...
- netperf 而网络性能测量
本文首先介绍网络性能測量的一些基本概念和方法.然后结合 netperf 工具的使用.详细的讨论怎样測试不同情况下的网络性能. 汤凯 (tangk73@hotmail.com), 2004 年 7 月 ...