my cognition

書きたいことを書きます。

「入門 モダンLinux」 ”1章 Linuxの入門”の各節で重要と考えたこと、及びCPUアーキテクチャの差異についての調査。

会社で「入門 モダンLinux」の輪読会が発生しているので、読んだ中で重要であることを書く。

www.oreilly.co.jp

1章 Linuxの入門

    1.1 モダンな環境とは何か?
    1.2 これまでのLinuxの歴史
    1.3 なぜオペレーティングシステムなのか?
    1.4 Linuxディストリビューション
    1.5 リソースの可視性
    1.6 Linuxの全体像
    1.7 まとめ

各節で重要なこと

  • 1.1 モダンな環境とは、古典的なPCとは異なり、モバイルデバイスをはじめ、Linuxが動くような全ての環境。
  • 1.2 (省略)
  • 1.3 カーネルはハードウェアを抽象化し、APIを提供するもの。つまり、具体的には、syscallを提供できる状態にするもの。LinuxカーネルはOSである。
  • 1.4 Linuxディストリビューションは、カーネル、パッケージ+パッケージマネージャー、ファイルシステムブートローダー、その他ユーザーソフトウェアをまとめたものを指す。
  • 1.5 ”リソースとはソフトウェア実行の支援のために使用できるすべてのもの”。”Linuxでは「すべてがファイルである」”のは、これらのリソースにアクセスできるということ。同一マシン上でも、OSの制御によって、リソースの見え方は異なること。
  • 1.6 仮想化やコンテナ化といった高度な技術は、Linuxのベーシックな機能のもとに成り立っていること。
  • 1.7 (省略)

何故OSはCPUアーキテクチャの相違をカバーできないのか

OSの概念を読んだ後は、「Linuxがハードウェアの差異を吸収するのであれば、何故 x86(x86_64) と ARM(aarch64) のCPUのアーキテクチャの差異をカバーできないのか」という疑問が生じる。

私の結論は、以下の2行に集約される。

  • OSは、実行ファイル、つまりバイナリによって構成されている
  • CPUに実行させたい命令の塊であるバイナリは、CPUアーキテクチャと1対1の関係にあるため、抽象化できない※

当然といえば当然の話であるが、知識としては知っていたものの、確認が不足していた部分なので、今回検証してみた。

※の点は、後述のQEMUなどの技術によって吸収できるという反論はあるかもしれない。しかしそれは、ある命令セットAで実行できるように、命令セットBのバイナリを命令セットAとして翻訳した結果であるので、該当しない。

命令セットの差異の確認

命令セットに差異があるということは、バイナリが異なるということである。 機械語に翻訳する前のアセンブラの差異を見ることによって、人間にもわかるような差異が発生する。

ソースコードは、ハローワールドを実行する hello.c を以下とする。尚、命令が異なることさえわかればいいので、動的リンクを採用し、アセンブラのコード量を抑えている

$ cat hello.c
#include <stdio.h>
int main(){
  printf("hello world");
}

x86_64

$ cc -S -O0 -march=x86-64 hello.c 
$ cat hello.s 
    .file "hello.c"
    .text
    .section  .rodata
.LC0:
    .string   "hello world"
    .text
    .globl    main
    .type main, @function
main:
.LFB0:
    .cfi_startproc
    endbr64
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq .LC0(%rip), %rax
    movq %rax, %rdi
    movl $0, %eax
    call printf@PLT
    movl $0, %eax
    popq %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size main, .-main
    .ident    "GCC: (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0"
    .section  .note.GNU-stack,"",@progbits
    .section  .note.gnu.property,"a"
    .align 8
    .long 1f - 0f
    .long 4f - 1f
    .long 5
0:
    .string   "GNU"
1:
    .align 8
    .long 0xc0000002
    .long 3f - 2f
2:
    .long 0x3
3:
    .align 8
4:

aarch64

$ aarch64-linux-gnu-gcc -S -O0 hello.c 
$ cat hello.s
    .arch armv8-a
    .file "hello.c"
    .text
    .section  .rodata
    .align    3
.LC0:
    .string   "hello world"
    .text
    .align    2
    .global   main
    .type main, %function
main:
.LFB0:
    .cfi_startproc
    stp  x29, x30, [sp, -16]!
    .cfi_def_cfa_offset 16
    .cfi_offset 29, -16
    .cfi_offset 30, -8
    mov  x29, sp
    adrp x0, .LC0
    add  x0, x0, :lo12:.LC0
    bl   printf
    mov  w0, 0
    ldp  x29, x30, [sp], 16
    .cfi_restore 30
    .cfi_restore 29
    .cfi_def_cfa_offset 0
    ret
    .cfi_endproc
.LFE0:
    .size main, .-main
    .ident    "GCC: (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0"
    .section  .note.GNU-stack,"",@progbits

参考文献

命令セット - Wikipedia
AArch64 - Wikipedia
RISC-V - Wikipedia
x86_64環境上でのARM64組み込みバイナリのクロスコンパイルに関するメモ - satumaimo_10の備忘録
x86, Arm, PowerPC のアセンブリコードを簡単に生成・比較する - Qiita
x86_64のUbuntuでC/C++のソースコードをARM/ARM64用にクロスコンパイルしてQEMUで実行する方法のまとめ - Qiita 11日目: [x86] アーキテクチャとバイナリ解析最速マスター - しかくいさんかく