This was a great learning curve that seemingly helped me foster more theoretical and conceptual ideas surrounding theories/laws on the underlying mysteries of operating systems and kernels. I had presumed, I had an okay idea on how OSs worked. However, creating my own OS was absolutely way more definite compared to just reading about. In my own, honest opinion. This was a way for me to have a better understanding of software I would want to mess with on kernel mode and user mode.
Before we begin, even though the kernel and operating system is 32bit. I will be explaining concepts in 64 bit too, evidently one of them being Long Mode.
Modes
Interrupts
Descriptors
Paging
Read up more on Protected Mode in Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1; Chapter 3
*
What are Interrupts
You can think of Interrupts as being a signal or data that is being sent by a device such as Keyboard, Solid State Drive, Hard Driver, or Mouse and Software that tell's the CPU that an event happened and need to immediately stop what it's currently doing, to proceed to what sent it the interrupt.
E.g. when you move/click a mouse, the mouse controller will send an interrupt to the Interrupt Controller for the CPU, the CPU attention will immediately go to the mouse interrupt and will proceed to execute a routine (mouse movement or clicking). After the mouse interrupt the CPU will continue doing whatever it was before the interrupt or go manage another interrupt if it has been signal to.
+------------------------+ +------------+
| TYPES OF INTERRUPTS |------------| Exceptions |
+------------------------+ +------------+
/
/
/
+------------+ +------------+
| HARDWARE | | SOFTWARE |
| INTERRUPTS | | INTERRUPTS |
+------------+ +------------+
IRQ Lines, or Pin-based IRQs: These are typically statically routed on the chipset. Wires or lines run from the devices on the chipset to an IRQ controller which serializes the interrupt requests sent by devices, sending them to the CPU one by one to prevent races. In many cases, an IRQ Controller will send multiple IRQs to the CPU at once, based on the priority of the device. An example of a very well known IRQ Controller is the Intel 8259 controller chain, which is present on all IBM-PC compatible chipsets, chaining two controllers together, each providing 8 input pins for a total of 16 usable IRQ signalling pins on the legacy IBM-PC.
Message Based Interrupts: These are signalled by writing a value to a memory location reserved for information about the interrupting device, the interrupt itself, and the vectoring information. The device is assigned a location to which it writes either by firmware or by the kernel software. Then, an IRQ is generated by the device using an arbitration protocol specific to the device's bus. An example of a bus which provides message based interrupt functionality is the PCI Bus. By wiki.osdev - https://wiki.osdev.org/Interrupts
Entry - The Entry defines a region in memory where to start, along with the limit of region and the access privileges associated with the entry. Access privilege as in telling processor if the OS is running in System (ring 0) or Application (ring 3). It prevents applications or usermode from having access to certain registers/operands and mnemonics. Such as CR registers and cli/sti respectively.
Limit - The size of the Segment Descriptor
Segment Selector - They're registers that hold the index of the Descriptors
to be more explicit, An index is not a selector
Things a Segment Register holds:
Access the Descriptor Table have privilegde too, this is called RPL (Request Privilege level)
for every register but for the cs is called CPL (Current Privilege Level). They're both serving
different purposes, which you can find out in the Intel or AMD manuals.
The table to use for looking into. One table is the GDT the other one is the LDT.
An informal rule to conceptually imagine the use of selector: So the informal rule is:
selector = index + table_to_use + privilege table_to_use + index = descriptor = all the information about the segment of memory to be used
where the plus sign is not a arithmetic operation
Bit field of segment selector register:
15 3 2 0
+--------------------------------------------------+----+--------+
| Index | TI | RPL |
+--------------------------------------------------+----+--------+
TI = Table Indicator: 0 = GDT, 1 = LDT
The TI specify if the Descriptor were dealing with is a GDT or LDT
IF(TI == 0) THEN
... THE DESCRIPTOR IS A GDT
ELSEIF(TI == 1) THEN
... THE DESCRIPTOR IS A LDT
GDT is 1:1 with Logical Address, An example of the GDT working with the selector:
<---- Selector ----> +----- Segment Selector Register
+-------+----+-----+ v
| Index | TI | RPL | = DS
+-------+----+-----+ GDT LDT
| | +---------------------+ +---------------------+
| +------------>| Null Descriptor | | Null Descriptor |
| +---------------------+ +---------------------+
| | Descriptor 1 | | Descriptor 1 |
| +---------------------+ +---------------------+
| | | | |
| ... ... ... ... ... ... ... ...
| | |
| +---------------------+
+------------------->| Descriptor K |
+---------------------+
| |
... ... ... ...
RPL (Request Privilege Level) describes the privilege for accessing the descriptor
We store the all the GDT base (address) and limit (size of our GDT) in the GDTR. The GDTR points to all our GDT Entries in memory, starting from the base. After that, it's then loaded with the lgdt mnemonic:
typedef union _gdt_descriptor
{
struct
{
uint64_t limit_low : 16;
uint64_t base_low : 16;
uint64_t base_middle : 8;
uint64_t access : 8;
uint64_t granularity : 8;
uint64_t base_high : 8;
};
} __attribute__((packed)) gdt_entry_t;
gdt_entry_t gdt_entrys[256];
/* The GDTR (GDT Register) */
struct gdtr
{
uint16_t limit;
uint32_t base;
} __attribute__((packed)) gdtr;
...
gdtr.base = &gdt_entrys;
gdtr.limit = (sizeof(gdt_descr) * 256) - 1)
Read up more on it in the AMD64 Architecture Programmer’s Manual, Volume 2, Section 4.7 (pg. 84 - 90{+})
reference to this and on onwards. https://notes.shichao.io/utlk/ch2/#paging-in-hardware This was by far the most fun I had, I was extremely excited once I understood it.
x86 OS Legacy-paging virtual address with 4KB pages:
x86 OS CR4.PAE paging virtual address with 4KB page:
x86_64 (ia-32e) OS CR4.LME/CR4.PAE paging virtual address with 4KB page:
• The WP and PG flags in control register CR0 (bit 16 and bit 31, respectively).
• The PSE, PAE, PGE, PCIDE, SMEP, SMAP, and PKE flags in control register CR4 (bit 4, bit 5, bit 7, bit 17, bit 20, bit 21, and bit 22, respectively).
• The LME and NXE flags in the IA32_EFER MSR (bit 8 and bit 11, respectively). • The AC flag in the EFLAGS register (bit 18). By Chapter 4 of Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1; Chapter 4
• The LME and NXE flags in the IA32_EFER MSR (bit 8 and bit 11, respectively). • The AC flag in the EFLAGS register (bit 18). Chapter 4 of Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1; Chapter 4
If CR4.PAE and/or CR4.LME is set to 1, then PSE is completely disregarded.
No terminal added
cd smkrnl; make run
for the spark of inspiration/support on my continuous effort on this project and for helping me understand certain concepts within kernel/OS development. =)