lds

ld script なるものがある事が判明。昨晩色々確認入れてたファイル達ってコメント等から類推するにこれクサい。ってコトで google 先生にご教示頂いた以下を確認。

てーコトは Makefile にソレな記述があるな、と以下。

$ $ grep 'vmlinux.lds' Makefile
vmlinux-lds  := arch/$(ARCH)/kernel/vmlinux.lds
      -T $(vmlinux-lds) $(vmlinux-init)                          \
      $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) FORCE ,$^)
.tmp_vmlinux1: $(vmlinux-lds) $(vmlinux-all) FORCE
.tmp_vmlinux2: $(vmlinux-lds) $(vmlinux-all) .tmp_kallsyms1.o FORCE
.tmp_vmlinux3: $(vmlinux-lds) $(vmlinux-all) .tmp_kallsyms2.o FORCE
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
$(sort $(vmlinux-init) $(vmlinux-main)) $(vmlinux-lds): $(vmlinux-dirs) ;
# Leave this as default for preprocessing vmlinux.lds.S, which is now
export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
$

しかし gnu の ld が ld script なるソレを使うなんて初めて知りました。

整理

してみます。
とりあえず、kernel 作る時の ld にて i386 なソレだと arch/i386/kernel/vmlinux.lds.S が使われるのか。この ld script な拡張子って微妙な情報しかないです。とりあえず

  • "ld"
  • "lds"
  • "lds.S"
  • "lds.in"
  • "ld.script"
  • "ld.script.balo"

との記述もありましたが、kernel の makefile にあるソレは

vmlinux-lds  := arch/$(ARCH)/kernel/vmlinux.lds

ってコトなので lds.S ではなく S になるんでしょうか ...

閑話休題

どうも微妙な方向にソレる事が多いな。整理続行。直前エントリでも引用した vmlinuz.lds.S な記述としてはこんなコメントがあって

  /* will be freed after init
   * Following ALIGN() is required to make sure no other data falls on the
   * same page where __smp_alt_end is pointing as that page might be freed
   * after boot. Always make sure that ALIGN() directive is present after
   * the section which contains __smp_alt_end.
   */
  . = ALIGN(4096);

ソコから何行か下に以下の記述がある。

  .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
        __initcall_start = .;
        INITCALLS
        __initcall_end = .;
  }

上記が展開されて以下になるはず

  .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
        __initcall_start = .;
        *(.initcall0.init) *(.initcall0s.init)  *(.initcall1.init)      \
        *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init)      \
        *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init)       \
        *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init)      \
        *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init)  \
        *(.initcall7.init) *(.initcall7s.init)
        __initcall_end = .;
  }

みたいなカンジ。こうすると

  • .initcall0.init なセクションとして定義されたシンボル
  • .initcall0s.init なセクションとして定義されたシンボル

という順で直列にならぶ、という理解で良いのかな。そうでないと for で順に取り出すコトは不可能ではあるのですが ...
しかしこーゆーソレを gcc の機能でヤッツケるあたりは凄いな。何が凄いんだかよく分かりませんが ...
# gcc が凄いのか kernel hacker が凄いのか、という意味です

成程

以下な形で定義すれば

	static initcall_t __initcall_populate_rootfsrootfs __attribute_used__ \
	__attribute__((__section__(".initcall" "rootfs" ".init"))) = populate_rootfs;

たとえば上記だと .initcallrootfs.init なセクションに追加されるのか。これを一気に解放する仕組みも凄いのでしょうが、init/main.c で以下のコードで順に取り出して呼び出してしまう仕組みもなかなかに凄い。

	initcall_t *call;
	int count = preempt_count();

	for (call = __initcall_start; call < __initcall_end; call++) {
		char *msg = NULL;
		char msgbuf[40];
		int result;

		if (initcall_debug) {
			printk("Calling initcall 0x%p", *call);
			print_fn_descriptor_symbol(": %s()",
					(unsigned long) *call);
			printk("\n");
		}

		result = (*call)();
/* 以下略 */

疑問が少しだけ解けたのは良いのですが、話を元に戻さないと、なコトに気がついてたり ...
# たしか元々は initramfs 云々を読むはずだったのですが ...