kalloc.c void kfree(char *v)

トップページ
jupiteroak.hatenablog.com


kalloc.c
https://github.com/mit-pdos/xv6-public/blob/master/kalloc.c#L59

void kfree(char *v)
{
  struct run *r;

  if((uint)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
    panic("kfree");

  // Fill with junk to catch dangling refs.
  memset(v, 1, PGSIZE);

  if(kmem.use_lock)
    acquire(&kmem.lock);
  r = (struct run*)v;
  r->next = kmem.freelist;
  kmem.freelist = r;
  if(kmem.use_lock)
    release(&kmem.lock);
}

kfree関数は、引数vを先頭アドレス(仮想アドレス)とする4KBのメモリ領域を解放します(メモリ管理のために使用している連結リストに要素を挿入します)。

引数 char *v
解放されるメモリ領域の先頭アドレス(仮想アドレス)です。


処理の内容

アドレスvが不正な値ではないことを確認する

 if((uint)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
    panic("kfree");

v % PGSIZE(#define PGSIZE 4096)が0以外の値になる場合→アドレスvが4KB境界(4KBの倍数値)ではない場合、v < endが真となる場合→アドレスvがメモリ管理のために使用されているメモリ領域よりも下位側にある場合、V2P(v) >= PHYSTOPが真となる場合→アドレスvがメモリ管理のために使用されているメモリ領域よりも上位側にある場合は、panic関数を呼び出して、メッセージを出力します。

メモリ管理のために使用されるメモリ領域について

xv6では、仮想アドレスend→0x8011 44a8(厳密には 0x8011 5000)から仮想アドレスP2V(PHYSTOP)→0x8E00 0000までのメモリ領域を、メモリ管理のために使用しています。

endについて

endは、カーネルプログラムにおけるbss領域の終端アドレスで、リンカスクリプトkernel.ldで定義されているシンボル(ラベル)です。リンカスクリプトkernel.ldにPROVIDE(end = .); と表記されています。
kernel.ld

/* Simple linker script for the JOS kernel.
   See the GNU ld 'info' manual ("info ld") to learn the syntax. */

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)

SECTIONS
{
	/* Link the kernel at this address: "." means the current address */
        /* Must be equal to KERNLINK */
	. = 0x80100000;

	.text : AT(0x100000) {
		*(.text .stub .text.* .gnu.linkonce.t.*)
	}

	PROVIDE(etext = .);	/* Define the 'etext' symbol to this value */

	.rodata : {
		*(.rodata .rodata.* .gnu.linkonce.r.*)
	}

	/* Include debugging information in kernel memory */
	.stab : {
		PROVIDE(__STAB_BEGIN__ = .);
		*(.stab);
		PROVIDE(__STAB_END__ = .);
	}

	.stabstr : {
		PROVIDE(__STABSTR_BEGIN__ = .);
		*(.stabstr);
		PROVIDE(__STABSTR_END__ = .);
	}

	/* Adjust the address for the data segment to the next page */
	. = ALIGN(0x1000);

	/* Conventionally, Unix linkers provide pseudo-symbols
	 * etext, edata, and end, at the end of the text, data, and bss.
	 * For the kernel mapping, we need the address at the beginning
	 * of the data section, but that's not one of the conventional
	 * symbols, because the convention started before there was a
	 * read-only rodata section between text and data. */
	PROVIDE(data = .);

	/* The data segment */
	.data : {
		*(.data)
	}

	PROVIDE(edata = .);

	.bss : {
		*(.bss)
	}

	PROVIDE(end = .);

	/DISCARD/ : {
		*(.eh_frame .note.GNU-stack)
	}
}

リンカスクリプトで定義されたシンボルendの値は、ソースコード内でextern char end[] を用いる事で参照できます。
kalloc.c

extern char end[];

PHYSTOPについて

memlayout.h

#define PHYSTOP 0xE000000

PHYSTOP→0xE00 0000はxv6が利用することができる物理アドレスの上限値です。xv6のメモリレイアウトでは、物理アドレスPHYSTOP→0xE00 0000と仮想アドレスP2V(PHYSTOP)→0x8E00 0000が対応付けされています。
条件文 V2P(v) >= PHYSTOP では、V2Pマクロを使って仮想アドレスvを物理アドレスV2P(v)に変換しています。

ぶらさがり参照の対策を行う

memset(v, 1, PGSIZE);

ぶらさがり参照によるデータの利用を防ぐために、memset関数を呼び出して、先頭アドレスがv・サイズがPGSIZE(#define PGSIZE 4096)のメモリ領域(フリーリストの要素として使用するメモリ領域)を、1で初期化します。

クリティカルセクションの入口を定める

if(kmem.use_lock)
    acquire(&kmem.lock);

kmem.use_lockが1の場合は→フリーリストのヘッダーであるkmem構造体排他制御する場合は、acquire関数を呼び出してロックを取得し、クリティカルセクションの入口とします。

連結リストの要素を挿入する

r = (struct run*)v;
r->next = kmem.freelist;
kmem.freelist = r;

メモリ管理のために使用している連結リストfreelistに要素を挿入します。


r = (struct run*)v;
解放されるメモリ領域の先頭部分を連結リストの要素(run構造体)として扱うために、アドレスvをrun構造体のポインタrに格納します。


r->next = kmem.freelist;
新規に追加される要素(r)が、連結リストの新しい先頭要素になるように挿入します。
そのために、新規に追加される要素(r)のnextが、既存の連結リストの先頭要素を参照するようにします(新規に追加される要素(r)のnextに、既存の連結リストの先頭要素のアドレスを格納します)。
既存の連結リストの先頭要素のアドレスは、連結リストのヘッダー(kmem構造体)のfreelistに格納されています。


kmem.freelist = r;
新規に追加される要素(r)が、連結リストの新しい先頭要素になるように挿入します。
そのために、連結リストのヘッダー(kmem構造体)のfreelistが、新規に追加された連結リストの要素(r)を参照するようにします(連結リストのヘッダー(kmem構造体)のfreelistに、新規に追加された連結リストの要素(r)のアドレスを格納します)。

クリティカルセクションの出口を定める

if(kmem.use_lock)
    release(&kmem.lock);

kmem.use_lockが1の場合は→連結リストのヘッダーであるkmem構造体を排他制御していた場合は、release関数を呼び出してロックを解放し、クリティカルセクションの出口とします。