Block Device Driver in Linux 경희대학교 컴퓨터공학과 조 진 성
주요 내용 주요내용 리눅스에서 디바이스 드라이버 개념이해 블록 디바이스 이해 램디스크 구현
디바이스 드라이버 제작 과정 Define objective of applications using device Study Hardware manual 장치 인터페이스 이해 (Registers) 인터럽트 발생의 원인 인지(vector number) 입출력 경로를 알아야 함 Understand interface to related utilities Device setup Device 등록 및 해제 ioctl command Design to adapt to future changes Module
리눅스 디바이스 드라이버 리눅스에서의 디바이스 Device Driver의 종류 Linux에서 Device는 특별한 하나의 파일처럼 취급되고, access가 가능함. 사용자는 File operation을 적용할 수 있음 각 디바이스는 Major number와 Minor number를 가짐 Device Driver의 종류 문자 디바이스 드라이버 블록 디바이스 드라이버 네트워크 디바이스 드라이버
디바이스 드라이버 구조 리눅스 시스템 구조 상의 블록 디바이스 드라이버 Application System Call Interface VFS Network Subsystem Buffer Cache Network D/D Block D/D Char Device Driver Device Interface Hardware Application area Kernel area
Block Device(블록 디바이스) Block device 특징 리눅스에서의 Block device random access 가능 블록 단위의 입출력이 가능한 장치 버퍼캐쉬에 의한 내부 장치 표현 파일 시스템에 의해 mount 되어 관리되는 장치 디스크, Ram Disk, CD-ROM 등 리눅스에서의 Block device brw------- 1 root floppy 2, 0 May 6 1998 fd0 brw-rw---- 1 root disk 3, 0 May 6 1998 hda brw-rw---- 1 root disk 3, 1 May 6 1998 hda1 brw-rw---- 1 root disk 8, 0 May 6 1998 sda brw-rw---- 1 root disk 8, 1 May 6 1998 sda1 fd* : Floppy disk Hd* : Hard disk sda : SCSI disk 파일 관련 정보 중 첫 문자인 b는 block device를 의미
블록 디바이스 드라이버의 데이터 구조 Block device의 커널 데이터 구조 Block Device Driver의 데이터 구조 Major number - include/linux/major.h 최대 255로 정의 : MAX_BLKDEV로 정의 blkdevs[ ] – fs/block_dev.c 커널은 해당 구조체 내의 index 값으로 block device driver의 major number를 사용 해당 driver가 제공하는 함수들도 찾음 ~/include/linux/fs.h static struct { const char *name; struct block_device_operations *bdops; } blkdevs[MAX_BLKDEV]; /*MAX_BLKDEV = 255*/
블록 디바이스 드라이버의 등록 Block device의 등록 ~/include/linux/fs.h int register_blkdev(unsigned int major, const char *name, struct block_device_operations *bdops); { if (major == 0){ for (major = MAX_BLKDEV-1; major > 0; major--) { if (blkdevs[major].bdops == NULL) { blkdevs[major].name = name; blkdevs[major].bdops = bdops; return major; } } return –EBUSY; if (major >= MAX_BLKDEV) return –EINVAL; if (blkdevs[major].bdops && blkdevs[major].bdops != bdops) return –EBUSY; return 0;
블록 디바이스 드라이버의 해제 Block device의 해제 int unregister_blkdev(unsigned int major, const char *name); { if (major >= MAX_BLKDEV) return –EINVAL; if (!blkdevs[major].bdops) if ( strcmp(blkdevs[major].name, name)) blkdevs[major].name = NULL; blkdevs[major].bdops = NULL; return 0; }
블록 디바이스 드라이버 연산(1) Block device의 연산 struct block_device_operations = { int (*open) (struct inode*, struct file*);/*open*/ int (*release) (struct inode*, struct file*, unsigned, unsigned long);/*close*/ int (*ioctl) (struct inode*, struct file*, unsigned, unsigned long);/*I/O control*/ int (*check_media_change) (kdev_t);/*제거 가능 미디어의 물리적 변화 판별*/ int (*revalidate) (kdev_t); /*버퍼와 캐쉬의 관리*/ };
블록 디바이스 드라이버 연산(2) 기본 블록 디바이스 연산 블록 디바이스 드라이버를 설정 하지 않았을 때 기본 연산으로 사용 struct file_operations def_blk_fops= { open : blkdev_open, /*장치 열기*/ release : blkdev_close, /*장치 해제*/ llseek : block_llseek, /*읽기와 쓰기 위치 변경*/ read : block_read, /*블록 디바이스 읽기*/ write : block_write, /*블록 디바이스 쓰기 */ fsync : block_fsync, /*메모리 내용을 디바이스에 쓰기*/ ioctl : blkdev_ioctl, /*디바이스에 제어 연산*/ };
모듈의 적재/제거 Init_module() request() ceanup_module() register_blkdev() block_read() unregister_blkdev() blk_dev[] blkdevs[] fops insmod rmmod Driver Module Core Kernel One function Other function Data Function call Data pointer Function pointer Assignment to data
IDE 구동기 작성 예(1) 하드디스크 드라이버 주요 기능 파일시스템에서 논리적인 블록에 대한 읽기/쓰기 요청이 발생했을 때 이 논리적인 블록을 물리적인 주소(헤더, 트랙, 섹터 및 요구되는 섹터의 수)로 변환시키는 것 물리적인 주소에서 실제 데이터를 주 메모리(실제로는 커널의 버퍼캐쉬공간)로 읽는 것 혹은 주메모리에 있는 데이터를 물리적인 주소에 쓰는 것 디스크에서 사건의 발생(인터럽트 발생)을 알렸을 때 그 사건을 처리하는 것
IDE 구동기 작성 예(2) 인터페이스 함수 중심의 IDE 방식 하드디스크 드라이브의 구조 hd_init() struct file_operations hd_ops /* driver/block/hd.c */ NULL, block_read, block_write NULL, NULL, hd_ioctl, hd_open, NULL hd_release, block_fsync hd_init() Buffer Cache hd_open( ) hd_release( ) hd_request( ) hd_ioctl( ) hd_interrupt( ) hd_out( ) check_status( ) 파일시스템과의 인터페이스 함수 하드웨어와의 인터페이스 함수 드라이버 초기화 함수
IDE 구동기 작성 예(3) 디스크 드라이버 초기화 struct file_operations hd_ops NULL, block_read, block_write NULL, NULL, hd_ioctl, hd_open, NULL hd_release, block_fsync struct file_operations hd_ops /* driver/block/hd.c */ register_blkdev() /* fs/devices.c */ hd_init() - register_blkdev(HD_MAJOR, “hd”, &hd_fops); - blkdevs[major].name = device name - blkdevs[major].fops = fops /* include/linux/major.h */ init_module init_process 디스크 드라이버 초기화
IDE 구동기 작성 예(4) 디스크 드라이버 open - get_unused_fd( ) - fd_install(fd, f) filp_open( ) /* fs/open.c */ sys_open( ) open_namei( ) /* fs/namei.c */ - get_unused_fd( ) - fd_install(fd, f) - struct file initialize - f->f_op->open( ) blkdev_open( ) /* fs/device.c */ - filp->f_op = get_blkfops(MAJOR(inode->i_rdev)); /* filp->f_op = blkdevs[major].fops */ - filp->f_op->open; /* hd_open */
IDE 구동기 작성 예(5) 문자 디바이스 드라이버와의 차이점 등록할 때 register_chrdev() 대신 register_blkdev() 사용 blkdevs 테이블에 드라이버 이름, 파일 연산 등 등록 읽기/쓰기 연산을 위한 별도의 함수가 존재하는 것이 아니라 hd_request() 함수에서 모두 지원 디스크에서 읽은 데이터를 직접 사용자 공간에 쓰는 것이 아니라 커널이 관리하는 공간인 버퍼캐쉬(buffer cache)라는 공간에 씀 파일 연산 자료구조에 읽기/쓰기 함수가 등록되어야 하는 위치에 block_read(), block_write() 라는 이름의 함수가 등록되어 있는데, 이것은 드라이버에서 구현하는 함수가 아니라 Linux 커널이 제공하는 함수임. 블록 디바이스는 블록 단위로 데이터를 입출력하는 방식을 사용
IDE 구동기 작성 예(6) 디스크 드라이버 read System call handling layer VFS layer generic_file_read() /* mm/filemap.c */ sys_read() /* fs/read_write.c */ - f->f_op->read - try to find page in cache, if (hit) OK. - inode->i_op->readpage() System call handling layer VFS layer sock_read() hd_read() tty_read() pipe_read() Specific File layer Specific FS layer
파일시스템이 드라이버에게 요청한 request IDE 구동기 작성 예(7) struct blk_dev_struct { request_fn; queue; request; ... } blk_dev[]; /* include/linux/blkdev.h */ struct request { rq_status rq_dev cmd /* R/W */ error sector, nr_sector buffer, bh sem next } block device driver queue req buffer cache bread block_read ll_rw_block make_request hd_request hd_out request_fn 파일시스템이 드라이버에게 요청한 request 디스크 드라이버와 데이터 전송
블럭 디바이스 드라이버 구현 블럭 디바이스 제작방법 문자 디바이스 드라이버와 마찬가지로 외부와는 파일 인터페이스로 연결, 파일 연산들을 제공 문자 디바이스 드라이버와는 별개로 관리되며, 문자 디바이스 드라이버와 major number가 겹쳐도 상관 없다 일반 read/write와는 달리 버퍼캐시를 통한 read/write을 하기 때문에 read/write을 하기 위한 별도의 인터페이스가 필요(request) Request 함수를 구현해야 하고, 이 함수에서 실질적인 read/write 작업을 한다. 또한 블럭, 섹터, 전체 크기 등을 따로 블럭 장치를 관리하는 전역 변수에 설정 해야 한다
블록 디바이스 드라이버 구현(2) 장치의 등록과 해제 파일 연산 등록 해제 문자 장치에서와 마찬가지로 major번호 0은 동적할당 해제 파일 연산 read/write/fsync함수는 따로 구현할 필요가 없음, block_read(), block_write(), block_fsync()를 그대로 사용 open, release는 문자 디바이스 드라이버와 동일하게 구현 ioctl함수는 블록 디바이스 드라이버에는 공통적으로 사용하는 여러 가지의 ioctl 명령이 있으며, 이들의 대부분은 반드시 지원해 주어야 한다 extern int devfs_register_blkdev(unsigned int major, const char name, struct block device operation *bdops) extern int devfs_unregister_blkdev(unsigned int major, const char *name)
블럭 디바이스 드라이버 예제 Mini RAMDISK RAM 상의 일부 메모리를 디스크처럼 사용하는 간단한 블록 디바이스 구축 구현 파일 연산을 정의하고 등록 Read/write는 일반적인 블록 read/write 함수를 이용 블록 디바이스 구현에 필요한 request함수와 블록 크기, 섹터 크기, 장치 크기 등을 지정 모듈을 load할 때 RAK Disk를 위해 필요한 메모리를 할당 Request 함수는 버퍼 캐시의 버퍼와 할당받은 메모리 사이에 read/write를 수행한다. 참조자료 리눅스 커널 프로그래밍 자료 - 이호 Site : http://www.linuxkernel.net/moduleprog/lkp/
블럭 디바이스 드라이버(1) RAM상에서 일부 메모리를 디스크처럼 활용 블럭 디바이스 드라이버 프로그램 소스 예제(minird.c) #include <linux/kernel.h> #include <linux/module.h> #ifdef CONFIG_MODVERSIONS #define MODVERSIONS #include <linux/modversions.h> #endif #include <linux/fs.h> #include <linux/vmalloc.h> #include <asm/uaccess.h> #include <linux/devfs_fs_kernel.h> #define MINIRD_MAJOR 0 /* major number : dynamic allocation */ #define DEVICE_NAME "MiniRD“ /* device name */ #define DEVICE_FILENAME "minird" #define DEVICE_NUM 2 /* minor device number */ #define MINIRD_DEF_SIZE 1024 /* default device size in KB */ #define MINIRD_BLOCKSIZE 1024 /* sizeof block */ #define MINIRD_SECTSIZE 512 /* sizeof sector */
블럭 디바이스 드라이버(2) extern int s_nMajor; #define MAJOR_NR (s_nMajor) #define DEVICE_NR(device) (MINOR(device)) #define DEVICE_ON(device) /* do nothing */ #define DEVICE_OFF(device) /* do nothing */ #define DEVICE_NO_RANDOM #include <linux/blk.h> #include <linux/blkpg.h> /* Globak Variables */ static int s_nMajor = 0; static int minird_size = MINIRD_DEF_SIZE; static int s_kbsize[DEVICE_NUM]; /* size in blocks of 1024 bytes */ static int s_blocksize[DEVICE_NUM]; /* size of 1024 byte block */ static int s_hardsect[DEVICE_NUM]; /* sizeof real block in bytes */ static int s_length[DEVICE_NUM]; /* size of disks in bytes */ static char *s_data[DEVICE_NUM]; static devfs_handle_t devfs_handle; MODULE_PARM(minird_size, "i"); MODULE_PARM_DESC(minird_size, "RAM Disk size in KB");
블럭 디바이스 드라이버(3) 파일 연산을 등록 /* Function Prototypes */ static int minird_open(struct inode *inodep, struct file *filp); static int minird_release(struct inode *inodep, struct file *filp); static int minird_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg); static int minird_make_request(request_queue_t *q, int rw, struct buffer_head *sbh); /* Device Operations */ static struct block_device_operations minird_fops = { open : minird_open, release : minird_release, ioctl : minird_ioctl, }; /* Module startup/cleanup */ int init_module(void) { int i, j; printk("Loading Mini RAM Disk Module\n"); for (i = 0; i < DEVICE_NUM; ++i) { s_kbsize[i] = minird_size; 파일 연산을 등록
블럭 디바이스 드라이버(4) 요구되는 메모리 할당 블럭 디바이스 등록 s_blocksize[i] = MINIRD_BLOCKSIZE; s_hardsect[i] = MINIRD_SECTSIZE; s_length[i] = (minird_size << BLOCK_SIZE_BITS); if ((s_data[i] = vmalloc(s_length[i])) == NULL) { for (j = 0; j < i; ++j) vfree(s_data[j]); return -ENOMEM; } if ((s_nMajor = devfs_register_blkdev(MINIRD_MAJOR, DEVICE_NAME, &minird_fops)) < 0) { printk(DEVICE_NAME " : Device registration failed (%d)\n", s_nMajor); return s_nMajor; printk(DEVICE_NAME " : Device registered with Major Number = %d\n", MAJOR_NR); devfs_handle = devfs_mk_dir(NULL, DEVICE_FILENAME, NULL); devfs_register_series(devfs_handle, "%u", DEVICE_NUM, DEVFS_FL_DEFAULT, s_nMajor, 0, S_IFBLK | S_IRUGO | S_IWUGO, &minird_fops, NULL); blk_queue_make_request(BLK_DEFAULT_QUEUE(MAJOR_NR), &minird_make_request); 요구되는 메모리 할당 블럭 디바이스 등록
블럭 디바이스 드라이버(5) 블럭 디바이스 해제 blk_size[MAJOR_NR] = s_kbsize; blksize_size[MAJOR_NR] = s_blocksize; hardsect_size[MAJOR_NR] = s_hardsect; return 0; } void cleanup_module(void) { int i; printk("Unloading Mini RAM Disk Module\n"); for (i = 0; i < DEVICE_NUM; ++i) destroy_buffers(MKDEV(MAJOR_NR, i)); /* flush the devices */ vfree(s_data[i]); devfs_unregister(devfs_handle); devfs_unregister_blkdev(MAJOR_NR, DEVICE_NAME); blk_size[MAJOR_NR] = NULL; blksize_size[MAJOR_NR] = NULL; hardsect_size[MAJOR_NR] = NULL; 블럭 디바이스 해제
블럭 디바이스 드라이버(6) /* Device Operations */ int minird_open(struct inode *inodep, struct file *filp) { if (DEVICE_NR(inodep->i_rdev) >= DEVICE_NUM) return -ENXIO; MOD_INC_USE_COUNT; return 0; } int minird_release(struct inode *inodep, struct file *filp) MOD_DEC_USE_COUNT; int minird_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg) int minor = DEVICE_NR(inodep->i_rdev); long devsize; switch (cmd) { case BLKGETSIZE : /* return device size */ devsize = s_length[minor] / s_hardsect[minor]; return put_user(devsize, (long *) arg);
블럭 디바이스 드라이버(7) case BLKSSZGET : /* block size of media */ return put_user(s_blocksize[minor], (int *) arg); case BLKFLSBUF : /* flush */ if (!capable(CAP_SYS_ADMIN)) return -EACCES; destroy_buffers(inodep->i_rdev); break; case BLKROSET : case BLKROGET : return blk_ioctl(inodep->i_rdev, cmd, arg); default : return -EINVAL; } return 0; /* Request Processing */ int minird_make_request(request_queue_t *q, int rw, struct buffer_head *sbh) { char *ptr, *bdata; int size, minor;
블럭 디바이스 드라이버(8) if ((minor = DEVICE_NR(sbh->b_rdev)) > DEVICE_NUM) { printk(DEVICE_NAME " : Unknown Minor Device\n"); goto fail; } ptr = s_data[minor] + sbh->b_rsector * s_hardsect[minor]; size = sbh->b_size; if (ptr + size > s_data[minor] + s_length[minor]) { printk(DEVICE_NAME " : Request past end of device\n"); bdata = bh_kmap(sbh); switch (rw) { case READ : case READA : memcpy(bdata, ptr, size); break; case WRITE : memcpy(ptr, bdata, size); default :
블럭 디바이스 드라이버(9) sbh->b_end_io(sbh, 1); return 0; fail: }
Makefile 작성 예 /* Makefile */ CC = arm-linux-gcc LD = arm-linux-ld KERNELDIR = /usr/src/linux-2.4.19 INCLUDEDIR = -I$(KERNELDIR)/include CFLAGS = -D__KERNEL__ -DMODULE -O2 -Wall $(INCLUDEDIR) OBJS = minird.o SRCS = minird.c $(OBJS): $(SRCS) $(CC) $(CFLAGS) -c $(SRCS) clean : rm -f *.o *~
블럭 디바이스 드라이버 실행 블럭 디바이스 드라이버 컴파일 방법 LED 디바이스 드라이버 실행 방법 블럭 디바이스 테스트 $ make LED 디바이스 드라이버 실행 방법 생성된 minird.o파일을 타겟 보드로 전송 $ insmod minird.o $ mknod /dev/minird0 b [major] 0 $ dd if=/dev/zero of=/dev/minird0 bs=1024 count=1024 $ mke2fs /dev/minird0 1024 $ mkdir /mnt/other 블럭 디바이스 테스트 $ mount /dev/minird0 /mnt/other $ cp /bin/ls /mnt/other $ ls /mnt/other $ umount /mnt/other $ mount /dev/minird0 /mnt/other
블럭 디바이스 드라이버 실행 예 디바이스 드라이버 컴파일
블럭 디바이스 드라이버 실행 예(2) 디바이스 드라이버 테스트
블럭 디바이스 드라이버 실행 예(3)