Stat (시스템 호출)
1. 개요
`stat`은 POSIX 운영 체제에서 파일의 상태 정보를 얻는 데 사용되는 시스템 호출 함수군이다. 이 함수들은 `sys/stat.h` 헤더 파일에 정의되어 있으며, `stat()`, `fstat()`, `lstat()` 함수를 포함한다. `stat()` 함수는 파일 이름을, `fstat()`은 파일 디스크립터를, `lstat()`은 파일 이름과 심볼릭 링크의 속성을 반환하는 데 사용된다. `stat` 구조체는 파일의 크기, 접근 권한, 소유자, 시간 정보 등 다양한 속성을 담고 있으며, 대용량 파일 지원을 위해 64비트 버전의 함수(stat64(), lstat64(), fstat64())도 제공된다. 파일 접근 시간(`atime`) 업데이트 정책은 성능 저하 논란이 있었으며, 리눅스 커널은 `relatime`, `noatime`, `lazytime` 등의 마운트 옵션을 통해 이를 개선했다.
| 설명: "stat() 시스템 호출은 지정된 파일에 대한 정보를 검색하는 데 사용되는 유닉스 시스템 호출이다." | |
| 설명: "stat 구조체의 내용은 파일 유형, 크기, 권한, 소유자, 타임스탬프(액세스, 수정, 변경), 하드 링크 수, i-노드 번호와 같은 파일의 다양한 속성을 설명한다." |
| 정의 위치 | ` |
|---|---|
| 시스템 | 유닉스 계열 |
| 반환 타입 | 정수형 (int) |
| 성공 시 반환 값 | 0 |
| 실패 시 반환 값 | -1 |
| 함수 원형 | int stat(const char *path, struct stat *buf); |
|---|---|
| 설명 | 주어진 경로 이름(path)에 해당하는 파일의 상태 정보를 stat 구조체(buf)에 저장한다. |
| 함수 원형 | int fstat(int fd, struct stat *buf); |
| 설명 | 열린 파일 디스크립터(fd)에 해당하는 파일의 상태 정보를 stat 구조체(buf)에 저장한다. |
| 함수 원형 | int lstat(const char *path, struct stat *buf); |
| 설명 | stat()과 유사하지만, 심볼릭 링크 자체의 정보를 반환한다. stat()은 심볼릭 링크가 가리키는 파일의 정보를 반환한다. |
| st_dev | 파일이 위치한 장치 ID |
|---|---|
| st_ino | i-노드 번호 |
| st_mode | 파일 유형 및 모드 (권한) |
| st_nlink | 하드 링크 수 |
| st_uid | 소유자 사용자 ID |
| st_gid | 소유자 그룹 ID |
| st_rdev | 장치 ID (특수 파일인 경우) |
| st_size | 파일 크기 (바이트) |
| st_atime | 마지막 접근 시간 |
| st_mtime | 마지막 수정 시간 |
| st_ctime | 마지막 상태 변경 시간 |
| st_blksize | 파일 시스템 블록 크기 |
| st_blocks | 할당된 블록 수 |
| 관련 명령어 | ls 명령어는 파일 정보를 표시하는 데 사용되며, 내부적으로 stat() 시스템 호출을 사용한다. |
|---|---|
| 예시 | ls -lu (마지막 접근 시간을 기준으로 정렬), ls -l (자세한 정보 표시), ls -lc (마지막 상태 변경 시간을 기준으로 정렬) |
-
시스템 호출 -
포크 (시스템 호출)
포크는 유닉스 계열 운영체제에서 새로운 프로세스를 생성하는 시스템 호출로, 호출 시 부모 프로세스의 복사본인 자식 프로세스가 생성되어 파일 서술자를 상속받으며, 유닉스 철학의 핵심 개념으로 필터 개발 및 파이프라인 구축에 용이하고 vfork, rfork, clone 등 다양한 변종이 존재한다. -
시스템 호출 -
Chroot
`chroot`는 유닉스 계열 운영체제에서 프로세스의 루트 디렉토리를 변경하는 시스템 호출 및 환경으로, 다양한 목적으로 사용되지만 보안 취약점과 제한 사항을 가지며, `fakechroot`는 root 권한 없이 유사한 환경을 제공한다. -
C POSIX 라이브러리 -
포크 (시스템 호출)
포크는 유닉스 계열 운영체제에서 새로운 프로세스를 생성하는 시스템 호출로, 호출 시 부모 프로세스의 복사본인 자식 프로세스가 생성되어 파일 서술자를 상속받으며, 유닉스 철학의 핵심 개념으로 필터 개발 및 파이프라인 구축에 용이하고 vfork, rfork, clone 등 다양한 변종이 존재한다. -
C POSIX 라이브러리 -
POSIX 스레드
POSIX 스레드는 C 프로그래밍 언어에서 스레드를 생성, 관리, 동기화하는 데 사용되는 함수들의 모음으로, pthread.h 헤더 파일과 스레드 라이브러리로 구현된다. -
POSIX -
오픈 그룹
오픈 그룹은 다양한 기술 공급업체, 구매자, 정부 기관을 포함하는 국제 컨소시엄으로, 표준 개발, 인증 프로그램 운영, TOGAF와 같은 IT 아키텍처 프레임워크 제공 및 UNIX 상표 권리 보유를 통해 개방형 IT 표준 개발 및 보급에 기여한다. -
POSIX -
본 셸
본 셸은 스티븐 본이 개발하여 1979년 유닉스에 포함된 셸로, 셸 스크립트, 제어 흐름, 변수, 시그널 처리 등의 기능을 제공하며 여러 셸에 영향을 주었고 현재도 널리 사용된다.
2. stat() 함수군
POSIX C 라이브러리의 `sys/stat.h` 헤더 파일은 `stat()`, `fstat()`, `lstat()` 함수를 정의한다. 이 함수들은 파일의 속성을 반환하는 `struct stat` 구조체에 대한 포인터를 인수로 받는다. 함수들은 성공하면 0을 반환하고, 오류가 발생하면 -1을 반환하며 errno가 적절하게 설정된다.
`stat()`과 `lstat()` 함수는 파일 이름을 인수로 받는다. 심볼릭 링크인 경우, `stat()`은 링크가 가리키는 파일의 속성을, `lstat()`은 링크 자체의 속성을 반환한다. `fstat()` 함수는 파일 디스크립터를 인수로 받는다.
대용량 파일 지원을 위해 `stat64()`, `lstat64()`, `fstat64()` 함수가 추가되었다. 이 함수들은 64비트 파일 크기를 지원하는 `struct stat64` 구조체를 사용하며, 2GiB 이상(최대 8EiB)의 파일에서 작동할 수 있다. `_FILE_OFFSET_BITS` C 매크로가 64로 정의된 경우, 64비트 함수를 원래 이름으로 사용할 수 있다.
2.1. stat() 함수
`stat()` 함수는 파일 이름을 인수로 받아 해당 파일의 정보를 `stat` 구조체에 저장한다. 심볼릭 링크의 경우, 링크가 가리키는 원본 파일의 정보를 반환한다. 함수는 성공하면 0을 반환하고, 오류가 발생하면 -1을 반환하며 errno가 적절하게 설정된다.
함수는 다음과 같이 정의된다.
```c
int stat(const char *filename, struct stat *buf);
2.2. lstat() 함수
`lstat()` 함수는 `stat()` 함수와 유사하게 파일 이름을 인수로 받지만, 파일이 심볼릭 링크인 경우 링크의 대상 파일이 아닌 링크 자체의 속성을 반환한다. `stat()` 함수는 심볼릭 링크의 경우 링크가 가리키는 최종 대상의 속성을 반환한다는 점에서 차이가 있다.
함수 정의는 다음과 같다.
```c
int lstat(const char *filename, struct stat *buf);
```
POSIX C 라이브러리의 `sys/stat.h` 헤더에 선언되어 있으며, POSIX 및 기타 유닉스 계열 운영 체제에서 사용 가능하다. 함수는 성공하면 0, 오류가 발생하면 -1을 반환하며, 오류 시 errno가 설정된다.
대용량 파일 지원을 위해 확장된 `lstat64()` 함수도 제공된다. 이 함수는 64비트 파일 크기를 지원하는 `struct stat64` 구조체를 사용하여 2GiB 이상의 파일(최대 8EiB)에서도 작동할 수 있다. `_FILE_OFFSET_BITS` C 매크로가 64로 정의되면 `lstat64()` 함수를 `lstat()` 이름으로 사용할 수 있다.
2.3. fstat() 함수
`fstat()` 함수는 파일 디스크립터를 인수로 받아 해당 파일의 정보를 `stat` 구조체에 저장한다. 함수는 성공하면 0을 반환하고, 오류가 발생하면 -1을 반환하며 errno가 적절하게 설정된다.
함수 정의는 다음과 같다:
int fstat(int filedesc, struct stat *buf);
POSIX C 라이브러리의 헤더 `sys/stat.h`는 `fstat()` 함수를 선언한다. 이 함수는 파일 속성을 반환하는 데 사용되는 `struct stat` 버퍼 인자에 대한 포인터를 인수로 받는다.
대용량 파일 지원을 위해 확장된 `fstat64()` 함수는 64비트 유형으로 파일 크기를 나타내는 `struct stat64` 구조체에 속성을 반환한다. 이를 통해 2GiB 이상(최대 8EiB)의 파일에서 작동할 수 있다. `_FILE_OFFSET_BITS` C 매크로가 64로 정의된 경우, `fstat64()` 함수는 `fstat()` 이름으로 사용할 수 있다.
2.4. 대용량 파일 지원
64비트 시스템에서는 `stat64()`, `lstat64()`, `fstat64()` 함수를 사용하여 2GiB 이상의 대용량 파일 정보를 처리할 수 있다. 이 함수들은 파일 크기를 64비트 유형으로 나타내는 `struct stat64` 구조에 속성을 반환하며, 최대 8EiB 크기의 파일까지 지원한다. `_FILE_OFFSET_BITS` C 매크로가 64로 정의된 경우, 원래 이름(stat, lstat, fstat)으로 64비트 함수를 사용할 수 있다.
3. stat 구조체
`stat` 구조체는 파일 정보를 담는 자료 구조이며, `sys/stat.h` 헤더 파일에 정의되어 있다. 구현에 따라 추가 필드를 정의할 수도 있다.
POSIX.1은 `st_rdev`, `st_blocks`, `st_blksize` 멤버를 요구하지 않지만, 이들은 Single Unix Specification의 XSI 옵션에 정의되어 있다.
2008년 이전 POSIX.1 표준에서는 시간 관련 필드를 `st_atime`, `st_mtime`, `st_ctime` (time_t영어 형식)으로 정의했다. 2008년 이후 표준에서는 `st_atim`, `st_mtim`, `st_ctim`으로 변경하고, 더 높은 해상도의 시간 단위를 제공하는 `timespec` 구조체 형식을 사용한다. 호환성을 위해 이전 이름은 `struct timespec`의 `tv_sec` 멤버를 사용하여 정의할 수 있다. (예: `st_atime`은 `st_atim.tv_sec`).
`stat` 구조체는 다음과 같이 정의된다.
```c
struct stat {
mode_t st_mode;
ino_t st_ino;
dev_t st_dev;
dev_t st_rdev;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
struct timespec st_atim;
struct timespec st_mtim;
struct timespec st_ctim;
blksize_t st_blksize;
blkcnt_t st_blocks;
};
```
`st_mode` 필드는 비트 필드로, 파일 접근 모드와 특수 파일 유형을 나타낸다.
3.1. 주요 멤버
`stat` 구조체는 파일의 정보를 담고 있으며, `sys/stat.h` 헤더 파일에 정의되어 있다. 구현에 따라 추가 필드가 정의될 수도 있다. POSIX.1은 `st_rdev`, `st_blocks`, `st_blksize` 멤버를 요구하지 않지만, 이 필드들은 Single Unix Specification의 XSI 옵션의 일부로 정의되어 있다.
2008년 이전 POSIX.1 표준에서는 시간 관련 필드가 `st_atime`, `st_mtime`, `st_ctime`으로 정의되었고, `time_t` 형식을 사용했다. 하지만 2008년 표준 이후, 더 높은 해상도의 시간 단위를 제공하기 위해 `timespec` 구조체를 사용하며, 필드 이름도 `st_atim`, `st_mtim`, `st_ctim`으로 변경되었다. 호환성을 위해 이전 이름(예: `st_atime`)은 `st_atim.tv_sec`과 같이 정의될 수 있다.
`struct stat` 구조체의 주요 멤버는 다음과 같다.
| 멤버 | 설명 | 비고 |
|---|---|---|
| `st_dev` | 파일을 포함하는 장치의 식별자 | |
| `st_ino` | 아이노드 번호 | |
| `st_mode` | 파일 종류 및 접근 권한 | 파일 모드를 나타내는 비트 필드 |
| `st_nlink` | 하드 링크 수 | |
| `st_uid` | 파일 소유자의 사용자 식별자 | |
| `st_gid` | 파일 소유 그룹의 그룹 식별자 | |
| `st_rdev` | 장치 파일의 경우 장치 식별자 | |
| `st_size` | 파일 크기 (바이트 단위) | |
| `st_atime` | ||
| `st_mtime` | ||
| `st_ctime` | ||
| `st_blksize` | 파일 시스템 I/O 블록 크기 | 시스템 및 파일 시스템 유형에 따라 달라질 수 있음 |
| `st_blocks` | 파일에 할당된 블록 수 | 일반적으로 512바이트의 배수 |
3.2. 시간 정보 (st_atime, st_mtime, st_ctime)
POSIX.1 표준의 이전 버전에서는 시간 관련 필드를 `st_atime`, `st_mtime`, `st_ctime`으로 정의하고, time_t영어 형식을 사용했다. 2008년 표준 이후, 이 필드들은 `st_atim`, `st_mtim`, `st_ctim`으로 이름이 변경되었고, `timespec` 구조체 형식을 사용하도록 변경되었다. `timespec` 구조체는 더 높은 해상도의 시간 단위를 제공하기 때문이다. 호환성을 위해 구현에서는 `struct timespec`의 `tv_sec` 멤버를 사용하여 이전 이름을 정의할 수 있다. 예를 들어 `st_atime`은 `st_atim.tv_sec`으로 정의할 수 있다.
* `st_atime`: 파일에 마지막으로 접근한 시간을 나타낸다. 파일을 읽거나 실행하는 등의 작업이 발생하면 이 시간이 업데이트된다.
* `st_mtime`: 파일의 내용이 마지막으로 수정된 시간을 나타낸다. 파일의 내용이 변경되면 이 시간이 업데이트된다.
* `st_ctime`: 파일의 상태 정보(소유권, 권한, 링크 수 등)가 마지막으로 변경된 시간을 나타낸다. 파일 내용이 수정될 때도 `st_ctime`은 함께 업데이트된다.
`st_atime` 및 `st_mtime`과 달리 `st_ctime`은 [[touch (command)영어 유틸리티에서 사용되는 `utime()` 함수를 사용하여 임의의 값으로 설정할 수 없다. 대신, `utime()` 함수가 사용되거나 파일 접근으로 인해 `st_atime`이 업데이트되는 경우, 또는 inode에 대한 다른 변경 사항이 발생하면 `st_ctime` 값은 현재 시간으로 설정된다.
4. atime 업데이트 정책 (Criticism of atime)
파일을 읽기만 해도 해당 파일의 `atime`이 변경되어 디스크 쓰기 작업이 필요하게 되는 것은, 읽기 전용 파일 시스템과 맞지 않아 비판받아왔다. 파일 시스템 캐시는 이러한 작업을 캐시 플러시당 한 번의 디스크 쓰기로 줄일 수 있지만, 여전히 성능 저하의 원인이 될 수 있다.
리눅스 커널 개발자인 잉고 몰나르는 2007년에 atime의 개념과 성능 저하를 공개적으로 비판했으며, 2009년에는 `relatime` 마운트 옵션이 기본값으로 설정되어 이러한 문제를 해결했다. `relatime`은 대부분의 목적에 충분한 성능을 제공하며, 널리 논의되어 중요한 응용 프로그램을 손상시키지 않는다.
리눅스는 `atime` 업데이트 정책을 개선하기 위해 다양한 마운트 옵션을 제공한다. (자세한 내용은 하위 섹션 '리눅스 마운트 옵션' 참고)
현재 리눅스, macOS, 솔라리스, FreeBSD, NetBSD 버전은 /etc/fstab에서 `noatime` 마운트 옵션을 지원하며, 이 옵션은 atime 필드가 업데이트되지 않도록 한다. atime 업데이트를 끄면 POSIX를 준수하지 않게 되며, "새로운 메일" 알림을 위한 mbox 기반 애플리케이션 및 tmpwatch와 같은 일부 파일 사용 감시 유틸리티가 손상될 수 있다.
OpenBSD의 `noatime` 옵션은 리눅스 `relatime`과 유사하게 동작한다.
4.1. 리눅스 마운트 옵션
리눅스 커널은 fstab에서 지정할 수 있는 다음과 같은 마운트 옵션을 지원한다.
| 옵션 | 설명 |
|---|---|
| `strictatime` | 항상 atime을 업데이트하여 POSIX 표준을 따른다. (과거에는 `atime`으로 불렸으며, 2.6.30 이전에는 기본값이었음) |
| `relatime` | 특정 상황에서만 atime을 업데이트한다. 이전 atime이 mtime 또는 ctime보다 오래되었거나, 24시간 이상 지난 경우에 업데이트한다. (2.6.20에 도입, 2.6.30부터 기본값) |
| `nodiratime` | 디렉터리의 atime은 업데이트하지 않지만, 다른 파일의 atime은 업데이트한다. |
| `noatime` | 파일이나 디렉터리의 atime을 업데이트하지 않는다. (`nodiratime`을 포함한다) 최고의 성능을 제공하지만, POSIX 호환성이 가장 낮다. |
| `lazytime` | 메모리 상에서 atime 업데이트를 수행하고, 특정 상황에 따라 디스크에 반영한다. POSIX 호환성을 유지하면서 성능을 향상시킨다. |
`lazytime` 옵션은 2015년 4월 12일에 출시된 리눅스 커널 메인라인 버전 4.0에서 도입되었다. 이 옵션을 사용하면 POSIX 스타일의 atime 업데이트를 메모리 내에서 수행하고, 다음 조건에 해당될 때 디스크에 반영한다.
* 동일한 파일에 대한 일부 시간 관련 I/O 작업
* sync 시스템 호출 실행
* 파일의 메모리 내 아이노드가 파일 시스템 캐시에서 제거되기 전
또한 atime 수정이 디스크에 반영되지 않은 상태로 유지될 수 있는 기간을 구성할 수 있다.
5. 시간 정밀도 (Time granularity)
time_t영어는 1초 단위의 시간 정보를 제공한다.
일부 파일 시스템은 더 세밀한 정밀도를 제공한다. 솔라리스 2.1은 1992년에 UFS에서 마이크로초 해상도를, ZFS에서 나노초 해상도를 도입했다.
리눅스 커널 2.5.48 이상에서는 stat 구조체가 세 가지 파일 타임스탬프 필드에 대해 나노초 해상도를 지원한다. 이들은 stat 구조체의 추가 필드로 노출된다.
FAT 파일 시스템의 생성 시간 해상도는 10밀리초이고, 쓰기 시간 해상도는 2초이며, 접근 시간 해상도는 하루이므로 접근 날짜 역할을 한다.
6. 예제
c
#include
#include
#include
#include
#include
#include
#include
int
main(int argc, char *argv[])
{
struct stat sb;
struct passwd *pwuser;
struct group *grpnam;
if (argc < 2)
{
fprintf(stderr, "사용법: %s: 파일 ...\n", argv[0]);
exit(EXIT_FAILURE);
}
for (int i = 1; i < argc; i++)
{
if (-1 == stat(argv[i], &sb))
{
perror("stat()");
exit(EXIT_FAILURE);
}
if (NULL == (pwuser = getpwuid(sb.st_uid)))
{
perror("getpwuid()");
exit(EXIT_FAILURE);
}
if (NULL == (grpnam = getgrgid(sb.st_gid)))
{
perror("getgrgid()");
exit(EXIT_FAILURE);
}
printf("%s:\n", argv[i]);
printf("\tinode: %u\n", sb.st_ino);
printf("\towner: %u (%s)\n", sb.st_uid, pwuser->pw_name);
printf("\tgroup: %u (%s)\n", sb.st_gid, grpnam->gr_name);
printf("\tperms: %o\n", sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
printf("\tlinks: %d\n", sb.st_nlink);
printf("\tsize: %ld\n", sb.st_size); /* %lld를 사용할 수 있습니다 */
printf("\tatime: %s", ctime(&sb.st_atim.tv_sec));
printf("\tmtime: %s", ctime(&sb.st_mtim.tv_sec));
printf("\tctime: %s", ctime(&sb.st_ctim.tv_sec));
printf("\n");
}
return 0;
}
```
다음은 C 언어를 사용한 `stat()` 함수 사용 예제이다.