Linuxカーネルのメモリ管理とメモリの割当て

カーネルのメモリ管理システムの機能によって管理している。

メモリの割当ては
– プロセスの生成時
– プロセス生成後、追加で動的にメモリ割当て(メモリ獲得用のシステムコール発動)

### 仮想メモリ
プロセスから見えるメモリを仮想メモリ
システムに搭載されている実際のアドレスを物理メモリと呼ぶ
仮想アドレスを用いて、間接的にアクセスさせる(プロセスから実際のメモリには直接アクセスしない)
=> 仮想アドレスのページテーブルに、物理アドレスのアドレスが入っている

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = NULL;
    puts("before invalid access");
    *p = 0;
    puts("after invalid access");
    exit(EXIT_SUCCESS);
}

$ ./main
before invalid access
Segmentation fault (core dumped)

### プロセスへのメモリ割当て
仮想アドレスと物理アドレスをマッピングして実行

#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <err.h>

#define BUFFER_SIZE 1000
#define ALLOC_SIZE (100*1024*1024)

static char command[BUFFER_SIZE];

int main(void) {
    pid_t pid;

    pid = getpid();
    snprintf(command, BUFFER_SIZE, "cat /proc/%d/maps", pid);

    puts("*** memory map before memory allocation ***");
    fflush(stdout);
    system(command);

    void *new_memory;
    new_memory = mmap(NULL, ALLOC_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (new_memory == (void *) -1)
        err(EXIT_FAILURE, "mmap() failed");

    puts("");
    printf("*** succeeded to allocate memory: address = %p; size = 0x%x ***\n", new_memory, ALLOC_SIZE);
    puts("");

    puts("*** memory map after memory allocation ***");
    fflush(stdout);
    system(command);

    if(munmap(new_memory, ALLOC_SIZE) == -1)
        err(EXIT_FAILURE, "munmap() failed");
    exit(EXIT_SUCCESS);
}

$ ./main
*** memory map before memory allocation ***
aaaabbfc0000-aaaabbfc1000 r-xp 00000000 fd:00 1125982 /home/vagrant/dev/work/stackmachine/main
aaaabbfd0000-aaaabbfd1000 r–p 00000000 fd:00 1125982 /home/vagrant/dev/work/stackmachine/main
aaaabbfd1000-aaaabbfd2000 rw-p 00001000 fd:00 1125982 /home/vagrant/dev/work/stackmachine/main
aaaac367f000-aaaac36a0000 rw-p 00000000 00:00 0 [heap]
ffff8fed0000-ffff90058000 r-xp 00000000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff90058000-ffff90067000 —p 00188000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff90067000-ffff9006b000 r–p 00187000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff9006b000-ffff9006d000 rw-p 0018b000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff9006d000-ffff90079000 rw-p 00000000 00:00 0
ffff9008c000-ffff900b7000 r-xp 00000000 fd:00 788335 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffff900c1000-ffff900c3000 rw-p 00000000 00:00 0
ffff900c3000-ffff900c5000 r–p 00000000 00:00 0 [vvar]
ffff900c5000-ffff900c6000 r-xp 00000000 00:00 0 [vdso]
ffff900c6000-ffff900c8000 r–p 0002a000 fd:00 788335 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffff900c8000-ffff900ca000 rw-p 0002c000 fd:00 788335 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffd9144000-ffffd9165000 rw-p 00000000 00:00 0 [stack]

*** succeeded to allocate memory: address = 0xffff89ad0000; size = 0x6400000 ***

*** memory map after memory allocation ***
aaaabbfc0000-aaaabbfc1000 r-xp 00000000 fd:00 1125982 /home/vagrant/dev/work/stackmachine/main
aaaabbfd0000-aaaabbfd1000 r–p 00000000 fd:00 1125982 /home/vagrant/dev/work/stackmachine/main
aaaabbfd1000-aaaabbfd2000 rw-p 00001000 fd:00 1125982 /home/vagrant/dev/work/stackmachine/main
aaaac367f000-aaaac36a0000 rw-p 00000000 00:00 0 [heap]
ffff89ad0000-ffff8fed0000 rw-p 00000000 00:00 0
ffff8fed0000-ffff90058000 r-xp 00000000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff90058000-ffff90067000 —p 00188000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff90067000-ffff9006b000 r–p 00187000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff9006b000-ffff9006d000 rw-p 0018b000 fd:00 788338 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff9006d000-ffff90079000 rw-p 00000000 00:00 0
ffff9008c000-ffff900b7000 r-xp 00000000 fd:00 788335 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffff900c1000-ffff900c3000 rw-p 00000000 00:00 0
ffff900c3000-ffff900c5000 r–p 00000000 00:00 0 [vvar]
ffff900c5000-ffff900c6000 r-xp 00000000 00:00 0 [vdso]
ffff900c6000-ffff900c8000 r–p 0002a000 fd:00 788335 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffff900c8000-ffff900ca000 rw-p 0002c000 fd:00 788335 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
ffffd9144000-ffffd9165000 rw-p 00000000 00:00 0 [stack]

execve()関数

実行ファイルは、
– コードを含むデータ領域のファイル上のオフセット、サイズ、メモリマップ開始アドレス
– 最初に実行する命令のメモリアドレス(エントリポイント)
を持っている。

100 オフセット(基準点)
100 サイズ
300 メモリマップ開始アドレス
200 データオフセット
200 データサイズ
400 メモリマップ開始アドレス
400 エントリポイント(エントリポイントからプログラム開始)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <err.h>

static void child(){
    char *args[] = {"/bin/echo", "hello", NULL};
    printf("I'm child! my pid is %d.\n", getpid());
    fflush(stdout);
    execve("/bin/echo", args, NULL);
    err(EXIT_FAILURE, "exec() failed");
}

static void parent(pid_t pid_c) {
    printf("I'm parent! my pid is %d and the pid of my child is %d.\n", getpid(), pid_c);
    exit(EXIT_SUCCESS);
}

int main(void) {
    pid_t ret;
    ret = fork();
    if (ret == -1)
        err(EXIT_FAILURE, "fork() failed");
    if (ret == 0) {
        child();
    } else {
        parent(ret);
    }
    err(EXIT_FAILURE, "shouldn't reach here");
}

$ ./fork
I’m parent! my pid is 314918 and the pid of my child is 314919.
I’m child! my pid is 314919.
hello

スタックマシンの場合はpushとpupだが、この場合はオフセット、サイズ、エントリポイント、メモリ(レジスタ)の番地を指定している。逆に言えば、スタックの場合は順番に気をつけなければならないが、オフセットなどを指定できる場合はより柔軟に対応できるということか。

Linuxのプロセス生成

Linuxのプロセス生成にはfork() ※clone(), execve()の2つのシステムコールがある。

同じプログラムの処理を複数のプロセスに分けるのはfork()
– プロセスを新規作成

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <err.h>

static void child(){
    printf("I'm child! my pid is %d.\n", getpid());
    exit(EXIT_SUCCESS);
}

static void parent(pid_t pid_c) {
    printf("I'm parent! my pid is %d and the pid of my child is %d.\n", getpid(), pid_c);
    exit(EXIT_SUCCESS);
}

int main(void) {
    pid_t ret;
    ret = fork();
    if (ret == -1)
        err(EXIT_FAILURE, "fork() failed");
    if (ret == 0) {
        child();
    } else {
        parent(ret);
    }
    err(EXIT_FAILURE, "shouldn't reach here");
}

$ ./fork
I’m child! my pid is 314564.
I’m parent! my pid is 314563 and the pid of my child is 314564.

システムコールのラッパー関数

システムコールはアーキテクチャ依存のアセンブリコードを使って呼び出す

$ ldd /bin/echo
linux-vdso.so.1 (0x0000ffffb992d000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffb9720000)
/lib/ld-linux-aarch64.so.1 (0x0000ffffb98f4000)

$ ldd /usr/bin/python3
linux-vdso.so.1 (0x0000ffffb5233000)
libm.so.6 => /lib/aarch64-linux-gnu/libm.so.6 (0x0000ffffb4b90000)
libexpat.so.1 => /lib/aarch64-linux-gnu/libexpat.so.1 (0x0000ffffb4b50000)
libz.so.1 => /lib/aarch64-linux-gnu/libz.so.1 (0x0000ffffb4b20000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffb4970000)
/lib/ld-linux-aarch64.so.1 (0x0000ffffb51fa000)

pythonは内部的に標準のclibraryを使っている。libcというのがcライブラリ
OSが提供するプログラムの一例として
init, sysctl, nice, sync, touch, mkidr, grep, sort, uniq, sar, iostat, gcc, perl, python, ruby, bash などがある。

【C】システムコール呼び出しの仕組み

### C言語の場合

#include <stdio.h>

int main(void) {
    puts("hello world");
    return 0;
}

$ strace -o hello.log ./hello
hello world

$ cat hello.log
execve(“./hello”, [“./hello”], 0xffffda929930 /* 42 vars */) = 0
brk(NULL) = 0xaaab00641000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff9126e000
faccessat(AT_FDCWD, “/etc/ld.so.preload”, R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, “/etc/ld.so.cache”, O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, “”, {st_mode=S_IFREG|0644, st_size=48255, …}, AT_EMPTY_PATH) = 0
mmap(NULL, 48255, PROT_READ, MAP_PRIVATE, 3, 0) = 0xffff9122d000
close(3) = 0
openat(AT_FDCWD, “/lib/aarch64-linux-gnu/libc.so.6”, O_RDONLY|O_CLOEXEC) = 3
read(3, “\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\340u\2\0\0\0\0\0″…, 832) = 832
newfstatat(3, “”, {st_mode=S_IFREG|0755, st_size=1637400, …}, AT_EMPTY_PATH) = 0
mmap(NULL, 1805928, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff91074000
mmap(0xffff91080000, 1740392, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff91080000
munmap(0xffff91074000, 49152) = 0
munmap(0xffff91229000, 15976) = 0
mprotect(0xffff91208000, 61440, PROT_NONE) = 0
mmap(0xffff91217000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x187000) = 0xffff91217000
mmap(0xffff9121d000, 48744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff9121d000
close(3) = 0
set_tid_address(0xffff9126ef30) = 313040
set_robust_list(0xffff9126ef40, 24) = 0
rseq(0xffff9126f600, 0x20, 0, 0xd428bc00) = 0
mprotect(0xffff91217000, 16384, PROT_READ) = 0
mprotect(0xaaaac9350000, 4096, PROT_READ) = 0
mprotect(0xffff91273000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0xffff9122d000, 48255) = 0
newfstatat(1, “”, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x3), …}, AT_EMPTY_PATH) = 0
getrandom(“\xbf\x00\x96\xce\x0e\x82\xcd\xa2”, 8, GRND_NONBLOCK) = 8
brk(NULL) = 0xaaab00641000
brk(0xaaab00662000) = 0xaaab00662000
write(1, “hello world\n”, 12) = 12
exit_group(0) = ?
+++ exited with 0 +++

色々はかれていますが、write()がシステムコールです。
write(1, “hello world\n”, 12) = 12

### Pythonの場合

#include <stdio.h>

int main(void) {
    puts("hello world");
    return 0;
}

$ strace -o hello.py python3 ./hello.py

システムコールってよく聞くけど、システムコールの理解が深まった気がする。

Average: CPU %user %nice %system %iowait %steal %idle
Average: all 1.62 0.00 1.69 0.19 0.00 96.50
Average: 0 1.56 0.00 1.79 0.11 0.00 96.54
Average: 1 1.68 0.00 1.60 0.26 0.00 96.46

ユーザモードはuser, niceで systemがカーネルモード、idelは何も動いていない状態です。

Rockey Linux

OSS Linux distribution
RedHat Enterprise Linux8のクローンとして開発されており、CentOS/AlmaLinuxと変更点はない
yumリポジトリが提供、コミュニティ中心の発展
Azureでの配布がない、セキュアブート対応もない
ミラーリポジトリはAlmaLinuxの方が多い

現場ではRockey Linuxの方が採用が多いみたい

Gentoo Linux と Alma Linux

### Gentoo Linux
Gentoo Linuxは上級者向けLinux distribution
Portageと呼ばれるパッケージ管理システム
RPMやdebと異なりソースコードからソフトウェアのインストールを行う
ebuildというスクリプトがあり、これに従ってソースコードのコンパイル、インストールなどを行う
-> 自由度の高いシステム構築を行うことができる。コンパイル時にもに設定できる機能を切り替えができる
カスタマイズ性が高いが、それゆえに難易度が高い。パッケージの理解がないと使いこなすのが難しい

### AlmaLinux
CentOS8.0の後継
yumリポジトリでのアップデート情報が提供されている
RockeyLinuxよりもリリースが早く、クラウド対応なども進んでいる
乗り換えOSとしては有力、開発スピードが速い