이동: Home à os0402

 

주소: http://www.kernel.bz/os/04/os0402.htm

이페이지의 저작권은

제목: 커널 2.6.x 디바이스 드라이버

저자에게 있습니다

저자: 정원영

최근수정일:2009-03-06

 

 

 

 

Header

커널 2.6.x 디바이스 드라이버

정원영
미지리서치
개발3실
suni00(at)kernel.pe.kr, suni00(at)mizi.com

이 문서의 원본은 여기(http://onsykim.springnote.com/pages/1770352)에 있습니다.
 
ver 0.3, 2004년 06월 24일.

커널 2.6.x는 소스파일 구성부터 이전과는 큰 차이를 보이며 커널코어 및 디바이스 드라이버의 구조등에 있어서도 많은 변화가 있다. 여기서는 커널 2.4.x에서 작성했던 디바이스 드라이버를 2.6.x로 포팅하는데 있어 기존과 다른 새로운점과 이로인한 디바이스 드라이버 작성할때 변화 내용을 알아보자. 물론 커널 2.6.x에서는 여전히 2.4.x에서 작성된 디바이스 드라이버와의 호환성 유지를 위해 어떤 부분들은 예전 코드로도 가능하도록 유지하고 있지만 실제 코딩을 해보면 제법 많은 차이를 실감할수 있을것이다.

 

차례

1. 커널 2.6.x 특징

    선점형 커널

             preempt_test.c

             Makefile

             compile

             test_preempt.c

             결과

    스케줄러

    가상 메모리

    Interrupt Handler

    HZ

    jiffies

    디바이스 넘버

    ndelay

    모듈의 변화

    sysfs

    initramfs

    wait_event() 관련

    LOGO

    Keyboard

3. 커널 config 등록및 컴파일

    Kconfig

             sound/Kconfig

             Kconfig 만들기

    Makefile

             sound/Makefile

             Makefile에 추가하기

5. 디바이스 드라이버 예제

    simple_driver-2.4.c

    simple_driver.c

    ioctl_test.c

7. 마치며

8. 참고

 

커널 2.6.x 특징

커널 2.6.x는 우선 버전 네이밍과정부터 커널 메일링리스트에 많은 의견이 올라왔었다. 개발버전 2.5.x 에서 다음 안정버전을 3.0.x로 해도 되지 않겠느냐등이었는데 그만큼 2.4.x에 비해 큰 변화가 있음을 시사하고있다. 이런 의견의 배경이된것은 커널 2.6.x에서 가장 핵심적 변화인 커널코어쪽에서 새로운 스케줄러와 선점형 커널이 가능해졌기 때문이다.

다음은 커널 2.6.x에서 새로운것과 특징중 디바이스 드라이버를 만들기 위해 먼저 알아야할 내용들을 나열해 보았다.

선점형 커널

 
커널 2.6.x에서는 다음과 같이 커널 config 설정중 

Processor type and features에서 [*] Preemptible Kernel 만 체크하면 선점형 커널이 된다.
 
선점형 커널을 위한 컴파일 옵션
 
 
이옵션은 현재 실행중인 프로세스가 어떤 상황에 있더라도 커널이 그 프로세스를 선점하여 다른 

프로세스를 실행할수있다는 것이다. (모든 경우에 대해 선점이 가능하진 않다. 이 부분에 관한 

테스트도 있으니 참고하기 바란다.)
 
반대로 커널이 비선점(Non-preemptive)일 경우는 위와 같이 현재 실행중인 프로세스를 커널이

가로챌수 없고 그 프로세스 스스로 제어권을 넘겨주기 전까지는 어떠한 우선순위를 가진 프로세스도

실행될수 없음을 의미한다.
 
간단한 예를 들면 만약 비선점형 커널에서 어떤 프로세스가 무한루프에 빠지게 된다면 커널이 

그 프로세스를 선점할수없어 무한루프 프로세스가 CPU를 계속 점유하게되어 다른 프로세스는 

실행될수 없게된다. 이는 일반적으로 말하는 컴퓨터가 다운된것과 같은 것이다.
 
하지만 선점형 커널은 설사 위와 같이 어떤 프로세스가 무한루프에 빠지더라도 커널이 그 프로세스를

선점하여 다른 프로세스를 실행할수 있기에 CPU가 먹통(?)이 되는 일은 없다. 

(Realtime OS의 경우 우선순위가 높은 프로세스가 무시되는 일없이 바로바로 실행되어야 하므로 

선점형 커널을 쓰고이다.)
 
커널 2.6.x에서 선점가능한 곳을 다음 테스트 드라이버로 알아보자.

(참고로 커널 2.4.x에서도 preempt patch를 적용하면 다음 테스트 드라이버로 테스트 가능하다.) 

preempt_test.c

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/miscdevice.h>
#include <linux/version.h>

#define PREEMPT_DEBUG
#ifdef PREEMPT_DEBUG
#define DPRINTK(fmt, args...) printk(KERN_INFO "%s: " fmt, __FUNCTION__ , ## args)
#else
#define DPRINTK(fmt, args...)
#endif


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#define preempt_get_count preempt_count
#endif

static int preempt_open(struct inode *inode, struct file *file)
{
DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());
return 0;
}

static int preempt_release(struct inode *inode, struct file *file)
{
DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());
return 0;
}

static ssize_t preempt_read(struct file *file, char *buf, size_t count,
loff_t *ppos)
{
DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());

return 0;
}

static ssize_t preempt_write(struct file * file, const char * buf,
size_t count, loff_t *ppos)
{
DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());

return 0;
}

static int preempt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());

return 0;
}

static struct file_operations char_fops = {
.owner = THIS_MODULE,
.open = preempt_open,
.release = preempt_release,
.read = preempt_read,
.write = preempt_write,
.ioctl = preempt_ioctl,
};

static struct miscdevice char_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "preempt_test",
.fops = &char_fops
};

int __init preempt_init(void)
{
int ret;

DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());

ret = misc_register(&char_dev);
if (ret) {
printk(KERN_ERR "misc_register() failedn");
}

DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());

return ret;
}

void __exit preempt_exit(void)
{
DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());

misc_deregister(&char_dev);

DPRINTK("%d: preempt_count : %dn", __LINE__, preempt_get_count());
}

module_init(preempt_init);
module_exit(preempt_exit);

MODULE_LICENSE("GPL");

 
이 디바이스 드라이버는 선점가능 여부를 'DPRINTK(fmt, args...)'로 출력하게되는데 

DPRINTK는 코드에서 알수있듯이 '#define PREEMPT_DEBUG'에 의해 

'printk(KERN_INFO "%s: " fmt, __FUNCTION__ , ## args)'로 변환되어 기본적으로

해당 함수이름을 출력해준다. 

만약 '#undef PREEMPT_DEBUG'로 수정한다면 'DPRINTK(fmt, args...)'는 정의 된것이 

아무것도 없기때문에 아무런 일을 하지 않게된다.

주로 초기 프로그래밍할때 디버깅메세지를 출력하거나 하지않기 위해 이런식으로 많이 사용한다.
 
또 한가지 알아둘만한것은 'int misc_register(struct miscdevice * misc)'를

사용한 부분이다. 이것은 간단한 디바이스 드라이버를 만들때 유용하게 쓸수있다. 

Makefile

 
다음과 같은 Makefile을 만든다.

#
# Makefile
#

obj-m := preempt_test.o

compile

make -C /usr/src/linux-2.6.1/ SUBDIRS=/tmp/module_test modules

 
'-C' 다음은 커널소스의 위치를 적어주고 'SUBDIRS=' 다음은 preempt_test.c와 

Makefile이 있는 디렉토리를 적어주면 모듈로 컴파일되어 preempt_test.ko라는 모듈이 생긴다. 

test_preempt.c

 
preempt_test.ko에 대한 테스트 프로그램으로 open, read, write, ioctl, close를 수행한다.

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


#define PREEMPT_NODE "/dev/misc/preempt_test"


int main(void)
{
int ret;
char buf[10];

ret = open(PREEMPT_NODE, O_RDWR);

read(ret, buf, 1);

write(ret, buf, 1);

ioctl(ret, 0, 0);

close(ret);

return 0;
}

 
컴파일

gcc -o test_preempt test_preempt.c

결과

 
printk는 '/var/log/messages'에 출력결과를 남기므로 실행결과를 보기위해 

'tail -f /var/log/messages'라고 일단 실행하자. 

그리고 preempt_test.ko 모듈을 올리면 '/dev/misc/preempt_test'란 노드가 생기며 

여기서 test_preempt를 실행시켜보면 각각의 file operation에서의 preempt_count 

값을 알수있다. (이 노드를 cat 명령등으로 read/write 해봐도 ioctl을 제외한 출력값을

알수는있다.)

마지막으로 preempt_test.ko 모듈을 내리면 다음과 같은 결과를 볼 수있다.

preempt_count값은 해당 method가 실행될때 preempt_count 값을 출력하도록 되어있고

preempt_count가 0일때는 선점가능하다는 의미이다.

preempt_init: 77: preempt_count : 0
preempt_init: 84: preempt_count : 0
preempt_open: 24: preempt_count : 1
preempt_read: 37: preempt_count : 0
preempt_write: 45: preempt_count : 0
preempt_ioctl: 53: preempt_count : 1
preempt_release: 30: preempt_count : 0
preempt_exit: 91: preempt_count : 0
preempt_exit: 95: preempt_count : 0

 
이 결과를 보면 file operation중 open일때와 ioctl을 사용할때는 선점가능하지 않음 

즉 비선점임을 알수있다. 그러므로 만약 무한루프 같은 루틴이 open이나 ioctl에 있다면 

그것이 실행되는 순간 CPU를 점유해버려 어떤 일도 할 수 없을것이다.

file operation중 open이나 ioctl에서는 비선점임을 명심하자. 

(참고로 커널 2.4.x에 preempt patch를 적용한경우 위와 같은 테스트를 해보면 module init과

 exit 과정에서도 preempt_count 값은 1이었다.)

스케줄러

 
커널 2.6.x의 스케줄러는 Ingo Molnar의 O(1) 알고리즘을 사용한다.
 
학교다닐때 자료구조인지 이산수학 시간인지 잘기억나진 않지만 그때를 얼핏 떠올려보면 

시간복잡도를 표시하는 방법중의 하나가 O표기법(Big-Oh notation)인데 

이것은 최악의 data가 들어왔을때의 처리속도를 의미한다.
 
예를들어 어떤 알고리즘이 O(N)이고 data가 1개라면 1이라는 시간만큼 걸리고 data가

100개라면 100이라는 시간만큼 걸리게 되는것이다. 또 하나 예를들어 O(logN) log의 밑이2인

알고리즘의 시간복잡도를 보면 data가 2개일때 log2 즉 1이라는 시간만큼 걸리고 data가 1024일때

log1024 즉 10이라는 시간만큼 걸리는걸 의미한다.

O(1)을 본다면 어떤 data가 들어와도 항상 1이라는 시간만 걸리므로 시간복잡도가 

가장 빠른 알고리즘이다.
 
커널 2.6.x의 스케줄러가 O(1) 알고리즘을 사용한다는것은 실행중인 프로세스의 갯수와 상관없이 

프로세스를 스케줄하는데 항상 일정한 시간이 걸린다는것이다. 다음 그림은 Rusty Russell의 

hackbench를 이용하여 커널 2.4.18-3과 2.6.0-test9를 1 CPU에서 벤치마킹한 결과를 인용하였다. 

더 자세한 내용은 http://developer.osdl.org/craiger/hackbench/에서 확인해 보기 바란다.
 
스케줄러 벤치마크
 
이러한 이유로 커널 2.6.x에서는 CPU의 load가 높거나 많은 프로세스가 실행 중이더라도 

사용자나 다른 프로세스의 요구에 빠른 응답을 보낼수있게된다. 

커널 2.6.x의 스케줄러는 이 O(1) 알고리즘 뿐만아니라 다른 특징들도 가지고있는데 결과적으로

시스템의 안정성과 효율성을 높였다.

가상 메모리

struct pte_chain {
unsigned long next_and_idx;
pte_addr_t ptes[NRPTE];
} ____cacheline_aligned;

 
커널 2.6.x는 Rik van Riel의 r-map (reverse mapping)의 도입으로 위 pte_chain이라는 

구조체를 두어 physical 에서 virtual로 쉽게 역매핑이 가능하다.
 
이것은 기존처럼 어떤 프로세스가 특정 physical page를 reference 하고있는지 찾기위해 

모든 프로세스의 page table을 뒤질 필요가 없어졌고 그러므로 위와 같은 특정 상황에서는 

뛰어난 성능을 보여줄수 있다. 

Interrupt Handler

 
인터럽트 핸들러의 리턴 type이 기존에는 void 였으나 커널 2.6.x에서는 irqreturn_t라는

int형의 리턴값을 가진다. 그리고 정상적으로 수행되었을때는 IRQ_HANDLED을 리턴한다.

static irqreturn_t interrupt_handler(int irq, void *dev_id,
struct pt_regs *regs)
{
/* run handler */
return IRQ_HANDLED;
}

HZ

 
기존에 HZ값은 100이었지만 커널 2.6.x에서는 architecture마다 다르므로 

schedule_timeout()등 시간과 관련된 함수를 사용할때는 꼭 HZ를 이용해야

원하는 결과를 얻을수 있을것이다.

예를들면 1초의 timeout를 주고싶다면 schedule_timeout(100) 이렇게 timeout값을 

직접적으로 사용하지 말고 schedule_timeout(HZ)로 만약 500ms timeout을 주고싶다면

schedule_timeout(HZ/2) 이런식으로 HZ를 이용하면 된다.

jiffies

 
32-bit 시스템에서 32bit jiffies의 경우는 HZ가 100일때는 시스템 부팅후 497일정도면 

wrap되었지만 HZ가 1000일경우는 49일정도면 wrap되어 버린다. 

그래서 32-bit 시스템에서 64bit jiffies를 원한다면 다음과 같이 얻어와야 한다.

u64 my_time = get_jiffies_64();

 
timer 관련 커널함수를 사용할때 이런 부분도 유의하기 바란다.

디바이스 넘버

 
unsigned short였던 kdev_t가 없어지고 unsigned int인 dev_t로 바뀌면서 

32bit로 확장되어 기존에 major number 8bit, minor number 8bit에서 

major number 12bit, minor number 20bit가 할당되었다.

ndelay

 
ndelay() 추가. 하드웨어와 CPU의 속도 증가로 인해 좀더 정밀한 타이밍을 위해 nano second delay가 

추가되었다. 하지만 대부분의 architecture에서는 ndelay(1)은 udelay(1)과 같을것이다.

모듈의 변화

 
커널 2.6.x 에서는 모듈구현 방식이 2.4.x와는 완전히 다르고 모듈명 또한 .ko(kernel object)로 

변경되었다. 또한 vermagic(version magic)이라는 커널버전 정보를 가지고 있어 현재 실행중인 

커널과 이 정보가 맞지 않으면 모듈이 올라가지 않는다. modinfo로 모듈의 버전정보를 알수있다.
 
모듈 버전정보 확인

suni00:linux-2.6.1/drivers/misc> modinfo simple_driver.ko
author: Won-young Chung <suni00@mizi.com>
description: Simple Device Driver for Kernel 2.6.x
license: GPL
vermagic: 2.6.1 preempt PENTIUM4 gcc-3.2
depends:

 
2.6.x 에서 새로운 모듈을 로딩하기 위해서는 Rusty Russel의 module-init-tools를 컴파일하여

새로운 modprobe, insmod, rmmod, depmod, lsmod를 만들어야 한다.

sysfs

 
mount -t sysfs none /sys

[P]

추가할것

initramfs

 
initrd를 대체할수 있으며 커널 컴파일시 built-in 된다.

커널소스 디렉토리중 usr밑에 그 예가 있으며 다음과 같이 임시 root 디렉토리를 구성했다면 
 
# cd temp_root

# find | cpio -co > ../initramfs_data.cpio
 
와 같은 방법으로 root 이미지를 만들수 있다. 그러면 커널 컴파일시 initramfs_data.cpio.gz가 

initramfs_data.o가 되어 커널이미지에 추가될것이다.

[P]

추가할것

wait_event() 관련

[P]

정리할것

DECLARE_WAIT_QUEUE_HEAD(queue);
DECLARE_WAITQUEUE(wait, current);

for (;;) {
add_wait_queue(&queue, &wait);
set_current_state(TASK_INTERRUPTIBLE);
if (condition)
break;
schedule();
remove_wait_queue(&queue, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
}
set_current_state(TASK_RUNNING);


2.6.x
DECLARE_WAIT_QUEUE_HEAD(queue);
DEFINE_WAIT(wait);

while (! condition) {
prepare_to_wait(&queue, &wait, TASK_INTERRUPTIBLE);
if (! condition)
schedule();
finish_wait(&queue, &wait)
}

LOGO

 
예전엔 fblogo로 png 이미지 파일을 *.h로 변환하고 fbcon.c에서 LOGO의 높이및 폭의

크기를 지정해줘야 했지만 2.6.x에서는 이미지 변환과정이 필요없어졌다.

커널소스 디렉토리 drivers/video/logo ppm이란 이미지파일을 넣고 logo.c와

Makefile에서 정의해 주면 된다.

ppm이란 파일 자체는 Gimp등에서 쉽게 변환할수 있다.

[P]

추가할것

Keyboard

 
handle_scancode()가 없어졌으며 input device로 만들어야 한다.

ARM processor 등에서 버튼 드라이버를 만들때 이젠 그리 만만치 않을것으로 보인다.

임시로 put_queue() EXPORT_SYMBOL해서 쓸수도 있을것이다.

[P]

추가할것

커널 config 등록및 컴파일

 
커널 2.6.x 소스를 받고 tar로 풀어보면 소스 디렉토리의 구조에 변화가 있음을 알수있는데

원하는 디바이스 드라이버를 어떤 위치에서 만들것이며 커널 config는 어떤 식으로 추가하며 

컴파일 하는지를 살펴보자.

Kconfig

 
Kconfig는 커널 2.6.x 버전 이전에 각각의 디렉토리내에 위치하던 Config.in과 

Documentation 디렉토리에 위치하던 도움말 파일인 Configure.help가 합쳐진 형태의 

커널 config 설정 파일이다. 우리가 'make menuconfig'등으로 커널 옵션을 설정할때

보여지는 부분이 이 파일에 있다.

커널 2.6.x의 sound 디렉토리밑에 있는 Kconfig를 예로 자세히 알아보자. 

sound/Kconfig

# sound/Config.in
#

menu "Sound"

config SOUND
tristate "Sound card support"
help
If you have a sound card in your computer, i.e. if it can say more
than an occasional beep, say Y. Be sure to have all the information
about your sound card and its configuration down (I/O port,
interrupt and DMA channel), because you will be asked for it.
.
.
.

 
위는 커널소스 'sound/' 디렉토리의 Kconfig의 일부분이다.
 
여기 Kconfig는 'make menuconfig'등으로 커널 config 설정할때

+- Device Drivers --->
+- Sound --->
+- < > Sound card support

 
위치에서 볼수있는데 위와 같이 'Device Drivers' 밑에 'Sound'라고 나타나는 까닭은 커널소스 

'drivers/Kconfig'에서 'sound/Kconfig'를 다음과 같이 불러주기 때문이다.

source "sound/Kconfig"

 
'source'라는 것은 현재 Kconfig에서 'source' 다음위치에 있는 설정파일을 불러온다.

일반적인 프로그래밍 언어에서 '#include'와 비슷한 역할이라고 보면되겠다.
 
Kconfig의 구성은 다음과 같다.

·         menu "Sound"

·                           
·                          커널 config 설정 make menuconfig등을 할때 'Sound  --->' 이렇게 보이는

·                          부분이다.

·         config SOUND

·                           
·                          커널 config 설정에서 실제 Sound를 선택했을때 CONFIG_가 붙어 CONFIG_SOUND와 

·                          같은 식으로 활성화 된다. 우리가 'make menuconfig'등을 하고 저장하고 빠져나왔을때 

·                          우리가 선택한 것들에 대한 기록이 모듈로 선택했을 경우는 'CONFIG_SOUND=m',

·                          커널내에 포함시켰을 경우는 'CONFIG_SOUND=y'로 남게된다.

·                          (커널 config 설정후 커널소스내 .config 파일을 열어보면 확인할 수 있다.)

·         tristate "Sound card support"

·                           
·                          tristate란 'Sound card support'를 3가지 상태로 둘수있는것을 의미하는데 3가지 상태란

·                          선택 안하던지, 모듈로 하던지 또는 커널내 포함(built-in)할 수 있다는 의미이다.

·                          여기서 tristate 대신 bool을 쓰게되면 선택 안하던지 아님 커널내 built-in 하던지 두가지를 

·                          선택할수 있으며 모듈로는 할수 없게된다. (커널 config 설정하다보면 'M' 즉 모듈로 선택할수

·                          없는것들이 모두 bool을 쓴것이다.)  

·         help

·                           
·                          타이틀 그대로 커널 config 화면에서 'Help'를 선택했을때 보여주는 도움말이다.
 
커널소스 'Documentation/kbuild/kconfig-language.txt'에서 좀더 상세한 내용을 볼수있으니 

참고하기 바란다. 

Kconfig 만들기

 
앞에서 설명한 내용을 참고로 뒤에 나올 디바이스 드라이버 예제를 위해 간단한 Kconfig를 만들어 보자.

위치는 앞으로 만들 디바이스 드라이버 예제와 성격이 맞는 커널소스 'drivers/misc'로 하겠다. 

(또 한가지 이유는 이 디렉토리에는 파일들이 별로없어서이다.)



다음과 같은 내용으로 drivers/misc/Kconfig를 만들자.

#
# Misc strange devices
#

menu "Misc devices"

config SIMPLE_DRIVER
tristate "Simple Device Driver"
help
This is a simple device driver for kernel 2.6.x.

endmenu

 
이미 Kconfig의 구성에 대해 설명했으므로 쉽게 이해할수 있을것이다.
 
이제 이 Kconfig가 다음과 같은 위치에 보이게 하기위해서는 'drivers/Kconfig'에 

'source "drivers/misc/Kconfig"' 를 추가하자.

(커널 버전에 따라 이미 '# source "drivers/misc/Kconfig"' 이와같이 추가되어

 있는 경우에는 앞에 주석을 풀어주자.)

+- Device Drivers --->
+- Misc devices --->
+- <M> Simple Device Driver

 

 
추가한 커널 컴파일 옵션

http://pain.mizi.com/wikix/file/MyDocKernel26DeviceDriver/simple%2Dconfig.png
 
여기까지의 과정으로 커널 설정파일 '.config'에는 'CONFIG_SIMPLE_DRIVER=m'이 

활성화된다.

Makefile

 
Makefile은 커널 config 설정(.config)에서 선택한 커널옵션을 위해 실제 어떤 소스파일을

컴파일 해야하는지 알고 그파일을 컴파일 할수있게 해준다.

sound/Makefile

 
일단 앞에서 'sound/Kconfig'를 예로 들었으므로 Makefile은 커널소스 'sound/Makefile'을

예로 설명하겠다. 

# Makefile for the Linux sound card driver
#

obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_SOUND_PRIME) += oss/
obj-$(CONFIG_DMASOUND) += oss/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/ parisc/ pcmcia/

ifeq ($(CONFIG_SND),y)
obj-y += last.o
endif

soundcore-objs := sound_core.o sound_firmware.o

 
만약 CONFIG_SOUND를 built-in 했다면 CONFIG_SOUND는 'y'로 대입되어 

'obj-y += soundcore.o'가 된다. 마찬가지로 모듈로 선택했다면 'obj-m += soundcore.o' 된다.

어찌되었든 CONFIG_SOUND를 모듈 또는 built-in으로 선택하면 soundcore.c라는 소스파일을 컴파일

하도록 되어있는것은 분명한걸 알수있다. 
 
참고로 'Sound card support'를 모듈로 했다면 커널소스의 최상위 경로에서 다음과 같은 명령으로 

sound 밑의 모듈만 컴파일해서 얻을수 있다.

make SUBDIRS=sound modules

Makefile에 추가하기

 
이제 우리가 만든 CONFIG_SIMPLE_DRIVER를 선택했을때 컴파일이 가능하도록

'driver/misc/Makefile'을 만들어보자.

컴파일할 파일은 simple_driver.c라고 가정하고 뒷부분에서 실제 이 테스트 

디바이스 드라이버를 만들어 볼것이다.

#
# Makefile for misc devices that really don't fit anywhere else.
#
#obj- := misc.o # Dummy rule to force built-in.o to be made

obj-$(CONFIG_SIMPLE_DRIVER) += simple_driver.o

 
모듈로 컴파일하면 컴파일 시간이나 원하는 대로 테스트 디바이스 드라이버를 수정해서

재부팅없이 테스트 가능하므로 다음과 같이 모듈로 컴파일 하도록 하자. 

make SUBDIRS=drivers/misc modules

 
simple_driver.c 파일이 있다면 simple_driver.ko라는 모듈이 생긴다.

  

디바이스 드라이버 예제

 
이제 커널 2.6.x용 simple_driver.c라는 디바이스 드라이버를 만들어 보자.

커널 2.4.x와 커널 2.6.x 디바이스 드라이버의 차이를 알아보기위해 

simple_driver-2.4.c라는 2.4.x용 디바이스 드라이버를 커널 2.6.x용

simple_driver.c로 변경하는 형식으로 설명하겠다.

이 디바이스 드라이버는 user의 입력을 지정된 시간만큼 타이머가 돌면서 다시 출력해주는

일을 한다. 실제 이런 디바이스 드라이버가 필요하진 않겠지만 커널 2.6.x에서 

새로운 function이나 문법을 설명하기위해 예를 들어 만들어보았다.

조금 억지스러운 면이나 불필요한 부분이 있더라도 그냥 이해하기 바란다.

또 한가지 이 디바이스 드라이버는 x86이든 arm이든 범용적이므로 직접 테스트 해보기

바란다.

simple_driver-2.4.c

1 /*
2 * drivers/misc/simple_driver-2.4.c
3 *
4 * Copyright (C) 2004, Won-young Chung <suni00@mizi.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 *
11 * This is a simple device driver for kernel 2.4.x.
12 *
13 *
14 * Author: Won-young Chung <suni00@mizi.com>
15 *
16 * Wed Jan 14 2004 Won-young Chung <suni00@mizi.com>
17 * - initial
18 *
19 */
20
21
22 #include <linux/config.h>
23 #include <linux/module.h>
24 #include <linux/kernel.h>
25 #include <linux/init.h>
26 #include <linux/types.h>
27 #include <linux/fs.h>
28 #include <linux/slab.h>
29 #include <linux/delay.h>
30 #include <linux/devfs_fs_kernel.h>
31 #include <linux/moduleparam.h>
32
33 #include <asm/uaccess.h>
34
35
36
37 #define SIMPLE_IOCTL_TEST _IOW('p', 0x80, unsigned int)
38
39 #define SIMPLE_MAJOR 251
40 #define DEVICE_NAME "pain/0"
41
42 #undef SIMPLE_DEBUG
43 #ifdef SIMPLE_DEBUG
44 #define DPRINTK(fmt, args...) printk(KERN_INFO "%s: " fmt, __FUNCTION__ , ## args)
45 #else
46 #define DPRINTK(fmt, args...)
47 #endif
48
49 static int open_count = 0;
50
51 static devfs_handle_t devfs_handle;
52
53
54 static int param_num = 1;
55 MODULE_PARM(param_num, "i");
56 MODULE_PARM_DESC(param_num, "parameters number");
57
58 static char *param_name = "parameters_name";
59 MODULE_PARM(param_name, "s");
60 MODULE_PARM_DESC(param_name, "parameters name");
61
62
63 static DECLARE_WAIT_QUEUE_HEAD(simple_wq);
64
65 static struct timer_list timer_task;
66
67 static unsigned long buffer_len = 1024;
68 static int buffer_pos = 0;
69 static int buffer_end = 0;
70 static unsigned char *buff_data = NULL;
71
72
73 static inline int buffer_empty(void)
74 {
75 return (buffer_pos == buffer_end);
76 }
77
78 static inline int buffer_full(void)
79 {
80 int pos = buffer_end + 1;
81 if (pos == buffer_len) pos = 0;
82 return (pos == buffer_pos) ? 1 : 0;
83 }
84
85 static struct tq_struct simple_bh_task;
86
87 static void timer_handler(unsigned long data)
88 {
89 DPRINTK("%lun", jiffies);
90
91 schedule_task(&simple_bh_task);
92
93 mod_timer(&timer_task, jiffies + (param_num * HZ));
94 }
95
96 static void simple_bh(void *data)
97 {
98 DPRINTK("run taskqueue!n");
99
100 while (!buffer_empty()) {
101
102 printk("%c", buff_data[buffer_pos++]);
103
104 if (buffer_pos == buffer_len) buffer_pos = 0;
105 }
106
107 wake_up_interruptible(&simple_wq);
108 }
109
110 static struct tq_struct simple_bh_task = {
111 routine: simple_bh
112 };
113
114
115 static ssize_t simple_read(struct file *file, char *buffer, size_t count,
116 loff_t * posp)
117 {
118 DPRINTK("count=%dn", count);
119
120 return 0;
121 }
122
123 static ssize_t simple_write(struct file *file, const char *buffer,
124 size_t count, loff_t * posp)
125 {
126 int ret = 0, end;
127
128 DPRINTK("count=%dn", count);
129
130 if (buffer_full()) {
131 if (file->f_flags & O_NONBLOCK) {
132 if (ret > 0) return ret;
133 return -EAGAIN;
134 }
135
136 /* wait for a buffer to become free */
137 interruptible_sleep_on(&simple_wq);
138 }
139
140 while (!buffer_full() && (count > 0)) {
141 end = buffer_end; /* because race condition */
142 get_user(buff_data[end++], buffer++);
143
144 if (end == buffer_len) end = 0;
145 buffer_end = end;
146
147 --count;
148 ++ret;
149 }
150
151 DPRINTK("write %d bytesn", ret);
152
153 return ret;
154 }
155
156 static int simple_open(struct inode *inode, struct file *file)
157 {
158 DPRINTK("n");
159
160 if (!open_count) {
161 open_count = 1;
162 } else {
163 printk("device already open !n");
164 return -EBUSY;
165 }
166
167 if (buff_data) kfree(buff_data);
168 buff_data = kmalloc(buffer_len, GFP_KERNEL);
169
170 buffer_pos = buffer_end = 0; /* make buffer empty */
171
172 add_timer(&timer_task);
173
174 MOD_INC_USE_COUNT;
175
176 return 0;
177 }
178
179 static int simple_release(struct inode *inode, struct file *file)
180 {
181 DPRINTK("n");
182
183 flush_scheduled_tasks();
184
185 del_timer_sync(&timer_task);
186
187 if (buff_data) kfree(buff_data);
188 buff_data = NULL;
189
190 open_count = 0;
191
192 MOD_DEC_USE_COUNT;
193
194 return 0;
195 }
196
197 static int simple_ioctl(struct inode *inode, struct file *file,
198 unsigned int cmd, unsigned long arg)
199 {
200 long val;
201
202 DPRINTK("cmd = %xn", cmd);
203
204 switch (cmd) {
205 case SIMPLE_IOCTL_TEST:
206 if (get_user(val, (unsigned long *)arg))
207 return -EINVAL;
208 param_num = val;
209 printk("param_num=%dn", param_num);
210 break;
211 default:
212 return -ENOIOCTLCMD;
213 }
214
215 return 0;
216 }
217
218 struct file_operations simple_fops = {
219 read: simple_read,
220 write: simple_write,
221 ioctl: simple_ioctl,
222 open: simple_open,
223 release: simple_release
224 };
225
226
227 static void __exit simple_exit(void)
228 {
229 devfs_unregister(devfs_handle);
230
231 printk("%s : module removed!n", __FUNCTION__);
232 }
233
234 static int __init simple_init(void)
235 {
236 printk("%s : module load!n", __FUNCTION__);
237
238 init_timer(&timer_task);
239 timer_task.function = timer_handler;
240 timer_task.expires = jiffies + (param_num * HZ);
241
242 devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT,
243 SIMPLE_MAJOR, 0,
244 S_IFCHR | S_IRUSR | S_IWUSR, &simple_fops,
245 NULL);
246
247 printk("param_name : %s , param_num : %dn", param_name, param_num);
248
249 return 0;
250 }
251
252 module_init(simple_init);
253 module_exit(simple_exit);
254
255 MODULE_AUTHOR("Won-young Chung <suni00@mizi.com>");
256 MODULE_DESCRIPTION("Simple Device Driver for Kernel 2.4.x");
257 MODULE_LICENSE("GPL");

 
1부터 19라인 까지는 소스코드의 위치, 어떤일을 하는가, 언제 처음 만들었으며 update되었다면 

어떤 부분이 개선되었는지 기록하는 실제 코딩을 하기전 준비단계이다.

자신만의 스타일로 만들면 되겠다.
 
22부터 33라인 까지는 실제 코딩에서 쓰이는 function들 또는 매크로의 헤더파일을 불러온다.

같은 2.4대의 커널이라도 커널버전에 따라 헤더파일들이 다를 수도있다.

대표적인것이 예전에 '#include <linux/malloc>'이 '#include <linux/slab.h>'으로

'#include <linux/module.h>'가 '#include <linux/moduleparam.h>'로 바뀌었다.
 
37라인은 ioctl 테스트를 위해 현재 다른곳에서 안쓰일것 같은 넘버로 define 하였다.

ioctl number에 대한 자세한 내용은 커널소스의 'Documentation/ioctl-number.txt'을

참고하기 바란다.
 
39,40라인은 등록할 디바이스의 메이저 넘버와 디바이스 명이다.
 
42부터 47라인까지는 디버깅을 위해 define하였으며 이 디바이스 드라이버의 디버깅 메세지를 

보고싶다면 42라인 #undef SIMPLE_DEBUG#define SIMPLE_DEBUG 하면된다.

디바이스 드라이버 초기 개발단계에서는 실제 그 function이 동작하는지 또는 제대로된 값을

읽어오는지등을 알아보기위해 printk를 많이 쓰게되는데 이렇게 define하면 SIMPLE_DEBUG를

define또는 undef 에 따라 간단히 디버깅 메세지를 찍거나 안찍을수 있다.
 
49라인 'open_count'는 생성된 디바이스 노드에 여러번 접근하는것을 막을 목적으로 사용하였다.
 
51라인은 devfs를 사용하기 위해 선 정의한것이다.
 
54부터 60라인은 모듈 로드시 줄수있는 parameter값을 정의했는데 2.6.x에서는

조금 간편하게 달라졌다. 54부터 60라인을 커널 2.6.x용으로 바꾼다면 다음과 같다.

static char *param_name = "parameters_name";
module_param(param_name, charp, 0);
static int param_num = 1;
module_param(param_num, int, 0);

 
모듈 로드시 'insmod ./simple_driver.ko param_name="문자" param_num=숫자'

이런식으로 인자값을 줄수있다.

module_param에 줄수있는 type으로는 byte, short, ushort, int, uint, long,

ulong, charp, bool or invbool등이 있다.

실제 이 디바이스 드라이버에서 param_num은 timer의 갱신주기로 몇 초마다 

사용자의 입력을 출력할것인지 하는 시간을 정하는 용도로 사용했다.
 
63라인은 wait_queue를 사용하기 위한 정의이며 이와 관련한 107라인

'wake_up_interruptible()은 2.4.x에서는 <linux/sched.h>''에 있었으나 

커널 2.6.x에서는 <linux/wait.h>에 정의되어있다. 

그리고 커널 2.6.x에서는 다음 매크로가 추가되었다는것도 참고로 알아두기 바란다.
 
wait_event_interruptible_timeout(wq, condition, timeout)
 
이것은 wait_event_interruptible(wq, condition)의 매크로에 timeout이

추가된것이다.
 
65라인은 타이머 인터럽트를 사용하기위해 구조를 정의하였다.
 
67부터 83라인까지는 버퍼를 사용하기위한 정의이다.

여기서는 환형큐(circular queue) 자료구조를 사용하고있다.
 
85라인은 bottom half를 사용하기위한 구조이며 91라인의 

schedule_task(&simple_bh_task);에 의해 110라인의 루틴인

static void simple_bh(void *data)가 수행된다.

커널 2.6.x 에서는 workqueue로 커널 2.4.x의 task queue를 대체하며

다음과 같이 선언하고 사용할수있다.
 
#include <linux/workqueue.h>
 
static DECLARE_WORK(simple_work, simple_bh, NULL);

 
schedule_work(&simple_work);
 
87라인 static void timer_handler(unsigned long data)

timer interrupt에 수행되는 timer handler로 91라인에 의해 simple_bh()

수행하게된다. 그리고 93에서 timer를 갱신하며 모듈 인자값인 param_num의 값에의해

갱신 주기가 결정되도록 되어있다.
 
96라인의 static void simple_bh(void *data)는 91라인의 

schedule_task(&simple_bh_task)에 의해 실행되는 bottom half 이다.

여기서 하는일은 버퍼에 들어있는 내용을 printk로 출력하는 일이다. 

107라인 wake_up_interruptible(&simple_wq);은 137라인 

interruptible_sleep_on(&simple_wq);에 의해 sleep일때 깨어나는 곳이다.
 
115라인 simple_read()는 file operation중 read부분이며 여기서는 어떤일도

하지않고 그냥 0를 return 한다.

즉 이 드라이버의 디바이스 노드인 /dev/pain/0 cat등으로 읽어도

항상 0 byte를 return 하므로 어떤 반응도 없다.

구색을 갖추기위해 넣어 두었다고 보면 되겠다.
 
123라인 simple_write()는 file operation중 wirte이며 버퍼를 사용자가 

입력한 값으로 채우는 일을한다. 'cat > /dev/pain/0' 이런식으로 write 테스트를

해봐도 된다.

130라인에서 보듯이 버퍼가 꽉 차있을 경우는 버퍼를 비우기위해 sleep한다. 

140라인은 버퍼에 내용이 있고 사용자가 입력한값들을 버퍼에 다 쓰지 않았을경우

사용자 입력을 계속 버퍼에 넣는 일을한다.

141과 145라인을 보면 하지않아도 되는 일을 하는건 아닌가 생각 할수도있을것이다.

그래서 두 라인을 제거하고 다음과 같은 식으로 했다고 가정하자.

142 get_user(buff_data[buffer_end++], buffer++);
143
144 if (buffer_end == buffer_len) end = 0;

 
하지만 이렇게 했을경우에는 race condition이 발생할수있다.

142라인에서 buffer_end++에 의해 buffer_end값을 증가시킨후 그 다음 라인인

144라인을 수행하기전에 인터럽트등에 의해 다른곳에서 buffer_end 값을 수정한후에

144라인이 수행되는 race condition이 발생할수있다. 

그렇기에 이런 문제가 발생하지 않게하기위해 여러가지 동기화 방법을 쓸수도 있지만

예제와 같이 간단하게 해결할수있기에 그렇게 하였다.

커널 2.6.x에서는 위와 다르게 새로 추가된 lock인 seqlock(sequence lock)을

사용하여 해결할수도있다.
 
seqlock은 다음과 같이 선언하며
 
#include <linux/seqlock.h>
 
seqlock_t lock1 = SEQLOCK_UNLOCKED;
 
다음과 같이 wrtie시 lock을 걸고 해제하며
 
write_seqlock(&lock1);

/* Make changes here */

write_sequnlock(&lock1);
 
다음과 같이 lock이 해제되었는지 체크하여 사용한다.
 
do {

    seq = read_seqbegin(&lock1);

    /* Make a copy of the data of interest */

} while read_seqretry(&lock1, seq);
 
seqlock시에는 비선점이라는 것도 알아두자.



156라인 simple_open()은 file operation중 open이며 160부터 165라인에서

보듯이 현재 디바이스 노드가 open되어있는 상황에서 다시 open하려 할때 디바이스가

사용중임을 나타내는 -EBUSY를 리턴한다. open_count는 190라인에 의해 release시

다시 초기화된다. 그리고 simple_open()에서는 초기 버퍼를 할당하고

168라인 buff_data = kmalloc(buffer_len, GFP_KERNEL);에 의해 

buffer_len만큼의 메모리를 할당한다.

타이머 인터럽트가 동작하도록 172라인에서 add_timer(&timer_task);하고있다.

174라인의 MOD_INC_USE_COUNT;는 커널 2.6.x에서는 더이상 해줄 필요가 없다.
 
179라인 simple_release()는 file operation중 release이며 

flush_scheduled_tasks();에 의해 스케쥴되는것이 있다면 다 동작시키고 

185라인 del_timer_sync(&timer_task);에 의해 타이머 인터럽트를 멈추게 한다.

그리고 버퍼를 위해 할당했던 메모리를 해제한다.

simple_open()때와 마찬가지로 커널 2.6.x에서는 MOD_DEC_USE_COUNT;

해줄필요는 없다.
 
197라인 simple_ioctl()은 file operation중 ioctl에 해당하며 여기서는

사용자의 입력을 param_num으로 넘겨준다. param_num값은 timer의 주기에 해당하므로

모듈을 올린후 timer의 주기를 바꾸기위한 목적으로 사용하도록 하였다.
 
218라인에서 이 디바이스 드라이버의 file operation을 정의했는데 커널 2.4.x에서

gcc label 확장으로 인해 일반적으로 다음과 같이 정의 했었는데
 
struct file_operations simple_fops = {

        read:           simple_read,

        write:          simple_write,

        ioctl:          simple_ioctl,

        open:           simple_open,

        release:        simple_release

};
 
커널 2.6.x에서는 ANSI C의 표준을 지켜 다음과 같이 사용하길 권장하고있다.
 
struct file_operations simple_fops = {

        .read           = simple_read,

        .write          = simple_write,

        .ioctl          = simple_ioctl,

        .open           = simple_open,

        .release        = simple_release,

};
 
227라인은 디바이스 드라이버의 exit 즉 모듈 내릴때의 동작으로 등록했던

디바이스 노드및 디렉토리를 해제한다.
 
234라인은 디바이스 드라이버의 init과정으로 초기 모듈이 load될때 수행된다.

238에서 240라인은 타이머 인터럽트를 사용하기위한 준비과정이고 

그외는 char 디바이스로 등록하는 부분인데 여기서는 devfs를 사용하고있다.   

커널 2.4.x까지는 devfs_register() function으로 file_operations까지

등록이 되었는데 커널 2.6.x에서는 devfs_register()가 없어지고 

devfs_mk_cdev(), devfs_mk_bdev()으로 char또는 block 디바이스를

등록한다.

그러므로 커널 2.6.x에서는 devfs_mk_cdev()로 디바이스 노드를 만드는데

devfs_mk_cdev() file_operations을 다루는 부분이 없기때문에 

file_operations은 register_chrdev()에서 다뤄주고 있다.

(현재 devfs는 devfs의 개발자가 maintain을 장기간 하고있지 않아 커널 개발자들은

 이에 대한 대안으로 udev등의 논의가 있는걸로 알고있다.)
 
252와 253라인은 모듈 init과 exit의 entry 포인터를 등록하고 해제해주는 부분이며

예전에는 init_module(), cleanup_module()로 직접 init과 exit를

하기도 했다.
 
255에서 257라인은 모듈 정보를 담고있으며 특히 MODULE_LICENSE("GPL");

같이 GPL이 아닌경우 GPL로된 커널 라이브러리등을 사용할 수 없는것도 있다.

simple_driver.c

 
앞에서 본 2.4.x용 디바이스 드라이버를 커널 2.6.x용으로 수정한 결과이다.

/*
* drivers/misc/simple_driver.c
*
* Copyright (C) 2004, Won-young Chung <suni00@mizi.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*
* This is a simple device driver for kernel 2.6.x.
*
*
* Author: Won-young Chung <suni00@mizi.com>
*
* Wed Jan 14 2004 Won-young Chung <suni00@mizi.com>
* - initial
*
*/


#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/moduleparam.h>
#include <linux/workqueue.h>
#include <linux/seqlock.h>

#include <asm/uaccess.h>



#define SIMPLE_IOCTL_TEST _IOW('p', 0x80, unsigned int)

#define SIMPLE_MAJOR 251
#define DEVICE_NAME "pain/0"

#undef SIMPLE_DEBUG
#ifdef SIMPLE_DEBUG
#define DPRINTK(fmt, args...) printk(KERN_INFO "%s: " fmt, __FUNCTION__ , ## args)
#else
#define DPRINTK(fmt, args...)
#endif

static int open_count = 0;

static char *param_name = "parameters_name";
module_param(param_name, charp, 0);
static int param_num = 1;
module_param(param_num, int, 0);

static DECLARE_WAIT_QUEUE_HEAD(simple_wq);

static struct timer_list timer_task;

static void simple_bh(void *data);
static DECLARE_WORK(simple_work, simple_bh, NULL);

seqlock_t lock1 = SEQLOCK_UNLOCKED;
static unsigned int seq;


static unsigned long buffer_len = 1024;
static int buffer_pos = 0;
static int buffer_end = 0;
static unsigned char *buff_data = NULL;


static inline int buffer_empty(void)
{
return (buffer_pos == buffer_end);
}

static inline int buffer_full(void)
{
int pos = buffer_end + 1;
if (pos == buffer_len) pos = 0;
return (pos == buffer_pos) ? 1 : 0;
}


static void timer_handler(unsigned long data)
{
DPRINTK("%lun", jiffies);

schedule_work(&simple_work);

mod_timer(&timer_task, jiffies + (param_num * HZ));
}

static void simple_bh(void *data)
{
DPRINTK("run workqueue!n");

do {
seq = read_seqbegin(&lock1);
while (!buffer_empty()) {
printk("%c", buff_data[buffer_pos++]);

if (buffer_pos == buffer_len) buffer_pos = 0;
}
} while (read_seqretry(&lock1, seq));

wake_up_interruptible(&simple_wq);
}


static ssize_t simple_read(struct file *file, char *buffer, size_t count,
loff_t * posp)
{
DPRINTK("count=%dn", count);

return 0;
}

static ssize_t simple_write(struct file *file, const char *buffer,
size_t count, loff_t * posp)
{
int ret = 0;

DPRINTK("count=%dn", count);

if (buffer_full()) {
if (file->f_flags & O_NONBLOCK) {
if (ret > 0) return ret;
return -EAGAIN;
}

/* wait for a buffer to become free */
interruptible_sleep_on(&simple_wq);
}

while (!buffer_full() && (count > 0)) {
write_seqlock(&lock1);
get_user(buff_data[buffer_end++], buffer++);

if (buffer_end == buffer_len) buffer_end = 0;
write_sequnlock(&lock1);

--count;
++ret;
}

DPRINTK("write %d bytesn", ret);

return ret;
}

static int simple_open(struct inode *inode, struct file *file)
{
DPRINTK("n");

if (!open_count) {
open_count = 1;
} else {
printk("device already open !n");
return -EBUSY;
}

if (buff_data) kfree(buff_data);
buff_data = kmalloc(buffer_len, GFP_KERNEL);

buffer_pos = buffer_end = 0; /* make buffer empty */

add_timer(&timer_task);

return 0;
}

static int simple_release(struct inode *inode, struct file *file)
{
DPRINTK("n");

flush_scheduled_work();

del_timer_sync(&timer_task);

if (buff_data) kfree(buff_data);
buff_data = NULL;

open_count = 0;

return 0;
}

static int simple_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
long val;

DPRINTK("cmd = %xn", cmd);

switch (cmd) {
case SIMPLE_IOCTL_TEST:
if (get_user(val, (unsigned long *)arg))
return -EINVAL;
param_num = val;
printk("param_num=%dn", param_num);
break;
default:
return -ENOIOCTLCMD;
}

return 0;
}

struct file_operations simple_fops = {
.read = simple_read,
.write = simple_write,
.ioctl = simple_ioctl,
.open = simple_open,
.release = simple_release,
};


static void __exit simple_exit(void)
{
unregister_chrdev(SIMPLE_MAJOR, DEVICE_NAME);

devfs_remove("pain/%d", 0);
devfs_remove("pain");

printk("%s : module removed!n", __FUNCTION__);
}

static int __init simple_init(void)
{
printk("%s : module load!n", __FUNCTION__);

init_timer(&timer_task);
timer_task.function = timer_handler;
timer_task.expires = jiffies + (param_num * HZ);

if (register_chrdev(SIMPLE_MAJOR, DEVICE_NAME, &simple_fops) < 0) {
printk("unable to register character devicen");
}

devfs_mk_dir("pain");
devfs_mk_cdev(MKDEV(SIMPLE_MAJOR,0),
S_IFCHR | S_IRUSR | S_IWUSR,
"pain/%d", 0);

printk("param_name : %s , param_num : %dn", param_name, param_num);

return 0;
}

module_init(simple_init);
module_exit(simple_exit);

MODULE_AUTHOR("Won-young Chung <suni00@mizi.com>");
MODULE_DESCRIPTION("Simple Device Driver for Kernel 2.6.x");
MODULE_LICENSE("GPL");

ioctl_test.c

 
위 디바이스 드라이버들에 대한 ioctl을 테스트 할수있는 프로그램이다.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <asm/ioctl.h>


#define SIMPLE_IOCTL_TEST _IOW('p', 0x80, unsigned int)

#define DEVICE_NODE "/dev/pain/0"


void usage(char * name) {
printf("usage :t"
"%s <sec>n", name);
exit(-1);
}


int main(int argc , char ** argv)
{
int fd, val;

if (argc != 2)
usage(argv[0]);

if ((fd = open(DEVICE_NODE, O_RDWR)) < 0) {
printf("Error! open %sn", DEVICE_NODE);
return -1;
}

val = atoi(argv[1]);

if (ioctl(fd, SIMPLE_IOCTL_TEST, &val) < 0) {
printf("Error! ioctl SIMPLE_IOCTL_TESTn");
return -1;
}

close(fd);

return 0;
}

 
컴파일

gcc -o ioctl_test ioctl_test.c

 
이제 테스트 결과를 보기위해 'tail -f /var/log/messages &' 실행한뒤

'insmod ./simple_driver.ko' 명령으로 모듈을 올리고 write를 하기 위해 

'cat > /dev/pain/0'한 다음 이것저것 문자를 입력해보자.  

디폴트로 1초 간격으로 사용자가 입력한 내용들이 로그로 출력될것이다.

5초 간격으로 출력되길 원한다면 './ioctl_test 5'하면 된다. 
 

마치며

여기까지 커널 2.6.x에서 디바이스 드라이버를 만들때 참고할 특징과 커널2.4.x에서 
커널 2.6.x으로 오면서 달라지거나 새로운 커널함수, 구조, 틀에 대해 알아보았다.
주 내용이 디바이스 드라이버 개발이기에 커널 2.6.x의 전반적인 내용에 관한 설명이
부족한데 다른 글들을 참고하여 같이 연계해서 본다면 좀더 도움이 될것이다.
또한 참고자료중 lwn.net에 있는 글은 새로 추가되거나 수정되는 부분들은 갱신해서
올려주므로 가끔 접속해서 살펴보는것도 좋을것이다.
되도록이면 많은 독자들에게 일반적인 디바이스 드라이버의 구조와 직접 자신의
데스크탑에서 테스트해보고 유사한 디바이스 드라이버를 만들어볼수있도록 하기위해
예제코드에서는 커널 2.6.x의 특징만이 아닌 일반적으로 많이 쓰이는 부분도 포함하고
있으니 아무쪼록 '디바이스 드라이버'의 이해에 도움이 되었으면 한다.

참고

·         http://lwn.net/Articles/driver-porting/

·         http://www-903.ibm.com/developerworks/kr/linux/library/l-inside.html

 

 

 

 

이동: Home à os0402

 

주소: http://www.kernel.bz/os/04/os0402.htm

이페이지의 저작권은

제목: 커널 2.6.x 디바이스 드라이버

저자에게 있습니다

저자: 정원영

최근수정일:2009-03-06