운영체제 기본/Linux/파일 시스템 콜/파일 오프셋과 열린 파일의 수명: 두 판 사이의 차이
잔글 관리자님이 운영체제 기본/Linux의 파일 시스템 콜/파일 위치와 수명 문서를 운영체제 기본/Linux/파일 시스템 콜/파일 위치와 수명 문서로 이동했습니다: Linux 각론 제목을 계층형 경로로 통일 |
Linux 계층형 경로에 맞춰 문서 링크 갱신 |
||
| 1번째 줄: | 1번째 줄: | ||
{{이전 다음 문서 | {{이전 다음 문서 | ||
|이전=← 이전 문서<br />[[운영체제 기본/ | |이전=← 이전 문서<br />[[운영체제 기본/Linux/파일 시스템 콜/read와 write|read와 write]] | ||
|목차=[[운영체제 기본/ | |목차=[[운영체제 기본/Linux/파일 시스템 콜|파일 시스템 콜 목차]] | ||
|다음=마지막 문서 | |다음=마지막 문서 | ||
}} | }} | ||
| 52번째 줄: | 52번째 줄: | ||
</pre> | </pre> | ||
복제한 fd가 남아 있거나 진행 중인 입출력·메모리 매핑이 참조를 보유하면 객체가 즉시 사라지지 않는다. 예를 들어 파일을 매핑한 뒤 원래 fd를 닫아도 매핑은 유지되며, <code>munmap()</code>은 별개의 주소 공간 해제다. 자세한 과정은 [[운영체제 기본/ | 복제한 fd가 남아 있거나 진행 중인 입출력·메모리 매핑이 참조를 보유하면 객체가 즉시 사라지지 않는다. 예를 들어 파일을 매핑한 뒤 원래 fd를 닫아도 매핑은 유지되며, <code>munmap()</code>은 별개의 주소 공간 해제다. 자세한 과정은 [[운영체제 기본/Linux/메모리 매핑|Linux의 메모리 매핑]]에서 다룬다. | ||
fd 슬롯은 닫기 후반부보다 먼저 해제된다. 따라서 닫기에서 오류가 났다고 같은 번호로 <code>close()</code>를 무작정 재시도하면, 다른 스레드가 그 번호로 새로 연 파일을 닫을 수 있다. <code>close()</code>는 파일 이름을 디렉터리에서 제거하는 <code>unlink()</code>와도 다른 연산이다.<ref name="man-close">[https://man7.org/linux/man-pages/man2/close.2.html Linux man-pages, close(2)].</ref><ref name="open"/><ref name="file"/> | fd 슬롯은 닫기 후반부보다 먼저 해제된다. 따라서 닫기에서 오류가 났다고 같은 번호로 <code>close()</code>를 무작정 재시도하면, 다른 스레드가 그 번호로 새로 연 파일을 닫을 수 있다. <code>close()</code>는 파일 이름을 디렉터리에서 제거하는 <code>unlink()</code>와도 다른 연산이다.<ref name="man-close">[https://man7.org/linux/man-pages/man2/close.2.html Linux man-pages, close(2)].</ref><ref name="open"/><ref name="file"/> | ||
| 60번째 줄: | 60번째 줄: | ||
일반적인 버퍼드 <code>write()</code>의 성공, <code>close()</code>의 성공, 저장 장치에 대한 동기화 완료는 서로 다른 조건이다. <code>fsync(fd)</code>는 <code>do_fsync()</code> → <code>vfs_fsync()</code> → <code>vfs_fsync_range()</code>를 거쳐 파일의 <code>f_op->fsync</code>를 호출한다. 파일 데이터와 필요한 메타데이터의 반영을 기다리며, 동기화 과정에서 발견된 오류도 호출자가 확인해야 한다.<ref name="sync">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/sync.c?h=v6.12 Linux v6.12 fs/sync.c], do_fsync, vfs_fsync, vfs_fsync_range.</ref> | 일반적인 버퍼드 <code>write()</code>의 성공, <code>close()</code>의 성공, 저장 장치에 대한 동기화 완료는 서로 다른 조건이다. <code>fsync(fd)</code>는 <code>do_fsync()</code> → <code>vfs_fsync()</code> → <code>vfs_fsync_range()</code>를 거쳐 파일의 <code>f_op->fsync</code>를 호출한다. 파일 데이터와 필요한 메타데이터의 반영을 기다리며, 동기화 과정에서 발견된 오류도 호출자가 확인해야 한다.<ref name="sync">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/sync.c?h=v6.12 Linux v6.12 fs/sync.c], do_fsync, vfs_fsync, vfs_fsync_range.</ref> | ||
<code>fdatasync()</code>도 데이터를 동기화하되, 메타데이터는 수정된 데이터를 다시 읽는 데 필요한 항목으로 한정한다. 새 파일 이름이나 이름 변경까지 장애 후 보존해야 한다면 관련 디렉터리의 동기화도 따로 고려해야 한다.<ref name="man-fsync">[https://man7.org/linux/man-pages/man2/fsync.2.html Linux man-pages, fsync(2)].</ref> 여기서의 구분은 ‘버퍼를 전달했다’, ‘fd를 닫았다’, ‘저장 완료를 요청하고 확인했다’가 같은 사건이 아니라는 점이다. 페이지 캐시와 라이트백의 관계는 [[운영체제 기본/ | <code>fdatasync()</code>도 데이터를 동기화하되, 메타데이터는 수정된 데이터를 다시 읽는 데 필요한 항목으로 한정한다. 새 파일 이름이나 이름 변경까지 장애 후 보존해야 한다면 관련 디렉터리의 동기화도 따로 고려해야 한다.<ref name="man-fsync">[https://man7.org/linux/man-pages/man2/fsync.2.html Linux man-pages, fsync(2)].</ref> 여기서의 구분은 ‘버퍼를 전달했다’, ‘fd를 닫았다’, ‘저장 완료를 요청하고 확인했다’가 같은 사건이 아니라는 점이다. 페이지 캐시와 라이트백의 관계는 [[운영체제 기본/Linux/파일시스템/페이지 캐시|페이지 캐시]]에서 이어진다. | ||
== 참고 문헌 == | == 참고 문헌 == | ||
| 66번째 줄: | 66번째 줄: | ||
{{이전 다음 문서 | {{이전 다음 문서 | ||
|이전=← 이전 문서<br />[[운영체제 기본/ | |이전=← 이전 문서<br />[[운영체제 기본/Linux/파일 시스템 콜/read와 write|read와 write]] | ||
|목차=[[운영체제 기본/ | |목차=[[운영체제 기본/Linux/파일 시스템 콜|파일 시스템 콜 목차]] | ||
|다음=마지막 문서 | |다음=마지막 문서 | ||
}} | }} | ||
{{운영체제 기본 둘러보기}} | {{운영체제 기본 둘러보기}} | ||
2026년 11월 1일 (일) 02:23 판
파일 위치는 fd 번호 자체가 아니라 열린 파일 객체의 상태다. 이 구분은 lseek(), pread()·pwrite(), dup(), close()의 동작을 함께 설명한다. 기준은 Linux v6.12다.
파일 위치의 변경
lseek(fd, offset, whence)는 열린 파일의 다음 입출력 위치를 바꾸고 새 위치를 반환한다. ksys_lseek()는 fdget_pos()로 파일을 얻고, vfs_llseek()에서 탐색 가능 여부를 확인한 뒤 f_op->llseek를 호출한다. 일반 파일은 SEEK_SET, SEEK_CUR, SEEK_END를 통해 시작점·현재 위치·끝을 기준으로 위치를 정할 수 있다. 파이프 같은 탐색 불가능한 대상은 ESPIPE로 실패한다.[1]
위치를 바꾸는 것만으로 데이터를 읽거나 파일을 늘리는 것은 아니다. 파일 끝을 넘는 위치로 이동한 뒤 실제로 쓰면 파일 크기가 늘어날 수 있고, 그 사이에 쓰지 않은 구간은 hole로 표현될 수 있다. 논리적 파일 범위와 실제 블록 할당의 구분은 파일과 입출력에서 다룬다.
명시적 위치의 입출력
pread(fd, buf, count, offset)와 pwrite(fd, buf, count, offset)는 호출 인자로 위치를 전달한다. ksys_pread64()·ksys_pwrite64()도 vfs_read()·vfs_write()를 사용하지만, file->f_pos가 아니라 전달받은 지역 위치 변수를 넘기고 공유 위치를 갱신하지 않는다.[1]
f_pos = 10 pread(fd, buf, 4, 0) -> 파일의 0~3번째 바이트, f_pos는 10 유지 read(fd, buf, 4) -> 파일의 10번째부터 읽기, f_pos 진행
이 방식은 여러 스레드가 서로 다른 구간을 다룰 때 lseek()와 read() 두 호출 사이의 위치 변경 경쟁을 피한다. 파일 내용 자체에 대한 동시 읽기·쓰기까지 원자적으로 만드는 것은 아니다. Linux의 O_APPEND 파일에서는 pwrite()도 지정 위치 대신 끝에 쓸 수 있으므로, 지정 위치 쓰기 예제는 O_APPEND 없이 연 파일을 기준으로 한다.[2]
파일 디스크립터 복제와 위치 공유
dup(fd)는 새로운 fd 슬롯에 같은 struct file의 참조를 설치한다. 파일을 다시 열어 새 상태를 만드는 것이 아니므로 위치와 파일 상태 플래그를 공유한다. 일반적인 fork() 뒤 부모와 자식의 대응 fd도 같은 열린 파일 객체를 참조한다. 반면 같은 경로를 별도로 두 번 열면 각각의 파일 위치를 가진다.[3]
fd 3 ----+ fd 3 -> struct file A (f_pos = 0)
+-> struct file (f_pos) |
fd 4 ----+ 같은 inode
dup(3) |
fd 4 -> struct file B (f_pos = 0)
같은 경로를 별도로 openat
파일에 ABCDE가 있고 fd 3을 복제해 fd 4를 얻었다면, fd 3에서 한 바이트를 읽은 뒤 fd 4에서 한 바이트를 읽으면 각각 A, B를 얻는다. fdget_pos()는 필요한 경우 파일 위치 락을 잡아 개별 입출력의 위치 갱신을 조정한다. 그러나 여러 시스템 콜을 묶은 작업 전체를 자동으로 하나의 임계구역으로 만들어 주지는 않는다.[3]
닫기와 마지막 참조
Linux v6.12의 close 시스템 콜은 먼저 file_close_fd()로 슬롯을 비우고 fd 번호를 다시 사용할 수 있게 한다. 이어 filp_flush()로 닫기 처리를 수행하고, __fput_sync()로 열린 파일에 대한 참조를 내려놓는다. 마지막 참조라면 __fput()이 f_op->release 등의 최종 정리를 수행한다.[4][5]
close(3)
-> fd[3] = NULL, 번호 재사용 가능
-> 닫기 처리와 참조 감소
├─ 다른 참조 있음: struct file 유지
└─ 마지막 참조: release 등 최종 정리
복제한 fd가 남아 있거나 진행 중인 입출력·메모리 매핑이 참조를 보유하면 객체가 즉시 사라지지 않는다. 예를 들어 파일을 매핑한 뒤 원래 fd를 닫아도 매핑은 유지되며, munmap()은 별개의 주소 공간 해제다. 자세한 과정은 Linux의 메모리 매핑에서 다룬다.
fd 슬롯은 닫기 후반부보다 먼저 해제된다. 따라서 닫기에서 오류가 났다고 같은 번호로 close()를 무작정 재시도하면, 다른 스레드가 그 번호로 새로 연 파일을 닫을 수 있다. close()는 파일 이름을 디렉터리에서 제거하는 unlink()와도 다른 연산이다.[6][4][3]
쓰기 완료와 저장 동기화
일반적인 버퍼드 write()의 성공, close()의 성공, 저장 장치에 대한 동기화 완료는 서로 다른 조건이다. fsync(fd)는 do_fsync() → vfs_fsync() → vfs_fsync_range()를 거쳐 파일의 f_op->fsync를 호출한다. 파일 데이터와 필요한 메타데이터의 반영을 기다리며, 동기화 과정에서 발견된 오류도 호출자가 확인해야 한다.[7]
fdatasync()도 데이터를 동기화하되, 메타데이터는 수정된 데이터를 다시 읽는 데 필요한 항목으로 한정한다. 새 파일 이름이나 이름 변경까지 장애 후 보존해야 한다면 관련 디렉터리의 동기화도 따로 고려해야 한다.[8] 여기서의 구분은 ‘버퍼를 전달했다’, ‘fd를 닫았다’, ‘저장 완료를 요청하고 확인했다’가 같은 사건이 아니라는 점이다. 페이지 캐시와 라이트백의 관계는 페이지 캐시에서 이어진다.
참고 문헌
- ↑ 1.0 1.1 Linux v6.12 fs/read_write.c, ksys_lseek, vfs_llseek, ksys_pread64, ksys_pwrite64.
- ↑ Linux man-pages, pread(2).
- ↑ 3.0 3.1 3.2 Linux v6.12 fs/file.c, dup, fget_raw, fd_install, dup_fd, fdget_pos.
- ↑ 4.0 4.1 Linux v6.12 fs/open.c, close, filp_flush.
- ↑ Linux v6.12 fs/file_table.c, __fput_sync, __fput.
- ↑ Linux man-pages, close(2).
- ↑ Linux v6.12 fs/sync.c, do_fsync, vfs_fsync, vfs_fsync_range.
- ↑ Linux man-pages, fsync(2).