start_kernel
4.2 では、start_kernel
init/main.c
から かなりの初期化プロセスであり、 main
と比較できます 関数。
これは最初に実行されるアーキテクチャに依存しないコードであり、カーネルの大部分をセットアップします。 main
のように 、 start_kernel
いくつかの下位レベルのセットアップ コードが先行します (crt*
で行われます)。 ユーザーランド main
のオブジェクト )、その後、「メイン」の汎用 C コードが実行されます。
どのように start_kernel
x86_64 で呼び出される
arch/x86/kernel/vmlinux.lds.S
、リンカー スクリプト、セット:
ENTRY(phys_startup_64)
そして
phys_startup_64 = startup_64 - LOAD_OFFSET;
そして:
#define LOAD_OFFSET __START_KERNEL_map
arch/x86/include/asm/page_64_types.h
__START_KERNEL_map
を定義 として:
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)
これはカーネルエントリアドレスです。 TODO そのアドレスに正確に到達するにはどうすればよいですか? Linux がブートローダーに公開するインターフェースを理解する必要があります。
arch/x86/kernel/vmlinux.lds.S
最初のブートローダ セクションを次のように設定します:
.text : AT(ADDR(.text) - LOAD_OFFSET) {
_text = .;
/* bootstrapping code */
HEAD_TEXT
include/asm-generic/vmlinux.lds.h
HEAD_TEXT
を定義 :
#define HEAD_TEXT *(.head.text)
arch/x86/kernel/head_64.S
startup_64
を定義 .これは、実行される最初の x86 カーネル コードです。 たくさん セグメンテーションとページングを含む低レベルのセットアップの。
ファイルが次で始まるため、これが最初に実行されます:
.text
__HEAD
.code64
.globl startup_64
と include/linux/init.h
__HEAD
を定義 として:
#define __HEAD .section ".head.text","ax"
リンカー スクリプトの最初の部分と同じです。
最後に x86_64_start_kernel
を呼び出します lretq
と少しぎこちない :
movq initial_code(%rip),%rax
pushq $0 # fake return address to stop unwinder
pushq $__KERNEL_CS # set correct cs
pushq %rax # target address in negative space
lretq
そして:
.balign 8
GLOBAL(initial_code)
.quad x86_64_start_kernel
arch/x86/kernel/head64.c
x86_64_start_kernel
を定義 x86_64_start_reservations
を呼び出す start_kernel
を呼び出す .
arm64 エントリ ポイント
v5.7 非圧縮カーネルで実行される最初の arm64 は、https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L72 で定義されているため、add x13, x18, #0x16
または b stext
CONFIG_EFI
による :
__HEAD
_head:
/*
* DO NOT MODIFY. Image header expected by Linux boot-loaders.
*/
#ifdef CONFIG_EFI
/*
* This add instruction has no meaningful effect except that
* its opcode forms the magic "MZ" signature required by UEFI.
*/
add x13, x18, #0x16
b stext
#else
b stext // branch to kernel start, magic
.long 0 // reserved
#endif
le64sym _kernel_offset_le // Image load offset from start of RAM, little-endian
le64sym _kernel_size_le // Effective size of kernel image, little-endian
le64sym _kernel_flags_le // Informative flags, little-endian
.quad 0 // reserved
.quad 0 // reserved
.quad 0 // reserved
.ascii ARM64_IMAGE_MAGIC // Magic number
#ifdef CONFIG_EFI
.long pe_header - _head // Offset to the PE header.
これは、圧縮されていないカーネル イメージの最初のバイトでもあります。
どちらの場合も stext
にジャンプします これにより、「実際の」アクションが開始されます。
コメントで述べたように、これら 2 つの命令は、https://github.com/cirosantilli/linux/blob/v5.7/Documentation/arm64/booting.rst#4-call で説明されている文書化されたヘッダーの最初の 64 バイトです。 -カーネルイメージ
arm64 の最初の MMU 対応命令:__primary_switched
__primary_switched
だと思います 頭の中.S:
/*
* The following fragment of code is executed with the MMU enabled.
*
* x0 = __PHYS_OFFSET
*/
__primary_switched:
この時点で、カーネルはページ テーブルを作成しているように見えますが、PC アドレスが vmlinux ELF ファイルのシンボルと一致するように自身を再配置する可能性があります。したがって、この時点で特別な魔法を使わなくても、GDB で意味のある関数名を確認できるはずです。
arm64 セカンダリ CPU エントリ ポイント
secondary_holding_pen
定義:https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691
エントリ手順について詳しくは、https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691
をご覧ください。
main()
で あなたはおそらく main()
を意味しています はプログラム、つまりその「エントリ ポイント」です。
init_module()
のモジュールの場合 .
Linux デバイス ドライバーの第 2 版から:
<ブロック引用>アプリケーションが最初から最後まで単一のタスクを実行するのに対して、モジュールは将来の要求に対応するために自身を登録し、その「メイン」機能はすぐに終了します。つまり、関数 init_module (モジュールのエントリ ポイント) のタスクは、後でモジュールの関数を呼び出す準備をすることです。モジュールが「私はここにいる、これが私にできることだ」と言っているようなものです。モジュールの 2 番目のエントリ ポイントである cleanup_module は、モジュールがアンロードされる直前に呼び出されます。カーネルに「私はもうそこにいません。他に何もするように頼まないでください。」と伝える必要があります。
基本的に、main()
という名前のルーチンは特別なことではありません。 .上記のように、main()
実行可能なロード モジュールのエントリ ポイントとして機能します。ただし、ロード モジュールには異なるエントリ ポイントを定義できます。実際、お気に入りの dll を参照するなど、複数のエントリ ポイントを定義できます。
オペレーティング システム (OS) の観点から、実際に必要なのは、デバイス ドライバーとして機能するコードのエントリ ポイントのアドレスだけです。デバイス ドライバーがデバイスへの I/O を実行する必要がある場合、OS はそのエントリ ポイントに制御を渡します。
システム プログラマは、デバイス間の接続、デバイスのドライバとして機能するロード モジュール、およびロード モジュール内のエントリ ポイントの名前を定義します (各 OS には独自の方法があります)。
各 OS には独自のカーネルがあり (明らかに)、一部は main()
で始まる場合があります。 しかし、main()
を使用するカーネルを見つけたら驚くでしょう。 UNIX などの単純なもの以外では!カーネル コードを作成する頃には、作成するすべてのモジュールに main()
という名前を付けるという要件をはるかに超えています。 .
これが役に立てば幸いです?
Unix バージョン 6 のカーネルからこのコード スニペットを見つけました。ご覧のとおり main()
は、開始しようとしている別のプログラムです!
main()
{
extern schar;
register i, *p;
/*
* zero and free all of core
*/
updlock = 0;
i = *ka6 + USIZE;
UISD->r[0] = 077406;
for(;;) {
if(fuibyte(0) < 0) break;
clearsig(i);
maxmem++;
mfree(coremap, 1, i);
i++;
}
if(cputype == 70)
for(i=0; i<62; i=+2) {
UBMAP->r[i] = i<<12;
UBMAP->r[i+1] = 0;
}
// etc. etc. etc.
いくつかの見方:
<オール>
デバイス ドライバはプログラムではありません。これらは、別のプログラム (カーネル) にロードされるモジュールです。そのため、main()
はありません。 関数。
すべてのプログラムに main()
が必要であるという事実 function はユーザー空間アプリケーションにのみ当てはまります。カーネルにもデバイス ドライバーにも適用されません。