TLPI Chapter 03 System Programming Concepts 読書メモ2
この章のエクササイズにreboot(2)のmagicナンバーの意味を考えようという問題がある。16進数に変換するのがヒントとのことだがググらないと意味が分からなかった。
なお、答えは、下記のリンクにある通りである。
https://stackoverflow.com/questions/4808748/magic-numbers-of-the-linux-reboot-system-call
TLPI Chapter 03 System Programming Concepts 読書メモ
第3章は主にsystem callの概要が書いてある。
その中でsystem callがユーザー関数と比べて実行がいかに遅いかを実験していたので追試した。
下記の2パターンの実行時間を比べた。
1. getppid(2)を一万回呼ぶ場合
2. 単に整数値を返す関数を一万回呼ぶ場合
実行時間は、おおよそ100倍異なりsystem callの遅さがわかった。1の場合は2.79秒だったのに対して、2の場合は0.03秒であった。
PostgreSQLサーバーにクライアントが接続した時のシステムコールを眺める
PostgreSQLサーバー(いわゆるpostmaster process)は常にクライアントからのコネクションを受け付けている。 クライアントからの接続が確立したら、バックエンドプロセスを立ち上げて自身は接続待ちに戻る。
ここで、psqlで接続した時のPostgreSQLサーバー(pid:4110)が発行するシステムコールを見てみる。
今回は、psql -h localhost postgres
で同一マシン上から接続してみる。
なお、同一マシン上でpsql postgres
とhオプションなしで接続するとUnix domain socketが利用されてtcp接続は行われない。
vagrant@localhost postgresql]$ strace -ytp 4110 strace: Process 4110 attached 17:47:58 select(6, [3<socket:[34941]> 4<socket:[34942]> 5<socket:[34943]>], NULL, NULL, {49, 937628}) = 1 (in [3], left {46, 591689}) 17:48:01 rt_sigprocmask(SIG_SETMASK, ~[ILL TRAP ABRT BUS FPE SEGV CONT SYS RTMIN RT_1], NULL, 8) = 0 17:48:01 accept(3<socket:[34941]>, {sa_family=AF_INET6, sin6_port=htons(54486), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 9<socket:[35571]> 17:48:01 getsockname(9<socket:[35571]>, {sa_family=AF_INET6, sin6_port=htons(5432), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0 17:48:01 setsockopt(9<socket:[35571]>, SOL_TCP, TCP_NODELAY, [1], 4) = 0 17:48:01 setsockopt(9<socket:[35571]>, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0 17:48:01 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fdafd8a2a10) = 4200 17:48:01 close(9<socket:[35571]>) = 0 17:48:01 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 17:48:01 select(6, [3<socket:[34941]> 4<socket:[34942]> 5<socket:[34943]>], NULL, NULL, {60, 0}^Cstrace: Process 4110 detached <detached ...>
- サーバーは起動時に作成していたソケットを用いてselect(2)でクライアントからの接続を待つ。接続が来たのでファイルディスクリプタfd=3のソケットをリターン
- 接続を受け付けたらシグナルをブロック
- select()で返されたfd=3のソケットを用いて接続を完了させる。この時新しいソケット(fd=9)で接続が完了する。接続の待ち受けに利用していたfd=3のソケットは待ち受けように再びそのまま利用される
- 自分のアドレスを取得している。成功したので0を返している
- tcpのオプションをソケットに設定
- tcpのオプションをソケットに設定
- バックエンドプロセスをforkするためのシステムコール
- コネクションを確立したソケットはこのプロセスではもう不要なので閉じる
- シグナルをブロックする
- 1に戻って待機
下記のnetstatnの表記を見ると、7のclone(2)で作成したバックエンドプロセス(pid=4200)が接続を確立していることが分かる
[vagrant@localhost postgresql]$ netstat -natp|grep postgres (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN 4110/postgres tcp6 0 0 ::1:5432 :::* LISTEN 4110/postgres tcp6 0 0 ::1:5432 ::1:54486 ESTABLISHED 4200/postgres: vagr
その他のメモ
- fdの番号はプロセスごとに割り当てられる。
ls -l /proc/$pid/fd/
でソケットに利用されているfdが分かる - この記事は、参考文献で書いてあることの一部をシステムコールを通して言い換えたものである 参考文献:PUG しくみ分科会 勉強会 PostgreSQLソースコード解析 ~ postmaster ~
Linux programming interface 3章システムプログラミング
断片的な読書メモです。
3.5章にサンプルコードについての説明がある。Makefileは本には記載されていないので、下記のサポートページからソースを一式ダウンロードして確認する。
Source code of the Programs in "The Linux Programming Interface"
この章で導入しているエラー処理ライブラリをビルドすると、 libtlpi.aというスタティックライブラリが作成される。
ビルドメモ:
-
@ echo ${BUILD_DIRS}
のように@
をコマンドの前につけると、make時にそのコマンドの出力が抑制される - gccには関数属性をチェックする機能がある。
__attribute__ ((__noreturn__))
と書くと-Wall
でコンパイルしたときに、呼び出し元に戻らない関数に対する警告文を出さないようにできる。関数のプロトタイプ宣言時にvoid hoge() __attribute__ ((__noreturn__))
のように記載
snprintf
snprintf()
の使い方
(sprintf()
はバッファーオーバーランの可能性があるので使わない)
/*配列bufを初期化してないが、fputs()で¥0まで読み込むからいいのかな*/ # define BUFSIZE 500 char buf[BUFSIZE]; /*余裕を持ったサイズの文字配列を宣言*/ snprintf(buf, sizeof(buf), "ERROR: %s", my_error); /*終端文字の¥0も含めて最大sizeof(buf)の値をbufに書き込む*/ fputs(buf, stderr); /*終端文字¥0は書き込まない*/ fflush(stderr);
strtol
manページの例がわかりやすい。文字列をlong型の整数に変換。
atoi()
関数もあるが、これはエラー処理ができないのでstrtol()
を使った方が良いようだ。詳細はman strtol
。
getenvの使い方
PostgreSQLを題材にシステムプログラミングを学ぼうと思う。
初めは簡単なところからということで、getenv
関数について。
題材はPostgreSQL 10.0のコード。
getenv
はstdlib.hに含まれるライブラリ関数です。その名の通りに引数にとった環境変数の値のポインタを取得。
取得できなかった場合はNULLを返す。
下記は、src/bin/scripts/createdb.c
から抜粋。
NULLの場合を考慮して、if(gettnv("FOO"))
という書き方になっているのが分かります。
if (dbname == NULL) { if (getenv("PGDATABASE")) dbname = getenv("PGDATABASE"); else if (getenv("PGUSER")) dbname = getenv("PGUSER"); else dbname = get_user_name_or_exit(progname); }
因みに、PostgreSQLではcreatedb
コマンドを引数なしで実行すると
PGDATABASEで指定した値、あるいはユーザーと同じ名前のデータベースが作成される。
今回の抜粋はその処理部分。