2.3. bzimage가 만들어지는 과정 추적-Makefile 분석

bzimage가 만들어지는 과정을 살펴 보고 이를 따라가면서 Makefile의 자세한 내용을 알아본다. 정확한 것은 2.4.2절을 참조하기 바란다.

시작은 물론 $(topDIR)/Makefile로 부터 시작한다.

커널 makefile은 몇 부분으로 나눌 수 있다.

  1. 기본 정보 정의

  2. 커널 설정

  3. 커널 소스 의존성 만들기

  4. 모듈 만들기

  5. 커널 실행 파일 만들기

  6. 모듈 설치하기

각 부분이 명확하게 구분되는 것은 아니지만 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)
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 \
	  -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		=$(topdir)/lib/lib.a
subdirs		=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
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 \
	.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	cppflags cFLAGS AFLAGS

export	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))

(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

(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

(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

#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:	clean spec
	find . \( -size 0 -o -name .depend -o -name .hdepend \) -type f -print | xargs rm -f
	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 되서 다른 아키텍쳐용 바이너리를 만들 수도 있기 때문에 실제 커널을 구성하는 코드 외에 커널을 만들기 위해 필요한 몇 몇 프로그램을 호스트 상에서 돌리기 위한 컴파일러를 지정 하는 것이다.
(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 할 때도 여기에서 적용된 사항들이 같이 적용될 수 있도록 한다.
(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의 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
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

(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로 출력되어 화면에는 나타나지 않는다.

컴파일되면 다음과 같은 결과가 나온다.
	.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

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에서 정의된 것을 따른다. 즉 '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
	$(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으로 지정한다.

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);
	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)
	if (argc > 2 && !strcmp(argv[1], "-b"))
	  {
	    is-big-kernel = 1;
	    argc--, argv++;
	  }
	if ((argc < 4) || (argc > 5))
		usage();
	if (argc > 4) {
(2)
		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);

(3)
	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);

(4)
	file-open(argv[2]);				    /* Copy the setup code */
	for (i=0 ; (c=read(fd, buf, sizeof(buf)))>0 ; i+=c )
		if (write(1, buf, c) != c)
			die("write call failed");
	if (c != 0)
		die("read-error on `setup'");
	close (fd);

(5)
	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;
	}

(6)
	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)		    /* Write sizes to the 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");

	return 0;					    /* Everything is OK */
}
			
(1)
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절을 참조한다.