Open hysryt opened 5 years ago
システムコールには以下のようなものがある。
・プロセスの生成、削除(fork
, clone
, kill
)
・メモリの確保、開放(brk
)
・プロセス間通信(pipe
)
・ネットワーク(socket
)
・ファイルシステム操作
・ファイル操作
システムコールが発行されると CPU に割り込みイベントが発生する。これによって CPU はユーザーモードからカーネルモードに切り替わり、カーネルの処理を開始する。カーネルでの処理が終了すると再びユーザーモードに切り替わり、プロセスの処理を継続する。
システムコールを介さずにユーザーモードからカーネルモードに切り替える方法はない。
strace
コマンドを使うことでプログラムが内部で行うシステムコールをトレースすることができる。
$ strace -o hello.log ./hello
$ cat hello.log
execve("./hello", ["./hello"], 0x7fffb1c95b30 /* 21 vars */) = 0
brk(NULL) = 0x55bbcf5e4000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/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
fstat(3, {st_mode=S_IFREG|0644, st_size=25586, ...}) = 0
mmap(NULL, 25586, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa90876b000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-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>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa908769000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa90815a000
mprotect(0x7fa908341000, 2097152, PROT_NONE) = 0
mmap(0x7fa908541000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fa908541000
mmap(0x7fa908547000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa908547000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7fa90876a4c0) = 0
mprotect(0x7fa908541000, 16384, PROT_READ) = 0
mprotect(0x55bbcdf5a000, 4096, PROT_READ) = 0
mprotect(0x7fa908772000, 4096, PROT_READ) = 0
munmap(0x7fa90876b000, 25586) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
brk(NULL) = 0x55bbcf5e4000
brk(0x55bbcf605000) = 0x55bbcf605000
write(1, "hello world!\n", 13) = 13
exit_group(0) = ?
+++ exited with 0 +++
プログラムの開始処理と終了処理だけでも多くのシステムコールが発行されることがわかる。
-T
オプションでシステムコールにかかった時間、-tt
オプションでシステムコールの開始時刻を表示できる。
$ strace -o hello.log -T ./hello
hello world!
$ cat hello.log
execve("./hello", ["./hello"], 0x7ffc0903edb8 /* 21 vars */) = 0 <0.000310>
brk(NULL) = 0x5601e8c3e000 <0.000060>
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000066>
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000063>
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000065>
fstat(3, {st_mode=S_IFREG|0644, st_size=25586, ...}) = 0 <0.000059>
mmap(NULL, 25586, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0f61895000 <0.000062>
close(3) = 0 <0.000058>
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000062>
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000065>
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832 <0.000060>
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0 <0.000065>
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f61893000 <0.000130>
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0f61284000 <0.000065>
mprotect(0x7f0f6146b000, 2097152, PROT_NONE) = 0 <0.000066>
mmap(0x7f0f6166b000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f0f6166b000 <0.000067>
mmap(0x7f0f61671000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0f61671000 <0.000068>
close(3) = 0 <0.000059>
arch_prctl(ARCH_SET_FS, 0x7f0f618944c0) = 0 <0.000058>
mprotect(0x7f0f6166b000, 16384, PROT_READ) = 0 <0.000064>
mprotect(0x5601e783f000, 4096, PROT_READ) = 0 <0.000063>
mprotect(0x7f0f6189c000, 4096, PROT_READ) = 0 <0.000066>
munmap(0x7f0f61895000, 25586) = 0 <0.000068>
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 <0.000060>
brk(NULL) = 0x5601e8c3e000 <0.000058>
brk(0x5601e8c5f000) = 0x5601e8c5f000 <0.000060>
write(1, "hello world!\n", 13) = 13 <0.000301>
exit_group(0) = ?
+++ exited with 0 +++
<>
の中が処理にかかった時間(秒)
$ strace -o hello.log -tt ./hello
hello world!
$ cat hello.log
12:24:45.677458 execve("./hello", ["./hello"], 0x7ffe9e340e08 /* 21 vars */) = 0
12:24:45.677810 brk(NULL) = 0x55e213ae9000
12:24:45.677881 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
12:24:45.677937 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
12:24:45.677982 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
12:24:45.678027 fstat(3, {st_mode=S_IFREG|0644, st_size=25586, ...}) = 0
12:24:45.678068 mmap(NULL, 25586, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6aeaa0f000
12:24:45.678107 close(3) = 0
12:24:45.678144 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
12:24:45.678188 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
12:24:45.678232 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832
12:24:45.678273 fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
12:24:45.678311 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6aeaa0d000
12:24:45.678353 mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f6aea3fe000
12:24:45.678393 mprotect(0x7f6aea5e5000, 2097152, PROT_NONE) = 0
12:24:45.678462 mmap(0x7f6aea7e5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f6aea7e5000
12:24:45.678583 mmap(0x7f6aea7eb000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f6aea7eb000
12:24:45.678635 close(3) = 0
12:24:45.678686 arch_prctl(ARCH_SET_FS, 0x7f6aeaa0e4c0) = 0
12:24:45.678775 mprotect(0x7f6aea7e5000, 16384, PROT_READ) = 0
12:24:45.678821 mprotect(0x55e213024000, 4096, PROT_READ) = 0
12:24:45.678863 mprotect(0x7f6aeaa16000, 4096, PROT_READ) = 0
12:24:45.678904 munmap(0x7f6aeaa0f000, 25586) = 0
12:24:45.679008 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
12:24:45.679083 brk(NULL) = 0x55e213ae9000
12:24:45.679118 brk(0x55e213b0a000) = 0x55e213b0a000
12:24:45.679160 write(1, "hello world!\n", 13) = 13
12:24:45.679996 exit_group(0) = ?
12:24:45.680168 +++ exited with 0 +++
sar
コマンドを使うことで CPU のユーザーモードとカーネルモードの処理の割合を確認できる。
$ sar -P ALL 1
Linux 4.15.0-33-generic (ubuntu-bionic) 02/24/19 _x86_64_ (2 CPU)
12:28:19 CPU %user %nice %system %iowait %steal %idle
12:28:20 all 0.00 0.00 0.50 0.00 0.00 99.50
12:28:20 0 0.00 0.00 0.00 0.00 0.00 100.00
12:28:20 1 0.00 0.00 0.00 0.00 0.00 100.00
12:28:20 CPU %user %nice %system %iowait %steal %idle
12:28:21 all 0.00 0.00 0.50 0.00 0.00 99.50
12:28:21 0 0.00 0.00 0.99 0.00 0.00 99.01
12:28:21 1 0.00 0.00 0.00 0.00 0.00 100.00
Average: CPU %user %nice %system %iowait %steal %idle
Average: all 0.00 0.00 0.50 0.00 0.00 99.50
Average: 0 0.00 0.00 0.50 0.00 0.00 99.50
Average: 1 0.00 0.00 0.00 0.00 0.00 100.00
%user と %nice がユーザーモード、%system がカーネルモードの処理の割合となっている。
%system が数十のような大きい値になっている場合はシステムの負荷が高すぎることが多い。
システムコールはアーキテクチャに依存したアセンブリコードからのみ発行することができ、C言語のような高級言語から直接発行することはできない。
しかしアプリケーション開発者が毎回アセンブリを書くわけには行かないため、各システムコールを呼び出すだけのラッパー関数が、アーキテクチャごとに用意されている。
Linux 場合、標準 C ライブラリおよびラッパー関数として多くは glibc が使用される。
プロセスを生成する目的
プロセスの生成には fork()
を使用する。
fork()
はプロセスを複製する。
fork()
をした瞬間からプロセスは枝分かれし、複製元が親プロセス、複製先が子プロセスとなる。
親プロセスでは fork()
の戻り値として子プロセスのプロセスIDを取得し、
子プロセスでは fork()
の戻り値として0を取得する。
fork()
で複製したプロセスは同じプログラムが動作するため、処理を分けたい場合は fork()
の戻り値で分岐させる。
execve()
は別プログラムを起動する。
新規のプロセスを作らずに、現在のプロセスを使用するため、プロセス数は増えない。
別のプログラムを新しいプロセスで実行する場合は、fork()
で子プロセスを作成し、
子プロセスで execve()
を実行する。(Fork-Exec)
メモリマップ開始アドレスがよくわからない ↓ 実行ファイルはメモリの決まった位置に読み込まないといけないということらしい。 厳密には各仮想アドレス空間内の決まった位置ということなので、複数の実行ファイルを同時にメモリに読み込むことができる。
複数プロセスを同時に動作させる(させているように見せる)機能。
1つのCPUで同時に(並行して)動作できるプロセスは1つ。短い時間で順番にプロセスを実行することで複数動作しているように見せる。
マルチコアの場合、1つのコアが1つのCPUとして認識される。コアの数だけプロセスを同時に(並列に)動作させられる。つまりデュアルコアCPUは2つ、クアッドコアCPUは4つのプロセスを同時に動作させることができる。
CPUの数(コアの数)は /proc/cpuinfo
を見ればわかる
$ grep -c processor /proc/cpuinfo
2
同じことを分散してやる場合はマルチコアが有利。 別のことをそれぞれ並行してやる場合はマルチCPUが有利。
指定したプログラムを指定した論理CPU上でのみ動作させる。 内部的に sched_setaffinity というCPUの使用を制限するシステムコールを呼び出している。
$ taskset -c 0 echo a
-c
でプロセッサを指定する。
論理CPU上で動作するプロセスが切り替わること。 プログラムの進行状況に関係なく発生する。
子プロセスのいずれかが終了するまで動作を停止する。 子プロセスがない場合はすぐに復帰する。 エラーの場合は -1 を返す。
システムのほとんどのプロセスはスリープ状態にあり、なんらかのイベントが発生するのを待っている。
プロセスの状態は ps
コマンドの STAT フィールドの一文字目で確認できる。
$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
vagrant 17480 0.0 0.5 23196 5268 pts/1 Ss 04:40 0:00 -bash
vagrant 17666 0.0 0.3 37792 3420 pts/1 R+ 06:21 0:00 ps u
R が実行中または実行待ち状態、SまたはDがスリープ状態、Zがゾンビ状態。
D は主にストレージデバイスのアクセス待ちであり、数ミリ程度で別の状態に遷移する。長時間Dのままの場合は何らかの不具合が発生している。
CPU 上で動作するプロセスがない場合に動作する特殊なプロセス。特殊な命令を使ってCPUを休止状態にし、他のプロセスが実行可能状態になるまで待機する。
アイドルプロセスが動作している割合は sar
コマンドの %idle で確認できる。
$ sar 1
Linux 4.15.0-46-generic (ubuntu-bionic) 03/16/19 _x86_64_ (2 CPU)
06:26:02 CPU %user %nice %system %iowait %steal %idle
06:26:03 all 0.00 0.00 0.50 0.00 0.00 99.50
単位時間あたりのそう仕事量。高い程よい。
処理の開始から終了までの経過時間。短いほど良い。
複数CPU間でプロセスを公平に分配する機能。 一つのCPUに処理が偏らないようにする。
time
コマンドを使うことでプロセスの実行にかかった経過時間と実際のCPUの使用時間を得ることができる。
$ time curl -O www.google.com/index.html
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 12465 0 12465 0 0 129k 0 --:--:-- --:--:-- --:--:-- 129k
real 0m0.104s
user 0m0.005s
sys 0m0.005s
real が経過時間。 user がCPUのユーザーモードでの使用時間。 sys がCPUのカーネルモードでの使用時間。
user と sys を足すことで CPU の使用時間がわかる。 使用時間はすべての CPU の使用時間の合計が出るため、マルチコアやマルチプロセッサの場合は経過時間より使用時間の方が大きくなることがある。
現在動作しているプロセスの現在までの経過時間と CPU の使用時間を表示できる。
$ ps -eo pid,etime,time
PID ELAPSED TIME
1 02:09:51 00:00:02
2 02:09:51 00:00:00
4 02:09:51 00:00:00
6 02:09:51 00:00:00
(略)
プロセスは -20 から 19 までの間で優先度を設定できる。 数が小さいほど優先度が高い(-20が最も優先度が高い)。 デフォルトは0。 優先度が高いほど多くのCPU時間を得られる。 root 権限を持ったユーザーのみ優先度を上げることができる。 一般ユーザでも下げることはできる。
プロセスの優先度を変更するシステムコール。
プロセスの優先度を変更するコマンド。
-n
オプションで優先度を設定する。
$ nice -n 10 echo 'hello';
%user は優先度が0のプロセスの実行時間の割合。 %nice は優先度が0以外のプロセスの実行時間の割合。
Linux では PC に搭載されている全てのメモリをカーネルが管理している。 カーネル自身が使用するメモリも自分で管理している。
搭載されているメモリの量や、現在使用中のメモリの量などを確認できる。 デフォルトの場合、単位はキロバイト。
$ free
total used free shared buff/cache available
Mem: 1008928 88584 444984 608 475360 774484
Swap: 0 0 0
-r
オプションでメモリに関する情報を取得できる。
$ sar -r
Linux 4.15.0-46-generic (ubuntu-bionic) 03/19/19 _x86_64_ (2 CPU)
10:05:51 LINUX RESTART (2 CPU)
10:15:01 kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
10:25:01 451240 774160 557688 55.28 23668 410000 215460 21.36 332164 140744 136
10:35:01 444480 773896 564448 55.95 24204 415896 216640 21.47 334192 145208 136
10:45:01 444372 773824 564556 55.96 24236 415900 216640 21.47 334224 145212 136
10:55:01 444588 774116 564340 55.93 24272 415920 216056 21.41 334284 145216 136
Average: 446170 773999 562758 55.78 24095 414429 216199 21.43 333716 144095 136
メモリの空きがなくなった時、適当なプロセスを強制終了してメモリ領域を確保するカーネルの機能。 業務サーバーの場合は中途半端にプロセスを終了させるよりシステム全体を終了した方が良いため、この機能をオフにすることもある。
プロセスへのメモリ割り当てもカーネルが行う。 カーネルがメモリ割り当てを行うタイミングは以下の2つ。 ・プロセス生成時 ・プロセス生成後に追加で動的にメモリを割り当てる時
CPU(とMMU?)の機能。 プロセスごとに仮想的に独自のアドレス空間を用意する。 プロセスからみたアドレスを仮想アドレス、メモリの実際のアドレスを物理アドレスという。 仮想アドレスと物理アドレスはページテーブルで紐づけられる。
readelf や cat proc/pic/maps で表示されるアドレスは全て仮想アドレス。
物理アドレスでメモリに直接アクセスする方法はない。 許可されていないアドレスにアクセスした場合、セグメンテーション違反が発生する。
・物理メモリ上で断片化しているメモリ領域を一つの大きな領域として見せることができる。 ・プロセスごとにアドレス空間が独立しているため、他のプロセスのアドレス空間にアクセスさせないようにできる。(実装の都合上、カーネルのメモリ空間は全ての仮想アドレス空間にマッピングされるが、カーネル用のページテーブルエントリはカーネルモードからしかアクセスできないようになっている。) ・マルチプロセスの場合でも、各プロセスが独立したアドレス空間を持つため、干渉を気にしなくていい。
仮想アドレスと物理アドレスの返還にはページテーブルを使用する。 ページテーブルはカーネルが使用するメモリ内に存在する。 仮想記憶ではメモリをページという単位で管理する。 ページテーブル内の1ページ分をページテーブルエントリという。
ページサイズはアーキテクチャによって変わる。 x86_64の場合は4キロバイト。
仮想アドレスから物理アドレスへの変換はカーネルを介さず、CPUが直接ページテーブルを見て行う。
物理アドレスに対応しない仮想アドレスにアクセスすると、CPU上でページフォールトという割り込みが発生する。 ページフォールトが発生するとカーネル上でページフォールトハンドラという処理が動く。 ページフォールトハンドラからプロセスに対しSIGSEGVというシグナルを通知し、それを受け取る処理を書いていないプロセスは強制終了する。
追加割り当て時は以下のようになる。
mmap はページ単位でメモリを確保する。 malloc はバイト単位でメモリを確保する。 glibc が事前に mmap で大きなメモリ領域を確保してプールしておき、malloc で要求された時に必要な分だけ返している。 プールに空きがなくなると再度 mmap でメモリーを要求する。
mmap はもともとファイルやデバイスをメモリにマップするシステムコールらしい https://www.ncaq.net/2018/01/19/10/27/30/
https://ja.wikipedia.org/wiki/メモリマップトファイル
ファイルの内容を仮想アドレス空間にマップする機能。
いろんなOSで実装されている。
Linux では mmap
システムコールによってマップを行う。
マップしたファイルにはメモリアクセスと同じ方法でアクセス(読み書き)できる。
ページ単位でマップするため、小さいファイルをマップするとフラグメンテーションによる無駄が生じやすくなってしまう。
プロセスローダーの実行ファイルの読み込みや、複数プロセス間でのメモリ共有にも使用されているらしい。
必要になってから物理メモリ領域を確保する仕組み。
mmap
などを呼び出した際、まずは仮想メモリ領域のみ確保し、物理メモリ領域は確保しない。
その後、仮想メモリ領域のアドレスにはじめてアクセスしたタイミングで物理メモリ領域をページ単位で確保する。
これによってメモリを無駄に確保するのを防ぐ。
流れとしては以下の通り。
プロセスはページフォールトの発生に気づくことなくプロセスを継続できる。
プロセスを fork する際、ユーザからはメモリ領域をすべてコピーしているように見えるが、実際にはページテーブルのみコピーし、メモリ領域は親子プロセスで共有している。 その後、どちらかのプロセスがメモリに書き込みを行ったタイミングでそのページのメモリ領域を複製する。
共有しているメモリへの書き込みを検出するために、ページテーブルのコピー時にメモリへの書き込み権限をなくしておく。 そうすることで書き込み時にページフォールトが発生し、ページフォールトハンドラにてコピーオンライトの処理をはさむことが出来る。
流れとしては以下の通り。
プロセスはページフォールトの発生に気づくことなくプロセスを継続できる。
ストレージデバイス(HDDなど)の一部をメモリとして使用する仕組み。 物理メモリが枯渇した際、空きメモリを作り出すために物理メモリのうちの一部をストレージデバイスに退避させる。 退避させるためのストレージデバイスの領域を「スワップ領域」という。(Windowsでは仮想メモリと呼んでいる。)
メモリからスワップ領域へ退避させることを「スワップアウト」、 スワップ領域からメモリへ復帰させることを「スワップイン」という。 どの領域をスワップアウトするかはカーネルが判断する。 スワップアウトやスワップインをすることを「スワッピング」いう。 Linux ではページ単位でスワッピングするため、「ページング」ともいう。
物理メモリが少ないためにスワッピングが常に起こっている状態を「スラッシング」という。
swapon --show
コマンドでスワップ領域を確認できる。
free
コマンドでもスワップ領域の容量を確認できる。
システム稼動中は sar -W
コマンドでスワッピングしているページ数を確認できる。
sar -S
ではスワッピングしている容量を確認できる。
ストレージデバイスへのアクセスが発生するページフォールト
ストレージデバイスへのアクセスが発生しないページフォールト
実際のページテーブルは階層的に実装されている。 これによってページテーブルサイズを節約できる。
Linux の機能。 通常のページよりサイズの大きいページ。 ページテーブルはページごとにエントリを作成するため、これによってページテーブルサイズを抑えることができる。
mmap
関数の引数に MAP_HUGETLB フラグを与えることでヒュージページを獲得する。
仮想アドレス空間内の連続する複数の4Kバイトページが所定の条件を満たしたときに自動的にヒュージページとする機能。 場合によっては性能が劣化することがあるため、無効化することもある。
上のものほど速度が速いが、価格が高く、容量が小さい。
つまりレジスタがいくら速くてもメモリの速度がボトルネックになる。 メモリの速度に律速するという。
メモリとレジスタの間を仲介し、所要時間の差を埋める記憶媒体。SRAM。 通常はCPUに内蔵されるが、外についているものもある。
キャッシュメモリは階層構造をとっており、複数のキャッシュメモリが存在する。 レジスタに近いキャッシュメモリほど速度が速いが、容量が少ない。 一番レジスタに近いキャッシュメモリを「L1キャッシュ(レベル1キャッシュ)」といい、レジスタから遠ざかるにつれて数字が大きくなっていく。 キャッシュメモリの数はCPUによって異なる。
メモリからレジスタにデータを読み出す際、キャッシュメモリにもデータを読み出す。
メモリ → キャッシュメモリ → レジスタ
読み出しは「キャッシュライン」という単位で行う。
一方、レジスタの値を書きかえた際はキャッシュメモリにのみかき戻す。
レジスタ → キャッシュメモリ
この時もキャッシュライン単位で書き込みを行う。 キャッシュメモリが書きかえられ、メモリと異なるデータを持っている状態を「ダーティ」という。
ダーティなキャッシュラインは所定のタイミングでメモリに書き戻す。 それに伴ってダーティなキャッシュラインはダーティではなくなる。
キャッシュメモリがいっぱいの状態でキャッシュに存在しないデータにアクセスした場合は、キャッシュラインのどれかを破棄する。 対象のキャッシュラインがダーティな場合、メモリに書き戻してから破棄する。
キャッシュメモリの情報は以下のディレクトリのファイルで確認できる。 以下はCPU0のL1キャッシュの例
$ ls /sys/devices/system/cpu/cpu0/cache/index0
coherency_line_size physical_line_partition size
id power type
level shared_cpu_list uevent
number_of_sets shared_cpu_map ways_of_associativity
たとえば size
ファイルをみるとそのキャッシュメモリのサイズを取得できる。
$ cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K
ページテーブルにある仮想アドレスと物理アドレスの変換情報(ページテーブルエントリ)をキャッシュする。 CPUに内蔵されており、キャッシュメモリと同様に高速にアクセスできる。
ストレージデバイス(HDDなど)のデータを主記憶(メモリ)にキャッシュする機能。 キャッシングはページ単位で行う。 ページキャッシュ用の領域はカーネル用のメモリ領域内に存在する。
ページキャッシュはすべてのプロセスで共有するため、別々のプロセスが同じファイルを読み込む際でも高速化が見込める。
キャッシュメモリと同様に、ページキャッシュに読み込んだあとはデータの読み書きはページキャッシュに対してのみ行い、所定のタイミングでストレージデバイスに書き戻す。 ページキャッシュに対して書き込みが行われ、ストレージデバイスのデータとの間に差異があるぺーじをダーティページと呼ぶ。
メモリに空きがある限り、ページキャッシュのサイズは増え続ける。 メモリが足りなくなるとページキャッシュを解放する。 まずダーティでないページキャッシュから解放していき、ダーティなページキャッシュだけになったらライトバックしたのち解放する。
ページキャッシュに書き込む際、ストレージデバイスにも同時に書き込む機能。
突然のシャットダウンなどでまだストレージデバイスに書き込まれてないデータが消えるのを防ぐ。
open
システムコールを呼び出す際、O_SYNC
フラグを設定することで使用できる。
都度ストレージデバイスに書き込むため、速度は低下する。
CPUの空費状態を有効活用する仕組み。 CPUの処理速度に比べるとレジスタへのアクセス速度は遅いため、そのようなタイミングでCPUの空費状態が発生している。
https://wa3.i-3-i.info/word12754.html http://e-words.jp/w/ハイパースレッディング.html https://www.intel.co.jp/content/www/jp/ja/architecture-and-technology/hyper-threading/hyper-threading-technology.html コアを複数個あるように見せかけ、並列処理を行う。 複数個の論理コアでキャッシュメモリを共有したり、命令の種類によっては並列化できないため、最良でも20%~30%程度の速度向上とのこと。
ストレージデバイス(HDD、SSDなど)にはファイルシステムを介してアクセスする。
ファイルシステムにはいくつか種類があるが、どのファイルシステムであってもアクセス時のシステムコールは一緒。
処理 | システムコール |
---|---|
ファイルの作成、削除 | creat 、 unlink |
ファイルを開く、閉じる | open 、 close |
開いたファイルからデータを読み出す | read |
開いたファイルにデータを書き込む | write |
開いたファイルの所定の位置に移動 | lseek |
ファイルシステム依存の特殊な処理 | ioctl |
システムコール発行時の処理は以下の通り
ファイルシステムによっては容量制限を課すことができる。 この機能をクォータという。
クォータの種類 | 説明 |
---|---|
ユーザクォータ | ユーザごとに容量を制限する。 |
ディレクトリクォータ | ディレクトリごとに容量を制限する。 |
サブボリュームクォータ | サブボリュームという単位に分けて容量を制限する |
ファイルシステムの更新中、システムの強制終了などでファイルシステムに不整合が生じることがある。この不整合を防ぐためにジャーナリングとコピーライトという方式がある。
ファイルシステム内にジャーナル領域という特殊な領域を用意する。 まずジャーナル領域に更新処理の処理一覧を書き出す。 その後、実際に処理を行う。 ジャーナル領域に書き出し中にシステムが強制終了した場合、次回起動時にジャーナル領域の内容を捨てる。 実際の処理の途中でシステムが強制終了した場合、次回起動時にジャーナル領域の処理一覧を最初から全てやり直す。 こうすることで不整合が起こらないようにする。
コピーオンライト方式のファイルシステムはファイルを更新するとき既存のものを上書きするのではなく、新しい領域に作り直す。 更新が終了したら新しいものにリンクを貼り直す。 こうすることで不整合が起こらないようにする。
Linuxはほぼすべてのデバイスをファイルとして表現する。これをデバイスファイルという。(ネットワークアダプタはデバイスに対応するファイルはない。)
デバイスファイルにも通常のファイルと同じように open
や read
などのシステムコールでアクセスする。複雑な操作には ioctl
システムコールを使用する。
デバイスファイルにはキャラクタデバイスとブロックデバイスの2種類がある。ls -l
コマンドを実行したとき、行の先頭文字が c
のものはキャラクタデバイス、 b
のものはブロックデバイスとなっている。
デバイスファイルには通常、 root のみアクセスできる。
読み書きはできるが、シークはできない。 端末、キーボード、マウスなど。
読み書きだけでなく、シークによるランダムアクセスが可能。 ストレージデバイス(HDD、SDD)など。 ブロックデバイスはファイルシステムを作成し、それをマウントすることによってファイルシステム経由でアクセスする。
ブロックデバイスを直接操作するのは次のような場合。
メモリ上に作成するファイルシステム。
電源を切ると消えるが、高速にアクセスできる。
free
コマンドの shared で表される。
リモートホスト上のファイルにアクセスするファイルシステム。 Windows ホスト上のファイルにアクセスするときには cifs 、 Linux などの UNIX 系 OS を使ったホスト上のファイルにアクセスするときは nfs というファイルシステムを使用する。
システムやプロセスについての情報を得るためのファイルシステム。
通常は /proc
にマウントされる。
procfs が複雑になったため作られたファイルシステム。デバイスやファイルシステムに関する情報を取得できる。
cgroup を操作するためのファイルシステム。
マルチボリューム。 複数のストレージデバイスやパーティションから一つのストレージプールを作成し、その上にマウント可能なサブボリュームを作成する。
サブボリューム単位でスナップショットを採取できる。
ファイルシステムレベルでRAIDを組める。
第1章
プロセス
プログラムの単位。 多くのOSは複数のプロセスを実行できる。
CPU
CPUにはカーネルモードとユーザーモードがある。 通常のプロセスはユーザーモードで動作する。 カーネルモードで動作するものには以下のものがある。
デバイスドライバ
デバイスを操作するためのプログラム。 プロセスからはデバイスドライバを介してデバイスを操作する。 デバイスドライバはカーネルモードで動作する。
カーネル
カーネルモードで動作するプログラムをまとめたもの。 OSはカーネルにユーザーモードで動作するプログラムを付け加えたもの。 カーネルはCPUやメモリを管理し、リソースを各プロセスに割り振る。
システムコール
ユーザーモードからカーネルへはシステムコールを介して処理を依頼する。