The 64-Bit Code

Contents

Before we can start multitasking a certain amount of preparation must be done, most noticeably the creation of an Interrupt Descriptor Table. Tasking will rely on a regular timer tick which is produced by an interrupt routine servicing the real-time clock interrupt. Also, we will want to set up interrupt routines to cover the most common faults (general protection faults and page faults); my routines print a trace of the last five stack entries which is very useful when debugging faults.

The start of the 64-bit code is contained in the file os.s. The first three instructions set up the data and stack segment registers. Then there are a series of writes to three Model-Specific Registers; these registers (0xC0000081, 0xC0000082, and 0xC0000083) specify the address and code and stack selectors to be used by the SYSCALL and SYSRET instructions (see section 6.1.1 in AMD2). These two, incredibly useful, instructions allow one to easily make calls from user-mode code to privileged system code, and back again; without them our task would be very much more complicated. The routine to be called is SysCalls in the file syscalls.s; this routine does an indirect jump to the various system-call routines based on the number of the call (which is passed in register R9).

A call to InitIDT (in the file gates.c) sets up the Interrupt Descriptor Table, which points to various routines in interrupts.s. At this stage we also construct a Task State Selector Descriptor and load a pointer to it into the Task Register. Although hardware task switching is not supported in long mode it is a requirement that at least one 64-bit Task State Segment exists. A pointer to the IDT is now loaded into the IDT register. Although the IDT is now set up, we don't allow interrupts just yet; that would allow task switching to start, but we don't yet have any tasks to switch to.

First we zero a page of memory where the task structures will reside and then initialize a few values in the first task structure - just enough to allow the first task to run. The next 3 instructions set the first element of the linked list of kernel heap memory allocation - kernel heap memory will start at 0x11000. An almost identical set of instructions sets the same for shared memory. I have a system call that any process can use to allocate shared memory (but don't seem, as yet, to use it anywhere).

Next InitializeHD is called. (Despite it's name this really initializes data about the FAT file system rather than the hard disk itself.) Memory pages are now allocated, and Page Table Entries created, for the stacks, program code, and data of the first task. The code for this first task is then moved to it's correct position. the sysretq instruction, which is what we shall use to actually start tasking, expects the current program location to be contained in register rcx and the flags register to be stored in register %r11. When tasking starts we want the flags to be as they currently are but with the interrupt flag set to enable interrupts (it is this enabling of interrupts that allows the timer to start and thus task switching to occur). To accomplish this we push the flags and pop them back to r11; then we switch on the interrupt flag in r11. The sysret instruction now sets the whole ball rolling.
Contents
Home