Linux kernel v4.4에서 간단한 블록 장치 드라이버 만들어보기

패이지 캐시 관리를 위한 데이터 구조 struct address_space

struct address_space

페이지캐시를 관리하는 데이터 구조체는 struct address_space입니다. 이 구조체의 객체는 가장 먼저 inode의 i_mapping 필드에 저장됩니다. inode는 파일시스템에서 생성하겠지요. 우리가만든 mybrd 드라이버는 디스크를 등록하면 장치 파일이 생성됩니다. 이때 파일이 생성된다는 것은 곧 inode도 생성된다는 것입니다. 디스크를 등록하는 add_disk 함수의 어딘가에 inode를 생성하는 코드가 숨어있습니다. 그리고 디스크의 장치 파일의 inode->i_mapping 필드는 모두 def_blk_aops가 저장됩니다.

파일이 열릴때 open 시스템 콜에서 inode의 address_space 객체가 file의 f_mapping 필드에 inode의 i_mapping값을 저장합니다. 그리고나면 read/write 등 모든 시스템 콜에서 사용하는 file 객체에 address_space 객체가 사용되는 것이지요. inode는 파일이 열릴때만 참조되고, 그 이후로는 항상 file 객체만 사용됩니다. 그래서 같은 파일을 여러번 열 수 있고, 공유할 수 있는 것입니다.

struct address_space에서 가장 중요한 필드는 struct address_space_operations 입니다.

fs/block_dev.c 파일을 열어보면 아래와같이 file_operations 타입의 객체와 address_space_operations 타입의 객체가 정의되어있습니다.

const struct file_operations def_blk_fops = {
    .open		= blkdev_open,
	.release	= blkdev_close,
	.llseek		= block_llseek,
	.read_iter	= blkdev_read_iter,
	.write_iter	= blkdev_write_iter,
	.mmap		= blkdev_mmap,
	.fsync		= blkdev_fsync,
	.unlocked_ioctl	= block_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= compat_blkdev_ioctl,
#endif
	.splice_read	= generic_file_splice_read,
	.splice_write	= iter_file_splice_write,
};
static const struct address_space_operations def_blk_aops = {
    .readpage	= blkdev_readpage,
	.readpages	= blkdev_readpages,
	.writepage	= blkdev_writepage,
	.write_begin	= blkdev_write_begin,
	.write_end	= blkdev_write_end,
	.writepages	= generic_writepages,
	.releasepage	= blkdev_releasepage,
	.direct_IO	= blkdev_direct_IO,
	.is_dirty_writeback = buffer_check_dirty_writeback,
};

open 시스템콜은 방금 말한대로 단지 file 구조체의 객체만 생성합니다. file의 객체의 f_op 필드에는 def_blk_fops의 포인터가 저장되고, file->f_mapping->a_ops 필드에 def_blk_aops의 포인터가 저장되는 것입니다. 그 다음에 read/write 등 실제 데이터를 처리하는 시스템콜이 호출되면 먼저 file_operaions에서 해당 콜백 함수가 호출되고, 그 콜백 함수에서 다시 address_space_operations의 콜백 함수가 호출되는 것입니다.

예를 들면 read 시스템콜은 vfs_read 함수등을 거쳐서 def_blk_fops.read_iter 를 호출합니다. 그러면 blkdev_read_iter가 호출될거고, blkdev_read_iter는 어느순간에 file->f_mapping->a_ops->readpages를 호출합니다. 그러면 blkdev_readpages가 호출되고, blkdev_readpages는 디스크에 접근합니다.

address_space 객체의 host 필드에 inode의 포인터가 있고, inode에는 해당 파일이 블럭 장치 파일일 경우 struct block_device 에 대한 포인터를 저장하고 있으므로 결국 address_space 객체만 있으면 현재 페이지캐시가 어떤 블럭 장치의 데이터를 저장하고 있는지를 알 수 있습니다. 따라서 IO를 실행하기 위해 드라이버로 전달할 bio 객체를 만들 때도 mybrd가 생성한 request-queue에 bio를 전달할 수 있는 것이지요.

struct page의 mapping 과 index 필드

struct page의 mapping과 index 필드도 페이지 캐시를 위해 사용됩니다. mapping 필드는 address_space의 포인터가 저장됩니다. index 필드는 파일에서 현재 페이지의 offset를 저장합니다. index필드는 mybrd에서 만든 것과 동일하게 사용되는 것입니다. brd.c 패치 히스토리를 읽다보면 페이지캐시의 데이터 저장 방식을 따라서 만들었는데, Linus Torvalds의 아이디어였다라는 기록이 있습니다.

아직은 감이 안오실건데 코드를 보다보면 순서가 들어오실 겁니다.

문서를 단순화하기위해 버퍼헤드에 대한 내용은 생략하겠습니다. 블럭 장치의 블럭 크기는 페이지 크기와 같으므로 버퍼헤드가 별다른 역할을 하지 않기 때문입니다. 블럭 장치의 페이지캐시가 눈에 익어지면 좀더 복잡한 일반 파일의 페이지캐시도 좀더 쉽게 접근이 될것같습니다.

댓글

댓글 본문
작성자
비밀번호
graphittie 자세히 보기