home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

C言語系/「デーモン君のソース探検」読書メモ/03, getchar(3)

C言語系/「デーモン君のソース探検」読書メモ/03, getchar(3)

C言語系 / 「デーモン君のソース探検」読書メモ / 03, getchar(3)
id: 543 所有者: msakamoto-sf    作成日: 2010-01-08 22:20:09
カテゴリ: BSD C言語 

お題:getchar(3)がバッファを使うことでファイルアクセス回数を少なくしている、その仕組みを追跡せよ。

まずgetchar()のソースを特定する。

$ locate getchar
...
/usr/src/lib/libc/stdio/getchar.c
...


int
getchar()
{
        FILE *fp = stdin;
        int r;
 
        FLOCKFILE(fp);
        r = __sgetc(fp);
        FUNLOCKFILE(fp);
        return r;
}

FLOCKFILE/FUNLOCKFILEは本によるとマルチスレッド環境におけるファイルロックに関連しているので今はスルーする。
中心部分は "r = __sgetc(fp);"部分。まず "__sgetc()"がマクロかどうか判別する為、getchar.cのオブジェクトファイルgetchar.oのシンボル一覧に"__sgetc"があるか確認する。

$ nm /usr/lib/libc.a | less
getchar.o:
         U __sF
         U __srget
00000000 T getchar
00000038 T getchar_unlocked

このように、"__sgetc"がシンボル一覧に無い。ということは、これは関数として定義されているのではなく、"#define"によるマクロ定義であることが予想される。そこで、"/usr/src/include/"の下を愚直にgrepしてみる。

$ cd /usr/src/include
$ grep -r sgetc *
stdio.h:#define __sgetc(p) (--(p)->_r < 0 ? __srget(p) : (int)(*(p)->_p++))
stdio.h:#define getc(fp)        __sgetc(fp)
stdio.h:#define getc_unlocked(fp)       __sgetc(fp)

stdio.h中で定義されていることが分かった。

__sgetc(p)
=
--(p)->_r < 0 ? __srget(p) : (int)(*(p)->_p++))

ifブロックで分かりやすく整形する:

if ( --(p)->_r < 0 ) {
    __srget(p)
} else {
    (int)(*(p)->_p++))
}

"--"や"++"演算子の優先順位を確認。

man operator

→次のように整形出来る:

--(p->_r);
if ((p->_r) < 0) {
    return __srget(p);
} else {
    (p->_p)++;
    return (int)(*(p->_p));
}

ここで"p"は__sgetc(p)のpで、すなわちFILE構造体へのポインタ。
/usr/src/lib/libc/stdio/getchar.cを見直してみる:

getchar() {
    FILE *fp = stdin;
    /* ... */
    r = __sgetc(fp);

FILE構造体はstdio.h中で定義されている。

/* ... */
 
/* stdio buffers */
struct __sbuf {
    unsigned char *_base;
    int     _size;
};
 
/* ... */
 
typedef struct __sFILE {
    unsigned char *_p;      /* current position in (some) buffer */
    int     _r;             /* read space left for getc() */
    int     _w;             /* write space left for putc() */
    short   _flags;         /* flags, below; this FILE is free if 0 */
    short   _file;          /* fileno, if Unix descriptor, else -1 */
    struct  __sbuf _bf;     /* the buffer (at least 1 byte, if !NULL) */
    int     _lbfsize;       /* 0 or -_bf._size, for inline putc */
 
    /* operations */
    void    *_cookie;       /* cookie passed to io functions */
    int     (*_close) __P((void *));
    int     (*_read)  __P((void *, char *, int));
    fpos_t  (*_seek)  __P((void *, fpos_t, int));
    int     (*_write) __P((void *, const char *, int));
 
    /* ... */
 
} FILE;
 
/* ... */

"__sgetc()"のマクロに戻れば、ここまでで処理内容が以下のように判明した。

--(p->_r); /* 1文字取得するので、読み込みバッファのサイズをデクリメント */
if ((p->_r) < 0) { /* マイナスになってしまったら、読み込み処理へ */
    return __srget(p);
} else {
    (p->_p)++; /* バッファの現在位置をインクリメント */
    return (int)(*(p->_p)); /* 上でインクリメントした位置の値を返す */
}

"__srget(p)"か関数なのか、マクロなのかはlibc.aのシンボル一覧から判別可能。

$ nm /usr/lib/libc.a | less
...
rget.o:
         U __srefill
00000000 T __srget

"rget.o"で定義、つまりrget.c中で関数定義されていることが予想される。

$ locate rget.c
...
/usr/src/lib/libc/stdio/rget.c
...

ということで /usr/src/lib/libc/stdio/rget.c を覗いてみると、短いコードと分かりやすい説明コメント:

/*
 * Handle getc() when the buffer ran out:
 * Refill, then return the first character
 * in the newly-filled buffer.
 */
int
__srget(fp)
        FILE *fp;
{
 
        _DIAGASSERT(fp != NULL);
 
        _SET_ORIENTATION(fp, -1);
        if (__srefill(fp) == 0) {
                fp->_r--;
                return (*fp->_p++);
        }
        return (EOF);
}

"_SET_ORIENTATION(fp, -1)" というのは "/usr/src/lib/libc/stdio/wcio.h" 中で定義されていて、wchar関連のフラグを操作しているらしい。今回は関連が薄そうなので、スルーする。
あとは見たままで、"__srefill(fp)"でバッファを埋め直し、戻り値が0であれば "_r"メンバを(1文字読むので)1デクリメントし、"_p"ポインタをインクリメントした上でその場所のデータを返している。

"__srefill()"は、libc.aのシンボル一覧を見ると refill.c にある。

refill.o:
...
00000028 T __srefill
...


$ locate refill.c
/usr/src/lib/libc/stdio/refill.c

→ "/usr/src/lib/libc/stdio/refill.c"を見てみる。"__srefill(fp)"の主要部分だけ抜き出す。

/*
 * Refill a stdio buffer.
 * Return EOF on eof or error, 0 otherwise.
 */
int
__srefill(fp)
        FILE *fp;
{
    /* ... */
    fp->_r = 0; /* largely a convenience for callers */
    /* ... */
    if (fp->_bf._base == NULL)
        __smakebuf(fp);
    /* ... */
    fp->_p = fp->_bf._base;
    fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);
    if (fp->_r <= 0) {
        /* ... */
        return (EOF);
    }
    return (0);
}

"__smakebuf(fp)"は " /usr/src/lib/libc/stdio/makebuf.c" 中で定義されており、適切なバッファサイズを計算・malloc()で確保する。FILE構造体の"_bf"メンバは"__sbuf"構造体であり、"_base"メンバはバッファへのポインタ、"_size"メンバはバッファのサイズとなる。
よって以下のコードで、"__smakebuf(fp)"で確保されたバッファのアドレスが "_p" メンバにコピーされたことになる。

fp->_p = fp->_bf._base;

次のコードだが、FILE構造体の"_read"メンバに入っている関数ポインタを経由し、バッファのアドレスとバッファサイズを引数に渡して実際の読み込み関数を呼び出している。

fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);

FILE構造体の"_read"メンバに何が入るのかは、fopen(3)を調べてみるとよい。

$ locate fopen
/usr/src/lib/libc/stdio/fopen.c


FILE *
fopen(file, mode)
    const char *file;
    const char *mode;
{
    FILE *fp;
    int f;
    /* ... */
    if ((f = open(file, oflags, DEFFILEMODE)) < 0)
        goto release;
    /* ... */
    fp->_file = f;
    /* ... */
    fp->_cookie = fp;
    fp->_read = __sread;
/* ... */
}

"_file"メンバにはopen(2)で取得したファイル記述子が、"_cookie"メンバにはFILE構造体へのポインタ自身が入るようだ。
"__sread"をlibc.aのシンボル一覧から調べてみる(関数ポインタに代入されている以上は、マクロではあり得ない筈)。

$ nm /usr/lib/libc.a | less
...
stdio.o:
...
00000000 T __sread
...


$ locate stdio.c
...
/usr/src/lib/libc/stdio/stdio.c


/*
 * Small standard I/O/seek/close functions.
 * These maintain the `known seek offset' for seek optimisation.
 */
int
__sread(cookie, buf, n)
        void *cookie;
        char *buf;
        int n;
{
    FILE *fp = cookie;
    int ret;
 
    /* ... */
 
    ret = read(fp->_file, buf, (size_t)n);
 
    /* ... */
}

これでようやく、read(2)システムコールまで到達し、バッファが足りない場合にread(2)でデータを取得している事が判明した。

getchar()あるいはgetc()による読み込みでのバッファ周りをまとめ直してみる。

A. 読み込み済のバッファが充分ある場合
→ /usr/src/include/stdio.h:"__sgetc()"マクロまでで完結。

B. 読み込み済のバッファが空になった場合
→ /usr/src/include/stdio.h:"__sgetc()"マクロ
→ /usr/src/lib/libc/stdio/rget.c:"__srget()"関数
→ /usr/src/lib/libc/stdio/refill.c:"__srefill()"関数
→ FILE構造体の"_read"メンバ関数ポインタ経由
→ /usr/src/lib/libc/stdio/fopen.c:"fopen()"関数でセットされた"__sread"関数
→ /usr/src/lib/libc/stdio/stdio.c:"__sread()"関数
→ read(2)システムコール

今回のお題に対しては、ここまで。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-01-10 15:45:03
md5:b731b775a4740fa63819a359a50da938
sha1:daa5a2b8a0268402077fb13672301b75e4f575cf
コメント
コメントを投稿するにはログインして下さい。