A lot has happened to ttOS since my last blogpost. If my memory serves me right, my previous blogpost contains information about programming the PIT. I will focus on a few key subsystems which have been worked on.
Physical memory management
I have written a botched implementation of a physical memory manager. Granted, I rarely use it in my code. It needs a complete redesign and rewrite as soon as possible, because I'm constantly working around having no dynamic memory allocation.
Because ttOS will not feature multiple tasks or processes running at once, paging and fancy memory management is not required. There is only a kernel and 1 optional user (ring 3) process running at a time. Therefore, the memory can easily be split in two regions; kernel and user space.
This is why I'm not convinced that I need the distinction between a physical and a virtual memory manager. I just need a kernel memory manager (KMM) and a user memory manager (UMM).
I think I will introduce an abstraction layer for the memory managers. Because I'm not sure which technique to use, and because it's interesting to play with different approaches, I want a relatively easy way to switch implementations.
The concept of a stack-based memory allocator intrigues me.
Preliminary driver to be able to read characters from the keyboard. It's a minidriver that has no abstraction layer yet. This will be improved later, so that terminal input can be sent from any input driver.
Right now, it's really hardcoded in the kernel. The getch() function just waits for IRQ1 and returns the value. Ideally, the getch() function would ask the input driver for a character.
I'm pretty happy with my current terminal, even though it's still a hardcoded part of the kernel. The terminal supports a limited number of commands which can be dynamically registered whenever the system requires it. This means that drivers can register commands to be able to interact with hardware. The commands are implemented in a function in the kernel address space.
The logic for reading and parsing the command line is implemented in a state machine. See the diagram below:
Unfortunately, because there is no memory allocator, the system is bound to a few fixed limitations. For example, only 3 parameters are supported, and each value is stored statically in kernel address space. When the memory manager is finished, I will fix this limit.
Save the best for last, they say, right? I've been experimenting with a modular driver framework, and storage devices seemed to be a simple way to test this. So, basically, a storage device is a device which contains data and can be accessed by the system. For example:
- RAM Disk
- Hard Disk
- USB Stick
The instances of these storage device types are stored in the Storage Device Table. You can see the struct definition below:
So, the name field is obvious. It gives a unique name to the device attached to the system. The type field tells you the type of the device. The driver field allows you to directly access the driver object, so you can read or write blocks. data0 and data1 are used as metadata, the driver decides what to put there. RAM disks will store the address and size of the ramdisk for example.
This is what the storage device driver struct looks like:
The name is used to identify the driver. I also use it to include a version number. detect_devices exposes a function to allow the driver to search the system for devices and register them in the Storage Device Table. initialize will do whatever initialization the driver needs. read_lba and write_lba will tell the driver to respectively read or write sectors to a LBA.
The RAM Disk implementation
I have a floppy driver, but it is too crappy for a reliable test. So I wrote a RAMDisk driver. I will explain what happens in each function.
We begin with ramdisk_get_driver. This function is called when the drivers are initialized to store the drivers in the storage device drivers table. The function constructs an instance of the storage_device_driver struct and populates it.
The ramdisk_init simply registers a new terminal command "ramdisk".
ramdisk_cmd_handler is the handler for the ramdisk terminal command. Based on the amount of parameters it will create a ramdisk with a fixed size at a static or dynamic memory address.
The ramdisk_create and ramdisk_create_with_address simply create a storage device table entry and register the device. When this is done, the terminal sd command can be used to show a listing of storage devices.
Finally, ramdisk_read_lba and ramdisk_write_lba will respectively read and write from the memory address as specified in the storage device table entry. See how I used a static variable to act as a "buffer"? That's because I don't have dynamic memory allocation :(.
Like I stated in the beginning, a lot of work has been done. I really want to focus on getting the memory management done. I will write a new post about that when it's finished.
Thanks for reading, and see you next time!