커널 프로그래밍과 시스템 콜을 만들어보고, 디바이스 드라이버에 대한 일반적인 개요를 살펴본다. 10. 커널과 디바이스 드라이버 커널 프로그래밍과 시스템 콜을 만들어보고, 디바이스 드라이버에 대한 일반적인 개요를 살펴본다.
목 차 1. Kernel Programming 이란? 2. System call 추가하기 (1) System call이란? (2) Kernel vs Application (3) Kernel programming 주의 사항 (4) Kernel Interface 함수 2. System call 추가하기 (1) System call이란? (2) POSIX API와 System calls (3) System call 원리 이해 (4) System call 추가하기 (5) System call 확장
목차 3. Device Driver (1) Device Driver 정의 (2) Device Driver 종류 (5) Makefile 만들기 (7) Driver를 커널에 모듈로 등록 및 해제하기
1. 커널 프로그래밍
Kernel Programming 이란? Linux kernel core 기능 추가 Linux kernel 알고리즘 개선
Kernel vs Application (1) 수행 방법 Application Program: 처음부터 순차적으로 수행 Kernel: 응용프로그램을 위한 system call이나 인터럽트 핸들러를 수행하기 위해 비동기적으로 수행 Application Kernel System call Interrupt
Kernel vs Application (2) Library Application Program: 모든 library를 link 하고 사용할 수 있다. Kernel: kernel에서 export 하는 것들만 사용할 수 있다. Kernel mode vs User mode Application Program: user mode에서 수행되며 하드웨어에 직접 접근하는 것과 메모리에 대한 허용되지 않은 접근이 제한된다. Kernel Program: 모든 것이 허용된다.
Kernel vs Application (3) Address Space Application Program과 Kernel Program은 서로 다른 메모리 매핑법을 가지고 있으며, 프로그램 코드는 서로 다른 address space를 가지고 있다. Kernel address space User address space 1 G byte 4 G byte 3 G byte 매모리 매핑법이란 주소 영역에서 stack 영역과 heap 영역 code 영역 등을 어떻게 사용할 것인가를 정의하는 방법을 이야기한다. 32bit cpu의 경우 4Gbyte 의 주소를 접근할 수 있는 리눅스에서는 이 4Gbyte 중 1Gbyte를 커널의 코드와 자료구조등을 저장하는데 사용하며 나머지 3Gbyte를 사용자모드 프로세스의 코드와 자료구조를 저장하는데 사용한다. 커널 영역에 해당하는 메모리를 사용하는 방식과 사용자 영역에 해당하는 메모리를 사용하는 방식이 다르기 때문에 두 영역에서 사용하는 가상 주소는 서로 다른 물리주소로 맵핑된다.
Kernel vs Application (4) Namespace pollution Application Program: 현재 개발하는 프로그램에서만 각 함수와 변수의 이름을 구별하여 주면 된다. Kernel Program: 현재 개발하는 모듈 외에도 커널 전반적으로 함수와 변수의 이름이 충돌하지 않도록 하여야 한다.
Kernel programming 주의 사항 (1) Library stdio.h 와 같은 일반 프로그램에서 사용하는 헤더 파일을 include해서는 안된다. 오직 /usr/include/linux 와 /usr/include/asm 아래에 선언된 헤더파일 만을 include 한다. Namespace pollution 외부 파일와 link하지 않을 모든 심볼을 static으로 선언 또는 외부 파일과 link할 symbol을 symbol table 등록 EXPORT_NO_SYMBOLS; EXPORT_SYMBOL(name); 전역 변수는 잘 정의된 prefix를 붙여 준다. Ex: sys_open() symbol(함수나 변수등의 identifier)을 static 으로 선언하면 link 시에 다른 object에서 참조 되지 않는다. static으로 선언하지 않는 경우 대부분 다른 object에서 참조할 수 있게 된다. 심볼을 심볼 테이블에 등록해주는 아래의 두 매크로를 사용할 경우에는 static으로 선언하지 않아도 외부에서 symbol을 참조할 수 없고 명시적으로 export해준 symbol만을 외부에서 참조할 수 있게된다. symbol table은 전역 커널 항목 -- 함수와 변수 – 을 담고 있다. /proc/ksyms에 text 형태로 저장되어 있으므로 사용자가 읽어 볼 수도 있다. Kernel 에 모듈을 추가할 경우 insmod 프로그램은 이 symbol table을 검사하여 추가하려는 모듈에서 아직 연결 되지 않은 (unresolved) symbol 을 연결 시켜 준다. EXPORT_NO_SYMBOLS; 링크할때 어떤 심볼도 다른 오브젝트에서 참조하지 못하도록 한다. EXPORT_SYMBOL(name) name에 해당하는 심볼을 다른 오브젝트에서 참조 할 수 있도록 한다.
Kernel programming 주의 사항 (2) Fault handling Kernel 은 하드웨어 접근에 대해 어떠한 제한도 없기 때문에 커널에서의 에러는 시스템에 치명적인 결과를 발생시킨다. 함수 호출 등의 작업 시 모든 에러 코드를 검사하고 처리해야 한다. Address space 커널이 사용하는 stack의 크기는 제한되어 있고, 인터럽트 핸들러도 동일한 스택을 사용하므로 큰 배열을 사용하거나, recursion이 많이 일어나지 않도록 주의해야 한다. Application 과 data를 주고 받기 위해(call by reference) 특별한 함수를 사용 하여야 한다. (뒷 부분에서 이를 위한 몇 가지 함수를 소개한다.) 커널과 application은 서로 다른 주소 공간을 사용한다. 때문에 call by reference를 사용할 수 없고 이를 위해 특별한 함수를 사용하여 커널과 application에서 data를 주고 받는다. 뒷 장에서 이러한 함수들을 소개한다. MMX[엠엠엑스]는 멀티미디어 응용프로그램들의 실행을 좀더 빠르게 할 수 있도록 설계된 인텔 펜티엄 프로세서이다. 인텔에 따르면, MMX 기능이 부가된 마이크로프로세서는 동일한 클록속도의 MMX 기능이 없는 프로세서에 비해, 멀티미디어 응용프로그램을 최고 60%까지 빠르게 실행할 수 있다고 한다. 그 외에도, MMX 마이크로프로세서는 일반적으로 다른 프로그램들도 약 10% 정도 더 빠르게 수행한다
Kernel programming 주의 사항 (3) 기타 실수연산 이나 MMX 연산을 사용할 수 없다. 커널과 application은 서로 다른 주소 공간을 사용한다. 때문에 call by reference를 사용할 수 없고 이를 위해 특별한 함수를 사용하여 커널과 application에서 data를 주고 받는다. 뒷 장에서 이러한 함수들을 소개한다. MMX[엠엠엑스]는 멀티미디어 응용프로그램들의 실행을 좀더 빠르게 할 수 있도록 설계된 인텔 펜티엄 프로세서이다. 인텔에 따르면, MMX 기능이 부가된 마이크로프로세서는 동일한 클록속도의 MMX 기능이 없는 프로세서에 비해, 멀티미디어 응용프로그램을 최고 60%까지 빠르게 실행할 수 있다고 한다. 그 외에도, MMX 마이크로프로세서는 일반적으로 다른 프로그램들도 약 10% 정도 더 빠르게 수행한다
Kernel Interface 함수 (1) 주의 사항 Kernel interface 함수 분류 Kernel program은 일반적인 library를 사용하지 못하고 kernel에서 export 해준 함수들 만을 사용할 수 있다. Kernel interface 함수 분류 Kernel 에서 제공하는 함수 중 kernel programming에 자주 사용되는 함수는 다음과 같이 분류할 수 있다. Port I/O Interrupt Memory Synchronization Kernel message 출력 Device Driver register
Kernel Interface 함수 (2) I/O device 와 data를 주고 받기 위한 함수: unsigned inb(unsigned port) Port에서 1byte를 읽는다. unsigned inw(unsigned port) Port에서 2byte를 읽는다. unsigned inl(unsigned port) Port에서 4byte를 읽는다. unsigned outb(char value, unsigned port) Port에 1byte value를 쓴다. unsigned outw(short int value, unsigned port) Port에 2byte value를 쓴다. unsigned outl(long int value, unsigned port) Port에 4byte value를 쓴다. 주변 장치에는 cpu 의 I/O 주소 영역에 mapping 되어 사용되는 장치가 있고 Memory 영역에 mapping 되어 사용되는 장치가 있다. 위 함수들은 I/O 주소 영역에 mapping 된 장치를 위한 것이고, Memory 영역에 mapping 된 장치는 일반적으로 memory를 사용하는 방식으로 접근할 수 있다.
Kernel Interface 함수 (3) I/O device 와 data를 주고 받기 위한 함수: void insb(unsigned port, void *addr, unsigned long count) Port에서 count bytes를 읽어서 메모리의 addr 주소부터 저장 void insw(unsigned port, void *addr, unsigned long count) Port에서 16bit * count 만큼 읽어서 메모리의 addr 주소부터 저장 void insl(unsigned port, void *addr, unsigned long count) Port에서 32bit * count 만큼 읽어서 메모리의 addr 주소부터 저장 void outsb(unsigned port, void *addr, unsigned long count) Memory의 addr번지 에서부터 count bytes를 읽어서 port에 쓴다. void outsw(unsigned port, void *addr, unsigned long count) Memory의 addr번지 에서부터 count * 16bit를 읽어서 port에 쓴다. void outsl(unsigned port, void *addr, unsigned long count) Memory의 addr번지 에서부터 count * 32bit를 읽어서 port에 쓴다.
Kernel Interface 함수 (4) I/O device 와 data를 주고 받기 위한 함수: Pausing I/O 예) inb() 함수의 경우 inb_p()
Kernel Interface 함수 (5) 인터럽트의 설정 및 처리에 관한 함수(or 매크로) : cli()/sti() clear/set interrupt enable save_flags(unsigned long flag), restore_flags(unsigned long flag) status register의 내용을 저장하고 복원 save_flags(), restore_flags() 두 매크로는 같은 함수 안에서 호출 되어야 한다. flag를 다른 함수로 pass해서는 안된다. int requst_irq(unsigned int irq, void (*handler)(int),unsigned long flags, const char *device) 커널로부터 IRQ를 요청하고, 이 IRQ에 대한 interrupt handler를 install cli()/sti() 는 전체 인터럽트를 금지하거나 가능하게 해주는 매크로이다. Status register는 system의 각 상태를 가지고 있는 레지스터이다. Processor 마다 차이가 있지만 일반적으로Interrupt 가 enable 상태 인지, 직전의 연산에서 carry가 발생하였는 가 등의 정보를 가지고 있다. requst_irq 는 사용자가 인터럽트를 추가하고자 할 때 사용한다. 인터럽트가 발생할 수 있는 source 를, 현재 사용하고 있지 않은 irq 에 등록하고, 이러한 인터럽트가 발생했을 때 수행될 interrupt handler 함수를 등록한다.
Kernel Interface 함수 (6) 인터럽트의 설정 및 처리에 관한 함수(or 매크로) : void free_irq(unsigned int irq) request_irq()에서 획득한 irq를 반납함
Kernel Interface 함수 (7) Kernel에서 동적 메모리를 할당할 때 사용하는 함수: void * kmalloc(unsigned int len, int priority) 커널 메모리 할당. 128~131056byte까지 가능 priority:GFP_BUFFER, GFP_ATOMIC, GFP_USER, GFP_KERNEL 물리적으로 연속적인 메모리를 할당한다. void kfree(void *obj) kmalloc()에서 할당 받은 커널 메모리를 반납 GFP_KERNEL : 일반적인 커널 메모리 할당. 할당 가능한 Memory가 부족할 경우 sleep할 수도 있다. GFP_BUFFER : 버퍼 캐쉬를 관리할때 사용되므로 할당자가 sleep상태로 갈수 있다. I/O 서브 시스템이 스스로 메모리를 필요로 할때 데드락과을 피하도록 하기 위해 디스크에 dirty page를 disk에 플러쉬함으로서 메모리를 free한다는 점에서 GFP_KERNEL과 다르다. GFP_ATOMIC : 인터럽트 핸들러 등 프로세스 컨텍스트 외부 코드에서 메모리를 할당할때 사용한다. 결코 sleep상태가 되지 않는다. GFP_USER : 사용자들이 메모리 할당할때 사용. 낮은 우선순위를 가진다. GFP_HIGHUSER High memory에서 할당할때 사용한다.
Kernel Interface 함수 (8) Kernel에서 동적 메모리를 할당할 때 사용하는 함수 : void * vmalloc(unsigned int len) 커널 메모리 할당 크기 제한 없음 가상 주소 공간에서 연속적인 메모리 영역을 할당 void vmfree(void *addr) vmalloc()에서 할당 받은 커널 메모리를 반납
Kernel Interface 함수 (9) 사용자 공간과 커널공간 사이에 데이터를 공유하기 위한 함수 : unsigned long copy_from_user(void *to, const void *from, unsigned long n) 사용자 주소공간에서 n byte만큼 data 복사. unsigned long copy_to_user(void *to, const void *from, unsigned long n) 사용자 주소 공간에 n byte만큼 data 복사 void * memset(void *s, char c, sizt_t count) 메모리 s에 c를 count만큼 복사 put_user(datum,ptr) / get_user(ptr) 사용자 공간에 datum을 전달하거나 가져오기 위한 매크로
Kernel Interface 함수 (10) 동기화 함수 : void sleep_on(struct wait_queue **q) q의 번지를 event로 sleep하며, uninterruptible void sleep_in_interruptible(struct wait_queue **q) q의 번지를 event로 sleep하며, interruptible void wake_up(struct wait_queue **q) sleep_on(q)에 의해 sleep한 task를 wakeup void wake_up_interruptible(struct wait_queuq **q) sleep_on_interruptible(q)에 의해 sleep한 task를 wakeup uninterruptible 이란 말은 wake_up 함수의 호출 외에 다른 signul 에 의해서 깨어날 수 없음을 의미하는 것이다. Interruptible 은 wake_up 함수가 호출되지 않아도 임의로 process에 signul을 주어 깨어나게 할 수 있다는 의미이다.
Kernel Interface 함수 (11) Standard out 으로 메시지를 출력하기 위한 함수 : printk(const char *fmt,… .) printf의 커널 버전 printk(LOG_LEVEL_ message) LOG_LEVEL:KERN_EMERG, KERN_ALERT, KERN_ERR,KERN_WARNING, KER_INFO, KERN_DEBUG 예 printk(“<1>Hello, World”); printk(KERN_WARNING”warning… \n”); LOG_LEVEL은 메시지의 우선 순위를 정의하는 것이다. LOG_LEVEL에 기초하여 커널을 메시지를 현재의 문자 콘솔에 출력하게 되는데 만약 console_loglevel 의 값 보다 우선 순위가 낮다면 console에 출력되지 않는다. console_loglevel은 sys_syslog 시스템 콜로 값을 바꿀 수 있다. LOG_LEVEL 은 <linux/kernel.h> 헤더에 정의 되어 있다.
Kernel Interface 함수 (12) 디바이스 드라이브 등록 함수 : int register_xxxdev (unsigned int major, const char *name,struct file_operations *fops) character/block driver를 xxxdev[major]에 등록 xxx:blk/chr int unregister_xxxdev(unsigned int major, const char *name) xxxdevs[major]에 등록되 있는 device driver를 제거 int register_netdev(const char *name) int unregister_netdev(const char *name) MAJOR(kdev_t dev)/MINOR(kdev_t dev) 장치번호dev로부터 major/minor 번호를 구함
2. System Call
System Call (1) User Application API(시스템 라이브러리) User Level System Call Interface User Level Kernel Level File System Buffer Cache Charater Device Driver Block Process Management (IPC-interprocess Communication, scheduling, Memory Management Process Acounting, etc) Hardware Interface
System Calls (2) user mode process와 kernel 간의 interface kernel의 자료구조 및 hardware 에 대한 접근 불가 user mode process가 kernel이 가지고 있는 시스템의 상태 정보를 열람하거나 hardware에 접근하여 hardware를 통제하기 위해서는 kernel 과의 communication channel이 필요. User Application API(시스템 라이브러리) System Call Interface User Level Kernel Level File System Buffer Cache Charater Device Driver Block Process Management (IPC-interprocess Communication, scheduling, Memory Management Process Acounting, etc) Hardware Interface 일반적으로 프로세스는 커널에 직접적으로 접근할 수 없다. 다시 말해, 프로세스는 커널 메모리에 접근한다거나 커널의 함수를 호출할 수 없다. 보호모드. 시스템 콜은 user program에게 커널 영역에 대한 접근을 허용하는 유일한 chanel이다. user program은 시스템 콜을 사용해서만 hardware 및 커널 자료구조에 접근할 수 있다.
POSIX API & System calls (1) POSIX API (Application Programming Interface) 유닉스 운영체계에 기반을 두고 있는 일련의 표준 운영체계 인터페이스. application이 시스템에 각 서비스를 요청할 때에 어떠한 함수를 사용해야 하는지 지정한 것. 표준을 두어 각각 다른 시스템에 응용 프로그램을 porting 하는 것이 용이 하게 하기 위한 목적. open(), close(), read(), write() 등 System Call 소프트웨어 인터럽트를 통해 커널에 서비스를 요청하는 것 Linux에서는 POSIX API를 준수하는 library 함수 안에서 system call 함수를 호출함으로써 system call을 사용한다.
POSIX API & System calls (2) 예> 응용프로그램 open() 호출 … sys_open() 호출 (POSIX API) (시스템 콜) 응용 프로그램 Libc 표준 라이브러리 (포장함수) 시스템 콜 핸들러 시스템 콜 서비스 루틴 시스템콜 인터페이스
System Call 원리 이해 (1) Linux 에서의 system call 처리 Interrupt PIC RTC CPU 주변 장치와 커널이 통신하는 방식 중 하나로 주변 장치가 자신에게 발생한 비동기적 사건을 kerenl에게 알리는 메커니즘. PIC disk tty network cdrom RTC CPU Kernel IDT(IVT) Interrupt handlers el3_interrupt() tty_interrupt() hd_interrupt() timer_interrupt() 1 2 3 4 … 인터럽트를 발생시킬 수 있는 주변 장치들은 하드웨어 적으로 PIC(Programmable Interrupt Controller)라는 칩의 각 핀에 연결되어 있다. PIC는 CPU의 한 PIN에 연결되어 있다. KERNEL은 부팅 과정에서 PIC를 초기화 시킨다. 주변장치에서 인터럽트를 발생시키면 PIC는 CPU에게 인터럽트가 발생되었음을 알리고 CPU는 PIC에게 몇번 인터럽트가 발생되었는가를 문의하여 해당 장치의 IRQ를 알려 준다. 3번 인터럽트가 발생했다고 가정하면 CPU는 KERNEL에게 3번 장치에서 인터럽트가 발생했음을 알린다. KERNEL은 IDT(Interrupt Descriptor Table) 에서 index 3 의 entry에 등록되어 있는 handler 함수를 호출하게 되며 위 그림에서는 el3_interrupt() 를 호출하게 된다. IDT: Interrupt Vector Tale(IVT)라고도 하는 entry table로 각 인터럽트가 발생했을 때 이를 처리해 주기위한 루틴의 시작 주소를 가지고 있다.
System Call 원리 이해 (2) System Call User task Kernel main () { …. mysyscall() } swi __NR_##name User task sys_mysyscall() … … … sys_write() sys_read() sys_fork() sys_exit() System_call_table 1 2 3 4 226 Name mysyscall __NR_myscall 226 Kernel printk(“…”); /* [kernel]/arch/arm/kernel/entry-common.S*/ ENTRY(vector_swi) get_scno … adr tbl, sys_call_table ldrcc pc, [tbl,scno, lsl #2] swi 는 software 인터럽트를 발생키셔주는 어셈블리 인스트럭션이다. .macro get_scno #ifdef CONFIG_ARM_THUMB tst r8, #T_BIT @ this is SPSR from save_user_regs addne scno, r7, #OS_NUMBER << 20 @ put OS number in ldreq scno, [lr, #-4] #else mask_pc lr, lr ldr scno, [lr, #-4] @ get SWI instruction #endif .endm
System Call 추가하기 (1) 목적 단계 기존 kernel에서 제공하지 않는 service를 user application에 제공 새로운 System Call 을 만든다. 단계 커널 수정 System Call 번호 할당 System Call 호출 테이블 등록 System Call 호출 함수 구현 Kernel 컴파일 및 target board에 적재 user application 제작 새로 구현한 System Call을 사용하는 application 제작 library 작성(반드시 필요한 것은 아니다.) Ramdisk에 포함시켜서 target board에 적재
System Call 추가하기 (2) 가정 /usr/local/pxa255/linux-2.4.19-cd 에 target system용 kernel source code가 있다. 위 디렉토리를 앞으로의 설명에서 [kernel]로 대치한다.
System Call 추가하기 (3) System Call 번호 할당 Linux 커널이 제공하는 모든 시스템 호출은 각각 고유한 번호를 가지고 있다. [kernel]/include/asm-arm/unistd.h 에 각 시스템 호출의 고유 번호에 정의 되어 있다. 새로 추가할 system call의 고유번호 정의 추가 [kernel]/include/asm-arm/unistd.h 파일을 vi로 연다.
System Call 추가하기 (4) System Call 번호 할당 (계속) unistd.h 파일의 위 내용을 보면 현재 system call 은 225 개가 있는 것을 확인할 수 있다. 추가할 system call 은 위 화면과 같이226번으로 할당한다. __NR_은 System Call 고유번호를 나타내는 접두어이며 그 뒤의 mysyscall이 추가할 System Call 처리 함수의 이름이다. (sys_mysyscall 이 아님을 주의 깊게 봐 둔다.) 현재 리눅스에서는 256개까지의 시스템 콜을 지원한다.
System Call 추가하기 (5) System Call 테이블에 System Call 처리 함수 등록 [kernel]/arch/arm/kernel/entry-comm.S 에 sys_call_table이라는 entry로 구현되어 있다. sys_call_table에는 system call 처리함수의 시작 주소들이 들어있고 각 함수들은 unistd.h에 정의 되어있는 system call 번호를 인덱스로 하여 접근된다. sys_call_table에 추가할 System Call 처리함수 등록 entry-comm.S 파일을 vi로 연다.
System Call 추가하기 (6) System Call 테이블에 System Call 처리 함수 등록 (계속) entry-comm.S 에서 다음과 같은 부분을 확인할 수 있다. ENTRY(sys_call_table) 다음에 calls.S를 include하고 있다. vi를 나와서 다시 calls.S를 열어 보자.
System Call 추가하기 (7) System Call 테이블에 System Call 처리 함수 등록 (계속) calls.S 의 내용은 다음과 같다. 다음과 같이 추가할 System Call 처리함수를 등록한 후 저장 하고 나온다. unistd.h에 정의한 번호와 일치
이 함수가 어셈블리 언어로 구현된 함수에서 호출될 때 사용하는 keyword System Call 추가하기 (8) 처리 함수 구현하기 System Call 이 발생했을 때 수행될 함수를 구현한다. [kernel]/kernel/test_syscall.c 를 다음과 같이 만든다. calls.S 에 등록한 이름과 동일한 이름 이 함수가 어셈블리 언어로 구현된 함수에서 호출될 때 사용하는 keyword
System Call 추가하기 (9) Makefile 수정 만들어진 처리함수를 커널이 컴파일 될 때에 함께 컴파일 될 수 있도록 [kernel]/kernel/Makefile에 다음과 같이 추가 한다.
System Call 추가하기 (10) Kernel image를 /tftpboot에 복사 [kernel] 디렉토리에서 커널을 컴파일하고 생성된 커널 이미지를 target에 전송하기 위해 /tftpboot로 복사. % cd [kernel] % make clean; make dep; make zImage % cp [kernel]/arch/arm/boot/zImage /tftpboot
System Call 추가하기 (11) System Call을 호출하는 user application 작성 library를 만들지 않고 user application을 만드는 경우 unistd.h 에 정의된 매크로로 System Call 처리함수의 타입과 이름을 인자로 넘겨준다. *sys 접두어를 붙이지 않는다. /* linux/unistd.h */ #define _syscall0(type,name) type name(void){ \ register long __res __asm__(“r0”); \ __asm__ __volatile__{ \ __syscall(name) \ : “=r” (__res) \ : “ \ : “lr”); \ __syscall_return(type,__res); \ } 아래쪽에서 오른쪽 창에 보면 __sys1(__NR_##name) 이라고 된 부분이 있는데 여기서 ##은 두 토큰을 결합한다는 의미이다. 즉 name에 “mysyscall” 을 넘겨준 경우 sys1(__NR_mysyscall) 과 같이 된다. #else #define __syscall(name) “swi\t” __sys1(__NR_##name)”\n\t” Software interrupt를 발생시키는 assemblier Unistd에서 정의한 symbol로 바꿘준다.
System Call 추가하기 (12) System Call을 호출하는 user application 작성 (계속) Library를 만들고 user application을 만드는 경우 newsys.c 파일을 다음과 같이 만든다. (임의의 directory에)
System Call 추가하기 (13) System Call을 호출하는 user application 작성 (계속) 앞에서 만든 newsys.c를 컴파일하고 라이브러리로 만들어 준다. (example에서는 /root/mylib 에 newsys.c를 만들었다.) target board 에 올라갈 kernel의 header file을 사용해야 한다. 검색할 Inclued 디렉토리 지정 Library로 만들기 위해 link 하지 않고object code 까지만 만든다.
System Call 추가하기 (14) System Call을 호출하는 user application 작성 (계속) 만들어진 library를 사용하는 application을 만든다.
System Call 추가하기 (15) System Call을 호출하는 user application 작성 (계속) 위에서 만든 application을 compile한다. 만들어진 user application을 /home/share에 복사 Library 검색 위치를 알려준다. ‘new’ 라는 이름의 library를 link
System Call 추가하기 (16) Test 앞서 system call을 추가하여 다시 컴파일한 커널이미지(현재 /tftpboot에 zImage로 존재) 를 target에 download하고 target 을 리눅스로 부팅한 후 host의 /home/share 마운트
System Call 추가하기 (17) 지금까지의 과정을 정리해 보면 다음과 같다. Kernel 수정 Application 작성 unistd.h 에 system call 번호 정의 calls.S 에 System Call 처리함수 등록 System Call 처리 함수 구현 Kernel 재 컴파일 Kernel을 Target system에 download Library 작성 Library 와 link 하여 system call을 호출하는 application 작성 Application 을 ramdisk에 추가 Ramdisk를 target system에 dwonload
3. Device Driver
Device Driver 정의 (1) 물리적인 hardware 장치를 다루고 관리하는 software user application이 driver에게 요청을 하면, driver는 hardware를 구동시켜서 목적을 달성 major number와 minor number를 이용하여 각각의 devices을 구분하여 사용 device와 system memory 간에 data의 전달을 담당하는 kernel 내부 기능 - Driver는 해석 그대로 사물을 구동하는 주체. - 응용프로그램에서 하드웨어 장치를 이용해서 데이터를 직접 읽고 쓰거나 제어해야 하는 경우에 디바이스 드라이버를 이용 - 일반적으로 위쪽으로는 파일 시스템과 인터페이스를 가지며, 아래쪽으로는 실제 디바이스 하드웨어와 인터페이스를 갖는다.
DDI (device driver interface) User application level Kernel task System call interface File system Block Driver Char Driver DDI (device driver interface) Network Driver terminal keyboard Hard disk CD-ROM ethernet User application level Kernel level Hardware level Application의 일반 함수는 kernel level로 접근하는게 제한되어 있다. 그래서 hardware를 제어하기 위해서 driver를 파일로 작성하고 커널에 모듈로 적재한다. 리눅스에서는 모든 장치는 파일로 다루어진다. 모듈 또한 /dev에 파일로 적재되고, Application에서 그 file을 불려서 hardware를 제어하낟.
Device Driver 종류 Driver 종류 설 명 Char Driver 설 명 Char Driver device를 file처럼 취급하고 접근하여 직접 read/write를 수행, data 형태는 stream 방식으로 전송 EX) console, keyboard, serial port driver등 Block Driver disk와 같이 file system을 기반으로 일정한 blcok 단위로 data read/write를 수행 EX) floppy disk , hard disk, CD-ROM driver 등 Network Driver network의 physical layer와 frame 단위의 데이터를 송수신 EX) Ethernet device driver(eth0) 등록함수명 register_chrdev() register_blkdev() register_netdev() - 문자 드라이버는 저속, 순차적, 사용자 응용프로그램과 byte 단위로 데이터를 송/수신. 이것은 오디오, UART, keyboard 같은 장치가 해당 - 블록 드라이버는 고속, 랜덤, 사용자 응용 프로그램과 블록 단위로 데이터를 송/수신. 이것은, 하드디스크 같은 고속 저장장치에 사용. - 네트웍 드라이버가 있는데 앞에서 설명했던 문자와 블록 드라이버는 사용자 프로그램과 데이터를 주고 받을 때, 파일 형태의 노드를 통해서 주고 받지만, 네트웍 드라이버는 메모리에 있는 구조체 형식을 통하여 데이터를 송/수신 함
Device Driver 작성 (1) 기본적인 device driver의 형태 Device Opertations static struct file_operations device_fops={ open : device_open, release : device_release, read : device_read, write : device_write,}; Function Prototypes static int device_open(); static int device_release(); ssize_t device_read(); static ssize_t device_write(); int init_function(); void cleanup_function(); Module 제거 시 반환작업 수행 module_exit(cleanup_function) Module 설치 시 초기화 수행 module_init(init_function) Header Files #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/init.h> Printf와 같은 기능의 커널 코드를 위한 함수 필요한 header. 모듈소스가 include해야함 insmod로 모듈을 적재하면 이 함수가 불려진다. 이 함수 안에서 regiseter_chrdev() 커널 함수를 뷸러서 모둘을 적재한다. rmmod로 적재된 모듈을 삭제할 경우 이 함수가 불려진다. unregister_chrdev() 커널 함수를 불려서 모듈을 삭제한다. “파일시스템”헤더는 디바이스 드라이버를 쓰는데 필요한 헤더파일 application에서 쓰는 함수와 driver에서 쓰는 함수를 mapping Module_init()과 module_exit() 정의됨 Header file은 상대경로로 표시되는데 위의 상대 경로는 /usr/src/리눅스 소스가 있는 디렉토리/include/ 밑에 있다. Device_open 함수는 이 드라이버를 MOD_INC_USE_COUNT라는 매크로를 사용하여 사용횟수를 증가시킨다. Divece_release 함수는 이 드라이버를 MOD_DEC_USE_COUNT라는 매크로를 사용하여 사용횟수를 감소시킨다. 사용수가 0일때에야만 모듈 삭제 가능.
Device Driver 작성(2) #include <linux/module.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/init.h> #define DEV_NAME "test_dd" int test_major = 0; int result; ///////////// 함 수 정 의 ////////////// int device_open (struct inode *inode, struct file *filp); int device_release (struct inode *inode, struct file *filp); ssize_t device_read (struct file *filp, unsigned int *buf, size_t count, loff_t *f_pos); ssize_t device_write (struct file *filp, unsigned int *buf, size_t count, loff_t *f_pos); int device_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned int arg); int sample_init(); void sample_cleanup(); 모듈을 커널에 적재하는 함수 register_chrdev()의 parameter로써 0으로 설정하면 major값을 뒤에서부터 자동으로 빈자리로 등록시킨다 register_chrdev()함수가 return하는 값을 넘겨받음. 0보다 적으면 모듇 등록 실패 application에서 open()함수로 driver를 호출할때 실행되는 함수 application에서 close()함수로 driver를 닫을때 실행되는 함수 ioctl() 대신 get_user() 또는 copy_to_user() 함수를 사용 가능 ioclt()과 copy_to_user()는 한번에 최대 4byte만 전송가능하나, put_user()는 4 byte 이상 보낼 수 있음. application에서 kernel로부터 넘어온 값을 처리하는 함수
Device Driver 작성 (3) 이 함수가 호출될때마다 count가 증가한다. int device_open (struct inode *inode, struct file *filp) { printk("test_open start!!!\n"); MOD_INC_USE_COUNT; return 0; } int device_release (struct inode *inode, struct file *filp) MOD_DEC_USE_COUNT; ssize_t device_read (struct file *filp, unsigned int *buf, size_t count, loff_t *f_pos) printk("read() ^^\n"); ssize_t device_write (struct file *filp, unsigned int *buf, size_t count, loff_t *f_pos) 이 함수가 호출될때마다 count가 증가한다. 이 함수가 호출될때마다 count가 감소한다. ssize_t는 long로 정의 됨… - /asm-arm/posix_types.h에서 typedef int __kernel_ssize_t로 정의됨 - /linux/types.h에서 typedef __kernel_ssize_t ssize_t로 정의됨. MOD_INC_USE_COUNT와 MOD_DEC_USE_COUNT는 module.h에서 define되어 있음. # define MOD_INC_USE_COUNT __MOD_INC_USE_COUNT(mod) __MOD_INC_USE_COUNT(mod)는 linux/mtd/compatmac.h에서 define 되어 있다 #define __MOD_INC_USE_COUNT(mod) (atomic_inc(&(mod)->uc.usecount), (mod)->flags |=MOD_VISITED|MOD_USED_ONCE) Application에서 read함수를 호출하면 terminal에 문자 출력
Device Driver 작성 (4) module_exit(sample_cleanup); int device_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned int arg) { printk("ioctl() ^^\n"); return 0; } struct file_operations test_fops = { open: device_open, read: device_read, write: device_write, ioctl: device_ioctl, release: device_release, }; module_init(sample_init); module_exit(sample_cleanup); Ioctl()호출이 성공하면 문자 출력 파일연산구조체는 char device driver에 대한 모든 연산을 포함한다. 즉 여기에 정의된 함수를 통해서 커널이 접근하게 된다. 즉 application에서 open함수를 호출하면 이는 driver의 test_open()과 mapping되어있어 이를 실행하게 된다. insmod로 불려지는 함수 rmmod로 불려지는 함수
driver를 커널에 모듈로 등록하는 함수, result에 major 값이 넘어옴 Device Driver 작성 (5) int sample_init(void) { result = register_chrdev(test_major, DEV_NAME, &test_fops); if (result < 0) { printk(KERN_WARNING "%s: can't get major %d\n", DEV_NAME, test_major); return result; } printk("<1> init module success!!.. %s major number : %d\n", DEV_NAME, result); return 0; void sample_cleanup(void) if( !unregister_chrdev(result, DEV_NAME) ) printk("%s cleanup_module success...\n", DEV_NAME); else printk("%s cleanup_module fail...\n", DEV_NAME); driver를 커널에 모듈로 등록하는 함수, result에 major 값이 넘어옴 driver를 커널에서 삭제하는 함수
Device Driver 검증 프로그램 만들기(1) #include <stdio.h> #include <fcntl.h> #include <string.h> #include <errno.h> int fd; int main(int argc, char **argv) { if( (fd = open("/dev/testdd", O_RDWR)) < 3) { fprintf(stderr, "/dev/testdd device file open error!! .. %s\n",strerror(errno)); return 0; } read(fd, 0, 0); ioctl(fd, NULL,0); close(fd); File에 대한 제어와 설정 Error에 관한 header file mknod를 통하여 /dev에 만들어진 node(file)을 open한다. open()과 fopen()의 차이점 - fopen()은 stdio.h를 include 해야하고, 함수의 return값은 FILE이라는 structure의 포인터이다. -open()은 fcntl.h를 include 해야하고, 함수의 returnr값은 integer인 현재 사용되지 않는 가장 작은 파일 지시자이다. -파일을 열어 그 파일을 세세하게 제어해야 하고 읽고 쓰는 속도가 크게 중요하지 않다면 fopen()이 낫다. 그렇지않고 연 파일을 세세하게 제어해야할 필요는 없지만 읽고 쓰는 속도가 중요하다면 open()함수를 권유 Open()의 마지막 parameter - O_RDONLY :파일을 읽기전용으로 연다 - O_WRONLY : 파일을 쓰기 전용으로 연다 - O_RDWR : 파일을 읽고 쓰는 용으로 연다 - O_APPEND : 파일은 연 후 쓰려고 하면 파일의 끝에 덧 붙인다.
Device Driver 검증 프로그램 만들기(2) 경고를 다 출력해준다는 의미 Compile Options %arm-linux-gcc –c –D__KERNEL__ -DMODULE –Wall –o2 –o test_dd.o test_dd.c test_dd는 커널에서 동작하고 모듈에 사용된다는 뜻 Compile과정에서 inlne 함수를 확장할 때 최적화함 모듈은 test_dd.o로 컴파일 되어야 하므로 –c 옵션 사용 옵션 O 옵션 01 과 동일 옵션 O0 최적화 하지 않음(기본값): 정확한 동작 및 컴파일 시간 적음 옵션 O1 코드 크기와 실행 시간을 줄여 줌 옵션 O2 더 많은 최적화를 수행함 -Wall 은 컴파일시 발생하는 모든 경고 메세지를 표현하라는 의미 인데 컴파일시 발생하는 경고를 무시하게 되면 모듈 삽입을 위하여 insmod 명량을 사용할때 심볼릭 에러를 발생하거나 모듈이 동작할때 이상하게 동작할 수 있으므로 필히 없애는 것이 정석입니다.
Device Driver 검증 프로그램 만들기(3) CC =arm-linux-gcc INCLUDEDIR = /usr/local/pza255/linux-2.4.19-huins/include CFLAGS = -D__KERNEL__ -DMODULE -Wall -O2 CFLAGS2 = -I.. -I$(INCLUDEDIR) All: test_dd test_app test_dd : test_dd.c $(CC) $(CFLAGS) $(CFLAGS2) -c -o test_dd.o test_dd.c test_app : test_app.c $(CC) -o test_app test_app.c clean : rm -rf test_dd.o test_app Macro 부분 C에서 define 역할 target command 위와 같이 Makefile을 만든 후 프롬프트에서 make 를 치면 컴파일 됨. 또한 object 파일을 지우려면 make clean을 치면 지워짐. Command 부분 파일들의 dependency를 설정 주의할 점은 이 공백은 반드시 Tab 공백
Device Driver를 커널에 모듈로 등록 및 해제하기 (1) Char Device Driver 등록 방법 외부와 device driver는 file interface (node를 의미)를 통해 연결 Device driver는 자신을 구별하기 위해 고유의 major number를 사용 장치의 등록과 해제 등록 : int register_chrdev(unsigned int major, const char *name, stuct file_operations *fops) Major : 등록할 major number. 0이면 사용하지 않는 번호 중 자동으로 할당 Name : device의 이름 Fops : device에 대한 file 연산 함수들 해제 : int unregister_chrdev(unsigned int major, const char *name) register_chrdev()가 성공하면 major 번호를 return unregister_chrdev()가 실패하면 0을 return
Device Driver를 커널에 모듈로 등록 및 해제하기 (2) user program open close read write kernel device driver file operations device_open device_close device_read device_write system call device
Device Driver를 커널에 모듈로 등록 및 해제하기 (3) Major number와 minor number 장치를 구분하는 방법으로 둘을 이용하여 특정 장치를 구별 Major number : 커널에서 디바이스 드라이버를 구분하는데 사용 Minor number : 디바이스 드라이버 내에서 필요한 경우 장치를 구분하기 위해 사용 새로운 디바이스는 새로운 major number를 가져야 함 register_chrdev()로 장치를 등록할 때 major number를 지정 같은 major number가 등록되어 있으면 등록 실패 Major와 minor 번호는 파일의 정보를 담고 있는 inode의 i_rdev에 16bit로 저장된다. 상위 8bit는 major, 하위 8bit는 minor이다.
Device Driver를 커널에 모듈로 등록 및 해제하기 (4) mknod 명령으로 디바이스 드라이버에 접근할 수 있는 장치 파일 생성 mknod [device file name] [type] [major] [minor] Ex] %mknod testdd c 252 0 mdev_t : 장치의 major, minor number를 표현하는 자료구조 MAJOR() : kdev_t에서 major number를 얻어내는 매크로 Ex] MAJOR(inode->i_rdev); MINOR() : kdev_t에서 minor number를 얻어내는 매크로 cat /proc/devices 명령으로 현재 로드된 디바이스 드라이버 확인 C는 char device drive의미, block device drive는 b를 사용 참고로 major와 minor 번호는 inode의 i_rdev에 16bit로 저장이 된다. 상위 8bit는 major번호로 maximm 255까지 지정할 수 있다.
Device Driver를 커널에 모듈로 등록 및 해제하기 (5) /usr/lcoal/pxa255/linux-2.4.19-cd/include/linux/fs.h 앞에서 작성했던 driver에 사용된 파일연산 함수 /usr/lcoal/pxa255/linux-2.4.19-cd/include/linux/fs.h
Device Driver를 커널에 모듈로 등록 및 해제하기 (6) 이 름 용 도 insmod module을 설치(install) rmmod 실행중인 modules을 제거(unload) lsmod Load된 module들의 정보를 표시 depmod Module들의 symbol들을 이용하여 Makefile과 유사한 dependency file을 생성 modprobe depmod명령으로 생성된 dependency를 이용하여 지정된 디렉토리의 module들과 연관된 module들을 자동으로 load modinfo 목적화일을 검사해서 관련된 정보를 표시 target에서 depmod를 실행시키면 /lib/modules/2.4.19-huins/modules.dep 라는 파일이 생성
Device Driver를 커널에 모듈로 등록 및 해제하기 (7)
Device Driver를 커널에 모듈로 등록 및 해제하기 (8) 앞에서 만든 드라이버 소스를 test_dd.c, test application은 test_app.c, 그리고 makefile은 Makefile로 작성 후 저장한다. make 명령어를 이용하여 위의 2 files을 컴파일한다. % make 생성된 test_dd.o 와 test_app를 target으로 전송한다. 전송방식은 앞에서 배웠던 minicom 또는 nfs방식을 이용한다. 밑에서는 nfs 방식을 통하여 파일을 전송하였다.
Device Driver를 커널에 모듈로 등록 및 해제하기 (9) Module을 kernel에 적재 major number는 253 Test application에서 open()로 이 nod를 연다. 이 nod로 application과 hardware가 만남 ls 명령으로 nod가 잘 만들어졌는지 확인. 253은 major 번호 결과 화면