In previous tutorial, we wrote ATA PIO driver.. now it is time to utilize the driver.
Before, starting with it.. I have made several changes in the structure, also compile script is no more bat file but rather a powershell script now, output is now stored inside a proper debug folder instead of in root folder. If this tutorial gets complicated, you can see the video I have posted on YouTube.
Following structural changes have been made:
Now let’s first write hello world type of code in kernel.h and kernel.c as following:
Following is the kernel.h file
void start();
Following is the kernel.c file
#include "kernel.h"
#include "../drivers/screen.h"
void start()
{
printf("I am the actual kernel :D");
}
Next, we need malloc utility in our OS, which will allow us to request memory.
mem.h file will have following content:
#include <stdint.h>
/* At this stage there is no 'free' implemented. */
uint32_t malloc(uint32_t size, int align, uint32_t *phys_addr);
and mem.c file will have following content:
#include <stdint.h>
#include "mem.h"
uint32_t free_mem_addr = 0x10000;
/* Implementation is just a pointer to some free memory which
* keeps growing */
uint32_t malloc(uint32_t size, int align, uint32_t *phys_addr) {
/* Pages are aligned to 4K, or 0x1000 */
if (align == 1 && (free_mem_addr & 0xFFFFF000)) {
free_mem_addr &= 0xFFFFF000;
free_mem_addr += 0x1000;
}
/* Save also the physical address */
if (phys_addr) *phys_addr = free_mem_addr;
uint32_t ret = free_mem_addr;
free_mem_addr += size; /* Remember to increment the pointer */
return ret;
}
Also, now our loader.c file will be changed to read from storage, allocate memory, load kernel at specified memory address and execute the kernel.
Following willbe our loader.c file:
#include "../drivers/screen.h"
#include "../utils/utils.h"
#include "../drivers/ata/ata.h"
#include "../utils/mem.h"
void loadkernel(void) {
clear(); /* clear the screen. */
printf("Welcome to Learn OS.\r\n");
printf("Memory address of loadkernel in: 0x%x\r\n\r\n", loadkernel); /* print memory address of loadkernel method */
uint32_t phy_addr;
uint32_t page = malloc(1000, 1, &phy_addr); /* allocate memory */
printf("page: %x\r\n", page);
printf("phy addr: %x\r\n", phy_addr);
printf("loading kernel into memory at 1M (0x%x)...\r\n", page);
read_sectors_ATA_PIO(page, 0x12, 38); /* read 38 sectors (size of our kernel) from 12th sector (That's where kernel will be stored.). */
uint32_t* kernelFile = page;
printf("\r\n\r\n");
if(kernelFile[0] == 0x464C457F) /*read first 4 bits of file to validate that it is a ELF file */
{
printf("Kernel is ELF File:\r\n");
}
int bits = (kernelFile[1] >> 0) & 0xFF; /* 5th bit is for 32-bit or 64-bit */
if(bits == 1)
{
printf("\t32-bit\r\n");
}
int endian = (kernelFile[1] >> 8) & 0xFF; /* 6th bit is for little endian or big endian */
if(endian == 1)
{
printf("\tlittle endian\r\n");
}
printf("\tentry position : 0x%x \r\n", page + kernelFile[6]); /* get the entry point of a kernel */
printf("\texecuting kernel from: 0x%x....\r\n\r\n", page + kernelFile[6]); /* add entry point of a kernel to base address of requested memory */
void (*func)(void) = page + kernelFile[6]; /* create a pointer to a function */
func(); /* execute it / pass control to kernel */
}
Next, we create kernel.ld file inside ld directory:
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x01000; /*0x001000*/
.text BLOCK(4K) : ALIGN(4K)
{
*(.text)
}
/*Read-only data*/
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
}
/*Read-write data (initialized)*/
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
/*Read-write data (uninitialized) and stack*/
.bss BLOCK(4K) : ALIGN(4K)
{
*(.bss)
}
}
We delete our compile.bat file and create a new file called compile.ps1. Our compiler process will now do more like creating a raw hard disk image, write kernel at the beginning of the raw disk created, join raw disk with the boot loader and 2nd stage loader. Code for the same is as follows:
param($paramName)
Write-Output $paramName
Function Create-Directory($directoryPath) {
if(Test-Path -Path $directoryPath -PathType Container) {
Write-Output "$directoryPath directory exists..."
}
else {
Write-Output "Creating $directoryPath Directory..."
New-Item -Path $directoryPath -ItemType Directory
}
}
Function CleanBinaries() {
Write-Output "Clearing all binaries...."
Remove-Item debug/bin/*.bin
Remove-Item debug/obj/*.o
Remove-Item debug/elf/*.elf
Remove-Item debug/objdump/*.objdump
Remove-Item *.log
}
Function Build() {
Write-Output "Creating Output Directories..."
Create-Directory('debug\bin')
Create-Directory('debug\obj')
Create-Directory('debug\objdump')
Create-Directory('debug\elf')
Write-Output "compile boot.asm"
fasm boot/boot.asm debug/bin/boot.bin
Write-Output "Compile second stage loader"
wsl gcc -g -m32 -ffreestanding -c boot/*.c drivers/*.c utils/*.c drivers/ata/*.c -nostartfiles -nostdlib
Move-Item *.o 'debug/obj'
Write-Output "link all c object files"
wsl ld -m elf_i386 -nostdlib -T ld/loader.ld debug/obj/*.o -o debug/elf/loader.elf
Write-Output "compile loader.asm"
fasm boot/loader.asm debug/obj/loader.o
Write-Output "Link loader asm and loader c"
wsl ld -m elf_i386 -nostdlib -T ld/loader.ld debug/obj/loader.o debug/elf/loader.elf -o debug/elf/loader_full.elf
Write-Output "objdump loader file"
objdump -phxd debug/obj/loader.o > debug/objdump/loader.objdump
Write-Output "objdump loader C file"
objdump -phxd debug/elf/loader_full.elf > debug/objdump/loader_full.objdump
Write-Output "Producing elf file"
wsl objcopy debug/elf/loader_full.elf -O binary debug/bin/loader.bin
Write-Output "Padding loader bin file"
$loader_file_size=$(wsl wc -c debug/bin/loader.bin).Split(" ")[0]
$loader_sectors=[math]::ceiling($loader_file_size/512)
$loader_final_bytes=$loader_sectors * 512
$loader_pad_bytes=$loader_final_bytes-$loader_file_size
wsl dd if=/dev/zero of=debug/bin/loader.bin bs=1 count=$loader_pad_bytes seek=$loader_file_size
Write-Output "Clearing .o and .elf"
Remove-Item debug/obj/*.o
Remove-Item debug/elf/*.elf
Write-Output "Compiling Kernel...."
wsl gcc -g -m32 -ffreestanding utils/*.c drivers/*.c kernel/*.c -nostartfiles -nostdlib -o kernel.o -T ld/kernel.ld
Move-Item *.o 'debug/obj'
#wsl ld -m elf_i386 -nostdlib -T linker.ld *.o -o prog.elf
objdump debug/obj/kernel.o -phxd > debug/objdump/kernel.objdump
Write-Output "Creating Empty Raw 10 MB HDD Image..."
wsl dd if=/dev/zero of=debug/bin/hdd.bin bs=1024 count=10240
Write-Output "Writing Kernel to HDD Image...."
$second_kernel_size=$(wsl wc -c debug/obj/kernel.o).Split(" ")[0]
wsl dd if=debug/obj/kernel.o of=debug/bin/hdd.bin count=$second_kernel_size conv=notrunc
}
Function Make()
{
Write-Output "Creating image...."
Get-Content debug/bin/boot.bin, debug/bin/loader.bin, debug/bin/hdd.bin -Raw | Set-Content debug/bin/os_image.bin -NoNewline
}
Function RunOS()
{
Write-Output "Launching QEMU"
qemu-system-x86_64 debug/bin/os_image.bin
# #bochs -f bochsconfig.conf
# #bochsdbg -f bochsconfig.conf
}
if($paramName -eq "all" -Or $Null -eq $paramName)
{
CleanBinaries
Build
Make
RunOS
}
if($paramName -eq "clean")
{
CleanBinaries
}
if($paramName -eq "build")
{
Build
}
if($paramName -eq "make")
{
Make
}
if($paramName -eq "run")
{
RunOS
}
Comments are closed.
Yo, i cant boot the system.
Took your powershell file, it works fine, but i get some warnings.
However, every file is being created etc.
But, QEMU wont boot. It only goes into PXE (Network Boot) Mode.
I already took the latest github changes.
What error are you getting?
Quite informative.
Please continue this series, it is great.
Can you write a blog on mapping the kernel from a lower address space to a higher address space?
i.e. implement a page table to map the kernel loaded at 1MB in the physical address space (0x00100000) to 2/3GB + 1MB in the virtual address space .
yes, I am going to do that. I am actually going to write up an entire OS series here.. File system, GUI, Network stack, etc… so many more things to come up. Thanks for reading. 🙂
Do you have plan to continue this series?
It is really interesting.
yes I am going to continue this series. Due to covid-19 situation unable to spend more time. From this month end will continue. Thanks for your comments. 🙂