1. 리눅스커널컴파일하기 리눅스일반 왜리눅스인가? 리눅스소스디렉토리구조 LXR 소스코드얻기 소스코드풀기 컴파일준비 커널설정 커널컴파일 14

Similar documents
ARM-Linux Makefile

untitled

슬라이드 제목 없음

PowerPoint 프레젠테이션

LXR 설치 및 사용법.doc

PowerPoint 프레젠테이션

슬라이드 1

휠세미나3 ver0.4

GNU/Linux 1, GNU/Linux MS-DOS LOADLIN DOS-MBR LILO DOS-MBR LILO... 6

PowerPoint 프레젠테이션

PowerPoint 프레젠테이션

Microsoft PowerPoint - 03-Development-Environment-2.ppt

PowerPoint 프레젠테이션

망고100 보드로 놀아보자 -10

6주차.key

PowerPoint 프레젠테이션

Mango220 Android How to compile and Transfer image to Target

지난시간에... 우리는 kernel compile을위하여 cross compile 환경을구축했음. UBUNTU 12.04에서 arm-2009q3를사용하여 간단한 c source를빌드함. 한번은 intel CPU를위한 gcc로, 한번은 ARM CPU를위한 gcc로. AR

PowerPoint 프레젠테이션

임베디드시스템설계강의자료 6 system call 2/2 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

PowerPoint 프레젠테이션

<4D F736F F F696E74202D20B8AEB4AABDBA20BFC0B7F920C3B3B8AEC7CFB1E22E BC8A3C8AF20B8F0B5E55D>

PowerPoint 프레젠테이션

/chroot/lib/ /chroot/etc/

chap7.key

28 THE ASIAN JOURNAL OF TEX [2] ko.tex [5]

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

Microsoft PowerPoint - chap01-C언어개요.pptx

Microsoft PowerPoint - lab14.pptx

Chapter. 5 Embedded System I Bootloader, Kernel, Ramdisk Professor. Jaeheung, Lee

Microsoft Word - ntasFrameBuilderInstallGuide2.5.doc

커알못의 커널 탐방기 이 세상의 모든 커알못을 위해서

Microsoft PowerPoint - em8-리눅스설치.ppt

강의10

untitled

Microsoft PowerPoint Android-SDK설치.HelloAndroid(1.0h).pptx

Mango-AM335x LCD Type 커널 Module Parameter에서 변경하기

Solaris Express Developer Edition

1. 안드로이드개발환경설정 안드로이드개발을위해선툴체인을비롯한다양한소프트웨어패키지가필요합니다 툴체인 (Cross-Compiler) 설치 안드로이드 2.2 프로요부터는소스에기본툴체인이 prebuilt 라는이름으로포함되어있지만, 리눅스 나부트로더 (U-boot)

PowerPoint 프레젠테이션

KEY 디바이스 드라이버

임베디드시스템설계강의자료 4 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

vi 사용법

Microsoft Word - Armjtag_문서1.doc

Microsoft Word doc

Microsoft Word - 3부A windows 환경 IVF + visual studio.doc

DE1-SoC Board

제1장 Unix란 무엇인가?

Chapter #01 Subject

MySQL-Ch10

C# Programming Guide - Types

Microsoft PowerPoint - 06-CompSys-11-System.ppt

슬라이드 1

10.

Abstract View of System Components

untitled

제1장 Unix란 무엇인가?

Remote UI Guide

K7VT2_QIG_v3

chap 5: Trees

문서 대제목

Adobe Flash 취약점 분석 (CVE )

ISP and CodeVisionAVR C Compiler.hwp

K&R2 Reference Manual 번역본

Microsoft PowerPoint - chap06-2pointer.ppt

CD-RW_Advanced.PDF

Microsoft PowerPoint SDK설치.HelloAndroid(1.5h).pptx

BMP 파일 처리

교육지원 IT시스템 선진화

슬라이드 1

API 매뉴얼

Microsoft PowerPoint - 02-Development-Environment-1.ppt

The Pocket Guide to TCP/IP Sockets: C Version

PowerPoint 프레젠테이션

[ 컴퓨터시스템 ] 3 주차 1 차시. 디렉토리사이의이동 3 주차 1 차시디렉토리사이의이동 학습목표 1. pwd 명령을사용하여현재디렉토리를확인할수있다. 2. cd 명령을사용하여다른디렉토리로이동할수있다. 3. ls 명령을사용하여디렉토리내의파일목록을옵션에따라다양하게확인할수

목차 1. 제품 소개 특징 개요 Function table 기능 소개 Copy Compare Copy & Compare Erase

<443A5C4C C4B48555C B3E25C32C7D0B1E25CBCB3B0E8C7C1B7CEC1A7C6AE425CBED0C3E0C7C1B7CEB1D7B7A55C D616E2E637070>

본문서는 초급자들을 대상으로 최대한 쉽게 작성하였습니다. 본문서에서는 설치방법만 기술했으며 자세한 설정방법은 검색을 통하시기 바랍니다. 1. 설치개요 워드프레스는 블로그 형태의 홈페이지를 빠르게 만들수 있게 해 주는 프로그램입니다. 다양한 기능을 하는 플러그인과 디자인

6. 설치가시작되는동안 USB 드라이버가자동으로로드됩니다. USB 드라이버가성공적으로로드되면 Setup is starting( 설치가시작되는중 )... 화면이표시됩니다. 7. 화면지침에따라 Windows 7 설치를완료합니다. 방법 2: 수정된 Windows 7 ISO

버퍼오버플로우-왕기초편 10. 메모리를 Hex dump 뜨기 앞서우리는버퍼오버플로우로인해리턴어드레스 (return address) 가변조될수있음을알았습니다. 이제곧리턴어드레스를원하는값으로변경하는실습을해볼것인데요, 그전에앞서, 메모리에저장된값들을살펴보는방법에대해배워보겠습

PowerPoint 프레젠테이션

Microsoft PowerPoint - o8.pptx

SRC PLUS 제어기 MANUAL

MPLAB C18 C

망고100 보드로 놀아보자-4

Microsoft Word - FunctionCall

B _00_Ko_p1-p51.indd

1217 WebTrafMon II

Copyright 2012, Oracle and/or its affiliates. All rights reserved.,,,,,,,,,,,,,.,..., U.S. GOVERNMENT END USERS. Oracle programs, including any operat

Microsoft PowerPoint - 02_Linux_Fedora_Core_8_Vmware_Installation [호환 모드]

Transcription:

임베디드시스템엔지니어를위한리눅스커널분석 남상규 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

이때 vmlinux 가만들어지면 $(TOPDIR)/arch/i386/boot 로이동해계속진행 vmlinux 의진행 의존관계에의해 version.h $(CONFIGURATION) init/main.o init/version.o linuxsubdirs 가먼저만들어짐 의존관계에의한만들기가끝나면 $(TOPDIR)/vmlinux 가만들어짐 의존관계에의해 $(CONFIGURE) bbootsect bsetup compressed/bvmlinux tools/build 가만들어진다 compressed/bvmlinux 의진행 의존관계에의해 piggy.o $(OBJECTS) 가먼저만들어짐 piggy.o 는 $(TOPDIR)/vmlinux 를압축해만든다. 순서대로나열했지만 Makefile 의특성상하나를만들기전에이미다른것이먼저만들어져야하는등의의존관계가있기때문에순서가뒤집힌것처럼보일것이다. 이를바로잡아먼저만들어지는순으로나열해보면아래와같다. 1. vmlinux a. include/linux/version.h b. init/main.o c. init/version.o d. linuxsubidrs(fs lib mm ipc kernel drivers net) 2. bzimage a. bbootsect b. bsetup c. compressed/bvmlinux i. piggy.o ii. head.o iii. misc.o d. tools/build 43

2.4.2. Log Makefile 을통해분석된것을이제는실예를사용해분석해보자. 아래 Log 는 'make bzimage 2>&1 tee log-bzimage.txt' 를사용해얻은것이다. 전체는필요없는부분이너무많기때문에필요없는부분은삭제하거나축약하고실었다. (1) gcc -D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes -Wnotrigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe -mpreferredstack-boundary=2 -march=i686 -malign-functions=4 -c -o init/main.o init/main.c. scripts/mkversion >.tmpversion (2) gcc -D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes -Wnotrigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe -mpreferredstack-boundary=2 -march=i686 -malign-functions=4 -DUTS-MACHINE='"i386"' -c -o init/version.o init/version.c make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C kernel (3) gcc -D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes -Wnotrigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe -mpreferredstack-boundary=2 -march=i686 -malign-functions=4 -DUTS-MACHINE='"i386"' -c -o init/version.o init/version.c make[1]: 들어감 `/usr/src/linux-2.4.16/kernel' 디렉토리 make all-targets make[2]: 들어감 `/usr/src/linux-2.4.16/kernel' 디렉토리 rm -f kernel.o ld -m elf-i386 -r -o kernel.o sched.o dma.o fork.o exec-domain.o panic.o printk.o module.o exit.o itimer.o info.o time.o softirq.o resource.o sysctl.o acct.o capability.o ptrace.o timer.o user.o signal.o sys.o kmod.o context.o uid16.o ksyms.o pm.o make[2]: 나감 `/usr/src/linux-2.4.16/kernel' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/kernel' 디렉토리 (4) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C drivers make[1]: 들어감 `/usr/src/linux-2.4.16/drivers' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/drivers' 디렉토리 (5) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C mm make[1]: 들어감 `/usr/src/linux-2.4.16/mm' 디렉토리 make all-targets make[2]: 들어감 `/usr/src/linux-2.4.16/mm' 디렉토리 rm -f mm.o ld -m elf-i386 -r -o mm.o memory.o mmap.o filemap.o mprotect.o mlock.o mremap.o vmalloc.o slab.o bootmem.o swap.o vmscan.o page-io.o page-alloc.o swap-state.o swapfile.o numa.o oom-kill.o shmem.o make[2]: 나감 `/usr/src/linux-2.4.16/mm' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/mm' 디렉토리 (6) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C fs make[1]: 들어감 `/usr/src/linux-2.4.16/fs' 디렉토리 rm -f fs.o ld -m elf-i386 -r -o fs.o open.o read-write.o devices.o file-table.o buffer.o super.o block-dev.o char-dev.o stat.o exec.o pipe.o namei.o fcntl.o ioctl.o readdir.o select.o fifo.o locks.o dcache.o inode.o attr.o bad-inode.o file.o iobuf.o dnotify.o filesystems.o namespace.o seq-file.o noquot.o binfmt-script.o binfmt-elf.o proc/proc.o partitions/partitions.o ext2/ext2.o isofs/isofs.o nls/nls.o autofs4/autofs4.o 44

devpts/devpts.o jfs/jfs.o make[1]: 나감 `/usr/src/linux-2.4.16/fs' 디렉토리 (7) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C net make[1]: 들어감 `/usr/src/linux-2.4.16/net' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/net' 디렉토리 (8) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C ipc make[1]: 들어감 `/usr/src/linux-2.4.16/ipc' 디렉토리 make all-targets make[2]: 들어감 `/usr/src/linux-2.4.16/ipc' 디렉토리 rm -f ipc.o ld -m elf-i386 -r -o ipc.o util.o msg.o sem.o shm.o make[2]: 나감 `/usr/src/linux-2.4.16/ipc' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/ipc' 디렉토리 (9) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C lib make[1]: 들어감 `/usr/src/linux-2.4.16/lib' 디렉토리 make all-targets make[2]: 들어감 `/usr/src/linux-2.4.16/lib' 디렉토리 rm -f lib.a ar rcs lib.a errno.o ctype.o string.o vsprintf.o brlock.o cmdline.o bust-spinlocks.o rbtree.o rwsem.o dec-and-lock.o make[2]: 나감 `/usr/src/linux-2.4.16/lib' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/lib' 디렉토리 (10) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C arch/i386/kernel make[1]: 들어감 `/usr/src/linux-2.4.16/arch/i386/kernel' 디렉토리 rm -f kernel.o ld -m elf-i386 -r -o kernel.o process.o semaphore.o signal.o entry.o traps.o irq.o vm86.o ptrace.o i8259.o ioport.o ldt.o setup.o time.o sys-i386.o pci-dma.o i386-ksyms.o i387.o bluesmoke.o dmi-scan.o pci-i386.o pci-pc.o pci-irq.o mtrr.o apm.o mpparse.o apic.o nmi.o io-apic.o acpitable.o gcc -D--ASSEMBLY-- -D--KERNEL-- -I/usr/src/linux-2.4.16/include -traditional -c head.s - o head.o gcc -D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes -Wnotrigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe -mpreferredstack-boundary=2 -march=i686 -malign-functions=4 -c -o init-task.o init-task.c make[1]: 나감 `/usr/src/linux-2.4.16/arch/i386/kernel' 디렉토리 (11) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C arch/i386/mm make[1]: 들어감 `/usr/src/linux-2.4.16/arch/i386/mm' 디렉토리 make all-targets make[2]: 들어감 `/usr/src/linux-2.4.16/arch/i386/mm' 디렉토리 rm -f mm.o ld -m elf-i386 -r -o mm.o init.o fault.o ioremap.o extable.o make[2]: 나감 `/usr/src/linux-2.4.16/arch/i386/mm' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/arch/i386/mm' 디렉토리 (12) make CFLAGS="-D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes - Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe - mpreferred-stack-boundary=2 -march=i686 -malign-functions=4 " -C arch/i386/lib make[1]: 들어감 `/usr/src/linux-2.4.16/arch/i386/lib' 디렉토리 45

make all-targets make[2]: 들어감 `/usr/src/linux-2.4.16/arch/i386/lib' 디렉토리 rm -f lib.a ar rcs lib.a checksum.o old-checksum.o delay.o usercopy.o getuser.o memcpy.o strstr.o mmx.o make[2]: 나감 `/usr/src/linux-2.4.16/arch/i386/lib' 디렉토리 make[1]: 나감 `/usr/src/linux-2.4.16/arch/i386/lib' 디렉토리 (13) ld -m elf-i386 -T /usr/src/linux-2.4.16/arch/i386/vmlinux.lds -e stext arch/i386/kernel/head.o arch/i386/kernel/init-task.o init/main.o init/version.o \ --start-group \ arch/i386/kernel/kernel.o arch/i386/mm/mm.o kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o \ drivers/acpi/acpi.o drivers/char/char.o drivers/block/block.o drivers/misc/misc.o drivers/net/net.o drivers/media/media.o drivers/char/agp/agp.o drivers/char/drm/drm.o drivers/ide/idedriver.o drivers/cdrom/driver.o drivers/sound/sounddrivers.o drivers/pci/driver.o drivers/pcmcia/pcmcia.o drivers/net/pcmcia/pcmcia-net.o drivers/pnp/pnp.o drivers/video/video.o drivers/md/mddev.o \ net/network.o \ /usr/src/linux-2.4.16/arch/i386/lib/lib.a /usr/src/linux-2.4.16/lib/lib.a /usr/src/linux-2.4.16/arch/i386/lib/lib.a \ --end-group \ -o vmlinux nm vmlinux grep -v '\(compiled\)\ \(\.o$\)\ \( [auw] \)\ \(\.\.ng$\)\ \(LASH[RL]DI\)' sort > System.map (14) make[1]: 들어감 `/usr/src/linux-2.4.16/arch/i386/boot' 디렉토리 gcc -E -D--KERNEL-- -I/usr/src/linux-2.4.16/include -D--BIG-KERNEL-- -traditional - DSVGA-MODE=NORMAL-VGA bootsect.s -o bbootsect.s as -o bbootsect.o bbootsect.s bbootsect.s: Assembler messages: bbootsect.s:257: Warning: indirect lcall without `*' ld -m elf-i386 -Ttext 0x0 -s --oformat binary bbootsect.o -o bbootsect (15) gcc -E -D--KERNEL-- -I/usr/src/linux-2.4.16/include -D--BIG-KERNEL-- -D--ASSEMBLY-- - traditional -DSVGA-MODE=NORMAL-VGA setup.s -o bsetup.s as -o bsetup.o bsetup.s bsetup.s: Assembler messages: bsetup.s:1716: Warning: indirect lcall without `*' ld -m elf-i386 -Ttext 0x0 -s --oformat binary -e begtext -o bsetup bsetup.o (16) make[2]: 들어감 `/usr/src/linux-2.4.16/arch/i386/boot/compressed' 디렉토리 tmppiggy=-tmp-$$piggy; \ rm -f $tmppiggy $tmppiggy.gz $tmppiggy.lnk; \ objcopy -O binary -R.note -R.comment -S /usr/src/linux-2.4.16/vmlinux $tmppiggy; \ gzip -f -9 < $tmppiggy > $tmppiggy.gz; \ echo "SECTIONS {.data : { input-len =.; LONG(input-data-end - input-data) input-data =.; *(.data) input-data-end =.; }}" > $tmppiggy.lnk; \ ld -m elf-i386 -r -o piggy.o -b binary $tmppiggy.gz -b elf32-i386 -T $tmppiggy.lnk; \ rm -f $tmppiggy $tmppiggy.gz $tmppiggy.lnk (17) gcc -D--ASSEMBLY-- -D--KERNEL-- -I/usr/src/linux-2.4.16/include -traditional -c head.s (18) gcc -D--KERNEL-- -I/usr/src/linux-2.4.16/include -Wall -Wstrict-prototypes -Wnotrigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -pipe -mpreferredstack-boundary=2 -march=i686 -malign-functions=4 -c misc.c (19) ld -m elf-i386 -Ttext 0x100000 -e startup-32 -o bvmlinux head.o misc.o piggy.o make[2]: 나감 `/usr/src/linux-2.4.16/arch/i386/boot/compressed' 디렉토리 (20) gcc -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -o tools/build tools/build.c - I/usr/src/linux-2.4.16/include 46

(21) objcopy -O binary -R.note -R.comment -S compressed/bvmlinux compressed/bvmlinux.out (22) tools/build -b bbootsect bsetup compressed/bvmlinux.out CURRENT > bzimage Root device is (3, 1) Boot sector 512 bytes. Setup is 4768 bytes. System is 899 kb make[1]: 나감 `/usr/src/linux-2.4.16/arch/i386/boot' 디렉토리 (1) main.o (2) version.o (3) kernel (4) drivers (5) mm (6) fs (7) net (8) ipc (9) lib (10) arch/i386/kernel (11) arch/i386/mm (12) arch/i386/lib (13) vmlinux (14) bbootsect (15) bsetup (16) arch/i386/boot/compressed/piggy.o (17) arch/i386/boot/compressed/head.o (18) arch/i386/boot/compressed/misc.o (19) arch/i386/boot/compressed/bvmlinux (20) build (21) bvmlinux.out (22) bzimage 47

위에열거한것과같이실제컴파일에서의순서가명확하게나왔다. drivers 와같은단계에선하위디렉토리가무척많아여러디렉토리를컴파일하는데그런것들은모두생략했다. 2.5. 단계별자세한분석 2.5.1. -Ttext 0x0 의의미 여기서 -Ttext 0x0 에대해좀알아보자..text.global -start -start: test-val:.long test-data nop test-data:.word 0xaa55 위와같은코드를 'gcc -E -traditional -o test.s test.s' 로컴파일하면 1 "test.s".text.global -start -start: test-val:.long test-data nop test-data:.word 0xaa55 이렇게되고이를다시 'as -o test.o test.s' 로어셈블하는데 -a 를사용해중간파일을얻으면다음과같다. GAS LISTING test.s page 1 1 1 "test.s" 2.text 3 4.global -start 5 -start: 6 0000 05000000 test-val:.long test-data 7 0004 90 nop 8 0005 55AA test-data:.word 0xaa55 9 0007 90 AS LISTING test.s page 2 DEFINED SYMBOLS test.s:5 test.s:6 test.s:8.text:00000000 -start.text:00000000 test-val.text:00000005 test-data NO UNDEFINED SYMBOLS test-val 에저장된값은 test-data 의.text 의시작점에서부터의 offset 이다. 프로그램의시작인 0 에서부터 5 번째에있단소리다. 48

최종적으로 'ld -m elf-i386 -s --oformat binary test.o -o test.1' 한결과를 hex 로살펴보면다음과같다. 00000000 05 80 04 08 90 55 aa 90 0x90 은 nop(no operation) 을의미한다. 그리고 'ld -m elf-i386 -Ttext 0x0 -s --oformat binary test.o -o test.2' 한결과는다음과같다. 00000000 05 00 00 00 90 55 aa 90 둘사이의차이점은 -Ttext 0x0 가있고없고의차이다. 바이너리포맷의경우.text 를지정해주지않으면시작번지를맘대로정해버리므로.text 를지정하지않은 test.1 에서는시작이 0x09048000 으로설정되어있는것을알수있다. 엉뚱한곳의값을사용하도록만들기때문에바이너리를사용할땐제대로된주소가들어가도록.text 를필요한곳으로지정해줄필요가있다. 또.text 의시작을 0x02 로했을때의바이너리는다음과같다. 00000000 90 90 09 00 00 00 90 55 aa 90 위에서보듯이 0xaa55 는 offset 이 7 이지만실제지정된것은 9 로.text 가 2 부터시작하기때문이다. 만약이바이너리를메모리에그대로올려놓는다면제대로된값을읽지못할수도있다. 이경우엔.text 가 2 에서시작하는것을염두에두고메모리에적재해야제대로동작할수있다. 쉽게하기위해선.text 를 0 에서시작하게하면바이너리가메모리의어느위치에있던상관없이잘동작할수있게된다. 2.5.2. 분석 2.4 절에서살펴본것과같은각단계마다자세한내용을살펴본다. 이절이끝나면이제리눅스커널이어떻게만들어지고어떤구조를갖는지완전히알수있을것이다. 1 ~ 12 단계는 vmlinux 를만들기위한한계이고커널설정을어떻게했는가에따라달라지므로여기서는다루지않는다. 또 17, 18 단계도 bvmlinux 를만들기위해필요한단계이므로생략한다. 필요한내용은 2.4 절을참조하거나각자 log 를만들어살펴보기바란다. 1. vmlinux (1) ld -m elf-i386 -T /usr/src/linux-2.4.16/arch/i386/vmlinux.lds -e stext arch/i386/kernel/head.o arch/i386/kernel/init-task.o init/main.o init/version.o \ --start-group \ arch/i386/kernel/kernel.o arch/i386/mm/mm.o kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o \ drivers/acpi/acpi.o drivers/char/char.o drivers/block/block.o drivers/misc/misc.o drivers/net/net.o drivers/media/media.o drivers/char/agp/agp.o drivers/char/drm/drm.o drivers/ide/idedriver.o drivers/cdrom/driver.o drivers/sound/sounddrivers.o drivers/pci/driver.o drivers/pcmcia/pcmcia.o drivers/net/pcmcia/pcmcia-net.o drivers/pnp/pnp.o drivers/video/video.o drivers/md/mddev.o \ 49

net/network.o \ /usr/src/linux-2.4.16/arch/i386/lib/lib.a /usr/src/linux-2.4.16/lib/lib.a /usr/src/linux-2.4.16/arch/i386/lib/lib.a \ --end-group \ -o vmlinux (2) nm vmlinux grep -v '\(compiled\)\ \(\.o$\)\ \( [auw] \)\ \(\.\.ng$\)\ \(LASH[RL]DI\)' sort > System.map (1) vmlinux 는커널자체를의미한다. 그래서링크되는오브젝트들이커널설정에서사용하겠다고한것들과선택된아키텍쳐에관계된것들이뭉쳐져하나의파일을만들어낸다. 사용된옵션은다음과같다. -m elf-i386 어떤포맷으로출력물을만들것인지지정 -T /usr/src/linux-2.4.16/arch/i386/vmlinux.lds 링킹하는데필요한스크립트파일을지정한다. 이파일에대한내용은아래 vmlinux.lds 를참조하기바란다. -e stext 프로그램의시작점을지정한다. 위스크립트에지정된.stext 를사용한다. --start-group... --end-group... 에지정된오브젝트를서로간에참조한변수나함수가에러나지않을때까지계속해서검색한다. -o vmlinux 출력물은 vmlinux 로지정 링크에사용된스크립트 (vmlinux.lds) 는아래와같다. /* ld script to make i386 Linux kernel * Written by Martin Mares <mj@atrey.karlin.mff.cuni.cz>; */ (1) OUTPUT-FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT-ARCH(i386) ENTRY(-start) SECTIONS { (2). = 0xC0000000 + 0x100000; -text =.; /* Text and read-only data */ (3).text : { 50

*(.text) *(.fixup) *(.gnu.warning) } = 0x9090.text.lock : { *(.text.lock) } /* out-of-line lock text */ -etext =.; /* End of text section */.rodata : { *(.rodata) *(.rodata.*) }.kstrtab : { *(.kstrtab) }. = ALIGN(16); /* Exception table */ --start---ex-table =.; --ex-table : { *(--ex-table) } --stop---ex-table =.; --start---ksymtab =.; /* Kernel symbol table */ --ksymtab : { *(--ksymtab) } --stop---ksymtab =.; (4).data : { /* Data */ *(.data) CONSTRUCTORS } -edata =.; /* End of data section */ (5). = ALIGN(8192); /* init-task */.data.init-task : { *(.data.init-task) }. = ALIGN(4096); /* Init code and data */ --init-begin =.;.text.init : { *(.text.init) }.data.init : { *(.data.init) }. = ALIGN(16); --setup-start =.;.setup.init : { *(.setup.init) } --setup-end =.; --initcall-start =.;.initcall.init : { *(.initcall.init) } --initcall-end =.;. = ALIGN(4096); --init-end =.; (6). = ALIGN(4096);.data.page-aligned : { *(.data.idt) }. = ALIGN(32);.data.cacheline-aligned : { *(.data.cacheline-aligned) } --bss-start =.; /* BSS */.bss : { *(.bss) } -end =. ; (7) /* Sections to be discarded */ /DISCARD/ : { *(.text.exit) *(.data.exit) *(.exitcall.exit) } /* Stabs debugging sections. */.stab 0 : { *(.stab) }.stabstr 0 : { *(.stabstr) }.stab.excl 0 : { *(.stab.excl) }.stab.exclstr 0 : { *(.stab.exclstr) } 51

.stab.index 0 : { *(.stab.index) }.stab.indexstr 0 : { *(.stab.indexstr) }.comment 0 : { *(.comment) } } (1) vmlinux 의포맷과아키텍쳐를지정하고프로그램시작점을지정한다. (2) vmlinux 의시작번지를지정한다. 0x100000 은 offset 이고앞의 0xc0000000 은 gdt 내에들어갈때필요한값으로물리적으로는 0x100000 번지를의미한다. 여기서부터는다른부분과달리 gdt 등이설정된상태인프로텍티드모드에서동작하므로메모리관련된것을실제어드레스를사용하면안된다. (3) 커널코드가위치할곳이다. 0x9090 은빈공간에채워넣기할때 0x9090 을사용하란말이다. (4) 특별히지정되지않은모든데이타는여기에위치한다. CONSTRUCTOR 는 C++ constructor 정보를여기에기록하란말이다. (5) arch/i386/kernel/init-task.c 에지정되어있고프로세스스택을다루는방식때문에 8192 bytes 단위로정렬되야한다. (6) arch/i386/traps.c 에정의되어있고 Pentium F0 0F 버그를피하기위한간단한방법으로페이지정렬을사용한다 ( 페이지는 4096 bytes 를의미한다 ). (7) 무시되고사용되지않는섹션으로 vmlinux 에포함되지않는다. 커널이 exit 할일은없기때문이다. (2) nm 은오브젝트파일에서심볼을추줄해주는프로그램이다. 커널이미지파일에서모든심볼을추출해내고이중에필요한부분만을추려 System.map 을만든다. grep 에사용된 -v 는뒤에나오는경우를제외한것들을찾아준다. 커널컴파일이끝난후 'nm vmlinux > test.map' 만한결과와 System.map 을비교해보면 grep 에서찾는것들이어떤것인지알수있을것이다. System.map 은처음부팅때메모리에읽혀올려지고드라이버등이커널심볼을찾을때사용한다. 2. bbootsect gcc -E -D--KERNEL-- -I/usr/src/linux-2.4.16/include -D--BIG-KERNEL-- -traditional - DSVGA-MODE=NORMAL-VGA bootsect.s -o bbootsect.s as -o bbootsect.o bbootsect.s ld -m elf-i386 -Ttext 0x0 -s --oformat binary bbootsect.o -o bbootsect bbootsect.s 는 bootsect.s 를컴파일해만들되 --BIG-KERNEL-- 을정의해만든다. bootsect.s 를살펴보면이와관련된곳이한군데있는것을발견할수있다. ld 에사용된옵션은다음과같다. -m elf-i386 52

elf-i386를에뮬레이션 -Ttext 0x0 text 세그먼트의시작을 0x0으로지정 -s 모든디버깅정보를없앤다 -oformat binary bbootsect의포맷은바이너리 3. bsetup gcc -E -D--KERNEL-- -I/usr/src/linux-2.4.16/include -D--BIG-KERNEL-- -D--ASSEMBLY-- - traditional -DSVGA-MODE=NORMAL-VGA setup.s -o bsetup.s as -o bsetup.o bsetup.s ld -m elf-i386 -Ttext 0x0 -s --oformat binary -e begtext -o bsetup bsetup.o bbootsect 와같은방법을만든다. 4. arch/i386/boot/compressed/piggy.o tmppiggy=-tmp-$$piggy; \ rm -f $tmppiggy $tmppiggy.gz $tmppiggy.lnk; \ objcopy -O binary -R.note -R.comment -S /usr/src/linux-2.4.16/vmlinux $tmppiggy; \ gzip -f -9 < $tmppiggy > $tmppiggy.gz; \ echo "SECTIONS {.data : { input-len =.; LONG(input-data-end - input-data) input-data =.; *(.data) input-data-end =.; }}" > $tmppiggy.lnk; \ ld -m elf-i386 -r -o piggy.o -b binary $tmppiggy.gz -b elf32-i386 -T $tmppiggy.lnk; \ rm -f $tmppiggy $tmppiggy.gz $tmppiggy.lnk piggy.o 는 $(TOPDIR)/vmlinux 를압축해만든다. 우선 vmlinux 에서심볼과필요없는섹션을없애고바이너리형태로만든다음 gzip 을이용해압축한다. 압축된것을다시 elf-i386 형태로만들어놓는다. ld 에사용된옵션은다음과같다. -m elf-i386 elf-i386를메뮬레이션 -b binary $tmppiggy.gz $tmppiggy.gz은바이너리형식 -b elf32-i386 piggy.o는 elf32-i386 형식 -T $tmppiggy.lnk $tmppiggy.lnk를사용해링크한다. 53

$tmppiggy.lnk 의내용은다음과같다. SECTIONS {.data : { input-len =.; LONG(input-data-end - input-data) input-data =.; *(.data) input-data-end =.; } } 압축된 vmlinux 는.data 에들어가게되고 *(.data) 로표시된곳에들어가게된다. 그전후에 LONG(input-data-end - input-data) 로압축된커널의크기를저장한다. 5. arch/i386/boot/compressed/bvmlinux ld -m elf-i386 -Ttext 0x100000 -e startup-32 -o bvmlinux head.o misc.o piggy.o bvmlinux 는압축된커널과 head.o, misc.o 를합쳐만든다. head.o 는메모리세팅이라고보면되고 misc.o 는압축을풀기위한코드가들어있다. ld 에사용된옵션은 $(TOPDIR)/vmlinux 를만들때와거의흡사하다. 단 text 의시작번지는 0x100000 이다. 부팅할때 bvmlinux 는반드시 0x100000 에올려져서실행되야한다. 그렇지않으면제대로동작하지않는다. 압축된커널의크기를 piggy.o 에저장해놓았기때문에메모리의어느위치에서 piggy.o 가끝나는지알수있다. 이뒤에압축을풀어놓고압축이풀린커널을다시 0x100000 으로옮겨와실행한다. 6. build gcc -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -o tools/build tools/build.c - I/usr/src/linux-2.4.16/include build 는 2.3.5 절에서와같이동작하도록만들어진다. 7. bvmlinux.out objcopy -O binary -R.note -R.comment -S compressed/bvmlinux compressed/bvmlinux.out bvmlinux 에서필요없는것을제외하고바이너리로만든다. 8. bzimage tools/build -b bbootsect bsetup compressed/bvmlinux.out CURRENT > bzimage Root device is (3, 1) Boot sector 512 bytes. Setup is 4768 bytes. System is 899 kb 54

build 를사용해 bzimage 를만든다. 지정된루트디바이스, 부트섹터크기, setup 의크기그리고커널의크기를표시해준다. build 의동작은 2.3.5 절을참조바란다. 3. 크로스컴파일러만들기 이장에서는임베디스시스템개발에없어선안될툴체인만드는방법을소개한다. 사용되는타겟은 StrongARM SA1110 을사용하는 Assabet 이란인텔에서발매하는개발용시험보드다. 3.1. 크로스? 임베디드시스템을개발할때일반적으로호스트와타겟이란말을사용하는데이말에대해알아보자. 호스트 임베디드시스템을개발하는과정에서프로그램을개발하는컴퓨터를가리킨다. 예를들어 ARM 프로세서로하드웨어를꾸미고여기에리눅스를 OS 로사용하는프로젝트를생각해보자. 처음하드웨어를만들어커널을올리는작업을할때만들어진하드웨어엔아무프로그램도올라가있지않기때문에다른곳에서커널을만들어하드웨어에심어줘야한다. 리눅스커널을만들어주는시스템을호스트라고한다. 보통은 PC 에서개발해이식할것이므로 PC 가호스트가되겠다. 타겟 임베디드시스템의개발에서만들어지는하드웨어를타겟이라한다. 타겟시스템이라면만들어진임베디드시스템을, 타겟프로세서라면만들어진임베디스시스템의프로세서를말한다고여기면될것이다. 그렇다면크로스컴파일러는무엇일까? 위에서말한대로호스트에서타겟에서돌아가는프로그램을만들어이식해주는데호스트와타겟에사용되는프로세서가다르다면? 필자는호스트로 Athlon CPU 를사용하는 PC 를사용하고있다. 여기에서실행되는 gcc 는 386, 486, 586, K6, 686 을지원한다. 그러나임베디스시스템에사용되는프로세서는지원하지않고있다. 그러므로프로그램을컴파일해도실제임베디드시스템에사용된프로세서에서는실행할수없게된다. 그러므로실행은호스트에서되지만만들어지는코드는타겟시스템에서돌아갈수있는컴파일러가필요해진다. 이것이바로크로스컴파일러다 [1]. 주석 [1] 크로스 (cross) 란말이호스트와타겟이다른단것을나타낸다. 55

3.2. 툴체인 3.2.1. 배경 툴체인이란여러다른컴포넌트로이루어져있다. 가장중요한것은 gcc 컴파일러자체다. 또 gcc 를사하기위해필요한 binutils 도포함된다. binutils 는바이너리를다루는데사용된다. 이둘로 C 라이브러리를사용하는것을제외한나머지대부분과커널을컴파일할수있다. 보통은컴파일러를컴파일하는경우부트스트랩에관계된문제때문에툴체인을만드는것이간단치않다. 이장에서는툴체인을어떻게만드는지알아본다. 툴체인만들기로시간을낭비하기싫거나시도하기싫다면이미만들어진것을사용해도좋다. 경험에의하면혼자만드는툴체인의경우조심하지않으면나중에사용할때어떤형태의이상한결과를만들어낼지모르기때문에검증된이미만들어진툴체인을사용하는것이좋다고감히말할수있다. 또만들어보면알겠지만상당한노력과주의를기울여야하기때문에이미만들어진것을사용하는게좋을것이다. 그러나한번쯤은경험으로라도컴파일러를만들고이것으로프로그램을만들어사용해보기바란다. ARM 프로세서에관한툴체인은 http://www.arm.linux. org.uk 혹은 http://www.armlinux.org 에서찾기바란다. 3.2.2. 미리만들어진툴체인 1.1.1.1 Native Compile ARM 상에서도는 Native 컴파일러의안정화버전바이너리는아래사이에서얻을수있다. 안정화버전 (armv3l 이상 ) - 데비안마스터 FTP 사이트 최신버전 (armv3l 이상 ) - 데비안마스터 FTP 사이트 arm.linux.org.uk 버전공식 ARM 리눅스사이트에서배포하는것이다. cross-2.95.3.tar.bz2 cross-3.0.tar.bz2 Embedian - 필자는데비안을사용하지않기때문에실제이툴이어떤지는잘모른다. Embedian 버전은 gcc 2.95.2, binutils 2.9.5.0.37 과 glibc 2.1.3 을포함한다. 인스톨은간단해데비안시스템을사용하면 apt 를사용해쉽게설치할수있다. 그러나 /usr/bin 에설치되므로이미시스템에깔린것에엎어쓰지않도록주의를요한다. 1.1.1.2 LART LART 타르볼은아래와같은것을포함한다. 56

gcc 2.95.2 binutils 2.9.5.0.22 glibc 2.1.2 설치는아래와같이한다. mkdir /data mkdir /data/lart cd /data/lart bzip2 -dc somewhere/arm-linux-cross.tar.bz2 tar xvf - 그리고 /data/lart/cross/bin 을 path 에추가해준다. C 와 C++ 컴파일러는 arm-linux-gcc 와 armlinux-g++ 와같이명령을사용해실행할수있다. Compaq - Itsy 인가하는프로젝트에있는데사용해보진않았다. 컴팩크로스툴체인은다음과같은것을포함한다. 1. gcc 2.95.2 2. binutils 2.9.5.0.22 3. glibc 2.1.2 with international crypt library 이툴체인은호스트는 i386, 타겟은 armv4l 이다. 설치는아래와같이한다. 이툴체인은반드시 /skiff/local 에설치되야한다. 그리고아래와같이심볼릭링크를만들어줘야한다. ln -s /usr/src/linux/include/asm /skiff/local/arm-linux/include/asm ln -s /usr/src/linux/include/linux /skiff/local/arm-linux/include/linux 3.2.3. 툴체인만들기 우선크로스컴파일러를만들어보자. root 로시작하는것이좋겠다. 혹은적어도설치할땐 root 로할필요가있다. binutils 와 gcc 를다음사이트에서다운받는다. 인텔 SA1100 Assabet 보드에관한리눅스개발사이트를참조하면보다자세한사항을알수있다. 인텔사이트. binutils-2.11.2.tar.gz gcc-2.95.3.tar.gz 57

linux-2.4.17.tar.bz2 patch-2.4.17-rmk5.gz glibc-2.1.3.tar.gz glibc-linuxthreads-2.1.3.tar.gz glibc-linuxthreads-2.1.tar.gz 받은파일이 /devel/arm/assabet 에들었다고가정하고그디렉토리에서다음과같은절차를사용해컴파일러를만든다. 1. binutils %cd /devel/arm/assabet %tar xzf binutils-2.11.2.tar.gz %cd binutils-2.11.2 %./configure --target=arm-linux --prefix=/usr/local/arm %make %make install configure 할때의 --target 은만들어질 binutils 가 arm-linux 용이란것을나타내고, --prefix 는만들어진 binutils 가설치될디렉토리를나타낸다. 설치된디렉토리의리스트는다음과같이될것이다. drwxr-xr-x 4 root root 16 2월 26 09:58 arm-linux drwxr-xr-x 2 root root 4096 2월 26 09:58 bin drwxr-xr-x 2 root root 48 2월 26 09:58 include drwxr-xr-x 2 root root 4096 2월 26 09:58 lib drwxr-xr-x 3 root root 8 2월 26 09:58 man drwxr-xr-x 2 root root 1 2월 26 09:58 share 정상적으로만들어졌으면다음절차에서사용되게하기위해 path 에임시로추가해준다. bash 를사용한다고가정했다. export PATH=/usr/local/arm/bin:$PATH 2. gcc gcc 를만들기위해선리눅스커널의헤더파일이필요하다. 우선커널소스를풀고 2 개의디렉토리를링크해준다. 사용된커널은원하는버전을받아사용하기바란다. 여기선 2.4.17 을사용했다. %cd /devel/arm/assabet %tar xvjf linux-2.4.17.tar.bz2 %mv linux linux-2.4.17 %cd /usr/local/arm/arm-linux %mkdir include %cd include %ln -s /devel/arm/assabet/linux-2.4.17/include/asm-arm asm 58

%ln -s /devel/arm/assabet/linux-2.4.17/include/linux linux %ls asm %ls linux %cd /devel/arm/assabet %tar xzf gcc-2.95.3.tar.gz %cd gcc-2.95.3 %./configure --target=arm-linux --prefix=/usr/local/arm %make LANGUAGES="c" make 를진행하는중에 stdlib.h, unistd.h 와같은몇개의파일이없다고에러가날것이다. ARM 리눅스사이트에서얻은정보에의하면, gcc/config/arm/t-linux 란파일을수정하라고되어있다. 아래와같이수정한다. %cd gcc/config/arm %vi t-linux 제일위에있는줄을찾아보면 'TARGET_LIBGCC2_CFLAGS=' 란줄이있을것이다. 이줄의끝에 '-Dinhibit_libc -D gthr_posix_h' 를추가해준다. 그리고 configure 를실행할때 -- disable-threads 를추가해주고다시 configure 과 make 를실행한다. %./configure --target=arm-linux --prefix=/usr/local/arm --disable-threads %make LANGUAGES="c" 컴파일후에러가나는것처럼보이지만에러나도필요한파일은만들어졌으므로아래명령으로확인한다. 만약 gcc/xgcc 가없다면 make 가제대로되지않은것이다. 아래명령의출력은 'arm-linux' 가된다. %./gcc/xgcc -dumpmachine arm-linux 위와같이출력된다면제대로된것이므로인스톨한다. %make LANGUAGES="c" install 3. glibc 를만드는절차에선커널의 version.h 가필요하다. version.h 는 make dep 를하면생성된다. %cd /devel/arm/assabet %cd linux-2.4.17 %zcat../patch-2.4.17-rmk5.gz patch -p1 patch 까지적용하고나서 Makefile 을수정한다. /devel/arm/assabet/linux-2.4.17/makefile 을열어 'ARCH := arm' 으로수정하고 'CROSS_COMPILE =/usr/local/arm/bin/arm-linux-' 으로수정한다. %make assabet_config %make config 59

그냥 enter 만쳐서일단 default setting 으로저장한다. %make dep %find -name "version.h" version.h 가 include/linux 에없다면에러! glibc 가설치될디렉토리를만들어준다. %mkdir /usr/local/arm/glibc %mkdir /usr/local/arm/glibc/arm-linux-glibc %cd /devel/arm/assabet %tar xzf glibc-2.1.3.tar.gz %cd glibc-2.1.3 %tar xzf../glibc-linuxthreads-2.1.3.tar.gz %tar xzf../glibc-crypt-2.1.tar.gz %CC=arm-linux-gcc./configure arm-linux --build=i686-pc-linux-gnu \ --prefix=/usr/local/arm/glibc/arm-linux-glibc --enable-add-ons \ --with-headers=/devel/arm/assabet/linux-2.4.17/include %make %make install 속도가느린호스트를사용한다면아마도집에퇴근하기전에 make 실행해놓고집에갔다오면에러가나있던지제대로컴파일이되어있던지할것이다. 전에 P-II 400MHz 에서약 1 시간걸린기억이있다. 현재 Athlon 1GHz 에서약 10 분걸린것같다. 4. c++ c++ 컴파일러도만들어보자. %cd /devel/arm/assabet %cd gcc-2.95.3 %./configure --host=i686-pc-linux-gnu --target=arm-linux \ --prefix=/usr/local/arm \ --with-headers=/usr/local/arm/glibc/arm-linux-glibc/include \ --with-libs=/usr/local/arm/glibc/arm-linux-glibc/lib %make LANGUAGES="c c++" %make LANGUAGES="c c++" install 5. 테스트 다만들어진컴파일러, 라이브러리를테스트해본다. 테스트전에 /usr/local/arm/bin 이패스에설정됐는지확인. 없다면넣어주거나아래 compiler 명령에서적당히 path 를삽입해줄것. %cat > hello.c /* * hello.c */ include <stdio.h> int main() { 60

} printf("hello.\n"); return 0; %arm-linux-gcc -o hello hello.c 에러없이컴파일되고 hello 가만들어졌는지확인할것. 그리고 file 명령으로만들어진 hello 가어떤내용인지확인해본다. 아래처럼 ELF 이고 ARM 용이면 OK. %file hello hello: ELF 32-bit LSB executable, ARM, version 1, dynamically linked (uses shared libs), not stripped 여기까지툴체인을만들어봤다. 커널이잘돌아간다면만든툴체인은잘동작한다고봐도될것이다. 4. ARM 리눅스 이장에선인텔의개발보드인 Assabet 을이용해 ARM 리눅스커널을올리는방법에대해알아본다. Assabet 보드는 StrongARM SA1110 을사용하고 LCD 등이달려있다. 이전에 SA1100 을사용하던 Brutus 보드와마찬가지로리눅스커널에서공식지원하고있는보드다. 이미많은사람들이이보드에서개발을하고자신만의플랫폼을만들기때문에표준이라할수있다. 게다가인텔사이트에서는친절하게도회로도를공개하고있기때문에많은개발자가별수없이 (?) 이프로세서와함께 Assabet 보드를사용한다. 4.1. ARM 프로세서 MMU(Memory Management Unit) ARM 리눅스를시작하기전에 ARM 프로세서에대해충분히알아야쉽게이해할수있을것이다. 그러나모든것을다루긴힘들고여기선 MMU 에대해다루고가상어드레스를어떻게이해하면되는지정도를습득하면되겠다. 이절의그림과글은모두 ARM architectural Reference Manual(Dave Jaggar 저 ) 를바탕으로번역했다. 4.1.1. 개요 ARM 프로세서의 MMU 는다음의큰두가지일을한다. 가상어드레스를물리어드레스로변환메모리접근권한제어그리고 MMU는위의일을하기위해다음과같은하드웨어를갖고있다. 적어도하나의 TLB(Translation Lookaside Buffer) 61

접근제어로직 translation-table-walking 로직 4.1.1.1. TLB TLB 는가상어드레스를물리어드레스로변환하는것과접근권한을캐싱하고있다. 만약 TLB 가가상어드레스에대한변환된엔트리를갖고있다면접근제어로직은접근이가능한지판별한다. 접근이허용된다면 MMU 는가상어드레스에대한물리어드레스를출력해준다. 접근이허용되지않는경우엔 MMU 가 CPU 에서 abort 시그널을보낸다. TLB 가없다면 ( 가상어드레스에대한변환된엔트리를갖고있지않다 ) 하드웨어를움직이는변환테이블은물리메모리내에있는변환테이블에서정보를읽어온다. 일단읽어온후엔그정보가 TLB 에저장된다. 이때원래있던엔트리는지워질지워질수도있다. 4.1.1.2. 4.1.1.2. 메모리접근 MMU 는두가지의메모리접근방식을지원한다. 섹션 1MB 블럭단위로메모리제어 페이지페이지방식엔또두가지방식이있다. a. small page - 4kB 블럭메모리 b. large page - 64kB 블럭메모리 섹션과큰페이지방식은 TLB 에하나의엔트리만이있을때큰메모리영역을매핑하는데사용된다. 추가로작은페이지방식은 1kB 서브페이지로확장되고큰페이지방식은 16kB 로확장된다. 4.1.1.3. 4.1.1.3. 변환테이블 주메모리내의변환테이블은두가지레벨을갖고있다. 1 레벨테이블 섹션변환과섹션레벨테이블에대한포인터를갖고있다. 2 레벨테이블 62

큰 / 작은페이지변환을갖고있다. 4.1.1.4. 4.1.1.4. 도메인 MMU 는도메인이란기능도제공한다. 이것은개별적접근권한을갖도록정의된메모리영역을말한다. DACR(Domain Access Control Register) 를사용해 16 개까지의도메인을지정할수있다. MMU 가 off 상태일때 ( 프로세서가리셋된직후가이렇다 ) 가상어드레스의출력은직접물리어드레스를가리키고메모리접근권한검사는하지않는다. 두 TLB 엔트리가중첩된메모리영역을가리는경우는예측할수없는일이발생한다. 이런경우는다른크기의페이지로재매핑된후 TLB 를갱신하지않아발생할수있다. 4.1.2. 변환절차 MMU 는 CPU 에의해만들어진가상어드레스를외부메모리에접근하기위한물리어드레스로변환한다. 그리고접근권한검사도병행한다. 어드레스가변환되는방식은섹션방식인가페이지방식 ( 페이지방식엔또두가지크기의페이지가있다 ) 인가에따라 3 가지가있다. 그러나변환절차는언제나같은식으로 1 레벨읽기로부터시작된다. 섹션방식은 1 레벨만필요하고페이지방식은 2 레벨도사용해야한다. 4.1.3. 변환테이블베이스 변환절차는요청된가상어드레스에대한엔트리가칩에내장된 TLB 에없을때부터시작된다. TTBR(Translation Table Base Register) 는 1 레벨테이블의베이스를가리키고 TTBR 의 14 에서 31 비트만이사용된다. 나머지는 0 이어야한다. 그러므로 1 레벨페이지테이블은반드시 16KB 단위로정렬되야한다 (214=16384). 4.1.4. 1 레벨읽기 TTBR 의비트 31:14 는그림 4-1 에서보듯이 30 비트어드레스를만들기위해가상어드레스의 31:20 비트와연결된다. 이어드레스는섹션용 1 레벨디스크립나 2 레벨페이지테이블포인터용디스크립터를나타내는 4 바이트길이의변환테이블엔트리를선택한다. 그림 4-1. 변환테이블 1 레벨디스크립터접근 63

4.1.5. 1 레벨디스크립터 1 레벨디스크립터는섹션디스크립터나 2 레벨페이지테이블포인터가될수있고포맷을그에따라변한다. 그림 4-2 에서비트 1:0 이디스크립터타입을나타낸다. 비트 1:0 이 00 인디스크립터를사용하면변환폴트를발생한다. 비트 1:0 이 11 인디스크립터는어떻게동작할지모른다. 그림 4-2. 1 레벨디스크립터포맷 4.1.6. 섹션디스크립터와섹션변환 그림 4-3 은섹션변환전체를나타낸다. 1 레벨디스크립터에포함된접근권한은물리어드레스를만들어내기전에먼저체크된다. 그림 4-3. 섹션변환 64

1 레벨디스크립터가섹션디스크립터인경우각필드는다음과같은의미를갖는다. 표 4-1. 섹션디스크립터필드 비트 1:0 디스크립터의타입을나타냄 (10은섹션디스크립터를의미함 ) 비트 3:2 캐시가능, 버퍼가능비트 비트 4 이비트의의미는 'IMPLEMENTATION DEPENDENt' 비트 8:5 이디스크립터에의해조정되는모든페이지에대한 16개까지의도메인지정 비트 9 사용되지않음. SHOULD BE ZERO 비트 11:10 접근권한 비트 19:12 사용되지않음. SHOULD BE ZERO 비트 31:20 물리어드레스의상위 12비트를구성하는섹션베이스어드레스 4.1.7. 페이지테이블디스크립터페이지테이블디스크립터를 1 레벨에서읽고나면 2 레벨디스크립터읽기가시작된다. 그림 4-4 에나타나있다. 그림 4-4. 2 레벨디스크립터접근 65

1 레벨디스크립터가페이지테이블디스크립터인경우각필드는다음과같은의미를갖는다. 표 4-2. 페이지디스크립터필드 비트 1:0 디스크립터의타입을나타냄 (01은페이지디스크립터를의미함 ) 비트 4:2 이비트의의미는 'IMPLEMENTATION DEPENDENt' 이디스크립터에의해조정되는모든페이지에대한 16개까지의도메인지 비트 8:5 정 비트 9 사용되지않음. SHOULD BE ZERO 페이지테이블베이스어드레스는 2레벨페이지테이블포인터이다. 2레벨페 비트 31:10 이지테이블은 1KB로정렬되야한다 (210=1024) 4.1.8. 2 레벨디스크립터 2 레벨디스크립터는큰페이지혹은작은페이지로정의된다. 그림 4-5. 2 레벨디스크립터포맷 각필드는다음과같은의미를갖는다. 66

표 4-3. 2 레벨디스크립터포맷 비트 1:0 비트 3:2 비트 11:4 비트 15:12 비트 31:12 비트 31:16 디스크립터의타입을나타냄캐시가능, 버퍼가능비트접근권한사용되지않음. SHOULD BE ZERO 작은페이지모드에서물리어드레스를만드는데사용됨큰페이지모드에서물리어드레스를만드는데사용됨 비트 11:4 의접근권한은다음과같은 4 가지의서브페이지로나뉜다. 표 4-4. 2 레벨디스크립터접근권한 AP0 1 서브페이지에대한접근권한 AP1 2 서브페이지에대한접근권한 AP2 3 서브페이지에대한접근권한 AP3 4 서브페이지에대한접근권한 4.1.9. 큰페이지변환 그림 4-6 은큰페이지변환을나타낸다. 페이지인텍스의상위 4 비트와 2 레벨테이블인덱스의하위 4 비트는서로겹치는데큰페이지에대한각페이지테이블엔트리는페이지테이블내에 16 번복사되야한다. 그림 4-6. 큰페이지변환 67

4.1.10. 작은페이지변환 그림 4-7 은작은페이지변환을나타낸다. 그림 4-7. 작은페이지변환 68

4.1.11. 캐시와쓰기버퍼제어 ARM 메모리시스템은각가상페이지마다개별적으로선택가능한두가지속성에의해제어된다. 표 4-5. 메모리시스템속성 Cacheable Bufferable 이속성은페이지내의데이터가캐시될수있음을나타낸다. 이렇게하면다음이어지는동작은메인메모리를읽을필요가없게된다. 또현재실행점을넘어미리명령을읽을수있음을나타내기도한다. 캐시는 write-back 혹은 write-through로만들어질수있다 ( 각가상페이지마다개별적으로설정할수있다 ). 페이지내의데이터가쓰기버퍼에저장될수있음을나타내고이렇게하면메인메모리보다빠른동작을할수있게된다. 쓰기버퍼는정확한쓰기명령을보장하지않는다. 그러므로같은위치에대한여러번의쓰기동작이여러번의외부쓰기동작을한다는보장은없다. Cacheable 이속성은페이지내의데이터가캐시될수있음을나타낸다. 이렇게하면다음 69

이어지는동작은메인메모리를읽을필요가없게된다. 또현재실행점을넘어미리명령을읽을수있음을나타내기도한다. 캐시는 write-back 혹은 write-through 로만들어질수있다 ( 각가상페이지마다개별적으로설정할수있다 ). Bufferable 페이지내의데이터가쓰기버퍼에저장될수있음을나타내고이렇게하면메인메모리보다빠른동작을할수있게된다. 쓰기버퍼는정확한쓰기명령을보장하지않는다. 그러므로같은위치에대한여러번의쓰기동작이여러번의외부쓰기동작을한다는보장은없다. 캐시와쓰기버퍼비트값은다음과같은의미를갖는다. 표 4-6. 캐시, 쓰기버퍼비트의의미 C B 의미 0 0 캐시불가능, 쓰기버퍼불가능 0 1 캐시불가능, 쓰기버퍼동작 1 0 캐시동작, 쓰기버퍼불가능혹은 write-through 캐시, 쓰기버퍼동작 1 1 캐시동작, 쓰기버퍼동작혹은 write-back 캐시, 쓰기버퍼동작 두가지타입의캐시를모두지원하려면 10 으로 write-through, 11 으로 writeback 캐시를지정한다. 어느한가지타입의캐시만을지원하려면 C 와 B 비트를엄격히적용해캐시가능과쓰기버퍼가능이각각동작하도록한다. 4.1.12. 접근권한 섹션과페이지디스크립터내의접근권한비트는해당섹션이나페이지의접근을제어한다. 접근권한은시스템 (S) 과롬 (R) 제어비트에의해변경된다. 테이블은 S, R 비트와결합된접근권한비트의의미를나타낸다. 필요한권한없이메모리에접근하면퍼미션폴트가발생한다. 표 4-7. 접근권한 AP S 권한 R 수퍼바이저유저 0 0 0 접근불가 접근불가 0 1 0 읽기만가능접근불가 0 0 1 읽기만가능읽기만가능 0 1 1 예측불가능 1 x x 읽기 / 쓰기 접근불가능 10 x x 읽기 / 쓰기 읽기만가능 11 x x 읽기 / 쓰기 읽기 / 쓰기 4.2. Assabet 보드용커널컴파일 커널컴파일에필요한기본툴체인을만들어두거나다운로드받아서설치해둔후진행하기바란다. 그외에필요한 file 은아래의것을받기바란다. 70

linux-2.4.17.tar.bz2 patch-2.4.17-rmk5.gz ramdisk_ks.gz angelboot-1.10.nk.tar.gz 1. 커널컴파일 우선커널소스를풀고필요한패치파일을적용해놓는다. %cd /devel/arm/assabet %tar xvjf linux-2.4.17.tar.bz2 %mv linux linux-2.4.17 %cd linux-2.4.17 %zcat../patch-2.4.17-rmk5.gz patch -p1 patch 까지적용하고나서 Makefile 을수정한다. /devel/arm/assabet/linux-2.4.17/makefile 을열어 'ARCH := arm' 으로수정하고 'CROSS_COMPILE =/usr/local/arm/bin/arm-linux-' 으로수정한다. %cd /devel/arm/assabet %cd linux-2.4.17 %make assabet_config %make menuconfig 여기선 Assabet 보드에사용할커널을가정했기때문에그냥기본을사용해도되지만각자에게맞는커널설정을한후컴파일하면된다. make menuconfig 후기본설정으로동작시키려면그냥 exit 하면서 configuration 만저장하면된다. assabet_config 외에도아래와같은다른설정이있다. 참조바란다. a. a5k_config b. ebsa110_config c. footbridge_config d. rpc_config e. brutus_config f. victor_config g. empeg_config 71

%make dep %make modules %make zimage %make modules_install INSTALL_MOD_PATH=/devel/arm/assabet/modules module 을설정했다면 make modules 가있어야한다. 그리고호스트에설치할것이아니기때문에일단 /devel/arm/assabet/modules 에설치하고 ramdisk 에넣어주면된다. arch/arm/boot/zimage 가만들어졌는지확인. -rw-r--r-- 1 root root 3718 2월 26 11:39 Makefile drwxr-xr-x 2 573 573 24 10월 12 01:04 bootp drwxr-xr-x 2 573 573 4096 2월 26 14:27 compressed -rw-r--r-- 1 573 573 1350 1월 21 1998 install.sh -rwxr-xr-x 1 root root 728036 2월 26 14:27 zimage 2. 램디스크설정 Assabet 보드에다운로드될램디스크이미지는파일로만들어져있으므로 loopback device 를사용해수정해야한다. %cd /devel/arm/assabet %mkdir ramdisk %cd ramdisk %mkdir rdisk %cp../ramdisk_ks.gz. %gunzip ramdisk_ks.gz %losetup /dev/loop0 ramdisk_ks %mount /dev/loop0 rdisk 이렇게하면 rdisk 란디렉토리에램디스크이미지가마운트되므로만들어진 module 등을넣거나사용자가만든프로그램을넣어서 Assabet 보드에다운로드후실행해볼수있다. 3. 커널테스트 angelboot 를컴파일해실행파일을만들어놓고아래와같은내용의파일을만들어둔다. minicom 은 ttys1/9600/8n1 으로맞춰둔다. 시리얼포트는사용자에따라달리변경하면된다. %cd /devel/arm/assabet %tar xzf angelboot-1.10.nk.tar.gz %cd angelboot-1.10.nk %make %cd.. %cat > opts base 0xc0008000 entry 0xc0008000 r0 0x00000000 r1 0x00000019 device /dev/ttys1 options "9600 8N1" baud 115200 otherfile ramdisk_ks.gz otherbase 0xc0800000 exec minicom 72

%./angelboot-1.10-nk/angelboot -f opts./linux-2.4.17/arch/arm/boot/zimage 커널은 0xc0008000 에올려지고시작도거기서부터시작된다. 램디스크는 0xc0800000 에올려진다. r0, r1 의값을전달하는데이값은커널부팅에사용되는값이다. r1 은아키텍쳐를구분해주는번호인데 $(TOPDIR)/arch/arm/tools/mach-types 에정의되어있다. Assabet 보드의경우 25. 여기까지실행되고나면 Assabet 보드의 LCD 에펭귄이보일것이고 mincom 엔로그인화면이나올것이다. 4.3. ARM 리눅스 Makefile 분석 2 장에서분석한것과같이 ARM 리눅스의 Makefile 도 i386 의것과비슷하다. 그러나부팅하는과정등의일부가 PC 가아닌다른시스템이기때문에많이다르다. 그럼에도불구하고대부분비슷한방법으로만들어지고실행된다. arch/arm 이하의것만제외한다면나머지는 i386 의것과동일하므로 2 장을참조하고나머지 ARM 리눅스에관련된부분만다룬다. 4.3.1. $(TOPDIR)/arch/arm/Makefile Assabet 보드용기본설정에해당하는.Config file 의내용은아래와같다. 이를참조해아래 Makefile 분석을좇아가기바란다. 세팅된것만추리고나머지는버렸다. Automatically generated by make menuconfig: don't edit CONFIG_ARM=y CONFIG_UID16=y CONFIG_RWSEM_GENERIC_SPINLOCK=y Code maturity level options CONFIG_EXPERIMENTAL=y Loadable module support CONFIG_MODULES=y System Type CONFIG_ARCH_SA1100=y SA11x0 Implementations CONFIG_SA1100_ASSABET=y CONFIG_SA1100_USB=m CONFIG_SA1100_USB_NETLINK=m CONFIG_CPU_32=y CONFIG_CPU_32v4=y CONFIG_CPU_SA1100=y 73

CONFIG_DISCONTIGMEM=y General setup CONFIG_PCI is not set CONFIG_ISA=y CONFIG_CPU_FREQ=y CONFIG_HOTPLUG=y PCMCIA/CardBus support CONFIG_PCMCIA=y CONFIG_PCMCIA_PROBE=y CONFIG_PCMCIA_SA1100=y CONFIG_NET=y CONFIG_SYSVIPC=y CONFIG_SYSCTL=y CONFIG_FPE_NWFPE=y CONFIG_KCORE_ELF=y CONFIG_BINFMT_ELF=y CONFIG_PM=y CONFIG_CMDLINE="root=1f04 mem=32m" CONFIG_LEDS=y CONFIG_LEDS_TIMER=y CONFIG_LEDS_CPU=y CONFIG_ALIGNMENT_TRAP=y Memory Technology Devices (MTD) CONFIG_MTD=y CONFIG_MTD_PARTITIONS=y CONFIG_MTD_REDBOOT_PARTS=y CONFIG_MTD_CHAR=y CONFIG_MTD_BLOCK=y RAM/ROM/Flash chip drivers CONFIG_MTD_CFI=y CONFIG_MTD_GEN_PROBE=y CONFIG_MTD_CFI_ADV_OPTIONS=y CONFIG_MTD_CFI_NOSWAP=y CONFIG_MTD_CFI_GEOMETRY=y CONFIG_MTD_CFI_B4=y CONFIG_MTD_CFI_I2=y CONFIG_MTD_CFI_INTELEXT=y Mapping drivers for chip access CONFIG_MTD_SA1100=y Block devices CONFIG_BLK_DEV_LOOP=m CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_SIZE=4096 CONFIG_BLK_DEV_INITRD=y Networking options CONFIG_UNIX=y CONFIG_INET=y Network device support 74

CONFIG_NETDEVICES=y Ethernet (10 or 100Mbit) CONFIG_NET_ETHERNET=y PCMCIA network device support CONFIG_NET_PCMCIA=y CONFIG_PCMCIA_PCNET=y IrDA (infrared) support CONFIG_IRDA=m CONFIG_IRLAN=m Infrared-port device drivers CONFIG_SA1100_FIR=m ATA/IDE/MFM/RLL support CONFIG_IDE=y IDE, ATA and ATAPI Block devices CONFIG_BLK_DEV_IDE=y CONFIG_BLK_DEV_IDEDISK=y CONFIG_BLK_DEV_IDECS=y Character devices CONFIG_VT=y Serial drivers CONFIG_SERIAL_SA1100=y CONFIG_SERIAL_SA1100_CONSOLE=y CONFIG_SA1100_DEFAULT_BAUDRATE=38400 CONFIG_SERIAL_8250=m CONFIG_SERIAL_CORE=y CONFIG_SERIAL_CORE_CONSOLE=y CONFIG_UNIX98_PTYS=y CONFIG_UNIX98_PTY_COUNT=32 L3 serial bus support CONFIG_L3=y CONFIG_L3_ALGOBIT=y CONFIG_L3_BIT_SA1100_GPIO=y CONFIG_BIT_SA1100_GPIO=y Watchdog Cards CONFIG_SA1100_RTC=y PCMCIA character devices CONFIG_PCMCIA_SERIAL_CS=m 75

File systems CONFIG_FAT_FS=y CONFIG_MSDOS_FS=y CONFIG_JFFS2_FS=y CONFIG_JFFS2_FS_DEBUG=0 CONFIG_TMPFS=y CONFIG_PROC_FS=y CONFIG_DEVPTS_FS=y CONFIG_EXT2_FS=y Network File Systems CONFIG_NFS_FS=y CONFIG_SUNRPC=y CONFIG_LOCKD=y Partition Types CONFIG_PARTITION_ADVANCED=y CONFIG_MSDOS_PARTITION=y CONFIG_NLS=y Native Language Support CONFIG_NLS_DEFAULT="iso8859-1" CONFIG_NLS_CODEPAGE_437=y Console drivers CONFIG_PC_KEYMAP=y Frame-buffer support CONFIG_FB=y CONFIG_DUMMY_CONSOLE=y CONFIG_FB_SA1100=y CONFIG_FBCON_CFB2=y CONFIG_FBCON_CFB4=y CONFIG_FBCON_CFB8=y CONFIG_FBCON_CFB16=y CONFIG_FBCON_FONTWIDTH8_ONLY=y CONFIG_FBCON_FONTS=y CONFIG_FONT_8x8=y Sound CONFIG_SOUND=y CONFIG_SOUND_SA1100=y CONFIG_SOUND_UDA1341=y CONFIG_SOUND_ASSABET_UDA1341=y Multimedia Capabilities Port drivers CONFIG_MCP=y CONFIG_MCP_SA1100=y CONFIG_MCP_UCB1200=y CONFIG_MCP_UCB1200_AUDIO=m CONFIG_MCP_UCB1200_TS=y Kernel hacking CONFIG_DEBUG_USER=y 76

arch/arm/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) 1995-2001 by Russell King LINKFLAGS :=-p -X -T arch/arm/vmlinux.lds GZFLAGS :=-9 CFLAGS +=-fno-common -pipe ifneq ($(CONFIG_NO_FRAME_POINTER),y) CFLAGS :=$(CFLAGS:-fomit-frame-pointer=) endif ifeq ($(CONFIG_DEBUG_INFO),y) CFLAGS +=-g endif Select CPU dependent flags. Note that order of declaration is important; the options further down the list override previous items. Note! For APCS-26 YOU MUST HAVE AN APCS-26 LIBGCC.A apcs-y :=-mapcs-32 apcs-$(config_cpu_26) :=-mapcs-26 -mcpu=arm3 -Os (1) This selects which instruction set is used. arch-y := arch-$(config_cpu_32v3) :=-march=armv3 arch-$(config_cpu_32v4) :=-march=armv4 arch-$(config_cpu_32v5) :=-march=armv5 (2) This selects how we optimise for the processor. tune-y := tune-$(config_cpu_arm610) :=-mtune=arm610 tune-$(config_cpu_arm710) :=-mtune=arm710 tune-$(config_cpu_arm720t) :=-mtune=arm7tdmi tune-$(config_cpu_arm920t) :=-mtune=arm9tdmi tune-$(config_cpu_arm922t) :=-mtune=arm9tdmi tune-$(config_cpu_arm926t) :=-mtune=arm9tdmi tune-$(config_cpu_sa110) :=-mtune=strongarm110 tune-$(config_cpu_sa1100) :=-mtune=strongarm1100 CFLAGS_BOOT CFLAGS AFLAGS :=$(apcs-y) $(arch-y) $(tune-y) -mshort-load-bytes -msoft-float +=$(apcs-y) $(arch-y) $(tune-y) -mshort-load-bytes -msoft-float +=$(apcs-y) $(arch-y) -mno-fpu -msoft-float ifeq ($(CONFIG_CPU_26),y) PROCESSOR = armo ifeq ($(CONFIG_ROM_KERNEL),y) DATAADDR = 0x02080000 TEXTADDR = 0x03800000 LDSCRIPT = arch/arm/vmlinux-armo-rom.lds.in else TEXTADDR = 0x02080000 LDSCRIPT = arch/arm/vmlinux-armo.lds.in endif endif (3) ifeq ($(CONFIG_CPU_32),y) PROCESSOR = armv TEXTADDR = 0xC0008000 LDSCRIPT = arch/arm/vmlinux-armv.lds.in endif 77

ifeq ($(CONFIG_ARCH_ARCA5K),y) MACHINE = arc endif ifeq ($(CONFIG_ARCH_RPC),y) MACHINE = rpc endif ifeq ($(CONFIG_ARCH_EBSA110),y) MACHINE = ebsa110 endif ifeq ($(CONFIG_ARCH_CLPS7500),y) MACHINE = clps7500 INCDIR = cl7500 endif ifeq ($(CONFIG_FOOTBRIDGE),y) MACHINE = footbridge INCDIR = ebsa285 endif ifeq ($(CONFIG_ARCH_CO285),y) TEXTADDR = 0x60008000 MACHINE = footbridge INCDIR = ebsa285 endif ifeq ($(CONFIG_ARCH_FTVPCI),y) MACHINE = ftvpci INCDIR = nexuspci endif ifeq ($(CONFIG_ARCH_TBOX),y) MACHINE = tbox endif ifeq ($(CONFIG_ARCH_SHARK),y) MACHINE = shark endif (4) ifeq ($(CONFIG_ARCH_SA1100),y) ifeq ($(CONFIG_SA1111),y) SA1111 DMA bug: we don't want the kernel to live in precious DMA-able memory TEXTADDR = 0xc0208000 endif MACHINE = sa1100 endif ifeq ($(CONFIG_ARCH_L7200),y) MACHINE = l7200 endif ifeq ($(CONFIG_ARCH_INTEGRATOR),y) MACHINE = integrator endif ifeq ($(CONFIG_ARCH_CAMELOT),y) MACHINE = epxa10db endif ifeq ($(CONFIG_ARCH_CLPS711X),y) TEXTADDR = 0xc0028000 MACHINE = clps711x endif ifeq ($(CONFIG_ARCH_FORTUNET),y) TEXTADDR = 0xc0008000 endif 78

ifeq ($(CONFIG_ARCH_ANAKIN),y) MACHINE = anakin endif export MACHINE PROCESSOR TEXTADDR GZFLAGS CFLAGS_BOOT Only set INCDIR if its not already defined above Grr,?= doesn't work as all the other assignment operators do. Make bug? ifeq ($(origin INCDIR), undefined) INCDIR := $(MACHINE) endif ifeq ($(origin DATAADDR), undefined) DATAADDR :=. endif (5) If we have a machine-specific directory, then include it in the build. MACHDIR := arch/arm/mach-$(machine) ifeq ($(MACHDIR),$(wildcard $(MACHDIR))) SUBDIRS += $(MACHDIR) CORE_FILES := $(MACHDIR)/$(MACHINE).o $(CORE_FILES) endif (6) HEAD := arch/arm/kernel/head-$(processor).o \ arch/arm/kernel/init_task.o SUBDIRS += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe CORE_FILES := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES) LIBS := arch/arm/lib/lib.a $(LIBS) ifeq ($(CONFIG_FPE_NWFPE),y) LIBS := arch/arm/nwfpe/math-emu.o $(LIBS) endif Only include fastfpe if it is part of the kernel tree. FASTFPE := arch/arm/fastfpe ifeq ($(FASTFPE),$(wildcard $(FASTFPE))) SUBDIRS += $(FASTFPE) ifeq ($(CONFIG_FPE_FASTFPE),y) LIBS := arch/arm/fastfpe/fast-math-emu.o $(LIBS) endif endif ifeq ($(findstring y,$(config_arch_clps7500) $(CONFIG_ARCH_L7200)),y) SUBDIRS += drivers/acorn/char DRIVERS += drivers/acorn/char/acorn-char.o endif MAKEBOOT = $(MAKE) -C arch/$(arch)/boot MAKETOOLS = $(MAKE) -C arch/$(arch)/tools The following is a hack to get 'constants.h' up to date before starting compilation $(patsubst %,_dir_%, $(SUBDIRS)): maketools $(patsubst %,_modsubdir_%,$(mod_dirs)): maketools symlinks: archsymlinks archsymlinks: $(RM) include/asm-arm/arch include/asm-arm/proc (cd include/asm-arm; ln -sf arch-$(incdir) arch; ln -sf proc-$(processor) proc) vmlinux: arch/arm/vmlinux.lds (7) arch/arm/vmlinux.lds: $(LDSCRIPT) dummy @sed 's/textaddr/$(textaddr)/;s/dataaddr/$(dataaddr)/' $(LDSCRIPT) >$@ arch/arm/kernel arch/arm/mm arch/arm/lib: dummy $(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" $(subst $@, _dir_$@, $@) 79

bzimage zimage zinstall Image bootpimage install: vmlinux @$(MAKEBOOT) $@ CLEAN_FILES += \ arch/arm/vmlinux.lds MRPROPER_FILES += \ include/asm-arm/arch \ include/asm-arm/proc \ include/asm-arm/constants.h* \ include/asm-arm/mach-types.h We use MRPROPER_FILES and CLEAN_FILES now archmrproper: @/bin/true archclean: @$(MAKEBOOT) clean archdep: scripts/mkdep archsymlinks @$(MAKETOOLS) dep @$(MAKEBOOT) dep we need version.h maketools: checkbin include/linux/version.h @$(MAKETOOLS) all Ensure this is ld "2.9.4" or later NEW_LINKER := $(shell $(LD) --gc-sections --version >/dev/null 2>&1; echo $$?) ifneq ($(NEW_LINKER),0) checkbin: @echo '*** ${VERSION}.${PATCHLEVEL} kernels no longer build correctly with old versions of binutils.' @echo '*** Please upgrade your binutils to 2.9.5.' @false else checkbin: @true endif My testing targets (that short circuit a few dependencies) zimg:; @$(MAKEBOOT) zimage Img:; @$(MAKEBOOT) Image i:; @$(MAKEBOOT) install zi:; @$(MAKEBOOT) zinstall bp:; @$(MAKEBOOT) bootpimage (8) Configuration targets. Use these to select a configuration for your architecture %_config: @( \ CFG=$(@:_config=); \ if [ -f arch/arm/def-configs/$$cfg ]; then \ [ -f.config ] && mv -f.config.config.old; \ cp arch/arm/def-configs/$$cfg.config; \ echo "*** Default configuration for $$CFG installed"; \ echo "*** Next, you may run 'make oldconfig'"; \ else \ echo "$$CFG does not exist"; \ fi; \ ) (1) ARM 프로세서의종류에따라컴파일러에게사용할아키텍쳐를알려준다. SA1110 은 armv4 80

를사용한다. ARM 아키텍쳐에서아키텍쳐에 (v6 포함 ) 대한자세한정보를얻기바란다. 간단히정리하면다음과같다. v3 32 비트어드레싱시작, 아래와같은종류가있다. T Thumb 코드실행 M long multiply 지원, 이것은 v4에서기본이됐다. v4 halfword load, store 지원 v5 개선된 ARM, Thumb 동작. CLZ 명령지원. 종류는 E 개선된 DSP 명령 J JAVA 지원 (2) 선정된아키텍쳐에속하는변종들중에정확한것을지정한다. (3) SA1110 의경우필요한변수들을설정한다..text 를 0xC0008000 에맞추고링크스크립트를그에맞는것을사용하도록한다. (4) MACHINE 은 arch/arm 디렉토리밑에있는많은하위디렉토리중에현재선택된시스템이어떤것인지에따라변경되는내용을담은것을선택하도록한다. (5) MACHINE 에서선택된것을사용해필요한디렉토리를선택한다. (6) ARM 프로세서의종류에따라처음실행되는코드가달라져야한다. 그것을선택한다. (7) 링크에사용될스크립트인 vmlinux.lds 를만들기위해 vmlinux.*.lds.in 에서필요한몇가지변수를조정해 vmlinux.lds 를만든다. 81

(8) ARM 커널을만들때 'make assabet_config' 를실행한것을기억하는가? 보드에따라기본설정을세팅할때사용하는명령에따라알맞은설정을복사하도록한다. arch/arm/def-config 에가능한모든정보가들어있다. 4.3.2. $(TOPDIR)/arch/arm/vmlinux.lds ARM 프로세서커널의링크에사용되는링크스크립트 vmlinux.lds 를분석해보자. 4.3.1 절에서본것처럼 vmlinux.lds 는설정된아키텍쳐에따라만들어진것이다. /* ld script to make ARM Linux kernel * taken from the i386 version by Russell King * Written by Martin Mares <mj@atrey.karlin.mff.cuni.cz> */ (1) OUTPUT_ARCH(arm) (2) ENTRY(stext) SECTIONS { (3). = 0xC0008000; (4).init : { /* Init code and data */ _stext =.; init_begin =.; *(.text.init) } proc_info_begin =.; *(.proc.info) proc_info_end =.; arch_info_begin =.; *(.arch.info) arch_info_end =.; tagtable_begin =.; *(.taglist) tagtable_end =.; *(.data.init). = ALIGN(16); setup_start =.; *(.setup.init) setup_end =.; initcall_start =.; *(.initcall.init) initcall_end =.;. = ALIGN(4096); init_end =.; (5) (6) /DISCARD/ : { /* Exit code and data */ *(.text.exit) *(.data.exit) *(.exitcall.exit) }.text : { /* Real text segment */ _text =.; /* Text and read-only data */ *(.text) *(.fixup) *(.gnu.warning) *(.rodata) *(.rodata.*) *(.glue_7) *(.glue_7t) *(.got) /* Global offset table */ 82

} _etext =.; /* End of text section */.kstrtab : { *(.kstrtab) }. = ALIGN(16); ex_table : { /* Exception table */ start ex_table =.; *( ex_table) stop ex_table =.; } ksymtab : { /* Kernel symbol table */ start ksymtab =.; *( ksymtab) stop ksymtab =.; } (7). = ALIGN(8192);.data : { /* * first, the init task union, aligned * to an 8192 byte boundary. */ *(.init.task) /* * then the cacheline aligned data */. = ALIGN(32); *(.data.cacheline_aligned) /* * and the usual data section */ *(.data) CONSTRUCTORS } _edata =.; }.bss : { bss_start =.; /* BSS */ *(.bss) *(COMMON) _end =. ; } /* Stabs debugging sections. */.stab 0 : { *(.stab) }.stabstr 0 : { *(.stabstr) }.stab.excl 0 : { *(.stab.excl) }.stab.exclstr 0 : { *(.stab.exclstr) }.stab.index 0 : { *(.stab.index) }.stab.indexstr 0 : { *(.stab.indexstr) }.comment 0 : { *(.comment) } (1) 최종출력의아키텍쳐를지정한다. (2) stext 는 arch/arm/kernel/head-armv.s 에정의되어있다. (3) 그림 4-8 을참조하면 0xc0008000 은 DRAM Bank 0 의일부분이다. 실제 Bank 0 는 83

0xc0000000 부터시작하지만앞부분얼마는 Angel 이사용하기때문에여기서부터올려져실행되도록만들어진다. 그림 4-8. SA-1110 메모리맵 84

(4) 각종초기화코드만따로모은다. ENTRY 가 stext 이고커널이미지의제일앞이.text.init 부터시작되므로실행은 stext 가위치한 arch/arm/kernel/head-armv.s 부터실행된다. (5) 만들어지긴하지만실제커널이미지엔포함되지않는코드다. 커널이 exit 할일은없기때문이다. (6) 진짜 text 세그먼트와읽기전용데이터가위치한다. (7) 한프로세스의스택이 8KB 단위로작동되도록만들어지므로 init task union 은 8KB 단위로정렬되야제대로동작할수있다. init_task 는 task_union 이란 union type 으로만들어져있고 $(TOPDIR)/include/linux/sched.h 에정의되어있다. union task_union { struct task_struct task; unsigned long stack[init_task_size/sizeof(long)]; }; 위에서보듯이 task_union 은 struct task_struct task; 와 unsigned long stack[]; 으로이뤄져있다. 또 stack 은 2048*sizeof(long) 의길이만큼을차지하는데 SA1100 에선 8192 바이트가된다. 스택이제대로동작하려면각태스크는적어도 8KB 단위로정렬되야제대로동작할수있게된다. 4.3.3. $(TOPDIR)/arch/arm/boot/compressed/vmlinux.lds /* * linux/arch/arm/boot/compressed/vmlinux.lds.in * * Copyright (C) 2000 Russell King * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ OUTPUT_ARCH(arm) (1) ENTRY(_start) SECTIONS { (2). = 0xc0008000; _load_addr =.;. = 0xc0008000; _text =.; (3).text : { _start =.; *(.start) *(.text) *(.fixup) *(.gnu.warning) *(.rodata) *(.rodata.*) *(.glue_7) *(.glue_7t) input_data =.; piggy.o input_data_end =.; 85

. = ALIGN(4); } _etext =.;.data : { *(.data) } _edata =.;. = ALIGN(4); bss_start =.;.bss : { *(.bss) } _end =.; (4).stack : { *(.stack) }.stab 0 : { *(.stab) }.stabstr 0 : { *(.stabstr) }.stab.excl 0 : { *(.stab.excl) }.stab.exclstr 0 : { *(.stab.exclstr) }.stab.index 0 : { *(.stab.index) }.stab.indexstr 0 : { *(.stab.indexstr) }.comment 0 : { *(.comment) } } (1) 최종커널의실행시작점은 _start 가된다. (2) 메모리에올려지는시작위치는 0xc0008000. SA-1110 의메모리맵에의하면램은 0xc0000000 부터시작하지만 angel 이앞부분얼마를사요하므로여기에일단읽어올려놓고실행한다. (3) 엔트리로지정된 _start 의위치를지정한다. 즉실행코드의시작이되고.start 부터시작하므로 head.s 의 start 에서부터커널이실행된다. (4) 스택의뒤에있는 exit 코드들은사실필요없으므로무시되고 stack 의바로뒤부터해서남은메모리영역에커널의압축이풀린다. 4.3.4. Log 분석 아래 Log 는 vmlinux 가만들어지는과정을생략하고 vmlinux 의 ld 가실행되는것과그이후의과정만을실었다. i386 에서추적했던것과비슷하게만들어진다.... (1) /usr/local/arm/bin/arm-linux-ld -p -X -T arch/arm/vmlinux.lds arch/arm/kernel/headarmv.o arch/arm/kernel/init-task.o init/main.o init/version.o \ --start-group \ arch/arm/kernel/kernel.o arch/arm/mm/mm.o arch/arm/mach-sa1100/sa1100.o kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o \ drivers/l3/l3.o drivers/serial/serial.o drivers/char/char.o drivers/block/block.o drivers/misc/misc.o drivers/net/net.o drivers/media/media.o drivers/ide/idedriver.o drivers/sound/sounddrivers.o drivers/mtd/mtdlink.o drivers/pcmcia/pcmcia.o drivers/net/pcmcia/pcmcia-net.o drivers/video/video.o \ 86

net/network.o \ arch/arm/nwfpe/math-emu.o arch/arm/lib/lib.a /devel/arm/assabet/linux- 2.4.17/lib/lib.a \ --end-group \ -o vmlinux /usr/local/arm/bin/arm-linux-nm vmlinux grep -v '\(compiled\)\ \(\.o$\)\ \( [auw] \)\ \(\.\.ng$\)\ \(LASH[RL]DI\)' sort > System.map make[1]: 들어감 `/devel/arm/assabet/linux-2.4.17/arch/arm/boot' 디렉토리 make[2]: 들어감 `/devel/arm/assabet/linux-2.4.17/arch/arm/boot/compressed' 디렉토리 (2) /usr/local/arm/bin/arm-linux-gcc -D--ASSEMBLY-- -D--KERNEL-- -I/devel/arm/assabet/linux- 2.4.17/include -mapcs-32 -march=armv4 -mno-fpu -msoft-float -traditional -c head.s /usr/local/arm/bin/arm-linux-gcc -D--KERNEL-- -I/devel/arm/assabet/linux-2.4.17/include -O2 -DSTDC-HEADERS -mapcs-32 -march=armv4 -mtune=strongarm1100 -mshort-load-bytes - msoft-float -D--KERNEL-- -I/devel/arm/assabet/linux-2.4.17/include -c -o misc.o misc.c (3) /usr/local/arm/bin/arm-linux-gcc -D--ASSEMBLY-- -D--KERNEL-- -I/devel/arm/assabet/linux- 2.4.17/include -mapcs-32 -march=armv4 -mno-fpu -msoft-float -c -o head-sa1100.o headsa1100.s (4) /usr/local/arm/bin/arm-linux-objcopy -O binary -R.note -R.comment -S /devel/arm/assabet/linux-2.4.17/vmlinux piggy gzip -9 < piggy > piggy.gz /usr/local/arm/bin/arm-linux-ld -r -o piggy.o -b binary piggy.gz rm -f piggy piggy.gz (5) /usr/local/arm/bin/arm-linux-ld -p -X -T vmlinux.lds head.o misc.o head-sa1100.o piggy.o /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a -o vmlinux make[2]: 나감 `/devel/arm/assabet/linux-2.4.17/arch/arm/boot/compressed' 디렉토리 (6) /usr/local/arm/bin/arm-linux-objcopy -O binary -R.note -R.comment -S compressed/vmlinux zimage make[1]: 나감 `/devel/arm/assabet/linux-2.4.17/arch/arm/boot' 디렉토리 (1) $(TOPDIR)/vmlinux 와 System.map 을만든다. (2) i386 과마찬가지로초기화를담당하는 head 와압축을풀어주는 misc 를컴파일한다. (3) SA1100 에관계된특정명령을수행한다. 우선 command line 으로입력된것을 0xc0000000 에복사하고캐시관계된일을처리하고시리얼로연결된호스트에터미널프로그램이뜰동안좀기다려준다. 4.2 절에서처럼커널과램디스크이미지를다운로드한다음에터미널을실행하는데기다려주지않으면터미널로는초기부팅과정에대한것을볼수없게된다. (4) $(TOPDIR)/vmlinux 를바이너리로만들고압축한다. 그리고다른것과링킹할수있도록해놓는다. (5) 이제 head, misc, head-sa1100 piggy 를합쳐커널이미지를만들어낸다. (6) 최종커널이미지를만들어낸다. 4.4. 소스분석 $(TOPDIR)/vmlinux가실행되기전까지의소스코드를분석한다. 4.4.1. arch/arm/boot/compressed/head.s 커널이미지의제일처음실행되는부분으로 head.s, misc.s, head-sa1100.s가같이실행된 87

다. 이중 head.s 는메모리초기화에해당하는중요한역할을담당한다. 아래소스코드를보고분석해보자. 커널이미지가메모리에어떤위치에올려지고압축풀린것이어디에위치하고다시어디로옮겨지는지등에관해도식적으로나타내봤다. 그림 4-9 을참조하면서 head.s 를분석한다. 그림 4-9. ARM 리눅스커널이미지메모리맵 /* * linux/arch/arm/boot/compressed/head.s * * Copyright (C) 1996-1999 Russell King * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ include <linux/config.h> include <linux/linkage.h> /* * Debugging stuff * * Note that these macros must not contain any code which is not * 100% relocatable. Any attempt to do so will result in a crash. * Please select one of the following when turning on debugging. 88

*/ ifdef DEBUG if defined(config_debug_dc21285_port).macro loadsp, rb mov \rb, 0x7c000000.endm.macro writeb, rb strb \rb, [r3, 0x3f8].endm elif defined(config_arch_rpc).macro loadsp, rb mov \rb, 0x03000000 orr \rb, \rb, 0x00010000.endm.macro writeb, rb strb \rb, [r3, 0x3f8 << 2].endm elif defined(config_arch_integrator).macro loadsp, rb mov \rb, 0x16000000.endm.macro writeb, rb strb \rb, [r3, 0].endm elif defined(config_arch_sa1100).macro loadsp, rb mov \rb, 0x80000000 @ physical base address if defined(config_debug_ll_ser3) add \rb, \rb, 0x00050000 @ Ser3 else add \rb, \rb, 0x00010000 @ Ser1 endif.endm.macro writeb, rb str \rb, [r3, 0x14] @ UTDR.endm else error no serial architecture defined endif endif.macro mov bl.endm.macro mov mov bl.endm kputc,val r0, \val putc kphex,val,len r0, \val r1, \len phex ifdef DEBUG endif.macro debug_reloc_start kputc '\n' kphex r6, 8 /* processor id */ kputc ':' kphex r7, 8 /* architecture id */ kputc ':' mrc p15, 0, r0, c1, c0 kphex r0, 8 /* control reg */ kputc '\n' kphex r5, 8 /* decompressed kernel start */ kputc '-' kphex r8, 8 /* decompressed kernel end */ kputc '>' kphex r4, 8 /* kernel execution address */ kputc '\n'.endm.macro debug_reloc_end 89

ifdef DEBUG endif kphex r5, 8 /* end of kernel */ kputc '\n' mov r0, r4 bl memdump /* dump 256 bytes at start of kernel */.endm (1).section ".start", alloc, execinstr /* * sort out different calling conventions */.align start: (2).type start,function.rept 8 mov r0, r0.endr (3) b 1f.word 0x016f2818 @ Magic numbers to help the loader.word start @ absolute load/run zimage address.word _edata @ zimage end address 1: mov r7, r1 @ save architecture ID mov r8, 0 @ save r0 ifndef ARM_ARCH_2 /* * Booting from Angel - need to enter SVC mode and disable * FIQs/IRQs (numeric definitions from angel arm.h source). * We only do this if we were in user mode on entry. */ mrs r2, cpsr @ get current mode tst r2, 3 @ not user? bne not_angel mov r0, 0x17 @ angel_swireason_entersvc swi 0x123456 @ angel_swi_arm not_angel: mrs r2, cpsr @ turn off interrupts to orr r2, r2, 0xc0 @ prevent angel from running msr cpsr_c, r2 else teqp pc, 0x0c000003 @ turn off interrupts endif /* * Note that some cache flushing and other stuff may * be needed here - is there an Angel SWI call for this? */ /* * some architecture specific code can be inserted * by the linker here, but it should preserve r7 and r8. */ /* * ldmia 에의해읽혀지는값은다음과같다. * r2 : bss_start * r3 : end * r4 : _load_addr * r5 : _start * r6 : _usr_stack+4096 */.text 1: adr r2, LC0 ldmia r2, {r2, r3, r4, r5, sp} mov r0, 0 90

1: str r0, [r2], 4 @ clear bss str r0, [r2], 4 str r0, [r2], 4 str r0, [r2], 4 cmp r2, r3 blt 1b mrc p15, 0, r6, c0, c0 @ get processor ID bl cache_on (4) (5) mov r1, sp @ malloc space above stack add r2, sp, 0x10000 @ 64k max teq r4, r5 @ will we overwrite ourselves? moveq r5, r2 @ decompress after image movne r5, r4 @ decompress to final location (6) mov mov bl r0, r5 r3, r7 SYMBOL_NAME(decompress_kernel) (7) teq r4, r5 @ do we need to relocate beq call_kernel @ the kernel? (8) add r0, r0, 127 bic r0, r0, 127 @ align the kernel length /* * r0 = decompressed kernel length * r1-r3 = unused * r4 = kernel execution address * r5 = decompressed kernel start * r6 = processor ID * r7 = architecture ID * r8-r14 = unused */ add r1, r5, r0 @ end of decompressed kernel adr r2, reloc_start adr r3, reloc_end 1: ldmia r2!, {r8 - r13} @ copy relocation code stmia r1!, {r8 - r13} ldmia r2!, {r8 - r13} stmia r1!, {r8 - r13} cmp r2, r3 blt 1b (9) bl cache_clean_flush add pc, r5, r0 @ call relocation code /* * _load_addr : 0xc0008000 * _start : 0xc0008000 * _user_stack+4096 : 총 4KB 만큼을 stack으로할당하게된다. */.type LC0, object LC0:.word bss_start.word _end.word _load_addr.word _start.word user_stack+4096.size LC0,. - LC0 /* * Turn on the cache. We need to setup some page tables so that we * can have both the I and D caches on. * 91

* We place the page tables 16k down from the kernel execution address, * and we hope that nothing else is using it. If we're using it, we * will go pop! * * On entry, * r4 = kernel execution address * r6 = processor ID * r7 = architecture number * r8 = run-time address of "start" * On exit, * r0, r1, r2, r3, r8, r9 corrupted * This routine must preserve: * r4, r5, r6, r7 */ (10).align 5 cache_on: ldr r1, proc_sa110_type eor r1, r1, r6 movs r1, r1, lsr 5 @ catch SA110 and SA1100 beq 1f ldr r1, proc_sa1110_type eor r1, r1, r6 movs r1, r1, lsr 4 @ movne pc, lr (11) 1: bne cache_off sub r3, r4, 16384 @ Page directory size bic r3, r3, 0xff @ Align the pointer bic r3, r3, 0x3f00 (12) /* * Initialise the page tables, turning on the cacheable and bufferable * bits for the RAM area only. */ mov r0, r3 mov r8, r0, lsr 18 mov r8, r8, lsl 18 @ start of RAM add r9, r8, 0x10000000 @ a reasonable RAM size mov r1, 0x12 orr r1, r1, 3 << 10 add r2, r3, 16384 1: cmp r1, r8 @ if virt > start of RAM orrge r1, r1, 0x0c @ set cacheable, bufferable cmp r1, r9 @ if virt > end of RAM bicge r1, r1, 0x0c @ clear cacheable, bufferable str r1, [r0], 4 @ 1:1 mapping add r1, r1, 1048576 teq r0, r2 bne 1b /* * If ever we are running from Flash, then we surely want the cache * to be enabled also for our execution instance... We map 2MB of it * so there is no map overlap problem for up to 1 MB compressed kernel. * If the execution is in RAM then we would only be duplicating the above. */ mov r1, 0x1e orr r1, r1, 3 << 10 mov r2, pc, lsr 20 orr r1, r1, r2, lsl 20 add r0, r3, r2, lsl 2 str r1, [r0], 4 add r1, r1, 1048576 str r1, [r0] mov r0, 0 mcr p15, 0, r0, c7, c10, 4 @ drain write buffer mcr p15, 0, r0, c8, c7 @ flush I,D TLBs mcr p15, 0, r3, c2, c0 @ load page table pointer mov r0, -1 mcr p15, 0, r0, c3, c0 @ load domain access register mrc p15, 0, r0, c1, c0 92

ifndef DEBUG endif orr r0, r0, 0x1000 @ I-cache enable orr r0, r0, 0x003d @ Write buffer, mmu mcr p15, 0, r0, c1, c0 mov pc, lr /* * This code is relocatable. It is relocated by the above code to the end * of the kernel and executed there. During this time, we have no stacks. * * r0 = decompressed kernel length * r1-r3 = unused * r4 = kernel execution address * r5 = decompressed kernel start * r6 = processor ID * r7 = architecture ID * r8-r14 = unused */.align 5 reloc_start: add r8, r5, r0 debug_reloc_start mov r1, r4 1:.rept 4 ldmia r5!, {r0, r2, r3, r9 - r13} @ relocate kernel stmia r1!, {r0, r2, r3, r9 - r13}.endr cmp r5, r8 blt 1b debug_reloc_end call_kernel: bl cache_clean_flush bl cache_off mov r0, 0 mov r1, r7 @ restore architecture number mov pc, r4 @ call kernel /* * Here follow the relocatable cache support functions for * the various processors. */.type proc_sa110_type:.word.size.type proc_sa1110_type:.word.size proc_sa110_type,object 0x4401a100 proc_sa110_type,. - proc_sa110_type proc_sa1110_type,object 0x6901b110 proc_sa1110_type,. - proc_sa1110_type /* * Turn off the Cache and MMU. ARMv3 does not support * reading the control register, but ARMv4 does. * * On entry, r6 = processor ID * On exit, r0, r1 corrupted * This routine must preserve: r4, r6, r7 */.align 5 cache_off: ifdef CONFIG_CPU_ARM610 eor r1, r6, 0x41000000 eor r1, r1, 0x00560000 bic r1, r1, 0x0000001f teq r1, 0x00000600 mov r0, 0x00000060 @ ARM6 control reg. beq armv3_cache_off 93

endif ifdef CONFIG_CPU_ARM710 eor r1, r6, 0x41000000 bic r1, r1, 0x00070000 endif bic r1, r1, 0x000000ff teq r1, 0x00007000 @ ARM7 teqne r1, 0x00007100 @ ARM710 mov r0, 0x00000070 @ ARM7 control reg. beq armv3_cache_off mrc p15, 0, r0, c1, c0 bic r0, r0, 0x000d mcr p15, 0, r0, c1, c0 @ turn MMU and cache off mov r0, 0 mcr p15, 0, r0, c7, c7 @ invalidate whole cache v4 mcr p15, 0, r0, c8, c7 @ invalidate whole TLB v4 mov pc, lr armv3_cache_off: mcr p15, 0, r0, c1, c0 @ turn MMU and cache off mov r0, 0 mcr p15, 0, r0, c7, c0 @ invalidate whole cache v3 mcr p15, 0, r0, c5, c0 @ invalidate whole TLB v3 mov pc, lr /* * Clean and flush the cache to maintain consistency. * * On entry, * r6 = processor ID * On exit, * r1, r2, r12 corrupted * This routine must preserve: * r4, r6, r7 */.align 5 cache_clean_flush: ldr r1, proc_sa110_type eor r1, r1, r6 movs r1, r1, lsr 5 @ catch SA110 and SA1100 beq 1f ldr r1, proc_sa1110_type eor r1, r1, r6 movs r1, r1, lsr 4 movne pc, lr 1: bic r1, pc, 31 add r2, r1, 32768 1: ldr r12, [r1], 32 @ s/w flush D cache teq r1, r2 bne 1b mcr p15, 0, r1, c7, c7, 0 @ flush I cache mcr p15, 0, r1, c7, c10, 4 @ drain WB mov pc, lr /* * Various debugging routines for printing hex characters and * memory, which again must be relocatable. */ ifdef DEBUG.type phexbuf,object phexbuf:.space 12.size phexbuf,. - phexbuf phex: adr r3, phexbuf mov r2, 0 strb r2, [r3, r1] 1: subs r1, r1, 1 movmi r0, r3 bmi puts and r2, r0, 15 94

mov r0, r0, lsr 4 cmp r2, 10 addge r2, r2, 7 add r2, r2, '0' strb r2, [r3, r1] b 1b puts: loadsp r3 1: ldrb r2, [r0], 1 teq r2, 0 moveq pc, lr 2: writeb r2 mov r1, 0x00020000 3: subs r1, r1, 1 bne 3b teq r2, '\n' moveq r2, '\r' beq 2b teq r0, 0 bne 1b mov pc, lr putc: mov r2, r0 mov r0, 0 loadsp r3 b 2b memdump: mov r12, r0 mov r10, lr mov r11, 0 2: mov r0, r11, lsl 2 add r0, r0, r12 mov r1, 8 bl phex mov r0, ':' bl putc 1: mov r0, ' ' bl putc ldr r0, [r12, r11, lsl 2] mov r1, 8 bl phex and r0, r11, 7 teq r0, 3 moveq r0, ' ' bleq putc and r0, r11, 7 add r11, r11, 1 teq r0, 7 bne 1b mov r0, '\n' bl putc cmp r11, 64 blt 2b mov pc, r10 endif reloc_end: (13).align.section ".stack" user_stack:.space 4096 (1).section 은새로운 section 을정의하는것이다. 어셈블러에관계된디렉티브들은모두 'info 95

as' 를사용해찾아보면정확한설명이나와있다. alloc 은 allocatable 을의미하고 execinstr 은 executable 을의미한다. Assabet 보드에올린커널은실행될때 angelboot 에의해실행되는데 angelboot 로부터 r0=0, r1=0x19 가넘어온다. 즉 r1 이아키텍쳐넘버가된다. 4.2 절참조. (2) start 가 function symbol 임을나타낸다. 그리고.rept 8 은.rept 부터.endr 사이의문장을 8 번반복하란소리다. 즉 mov r0, r0 를 8 번반복한다. (3) b 1f 가의미하는것은 1 이란심볼로점프하는것을의미하고 f 는 forward 를의미해현재위치에서앞으로 1 이란심볼을찾아점프한다. 반대방향인경우는 b 를사용한다. (4) stack 위의메모리에 64KB 를할당하는데 stack 은 4.3.3 절에서지정된것처럼커널이미지의마지막에위치한다. 즉, stack 으로할당한곳뒤의메모리는사용하지않는영역이므로여기를할당해사용한다. 새로할당한영역은압축을풀때사용하는영역이다. (5) _load_addr 과 _start 의위치가같다면압축을푸는시작위치를바로위에서할당한 64KB 이후에서부터시작하고같지않다면 _load_addr 에압축을풀어놓는다. (6) decompress_kernel() 은 misc.c 에정의되어있다. 프로토타입은 ulg decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p, int arch_id); head.s 에서 r0 에넘긴값이 output_start 가 r1 은 free_mem_ptr_p, r2 는 free_mem_ptr_end_p 그리고 r3 이 arch_id 가된다. (7) _load_addr 과 _start 가서로다른경우는 call_kernel 을통해커널을실행하고같은경우는압축풀린커널을 _load_addr 로옮긴다. (8) 압축풀린커널을 _load_addr 로옮기기위해리로케이션하는코드를압축풀린커널의뒤에복사한다. 다시말해압축풀린커널을그대로 _load_addr 로옮기면현재실행되고있는 head.s 를엎어쓰기때문에계속해서제대로실행되지않는다. 그러므로재배치해주는코드를먼저대피해놓고그코드를실행해압축풀린커널을옮긴다. (9) 최종적으로재배치코드를실행한다. (10) 정렬은 25=32 바이트로한다. (11) 1 레벨페이지테이블은반드시 16KB 단위로정렬되야한다. 4.1.3 절을참조. (12) 페이지데이블내용을채워넣는다. 램의시작점은 0xc0000000 이지만실제커널이올려지는위치가 0xc0008000 이므로 0xc0000000 ~ 0xc0008000 사이에페이지테이블을만들고섹션디스크립터로채워넣는다. 각섹션디스크립터는 AP=11, IMP=1, Domain=00 이란속성을갖는다. Assabet 보드의경우 0xc0004000 ~ 0xc0008000 에페이지테이블이만들어진다. 4.4.2. arch/arm/kernel/head-armv.s 4.3.2 절와 4.4.1 절에서살펴본것처럼커널이미지의압축이풀리고재배치후에실행되는코드는 stext 로 $(TOPDIR)/arch/arm/kernel/head-armv.S 에정의되어있다. 96

/* * linux/arch/arm/kernel/head-armv.s * * Copyright (C) 1994-1999 Russell King * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 32-bit kernel startup code for all architectures */ include <linux/config.h> include <linux/linkage.h> include <asm/assembler.h> include <asm/mach-types.h> include <asm/mach/arch.h> define K(a,b,c) ((a) << 24 (b) << 12 (c)) /* * We place the page tables 16K below TEXTADDR. Therefore, we must make sure * that TEXTADDR is correctly set. Currently, we expect the least significant * "short" to be 0x8000, but we could probably relax this restriction to * TEXTADDR > PAGE_OFFSET + 0x4000 * * Note that swapper_pg_dir is the virtual address of the page tables, and * pgtbl gives us a position-independent reference to these tables. We can * do this because stext == TEXTADDR * * swapper_pg_dir, pgtbl and krnladr are all closely related. */ if (TEXTADDR & 0xffff)!= 0x8000 error TEXTADDR must start at 0xXXXX8000 endif.globl.equ SYMBOL_NAME(swapper_pg_dir) SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000.macro pgtbl, reg, rambase adr \reg, stext sub \reg, \reg, 0x4000.endm /* * Since the page table is closely related to the kernel start address, we * can convert the page table base address to the base address of the section * containing both. */.macro krnladr, rd, pgtable, rambase bic \rd, \pgtable, 0x000ff000.endm /* * Kernel startup entry point. * * The rules are: * r0 - should be 0 * r1 - unique architecture number * MMU - off * I-cache - on or off * D-cache - off * * See linux/arch/arm/tools/mach-types for the complete list of numbers * for r1. */ (1).section ".text.init",alloc,execinstr.type stext, function ENTRY(stext) mov r12, r0 97

/* * NOTE! Any code which is placed here should be done for one of * the following reasons: * * 1. Compatability with old production boot firmware (ie, users * actually have and are booting the kernel with the old firmware) * and therefore will be eventually removed. * 2. Cover the case when there is no boot firmware. This is not * ideal, but in this case, it should ONLY set r0 and r1 to the * appropriate value. */ if defined(config_arch_netwinder) /* * Compatability cruft for old NetWinder NeTTroms. This * code is currently scheduled for destruction in 2.5.xx */.rept 8 mov r0, r0.endr adr r2, 1f ldmdb r2, {r7, r8} and r3, r2, 0xc000 teq r3, 0x8000 beq entry bic r3, r2, 0xc000 orr r3, r3, 0x8000 mov r0, r3 mov r4, 64 sub r5, r8, r7 b 1f.word.word _stext bss_start 1:.rept 4 ldmia r2!, {r6, r7, r8, r9} stmia r3!, {r6, r7, r8, r9}.endr subs r4, r4, 64 bcs 1b movs r4, r5 mov r5, 0 movne pc, r0 mov r1, MACH_TYPE_NETWINDER @ (will go in 2.5) mov r12, 2 << 24 @ scheduled for removal in 2.5.xx orr r12, r12, 5 << 12 entry: endif if defined(config_arch_l7200) /* * FIXME - No bootloader, so manually set 'r1' with our architecture number. */ mov r1, MACH_TYPE_L7200 endif (2) (3) mov r0, F_BIT I_BIT MODE_SVC @ make sure svc mode msr cpsr_c, r0 @ and all irqs disabled bl lookup_processor_type teq r10, 0 @ invalid processor? moveq r0, 'p' @ yes, error 'p' beq error bl lookup_architecture_type teq r7, 0 @ invalid architecture? moveq r0, 'a' @ yes, error 'a' beq error bl create_page_tables 98

adr lr, ret @ return address add pc, r10, 12 @ initialise processor @ (return control reg).type switch_data, %object switch_data:.long mmap_switched.long SYMBOL_NAME(compat).long SYMBOL_NAME( bss_start).long SYMBOL_NAME(_end).long SYMBOL_NAME(processor_id).long SYMBOL_NAME( machine_arch_type).long SYMBOL_NAME(cr_alignment).long SYMBOL_NAME(init_task_union)+8192 (4).type ret, %function ret: ldr lr, switch_data mcr p15, 0, r0, c1, c0 mov r0, r0 mov r0, r0 mov r0, r0 mov pc, lr /* * This code follows on after the page * table switch and jump above. * * r0 = processor control register * r1 = machine ID * r9 = processor ID */.align 5 mmap_switched: adr r3, switch_data + 4 ldmia r3, {r2, r4, r5, r6, r7, r8, sp}@ r2 = compat @ sp = stack pointer str r12, [r2] mov fp, 0 @ Clear BSS (and zero fp) 1: cmp r4, r5 strcc fp, [r4],4 bcc 1b str r9, [r6] @ Save processor ID str r1, [r7] @ Save machine type ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, 2 @...A. endif bic r2, r0, 2 @ Clear 'A' bit stmia r8, {r0, r2} @ Save control register values (5) b SYMBOL_NAME(start_kernel) (6) /* * Setup the initial page tables. We only setup the barest * amount which are required to get the kernel running, which * generally means mapping in the kernel code. * * We only map in 4MB of RAM, which should be sufficient in * all cases. * * r5 = physical address of start of RAM * r6 = physical IO address * r7 = byte offset into page tables for IO * r8 = page table flags */ create_page_tables: (7) pgtbl r4, r5 @ page table address 99

(8) /* * Clear the 16K level 1 swapper page table */ mov r0, r4 mov r3, 0 add r2, r0, 0x4000 1: str r3, [r0], 4 str r3, [r0], 4 str r3, [r0], 4 str r3, [r0], 4 teq r0, r2 bne 1b (9) (10) (11) /* * Create identity mapping for first MB of kernel to * cater for the MMU enable. This identity mapping * will be removed by paging_init() */ krnladr r2, r4, r5 @ start of kernel add r3, r8, r2 @ flags + kernel base str r3, [r4, r2, lsr 18] @ identity mapping /* * Now setup the pagetables for our kernel direct * mapped region. We round TEXTADDR down to the * nearest megabyte boundary. */ add r0, r4, (TEXTADDR & 0xff000000) >> 18 @ start of kernel bic r2, r3, 0x00f00000 str r2, [r0] @ PAGE_OFFSET + 0MB add r0, r0, (TEXTADDR & 0x00f00000) >> 18 str r3, [r0], 4 @ KERNEL + 0MB add r3, r3, 1 << 20 str r3, [r0], 4 @ KERNEL + 1MB add r3, r3, 1 << 20 str r3, [r0], 4 @ KERNEL + 2MB add r3, r3, 1 << 20 str r3, [r0], 4 @ KERNEL + 3MB /* * Ensure that the first section of RAM is present. * we assume that: * 1. the RAM is aligned to a 32MB boundary * 2. the kernel is executing in the same 32MB chunk * as the start of RAM. */ bic r0, r0, 0x01f00000 >> 18 @ round down and r2, r5, 0xfe000000 @ round down add r3, r8, r2 @ flags + rambase str r3, [r0] bic r8, r8, 0x0c @ turn off cacheable @ and bufferable bits ifdef CONFIG_DEBUG_LL /* * Map in IO space for serial debugging. * This allows debug messages to be output * via a serial console before paging_init. */ add r0, r4, r7 rsb r3, r7, 0x4000 @ PTRS_PER_PGD*sizeof(long) cmp r3, 0x0800 addge r2, r0, 0x0800 addlt r2, r0, r3 orr r3, r6, r8 1: str r3, [r0], 4 add r3, r3, 1 << 20 100

teq r0, r2 bne 1b if defined(config_arch_netwinder) defined(config_arch_cats) /* * If we're using the NetWinder, we need to map in * the 16550-type serial port for the debug messages */ teq r1, MACH_TYPE_NETWINDER teqne r1, MACH_TYPE_CATS bne 1f add r0, r4, 0x3fc0 mov r3, 0x7c000000 orr r3, r3, r8 str r3, [r0], 4 add r3, r3, 1 << 20 str r3, [r0], 4 1: endif endif ifdef CONFIG_ARCH_RPC /* * Map in screen at 0x02000000 & SCREEN2_BASE * Similar reasons here - for debug. This is * only for Acorn RiscPC architectures. */ add r0, r4, 0x80 @ 02000000 mov r3, 0x02000000 orr r3, r3, r8 endif (12) str r3, [r0] add r0, r4, 0x3600 @ d8000000 str r3, [r0] mov pc, lr /* * Exception handling. Something went wrong and we can't * proceed. We ought to tell the user, but since we * don't have any guarantee that we're even running on * the right architecture, we do virtually nothing. * r0 = ascii error character: * a = invalid architecture * p = invalid processor * i = invalid calling convention * * Generally, only serious errors cause this. */ error: ifdef CONFIG_DEBUG_LL mov r8, r0 @ preserve r0 adr r0, err_str bl printascii mov r0, r8 bl printch endif ifdef CONFIG_ARCH_RPC /* * Turn the screen red on a error - RiscPC only. */ mov r0, 0x02000000 mov r3, 0x11 orr r3, r3, r3, lsl 8 orr r3, r3, r3, lsl 16 str r3, [r0], 4 str r3, [r0], 4 str r3, [r0], 4 str r3, [r0], 4 endif 1: mov r0, r0 b 1b 101

ifdef CONFIG_DEBUG_LL err_str:.asciz "\nerror: ".align endif (13) /* * Read processor ID register (CP15, CR0), and look up in the linker-built * supported processor list. Note that we can't use the absolute addresses * for the proc_info lists since we aren't running with the MMU on * (and therefore, we are not in the correct address space). We have to * calculate the offset. * * Returns: * r5, r6, r7 corrupted * r8 = page table flags * r9 = processor ID * r10 = pointer to processor structure */ lookup_processor_type: adr r5, 2f (14) ldmia r5, {r7, r9, r10} sub r5, r5, r10 @ convert addresses add r7, r7, r5 @ to our address space add r10, r9, r5 mrc p15, 0, r9, c0, c0 @ get processor id (15) 1: ldmia r10, {r5, r6, r8} @ value, mask, mmuflags and r6, r6, r9 @ mask wanted bits teq r5, r6 moveq pc, lr add r10, r10, 36 @ sizeof(proc_info_list) cmp r10, r7 blt 1b mov r10, 0 @ unknown processor mov pc, lr (16) /* * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for * more information about the proc_info and arch_info structures. */ 2:.long proc_info_end.long proc_info_begin.long 2b.long arch_info_begin.long arch_info_end (17) /* * Lookup machine architecture in the linker-build list of architectures. * Note that we can't use the absolute addresses for the arch_info * lists since we aren't running with the MMU on (and therefore, we are * not in the correct address space). We have to calculate the offset. * * r1 = machine architecture number * Returns: * r2, r3, r4 corrupted * r5 = physical start address of RAM * r6 = physical address of IO * r7 = byte offset into page tables for IO */ lookup_architecture_type: adr r4, 2b ldmia r4, {r2, r3, r5, r6, r7} @ throw away r2, r3 sub r5, r4, r5 @ convert addresses add r4, r6, r5 @ to our address space add r7, r7, r5 1: ldr r5, [r4] @ get machine type teq r5, r1 102

beq 2f add r4, r4, SIZEOF_MACHINE_DESC cmp r4, r7 blt 1b mov r7, 0 @ unknown architecture mov pc, lr 2: ldmib r4, {r5, r6, r7} @ found, get results mov pc, lr (1) 압축풀린커널의실행시작위치로 0xc0008000 이된다. 바로밑의 if define 으로된두부분은 Assabet 보드의경우엔해당사항없기때문에무시되고넘어간다. (2) Assabet 보드의실제실행코드시작점으로인터럽트를불가능으로만들고프로세서타입아키텍져타입을알아낸후필요한페이지테이블을만들고프로세서를설정한다. (3) 페이지테이블을만든후무언가를실행하는데돌아오는위치는 ret 가되도록한다. 실행되는그무엇은 r10 + 12 로 Assabet 보드의경우 sa1100_setup 이란곳이된다. 이곳은 $(TOPDIR)/arch/arm/mm/proc-sa110.S 에정의되어있다. (4) sa1100_setup 이실행된후돌아오는곳이이곳인데다시 switch_data 란곳으로분기할준비를한다. 즉 mmap_switched 를실행하게된다. (5) 이제모든준비가끝났다. 리눅스커널을시작한다. start_kernel 은 $(TOPDIR)/init/main.c 에정의되어있다. (6) 초기페이지테이블을설정한다. 여기서는커널이실행될만큼만설정하고커널이초기화되면서설정된다. 4MB 정도면커널이실행되는덴충분하므로이정도만설정한다. (7) 페이지테이블의시작어드레스를계산한다. r4=0xc0004000 이된다. (8) 이미설정되어있던페이지테이블을모두지운다. (9) 페이지테이블에들어갈디스크립터값을설정한다. krnladr 은커널의시작이위치한곳을 1MB 단위로끊어준값이다. 여기선페이지테이블을섹션디스크립터로채우기때문에 1MB 단위로끊을필요가있는것이다. r2=0xc0000000 이된다 ( 램의시작점 ). 섹션디스크립터의값은 AP=11, Domain=0, IMP=0, C=1, B=1 의속성을갖는다 (0xC0E). [r4, r2, lsr 18] 이의미하는것은 r2 를 18bit Left Shift 하고 r4 에더한다는뜻으로, 최종값은 0xc0007000 이된다. 이값이의미하는것은페이지테이블내의커널시작어드레스를가리키는위치가된다. 섹션디스크립터는 1MB 단위로메모리를관리하고 32bit ARM 프로세서의최대가능용량인 4GB 를다루기위해선 4G/1M=4K 만큼의디스크립터가필요하다. 또각디스크립터는 4Bytes 로구성되므로페이지테이블은 4K*4Bytes=16KB 만큼의크기가필요하다. 그렇다면커널이시작하는곳의디스크립터는 16KB 내의어디에위치하는것일까? 커널의시작이포함된 1MB 단위의램시작점이 0xc0000000 이므로 0xc0000000/1M*4=0x3000 이된다. 페이지테이블의시작이 0xc0004000 이므로여기에 0x3000 을더한 0xc0007000 에위치한디스크립터가바로커널의시작위치를가리키는디스크립터가된다. (10) 103

위에서말한대로필요한 4MB 만을위한디스크립터를설정한다. (11) 램이 32MB 로정렬됐고커널이같은 32MB 내에서실행된다고가정하고이에해당하는디스크립터를조정한다. (12) 모든페이지테이블설정을마치고되돌아간다. (13) 프로세서타입을알아낸다. (14) 첫부분에 ldmia 로값을읽어내는데 r7= proc_info_end, r9= proc_info_begin, r10=2f 의어드레스가된다. (15) 프로세서에대한정보는 SA-110, SA-1100, SA-1110 의정보가연속해존재하므로 SA-110 부터시작해현재실행되고있는프로세서와비교해맞는것을골라정보를얻는다. (16) proc_info_end 등은 $(TOPDIR)/arch/arm/vmlinux.lds 에정의되어있다. 그리고이값은 $(TOPDIR)/arch/arm/mm/proc-sa110.S 에다음과같이정의되어있다. sa1110_proc_info:.long 0x6901b110.long 0xfffffff0.long 0x00000c0e b sa1100_setup.long cpu_arch_name.long cpu_elf_name.long HWCAP_SWP HWCAP_HALF HWCAP_26BIT HWCAP_FAST_MULT.long cpu_sa1110_info.long sa1100_processor_functions.size sa1110_proc_info,. - sa1110_proc_info (17) 아키텍쳐타입에대한정보를얻어낸다. $(TOPDIR)/arch/arm/mach-sa1100/assabet.c 에다음과같이정의되어있다. MACHINE_START(ASSABET, "Intel-Assabet") BOOT_MEM(0xc0000000, 0x80000000, 0xf8000000) BOOT_PARAMS(0xc0000100) FIXUP(fixup_assabet) MAPIO(assabet_map_io) INITIRQ(sa1100_init_irq) MACHINE_END 위정의에대한매크로는 $(TOPDIR)/include/asm-arm/mach/arch.h 에다음과같이정의되어있다. /* * Set of macros to define architecture features. This is built into * a table by the linker. */ define MACHINE_START(_type,_name) \ const struct machine_desc mach_desc type \ attribute (( section (".arch.info"))) = { \ nr: MACH_TYPE type, \ name: _name, define MAINTAINER(n) define BOOT_MEM(_pram,_pio,_vio) \ 104

phys_ram: _pram, \ phys_io: _pio, \ io_pg_offst: ((_vio)>>18)&0xfffc, define BOOT_PARAMS(_params) \ param_offset: _params, define VIDEO(_start,_end) \ video_start: _start, \ video_end: _end, define DISABLE_PARPORT(_n) \ reserve_lp_n: 1, define BROKEN_HLT /* unused */ define SOFT_REBOOT \ soft_reboot: 1, define FIXUP(_func) \ fixup: _func, define MAPIO(_func) \ map_io: _func, define INITIRQ(_func) \ init_irq: _func, define MACHINE_END \ }; 5. 리눅스커널부팅 이장에서는리눅스커널의압축이풀린후실행되는 start_kernel() 부터 init 가실행될때까지의절차를추적해보고필요한것들을분석해본다. 5.1. 커널시작 4.4.2 절의 (5) 에서 start_kernel 이불리는데여기부터가일반적인커널의시작이라고생각하면된다. start_kernel 전까지는리눅스커널이실행되기위한기본적인초기화등을해놓은상태라고생각하면된다. 아래에 start_kernel() 만을발췌해놨다. 또커널부팅중남은기록은 5.8 절를참조바란다. /* * Activate the first processor. */ asmlinkage void init start_kernel(void) { char * command_line; unsigned long mempages; extern char saved_command_line[]; /* * Interrupts are still disabled. Do necessary setups, then * enable them */ (1) lock_kernel(); (2) 105

(3) (4) (5) (6) (7) (8) (9) (10) printk(linux_banner); setup_arch(&command_line); printk("kernel command line: %s\n", saved_command_line); parse_options(command_line); trap_init(); init_irq(); sched_init(); softirq_init(); time_init(); /* * HACK ALERT! This is early. We're enabling the console before * we've done PCI setups etc, and console_init() must be aware of * this. But we do want output early, in case something goes wrong. */ (11) console_init(); ifdef CONFIG_MODULES (12) init_modules(); endif if (prof_shift) { unsigned int size; /* only text is profiled */ prof_len = (unsigned long) &_etext - (unsigned long) &_stext; prof_len >>= prof_shift; } size = prof_len * sizeof(unsigned int) + PAGE_SIZE-1; prof_buffer = (unsigned int *) alloc_bootmem(size); (13) kmem_cache_init(); (14) sti(); (15) calibrate_delay(); ifdef CONFIG_BLK_DEV_INITRD if (initrd_start &&!initrd_below_start_ok && initrd_start < min_low_pfn << PAGE_SHIFT) { printk(kern_crit "initrd overwritten (0x%08lx < 0x%08lx) - " "disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT); initrd_start = 0; } endif (16) mem_init(); (17) kmem_cache_sizes_init(); pgtable_cache_init(); mempages = num_physpages; (18) fork_init(mempages); (19) proc_caches_init(); (20) vfs_caches_init(mempages); buffer_init(mempages); page_cache_init(mempages); if defined(config_arch_s390) ccwcache_init(); endif 106

signals_init(); ifdef CONFIG_PROC_FS proc_root_init(); endif if defined(config_sysvipc) (21) ipc_init(); endif (22) check_bugs(); printk("posix conformance testing by UNIFIX\n"); (23) (24) } /* * We count on the initial thread going ok * Like idlers init is an unlocked kernel thread, which will * make syscalls (and thus be locked). */ smp_init(); rest_init(); (1) 5.2 절참조 (2) linux_banner 는 init/version.c 에다음과같이정의되어있다. const char *linux_banner = "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@" LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n"; 이내용은부팅할때아래와같이출력되고 /var/log/dmesg 의첫줄에기록된다. Linux version 2.4.16 (root@localhost) (gcc version 2.95.3 20010315 (release)) 22 2002. 02. 27. ( 수 ) 13:30:14 KST (3) 5.3 절참조 (4) 화면에 command line option 을출력한다. (5) command line option 을해석한다. 여기에서해석되는것은모두커널내부적으로사용되는것이고 init 로보내지진않는다. 해석된옵션은 '=' 가있으면환경변수로취급되고없으면옵션을처리되환경변수는 envp_init[] 에담기고옵션은 argv_init[] 에담긴다. (6) 5.4 절참조 (7) 5.5 절참조 (8) 5.6 절참조 (9) 107

소프트웨어인터럽트를기본시스템을초기화한다. bh 에대한초기화가이뤄지기도한다. (10) CMOS 에서시간을읽고 CPU 의속도를얻어낸다. /var/log/dmesg 에 "Detected 1009.016 MHz processor." 라고출력되는부분이기도하다. (11) 콘솔디바이스를초기화한다. 모든초기화를수행하는것은아니고초기에필요한정도만하고나머지는나중에한다. dmesg 에 'Console: colour VGA+ 132x43' 를출력한다. (12) 모듈의초기화를하지만 i386, ARM 에선아무것도하지않는다. ia64 만뭔가를한다. (13) 대부분의슬랩배정자 (slab allocator) 를초기화한다. (14) 인터럽트를가능하게한다. (15) BogoMIPS 를계산한다. dmesg 에 'Calibrating delay loop... 2011.95 BogoMIPS' 라고출력된다. (16) max_mapnr, totalram_pages, high_memory 를계산하고 dmesg 에 'Memory: 512920k/524208k available (1213k kernel code, 10900k reserved, 482k data, 228k init, 0k highmem)' 라고출력한다. (17) 슬랩초기화를마친다. (18) uid_cache 를만들고사용가능한메모리의양에따라 max_threads 를초기화하고 RLIMIT_NPROC 을 max_threads/2 로정한다. (19) procfs 가사용하는데이터스트럭쳐를초기화한다. (20) VFS, VM, buffer cache 등에대한슬램캐시를만든다. (21) System V IPC 가지원되는커널이라면 IPC 하부시스템을초기화한다. 세마포어, 메시지큐, 공유메모리를초기화한다. (22) 아키텍쳐에따른버그를검사한다. 예를들어 ia32 의 "f00f 버그 " 다. (23) 멀티프로세서시스템이가능한아키텍쳐의경우에만해당하는내용으로 SMP 를초기화한다. (24) rest_init() 자체는무척간단하다. 우선 init 프로세스를실행해준다. 그리고 start_kernel() 의첫부분에서 lock 했던커널을 unlock 해주고 idle 상태로들어간다. idle 상태로들어가도이미 init 프로세스가생성된후기때문에상관없이커널의부팅은진행된다. idle 프로세스는 0 번의프로세스번호를갖는다. 나머지에대해선 5.7 절참조 5.2. lock_kernel() lock_kernel() 은각아키텍쳐마다하나씩따로정의되어있다. 보통 $(TOPDIR)/include/asm- */smplock.h 에함수로정의되어있고 spark64, sh, cris 아키텍쳐는매크로로정의되어있다. 108

5.2.1. Lock 이왜필요하지? 리눅스는 CPU 를여러개가진시스템에서도실행되고이를지원하고있다. 만약여러개의 CPU 가동시에같은변수의값을조정하고읽는경우가생긴다면어떻게되겠는가? 아래의간단한예를보면서얘기해보자. very_important_count++; 두개의 CPU 가동시에같은코드를실행했다면아래와같은표처럼실행되야맞다고가정해보자. 표 5-1. 예상결과 Instance1 read very_important_count (5) add 1 (6) write very_important_count (6) Instance2 read very_important_count (6) add 1 (7) write very_important_count (7) 그러나이건아마도이런식으로실행될수도있다. 표 5-2. 가능한결과 Instance1 read very_important_count (5) add 1 (6) write very_important_count (6) Instance2 read very_important_count (5) add 1 (6) write very_important_count (6) 결과적으로예상은 Instance1 이실행됐을때값이 6 이되고 Instance2 가실행됐을때 7 이되길바란것이지만표 5-2 처럼서로실행이중첩되버리면결과값이 6 으로엉망이될수도있다. 이런일을막기위해보통락 (lock) 이란것을쓰는데쉽게말해서로중첩되서실행되지않도록보장하는것이라고이해하면된다. 시스템을초기화하는동안에이런일이발생하면시스템초기화는엉망이되고커널은제대로부팅할수없게된다. 그래서초기화동안락을걸고나중에초기화가다끝나면락을풀어주게된다. 109

5.2.2. Lock - 기초적설명 멀티태스킹 OS 에서한변수를여러개의프로세스가공유하고이를동시에사용한다면여러프로세스간의연계된시간에따라이변수를사용하는것이중첩되는데이를보통레이스컨디션 (race condition) 이라고부른다. 그리고동시발생문제를다루는코드를크리티컬리전 (critical region) 이라부른다. 리눅스는 SMP 상에서동작하므로이런문제가커널디자인에있어서중요한문제중하나다. 위에서말한동시접근과같은문제의해결은락 (lock) 을이용하는것이고락은한번에하나의접근만이크리티컬리전에들어가도록해서해결한다. 락엔두가지타입이있다. 하나는스핀락 (spinlock) 으로 include/asm/spinlock.h 에정의되어있다. 이타입은싱글홀더락 (single-holder lock) 이고매우작고빠르고아무데서나사용할수있다. 두번째타입은세마포어로 include/asm/semaphore.h 에정의되어있다. 세마포어는보통싱글홀더락 (mutex) 으로사용되지만한번에여러홀더를가질수있다. 사용자가세마포어를얻지못하면사용자의프로세스는큐에넣어지고세마포어가사용가능해질때깨어나게된다. 이말은프로세스가기다리는동안 CPU 가다른뭔가를한다는것이다. 그러나많은경우에그냥기다릴수없을때가있다. 이경우엔스핀락을대신사용해야한다. 커널을설정할때 SMP 지원을체크하지않았다면스핀락은존재하지않게된다. 이런결정은아무도동시에실행하지않고락을걸이유가없는경우아주좋은커널디자인이라말할수있다. 세마포어는언제나존재하는데이는사용자프로세스간에동기를맞추기위해필요하기때문이다. 5.2.3. i386, ARM 의스핀락 i386 계열은 SMP 시스템이존재하기때문에스핀락이정의되어있고사용되지만 ARM 의경우 SMP 시스템이없기때문에스핀락이정의되어있지않다. 그래서 ARM 의 asm/smplock.h 는기본으로정의된것이사용된다. 기본정의된스핀락은 include/linux/spinlock.h 에다음과같이정의되어있다. define spin_lock(lock) (void)(lock) /* Not "unused variable". */ 이에반해 i386 은 include/asm-i386/spinlock.h 에다음과같이정의되어있다. static inline void spin_lock(spinlock_t *lock) { if SPINLOCK_DEBUG label here; here: if (lock->magic!= SPINLOCK_MAGIC) { printk("eip: %p\n", &&here); BUG(); } endif (1) asm volatile ( spin_lock_string :"=m" (lock->lock) : : "memory"); } 110

(1) 인라인어셈블리에대해선부록 C 를참조해무슨내용인지확인하기바란다. 5.3. setup_arch() setup_arch() 는 arch/*/kernel/setup.c 에각아키텍쳐에따른정의가되어있다. 여기서는아키텍쳐 ( 좀더정확히는타겟보드에따라 ) 에따른설정을한다. i386 에서는아래와같은정보를수집하거나초기화해놓는다. CPU 가초기화되면서 /var/log/dmesg 에 "Initializing CPU0" 를출력한다. 기본루트디바이스선택 시스템에연결되어있는드라이브정보수집 화면정보수집 APM 정보수집 시스템정보수집 램디스크플래그설정 메모리영역설정 메모리매니져변수초기화 커맨드라인명령해석 부팅할때사용하는메모리초기화 페이징시스템초기화 전원관리초기화 표준롬초기화 ARM 의경우 i386 과는달리프로세서종류가몇가지되므로프로세서와아키텍쳐타입에따른설정을마친후커맨드라인명령을해석한다. 이어메모리설정을초기화하고페이지설정도한다. 5.4. trap_init() 트랩은인터럽트와는달리정해진곳으로분기하도록되어있고번호로정해져있다. 아래는 i386 에서정해져있는트랩의일부를열거한것이다. 0 - divide_error 111

1 - debug 2 - nmi 3 - int3 4 - overflow 5 - bounds 6 - invalid_op 7 - device_not_available 8 - double_fault 9 - coprocessor_segment_overrun 10 - invalid_tss 11 - segment_not_present 12 - stack_segment 13 - general_protection 14 - page_fault 15 - spurious_interrupt_bug 16 - coprocessor_error 17 - alignment_check 18 - machine_check 19 - simd_coprocessor_error trap_init() 에선시스템콜을위한초기화도실행해 0x80 을시스템콜에사용하도록해놓는다. 그리고 CPU 를초기화한다. CPU 초기화에선페이지, gdt, ldt, idt, tss 등이설정되고이를사용할수있는상태로만들어본격적인커널실행에들어간다. /var/log/dmesg 의 (6) 에출력된한줄이 CPU 의초기화를의미한다. i386 에서 trap 을초기화하는함수인 _set_gate() 는 C.2.2 절를참조하기바란다. ARM 프로세서의 trap 은 arch/arm/kernel/entry-armv.s 나 arch/arm/kernel/entry-armo.s 에정의되어있고내용은다음과같다. 앞의값은 vector 의 offset 을말한다. 0x00000000 - reset 0x00000004 - Undefined instruction 0x00000008 - Software Interrupt(SWI) 112

0x0000000C - Prefetch Abort(Instruction fetch memory abort) 0x00000010 - Data Abort(Data Access memory abort) 0x00000018 - IRQ(Interrupt) 0x0000001C - FIQ(Fast Interrupt) 5.5. init_irq() i386 의 PC 계열에선 ISA 혹은 APIC 를지원하는시스템인경우에따라인터럽트설정을하고타이머인터럽트를동작시킨다. 아직은인터럽트가사용가능하지않으므로인터럽트가동작하진않는다. 0x20 ~ 0x2f 까지의벡터는 ISA 인터럽트용벡터이고 0xf0 ~ 0xff 는 SMP 시스템용인터럽트벡터로사용된다. 나머지 0x30 ~ 0xee 는 APIC 가사용한다. 단 0x80 은시스템콜이사용하므로제외한다. ARM Assabet 보드에사용된 SA-1100 CPU 의경우 arch/arm/mach-sa1100/assabet.c 에정의된것에따라 sa1100_init_irq 가불리게된다. 다음과같다. ARM 의경우 cpu 가같아도플랫폼이다르거나 CPU 의종류도많으므로각 CPU 나시스템의타입에따라다른함수를사용할수있도록만들어져있다. MACHINE_START(ASSABET, "Intel-Assabet") BOOT_MEM(0xc0000000, 0x80000000, 0xf8000000) BOOT_PARAMS(0xc0000100) FIXUP(fixup_assabet) MAPIO(assabet_map_io) INITIRQ(sa1100_init_irq) MACHINE_END 5.6. sched_init() init 태스크가사용하는 CPU 번로를할당해주고 pid hash table 을초기화한다. 이어타이머인터럽트벡터를초기화한다. 인터럽트처리루틴은되도록이면간결하고빨라야할필요가있다. 프로그램이실행중에인터럽트가걸리면프로그램의실행을멈추고인터럽트를처리하므로인터럽트처리시간이많이걸린다면다른프로그램의실행에영향을미치게된다. 리눅스에선긴처리시간을필요로하는인터럽트루틴의문제를해결하기위해인터럽트루틴을둘로나눠이문제를해결한다. 이둘을 top-half, bottom-half 라고부른다. top-half 는 request_irq 로등록되는부분이고 bottom-half( 줄여서 bh) 는나중에시간이충분할때실행되도록 top-half 에의해스케쥴된다. top-half 와 bh 의차이라면 bh 가실행되는동안엔다른모든인터럽트가가능상태인것이다. 즉 top-half 는인터럽트가걸리면처음실행되고디바이스의데이터를특정버퍼에저장해놓고자신의 bh 에표시를한다음빠져나간다. 이렇게하면 top-half 는매우빠르게실행되기때문에다른것에영향을주지않게된다. 그러나만약 top-half 가동작하는중에다른인터럽트가걸리면이것은무시된다. 왜냐면 top-half 가실행되는동안엔인터럽트컨트롤러의 IRQ 라인이불가능상태이기때문이다. 113

가장대표적인인터럽트루틴인네트워크인터럽트루틴은새로운패킷이도착하면핸들러가도착한데이터만을읽어프로토콜레이어에전달하고실제의처리는나중에 bh 에의해나중에실행된다. 스케쥴러의초기화에선가장근본적인 3 개의 bh 를초기화한다. TIMER_BH, TQUEUE_BH, IMMEDIATE_BH 의 3 개이다. 5.7. init() 커널의부팅에필요한기본초기화 (CPU, 메모리등 ) 가끝나면 init 프로세스가만들어지고시스템에존재하는다른하드웨어등을초기화한다음루트디바이스를찾아나머지부팅을시작한다. init 프로세스는 1 번프로세스번호를갖는다. static int init(void * unused) { lock_kernel(); (1) do_basic_setup(); (2) (3) (4) prepare_namespace(); /* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */ free_initmem(); unlock_kernel(); if (open("/dev/console", O_RDWR, 0) < 0) printk("warning: unable to open an initial console.\n"); (void) dup(0); (void) dup(0); /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ (5) (6) } if (execute_command) execve(execute_command,argv_init,envp_init); execve("/sbin/init",argv_init,envp_init); execve("/etc/init",argv_init,envp_init); execve("/bin/init",argv_init,envp_init); execve("/bin/sh",argv_init,envp_init); execve("/sbin/init",argv_init,envp_init); panic("no init found. Try passing init= option to kernel."); (1) 여기까지의상태는시스템이이제사용가능한상태까지는왔지만붙어있는다른모든디 114

바이스에대해선초기화가되지않은상태다. CPU 하부시스템, 메모리그리고프로세스관리는동작하는상태다. 이제할것은나머지디바이스들을모두초기화하는일을하는것이다. 초기화하는목록은다음과같다. 이외에더있으나중요한것만조금간추렸다. mtrr 현재는 i386 에서만존재하는기능으로 MTRR(Memory Type Range Register) 를말한다. PCI 나 AGP 비디오카드를좀더빨리쓸수있도록해준다. sysctrl proc file system 을사용하도록설정되어있으면이를초기화해준다. pci PCI 시스템을초기화한다. PCI 의루트디바이스를초기화하고이어 PCI 버스에연결된모든다른하드웨어를찾아리스트에등록한다. isapnp ISA 버스에물려있는 PnP 디바이스를초기화한다. socket 사용되는프로토콜을초기화한다. 소켓용슬랩도초기화하고 netlink, netfileter 등도초기화한다. context thread keventd 를 kernel thread 로실행한다. pcmcia PCMCIA 디바이스초기화한다. (2) 무엇을어디서마운트할지결정한다. 루트디바이스를마운트하고램디스크를읽어들이는일도한다. (3) 바로전까지실행되면이제커널이완전히부팅한것으로봐도된다. 커널부팅에사용된메모리중필요없는것을반환한다. (4) 초기콘솔을열고 stdin, stdout, stderr 을 open 한다. (5) 이제마운트된루트파일시스템에서 init 를찾아실행해준다. (6) init 를찾지못하면여기와서커널의부팅이멈춘다. 여기까지온다는것은아마도루트파일시스템을마운트하지못했거나루트파일시스템에 init 가없기때문일것이다. 5.8. dmesg 정리 /var/log/dmesg 는부팅하는동안커널의기록을남겨놓은파일이다. 이파일의출력을구분지어어느단계에서어떤메시지가출력되는지보자. 단계를구분지어놓으면 start_kernel() 을분석하는데많은도움이될것이고커널부팅중에에러가났다면어느단계에서에러났는지범위를좁히고찾아내는데많은도움이될것이다. 115

(1) Linux version 2.4.16 (root@halite) (gcc version 2.95.3 20010315 (release)) 22 2002. 02. 27. ( 수 ) 13:30:14 KST (2) BIOS-provided physical RAM map: BIOS-e820: 0000000000000000-000000000009fc00 (usable) BIOS-e820: 000000000009fc00-00000000000a0000 (reserved) BIOS-e820: 00000000000f0000-0000000000100000 (reserved) BIOS-e820: 0000000000100000-000000001ffec000 (usable) BIOS-e820: 000000001ffec000-000000001ffef000 (ACPI data) BIOS-e820: 000000001ffef000-000000001ffff000 (reserved) BIOS-e820: 000000001ffff000-0000000020000000 (ACPI NVS) BIOS-e820: 00000000ffff0000-0000000100000000 (reserved) (3) On node 0 totalpages: 131052 zone(0): 4096 pages. zone(1): 126956 pages. zone(2): 0 pages. (4) Local APIC disabled by BIOS -- reenabling. Found and enabled local APIC! (5) Kernel command line: BOOT_IMAGE=linux ro root=301 mem=nopentium hdd=ide-scsi ide_setup: hdd=ide-scsi (6) Initializing CPU0 (7) Detected 1009.016 MHz processor. (8) Console: colour VGA+ 132x43 (9) Calibrating delay loop... 2011.95 BogoMIPS (10) Memory: 512920k/524208k available (1213k kernel code, 10900k reserved, 482k data, 228k init, 0k highmem) Checking if this processor honours the WP bit even in supervisor mode... Ok. (11) Dentry-cache hash table entries: 65536 (order: 7, 524288 bytes) (12) Inode-cache hash table entries: 32768 (order: 6, 262144 bytes) (13) Mount-cache hash table entries: 8192 (order: 4, 65536 bytes) (14) Buffer-cache hash table entries: 32768 (order: 5, 131072 bytes) (15) Page-cache hash table entries: 131072 (order: 7, 524288 bytes) (16) CPU: Before vendor init, caps: 0183fbff c1c7fbff 00000000, vendor = 2 Intel machine check architecture supported. Intel machine check reporting enabled on CPU0. CPU: L1 I Cache: 64K (64 bytes/line), D cache 64K (64 bytes/line) CPU: L2 Cache: 256K (64 bytes/line) CPU: After vendor init, caps: 0183fbff c1c7fbff 00000000 00000000 CPU: After generic, caps: 0183fbff c1c7fbff 00000000 00000000 CPU: Common caps: 0183fbff c1c7fbff 00000000 00000000 CPU: AMD Athlon(tm) Processor stepping 02 Enabling fast FPU save and restore... done. Checking 'hlt' instruction... OK. (17) POSIX conformance testing by UNIFIX (18) enabled ExtINT on CPU0 ESR value before enabling vector: 00000000 ESR value after enabling vector: 00000000 (19) Using local APIC timer interrupts. (20) calibrating APIC timer...... CPU clock speed is 1009.0421 MHz.... host bus clock speed is 201.8084 MHz. (21) 116

cpu: 0, clocks: 2018084, slice: 1009042 CPU0<T0:2018080,T1:1009024,D:14,S:1009042,C:2018084> (22) mtrr: v1.40 (20010327) Richard Gooch (rgooch@atnf.csiro.au) mtrr: detected mtrr type: Intel (23) PCI: PCI BIOS revision 2.10 entry at 0xf1180, last bus=1 (24) PCI: Using configuration type 1 (25) PCI: Probing PCI hardware (26) Unknown bridge resource 0: assuming transparent (27) PCI: Using IRQ router VIA [1106/0686] at 00:04.0 PCI: Found IRQ 10 for device 00:0b.0 PCI: Sharing IRQ 10 with 00:11.0 PCI: Found IRQ 5 for device 00:0d.0 PCI: Sharing IRQ 5 with 00:04.2 PCI: Sharing IRQ 5 with 00:04.3 PCI: Disabling Via external APIC routing (28) isapnp: Scanning for PnP cards... isapnp: No Plug & Play device found (29) Linux NET4.0 for Linux 2.4 Based upon Swansea University Computer Society NET3.039 (30) Initializing RT netlink socket (31) apm: BIOS version 1.2 Flags 0x03 (Driver version 1.15) (32) Starting kswapd JFS development version: $Name: $ (33) ACPI: APM is already active, exiting (34) pty: 256 Unix98 ptys configured (35) Serial driver version 5.05c (2001-07-08) with MANY_PORTS SHARE_IRQ SERIAL_PCI ISAPNP enabled ttys01 at 0x02f8 (irq = 3) is a 16550A (36) block: 128 slots per queue, batch=32 (37) Uniform Multi-Platform E-IDE driver Revision: 6.31 (38) ide: Assuming 33MHz system bus speed for PIO modes; override with idebus=xx (39) VP_IDE: IDE controller on PCI bus 00 dev 21 VP_IDE: chipset revision 16 VP_IDE: not 100% native mode: will probe irqs later VP_IDE: VIA vt82c686a (rev 22) IDE UDMA66 controller on pci00:04.1 ide0: BM-DMA at 0xd800-0xd807, BIOS settings: hda:dma, hdb:dma ide1: BM-DMA at 0xd808-0xd80f, BIOS settings: hdc:dma, hdd:dma (40) PDC20265: IDE controller on PCI bus 00 dev 88 PCI: Found IRQ 10 for device 00:11.0 PCI: Sharing IRQ 10 with 00:0b.0 PDC20265: chipset revision 2 PDC20265: not 100% native mode: will probe irqs later ide2: BM-DMA at 0x8000-0x8007, BIOS settings: hde:dma, hdf:dma ide3: BM-DMA at 0x8008-0x800f, BIOS settings: hdg:dma, hdh:pio (41) hda: Maxtor 4W080H6, ATA DISK drive hdb: IC35L040AVER07-0, ATA DISK drive hdc: QUANTUM FIREBALLlct15 20, ATA DISK drive hdd: LG CD-RW CED-8080B, ATAPI CD/DVD-ROM drive (42) ide0 at 0x1f0-0x1f7,0x3f6 on irq 14 ide1 at 0x170-0x177,0x376 on irq 15 hda: 160086528 sectors (81964 MB) w/2048kib Cache, CHS=9964/255/63, UDMA(33) 117

hdb: 80418240 sectors (41174 MB) w/1916kib Cache, CHS=5005/255/63, UDMA(33) hdc: 39876480 sectors (20417 MB) w/418kib Cache, CHS=39560/16/63, UDMA(33) (43) Partition check: hda: hda1 hda2 hdb: hdb1 hdc: [PTBL] [2482/255/63] hdc1 hdc2 hdc3 (44) Floppy drive(s): fd0 is 1.44M FDC 0 is a post-1991 82077 (45) Linux agpgart interface v0.99 (c) Jeff Hartmann agpgart: Maximum main memory to use for agp memory: 439M agpgart: Detected Via Apollo Pro KT133 chipset agpgart: AGP aperture is 128M @ 0xe0000000 [drm] AGP 0.99 on VIA Apollo KT133 @ 0xe0000000 128MB [drm] Initialized mga 3.0.2 20010321 on minor 0 (46) Linux Kernel Card Services 3.1.22 options: [pci] [cardbus] [pm] (47) NET4: Linux TCP/IP 1.0 for NET4.0 IP Protocols: ICMP, UDP, TCP, IGMP IP: routing cache hash table of 4096 buckets, 32Kbytes TCP: Hash tables configured (established 32768 bind 32768) NET4: Unix domain sockets 1.0/SMP for Linux NET4.0. (48) ds: no socket drivers loaded! (49) request_module[nls_euc-kr]: Root fs not mounted Unable to load NLS charset EUC-KR (50) VFS: Mounted root (jfs filesystem) readonly. (51) Freeing unused kernel memory: 228k freed (1) start_kernel() (2) setup_memory_region()/setup_arch()/start_kerenel() (3) free_area_init_core()/free_area_init()/paging_init()/setup_arch()/start_kernel() (4) detect_init_apic()/init_apic_mappings()/setup_arch()/start_kernel() (5) start_kernel() (6) cpu_init()/trap_init()/start_kernel() (7) time_init()/start_kernel() (8) con_init()/console_init()/start_kernel() (9) calibrate_delay()/start_kernel() (10) mem_init()/start_kernel() (11) dcache_init()/vfs_caches_init()/start_kernel() (12) inode_init()/vfs_caches_init()/start_kernel() (13) 118

119 mnt_init()/vfs_caches_init()/start_kernel() (14) buffer_init()/start_kernel() (15) page_cache_init()/start_kernel() (16) identify_cpu()/check_bugs()/start_kernel() (17) start_kernel() (18) setup_local_apic()/apic_init_uniprocessor()/smp_init()/start_kernel() (19) setup_apic_clocks()/apic_init_uniprocessor/smp_init()/start_kernel() (20) calibrate_apic_clock()/setup_apic_clocks()/apic_init_uniprocessor/smp_init()/ start_kernel() (21) setup_apic_timer()/setup_apic_clocks()/apic_init_uniprocessor/smp_init()/start_kernel() (22) mtrr_setup()/mtrr_init()/do_basic_setup()/init()/rest_init()/start_kernel() (23) check_pcibios()/pci_find_bios()/pcibios_config_init()/pcibios_init()/pci_init()/ do_basic_setup()/init()/rest_init()/start_kernel() (24) pci_check_direct()/pcibios_config_init()/pcibios_init()/pci_init()/ do_basic_setup()/init()/rest_init()/start_kernel() (25) pcibios_init()/pci_init()/do_basic_setup()/init()/rest_init()/start_kernel() (26) pci_read_bridge_bases()/pcibios_fixup_bus()/pci_do_scan_bus()/pci_scan_bus()/ pcibios_init()/pci_init()/do_basic_setup()/init()/rest_init()/start_kernel() (27) pirq_find_router()/pcibios_irq_init()/ pcibios_init()/pci_init()/do_basic_setup()/init()/rest_init()/start_kernel() (28) isapnp_init()/do_basic_setup()/init()/rest_init()/start_kernel() (29) sock_init()/do_basic_setup()/init()/rest_init()/start_kernel() (30) rtnetlink_init()/sock_init()/do_basic_setup()/init()/rest_init()/start_kernel() (32) kswapd_init()/do_initcalls()/do_basic_setup()/init()/rest_init()/start_kernel() (33) acpi_init()/do_initcalls()/do_basic_setup()/init()/rest_init()/start_kernel() (34) pty_init()/do_initcalls()/do_basic_setup()/init()/rest_init()/start_kernel() (35) show_serial_version()/rs_init()/do_basic_setup()/init()/rest_init()/start_kernel() (36) blk_dev_init()/do_basic_setup()/init()/rest_init()/start_kernel() (37) ide_init()/do_basic_setup()/init()/rest_init()/start_kernel() (38) ide_system_bus_speed()/ide_init()/do_basic_setup()/init()/rest_init()/start_kernel() (39) ide_scan_pcidev()/ide_scan_pcibus()/probe_for_hwifs()/ide_init_builtin_drivers()/ ide_init()/do_basic_setup()/init()/rest_init()/start_kernel() (40)

ide_setup_pci_device()/ide_scan_pcidev()/ide_scan_pcibus()/probe_for_hwifs()/ ide_init_builtin_drivers()/ide_init()/do_basic_setup()/init()/rest_init()/start_kernel() (41) do_identify()/actual_try_to_identify()/try_to_identify()/do_probe()/probe_for_drive()/ probe_hwif()/ideprobe_init()/init_module()/do_basic_setup()/init()/rest_init()/start_kernel() (42) init_irq()/hwif_init()/ideprobe_init()/init_module()/do_basic_setup()/init()/rest_init()/ start_kernel() (43) check_partition()/grok_partitions()/idedisk_revalidate/init_module()/do_basic_setup()/ init()/rest_init()/start_kernel() (44) config_types()/floppy_init()/do_basic_setup()/init()/rest_init()/start_kernel() (45) agp_init()/do_basic_setup()/init()/rest_init()/start_kernel() (46) init_pcmcia_cs()/do_basic_setup()/init()/rest_init()/start_kernel() (47) inet_init()/do_basic_setup()/init()/rest_init()/start_kernel() (48) init_pcmcia_ds()/do_basic_setup()/init()/rest_init()/start_kernel() (49) request_module()/load_nls()/init_nls_euc_kr()/do_basic_setup()/init()/ rest_init()/start_kernel() (50) mount_root()/prepare_namespace()/init()/rest_init()/start_kernel() (51) free_initmem()/init()/rest_init()/start_kernel() 커널의초기화동안등록된드라이버들이자동으로실행되고초기화되도록하는데, 커널을어떻게설정하는가에따라커널에포함되는것이다르므로일일히기록하는대신에자동으로커널컴파일하는동안포함되도록할수있다. 필요한모듈에 init 란속성을사용하고 module_init() 를사용해이런일이가능하도록한다. module_init() 는 include/linux/init.h 에다음과같이정의되어있다. define module_init(x) initcall(x); _initcall(x) 는 module 로지정됐는가아닌가에따라정의가달라지는데어찌됐든.initcall.init 란섹션에속하게된다. 여기에속하면커널의링킹동안모아진.initcall.init 가 do_basic_setup() 혹은 do_initcalls() 에의해자동으로불려지게된다 (vmlinux.lds 를보면.initcall.init 를모아주는부분이있다 ). 120

6. 디바이스드라이버 임베디스시스템개발자가커널의포팅이후에필요한작업이자신이만든새로운시스템에존재하는많은디바이스를사용하는것이다. 예를들어일반적이지않는통신포트를하나갖고있는시스템을만들었고이미커널은포팅되서동작한다고가정하면이통신포트를사용하기위한일을해줘야한다. 보통은이포트에대한디바이스드라이버를만들어사용하면될것이다. 그렇다면리눅스에선어떤식으로디바이스드라이버가만들어지는지알아보자. 6.1. 디바이스번호 처음유닉스접했을때 ( 사실유닉스라기보다는리눅스가맞겠다 ) /dev 디렉토리에들어있는것들이무엇인지궁금한적이있었다. 사운드블래스터카드에붙이는 CD-ROM 드라이브를사용하기위해 How-to 문서를뒤적여 mknod 란것도처음사용해보곤했지만 /dev 디렉토리내의파일이갖는정확한의미를알지는못했다. 나중에리눅스에서프로그래밍을하면서깨닫게됐지만 /dev 디렉토리가의미하는것은 device 의약자이고여기에들어있는것들은어떤물리적인디바이스를나타낸다는것을알았다. 예를들어 o/dev/ttys0 와 /dev/fd0 는다음과같다. 참고로 ttys0 는시리얼포트 1 번을의미하고 ( 일반적으로 PC 에서 COM1 으로불린다 ) fd0 는플로피디스크드라이브첫번째것으로 a: 를의미한다. crw-rw---- 1 root uucp 4, 64 4 월 30 11:23 /dev/ttys0 brw-rw---- 1 root floppy 2, 0 4 월 30 11:23 /dev/fd0 ttys0 는속성에보면 'c' 가처음에시작하는데이문자가의미하는것은 'character device' 즉문자디바이스를의미한다. 이에반해 fd0 는 'b' 로시작하고 'block device' 즉블럭디바이스를의미한다. 문자 / 블럭디바이스에대한내용은다음을참조하라. 문자디바이스문자디바이스는하나혹은수십내지수백개의가변크기의버퍼를사용해디바이스에일고쓰기를한다. 블럭디바이스블럭이라불리는일정크기의버퍼 (512, 1K Bytes 등, 장치의존적 ) 단위로데이터의읽기쓰기가행해진다. 그리고나오는것이 owner, group 인데이것은각디바이스에따라다르므로각디바이스의속석을참조하기바란다. 그리고여기서얘기해야할가장중요한부분이나오는데일반적인파일의경우 'ls -l' 로보면그룹을나타내는곳뒤에파일의크기가나오지만 /dev 내의것은크기가아니라두개의숫자가나온다. 이숫자가의미하는것은디바이스의번호로리눅스시스템에서혹은유닉스시스템에서는정해진유일한번호를갖는다. 즉 ttys0 는무조건 4, 64 의번호를가져야이디바이스가첫 121

번째시리얼포트를나타내게된다. 번호를 4, 64 를갖고이름이다른경우라도상관없이첫번째시리얼포트를나타낸다. 4, 64 에서첫번째것은주 (major) 디바이스번호고두번째것은부 (minor) 디바이스번호다. 주번호가의미하는것은이디바이스가무엇인지를나타내고부번호가나타내는것은이디바이스의몇번째것혹은여러종류중의구분을의미한다. 예를들어 PC 엔시리얼포트가적어도 2 개가있고많게는 4 개까지도존재한다. 이런경우라면여러개모두시리얼포트이기때문에주번호는시리얼포트란것을나태내도록통일해주고 (4 번이시리얼포트를의미한다 ) 부번호를사용해각각의시리얼포트를구분해주게된다 (64 번이 COM1, 65 번이 COM2). 리눅스내의모든디바이스는반드시주 / 부디바이스번호를사용해구분되야한다. 번호가같은디바이스는이름이다를지라도같은디바이스를의미한다. 아래는필자의 PC 에사용중인다비이스들을나열한것이다. 이정보는 /proc/device 를읽어보면알수있다. 각디바이스의이름앞번호는디바이스의주번호를의미한다. Character devices: 1 mem 2 pty 3 ttyp 4 ttys 5 cua 7 vcs 10 misc 13 input 14 sound 29 fb 116 alsa 119 vmnet 128 ptm 136 pts 162 raw 180 usb 226 drm Block devices: 2 fd 3 ide0 8 sd 9 md 11 sr 22 ide1 65 sd 66 sd 리눅스에등록된디바이스리스트는커널소스디렉토리의 Documents/devices.txt 에있거나 http://www.lanana.org/docs/device-list/ 에서확인할수있다. 주 / 부번호모두 255 까지가능하다. 위의리스트에등록됐다고하지만실제많이쓰이지않는디바이스에대한것은 /dev 에존재하지않는경우도있고새로필요해만든디바이스드라이버에게번호를할당하기위해선필요없는것에서번호를선택하거나아니면현재시스템을검색해사용하지않는번호를할당할수도있다. 리눅스에서사용가능한주번호는 60~63, 120~127, 240~254 까지고여기엔아무런것도할당되어있지않은상태다. 122

6.2. 샘플디바이스드라이버 디바이스드라이버를만드는방법에대해알아보자. 우선대부분의문서에나오는간단한예제를이용해보자. /* hello.c */ (1) ifndef KERNEL define KERNEL endif ifndef MODULE define MODULE endif (2) define NO_VERSION include <linux/module.h> include <linux/version.h> include <linux/fs.h> (3) struct file_operations Fops = { NULL, /* owener */ NULL, /* llseek */ NULL, /* read */ NULL, /* write */ NULL, /* readdir */ NULL, /* poll */ NULL, /* ioctl */ NULL, /* mmap */ NULL, /* open */ NULL, /* flush */ NULL, /* release */ NULL, /* fsync */ NULL, /* lock */ NULL, /* readv */ NULL, /* writev */ NULL, /* sendpage */ NULL /* get_unmapped_area */ }; (4) int init_module() { if (register_chrdev(213, "hello", &Fops) < 0) return -EIO; printk("hello.o start\n"); } return 0; (5) void cleanup_module() { unregister_chrdev(213, "hello"); printk("hello.o end\n"); } 컴파일은다음과같이한다. gcc -o hello.o -c -D KERNEL -DMODULE -O -Wall -I/usr/include hello.c 123

컴파일할때컴파일러에게커널에해당하는프로그램임과이것이 MODULE 임을알려준다. 일반적인프로그램과는달리 -c 옵션을사용해링킹을하진않아야한다. 에러없이컴파일됐으면다음과같은명령으로만들어진디바이스드라이버를등록해보자. insmod hello.o 화면에뭔가출력되는가? 아무것도출려되지않으면 dmesg 명령을사용해커널에서뿌린메시지의마지막에 'hello.o start' 가찍혔는지확인한다. 또 lsmod 명령으로정상적으로 hello.o 가등록됐느지확인해보자. 아래것은필자의리눅스시스템에올라간모듈들을본것이다. 제일위에 hello.o 가등록된것이보일것이다. 비록아무것도사용하지않는다고나와있지만처음만들어본디바이스드라이버가등록된것을확인할수있다. Module Size Used by hello 592 0 (unused) smbfs 31376 4 (autoclean) sd_mod 10640 2 (autoclean) vfat 9520 1 (autoclean) fat 29696 0 (autoclean) [vfat] sr_mod 12176 0 (autoclean) tuner 8176 0 (autoclean) (unused) i2c-core 13024 0 (autoclean) [tuner] vmnet 17984 6 parport_pc 19648 0 (unused) parport 14176 0 [parport_pc] vmmon 18784 0 (unused) 3c59x 24960 1 ide-scsi 7648 0 ide-cd 26656 0 cdrom 29056 0 [sr_mod ide-cd] md 44224 0 (unused) snd-pcm-oss 35792 0 (unused) snd-mixer-oss 8528 1 [snd-pcm-oss] snd-card-fm801 7296 1 snd-pcm 47744 0 [snd-pcm-oss snd-card-fm801] snd-mpu401-uart 2656 0 [snd-card-fm801] snd-rawmidi 11968 0 [snd-mpu401-uart] snd-ac97-codec 22832 0 [snd-card-fm801] snd-opl3 5264 0 [snd-card-fm801] snd-timer 9584 0 [snd-pcm snd-opl3] snd-hwdep 3376 0 [snd-opl3] snd-seq-device 3744 0 [snd-rawmidi snd-opl3] snd 23632 0 [snd-pcm-oss snd-mixer-oss snd-card-fm801 snd-pcm sndmpu401-uart snd-rawmidi snd-ac97-codec snd-opl3 snd-timer snd-hwdep snd-seq-device] hid 19152 0 (unused) input 3360 0 [hid] usb-storage 26400 1 scsi_mod 88400 4 [sd_mod sr_mod ide-scsi usb-storage] usb-uhci 21408 0 (unused) usbcore 49632 1 [hid usb-storage usb-uhci] rtc 5600 0 (autoclean) 이어 'rmmod hello' 란명령을실행해보자. insmod 때와마찬가지로화면에아무것도출력되지않으면 dmesg 를사용해또확인해보자. 'hello.o end' 란말이출력됐는가? 이어 lsmod 를사용해 hello.o 가해제됐는지확인해보기바란다. hello.c 를간단하게분석해보자. (1) 124

모듈로만들어지는디바이스드라이버는 KERNEL 과 MODULE 이반드시정의되야만한다. (2) 필요한헤더를읽어들인다. (3) file_operations 라는구조체로모든모듈엔반드시존재해야한다. hello.c 는아무동작도하지않기때문에여기에아무것도채워넣지않았지만다른디바이스드라이버를만들땐알맞는항목을채워넣어동작하도록해줘야한다. (4) init_module() 은 insmod 명령등을이용해디바이스드라이버를커널에등록할때무조건처음실행되는함수다. 다시말해모든모듈엔 init_module() 과 cleanup_module() 이존재해야한다. 보통은 init_module() 에서디바이스드라이버를주 / 부번호를사용해등록하는함수를실행한다. (5) cleanup_module() 은모듈을제거할때커널에의해무조건불리는함수로 init_module() 과는반대로등록된모듈을해제하는함수를부른다. 만약위의예제를 X-window 상에서 insmod/rmmod 한다면화면에아무것도나오지않을것이다. 이는 printk 의출력이가상터미널 7 번에출력되기때문이다. 그러므로 xterm 에옵션을주지않고그냥연창엔출력되지않으므로 demsg 를사용해확인해야한다. 대신 xterm -C 로연 xterm 에선바로확인이가능할것이다. 예제는그크기가작기때문에하나의파일에모두들어가지만일반적인경우한개의파일에모듈전체의내용이들어가지않을경우엔소스코드여러개에나눠쓰게된다. 이런경우엔각각의파일에모듈에필요한정의를하고컴파일후링커를사용해합쳐주면된다. gcc -D KERNEL -DMODULE -Wall -O -c -o start.o start.c gcc -D KERNEL -DMODULE -Wall -O -c -o stop.o stop.c ld -m elf_i386 -r -o hello.o start.o stop.o start.c 에 init_module() 이들어있고 stop.c 에 cleanup_module() 이들어있다면각각을컴파일한후 ld 를사용해하나로묶어준다. 6.3. 모듈동작의이해 모듈은등록될때디바이스번호를등록하고이와함께 file_operations 라는구조체를커널에알려준다. file_operations 는 include/linux/fs.h 에정의되어있고다음과같다. /* * NOTE: * read, write, poll, fsync, readv, writev can be called * without the big kernel lock held in all filesystems. */ struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); 125

int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); }; 모든디바이스드라이버는사용자가 file_operations 를사용해등록해준표준화되어있는인터페이스를사용해입 / 출력등의일을하게된다. 유닉스에서는디바이스나네트웍이나모두하나의파일처럼동작하도록되어있는데이에따른함수들이등록되어있다. 예를들어디바이스로부터읽기동작을원한다면 file_operations 에등록된 read 함수를사용해읽기를한다. file_operations 는모두채울필요는없다. 필요하거나지원되야하는것을채워넣으면된다. 그러나범용적인디바이스를만든다면되도록모든함수를다채워넣어주는것이좋을것이다. 그러나 file_operations 에존재하는함수의개수에제약이있으므로디바이스에대해 file_operations 외의다른기능혹은함수를원하는경우엔 ioctl 을사용한다. ioctl 의 C 라이브러리내의정의는다음과같이되어있다. include <sys/ioctl.h> int ioctl(int d, int request,...) ioctl 함수를사용할때 request 란숫자를전달해주는데이것이 ioctl 에의해불리는함수의인덱스가된다. 즉 ioctl 로불리는함수는 switch 문과같은것을이용해 request 로전달된값을비교해해당함수를다시호출해주게된다. 다음소스는하드디스크의시리얼번호를읽어내는기능을하는디바이스드라이버를만들어본것이다. /* hddinfo.c */ ifndef KERNEL define KERNEL endif ifndef MODULE define MODULE endif define NO_VERSION include <linux/module.h> include <linux/version.h> include <linux/errno.h> include <linux/fs.h> include <linux/ide.h> include <asm/uaccess.h> define HDA define HARDDISK HDA 0x0300 char kernel_version[] = UTS_RELEASE; static int hddinfo_open(struct inode *node, struct file *f) { return 0; } 126

static int hddinfo_release(struct inode *node, struct file *f) { return 0; } static ssize_t hddinfo_read(struct file *f, char *buf, size_t nbytes, loff_t *ppos) { return nbytes; } static ssize_t read_serial(char *dst) { ide_drive_t *drv; drv = get_info_ptr(harddisk); if (drv) copy_to_user(dst, drv->id->serial_no, 20); else { ;//PDEBUG("HDDINFO : Cannot get the drive information\n"); return 0; } } return 20; int hddinfo_ioctl(struct inode *node, struct file *f, unsigned int ioctl_num, unsigned long ioctl_param) { switch (ioctl_num) { case 0 : read_serial((char *)(ioctl_param)); break; } } return 0; struct file_operations Fops = { NULL, NULL, hddinfo_read, NULL, NULL, NULL, hddinfo_ioctl, NULL, hddinfo_open, NULL, hddinfo_release }; int init_module() { if (register_chrdev(212, "hddinfo", &Fops) < 0) { //PDEBUG("HDDINFO : Unable to register driver.\n"); return -EIO; } } return 0; void cleanup_module() { if (unregister_chrdev(212, "hddinfo") < 0) ;//PDEBUG("HDDINFO : Unable to unregister\n"); } 127

hddinfo.c 에서정의된 file_operations 는 read/ioctl/open/release 만을사용한다. open 과 release 는이디바이스를 open/close 할때불리므로디바이스를사용하기전에초기화해야하거나혹은사용을중지하기전에또필요한작업을해야하는경우이함수들에필요한기능을넣으면된다. hddinfo.c 에서는 open/close 에따른작업을할필요가없어아무것도넣지않고그냥 0 을리턴하는기능을넣어예제로올렸다. read 를사용해하드디스크의시리얼번호를읽도록해도되지만여기선 ioctl 의사용을보기위해일부러 read 에서할읽을 ioctl 로뽑아만들어봤다. 이모듈의사용은아래와같은프로그램으로동작시킨다. include <stdio.h> include <unistd.h> include <sys/ioctl.h> include <stdlib.h> include <fcntl.h> int main() { int fd; char buf[256]; fd = open("/dev/hdd_info", O_RDWR); if (fd < 0) { printf("device open error.\n"); return -1; } ioctl(fd, 0, buf); printf("buf : %s\n", buf); close(fd); } return 0; test.c 를동작시키기전에 /dev/hdd_info 를만들어줘야한다. 'mknod c 212 0 /dev/hdd_info' 로만들어준다. 일반적으로디바이스를사용하기위해디바이스파일을사용해디바이스를 open 한다. 여기에서얻어지는핸들을사용해디바이스에읽고쓰기동작등을한후다사용했으면 close 로사용을중지해준다. 이절차가전형적인절차에해당한다. test.c 에서도 open 후 ioctl 의 0 번함수를호출해 hddinfo.c 의 read_serial() 을불러하드디스크의시리얼번호를읽어온다. 하드디스크의시리얼번호는커널부팅할때이미얻어진하드디스크에대한정보를갖고있는구조체에서복사한다. 6.4. 알아야할것들 디바이스드라이버를만들기위한아주기본적인것은이미알았을것이다. 이제는좀더복잡하지만알아야만하는것들에대해얘기해보자. 버전모듈을만드는것은커널의버전과밀접한관계가있고커널의버전이변경되면서디바이스드라이버의구조자체도조금씩변하므로여러버전의커널에서동시에사용될수있도록 128

만들기위해선커널의버전을구분해컴파일되고동작되도록해줘야한다. 리눅스에선현재커널의버전을 LINUX_VERSION_CODE 로나타낸다. 그리고 KERNEL_VERSION 이란매크로가있어이것을사용하면 LINUX_VERSION_CODE 와비교할수있게된다. 디바이스번호동적할당디바이스번호가이미정해진것이많기때문에그값을정해쓸필요가없는경우엔현재시스템에서사용하지않는번호를찾아사용하면되기때문에모듈을등록하는당시에비어있는번호를동적으로알아내그것을사용한다. 사용은 init_module() 에서다음함수를사용해주번호를얻어온다. define DEVICE_NAME "char_dev" static int Major;... int init_module() { Major = module_register_chrdev(0, DEVICE_NAME, &Fops); }...... module_register_chrdev() 함수의처음값인주번호에 0 을넘겨주면동적으로할당해준다. 리턴되어오는값이음수인경우는에러가있는것이고양수인경우는그것을그대로사용하면된다. cleanup_module() 에선얻어져저장되어있는 Major 변수의값을사용하면된다. Use Count lsmod 명령으로현재시스템에등록된모듈에대해열거해보면세번재항목이 'Used' 인데이것은이모듈이다른모듈에의해멀마나사용되는가를나타낸다. 이값을위한매크로가준비되어있는데 MOD_INC_USE_COUNT, MOD_DEC_USE_COUNT 이고각각을 open 과 release 에넣어주면 lsmod 를사용해값을알수있게된다. /proc 리눅스커널은 /proc 이란파일시스템이존재한다. /proc 엔커널의내부에존재하는정보를얻을수있거나혹은커널이나모듈프로세스로정보를전달하고읽을때사용할수있다. 예를들어 'cat /proc/interrupt' 을해보면현재시스템의인러텁드에대한정보를알수있는데이내용은모두커널내부에들어있는것들이다. /proc 을사용하기위해선 init_module() 에서특정정보를등록해주고 cleanup_module() 에서해제해주면된다. 그러나 /proc 으로는 Use Count 를알수없고특히파일은열리고모듈은제거됐으면결과는예측할수없게된다. /proc 을위한구조체의정의는 include/linux/proc_fs.h 에정의된 proc_dir_entry 다. struct proc_dir_entry { unsigned short low_ino; unsigned short namelen; const char *name; mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; unsigned long size; 129

}; struct inode_operations * proc_iops; struct file_operations * proc_fops; get_info_t *get_info; struct module *owner; struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; write_proc_t *write_proc; atomic_t count; /* use count */ int deleted; /* delete flag */ kdev_t rdev; 실제사용하는것은다음과같다. Ori Pomerantz 가지은 ' 리눅스커널모듈프로그래밍안내서 ' 에서발췌했다. int procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int zero) { int len; static char my_buffer[80]; static int count = 1; if (offset > 0) return 0; len = sprintf(my_buffer, "For the %d%s time, go away!\n", count, (count % 100 > 10 && count % 100 < 14)? "th" : (count % 10 == 1)? "st" : (count % 10 == 2)? "nd" : (count % 10 == 3)? "rd" : "th" ); count++; *buffer_location = my_buffer; } return len; struct proc_dir_entry Our_Proc_File = { 0, 4, "test", S_IFREG S_IRUGO, 1, 0, 0, 80, NULL, procfile_read, NULL }; int init_module() { return proc_register(&proc_root, &Our_Proc_File); } void cleanup_module() { proc_unregister(&proc_root, Our_Proc_File.low_ino); } 변수 130

리눅스에서 Ethernet Card 나 Sound Card 설정을해본사람은이카드들에게특정경우 IO 어드레스나 IRQ 등을지정하기위해 insmod 등을사용하면서같이파라미터를넘겨준경험이있을것이다. 커널모듈은 argc, argv 를받을수없기때문에대신전역변수를사용해값을넘겨줄수있도록되어있다. 변수는전역으로설정하고특정매크로를사용해준다. char *str1, *str2; if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) MODULE_PARM(str1, "s"); MODULE_PARM(str2, "s"); endif 위에서 str1 과 str2 가전역변수로선언되고모듈파라미터로정의됐다. 나중에 insmod 를실행할때 'insmod str1=abc str2=def' 와같이하면되다. 실수모듈은커널과같은레벨에서실행되므로표준라이브러리는사용할수없다. 사용할수있는것은커널함수이고 /proc/ksyms 에서확인할수있다. 인터럽트를사용하는경우처리하는동안다른인터럽트가걸리지않도록하기위해인터럽트를막아놓았다면처리가다끝나고나서는반드시인터럽트를가능하도록해줘야한다. 그러지않으면시스템은먹통이될것이다. 7. 부록 A. SEGA DreamCast Linux 세가사의드림캐스트게임기엔히타치의 SH4 란프로세서가사용된다. 물론 SH4 프로세서가그대로사용되는것은아니고게임기에필요한여러기능이합쳐진형태의프로세서를사용한다. 필자도새로운임베디드시스템에사용할프로세서를수배하다가이것을써볼요량으로드림캐스트를구입하게됐다. 중고로구입했지만사실리눅스보다는게임을더많이했다. SH 프로세서는종류가여러가지인데 SH 뒤의숫자가버전정도의의미를갖는다. ARM 프로세서는 ARM7, ARM9 등과같이말하는데이와비슷한개념이다. SH 는 SuperH 의약자로현재 SH1 부터 SH4 까지나와있는상태다. ARM 과비슷한리스크프로세서로카시오의초기 PDA 등에사용된적이있다. 7.1. A.1. LinuxSH SuperH 프로세서에포팅하는리눅스에대한정보는 SourceForge 에있는 http://linuxsh.sourceforge.net/ 에서정보를얻을수있다. 개발자는주로일본사람들이며 ( 프로세서가일본제니어쩔수없나?) 꽤많이진척되어있고최근의커널버전을지원한다. CVS 를통해최근의커널소스코드를얻을수있다. 필자가가장많이찾는사이트는 http://www.m17n.org/linux-sh/ 로드림캐스트에대한정보를많이얻을수있고 RedHat 에서만든 RedBoot 등을얻을수있다. 또 Debian GNU/Linux on SuperH 에대한정보도얻을수있다. 드림캐스트에서돌아가는리눅스의모습을보려면 http://www.m17n.org/linux-sh/dreamcast/ 를보기바란다. 131

이런것에앞서드림캐스트에서프로그래밍을하려는사람은 http://mc.pp.se/dc/ 를참조바란다. 드림캐스드에서사용되는 cd-rom 을만들기위한준비나만드는방법등이설명되어있다. 필자의드림캐스트에서돌고있는리눅스의모습은 http://ruby.medison.co.kr/~halite 에서확인할수있다. 드림캐스트가 PC 와는달리저장공간도없고메모리도작기때문에뭔가를하려면항상 cd-rom 을읽기때문에무척느리긴하다. 그러나 SuperH 를사용하는사람이고또리눅스를포팅하려는사람이라면충분히좋은테스트기계가될것이다. 7.2. A.2. 드림캐스트에서리눅스실행해보기 이미만들어진이미지를사용해드림캐스트에서실행되는리눅스를맛볼수있다. ftp://ftp.m17n.org/pub/super-h/dreamcast/ 에서미리만들어진이미지를받아사용하면된다. 만드는방법은 http://www.m17n.org/linux-sh/dreamcast/distribution 을참조하고미리 IP.BIN 이란것을만들어둬야한다. IP.BIN 은 http://mc.pp.se/dc/files/makeip.tar.gz 을사용해만들기바란다. 만들때 CD-R 을사용하는데 cdrecord 란프로그램에서제대로지원하지못하는경우도있기때문에 cdrecord 사이트에서지원하는것인지확인한후진행하기바란다. 필자는 LG CED-8080B 를가지고있고지원하는드라이브라고나와있지만제대로구어지지않아여러장의 CD-R 을버리고나서 SONY VAIO GR9/K 에있는 CD-RW/DVD 콤보로굽는데성공했다. 8. 부록 B. 리눅스에시스템콜만들어넣기 커널을임베디스시스템에포팅해넣다보면가끔나만의시스템콜을사용할때가있는데직접시스템콜을하나만들어넣고이를통해커널의소스구조를조금알아보자. 8.1. B.1. 시스템콜의흐름리눅스내에서시스템콜이발생하면진행되는흐름은다음과같다. 1. 사용자프로세스 2. libc.a 아규먼트스택에넣음시스템콜번호저장트랩 (trap) 발생 3. system_call() 132

IDT에의해트랩을시작진짜핸들러실행 sys_call_table 사용 4. 진짜시스템콜핸들러 8.2. B.2. IDT(Interrupt Descriptor Table) 시스템콜을호출하면최종적으로트랩을발생시키는데이것은소프트웨어인터럽트라고이해하면된다. i386 에선 IDT 를통해모든인터럽트가관리되는데시스템콜은 0x80 번의인터럽트를사용한다. 아래에 IDT 의구조가나와있다. 그림 B-1. IDT 구조 8.3. B.3. 시스템콜테이블 0x80 트랩핸들러는모든시스템콜에의해불려지고이핸들러는불려질당시의시스템콜번호를가지고해당시스템콜을시스템콜테이블에서찾아실행해준다. 모든시스템콜의번호는 $(TOPDIR)/include/asm/unistd.h 에정의되어있다. 133

define NR_exit 1 define NR_fork 2 define NR_read 3... define NR_vfork 190 시스템콜테이블은 $(TOPDIR)/arch/i386/kernel/entry.S 에정의되어있고각시스템콜의주소가연속되게적혀져있다..long SYMBOL_NAME(sys_ni_syscall).long SYMBOL_NAME(sys_exit).long SYMBOL_NAME(sys_fork).long SYMBOL_NAME(sys_read)....long SYMBOL_NAME(sys_vfork).rept NR_syscalls-190 그림 B-2 은 fork() 가실행될때의흐름을나타낸그림으로이해에도움을줄수있을것이다. 그림 B-2. fork() 가실행될때의흐름 134

8.4. B.4. 시스템콜추가 시스템콜을추가해보자. 우선새로운시스템콜이들어있는파일을만들자 ($TOPDIR)/kernel/mysyscall.c /* $(TOPDIR)/kernel/mysyscall.c */ include <linux/linkage.h> asmlinkage int sys_mysyscall() { printk("my First System Call.\n"); } $(TOPDIR)/include/asm/unistd.h 에새로운시스템콜을위한번호를추가한다.... define NR_vfork 190 define NR_mysyscall 191 $(TOPDIR)/i386/kernel/entry.S에있는시스템콜테이블에등록한다. ENTRY(sys_call_table)....long SYSBOL_NAME(sys_mysyscall).rept NR_syscalls-191 커널을컴파일하는데위에서만든 mysyscall.c 를 Makefile 에등록해준다. 간단히 135

'O_OBJS=' 이란줄에 mysyscall.o 라고추가해주면된다. 커널컴파일이끝나면새로운커널을설치하고재부팅한다음아래와같은테스트프로그램을만들어실행해보자. /* test.c */ include <linux/unistd.h> _syscall0(int, mysyscall); int main() { int i; i = mysyscall(); } return i; 테스트프로그램을실행했을때화면에 'My First System Call' 이라고출려되면다행인데아무런출력도없다면 dmesg 를사용해커널출력을확인해보자. 제일끝에문장이제대로찍혔는가? 136

9. 부록 C. Inline Assembly 인라인어셈블리는어셈블리로짜는소스코드가아닌다른언어내에들어가는어셈블리형태의코드를말한다. 예를들어많이사용하는 C 내에서 C 로는할수없는일이나속도가아주빠른작업을원할때직접어셈블리코드를입력해사용할수있다. 시스템프로그래밍이나성능을높여야하는경우에보통은어셈블리를사용한다. 즉프로그램의대부분을 C 나 C++ 등으로만들고고급언어로는할수없거나, 그부분에어셈블리를써서속도향상이가능할때그부분만을어셈블리로만들어 C 나 C++ 로만든나머지부분과같이쓰게된다. C 로된부분과어셈블리로된부분을같이동작시키는데는크게두가지방법이있다. 하나는어셈블리로쓴부분을독립된함수로만들어따로어셈블한후에오브젝트화일을링크시키는방법이있고나머지하나는인라인어셈블리를쓰는방법이있다. 어셈블리파일을따로만드는경우엔어셈블리함수가 C 의함수호출방식을따르도록해주면 C 와사용하기쉬우므로크기가큰경우라면이방법을쓰는것이좋다. 그러나일부분에서만어셈블리가필요하거나특히고급언어가사용하지못하는프로세서의특정한기능을쓰기위해어셈블리를쓸때는많아도이삼십개정도의명령만을어셈블리로만들면되는경우가대부분이고이를위해서따로함수를만들어링크하는것은번거로운데다가자주호출되는경우라면성능에도영향을미친다. 이런경우에인라인어셈블리를쓰게된다 9.1. C.1 절. 인라인어셈블리기초 9.1.1. C.1.1 절. 알아야할것들 인라인어셈블리를사용할땐다음과같은것을명시해줘야한다. 물론빼고사용할수도있다. 어셈블리코드 output 변수 input 변수 값이바뀌는레지스터 그리고사용되는문법의형태는다음과같다. asm volatile (asms : output : input : clobber); asm 137

다음에나오는것이인라인어셈블리임을나타낸다. ANSI 엔 asm 으로만정으되어있으므로 asm 과같은키워드는사용하지않는것이바람직하다. volatile 이키워드를사용하면컴파일러는프로그래머가입력한그래도남겨두게된다. 즉최적화나위치를옮기는등의일을하지않는다. 예를들어 output 변수중하나가인라인어셈블리엔명시되어있지만다른곳에서사용되지않는다고판단되면컴파일러는이변수를알아서잘없애주기도한다. 이런경우이런것을고려해프로그램을짰다면상관없겠지만만에하나컴파일러가자동으로해준일때문에버그가발생할수도있다. 그러므로 volatile 키워드를사용해주는것이좋다. asms 따옴표로둘러싸인어셈블리코드. 코드내에서는 %x 과같은형태로 input, output 파라미터를사용할수있으며컴파일하면파라미터가치환된대로어셈블리코드로나타난다. output 변수들을적어주고각각은쉼표고구분된다. 결과값을출력하는변수를적는다. input output 과같은방식으로사용하고인라인어셈블리코드에넘겨주는파라미터를적는다. clobber output, input 에명시되어있진않지만 asms 를실행해서값이변하는것을적어준다. 각변수는쉼표로구분되고각각을따옴표로감싸준다. asms 는반드시있어야하지만 output, input, clobber 는각각없을수도있다. 만약 clobber 가없는경우라면 clobber 와바로앞의콜론 (:) 을같이쓰지않아도된다. 마찬가지로 input, clobber 가없다면 output 까지만쓰면된다. 그러나 output, clobber 는있고 input 이없는경우엔다음과같이 input 만을제외한나머지는반드시써줘야한다. asm volatile (asms : output : : clobber); 중간에있는것이없는경우엔해당항목만을없애고콜론은그대로내버려둬야다음필드가어떤것을의미하는지나타내게된다. 인라인어셈블리가사용된예를들어보자. include/asm-i386/bitops.h 에정의되어있는함수다. /** * test_and_set_bit - Set a bit and return its old value * @nr: Bit to set * @addr: Address to count from * * This operation is atomic and cannot be reordered. * It also implies a memory barrier. */ static inline int test_and_set_bit(int nr, volatile void * addr) { int oldbit; asm volatile ( LOCK_PREFIX "btsl %2,%1\n\tsbbl %0,%0" :"=r" (oldbit),"=m" (ADDR) :"Ir" (nr) : "memory"); return oldbit; 138

} 9.1.2. C.1.2 절. 어셈블리 인라인어셈블리중 asms 에해당하는실제코드를적는부분은 AT&T 어셈블리문법을따르고여기에적인그대로가컴파일후 gasm 에넘겨지기때문에 gasm 의문법을따라야한다. 명령의구분은세미콜론 (;) 이나개행문자 (\n) 으로한다. 그리고 gasm 의문법에서주의할것은레지스터를 %ax 과같은식으로쓴다는것과인텔어셈블리와는달리 destination 이뒤에나온다는것이다. 그러므로인텔문법에익숙한사람은사고의전환이필요할것이다. 인라인어셈블리에선 %0, %1 등을사용해 input, output 오퍼랜드를나타낸다. output 에서부터시작해 input 에나열된변수들의순서대로 %0, %1,... 으로번호가매겨진다. 모든코드는따옴표안에있어야하기때문에많은수의명령을한줄로쓰면보기도않좋기때문에명령수가많아지면각명령을따옴표로감싸고뒤에 \t\n 을넣고다음줄에다시명령을따옴표로적으면된다. 아래의예를보면이해가쉬울것이다. static inline int find_first_zero_bit(void * addr, unsigned size) { int d0, d1, d2; int res; } if (!size) return 0; /* This looks at memory. Mark it volatile to tell gcc not to move it around */ asm volatile ( "movl $-1,%%eax\n\t" "xorl %%edx,%%edx\n\t" "repe; scasl\n\t" "je 1f\n\t" "xorl -4(%%edi),%%eax\n\t" "subl $4,%%edi\n\t" "bsfl %%eax,%%edx\n" "1:\tsubl %%ebx,%%edi\n\t" "shll $3,%%edi\n\t" "addl %%edi,%%edx" :"=d" (res), "=&c" (d0), "=&D" (d1), "=&a" (d2) :"1" ((size + 31) >> 5), "2" (addr), "b" (addr)); return res; 바로위의예에서 %eax 가아니라 %%eax 라고씌어진것이있는데 %% 는 gasm 에넘겨질때 % 로해석되넘겨진다. 즉 output, input 에레지스터를직접지정할때이렇게쓴다. 그러나 output, input 에아무것도지정되어있지않다면 %% 는 % 로바뀌지않는다. 그러므로 %eax 와같이써야만한다. 9.1.3. C.1.3 절. Output/Input 이전의예들에서보면 output, input 에지정된것이무척어렵게되어있는데 output, input 은 constraints 와변수이름이쉼표로구분된리스트로이루어져있다. constraints 는의미를나타내는문자와몇가지 modifier 를조합해만들어진다. 자세한내용은 139

'info gcc' 를해서 ::Constraints 항목에서찾길바란다. 아래열거된것은몇가지만을간추린것이다. 9.1.3.1. C.1.3.1 절. Constraints 'm' 아키텍쳐가지원하는모든종류의메모리어드레스를사용하는오퍼랜드 'o' 옵셋화가능한어드레스를사용하는메모리오퍼랜드 'V' 옵셋화불가능한어드레스를사용하는메모리오퍼랜드 '<' 자동감소 ( 미리감소하거나나중에감소한다 ) 어드레스용메모리오퍼랜드 '>' 자동증가 ( 미리증가하거나나중에증가한다 ) 어드레스용메모리오퍼랜드 'r' 일반레지스터사용오퍼랜드 'd', 'a', 'f',... 시스템에따른레지스터를나타내는다른오퍼랜드로 d, a, f 는각각 68000/68020 에서데이터, 어드레스, 플로팅포인트레지스터를나타낸다. 'i' immediate 정수값을나타내는오퍼랜드. 심볼로된상수도여기에해당한다. 'n' immediate 정수값으로알려진정수값을나타낸다. 많은시스템이어셈블할때한워드이하의오퍼랜드용상수를지원하지않으므로 'i' 보단 'n' 을사용하는것이바람직하다. 'I', 'J', 'K',... 'P' 시스템에따라특정범위내의값을나타내는오퍼랜드. 68000 에선 'I' 가 1 에서 8 까지의값을나타낸다. 이것은시프트명령에서허용된시프트카운트의범위다. 'E' immediate 플로팅오퍼랜드로호스트와같은타겟플로팅포인트포맷인경우에만사용가능. 'F' immediate 플로팅오퍼랜드. 'G', 'H' 특정범위내의값을나타내는플로팅오퍼랜드로시스템에따라다르다. 's' 값이명확히정해지지않은 immediate 정수를나타내는오퍼랜드 's' 값이명확히정해지지않은 immediate 정수를나타내는오퍼랜드. 's' 를 'i'? 대신쓰는이유는좀더좋은코드를만들어낼수도있기때문이다. 140

'g' 특수레지스터를제외한일반레지스터, 메모리혹은 immediate 정수중아무것이나나타내는오퍼랜드. '0', '1', '2',... '9' 같이사용된오퍼랜드의번호를나타냄. 'p' 올바른메모리어드레스를나타내는오퍼랜드. "load address" 와 "push address" 명령을위한것. 'Q', 'R', 'S',... 'U' Q 에서 U 까지의문자는시스템에따라변하는여러다른오퍼랜드를의미한다. C.1.3.2 절. Modifier '=' 오퍼랜드가쓰기전용임을나타냄. 이전값은없어지고새로운값으로교체됨. '+' 읽기, 쓰기모두가능. '=' 는보통 output 용 '+' 는 input/output 모두에사용가능하다. 나머지다른모든오퍼랜드는 input 전용으로간주된다. '&' "earlyclobber" 오퍼랜드를나타내고 input 오퍼랜드를사용하는명령이끝나기전에변경된다는것을의미한다. 그래서 input 오퍼랜드나메모리어드레스의일부을나타내는레지스터엔못쓴다. gcc 는 input 변수가다사용되고나면 output 에사용된다고가정하기때문에 input 에사용된변수가 output 과같게되고또 output 이 input 보다먼저사용되는경우가발생할수있다. 이런경우를막기위해 output 에사용된변수가 input 이모두사용되기전에변경될수도있다고알려줘야만 input 과 output 이같아져생기는에러를막을수있다. '%' % 뒤에따라오는오퍼랜드로대체가능함을나타낸다. 직접레지스터를명시하고사용할때 %%eax 등과같이하는것을기억하는가? '' 이후의쉼표가나올때까지의모든문자를 constraints 로취급하지않는다. 9.1.3.2. C.1.3.3 절. ARM Family Constraints 'f' 플로팅포인트레지스터 'F' 0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 10.0 중의하나를나타내는플로팅포인트상수 'G' 음수값인경우의 'F' 'I' 141

데이터프로세싱명령에서유효한 immediate 정수값오퍼랜드. 0 에서 255 사이의 2 의배수값을나타낸다. 'J' -4095 에서 4095 사이의정수 'K' 'I' 를만족하는값을 1 의보수취한것 'L' 'I' 를만족하는값을음수로취한값 (2 의보수 ) 'M' 0 에서 32 사이의정수값 'Q' 한레지스터에담겨있는정확한어드레스를나태내는메모리 'R' constalt pool 내의아이템 'S' 현재파일의텍스트세그먼트내의심볼 C.1.3.4 절. i386 Family Constraints 'q' 'a', 'b', 'c', 'd' 레지스터 'A' 'a' 또는 'd' 레지스터 (64 비트정수용 ) 'f' 플로팅포인트레지스터 't' 첫번째 ( 스택의최상위 ) 플로팅포인트레지스터 'u' 두번째플로팅포인트레지스터 'a' 'a' 레지스터 'b' 'b' 레지스터 'c' 'c' 레지스터 'd' 'd' 레지스터 'D' 142

'di' 레지스터 'S' 'si' 레지스터 'I' 0 에서 31 사이의상수 (32 비트시프트용 ) 'J' 0 에서 63 사이의상수 (64 비트시프트용 ) 'K' '0xff' 'L' '0xffff' 'M' 0, 1, 2, 3 (lea 명령을위한시프트 ) 'N' 0 에서 255 사이의값 (out 명령용 ) 'G' 80387 플로팅포인트상수를나타냄 9.2. C.2 절. 사례분석 리눅스커널에이미사용된수많은예를통해어떤식으로인라인어셈블리가사용됐는지알아보자. 9.2.1. C.2.1 절. strcpy() 아래소스코드는 include/asm-i386/string.h 에있는 strcpy() 함수를가져와컴파일해보기위해조금추가한코드다. /* test.c */ static inline char * strcpy(char * dest,const char *src) { int d0, d1, d2; asm volatile ( "1:\tlodsb\n\t" "stosb\n\t" "testb %%al,%%al\n\t" "jne 1b" : "=&S" (d0), "=&D" (d1), "=&a" (d2) :"0" (src),"1" (dest) : "memory"); return dest; } int main() { char a[] = "1234"; char b[] = "4567"; strcpy(a, b); return 0; 143

} 컴파일은 'gcc -S -c test.c' 라고한다. 그러면 test.s 가생길것이다. test.s 는다음과같다. APP NO_APP.file "test.c".version "01.01" gcc2_compiled.:.section.rodata.lc0:.string "1234".LC1:.string "5678".text.align 4.globl main.type main,@function main: pushl %ebp movl %esp,%ebp subl $24,%esp leal -8(%ebp),%eax movl.lc0,%edx movl %edx,-8(%ebp) movb.lc0+4,%al movb %al,-4(%ebp) leal -16(%ebp),%eax movl.lc1,%edx movl %edx,-16(%ebp) movb.lc1+4,%al movb %al,-12(%ebp) addl $-8,%esp leal -16(%ebp),%eax pushl %eax leal -8(%ebp),%eax pushl %eax call strcpy addl $16,%esp xorl %eax,%eax jmp.l3.p2align 4,,7.L3: movl %ebp,%esp popl %ebp ret.lfe1:.size main,.lfe1-main.align 4.type strcpy,@function strcpy: pushl %ebp movl %esp,%ebp subl $28,%esp pushl %edi pushl %esi pushl %ebx movl 12(%ebp),%esi movl 8(%ebp),%edi 1: lodsb stosb testb %al,%al jne 1b movl %esi,%ecx movl %edi,%edx movl %ecx,%ebx movl %ebx,-4(%ebp) movl %edx,%edx 144

.L2:.Lfe2: movl %edx,-8(%ebp) movl %eax,%eax movl %eax,-12(%ebp) movl 8(%ebp),%eax jmp.l2 leal -40(%ebp),%esp popl %ebx popl %esi popl %edi movl %ebp,%esp popl %ebp ret.size.ident strcpy,.lfe2-strcpy "GCC: (GNU) 2.95.3 20010315 (release)" 인라인어셈블리는 APP 와 NO_APP 사이에존재한다. : "=&S" (d0), "=&D" (d1), "=&a" (d2) output 의구성을나타낸다. "=&S" (d0) 는 d0 를 'si' 레지스터에저장하는것이고 "=&D" (d1) 은 d1 을 'di' 레지스터에저장하란것이고 "=&a" (d2) 는 d2 를 'a' 레지스터에저장하란것이다. test.s 에의하면어셈블리코드가실행된후 output 으로 d0, d1, d2 가있는데 NO_APP 바로밑의 3 줄이이역할을한다. d2 는 %ebx 에할당됐음을알수있다. :"0" (src),"1" (dest) input 의구성을나타낸다. "0" (src) 는 src 가 0 번째오퍼랜드와같은위치를점유하란말로 %0 인 d0 를의미한다. 또 d0 가 si 를사용하므로결국 si 의초기값이 src 가된다. dest 는 %1 인 di 에입력된다. test.s 에의하면 APP 바로전의두줄이 input 에해당하고 %esi 와 %edi 에 src, dest 를입력해준다. : "memory" clobber 에지정된 "memory" 는컴파일러에게어셈블리코드가메모리의어딘가를변경한다고가르쳐주는것이다. 이것을사용하지않으면어셈블리코드에서메모리의내용을변경하는것을컴파일러는전혀알수없다. 잘못하면어셈블리에서고친값과다른값을컴파일러는사용하고있을가능성도있다. "memory" 를명시해주면컴파일러는어셈블리코드를실행하기전 / 후에레지스터에저장되어있는모든변수의값을갱신하도록한다. "1:\tlodsb\n\t" 1: 은 label 을의미한다. loadsb 명령으로 al 레지스터에 es:esi 의내용을읽어온다. 여기서 src 의내용을읽어온다. 명령실행후 esi 는자동으로 1 이증가한다 ( 바이트단위로읽기때문 ). "stosb\n\t" al 의값을 es:edi 에저장한다. edi 도명령실행후 1 증가한다. "testb %%al,%%al\n\t" al 의내용이 0 인지테스트한다. 스트링을복사할땐 NULL 캐릭터가나올때까지복사하기때문에 0 인지판별한다. "jne 1b" 145

0 이아닌경우, 즉 NULL 캐릭터가아닌경우계속해서복사한다. 9.2.2. C.2.2 절. _set_gate() arch/i386/kernel/trap.c 에있는 _set_gate() 의내용을가져다컴파일하기위해약간변경한것이다. /* sg.c */ define KERNEL_CS 0x10 define _set_gate(gate_addr,type,dpl,addr) \ do { \ int d0, d1; \ asm volatile ("movw %%dx,%%ax\n\t" \ "movw %4,%%dx\n\t" \ "movl %%eax,%0\n\t" \ "movl %%edx,%1" \ :"=m" (*((long *) (gate_addr))), \ "=m" (*(1+(long *) (gate_addr))), "=&a" ( d0), "=&d" ( d1) \ :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ "3" ((char *) (addr)),"2" ( KERNEL_CS << 16)); \ } while (0) int main() { _set_gate(0, 1, 2, 3); return 0; } 'gcc -S -c sg.c' 로컴파일한것은다음과같다..file "sg.c".version "01.01" gcc2_compiled.:.text.align 4.globl main.type main,@function main: pushl %ebp movl %esp,%ebp subl $24,%esp nop.p2align 4,,7.L3: movl $3,%edx movl $1048576,%ecx movl %ecx,%eax APP movw %dx,%ax movw $-16128,%dx movl %eax,0 movl %edx,4 NO_APP movl %eax,%ecx movl %ecx,-4(%ebp) movl %edx,%eax movl %eax,-8(%ebp).l5: jmp.l4.p2align 4,,7.L6: jmp.l3 146

.L4:.L2:.Lfe1:.p2align 4,,7 xorl %eax,%eax jmp.l2.p2align 4,,7 movl %ebp,%esp popl %ebp ret.size.ident main,.lfe1-main "GCC: (GNU) 2.95.3 20010315 (release)" :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), "3" ((char *) (addr)),"2" ( KERNEL_CS << 16)); input 으로정의된것들이다. "3", "2" 는각각 %3( d0), %2( d1) 로대응되도록한다. $APP 전의 3 줄중윗 2 줄이 "3", "2" 에해당하는것들이다. :"=m" (*((long *) (gate_addr))), "=m" (*(1+(long *) (gate_addr))), "=&a" ( d0), "=&d" ( d1) output 으로정의된것들. %0 은값이 0 이되고 (main 에서 _set_gate(0, 1, 2, 3) 으로했기때문에 ) %1 은 4 가된다. 147