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に戻る日はいつなのだろう。。