프로젝트 1 Interrupt, Module Programming 단국대학교컴퓨터학과 2009 백승재 ibanez1383@dankook.ac.kr k k http://embedded.dankook.ac.kr/~ibanez1383
강의목표 Linux 의인터럽트처리과정이해 시스템호출원리파악 모듈프로그래밍기법숙지
인터럽트의분류 3 인터럽트 외부인터럽트 fault page fault, 트랩 trap int, system call, abort devide by zero,
인터럽트와트랩의처리 4 devide by zero error debug nmi int3 H/W 0 1 CPU 2 IDT(IVT) idt_table devide_error debug nmi Kernel sys_call_table 0 sys_restart_syscall syscall sys_exit sys_write timer 3 int3 N action irq_desc action keyboard 32 0 timer_interrupt PIC floppy do_irq floppy_interrupt HDD 128 system_call 255 223 floppy_interrupt action action
irq_desc 5 irq_desc NR_IRQS( 보통 224) 개의 irq_desc_t 디스크립터의배열 status : IRQ 선의상태나타내는 flags handler : IRQ선을처리하는 PIC회로를나타내는 hw_interrupt_type 디스크립터에대한ptr action : IRQ가발생할때호출할 ISR lock : IRQ디스크립터에대한 spin lock
irqaction 6 irqaction 디스크립터 handler : I/O device 용 ISR 을가리킴 flags : IRQ 선과 I/O device 간의관계설명 name : I/O device 의이름 dev_id : I/O device 서사용하는 private 필드 next : irqaction 디스크립터리스트의다음항목을가리킴
인터럽트처리과정 7 do_irq() 스택에는 return addr, SAVE_ALL로저장한 reg값, 변환한 IRQ번호, INT발생시 CU가자동저장한 reg unsigned int do_irq(struct pt_regs regs) pt_regs 처음 9필드는 SAVE_ALL로저장한 reg값, 10번째는 orig_eax로참조하는필드 = 변환한 IRQ번호, 나머지는 CU가자동저장한 regls
시스템호출처리 8 User program C library Call gate 유저레벨커널레벨 System call handler System call rountine
시스템호출처리과정 9 시스템호출처리과정예 : fork user task main(). fork() libc.a 0x0 IDT divide_error() debug() nmi() Kernel ENTRY(system_call) /* arch/i386/kernel/entry.s */ SAVE_ALL. call *SYMBOL_NAME(sys_call_table)(,%eax,4). ret_from_sys_call (schedule, signal, bh_active, nested interrupt handling). sys_call_table. fork(). movl 2, %eax 1 sys_exit() int $0x80 0x80 system_call(). 2 sys_fork().. 3 sys_read () 4 sys_write (). sys_fork() /* arch/i386/kernel/process.c c */ /* kernel/fork.c */
시스템호출처리관련 10 system_call() sys_system_call() asmlinkage 함수의 argument 를 stack 을통해전달받음 Asm내에서 C 함수를호출할수있도록함 시스템콜번호 각시스템콜에시스템콜번호부여. 고유한숫자 sys_call_table에저장하고있다 시스템콜핸들러 int $0x80 128번 vector의, 프로그래밍에의한예외발생 순서 시스템콜사용위해커널에인터럽트를건다 (eax에 syscall번호 ) reg를커널모드스택에저장 시스템콜서비스루틴호출하여처리 ret_from_sys_call() 로핸들러서빠져나옴적법한매개변수, 퍼미션인지검사필수 copy_to_user(), copy_from_user() 사용
system_call 함수 11 1 2 3 넘어온매개변수를커널모드스택에저장 4 5 1. 제어유닛이자동으로저장한 eflags, cs, eip, ss, esp 제외한모든 reg 를스택에저장 2. ebx reg 에 current P 의디스크립터를저장 3. current 의 ptrace 필드에 PT_TRACESYS 플래그가들어있는지, 즉디버거가프로그램의시스템콜호출을추적중인지검사 syscall_trace() 를처음, 마지막두번호출하게됨 4. 올바른 syscall 번호인지검사. 잘못된번호이면바로종료 5. dispatch table의각엔트리는 4바이트이므로, 시스템콜번호에 4를곱한후 + sys_call_table 시작주소를더해서 서비스루틴의 ptr얻어와서호출함. 호출종료되면리턴값을저장한스택 ( 사용자모드에서의 eax) 에저장해놓고,syscall 핸들러를종료하는 ret_from_sys_call로점프
복귀과정 12
매개변수와주소공간 13 매개변수확인 매개변수가주소인경우검사방법두가지선형주소가 P 주소공간에속하는지, 속하면접근권한이있는지검사선형주소가 PAGE_OFFSET보다낮은지만확인 access_ok() 이용 system call에전달한주소검사 프로세스주소공간접근
새로운시스템호출구현 (1/7) 14 새로운시스템호출구현 커널수정 : 4 단계 1. 새로운시스템호출번호할당 (allocate syscall_number) 2. 새로운시스템호출함수 sys_call_table[] 에등록 3. 새로운시스템호출함수커널에구현 4. 커널컴파일및리부팅 사용자응용작성 : 2단계 1. 새로운시스템호출을사용하는사용자수준응용작성 2. 라이브러리로사용자응용작성 (optional) : ar, ranlib
새로운시스템호출구현 (2/7) 새로운시스템호출구현예 : newsyscall() 이라는이름의새로운시스템호출구현 15 1. 새로운시스템호출번호할당 /* include/x86/unistd_32.h 파일의내용 */ #define NR_restart_syscall 0 #define NR_exit 1 #define NR_fork 2 #define NR_read 3 #define NR_write 4 #define NR_open 5 #define NR_close 6 #define NR_epoll_create 254 #define NR_epoll_ctl 255 #define NR_epoll_wait 256 #define NR_fallocate 324 #define NR_newsyscall 325 #define NR_syscalls 326
새로운시스템호출구현 (3/7) 16 2. 새로운시스템호출함수 sys_call_table[] 에등록 sys_call_table /* arch/x86/kernel/syscall_table_32.s ll 파일의내용 */ ENTRY(sys_call_table).long sys_restart_syscall syscall.long sys_exit.long sys_fork.long sys_read.long sys_write.long sys_open.long sys_close.long sys_epoll_create.long sys_epoll_ctl.long sys_epoll_wait.long sys_fallocate.long sys_newsyscall 0 sys_ni_syscall() syscall() sys_exit() sys_fork() sys_read() sys_write() 256 sys_epoll_wait() 324 sys_fallocate() 325 sys_newsyscall()
새로운시스템호출구현 (4/7) 17 3. 새로운시스템호출함수커널에구현 /* kernel/newfile.c 파일의내용 */ #include <linux/unistd.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> asmlinkage long sys_newsyscall(void) printk("<0>hello Linux, I'm in Kernel\n"); return 0; EXPORT_SYMBOL_GPL(sys_newsyscall); printf() 가아니라 printk() 임에주의
새로운시스템호출구현 (5/7) 18 4. 커널컴파일및리부팅 커널컴파일전에 makefile 을다음과같이수정해야한다. /* kernel/makefile 의변경전내용 */ obj-y = sched.o fork.o exec_domain.o panic.o printk.o profile.o \ exit.o itimer.o time.o softirq.o resource.o \ sysctl.o capability.o ptrace.o timer.o user.o \ signal.o sys.o kmod.o workqueue.o pid.o \ rcupdate.o extable.o params.o posix-timers.o \ kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \ hrtimer.o rwsem.o /* kernel/makefile 의변경후내용 */ obj-y = sched.o fork.o exec_domain.o panic.o printk.o profile.o \ exit.o itimer.o time.o softirq.o resource.o \ sysctl.o capability.o ptrace.o timer.o user.o \ signal.o sys.o kmod.oo workqueue.oo pid.o \ rcupdate.o extable.o params.o posix-timers.o \ kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \ hrtimer.o rwsem.o newfile.o 커널컴파일후재부팅
새로운시스템호출구현 (6/7) 19 1. 새로운시스템호출을사용하는사용자수준응용작성 /* /usr/include/asm-x86/unistd_32.h */ #define NR_signalfd 321 #define NR_timerfd 322 #define NR_eventd 323 #define NR_fallocate 324 #include <linux/unistd.h> int main(void) syscall(325); return 0; /* /usr/include/asm-x86/unistd_32.h */ #define NR_signalfd 321 #define NR_timerfd 322 #define NR_eventd 323 #define NR_fallocate 324 #define NR_newsyscall 325 #include <linux/unistd.h> int main(void) syscall( NR_newsyscall); return 0;
syscall 매크로 20 /* /usr/include/unistd.h */ extern long int syscall (long int sysno,...) THROW; /* glibc/sysdeps/unix/sysv/linux/i386/syscall.s */.text ENTRY (syscall) PUSHARGS_6 /* Save register contents. */ _DOARGS_6(44) /* Load arguments. */ movl 20(%esp), %eax /* Load syscall number into %eax. */ ENTER_KERNEL /* Do the system call. */ POPARGS_6 /* Restore register contents. */ cmpl $-4095, %eax /* Check %eax for error. */ jae SYSCALL_ERROR_LABEL /* Jump to error handler if error. */ L(pseudo_end): ret /* Return to caller. */ PSEUDO_END END (syscall) /* glibc/sysdeps/unix/sysv/linux/i386/sysdep.h */ # define ENTER_KERNEL int $0x80
새로운시스템호출구현 (7/7) 21 2. 라이브러리로사용자응용작성 $ vi newsys.c $ gcc c newsys.c $ ar -r libnew.a newsys.o $ vi test.c $ gcc O o test test.c L./ -lnew $./test #include <linux/unistd.h> int newsyscall(void) syscall( NR_newsyscall); #include <linux/unistd.h> int main(void) newsyscall(); return 0;
시스템호출구현확장 (1/7) 22 인자전달 기존시스템호출분석 커널정보얻기 모듈프로그래밍을이용한시스템호출구현 => 모듈프로그래밍장참조 Just Do It ( 百見不如一打 )
시스템호출구현확장 (2/7) 23 인자전달 : show_mult(arg1, arg2, result) 1. 새로운시스템호출번호할당 : 192번 2. 새로운시스템호출함수 sys_call_table[] 에등록 3. 새로운시스템호출함수커널에구현 #include<linux/unistd.h> #include<linux/kernel.h> #include<asm-x86/uaccess.h> asmlinkage int sys_show_mult(int x, int y, int* res) int error, compute; int i; error = access_ok(verify_write,res,sizeof(*res)); if(error < 0) printk("error in cdang\n"); printk("error is %d\n",error); return error; compute = x*y; printk("computeis %d\n",compute); i= copy_to_user(res,&compute,sizeof(int)); return 0; 4. 커널컴파일및리부팅
시스템호출구현확장 (3/7) 24 인자전달 : show_mult(arg1, arg2, result) 1. 사용자수준응용 #include <stdio.h> #include <linux/unistd.h> int main(void) int mult_ret = 0; int x = 2,y=5; int i; i=syscall(325,x,y,&mult_ret); printf("x is %d\ny is %d\nret is %d\n",x,y,mult_ret); return 0;
시스템호출구현확장 (4/7) 25 커널정보얻기 : gettaskinfo() header //mystat.h #include<linux/kernel.h> #include<linux/sched.h> struct mystat pid_t pid; pid_t ppid; int stat; int priority; int policy; long utime; long stime; long starttime; unsigned long min_flt; unsigned long maj_flt; long open_files; ;
시스템호출구현확장 (5/7) 26 커널정보얻기 : gettaskinfo() 커널함수 asmlinkage int sys _g gettaskinfo(int id, struct mystat *user_ buf) struct mystat *buf; int i, cnt = 0; struct task_struct *search; struct file *fp; search = &init_task; while(search->pid!= id) // search = next_task(search); // 아래 list_entry 대신사용가능 search = list_entry((search)->tasks.next,struct task_struct,tasks); if(search->pid == init_task.pid) printk("init_task\n"); return -1; buf = (char*)kmalloc(sizeof(struct mystat),gfp_kernel); if(buf == NULL) printk("buf is NULL\n"); return -1; buf->pid = search->pid; buf->ppid = search->parent->pid; buf->stat = search->state; buf->priority = search->prio; buf->policy = search ->policy; buf->utime = search->utime; buf->stime = search->stime; buf->starttime = search->start_time.tv_sec; buf->min_flt flt = search->min_flt; buf->maj_flt = search->maj_flt; for(i = 0; i<32; i++) if( (search->files->fd_array[i])!=null) cnt++; buf->open_files = cnt; copy_to_user(user_buf,buf,sizeof(struct mystat)); return 0;
시스템호출구현확장 (6/7) 27 커널정보얻기 : gettaskinfo() 사용자수준응용 #include"mystat.h" #include<linux/unistd.h> int main(int argc, char* argv[]) int task_number; struct mystat* mybuf; if(argc!=2) printf("usage : a.out pid \n"); exit(1); task_number = atoi(argv[1]); mybuf = (char*)malloc(sizeof(struct mystat)); if(mybuf == NULL) exit(1); syscall(326,task_number,mybuf); printf("pid is %d\n",(int)mybuf->pid); printf("ppid ppid is %d\n",(int)mybuf->ppid); printf("state is %d\n",(int)mybuf->stat); printf("policy is %d\n",(int)mybuf->policy); printf("file count is %d\n",mybuf->open_files); printf("start time is %d\n",mybuf->starttime); return 0;
시스템호출구현확장 (7/7) 28 기존시스템호출분석 fork - p = alloc_task_struct() - task structure initialize - copy_mm(). - copy_thread() -wake_up_process(p) -return(p->pid) p /* arch/i386/kernel/process.c c */ sys_fork() /* kernel/fork.c */ do_fork() /* arch/i386/kernel/entry.s */ ret_from_sys_call() /* arch/i386/kernel/process.c */ copy_thread(). - p->tss.eax = 0; - p->tss.eip = ret_from_fork; /* kernel/sched.c */ wake_up_process() - add_to_runqueue(p); - current->need >need_resched = 1 /* kernel/sched.c */ schedule() if (schedule parent) else (schedule child)
모듈프로그래밍의동기 29 Linux 에서모듈프로그래밍이개발된이유 Linux 는모노리딕커널 (monolithic kernel) 모든기능이커널내부에구현되어있다. 사소한커널변경 (trivial modification) 에도커널컴파일과리부팅이필요 커널의크기가너무크다. 커널의일부기능은거의사용되지않지만, 메모리에상주하고있다. 모듈프로그래밍 module: steps toward micro-kernelized Linux 커널의일부기능을커널에서빼고모듈로구현 그기능이필요할때만메모리에적재 작고깔끔한커널가능 (small and clean kernel) 메모리의효율적이용 커널기능의수정때마다컴파일및리부팅의필요가없다.
모듈프로그래밍의범위 30 모듈프로그래밍의범위 하드웨어종속적인부분과초기화부분을제외한거의모든커널기능을모듈로만들수있다. 주로파일시스템과디바이스드라이버 현재 Linux 가지원하는모듈인터페이스 file system register_filesystem, unregister_filesystem read_super, put_super block device driver register_blkdev, unregister_blkdev open, release character device driver register_chrdev, unregister_chrdev open, release network device driver register_netdev, unregister_netdev open, close exec domain register_exec_domain, i unregister_exec_domain load_binary, personality binary format register_binfmt, unregister_binfmt load_binary.
모듈의관리 31 모듈의메모리적재 insmod, lsmod, rmmod, modprobe #insmod fat.o #lsmod Module: #pages : Used by fat 6 0 #rmmod fat kerneld (kmod) : 자동모듈적재데몬 eg: mount -t msdos /dev/fd0 /mnt => transparent load fat & msdos modules
모듈프로그래밍예 (1/3) - 2.6 32 2.6 커널을위한 hello_module #include <linux/kernel.h> #include <linux/module.h> int hello_module_init(void) _ printk(kern_emerg "Hello Module~! I'm in Kernel n"); return 0; void hello_module_cleanup(void) printk("<0>bye Module~! n"); module_init(hello_module_init); init); module_exit(hello_module_cleanup); MODULE_LICENSE("GPL");
모듈프로그래밍예 (2/3) - 2.6 33 2.6 커널에서모듈컴파일을위한 Makefile O_TARGET := hello_module.ko obj-m := hello_module.o KERNEL_DIR := /lib/modules/$(shell uname -r)/build MODULE_DIR := /lib/modules/$(shell uname -r)/kernel/hello_module PWD := $(shell pwd) default : $(MAKE) -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules install : mkdir -p $(MODULE_DIR) cp -f $(O_TARGET) $(MODULE_DIR) clean : $(MAKE) -C $(KERNEL_DIR) SUBDIRS=$(PWD) clean
모듈프로그래밍예 (3/3) - 2.6 34 2.6 커널에서모듈테스트를위한명령어수행 $ vi Makefile $ vi hello_module.c $ ls Makefile hello_module.c $make $ ls Makefile hello_module.c hello_module.ko $ insmod hello_module.ko Hello Module~! I m in Kernel $ rmmod hello_module Bye Module~! $makeinstall $ ls l /lib/modules/2.6.18/kernel/hello_module hello_module.ko $ make clean $ ls Makefile hello_module.c
모듈프로그램 : system call wrapper (1/3) - 2.6 26 2.6 커널에서모듈을통한 system call wrapper 35 #include <linux/kernel.h> #include <linux/module.h> #include <linux/syscalls.h> /y #include <linux/slab.h> #include <linux/sched.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <asm-i386/unistd.h> #include <asm-i386/pgtable.h> unsigned long **sys_call_table; unsigned long **locate_sys_call_table(void) unsigned long temp; unsigned *p; unsigned long **sys_table; for ( temp = 0xc0000000; temp < 0xd0000000; temp+= sizeof(void *)) p = (unsigned long *)temp; if( p[ NR_close] == (unsigned long)sys_close) l ) sys_table = (unsigned long **)p; return &sys_table[0]; return NULL; asmlinkage int (*original_call)(const char *, int, int); asmlinkage int sys_our_open(const char *filename, int flags, int mode) printk("<0>open system call n"); return (original_call(filename, flags, mode));
모듈프로그램 : system call wrapper (2/3) - 2.6 36 #define flush_tlb_single(addr) single(addr) asm volatile ("invlpg (%0)" ::"r" (addr) : "memory") int syscall_hooking_init(void) if( (sys_call_table = locate_sys_call_table()) == NULL) printk("<0> Can't find sys_call_table n"); return -1; pgd_t *pgd = pgd_offset_k((unsigned long)sys_call_table); pud _ t *pud; pmd _ t *pmd; pte _ t *pte; if( pgd_none(*pgd)) return NULL; pud = pud_offset(pgd, (unsigned long)sys_call_table); if( pud_none(*pud)) return NULL; pmd = pmd_offset(pud, (unsigned long)sys_call_table); if( pmd_none(*pmd)) return NULL; if( pmd_large(*pmd)) pte = (pte_t *)pmd; else pte = pte_offset_kernel(pmd, (unsigned long)sys_call_table); pte->pte_low = _PAGE_KERNEL; flush_tlb_single((unsigned long)sys_call_table); printk("<0> sys_call_table is loaded at %p n", sys_call_table); original_call = (void *)sys_call_table[ NR_open]; sys_call_table[ NR_open] = (void *)sys_our_open; printk("<0> Module Init n"); return 0;
모듈프로그램 : system call wrapper (3/3) - 2.6 37 void syscall_hooking_cleanup(void) cleanup(void) sys_call_table[ NR_open] = original_call; printk("<0> Module cleanup n"); module_init(syscall_hooking_init); module_exit(syscall_hooking_cleanup); MODULE_LICENSE("GPL");