리눅스 디바이스 드라이버 기초와 예제

▢  디바이스 드라이버

 디바이스 드라이버는 시스템이 지원하는 하드웨어를 사용자가 응용 프로그램에서 사용할 수 있도록 커널의 일부 영역을 사용하여 동작하는 일종의 프로그램이다. 

 리눅스에서 사용자가 커널 모드의 디바이스 드라이버를 사용하여 실제 하드웨어 디바이스에 접근하고자 할 때 아래 그림과 같은 추상적인 계층들을 통과해야 한다. 

 


리눅스 사용자 관점에서 디바이스는 하나의 파일로 인식된다. 이런 파일들은 /dev 디렉토리에 위치하며, 이들을 디바이스 파일이라고 한다. 

 


디바이스 드라이버는 문자/블록/네트워크 디바이스 드라이버 세 종류가 있으며, 각 디바이스는 주 번호(Major number)와 부 번호(Minor number)를 갖고 있다. 

문자 디바이스 (Char Device)

자료의 순차성을 지닌 장치로 버퍼 캐쉬를 사용하지 않으며, 장치의 raw data를 사용자에게 제공한다. 시리얼, 키보드, 프린터, 마우스 등이 이에 속한다. 

블록 디바이스 (Block Device)

Radom access, 블록 단위의 입출력이 가능하고, 파일 시스템에 의해 mount 되어 관리되는 장치. 디스크, CD-ROM등이 이에 속한다. 

네트워크 디바이스 (Network Device)

대응하는 장치 파일 없으며, 네트워크 통신을 통해 패킷을 송수신할 수 있는 장치. 이더넷 등이 이에 속한다. 

 




▢  커널 모듈

커널 모듈은 리눅스 커널이 부팅되어 동작 중일때도 동적으로 추가하거나 제거할 수 있는 커널의 구성요소이다. 디바이스 드라이버, 파일 시스템, 네트워크 프로토콜 등이 모듈로 제공된다. 

커널 모듈은 커널 영역에서 동작한다. 일반 응용프로그램과 달리 main함수가 없으며, 모듈이 적재될 때와 해지될 때 호출되는 함수가 존재한다. 

module_init(hellomodule_init);
module_exit(hellomodule_exit);


모듈 관련 app

  • insmod : 모듈을 커널에 적재
  • rmmod : 커널에서 모듈을 제거
  • lsmod : 커널에 로딩된 목록을 출력


다음은 모듈이 커널에 적재될 때와 제거될 때 로그 메시지를 출력하는 예제이다.

#ifndef __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>


static int hellomodule_init(void)
{
printk("hello module\n");
return 0;
}


static void hellomodule_exit(void)
{
printk("goodbye module\n");
}


module_init(hellomodule_init);
module_exit(hellomodule_exit);

MODULE_LICENSE("Daul BSD/GPL");


arm64 계열의 임베디드 리눅스 디바이스에서 동작하기 위한 Makefile은 아래와 같다.

CROSS_COMPILE=/home/r/external-toolchain/bin/aarch64-linux-gnu-

obj-m := hellomodule.o
KDIR := /home/r/linux-3.10
PWD := $(shell pwd)

default:
$(MAKE) ARCH=arm64 -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=${CROSS_COMPILE}
clean:
rm -f *.ko
rm -f *.o
rm -f *.mod.*
rm -f .*.cmd


예제의 모듈을 임베디드 디바이스에 복사한 후, 커널에 적재하고 제거하면 아래와 같은 메시지를 출력한다.

 


커널에 모듈이 적재될 때 module_init로 지정된 함수가 호출되어 hello modul을 출력하고, 커널에서 모듈이 제거될 때 module_exit로 지정된 함수가 호출되어 goodbye module이 출력되는 것을 볼 수 있다. 



▢  리눅스 커널 모듈 형태의 디바이스 드라이버 예제

커널 모듈 형태의 디바이스 드라이버를 만들기 위해 다음의 함수들을 구현해야 한다.

1. module_init

- 디바이스 드라이버의 커널 등록

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
int register_blkdev(unsigned int, const char *)
int register_netdev(struct net_device *dev)

- 드라이버 초기화 

2. module_exit

- 디바이스 드라이버 등록 해제

void unregister_chrdev(unsigned int major, const char *name)
int unregister_filesystem(struct file_system_type *)
void unregister_netdev(struct net_device *dev)


3. char device driver경우 struct file_operations 정의 및 함수

디바이스 드라이버는 파일과 같은 인터페이스를 이용하여 관리된다. file_operations 전체 구조체는 linux/fs.h에 정의 되어있으며 전체 구조는 다음과 같다. 이 구조체의 인터페이스를 모두 구현할 필요는 없고, 디바이스 드라이버 동작에 필요한 인터페이스만 구현해도 된다. 또한 커널 버전에 따라 구조체의 내용은 일정부분 변경될 수 있다.

 



다음은 간단하게 구현된 char device driver 예제 소스코드이다.

#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/poll.h>

#define cddex_dev_name "ryan cddex"
#define data_max_len 128

int cddex_major = 0;
static char data[data_max_len];
static int data_len;

static int cddex_open( struct inode *inode, struct file *filp )
{
    printk("cddex_open\n");
    data_len = 0;
    return 0;
}

static ssize_t cddex_read(struct file *filp, char __user  *buf, size_t count, loff_t *ppos)
{
    int copylen = count;

    if (!buf) return -1;
    if (count <= 0) return -1;

    if (copylen > data_len)
        copylen = data_len;

    if (copylen > 0)
    {
        if (copy_to_user(buf,data,copylen*sizeof(char)))
        {
            return -1;
        }
    }

    return copylen;
}

static ssize_t cddex_write(struct file *filp, const char __user  *buf, size_t count, loff_t *ppos)
{
    int copylen = count;
    if(!buf) return -1;
    if (copylen > data_max_len)
        copylen = data_max_len;

    if (copylen > 0)
    {
        if (copy_from_user(data, buf, copylen*sizeof(char)))
        {
            return -1;
        }
    }

    data_len = copylen;

    return copylen;
}

static int cddex_release( struct inode *inode, struct file *filp)
{
    printk("cddex_release\n");
    return 0;
}

static struct file_operations cddex_fops =
{
.owner=THIS_MODULE,
.open= cddex_open, 
.release= cddex_release, 
.read= cddex_read,
.write= cddex_write,
};

static int cddex_init(void)
{
    cddex_major = register_chrdev( 0, cddex_dev_name, &cddex_fops );

    if (cddex_major >= 0)
    {
        printk("register device %s Success (major=%d)\n", cddex_dev_name, cddex_major );
    }        
    else
    {        
        printk("register device %s Fail\n", cddex_dev_name);
        return -EBUSY;
    }
    return 0;
}

static void cddex_exit(void)
{
    unregister_chrdev( cddex_major, cddex_dev_name );
}

module_init(cddex_init);
module_exit(cddex_exit);

MODULE_AUTHOR("ryanclaire.blogspot.com");
MODULE_LICENSE("Daul BSD/GPL");


Makefile은 다음과 같다.

CROSS_COMPILE=/home/r/external-toolchain/bin/aarch64-linux-gnu-

obj-m := cddex.o
KDIR := /home/r/linux-3.10
PWD := $(shell pwd)

default:
$(MAKE) ARCH=arm64 -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=${CROSS_COMPILE}
clean:
rm -f *.ko
rm -f *.o
rm -f *.mod.*
rm -f .*.cmd


드라이버가 커널에 적재될 때 register_chrdev함수를 사용해 char device deriver로 등록했으며, 드라이버가 커널에서 제거될 때 unregister_chrdev함수를 사용해 드라이버 등록을 해제했다. 

파일 인터페이스와 관련하여 file_operations 구조체의 open, release, read, wirte를 구현하였다.

static struct file_operations cddex_fops =
{
.owner=THIS_MODULE,
.open= cddex_open, 
.release= cddex_release, 
.read= cddex_read,
.write= cddex_write,
};


file_operations.open은 사용자 모드의 app에서 open함수 호출 시 file_operations.open에 할당된 디바이스 드라이버 함수가 호출된다.

file_operations.release 는 사용자 모드 app의 close 함수 호출 시 할당된 디바이스 드라이버 함수가 호출된다. 

file_operations.read/ file_operations.write는 각각 사용자 모드 app에서 read/write을 사용할 때 디바이스 드라이버에 할당된 함수가 호출된다.

예제에서는 write로 쓰여진 데이터를 저장했다. read가 호출될 때 저장된 데이터를 app에서 읽을 수 있도록 하였다. 데이터는 사용자 모드와 커널 모드의 메모리 변환을 고려하여 copy_to_user/ copy_from_user를 사용하였다.



▢  디바이스 드라이버 예제 사용 App

위 예제 코드에서 생성된 디바이스 드라이버는 insmod를 이용해 커널에 적재한다음 사용자 모드의 app에서 이를 사용하기 위해 노드(파일)을 생성해야 한다. 

mknod 파일이름 드라이버타입 주번호 부번호

위 예제에서는 드라이버가 적재될 때 주번호를 화면에 출력하도록 하였다.

mknod /dev/cddex c 250 0


위 디바이스 드라이버 사용 예제 코드는 다음과 같다. 디바이스 드라이버에 ‘hello\r\n’을 쓴 후 쓰여진 데이터를 읽는 예제 코드이다.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>


int main( int argc, char **argv )
{
    char *msg = "hello\r\n";
    char buffer[128] = {0,};

    int fd = open("/dev/cddex", O_RDWR| O_NDELAY );
    if(fd < 0)
    {
        printf("cddex open fail\n");
        return -1;
    }

    write(fd,msg,strlen(msg));
    read(fd,buffer,128);
    printf("read : %s\r\n",buffer);

    close(fd);

    return 0;
}


드라이버를 등록하고 app를 실행한 결과는 아래와 같다.




관련 포스트

insmod vmalloc: allocation failure 발생 해결법

댓글

이 블로그의 인기 게시물

아두이노(arduino) 심박센서 (heart rate sensor) 심박수 측정 example code

쉽게 설명한 파티클 필터(particle filter) 동작 원리와 예제

간단한 cfar 알고리즘에 대해

windows에서 간단하게 크롬캐스트(Chromecast)를 통해 윈도우 화면 미러링 방법