리눅스커널프로그래밍 Ki-Hyun JUNG kingjung@paran.com 참고 : Linux Kernel Programming Kernel Module Programming 커널프로그래밍 Add-on Linux Kernel 1
운영체제가관리 자원관리 물리적인자원 (Physical Resource) CPU, 메모리, 디스크, 터미널, 네트워크등 시스템을구성하고있는요소들과주변장치 추상적인자원 (Abstract Resource) 물리적인자원을운영체제가관리하기위해 추상화시킨객체들 2
추상적인자원 CPU 추상화자원 태스크 (Task), 쓰레드 (Thread) 메모리추상화자원 세그먼트 (Segment), 페이지 (Page) 디스크추상화자원 파일, inode, 디스크드라이버 터미널추상화자원 회선규범 (Line Discipline), 터미널드라이버 네트워크추상화자원 통신프로토콜, 패킷 (Packet) 추상적인객체로만존재하는자원 보안 (Security), 접근제어 (Access Control) 3
논리적인구성요소 태스크관리자 메모리관리자 파일시스템 네트워크관리자 디바이스드라이버 리눅스커널 4
Application Level Process 1 Process 2 Process 3 System Calls Interface Kernel Level File System ext2fs xiafs proc minix nif msdos iso9660 Buffer Cache Memory Manager virtual memory page fault handling swapping Task Manager context scheduler signals loadable modules Device Manager block character hd cdrom isdn network scsi pci Network Manager ipc4 ethernet. Hardware Level Machine Interface (Interrupt) Device Memory CPU 5
태스크 1. 태스크관리자 생성, 실행, 상태전이 (State Transition) 스케줄링 시그널처리 프로세스간통신 (IPC) InterProcess Communication) 모듈 6
가상메모리 2. 메모리관리자 주소변환 (Address Translation) 페이지부재결함 (Page Fault) 처리 7
3. 파일시스템 파일생성, 접근제어, inode 관리 디렉토리관리 수퍼블록관리 버퍼캐쉬관리 8
4. 네트워크관리자 소켓 (Socket) 인터페이스 통신프로토콜 TCP/IP, UDP 9
5. 디바이스드라이버 디스크, 터미널, CD, 네트워크카드등과같은주변장치를구동하는드라이버 10
Application Level ls ps mount aout a.out... Kernel Level System Calls Interface File System open() read() write() Buffer Cache Memory Manager brk() 시스템호출인터페이스를이용하여사용자응용프로그램과통신 Task Manager fork() execve() getpid() signal() 사용자레벨에서직접호출되지않고파일시스템을거쳐디바이스드라이버의서비스가호출 Hardware Level Device Manager Network Manager 시스템호출명령없음 socket() bind() connect() Machine Interface (Interrupt) Device Memory CPU 기계인터페이스와인터럽트처리매커니즘등을이용하여하드웨어와통신 11
표준입출력라이브러리와시스템호출 C library functions Application code User Process System Calls Kernel 12
C 프로그램의시작과종료 return User functions main function call exit exit exit function call exit handler... return exit handler return C start-up routine call exit _exit Standard I/O cleanup exec 컴파일러가응용프로그램과링크 - 명령어라인에전달된인자들을벡터형태로변환하여 main() 함수호출 Kernel 13
1. 커널내부관리자연동 :read() Process read() 시스템호출 System Call sys_read() 시스템처리함수 File System 1. 디렉토리탐색하여요청한파일의 inode 찾음 2. Inode의정보를이용해디스크상에서요청한데이터의논리적인위치를찾음 3. 버퍼캐스공간에요청한데이터가이미존재하는지검사 - 있으면 ((buffer cache hit) 데이터전달 - 없으면디스크드라이버에게해당디스크블록요청 Buffer Cache Device Manager bread() hd_request() Disk Device Driver Machine Interface (Interrupt) 1. 요청된논리적디스크블록의헤드, 트랙, 섹터계산하여해당섹터를읽어옴 2. 읽혀진데이터를버퍼캐시에전달 3. 파일시스템은사용자가요청한크기만큼데이터를사용자에게전달 hd_io()... Memory CPU 리눅스는일반적으로 sys_*() 으로명명 14
2. 커널내부관리자연동 : fork() 1. 새로운태스크를위한태스크자료구조요청 Process fork() Task Manager copy_mm() Memory Manager System Call sys_fork() Process Management copy_thread() - 태스크번호 ( 프로세스번호, pid), 스케줄링정보, 시그널정보, 파일디스크립터 2. 메모리객체 ( 세그먼트, 페이지들 ) 를메모리관리자에게요청후할당 3. 새로운제어흐름을위한스레드자료구조생성 4. 태스크자료구조에메모리객체와스레드에대한포인터설정 5. 생성된태스크자료구조를실행큐에연결 6. 스케줄러가새로생성된태스크를스케줄링 Machine Interface (Interrupt)... Memory CPU 15
리눅스커널소스트리구조 /usr/src/linux arch fs init include net driver scripts mm 하드웨어종속적인부분구현 다양한파일시스템, open(), read(), write(), 파이프 디바이스드라이버구현 - 블록, 문자, 네트웍으로구분 커널초기화부분 - 커널의메인시작함수구현, 메모리관리자구현 message passing, shared memory, semaphore lib ipc 통신프로토콜구현, 소켓 - TCP/IP, WAN(X.25, 802), IPX, SUN RPC, AppleTalk kernel 태스크관리자구현, 시그널 doc 하드웨어종속적인태스크는 arch/i386/kernel 16
시스템초기화과정 전원이켜지면 CMOS 수행, 하드디스크의구조 (Geometry) 파악 arch/i386/boot/ 디렉토리의부트스트랩코드를메모리에적재하고, 제어를이곳으로넘김 부트스트랩코드는루트파일시스템의구조확인하고, 리눅스커널을메모리에적재하여제어를이곳으로넘김 커널의시작위치는물리메모리의정해진영역에적재되도록구현됨, 약속된주소로분기 arch/i386/kernel/head.s / / 파일에구현된 startup_32 함수수행 메모리초기화, 인터럽트초기화, SMP 관련초기화 커널의메인시작함수호출 이디렉토리의 start_kernel() 함수수행 디바이스드라이버초기화, 커널내부자료구조할당및초기화 태스크 0(idle), 태스크 1(init) 생성후수행 init 태스크 ( 리눅스시스템초기화 ) Kflushd 데몬, syslogd 데몬, inetd 데몬등의데몬태스크생성 파일시스템마운트및초기화, 터미널초기화, 네트웍초기화 Login 태스크생성 17
커널특징 메모리에상주 리눅스커널컴파일 커널권한 (kernel level) 으로동작 * a.out( 사용자실행파일 ) 필요할때메모리에적재, 사용자권한으로동작 리눅스커널생성과정 커널구성 (kernel configuration) 커널컴파일 (kernel compile) 커널인스톨 (kernel installation) 18
커널생성 /* 커널소스의상위디렉토리로이동 */ $cd /usr/src/linux /* 커널구성 */ $make config ( 또는 xconfig, menuconfig) /* query and answer */ /* 커널컴파일 */ $make dep $make clean $make bzimage /* 실제커널소스가컴파일됨 */ /* 인스톨 */ $make modules $make modules_install /* 생성된커널을 /boot 디렉토리에복사 */ $cp arch/i386/boot/bzimage /boot/vmlinuz-new /* edit /etc/lilo.conf file */ $vi /etc/lilo.conf $lilo // Linux Loader $reboot boot=/dev/hda map=/boot/map install=/boot/boot.b prompt timeout=50 lineardefault=linux image=/boot/vmlinuz-2.2.14-12kr label=linux read-only root=/dev/hda7 /* 이부분추가 */ image=/boot/vmlinuz-new label=linux-new read-only root=/dev/hda7 other=/dev/hda1 label=dos table=/dev/hda 19
커널 Rebuild /* 최신커널다운로드 ftp://ftp.kernel.org/pub/linux/kernel $rm rf linux /* 이전 symbolic link 지우기 */ $mkdir linux-2.4.18 $ln s linux-2.4.18 linux /* 새로운 symbolic link 생성 */ $gzip dc linux-2.4.18.tar.gz tar xvfz linux-2.4.18.tar.gz $cd /usr/src/linux $make mrproper /* 기존커널의존성제거 */ $make menuconfig /* 새로운커널설정작업 */ $make dep /* 모든오브젝트파일과구버전에남겨놓은의존성제거 */ $make clean $make bzimage $make modules /* 커널옵션을설정할때 module 로설정한옵션컴파일및설치 */ $make modules-install $cp /usr/src/linux/arch/i386/boot/bzimage /boot/bzimage-2.4.18-test $cp /usr/src/linux/system.map /boot/system.map-2.4.18-test $vi /etc/lilo.conf $lilo $reboot 20
시스템호출 커널은각시스템호출을함수로 ( 시스템호출핸들러 ) 구현해놓고각시스템호출이요청되었을때대응되는함수를호출하여서비스제공 리눅스커널에서는시스템호출을구현한함수이름앞에 sys_ 라는접두어를붙임 ( 관습 ) 사용자프로그램 fork() 시스템호출, 커널은 sys_fork() 라는이름으로구현 21
Linux Kernel 시스템호출구현 1. 커널수정 시스템호출번호할당 시스템호출테이블등록 시스템호출처리함수구현 커널컴파일및 Rebooting 2. 사용자레벨 Application 작성 시스템호출을사용하는프로그램작성 라이브러리작성 (option) 22
11 1.1 시스템호출번호할당 리눅스커널이제공하는모든시스템호출은각각고유한번호소유 include/asm-i386/unistd.h / 에구현 /usr/src/linux 새로운시스템호출번호할당 #define _NR_exit 1 #define NR_fork 2 #define _NR_newsyscall 243 23
12 1.2 시스템호출테이블등록 리눅스커널이제공하는모든시스템호출처리함수는 sys_call_table에등록 arch/i386/kernel/entry.s 각테이블엔트리에시스템호출처리함수시작점주소가들어있고, 각엔트리는각시스템호출번호를인덱스로접근 ENTRY(sys_call_table).long SYMBOL_NAME(sys_ni_syscall) /* 0 */.long SYMBOL_NAME(sys_exit) /* 1 */.long SYMBOL_NAME(sys_fork) /* 2 */.long SYMBOL_NAME(sys_newsyscall).rept NR_syscalls-243 24
13 1.3 시스템호출처리함수구현 태스크관리자와관련된함수들 커널소스트리에서 kernel/ 디렉토리에구현 파일시스템과관련된함수들 fs/ 디렉토리에구현 /* /usr/src/linux/kernel/newfile.c */ #include <linux/unistd.h> i #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> asmlinkage int sys_newsyscall() { printk( Hello, Linux, I m in Kernel n ); return(0); } asmlinkage 키워드 - C 로구현한함수가어셈블리언어로구현된함수에서호출될때사용 - 인텔 CPU 에서는특별히하는기능이없으며, Alpha CPU 같은경우전처리수행 printk() 함수 - 커널레벨에서수행되는함수 - 커널라이브러리 25
14 1.4 커널컴파일및리부팅 kernel/ 디렉토리의 Makefile 수정 O_TARGET := kernel.o O_OBJS := sched.o newfile.o 리눅스버전에따라서조금씩다름 make 수행 Kernel rebuild rebooting 26
21 2.1 시스템호출응용프로그램작성 프로그램작성 /* test_newsyscall.c */ /* 새로운시스템호출을위한응용프로그램 */ /* include/asm-i386/unistd.h */ #include <linux/unistd.h> _syscall0(int, newsyscall); main() { int r; r = newsyscall(); } #define _syscall0(type, name) 인자가 1 개 : _syscall1() 인자가 2개 : _syscall2() 컴파일 gcc, cc * printk() : 콘솔에문자열을출력 27
22 2.2 라이브러리작성 (option) $vi test_newsyscall.c // editing $gcc c test_newsyscall.c $ls test_newsyscall.c test_newsyscall.o /* contents of test_newsyscall.c */ #include <linux/unistd.h> _syscall0(int, newsyscall); $ar r libnew.a test_newsyscall.o $ranlib // generating index to archive $ar t libnew.a $vi test.c // editing $gcc test.c L / lnew $a.out /* contents of test.c */ main() { int k; k = newsyscall(); } -L // 라이브러리가존재하는디렉토리 -l // 라이브러리링크 28
실습 : 라이브러리생성 $vi test_newlibcall.c // editing $gcc c test_newlibcall.c $ls test_newlibcall.c test_newlibcall.o /* contents of test_newlibcall.c */ #include <stdio.h> viod newlibcall() { printf( testing : library call n ); } $ar r libnew.a test_newlibcall.o $ranlib $ar t libnew.a $vi test.c // editing $gcc test.c L./ lnew $a.out /* contents of test.c */ main() { newlibcall(); } -L // 라이브러리가존재하는디렉토리 -l // 라이브러리링크 29
시스템호출 fork() 시스템호출과정 sys_fork() /* arch/i386/kernel/process.c */ read() sys_read() /* fs/read_write.c */ 시스템호출처리함수들의시작점주소 sys_call_table() /* arch/i386/kernel/entry.s */ 테이블에등록된시스템호출처리함수를호출 system_call() /* arch/i386/kernel/entry.s */ sys_call_table 관리 인터럽트 ( 트랩 ) 처리메커니즘에의해호출 30
인터럽트처리과정 인터럽트 주변장치가자신에게발생한비동기적인사건을커널에게알리는 매커니즘 인터럽트처리를위한루틴들을함수로구현 각함수의시작점주소 : IDT(Interrupt Descriptor Table), IVT(Interrupt Vector Table) 에등록 리눅스커널 : idt_table table /* arch/i386/kernel/traps.c */ Real time Clock CPU Kernel disk tty network cdrom PIC IDT(IVT). 0 timer_interrupt(). 1 hd_interrupt() t(). 인터럽트를발생시킬수있는주변장치들은 Programmable Interrupt Controller 칩의각핀에연결 2 3 tty_interrupt()... el3_interrupt() interrupt_ handlers Timer_interrupt()... Hd_interrupt() 31
주변장치와커널통신방법 인터럽트 (interrupt) 방식 커널이주변장치에게일을시킨후다른일을하며그장치가일을완료한후커널에게인터럽트로알리는방식 사건이발생하면주변장치가커널에게알리는방식 폴링 (polling) 방식 커널이주변장치에게일을시킨후그일이완료되었는지검사하는방식 커널이주변장치에서사건의발생을검사하는방식 32
인터럽트 vs. 트랩 시스템에서발생하는비동기적인사건을알리는메커니즘 인터럽트 현재수행중인프로세스와관계없이하드웨어적인사건을알리는메커니즘 트랩 (Trap) 현재수행중인프로그램이유발한소프트웨어적인사건의발생을알리는메커니즘 예외처리 (exception handling) Divide by zero, segment fault, page fault, invalid operation code, security fault 등 인터럽트처리와같은방법으로처리 33
IDT 테이블구조 0x00 0x08 0x20 0x80 0xFF idt_tabletable divide_error() debug() nmi() segment_not_present() not page_fault timer_interrupt() hd_interrupt() system_call() Include/asm_i386/desc.h Include/asm _ i386/irq.h Arch/i386/kernel/traps.c Common trap handler for 8086 FIRST_EXTERNAL_VECTOR Device interrupt handler (IRQ) /* 주변장치 */ SYSCALL_VECTOR 34
시스템호출과정 ( 요약 ) /* user task */ main() { fork() } 0x00 idt_table /* arch/i386/kernel/entry.s */ divide_error() ENTRY(system_call) SAVE_ALL debug() call nmi() *SYMBOL_NAME(sys_call_table)(,%eax,4), ) /* libc.a */ fork() { } movl 2, %eax int $0x80 0x08 0x20 0x80 0xFF Kernel page_fault timer_interrupt() interrupt() hd_interrupt() system_call() 1 2 sys_call_table sys_exit() sys_fork() sys_fork() /* arch/i386/kernel/process.c c */ /* kernel/fork.c */ 35
과제 앞에서구현한새로운시스템호출처리과정을그림으로나타내시오 36
실습 1: 커널정보출력 #include <linux/unistd.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> asmlinkage int sys_gettaskinfo() { int i, cnt=0; printk("pid : %d n", current->pid); printk("ppid : %d n", current->p_pptr->pid); if (current->state == -1) printk("unrunnable state n"); else if (current->state == 0) printk("runnable state n"); else if (current->state t t == 1) printk("interruptable state n"); else if (current->state == 2) printk("uninterruptable state n"); else if (current->state t t == 4) printk("zombie state n"); else if (current->state == 8) printk("stopped state n"); else if (current->state t t == 16) printk("swapping state n"); else printk("unknown state n"); } printk("priority : %lu n", current->priority); printk("scheduling Policy : %lu n", current->policy); printk("user CPU time : %lu ticks n", current->times.tms_utime); printk("system CPU time : %lu ticks n", current->times.tms_stime); printk("start time : %lu n", current->start_time); printk("number of major faults : %lu n", current->maj_flt); printk("number of minor faults : %lu n", current->min_flt); for (i=0; i<256; i++) if (current->files->fd_array[i]!= NULL) { cnt++; } return(0); printk("number of opened file : %d n", cnt); #include <linux/unistd.h> _syscall0(int, gettaskinfo); main() { int i; i = gettaskinfo(); } 37
실습 2: 인자전달 #include <linux/unistd.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> #include <asm-i386/uaccess.h> asmlinkage int sys_show_mult(int x, int y, int *res) { int error, compute; error = verify_area(verify_write, res, sizeof(init)); if (error) return error; compute = x*y; put_user(compute, res); return(0); } put_user() - 곱셉한결과를사용자레벨공간에전달하기위해 verify_area() - res라는사용자공간에쓰기가가능한지확인 #include <linux/unistd.h> _syscall3(int, show_mult, int, x, int, y, int *, result); main() { int mult_ret = 0; int x = 2; int y = 5; } printf("%d * %d = %d n", x, y, mult_ret); show_mult(x, y, &mult_ret); printf("%d * %d = %d n", x, y, mult_ret); 38
실습 3: 구조체를이용한인자전달 /* mystat.h */ search = &init_task; struct mystat { while (search->pid!= id) { int pid; // pid_t pid; search = search->next_task; task; int ppid; // pid_t ppid; if (search->pid == init_task.pid) int state; return(-1); int priority; } int policy; long utime; long stime; long starttime; unsigned long min_flt; unsigned long maj_flt; buf->pid = search->pid; int open_files; }; #include <linux/unistd.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> #include <asm-i386/uaccess i386/uaccess.h> #include "mystat.h" #include <linux/malloc.h> asmlinkage int sys_getstat (int id, struct mystat *user_buf) { struct mystat *buf; int i, cnt=0; struct task_struct *search; } buf = kmalloc(sizeof(struct mystat), GFP_KERNEL); if (buf == NULL) return (-1); buf->ppid = search->p_pptr->pid; buf->state = search->state; buf->priority = search->priority; buf->policy = search->policy; buf->utime utime = search->times.tms_utime; buf->stime = search->times.tms_stime; buf->starttime = search->starttime; buf->min_flt = search->min_flt; buf->maj_flt = search->maj_flt; for (i=0; i<256; i++) if (current->files->fd_array[i]!= NULL) cnt++ buf->open_files = cnt; copy_to_user(user_buf, buf, sizeof(struct mystat)); return(0); 39
실습 3: 구조체를이용한인자전달 ( 계속 ) #include <stdio.h> #include <linux/unistd.h> #include "mystat.h" struct mystat *mybuf; _syscall2(int, getstat, int, taskid, struct mystat *, ret_buf); int main(int argc, int* argv[]) { int task_number; if (argc!= 2) { printf("usage : a.out pid n"); exit(1); } task_number = atoi(argv[1]); my_buf = (struct mystat *) malloc(sizeof(struct mystat)); if (my_buf == NULL) { printf("out of Memory n"); exit(1); } getstat(task_number, mybuf); printf("pid : %d n", mybuf->pid); printf("ppid : %d n", mybuf->ppid); if (mybuf->state == -1) printf("unrunnable state n"); else if (mybuf->state == 0) printf("runnable state n"); else if (mybuf->state == 1) printf("interruptable state n"); else if (mybuf->state == 2) printf("uninterruptable state n"); else if (mybuf->state == 4) printf("zombie state n"); else if (mybuf->state == 8) printf("stopped state n"); else if (mybuf->state == 16) printf("swapping state n"); else printf("unknown state n"); printf("priority : %d n", mybuf->priority); printf("scheduling Policy : %d n", mybuf->policy); printf("user CPU time : %lu ticks n", mybuf->utime); printf("system CPU time : %lu ticks n", mybuf->stime); printf("start time : %lu n", mybuf->starttime); printf("number of major faults : %lu n", mybuf->maj_flt); printf("number of minor faults : %lu n", mybuf->min_flt); printf("opened files : %lu n", mybuf->open_files); files); } return(0); 40
커널모듈프로그래밍 1 /* hello.c */ #include <linux/kernel.h> #include <linux/module.h> int init_module() { printk( Hello, World n ); return 0; } void cleanup_module() { printk( End, Module Kernel n ); } $make $more /proc/modules $lsmod $insmod hello.o /* -f */ $more /proc/modules / $rmmod hello.o /* Makefile */ CC=gcc MODFLAGS := -Wall DMODULE D KERNEL -DLINUX hello.o : hello.c /usr/include/linux/version.h $CC $(MODFLAGS) c hello.c W:Warning Warning D : Definition 41
커널모듈프로그래밍 2 #include <linux/module.h> // Needed by all modules #include <linux/kernel.h> l // Needed d for KERN_ALERT #include <linux/init.h> // Needed for the macros static int hello_2_init(void) { printk(kern_alert "Hello, world 2 n"); return 0; } static ti void hello_2_exit(void) id) { printk(kern_alert "Goodbye, world 2 n"); } module_init(hello_2_init); module_exit(hello_2_exit); /* Makefile */ CC=gcc MODFLAGS := -Wall DMODULE D KERNEL -DLINUX hello2.o : hello2.c /usr/include/linux/version.h $CC $(MODFLAGS) c hello2.c 42
주요커널자료구조 태스크 struct t task_struct t t /* include/linux/sched.h / d h */ 메모리 struct vm_area_struct /* include/linux/sched.h */ 페이지 /* include/asm-i386/page.h i386/ */ 파일 struct file, struct inode /* include/linux/fs.h, ext2_fs_i.h */ 파일시스템 struct super_block /* include/linux/fs.h */ 디바이스드라이버 struct device_struct /* fs/devices.c, driver/* */ IPC 메시지, 세마포어, 공유메모리 /* include/linux/ipc.h, sem.h, msg.h, shm.h h */ 소켓 /* include/linux/net.h */ 통신프로토콜 TCP/IP /* include/linux/tcp.h, ip.h */ 43