xv6源码剖析 004

xv6源码剖析 004

今天晚上我们来看看xv6的内存配置器和用户进程的初始化,分别是umalloc.cinit.c这两个文件。
ps:2024/4/11,昨晚着急睡觉,所以有些地方疏忽了,不好意思不好意思

umalloc.c

我们先来看看配置维护的数据结构

typedef long Align;
union header {
  struct {
    union header *ptr;
    uint size;
  } s;
  Align x;
};

xv6的核心配置器是一个联合体,这个联合体可以被当成long使用也可以被当成一个结构体来使用,当成结构体来使用的时候就是一个链表,

umalloc.c中定义的全局静态变量

typedef union header Header;

static Header base;
static Header *freep;

在这里插入图片描述
可以看到有两个联合体一个是base,一个是free。我们继续往下看,现在还看不出什么门道(没什么内核开发的经验)。我们知道,在c语言中,POSIX提供了两个分配内存的接口,分别是mallocfree。在这个源文件中大佬也写了这两个接口,但是在malloc中是调用了另一功能函数morecore(uint nu)

看了一下,好像并没有什么用,那我们直接忽略这个

  • free(void *ap)
  • static Header *morecore(uint nu)
  • void *malloc(uint nbytes)

我们先来看看

free(void *ap)

这段代码真的很坑人,我开始带入我自己的内存池模型,掉大坑了,下面我通过画图的方式来讲解一下

void
free(void *ap)
{
  Header *bp, *p;

  bp = (Header*)ap - 1;
    /// 1.
  for(p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
    if(p >= p->s.ptr && (bp > p || bp < p->s.ptr))
      break;
    /// 2.
  if(bp + bp->s.size == p->s.ptr){
    bp->s.size += p->s.ptr->s.size;
    bp->s.ptr = p->s.ptr->s.ptr;
  } else
    bp->s.ptr = p->s.ptr;
 	/// 3. 
 if(p + p->s.size == bp){
    p->s.size += bp->s.size;
    p->s.ptr = bp->s.ptr;
  } else
    p->s.ptr = bp;
  freep = p;
}

我们将这个函数的过程分为三个部分

第一部分:确定释放的内存在内存池中的位置

  for(p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
    if(p >= p->s.ptr && (bp > p || bp < p->s.ptr))
      break;

知道确定了释放的内存块在内存池具体的块之后,也就是当bp > p && bp < p->s.str成立时,退出循环进入下一个步骤

第二步:设置空闲块的属性,我们在看一个图

如果条件bp + bp->s.size == p->s.ptr成立,那么就会将两个块合成一个大块,因为在内存池中的内存块不一定是连续的,如果不成立的话就将这个块作为一个单独的块加入内存池中。

    /// 2.
  if(bp + bp->s.size == p->s.ptr){
    bp->s.size += p->s.ptr->s.size;
    bp->s.ptr = p->s.ptr->s.ptr;
  } else
    bp->s.ptr = p->s.ptr;

如下图所示
在这里插入图片描述

下面给大家展示一下,执行完步骤二后的内存池中的情况

在这里插入图片描述

最后一步就是:设置p的值了,也就是freep的值

相信通过上面的分析,这一步也很简单了,核心思路也很简单,如果连续就合并,如果不连续就修改一下指针。

 if(p + p->s.size == bp){
    p->s.size += bp->s.size;
    p->s.ptr = bp->s.ptr;
  } else
    p->s.ptr = bp;
  freep = p;

ok,我们再来看看另一函数

morecore(uint nu)

static Header* morecore(uint nu)
{
  char *p;
  Header *hp;

  if(nu < 4096)					// 1.
    nu = 4096;
  p = sbrk(nu * sizeof(Header)); // 2.系统调用sbrk
  if(p == (char*)-1)			// 3.
    return 0;
  hp = (Header*)p;				// 4.
  hp->s.size = nu;
  free((void*)(hp + 1));
  return freep;
}

morecore我们可以将函数看成四个主要的步骤:

  1. 检查申请的内存的大小是否满足一个page的大小(在xv6中一个页固定大小为4096,当然我们实现内存池的时候当然可以根据我们自己的需要将内存划分为多种不同的大小的块,就是将xv6的这个配置器看成我们内存池的一个子集)。为什么要分配固定分配4096个字节的数据呢?这主要是为了避免内存碎片(这个是错的,大家看完了就知道了) 申请4096的大小的空间是因为xv6内核的页就是固定的4096个字节,哈哈哈,(这是我猜的)在原来的libc中的cmalloc中,经过长时间的malloc和free,在堆区中会长生大量的内存碎片,看下图。可以看到其实在下面的内存中我们是有足够的空间来容纳两个新的数据块的,但是,因为这几个空闲内存块并不连续,导致了malloc返回一个空指针,在实际情况中我们可能也会发现,我们的内存利用率并不高,但是内存分配总是失败,有可能就是这个原因。

    • 我们再来说一下内存对齐(结构体对齐)算了时间不是很充足,兄弟们自己了解一下吧

在这里插入图片描述

  1. 第二步就是进行系统调用sbrk()向操作系统申请内存,我们需要注意的是这个系统调用是为进程申请空间。后面我们还会接触到这个系统调用的

  2. 然后就是判空了

  3. 最后分配成功,我们为这个新的内存块设置属性,并将这个内存块添加到空闲列表中(freep


最后一个函数 malloc(uint nbytes)

我先说明一下下面三个变量的作用吧

prevp:跟踪迭代器,以便修改内存池的块的信息

p:迭代器

nunits:将用户给定的申请的内存大小进行对齐,以便找出一个合适的内存块

不难看出,xv6的内存池使用的是优先匹配原则:一旦遇到合适的块就给出去

void* malloc(uint nbytes)
{
  Header *p, *prevp;
  uint nunits;
  
  /// 1.
  /// sizeof(Header) = 8
  nunits = (nbytes + sizeof(Header) - 1)/sizeof(Header) + 1;
  /// 2.
  if((prevp = freep) == 0){
    base.s.ptr = freep = prevp = &base;
    base.s.size = 0;
  }
  /// 3.
  for(p = prevp->s.ptr; ; prevp = p, p = p->s.ptr){
    if(p->s.size >= nunits){
      if(p->s.size == nunits)
        prevp->s.ptr = p->s.ptr;
      else {
        p->s.size -= nunits;
        p += p->s.size;
        p->s.size = nunits;
      }
      freep = prevp;
      return (void*)(p + 1);
    }
    if(p == freep)
      if((p = morecore(nunits)) == 0)
        return 0;
  }
}
  1. 这一步有点类似单位转换,顺便进行对齐

  2. 这一步也很简单,检查freep是否被初始化了,

    base就类似我们所说的虚拟头节点

  3. 这一步也很简单,找到第一个合适的块,如果块的大小刚刚好,直接全部给,否则就进行切割,

    如果确实没有内存的话就执行morecore先内核申请一块。

init.c

ok,还有点时间我们光速过一下init.c

直接上代码,so easy,明天看看应该就能够进入内核篇了,内核才有意思哈哈哈哈。

int
main(void)
{
  int pid, wpid;

    /// 1.
     打开终端,终端不是我们说的shell
  if(open("console", O_RDWR) < 0){
    mknod("console", CONSOLE, 0);
    open("console", O_RDWR);
  }
    /// 将描述符进行拷贝,
    /// 这时候,我们的fd 0 1 2
    /// 分别为 stdin stdout stderr
  dup(0);  // stdout
  dup(0);  // stderr
	
    /// 运行shell
    /// 这时候才有我们看到的“终端”
  for(;;){
    printf("init: starting sh\n");
    pid = fork();
    if(pid < 0){
      printf("init: fork failed\n");
      exit(1);
    }
    if(pid == 0){
      exec("sh", argv);
      printf("init: exec sh failed\n");
      exit(1);
    }
	
      /// 作为shell的父进程等待shell结束
    for(;;){
      // this call to wait() returns if the shell exits,
      // or if a parentless process exits.
      wpid = wait((int *) 0);
      if(wpid == pid){
        // the shell exited; restart it.
        break;
      } else if(wpid < 0){
        printf("init: wait returned an error\n");
        exit(1);
      } else {
        // it was a parentless process; do nothing.
      }
    }
  }
}
  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值