特此声明:在本文中,引用另一篇文章和帖子,结合的概括的理解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()实现源的更多相关文章

  1. 痞子衡嵌入式:IVT里的不同entry设置可能会造成i.MXRT1xxx系列启动App后发生异常跑飞

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IVT里的不同entry设置可能会造成i.MXRT1xxx系列启动App后发生异常跑飞问题的分析解决经验. 事情缘起恩智浦官方论坛上的一 ...

  2. 单片机main函数退出后发生什么——以stm32为例

    STM32:main函数退出后发生什么? 我们都在说单片机要运行在无限循环里,不能退出,可退出之后会发生什么? 讨论STM32启动过程的文章数不胜数,可main函数结束之后会发生什么却少有讨论. 几日 ...

  3. 在浏览器中输入URL按下回车键后发生了什么

    在浏览器中输入URL按下回车键后发生了什么 [1]解析URL[2]DNS查询,解析域名,将域名解析为IP地址[3]ARP广播,根据IP地址来解析MAC地址[4]分别从应用层到传输层.网络层和数据链路层 ...

  4. ASP.NET MVC 如何解决“上下文的模型已在数据库创建后发生更改”问题

    问题描述:支持"XXContext"(泛指之类的数据库上下文模型)上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库. 问题解决:坑爹的MVC会 ...

  5. 支持“***Context”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。

    在用VS进行MVC开发的过程中遇到如下问题: 支持“***Context”上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库(http://go.microsoft ...

  6. TProcedure,TMethod,TNotifyEvent,TWndMethod的区别,并模拟点击按钮后发生的动作

    忽然发现TProcedure和TNotifEvent的区别还挺大的: procedure TForm1.Button2Click(Sender: TObject); begin ShowMessage ...

  7. "ApplicationDbContext"(泛指之类的数据库上下文模型)上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库。

    一,在我使用自动生成数据库的时候,当你改变了数据库就会出现下面问题 "ApplicationDbContext"(泛指之类的数据库上下文模型)上下文的模型已在数据库创建后发生更改. ...

  8. System.InvalidOperationException: 支持“XXX”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。

    System.InvalidOperationException: 支持“XXX”上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库(http://go.micro ...

  9. 支持“xxxContext”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库

    将项目的数据库连接用户及密码修改后(切换用户,用户名与原来不一样,用户下对象结构一致),报以下错误: 支持“XXXDBContext”上下文的模型已在数据库创建后发生更改.请考虑使用 Code Fir ...

随机推荐

  1. _00019 Storm架构介绍和Storm获取案例(简单的官方网站Java案例)

    博文作者:妳那伊抹微笑 itdog8 地址链接 : http://www.itdog8.com(个人链接) 博客地址:http://blog.csdn.net/u012185296 博文标题:_000 ...

  2. The mell hall——坑爹

    The mell hall 题目描述 In HUST,there are always manystudents go to the mell hall at the same time as soo ...

  3. 数据库连接技术之OLE DB

    之前的博客介绍了ODBC和JDBC.这次简单的介绍一下OLE DB.ODBC的总结不知道是没贴到博客上还是不在这个博客上,我再找找,没有的话我再补充到时候.好了.開始吧. 回想 之前呢介绍过了ODBC ...

  4. SO_REUSEADDR 套接字选项应用

    在网络上的SO_REUSEADDR套接字选项是用来解决地址问题重用了大量的信息.但仅仅停留在文字的表达.并没有实例,非常easy误导谁刚开始学习,和不解,此处不再赘述. 的使用该选项,以及须要注意的问 ...

  5. CSDN-markdown语法之怎样使用LaTeX语法编写数学公式

    文件夹 文件夹 正文 标记公式 行内公式 块级公式 上标和下标 分数表示 各种括号 根号表示 省略号 矢量表示 间隔空间 希腊字母 特殊字符 关系运算符 集合运算符 对数运算符 三角运算符 微积分运算 ...

  6. ZOJ 3795 Grouping 求最长链序列露点拓扑

    意甲冠军:特定n积分.m向边条. 该点被划分成多个集合随机的每个集合,使得2问题的关键是无法访问(集合只能容纳一个点) 问至少需要被分成几个集合. 假设没有戒指,接着这个话题正在寻求产业链最长的一个有 ...

  7. jenkins配置.net mvc网站

    jenkins配置.net mvc网站 上一篇使用jenkins配置.net mvc网站进行持续集成一只是简单介绍了jenkins构建站点到本地服务器,这一篇,就来讲解如何部署站点到指定的服务器上面. ...

  8. JQuery打印

    jquery.jqprint-0.3.js JQuery提供的局部打印功能: <input type="button" value="打印" onclic ...

  9. 阶乘因式分解(一)(南阳oj56)

    阶乘因式分解(一) 时间限制:3000 ms  |  内存限制:65535 KB 难度:2 描写叙述 给定两个数m,n,当中m是一个素数. 将n(0<=n<=10000)的阶乘分解质因数, ...

  10. CSharp SQLServer 登陆

    =======后台SQLServer存储过程================ --创建数据库create database Stu; --创建表use MyShool;if exists(select ...