Linuxのコンピュータがどのように動作しているかを深く理解しますか?

11314 ワード

SA 12226242施健情報セキュリティ

リード


Linuxを深く理解する前に、コンピュータがどのように動作しているかを理解する必要があります.Exampleのcコードを用いるそれぞれ生成する.cpp,.s,.oとELFはファイルを実行し、実行、分析をロードすることができる.sアセンブリコードのCPUでの実行手順.

一、C言語のコンパイルプロセス


1.1 C言語のコンパイルプロセス
単一ファイルのプログラムであるため,リンクのプロセスは省略する.詳しくは『プログラマーの自己修養』2.1節で隠された過程を参照[1]
1.2ソースファイルc
 1 // example.c 2  3 int g(int x) 4 { 5     return x + 3; 6 } 7   8 int f(int x) 9 {10     return g(x);11 }12 13 int main()14 {15     return f(8) + 1;16 }

1.3前処理
前処理は主にマクロ命令とinclude命令を処理する.コマンドgcc-E-o exampleを使用します.cpp example.c.
 1 # 1 "example.c" 2 # 1 "<built-in>" 3 # 1 "<command-line>" 4 # 1 "example.c" 5  6  7 int g(int x) 8 { 9     return x + 3;10 }11 12 int f(int x)13 {14     return g(x);15 }16 17 int main()18 {19     return f(8) + 1;20 }

1.4アセンブリコードへのコンパイル
コンパイルのプロセスは、一連の文法分析、文法分析、意味分析、最適化を使用して、対応するアセンブリコードを生成します.コマンドgcc-x cpp-output-S-o exampleを使用します.s example.cpp
 1     .file    "example.c" 2     .text 3 .globl g 4     .type    g, @function 5 g: 6     pushl    %ebp 7     movl    %esp, %ebp 8     movl    8(%ebp), %eax 9     addl    $3, %eax10     popl    %ebp11     ret12     .size    g, .-g13 .globl f14     .type    f, @function15 f:16     pushl    %ebp17     movl    %esp, %ebp18     subl    $4, %esp19     movl    8(%ebp), %eax20     movl    %eax, (%esp)21     call    g22     leave23     ret24     .size    f, .-f25 .globl main26     .type    main, @function27 main:28     leal    4(%esp), %ecx29     andl    $-16, %esp30     pushl    -4(%ecx)31     pushl    %ebp32     movl    %esp, %ebp33     pushl    %ecx34     subl    $4, %esp35     movl    $8, (%esp)36     call    f37     addl    $1, %eax38     addl    $4, %esp39     popl    %ecx40     popl    %ebp41     leal    -4(%ecx), %esp42     ret43     .size    main, .-main44     .ident    "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"45     .section    .note.GNU-stack,"",@progbits

1.5ターゲットコードへのアセンブリ
アセンブリは、アセンブリコードをマシンが実行できる命令に変換し、各アセンブリ文はほとんど1つのマシン命令に対応します.したがって、コンパイラのアセンブリプロセスはコンパイラに比べて簡単で、複雑な文法も意味もなく、命令の最適化も必要ありません.アセンブリ命令と機械命令の対照表に基づいて一つ一つ翻訳すればいいだけです.「アセンブリ」という名前もここから来ています.[1]
コマンドgcc-x assembler-c exampleを使用します.s -o example.o
1.6リンク
詳しくは『プログラマーの自己修養』2.1.4を参照[1]
 

二、Cプログラムの実行


ここでは,アセンブリされたコードを観察し,C言語の実行中にスタックの変化をシミュレートし,Cプログラムの実行過程を深く理解する.
まず、Cプログラムはmain関数の入り口から実行に入ります.
28~30:
27 main:->  28     leal    4(%esp), %ecx29     andl    $-16, %esp30     pushl    -4(%ecx)

この3つの命令は、espを16バイトで位置合わせし、push espを実行することです.31~32行:
->  31     pushl    %ebp32     movl    %esp, %ebp

31行はebpをスタックに圧し、
32行はespのアドレスをebpに割り当て,区別するためにebp 1で表す.対応するスタックは次のように意図されています.
     +---------------+
       |               |
       +---------------+
ebp1-> |      ebp      | <- esp
       +---------------+
       |               |
 +---------------+

このときmain関数の実行フレームワークが構築されます.
33行:
33行目のコードを見続けます
->  33     pushl    %ecx

ecxをスタックに圧縮し、具体的な原因は一時的に不明であり、スタックは意図を示している.
     +---------------+
       |               |
       +---------------+
ebp1-> |     ebp       | 
       +---------------+
       |     ecx       | <- esp
       +---------------+
       |               | 
     +---------------+

34~36行:
->  34     subl    $4, %esp35     movl    $8, (%esp)36     call    f

34行はespを4バイトのサイズに減らし、4バイトが割り当てられた空間と等価にする.
35行は、espが指すメモリに即時数8を入れます.これは、実際には圧入パラメータです.スタック意図
     +---------------+
       |               |
       +---------------+
ebp1-> |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | <- esp
       +---------------+
       |               | 
     +---------------+

36はマクロ命令callであり、現在のcs:eipの値をスタックトップに押し込むことに等価であり、cs:eipは呼び出された関数のエントリアドレスを指す.[2]
スタックの意図:
     +---------------+
       |               |
       +---------------+
ebp1-> |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | <- esp   +---------------+
       |               | 
     +---------------+

次に実行するのは関数fです.15行から続けてみましょう.
15~17行:
->  15 f:16     pushl    %ebp17     movl    %esp, %ebp

16行ebpスタック
17行はespの内容をebpに割り当て、区別するためにebp 2と命名し、スタックは意図を示す.
     +---------------+
       |               |
       +---------------+
       |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | 
       +---------------+
ebp2-> |     ebp1      | <- esp
       +---------------+
       |               | 
     +---------------+

今、f関数の実行フレームワークが組み立てられました.
18~20行:
->  18     subl    $4, %esp19     movl    8(%ebp), %eax20     movl    %eax, (%esp)

18行は4バイトサイズのスタックスペースを申請しました
19行はebp+8位置のコンテンツをeaxレジスタに格納する.ebp+8の位置は、最初のパラメータの位置です.だから、この言葉は実は伝達パラメータです.
20行はeaxレジスタの内容をスタックトップに格納する.対応するスタックの意図:
     +---------------+
       |               |
       +---------------+
       |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | 
       +---------------+
ebp2-> |     ebp1      | 
       +---------------+
       |       8       | <- esp
       +---------------+
       |               | 
     +---------------+

21行:
->  21     call    g

21行は関数gを呼び出す.callはマクロ命令であり、スタックは意図を示す.
     +---------------+
       |               |
       +---------------+
       |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | 
       +---------------+
ebp2-> |     ebp1      | 
       +---------------+
       |       8       | 
       +---------------+
       |    cs:eip     | <- esp   +---------------+
       |               | 
     +---------------+

関数gを観察すると、アセンブリは5行から10行に対応します.
5~7行:
->  5 g: 6     pushl    %ebp     7     movl    %esp, %ebp

6行、スタックebp
7行、espをebpに割り当て、違いを示すためにebp 3を使用します.
スタックの意図:
     +---------------+
       |               |
       +---------------+
       |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | 
       +---------------+
       |     ebp1      | 
       +---------------+
       |       8       | 
       +---------------+
       |    cs:eip     | 
       +---------------+
ebp3-> |      ebp2     | <- esp
       +---------------+
       |               | 
     +---------------+

関数gの実行環境が構築された.
8~9行:
->   8     movl    8(%ebp), %eax     9     addl    $3, %eax

8行、ebp+8の位置からパラメータを取り出し、eaxレジスタに入れる.
9行、eaxの内容を3増加します.
この場合、スタックには何の変化もありません.
10-11行:
->  10     popl    %ebp11     ret

20行、スタックトップからデータを取り出し、ebpに入れます.対応するスタックは次のように変化します.
     +---------------+
       |               |
       +---------------+
       |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | 
       +---------------+
ebp2-> |     ebp1      | 
       +---------------+
       |       8       | 
       +---------------+
       |    cs:eip     | <- esp
       +---------------+
       |               | 
     +---------------+

11行retはマクロ命令で、スタックの頂上からここに保存されていたcs:eipの値をポップアップし、cs:eipに入れる機能です.[2]
対応するスタックの変化の概略図:
     +---------------+
       |               |
       +---------------+
       |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | 
       +---------------+
ebp2-> |     ebp1      | 
       +---------------+
       |       8       | <- esp
       +---------------+
       |               | 
     +---------------+

このとき,f関数におけるcall命令の次の命令,すなわち22行目に戻った.
22行から23行:
->  22     leave23     ret

22行、leaveはmovl%ebp,%espおよびpopl%ebpに相当するマクロ命令である
実行後、スタックは次のように意図されます.
     +---------------+
       |               |
       +---------------+ebp1-> |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | 
       +---------------+
       |    cs:eip     | <- esp
       +---------------+
       |               | 
     +---------------+

23行、retはeipをmain関数のcallの次の命令に復元します.
スタックの意図:
     +---------------+
       |               |
       +---------------+
ebp1-> |     ebp       | 
       +---------------+
       |     ecx       | 
       +---------------+
       |      8        | <- esp
       +---------------+
       |               | 
     +---------------+

main関数の37行に戻り、実行を続行します.
37~39行:
->  37     addl    $1, %eax38     addl    $4, %esp39     popl    %ecx

37行:eaxの内容に1を加え、eaxは通常戻り値として使用されるため、eaxはfを呼び出した戻り値を格納する.
38行:espを4増加します.これは、呼び出しfを破棄したパラメータスタックです.
39行:ecxレジスタを復元します.まだ分からないが、これは何をしているのか.
スタックの意図:
     +---------------+
       |               |
       +---------------+
ebp1-> |     ebp       | <- esp
       +---------------+
       |               | 
     +---------------+

40~42行:
->  40     popl    %ebp
    41     leal    -4(%ecx), %esp
    42     ret

40行:スタックトップコンテンツをebpレジスタにスタックします.
41行:ecx-4のコンテンツをespにスタックし、直前に保存したespコンテンツをスタックします.
42行:eipをある場所に復元して実行を続行します.終わります.
ここで,C言語のプログラム実行呼び出しについて大まかな理解を用いた.では、コンピューターはどのように働いているのでしょうか.
 

三.コンピュータはどのように動作しますか


以上の解析から,コンピュータの動作過程は実際には命令を取る工程であることが分かった.基本モデルは次のとおりです.
for (  read_next_intruction();
  execute_intruction();

コンピュータでは、単純に線形に実行されるわけではありません.また、cpuはeipが指すメモリ位置から1つの命令を取り出すたびにeipレジスタを修正することによって実現されるジャンプ命令もある.
単一タスクでは、このモデルは問題ありません.もしマルチタスクがあったらどうしますか?
マルチタスクでは,割り込みの概念を導入した.割り込み信号は、プロセッサが通常の制御ストリーム以外のコードを実行するように特別な方法を提供する.割り込み信号が到着すると、CPUは現在やっていることを停止し、新しいアクティビティに切り替える必要があります.[3]
これにより、上のモデルを下のように変更することができます.
for (;;) {
    read_next_intruction();
    execute_intruction();
    detect_interrupt();   
}

割り込みの概念はコンピュータシステムにおいて非常に重要であり、例えば、IO割り込み、タイムスライス割り込み、システム呼び出し割り込みなどである.
このようにして、私たちはコンピュータがどのように働いているのかを基礎的に理解しました.
次回は、Linuxカーネルを自分でコンパイルしてみます.引き続き注目してください.

参考資料:


[1]プログラマーの自己修養
[2]Linuxオペレーティングシステムの分析に必要な基礎知識.ppt by孟寧
[3]Linuxカーネルの第3版を深く理解する