fd를 얻는 순간과 I/O를 수행하는 순간
openat2() 계열은 요청 flag·pathname을 준비하고 fd 번호를 예약한 뒤 파일을 연다. fd_install()이 성공한 struct file *를 fdtable에 공개한다. 이후 read()/write()는 이미 공개된 fd를 다시 조회한다.
build_open_flags → getnameget_unused_fd_flags. 아직 file * 미공개do_filp_open 성공 뒤 fd_install(fd, f)put_unused_fd(fd), 오류 반환putname(tmp)는 fd 예약 뒤 어느 결과에서도 실행된다. fd_install 뒤에는 이 함수가 put_unused_fd로 되돌리지 않는다.
고정 원문 위치
두 대표 상자는 각각 v6.6 태그의 한 연속 구간이다. 다른 버전의 코드·상수를 섞지 않는다.
| 원문 | 이 글에서 읽는 범위 |
|---|---|
| fs/open.c:1406–1432 | do_sys_openat2()의 fd 예약/오류/공개 |
| fs/read_write.c:602–644 | 연속된 ksys_read()·ksys_write()와 read syscall wrapper |
| fs/file.c | fdtable 예약·설치의 내부 규칙 |
| include/linux/file.h | fd position helper/cleanup 계약 |
대표 코드
do_sys_openat2
static long do_sys_openat2(int dfd, const char __user *filename,
struct open_how *how)
{
struct open_flags op;
int fd = build_open_flags(how, &op);
struct filename *tmp;
if (fd)
return fd;
tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
fd = get_unused_fd_flags(how->flags);
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fd_install(fd, f);
}
}
putname(tmp);
return fd;
}읽는 법: fd 예약은 반환할 번호 확보이며 fd_install()이 file 객체의 공개 경계다. do_filp_open() 실패 때만 예약을 put_unused_fd()로 되돌린다.
ksys_read / ksys_write
ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos, *ppos = file_ppos(f.file);
if (ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_read(f.file, buf, count, ppos);
if (ret >= 0 && ppos)
f.file->f_pos = pos;
fdput_pos(f);
}
return ret;
}
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos, *ppos = file_ppos(f.file);
if (ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_write(f.file, buf, count, ppos);
if (ret >= 0 && ppos)
f.file->f_pos = pos;
fdput_pos(f);
}
return ret;
}읽는 법: 두 함수 모두 fd 조회 뒤 stream이면 위치 pointer를 넘기지 않는다. 그렇지 않으면 임시 pos를 전달하고 ret >= 0일 때 f_pos를 갱신한다.
open과 read/write의 조건 분기
do_sys_openat2()는 열린 struct file을 fdtable에 연결해 정수 fd를 돌려준다. read/write는 fd를 통해 그 file을 찾고 같은 열린 파일의 위치를 사용한다. filesystem/driver별 file_operations는 vfs_read/vfs_write 아래에서 선택된다.
fdget_pos(fd)와 f.file; 없으면 -EBADFfile_ppos(): stream은 NULL, 나머지는 임시 posvfs_read/write 뒤 ret ≥ 0 및 ppos일 때 f_pos 반영ppos == NULL. file의 f_pos를 이 경로에서 갱신하지 않는다.pos를 넘기고 성공 결과에서 file->f_pos = pos.두 함수가 명시적으로 fdput_pos(f)를 호출한다. I/O 오류는 앞서 성공한 open의 fd 예약을 되돌리는 분기가 아니다.
fd 번호, file 객체, 위치의 소유 관계
struct fileopen instance·mode·f_posdup() 등으로 여러 fd가 같은 열린 file 객체를 가리킬 수 있다. fd 번호 자체와 f_pos의 수명을 동일시하지 않는다.
| 대상 | 이 글의 경계 | 주의점 |
|---|---|---|
| fd 번호 | get_unused_fd_flags 예약 → fd_install 공개 | 열린 file이 없으면 예약 취소 |
struct file | do_filp_open 성공으로 획득 | 공개 후 fd lookup이 가리킴 |
f_pos | stream이 아닐 때 위치를 가진 file에 저장 | read/write의 ret ≥ 0이면 임시 pos 반영 |
filename *tmp | getname 성공 후 사용 | open 결과와 관계없이 putname |
버전별 분기와 정리
| 단계 | v6.6 | v6.18.37 | 실패·성공의 의미 |
|---|---|---|---|
| open flag 검사 | long do_sys_openat2; int fd = build_open_flags(...) | int do_sys_openat2; 별도 err와 unlikely(err) | 오류면 fd 예약 전 반환 |
| fd 예약·open | fd >= 0 검사 | likely(fd >= 0) 검사 | open 실패 때만 put_unused_fd |
| read/write fd lookup | fdget_pos / f.file | CLASS(fd_pos,f) / fd_empty·fd_file | 없으면 -EBADF |
| read/write cleanup | 명시적 fdput_pos(f) | class scope가 fdput_pos 수행 | file position 관련 정리; open rollback과 별개 |
| f_pos 갱신 | ret >= 0 && ppos | ret >= 0 && ppos | 오류 ret 또는 stream이면 copyback 없음 |
성공과 오류를 섞지 않는 기준
| 조건 | 코드에서 확인할 결과 |
|---|---|
| fd 예약 전 flag/getname 오류 | file 객체 공개 전 반환; 예약 취소 불필요 |
fd 예약 성공 뒤 do_filp_open 오류 | put_unused_fd(fd)로 이 예약만 해제 |
fd_install 성공 | fdtable에서 열린 file * 조회 가능 |
| read/write fd 조회 실패 | -EBADF; 이미 열린 다른 fd를 건드리지 않음 |
vfs_read/write 실패 | f_pos 임시값 미반영; open 성공 자체는 유지 |
FMODE_STREAM | 위치 pointer가 NULL, f_pos 경로와 분리 |
여기서는 callback/timer/IRQ teardown이 아니라 예약한 fd, 열린 file, 위치 copyback의 경계가 핵심이다.
읽기 중심의 관찰 범위
이 검토 작업은 보드에서 파일시스템 I/O를 실행하지 않았다. 공용 tracefs를 mount하거나 전역 event/filter/trace_pipe를 바꾸지 않고, 대상 설정에서 사용할 수 있는 관찰 지점을 먼저 확인한다.
| 자료 | 읽기 예시 | 알 수 있는 것과 한계 |
|---|---|---|
| tracefs event 목록 | cat /sys/kernel/tracing/available_events | 지원 event 이름 확인. fd 예약·설치 순서를 직접 증명하지는 않음 |
| syscall event 형식 | cat /sys/kernel/tracing/events/syscalls/sys_enter_openat/format | 해당 event가 있을 때 인자 형식 확인. open 내부 분기는 별도 계측 필요 |
| fdinfo | cat /proc/<pid>/fdinfo/<fd> | 현재 offset/flags의 스냅샷; 과거 실패 지점은 알 수 없음 |
| 원본 코드/빌드 | 이 페이지의 태그 고정 함수와 대상 커널 구성 대조 | CLASS(fd_pos) 등 버전별 구현 확인 |
실제 syscall 진입·복귀 추적이 필요하면 대상 프로세스에 범위를 제한한 도구나 독립 trace instance를 별도 계획한다. 공용 trace 상태를 바꾸는 명령을 일반 예제로 제시하지 않는다.
추가 확인 사항
- fd 번호 예약 후
do_filp_open()이 실패하면 어느 번호만 되돌리는가? fd_install()전후 중 어느 시점부터 fd 조회가 file 객체를 얻는가?- stream file과 일반 file에 같은 위치 pointer를 넘기는가?
- read/write가 음수 오류를 반환하면 왜
f_pos를 copyback하지 않는가? - 6.18.37의
CLASS(fd_pos)cleanup은 6.6의 어떤 명시적 호출에 해당하는가?