Kernel Initialization
A brief view of kernel initialization
Important file(s):
main.c
lapic.c
console.c
ide.c
proc.c
Two categories of initializations:
per-cpu
only-once
Local APIC (advanced programmable interrupt controller) -- per cpu
https://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller
On Linux: cat /proc/interrupts
IOAPIC -- only once
Other per-CPU initialization:
switchkvm(): loading the (root of) kernel page table into %cr3
seginit(): configuration segment registers, setup space for per-cpu storage
idtinit(): interrupt descriptor table (IDT)
syscallinit(): system call setup
read more in main.c
Device driver and xv6's vfs: console
Linux (Unix) uses a major:minor pair to specify a device. The numbers are only meaningful at runtime.
major: identify the device type. devices having the same major number uses the same driver code.
minor: to identify a unique device.
To show a list of devices on your Linux: ls -al /dev
Example, tty0, tty1, ... all has the same #major, but different #minor
Similarly, In xv6, the major number is used to select the driver API (read, write, ioctl, etc.).
file.c:devsw is a statically-allocated table of NDEV entries. There can be at most NDEV types of drivers in xv6.
Read the readi() and writei() functions in fs.c. When performing read/write operations on an inode, the kernel will check if this is a device file. If yes, the appropriate driver function will be selected and called.
writei():
devsw[ip->major].write(ip, src, off, n);
UNIX has a famous "everything is a file" philosophy. As it says, almost everything in the system, including real file and "fake" files are linked into one root file system. This explains why the driver code is mingled with the file system code.
Initialization:
console.c:consoleinit()
The console driver is statically assigned a major number CONSOLE (1). The kernel registers the console driver on the devsw table.
init.c: the first process "init" creates a device file "console", with major=1.
After this, when the "console" file is opened and is read/written, the driver functions in console.c will be invoked to handle the read/write request.
Now you know that adding a new driver to xv6 is quite easy. It's somehow more complex in Linux.Â
Extended reading:
The classic book "Linux Device Driver v3" explains almost everything about this topic. https://lwn.net/Kernel/LDD3/
(up-to-date) Linux Driver Implementer's API Guide
(The first a few slides of) "Linux Kernel Fastboot On the Way", Linux Plumbers Conference '19
The IDE hard disk driver (ide.c)
https://wiki.osdev.org/ATA_PIO_Mode#Registers this explains the io ports.
https://wiki.osdev.org/ATA_PIO_Mode#x86_Directions this shows some programming examples.
ideinit():
Enable IDE interrupt on the IOAPIC.
checkout if the second disk presents.
iderw():
this is called when an I/O request is sent from file system to the IDE driver.
Append the request to the queue.
If the request is the only one in the queue (the queue head), send the request to the disk.
otherwise (there are other requests pending), don't send because there can be only on-the-fly request at a time.
wait for completion: the while loop
idestart():
Send one requests to the disk and return.
ideintr():
the handler of ide interrupt event.
It sees that an I/O has been completed. For read operation, insl(), like memcpy(), copies 128 * 4 bytes from the "data port" to b->data.
Notifies any (usually one) processes waiting on this event.
The wake-up is asynchronous. The processes will be brought back for really running by the scheduler. We will discuss the details later.
Since the I/O is done, it can send out the next request in the queue if any.
Note that this way of issuing real I/O operations minimizes the delay between consecutive requests.
If only iderw() can issue I/O operations (calling idestart()), iderw() will need to wait and wake up later to send the request. This will put a longer delay between the I/O operations.
About the queue(s):
This driver has a software queue of unlimited length (idequeue).
The driver can only send one outstanding I/O operation at a time. This is equivalent to having a hardware queue of length 1.
Newer hardware/devices usually provide real hardware queue(s). SATA specifies NCQ with length=32. Today's SSDs also have multiple hardware queues for better parallelism (per-queue locking).