Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[리눅스커널][가상파일시스템] 파일 객체란 13. Virtual Filesystem

파일 객체
대부분 유저는 파일을 생성하고 읽고 쓰기 위한 용도로 씁니다. 이 때 파일 동작에 대한 상세 규칙과 속성은 파일 객체에서 확인할 수 있습니다.

만약 라즈베리파이에서 2개 Geany프로그램에서 1개 파일을 열 수 있습니다. 이 때 몇 개 파일 객체가 생성될까요? 2개 파일 객체는 각각 프로세스 별로 생성합니다. 파일 객체는 이렇게 파일을 오픈하고 읽고 쓰는 상황에서 프로세스가 파일을 관리하기 위해 생성합니다.

파일 객체에 대한 상세 내용은 struct file 구조체에서 확인할 수 있습니다. 다음 소절에서 struct file 구조체에 대해 알아보겠습니다.

struc file 구조체 분석

파일 객체는 다음 해더 파일에 정의돼 있습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/include/linux/fs.h]
struct file {
union {
struct llist_node fu_llist;
struct rcu_head  fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;

struct file 멤버 중 중요한 항목을 살펴봅시다.
타입 멤버 설명
struct path f_path 가상 파일 시스템 마운트 정보와 덴트리
struct inode *f_inode 아이노드 
const struct file_operations *f_op 파일 오퍼레이션
spinlock_t f_lock 파일 객체 구조체에서 쓰는 락
atomic_long_t  f_count 파일 객체가 참조된 횟수
unsigned int  f_flags 파일을 오픈할 때 설정한 플래그
fmode_t    f_mode 파일 접근 시 모드
loff_t  f_pos 파일을 처리할 때 오프셋
void *private_data 디바이스 드라이버에서 쓰는 핸들

파일 객체 멤버 중 많이 쓰이는 f_flags와 f_mode에 대해서 짚어 보겠습니다.

f_flags

유저 공간에서 파일을 열거나 생성할 때 지정한 옵션 정보를 저장합니다.

f_flags 멤버에 저장하는 옵션은 다음 해더 파일에 정의돼 있습니다.
https://elixir.bootlin.com/linux/v4.14.70/source/include/uapi/asm-generic/fcntl.h

다음 테이블에서 각 파일 오픈 시 옵션을 확인할 수 있습니다.
옵션 특징
O_RDONLY 읽기 전용
O_WRONLY 쓰기 전용
O_RDWR 읽기와 쓰기 모두 가능
O_CREAT 지정한 파일이 없으면 파일을 생성
O_EXCL O_CREAT와 함께 설정하면 생성할 파일이 이미 있을 때 
open() 함수가 실행되지 않아 이전 파일 보존할 수 있음
O_TRUNC 기존의 파일 내용을 모두 삭제
O_APPEND 파일에 추가하여 쓰기 동작이 수행되게 open 후에 쓰기 포인터가 파일의 끝에 위치함
O_NOCITTY 열기 대상이 터미널일 경우, 이 터미널이 플로그램의 제어 터미널로 할당 안함
O_NONBLOCK 읽을 내용이 없을 때 읽을 내용이 있을 때까지 기다리지 않고 바로 복귀
O_SYNC 쓰기 요청을 한 후 쓴 내용이 디스크에 기록될 때 까지 기다림

가상 파일시스템과 개별 파일시스템에서 f_flags 플래그에 지정된 옵션에 따라 파일을 제어합니다.

f_mode

파일을 오픈한 이력과 파일에 대한 세부 동작 상태를 저장하는 플래그입니다.
가상 파일시스템이나 각 파일시스템에서 커널 세부 동작을 중 파일 처리 상태를 식별해 제어하려는 목적으로 씁니다.
다음 테이블에서 f_mode 멤버에 저장하는 세부 파일 처리 옵션을 확인할 수 있습니다.
옵션 설명
FMODE_READ 읽기를 위해 파일을 오픈한 상태
FMODE_WRITE 쓰기를 위해 파일을 오픈한 상태
FMODE_LSEEK 파일 포인터 위치를 바꾸는 동작
FMODE_PREAD pread를 통해 파일 접근
FMODE_PWRITE  pwrite 시스템 콜로 파일 접근
FMODE_EXEC 파일 실행을 위해 오픈됨 
FMODE_NDELAY  O_NDELAY 옵션으로 파일을 오픈한 상태
FMODE_EXCL  O_EXCL 옵션으로 파일을 오픈한 상태
FMODE_WRITE_IOCTL  ioctl 시스템 콜로 파일이 오픈된 상태

다음 소절에서는 파일 객체 함수 오퍼레이션에 대해 살펴보겠습니다.

파일 객체 함수 오퍼레이션

가상 파일 시스템에서 파일 시스템 별로 파일을 열고 쓰고 읽는 함수 포인터 테이블을 지원합니다. 이 정보를 파일 오퍼레이션이라고 하며 다음 해더 파일에 정의돼 있습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/include/linux/fs.h]
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
...
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
u64);
} __randomize_layout;

위 함수 오퍼레이션 중 가장 많이 쓰는 멤버들에 대해 소개합니다.

llseek
loff_t (*llseek) (struct file *file, loff_t offset, int whence)

파일 포인터를 offset 값으로 갱신합니다.

read
ssize_t (*read) (struct file *file, char __user *buf,
    size_t count, loff_t *offset);

파일 오프셋(offset) 위치에서 count 바이트만큼 읽습니다. 이 동작을 수행하면서 파일 오프셋인 *offset은 업데이트됩니다.

write
ssize_t (*write) (struct file *file, const char __user buf*, 
    size_t count, loff_t offset*);

파일의 오프셋(*offset) 위치에 count 바이트만큼 buf에 있는 데이터를 써줍니다.

poll
unsigned int (*poll) (struct file *, struct poll_table_struct *);
파일 동작을 점검하고 파일에 대한 동작이 발생하기 전까지 휴면 상태에 진입합니다.

ioctl
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);


각각 멤버들은 포인터로 정의돼 있으며 함수 포인터로 파일 시스템 마다 지정된 서로 다른 함수들을 호출합니다.

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


Reference(가상 파일시스템)

가상 파일시스템 소개
파일 객체
파일 객체 함수 오퍼레이션 동작
프로세스는 파일객체 자료구조를 어떻게 관리할까?
슈퍼블록 객체
아이노드 객체
덴트리 객체
가상 파일시스템 디버깅


핑백

덧글

  • 살벌한 눈의여왕 2019/10/03 20:57 # 답글

    ioctl()에 관한 질문이 있습니다.
    각 파일시스템마다 ioctol()을 다르게 구현할 텐데, user application은 내가 open한 파일이 어느 파일시스템에 있는 파일인지 모르지 않습니까?
    따라서 애플리케이션이 날리는 ioctol()이 어떻게 동작될지가 undefined 인데, ioctl()의 실용성이 의심이 됩니다.

    좋은 글 감사드립니다.
  • AustinKim 2019/10/04 16:57 #

    댓글에 답글을 달다가 길어져 글을 새로 올렸습니다.
    http://rousalome.egloos.com/10004382

    가상 파일시스템은 조금 어려운 내용인 것 같습니다.
댓글 입력 영역