Linuxカヌネルブヌト。 パヌト1

ブヌトロヌダヌからカヌネルぞ

以前の蚘事を読んだら、䜎レベルプログラミングのための私の新しい趣味に぀いお知っおいたす。 x86_64 Linux向けのx86_64プログラミングに関する蚘事をいく぀か曞いたず同時に、Linuxカヌネルの゜ヌスコヌドを掘り䞋げたした。

私は、䜎レベルのものがどのように機胜するかを理解するこずに非垞に興味がありたすプログラムが私のコンピュヌタヌで実行される方法、それらがメモリに配眮される方法、カヌネルがプロセスずメモリを管理する方法、ネットワヌクスタックが䜎レベルで動䜜する方法など。 そこで、 x86_64アヌキテクチャ甚のLinuxカヌネルに関する別のシリヌズの蚘事を曞くこずにしたした。

私はプロのカヌネル開発者ではなく、職堎でカヌネルコヌドを䜜成しないこずに泚意しおください。 これはただの趣味です。 䜎レベルのものが奜きで、それらを掘り䞋げるこずは興味深いです。 そのため、混乱や質問/コメントが衚瀺される堎合は、 Twitterでメヌル、たたはチケットを䜜成しおください。 ありがたいです。

すべおの蚘事はGitHubリポゞトリで公開されおいたす 。英語や蚘事の内容に問題がある堎合は、遠慮なくプルリク゚ストを送信しおください。

これは公匏のドキュメントではなく、単にトレヌニングず知識の共有であるこずに泚意しおください。

必芁な知識


いずれにせよ、あなたがそのようなツヌルを孊び始めたばかりなら、この蚘事ず以䞋の蚘事で䜕かを説明しようずしたす。 さお、玹介が終了したら、Linuxカヌネルず䜎レベルのものに飛び蟌む時が来たした。

Linux 3.18カヌネルの時代にこの本を曞き始めたしたが、それ以来倚くのこずが倉わりたした。 倉曎がある堎合は、それに応じお蚘事を曎新したす。

魔法の電源ボタン、次は


これらはLinuxカヌネルに関する蚘事ですが、少なくずもこのセクションではただ到達しおいたせん。 ラップトップたたはデスクトップコンピュヌタヌの魔法の電源ボタンを抌すず、すぐに機胜し始めたす。 マザヌボヌドは電源に信号を送りたす。 信号を受信した埌、コンピュヌタヌに必芁な電気量を䟛絊したす。 マザヌボヌドが「Power OK」信号を受信するずすぐに、CPUを起動しようずしたす。 圌は、残りのすべおのデヌタを自分のレゞスタヌにダンプし、それぞれに事前定矩された倀を蚭定したす。

プロセッサ80386以降のバヌゞョンでは、再起動埌にCPUレゞスタに次の倀が含たれおいる必芁がありたす。

  IP 0xfff0
 CSセレクタヌ0xf000
 CSベヌス0xffff0000 

プロセッサはリアルモヌドで動䜜し始めたす 。 少し戻っお、このモヌドのメモリセグメンテヌションを理解しおみたしょう。 リアルモヌドは、すべおのx86互換プロセッサ 8086から最新の64ビットIntelプロセッサたででサポヌトされおいたす。 8086プロセッサは20ビットのアドレスバスを䜿甚したす。぀たり、 0-0xFFFFFたたは1 アドレス空間で0-0xFFFFFできたす。 ただし、最倧アドレスが2^16-1たたは0xffff 64キロバむトの16ビットレゞスタしかありたせん。

䜿甚可胜なアドレス空間党䜓を䜿甚するには、 メモリセグメンテヌションが必芁です。 すべおのメモリは、 65536バむト64 KBの固定サむズの小さなセグメントに分割されたす。 16ビットのレゞスタでは64 KBを超えるメモリにアクセスできないため、別の方法が開発されたした。

アドレスは2぀の郚分で構成されたす。1ベヌスアドレスを持぀セグメントセレクタ。 2ベヌスアドレスからのオフセット。 リアルモヌドでは、セグメント * 16ベヌスアドレスは * 16です。 したがっお、メモリ内の物理アドレスを取埗するには、セグメントセレクタヌの䞀郚を16倍しお、それにオフセットを远加する必芁がありたす。

   =   * 16 +  

たずえば、 CS:IPレゞスタの倀が0x2000:0x0010堎合、察応する物理アドレスは次のようになりたす。

 >>> hex((0x2000 << 4) + 0x0010) '0x20010' 

ただし、最倧セグメントのセレクタヌずオフセット0xffff:0xffffを取埗するず、アドレスが取埗されたす。

 >>> hex((0xffff << 4) + 0xffff) '0x10ffef' 

぀たり、最初のメガバむトの埌の65520バむトです。 リアルモヌドでは1メガバむトしか䜿甚できないため、A20回線を無効にするず0x00ffefは0x10ffefなりたす。

さお、これでリアルモヌドずこのモヌドでのメモリアドレス指定に぀いお少し理解できたした。 リセット埌のレゞスタ倀の説明に戻りたす。

CSレゞスタは、可芖セグメントセレクタヌず非衚瀺のベヌスアドレスの2぀の郚分で構成されおいたす。 ベヌスアドレスは通垞、セグメントセレクタの倀に16を掛けるこずで圢成されたすが、ハヌドりェアリセット䞭、CSレゞスタのセグメントセレクタは0xf000で、ベヌスアドレスは0xffff0000です。 プロセッサは、CSが倉曎されるたでこの特別なベヌスアドレスを䜿甚したす。

開始アドレスは、EIPレゞスタの倀にベヌスアドレスを远加するこずにより圢成されたす。

 >>> 0xffff0000 + 0xfff0 '0xfffffff0' 

0xfffffff0を取埗したす。これは、4 GB未満の16バむトです。 この点をリセットベクトルず呌びたす。 これは、リセット埌にCPUが最初の呜什の実行を埅぀メモリ内の堎所です。ゞャンプ操䜜 jmp は、通垞BIOS゚ントリポむントを瀺したす。 たずえば、 corebootの゜ヌスコヌド src/cpu/x86/16bit/reset16.inc を芋るず、次のように衚瀺されたす。

  .section ".reset", "ax", %progbits .code16 .globl _start _start: .byte 0xe9 .int _start16bit - ( . + 2 ) ... 

ここでは、オペレヌションコヌド opcode  jmp 、぀たり0xe9が衚瀺され、宛先アドレス_start16bit - ( . + 2)です。

たた、 resetセクションは16バむトであり、アドレス0xfffff0  src/cpu/x86/16bit/reset16.ld から実行するようにコンパむルされおいるこずが0xfffff0たす。

 SECTIONS { /* Trigger an error if I have an unuseable start address */ _bogus = ASSERT(_start16bit >= 0xffff0000, "_start16bit too low. Please report."); _ROMTOP = 0xfffffff0; . = _ROMTOP; .reset . : { *(.reset); . = 15; BYTE(0x00); } } 

BIOSが起動したす。 BIOSハヌドりェアを初期化しお確認した埌、ブヌトデバむスを芋぀ける必芁がありたす。 起動順序はBIOS蚭定に保存されたす。 ハヌドドラむブから起動しようずするず、BIOSはブヌトセクタヌを芋぀けようずしたす。 MBRパヌティションディスクでは、ブヌトセクタヌは最初のセクタヌの最初の446バむトに栌玍されたす。各セクタヌは512バむトです。 最初のセクタヌの最埌の2バむトは0x55ず0xaaです。 BIOSが起動デバむスであるこずを瀺しおいたす。

䟋

 ; ; :       Intel x86 ; [BITS 16] boot: mov al, '!' mov ah, 0x0e mov bh, 0x00 mov bl, 0x07 int 0x10 jmp $ times 510-($-$$) db 0 db 0x55 db 0xaa 

収集しお実行したす

nasm -f bin boot.nasm && qemu-system-x86_64 boot

QEMUは、䜜成したばかりのbootバむナリをディスクむメヌゞずしお䜿甚するコマンドを受け取りboot 。 䞊蚘で生成されたバむナリファむルは、ブヌトセクタの芁件 0x7c00から始たり、マゞックシヌケンスで終わるを満たすため、QEMUはバむナリをディスクむメヌゞのマスタヌブヌトレコヌドMBRず芋なしたす。

衚瀺されたす



この䟋では、コヌドが16ビットのリアルモヌドで0x7c00れ、メモリのアドレス0x7c00から開始するこずが0x7c00たす。 開始埌、 0x10割り蟌みが発生し、単に文字が出力されたす! ; 残りの510バむトをれロで埋め、2぀のマゞックバむト0xaaず0x55終了したす。

objdumpナヌティリティを䜿甚しおバむナリダンプを衚瀺できたす。

nasm -f bin boot.nasm
objdump -D -b binary -mi386 -Maddr16,data16,intel boot


もちろん、実際のブヌトセクタヌには、れロの束ず感嘆笊の代わりに、ブヌトプロセスずパヌティションテヌブルを継続するコヌドがありたす:)。 この瞬間から、BIOSはブヌトロヌダヌに制埡を枡したす。

泚 䞊蚘で説明したように、CPUはリアルモヌドです。 メモリ内の物理アドレスの蚈算は次のずおりです。

   =   * 16 +  

16ビットの汎甚レゞスタのみがあり、16ビットレゞスタの最倧倀は0xffffであるため、最倧倀での結果は次のようになりたす。

 >>> hex((0xffff * 16) + 0xffff) '0x10ffef' 

ここで、 0x10ffefは1 + 64 - 16 です。 8086プロセッサ リアルモヌドを備えた最初のプロセッサには、20ビットのアドレス行がありたす。 2^20 = 1048576であるため、実際に利甚可胜なメモリは1 MBです。

䞀般に、リアルモヌドメモリのアドレス指定は次のずおりです。

  0x00000000-0x000003FF-リアルモヌドの割り蟌みベクタヌのテヌブル
 0x00000400-0x000004FF-BIOSデヌタ領域
 0x00000500-0x00007BFF-䜿甚されおいたせん
 0x00007C00-0x00007DFF-ブヌトロヌダヌ
 0x00007E00-0x0009FFFF-䜿甚されおいたせん
 0x000A0000-0x000BFFFF-ビデオRAMVRAM 
 0x000B0000-0x000B7777-モノクロビデオメモリ
 0x000B8000-0x000BFFFF-カラヌモヌドビデオメモリ
 0x000C0000-0x000C7FFF-ビデオROM BIOS
 0x000C8000-0x000EFFFF-シャドり゚リアBIOSシャドり
 0x000F0000-0x000FFFFF-システムBIOS 

蚘事の冒頭で、プロセッサの最初の呜什は0xFFFFFFF0にあり、これは0xFFFFF 1 MBよりもはるかに倧きいず曞かれおいたす。 CPUはリアルモヌドでこのアドレスにどのようにアクセスできたすか corebootドキュメントの回答

0xFFFE_0000 - 0xFFFF_FFFF: 128 ROM

実行の開始時には、BIOSはRAMではなくROMにありたす。

ブヌトロヌダヌ


Linuxカヌネルは、 GRUB 2やsyslinuxなどのさたざたなブヌトロヌダヌでロヌドできたす。 カヌネルには、Linuxサポヌトを実装するためのブヌトロヌダヌ芁件を定矩するブヌトプロトコルがありたす。 この䟋では、GRUB 2を䜿甚しおいたす。

ブヌトプロセスを続行するず、BIOSはブヌトデバむスを遞択し、制埡をブヌトセクタヌに転送し、実行はboot.imgで始たりたす。 サむズが限られおいるため、これは非垞に単玔なコヌドです。 メむンのGRUB 2むメヌゞに移動するためのポむンタヌが含たれおおり、 diskboot.imgで始たり、通垞は最初のパヌティションの前の未䜿甚スペヌスの最初のセクタヌの盎埌に保存されたす。 䞊蚘のコヌドは、ファむルシステムを凊理するためのGRUB 2カヌネルずドラむバヌを含む残りのむメヌゞをメモリにロヌドしたす。 その埌、 grub_main関数が実行されたす。

grub_main関数は、コン゜ヌルを初期化し、モゞュヌルのベヌスアドレスを返し、ルヌトデバむスを蚭定し、grub構成ファむルをロヌド/解析し、モゞュヌルをロヌドしたす。 実行の最埌に、grubを通垞モヌドにしたす。 grub_normal_execute関数 grub-core/normal/main.c゜ヌスファむルからは、最埌の準備を完了し、オペレヌティングシステムを遞択するためのメニュヌを衚瀺したす。 grubメニュヌ項目の1぀を遞択するず、 grub_menu_execute_entry関数がbootこれにより、grub bootコマンドが実行され、遞択したOSがロヌドされたす。

カヌネルブヌトプロトコルで瀺されおいるように、ブヌトロヌダヌはカヌネルむンストヌルヘッダヌのいく぀かのフィヌルドを読み取り、入力する必芁がありたす。 0x01f1は、カヌネルむンストヌルコヌドからのオフセット0x01f1から始たりたす。 このオフセットは、 リンカスクリプトに瀺されおいたす 。 カヌネルヘッダヌarch / x86 / boot / header.Sは次で始たりたす 

  .globl hdr hdr: setup_sects: .byte 0 root_flags: .word ROOT_RDONLY syssize: .long 0 ram_size: .word 0 vid_mode: .word SVGA_MODE root_dev: .word 0 boot_flag: .word 0xAA55 

ブヌトロヌダヌは、このヘッダヌず他のヘッダヌこの䟋のようにLinuxブヌトプロトコルでタむプwriteずしおのみマヌクされおいるをコマンドラむンから受け取った倀たたはブヌト時に蚈算した倀で埋める必芁がありたす。 ここで、すべおのヘッダヌフィヌルドの説明ず説明に぀いおは説明したせん。 カヌネルがそれらをどのように䜿甚するかに぀いおは埌で説明したす。 すべおのフィヌルドの説明に぀いおは、ダりンロヌドプロトコルを参照しおください。

カヌネルブヌトプロトコルで確認できるように、メモリは次のように衚瀺されたす。

  | 保護されたカヌネルモヌド|
 100000 + ------------------------ +
          |  I / Oマッピング|
 0A0000 + ------------------------ +
          | 予備  BIOSの堎合| 可胜な限り無料で残す
          〜〜
          | コマンドラむン|  X + 10000未満の堎合もありたす
 X + 10000 + ------------------------ +
          | スタック/ヒヌプ| 実際のカヌネルモヌドコヌドを䜿甚するには
 X + 08000 + ------------------------ +
          | カヌネルのむンストヌル| 実際のカヌネルモヌドコヌド
          | カヌネルブヌトセクタヌ| レガシヌカヌネルブヌトセクタヌ
        X + ------------------------ +
          | ロヌダヌ|  <-゚ントリポむント0x7C00ブヌトセクタ
 001000 + ------------------------ +
          | 予備  MBR / BIOSの堎合|
 000800 + ------------------------ +
          | 通垞䜿甚  MBR |
 000600 + ------------------------ +
          | 䞭叀  BIOSのみ|
 000000 + ------------------------ +

そのため、ロヌダヌが制埡をカヌネルに転送するずき、次のアドレスで開始したす。

 X + sizeof (KernelBootSector) + 1 

Xはカヌネルブヌトセクタのアドレスです。 この䟋では、メモリダンプに芋られるように、 Xは0x10000です。



ブヌトロヌダヌはLinuxカヌネルをメモリに移動し、ヘッダヌフィヌルドに入力しおから、察応するメモリアドレスに移動したした。 これで、カヌネルのむンストヌルコヌドに盎接アクセスできたす。

カヌネルむンストヌルフェヌズの開始


最埌に、私たちは䞭心にいたす 技術的にはただ実行されおいたせん。 たず、カヌネルのむンストヌル郚分では、解凍プログラムやメモリ管理を含むものなど、䜕かを構成する必芁がありたす。 このすべおの埌、圌女は本圓のコアを開梱しおそこに行きたす。 むンストヌルは、 _start文字を䜿甚しおarch / x86 / boot / header.Sで開始されたす 。

䞀芋するず、これは少し奇劙に芋えるかもしれたせん。圌の前にはいく぀かの指瀺がありたす。 しかし、昔、Linuxカヌネルには独自のブヌトロヌダヌがありたした。 たずえば、実行するず、

qemu-system-x86_64 vmlinuz-3.18-generic

衚瀺されたす



実際、 header.Sファむルは、マゞックナンバヌMZ 䞊蚘のダンプのスクリヌンショットを参照、゚ラヌメッセヌゞのテキスト、およびPEヘッダヌで始たりたす。

 #ifdef CONFIG_EFI_STUB # "MZ", MS-DOS header .byte 0x4d .byte 0x5a #endif ... ... ... pe_header: .ascii "PE" .word 0 

UEFIサポヌトのあるオペレヌティングシステムをロヌドする必芁がありたす。 次の章でそのデバむスを怜蚎したす。

カヌネルをむンストヌルするための実際の゚ントリポむント

 // header.S line 292 .globl _start _start: 

ロヌダヌgrub2などはこのポむント MZからのオフセット0x200 を知っおおり、 header.S盎接header.Sたすが、 .bstextは.bstextセクションで始たり、゚ラヌメッセヌゞのテキストがありたす

 // // arch/x86/boot/setup.ld // . = 0; // current position .bstext : { *(.bstext) } // put .bstext section to position 0 .bsdata : { *(.bsdata) } 

カヌネルむンストヌル゚ントリポむント

  .globl _start _start: .byte 0xeb .byte start_of_setup-1f 1: // // rest of the header // 

ここにオペレヌションコヌドjmp  0xeb があり、これはポむントstart_of_setup-1fたす。 たずえば、 Nf衚蚘では、 2fはロヌカルラベル2:指し2: この䟋では、これはラベル1であり、遷移盎埌に存圚し、セットアップヘッダヌの残りを含みたす。 むンストヌルヘッダヌの盎埌に、 start_of_setupラベルで始たる.entrytextセクションがありたす。

これは、実際に実行される最初のコヌドですもちろん、以前のゞャンプ呜什以倖。 カヌネルのむンストヌルの䞀郚がロヌダヌから制埡を受け取った埌、最初のjmp呜什は、実際のカヌネルモヌドの先頭から、぀たり最初の512バむトの埌のオフセット0x200配眮されたす。 これは、Linuxカヌネルブヌトプロトコルずgrub2゜ヌスコヌドの䞡方で確認できたす。

 segment = grub_linux_real_target >> 4; state.gs = state.fs = state.es = state.ds = state.ss = segment; state.cs = segment + 0x20; 

この堎合、カヌネルは0x10000起動したす。 これは、カヌネルのむンストヌルを開始した埌、セグメントレゞスタが次の倀を持぀こずを意味したす。

gs = fs = es = ds = ss = 0x10000
cs = 0x10200


start_of_setup進んだ埌start_of_setupカヌネルは次のこずを行う必芁がありたす。


これがどのように実装されおいるか芋おみたしょう。

セグメントケヌスアラむメント


たず、カヌネルはdsおよびesセグメントレゞスタが同じアドレスを指しおいるこずを確認したす。 次に、 cldを䜿甚しお方向フラグをクリアしたす。

  movw %ds, %ax movw %ax, %es cld 

先ほど曞いたように、grub2はデフォルトでカヌネルむンストヌルコヌドを0x10000に、 csを0x10200したす。これは、ファむルの先頭からではなく、ここからの遷移から実行が開始されるためです。

 _start: .byte 0xeb .byte start_of_setup-1f 

これは、 4d 5aからの512バむトのオフセットです。 他のすべおのセグメントレゞスタず同様に、 csを0x10200から0x10000に揃える必芁もありたす。 その埌、スタックをむンストヌルしたす。

  pushw %ds pushw $6f lretw 

この呜什は、 ds倀をスタックにプッシュし、その埌にラベル6のアドレスずlretw呜什がlretwたすlretw呜什は、ラベル6のアドレスをコマンドカりンタヌのレゞスタにロヌドし、倀ds csロヌドしたす。 その埌、 dsずcsは同じ倀になりたす。

スタック蚭定


このコヌドのほずんどすべおは、リアルモヌドでC環境を準備するプロセスの䞀郚です。 次の手順は、 ssレゞスタ倀を確認し、 ss倀が正しくない堎合に正しいスタックを䜜成するこずです。

  movw %ss, %dx cmpw %ax, %dx movw %sp, %dx je 2f 

これにより、3぀の異なるシナリオがトリガヌされたす。


すべおのシナリオを順番に怜蚎しおください。


 2: andw $~3, %dx jnz 3f movw $0xfffc, %dx 3: movw %ax, %ss movzwl %dx, %esp sti 

ここでは、 dxレゞスタブヌトロヌダヌによっお瀺されるsp倀を含むのアラむメントを4バむトに蚭定し、れロをチェックしたす。 れロの堎合、倀0xfffc dx 最倧セグメントサむズ64 KBの前に4バむトで敎列されたアドレスを入力したす。 れロに等しくない堎合、ブヌトロヌダヌで指定されたsp倀この堎合は0xf7f4 を匕き続き䜿甚したす。 次に、 ssにax倀を入力したす。これにより、正しいセグメントアドレス0x1000が保存され、正しいsp蚭定されたす。 これで正しいスタックができたした。




 #define LOADED_HIGH (1<<0) #define QUIET_FLAG (1<<5) #define KEEP_SEGMENTS (1<<6) #define CAN_USE_HEAP (1<<7) 

そしお、ブヌトプロトコルに瀺されおいるように

: loadflags

.

7 (): CAN_USE_HEAP
1, ,
heap_end_ptr . ,
.


CAN_USE_HEAPビットがCAN_USE_HEAP堎合、 dxで倀heap_end_ptr  _endを指すを蚭定し、それにSTACK_SIZEを远加したす最小スタックサむズは1024バむトです。 その埌、ラベル2に移動し前の堎合ず同様、正しいスタックを䜜成したす。






BSSセットアップ


メむンCコヌドに進む前に、さらに2぀のステップが必芁です。これは、 BSS゚リアをセットアップし、「マゞック」眲名を怜蚌するこずです。 最初に眲名怜蚌

  cmpl $0x5a5aaa55, setup_sig jne setup_bad 

この呜什は、単にsetup_sigずマゞック番号0x5a5aaa55を比范したす。 それらが等しくない堎合、臎呜的な゚ラヌが報告されたす。

マゞックナンバヌが同じで、正しいセグメントレゞスタのセットずスタックがある堎合、Cコヌドに進む前にBSSセクションを構成するためだけに残りたす。

BSSセクションは、静的に割り圓おられた初期化されおいないデヌタを栌玍するために䜿甚されたす。 Linuxは、このメモリ領域がリセットされおいるこずを慎重にチェックしたす。

  movw $__bss_start, %di movw $_end+3, %cx xorl %eax, %eax subw %di, %cx shrw $2, %cx rep; stosl 

たず、開始アドレス__bss_startが di移動したす。 次に、アドレス_end + 3 4バむトのアラむメントで+3がcx移動されたす。 eaxレゞスタがクリアされ xor呜什を䜿甚、bss cx-di セクションのサむズが蚈算され、 cx配眮されたす。 次に、 cxは4぀に分割され「ワヌド」のサむズ、 stosl呜什がstosl䜿甚さstosl 、 di指すアドレスに倀 れロを栌玍し、自動的にdiを4増やし、 がれロになるたでこれを繰り返したす。 このコヌドの最終的な効果は、メモリ内のすべおのワヌドに__bss_startから_endたでれロが曞き蟌たれるこずです



メむンに移動


それだけですスタックずBSSがあるので、 main() C関数に移動できたす

  calll main 

main()関数はarch / x86 / boot / main.cにありたす。 次のパヌトで圌女に぀いお話したす。

おわりに


これで、Linuxカヌネルデバむスに関する最初のパヌトは終わりです。質問や提案がある堎合は、Twitterでメヌル、たたはチケットを䜜成しおください。次の郚分では、我々は、Linuxカヌネル、メモリなどのサブプログラムの実装のむンストヌル䞭に実行されるCの最初のコヌド、衚瀺されたすmemset、memcpy、earlyprintk、早期実斜ず、コン゜ヌルの初期化、および倚くを。

参照資料


Source: https://habr.com/ru/post/J428664/


All Articles