Linux勉强メモ
この本を参考に。
ある程度シェルとか扱っていることに慣れていると、これはとてもわかりやすく感じる
Linuxのカーネル、プロセス、ストリーム、ファイルシステム、システムコールなどの理解がざっくりだった為、
gccプログラミングを通じてその辺りの理解を深めたい
環境を用意する
Dockerfileを用意した。
# build
docker build -t bob:v1 .
# Run
docker run -it --rm --mount type=bind,source=`pwd`,target=/app bob:v1 bash
# CentOSのイメージ取得
$ docker pull centos:7
# 名前付けてコンテナ作成
$ docker run -it -d --name linuxpg centos:7
# コンテナの中に入る
$ docker exec -it linuxpg bash
コンテナ入ってgccコマンドが使えるようにする
$ yum update
# 1.2章を参考にしたが、若干コマンド違った。CentOS7系だから?
$ yum groupinstall -y 'Development Tools'
# バージョン確認
$ gcc -dumpversion
4.8.5
manコマンドを使えるようにする
Dockerで立てたCentOSにはmanコマンドが入っていない。
tsflags=nodocs
となっているのでインストールしてくれないっぽい
$ cat /etc/yum.conf
[main]
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
installonly_limit=5
bugtracker_url=http://bugs.centos.org/set_project.php?project_id=23&ref=http://bugs.centos.org/bug_report_page.php?category=yum
distroverpkg=centos-release
override_install_langs=en_US.utf8
tsflags=nodocs
$ yum -y install man-pages
$ yum -y install man
$ manpath
/usr/local/share/man:/usr/share/man
# 再度、設定フラグを付けてインストール
$ yum --setopt=tsflags='' reinstall man-pages
gccでのビルド
Segmentation fault
は、NULLポインタ参照したり、配列の範囲を大きく超えてアクセスしたり。
int main(int argc, char *argv[])
-
int argc
には、コマンドラインに入力した引数の数 -
char *argv[]
には、コマンドラインに入力した引数の値- 注意すべきは、コマンド名や実行したファイルも引数の1つとなる
デバイスファイル
Linuxではデータを保持する物以外もファイルとして扱う。
デバイス(HDD、SSD、キーボード、ディスプレイ)もファイルとして扱う。
→ デバイスファイル
すごい抽象化。
[root@372ea366ea04 hello]# ll /dev/
total 0
crw--w---- 1 root tty 136, 0 Jan 15 12:34 console
lrwxrwxrwx 1 root root 11 Jan 15 12:34 core -> /proc/kcore
lrwxrwxrwx 1 root root 13 Jan 15 12:34 fd -> /proc/self/fd
crw-rw-rw- 1 root root 1, 7 Jan 15 12:34 full
drwxrwxrwt 2 root root 40 Jan 15 12:34 mqueue
crw-rw-rw- 1 root root 1, 3 Jan 15 12:34 null
lrwxrwxrwx 1 root root 8 Jan 15 12:34 ptmx -> pts/ptmx
drwxr-xr-x 2 root root 0 Jan 15 12:34 pts
crw-rw-rw- 1 root root 1, 8 Jan 15 12:34 random
drwxrwxrwt 2 root root 40 Jan 15 12:34 shm
lrwxrwxrwx 1 root root 15 Jan 15 12:34 stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Jan 15 12:34 stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Jan 15 12:34 stdout -> /proc/self/fd/1
crw-rw-rw- 1 root root 5, 0 Jan 15 13:58 tty
crw-rw-rw- 1 root root 1, 9 Jan 15 12:34 urandom
crw-rw-rw- 1 root root 1, 5 Jan 15 12:34 zero
/dev/random
は乱数を生成してくれる。
cat /dev/random をやると、延々とデータが吐き出される。
/dev/urandom
も乱数発生だが、過去の乱数シードを使い回すっぽい?
/dev/null
はデータを捨ててくれるゴミ箱的存在
プロセス
プロセスは、動作中のプログラムのこと。
プログラムは、ファイルのような形態で存在している、データのことも含む言葉
catコマンドはプログラム?
catコマンドを叩いて実行しようとしているのがプロセス?
コマンドプロンプトも、プロセス?
ターミナルを開いて、入力を待ち受けているやつもプロセス。
# 現在のプロセスID確認
$ echo $$
12400
# 起動中プロセスの確認
$ ps auxwef
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 12400 0.0 0.0 11908 1776 pts/1 Ss 01:24 0:00 bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/
root 13129 0.0 0.1 51740 3408 pts/1 R+ 06:54 0:00 \_ ps auxwef HOSTNAME=372ea366ea04 TERM=xterm LS_COLORS=rs=0:di=01
root 1 0.0 0.0 11840 668 pts/0 Ss+ 00:48 0:00 /bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/s
TTY :端末
psコマンドで出たTTY
は、このプロセスがどの端末に繋がっているかを示してる。
TeleTYpe(テレタイプ)の略
ファイルディスクリプタ
ファイルディスクリプタは、プロセスの接続先を示している(一番初めに挙げた本だと、ストリームと言っている)
各プロセスにファイルディスクリプタが設定されており、0が標準入力、1が標準出力、2が標準エラー出力は基本存在している。
/proc/${プロセスID}/fd
を見ると、そのプロセスが使えるファイルディスクリプタを確認できる。
以下は、0〜2が同じ /dev/pts/1
という仮想端末に繋がっている。
$ ll /proc/$$/fd
total 0
lrwx------ 1 root root 64 Jan 16 06:45 0 -> /dev/pts/1
lrwx------ 1 root root 64 Jan 16 06:45 1 -> /dev/pts/1
lrwx------ 1 root root 64 Jan 16 06:45 2 -> /dev/pts/1
lrwx------ 1 root root 64 Jan 16 06:45 255 -> /dev/pts/1
参考記事
標準入力を受け取り、標準出力として出すような、catコマンドみたいなものをCで再現しようとしたときに、
read関数で0〜2どれを選んでも同じ挙動となっていた。
それは、0〜2が同じ仮想端末に繋がっているから問題が無かったといえる。
for (;;) {
n = read(STDIN_FILENO, buf, sizeof buf);
if (n == 0) break;
write(STDOUT_FILENO, buf, n);
}
パイプで繋がっている状況を確認する
プロセスごとにファイルディスクリプタに示されている接続先が違うのであれば、
パイプはどうなっているのか。
試す
Aターミナル、Bターミナルと用意
Bターミナル
# 入力が来るまで待ち状態
$ cat | cat
Aターミナル
$ ps auxw
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 11840 2752 pts/0 Ss+ 01:38 0:00 /bin/bash
root 16 0.0 0.0 11840 2936 pts/1 Ss 01:39 0:00 bash
root 31 0.0 0.0 11840 2852 pts/2 Ss 01:39 0:00 bash
root 45 0.0 0.0 4396 680 pts/2 S+ 01:39 0:00 cat
root 46 0.0 0.0 4396 660 pts/2 S+ 01:39 0:00 cat
root 51 0.0 0.1 51744 3180 pts/1 R+ 01:43 0:00 ps auxw
cat のプロセスの1つが、標準出力がpipe:[111261] に繋がり、
もう1つのプロセスの標準入力にpipe:[111261]に切り替わっていることがわかる
$ ll /proc/{45,46}/fd
/proc/45/fd:
total 0
lrwx------ 1 root root 64 Jan 18 01:40 0 -> /dev/pts/2
l-wx------ 1 root root 64 Jan 18 01:40 1 -> pipe:[111261]
lrwx------ 1 root root 64 Jan 18 01:39 2 -> /dev/pts/2
/proc/46/fd:
total 0
lr-x------ 1 root root 64 Jan 18 01:40 0 -> pipe:[111261]
lrwx------ 1 root root 64 Jan 18 01:40 1 -> /dev/pts/2
lrwx------ 1 root root 64 Jan 18 01:39 2 -> /dev/pts/2
FILE型
ファイルディスクリプタに、バッファ機能を付けたもの。ラッパー。
それらを扱った入出力で fopen
fclose
fgetc
fputc
などがある
stdioのライブラリはちゃんとバッファリングモードが利いているか
straceコマンドをインストール
yum -y install strace
データの準備
空文字で4096バイト分のファイルを用意
dd bs=4096 if=/dev/zero count=1 of=data
read、writeのシステムコール版
bufサイズは2048をファイル内で指定
# strace -e trace=open,read,write,close ./cat data > /dev/null
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
close(3) = 0
open("/lib64/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`&\2\0\0\0\0\0"..., 832) = 832
close(3) = 0
open("data", O_RDONLY) = 3
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2048) = 2048
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2048) = 2048
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2048) = 2048
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2048) = 2048
read(3, "", 2048) = 0
close(3) = 0
+++ exited with 0 +++
stdio版
一回のreadとwriteのシステムコールで終了している。
# strace -e trace=open,read,write,close ./catstd data > /dev/null
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
close(3) = 0
open("/lib64/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`&\2\0\0\0\0\0"..., 832) = 832
close(3) = 0
open("data", O_RDONLY) = 3
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
read(3, "", 4096) = 0
close(3) = 0
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
+++ exited with 0 +++
fgetc() って何を返してくれている?
#include <stdio.h>
int fgetc(FILE *stream);
1バイト読み込み、その数値を返してくれている。
中身は何をやっているのか。確認をしてみる。
・・・
f = fopen(argv[i], "r");
while ((c = fgetc(f)) != EOF){
printf("%d\n", c);
if(putchar(c) < 0) exit(1);
printf("\n");
}
・・・
c = fgetc(f) で1バイトずつファイル終端に来るまで読み込みながら、putcharで読み込んだバイトを出力している。
そのc自体が何を来るのか確認してみる。
テストデータ
# cat data2
alice
bob
cathy
実行
# ./catstd data2 | head
97
a
108
l
105
i
99
c
101
e
各文字とそれに対応する文字コードが表示されている。
プログラム上では1バイトずつ読み込んで、書き出すことをやっているけど、
背後のシステムコールは1度のwriteとreadでやっているみたい
stdioのライブラリのfgetcとfputcharを使った場合、バッファリングは4096っぽいけど、setvbuf で変更はできる。
第7章 headコマンドを作る
オプションの慣習について
パラメータを取らないオプションと、パラメータを取るオプションで大別ができる。
ls -a
head -n 5
パラメータを取らないオプションは、 ls -als
のように、1つに纏められる。
パラメータありのオプションは、 head -n5
のように、オプションとパラメータをくっつけることも可能。
ハイフンが2つあるのはロングオプションと言われ、
ハイフンが1つのものはショートプションとなっている。
psコマンドでは、BSDスタイルという、オプションにハイフンがつかないスタイルも存在している。
mktempでユニークな一時ファイル、一時ディレクトリが作れるっぽい
[root@a5d4c09a13d7 linux]# ROOTFS=$(mktemp -d)
[root@a5d4c09a13d7 linux]# echo $ROOTFS
/tmp/tmp.20Jxja5FQj
nvmeファイルシステムとは
Filesystemで /dev/nvme
とは何かと思った。
# df
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 490691512 23054456 442641576 5% /
tmpfs 65536 0 65536 0% /dev
tmpfs 8014824 0 8014824 0% /sys/fs/cgroup
shm 65536 0 65536 0% /dev/shm
/dev/nvme0n1p2 490691512 23054456 442641576 5% /app
tmpfs 8014824 0 8014824 0% /proc/asound
tmpfs 8014824 0 8014824 0% /proc/acpi
tmpfs 8014824 0 8014824 0% /proc/scsi
tmpfs 8014824 0 8014824 0% /sys/firmware
SSDにアクセスするための仕様っぽい?
標準入力とコマンドライン引数によるファイルディスクリプタの違い
たとえばファイルの中身を見るless
コマンドを使う時に、二通りのやり方ができる
less cat.c
less < cat.c
(もしくは、cat cat.c | less
)
動作的に違いは全く無いが、ファイルディスクリプタを見ると違いが出てくるのがわかる。
標準入力で渡す
$ less < app/cat.c
$ ll /proc/196794/fd
total 0
lr-x------ 1 root root 64 Mar 23 02:49 0 -> /app/cat.c
lrwx------ 1 root root 64 Mar 23 02:49 1 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 23 02:49 2 -> /dev/pts/1
lr-x------ 1 root root 64 Mar 23 02:49 3 -> /dev/tty
これは予想通りの動き
コマンドライン引数で渡す
$ less app/cat.c
# 別端末で確認
$ ll /proc/196788/fd
total 0
lrwx------ 1 root root 64 Mar 23 02:47 0 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 23 02:47 1 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 23 02:47 2 -> /dev/pts/1
lr-x------ 1 root root 64 Mar 23 02:47 3 -> /dev/tty
lr-x------ 1 root root 64 Mar 23 02:47 4 -> /app/cat.c
コマンドライン引数で渡した時に、引数がファイルディスクリプタの4番で渡っている。
試しにvimでファイルをコマンドライン引数に渡して開いてみる
vi app/cat.c
# 別端末で確認
$ ll /proc/196798/fd
total 0
lrwx------ 1 root root 64 Mar 23 02:51 0 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 23 02:51 1 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 23 02:50 2 -> /dev/pts/1
lrwx------ 1 root root 64 Mar 23 02:51 4 -> /app/.cat.c.swp
コマンドライン引数などは、もしかしたら4番以降に入るとか、あるのかもしれない?