読者です 読者をやめる 読者になる 読者になる

気がついたら一ヶ月経ってた

前回ブログを書いてから、気がついたら一ヶ月経ってた。びっくり。

 修論関係のいろいろが終わった

ここ最近は、修論を書いたり提出したり発表したりしてた。修論はまあ書いたらいいだけなのでアレなんだけど、発表は恐ろしく苦手なのでめちゃめちゃ練習した。修論提出後に本番の発表まで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月から働かせてもらおうと思ってて、今度その抱負とか書きたいと思う。