fopen 함수의 소스코드를 보자.
# define fopen(fname, mode) _IO_new_fopen (fname, mode)
typedef로 _IO_new_fopen
이 연결되어 있다.
_IO_new_fopen
FILE *
_IO_new_fopen (const char *filename, const char *mode)
{
return __fopen_internal (filename, mode, 1);
}
내부적으로 __fopen_internal
을 호출한다.
__fopen_internal
FILE *
__fopen_internal (const char *filename, const char *mode, int is32)
{
struct locked_FILE
{
struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct _IO_wide_data wd;
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
if (new_f == NULL)
return NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_new_file_init_internal (&new_f->fp);
if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}
사실상 여기부터 제대로 된 함수가 시작된다.
우선, struct locked_FILE
구조체를 할당하는데, 멤버 변수로 struct _IO_FILE_plus, _IO_lock_t, struct _IO_wide_data
를 가진다. _IO_lock_t
와 struct _IO_wide_data
는 일단 넘기도록 하자.
struct _IO_FILE_plus
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
FILE
과 struct _IO_jump_t
로 이루어져 있다.
여기서 뭔가 눈에 익은 변수 이름이 보이는데, vtable이라는 녀석이다.
객체지향을 배우면 알게되는 class에서의 다형성을 구현하기 위한 vtable과 이름이 똑같다.
물론 이름만 같은 것이 아니라 그 기능을 구현하기 때문에 vtable이라는 이름이 붙게 되었다. 그러면 struct _IO_jump_t
에 대해서 알아보자.
struct _IO_jump_t
#define JUMP_FIELD(TYPE, NAME) TYPE NAME
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
보면 알겠지만, 함수 포인터들로 구성되어 있다. glibc 파일을 뒤적이다 보면 정말로 많은 매크로들이 즐비한데, 이 매크로를 통해서 각 구조체마다 해당하는 함수를 호출할 수 있게 된다.
그 중에 우리가 이번에 살펴볼 _IO_file_jumps
는 다음과 같이 생겼다.
#define JUMP_INIT(NAME, VALUE) VALUE
const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
JUMP_INIT(overflow, _IO_file_overflow),
JUMP_INIT(underflow, _IO_file_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_default_pbackfail),
JUMP_INIT(xsputn, _IO_file_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_new_file_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, _IO_new_file_sync),
JUMP_INIT(doallocate, _IO_file_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)
이 테이블을 바탕으로 내부적으로 어떤 함수가 호출되는지 알 수 있다.
다시 __fopen_internal
로 돌아가면,,
struct locked_FILE
은 내부적으로 FILE 구조체 뿐만 아니라 해당되는 vtable까지 포함한다는 것을 알 수 있다.
그 다음에 _IO_file_fopen
앞에 있는 나머지 함수/매크로들은 전부 init에 쓰이는 함수들이다. 이제 _IO_file_fopen
함수에 대해 알아보자.
_IO_new_file_fopen
_IO_file_fopen
을 호출한댔는데 갑자기 _IO_new_file_fopen
이 왜 나옴??libc_hidden_ver(_IO_new_file_fopen, _IO_file_fopen)
이라고 적혀있는데, _IO_new_file_fopen
은 외부로부터 숨긴다는 뜻이고, _IO_file_fopen
을 호출할 때에 대신 _IO_new_file_fopen
을 호출한다는 뜻이다.FILE *
_IO_new_file_fopen (FILE *fp, const char *filename, const char *mode,
int is32not64)
{
int oflags = 0, omode;
int read_write;
int oprot = 0666;
int i;
FILE *result;
const char *cs;
const char *last_recognized;
if (_IO_file_is_open (fp))
return 0;
switch (*mode)
{
case 'r':
omode = O_RDONLY;
read_write = _IO_NO_WRITES;
break;
case 'w':
omode = O_WRONLY;
oflags = O_CREAT|O_TRUNC;
read_write = _IO_NO_READS;
break;
case 'a':
omode = O_WRONLY;
oflags = O_CREAT|O_APPEND;
read_write = _IO_NO_READS|_IO_IS_APPENDING;
break;
default:
__set_errno (EINVAL);
return NULL;
}
last_recognized = mode;
for (i = 1; i < 7; ++i)
{
switch (*++mode)
{
case '\0':
break;
case '+':
omode = O_RDWR;
read_write &= _IO_IS_APPENDING;
last_recognized = mode;
continue;
case 'x':
oflags |= O_EXCL;
last_recognized = mode;
continue;
case 'b':
last_recognized = mode;
continue;
case 'm':
fp->_flags2 |= _IO_FLAGS2_MMAP;
continue;
case 'c':
fp->_flags2 |= _IO_FLAGS2_NOTCANCEL;
continue;
case 'e':
oflags |= O_CLOEXEC;
fp->_flags2 |= _IO_FLAGS2_CLOEXEC;
continue;
default:
/* Ignore. */
continue;
}
break;
}
result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
is32not64);
... (생략) ...
return result;
}
libc_hidden_ver (_IO_new_file_fopen, _IO_file_fopen)
앞부분은 전부다 open mode와 관련된 부분이다. open 함수를 호출할 때 flag, mode값에 들어갈 값들을 세팅해 준다.
mode 세팅이 끝나고 나면 _IO_file_open
함수를 호출하여 내부적으로 open 시스템 콜을 호출하게 된다.
_IO_file_open
FILE *
_IO_file_open (FILE *fp, const char *filename, int posix_mode, int prot,
int read_write, int is32not64)
{
int fdesc;
if (__glibc_unlikely (fp->_flags2 & _IO_FLAGS2_NOTCANCEL))
fdesc = __open_nocancel (filename,
posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
else
fdesc = __open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
if (fdesc < 0)
return NULL;
fp->_fileno = fdesc;
_IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
/* For append mode, send the file offset to the end of the file. Don't
update the offset cache though, since the file handle is not active. */
if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
== (_IO_IS_APPENDING | _IO_NO_READS))
{
off64_t new_pos = _IO_SYSSEEK (fp, 0, _IO_seek_end);
if (new_pos == _IO_pos_BAD && errno != ESPIPE)
{
__close_nocancel (fdesc);
return NULL;
}
}
_IO_link_in ((struct _IO_FILE_plus *) fp);
return fp;
}
libc_hidden_def (_IO_file_open)
순서 : open systemcall을 호출 -> (appending mode라면 파일 offset을 끝으로 옮겨줌, 내부적으로 커널 버퍼를 이용할 것으로 추측) -> _IO_link_in
을 호출하여 linked list에 연결해 준다. (_IO_link_in
코드를 보면 어렵지 않게 알 수 있음)
이러고 나서 다시 __fopen_internal
로 돌아가서, open이 성공 시 리턴, 실패 시 unlink, free 후 종료하게 된다.
너무 복잡하긴 한듯.. 여러 아키텍쳐 및 세팅을 고려해야 하기 때문에 매크로가 굉장히 많고, 함수 자체도 중간에 #ifdef
가 굉장히 많이 있다.
큰 흐름에 필요가 없는 thread부분(lock-unlock), mmap, wide는 생략하였다.
참고 자료