カテゴリー別アーカイブ: リーディングコード

phpの配列はどのようにして初期化され実行されるのか

概要

phpなどのLLは、記述するだけでコンパイルなしに実行されますがその中身はどうなっているのでしょうか。
今回は配列を例にとって、実際にphp処理系をおってみます。
主に字句解析、構文解析の実装について順を追って解説していきたいと思います。

zend処理系

まずはphpの処理系について全体像を軽く解説します。
ドキュメントも結構まとまっておりこの辺りなど非常に参考になります。(4年前ですがこの勉強会行きたかったな・・・)

さて、話を戻しますがphpのコアな部分はzend処理系と呼ばれる実装によって成り立っています。
zend frameworkというフレームワークが存在しますが、そちらのフレームワークとしてのzendではなくてphpの初期の発展に寄与したグループのzendです。

zend処理系ではphpのコードから実際に処理を実行するまでに下記のフェーズを実行します。

  1. 字句解析
  2. コードを字句(トークン)に分解します。トークンとは文が構成される最小単位のことです

  3. 構文解析
  4. 分解されたトークンを実際にどういった命令であるかということを解釈します

  5. OPcodeに翻訳
  6. 解釈した公文をOPcodeという中間コードに翻訳します

  7. 実行
  8. 実際にOPcodeを実行します

通常のアプリケーション開発なんかで言うとOPcodeなどは聞いたことがある方も多いのではないでしょうか。
有名どころではOPcacheなどがありますね。

OPcacheはその名の通りOPcodeをキャッシングします。
phpのコードに対して字句解析・構文解析を実行することで中間コードを生成するのですが、同じコードを毎回解析するのはかなり無駄なコストですね。
この中間コードをキャッシングすることで実行性能を上昇させることを目的とした機能です。
余談ですがこちらはphp5.5以降では標準搭載されるようになっていますね。

実際にはその前のフェーズとして字句解析・構文解析が行われます。
これらはごく普通にphpアプリケーションを開発しているだけだとあまり関わることがありません。今回はこちらについて掘り下げていきたいと思います。

字句解析

まず字句解析について。先に簡単に説明したようにコードを字句(トークン)に分解する作業です。

イメージしやすいように下記のサンプルコードを実行してみます。

<?php
$tokens = token_get_all('<?php
$hoge = array();');
foreach ($tokens as $i => $token) {
	echo is_array($token) ? token_name($token[0]) : $token;
	echo PHP_EOL;
}

echo PHP_EOL;

$tokens = token_get_all('<?php
$fuga = [];');
foreach ($tokens as $i => $token) {
	echo is_array($token) ? token_name($token[0]) : $token;
	echo PHP_EOL;
}

token_get_allはphpコードを引数として受け取り、そのコードをトークンレベルまで分解します。
またtoken_nameは分解されたトークンにzend処理系で管理している enum yytokentype を取得します。詳しくは zend_language_parser.c などを参照しましょう。

さて、サンプルコードの実行結果は下記のようになります。

T_OPEN_TAG
T_VARIABLE
T_WHITESPACE
=
T_WHITESPACE
T_ARRAY
(
)
;


T_OPEN_TAG
T_VARIABLE
T_WHITESPACE
=
T_WHITESPACE
[
]
;

T_OPEN_TAGはphpの開始タグを表すenum定数になります。またT_WHITESPACEはホワイトスペースを表すenum定数になります。
このようにphpのセンテンスをzend処理系で用意されたトークンに細かく分解していきます。

さらにここでarrayはT_ARRAYにトークン解析されていることがわかります。またそのあとのカッコについてはそのままになっていることがわかりますね。
これはそれ自体で意味を持っているメタ文字になるからです。

各種zend処理系での予約後に関してはトークン解析によってenumに置き換えられ、それ以外はメタ文字としてそのまま認識されることになります。

構文解析

続いて解析できたトークンを意味のある文として解釈します。zend処理系ではこの構文解析の実装とbisonと呼ばれるgnuツールを利用しています。bisonはBNFを基本とした亜種で文法を表現します。

bisonについてはこちらでかなり詳しく説明しておられるので参考にしてみると良いでしょう。

bisonでは終端トークン(最小単位のトークン)を組み合わせて文として認識できる構成を定義していきます。
zend処理系における実装はzend_language_parser.yに記述されておりますので確認してみてください。

例えば下記のようなコードが合った時

<?php
$a = 'str';

先に述べたようにこれらはまずトークン解析され、続いてbisonによって記述された文に一致するパターンを検索します。
もし文が適合すれば処理しますし、仮に文が解釈できないようであればパースエラーとして実行されないで終了します。
bison(およびBNF記法)の強力なところはそれぞれの文章を再帰的に表現することや、文の定義に他の文を含むことができるので非常に柔軟に文を定義できるところにあります。

arrayの文を確認する

構文解析まで理解できたところで実際にphpではarrayというセンテンスをどのように解釈して処理系が動作しているのかというところを追いかけてみます。

例えば下記のようなphpコードを対象として取り上げてみてみましょう。

$a = array();

字句解析の項目で解説したようにarrayという文字列はT_ARRAYとしてトークナイズされます。bisonの定義ファイルからこれの定義を探してみます。

%token T_ARRAY           "array (T_ARRAY)"

すると上記のような項目が見つかりました。これはトークンT_ARRAYを定義していることを表します。
続いてT_ARRAYを文として定義している箇所(=構文解析を行っていると思われる箇所)を探してみましょう。

combined_scalar:
		T_ARRAY '(' array_pair_list ')' { $$ = $3; }
	|	'[' array_pair_list ']' { $$ = $2; }
;

見つかりましたcombined_scalarという文が定義されており、トークンとしてT_ARRAYが使用されていることから、これが配列の初期化としての文であることが理解できます。

bisonではパイプで区切ることで複数の文を定義することができます。
よって二つ目の文についてはPHP5.4以降で新たに定義されたブラケット(カギカッコ)による配列の定義が確認できますね。
カギカッコの中は構文により呼び出されるc言語による実装です。$$は戻り値を示しており、$xはx番目にマッチするトークンを表しています。

さてここで先に述べたようにbison(BNF)では文を再帰的に定義できますし、文の定義に他の文の定義を含むことができます。
ここではarray_pair_listという文が定義されていることが確認できるので更に文の定義を追いかけます。

array_pair_list:
		/* empty */ { zend_do_init_array(&$$, NULL, NULL, 0 TSRMLS_CC); }
	|	non_empty_array_pair_list possible_comma	{ $$ = $1; }
;

non_empty_array_pair_list:
		non_empty_array_pair_list ',' expr T_DOUBLE_ARROW expr	{ zend_do_add_array_element(&$$, &$5, &$3, 0 TSRMLS_CC); }
	|	non_empty_array_pair_list ',' expr			{ zend_do_add_array_element(&$$, &$3, NULL, 0 TSRMLS_CC); }
	|	expr T_DOUBLE_ARROW expr	{ zend_do_init_array(&$$, &$3, &$1, 0 TSRMLS_CC); }
	|	expr 				{ zend_do_init_array(&$$, &$1, NULL, 0 TSRMLS_CC); }
	|	non_empty_array_pair_list ',' expr T_DOUBLE_ARROW '&' w_variable { zend_do_add_array_element(&$$, &$6, &$3, 1 TSRMLS_CC); }
	|	non_empty_array_pair_list ',' '&' w_variable { zend_do_add_array_element(&$$, &$4, NULL, 1 TSRMLS_CC); }
	|	expr T_DOUBLE_ARROW '&' w_variable	{ zend_do_init_array(&$$, &$4, &$1, 1 TSRMLS_CC); }
	|	'&' w_variable 			{ zend_do_init_array(&$$, &$2, NULL, 1 TSRMLS_CC); }
;

possible_comma:
		/* empty */
	|	','
;

array_pair_listの文を確認するとempty(0個のトークンで構成される)またはnot_empty_array_pair_list possible_commaで構成されることがわかります。

possible_commaの文定義を確認するとコンマもしくはなにもなし、と定義されることがわかります。PHPの配列では末尾にコンマをつけてもつけなくても認識してくれますが、それはこの文定義によるものだということが理解できますね。

さらにnot_empty_array_pair_listを確認してみます。定義がそれぞれありますがphpの多様な配列定義を文として定義しています。
例えばphpのセンテンスとして下記のようなものがありますが、それぞれがこの構文として定義されています。

<?php
$array1 = array('key' => value);
$array2 = array(1, 2);
$array3 = array();

最終的にzend_do_init_arrayというcの関数が呼び出されていることがわかります。
今回は構文解析からarrayの処理系の解釈を追うことが目的ですので、zend_do_init_arrayについては次の機会に取り上げたいと思います。

まとめ

いかがでしたでしょうか。
意外とおってみるとそんなに難しくないことがわかります。これもbisonなど先人が作り出してきた便利なツールによるものですね。


phpのcount関数の実装を見る

ご無沙汰しております。
今日は掲題のようにcount関数の実装を見て行きたいと思います。

概要

phpの開発を行ったことがある方であれば、下記のようなコード見たことあると思います。

$something = array('a', 'b', 'c');

for($i=0; $i<count($something); $i++) {
 // do something
}

また下記のようなコードも目にすることがあると思います。

$something = array('a', 'b', 'c');
$count = count($something);

for($i=0; $i<$count; $i++) {
 // do something
}

これらのコードどちらが効率的なのでしょうか。
なんとなく後者のほうが効率が良いようなことは想像に難しくないと思います。
でも、はっきり前者は悪だと断言できる方は実は少ないのではないでしょうか。

後者にしておけば困ったことにはならないでしょう。ただし、コードが一行増える。微々たるものですが。
どちらかと言うと前者のほうがコードとしてはすっきりしていると思います。

それに何より中身がどうなっているのかわからないのが気分的にすっきりしない。
今日はphpのcount関数の実装を確認してみたいと思います。

調査

php公式からソースコードをダウンロードして調査します。
バージョンは現在の最新安定版である5.6.14としました。

zval構造体

まずphpのzend構造体から確認します。
zend.hをみてみましょう。

typedef struct _zval_struct zval;
struct _zval_struct {
    /* Variable information */
    zvalue_value value;       /* value */
    zend_uint refcount__gc;
    zend_uchar type;          /* active type */
    zend_uchar is_ref__gc;
};

typedef union _zvalue_value {
    long lval;        /* long value */
    double dval;      /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;    /* hash table value */
    zend_object_value obj;
    zend_ast *ast;
} zvalue_value;

phpでは変数はzval構造体で表されます。
zval構造体は参照カウントや、変数実体を保持するzvalue_valueという共用体を更に内部で保持します。
union構造体とは宣言された値の内どれか一つを保持する構造体のことです。

phpではlongやdoubleやstringなどの値に応じてzvalue_value共用体が保持する実体を切り替えるようになっています。
配列変数の場合はHashTable構造体を保持することになります。

Hashtable構造体

さらにzend_hash.hファイルを確認してその構造を確認してみましょう。

struct _hashtable;
typedef struct _hashtable {
    uint nTableSize;
    uint nTableMask;
    uint nNumOfElements;
    ulong nNextFreeElement;
    Bucket *pInternalPointer;       /* Used for element traversal */
    Bucket *pListHead;
    Bucket *pListTail;
    Bucket **arBuckets;
    dtor_func_t pDestructor;
    zend_bool persistent;
    unsigned char nApplyCount;
    zend_bool bApplyProtection;
 #if ZEND_DEBUG
    int inconsistent;
 #endif
} HashTable;

hashtable構造体については詳細な説明は今回は割愛します。
いったんデータ構造を確認したら今度はcount関数の実装を見てみることにしましょう。

count関数

array.cファイルを覗いてみます。

PHP_FUNCTION(count)
{
    zval *array;
    long mode = COUNT_NORMAL;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE) {
        return;
    }

    switch (Z_TYPE_P(array)) {
        case IS_NULL:
            RETURN_LONG(0);
            break;
        case IS_ARRAY:
            RETURN_LONG (php_count_recursive (array, mode TSRMLS_CC));
            break;
        case IS_OBJECT: {
#ifdef HAVE_SPL
            zval *retval;
#endif
            /* first, we check if the handler is defined */
            if (Z_OBJ_HT_P(array)->count_elements) {
                RETVAL_LONG(1);
                if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value) TSRMLS_CC)) {
                    return;
                }
            }
#ifdef HAVE_SPL
            /* if not and the object implements Countable we call its count() method */
            if (Z_OBJ_HT_P(array)->get_class_entry && instanceof_function(Z_OBJCE_P(array), spl_ce_Countable TSRMLS_CC)) {
                zend_call_method_with_0_params(&array, NULL, NULL, "count", &retval);
                if (retval) {
                    convert_to_long_ex(&retval);
                    RETVAL_LONG(Z_LVAL_P(retval));
                    zval_ptr_dtor(&retval);
                }
                return;
            }
#endif
        }
        default:
            RETURN_LONG(1);
            break;
    }
}

ごちゃごちゃと記述してありますが、今回は単純な配列な値の場合を確認します。
すると対象は一行だけでphp_count_recursiveを呼び出していることがわかります。

さらにphp_count_recursiveをおってみましょう。

PHPAPI int php_count_recursive(zval *array, long mode TSRMLS_DC) /* {{{ */
{
    long cnt = 0;
    zval **element;

    if (Z_TYPE_P(array) == IS_ARRAY) {
        if (Z_ARRVAL_P(array)->nApplyCount > 1) {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
            return 0;
        }

        cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
        if (mode == COUNT_RECURSIVE) {
            HashPosition pos;

            for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos);
                zend_hash_get_current_data_ex(Z_ARRVAL_P(array), (void **) &element, &pos) == SUCCESS;
                zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos)
            ) {
                Z_ARRVAL_P(array)->nApplyCount++;
                cnt += php_count_recursive(*element, COUNT_RECURSIVE TSRMLS_CC);
                Z_ARRVAL_P(array)->nApplyCount--;
            }
        }
    }

    return cnt;
}

ここでも型チェックや再帰的に関数を呼び出している部分がありますが、本質的な部分は一行だけです。
変数cntを代入しているzend_hash_num_elementsを見てみましょう。
実装はzend_hash.cにあります。

ZEND_API int zend_hash_num_elements(const HashTable *ht)
{
    IS_CONSISTENT(ht);

    return ht->nNumOfElements;
}

IS_CONSISTENTマクロはハッシュテーブルが破壊されていないかどうかを確認するものです。
さてここでようやく実装が見えましたね。
count関数の行っていることはhashtable構造体のnNumOfElementsを返却しているだけなのです。

結論

冒頭で出した2つのコードのパフォーマンスの違いはそこまで大きくないでしょう、ビッグオー的に計算量で表すとどちらもO(1)になるからです。

ただ実装で言うとメソッドの呼び出しが3つほど噛まれていますので、関数スタックにガツガツと何度も積み上げられることになります。
特にパフォーマンスに気を使う部分であれば、count関数の呼び出しは一度だけに限定するほうが良いでしょう。

個人的にはfor文の中に挟み込む方式は、あまり推奨しないかな。
コードは行儀、シンプルに誰でもわかるように書きべきでしょう。


Cのソケット実装を追う

最近TokyoTyrant、TokyoCabinetに触る機会があって、その実装レベルまで込み入った話に接する機会があった。
本日は最近気になっているTokyoTyrant, TokyoCabinetに関するモジュールのソースを追って、C言語で通信をどのように実装しているのかを確認してみたいと思います。

さてちょっといきなり飛ぶが、tokyotyrantのインタフェースから各種のメソッドを呼び出すとき
当然初めにリモートホストとの接続リソースを生成する必要がある。そこから始めよう。

tokyotyrantでは接続リソースを生成する際に下記のtcrdbopenを利用する。

/* Open a remote database. */
bool tcrdbopen(TCRDB *rdb, const char *host, int port){
  assert(rdb && host);
  if(!tcrdblockmethod(rdb)) return false;
  bool rv;
  pthread_cleanup_push((void (*)(void *))tcrdbunlockmethod, rdb);
  rv = tcrdbopenimpl(rdb, host, port);
  pthread_cleanup_pop(1);
  return rv;
}

リモートリソースをオープンするときtcrdblockmethodを使用。
ロックの取得を行っている。

/* Lock a method of the remote database object.
   <code>rdb' specifies the remote database object.
   If successful, the return value is true, else, it is false. */
static bool tcrdblockmethod(TCRDB *rdb){
  assert(rdb);
  if(pthread_mutex_lock(&amp;rdb-&gt;mmtx) != 0){
    tcrdbsetecode(rdb, TCEMISC);
    return false;
  }
  return true;
}

実態は何をやっているかというと下記メソッドを実行している
pthread_mutex_lock(&rdb->mmtx)

&rdb構造体を確認すると

typedef struct {                         /* type of structure for a remote database */
  pthread_mutex_t mmtx;                  /* mutex for method */
  pthread_key_t eckey;                   /* key for thread specific error code */
  char *host;                            /* host name */
  int port;                              /* port number */
  char *expr;                            /* simple server expression */
  int fd;                                /* file descriptor */
  TTSOCK *sock;                          /* socket object */
  double timeout;                        /* timeout */
  int opts;                              /* options */
} TCRDB;

各種メソッドのためのmutexロックとある
ところでさらにpthread_mutex_tという型が何なのか。

pthread_mutex_tとはtokyotyrantにおける拡張ではなくc言語で用意されている機構である。
manpageは下記になる。

http://linuxjm.sourceforge.jp/html/glibc-linuxthreads/man3/pthread_mutex_lock.3.html

pthread_mutex_lockはmutexロックを取得しようとする。mutexロックはスレッドの間で共通のロックを一つだけ持ち共有する。
ということはプロセス間ではそのロックは有効ではないので、その場合はまた別のロック機構が必要となる。

またpthread_mutex_lockはロックを取得できれば、直ちにロックを返却し、ロックが取得できなかった場合はロックが取得できるまでスレッドの実行を停止させるなどの挙動を示す。
ここですでにロックを取得していたスレッドがいた場合の挙動は、mutexを初期化した際の状態に依存して異なる。
tcrdbの初期化処理を見てみよう。

/* Create a remote database object. */
TCRDB *tcrdbnew(void){
  TCRDB *rdb = tcmalloc(sizeof(*rdb));
  if(pthread_mutex_init(&rdb->mmtx, NULL) != 0) tcmyfatal("pthread_mutex_init failed");
  if(pthread_key_create(&rdb->eckey, NULL) != 0) tcmyfatal("pthread_key_create failed");
  rdb->host = NULL;
  rdb->port = -1;
  rdb->expr = NULL;
  rdb->fd = -1;
  rdb->sock = NULL;
  rdb->timeout = UINT_MAX;
  rdb->opts = 0;
  tcrdbsetecode(rdb, TTESUCCESS);
  return rdb;
}

するとpthread_mutex_init関数を用いて、第二引数がNULLで初期化されていることがわかる。
pthread_mutex_initのmanpageは下記にある

http://linuxjm.sourceforge.jp/html/glibc-linuxthreads/man3/pthread_mutexattr_init.3.html

マニュアルを拝見すると第二引数はpthread_mutexattr_tという属性を指定することになる。
これがNULLである場合代わりにデフォルトの属性が使用される。
デフォルトのmutex種別は「速い(fast)」である。

さて、先のコードに戻って、このfast種別の場合、ロックを取得しようと場合どうなるかというところに話を戻そう。
mutexがfastの場合、すでにロックがあるmutexを取得しようとした場合呼び出しスレッドを永遠に停止させる。
ちなみにpthread_mutexattr_initの返り値の定義に関しては常に0を返却する。とある。
これはつまりtcrdblockmethodメソッドは、純粋にロックを直列に管理するような機構になっている事がわかる。
0でない場合雑多なエラーとして返却しているが、これはプログラムとして異常な状態になっていることを管理する機構であると推測される。

さて無事にほかスレッドとの競合に打ち勝ってロックが取得できたところで、再びtcrdbopenに戻ってみよう。

/* Open a remote database. */
bool tcrdbopen(TCRDB *rdb, const char *host, int port){
  assert(rdb && host);
  if(!tcrdblockmethod(rdb)) return false;
  bool rv;
  pthread_cleanup_push((void (*)(void *))tcrdbunlockmethod, rdb);
  rv = tcrdbopenimpl(rdb, host, port);
  pthread_cleanup_pop(1);
  return rv;
}

次にpthread_cleanup_pushという関数を呼び出している。これを見てみよう。
マニュアルによると、スレッドがキャンセルされた場合の、ハンドラメソッドを追加するとある。
C言語では作成したスレッドは明示的に終了することができ、その際にpthread_cleanup_pushで登録したハンドラが実行される。
この場合tcrdbunlockmethodが実行され、その時の引数としてrdbを渡すということを宣言している。

ここでtcrdbunlockmethodを見てみよう

/* Unlock a method of the remote database object.
   </code>rdb' specifies the remote database object. */
static void tcrdbunlockmethod(TCRDB *rdb){
  assert(rdb);
  if(pthread_mutex_unlock(&amp;rdb-&gt;mmtx) != 0) tcrdbsetecode(rdb, TCEMISC);
}

非常にシンプルでmutex_lockを開放していることがわかる。

さて再びtcrdbopen関数に戻って、tcrdbopenimplを参照してみよう。

/* Open a remote database.
   <code>rdb' specifies the remote database object.
   </code>host' specifies the name or the address of the server.
   `port' specifies the port number.
   If successful, the return value is true, else, it is false. */
static bool tcrdbopenimpl(TCRDB *rdb, const char *host, int port){
  assert(rdb &amp;&amp; host);
  if(rdb-&gt;fd &gt;= 0){
    tcrdbsetecode(rdb, TTEINVALID);
    return false;
  }
  int fd;
  if(port &lt; 1){
    fd = ttopensockunix(host);
  } else {
    char addr[TTADDRBUFSIZ];
    if(!ttgethostaddr(host, addr)){
      tcrdbsetecode(rdb, TTENOHOST);
      return false;
    }
    fd = ttopensock(addr, port);
  }
  if(fd == -1){
    tcrdbsetecode(rdb, TTEREFUSED);
    return false;
  }
  if(rdb-&gt;host) tcfree(rdb-&gt;host);
  rdb-&gt;host = tcstrdup(host);
  rdb-&gt;port = port;
  rdb-&gt;expr = tcsprintf(&quot;%s:%d&quot;, host, port);
  rdb-&gt;fd = fd;
  rdb-&gt;sock = ttsocknew(fd);
  return true;
}

細かなエラーチェックは割愛し、本体を見てみると
port番号が1未満、これを想定しているのはおそらく未指定であった場合であるが、そのときはttopensockunix関数を呼び出している。
またportが指定されていた場合はttopensockが呼び出されている。これらについて追っていこう。

初めにttopensockunixから参照してみる。

/* Open a client socket of UNIX domain stream to a server. */
int ttopensockunix(const char *path){
  assert(path);
  struct sockaddr_un saun;
  memset(&saun, 0, sizeof(saun));
  saun.sun_family = AF_UNIX;
  snprintf(saun.sun_path, SOCKPATHBUFSIZ, "%s", path);
  int fd = socket(PF_UNIX, SOCK_STREAM, 0);
  if(fd == -1) return -1;
  int optint = 1;
  setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&optint, sizeof(optint));
  struct timeval opttv;
  opttv.tv_sec = (int)SOCKRCVTIMEO;
  opttv.tv_usec = (SOCKRCVTIMEO - (int)SOCKRCVTIMEO) * 1000000;
  setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&opttv, sizeof(opttv));
  opttv.tv_sec = (int)SOCKSNDTIMEO;
  opttv.tv_usec = (SOCKSNDTIMEO - (int)SOCKSNDTIMEO) * 1000000;
  setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&opttv, sizeof(opttv));
  double dl = tctime() + SOCKCNCTTIMEO;
  do {
    int ocs = PTHREAD_CANCEL_DISABLE;
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ocs);
    int rv = connect(fd, (struct sockaddr *)&saun, sizeof(saun));
    int en = errno;
    pthread_setcancelstate(ocs, NULL);
    if(rv == 0) return fd;
    if(en != EINTR && en != EAGAIN && en != EINPROGRESS && en != EALREADY && en != ETIMEDOUT)
      break;
  } while(tctime() <= dl);
  close(fd);
  return -1;
}

sockaddr_unという構造体が出現しているが、これはUNIX LOCALなソケットのアドレスの情報を表す構造体である。
通信のためのfd(ファイルディスクリプタ)を取得するソケット生成の実態はscoket関数で行われている
引数がPF_UNIX, SOCK_STREAM, 0となっているが順に説明する。

PF_UNIXはソケット通信にファイルを用いることを示す。
詳しくは割愛するが、UNIXのローカルのソケット通信にはファイルを用いたものとTCP通信を用いたものがある。
またSOCK_STREAMに関してはソケットのタイプを表しており、これも詳しくは割愛するが
UNIXのソケットタイプにはSOCK_STREAMとSOCK_DGRAMが存在する。

ちなみにドメインソケットのアドレスは下記の構造体でシンプルに示される

#define UNIX_PATH_MAX    108

struct sockaddr_un {
    sa_family_t sun_family;               /* AF_UNIX */
    char        sun_path[UNIX_PATH_MAX];  /* pathname */
};

また作成したソケットに対してオプション設定している箇所がある。
SOL_SOCKETはオプションの層を表す識別子で、ソケットAPIそうでそうでオプションを有効にすることを表す。
SO_RCVTIMEO, SO_SNDTIMEOはそれぞれ受信、送信のタイムアウト設定をしている。これを超えた場合エラーが送信される。

最終的にconnectシステムコールを要求してファイルディスクリプタが参照しているソケットを引数で渡しているアドレス空間が示す場所とに連結してます。
この一連の処理によってサーバと接続されたファイルディスクリプタが取得でき、それによって通信することができるようになるわけですね。

これ以降の深いレイヤについてはUnixソースを参照していくと良いと思います。

さて対してリモートサーバと通信する場合はどうしているのかを追っていきましょう。

/* Open a client socket of TCP/IP stream to a server. */
int ttopensock(const char *addr, int port){
  assert(addr && port >= 0);
  struct sockaddr_in sain;
  memset(&sain, 0, sizeof(sain));
  sain.sin_family = AF_INET;
  if(inet_aton(addr, &sain.sin_addr) == 0) return -1;
  uint16_t snum = port;
  sain.sin_port = htons(snum);
  int fd = socket(PF_INET, SOCK_STREAM, 0);
  if(fd == -1) return -1;
  int optint = 1;
  setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&optint, sizeof(optint));
  struct timeval opttv;
  opttv.tv_sec = (int)SOCKRCVTIMEO;
  opttv.tv_usec = (SOCKRCVTIMEO - (int)SOCKRCVTIMEO) * 1000000;
  setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&opttv, sizeof(opttv));
  opttv.tv_sec = (int)SOCKSNDTIMEO;
  opttv.tv_usec = (SOCKSNDTIMEO - (int)SOCKSNDTIMEO) * 1000000;
  setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&opttv, sizeof(opttv));
  optint = 1;
  setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&optint, sizeof(optint));
  double dl = tctime() + SOCKCNCTTIMEO;
  do {
    int ocs = PTHREAD_CANCEL_DISABLE;
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ocs);
    int rv = connect(fd, (struct sockaddr *)&sain, sizeof(sain));
    int en = errno;
    pthread_setcancelstate(ocs, NULL);
    if(rv == 0) return fd;
    if(en != EINTR && en != EAGAIN && en != EINPROGRESS && en != EALREADY && en != ETIMEDOUT)
      break;
  } while(tctime() <= dl);
  close(fd);
  return -1;
}

ところどころがTCP通信に対応するような設定になっているだけで、基本的な内容はほぼ同じになっています。
sockaddr_inについても下記のような定義になっており、ポート番号が増えているなどTCPレイヤに対応していることが予想できます。

struct sockaddr_in {
    uint8_t sin_len;
    sa_family_t     sin_family;
    in_port_t       sin_port;
    struct  in_addr sin_addr;
    char    sin_zero[8];
};

こんなかんじにCではsocketを生成しているようですね。
ちなみに生成したfdを元にデータをやりとりする場合sendというシステムコールを利用することになりますが、これはまた後々取り上げたいと思います。