Directory organization suggestions

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Directory organization suggestions

Post by sunnysideup »

Hello! I have been writing a small operating system so far, from a bootloader to a basic kernel. Stuff like interrupt handling, keyboard drivers, etc. are already done. I want to organize my code a little better. Here's what I haven't done yet... No user mode, no libc, etc.

Here's a tree organization of my directory... (you can also find it on http://www.github.com/kssuraaj28)

Code: Select all

├── boot
│   ├── stage1
│   │   ├── CHS.asm
│   │   ├── debug
│   │   ├── disk_read.asm
│   │   ├── print.asm
│   │   └── stage1.asm
│   └── stage2
│       ├── func16.asm
│       ├── func32.asm
│       ├── GDT.asm
│       ├── pagingsetup.asm
│       └── stage2.asm
├── disk.img
├── kernel
│   ├── asm
│   │   ├── cursor.asm
│   │   ├── cursor.o
│   │   ├── entry.asm
│   │   ├── entry.o
│   │   ├── hal.asm
│   │   ├── hal.o
│   │   ├── interruptstubs.asm
│   │   ├── interruptstubs.o
│   │   └── timer.o
│   ├── c
│   │   ├── io.c
│   │   ├── o.h
│   │   ├── io.o
│   │   ├── hal.c
│   │   ├── hal.h
│   │   ├── hal.o
│   │   ├── inthandling.c
│   │   ├── inthandling.h
│   │   ├── inthandling.o
│   │   ├── kernel.c
│   │   ├── kernel.o
│   │   ├── keyboard.c
│   │   ├── keyboard.h
│   │   ├── keyboard.o
│   │   ├── kshell.c
│   │   ├── kshell.o
│   │   ├── phymem.c
│   │   ├── phymem.h
│   │   ├── phymem.o
│   │   ├── timer.c
│   │   ├── timer.h
│   │   ├── timer.o
│   │   ├── virtmem.c
│   │   ├── virtmem.h
│   │   └── virtmem.o
│   ├── kernel.elf
│   └── linker.ld
├── kernel.bin
├── Makefile
├── readme.md
├── script.sh
├── stage1.bin
└── stage2.bin
Clearly the code organization of the kernel directory is very very poor. What's the tradtional way this is usually done?
Here's the makefile, which describes how I build the OS.

Code: Select all

C_SOURCES = $(wildcard kernel/c/*.c)
ASM_SOURCES = $(wildcard kernel/asm/*.asm)
C_OBJECTS = ${C_SOURCES:.c=.o}
ASM_OBJECTS = ${ASM_SOURCES:.asm=.o}

.PHONY : all assemble run clean

all: assemble

run : assemble
	qemu-system-i386 -drive format=raw,file=disk.img  -monitor stdio

debug: assemble
	qemu-system-i386 -s -hda disk.img &
	gdb -ex "target remote localhost:1234" -ex "symbol-file kernel/kernel.elf" -ex "b kmain" -ex "continue"

assemble: disk.img kernel.bin stage1.bin stage2.bin 
	dd if=stage1.bin of=disk.img bs=1 count=3 seek=0 skip=0 conv=notrunc
	dd if=stage1.bin of=disk.img bs=1 count=451 seek=62 skip=62 conv=notrunc
	mcopy -i disk.img stage2.bin kernel.bin ::  -D o


kernel.bin : kernel/kernel.elf  
	objcopy -O binary $^ $@
	chmod -x $@

#You can use the --print-map option to look at what the linker does
kernel/kernel.elf : $(C_OBJECTS) $(ASM_OBJECTS)
	i686-elf-ld  $^ -T kernel/linker.ld -e kmain -o $@ 
	chmod -x $@

%.o : %.c
	i686-elf-gcc -ffreestanding $< -c -o $@ -Wall -Werror -g 
%.o : %.asm
	nasm $< -o $@ -f elf32

stage1.bin : boot/stage1/stage1.asm
	nasm $^ -f bin -o $@
stage2.bin: boot/stage2/stage2.asm
	nasm $^ -f bin -o $@
	
disk.img: 
	truncate $@ -s 1M
	mkfs.vfat -F12 -S512 -s1 $@
	
clean :
	rm $(C_OBJECTS) $(ASM_OBJECTS) *.bin
This is really bad organization.... and I believe that it would cause scalability problems in the future.. Any suggestions/criticisms?
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: Directory organization suggestions

Post by bzt »

Hi,
sunnysideup wrote:This is really bad organization.... and I believe that it would cause scalability problems in the future.. Any suggestions/criticisms?
I'm not sure what you mean by scalable source tree. But if you meant to be expendable or portable in the future, then I'd recommend organizing your code along those lines. For example, instead of "asm", use "x86" or something. Otherwise your structure is not bad, and if it's x86 only and monolithic, then it's perfectly fine. Maybe I'd create a separate "drivers" directory, but that's all.

No golden rule here, all OSes are different, so a different directory structure fits their need. I'd recommend to think about what parts you want to be extendable and separate those (boot, common executor, drivers etc.). If your OS is going to be multiplatform, then separate platform dependent code from common code too, otherwise don't care.

Cheers,
bzt
Korona
Member
Member
Posts: 1000
Joined: Thu May 17, 2007 1:27 pm
Contact:

Re: Directory organization suggestions

Post by Korona »

There is no one-size-fits-all solution for this. If you want your OS to be portable, separate architecture specific code (assembly + C) from architecture-independent code (C files only). You might want to split your directories according to functionality (MM, tasking, etc.) instead of C/assembly.

A few other notes regarding your build system:
  • Storing object files and source files in the same directory is bad practice. It prevents you from using multiple build directories which in turn prevents other things like performing clean builds with clang-tidy, scan-build or other static analysis tools. With out-of-source trees, you just delete the build directory to "make clean". You can be 100% sure that a clean rebuild is reproducible, without any effort.
  • This might be controversial on this forum but plain makefiles are not an adequate solution in 2020. Use Meson, CMake or any other established build system (but not autotools, please!). Header dependency discovery in plain makefiles is a pain. configure scripts tend to be hand-rolled with plain make which is also a pain to deal with. Many custom makefiles neglect to support --prefix and/or DESTDIR. Many custom makefiles to not support out-of-source builds. Often, wildcards are used that prevent the makefiles from scaling to large directories. The list goes on...
If you don't have experience with build systems, I recommend Meson. It supports cross-compilation (also to bare metal ELF targets) out-of-the-box and does not need customization for non-mainstream OSes.
Last edited by Korona on Sat Mar 28, 2020 4:23 am, edited 1 time in total.
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: Directory organization suggestions

Post by bzt »

Korona wrote:wildcards are used that prevent the makefiles from scaling to large directories.
What do you mean by that? Would you mind to elaborate? (I'm not saying you're not correct about this, I'm not sure what you mean).

Otherwise I completely agree with you on the other points. Unless you're an experienced programmer and you know exactly what you're doing, hand written configure and make only build is not suitable for you. That requires a lot of experience to do it right, beginners are better off with CMake or something similar.

Cheers,
bzt
User avatar
eekee
Member
Member
Posts: 892
Joined: Mon May 22, 2017 5:56 am
Location: Kerbin
Discord: eekee
Contact:

Re: Directory organization suggestions

Post by eekee »

bzt wrote:instead of "asm", use "x86" or something. Otherwise your structure is not bad, and if it's x86 only and monolithic, then it's perfectly fine.
This can work well, yes. Plan 9 gets a lot of mileage out of it. The kernel build files operate in arch-specific dirs, sometimes building source from `../port`.
Korona wrote:
  • Storing object files and source files in the same directory is bad practice. It prevents you from using multiple build directories which in turn prevents other things like performing clean builds with clang-tidy, scan-build or other static analysis tools. With out-of-source trees, you just delete the build directory to "make clean". You can be 100% sure that a clean rebuild is reproducible, without any effort.
Yeah. Plan 9 stores object files with source files, and developed bugs where `make clean` didn't clean properly. Also, it means Plan 9 has to use a different extension for each architecture, and uses that extension for a prefix for binaries before they're installed. This creates complexity for Plan 9 with its `mk` and unique compilers; it would be worse with mainstream compilers, make, or other build systems. Better to put object files in a separate directory. (The fact that the extension is different from $objtype also creates problems including complexifying scripts to premature obsolescence. They reassigned '7' from Alpha to Arm64 while my friend was still using Alpha machines. They could instead store object files and pre-install executables under, for example, /386/obj, where executables go in /386/bin and libraries in /386/lib.)

Other than that big caveat, Plan 9 has an interesting build system. Its `mk` is quite programmable. Defaults are provided by standard mkfiles included in most other mkfiles.
bzt wrote:beginners are better off with CMake or something similar.
CMake itself seems to have developed a Red Hat style business model: Make all but beginner tasks difficult enough that people will pay for support contracts. It would probably be best to look into other build systems instead. I got burned badly when Red Hat adopted this business model just as I was getting into Linux.
Kaph — a modular OS intended to be easy and fun to administer and code for.
"May wisdom, fun, and the greater good shine forth in all your work." — Leo Brodie
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Directory organization suggestions

Post by sunnysideup »

Alright, where would you suggest I put header files? The same directory as their corresponding .c files? This seems like a bad idea... Do I make a include directory? I feel this is more appropriate with a libc.. any suggestions?
Klakap
Member
Member
Posts: 297
Joined: Sat Mar 10, 2018 10:16 am

Re: Directory organization suggestions

Post by Klakap »

I dont use header files, all is on start of c file.
nullplan
Member
Member
Posts: 1794
Joined: Wed Aug 30, 2017 8:24 am

Re: Directory organization suggestions

Post by nullplan »

sunnysideup wrote:Alright, where would you suggest I put header files? The same directory as their corresponding .c files? This seems like a bad idea... Do I make a include directory? I feel this is more appropriate with a libc.. any suggestions?
Depends on taste, really. Include directories seem to be the standard way, though they can get quite complex (you didn't plan on dumping all header files into a single directory, did you?). Also note that there is no need to have one header for every C file, you can group headers together if you'd like. Also, you might need headers for your assembly files as well.
Klakap wrote:I dont use header files, all is on start of c file.
That is an astonishingly bad idea. At work I have to deal with code created like this, and the fallout is horrible to this day. Because this way you end up repeating declarations all over the place, and if any declaration changes, but you overlook one declaration somewhere, the compiler will not notice, nor will the linker, and you end up calling functions with the wrong arguments. Just don't do this.
Carpe diem!
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Directory organization suggestions

Post by sunnysideup »

Alright, I've done a bit of remodelling, but my lack of experience seems very relevant :( :| :| .
Here's my new structure:

Code: Select all

.
├── boot
│   ├── stage1
│   │   ├── CHS.asm
│   │   ├── debug
│   │   ├── disk_read.asm
│   │   ├── print.asm
│   │   └── stage1.asm
│   └── stage2
│       ├── func16.asm
│       ├── func32.asm
│       ├── GDT.asm
│       ├── pagingsetup.asm
│       └── stage2.asm
├── kernel
│   ├── driver
│   │   ├── ATA.c
│   │   ├── ATA.o
│   │   ├── cursor.asm
│   │   ├── cursor.o
│   │   ├── dadio.c
│   │   ├── dadio.o
│   │   ├── keyboard.c
│   │   ├── keyboard.o
│   │   ├── timer.c
│   │   └── timer.o
│   ├── hal
│   │   ├── hal.c
│   │   ├── hal.o
│   │   ├── interruptstubs.asm
│   │   ├── interruptstubs.o
│   │   ├── inthandling.c
│   │   ├── inthandling.o
│   │   ├── x86.asm
│   │   └── x86.o
│   ├── include
│   │   ├── ATA.h
│   │   ├── dadio.h
│   │   ├── FAT12.h
│   │   ├── hal.h
│   │   ├── inthandling.h
│   │   ├── keyboard.h
│   │   ├── phymem.h
│   │   ├── timer.h
│   │   └── virtmem.h
│   ├── kernel
│   │   ├── entry.asm
│   │   ├── entry.o
│   │   ├── kernel.c
│   │   ├── kernel.o
│   │   ├── kshell.c
│   │   └── kshell.o
│   ├── kernel.elf
│   ├── linker.ld
│   └── mem
│       ├── phymem.c
│       ├── phymem.o
│       ├── virtmem.c
│       └── virtmem.o
├── Makefile
├── readme.md
├── script.sh

Code: Select all

C_SOURCES = $(wildcard kernel/*/*.c)
ASM_SOURCES = $(wildcard kernel/*/*.asm)
C_OBJECTS = ${C_SOURCES:.c=.o}
ASM_OBJECTS = ${ASM_SOURCES:.asm=.o}

.PHONY : all assemble run clean

all: run

run : assemble
	qemu-system-i386 -drive format=raw,file=disk.img  -monitor stdio

debug: assemble
	qemu-system-i386 -s -hda disk.img &
	gdb -ex "target remote localhost:1234" -ex "symbol-file kernel/kernel.elf" -ex "b kmain" -ex "continue"

assemble: disk.img kernel.bin stage1.bin stage2.bin 
	dd if=stage1.bin of=disk.img bs=1 count=3 seek=0 skip=0 conv=notrunc
	dd if=stage1.bin of=disk.img bs=1 count=451 seek=62 skip=62 conv=notrunc
	mcopy -i disk.img stage2.bin kernel.bin ::  -D o


kernel.bin : kernel/kernel.elf  
	objcopy -O binary $^ $@
	chmod -x $@

#You can use the --print-map option to look at what the linker does
kernel/kernel.elf : $(C_OBJECTS) $(ASM_OBJECTS)
	i686-elf-ld  $^ -T kernel/linker.ld -e kmain -o $@ 
	chmod -x $@

%.o : %.c
	i686-elf-gcc  -ffreestanding $< -c -o $@ -Wall -Werror -g  -Ikernel/include  ## Using -I seems like a bad idea??
%.o : %.asm
	nasm $< -o $@ -f elf32

stage1.bin : boot/stage1/stage1.asm
	nasm $^ -f bin -o $@
stage2.bin: boot/stage2/stage2.asm
	nasm $^ -f bin -o $@
	
disk.img: 
	truncate $@ -s 1M
	mkfs.vfat -F12 -S512 -s1 $@
	
clean :
	rm $(C_OBJECTS) $(ASM_OBJECTS) *.bin
The organisation looks better IMHO.. But I'm worried about two things right now:
1. How I deal with header files. This is really worrying me. As you can see, I use -I when I compile, and it seems really really 'patchy'
2. I still want to know how I can efficiently make all my object files get produced a separate directory. What changes can I make in the Makefile to make this happen? (I'm guessing I use some sort of pattern substitution?)
3."C_SOURCES = $(wildcard kernel/*/*.c)" - Really bad idea?
nexos
Member
Member
Posts: 1081
Joined: Tue Feb 18, 2020 3:29 pm
Libera.chat IRC: nexos

Re: Directory organization suggestions

Post by nexos »

In my operating system, all includes are in a directory called include in the root of my source tree. I specify this directory with the -I option in the gcc command line
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg
Post Reply