This is the second part of my introduction on kernels for 32 bit x86 machines. In this part I will be looking into how to make a kernel, which takes advantage of a boot loader which complies with the multiboot specifications as defined by GNU[1]. This allows us to avoid messing about with moving into 32 bit mode and clears the interrupt flag as well as a few other things — it will also allow us to do a few other things with the hardware before the kernel starts, but that will be something I will not be looking into now. Before I go into the kernel itself, I will be looking into how to build the kernel, which to me, was far more troublesome than writing the kernel. I will, of course, still be using GCC 4.4.2 (I believe any version will do — I’m not doing anything fancy) and make. Using the built-in rules of make the end result is this:

OBJECTS = kernel.o boot.o
CFLAGS = -Wall -ffree-standing -pedantic -m32 -std=c99
ASFLAGS = $(CFLAGS)
LDFLAGS =  -melf_i386 -Ttext 1000000

kernel: $(OBJECTS)
	$(LD) $(LDFLAGS) $(OBJECTS) -o $@
clean:
	$(RM) *~ *.o kernel

Simply save that to the file Makefile

and it should be able to compile the kernel (that I am about to supply the code for. About the Makefile: The makefile contains three symbol definitions at the top CFLAGS, ASFLAGS and LDFLAGS. The first two (being identical) are used when compiling C source (CFLAGS) and assembler source (ASFLAGS). The final symbol is used when the kernel is linked. The CFLAGS symbol contains flags for the C compiler, which tell it that the source code is not to be linked to anything other than what is explicitly given — this is what -ffree-standing is for — and is by far the most important flag given. It is this flag which allows the kernel to run at all. The flag -m32 is only important if you are compiling the kernel on a x86-64 operating system. If you’re compiling on a i386 operating system it won’t make any difference and on any other machine the compile will utterly fail if you have it or not — in that case you need a cross compiler (which is far beyond the scope of this article, yet a simple thing to get done once you know what you’re doing). The last couple of flags are ultimately unimportant, except that -std=c99 and -pedantic changes how we can express things in the source.

The kernel it builds is the sample kernel provided in the multiboot specifications — it is a simply thing that simply prints some information to the video display and then halts the computer. It contains three files: multiboot.h, boot.S and kernel.c. The files can be found here. The file is a gzip compressed tarball of the four files specified here. I have set up and environment where the commands to build the disk image explained in the first example has been compiled into a makefile which allows me to erase the image, recreate it, compile the kernel, copy it onto the disk and boot it with Qemu with one command. The makefile in question contains the following commands:

QEMU_FLAGS = -vga std

boot: disk.img
	make -C kernel
	sudo mount -o loop,offset=16384 -t ext2 disk.img mnt
	cp kernel/kernel mnt
	sudo umount mnt
	qemu $(QEMU_FLAGS) -hda disk.img

commit:
	SVN_EDITOR=vim svn commit

clean:
	make -C kernel clean
	$(RM) -r mnt *~

disk.img: grub.input parted.input mnt grub.conf
	qemu-img create disk.img 20M
	sudo parted disk.img < parted.input
	sudo mount -o loop,offset=16384 -t ext2 disk.img mnt
	sudo chown `whoami` mnt -R
	mkdir mnt/grub -p
	cp /boot/grub/stage{1,2} /boot/grub/e2fs_stage1_5 mnt/grub
	grub --device=/dev/null < grub.input
	cp grub.conf mnt/grub
	sudo umount mnt

mnt:
	mkdir mnt

The files grub.input parted.input simply contains the commands given to the shell as specified in the first part.

That’s it. You don’t need anything else to compile a kernel for 32 bit x86 computers. For the next part I will be dissecting the source for this minimalistic, and ultimately, useless kernel.

Advertisement