2009年10月23日金曜日

trap_init

今回はstart_kernelから「trap_init」を見てみます。
実体は「arch/x86/kernel/trap.c」にあります。
=========
void __init trap_init(void)
{
int i;

~(省略)~

/* トラップゲートを登録 */
set_intr_gate(0, &divide_error);
/* 割り込みゲートを登録 */
set_intr_gate_ist(1, &debug, DEBUG_STACK);
set_intr_gate_ist(2, &nmi, NMI_STACK);
/* ユーザープログラムから直接呼び出せる割り込みゲートを登録 */
set_system_intr_gate_ist(3, &int3, DEBUG_STACK);
~(省略)~
#ifdef CONFIG_X86_32
/* タスクゲートの登録 */
set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);
#else
set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);
#endif
~(省略)~


/* Reserve all the builtin and the syscall vector: */
for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
set_bit(i, used_vectors);

#ifdef CONFIG_X86_64
set_bit(IA32_SYSCALL_VECTOR, used_vectors);
#else
set_bit(SYSCALL_VECTOR, used_vectors);
#endif
/*CPUの初期化。詳しくは次回 */
cpu_init();

#ifdef CONFIG_X86_32
/* 特になにもしない */
trap_init_hook();
#endif
}

=========

主に使っている関数を整理すると以下のようになります。
set_intr_gate:トラップゲートの登録
set_intr_gate_ist:割り込みゲートの登録
set_system_intr_gate_ist:ユーザープログラムから直接呼び出せる割り込みゲートの登録
set_task_gate:タスクゲートの登録

トラップゲートと割り込みゲートは何が違うの?というと割り込みゲートが呼び出された場合は「割り込み禁止」状態になっているという違いです。

2009年10月19日月曜日

page_alloc_init、parse_early_param、parse_args、sort_main_extable

よーやくstart_kernelに戻ってきました。
忘れてしまっているのでもう一度今呼んでいる部分を中心に抜粋。

=========
preempt_disable();
build_all_zonelists();
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
parse_earlpy_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
if (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it\n");
local_irq_disable();
}
sort_main_extable();
trap_init();
rcu_init();

========

前回まででbuild_all_zonelistsを見てきたので今回はpage_alloc_init、
これはCPUのホットプラグをサポートしている場合のみに走る処理。
次のparse_early_paramはコマンドラインで渡された関数を実行するもの。
parse_argsはカーネルに渡すパラメータを設定します。
sort_main_extableは例外テーブルをソートします。

ちょっと駆け足だけれど、今日はここまで。

2009年10月15日木曜日

nr_free_zone_pages

9/25の「build_all_zonelists」から延々と潜り込んできましたが、ようやくbuild_all_zonelistsまで戻って来ました。
次はnr_free_pagecache_pagesを見てみましょう。
実体は同じくmm/page_alloc.cです。

=======
unsigned int nr_free_pagecache_pages(void)
{
return nr_free_zone_pages(gfp_zone(GFP_HIGHUSER_MOVABLE));
}

=======

というわけで「nr_free_zone_pages」を見てみましょう。

=======
static unsigned int nr_free_zone_pages(int offset)
{
struct zoneref *z;
struct zone *zone;

/* Just pick one node, since fallback list is circular */
unsigned int sum = 0;

/* NUMAじゃなければ自ノードのゾーンリストがとれてくる */
struct zonelist *zonelist = node_zonelist(numa_node_id(), GFP_KERNEL);

/* ゾーンリストにゾーンがあるだけ繰り返す */
for_each_zone_zonelist(zone, z, zonelist, offset) {
/* ゾーンの総ページ数 */
unsigned long size = zone->present_pages;
/* 空きページ緊急確保量 */
unsigned long high = zone->pages_high;
if (size > high)
/* 割り当て可能サイズを加算 */
sum += size - high;
}

return sum;
}

=======

このようにして割り当て可能なページ数を算出しています。

2009年10月12日月曜日

build_zonelists_in_zone_orderとbuild_zonlists_node

今回もまた「build_zonelists」の中から呼んでいる「build_zonelists_in_zone_order」を
見てみます。今回も実体は「mm/page_alloc.c」にあります。

========
static void build_zonelists_in_node_order(pg_data_t *pgdat, int node)
{
int j;
struct zonelist *zonelist;

zonelist = &pgdat->node_zonelists[0]

/* ゾーンリストの空きを探す */     
for (j = 0; zonelist->_zonerefs[j].zone != NULL; j++)
;
/* ノードのゾーンリストにゾーンを設定する */
j = build_zonelists_node(NODE_DATA(node), zonelist, j,
MAX_NR_ZONES - 1);
/* ゾーンリストの末尾をNULLに設定 */
zonelist->_zonerefs[j].zone = NULL;
zonelist->_zonerefs[j].zone_idx = 0;
}

========


ついでに「build_zonlists_node」も見ておきましょう。

=========
static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist,
int nr_zones, enum zone_type zone_type)
{
struct zone *zone;

BUG_ON(zone_type >= MAX_NR_ZONES);
zone_type++;

do {
zone_type--;
zone = pgdat->node_zones + zone_type;

/* ゾーンの総ページ数が0でなければ */
if (populated_zone(zone)) {
/* ゾーンリストにゾーンを設定 */
zoneref_set_zone(zone,
&zonelist->_zonerefs[nr_zones++]);
check_highest_zone(zone_type);
}

} while (zone_type);
return nr_zones;
}

=========

こうしてノードごとのゾーンリストにゾーンを格納していっています。
ところで「populated_zone」の定義は以下のようになっています。

==========
static inline int populated_zone(struct zone *zone)
{
return (!!zone->present_pages);
}

==========

なんでまた「!!」なんてしているんでしょうか。
否定の否定なので何もつけないのと同じですが、何か意図があるんでしょうか?

2009年10月9日金曜日

find_next_best_node

前回見ていた「build_zonelists」の中から今回は「find_next_best_node」を見てみます。
ゾーンリストに追加するノードを検索する関数で実体は「mm/page_alloc.c」にあります。

==========
static int find_next_best_node(int node, nodemask_t *used_node_mask)
{
int n, val;
int min_val = INT_MAX;
int best_node = -1;
node_to_cpumask_ptr(tmp, 0);

/* nodeが未使用状態であるならnodeをそのまま返す */
if (!node_isset(node, *used_node_mask)) {
/* nodeが使用中であるというbitをused_node_maskに立ててやる */
node_set(node, *used_node_mask);
return node;
}

/* nodeの数だけ繰り返す */
for_each_node_state(n, N_HIGH_MEMORY) {

/* 既にチェック済みのノードは飛ばす */
if (node_isset(n, *used_node_mask))
continue;

/* nodeとnが同じかどうか */
val = node_distance(node, n);

/* nodeより小さいノードへペナルティをかけてやる */
val += (n < node);

/* 先頭ではなくて未使用のノードを優先する */
node_to_cpumask_ptr_next(tmp, n);
/* CPUを持っているノードにはペナルティ */
if (!cpus_empty(*tmp))
val += PENALTY_FOR_NODE_WITH_CPUS;

/* Slight preference for less loaded node */
val *= (MAX_NODE_LOAD*MAX_NUMNODES);
val += node_load[n];

/* valがこれまでの最小値より小さければ最適ノードを入れ替え *//*
if (val < min_val) {
min_val = val;
best_node = n;
}
}

/* 次の検索にひっかからないようにビットを立ててやる */
if (best_node >= 0)
node_set(best_node, *used_node_mask);

return best_node;
}

==========

ゾーンリストに追加したくない条件についてはペナルティをかけてやって、そのノードが選ばれないようにしています。後半に出てくる「node_load」というのは、前回見たuild_zonelistsで値をセットしていて新たに選ばれたノードが格納されています。
既に選ばれたノードについてはfind_next_best_nodeの「val += node_load[n]」という部分でペナルティがかかるため、ページの割り当てが局所化しないようになっているのです。

2009年10月5日月曜日

__build_all_zonelistsとbuild_zonelists

今回はbuild_all_zonelists関数から呼び出している「__build_all_zonelists」を見てみます。その名の通りすべてのノードのzonelistをビルドするのですが、「ノードをビルドする」とはなんなのか?と。
実体はbuild_all_zonelistsと同じmm/page_alloc.cにあります

========
static int __build_all_zonelists(void *dummy)
{
int nid;

for_each_online_node(nid) {
pg_data_t *pgdat = NODE_DATA(nid);

build_zonelists(pgdat);
build_zonelist_cache(pgdat);
}
return 0;
}

========

ノードリストから一つ一つゾーンリストを取り出してbuild_zonelists、build_zonelist_cacheしています。
「pg_dat_t」というのはノードごとにメモリを管理するための構造体です。
詳しくは「LinuxKernelHackJapan」の解説をどうぞ。

次にここから呼び出している「build_zonelists」を見てみます。
実体は同じファイルにあります。

======
static void build_zonelists(pg_data_t *pgdat)
{
~(変数宣言)~

/* ゾーンリストを初期化する */
for (i = 0; i < MAX_ZONELISTS; i++) {
zonelist = pgdat->node_zonelists + i;
zonelist->_zonerefs[0].zone = NULL;
zonelist->_zonerefs[0].zone_idx = 0;
}


/* used_maskをゼロ埋め */
local_node = pgdat->node_id;
load = num_online_nodes();
prev_node = local_node;
nodes_clear(used_mask);

/* 色々初期化 */
memset(node_load, 0, sizeof(node_load));
memset(node_order, 0, sizeof(node_order));
j = 0;

/* ゾーンリストに追加する最適なノードを探す */
while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {
/* local_nodeとnodeが同じノードを指しているかどうか(違っていれば1) */
int distance = node_distance(local_node, node);

/* 異なるノードで再利用するのに十分に離れたノードであった場合 */
if (distance > RECLAIM_DISTANCE)
zone_reclaim_mode = 1;

/* ノードの割り当てが局所化しないように、注意しながらloadするノードを格納 */
if (distance != node_distance(local_node, prev_node))
node_load[node] = load;

prev_node = node;
load--;
/* ノードの順序に従って格納してやる */
if (order == ZONELIST_ORDER_NODE)
build_zonelists_in_node_order(pgdat, node);
else
node_order[j++] = node; /* remember order */
}

if (order == ZONELIST_ORDER_ZONE) {
/* calculate node order -- i.e., DMA last! */
build_zonelists_in_zone_order(pgdat, j);
}

/* 自ノードのゾーンリストを構築する */
build_thisnode_zonelists(pgdat);
}

======

長かったのだけど、いまいちチンプンカンプンなのだ。。
ページの管理はゾーンという単位でおこなっていて、ノードごとにどのゾーンからページを確保するかという順番を決めるゾーンリストがあります。そのゾーンリストがノードごとにあって、ここではゾーンリストを構築している、という処理になります。
ここから呼び出している関数はまた次回から順に見ていきます。
start_kernelに戻る日はいつなのだろう。。

2009年9月29日火曜日

set_zonelist_orderとdefault_zonelist_order

前回見たbuild_all_zonelists関数から呼び出している関数のうち、今回は最初に呼んでいる
「set_zonelist_order」を見てみます。
実体は同じくmm/page_alloc.cにあります。

========
2298 static void set_zonelist_order(void)
2299 {
2300 if (user_zonelist_order == ZONELIST_ORDER_DEFAULT)
2301 current_zonelist_order = default_zonelist_order();
2302 else
2303 current_zonelist_order = user_zonelist_order;
2304 }
========

となっているので「deaflt_zonelist_order」を見てみます。

========
2244 static int default_zonelist_order(void)
2245 {
2246 ~変数宣言~

2257 low_kmem_size = 0;
2258 total_size = 0;
/* 全ノードのゾーンリストをチェックする */
2259 for_each_online_node(nid) {
2260 for (zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) {
2261 z = &NODE_DATA(nid)->node_zones[zone_type];
2262 if (populated_zone(z)) {
/* ゾーンタイプがZON_DMAかZONE_DMA32ならlow_kmem_sizeに加算 */
2263 if (zone_type < ZONE_NORMAL)
2264 low_kmem_size += z->present_pages;
/* トータルのサイズを加算 */
2265 total_size += z->present_pages;
2266 }
2267 }
2268 }
/* DMAエリアが無かったり、巨大ならZONELIST_ORDER_NODEでリターン */
2269 if (!low_kmem_size || /* there are no DMA area. */
2270 low_kmem_size > total_size/2) /* DMA/DMA32 is big. */
2271 return ZONELIST_ORDER_NODE;

/* 平均サイズを算出 */
2277 average_size = total_size /
2278 (nodes_weight(node_states[N_HIGH_MEMORY]) + 1);
2279 for_each_online_node(nid) {
2280 low_kmem_size = 0;
2281 total_size = 0;
/* もう一度同じように計算する */
2282 for (zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) {
2283 z = &NODE_DATA(nid)->node_zones[zone_type];
2284 if (populated_zone(z)) {
2285 if (zone_type < ZONE_NORMAL)
2286 low_kmem_size += z->present_pages;
2287 total_size += z->present_pages;
2288 }
2289 }
/* 平均より大きく、全サイズの70%を占めるノードがあるならZONELIST_ORDER_NODEでリターン*/
2290 if (low_kmem_size &&
2291 total_size > average_size && /* ignore small node */
2292 low_kmem_size > total_size * 70/100)
2293 return ZONELIST_ORDER_NODE;
2294 }
/* ここまで来たらZONELIST_ORDER_ZONEでリターン */
2295 return ZONELIST_ORDER_ZONE;
2296 }

=========

ノードリスト中のノードサイズに従ってどのゾーンから空きページを探すかを決定している、、、
のだと思います。まだ自信なし。。
ここで返った値が、先述のset_zonelist_orderの中でcurrent_zonelist_orderに代入されます。

2009年9月25日金曜日

build_all_zonelists

今回見てみるのはbuild_all_zonelistsです。
実体はmm/page_alloc.cにあります。

========
/* コメントやprintkは省略してます */
void build_all_zonelists(void)
{
set_zonelist_order();

if (system_state == SYSTEM_BOOTING) {
/* すべてのノードのzonelistsを構築する */
__build_all_zonelists(NULL);
/* デバッグ用なので無視 */
mminit_verify_zonelist();
/* current->mems_allowedを初期化している。なんだ?? */
cpuset_init_current_mems_allowed();
} else {
/* ここに来たら異常。システムを停止させる */
stop_machine(__build_all_zonelists, NULL, NULL);
}
/* 全ノードの割り当て可能なページ数を取得 */
vm_total_pages = nr_free_pagecache_pages();

/* デバッグ用なので省略 */
}

========

要約するとゾーンリストを構築して、割り当て可能なページ数を「vm_total_pages」に格納する処理になります。
Linuxではページをゾーンという単位で管理しており、ゾーンリストというのはそれらゾーンをリストしたものです。

ゾーンリストについては下記を参考にしました。
Linux Kernel Hack Japan「build_all_zonelists()/linux2.6」


ここから呼び出している関数群についてはまた次回から見ていこうと思います。

2009年9月21日月曜日

preempt_disable

間があいてしまいました。
忘れないうちに今日はpreempt_disableを見てみましょう。

実体はinclude/linux/preempt.hにあります。

======
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
======

inc_preempt_countでプリエンプションのカウンタをインクリメントしています。preempt_enableのときは逆にデクリメントします。その後のbarrierは最適化しないでね、とコンパイラに指示するものです。
do-while文ですが条件が0固定なので1度しか実行されません。敢えてdo-whileにしているのでしょうが、なぜなのでしょう?

と思って調べてみたら解説がありました。
[ruby-dev:11748] Re: do while(0)


なるほど。。。

2009年9月7日月曜日

smp_prepare_boot_cp(パス)

次に来るsmp_prepare_boot_cpはSMP構成のみ有効になるコードなのでパスします。
start_kernelの次の部分を見てみましょう。

=====
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
=====

スケジューラの初期化です。
実体はkernel/sched.cにありますが、大きな関数なのでスケジューラ関係は
別の機会にまとめて見てみようと思います。
なのでその次。

=====
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
build_all_zonelists();
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s¥n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
if (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it¥n");
local_irq_disable();
}
sort_main_extable();
trap_init();
rcu_init();

=====

次回から1つずつ追っていこうと思います。

2009年9月6日日曜日

setup_nr_cpu_ids

今回はsetup_nr_cpu_idsを見てみます。
実体はinit/main.cにあります。

=====
static void __init setup_nr_cpu_ids(void)
{
/* 有効なプロセッサIDを取得 */
nr_cpu_ids = find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1;
}

=====

これだけ!

2009年9月5日土曜日

setup_per_cpu_areas

今回はsetup_per_cpu_areasを見てみます。
実体はinit/main.cにあります。

=====
static void __init setup_per_cpu_areas(void)
{
unsigned long size, i;
char *ptr;

/* CPUの数を取得 */
unsigned long nr_possible_cpus = num_possible_cpus();

/* コピーに必要なサイズをとってくる */
size = ALIGN(PERCPU_ENOUGH_ROOM, PAGE_SIZE);
/* メモリを確保 */
ptr = alloc_bootmem_pages(size * nr_possible_cpus);

/* CPUの数だけ繰り返す */
for_each_possible_cpu(i) {
__per_cpu_offset[i] = ptr - __per_cpu_start;
/* 確保した領域に__per_cpu_startのセクションをコピーする */
memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
ptr += size;
}
}

=====

__per_cpu_startセクションはarch/x86/kernel/vmlinux_32.lds.Sにあります。

2009年9月3日木曜日

setup_command_line

今日はsetup_command_lineを見てみましょう。
実体はstart_kernelと同じinit/main.cにあります。

=====
static void __init setup_command_line(char *command_line)
{
saved_command_line = alloc_bootmem(strlen (boot_command_line)+1);
static_command_line = alloc_bootmem(strlen (command_line)+1);
strcpy (saved_command_line, boot_command_line);
strcpy (static_command_line, command_line);
}

=====

これはブート時のコマンドライン引数を保存しているだけですね。

2009年9月1日火曜日

mm_init_owner

今日はsetup_archを見てみましょう。
と、思ったのだけど非常に大規模なソースなので、ちょっとパス。start_kernelが見終わったら改めて見てみようと思います。実体はarch/x86/kernel/setup.cにあって、BIOSからとってきた情報をいろいろとセットしているみたい。

というわけで、ちょっとスキップして今回はmm_init_ownerを見てみます。
実体はkernel/fork.cにあります。
======
void mm_init_owner(struct mm_struct *mm, struct task_struct *p)
{
mm->owner = p;
}
======

これだけ。
start_kernel内では
mm_init_owner(&init_mm, &init_task);
としているので、init_mm->ownerにinit_taskのアドレスを格納しています。
で、この2つはどこから来たのでしょう。

それぞれarch/x86/kernel/init_task.cに以下のようにあります。
=====
struct mm_struct init_mm = INIT_MM(init_mm);
~(略)~
struct task_struct init_task = INIT_TASK(init_task);
=====

INIT_MM、INIT_TASKはinclude/linux/init_task.hにあります。
=====
#define INIT_MM(name) \
{ \
.mm_rb = RB_ROOT,
.pgd = swapper_pg_dir, \
.mm_users = ATOMIC_INIT(2), \
.mm_count = ATOMIC_INIT(1), \
.mmap_sem = __RWSEM_INITIALIZER(name.mmap_sem), \
.page_table_lock = __SPIN_LOCK_UNLOCKED(name.page_table_lock), \
.mmlist = LIST_HEAD_INIT(name.mmlist), \
.cpu_vm_mask = CPU_MASK_ALL, \
}

~(略)~

#define INIT_TASK(tsk) \
{ \
.state = 0, \
.stack = &init_thread_info, \
.usage = ATOMIC_INIT(2), \
.flags = PF_KTHREAD, \
.lock_depth = -1, \
.prio = MAX_PRIO-20, \
.static_prio = MAX_PRIO-20, \
.normal_prio = MAX_PRIO-20, \
.policy = SCHED_NORMAL, \
.cpus_allowed = CPU_MASK_ALL, \
.mm = NULL, \
.active_mm = &init_mm, \
.se = { \
.group_node = LIST_HEAD_INIT(tsk.se.group_node), \
}, \
.rt = { \
.run_list = LIST_HEAD_INIT(tsk.rt.run_list), \
.time_slice = HZ, \
.nr_cpus_allowed = NR_CPUS, \
}, \
.tasks = LIST_HEAD_INIT(tsk.tasks), \
.ptraced = LIST_HEAD_INIT(tsk.ptraced), \
.ptrace_entry = LIST_HEAD_INIT(tsk.ptrace_entry), \
.real_parent = &tsk, \
.parent = &tsk, \
.children = LIST_HEAD_INIT(tsk.children), \
.sibling = LIST_HEAD_INIT(tsk.sibling), \
.group_leader = &tsk, \
.real_cred = &init_cred, \
.cred = &init_cred, \
.cred_exec_mutex = \
__MUTEX_INITIALIZER(tsk.cred_exec_mutex), \
.comm = "swapper", \
.thread = INIT_THREAD, \
.fs = &init_fs, \
.files = &init_files, \
.signal = &init_signals, \
.sighand = &init_sighand, \
.nsproxy = &init_nsproxy, \
.pending = { \
.list = LIST_HEAD_INIT(tsk.pending.list), \
.signal = {{0}}}, \
.blocked = {{0}}, \
.alloc_lock = __SPIN_LOCK_UNLOCKED(tsk.alloc_lock), \
.journal_info = NULL, \
.cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers), \
.fs_excl = ATOMIC_INIT(0), \
.pi_lock = __SPIN_LOCK_UNLOCKED(tsk.pi_lock), \
.timer_slack_ns = 50000, /* 50 usec default slack */ \
.pids = { \
[PIDTYPE_PID] = INIT_PID_LINK(PIDTYPE_PID), \
[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID), \
[PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \
}, \
.dirties = INIT_PROP_LOCAL_SINGLE(dirties), \
INIT_IDS \
INIT_TRACE_IRQFLAGS \
INIT_LOCKDEP \
}

=====

とinit_mm、init_taskの一部メンバを初期化しています。

2009年8月31日月曜日

page_address_init

今日はpage_address_initを見てみましょう。

実体はmm/highmem.cにあります。

=====
void __init page_address_init(void)
{
int i;

INIT_LIST_HEAD(&page_address_pool);
for (i = 0; i < ARRAY_SIZE(page_address_maps); i++)
list_add(&page_address_maps[i].list, &page_address_pool);
for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
INIT_LIST_HEAD(&page_address_htable[i].lh);
spin_lock_init(&page_address_htable[i].lock);
}
spin_lock_init(&pool_lock);
}

=====

ページアドレスマップのリストとハッシュテーブルを初期化しています。
ページとアドレスの対応付けを管理する変数なのでしょう。

2009年8月28日金曜日

boot_cpu_init

今回はboot_cpu_initを見ていきます。

実体はstart_kernelと同じinit/main.cにあります。

======
static void __init boot_cpu_init(void)
{
/* カレントCPUのプロセッサIDを持ってくる */
int cpu = smp_processor_id();
/* Mark the boot cpu "present", "online" etc for SMP and UP case */
set_cpu_online(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);
}

======

プロセッサIDはどこから拾ってくるのかsmp_processor_idを追ってみたけれど、スタックポインタの前あたりに置いてあるようです。いまいち追いきれていません。。
あとはビットを立てているようで、これはレジスタのbitなのかなと思ったのだけどどうやらカーネル内部で持っている構造体?のメンバのようなのだけど、ちっとも追えません。

ちょっと私には難易度高かったです。

2009年8月26日水曜日

tick_init

お次はtick_initを見てみます。
tickを初期化しているのでしょう。

実体はkernel/time/tick-common.cにあります。
======
/**
* tick_init - initialize the tick control
*
* Register the notifier with the clockevents framework
*/
void __init tick_init(void)
{
clockevents_register_notifier(&tick_notifier);
}

======

時間計測のイベントを受け取るための登録をしています。
clockevents_register_notifierは同じ階層のclockevents.cにあります。

======
/**
* clockevents_register_notifier - register a clock events change listener
*/
int clockevents_register_notifier(struct notifier_block *nb)
{
int ret;

spin_lock(&clockevents_lock);
ret = raw_notifier_chain_register(&clockevents_chain, nb);
spin_unlock(&clockevents_lock);

return ret;
}

======

こうしてイベントを通知してもらうためのリスナーチェインに登録しています。
この辺の仕組みはまた別の機会に、、、

2009年8月24日月曜日

lock_kernel

start_kernelは少し進みます。

======
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
lock_kernel();
tick_init();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE);
printk(linux_banner);
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
setup_command_line(command_line);
unwind_setup();
setup_per_cpu_areas();
setup_nr_cpu_ids();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */

======

今回はlock_kernelを見てみましょう。
実体は「__acquires」という名前でarch/x86/kernel/cpu/mtrr/generic.cにあります。

======
static void prepare_set(void) __acquires(set_atomicity_lock)
{
unsigned long cr0;

/* スピンロックを獲得 */
spin_lock(&set_atomicity_lock);

/* CR0レジストをCD=1、NW=0の状態にする */
cr0 = read_cr0() | X86_CR0_CD;
write_cr0(cr0);
wbinvd();

/* グローバルページ機能をDisableにする */
if ( cpu_has_pge ) {
cr4 = read_cr4();
write_cr4(cr4 & ~X86_CR4_PGE);
}

/* TLBをクリア */
__flush_tlb();

/* MTRRの値を読み取る */
rdmsr(MTRRdefType_MSR, deftype_lo, deftype_hi);

/* MTRRを無効にしてuncachableに設定する */
mtrr_wrmsr(MTRRdefType_MSR, deftype_lo & ~0xcff, deftype_hi);
}

======

まあ、なんか、いろいろとクリアしているみたいです。

2009年8月21日金曜日

early_init_irq_lock_class

前回のつづきで、「early_init_irq_lock_class」を見てみましょう。

実体はkernel/irq/handle.cにあります。

======
void early_init_irq_lock_class(void)
{
int i;

for (i = 0; i < NR_IRQS; i++)
lockdep_set_class(&irq_desc[i].lock, &irq_desc_lock_class);
}
======

ロックの整合性を検証するlockdepのためにロックのマッピング情報を初期化しています。
ここもこれだけ。

early_boot_irqs_off

前回のつづきで、「early_boot_irqs_off」を見てみましょう。

実体はkernel/trace/trace_irqoff.cにあります。

======
void early_boot_irqs_off(void)
{
}
======

空です。。

local_irq_disable

前回からstart_kernelの位置は少し進みます。

======
local_irq_disable();
early_boot_irqs_off();
early_init_irq_lock_class();
======

今日はlocal_irq_disableを見てみましょう。こちらはdefineで切られていて、実際に処理するのは「native_irq_disable」関数です。

include/asm-x86/irqflags.h
=====
static inline void native_irq_disable(void)
{
asm volatile("cli": : :"memory");
}
=====

cliで割り込みを禁止しているだけです。最後の「memory」はコンパイラにメモリの場所を変更することを伝えています。なぜこれが必要なのかは理解できていません。。。

2009年8月20日木曜日

cgroup_init_early

今日も前回のつづきです。

======

/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();
cgroup_init_early();

======

今回はcgroup_init_earlyを見ていきましょう。
実体はkernel/cgroup.cにあります。

======
int __init cgroup_init_early(void)
{
int i;
kref_init(&init_css_set.ref);
kref_get(&init_css_set.ref);
INIT_LIST_HEAD(&init_css_set.cg_links);
INIT_LIST_HEAD(&init_css_set.tasks)
INIT_HLIST_NODE(&init_css_set.hlist);
css_set_count = 1;
init_cgroup_root(&rootnode);
list_add(&rootnode.root_list, &roots);
root_count = 1;
init_task.cgroups = &init_css_set;

init_css_set_link.cg = &init_css_set;
list_add(&init_css_set_link.cgrp_link_list,
&rootnode.top_cgroup.css_sets);
list_add(&init_css_set_link.cg_link_list,
&init_css_set.cg_links);

for (i = 0; i < i =" 0;" ss =" subsys[i];">early_init)
cgroup_init_subsys(ss);
}
return 0;
}

=====

cgroupというのはControl Groupの略でカーネル2.6.25で追加されたリソース管理の仕組みです。以下の記事に詳しい解説があります。

Linux Kernel Watch
http://www.atmarkit.co.jp/flinux/rensai/watch2008/watch05a.html

ここではcgroupを管理するためのツリーを初期化しています。

2009年8月17日月曜日

debug_objects_early_init

前回のつづきです。

======
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();
cgroup_init_early();
======

今回はdebug_objects_early_initを見ていきましょう。
実体はkernel/lib/debugobjects.cにあります。

======
void __init debug_objects_early_init(void)
{
int i;

for (i = 0; i < ODEBUG_HASH_SIZE; i++)
spin_lock_init(&obj_hash[i].lock);

for (i = 0; i < ODEBUG_POOL_SIZE; i++)
hlist_add_head(&obj_static_pool[i].node, &obj_pool);
}
======

debugobjects.cというファイルがどうもデバッグ用オブジェクトを管理するためのインフラを提供するためのもののようで、ここではその初期化をしているようです。初期化せずに使ってますとか、獲得したけど解放していません、といったことを検出するための仕組みのようです。
ちょっと自信ないけど。。。

2009年8月14日金曜日

lockdep_init

解凍したカーネルのジャンプ先をソースや諸々の設定ファイルから読み取るのは断念中なのですが、とにもかくにも解凍しおえたカーネルのジャンプ先はarch/x86/kernel/head_32.Sの「startup_32」になります。※
同名ファイルの同名関数を過去に見ていますが、それとは別物です。この辺りのアセンブラは勉強して再チャレンジということにして、ここではパスします。このstartup_32ではページテーブルに関して設定しています。そしてここからジャンプする先がinit/main.cにある「start_kernel」です。
今後はここを起点にソースを見て行くことにしましょう。

Inside the Linux boot process

「start_kernel」はとても長いので少しずつ見て行きましょう。
======
asmlinkage void __init start_kernel(void)
======

「__init」キーワードは一度実行したらメモリから消すように指示します。「asmlinkage」というキーワードはこれがシステムコールであることをコンパイラに
伝えるためのものです。
詳細は以下が参考になります。

はじめてのカーネルソース
ITpro 第3回 ソース内のシステム・コールを確認する

======
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];

smp_setup_processor_id();
======

ローカル変数宣言のあと、smp_setup_processor_idをコールしますがSMP向けなのでパス。

======
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();
cgroup_init_early();
======

まずはここ「lockdep_init」から。
lockdep_initの実体はkernel/lockdep.cにあります。
=====
void lockdep_init(void)
{
int i;

/*
* Some architectures have their own start_kernel()
* code which calls lockdep_init(), while we also
* call lockdep_init() from the start_kernel() itself,
* and we want to initialize the hashes only once:
*/
if (lockdep_initialized)
return;

for (i = 0; i < CLASSHASH_SIZE; i++)
INIT_LIST_HEAD(classhash_table + i);

for (i = 0; i < CHAINHASH_SIZE; i++)
INIT_LIST_HEAD(chainhash_table + i);

lockdep_initialized = 1;
}

=====

ハッシュテーブルを初期化しているようですが、このハッシュテーブルは何者でしょうか。調べてみるとlockdepというのはカーネル内のロックが正しく使われているか実行時に検証する仕組みのようです。カーネルソースに含まれているDocumentation/lockdep-design.txtに詳細があります。
※JFではまだ日本語化されていないようです

2009年8月10日月曜日

decompress_kernel

前回、decompress_kernelをコールして圧縮されたカーネルを解凍するところまで見ました。
decompress_kernelで解凍してもらっている圧縮された部分を探してみよう、というのが
今回の予定でした。
が、、、調べてみてもどうにも理解できず。
力不足です。

今後のためにどこまで分かって何が分からないかをメモしておきます。

decompress_kernelの入力として渡している解凍元は以下のように指定されています。
====
leal input_data(%ebx), %eax
====

input_dataを探してみると、多分以下の部分に当たると思います。

arch/x86/boot/compressed/vmlinux.scr
====
SECTIONS
{
.rodata.compressed : {
input_len = .;
LONG(input_data_end - input_data) input_data = .;
*(.data)
output_len = . - 4;
input_data_end = .;
}
}
====

そこでリンク時にこのvmlinux.scrを参照しているのはどこだろうかとMakefileを
調べてみます。

arch/x86/boot/compressed/Makefile
====
$(obj)/piggy.o: $(obj)/vmlinux.scr $(obj)/vmlinux.bin.gz FORCE
$(call if_changed,ld)
====

piggy.oというのがinput_dataに相当するのかな、と想像。
この元になっているvmlinux.bin.gzの出所を順々に調べてみます。

====
$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE
$(call if_changed,gzip)
====
$(obj)/vmlinux.bin: vmlinux FORCE
$(call if_changed,objcopy)
====
$(obj)/vmlinux: $(src)/vmlinux_$(BITS).lds $(obj)/head_$(BITS).o $(obj)/misc.o $(obj)/piggy.o FORCE
$(call if_changed,ld)
====

また、piggy.oに帰ってきてしまった。。
想像に想像を重ねたような推測で進めているので、途中の想定で大きな方向間違いがあるのかもしれません。
この部分についてはまたいずれ、出直してきます。。。

2009年8月3日月曜日

relocated

前回のつづきでhead_32.Sのrelocatedラベルの処理を見てみましょう。

arch/x86/boot/compressed/head_32.S
=============
.section ".text"
relocated:

/*
* BSSセクションをクリア
*/
xorl %eax,%eax
leal _edata(%ebx),%edi
leal _end(%ebx), %ecx
subl %edi,%ecx
cld
rep
stosb

/*
* デコンプレッサ用にスタックをセット
*/
leal boot_stack_end(%ebx), %esp

/*
* パラメータをセットして、decompress_kernel関数をコールする
*/
movl output_len(%ebx), %eax
pushl %eax
pushl %ebp # output address
movl input_len(%ebx), %eax
pushl %eax # input_len
leal input_data(%ebx), %eax
pushl %eax # input_data
leal boot_heap(%ebx), %eax
pushl %eax # heap area as third argument
pushl %esi # real mode pointer as second arg
call decompress_kernel
addl $20, %esp
popl %ecx

=============
ここでやっているのはまずBSSセクションをクリアしてスタックをセットした後、decomoress_kernel関数を呼び出すというものです。
decompress_kernelコール前には解凍先アドレス、解凍元アドレス、サイズを引数として渡すためにスタックに積んでいます。

2009年7月30日木曜日

startup_32

プロテクトモードに設定するとアドレス0x100000へジャンプしています。ここがプロテクトモードにおけるカーネルのエントリポイントとなります。プロテクトモードにおけるカーネルはいわばカーネル本体なのであって、ここは圧縮された状態で読み込まれます。ソースのパスとしては「arch/x86/boot/compressed」以下のソースに当たります。ここのリンカスクリプトを見てみると(ここでは32bitマシンを想定して「vmlinux_32.lds」を見てみます)、エントリポイントとして「startup_32」を指定していることが分かります。
それではこのstartup_32を見てみましょう。

arch/x86/boot/compressed/head_32.S
=============
startup_32:
cld /* 割り込み禁止 */
/* KEEP_SEGMENTSフラグが立っていたらセグメントを書き換えない */
testb $(1<<6), BP_loadflags(%esi)
jnz 1f

cli /* 割り込み許可 */

/* __BOOT_DSでセグメントレジスタを更新する */
movl $(__BOOT_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
movl %eax,%ss
1:

/* スタックポインタを再計算。よく分かりません */
leal (0x1e4+4)(%esi), %esp
call 1f

1: popl %ebp
subl $1b, %ebp

/* EBPにはロードされた場所が入っている。EBXには圧縮したカーネルイメージを
 安全に解凍すべきアドレスを入れてやる。*
#ifdef CONFIG_RELOCATABLE
movl %ebp, %ebx
addl $(CONFIG_PHYSICAL_ALIGN - 1), %ebx
andl $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebx
#else
movl $LOAD_PHYSICAL_ADDR, %ebx
#endif

/* 圧縮データサイズと解凍したデータサイズを入れ替える */
/* あとはアライメントやら何やらの設定。よく分かりません */
subl input_len(%ebp), %ebx
movl output_len(%ebp), %eax
addl %eax, %ebx
/* Add 8 bytes for every 32K input block */
shrl $12, %eax
addl %eax, %ebx
/* Add 32K + 18 bytes of extra slack */
addl $(32768 + 18), %ebx
/* Align on a 4K boundary */
addl $4095, %ebx
andl $~4095, %ebx

/* 解凍したカーネルをコピーする */
pushl %esi
leal _end(%ebp), %esi
leal _end(%ebx), %edi
movl $(_end - startup_32), %ecx
std
rep
movsb
cld
popl %esi

/* カーネルのスタートアドレスを算出してEBPへ格納 */
#ifdef CONFIG_RELOCATABLE
addl $(CONFIG_PHYSICAL_ALIGN - 1), %ebp
andl $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebp
#else
movl $LOAD_PHYSICAL_ADDR, %ebp
#endif

/*
* カーネルを再配置する
*/
leal relocated(%ebx), %eax
jmp *%eax

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

ブートしていた頃のアセンブラよりも複雑でなかなか読み進められません。思いっきり要約してしまうと、セグメントレジスタを初期化して、圧縮されているカーネルの展開先アドレスを
決めて、最終的にジャンプしています。最後のジャンプ先は「leal relocated(%ebx), %ebx」となっていて、これは「EBX=relocated + EBX」ということ。そしてその「relocated」というのはすぐ下にあります。
こちらは次回。

2009年7月23日木曜日

protected_mode_jump

go_to_protected_mode関数で最後に呼んでいるのは「protected_mode_jump」です。
こちらはアセンブラになります。

arch/x86/boot/pmjump.S
=====
protected_mode_jump:
movl %edx, %esi # ブートパラメータ構造体のアドレスをESIにセット

xorl %ebx, %ebx #EBXをクリア
movw %cs, %bx  #CSの値をBXへ
shll $4, %ebx   #EBXの値を4ビット左シフト
addl %ebx, 2f   #EBXの値にラベル「2」のアドレスを加算
jmp 1f # ラベル「1」へジャンプ
1:

movw $__BOOT_DS, %cx #CXに__BOOT_DSの値(24)をセット
movw $__BOOT_TSS, %di #DIに__BOOT_TSSの値(32)をセット

movl %cr0, %edx #CR0の値をEDXにセット
orb $X86_CR0_PE, %dl # OR演算でプロテクトモードフラグを立てる
movl %edx, %cr0   #更新したEDXの値をCR0にセットする

# Transition to 32-bit mode
.byte 0x66, 0xea # キャッシュをクリアするためジャンプして次へ

#ラベル「2」は値の定義です
2: .long in_pm32 # offset
.word __BOOT_CS # segment

.size protected_mode_jump, .-protected_mode_jump

.code32
.type in_pm32, @function

in_pm32:
# セグメントレジスタをECXの値で初期化
movl %ecx, %ds
movl %ecx, %es
movl %ecx, %fs
movl %ecx, %gs
movl %ecx, %ss
# スタックはEDXで初期化
addl %ebx, %esp

# TRレジスタDIの値をセット
ltr %di

# 汎用レジスタをクリア
xorl %ecx, %ecx
xorl %edx, %edx
xorl %ebx, %ebx
xorl %ebp, %ebp
xorl %edi, %edi

# 割り込みディスクリプタテーブル(ここではダミー)をロード
lldt %cx

 #プロテクトモードでのエントリポイントへジャンプ
jmpl *%eax # Jump to the 32-bit entrypoint

.size in_pm32, .-in_pm32
=====
最後にジャンプしているエントリポイントは「protected_mode_jump」関数の第1パラメータで指定しています。
呼び出しもとの「go_to_protected_mode」を見てみると

protected_mode_jump(boot_params.hdr.code32_start,
    (u32)&boot_params + (ds() << 4));
}


となっています。
ブートパラメータのcode32_startメンバはheader.Sで定義した値を読み込んだものでs。

======
code32_start: # here loaders can put a different
# start address for 32-bit code.
#ifndef __BIG_KERNEL__
.long 0x1000 # 0x1000 = default for zImage
#else
.long 0x100000 # 0x100000 = default for big kernel
#endif

======
となっていて、通常のbzImageであれば0x100000へジャンプしています。
で、それはどこなのだ?というのは次回。

2009年7月19日日曜日

setup_idt/setup_gdt

go_to_protected_mode関数のつづきを見ていきましょう。
まずは割り込みハンドラを登録するためのInterrupt Description Tableをセットするsetup_idtです。

arch/x86/boot/pm.c
=====

static void setup_idt(void)
{
static const struct gdt_ptr null_idt = {0, 0};
asm volatile("lidtl %0" : : "m" (null_idt));
}

=====
ここでは何も定義していないダミーのIDTを用意して、lidtl命令でロードしています。
ちゃんとしたものを後で登録するのだと思います。
次はセグメントを登録するためのGlobal Description Tableをセットするsetup_gdtです。

=====

static void setup_gdt(void)
{
static const u64 boot_gdt[] __attribute__((aligned(16))) = {
/* CS: code, read/execute, 4 GB, base 0 */
[GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
/* DS: data, read/write, 4 GB, base 0 */
[GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
/* TSS: 32-bit tss, 104 bytes, base 4096 */
/* We only have a TSS here to keep Intel VT happy;
we don't actually use it for anything. */
[GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
};
/* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
of the gdt_ptr contents. Thus, make it static so it will
stay in memory, at least long enough that we switch to the
proper kernel GDT. */
static struct gdt_ptr gdt;

gdt.len = sizeof(boot_gdt)-1;
gdt.ptr = (u32)&boot_gdt + (ds() << 4);

asm volatile("lgdtl %0" : : "m" (gdt));
}

=====
最初にGDTを定義して最後にlgdtl命令でGDTをロードしています。
ここで設定しているGDTは以下のような設定になっています。

CS:コードセグメント、読み込み/実行用、0開始で4GB
DS:データセグメント、読み込み/書き込み、0開始で4GB
TSS:4096開始で104バイト。

TSSはタスクスイッチの際に実行中のタスク情報を退避させておく領域です。
ここで設定しているGDTもおそらくIDTと同じように暫定的な設定だと思います。

2009年7月18日土曜日

reset_coprocessor/mask_all_interrupts

前回見たenable_a20関数で無事A20ゲートを有効に出来たらgo_to_protected_mode関数では次にコプロセッサをリセットするために「reset_coprocessor」関数をコールします。

arch/x86/boot/pm.c
======
static void reset_coprocessor(void)
{
outb(0, 0xf0);
io_delay();
outb(0, 0xf1);
io_delay();
}
======
outbを使ってコマンドを出力しています。
これできっとリセットしているんでしょう。。

次に呼んでいるのは「mask_all_interrupts」です。

=====
static void mask_all_interrupts(void)
{
outb(0xff, 0xa1); /* Mask all interrupts on the secondary PIC */
io_delay();
outb(0xfb, 0x21); /* Mask all but cascade on the primary PIC */
io_delay();
}
=====

似たようなコードですが、プライマリPIC、セカンダリPICそれぞれの割り込みに対してマスクをかけて割り込みが通知されないようにしています。


2009年7月17日金曜日

enable_a20

go_to_protected_mode関数にて「realmode_switch_hook」の次に呼んでいるのは「move_kernel_around」ですがzImage用の処理なのでここではパス。その次はプロテクトモードへ移行する本体ともいえる「enable_a20」関数になります。

arch/x86/boot/a20.c
======
int enable_a20(void)
{
/*~省略~*/

int loops = A20_ENABLE_LOOPS;
while (loops--) {
/* 既にA20が有効になっていないかチェックする */
if (a20_test_short())
return 0;

/* BIOSを使って有効にしてみる */
enable_a20_bios();
/* なったかな? */
if (a20_test_short())
return 0;

/* キーボードコントローラを読み取ってチェックしてみる */
empty_8042();
/* なったかな?*/
if (a20_test_short())
return 0; /* BIOS worked, but with delayed reaction */

/* キーボードコントローラから有効にしてみる */
enable_a20_kbc();
/* なったかな?(長めのチェック) */
if (a20_test_long())
return 0;

/* ポート92から自力で有効にしてみる */
enable_a20_fast();
/* なったかな?(長めのチェック) */
if (a20_test_long())
return 0;
}

return -1;
#endif
}

======

このように色な手段を使ってA20ゲートを有効にしています。ちなみに、なぜこんなところでキーボードコントローラが出てくるのかというとA20ゲートがキーボードコントローラとつながっているからです。

2009年7月13日月曜日

go_to_protected_mode

main関数最後の「go_to_protected_mode」関数を見ていきましょう。

arch/x86/boot/pm.c
======
void go_to_protected_mode(void)
{
/* プロテクトモードに移行する前にフック関数を呼んでおく */
realmode_switch_hook();

/* セグメントを設定 */
move_kernel_around();

/* A20ゲートを有効にする */
if (enable_a20()) {
puts("A20 gate not responding, unable to boot...\n");
die();
}

/* コプロセッサをリセット */
reset_coprocessor();

/* PICの割り込み設定をすべてマスクする */
mask_all_interrupts();

/* プロテクトモードへの移行*/
setup_idt();
setup_gdt();
protected_mode_jump(boot_params.hdr.code32_start,
    (u32)&boot_params + (ds() << 4));
}

======

今回からはこの中の関数を順に見ていきます。
まずプロテクトモード移行前にフック関数をコールしておく「realmode_switch_hook」です。
======
static void realmode_switch_hook(void)
{
/* ブートパラメータのヘッダ情報にフック指定あれば、それを呼び出す */
if (boot_params.hdr.realmode_swtch) {
asm volatile("lcallw *%0"
     : : "m" (boot_params.hdr.realmode_swtch)
     : "eax", "ebx", "ecx", "edx");
} else {
/* フック指定なければ割り込みを禁止する */
asm volatile("cli");
outb(0x80, 0x70); /* Disable NMI */
io_delay();
}
}

======
ちなみにブートパラメータのヘッダ情報(header.S)のrealmode_swtchの項は
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
と何も指定していないので、変更しなければelse句に入って割り込み禁止します。

2009年7月7日火曜日

main関数おさらい

長いこと見てきたmain関数ですが、ついに最後の関数「go_to_protected_mode」を残すのみとなりました。
go_to_protected_modeでプロテクトモードに移行します。
その前に、ここで一度main関数をまとめておきましょう。

arch/x86/boot/main.c
======
void main(void)
{
/* ブートパラメータのヘッダ情報を取得する */
copy_boot_params();

/* ヒープの末尾位置をセット */
init_heap();

/* CPU情報を取得してチェック */
if (validate_cpu()) {
~省略~
}
~省略~

/* メモリマップとメモリサイズを取得 */
detect_memory();

/* キーボードのリピートレートをMAXに設定 */
keyboard_set_repeat();

/* ROMテーブルを取得 */
query_mca();

~省略~

/* IST(Intel Speed step Technology)情報を取得 */
query_ist();

/* APM(Advanced Power Management)情報を取得 */
query_apm_bios();

/* EDD(Enhanced Disk Drive)情報を取得 */
query_edd();

/* ビデオカード情報、ディスプレイ情報を取得 */
set_video();

~省略~

/* プロテクトモードへ移行する */
go_to_protected_mode();
}

======

と、ハード関係の情報をブートパラメータ構造体に格納してからプロテクトモードに移行しています。

2009年7月6日月曜日

set_video

今回は「set_video」を見ていきます。

arch/x86/boot/video.c
=====
void set_video(void)
{
  /* ここにはSVGAモードが入る(header.Sより) */
  u16 mode = boot_params.hdr.vid_mode;

  /* ヒープ位置をリセット */
  RESET_HEAP();
  /* ビデオ関連のパラメータセット(後述) */
  store_mode_params();
  /* set_mode_paramsで設定したパラメータを別変数に退避 */
  save_screen();
  /* ビデオカードのプローブ */
  probe_cards(0);

  /*〜省略〜 */

  /* EDIDを読み込みブートパラメータ構造体にセット */
  vesa_store_edid();
  /* ビデオ関連のパラメータセット#2回目 */
  store_mode_params();

  /* 必要ならsave_screen()で退避させた情報を使ってスクリーン情報を復元する */
  if (do_restore)
  restore_screen();
 }
=====

まずboot_params.hdr.vid_modeの値をローカル変数に代入して、このローカル変数を関数内で参照していますが
この値はheader.Sにて定義されています。


そしてその後は各関数をコールしてビデオ関連の情報をブートパラメータにセットします。
まずは「store_mode_params」。

=====
static void store_mode_params(void)
{
  /*〜省略〜*/

  /* 16色テキストモード(80*25)に設定してカーソル位置を */
  /* boot_params.screen_info.orig_x/y に格納 */
  store_cursor_position();
  /* ビデオモードを取得して、                             */
  /* boot_params.screen_info.orig_video_modeと */
  /* boot_params.screen_info.orig_video_page に格納 */
  store_video_mode();

  /* ビデオモードに合わせてビデオセグメントをセット */
  if (boot_params.screen_info.orig_video_mode == 0x07) {
   /* 〜省略〜 */
 }

  /* フォントサイズをセット */
 set_fs(0);
 font_size = rdfs16(0x485); /* Font size, BIOS area */
 boot_params.screen_info.orig_video_points = font_size;

  /* 〜省略〜*/

 /* ビデオの縦横をセット */
  boot_params.screen_info.orig_video_cols  = x;
 boot_params.screen_info.orig_video_lines = y;
}
======
実際にBIOS関数を使ってビデオモードを取得しているのは「store_vide_mode」関数になります。レジスタに値をセットしてソフトウェア割り込みを起こすいつもの手順なので詳細はスキップします。次にビデオカードをプローブする「probe_cards」です。

arch/x86/boot/video-mode.c
======
void probe_cards(int unsafe)
{
  /*〜省略〜 */

  /* .videocardsセクションの情報をチェック */
  for (card = video_cards; card < video_cards_end; card++) {
   /* unsafeの値がパラメータと一致したらプローブ関数をコールする */
   if (card->unsafe == unsafe) {
   if (card->probe)
     card->nmodes = card->probe();
   else
    card->nmodes = 0;
   }
  }
}
======
card_info構造体にプローブ時に呼び出すためのコールバック関数が指定されていればそのプローブ関数をコールして戻り値をnmodesメンバに格納します。プローブ対象の領域はvideocardsセクションで指定された領域になります。

ビデオカードの情報をセットした次は接続しているディスプレイの情報をとりにいきます。

arch/x86/boot/video-vesa.c
======
void vesa_store_edid(void)
{
  /* 〜省略〜 */

  /* EDIDに未対応のバージョンなら何もしない */
  if (vginfo.version < 0x0200)
   return; /* EDID requires VBE 2.0+ */
 
  ax = 0x4f15; /* VBE DDC */
  bx = 0x0000; /* Report DDC capabilities */
  cx = 0; /* Controller 0 */
  di = 0; /* ES:DI must be 0 by spec */

  /*  DDCが使えるか問い合わせ */
  asm("pushw %%es; movw %2,%%es; "INT10"; popw %%es"
    : "+a" (ax), "+b" (bx), "+c" (cx), "+D" (di)
    : : "esi", "edx");

  /* 〜省略〜 */

  /* EDIDを読み込んで、boot_params.edid_infoにセット */
  ax = 0x4f15; /* VBE DDC */
  bx = 0x0001; /* Read EDID */
  cx = 0; /* Controller 0 */
  dx = 0; /* EDID block number */
  di =(size_t) &boot_params.edid_info; /* (ES:)Pointer to block */
  asm(INT10
    : "+a" (ax), "+b" (bx), "+d" (dx), "=m" (boot_params.edid_info),
      "+c" (cx), "+D" (di)
    : : "esi");
}

======
EDIDとは接続しているディスプレイのIDのことです。このIDをもとに解像度を設定します。DDCとはDisplay Data Channelのことでディスプレイの情報を通知する方式です。ここではEDIDをとってきてブートパラメータにセットしているだけです。

2009年7月3日金曜日

GCC拡張インラインアセンブラ

ちょっと今回は番外編。
ここまで見てきたソースの中でよく出くわすのがインラインアセンブラ。基本的にはasm("~アセンブラ~")という記述なのですが、カーネルで出てくるのはGCCの拡張インラインアセンブラといわれるもので、ごく普通のGAS形式のアセンブラともまたちょっと違うので読みにくさが増しています。

そこで参考にしたのが下記のサイトです。
GCC Inline Assembler

例えば前回見た「get_edd_info」には下記のようなインラインアセンブラがあります。


asm("pushfl; stc; int $0x13; setc %%al; popfl"
    : "+a" (ax), "+b" (bx), "=c" (cx), "+d" (dx)
    : : "esi", "edi");



これを整理してみると、、、

(アセンブラコード)
pushfl   フラグ退避
stc    キャリーフラグセット
int 0x13  BIOS interrupt call 
setc AL  キャリーフラグをALに格納
popfl   フラグをポップ

(出力オペランド)
ax 入出力で使う
bx 入出力で使う
cx 出力で使う
dx 入出力で使う

(入力オペランド)
指定なし

(壊れるレジスタ)
esi
edi

関数の先頭でax=0x4100、bx=0x55aa、dx=ドライブ番号と指定しています。この3つはいずれも「入出力」として指定されていることに注目。ax, bx, dxにこれらの値を入れてint 0x13を実行するとdxで指定したドライブのEDD情報を取得する、という命令になります。

query_edd

main関数もようやく終盤、今回は「query_edd」を見ていきます。
EDDというのは「Enhanced Disk Drive」の略で、4GBを超えたメモリアクセスを可能にする仕組みです。

arch/x86/boor/edd.c
======
void query_edd(void)
{
/* ~省略(変数宣言)~*/

/* 起動オプションでeddのON/OFFが指定されているか? */
if (cmdline_find_option("edd", eddarg, sizeof eddarg) > 0) {
/* ~省略(チェック結果で変数更新)~*/
}

/* "quiet"オプションが指定されているか? */
be_quiet = cmdline_find_option_bool("quiet");

edp    = boot_params.eddbuf;
mbrptr = boot_params.edd_mbr_sig_buffer;

/* EDD拡張いらないならおしまい */
if (!do_edd)
return;

/* "quiet"指定されていなければメッセージ出力 */
if (!be_quiet)
printf("Probing EDD (edd=off to disable)... ");

/* デバイスをチェックする */
for (devno = 0x80; devno < 0x80+EDD_MBR_SIG_MAX; devno++) { /* EDD情報を取得 */ if (!get_edd_info(devno, &ei)     && boot_params.eddbuf_entries < EDDMAXNR) { memcpy(edp, &ei, sizeof ei); edp++; /* ここでboot_params.eddbufにEDD情報が格納される */ boot_params.eddbuf_entries++; } if (do_mbr && !read_mbr_sig(devno, &ei, mbrptr++)) boot_params.edd_mbr_sig_buf_entries = devno-0x80+1; } if (!be_quiet) printf("ok\n"); }

======

query_eddの中で呼んでいる「get_edd_info」「read_mbr_sig」を見てみます。
まずは「get_edd_info」から。

======
static int get_edd_info(u8 devno, struct edd_info *ei)
{
/*~省略~*/

/* EDD情報を取得 */
ax = 0x4100;
bx = EDDMAGIC1;
dx = devno;
asm("pushfl; stc; int $0x13; setc %%al; popfl"
    : "+a" (ax), "+b" (bx), "=c" (cx), "+d" (dx)
    : : "esi", "edi");

/*~省略~*/

/* EDDバージョン、機能セットの格納しておく */
ei->device  = devno;
ei->version = ax >> 8; /* EDD version number */
ei->interface_support = cx; /* EDD functionality subsets */

/* 拡張デバイスパラメータを取得してei->paramsに格納する */
ei->params.length = sizeof(ei->params);
ax = 0x4800;
dx = devno;
asm("pushfl; int $0x13; popfl"
    : "+a" (ax), "+d" (dx), "=m" (ei->params)
    : "S" (&ei->params)
    : "ebx", "ecx", "edi");

/* レガシーなCHS(Cylinder, Head, Sector)情報を取得 */
ax = 0x0800;
dx = devno;
di = 0;
asm("pushw %%es; "
    "movw %%di,%%es; "
    "pushfl; stc; int $0x13; setc %%al; popfl; "
    "popw %%es"
    : "+a" (ax), "=b" (bx), "=c" (cx), "+d" (dx), "+D" (di)
    : : "esi");

if ((u8)ax == 0) {
ei->legacy_max_cylinder = (cx >> 8) + ((cx & 0xc0) << 2); ei->legacy_max_head = dx >> 8;
ei->legacy_sectors_per_track = cx & 0x3f;
}

return 0;
}

======

次に「read_mbr_sig」。

======
static u32 read_mbr_sig(u8 devno, struct edd_info *ei, u32 *mbrsig)
{
/*~省略~*/

/* セクタサイズが未設定なら512にしておく */
sector_size = ei->params.bytes_per_sector;
if (!sector_size)
sector_size = 512; /* Best available guess */

/* ヒープ上のアラインをセット */
buf_base = (ds() << 4) + (u32)&_end; mbr_base = (buf_base+sector_size-1) & ~(sector_size-1); mbrbuf_ptr = _end + (mbr_base-buf_base); mbrbuf_end = mbrbuf_ptr + sector_size; /*~省略~*/ /* MBRを読み込む */ if (read_mbr(devno, mbrbuf_ptr)) return -1; *mbrsig = *(u32 *)&mbrbuf_ptr[EDD_MBR_SIG_OFFSET]; return 0; }

======

というわけで、「query_edd」とそのサブ関数を見てきたわけですが中心となっているのは「get_edd_info」でBIOS関数を使ってEDD情報を取ってきてブートパラメータ構造体に格納する、といういつもの流れです。

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

====

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