気がついたら一ヶ月経ってた
前回ブログを書いてから、気がついたら一ヶ月経ってた。びっくり。
修論関係のいろいろが終わった
ここ最近は、修論を書いたり提出したり発表したりしてた。修論はまあ書いたらいいだけなのでアレなんだけど、発表は恐ろしく苦手なのでめちゃめちゃ練習した。修論提出後に本番の発表まで1週間くらい時間があって、その間にスライドを作っては発表してフィードバックを貰って修正してまた発表を見てもらって、みたいなサイクルをずっと繰り返してた。
卒論の時もそうだったんだけど、最初は通して喋り切る事すら無理だったのに、練習すればするほど喋れるようになってくる。最終的には「どこで強調するか」とか「どこでタメるか」とか「時間をどう合わすか」とか考えながら話せるようになる。やればやるだけ改善されるので、練習するのは結構楽しかった。
「ふつうのLinuxプログラミング」読んだ
発表終わったのが先週の火曜で、そこからの一週間は勢いで買った「ふつうのLinuxプログラミング」を読んだりデータの整理をしたりいろいろ書類書いたりしてた。
「ふつうのLinuxプログラミング」は Linux でのプログラミング入門用の本みたいな位置づけなんだけど、内容としては「システムコール」からLinuxの姿を捉える感じになってた。いろいろ聞きかじりながらも断片的だった知識が読み進めていく程に整理されていって、良さげだった。お金が無いのに本買っちゃって後悔しかけたけど、めちゃめちゃ良い本だったので今は満足している。
以下、読み進めがら適当にメモってた事。メモるべき事はもっと多かった気もするので、また読み返した方が良いかもしれない。
printf の濫用の問題点
fputs(buf, sizeof buf, stdin); printf(buf);
とやると、例えば標準入力に %
を含む文字を入力されちゃうと printf がその%を型指定子の%だと思ってしまうので、第二引数以降が渡されてないと不正な動作となってしまい危険。
directoryのpermission
rはls権限、wはfile作成権限。ここまでは良いとして、xはディレクトリ内のファイルへのアクセス権限で、直感と反する。ファイルへのアクセス権限が無いと、読み書き実行全部出来ない。
クレデンシャル
プロセスには、 クレデンシャル と呼ばれる属性があって、これは特定のユーザの権限で実行している事を表す。例えば、シェルのクレデンシャルはログインしたユーザーになっている。プロセスがどのユーザーの権限で動いているかは、ps u
した時のUSER
で確認出来る。このユーザー(およびユーザーが属するグループ)の持つ権限に応じて、ファイルやディレクトリへのアクセス制限がなされる。
各種ディレクトリの用途
usr以下がディストリビューション由来のもの。ディストリビューション付属のパッケージ管理システム(ubuntuにおけるapt-getとか)でインストールしたものは /usr/bin
に入るっぽい。
usr/localがユーザーが自分でインストールするものとかを置く場所。OSX であれば、Homebrewは/usr/local以下にインストールを行う。(コマンドは/usr/local/bin
, ライブラリは/usr/local/lib
)
ファイルとしての抽象化
unixはデバイスをファイルとして抽象化して /dev
フォルダに各種デバイスに対応するファイルが置かれてる。ファイルへの読み書きがデバイスに対する読み書きに対応する。
/proc
にはプロセスファイルシステムがマウントされてて、プロセスの情報にファイルとしてアクセス出来る。例えば、プロセスID 1 のプロセスの情報は、 /proc/1
フォルダの中の各種ファイルで参照出来る。
$ sudo ls /proc/1 attr clear_refs cpuset fd limits mem net oom_score root smaps status wchan autogroup cmdline cwd fdinfo loginuid mountinfo ns oom_score_adj sched stack syscall auxv comm environ io map_files mounts numa_maps pagemap schedstat stat task cgroup coredump_filter exe latency maps mountstats oom_adj personality sessionid statm timers
デバイスがファイルとして抽象化されてるのは知ってたけど、プロセスまでそうだとは知らなかった...
ps
コマンドとかはこのプロセスファイルシステムを利用してるらしい。
/proc のより広い役割
プロセスファイルシステムはカーネルの情報をリアルタイムに出力する仕組みとしても使われてる。例えば、/proc/loadavg
ファイルは負荷情報を記すファイルとして存在する。
ディレクトリのパーミッション
引数として渡したmode_t mode
に加えて、umaskも考慮して決まる。ファイル作成時も同様。
c言語における文字列リテラルについて
"~"
みたいなダブルクオートで囲んだ文字列リテラルは、staticな領域に確保されたchar *
として振舞うっぽい。だから、returnしても解放されない。あと、char *
型の変数にも代入できる。
注意点としては、例えば
char *str; str = "文字列リテラル使用";
みたいにポインタ変数に文字列リテラルのアドレスを格納してる場合、
str[0] = 'a'; // 不正なコード!!
の様に一部を書き換えようとするとstatic領域のメモリへのアクセスになってエラーが出る。(仕様上は動作が未定義。OSXだとsegmentation faultが出た。)
文字列リテラルは配列の初期化に使える。例えば、
char str[] = "文字列リテラル使用";
と書いた場合は、staticな領域に確保されたものと同じサイズ、内容のcharacter arrayがstackメモリ上に積まれる。この場合は変数str
の指す先はstackメモリ上のアドレスである為、
str[0] = 'a'; // これはOK!!!
の様に一部を書き換える事が出来る。
参考: https://www.jpcert.or.jp/sc-rules/c-str30-c.html
この辺、ややこしいのでクソ仕様だと思う。
シグナル
sigaction
システムコールでシグナルの補足を行う(signal
でも出来るけど、挙動がOS依存なので非推奨)。シグナルの送信は、kill
システムコールで行う。
「ふつlinu」に載ってた主なシグナルは以下。
名前 | 補足可能 | デフォルトの挙動 | 生成原因および用途 |
---|---|---|---|
SIGINT | ○ | 終了 | 割り込み。ctrl+C で生成。 |
SIGHUP | ○ | 終了 | ユーザーログアウト時などに生成。デーモンプロセスに対しては設定ファイルの読み直しに使われる |
SIGPIPE | ○ | 終了 | 切れたパイプに書き込むと生成。 |
SIGTERM | ○ | 終了 | killでシグナルを指定しなかった時のデフォルトの値 |
SIGKILL | × | 終了 | 確実なプロセスの終了に使う。 |
SIGCHLD | ○ | 無視 | 子プロセスが停止または終了時に生成される。 |
SIGSEGV | ○ | コアダンプ | 不正なメモリアクセスがあった時に生成。いわゆるsegmentation fault。 |
SIGBUS | ○ | コアダンプ | アラインメント違反。ポインタ操作を間違えた時などに生成。 |
SIGFPE | ○ | コアダンプ | 算術演算でのエラー。ゼロ除算や浮動小数点数のオーバーフローなど |
SIGHUP, SIGTERM, SIGKILLらへんはよく聞く気がする。
プロセスの環境
カレントディレクトリ
プロセスには、「今いるディレクトリ」が存在。これを current working directory と呼び、getcwd(3)
で取得出来る。
どうでも良いけど、libcの関数で引数として渡したbufに文字列を書き込ませるものは、bufのサイズが足りないとエラーが返る。だから、bufのサイズを大きくして何度でもトライ出来るようにしなきゃならないらしい。例えば、以下のコードが例として載ってた。
#include <unistd.h> #include <errno.h> #define INIT_BUFSIZE 1024 char *my_getcwd(void) { char *buf, *tmp; size_t size = INIT_BIFSIZE; buf = malloc(size); if (!buf) return NULL; for (;;) { errno = 0; if (getcwd(buf, size)) return buf; if (errno != ERANGE) break; size *= 2; tmp = realloc(buf, size); if (!tmp) break; buf = tmp; } free(buf); return NULL; }
cwdを確保出来るまでbufのサイズを大きくして、確保できればヒープ上に確保された文字列へのポインタを、確保できなければNULLが返る。
環境変数
プロセスの親子関係を通じて伝播するグローバル変数のようなもの。
デーモン化
親プロセスが存在せず端末にも繋がれてないプロセスは「デーモン」と呼ばれる。大体はサーバプロセスとかがデーモン化されてる。
デーモン作成のコード例は以下。
18 static void become_deamon(void) { 19 int n; 20 21 if (chdir("/") < 0) { /* log_exitはオリジナルのlog&exit用関数 */ /* chdir が失敗した場合はexit */ 22 log_exit("chdir(2) failed: %s", strerror(errno)); 23 } 24 freopen("dev/null", "r", stdin); 25 freopen("dev/null", "w", stdout); 26 freopen("dev/null", "w", stderr); 27 n = fork(); 28 if (n < 0) log_exit("fork(2) failed: %s", strerror(errno)); 29 if (n != 0) _exit(0); /* 親プロセスは終了 */ 30 if (setsid() < 0) log_exit("setsid(2) failed: %s", strerror(errno)); 31 }
ポイントは30行目のsetsid
というシステムコールで、これを呼び出すとセッションが新規で作られ、自身(setsid
を呼んだプロセス)がその新規セッションのセッションリーダーとなる。その際、setsid
を呼んだプロセスは 制御端末を持たなくなる 。その為、ユーザー等とは無関係に独立して動作する。
注意点としては、setsid
はプロセスグループリーダーであるプロセスが呼び出すと失敗する。そこで、確実にsetsid
を成功させる為に事前にfork
で新しいプロセスが作られる。それが27行目。親プロセスは29行目の様に即座に_exit
する為、デーモンがゾンビ化する事も無い。
24-26行目は、標準入出力を/dev/null
に繋ぎ直してる。要は標準入出力を使わないようにしてるということ。また、デーモンが特定のディレクトリをcurrent working directoryとして動き続けるとファイルシステムがマウント出来ない等の不都合があるため、chdir
でcurrent working directoryをルート(/
)に変更している。
「ふつLinu」の話はここまで。ついでに、勢いで「デザインの教室」も買った
これも前から目をつけてた本で、「ふつLinu」を読み終わったのを機につい買ってしまった。初っ端で「内的必然性」について触れられてて、ノンデザイナーズデザインブックで出てきた「近接・整列・反復・コントラスト」の4原則をさらにまとめる概念っぽく感じた。「内的必然性」を持たせる為の手段として「4原則」があるイメージというか。良さげ。
まとめ
とりあえず何やかんやしてたら2月はあっという間に終わりそう。実は就職予定の会社には3月から働かせてもらおうと思ってて、今度その抱負とか書きたいと思う。