프로젝트 2 부트로더동작원리분석 단국대학교컴퓨터학과 2009 백승재 baeksj@dankook.ac.kr k k k http://embedded.dankook.ac.kr/~baeksj
Target Board 가생기면... Schematic Chip manual Bootloader blob, u-boot,... Kernel Rootfs ramdisk, cramfs, jffs,...
Linux 의부팅과정이해 강의목표 실행파일과링커스크립트관계이해 부트로더구조및원리파악 Blob 을통한부트로더소스분석
Booting 의의미 Booting 의정의 Kernel이메모리에올려지고하드웨어가초기화되어바로사용가능한상태로만드는과정을부팅이라고한다. Booting 의목적 processor 초기화 memory 점검및초기화 각종하드웨어점검및초기화 kernel loading kernel 자료구조등록및초기화 사용환경조성
부팅과정도식도 (1/5) SUN Sparc, Intel ix86 ROM BIOS HDD 의 MBR Power On VGA 체크 Memory 체크 IDE 장치체크각종장치정보수집 FDD 의 MBR StrongARM, XScale, Flash Memory MBR 에있는 Boot loader 로딩또는 bootsect.s 로딩
부팅과정도식도 (2/5) kernel 을 main memory 로로딩 kernel 압축해제
부팅과정도식도 (3/5) 초기화 code 수행 start_kernel() { Architecture 의존적인설정 trap에대한초기화 } Interrupt에대한초기화 Scheduler 에대한초기화 softirq에대한초기화 Timer 초기화 Console 초기화 kernel module 사용을위한초기화 kernel cache 에대한초기설정 Clock tick과 BogoMIPS를구함 buddy system 사용을위한 memory 초기화 kernel cache 에대한초기화 fork에관한초기화 (max threads) 각종 kernel cache 및 buffer에대한생성및초기화 /proc 디렉토리에대한초기화 IPC 에대한초기화 SMP에대한초기화 init kernel thread 시작 kernel idle init kernel thread 각종 interface 장치초기화 network interface 초기화 initrd 로딩및 / mount free memory 재계산 console open /sbin/init process 수행
부팅과정도식도 (4/5) /sbin/init 수행 signal handler 설정 console 설정 /etc/inittab 파일 read /etc/rc.d/rc.sysinit script 수행 /etc/rc.d/rc script 수행 /sbin/mingetty 수행 run level 5이면 /etc/x11/prefdm수행 /etc/x11/prefdm /etc/rc.d/rc.sysinit host name 설정시간설정 usb 설정 file system check ISA 설정 sound 설정 /etc/rc.d/rc 3 run level에따른 /etc/rc*.d 디렉토리의 script 를수행 gdm 또는 kdm 또는 xdm 실행 /sbin/mingetty 가상터미널을띄우고 login 프로그램실행
부팅과정도식도 (5/5) /sbin/login 인증수행후 shell 실행 Shell 실행
Bootloader 의역할과종류 Boot Loader 의역할 Kernel을 memory에적재하고제어를 kernel로옮김 OS의선택적부팅 Kernel 다운로드를제공하기도한다 Embedded system을위한 boot loader는 BIOS에서해주는하드웨어초기화작업담당 Boot Loader 의종류 LILO 전통적인 linux boot loader이다. 일반적인 boot loader가그렇듯 assembly로짜여져있고크게 MBR에들어가는 first.s와 /boot/boot.b로만들어지는 second.s 두부분으로이루어져있다 GRUB 최근에주목받고있는 boot loader로서기능과유연성면에서 LILO보다앞선다. GNU에서만들었으며뛰어난 shell interface를제공한다 Blob http://sourceforge.net/projects/blob ARM SA-11x0 architecture에서사용하는대표적인 boot loader로서 GNU GPL이여서사용에제한이없고 serial을통한다운로드를지원한다 u-boot http://www.denx.de/wiki/u-boot
프로젝트 2 Blob
Entry point 확인 ~/src/blob/start-ld-script OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS {. = 0x00000000;. = ALIGN(4);.text : { *(.text) }. = ALIGN(4);.rodata : { *(.rodata) } 32bit ELF 포맷을가지는 little endian 형태의코드를생성 이파일이컴파일된뒤 ARM 이라는 Architecture에서실행됨을알려줌 실행파일의 entry point 가 _start 라는레이블임을알려줌 }. = ALIGN(4);.data : { *(.data) }. = ALIGN(4);.got : { *(.got) }. = ALIGN(4);.bss : { *(.bss) } 물리주소 0x00000000 번지에서수행이시작되며, 실행파일은코드가들어가있는 text와 rodata, data, got, bss의순서대로 4byte 단위로구성됨을의미
Memory 구성도 start.s 0x 0000 0000
_start 레이블 ~/src/blob/start.s.text 영역의시작임을나타냄.text /* Jump vector table as in table 3.1 in [1] */.globl _start _start: b reset b undefined_instruction b software_interrupt b prefetch_abort b data_abort b not_used b irq.globl로선언된_start 즉, 외부에서 extern 해다쓸수있는 _start 심볼 b fiq ARM의각exception의 handler 를등록시켜놓은table
reset 레이블 ~/src/blob/start.s IC_BASE:.word 0x90050000 #define ICMR 0x04 reset: /* First, mask **ALL** interrupts */ ldr r0, IC_BASE mov r1, #0x00 str r1, [r0, #ICMR] 모든인터럽트를불허함
~/src/blob/start.s PWR_BASE:.word 0x90020000 #define PSPR 0x08 #define PPCR 0x14 reset 레이블 /* switch CPU to correct speed */ ldr r0, PWR_BASE LDR r1, cpuspeed str r1, [r0, #PPCR] /* setup memory */ bl memsetup /* init LED */ bl ledinit CPU clock speed 설정
reset 레이블 ~/src/blob/start.s /* The initial CPU speed. Note that the SA11x0 CPUs can be safely overclocked: * 190 MHz CPUs are able to run at 221 MHz, 133 MHz CPUs can do 206 Mhz. */ #if (defined ASSABET) (defined CLART) (defined LART) \ (defined NESA) (defined NESA) cpuspeed:.long 00b 0x0b /* 221 MHz */ #elif defined SHANNON cpuspeed:.long 0x09 /* 191.7 MHz */ #else #warning "FIXME: Include code to use the correct clock speed for your board" cpuspeed:.long 0x05 /* safe 133 MHz speed */ #endif
~/src/blob/ledasm.s ledinit 레이블.text LED:.long LED_GPIO GPIO_BASE:.long 0x90040000 #define GPDR 0x00000004 #define GPSR 0x00000008 #define GPCR 0x0000000c LED 점등후원래루틴으로복귀.globl ledinit /* initialise LED GPIO and turn LED on. * clobbers r0 and r1 */ ledinit: ldr r0, GPIO_BASE ldr r1, LED str r1, [r0, #GPDR] /* LED GPIO is output */ str r1, [r0, #GPSR] /* turn LED on */ mov pc, lr
reset 레이블 ~/src/blob/start.s RST_BASE:.word 0x90030000 #define RCSR 004 0x04 /* check if this is a wake-up from sleep */ ldr r0, RST_BASE ldr r1, [r0, #RCSR] and r1, r1, #0x0f teq r1, #0x08 bne normal_boot /* no, continue booting */ SA11X0엔 H/W reset, S/W reset, Watchdog reset, Sleep reset 네가지의 reset이존재한다. RCSR register는어떤이유로 reset 인터럽트가발생했는가를나타내며, RSSR register는 S/W reset을발생시키는 register이다. 따라서 Blob에선 RCSR register의값을읽어서하위네비트값을통해어떤이유로 reset되었는지를살펴본뒤, Sleep reset 인경우엔대응하는비트를클리어한뒤, PSPR register값을읽어 r1에넣어주고, 이값으로 jump함으로써이전상태로복원하게되며, 정상적인 H/W reset인경우라면 normal_boot 레이블로이동하게된다.
~/src/blob/start.s normal_boot 레이블 normal_boot: /* check the first 1MB of BLOB_START in increments of 4k */ mov r7, #0x1000 mov r6, r7, lsl #8 /* 4k << 2^8 = 1MB */ ldr r5, BLOB_START mem_test_loop: mov r0, r5 bl testram teq r0, #1 beq badram add r5, r5, r7 subs r6, r6, r7 bne mem_test_loop 메모리시작번지로부터 1MB를테스트한다. r7에 0x1000을넣고좌로 8번 shift연산을수행하여 r6에 1M를넣은후, 메모리의시작번지주소인 0xc000 0000 을 r5 register에넣는다.
~/src/blob/start.s normal_boot 레이블 relocate: adr r0, _start /* relocate the second stage loader */ add r2, r0, #(64 * 1024) /* blob maximum size is 64kB */ add r0, r0, #0x400 /* skip first 1024 bytes */ ldr r1, BLOB_START /* r0 = source address * r1 = target address * r2 = source end address */ copy_loop: ldmia r0!, {r3-r10} stmia r1!, {r3-r10} cmp r0, r2 ble copy_loop bl led_off /* blob b is copied to ram, so jump to it */ ldr r0, BLOB_START mov pc, r0 Blob 을 RAM 상으로복사한뒤, 해당주소로점프
rest-ld-script OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_trampoline) SECTIONS {. = 0xc0000400; }. = ALIGN(4);.text : { *(.text) }. = ALIGN(4);.rodata : { *(.rodata) }. = ALIGN(4);.data : { *(.data) }. = ALIGN(4);.got : { *(.got) }. = ALIGN(4);.bss : { *(.bss) } 실행파일의 entry point가 _trampoline 이라는레이블임을알려줌물리주소 0xc0000400 번지에서수행이시작되며, 실행파일은코드가들어가있는text와 rodata, data,,g got, bss의순서대로 4byte 단위로구성됨을의미
Memory 구성도 start.s 0x 0000 0000 rest(trampoline.s, main.c ) 0x c000 0400
_trampoline 레이블 ~/src/blob/trampoline.s.text.globl _trampoline _trampoline: /* clear the BSS section */ ldr r1, bss_start ldr r0, bss_end sub r0, r0, r1 /* r1 = start address */ /* r0 = #number of bytes */ mov r2, #0 clear_bss: stmia r1!, {r2} subs r0, r0, #4 bne clear_bss /* setup the stack pointer */ ldr r0, stack_end sub sp, r0, #4 /* jump to C code */ bl main /* if main ever returns we just call it again */ b _trampoline BSS 영역 clear 후, stack pointer 설정하고, C 로작성된 main 함수로제어를넘김
~/src/blob/main.c int main(void) { int numread = 0; char commandline[max_commandline_length]; int i; int retval = 0; #ifdef PARAM_START u32 conf; #endif /* call subsystems */ init_subsystems(); /* initialise status */ blob_status.paramtype = fromflash; blob_status.kerneltype = fromflash; blob_status.ramdisktype = fromflash; blob_status.downloadspeed = baud_115200; main 함수 blob_status.terminalspeed terminalspeed = baud_9600; blob_status.load_ramdisk = 1; blob_status.cmdline[0] = '\0'; blob_status.boot_delay = 10; /* call serial_init() because the default 9k6 speed might not 시리얼포트를초기화한뒤 blob_status 구조체에변수를초기화함 be what the user requested */ #if defined(h3600) defined(shannon) defined(idr) defined(badge4) defined(jornada720) blob_status.terminalspeed = baud_115200; /* DEBUG */ #endif #if defined(pt_system3) blob_status.terminalspeed = baud_38400; #endif serial_init(blob_status.terminalspeed);
main 함수 ~/src/blob/main.c /* Print the required GPL string */ SerialOutputString("\nConsider yourself LARTed!\n\n"); SerialOutputString(version_str); SerialOutputString("Copyright (C) 1999 2000 2001 " "Jan-Derk Bakker and Erik Mouw\n"); SerialOutputString(PACKAGE " comes with ABSOLUTELY NO WARRANTY; " "read the GNU GPL for details.\n"); SerialOutputString("This is free software, and you are welcome " "to redistribute it\n"); SerialOutputString("under certain conditions; " "read the GNU GPL for details.\n"); /* get the amount of memory */ get_memory_map(); print_elf_sections(); /* Parse all the tags in the paramater block */ #ifdef PARAM_START parse_ptags((void *) PARAM_START, &conf); #endif get_memory_map() 함수를호출하여시스템의메모리양을결정하고 blob_status 구조체에변수를초기화함
blob_status 구조체설명 typedef enum { fromflash = 0, fromdownload = 1 } block_source_t; typedef struct t { int kernelsize; block_source_t kerneltype; u32 kernel_md5_digest[4]; int paramsize; block_source_t paramtype; u32 param_md5_digest[4]; int ramdisksize; block_source_t ramdisktype; u32 ramdisk_md5_digest[4]; md5 digest[4]; int blobsize; block_source_t blobtype; u32 blob_md5_digest[4]; serial_baud_t downloadspeed; serial_baud_t terminalspeed; int load_ramdisk; int boot_delay; char cmdline[command_line_size]; } blob_status_t; blob_status_t blob_status; 커널이미지의크기커널을어디서가져오는지를정의 Ramdisk 의크기 Ramdisk 를어디서가져오는지를정의 Blob 자체의크기 Blob 를어디서가져오는지를정의
main 함수 ~/src/blob/main.c do_reload("blob"); do_reload("kernel"); d("k l"); do_reload() 함수를이용하여 blob, kernel, ramdisk 를메인메모리로끌고올라옴 if(blob_status.load_ramdisk) do_reload("ramdisk"); /* wait 10 seconds before starting autoboot */ SerialOutputString("Autoboot in progress, press any key to stop "); for(i = 0; i < blob_status.boot_delay; i++) { serial_write('.'); retval = SerialInputBlock(commandline, 1, 1); } if(retval > 0) break; /* no key was pressed, so proceed booting the kernel */ if(retval == 0) { commandline[0] = '\0'; parse_command("boot"); do_reload() 함수를이용하여 blob, kernel, ramdisk 를메인메모리로끌고올라옴 }
~/src/blob/main.cc void do_reload(char *commandline) { u32 *src = 0; u32 *dst = 0; int numwords; if(mystrncmp(commandline, "blob", 4) == 0) { src = (u32 *)BLOB_RAM_BASE; dst = (u32 *)BLOB_START; numwords = BLOB_LEN / 4; blob_status.blobsize = 0; blob_status.blobtype = fromflash; SerialOutputString("Loading blob from flash "); } else if(mystrncmp(commandline, "kernel", 6) == 0) { src = (u32 *)KERNEL_RAM_BASE; dst = (u32 *)KERNEL_START; numwords = KERNEL_LEN / 4; blob_status.kernelsize = 0; blob_status.kerneltype = fromflash; SerialOutputString("Loading kernel from flash "); } else if(mystrncmp(commandline, "ramdisk", 7) == 0) { src = (u32 *)RAMDISK_RAM_BASE; dst = (u32 *)INITRD_START; numwords = INITRD_LEN / 4; blob_status.ramdisksize = 0; blob_status.ramdisktype = fromflash; SerialOutputString("Loading g ramdisk from flash "); } else { SerialOutputString("*** Don't know how to reload \""); SerialOutputString(commandline); SerialOutputString("\"\n"); return; } Reload 함수 MyStrNCmp() 함수를이용하여인자로넘어온이미지를 Blob인경우0xc100 0000 번지에 Kernel인경우0xc000 8000 번지에 Ramdisk인경우0xc080 0000 번지에올린다. } MyMemCpy(src, dst, numwords); SerialOutputString(" done\n");
Memory 구성도 start.s 0x 0000 0000 rest(trampoline.s, main.c ) 0x c000 0400 Kernel Ramdisk Blob 0x c000 8000 0x c080 0000 0x c100 0000
~/src/blob/main.cc main 함수 static int boot_linux(int argc, char *argv[]) { void (*thekernel)(int zero, int arch) = (void (*)(int, int))kernel_ram_base; setup_start_tag(); setup_memory_tags(); setup_commandline_tag(argc, argv); setup_initrd_tag(); setup_ramdisk_tag(); tag(); setup_end_tag(); 커널이사용할 BOOT_PARAMS 를채워서메모리 0xc000 0100 에올려놓고 } /* we assume that the kernel is in place */ SerialOutputString("\nStarting kernel...\n\n"); serial_flush_output(); /* disable subsystems that want to be disabled before kernel boot */ exit_subsystems(); /* start kernel */ thekernel(0, ARCH_NUMBER); SerialOutputString("Hey, the kernel returned! This should not happen.\n"); return 0; 첫번째 arg 에는 0 을두번째 arg에는 ARCH_NUMBER를넣고제어를넘긴다.
tag구조체 linux/include/asm-arm/setup arm/setup.h struct tag_header { u32 size; u32 tag; }; struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; tag 의크기와 magic number 메모리의크기와시작주소비디오램의위치와 resolution ramdisk의옵션, 크기및시작주소 Initial RAM disk의크기와시작주소 Serial의개수 Revision 번호 Video Frame Buffer의설정사항 }; } u; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk;
Memory 구성도 start.s 0x 0000 0000 BOOT_PARAMS rest(trampoline.s, main.c ) 0x c000 0100 0x c000 0400 Kernel Ramdisk Blob 0x c000 8000 0x c080 0000 0x c100 0000