File System · Linux v6.6 · source + diagram note

open/read/write

fd 번호의 예약과 file 객체 공개, 이후 read/write의 현재 위치 갱신을 원본 함수 경계에서 구분한다.

fd를 얻는 순간과 I/O를 수행하는 순간

openat2() 계열은 요청 flag·pathname을 준비하고 fd 번호를 예약한 뒤 파일을 연다. fd_install()이 성공한 struct file *를 fdtable에 공개한다. 이후 read()/write()는 이미 공개된 fd를 다시 조회한다.

그림 1. fd 예약 → file 획득 → 공개
요청 준비build_open_flags → getname
fd 번호 예약get_unused_fd_flags. 아직 file * 미공개
file 공개do_filp_open 성공 뒤 fd_install(fd, f)
flag/name 오류fd 예약 전 반환
fd 예약 실패음수 fd 반환, file open 없음
path/open 실패put_unused_fd(fd), 오류 반환

putname(tmp)는 fd 예약 뒤 어느 결과에서도 실행된다. fd_install 뒤에는 이 함수가 put_unused_fd로 되돌리지 않는다.

고정 원문 위치

두 대표 상자는 각각 v6.6 태그의 한 연속 구간이다. 다른 버전의 코드·상수를 섞지 않는다.

원문이 글에서 읽는 범위
fs/open.c:1406–1432do_sys_openat2()의 fd 예약/오류/공개
fs/read_write.c:602–644연속된 ksys_read()·ksys_write()와 read syscall wrapper
fs/file.cfdtable 예약·설치의 내부 규칙
include/linux/file.hfd position helper/cleanup 계약

대표 코드

do_sys_openat2

Linux v6.6 fs/open.c:1406–1432 · 연속 원문
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

Linux v6.6 fs/read_write.c:602–644 · 연속 원문
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 아래에서 선택된다.

그림 2. 공개된 fd를 사용하는 read/write 공통 분기
fd 조회fdget_pos(fd)와 f.file; 없으면 -EBADF
위치 pointer 결정file_ppos(): stream은 NULL, 나머지는 임시 pos
VFS I/Ovfs_read/write 뒤 ret ≥ 0 및 ppos일 때 f_pos 반영
FMODE_STREAMppos == NULL. file의 f_pos를 이 경로에서 갱신하지 않는다.
위치를 가진 file임시 pos를 넘기고 성공 결과에서 file->f_pos = pos.

두 함수가 명시적으로 fdput_pos(f)를 호출한다. I/O 오류는 앞서 성공한 open의 fd 예약을 되돌리는 분기가 아니다.

fd 번호, file 객체, 위치의 소유 관계

그림 3. 숫자 fd는 열린 file 객체를 가리킨다
프로세스 fdtable정수 fd slot
struct fileopen instance·mode·f_pos
path/inode/f_op이름 해석 결과와 I/O operation

dup() 등으로 여러 fd가 같은 열린 file 객체를 가리킬 수 있다. fd 번호 자체와 f_pos의 수명을 동일시하지 않는다.

대상이 글의 경계주의점
fd 번호get_unused_fd_flags 예약 → fd_install 공개열린 file이 없으면 예약 취소
struct filedo_filp_open 성공으로 획득공개 후 fd lookup이 가리킴
f_posstream이 아닐 때 위치를 가진 file에 저장read/write의 ret ≥ 0이면 임시 pos 반영
filename *tmpgetname 성공 후 사용open 결과와 관계없이 putname

버전별 분기와 정리

단계v6.6v6.18.37실패·성공의 의미
open flag 검사long do_sys_openat2; int fd = build_open_flags(...)int do_sys_openat2; 별도 err와 unlikely(err)오류면 fd 예약 전 반환
fd 예약·openfd >= 0 검사likely(fd >= 0) 검사open 실패 때만 put_unused_fd
read/write fd lookupfdget_pos / f.fileCLASS(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 && pposret >= 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 내부 분기는 별도 계측 필요
fdinfocat /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의 어떤 명시적 호출에 해당하는가?