이 코드는 어떤 문제를 푸나요?
사용자 프로그램은 정수 파일 디스크립터로 read를 부르지만 커널 내부에서는 열린 파일을 나타내는 struct file을 찾습니다. open은 이 연결을 만들고 read와 write는 파일의 연산 표를 통해 실제 구현으로 이어집니다. 여기서는 vfs_read 전체를 보며 권한, 사용자 버퍼, 요청 범위, 파일별 함수를 어떤 순서로 확인하는지 설명합니다.
읽을 범위: v6.6 · fs/read_write.c · vfs_read 450–479행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
fd와 struct file
fd는 프로세스의 파일 디스크립터 표에 쓰는 정수 인덱스이고 struct file은 열린 파일 상태입니다. 두 프로세스의 같은 fd 숫자가 같은 파일을 뜻하지는 않습니다.
사용자 포인터
__user는 사용자 주소 공간의 포인터임을 표시합니다. access_ok의 범위 검사를 통과해도 실제 메모리 접근이 언제나 성공하는 것은 아니므로 후속 복사 과정의 오류 처리도 필요합니다.
file_operations
파일 종류마다 read 또는 read_iter 같은 함수를 제공하는 연산 표입니다. 일반 파일·장치·파이프가 같은 시스템 호출을 쓰면서 다르게 동작할 수 있는 이유입니다.
부분 읽기
요청한 길이보다 적은 양수가 반환될 수도 있습니다. 0은 읽은 데이터가 없음을, 음수는 커널 내부 오류 코드를 나타냅니다.
처음 읽을 때
예시 fd 3에서 100바이트를 읽는다고 가정하더라도 반환값은 100으로 고정되지 않습니다. 이 함수는 요청을 검증하고 해당 파일 구현의 실제 결과를 돌려줍니다.
더 깊이 살펴볼 때
FMODE_READ와 FMODE_CAN_READ가 각각 열기 권한과 읽기 구현 가능성을 검사한다는 점을 구분하십시오. read_iter를 동기식 호출에 연결하는 new_sync_read가 위치와 반복자를 어떻게 구성하는지도 이어서 볼 수 있습니다.
그림으로 보는 변화

1. 열린 파일을 찾습니다
시스템 호출 경로에서 fd를 struct file로 연결합니다. 아래 vfs_read는 그 포인터를 받은 뒤 시작합니다.
화살표는 정수 핸들에서 커널 객체를 찾는 참조 관계입니다.
2. 읽기 요청을 검증합니다
열기 모드, 읽기 지원, 사용자 버퍼 범위와 파일 위치를 검사합니다.
분기 화살표는 오류 반환 또는 다음 검사로의 진행입니다.
3. 파일 구현을 호출합니다
read 또는 read_iter에 연결한 경로로 읽고 바이트 수와 계측 정보를 갱신합니다.
화살표는 파일별 함수 호출이며 모든 파일이 디스크에 접근함을 뜻하지 않습니다.
vfs_read를 한 줄씩 읽기
줄 번호는 v6.6 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_READ))
return -EINVAL;
if (unlikely(!access_ok(buf, count)))
return -EFAULT;
ret = rw_verify_area(READ, file, pos, count);
if (ret)
return ret;
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT;
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
else if (file->f_op->read_iter)
ret = new_sync_read(file, buf, count, pos);
else
ret = -EINVAL;
if (ret > 0) {
fsnotify_access(file);
add_rchar(current, ret);
}
inc_syscr(current);
return ret;
}ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)열린 파일, 사용자 목적지 버퍼, 요청 길이와 파일 위치 포인터를 받습니다. fd 숫자를 직접 받는 함수는 아닙니다.
ssize_t ret;실제 읽은 양 또는 음수 오류를 저장합니다.
if (!(file->f_mode & FMODE_READ))이 열린 파일이 읽기 모드를 가지고 있는지 비트로 확인합니다.
return -EBADF;읽기 권한으로 열린 파일이 아니므로 잘못된 파일 디스크립터 사용이라는 -EBADF를 반환합니다.
if (!(file->f_mode & FMODE_CAN_READ))파일에 읽기 가능한 연산이 마련되어 있는지 검사합니다.
return -EINVAL;읽기 연산을 제공하지 않는 조건은 -EINVAL로 반환합니다.
if (unlikely(!access_ok(buf, count)))사용자 버퍼와 길이가 접근 가능한 주소 범위인지 검사합니다. 실제 복사 성공 보장은 아닙니다.
return -EFAULT;잘못된 사용자 주소 범위이면 -EFAULT를 반환합니다.
ret = rw_verify_area(READ, file, pos, count);읽기 방향, 파일, 위치와 길이에 대해 VFS의 공통 범위·보안 검사를 수행합니다.
if (ret)검사에서 오류가 나왔는지 확인합니다.
return ret;검사 오류를 그대로 반환하고 파일 읽기를 실행하지 않습니다.
if (count > MAX_RW_COUNT)한 번의 읽기에 허용하는 최대 크기를 초과하는지 확인합니다.
count = MAX_RW_COUNT;처리할 요청 크기를 MAX_RW_COUNT로 제한합니다. 나머지는 호출자가 후속 읽기로 처리할 수 있습니다.
if (file->f_op->read)이 파일이 전통적인 read 콜백을 제공하는지 먼저 확인합니다.
ret = file->f_op->read(file, buf, count, pos);파일별 read 구현에 동일한 파일·사용자 버퍼·길이·위치를 넘깁니다.
else if (file->f_op->read_iter)read가 없으면 반복자 기반 read_iter 구현이 있는지 확인합니다.
ret = new_sync_read(file, buf, count, pos);동기식 read 요청을 반복자 기반 호출로 구성해 실행합니다.
else두 가지 읽기 구현을 모두 제공하지 않는 경우입니다.
ret = -EINVAL;적합한 연산이 없으므로 오류를 결과로 지정합니다.
if (ret > 0) {실제로 양수 바이트를 읽은 경우에만 데이터 접근 관련 후처리를 합니다.
fsnotify_access(file);파일 접근 감시자에게 읽기 사실을 알립니다.
add_rchar(current, ret);현재 태스크의 읽은 문자·바이트 계측에 실제 반환량을 더합니다.
inc_syscr(current);이 경로의 읽기 호출 횟수 계측을 늘립니다. 실제 읽은 바이트 계수와는 다릅니다.
return ret;파일 구현의 결과 또는 검증 오류를 호출자에게 반환합니다.
함께 생각해 볼 질문
읽기 전 access_ok만 통과하면 page fault가 없나요?
아닙니다. 주소 범위를 확인하는 검사이며 실제 메모리 접근의 성공이나 상주를 보장하지 않습니다.
왜 FMODE_READ와 FMODE_CAN_READ를 둘 다 보나요?
읽기 권한으로 연 파일인지와 해당 파일이 읽기 동작을 제공할 수 있는지는 다른 조건입니다.
read_iter만 있으면 read 시스템 호출을 못 쓰나요?
new_sync_read가 반복자 기반 구현을 동기식 read 요청에 연결합니다.
출처와 읽은 범위
Linux stable v6.6 · fs/read_write.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
