임베디드시스템엔지니어를위한리눅스커널분석 남상규 http://ruby.medison.co.kr/~halite 선임연구원 ( 주 ) 메디슨초음파연구소 halite (at) medison.com $Date: 2002/05/21 01:10:08 $ Copyright c 2002 by 남상규 1
1. 리눅스커널컴파일하기 6 1.1. 리눅스일반 6 1.1.1. 왜리눅스인가? 6 1.1.2. 리눅스소스디렉토리구조 7 1.2. LXR 11 1.3. 소스코드얻기 11 1.4. 소스코드풀기 11 1.5. 컴파일준비 12 1.6. 커널설정 13 1.7. 커널컴파일 14 1.8. 커널테스트및설치 14 2. Makefile 분석 15 2.1. 부팅과정의이해 [1] 16 2.2. 커널이미지파일의구조 18 2.2.1. 커널의부팅 19 2.2.2. zimage와 bzimage의차이 20 2.3. bzimage가만들어지는과정추적-makefile 분석 20 2.3.1. $(topdir)/makefile 21 2.3.2. $(topdir)/arch/i386/makefile 32 2.3.3. $(topdir)/arch/i386/boot/makefile 35 2.3.4. $(topdir)/arch/i386/boot/compressed/makefile 37 2.3.5. $(topdir)/arch/i386/boot/tools/build.c 39 2.4. bzimage가만들어지는과정추적-log 분석 42 2.4.1. make bzimage 순서정리 42 2.4.2. Log 44 2.5. 단계별자세한분석 48 2.5.1. -Ttext 0x0의의미 48 2.5.2. 분석 49 3. 크로스컴파일러만들기 55 3.1. 크로스? 55 3.2. 툴체인 56 3.2.1. 배경 56 3.2.2. 미리만들어진툴체인 56 3.2.3. 툴체인만들기 57 4. ARM 리눅스 61 4.1. ARM 프로세서 MMU(Memory Management Unit) 61 4.1.1. 개요 61 4.1.2. 변환절차 63 4.1.3. 변환테이블베이스 63 4.1.4. 1레벨읽기 63 4.1.5. 1레벨디스크립터 64 2
4.1.6. 섹션디스크립터와섹션변환 64 4.1.7. 페이지테이블디스크립터 65 4.1.8. 2레벨디스크립터 66 4.1.9. 큰페이지변환 67 4.1.10. 작은페이지변환 68 4.1.11. 캐시와쓰기버퍼제어 69 4.1.12. 접근권한 70 4.2. Assabet 보드용커널컴파일 70 4.3. ARM 리눅스 Makefile 분석 73 4.3.1. $(TOPDIR)/arch/arm/Makefile 73 4.3.2. $(TOPDIR)/arch/arm/vmlinux.lds 82 4.3.3. $(TOPDIR)/arch/arm/boot/compressed/vmlinux.lds 85 4.3.4. Log 분석 86 4.4. 소스분석 87 4.4.1. arch/arm/boot/compressed/head.s 87 4.4.2. arch/arm/kernel/head-armv.s 96 5. 리눅스커널부팅 105 5.1. 커널시작 105 5.2. lock_kernel() 108 5.2.1. Lock이왜필요하지? 109 5.2.2. Lock - 기초적설명 110 5.2.3. i386, ARM의스핀락 110 5.3. setup_arch() 111 5.4. trap_init() 111 5.5. init_irq() 113 5.6. sched_init() 113 5.7. init() 114 5.8. dmesg 정리 115 6. 디바이스드라이버 121 6.1. 디바이스번호 121 6.2. 샘플디바이스드라이버 123 6.3. 모듈동작의이해 125 6.4. 알아야할것들 128 7. 부록 A. SEGA DreamCast Linux 131 7.1. A.1. LinuxSH 131 7.2. A.2. 드림캐스트에서리눅스실행해보기 132 8. 부록 B. 리눅스에시스템콜만들어넣기 132 8.1. B.1. 시스템콜의흐름 132 8.2. B.2. IDT(Interrupt Descriptor Table) 133 8.3. B.3. 시스템콜테이블 133 3
8.4. B.4. 시스템콜추가 135 9. 부록 C. Inline Assembly 137 9.1. C.1절. 인라인어셈블리기초 137 9.1.1. C.1.1절. 알아야할것들 137 9.1.2. C.1.2절. 어셈블리 139 9.1.3. C.1.3절. Output/Input 139 9.2. C.2절. 사례분석 143 9.2.1. C.2.1절. strcpy() 143 9.2.2. C.2.2절. _set_gate() 146 4
이문서는리눅스커널을임베디드시스템에포팅하려는엔지니어들을위한기본지식습득을위해만들어졌다. 리눅스커널자체의원론적인것보다는임베디드시스템에리눅스커널을포팅할때엔지니어가리눅스커널에쉽게접근하기위한정보나혹은방법을제공하는것이목적이다. 그러므로 OS 에대한이론보다는 OS 가만들어지는방법이나부팅되는순서메모리에적재되고실행되는순서등에대해기술하고더불어커널을만들기위해필요한도구들의사용법에대해알아본다. 원문은 http://ruby.medison.co.kr/~halite 에있고가장먼저업데이트될것이다. 틀린내용이있을수도있다. 이런것에대해선 <halite (at) medison.com> 으로연락바란다. $Revision: 1.13 5
1. 리눅스커널컴파일하기 리눅스시스템에있어서가장중요한커널을컴파일해돌려보는방법을주변에서가장찾기쉬운시스템인 PC 에서경험해보고기본적인방법차례를습득한다. 임베디드시스템엔지니어를위한리눅스커널분석 1.1. 리눅스일반리눅스의배경이나리눅스가왜좋은지그리고리눅스소스디렉토리의구조는어떻게생겼는지등을기술한다. 1.1.1. 왜리눅스인가? 리눅스가왜많이사용되고각광받는가? 이에대한대답은수많이존재할것이지만조금간추려보면대충아래와같은몇몇답으로축약할수있을것이다. 공짜아마이말이상의좋은매력은없을것이다. 리눅스가갖는가장좋은매력중하나가바로공짜가아닐까생각해본다. 만약리눅스가유료였다면현재만큼성장할수있었을까? 절대로그렇지않다고생각한다. 게다가리눅스는 GNU 의정신을따르기때문에 Copyleft 란말을쓴다 (copyright 의반대 ). 누구든어떤식으로든사용가능한셈이다. 공짜로성공하거나유명해진것은많지만컴퓨터분야에서가장유명한것이바로리눅스일것이다. 이런리눅의배경은다음과같은몇줄로압축할수있다. 리누스토발즈가개발, Copyleft 1991년 0.01이첫발표됨레드햇, 데비안, 슬랙왜어등의배포본이있음. 많은회사들에의해지원됨 특징 멀티태스킹멀티유저멀티프로세서많은아키텍쳐지원페이징 6
하드디스크용다이나믹캐시공유라이브러리 POSIX 1003.1 지원여러형태의실행파일지원진짜 386 프로텍티드모드수치프로세서에뮬레이션여러나라별키보드, 언어지원여러파일시스템지원 TCP/IP, SLIP, PPP BSD 소켓 System V IPC 버추얼콘솔 단점 모놀리딕커널 - 마이크로커널에비해많은부분이커널레벨에서구현된다. 초보에겐어렵다 - 시스템프로그래머를위한것이다 잘짜여진구조가아니다 ( 성능에초점이맞춰져있다 ) 매력 많은시스템에의해성능이검증되어있다 우리스스로가커널을조정하거나수정할수있다 1.1.2. 리눅스소스디렉토리구조리눅스커널소스는압축된것이약 20MB 이상이다. 그러므로수많은디렉토리와수많은파일을포함하고있다. 필자가완료한몇몇아주큰프로젝트에서도전체소스코드를압축해봤자 10MB 를넘기기는힘들었다. 그러나양으로도리눅스커널은이미함부로대하기힘든상대임을나타내는데, 여기서는리눅스커널소스의압축을풀면생기는디렉토리에대해알아보자. 한가지주의해야할점은리눅스커널이지금이순간에도계속변하고있으므로아래의내용이언제바뀌어틀리게될지모른다. 그러므로항상최신버전을소스코드를참조하기 7
바란다. 아래의내용은 2.4.16 ~ 2.4.18 의것을참조해설명한것이다. 모든코드는 /usr/src/linux 에서시작한다고가정한다. 이글에선이위치를 $(TOPDIR) 이라표현한다. Documents 커널에관계된문서들이들어있다. 커널을분석하거나할때필요한정보는여기를먼저보고나서다른곳을찾는것이빠른정보를얻는길이다. kernel 커널의핵심코드, 스스템콜, 스케쥴러, 시그널핸들링 ipc 전통적프로세스간의통신, 세마포어, 공유메모리, 메세지큐 lib 커널라이브러리함수 (printk 와같은것들 ) mm 버추얼메모리관리, 페이징, 커널메모리관리 scripts 코드사이의의존성을만들어주는등의스크립트나실행파일이모여있다. arch 아키텍쳐에관계된코드가들어있다. alpha arm m68k mips ppc sparc i386 boot 부트스프랩코드, 메모리 / 디바이스설정 kernel 커널시작점, 컨텍스트스위칭 8
lib math-emu mm 각아키텍쳐에관계된메모리코드... fs 버추얼파일시스템인터페이스, 여러파일시스템지원 coda ext2 hpfs msdos nfs ntfs ufs... init 커널이실행되기위한모든코드, 프로세스 0, 프로세스생성... include 커널에관계된헤더파일,asm-* 은아키텍쳐관련,linux 는리눅스커널관련 asm-alpha... asm-i386 linux net scsi video 9
... net 많은종류의네트웍프로토콜지원, 소켓지원 802 appletalk decnet ethernet ipv6 unix sunrpc x25... driver 하드웨어에대한드라이버 block 하드디스크같은블럭디바이스 cdrom char 시리얼포트, 모뎀,tty 같은문자디바이스 net 네트웍카드 pci PCI 버스컨드롤 pnp sbus scsi 10
SCSI 카드 sound 사운드카드 viceo... 1.2. LXR 리눅스소스코드는압축된양만해도약 20MB 정도되는방대한양이다. 게다가많은양의소스코드에산재해있는많은함수나정의를찾기란여간힘든것이아니다. 예를들어 root 파일시스템을마운트하는부분에대해분석하고싶다면과연어디서부터시작해야할것인가? 시작하는곳을찾는것까진했다고해도그럼역으로어디서마운트를실행하는가? 소스코드분석에가장필요한것이내가알고싶은함수 / 변수 / 정의가어디에있는가와어디에서불려지는가일것이다. 보통은 grep 을사용해찾아보고하나씩열어보는방법을동원하는데이게너무힘들다. 그래서리눅스커널관련프로젝트중에하나인 lxr 프로젝트를소개한다. lxr 은 linux cross reference 정도의의미로이해하면되겠고리눅스소스코드의모든함수 / 변수 / 정의등에대한크로스레퍼런스를온라인으로제공한다. URL 은 http://lxr.linux.no 다. 여기에서원하는버전을사용해찾아가면쉽게접근할수있을것이다. 예를들어커널의시작인 start_kernel() 을분석하는중에처음나오는함수인 lock_kernel() 이어디에있는지알고싶다면 lock_kernel() 을클릭해본다. 그럼 lock_kernel 이함수로정의된소스코드, 매크로로정의된소스코드그리고 lock_kernel 이불린위치가차례로열거된다. 정의를알고싶으면정의에해당하는것을클릭하면정의되어있는소스코드로가게되고불린위치를원하면불린위치가적힌곳을클리하면해당소스코드로이동하게된다. 1.3. 소스코드얻기리눅스커널소스코드는 http://www.kernel.org 에서구할수있다. 미러사이트가있으니가장빠른곳에서받으면된다. 커널소스는각버전별로구분된디렉토리내에있고패치와각버전의완전한소스코드가같이있다. 이중에원하는버전의완전한코드를받는다. 보통 gz 과 bz2 의압축으로되어있는데 bz2 가약간더작기때문에필자는이타입을선호한다. 1.4. 소스코드풀기먼저커널을컴파일하기위해 root 의권한을가져야한다. 시스템에 root 로로그인하거나 su 11
를사용해 root 가된후다음절차를시작한다. 보통의커널은 /usr/src 밑에위치하게된다. 필자는여기에각버전번호를가지고디렉토리를만들고각각에압축을풀어놓고사용중이다. 현재사용되고있는커널버전을 linux 란이름으로링크시켜놓고사용중이다. 즉 /usr/src/linux 는현재사용되고있는커널을가리키게된다. [root@localhost src] ls -l lrwxrwxrwx 1 root root 12 12월 24 15:23 linux -> linux-2.4.16 lrwxrwxrwx 1 root root 14 12월 24 15:22 linux-2.4 -> linux-2.4.7-10 drwxr-xr-x 14 root root 8192 2월 5 11:00 linux-2.4.16 drwxr-xr-x 16 root root 4096 12월 24 15:22 linux-2.4.7-10 drwxr-xr-x 7 root root 40 12월 24 15:21 redhat 이렇게한이유는 /usr/include 에 linux 와 asm 이란리눅스커널소스코드내의디렉토리를링크하게되어있는데사용하는커널버전이자주바뀌거나여러커널을같이사용중이라면변하지않는패스를사용하지않는한엔 linux 와 asm 의링크가자꾸변하게되불편을감수해야하기때문에이두링크는언제나 /usr/src/linux/include/linux 와 /usr/src/linux/include/asm 을가리키도록해놓고 /usr/src/linux 만을변경해주도록했다. [root@localhost include] ls -l linux asm lrwxrwxrwx 1 root root 26 1월 17 10:46 asm -> /usr/src/linux/include/asm lrwxrwxrwx 1 root root 28 1월 17 10:45 linux -> /usr/src/linux/include/linux 압축을풀기전에주의해야할것은압축된커널의소스코드는모두 linux 란이름의디렉토리로시작하므로만약 /usr/src 에 linux 란링크나디렉토리가있는상태에서여기에압축을풀어버리면다른버전의코드를엎어쓸가능성이있으므로조심해야한다. 우선 /usr/src/linux 의링크를없애고 tar 를사용해압축을푼후 /usr/src/linux 를해당버전으로바꿔준다. 절차는다음과같다. cd /usr/src rm -f linux tar xvjf somewhere/linux-2.4.16.tar.bz2 mv linux linux-2.4.16 ln -s linux-2.4.16 linux 1.5. 컴파일준비소스코드의압축을푼후 /usr/src/linux 로이동해혹시있을지모르는것들을지우고컴파일이제대로되게하기위해 make mrproper 라고명령을실행한다. mrproper 는소스코드를처음깔았을때와같은상태로돌려준다고생각하면된다. 만약커널설정등을해놓은상태에서이명령을실행하면설정이사라지기때문에필요한때만주의를기울여실행해야한다. 12
cd /usr/src/linux/documentation 하고 vi Changes 를한다. Changes 엔현재커널을컴파일하고사용하기위해필요한툴들의버전정보가있다. Current Minimal Requirements 항목을보고자신의시스템을한번쯤체크해보기바란다. 대부분의경우는만족할것이다. 2.4.16 버전은다음과같은사항을만족해야한다. 표 1-1. v2.4.16 커널컴파일을위한최소요구사항 툴버전확인방법 Gnu C 2.95.3 gcc --version Gnu make 3.77 make --version binutils 2.9.1.0.25 ld -v util-linux 2.10o fdformat --version modutils 2.4.2 insmod -V e2fsprogs 1.19 tune2fs reiserfsprogs 3.x.0j reiserfsck 2>&1 grep reiserfsprogs pcmcia-cs 3.1.21 cardmgr -V PPP 2.4.0 pppd --version isdn4k-utils 3.1pre1 isdnctrl 2>&1 grep version 1.6. 커널설정커널설정은몇가지방법이있다. 고전적인방법, 텍스트기반의메뉴를이용하는방법, X- Window 상에서 GUI 를이용하는방법이다. 원하는방법중하나를택해사용하면된다. 필자는손에익은대로 menuconfig 를주로사용한다. 각각은다음과같이실행된다. make config make menuconfig make xconfig 커널설정에관한자세한내용은여기서다루지않는다. 커널설정에관한자세한것은 http://www.kldp.org 를참조하기바란다. 커널의설정이끝나면 /usr/src/linux/.config 가만들어진다. 이파일의내용을보면다음과같다. CONFIG_X86=y... CONFIG_MK7=y... CONFIG_MODULES=y... CONFIG_NET=y... CONFIG_ACPI_DEBUG is not set... CONFIG_PARPORT=m... 13
모두 CONFIG_ 로시작하고뒤에각항목의이름이붙는다. 예를들어위에서 CONFIG_MK7 은 AMD 의 Athlon CPU 를의미한다. 그리고 y 혹은 m 아니면 으로막혀져있는것이있는데 y 는커널에직접포함되도록설정한항목을의미하고 m 은 module 로설정한것, 으로막힌것은사용되지않는것을의미한다. 1.7. 커널컴파일커널을컴파일해보자. 컴파일순서는다음과같다. make dep make modules make bzimage make modules_install make dep 로소스파일과헤더와의의존성을검사해 /usr/src/linux/.depend 를만든다. make modules 는설정에서 module 로선택한것들을 *.o 의형태로만들어준다. make bzimage 는커널자체를만들어준다. make zimage 를하는경우커널의크기가너무커서에러가날수도있다. 이경우엔더많은부분을모듈로만들거나 bzimage 를사용해야한다. make modules_install 은만들어진 module 을 /lib/modules/2.4.16 에설치해준다. 설치와함께 depmod 를실행해 module 간의의존성도만들준다. 1.8. 커널테스트및설치커널컴파일이끝난후만들어진커널이제대로동작하는지확인해본후기본커널로설치해사용해야한다. 그렇지않고무턱대고원래의잘돌아가던커널을대체해버리면혹시라도에러있는커널인경우엔부팅이안되는위급한사태가발생하게된다. 그러므로만들어진커널을먼저테스트후사용하기바란다. 테스트방법엔 2 가지정도를추천한다. 하나는플로피를사용하는것이고나머지하나는 LILO 를사용하는것이다. 플로피를사용하는방법플로피를사용하기위해 make zdisk 를사용해만들어진커널을플로피에담는다. 복사가끝나면플로피를사용해부팅해정상적으로동작하는지확인한다. 가장안전한방법중하나로생각되고문제가있으면플로피를제거하고다시부팅해커널설정등을다시하고테스트를하면된다. LILO 를사용하는방법 LILO 를사용해하는방법은아래와같이 /etc/lilo.conf 를우선수정한다. 먼저염두에둬야할것은만들어진커널이미지를어디에무슨이름으로복사할것인지결정해두고일을진행 14
해야한다는것이다. 필자의경우보통은 $(TOPDIR)/arch/i386/boot/bzImage[1] 를 /boot/test_img 로복사하고테스트한다. 아래는필자의 lilo.conf 다. 기본부팅용항목을그대로복사하고 image 와 label 만을바꿔하나더등록해부팅한다. 부팅후엔 LILO 에 test 라입력해새로만든테스트커널을실험해보면된다. prompt timeout=50 default=linux boot=/dev/hda map=/boot/map install=/boot/boot.b message=/boot/message lba32 vga=0x030a image=/boot/bzimage-2.4.16 label=linux initrd=/boot/initrd-2.4.16.img read-only root=/dev/hda1 append="mem=nopentium hdd=ide-scsi" image=/boot/test_img label=test initrd=/boot/initrd-2.4.16.img read-only root=/dev/hda1 append="mem=nopentium hdd=ide-scsi" 커널의설치는간단하다. 커널이미지와맵파일을복사하고 lilo.conf 를수정해주면된다. 복사할파일은 $(TOPDIR)/arch/i386/boot/bzImage 와 $(TOPDIR)/System.map 두개다. 필자는 bzimage 를 /boot/bzimage-2.4.16 과같이버전이름을사용해복사하고 System.map 을 /boot/sysstem.map-2.4.16 과같이복사한다. 그리고 /boot/system.map-2.4.16 을 /boot/system.map 으로심볼릭링크를만들어준다. 커널은 /boot/system.map 을찾아사용하기때문이다. 주석 [1] $(TOPDIR) 은커널소스디렉토리를가리킨다. 즉 /usr/src/linux. 2. Makefile 분석 필자는리눅스를공부하면서처음엔소스코드를찾아스케쥴링부터보고다음엔시스템콜을보고하는식으로접근했다. 그러나실제임베디드시스템에적용하는데는그런단계가먼저필요한것이아니라커널이미지가어떻게구성됐으며부팅과정에서어떤식으로실 15
행되고어떤절차를거쳐리눅스란 os 를구성하는가가먼저필요하단것을느꼈다. 이런생각은자연스레커널을만들때어떤식으로커널이만들어지는지를알고있어야다른임베디드시스템에커널을만들어넣을때도쉽게접근할수있다는생각을갖게만들었다. 이장에선커널이미지파일의구조와 Makefile 을통해커널이만들어지는과정을추적해본다. 2.1. 부팅과정의이해 [1] i386 계열의 pc 를중심으로부팅과정을알아본다. 임베디드시스템에선 PC 와는다른부팅과정이필요할것이지만 pc 에서의부팅과정을이해한후엔훨씬접근이쉬울것이다. 또대체적인큰항목들은 pc 든임베디드시스템이든같기때문에좋은예가될것이다. pc 의전원을처음넣으면 CPU 는 ROM 에서 BIOS 코드를읽어실행하기시작한다. 처음코드는 PC 의기본적인초기화를할것이고이어각종 HW 의초기화를실행할것이다. 기본초기화가끝나면 VGA 를통해화면이보이기시작할것이고 RAM 체크, HDD 인식, PnP 세팅등을실행한다. 여기까지가넓은의미에서의 HW 초기화라고봐야할것이다. 모든초기화와세팅이끝난후는기본적인 HW 인플로피나하드디스크가사용가능한상태가된다. 이제 bios 는부팅가능한순서가지정된디바이스를찾아부팅을시도한다. 부팅가능하면그디바이스의첫 512 bytes 를읽어실행한다. 하드디스크의첫섹터를 MBR[2] 이라부르고이섹터만이부팅에사용된다. 하드디스크가여러개있더라도정해진첫드라이브의 MBR 만이사용된다. 이섹터는로더프로그램과파티션테이블정보를담고있다. 로더는일반적으로부트섹터를읽어부트한다. 실제로 MBR 과부트섹터는 MBR 이파티션정보를담고있다는것을제외하고는기능적인차이가없다. mbr 의첫 446 바이트 (0x1BE) 는로더프로그램이고그뒤 64 바이트는파티션테일블정보를담고있다. 마지만두바이트는매직넘버를갖고있고이숫자는이섹터가진짜부트섹터인지판별할때사용된다. 그림 2-1. mbr 의구조 16
위그림에서보듯이처음읽혀지는코드의크기는 512 bytes 이므로충분하다고는볼수없을것이다. 그러므로이작은용량엔실제코드를읽어실행하도록하는기능만을넣는것이보통이다. 나머지는해당 OS 파티션의부트섹터에기록된다. 여기까지가일반적인 PC 의부팅과정이다. 이제부터는어떤 OS 가깔렸는지에따라부팅이달라지게된다. lilo 와같은부트로더가실행되원하는 OS 를부트한다. 이책에서는리눅스에대해다루므로 BIOS 에의해처음읽혀실행되는것이 LILO( 혹은 GRUB) 가될것이다. lilo 를설치할때 LILO 는 MBR 에자신을위한로더를기록해부팅할때 LILO 가실행되도록할것이다. lilo 의첫스테이지부트섹터는여러다른곳에서 LILO 의나머지부분을읽어들인다. 다음은필자의 lilo.conf 로 lilo 를실행하면 install 에지정된 /boot/boot.b 가 MBR 에써진다. boot.b 는 lilo 의첫스테이지와두번째스테이지가묶여진 file 로첫스테이지는부트섹터에기록되도록 512 바이트이고두번째스테이지는그나머지부분이다. 실제로필자의컴퓨터 boot.b 는 4566 바이트로앞 512 바이트가첫번째스테이지, 나머지부분인 4054 바이트가두번째스테이지가된다. prompt timeout=50 default=linux boot=/dev/hda map=/boot/map install=/boot/boot.b lba32 17
vga=0x030a image=/boot/bzimage-2.4.16 label=linux initrd=/boot/initrd-2.4.16.img read-only root=/dev/hda1 append="mem=nopentium hdd=ide-scsi" 그러나 /boot/boot.b 를 hexedit 와같은것으로보면앞부분에있어야할중요한정보들이비어있는것을알수있다. 이것은 lilo 를실행해 MBR 에 boot.b 를등록할때정보가채워지게된다. 일단 bios 가 mbr 을읽어 LILO 를기동하면 LILO 는 lilo.conf 를사용해등록된메뉴를갖고사용자의입력에따라해당 os 를시작하게된다. 필자의 lilo.conf 에의하면선택가능한 OS 는 "linux" 로커널이미지는 /boot/bzimage-2.4.16 임을알수있다. 즉 LILO 는사용자가 "linux" 를선택했을때 /boot/bzimage-2.4.16 를읽어들이고실행해주는역할을한다. 주석 [1] 부팅에관한좀더자세한정보는 LILO 의 README 를참조하기바란다. [2] Master Boot Record 2.2. 커널이미지파일의구조이미커널을컴파일해본사람은최종커널이미지파일이압축되어있단것을알수있을것이다. [1] 이제 LILO 가메모리에커널을읽어올리고실행해주기까지의과정을살펴볼것이다. 그에앞서커널이미지파일의구조에대해먼저알아본다. 구조를알아야 LILO 가커널을어떤식으로부팅하게해주는지이해가빠를것이다. zimage 나 bzimage 나구조는같다. 단지메모리에올려지는위치나동작방식이약간차이가있기때문에이것이감안된각각에맞는코드가사용될뿐이다. 그림 2-2. bzimage 의구조 18
그림 2-2 에서회색부분이진짜리눅스커널이고압축되어있는상태다. 여기에압축을풀어주기위한 'head + misc' 가앞부분에붙어있고다시이한덩어리의이미지에메모리에올려진커널의압축이제대로풀리도록미리준비하는 "setup" 과부팅할때사용되는 "bootsect" 가붙어최종커널이미지파일을만든다. bbootsect 나 bsetup 의앞에붙은 b 는 bzimage 의앞에붙은 b 와같은의미로 "big kernel" 을의미한다. bbootsect 는플로피로부팅될때즉부트로더가없이커널이직접읽혀져부팅될때필요한부트섹터다. lilo 에의해부팅되는경우는필요없는부분이다. 2.2.1. 커널의부팅 lilo 에의해부팅이시작되면 LILO 는 bzimage 를하드디스크에서읽어메모리에올려놓고 LILO 에의한부팅일경우필요없는 bbootsect 를건너뛴 bsetup 에서부터실행되도록해준다. LILO 의역할은 bsetup 에실행권을넘겨주는데까지다. 실행된 bsetup 은메모리세팅을마치고압축된커널이미지의압축을풀기위한코드로실 19
행을옮긴다. "head + misc" 로표시된부분이고이곳이실행되면 piggy.o 는압축이풀려실행가능한리눅스커널이메모리에존재하게된다. 압축이풀릴때화면에 "Uncompressing Linux... " 란메시지가출력된다. 그러나압축이풀렸다고해서바로커널을실행하는것은아니고메모리낭비를막기위한정리를한번다시하고나서커널이실행된다. 압축이풀리고나면처음 LILO 에의해읽혀진커널이미지는필요없게된다. 그러므로이부분을내버려두면그만큼메모리낭비이므로압축풀린커널이미지를옮겨와되도록많은메모리를사용하도록한후커널을실행해준다. 커널이실행되기시작하면 "ok, booting the kernel." 이출려되고커널에의한출력이화면에나타나게된다. 2.2.2. zimage 와 bzimage 의차이 zimage 와 bzimage 는무슨차이가있는걸까? 필자는처음에 z 와 b 의의미때문에 gzip 으로압축하거나아니면 bzip2 로압축한것의차이인줄알았지만압축은 gzip 으로같고단지 z 는압축했단의미고 b 는 'big kernel' 이란뜻인걸알았다. 왜이렇게나눠졌는가? 이미 1.7 절에서언급했던것처럼커널의크기가너무커서압축후에도일정크기를넘어가면 zimage 대신 bzimage 를사용해야한다고했는데이유는다음과같다. pc 가처음만들어질땐 OS 로도스가사용됐고이때 M$ 의유명한분이 640KB 면충분하다고했단소릴들은적이있을것이다. 처음 PC 가만들어질때의 CPU 는 8086 으로 16bit CPU 였다. 이프로세서가지원하는최대의메모리는 1MB 였기때문에모든어드레스스페이스가 1MB 내로제한됐다. 그러므로램을 640kb 사용하고나머지영역엔 MGA, VGA 와같은다른디바이스를할당해줬다. 문제는여기서시작되는데 AT 시절의 PC 기본구조는현재까지도계속유지되고있기때문에 PC 가처음부팅되면하위 1MB 만을사용한다고생각하면된다. 보호모드라고알고있는 386 이상의 cpu 가가진기능을사용하지않고리얼모드란 8086 호환모드를사용하기때문인데이는 OS 가보호모드를사용할상태를만들고전환하기전까지는계속리얼모드로남아있기때문이다. 리눅스커널의크기가커서커널을읽어들이는프로그램크기나시스템에서사용되는약간의메모리를제외한나머지램의빈공간에읽어들이지못하면하위 1MB 가아니라그이상의연속된메모리에커널을읽어들이고압축을푸는등의일을해야할것이다. 반대로남은용량에커널이들어갈수있다면당연히읽어들이고압축을풀면끝날것이고... 이렇게메모리에처음적재되고압축풀리고하는절차와위치가다르기때문에 zimage 와 bzimage 오나뉜것이고커널이미지파일의앞부분 bootsect 와 setup 이각각에따라맞는것으로합쳐지게된다. 그리고 bzimage 의경우하위 1M 는사용하지못하는데리눅스에선그렇다! 컴파일단계에서 make zimage 했을경우 System is too big. Try using bzimage or modules. 라고에러가난다면더많은부분을 module 로만들거나 bzimage 를사용해야한다. 주석 [1] zimage, bzimage 등에서 z 가의미하는것이 gzip 으로압축됐단것이다. 2.3. bzimage 가만들어지는과정추적 -Makefile 분석 bzimage 가만들어지는과정을살펴보고이를따라가면서 Makefile 의자세한내용을알아본 20
다. 정확한것은 2.4.2 절을참조하기바란다. 시작은물론 $(topdir)/makefile 로부터시작한다. 커널 makefile 은몇부분으로나눌수있다. 기본정보정의 커널설정 커널소스의존성만들기 모듈만들기 커널실행파일만들기 모듈설치하기 각부분이명확하게구분되는것은아니지만 make 의동작을이해하는사람이라면대충구분을지어이해할수있을것이다. 구분은커널을컴파일하는절차에따라나눈것으로이해하면쉬울것이다. 일반적으로많이쓰이지않는부분은넘어가고중요한부분만을자세히이해하자. 시작에앞서사용되는 Makefile 들을설명해놓는다. 이것들을참조로추적해나가므로시작하기에앞서한번쯤훑어보는것도좋을것이다. 2.3.1. $(topdir)/makefile 아래 makefile 에 (1), (2) 와같이표시된것은아래줄에대한설명을달아놓은것으로 Makefile 의끝부분에붙어있는설명을참조해가면서분석하면되겠다. version = 2 patchlevel = 4 sublevel = 16 extraversion = kernelrelease=$(version).$(patchlevel).$(sublevel)$(extraversion) (1) arch := $(shell uname -m sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/) (2) kernelpath=kernel-$(shell echo $(KERNELRELEASE) sed -e "s/-//") (3) config-shell := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi) (4) topdir := $(shell /bin/pwd) hpath = $(topdir)/include findhpath = $(hpath)/asm $(HPATH)/linux $(HPATH)/scsi $(HPATH)/net (5) hostcc = gcc hostcflags = -wall -Wstrict-prototypes -O2 -fomit-frame-pointer (6) 21
cross-compile = (7) include the make variables (CC, etc...) as = $(cross-compile)as ld = $(cross-compile)ld cc = $(cross-compile)gcc cpp = $(cc) -e ar = $(cross-compile)ar nm = $(cross-compile)nm strip = $(cross-compile)strip objcopy = $(cross-compile)objcopy objdump = $(cross-compile)objdump makefiles = $(topdir)/.config genksyms = /sbin/genksyms depmod = /sbin/depmod modflags = -dmodule cflags-kernel = perl = perl (8) export version patchlevel SUBLEVEL EXTRAVERSION KERNELRELEASE ARCH \ config-shell topdir HPATH HOSTCC HOSTCFLAGS CROSS-COMPILE AS LD CC \ cpp ar nm strip OBJCOPY OBJDUMP MAKE MAKEFILES GENKSYMS MODFLAGS PERL all: do-it-all (9) make "config" the default target if there is no configuration file or "depend" the target if there is no top-level dependency information. ifeq (.config,$(wildcard.config)) include.config ifeq (.depend,$(wildcard.depend)) include.depend do-it-all: version vmlinux else (10) configuration = depend do-it-all: depend endif else (11) configuration = config do-it-all: config endif (12) install-path specifies where to place the updated kernel and system map images. uncomment if you want to place them anywhere other than root. export install-path=/boot (13) install-mod-path specifies a prefix to MODLIB for module directory relocations required by build roots. This is not defined in the makefile but the arguement can be passed to make if needed. modlib := $(install-mod-path)/lib/modules/$(kernelrelease) export modlib standard cflags cppflags := -d--kernel-- -I$(HPATH) cflags := $(cppflags) -Wall -Wstrict-prototypes -Wno-trigraphs -O2 \ 22
-fomit-frame-pointer -fno-strict-aliasing -fno-common aflags := -d--assembly-- $(CPPFLAGS) (14) root-dev specifies the default root-device when making the image. this can be either FLOPPY, CURRENT, /dev/xxxx or empty, in which case the default of floppy is used by 'build'. this is i386 specific. export root-dev = CURRENT (15) if you want to preset the SVGA mode, uncomment the next line and set svga-mode to whatever number you want. set it to -dsvga-mode=normal-vga if you just want the EGA/VGA mode. the number is the same as you would ordinarily press at bootup. this is i386 specific. export svga-mode = -DSVGA-MODE=NORMAL-VGA (16) if you want the RAM disk device, define this to be the size in blocks. this is i386 specific. export ramdisk = -DRAMDISK=512 (17) core-files =kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o networks =net/network.o libs subdirs =$(topdir)/lib/lib.a =kernel drivers mm fs net ipc lib (18) drivers-n := drivers-y := drivers-m := drivers- := drivers-$(config-acpi) += drivers/acpi/acpi.o drivers-$(config-parport) += drivers/parport/driver.o drivers-y += drivers/char/char.o \ drivers/block/block.o \ drivers/misc/misc.o \ drivers/net/net.o \ drivers/media/media.o drivers-$(config-agp) += drivers/char/agp/agp.o drivers-$(config-drm) += drivers/char/drm/drm.o drivers-$(config-nubus) += drivers/nubus/nubus.a drivers-$(config-isdn) += drivers/isdn/isdn.a drivers-$(config-net-fc) += drivers/net/fc/fc.o drivers-$(config-appletalk) += drivers/net/appletalk/appletalk.o drivers-$(config-tr) += drivers/net/tokenring/tr.o drivers-$(config-wan) += drivers/net/wan/wan.o drivers-$(config-arcnet) += drivers/net/arcnet/arcnetdrv.o drivers-$(config-atm) += drivers/atm/atm.o drivers-$(config-ide) += drivers/ide/idedriver.o drivers-$(config-fc4) += drivers/fc4/fc4.a drivers-$(config-scsi) += drivers/scsi/scsidrv.o drivers-$(config-fusion-boot) += drivers/message/fusion/fusion.o drivers-$(config-ieee1394) += drivers/ieee1394/ieee1394drv.o ifneq ($(config-cd-no-idescsi)$(config-blk-dev-idecd)$(config-blk-dev-sr)$(config- PARIDE-PCD),) drivers-y += drivers/cdrom/driver.o endif drivers-$(config-sound) += drivers/sound/sounddrivers.o drivers-$(config-pci) += drivers/pci/driver.o 23
drivers-$(config-mtd) += drivers/mtd/mtdlink.o drivers-$(config-pcmcia) += drivers/pcmcia/pcmcia.o drivers-$(config-net-pcmcia) += drivers/net/pcmcia/pcmcia-net.o drivers-$(config-net-wireless) += drivers/net/wireless/wireless-net.o drivers-$(config-pcmcia-chrdev) += drivers/char/pcmcia/pcmcia-char.o drivers-$(config-dio) += drivers/dio/dio.a drivers-$(config-sbus) += drivers/sbus/sbus-all.o drivers-$(config-zorro) += drivers/zorro/driver.o drivers-$(config-fc4) += drivers/fc4/fc4.a drivers-$(config-all-ppc) += drivers/macintosh/macintosh.o drivers-$(config-mac) += drivers/macintosh/macintosh.o drivers-$(config-isapnp) += drivers/pnp/pnp.o drivers-$(config-sgi-ip22) += drivers/sgi/sgi.a drivers-$(config-vt) += drivers/video/video.o drivers-$(config-paride) += drivers/block/paride/paride.a drivers-$(config-hamradio) += drivers/net/hamradio/hamradio.o drivers-$(config-tc) += drivers/tc/tc.a drivers-$(config-usb) += drivers/usb/usbdrv.o drivers-$(config-input) += drivers/input/inputdrv.o drivers-$(config-i2o) += drivers/message/i2o/i2o.o drivers-$(config-irda) += drivers/net/irda/irda.o drivers-$(config-i2c) += drivers/i2c/i2c.o drivers-$(config-phone) += drivers/telephony/telephony.o drivers-$(config-md) += drivers/md/mddev.o drivers-$(config-bluez) += drivers/bluetooth/bluetooth.o drivers-$(config-hotplug-pci) += drivers/hotplug/vmlinux-obj.o (19) drivers := $(drivers-y) (20) files removed with 'make clean' clean-files = \ kernel/ksyms.lst include/linux/compile.h \ vmlinux system.map \.tmp* \ drivers/char/consolemap-deftbl.c drivers/video/promcon-tbl.c \ drivers/char/conmakehash \ drivers/char/drm/*-mod.c \ drivers/pci/devlist.h drivers/pci/classlist.h drivers/pci/gen-devlist \ drivers/zorro/devlist.h drivers/zorro/gen-devlist \ drivers/sound/bin2hex drivers/sound/hex2hex \ drivers/atm/fore200e-mkfirm drivers/atm/{pca,sba}*{.bin,.bin1,.bin2} \ drivers/scsi/aic7xxx/aicasm/aicasm-gram.c \ drivers/scsi/aic7xxx/aicasm/aicasm-scan.c \ drivers/scsi/aic7xxx/aicasm/y.tab.h \ drivers/scsi/aic7xxx/aicasm/aicasm \ drivers/scsi/53c700-mem.c \ net/khttpd/make-times-h \ net/khttpd/times.h \ submenu* directories removed with 'make clean' clean-dirs = \ modules (21) files removed with 'make mrproper' mrproper-files = \ include/linux/autoconf.h include/linux/version.h \ drivers/net/hamradio/soundmodem/sm-tbl-{afsk1200,afsk2666,fsk9600}.h \ drivers/net/hamradio/soundmodem/sm-tbl-{hapn4800,psk4800}.h \ drivers/net/hamradio/soundmodem/sm-tbl-{afsk2400-7,afsk2400-8}.h \ drivers/net/hamradio/soundmodem/gentbl \ drivers/sound/*-boot.h drivers/sound/.*.boot \ drivers/sound/msndinit.c \ drivers/sound/msndperm.c \ drivers/sound/pndsperm.c \ drivers/sound/pndspini.c \ drivers/atm/fore200e-*-fw.c drivers/atm/.fore200e-*.fw \.version.config* config.in config.old \ scripts/tkparse scripts/kconfig.tk scripts/kconfig.tmp \ scripts/lxdialog/*.o scripts/lxdialog/lxdialog \ 24
.menuconfig.log \ include/asm \.hdepend scripts/mkdep scripts/split-include scripts/docproc \ $(topdir)/include/linux/modversions.h \ kernel.spec directories removed with 'make mrproper' mrproper-dirs = \ include/config \ $(topdir)/include/linux/modules (22) include arch/$(arch)/makefile (23) export export cppflags cflags AFLAGS networks drivers LIBS HEAD LDFLAGS LINKFLAGS MAKEBOOT ASFLAGS (24).s.s: $(cpp) $(aflags) -traditional -o $*.s $<.s.o: $(cc) $(aflags) -traditional -c -o $*.o $< version: dummy @rm -f include/linux/compile.h boot: vmlinux @$(make) cflags="$(cflags) $(CFLAGS-KERNEL)" -C arch/$(arch)/boot (25) vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o linuxsubdirs $(ld) $(linkflags) $(HEAD) init/main.o init/version.o \ --start-group \ $(core-files) \ $(drivers) \ $(networks) \ $(libs) \ --end-group \ -o vmlinux $(nm) vmlinux grep -v '\(compiled\)\ \(\.o$$\)\ \( [auw] \)\ \(\.\.ng$$\)\ \(LASH[RL]DI\)' sort > System.map (26) symlinks: rm -f include/asm ( cd include ; ln -sf asm-$(arch) asm) @if [! -d include/linux/modules ]; then \ mkdir include/linux/modules; \ fi (27) oldconfig: symlinks $(config-shell) scripts/configure -d arch/$(arch)/config.in xconfig: symlinks $(make) -c scripts kconfig.tk wish -f scripts/kconfig.tk menuconfig: include/linux/version.h symlinks $(make) -c scripts/lxdialog all $(config-shell) scripts/menuconfig arch/$(arch)/config.in config: symlinks $(config-shell) scripts/configure arch/$(arch)/config.in include/config/marker: scripts/split-include include/linux/autoconf.h scripts/split-include include/linux/autoconf.h include/config @ touch include/config/marker (28) linuxsubdirs: $(patsubst %, -dir-%, $(SUBDIRS)) 25
(29) $(patsubst %, -dir-%, $(SUBDIRS)) : dummy include/linux/version.h include/config/marker $(make) cflags="$(cflags) $(CFLAGS-KERNEL)" -C $(patsubst -dir-%, %, $@) $(topdir)/include/linux/version.h: include/linux/version.h $(topdir)/include/linux/compile.h: include/linux/compile.h newversion:. scripts/mkversion >.tmpversion @mv -f.tmpversion.version (30) include/linux/compile.h: $(CONFIGURATION) include/linux/version.h newversion @echo -n \define UTS-VERSION \"\`cat.version` >.ver @if [ -n "$(config-smp)" ] ; then echo -n " SMP" >>.ver; fi @if [ -f.name ]; then echo -n \-`cat.name` >>.ver; fi @echo ' '`date`'"' >>.ver @echo \define linux-compile-time \"`date +%T`\" >>.ver @echo \define linux-compile-by \"`whoami`\" >>.ver @echo \define linux-compile-host \"`hostname`\" >>.ver @if [ -x /bin/dnsdomainname ]; then \ echo \define LINUX-COMPILE-DOMAIN \"`dnsdomainname`\"; \ elif [ -x /bin/domainname ]; then \ echo \define LINUX-COMPILE-DOMAIN \"`domainname`\"; \ else \ echo \define LINUX-COMPILE-DOMAIN ; \ fi >>.ver @echo \define linux-compiler \"`$(CC) $(CFLAGS) -v 2>&1 tail -1`\" >>.ver @mv -f.ver $@ (31) include/linux/version.h:./makefile @echo \define uts-release \"$(KERNELRELEASE)\" >.ver @echo \define linux-version-code `expr $(VERSION) \\* 65536 + $(PATCHLEVEL) \\* 256 + $(SUBLEVEL)` >>.ver @echo 'define kernel-version(a,b,c) (((a) << 16) + ((b) << 8) + (c))' >>.ver @mv -f.ver $@ init/version.o: init/version.c include/linux/compile.h include/config/marker $(cc) $(cflags) $(CFLAGS-KERNEL) -DUTS-MACHINE='"$(ARCH)"' -c -o init/version.o init/version.c (32) init/main.o: init/main.c include/config/marker $(cc) $(cflags) $(CFLAGS-KERNEL) $(PROFILING) -c -o $*.o $< (33) fs lib mm ipc kernel drivers net: dummy $(make) cflags="$(cflags) $(CFLAGS-KERNEL)" $(subst $@, -dir-$@, $@) (34) emacs, vi 용 tag 를만든다. tags: dummy etags `find include/asm-$(arch) -name '*.h'` find include -type d \( -name "asm-*" -o -name config \) -prune -o -name '*.h' - print xargs etags -a find $(subdirs) init -name '*.[ch]' xargs etags -a exuberant ctags works better with -I tags: dummy ctagsf=`ctags --version grep -i exuberant >/dev/null && echo "-I --initdata,-- exitdata,export-symbol,export-symbol-novers"`; \ ctags $$ctagsf `find include/asm-$(arch) -name '*.h'` && \ find include -type d \( -name "asm-*" -o -name config \) -prune -o -name '*.h' -print xargs ctags $$CTAGSF -a && \ find $(subdirs) init -name '*.[ch]' xargs ctags $$CTAGSF -a ifdef config-modules ifdef config-modversions modflags += -dmodversions -include $(HPATH)/linux/modversions.h endif 26
(35).phony: modules modules: $(patsubst %, -mod-%, $(SUBDIRS)).phony: $(patsubst %, -mod-%, $(SUBDIRS)) $(patsubst %, -mod-%, $(SUBDIRS)) : include/linux/version.h include/config/marker $(make) -c $(patsubst -mod-%, %, $@) CFLAGS="$(CFLAGS) $(MODFLAGS)" MAKING- MODULES=1 modules.phony: modules-install modules-install: -modinst- $(patsubst %, -modinst-%, $(SUBDIRS)) -modinst-post.phony: -modinst- -modinst-: @rm -rf $(modlib)/kernel @rm -f $(modlib)/build @mkdir -p $(modlib)/kernel @ln -s $(topdir) $(MODLIB)/build (36) if system.map exists, run depmod. This deliberately does not have a dependency on system.map since that would run the dependency tree on vmlinux. this depmod is only for convenience to give the initial boot a modules.dep even before / is mounted read-write. However the boot script depmod is the master version. ifeq "$(strip $(install-mod-path))" "" depmod-opts := else depmod-opts := -b $(INSTALL-MOD-PATH) -r endif.phony: -modinst-post -modinst-post: -modinst-post-pcmcia if [ -r system.map ]; then $(DEPMOD) -ae -F System.map $(depmod-opts) $(KERNELRELEASE); fi backwards compatibilty symlinks for people still using old versions of pcmcia-cs with hard coded pathnames on insmod. Remove -modinst-post-pcmcia for kernel 2.4.1..phony: -modinst-post-pcmcia -modinst-post-pcmcia: cd $(modlib); \ mkdir -p pcmcia; \ find kernel -path '*/pcmcia/*' -name '*.o' xargs -i -r ln -sf../{} pcmcia.phony: $(patsubst %, -modinst-%, $(SUBDIRS)) $(patsubst %, -modinst-%, $(SUBDIRS)) : $(make) -c $(patsubst -modinst-%, %, $@) modules-install modules disabled... else modules modules-install: dummy @echo @echo "the present kernel configuration has modules disabled." @echo "type 'make config' and enable loadable module support." @echo "then build a kernel with module support enabled." @echo @exit 1 endif clean: archclean find. \( -name '*.[oas]' -o -name core -o -name '.*.flags' \) -type f -print \ grep -v lxdialog/ xargs rm -f rm -f $(clean-files) rm -rf $(clean-dirs) $(make) -c documentation/docbook clean mrproper: clean archmrproper find. \( -size 0 -o -name.depend \) -type f -print xargs rm -f rm -f $(mrproper-files) rm -rf $(mrproper-dirs) $(make) -c documentation/docbook mrproper 27
(37) distclean: mrproper rm -f core `find. \( -not -type d \) -and \ \( -name '*.orig' -o -name '*.rej' -o -name '*~' \ -o -name '*.bak' -o -name '*' -o -name '.*.orig' \ -o -name '.*.rej' -o -name '.SUMS' -o -size 0 \) -type f -print` TAGS tags backup: mrproper cd.. && tar cf - linux/ gzip -9 > backup.gz sync sgmldocs: chmod 755 $(topdir)/scripts/docgen chmod 755 $(topdir)/scripts/gen-all-syms chmod 755 $(topdir)/scripts/kernel-doc $(make) -c $(topdir)/documentation/docbook books psdocs: sgmldocs $(make) -c documentation/docbook ps pdfdocs: sgmldocs $(make) -c documentation/docbook pdf htmldocs: sgmldocs $(make) -c documentation/docbook html sums: find. -type f -print sort xargs sum >.SUMS dep-files: scripts/mkdep archdep include/linux/version.h scripts/mkdep -- init/*.c >.depend scripts/mkdep -- `find $(FINDHPATH) -name SCCS -prune -o -follow -name \*.h! - name modversions.h -print` >.hdepend $(make) $(patsubst %,-sfdep-%,$(subdirs)) -FASTDEP-ALL-SUB-DIRS="$(SUBDIRS)" ifdef config-modversions $(make) update-modverfile endif ifdef config-modversions modverfile := $(topdir)/include/linux/modversions.h else modverfile := endif export modverfile depend dep: dep-files checkconfig: find * -name '*.[hcs]' -type f -print sort xargs $(PERL) -w scripts/checkconfig.pl checkhelp: find * -name [cc]onfig.in -print sort xargs $(PERL) -w scripts/checkhelp.pl checkincludes: find * -name '*.[hcs]' -type f -print sort xargs $(PERL) -w scripts/checkincludes.pl ifdef configuration..$(configuration): @echo @echo "you have a bad or nonexistent".$(configuration) ": running 'make" $(CONFIGURATION)"'" @echo $(make) $(configuration) @echo @echo "successful. Try re-making (ignore the error that follows)" @echo exit 1 28
dummy:..$(configuration) dummy: else dummy: endif (38) include rules.make (39) this generates dependencies for the.h files. scripts/mkdep: scripts/mkdep.c $(hostcc) $(hostcflags) -o scripts/mkdep scripts/mkdep.c scripts/split-include: scripts/split-include.c $(hostcc) $(hostcflags) -o scripts/split-include scripts/split-include.c (40) rpm target if you do a make spec before packing the tarball you can rpm -ta it spec:. scripts/mkspec > kernel.spec build a tar ball, generate an rpm from it and pack the result there arw two bits of magic here 1) the use of /. to avoid tar packing just the symlink 2) removing the.dep files as they have source paths in them that will become invalid rpm: rm -f clean spec find. \( -size 0 -o -name.depend -o -name.hdepend \) -type f -print xargs set -e; \ cd $(topdir)/.. ; \ ln -sf $(topdir) $(KERNELPATH) ; \ tar -cvz --exclude CVS -f $(KERNELPATH).tar.gz $(KERNELPATH)/. ; \ rm $(kernelpath) ; \ cd $(topdir) ; \. scripts/mkversion >.version ; \ rpm -ta $(topdir)/../$(kernelpath).tar.gz ; \ rm $(topdir)/../$(kernelpath).tar.gz (1) arch 는아래와같이 uname 으로얻어지는아키텍쳐를지칭하는값을갖는다. intel 계열에선 i386 이되고 ARM 계열에선 arm 이된다. (2) kernelpath 는 kernel-2.4.16 이된다. (3) 현재사용중인 shell 을알아낸다. (4) topdir 은커널소스코드가들어있는최상위디렉토리 (5) host 가붙은것은커널이 cross compile 되서다른아키텍쳐용바이너리를만들수도있기때문에실제커널을구성하는코드외에커널을만들기위해필요한몇몇프로그램을호스 29
트상에서돌리기위한컴파일러를지정하는것이다. (6) 보통의경우 HOST 와 TARGET 이같으면 CROSS-COMPILE 에아무것도없으나 target 이다르면여기에컴파일러의 prefix 를적어줘야한다. 예를들어 PDA 에많이사용되는 arm processor 를 TARGET 으로한경우엔 cross-compile = arm-linux- 와같이된다. (7) 위에서정의한 CROSS-COMPILE 이컴파일러등의 prefix 로쓰이는데 arm processor 의경우엔 CC = arm-linux-gcc 와같이된다. (8) 여기까지정의된변수들은커널컴파일전반에사용될것들이므로아래와같이해서각디렉토리등에들어있는 Makefile 로값을전달해준다. (9) 리눅스커널은컴파일전에반드시설정 / 의존성설정이되어있어야하므로어느경우든두절차를검사한다. 먼저.config 가있다는것은커널설정이된상태를의미하고.depend 는의존성설정이끝난것을의미한다. (10).config 는있지만.depend 는없는경우 (11).config 가없는경우엔커널설정을먼저하도록한다. (12) 커널컴파일이끝나고설치될디렉토리를지정한다. 보통은사용되지않는다. (13) module 이설치될디렉토리를지정한다. 보통은 /lib/modules/2.4.16 과같이된다. (14) i386 아키텍쳐에서루트디바이스를지정한다. current 는커널컴파일할당시의 root device 를의미한다. (15) i386 아키텍쳐에서초기부팅시의화면모드를설정한다. (16) i386 에서램디스크가필요한경우사용한다. (17) core-files 는리눅스커널을이루는근간이되는몇몇부분을나타낸다. 리눅스커널은아래와같이 kernel, drivers, mm, fs, ipc, network, lib 로구분된다고볼수있다. (18) 커널설정할때어떤기능을 yes, no, module 로설정할수있는데 yes 로하면 drivers-y 에, no 는 DRIVERS-n 에, module 은 DRIVERS-m 에모이게된다. 예를들어 ACPI 기능을사용하지않는다고했을경우엔아래줄이 drivers- += drivers/acpi/acpi.o 가된다..config 의내용을한번읽어보면금방이해될것이다. (19) 실제커널에포함되는드라이버는모두 DRIVERS 에기록된다. (20) make clean 했을때지워지는 file 들을지정한다. clean 은 object 등을지울뿐커널설정등은지우지않는다. (21) mrproper 는세팅까지도지워버리고완전히초기화시켜버린다. (22) 리눅스커널은여러종류의타켓을지원하므로처음 Makefile 에서확인한아키텍처에따른 Makefile 을읽어사용하게된다. make bzimage 등을했을때사용되는 Makefile 은여기서 include 된다. (23) 필요한플래그를 export 해서하위디렉토리등에서 make 할때도여기에서적용된사항들 30
이같이적용될수있도록한다. (24) 어셈블리코드컴파일방법을지정 (25) 컴파일된커널이일차적으로하나로뭉쳐 vmlinux 를만들어낸다. 이것을압축하고부팅에관계된코드를덧붙여주면커널이완성된다. (26) include 디렉토리내의심볼릭링크를설정한다. (27) 커널세팅하는방법에따라설정에필요한프로그램을만들고설정을시작한다. (28) linuxsubdirs 는 SUBDIRS 에정의된것들에서앞에 -dir- 을붙여새로운이름을하나씩만들어낸다. patsubst 는 $(patsubst PATTERN, REPLACEMENT, TEXT) 의형식으로 TEXT 에서 pattern 과일치하는부분을 REPLACEMENT 로교체한다. (29) 각하위디렉토리를 make 한다. patsubst 에의해실제디렉토리로이동하게된다. (30) 컴파일한시간, 누가했는가, gcc 버전등이기록된다. 내용은아래와같다. define uts-version "11 2002. 01. 25. ( 금 ) 16:35:16 KST" define linux-compile-time "16:35:16" define linux-compile-by "root" define linux-compile-host "halite" define linux-compile-domain "" define linux-compiler "gcc version 2.95.3 20010315 (release)" (31) include/linux/version.h 는현재컴파일될리눅스커널의버전정보를담는헤더파일이고아래스크립트에의해만들어진다. 내용은아래 3 줄과같다. define uts-release "2.4.16" define linux-version-code 132112 define kernel-version(a,b,c) (((a) << 16) + ((b) << 8) + (c)) (32) main.c 엔 start-kernel() 이들어있고이함수는 LILO 등에의해메모리에올려진커널이불리게되는시작위치다. (33) 하위디렉토리는위에정의된하위디렉토리 make 방법에따라 make 된다. 즉 $(patsubst %, -dir-%, $(SUBDIRS)) 에의해 make 된다. (34) emacs, vi 용 tag 를만든다. (35) module 로지정된놈들을다만들어준다..phony 를사용하면정의된이름이 file 이아님을알려주고퍼포먼스를올려준다. 자세한것은 'info make' 를해서참조바란다. (36) 커널을 컴파일할 때 System.map이 만들어지는데 이게 존재하는 경우 module의 31
dependency 를만들어준다. (37) distclean 은현재커널버전개발을끝내고 release 하려할때실행한다. (38) 리눅스커널은많은하위디렉토리가있고여기에각각의 Makefile 이존재하는데공통으로사용될수있는것을을모아 Rules.make 로만들고이를사용한다. (39) 커널설정을마친후헤더와소스사이의의존관계를만들어주는실행파일을만든다. (40) rpm 배포용 spec 파일과 rpm 파일을만든다. 2.3.2. $(topdir)/arch/i386/makefile (1) i386/makefile this file is included by the global makefile so that you can add your own architecture-specific flags and dependencies. Remember to do have actions for "archclean" and "archdep" for cleaning up and making dependencies for this architecture this file is subject to the terms and conditions of the GNU General Public license. see the file "COPYING" in the main directory of this archive for more details. copyright (c) 1994 by Linus Torvalds 19990713 artur Skawina <skawina@geocities.com> added '-march' and '-mpreferred-stack-boundary' support (2) ld=$(cross-compile)ld -m elf-i386 objcopy=$(cross-compile)objcopy -O binary -R.note -R.comment -S ldflags=-e stext linkflags =-t $(topdir)/arch/i386/vmlinux.lds $(LDFLAGS) cflags += -pipe (3) prevent gcc from keeping the stack 16 byte aligned cflags += $(shell if $(CC) -mpreferred-stack-boundary=2 -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-mpreferred-stack-boundary=2"; fi) ifdef config-m386 cflags += -march=i386 endif ifdef config-m486 cflags += -march=i486 endif ifdef config-m586 cflags += -march=i586 endif ifdef config-m586tsc cflags += -march=i586 endif ifdef config-m586mmx cflags += -march=i586 endif ifdef config-m686 cflags += -march=i686 32
endif ifdef config-mpentiumiii cflags += -march=i686 endif ifdef config-mpentium4 cflags += -march=i686 endif (4) ifdef config-mk6 cflags += $(shell if $(CC) -march=k6 -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-march=k6"; else echo "-march=i586"; fi) endif (5) ifdef config-mk7 cflags += $(shell if $(CC) -march=athlon -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-march=athlon"; else echo "-march=i686 -malign-functions=4"; fi) endif ifdef config-mcrusoe cflags += -march=i686 -malign-functions=0 -malign-jumps=0 -malign-loops=0 endif ifdef config-mwinchipc6 cflags += -march=i586 endif ifdef config-mwinchip2 cflags += -march=i586 endif ifdef config-mwinchip3d cflags += -march=i586 endif ifdef config-mcyrixiii cflags += -march=i586 endif head := arch/i386/kernel/head.o arch/i386/kernel/init-task.o subdirs += arch/i386/kernel arch/i386/mm arch/i386/lib core-files := arch/i386/kernel/kernel.o arch/i386/mm/mm.o $(CORE-FILES) libs := $(topdir)/arch/i386/lib/lib.a $(LIBS) $(TOPDIR)/arch/i386/lib/lib.a ifdef config-math-emulation subdirs += arch/i386/math-emu drivers += arch/i386/math-emu/math.o endif arch/i386/kernel: dummy $(make) linuxsubdirs SUBDIRS=arch/i386/kernel arch/i386/mm: dummy $(make) linuxsubdirs SUBDIRS=arch/i386/mm makeboot = $(make) -C arch/$(arch)/boot vmlinux: arch/i386/vmlinux.lds force: ;.phony: zimage bzimage compressed zlilo bzlilo zdisk bzdisk install \ clean archclean archmrproper archdep zimage: vmlinux @$(makeboot) zimage 33
(6) bzimage: vmlinux @$(makeboot) bzimage compressed: zimage zlilo: vmlinux @$(makeboot) bootimage=zimage zlilo tmp: @$(makeboot) bootimage=bzimage zlilo bzlilo: vmlinux @$(makeboot) bootimage=bzimage zlilo zdisk: vmlinux @$(makeboot) bootimage=zimage zdisk bzdisk: vmlinux @$(makeboot) bootimage=bzimage zdisk install: vmlinux @$(makeboot) bootimage=bzimage install archclean: @$(makeboot) clean archmrproper: archdep: @$(makeboot) dep (1) 이 makefile 은 $(TOPDIR)/Makefile 에의해읽여들여지므로 export 된많은변수들을그대로사용가능하다. (2) ld 는최종 output 을 elf-i386 의형태로만든다. objcopy 는입력에서.note,.comment 섹션을삭제하고리로케이션정보와심볼정보를삭제한다. 출력포맷은 binary. 링크할땐 $(TOPDIR)/arch/i386/vmlinux.lds 란파일에기록되어있는방법을따라링크한다. (3) gcc 가스택을 16 byte 단위로정렬하지못하도록한다. 사용되는옵션은다음과같은의미를갖는다. -mpreferred-stack-boundary=2 : 스택을 22 byte 로정렬하도록한다. (=4 면 24) -S : 컴파일스테이지까지만하고어셈블은하지않는다. -xc : c 언어로컴파일한다. 즉스택바운더리정렬이 4 바이트로가능한지알아봐서가능하면 4 바이트정렬을사용한다. 만약 4 바이트정렬을지원하지않으면컴파일중에에러가날것이다. 이땐기본값을사용한다. /dev/null 이 $(CC) 의입력으로지정됐으므로 /dev/null 을읽어컴파일한다. /dev/null 을읽으면 EOF 를돌려주므로컴파일된출력은다음과같을것이지만바로 /dev/null 로출력되어화면에는나타나지않는다. 34
컴파일되면다음과같은결과가나온다..file "null".version "01.01" gcc2-compiled.:.ident "gcc: (gnu) 2.95.3 20010315 (release)" 에러없이컴파일이끝나면 $(CC) 의결과는 true 가될것이고에러가있다면 false 가될것이다. >/dev/null 2>&1 은출력되는에러메시지는화면에나오게하지않고결과가 true 인지 false 인지만을판별하기위해넣은것이다. (4) amd k6 CPU 는지원하는지여부에따라지원하지않을경우엔 i586 으로간주한다. (5) athlon 을사용한다고했지만지원하는지판단후지원하지않으면 i686 으로간주하고정렬을 16 바이트로한다. (6) bzimage 를만들경우엔먼저 vmlinux 를만들고나서 $(TOPDIR)/arch/i386/boot 에서 make bzimage 를다시실행한다. 2.3.3. $(topdir)/arch/i386/boot/makefile arch/i386/boot/makefile this file is subject to the terms and conditions of the GNU General Public license. see the file "COPYING" in the main directory of this archive for more details. copyright (c) 1994 by Linus Torvalds boot-incl = $(topdir)/include/linux/config.h \ $(topdir)/include/linux/autoconf.h \ $(topdir)/include/asm/boot.h (1) zimage: $(configure) bootsect setup compressed/vmlinux tools/build $(objcopy) compressed/vmlinux compressed/vmlinux.out tools/build bootsect setup compressed/vmlinux.out $(ROOT-DEV) > zimage (2) bzimage: $(configure) bbootsect bsetup compressed/bvmlinux tools/build $(objcopy) compressed/bvmlinux compressed/bvmlinux.out tools/build -b bbootsect bsetup compressed/bvmlinux.out $(ROOT-DEV) > bzimage compressed/vmlinux: $(TOPDIR)/vmlinux @$(make) -c compressed vmlinux compressed/bvmlinux: $(TOPDIR)/vmlinux @$(make) -c compressed bvmlinux zdisk: $(bootimage) dd bs=8192 if=$(bootimage) of=/dev/fd0 zlilo: $(configure) $(BOOTIMAGE) if [ -f $(install-path)/vmlinuz ]; then mv $(INSTALL-PATH)/vmlinuz $(INSTALL- PATH)/vmlinuz.old; fi if [ -f $(install-path)/system.map ]; then mv $(INSTALL-PATH)/System.map $(INSTALL-PATH)/System.old; fi cat $(bootimage) > $(INSTALL-PATH)/vmlinuz cp $(topdir)/system.map $(INSTALL-PATH)/ if [ -x /sbin/lilo ]; then /sbin/lilo; else /etc/lilo/install; fi 35
install: $(configure) $(BOOTIMAGE) sh -x./install.sh $(KERNELRELEASE) $(BOOTIMAGE) $(TOPDIR)/System.map "$(INSTALL-PATH)" (3) tools/build: tools/build.c $(hostcc) $(hostcflags) -o $@ $< -I$(TOPDIR)/include bootsect: bootsect.o $(ld) -ttext 0x0 -s --oformat binary -o $@ $< bootsect.o: bootsect.s $(as) -o $@ $< bootsect.s: bootsect.s Makefile $(BOOT-INCL) $(cpp) $(cppflags) -traditional $(SVGA-MODE) $(RAMDISK) $< -o $@ (4) bbootsect: bbootsect.o $(ld) -ttext 0x0 -s --oformat binary $< -o $@ bbootsect.o: bbootsect.s $(as) -o $@ $< (5) bbootsect.s: bootsect.s Makefile $(BOOT-INCL) $(cpp) $(cppflags) -D--BIG-KERNEL-- -traditional $(SVGA-MODE) $(RAMDISK) $< -o $@ setup: setup.o $(ld) -ttext 0x0 -s --oformat binary -e begtext -o $@ $< setup.o: setup.s $(as) -o $@ $< setup.s: setup.s video.s Makefile $(BOOT-INCL) $(TOPDIR)/include/linux/version.h $(TOPDIR)/include/linux/compile.h $(cpp) $(cppflags) -D--ASSEMBLY-- -traditional $(SVGA-MODE) $(RAMDISK) $< -o $@ (6) bsetup: bsetup.o $(ld) -ttext 0x0 -s --oformat binary -e begtext -o $@ $< bsetup.o: bsetup.s $(as) -o $@ $< bsetup.s: setup.s video.s Makefile $(BOOT-INCL) $(TOPDIR)/include/linux/version.h $(TOPDIR)/include/linux/compile.h $(cpp) $(cppflags) -D--BIG-KERNEL-- -D--ASSEMBLY-- -traditional $(SVGA-MODE) $(RAMDISK) $< -o $@ dep: clean: rm -f tools/build rm -f setup bootsect zimage compressed/vmlinux.out rm -f bsetup bbootsect bzimage compressed/bvmlinux.out @$(make) -c compressed clean (1) 현재커널은 zimage, bzimage 두가지가존재한다. zimage 는 gzip 으로압축되고하위 1M 메모리내에적재될수있는크기의커널 [1] bzimage 는 gzip 으로압축되고하위 1M 메모리내에적재될수없는크기의커널 $(objcopy) 는 $(TOPDIR)/arch/i386/Makefile 에서정의된것을따른다. 즉 36
'objcopy=$(cross-compile)objcopy -O binary -R.note -R.comment -S' 가된다. (2) bvmlinux 를 objcopy 를사용해심볼등을빼고 build 란것을사용해최종커널을만든다. build 는 bbootsect(512 bytes)+bsetup+bvmlinux.out 을합쳐하나의 bzimage 를만든다 ( 그림 2-2). (3) build 프로그램은최종커널을만드는유틸리티다. 더자세한것은여기에서다룬다. (4) 어셈블끝난 bbootsect.o 를링크한다. 사용된옵션은 -ttext 0x0 : 코드의시작을 0 번지부터시작한다고하고링크한다. 이렇게하면링크된최종출력물은특별한위치를가리지않고메모리의아무위치에나적재가가능하고실행가능해진다. -s : 출력물에서심볼정보를모두없앤다. --oformat binary : 출력물의포맷을바이너리로한다. (5) bootsect.s 를프리컴파일하는데 --BIG-KERNEL-- 을정의해 bzimage 의부트섹터를만든다. 초기 vga 모드와램디스크크기등을정보로전달해준다. (6) bsetup 또한 bbootsect 와같은방법으로만들어진다. 2.3.4. $(topdir)/arch/i386/boot/compressed/makefile linux/arch/i386/boot/compressed/makefile create a compressed vmlinux image from the original vmlinux head = head.o system = $(topdir)/vmlinux objects = $(head) misc.o zldflags = -e startup-32 (1) zimage-offset is the load offset of the compression loader bzimage-offset is the load offset of the high loaded compression loader zimage-offset = 0x1000 bzimage-offset = 0x100000 zlinkflags = -ttext $(ZIMAGE-OFFSET) $(ZLDFLAGS) bzlinkflags = -ttext $(BZIMAGE-OFFSET) $(ZLDFLAGS) all: vmlinux vmlinux: piggy.o $(OBJECTS) $(ld) $(zlinkflags) -o vmlinux $(OBJECTS) piggy.o (2) bvmlinux: piggy.o $(OBJECTS) $(ld) $(bzlinkflags) -o bvmlinux $(OBJECTS) piggy.o (3) head.o: head.s $(cc) $(aflags) -traditional -c head.s misc.o: misc.c 37
$(cc) $(cflags) -c misc.c (4) piggy.o: $(system) tmppiggy=-tmp-$$$$piggy; \ rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk; \ $(objcopy) $(system) $$tmppiggy; \ gzip -f -9 < $$tmppiggy > $$tmppiggy.gz; \ echo "sections {.data : { input-len =.; LONG(input-data-end - input-data) input-data =.; *(.data) input-data-end =.; }}" >gt; $$tmppiggy.lnk; \ $(ld) -r -o piggy.o -b binary $$tmppiggy.gz -b elf32-i386 -T $$tmppiggy.lnk; \ rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk clean: rm -f vmlinux bvmlinux -tmp-* (1) zimage 와 bzimage 의메모리적재위치가서로달라 zimage 는 0x1000 에서부터메모리에적재되고 bzimage 는 0x100000 에적재된다. (2) bvmlinux 는 vmlinux 와마찬가지로 heas.o, misc.o, piggy.o 가합쳐져만들어진다. 그러나링크플래그가서로다르게설정되어있다. (3) 커널의압축을풀고메모리에적재하는등의일을하는부분이다. (4) 커널의핵심부분이모두컴파일되링크되면 elf type 으로 $(TOP-DIR)/vmlinux 가만들어지는데이것에서디버깅정보등을없애고압축해서만든것이 piggy.o 가된다. 압축은 gzip 으로한다. $$$$piggy 의 4 개의 $ 는 4 자리의임의의숫자로채워진다. 즉 tmppiggy=-tmp-1234piggy 와같이된다. 더불어 $$tmppiggy.gz 은 -tmp-1234piggy.gz, tmppiggy.lnk 는 -tmp-1234piggy.lnk 와같이된다. piggy.o 는 head.o, misc.o 와합쳐져하나의다른 file 로만들어져야하므로다시링커를통해 elf-i386 포맷으로만들어진다. $(objcopy) 에사용된옵션은다음과같다. -O : output format. 여기선 binary -R : 지정된 section 이름을지운다..note,.comment는없앤다. -S : input file을지정한다. $(ld) 에사용된옵션은다음과같다. -m elf-i386 : ld가 elf-i386을 emulation 하도록지정한다. -r : relocatable, 메모리에적재될때재배치가능하도록한다. -b binary : input file의 format을말한다. 여기선 $$tmppiggy.gz은 binary -T : linker script file을지정한다. -b elf32-i386 : output을 elf-i386 format으로지정한다. 38
2.3.5. $(topdir)/arch/i386/boot/tools/build.c $(topdir)/arch/i386/boot/tools/build 는커널이미지만드는과정의최종단계에서몇개의파일을합쳐하나의커널이미지를만들어낸다. 이런일을담당하는프로그램을분석해야이미나온부팅과정에서의동작을이해할수있을것이다. 최종만들어지는이미지는그림 2-2 이된다. 부팅할때 bootsect 는 build 에의해기록된루트디바이스, setup 의크기, 압축커널의크기를바탕을부팅절차를계속진행한다. /* * $id: chap2.sgml,v 1.8 2002/02/15 15:59:43 halite Exp $ * * copyright (c) 1991, 1992 Linus Torvalds * copyright (c) 1997 Martin Mares */ /* * this file builds a disk-image from three different files: * * - bootsect: exactly 512 bytes of 8086 machine code, loads the rest * - setup: 8086 machine code, sets up system parm * - system: 80386 code for actual system * * it does some checking that all files are of the correct type, and * just writes the result to stdout, removing headers and padding to * the right amount. It also writes some system data to stderr. */ /* * changes by tytso to allow root device specification * high loaded stuff by Hans Lermen & Werner Almesberger, Feb. 1996 * cross compiling fixes by Gertjan van Wingerde, July 1996 * rewritten by martin Mares, April 1997 */ include <stdio.h> include <string.h> include <stdlib.h> include <stdarg.h> include <sys/types.h> include <sys/stat.h> include <sys/sysmacros.h> include <unistd.h> include <fcntl.h> include <asm/boot.h> typedef unsigned char byte; typedef unsigned short word; typedef unsigned long u32; define default-major-root 0 define default-minor-root 0 /* minimal number of setup sectors (see also bootsect.s) */ define setup-sects 4 byte buf[1024]; int fd; int is-big-kernel; void die(const char * str,...) { va-list args; va-start(args, str); vfprintf(stderr, str, args); fputc('\n', stderr); 39
} exit(1); void file-open(const char *name) { if ((fd = open(name, O-RDONLY, 0)) < 0) die("unable to open `%s': %m", name); } void usage(void) { die("usage: build [-b] bootsect setup system [rootdev] [> image]"); } int main(int argc, char ** argv) { unsigned int i, c, sz, setup-sectors; u32 sys-size; byte major-root, minor-root; struct stat sb; (1) (2) (3) (4) if (argc > 2 &&!strcmp(argv[1], "-b")) { is-big-kernel = 1; argc--, argv++; } if ((argc < 4) (argc > 5)) usage(); if (argc > 4) { if (!strcmp(argv[4], "CURRENT")) { if (stat("/", &sb)) { perror("/"); die("couldn't stat /"); } major-root = major(sb.st-dev); minor-root = minor(sb.st-dev); } else if (strcmp(argv[4], "FLOPPY")) { if (stat(argv[4], &sb)) { perror(argv[4]); die("couldn't stat root device."); } major-root = major(sb.st-rdev); minor-root = minor(sb.st-rdev); } else { major-root = 0; minor-root = 0; } } else { major-root = default-major-root; minor-root = default-minor-root; } fprintf(stderr, "Root device is (%d, %d)\n", major-root, minor-root); file-open(argv[1]); i = read(fd, buf, sizeof(buf)); fprintf(stderr,"boot sector %d bytes.\n",i); if (i!= 512) die("boot block must be exactly 512 bytes"); if (buf[510]!= 0x55 buf[511]!= 0xaa) die("boot block hasn't got boot flag (0xAA55)"); buf[508] = minor-root; buf[509] = major-root; if (write(1, buf, 512)!= 512) die("write call failed"); close (fd); file-open(argv[2]); /* Copy the setup code */ for (i=0 ; (c=read(fd, buf, sizeof(buf)))>0 ; i+=c ) 40
if (write(1, buf, c)!= c) die("write call failed"); if (c!= 0) die("read-error on `setup'"); close (fd); (5) (6) setup-sectors = (i + 511) / 512; /* Pad unused space with zeros */ /* for compatibility with ancient versions of LILO. */ if (setup-sectors < SETUP-SECTS) setup-sectors = SETUP-SECTS; fprintf(stderr, "Setup is %d bytes.\n", i); memset(buf, 0, sizeof(buf)); while (i < setup-sectors * 512) { c = setup-sectors * 512 - i; if (c > sizeof(buf)) c = sizeof(buf); if (write(1, buf, c)!= c) die("write call failed"); i += c; } file-open(argv[3]); if (fstat (fd, &sb)) die("unable to stat `%s': %m", argv[3]); sz = sb.st-size; fprintf (stderr, "System is %d kb\n", sz/1024); sys-size = (sz + 15) / 16; /* 0x28000*16 = 2.5 MB, conservative estimate for the current maximum */ if (sys-size > (is-big-kernel? 0x28000 : DEF-SYSSIZE)) die("system is too big. Try using %smodules.", is-big-kernel? "" : "bzimage or "); if (sys-size > 0xefff) fprintf(stderr,"warning: kernel is too big for standalone boot " "from floppy\n"); while (sz > 0) { int l, n; l = (sz > sizeof(buf))? sizeof(buf) : sz; if ((n=read(fd, buf, l))!= l) { if (n < 0) die("error reading %s: %m", argv[3]); else die("%s: unexpected EOF", argv[3]); } if (write(1, buf, l)!= l) die("write failed"); sz -= l; } close(fd); if (lseek(1, 497, SEEK-SET)!= 497) bootsector */ die("output: seek failed"); buf[0] = setup-sectors; if (write(1, buf, 1)!= 1) die("write of setup sector count failed"); if (lseek(1, 500, SEEK-SET)!= 500) die("output: seek failed"); buf[0] = (sys-size & 0xff); buf[1] = ((sys-size >> 8) & 0xff); if (write(1, buf, 2)!= 2) die("write of image length failed"); /* Write sizes to the } return 0; /* Everything is OK */ (1) 41
build 의 command line 에 -b 옵션을주면이는 big kernel 임을의미하게된다. (2) 루트디바이스의 major, minor 번호를알아낸다. current 는 / 의 major, minor number 를사용한다. 필자의리눅스박스는 hda1 이 / 이므로 major=0x03, minor=0x01 이될것이다. 플로피가루트디바이스로지정됐으면 major=minor=0 이된다. command line 에아무것도지정되지않으면기본값이사용된다 ( 기본값은사실플로피와같은값을갖는다 ). (3) 부트섹터파일을읽어 512 byte 가아니면에러를낸다. 부트섹터는정확히 512 byte 여야하기때문이다. 그리고 MagicNumber 를체크해정말부트섹터인지확인한다. 또 508(0x1FC), 509(0x1FD) 번째바이트에루트디바이스의 minor, major 번호를써넣는다. 수정후표준출력으로 bootsect 의 512 byte 를출력한다 ( 원래 512 byte 였으므로수정내용을포함해그대로출력될것이다 ). (4) setup 은크기가정확히얼마인지알수없으므로 1024 byte 단위로읽으면서크기를변수 i 에기억해놓는다. 읽은 1KB 는읽는즉시표준출력을출력된다. (5) setup 을 512 byte 단위로끊고적어도 SETUP-SECTOR( 값은 4) 만큼이되는지확인해모자란부분은 0 으로채워넣는다. 디스크는섹터단위로입출력한다는것을기억하기바란다. 예를들어 setup 의크기가 4768 byte 라면 4768/512=9.3125 이므로 9 섹터를차지하고 10 번째섹터는다사용하지않고조금만사용하게된다. 10 번째섹터의경우 160 byte 를제외한 352 byte 만큼을 0 으로채워넣는다. (6) 압축된커널의크기를계산해 zimage 의경우 0x7F000 보다큰지확인하고, bzimage 는 0x280000 보다큰지확인한다. 만약지정된크기보다크다면현재로서는수용할수없는크기의커널이므로에러를낸다. 또플로피부팅의경우플로피에들어갈수있는크기인지확인한다. 1024 byte 단위로읽어표준출력에출력하고 bootsect 의 497(0x1F1) 에 setup 이몇섹터를차지하는지기록하고 500(0x1F4) 에압축커널의크기를 16 byte 단위로기록해준다. 주석 [1] 하위 1M 에대한것은 2.2.2 절을참조한다. 2.4. bzimage 가만들어지는과정추적 -Log 분석 2.4.1. make bzimage 순서정리 make bzimage 를실행했을때실행되는순서를 Makefile 을기준으로나열해봤다. $(TOPDIR)/Makefile 에포함된 $(TOPDIR)/arch/i386/Makefile 에있는 'bzimage:' 로부터시작 의존관계에의해 vmlinux 가먼저만들어짐 42