This section is more of an exercise in C project organization than OS Dev. If that doesn’t interest you, you should skip ahead to Part 4 - Wrangling Memory
If you want to download the code and play with it yourself, see my git repo.
Separating our files
Right now, we have our C files, our linker file, our compiled objects, and our compiled kernel all floating around in the same directory. Before we start making the kernel more complex, it would be a good idea to separate different kinds of files.
The way that we are going to do it is to separate C files, header files, and compilation files into separate directories:
include are going to be structured very similarly, so when I talk about one, assume it holds for the other unless otherwise stated.
include are going to have subdirectories
kernel is for files that are exclusive to the kernel, and
common is for files that
contain standard functionality that may not be exclusive to the kernel, such as
build is going to contain our linker file, and a makefile. During compilation, the makefile will put all object files, as well as the compiled kernel binary in the
Here is the code for the makefile:
# Don't use normal gcc, use the arm cross compiler CC = ./gcc-arm-none-eabi-6-2017-q2-update/bin/arm-none-eabi-gcc # Set any constants based on the raspberry pi model. Version 1 has some differences to 2 and 3 ifeq ($(RASPI_MODEL),1) CPU = arm1176jzf-s DIRECTIVES = -D MODEL_1 else CPU = cortex-a7 endif CFLAGS= -mcpu=$(CPU) -fpic -ffreestanding $(DIRECTIVES) CSRCFLAGS= -O2 -Wall -Wextra LFLAGS= -ffreestanding -O2 -nostdlib # Location of the files KER_SRC = ../src/kernel KER_HEAD = ../include COMMON_SRC = ../src/common OBJ_DIR = objects KERSOURCES = $(wildcard $(KER_SRC)/*.c) COMMONSOURCES = $(wildcard $(COMMON_SRC)/*.c) ASMSOURCES = $(wildcard $(KER_SRC)/*.S) OBJECTS = $(patsubst $(KER_SRC)/%.c, $(OBJ_DIR)/%.o, $(KERSOURCES)) OBJECTS += $(patsubst $(COMMON_SRC)/%.c, $(OBJ_DIR)/%.o, $(COMMONSOURCES)) OBJECTS += $(patsubst $(KER_SRC)/%.S, $(OBJ_DIR)/%.o, $(ASMSOURCES)) HEADERS = $(wildcard $(KER_HEAD)/*.h) IMG_NAME=kernel.img build: $(OBJECTS) $(HEADERS) echo $(OBJECTS) $(CC) -T linker.ld -o $(IMG_NAME) $(LFLAGS) $(OBJECTS) $(OBJ_DIR)/%.o: $(KER_SRC)/%.c mkdir -p $(@D) $(CC) $(CFLAGS) -I$(KER_SRC) -I$(KER_HEAD) -c $< -o $@ $(CSRCFLAGS) $(OBJ_DIR)/%.o: $(KER_SRC)/%.S mkdir -p $(@D) $(CC) $(CFLAGS) -I$(KER_SRC) -c $< -o $@ $(OBJ_DIR)/%.o: $(COMMON_SRC)/%.c mkdir -p $(@D) $(CC) $(CFLAGS) -I$(KER_SRC) -I$(KER_HEAD) -c $< -o $@ $(CSRCFLAGS) clean: rm -rf $(OBJ_DIR) rm $(IMG_NAME) run: build qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel kernel.img
For a line-by-line explanation of this code, see this page
This leaves our directory structure like this:
raspi-kernel/ L__ src/ L__ common/ L__ kernel/ L__ kernel.c L__ boot.S L__ include/ L__ common/ L__ kernel/ L__ build/ L__ Makefile L__ linker.ld
Right now, kernel.c contains all of the C source code for the entire kernel. It contains the logic for setting up UART, and for doing IO. Let’s factor that stuff out into logical files.
First, we are going to put that that big enum from kernel.c that describes the UART peripheral, and all of the function signatures related to UART into
include/kernel/uart.h. Then we are going to move all of the uart code to
src/kernel/uart.c. This should leave
kernel.c with just
kernel_main in it.
Now, we are going to write some library code. We create the files
src/common/stdio.c and corresponding header files.
stdlib.c, we define the functions
bzero, as these will come in handy later, and we define
itoa (integer to ascii) to make debugging easier.
stdio.c, we define
puts as general purpose IO functions. We do this even though
uart_puts because later we are going to want to swap out
uart_putc for a function that renders text to an actuall screen, and it will be easier to replace one call to
uart_putc here than many possible places.
The implementation of these functions is not that important. If you really want to see them, check the git repo.
Now our directory structure looks like this:
raspi-kernel/ L__ src/ L__ common/ L__ stdio.c L__ stdlib.c L__ kernel/ L__ kernel.c L__ boot.S L__ uart.c L__ include/ L__ common/ L__ stdio.h L__ stdlib.h L__ kernel/ L__ uart.h L__ build/ L__ Makefile L__ linker.ld
Now that our project is organized sensibly, lets look at how to manage memory.
Previous: Part 2 - Getting Something to Boot