mind-tech

主にテクノロジーや啓発系、副業などのテーマ中心

NetBSDのソースを読んでいく getchar編

テーマ「ファイル入出力に使われるfreadなどの内部で使われる低レベルシステムコールreadとバッファ管理されてファイルの入出力を極力させないgetcharの違いを調べる。」

まずgetcharを見ていく。んがmanを見る限りgetcharはgetcにstdinを指定したものと同じであり、getcはfgetcと同じ機能でマクロで書いてある。



なのでまずはfgetcを読む。

#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)fgetc.c 8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: fgetc.c,v 1.9 1999/09/20 04:39:26 lukem Exp $");
#endif
#endif /* LIBC_SCCS and not lint */

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include "reentrant.h"

int
fgetc(fp)
FILE *fp;
{
int r;

_DIAGASSERT(fp != NULL);

FLOCKFILE(fp);
r = __sgetc(fp);
FUNLOCKFILE(fp);
return r;
}



FLOCKFILEはgrepなどで探したりしても、NetBSDではthread対応のためのコードであり将来のサポートを見越しての記述らしいので無視。

大事なのは本質を追うこと。全部解読しようとするなんてナンセンス。

__sgetcを見ていく。

$ locate __sgetc.c

見当たらない。manでも見てみる。

$ man __sgetc.c
No manual entry for __sgetc.c

見当たらない。includeの方を探してみる。

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

stdioでマクロで定義されてある。この定義で"--"と->の強弱の確認方法は

$ man operator

よってここでは以下のように整形できる。

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

ここで"p"はFILE構造体へのポインタ。_rは現時点では分からない。

/usr/src/lib/libc/stdio/getchar.c にはstdio.hをincludeするよう書いてあり、stdio.hの内部にはFILE構造体の記述がある。

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

stdio.hに_rが書いてある

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 extension */
struct __sbuf _ext;

/* separate buffer for long sequences of ungetc() */
unsigned char *_up; /* saved _p when _p is doing ungetc data */
int _ur; /* saved _r when _r is counting ungetc data */

/* tricks to meet minimum requirements even when malloc() fails */
unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */
unsigned char _nbuf[1]; /* guarantee a getc() buffer */

/* separate buffer for fgetln() when line crosses buffer boundary */
struct __sbuf _lb; /* buffer for fgetln() */

/* Unix stdio files get aligned to block boundaries on fseek() */
int _blksize; /* stat.st_blksize (may be != _bf._size) */
fpos_t _offset; /* current lseek offset */
} FILE;

__BEGIN_DECLS
extern FILE __sF[];
__END_DECLS


つまり、_pは現在の文字、_rは読み出し用に既に読み込んである文字総数。_fileが開いているファイルへの記述子。_bf._baseがバッファへのポインタ。_pは通常この中身を指す。

よってfgetcの本質である__sgetc()は_rが一以上、つまりバッファに読み込んである文字がまだあれば__srget()の返り値を返す。この"__s"は上記から分かるようにstdioの下位関数なのでrgetを見てみる。

#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)rget.c 8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: rget.c,v 1.10 2001/12/07 11:47:43 yamt Exp $");
#endif
#endif /* LIBC_SCCS and not lint */

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include "local.h"

/*
* 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);
}

ソースを見る限り__srefillで文字を読み込んで、バッファを減らし、その中から一文字返しているっぽい。

__srefillを見てみる。

$ locate refill
/usr/share/emacs/22.1/lisp/textmodes/refill.el.gz
/usr/share/emacs/22.1/lisp/textmodes/refill.elc
/usr/src/lib/libc/stdio/refill.c

まず、全部のっける。

__srefill(fp)
FILE *fp;
{

_DIAGASSERT(fp != NULL);

/* make sure stdio is set up */
if (!__sdidinit)
__sinit();

fp->_r = 0; /* largely a convenience for callers */

/* SysV does not make this test; take it out for compatibility */
if (fp->_flags & __SEOF)
return (EOF);

/* if not already reading, have to be reading and writing */
if ((fp->_flags & __SRD) == 0) {
if ((fp->_flags & __SRW) == 0) {
errno = EBADF;
return (EOF);
}
/* switch to reading */
if (fp->_flags & __SWR) {
if (__sflush(fp))
return (EOF);
fp->_flags &= ~__SWR;
fp->_w = 0;
fp->_lbfsize = 0;
}
fp->_flags |= __SRD;
} else {
/*
* We were reading. If there is an ungetc buffer,
* we must have been reading from that. Drop it,
* restoring the previous buffer (if any). If there
* is anything in that buffer, return.
*/
if (HASUB(fp)) {
FREEUB(fp);
if ((fp->_r = fp->_ur) != 0) {
fp->_p = fp->_up;
return (0);
}
}
}

if (fp->_bf._base == NULL)
__smakebuf(fp);

/*
* Before reading from a line buffered or unbuffered file,
* flush all line buffered output files, per the ANSI C
* standard.
*/
if (fp->_flags & (__SLBF|__SNBF)) {
rwlock_rdlock(&__sfp_lock);
(void) _fwalk(lflush);
rwlock_unlock(&__sfp_lock);
}
fp->_p = fp->_bf._base;
fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);
fp->_flags &= ~__SMOD; /* buffer contents are again pristine */
if (fp->_r <= 0) {
if (fp->_r == 0)
fp->_flags |= __SEOF;
else {
fp->_r = 0;
fp->_flags |= __SERR;
}
return (EOF);
}
return (0);
}


この真ん中あたりでまずバッファを確保してる。

        if (fp->_bf._base == NULL)
__smakebuf(fp);

ほんでもってここが難しい。
        fp->_p = fp->_bf._base;
fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);
fp->_flags &= ~__SMOD; /* buffer contents are again pristine */
if (fp->_r <= 0) {
if (fp->_r == 0)
fp->_flags |= __SEOF;
else {
fp->_r = 0;
fp->_flags |= __SERR;
}
return (EOF);
}
return (0);
}

ここ
       fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);
これはポインタによる関数呼び出し。関数名とは実は関数本体へのポインタで"*"をつけると関数そのものになる。例えば↓

hogehoge()と(*hogehoge)()は同じ意味。

よって、ここでやっている事は_read()の返す値を_rに代入している。

_read()の返り値はfread()のmanを見るとSEE ALSOの部分にread(2)とあったのでシステムコールreadのmanを見てみる。



つまり何バイト読み込んだかを_rに代入している。これがバッファ処理の仕組み。

あらかじめバッファにファイルからデータをある程度読み込んでおき、データがバッファにあるうちはそこから文字を取る。空になればまたファイルから読み込む。

これでファイルへのアクセス回数を減らしている。

Copyright ©mind-teck All rights reserved..
🔒プライバシーポリシー