앞서서 FILE 구조체, fopen함수에 대해서 알아보았다.
이번에는 이어서, 실제로 파일을 읽고 쓰는 함수인 fread, fwrite 중 fread에 대해서 알아보도록 하자.
#define fread(p, m, n, s) _IO_fread (p, m, n, s)
size_t
_IO_fread (void *buf, size_t size, size_t count, FILE *fp)
{
size_t bytes_requested = size * count;
size_t bytes_read;
CHECK_FILE (fp, 0);
if (bytes_requested == 0)
return 0;
_IO_acquire_lock (fp);
bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
_IO_release_lock (fp);
return bytes_requested == bytes_read ? count : bytes_read / size;
}
libc_hidden_def (_IO_fread)
_IO_sgetn
으로 넘어가자.size_t
_IO_sgetn (FILE *fp, void *data, size_t n)
{
/* FIXME handle putback buffer here! */
return _IO_XSGETN (fp, data, n);
}
libc_hidden_def (_IO_sgetn)
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
#define _IO_JUMPS_FILE_plus(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)
#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
(*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
+ offsetof(TYPE, MEMBER)))
#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
...
_IO_file_xsgetn
이 호출된다._IO_file_xsgetn
size_t
_IO_file_xsgetn (FILE *fp, void *data, size_t n)
{
size_t want, have;
ssize_t count;
char *s = data;
want = n;
if (fp->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if (fp->_IO_save_base != NULL)
{
free (fp->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_doallocbuf (fp);
}
while (want > 0)
{
have = fp->_IO_read_end - fp->_IO_read_ptr;
if (want <= have)
{
memcpy (s, fp->_IO_read_ptr, want);
fp->_IO_read_ptr += want;
want = 0;
}
else
{
if (have > 0)
{
s = __mempcpy (s, fp->_IO_read_ptr, have);
want -= have;
fp->_IO_read_ptr += have;
}
/* Check for backup and repeat */
if (_IO_in_backup (fp))
{
_IO_switch_to_main_get_area (fp);
continue;
}
/* If we now want less than a buffer, underflow and repeat
the copy. Otherwise, _IO_SYSREAD directly to
the user buffer. */
if (fp->_IO_buf_base
&& want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if (__underflow (fp) == EOF)
break;
continue;
}
/* These must be set before the sysread as we might longjmp out
waiting for input. */
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);
/* Try to maintain alignment: read a whole number of blocks. */
count = want;
if (fp->_IO_buf_base)
{
size_t block_size = fp->_IO_buf_end - fp->_IO_buf_base;
if (block_size >= 128)
count -= want % block_size;
}
count = _IO_SYSREAD (fp, s, count);
if (count <= 0)
{
if (count == 0)
fp->_flags |= _IO_EOF_SEEN;
else
fp->_flags |= _IO_ERR_SEEN;
break;
}
s += count;
want -= count;
if (fp->_offset != _IO_pos_BAD)
_IO_pos_adjust (fp->_offset, count);
}
}
return n - want;
}
libc_hidden_def (_IO_file_xsgetn)
뭔가 정렬이 이상하게 안 되어있긴 하다. 코드 긁어올 때 뭔가 제대로 복사가 안 된듯.
우선 이 함수의 동작 원리부터 정리하고 가야될 것 같다.
__underflow
를 통해 다시 버퍼에 읽어올 값들을 채워 넣는다.하나 조심해야 할 것은, _IO_buf_...
멤버 변수와 _IO_read_...
멤버 변수는 구분해야 한다는 것이다.
예를 들어보자.
_IO_buf_base = 0x10000
이라고 하면, _IO_buf_end = 0x11000
이 된다._IO_read_end
값은 '읽어올 수 있는 최대 길이' 에 해당하여 0x10500의 값을 가지게 된다._IO_read_ptr
의 값은 0x10300이 된다.예시를 통해 봤었지만, 여기서 각 buffer를 가리키는 포인터들의 역할을 다시 한 번 더 정리해 보자.
- _IO_buf_base
: 할당된 버퍼의 시작지점
- _IO_buf_end
: 할당된 버퍼의 끝지점
- _IO_read_base
: read syscall을 통해서 미리 읽어온 데이터의 시작 지점 (이지만 사실 _IO_buf_base
와 동일)
- _IO_read_end
: read syscall을 통해서 미리 읽어온 데이터의 마지막 지점 (_IO_buf_end
와 구분!!)
- _IO_read_ptr
: 실제 fread를 통해서 지금까지 읽은 지점
세부적으로 들어가 보자.
_IO_read_end - _IO_read_ptr
)__underflow
를 호출해서 syscall을 통해 다시 버퍼에 값을 채워넣고, while문을 다시 돌아 나머지 값들을 읽어온다.__underflow
int
_IO_new_file_underflow (FILE *fp)
{
ssize_t count;
...(생략)...
fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;
fp->_IO_read_end = fp->_IO_buf_base;
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
= fp->_IO_buf_base;
count = _IO_SYSREAD (fp, fp->_IO_buf_base,
fp->_IO_buf_end - fp->_IO_buf_base);
if (count <= 0)
{
if (count == 0)
fp->_flags |= _IO_EOF_SEEN;
else
fp->_flags |= _IO_ERR_SEEN, count = 0;
}
fp->_IO_read_end += count;
if (count == 0)
{
/* If a stream is read to EOF, the calling application may switch active
handles. As a result, our offset cache would no longer be valid, so
unset it. */
fp->_offset = _IO_pos_BAD;
return EOF;
}
if (fp->_offset != _IO_pos_BAD)
_IO_pos_adjust (fp->_offset, count);
return *(unsigned char *) fp->_IO_read_ptr;
}
libc_hidden_ver (_IO_new_file_underflow, _IO_file_underflow)
보면 알겠지만, 포인터들을 모두 _IO_buf_base
로 초기화 시켜준 후, read syscall 호출, 마지막으로 read해준 길이만큼 _IO_read_end
를 옮겨주는 것을 확인할 수 있다.
이런 식으로 동작하게 된다.
참고 자료