커널 2.6.x는 소스파일 구성부터 이전과는 큰 차이를 보이며 커널코어 및 디바이스 드라이버의 구조등에 있어서도 많은 변화가 있다. 여기서는 커널 2.4.x에서 작성했던 디바이스 드라이버를 2.6.x로 포팅하는데 있어 기존과 다른 새로운점과 이로인한 디바이스 드라이버 작성할때 변화 내용을 알아보자. 물론 커널 2.6.x에서는 여전히 2.4.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를 적용하면 다음 테스트 드라이버로 테스트 가능하다.)
이 디바이스 드라이버는 선점가능 여부를 '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)'를사용한 부분이다. 이것은 간단한 디바이스 드라이버를 만들때 유용하게 쓸수있다.
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일때는 선점가능하다는 의미이다.
이 결과를 보면 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) 알고리즘 뿐만아니라 다른 특징들도 가지고있는데 결과적으로시스템의 안정성과 효율성을 높였다.
기존에 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를 원한다면 다음과 같이 얻어와야 한다.
u64my_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로 모듈의 버전정보를 알수있다.
예전엔 fblogo로 png 이미지 파일을 *.h로 변환하고 fbcon.c에서 LOGO의 높이및 폭의크기를 지정해줘야 했지만 2.6.x에서는 이미지 변환과정이 필요없어졌다.커널소스 디렉토리 drivers/video/logo에 ppm이란 이미지파일을 넣고 logo.c와Makefile에서 정의해 주면 된다.ppm이란 파일 자체는 Gimp등에서 쉽게 변환할수 있다.
커널 2.6.x 소스를 받고 tar로 풀어보면 소스 디렉토리의 구조에 변화가 있음을 알수있는데원하는 디바이스 드라이버를 어떤 위치에서 만들것이며 커널 config는 어떤 식으로 추가하며 컴파일 하는지를 살펴보자.
Kconfig
Kconfig는 커널 2.6.x 버전 이전에 각각의 디렉토리내에 위치하던 Config.in과 Documentation 디렉토리에 위치하던 도움말 파일인 Configure.help가 합쳐진 형태의 커널 config 설정 파일이다. 우리가 'make menuconfig'등으로 커널 옵션을 설정할때보여지는 부분이 이 파일에 있다.커널 2.6.x의 sound 디렉토리밑에 있는 Kconfig를 예로 자세히 알아보자.
위치에서 볼수있는데 위와 같이 '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를 만들자.
이제 이 Kconfig가 다음과 같은 위치에 보이게 하기위해서는 'drivers/Kconfig'에 'source "drivers/misc/Kconfig"' 를 추가하자.(커널 버전에 따라 이미 '# source "drivers/misc/Kconfig"' 이와같이 추가되어 있는 경우에는 앞에 주석을 풀어주자.)
모듈로 컴파일하면 컴파일 시간이나 원하는 대로 테스트 디바이스 드라이버를 수정해서재부팅없이 테스트 가능하므로 다음과 같이 모듈로 컴파일 하도록 하자.
makeSUBDIRS=drivers/miscmodules
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이든 범용적이므로 직접 테스트 해보기바란다.