Create Your Own Kernel In C

转自:https://www.codeproject.com/Articles/1225196/Create-Your-Own-Kernel-In-C

boot.S

# set magic number to 0x1BADB002 to identified by bootloader 
.set MAGIC,    0x1BADB002

# set flags to 0
.set FLAGS,    0

# set the checksum
.set CHECKSUM, -(MAGIC + FLAGS)

# set multiboot enabled
.section .multiboot

# define type to long for each data defined as above
.long MAGIC
.long FLAGS
.long CHECKSUM


# set the stack bottom 
stackBottom:

# define the maximum size of stack to 512 bytes
.skip 512


# set the stack top which grows from higher to lower
stackTop:

.section .text
.global _start
.type _start, @function


_start:

  # assign current stack pointer location to stackTop
    mov $stackTop, %esp

  # call the kernel main source
    call KERNEL_MAIN

    cli


# put system in infinite loop
hltLoop:

    hlt
    jmp hltLoop

.size _start, . - _start

 

We have defined a stack of size 512 bytes and managed by stackBottom and stackTop identifiers.

Then in _start, we are storing a current stack pointer, and calling the main function of a kernel.

As you know, every process consists of different sections such as data, bss, rodata and text.
You can see the each sections by compiling the source code without assembling it.

e.g.: Run the following command
        gcc -S kernel.c
      and see the kernel.S file.

And this sections requires a memory to store them, this memory size is provided by the linker image file.
Each memory is aligned with the size of each block.
It mostly require to link all the object files together to form a final kernel image.
Linker image file provides how much size should be allocated to each of the sections.
The information is stored in the final kernel image.
If you open the final kernel image(.bin file) in hexeditor, you can see lots of 00 bytes.
the linker image file consists of an entry point,(in our case it is _start defined in file boot.S) and sections with size defined in the BLOCK keyword aligned from how much spaced.


linker.ld

Now you need a configuration file that instruct the grub to load menu with associated image file
grub.cfg

menuentry "MyOS" {
    multiboot /boot/MyOS.bin
}

Now let's write a simple HelloWorld kernel code.

Image 4 for Create Your Own Kernel In C

 

kernel_1 :-


kernel.h

#ifndef _KERNEL_H_
#define _KERNEL_H_

#define VGA_ADDRESS 0xB8000

#define WHITE_COLOR 15

typedef unsigned short UINT16;

UINT16* TERMINAL_BUFFER;

#endif

Here we are using 16 bit, on my machine the VGA address is starts at 0xB8000 and 32 bit starts at 0xA0000.
An unsigned 16 bit type terminal buffer pointer that points to VGA address.
It has 8*16 pixel font size.
see above image.

kernel.c

The value returned by VGA_DefaultEntry() function is the UINT16 type with highlighting the character to print with white color.
The value is stored in the buffer to display the characters on a screen.
First lets point our pointer TERMINAL_BUFFER to VGA address 0xB8000.
Now you have an array of VGA, you just need to assign specific value to each index of array according to what to print on a screen as we usually do in assigning the value to array.
See the above code that prints each character of HelloWorld on a screen.

Ok lets compile the source.
type sh run.sh command on terminal.

run.sh

#assemble boot.s file
as --32 boot.s -o boot.o

#compile kernel.c file
gcc -m32 -c kernel.c -o kernel.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra

#linking the kernel with kernel.o and boot.o files
ld -m elf_i386 -T linker.ld kernel.o boot.o -o MyOS.bin -nostdlib

#check MyOS.bin file is x86 multiboot file or not
grub-file --is-x86-multiboot MyOS.bin

#building the iso file
mkdir -p isodir/boot/grub
cp MyOS.bin isodir/boot/MyOS.bin
cp grub.cfg isodir/boot/grub/grub.cfg
grub-mkrescue -o MyOS.iso isodir

#run it in qemu
qemu-system-x86_64 -cdrom MyOS.iso

Make sure you have installed all the packages that requires to build a kernel.

the output is 

Image 5 for Create Your Own Kernel In CImage 6 for Create Your Own Kernel In C

As you can see, it is a overhead to assign each and every value to VGA buffer, so we can write a function for that, which can print our string on a screen (means assigning each character value to VGA buffer from a string).

 

kernel_2 :-

kernel.h

#ifndef _KERNEL_H_
#define _KERNEL_H_

#define VGA_ADDRESS 0xB8000

#define WHITE_COLOR 15

typedef unsigned short UINT16;

int DIGIT_ASCII_CODES[10] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};

unsigned int VGA_INDEX;

#define BUFSIZE 2200

UINT16* TERMINAL_BUFFER;

#endif

DIGIT_ASCII_CODES are hexadecimal values of characters 0 to 9. we need them when we want to print them on a screen.
VGA_INDEX is the our VGA array index. VGA_INDEX is increased when value is assigned to that index.
BUFSIZE is the limit of our VGA.
following function prints a string on a string by assigning each character to VGA.

To print an 32 bit integer, first you need to convert it into a string.

To print a new line, you have to skip some bytes in VGA pointer(TERMINAL_BUFFER) according to the pixel font size.
For this we need another variable that stores the current Y th index.

And in KERNEL_MAIN(), just call the functions,

Image 8 for Create Your Own Kernel In C

As you can see it is the overhead to call each and every function for displaying the values, that's why C programming provides a printf() function with format specifiers which print/set specific value to standard output device with each specifier with literals such as \n, \t, \r etc.

 

kernel_3 :-

VGA provides 15 colors,
   

 BLACK = 0,
    BLUE = 1,
    GREEN = 2,
    CYAN = 3,
    RED = 4,
    MAGENTA = 5,
    BROWN = 6,
    LIGHT_GREY = 7,
    DARK_GREY = 8,
    LIGHT_BLUE = 9,
    LIGHT_GREEN = 10,
    LIGHT_CYAN = 11,
    LIGHT_RED = 12,
    LIGHT_MAGENTA = 13,
    YELLOW = 14,
    WHITE = 15,

Just change the function name VGA_DefaultEntry() to some another with UINT8 type of color parameter with replacing the WHITE_COLOR to it.
For keyboard interrupt, you have inX function provided by gas, where X could be byte,word,dword or long etc.
The BIOS keyboard interrupt value is 0x60, which is in bytes, passed to the parameter as to inb instruction.

UINT8 IN_B(UINT16 port)
{
  UINT8 ret;
  asm volatile("inb %1, %0" :"=a"(ret) :"Nd"(port) );
  return ret;
}

We can also create a simple linked list data structure, as a starting point of an file system.
let's say we have following record,

typedef struct list_node{
  int data;
  struct list_node *next;
}LIST_NODE;

but we need memory to allocate this block because there is no malloc() function exists.
Instead we use a memory address assigning to pointer to structure for storing this data block.
well you can use any memory address but not those addresses who are used for special purposes.

0x00000000 - 0x000003FF - Real Mode Interrupt Vector Table
0x00000400 - 0x000004FF - BIOS Data Area
0x00000500 - 0x00007BFF - Unused
0x00007C00 - 0x00007DFF - Our Bootloader
0x00007E00 - 0x0009FFFF - Unused
0x000A0000 - 0x000BFFFF - Video RAM (VRAM) Memory
0x000B0000 - 0x000B7777 - Monochrome Video Memory
0x000B8000 - 0x000BFFFF - Color Video Memory
0x000C0000 - 0x000C7FFF - Video ROM BIOS
0x000C8000 - 0x000EFFFF - BIOS Shadow Area
0x000F0000 - 0x000FFFFF - System BIOS

In above addresses range, 0x00000500 - 0x00007BFF or 0x00007E00 - 0x0009FFFF can be used to store our linked list data.
You can access the whole memory(RAM) if you know the limit of it or can be stored in a stack.
So here's a function that return a allocated LIST_NODE memory block with starting at address 0x00000500,

Image 9 for Create Your Own Kernel In CImage 10 for Create Your Own Kernel In C

For more about os from scratch, os calculator and low level graphics in operating system, see this link.

References

 

posted @ 2019-04-02 17:22  逐梦客!  阅读(229)  评论(0编辑  收藏  举报