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 ...>
  1. サーバーは起動時に作成していたソケットを用いてselect(2)でクライアントからの接続を待つ。接続が来たのでファイルディスクリプタfd=3のソケットをリターン
  2. 接続を受け付けたらシグナルをブロック
  3. select()で返されたfd=3のソケットを用いて接続を完了させる。この時新しいソケット(fd=9)で接続が完了する。接続の待ち受けに利用していたfd=3のソケットは待ち受けように再びそのまま利用される
  4. 自分のアドレスを取得している。成功したので0を返している
  5. tcpのオプションをソケットに設定
  6. tcpのオプションをソケットに設定
  7. バックエンドプロセスをforkするためのシステムコール
  8. コネクションを確立したソケットはこのプロセスではもう不要なので閉じる
  9. シグナルをブロックする
  10. 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

その他のメモ

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で指定した値、あるいはユーザーと同じ名前のデータベースが作成される。 今回の抜粋はその処理部分。