Kernel Programming 이란? 커널모드에서수행하는프로그램을작성하는것 임베디드리눅스커널프로그래밍 커널프로그래밍종류 Linux kernel core 기능추가 Linux kernel 알고리즘개선 Linux kernel 모듈프로그래밍 커널컴파일필요없음 2 Kernel Program vs. Application Program (1) Kernel Program vs. Application Program (2) 수행방법 Application Program: 처음부터순차적으로수행 Kernel: 응용프로그램이호출한 system call이나 interrupt handler 를수행하기위해비동기적으로수행 Library Application: 모든 library를 link하고사용할수있다. Kernel: kernel에서 export 하는것들만사용할수있다. EXPORT_SYMBOL(func_name) System call Kernel mode vs User mode Application Kernel Interrupt Application: user mode 에서수행되며하드웨어에직접접근하는것과메모리에대한허용되지않은접근이제한된다. Kernel: kernel mode 에서수행되며모든것이허용된다. 3 4
Kernel Program vs. Application Program (3) Kernel Program vs. Application Program(4) Address Space Application Program 과 Kernel Program 은서로다른 memory address space 를가지고있음 32-bit virtual address space 에서 Kernel address space 1 GB Namespace pollution 문제 Application Program: 현재개발하는프로그램에서만각함수와변수의이름을구별하여주면된다. Kernel Program: 리눅스커널 = 커널 core + 다수의커널모듈 현재개발하는커널모듈뿐만아니라커널전체에서함수와변수의이름이충돌하지않도록해야한다. 4 GB User address space 3 GB 0 5 6 Kernel programming 주의사항 Kernel programming 주의사항 (2) Library stdio.h 와같이보통프로그램에서사용하는헤더파일을 include 해서사용할수없음 다음에선언된헤더파일만 include 함 /usr/include/linux 와 /usr/include/asm ( 예 ) #include <linux/***.h > #include <asm/***.h.> Namespace pollution 외부파일와 link 하지않을모든심볼을 static 으로선언함. static int name; 외부파일과 link 할 symbol 을 symbol table 등록 EXPORT_SYMBOL(name); 심볼 name 을 export 함 전역변수는잘정의된 prefix 를붙여준다. (ex) sys_open() Fault handling Kernel 은하드웨어접근에대해어떠한제한도없기때문에커널에서의에러는시스템에치명적인결과를발생시킨다. 함수호출등의작업시모든에러코드를검사하고처리해야한다. Address space 커널이사용하는 stack의크기는제한되어있고, 인터럽트핸들러도같은스택을사용할수도있으므로큰배열을지역변수로사용하거나, recursion이많이일어나지않도록주의해야한다. Application과 data를주고받기위해특별한함수를사용하여야한다. call by reference ( 뒷부분에서이를위한몇가지함수를소개한다.) 기타 실수연산이나 MMX/SSE 연산 (x86) 을사용할수없다. 7 8
커널 Data Type Kernel Interface 함수 C 언어의원래의자료형대신에 typedef 으로정의된자료형이많이사용된다. 커널에서여러종류의값을나타내는데에 int 나 long 등대신에접미사 _t 로끝나는자료형을많이사용한다. pid_t, uid_t, gid_t, dev_t, size_t,... <linux/types.h> 에정의되어있음 데이터의크기를명시적으로표현한자료형 u8, u16, u32, s8, s16, s32 ( 커널코드에서만사용가능 ) 사용자프로그램에서는 u8, s8 과같이 _ 를두개붙여서사용 <asm/types.h> 에정의되어있음 주의사항 Kernel program 은일반적인 library 를사용하지못하고 kernel 에서 export 한함수들만을사용할수있다. Kernel interface 함수분류 I/O port, I/O memory Interrupt Memory Synchronization Kernel message 출력 Device Driver register 9 10 Kernel Interface 함수 port I/O Kernel Interface 함수 - Interrupt I/O 에대한가상주소맵핑 void *ioremap(unsigned long phys_addr, unsigned long size); void *ioremap_nocache(unsigned long phys_addr, unsigned long size); void iounmap(void * addr); I/O memory 읽기 old version unsigned int ioread8(void *addr); readb(addr) unsigned int ioread16(void *addr); readw(addr) unsigned int ioread32(void *addr); readl(addr) I/O memory 쓰기 void iowrite8(u8 value, void *addr); writeb(value, addr) void iowrite16(u16 value, void *addr); writew(value, addr) void iowrite32(u32 value, void *addr); writel(value, addr) I/O port 입출력 (x86) unsigned inb(unsigned port); / inw() / inl() void outb(unsigned char byte, unsigned port) / outw() / outl() 인터럽트비활성화 / 활성화 (macro) local_irq_disable() local_irq_enable() 인터럽트비활성화 / 활성화시 status register 저장 / 복원 (macro) unsigned long flags; local_irq_save(flags) local_irq_restore(flags) 인터럽트핸들러등록 / 해제 int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long irqflags, const char *devname, void* device) void free_irq(unsigned int irq, void *devid) request_irq() 에서획득한 irq 를반납함 11 12
Kernel Interface 함수 -Memory Kernel Interface 함수 데이터복사 커널물리적메모리동적할당 / 해제 void *kmalloc(size_t size, int flags) 커널메모리할당 : 최대크기제한있음 ( 초기 128KB, 현재증가함 ) 물리적주소공간에서연속적인메모리를할당한다. flags: GFP_USER( 사용자프로그램용메모리할당 ), GFP_KERNEL( 커널용메모리할당, sleep 가능 ), GFP_ATOMIC( 인터럽트핸들러등에서사용, sleep 불가 ) 등 void kfree(void *obj) 커널가상메모리동적할당 / 해제 void * vmalloc(unsigned int len) 커널메모리할당, 크기제한없음 가상주소공간에서연속적인메모리영역을할당 void vmfree(void *addr) vmalloc() 에서할당받은커널메모리를반납 사용자공간과커널공간사이에데이터복사 unsigned long copy_from_user(void *to, const void *from, unsigned long n). unsigned long copy_to_user(void *to, const void *from, unsigned long n) put_user(data, ptr) / get_user(ptr) (macro) ptr 이가리키는사용자공간에 data 를전달 (put) 하거나가져옴 (get) ptr 의 type 에따라서복사할데이터크기를인식함 (1,2,4 byte) 커널공간에서사용자공간의메모리를읽고쓰는데접근할수있는지를검사하는동작을포함 메모리값설정 void * memset(void *s, char c, size_t count) 메모리 s 에 c 를 count 만큼복사 13 14 Kernel Interface 함수 Synchronization Kernel Interface 함수 메시지출력 동기화 void sleep_on(struct wait_queue **q) // deprecate void sleep_in_interruptible(struct wait_queue **q) // deprecate q 의번지를 event 로 sleep 하며, uninterruptible/interruptible 표준출력 printk(const char *fmt,.) printf 의커널버전 printk(log_level message) wait_event(queue, condition) (macro) wait_event_interruptible(queue, condition) condition 이 1 일때까지 wait, queue 번지를 event 로하여 sleep 하며 uninterruptible/interruptible void wake_up(struct wait_queue **q) void wake_up_interruptible(struct wait_queue **q) sleep_on... (q) / wait_event...(q, cond) 에의해 sleep 한 task 를 wakeup LOG_LEVEL:KERN_EMERG, KERN_ALERT, KERN_ERR, KERN_WARNING, KER_INFO, KERN_DEBUG <linux/kernel.h> 참조 예 printk( <1>Hello, World ); printk(kern_warning warning \n ); // <4> 15 16
Kernel Interface 함수 device driver 관련 Kernel Interface 함수 signal 함수 Device Driver 등록 / 해제 int register_xxxdev(unsigned int major, const char *name, struct file_operations *fops) character/block driver 를 xxxdev[major] 에등록 xxx: chr 또는 blk int unregister_xxxdev(unsigned int major, const char *name) xxxdevs[major] 에등록되어있는 device driver 를제거 int register_netdev(struct net_device *dev) int unregister_netdev(struct net_device *dev) 디바이스드라이버에서사용자프로그램에 signal 을보내는함수 int kill_proc(pid_t pid, int signum, int priv) pid current : 현재프로세스 task_struct 에대한 ptr (macro) current->pid : 현재프로세스의 pid current->ppid : 현재프로세스의부모프로세스의 pid 사용자프로그램에서 signal handler 등록 void (*signal(int signum, void(*handler)(int)) (int); MAJOR(kdev_t dev), MINOR(kdev_t dev) 장치번호 dev 로부터 major/minor 번호를구함 17 18 시스템호출과정 시스템호출의구현 시스템호출함수정의 sys_funcname... 사용자프로그램에서는 funcname호출 시스템호출번호할당 include/asm-arm/unistd.h 파일에추가 시스템호출함수등록 arch/arm/kernel/call.s 파일에등록 커널재컴파일 19 20
시스템호출함수정의 kernel 소스의 kernel 디렉토리에시스템호출함수를정의한프로그램파일 (hello.c) 생성 시스템호출번호할당 include/asm-arm/unistd.h 파일편집 #include <linux/kernel.h> asmlinkage void sys_hello() { printk ("Hello, this is new system call!\n"); } fork SWI 0x900002 asmlinkage : stack 을통하여 parameter 전달 fastcall : 몇개의 parameter 는 register 를통하여전달... 21 22 시스템호출함수등록 unistd.h 파일의시스템호출번호의마지막에 sys_hello() 를위한번호 322 를추가함 linux/arch/arm/kernel/calls.s 파일편집 #define NR_get_mempolicy ( NR_SYSCALL_BASE+320) #define NR_set_mempolicy ( NR_SYSCALL_BASE+321) #define NR_hello ( NR_SYSCALL_BASE+322) sys_hello 함수는 SWI 0x900322 로호출되어수행됨... 23 24
Makefile 수정및커널재컴파일 / 설치 마지막에 sys_hello 함수엔트리추가 /* 320 */.long sys_get_mempolicy.long sys_set_mempolicy /* 322 */.long sys_hello 추가된 hello.c 가위치한디렉토리 (kernel) 의 Makefile 수정 obj-y 에 hello.o 를추가함.rept NR_syscalls (. 100b) / 4.long sys_ni_syscall.endr hello.o... 커널재컴파일및타겟시스템설치 25 26 시스템호출을수행하는사용자프로그램작성 사용자프로그램 test-hello.c 작성 #include <linux/unistd.h> #include <errno.h> #define NR_hello ( NR_SYSCALL_BASE+322) /* system call stub function */ _syscall0 (void, hello); // hello 함수선언 인수없음 int main() { hello(); return 0; } 시스템호출함수선언을위한매크로 unistd.h 에정의되어있음 syscallx (type, name, type1, arg1, type2, arg2,, typex, argx); type: 시스템호출함수의 return 자료형 name: 시스템호출함수이름 (sys_ 를제외 ) type1, arg1 : 첫번째인수의자료형과인수이름... typex, argx : x번째인수의자료형과인수이름 커널에시스템호출을추가하는경우는많지않음 27 28
Handling Interrupts from Peripherals Kernel Modules for handling interrupts This requires a Linux device driver A. Modify the Linux kernel B. Write a kernel module Initialization Sets up the module upon loading module_init(init_routine); Kernel modules can be loaded at run-time Easily register interrupt handler using library functions Interrupt handling (optional) Register an interrupt handler request_irq( ) Unregister an interrupt handler free_irq( ) Exit routine Clears used resources upon unloading module_exit(exit_routine); 29 30 Exercise: Creating a Linux Device Driver Kernel Module - Initialization We will use the red LEDs and pushbuttons void *lwbridgebase; The program increments the value displayed on the LEDs when a pushbutton is pressed static int init intitialize_pushbutton_handler(void) { // get the virtual addr that maps to 0xff200000 lwbridgebase = ioremap_nocache(0xff200000, 0x200000); // Set LEDs to 0x200 (the leftmost LED will turn on) iowrite32(0x200, lwbridgebase); // the leftmost LED turn on // Clear the PIO edgecapture register(clear any pending intr) iowrite32(0xf,lwbridgebase+0x5c); // Enable IRQ generation for the 4 buttons iowrite32(0xf,lwbridgebase+0x58); (continue) 31 32
Kernel Module Interrupt Handler } // Register the interrupt handler. return request_irq(73, (irq_handler_t)irq_handler, IRQF_SHARED, "pushbutton_irq_handler", (void *)(irq_handler)); module_init(intitialize_pushbutton_handler); irq_handler_t irq_handler(int irq, void *dev_id, struct pt_regs *regs) { // Increment the value on the LEDs iowrite32(ioread32(lwbridgebase)+1,lwbridgebase); // Clear the edgecapture register (clears current intr.) iowrite32(0xf,lwbridgebase+0x5c); } return (irq_handler_t) IRQ_HANDLED; IRQ_HANDLED 인터럽트가이디바이스에서나올때 return 값 33 34 Kernel Module Exit Routine Compile a kernel module static void exit cleanup_pushbutton_handler(void) { // Turn off LEDs and de register irq handler iowrite32(0x0,lwbridgebase); free_irq(73, (void*) irq_handler); } module_exit(cleanup_pushbutton_handler); Makefile - 커널모듈컴파일을위한 Makefile obj-m += pushbutton_irq_handler.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean uname 명령어 # uname r // print kernel release 3.13.0-00293-g1664d72 compile # make // 커널모듈 pushbutton_irq_handler.ko 생성 35 36
Insert Newly Compiled Kernel Module into Linux # insmod pushbutton_irq_handler.ko // 커널모듈설치 ( 파일이름 ) List All Loaded Kernel Modules # lsmod // 커널모듈설치확인 Try Toggling the Pushbuttons 임의의 pushbutton 을누를때마다 red LED 값이 1 씩증가함 Remove the Kernel Module # rmmod pushbutton_irq_handler // 커널모듈제거 ( 모듈이름사용 ) # lsmod // 커널모듈제거확인 37