가상 문자 디바이스 드라이버 구현

Jin Hur·2021년 8월 7일
0
post-custom-banner

reference: "리눅스 커널 내부구조" / 백승제, 최종무

설계: 메모리의 일부를 가상적인 디스크 디바이스 공간으로 사용.
간단한 가상 문자 디바이스 드라이버를 구현하여 이 공간에 쓰기와 데이터 읽기 연산 제공.

  1. 디바이스 드라이버의 이름과 주 번호를 결정한다.(주 번호의 동적 할당 사용)
  2. 디바이스 드라이버가 제공하는 인터페이스를 위한 함수 구현
  3. 인터페이스 함수들의 시작 주소는 file_operations 자료구조에 초기회
  4. 커널에 디바이스 드라이버 추가.
  5. /dev 디렉터리에 구현한 디바이스 드라이버를 접근할 수 있도록 장치파일 생성

my device drive 코드

#include <linux/kernel.h>	// 현재 디바이스 드라이버 프로그램은 커널 공간에서 모듈로써 동작하게 됨으로써 
#include <linux/module.h>	// kernel.h, module.h 헤더 필요
#include <linux/slab.h>		// for kmalloc(), kfree() (커널 공간 할당/해제)
#include <linux/fs.h>
#include <linux/cdev.h>		// 하나의 디바이스 드라이버를 관리하기 위한 리눅스 자료구조.
				// ops 필드에는 드라이버 제공하는 file_operations 구조체 저장
#include <linux/device.h>
#include <asm/uaccess.h>

#define	DEVICE_NAME	"Mydrv"		// 디바이스 드라이버 이름
#define MYDRV_MAX_LENGTH	4096	// 가상 디스크 공간으로 활용한 커널 공간의 크기
#define MIN(a, b) (((a) < (b)) ? (a) : (b))	// MIN() 메크로 함수

struct cdev *mycdev;	// cdev 구조체
dev_t mydev;		// for 디바이스 드라이버 등록
struct class *myclass;	// for 장치파일
struct device *mydevice;	// for 장치파일


static char *mydrv_data;
static int mydrv_read_offset;
static int mydrv_write_offset;

// file_operations 자료구조에 등록시킬 함수들
// open(), release() (=close), read(), write(), ioctl()

/*
* 커널 공간을 할당받아 가상의 디스크를 만들고, 이에 대한 디바이스 드라이브
* 따라서 mydrv_opne() 또는 mydrv_release()는 주 번호를 확인하는 것 외에 특별한 작업을 하지 않음.
* 만약 실제 물리적인 하드웨어를 구동하는 것이라면 하드웨어 초기화와 같은 작업들을 수행해야 함. 
*/
static int mydrv_open(struct inode *inode, struct file *file){
	printk("%s \n", __FUNCTION__);
	return 0;
}

static int mydrv_release(struct inode *inode, struct file *file){
	printk("%s \n", __FUNCTION__);
	return 0;
}

/*
* mydrv_data 커널 공간에 데이터(문자열)을 쓰거나 읽는 작업
* 커널과의 블록 메모리 복사 함수인 copy_to_user, copy_from_user를 file_operations 구조체에 매핑시키기 위한 일종의 래퍼라 할 수 있음.
* copy_to_user(): Kernel 메모리를 User 메모리로 블록 데이터를 복사함.
* copy_from_user(): User 메모리를 Kernel 메모리로 블록 데이터를 복사함.
* mydrv_read(): 몇가지 예외 조건을 검사한 후, mydrv_data 커널 공간에 존재하는 데이터를 사용자에게 전달
* mydrv_write(): 몇가지 예외 조건을 검사한 후, mydrv_data 커널 공간에 사용자 요청 데이터를 씀. (write_offset 이동) 
*/
static ssize_t mydrv_read(struct file *file, char *buf, size_t count, loff_t *ppos){
	if( buf == NULL || count < 0 )
		return -EINVAL;
	if( mydrv_write_offset <= mydrv_read_offset )
		return 0;
	count = MIN((mydrv_write_offset-mydrv_read_offset), count);

	if( copy_to_user(buf, mydrv_data + mydrv_read_offset, count) )	// 커널 메모리 => 유저 메모리, 블록 데이터 복사
		return -EFAULT;

	mydrv_read_offset += count;

	return count;
}

static ssize_t mydrv_write(struct file *file, const char *buf, size_t count, loff_t *ppos){
	if( buf == NULL || count < 0)
		return -EINVAL;
	if( count + mydrv_write_offset >= MYDRV_MAX_LENGTH )
		return 0;

	if( copy_from_user(mydrv_data + mydrv_write_offset, buf, count) )	// 유저 메모리 => 커널 메모리, 블록 데이터 복사
		return -EFAULT;

	mydrv_write_offset += count;
	return count;
}


// 위에서 정의한 함수들의 시작 주소 등록
struct file_operations mydrv_fops = {
	.owner = THIS_MODULE,
	.read = mydrv_read,
	.write = mydrv_write,
	.open = mydrv_open,
	.release = mydrv_release,
};

// 모듈 초기화 함수 
int mydrv_init(void){
	//  alloc_register_chrdev() 함수를 통해 디바이스 드라이버 주 번호를 동적으로 할당 받음. 
	if( alloc_chrdev_region(&mydev, 0, 1, DEVICE_NAME) < 0 )
		return -EBUSY;

	// 커널에서는 udev를 통한 동적인 장치파일 생성/제거를 지원할 수도 있음.
    // class_create(), device_create() 사용
	myclass = class_create(THIS_MODULE, "mycharclass");
	if(IS_ERR(myclass)){
		unregister_chrdev_region(mydev, 1);
		return PTR_ERR(myclass);
	}

	mydevice = device_create(myclass, NULL, mydev, NULL, "mydevicefile");
	if(IS_ERR(mydevice)){
		class_destroy(myclass);
		unregister_chrdev_region(mydev, 1);
		return PTR_ERR(mydevice);
	}

	
	mycdev = cdev_alloc();	// cdev 구조체 할당받기
	mycdev->ops = &mydrv_fops;	// 드라이버 랩퍼 함수들의 시작주소를 저장한 file_operations 구조체(mydrv_fops)를 등록
	mycdev->owner = THIS_MODULE;
	if( cdev_add(mycdev, mydev, 1) < 0) {	// 초기화된 cdev 구조체를 커널에 등록
		device_destroy(myclass, mydev);
		class_destroy(myclass);
		unregister_chrdev_region(mydev, 1);
		return -EBUSY;
	}

	// 가상 디스크 공간으로 사용할 커널 공간 할당받기, 이름: mydrv_data
	mydrv_data = (char *)kmalloc(MYDRV_MAX_LENGTH * sizeof(char), GFP_KERNEL);
	// 오프셋 초기화
    mydrv_read_offset = mydrv_write_offset = 0;
	
    return 0;
}

// 모듈 제거 시 호출될 함수 
void mydrv_cleanup(void){
	kfree(mydrv_data);
	cdev_del(mycdev);
	device_destroy(myclass, mydev);
	class_destroy(myclass);
	unregister_chrdev_region(mydev, 1);
}

// 매크로(module_init, module_exit 사용, mydrv_init(), mydrv_exit() 지정)
module_init(mydrv_init);
module_exit(mydrv_cleanup);
MODULE_LICENSE("GPL");

디바이스 드라이버 소스 컴파일 & 모듈 커널 적재

Makefile

디바이스 드라이버 컴파일
$ make

생성된 모듈 커널에 적재
$ insmod chr_test.ko
"insmod" 명령어를 통해 모듈을 커널에 적재한 후에 비로소 장치파일 생성

insmod 명령어를 통해 디바이스 드라이버를 커널에 적재하면 module_init() 매크로에 지정된 함수를 호출 => mydrv_open() 함수 호출
mydrv_open() 함수는 내부적으로 cdev_add() 호출, 커널 내부 자료구조인 cdev_map 자료구조에 가상 디스크 디바이스 드라이버의 file_operations 자료구조를 등록한다. 또한 장치파일은 udev를 잉요해 자동 생성된다.

사용자 애플리케이션에서 이 파일을 열고 사용하려한다면 우선 장치파일의 아이노드 객체를 찾아서 i_mode와 i_rdev를 확인한다.
문자 장치파일이므로 i_mode는 'S_IFCHR'이고, 문자 장치파일임을 인식한 후 디폴트 open인 chrdev_open()을 호출한다.

chrdev_open(): 문자형 디바이스 드라이버를 위한 자료구조인 cdev_map에서 i_rdev 필드에 저장된 주 번호를 인자로 하여 인덱싱하고, file_operations 자료구조를 찾는다. 그리고 이 구조체를 파일 객체의 f_op필드에 연결함으로써 추후 애플리케이션이 read()나 write()를 호출할 때 디바이스 드라이버를 만들 때 작성한 함수들(mydrv_read() 또는 mydrv_write() 등의 함수, mydrv_fops 내 등록)을 호출하게 한다.

만들어진 가상 문자 디바이스 드라이버 응용 프로그램

(리눅스 내부작동)
1. 장치파일 이름을 인자로 파일 열기 요청.
2. 장치파일의 아이노드 객체를 통해 주 번호, 부 번호를 얻고 장치파일의 유형을 얻음.
3. 장치파일의 유형을 통해 cdev_map 자료구조에 접근하여 주 번호를 인자로 cdev 구조체 검색.
4. ops의 필드를 통해 file_operations 구조체에 등록된 open() 호출 => mydrv_open() 호출.

#include <stdio.h>
#include <fcntl.h>

#define MAX_BUF 26
char buf_in[MAX_BUF];
char buf_out[MAX_BUF];

int main(void){
        int fd, i, c=65;
        if( (fd=open("/dev/mydevicefile", O_RDWR)) < 0){  // 생성된 장치파일에 접근해 가상 장치 접근
                perror("OPEN ERR");
                return -1;
        }

        for(i=0; i<MAX_BUF; i++){
                buf_out[i] = c++;	// ABCDEFGH....
                buf_in[i] = 65;		// AAAAAAAA....
        }

        for(i=0; i<MAX_BUF; i++){
                fprintf(stderr, "%c", buf_in[i]);	// AAAAAA....
        }
        fprintf(stderr, "\n");

	// 가상 장치에 I/O
        write(fd, buf_out, MAX_BUF);	// ABCDEFG.. 쓰기
        read(fd, buf_in, MAX_BUF);	// (커널 메모리 내)ABCDEFG.. 읽기

        for(i=0; i<MAX_BUF; i++){
                fprintf(stderr, "%c", buf_in[i]);	// ABCDEFG..
        }
        fprintf(stderr, "\n");

        close(fd);
        return 0;
}
post-custom-banner

0개의 댓글