2009年6月30日火曜日

query_apm_bios

今回は「query_apm_bios」関数を見てみます。
例によってBIOSにAPMの情報を問い合わせて、ブートパラメータ構造体に格納している関数に違いありません。

arch/x86/boot/apm.c
======
int query_apm_bios(void)
{
/* ~省略(変数宣言)~ */

/* APM対応かチェックする */
ax = 0x5300;
bx = cx = 0;
asm volatile("pushl %%ebp ; int $0x15 ; popl %%ebp ; setc %0"
     : "=d" (err), "+a" (ax), "+b" (bx), "+c" (cx)
     : : "esi", "edi");

/* ~省略(エラーチェック)~*/

/* いったんAPMから切断する */
ax = 0x5304;
bx = 0;
asm volatile("pushl %%ebp ; int $0x15 ; popl %%ebp"
     : "+a" (ax), "+b" (bx)
     : : "ecx", "edx", "esi", "edi");

/* Paranoia */
ebx = esi = 0;
cx = dx = di = 0;

/* 改めてAPM接続 */
asm volatile("pushl %%ebp ; int $0x15 ; popl %%ebp ; setc %6"
     : "=a" (ax), "+b" (ebx), "+c" (cx), "+d" (dx),
       "+S" (esi), "+D" (di), "=m" (err)
     : "a" (0x5303));

/* ブートパラメータ構造体にセット */
boot_params.apm_bios_info.cseg = ax;
boot_params.apm_bios_info.offset = ebx;
boot_params.apm_bios_info.cseg_16 = cx;
boot_params.apm_bios_info.dseg = dx;
boot_params.apm_bios_info.cseg_len = (u16)esi;
boot_params.apm_bios_info.cseg_16_len = esi >> 16;
boot_params.apm_bios_info.dseg_len = di;
/* ~省略~ */

/* もう一度APM情報を取得してくる */
ax = 0x5300;
bx = cx = 0;
asm volatile("pushl %%ebp ; int $0x15 ; popl %%ebp ; setc %0"
     : "=d" (err), "+a" (ax), "+b" (bx), "+c" (cx)
     : : "esi", "edi");
/*~省略(エラーチェック)~*/

/* バージョンとフラグ情報をセット */
boot_params.apm_bios_info.version = ax;
boot_params.apm_bios_info.flags = cx;
return 0;
}

======

なんだか似たようなコードばかり読んでいますが、こういった地道な作業の積み重ねなのです。

2009年6月29日月曜日

query_ist

main関数で「query_mca」の次にあるのは「query_voyager」ですが、NUMAシステム用なのでパスします。
その次の「query_ist」を見ていきます。
ISTとは「Intel Speedstep Technology」の略で、省電力機能です。
BIOSを使ってその辺の情報を拾ってくるんだろうな、と。。

同関数はmain.cの中にあります。
======
static void query_ist(void)
{
/* 古いBIOSでこの問い合わせするとクラッシュする */
if (cpu.level < 6)
return;

asm("int $0x15"
    : "=a" (boot_params.ist_info.signature),
      "=b" (boot_params.ist_info.command),
      "=c" (boot_params.ist_info.event),
      "=d" (boot_params.ist_info.perf_level)
    : "a" (0x0000e980),  /* IST Support */
      "d" (0x47534943)); /* Request value */
}

======
徐々にパターンが見えてきた気がしませんか。
問い合わせたい情報をレジスタにセットして0x15でソフトウェア割り込み。
とれた値を構造体に格納。
でもネットで調べてみてもISTを取得するためのBIOS機能なんて見つからず。
きっとここではそうやって取っているんだろうな、という解釈で進めますか、、、

2009年6月28日日曜日

query_mca

main関数もようやく中盤に差し掛かってきました。
今回見るのは「query_mca」。

arch/x86/boot/mca.c
======
int query_mca(void)
{
u8 err;
u16 es, bx, len;

/* BIOSを使ってROMテーブルを取得する */
asm("pushw %%es ; "
"int $0x15 ; "
"setc %0 ; "
"movw %%es, %1 ; "
"popw %%es"
: "=acd" (err), "=acdSD" (es), "=b" (bx)
: "a" (0xc000));

if (err)
return -1; /* No MCA present */

/* thread_info->addr_limitにesの値を格納する */
set_fs(es);
/* テーブルサイズを取得 */
len = rdfs16(bx);

if (len > sizeof(boot_params.sys_desc_table))
len = sizeof(boot_params.sys_desc_table);

/* ROMテーブルの内容をboot_params.sys_desc_tableにコピーする */
copy_from_fs(&boot_params.sys_desc_table, bx, len);
return 0;
}

======
int 0x15(AH:0xc0)でブートパラメータをとってきてboot_params_sysにコピーしている、というのがquery_mcaでやっていること。
で、これでとれてくるブートパラメータって何?というところは色々調べてみたのだけど分からずじまい。。。

この関数で呼んでいる「set_fs」「rdfs16」「copy_from_fs」といった関数はコメント以上のことは
やっていない単純なものなので見ないおきます。

2009年6月25日木曜日

keyboard_set_repeat

main関数で呼び出しているお次は「keyboard_set_repeat」。
コメントにもなぜここでやるのか分からん、と書いてあるような関数なので
モチベーションあがりませんが、短いので見ておきましょう。

======
static void keyboard_set_repeat(void)
{
u16 ax = 0x0305;
u16 bx = 0;
asm volatile("int $0x16"
: "+a" (ax), "+b" (bx)
: : "ecx", "edx", "esi", "edi");
}

======
キーボードのリピートレートをMAXに設定しています。
この部分はBIOSの機能としてはかなりマニアックなようでネット探しても
なかなか詳細な解説には出会えません。

linux-usersメーリングリストの過去ログから拾ってきました
http://search.luky.org/linux-users.3/msg02165.html

追記)
こちらも参考になります。
ブートローダー(その4)ーありえるえりあ

2009年6月24日水曜日

detect_memory

main関数で呼び出しているお次は「detect_memory」。
その名のとおり、メモリを検出しているに違いありません。

arch/x86/boot/memory.c
======
int detect_memory(void)
{
int err = -1;

if (detect_memory_e820() > 0)
err = 0;

if (!detect_memory_e801())
err = 0;

if (!detect_memory_88())
err = 0;

return err;
}

======

それでは各サブ関数を見ていきましょう。
すべて同じファイル内にあります。

======
static int detect_memory_e820(void)
{
/* ~変数宣言~ */

do {
size = sizeof(struct e820entry);

/* システムのアドレスマップをBIOSに問い合わせる。 */
asm("int $0x15; setc %0"
: "=d" (err), "+b" (next), "=a" (id), "+c" (size),
"=m" (*desc)
: "D" (desc), "d" (SMAP), "a" (0xe820));

/* 失敗してたらループを抜ける*/
if (err)
break;

/* BIOSによってはこのループ中にSMAPを返すのをやめてしまいます。
こちらとしてはここまで成功してんだか失敗してんだか
          ゴミまざってんだか分からないのでエントリ数0で
          ループ抜けちゃいます */
if (id != SMAP) {
count = 0;
break;
}

/* アドレスマップのエントリ数を加算 */
count++;
desc++;

/* アドレスマップエントリの配列サイズ(=128)を越えてないか? */
} while (next && count < e820_entries =" count;">

======
SMAPとか出てきますがこれは「System Memoy mAP」の略です。
BIOSからもらえるメモリマップの情報です。
要約してしまうとこの関数はBIOSの機能を使ってシステムのメモリマップを取得しているのです。
次に「detect_memory_e801」を見てみます。

======
static int detect_memory_e801(void)
{
/* ~変数宣言~ */

/* BIOSから拡張メモリ領域サイズ(64MBを超える部分)を取得する */
bx = cx = dx = 0;
ax = 0xe801;
asm("stc; int $0x15; setc %0"
: "=m" (err), "+a" (ax), "+b" (bx), "+c" (cx), "+d" (dx));

if (err)
return -1;

/* これ必要?(※原文まま)*/
if (cx || dx) {
ax = cx;
bx = dx;
}

/* とれてくる値が16MB超過していたらおかしい */
if (ax > 15*1024)
return -1; /* Bogus! */

/* とってきた64MBを超える分の拡張メモリサイズを格納しておく */
boot_params.alt_mem_k = (ax == 15*1024) ? (dx <<>


======
さっきから出てくる「int 0x15」というのはBIOSの機能を呼び出している部分で、
ファンクション表は英語のWikipediaが便利です。
あんまり難しい英語でもないのでなんとかいけます。

http://en.wikipedia.org/wiki/BIOS_call


他に親切な解説としては下記が詳しくて参考になります。
ブートローダー(その3) -ありえるえりあ


最後の方はコメントを無理やり訳してみましたが、正直よく理解できてません。
めげずに次へ

======
static int detect_memory_88(void)
{
u16 ax;
u8 err;

/* 拡張メモリサイズをとってくる */
ax = 0x8800;
asm("stc; int $0x15; setc %0" : "=bcdm" (err), "+a" (ax));

/* とってきたメモリサイズを格納しておく */
boot_params.screen_info.ext_mem_k = ax;
/* エラーの結果を返す */
return -err;
}

=======

というわけで「detect_memory」の内容をまとめると
1)「detect_memory_e820」でシステムメモリのアドレスマップエントリ数を取得
2)「detect_memory_e801」で64MBを超えた部分の拡張メモリサイズを取得
3)「detect_memory_88」で拡張メモリサイズを取得
ということをしているのです。

2009年6月23日火曜日

set_bios_mode

main関数でvalidate_cpuの次に呼び出しているのは「set_bios_mode」、今回はこちらを見ていきます。

=====================
static void set_bios_mode(void)
{
#ifdef CONFIG_X86_64
u32 eax, ebx;

eax = 0xec00;
ebx = 2;
asm volatile("int $0x15"
     : "+a" (eax), "+b" (ebx)
     : : "ecx", "edx", "esi", "edi");
#endif
}

=====================

、、、64bit CPUでないと何もしないようなのでおしまい。
CPUモードをBIOSに設定しているようです。

2009年6月22日月曜日

validate_cpu

main関数から呼び出している関数のうち、前回までにcopy_boot_params、init_heapの2つを
見てきました。今回は3つめ「validate_cpu」を見てみましょう。

arch/x86/boot/cpu.c
======
int validate_cpu(void)
{
  /*変数宣言(省略) */

  /* CPUのチェック */
  check_cpu(&cpu_level, &req_level, &err_flags);

  /* CPUレベルと要求レベルの比較 */
  if (cpu_level < req_level) {
    /* エラー処理(省略) */
  }

  /* エラーフラグが立っているか?*/
  if (err_flags) {
    /* エラー処理(省略)*/
  } else {
    return 0;
  }
}

=======

check_cpuという関数を呼び出して、関数内で設定された値をチェックして必要に応じてエラーメッセージを表示しているのみ。なので、実機能はcheck_cpuにあります。

arch/x86/boot/cpucheck.c
=======
int check_cpu(int *cpu_level_ptr, int *req_level_ptr, u32 **err_flags_ptr)
{
int err;

/* とりあえずCPUレベル3をセット */
memset(&cpu.flags, 0, sizeof cpu.flags);
cpu.level = 3;

/* アライメントチェックフラグがあればレベル4 */
if (has_eflag(X86_EFLAGS_AC))
cpu.level = 4;

/* CPUID命令でプロセッサを識別する。必要ならレベルも更新 */
get_flags();
/* エラーがないかチェックする */
err = check_flags();

/* 64bitかどうか */
if (test_bit(X86_FEATURE_LM, cpu.flags))
cpu.level = 64;

if (/*AMDの場合*/) {
~省略~
} else if (/* VIA C3の場合 */) {
~省略~
} else if (/* Transmetaの場合 */) {
~省略~
}

if (err_flags_ptr)
*err_flags_ptr = err ? err_flags : NULL;
if (cpu_level_ptr)
*cpu_level_ptr = cpu.level;
if (req_level_ptr)
*req_level_ptr = req_level;

return (cpu.level < req_level || err) ? -1 : 0;
}

=======

どんなCPUを使っているの?という情報をとってきてパラメータに格納しています。

2009年6月21日日曜日

init_heap

今回はmain関数の中で「copy_boot_params」の次に呼び出している「init_heap」を見てみます。

====
static void init_heap(void)
{
char *stack_end;

  /* ヒープを使うかどうか */
if (boot_params.hdr.loadflags & CAN_USE_HEAP) {
asm("leal %P1(%%esp),%0"
   : "=r" (stack_end) : "i" (-STACK_SIZE));

/* ヒープのオフセットリミットをheap_end_ptr に格納する */
heap_end = (char *)
((size_t)boot_params.hdr.heap_end_ptr + 0x200);
if (heap_end > stack_end)
heap_end = stack_end;
} else {
/* ブートプロトコル2.0以前のものはヒープを用意できない */
puts("WARNING: Ancient bootloader, some functionality "
    "may be limited!¥n");
}
}
====

boot_params.hdr.loadflagsの値を見てヒープを使うかどうかを判定しています。
ブートプロトコルが2.02以降ならば基本的にif文の内側に入ってきます。
if文の内側ではヒープのオフセットリミットをheap_end_ptrメンバに格納しています。
boot_params.hdrは前回見たcopy_boot_paramsにてコピーして来たものです。
bott_params.hdrは「setup_header」という構造体でbootparam.hで定義しています。

arch/x86/include/asm/bootparam.h
====
struct setup_header {
__u8 setup_sects;
__u16 root_flags;
__u32 syssize;
__u16 ram_size;
#define RAMDISK_IMAGE_START_MASK 0x07FF
#define RAMDISK_PROMPT_FLAG 0x8000
#define RAMDISK_LOAD_FLAG 0x4000
__u16 vid_mode;
__u16 root_dev;
__u16 boot_flag;
__u16 jump;
__u32 header;
__u16 version;
__u32 realmode_swtch;
__u16 start_sys;
__u16 kernel_version;
__u8 type_of_loader;
__u8 loadflags;
#define LOADED_HIGH (1<<0)
#define QUIET_FLAG (1<<5)
#define KEEP_SEGMENTS (1<<6)
#define CAN_USE_HEAP (1<<7)
__u16 setup_move_size;
__u32 code32_start;
__u32 ramdisk_image;
__u32 ramdisk_size;
__u32 bootsect_kludge;
__u16 heap_end_ptr;
__u16 _pad1;
__u32 cmd_line_ptr;
__u32 initrd_addr_max;
__u32 kernel_alignment;
__u8 relocatable_kernel;
__u8 _pad2[3];
__u32 cmdline_size;
__u32 hardware_subarch;
__u64 hardware_subarch_data;
__u32 payload_offset;
__u32 payload_length;
__u64 setup_data;
} __attribute__((packed));
=====

でかい。
前回も見たheader.Sの中での「hdr」のアドレス部分からの抜粋を下記に。

=====
hdr:
setup_sects: .byte SETUPSECTS
root_flags: .word ROOT_RDONLY
syssize: .long SYSSIZE
ram_size: .word RAMDISK
vid_mode: .word SVGA_MODE
root_dev: .word ROOT_DEV
boot_flag: .word 0xAA55
=====

先述の構造体の「boot_flag」までがここまでと対応しています。
さらにheader.Sを見て行きましょう。

====
.globl _start
_start:

...(省略)...

start_sys_seg: .word SYSSEG
.word kernel_version-512 # pointing to kernel version string
# above section of header is compatible
# with loadlin-1.5 (header v1.5). Don't
# change it.

type_of_loader: .byte 0 # = 0, old one (LILO, Loadlin,
#      Bootlin, SYSLX, bootsect...)
# See Documentation/i386/boot.txt for
# assigned ids

# flags, unused bits must be zero (RFU) bit within loadflags
loadflags:
LOADED_HIGH = 1 # If set, the kernel is loaded high
CAN_USE_HEAP = 0x80 # If set, the loader also has set
# heap_end_ptr to tell how much
# space behind setup.S can be used for
# heap purposes.
# Only the loader knows what is free

...(省略)...

payload_offset: .long input_data
payload_length: .long input_data_end-input_data

setup_data: .quad 0 # 64-bit physical pointer to
# single linked list of
# struct setup_data

====

長いので一部省略していますが、構造体定義と照らし合わせてみれば最後まで値が入ってくることが分かります。

2009年6月20日土曜日

copy_boot_params

今回はmain関数から最初に呼び出している「copy_boot_params」を見てみます。

====
static void copy_boot_params(void)
{
  struct old_cmdline {
   u16 cl_magic;
   u16 cl_offset;
  };
  const struct old_cmdline * const oldcmd =
   (const struct old_cmdline *)OLD_CL_ADDRESS;

  BUILD_BUG_ON(sizeof boot_params != 4096);
  /* ブートパラメータをコピーする */
  /* コピー元のhdrは「header.S」にて定義されているもの */
  memcpy(&boot_params.hdr, &hdr, sizeof hdr);

    /* 以下はカーネルバージョンが古いとき(2.02以前?)に通る処理 */
  if (!boot_params.hdr.cmd_line_ptr &&
     oldcmd->cl_magic == OLD_CL_MAGIC) {
  /* Old-style command line protocol. */
  u16 cmdline_seg;

   /* Figure out if the command line falls in the region
     of memory that an old kernel would have copied up
     to 0x90000... */
  if (oldcmd->cl_offset < boot_params.hdr.setup_move_size)
    cmdline_seg = ds();
   else
   cmdline_seg = 0x9000;

   boot_params.hdr.cmd_line_ptr =
   (cmdline_seg  << 4) + oldcmd->cl_offset;
  }
}

====

カーネルバージョンが新しければブートパラメータを「boot_params.hdr」へコピーしているだけです。
コピー元のhdrは先日見た「header.S」で定義しています。

====
.section ".header", "a"
.globl hdr
hdr:
setup_sects: .byte SETUPSECTS
root_flags: .word ROOT_RDONLY
syssize: .long SYSSIZE
ram_size: .word RAMDISK
vid_mode: .word SVGA_MODE
root_dev: .word ROOT_DEV
boot_flag: .word 0xAA55
====

各々の意味については下記のドキュメントが参考になります。
Linux JF (Japanese FAQ) Project.

main.c

今回はmain関数を読み始めてみます。

arch/x86/boot/main.c

======
void main(void)
{
/* ブートヘッダをゼロページにコピー */
copy_boot_params();

/* ヒープの末尾チェック */
init_heap();

/* サポートしているCPUかチェック */
if (validate_cpu()) {
puts("Unable to boot - please use a kernel appropriate "
"for your CPU.\n");
die();
}

/* BIOSにCPUモードをセット */
set_bios_mode();

/* メモリ配置の検出 */
detect_memory();

/* キーボードのリピート設定(なぜ?) ※原文まま */
keyboard_set_repeat();

/*NUMA用なので気にしない*/
query_mca();

/* NUMA用なので気にしない */
#ifdef CONFIG_X86_VOYAGER
query_voyager();
#endif

/* Intel SpeedStep(省電力技術)の問い合わせ */
query_ist();

/* APM情報(電源管理)の問い合わせ */
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
query_apm_bios();
#endif

/*拡張BIOS(?)、Enhanced Disk Drive情報の問い合わせ、だと思う*/
#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
query_edd();
#endif

/* ビデオモードの設定 */
set_video();

/* コマンド引数に"quiet"とあればビットを立てておく*/
if (cmdline_find_option_bool("quiet"))
boot_params.hdr.loadflags |= QUIET_FLAG;

/*プロテクトモードへ!! */
go_to_protected_mode();
}

======

ここの最後でプロテクトモードへ移行しているので、GDTやIDTの設定もおそらくそれ以前にしているハズ。main関数内で呼び出している各関数について、次回から見ていきます。

2009年6月17日水曜日

header.S

今日はブート時のエントリポイントが記述されている「arch/x86/boot/header.S」を見てみましょう。

その前に、前回見たリンカスクリプトのセクション定義の一部を抜粋。

. = 0;
.bstext : { *(.bstext) }
.bsdata : { *(.bsdata) }
. = 497;
.header : { *(.header) }
.inittext : { *(.inittext) }
.initdata : { *(.initdata) }
.text : { *(.text*) }


「header.S」を見てみると先頭に「.bstext」と「.bsdata」というセクションで記述されているのが分かります。これらはフロッピーからブートした場合に走る処理で推奨しない旨のメッセージを表示しています。

その次に「.header」セクションがあります。
この中にリンカスクリプトでエントリポイントとして指定のあった「_start」があります。
start_of_setupというアドレスまでいっきにジャンプしてしまいます。
この飛び越えてしまう間には後々に参照するための各種シンボルが定義されています。
では飛んだ先である「start_of_setup」から見てみましょう。
ここは「.inittext」セクションであるため、「.header」セクションの直後に位置することがリンカスクリプトから分かります。

#「.inittext」セクションに配置
.section ".inittext", "ax"
start_of_setup:

#「SAFE_RESET_DISK_CONTROLLER」が定義されていたらディスクコントローラをリセット
#ifdef SAFE_RESET_DISK_CONTROLLER
# Reset the disk controller.
movw $0x0000, %ax # Reset disk controller
movb $0x80, %dl # All disks
int $0x13
#endif

# Force %es = %ds
movw %ds, %ax #dsの値を
movw %ax, %es #esにコピー
cld #ディレクションフラグをクリア

# 過去のコードではssレジスタとdsレジストの値が一致しない状態でLILOからカーネルに
# 移譲されたときに問題が起きていました。
# ssレジスタの値が不正のときはスタックポインタを再計算します。
# そうでないときは放っておきます。
# Apparently some ancient versions of LILO invoked the kernel with %ss != %ds,
# which happened to work by accident for the old code. Recalculate the stack
# pointer if %ss is invalid. Otherwise leave it alone, LOADLIN sets up the
# stack behind its own code, so we can't blindly put it directly past the heap.

movw %ss, %dx
cmpw %ax, %dx # %ds == %ss?
movw %sp, %dx
je 2f # -> assume %sp is reasonably set

# ここに来たらssレジスタが不正
# Invalid %ss, make up a new stack
movw $_end, %dx
testb $CAN_USE_HEAP, loadflags
jz 1f
movw heap_end_ptr, %dx
1: addw $STACK_SIZE, %dx
jnc 2f
xorw %dx, %dx # Prevent wraparound

# dxがスタック領域の末尾を指すようにする
2: # Now %dx should point to the end of our stack space
andw $~3, %dx # dword align (might as well...)
jnz 3f
movw $0xfffc, %dx # Make sure we're not zero
3: movw %ax, %ss
movzwl %dx, %esp # Clear upper half of %esp
sti # Now we should have a working stack

# We will have entered with %cs = %ds+0x20, normalize %cs so
# it is on par with the other segments.
# スタックに「6f」を積んでからリターン(lretw)しているので直後の「6:」の
# ラベル位置にジャンプする。なぜこうする必要があるのだろう??
pushw %ds
pushw $6f
lretw
6:

# 「setup_sig」はリンカスクリプトにて
# .signature : {
# setup_sig = .;
# LONG(0x5a5aaa55)
#}
# と、定義されています
# Check signature at end of setup
cmpl $0x5a5aaa55, setup_sig
jne setup_bad

# 「__bss_start」もリンカスクリプトにて定義
# stosl命令はEAXの値をDIに書き込む
# 下記のコードはbss領域を0クリアしている
# Zero the bss
movw $__bss_start, %di
movw $_end+3, %cx
xorl %eax, %eax
subw %di, %cx
shrw $2, %cx
rep; stosl

# C言語で記述している「main」関数を呼び出す
# Jump to C code (should not return)
calll main

# Setup corrupt somehow...
...以下、エラー処理なので省略...



と、ようやく「main」関数の呼び出しです。

2009年6月15日月曜日

setup.ld

今日からLinuxカーネルを読み始めてみます。
バージョン2.6.xが対象です。

まず最初に読んでみる対象は
「arch/x86/boot/setup.ld」
というリンカスクリプトです。

リンク時に参照されるものであって、カーネルではありません。
でもこのファイルでエントリポイント(最初に実行する箇所)を指定しているので
最初に読んでみるファイルとしては相応しいと考えました。
/*~*/がワタクシのコメントです。

===
/* 出力形式の指定 i386を指定している */
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)

/* エントリポイント。最初に実行するのは「_start」が指すアドレス */
ENTRY(_start)

/* 以下、セクションの定義 */
SECTIONS
{
/* アドレス0からbstext,bsdataセクション */
. = 0;
.bstext : { *(.bstext) }
.bsdata : { *(.bsdata) }

/* アドレス497からはheader, inittext, initdata, textセクション */
. = 497;
.header : { *(.header) }
.inittext : { *(.inittext) }
.initdata : { *(.initdata) }
.text : { *(.text*) }

/* 16byteアラインして、rodate, videocards,セクション */
. = ALIGN(16);
.rodata : { *(.rodata*) }

.videocards : {
video_cards = .;
*(.videocards)
video_cards_end = .;
}

/* 16byteアラインして、data, signatureセクション */
. = ALIGN(16);
.data : { *(.data*) }

.signature : {
setup_sig = .;
LONG(0x5a5aaa55)
}

/* 16byteアラインして、bssセクション */
. = ALIGN(16);
.bss :
{
__bss_start = .;
*(.bss)
__bss_end = .;
}
. = ALIGN(16);
_end = .;

/DISCARD/ : { *(.note*) }

. = ASSERT(_end <= 0x8000, "Setup too big!"); . = ASSERT(hdr == 0x1f1, "The setup header has the wrong offset!"); }

===
エントリポイントには「_start」を指定しているので、プログラム実行時に
最初に実行するのが「_start」が指すアドレスになります。
「_start」については次回見てみましょう。

アセンブラソースを見ていると
.section ".bsdata", "a"
こんな記述を見かけますが、これらの定義が上記リンカスクリプトにあります。
上述の例だと、bsdataセクションを指定しているので、アドレス0からの位置に
配置されるのだと分かります。
===
リンカスクリプトについては下記サイトを参考にしています。
初めてのC言語-第9回 リンカスクリプト
GNU リンカ LD の使い方