CS111 Lecture 2: Abstractions and Bootstrapping

Scribe notes by John Yoon, Seyed Amir Mirsalimi, Jeremiah Ingham, and Gary Tai Chao

The Paranoid Professor Case

An English professor wants you to write a program that counts the number of words in a text file.

Here are the requirements and specifications:

Goals of an Operating System:

A View of the System in Question

7200rpm, 120Hz, 1sec/120Hz / 2 semi-circle = 4.167 ms.average latency + 8ms average seek time.

Suggestion: put program and data in one track (if it fits)

Aside: Note that this limit of 32 bits means that we could only address 232 sectors * 29(512 bytes/sector) = 2TB of disk space. This is the reason that the industry is currently (April 2010) working on increasing the number of bytes each sector could store.

In the disk controller, there are several "disk controller registers" that allows communication between the CPU and the disk drive. These registers are addressable by the CPU. The register mapped to 0x1F7 is known as the status register. When the register reads 01, representing the ready state, the CPU could communicate with the disk drive. The CPU requests for data by storing the number of sectors it wants to read into disk controller register 0x1F2. A sector is simply a unit of disk space (512 bytes). Each sector on the disk drive is indexed in a similar fashion as arrays. The CPU then stores a sector offset into registers 0x1F3 through 0x1F6. A sector offset is simply the index number of the first sector it wants to be read. Note that each register could only store 8 bytes. Thus, since we can only store the offset into 4 registers, the longest offset could only be 32 bytes long.

Once the number of bits and the offset is stored, the CPU then stores the READ command into register 0x1F7 and wait for the disk to go in the READY state. Then, the CPU will obtain results of the read as data out of register 0x1F0 and store them into RAM. At this point, you might wander what would happen if the required data would not fit in a single register at once. (8 bytes). This actually does not present a problem since the data is obtained in a streaming fashion that allows for continuous data flow from the disk to the CPU and then to RAM.

Bootstrapping

It occurs when the computer is powered on, and the Program Counter (PC) points to an EEPROM (Electrically Erasable Programmable Read-Only Memory) chip which contains a small program called a BIOS (Basic Input Output System). This BIOS program can be changed by flashing the EEPROM with the new BIOS, but the BIOS will typically come installed on a machine at purchase.

Initial State of CPU

BIOS (Basic Input/Output System) - What does it do (traditionally)?

  1. Test the system
    • Checks CPU, RAM, memory pattern etc.
    • Note: Server tests tend to be more comprehensive than those for consumer computers. Therefore servers take much longer to boot.
  2. Look for devices / Probe for devices
    • Loads and stores to device registers
    • Tries to read from first sector of every device to check if device is bootable/a boot program exists.
    • Components of a bootable device's first sector:

  3. Find Master Boot Record (MBR) - first sector of device, x86 machine code, OS independent, programs rarely fit into 446 bits therefore MBR boots other things instead.
    • Partition Table - status (bootable?), type, start sector number, size of partition (16 bytes total for each partition)
    • Signature - 0x55, 0xAA (or 0xAA55 due to little endian)
    • 1/(2^16) chance that the BIOS will mistake a partition to be MBR.

  4. BIOS copies entire sector into RAM at address 0x7C00
    • Typical MBR operation:
      1. Scan partition table
      2. Find bootable partition
      3. Load volume boot record (VBR), the first sector of that partition, into RAM (Usually designed for OS on the partition). MBR is not big enough to contain all the information about the partitions.
      4. Jump to it => VBR takes over (for the case of our application, copies our application)
      5. Example: Linux MBR => VBR => GRUB => OS. At each stage, booting program is the OS (can choose to shut down the computer etc.). MBR and VBR are separated, so that MBR could be BIOS only and agnostic to the OS on the partitions. VBR is OS-specific.

Memory Layout

Disk Layout

Boot Loader for the VBR

We are assuming here that there are only 19 sectors worth of code.

	// Starting from 1 because sector 0 is the VBR
	for (i = 1, i < 20; i++) {                                                       
		read_ide_sector (i, (char *) 0x100000 + (i - 1)*512); // we have to cast the address as a char * or we would have a type mismatch.
	}
	goto (char *)0x100000; // jump to our program's address

Read IDE Sector Function

	// s is the sector number, a is the address to read to
	void read_ide_sector (int s, char *buf) {
		// this compares the 7th and 8th bits in 0x1F7 (11000000) to 0x40
		// (01000000)
		// this is the "check for ready" loop
		while ((inb(0x1F7) & 0xC0) != 0x40)
			continue;  
			// store value of '1' into the number of sectors register
			outb (0x1F2,1);
			// set the sector offset registers                                                    
			outb (0x1F3, s & 0xFF);                                      
			outb (0x1F4, (s >> 8) & 0xFF);
			outb (0x1F5, (s >> 16) & 0xFF);
			outb (0x1F6, (s >> 24) & 0xFF);
			// Set ready register
			outb (0x1F7, 0x20);
		// again wait until device is ready (void wait_for_ready (void)
		// 0x1F0 is the device register location                                             
		while ((inb(0x1F7) & 0xC0) != 0x40) continue;
			// and 128 is the amount of words (which = 4bytes) we will be inserting (1 sector = 512 bytes = 4 bytes * 128 words).
		//insl: write buffer gets written 128 times from the register via CPU.
			insl (0x1F0, buf, 128); 
	}                 

Main Program

	void main(void) 
	{
		int words = 0; // has not read anything yet
		bool inword = false; // if the cursor is pointing at an alphabet, the previous character was an alphabet.
		// We are arbitrarily choosing this sector for our reading location (sector/byte)
		int s = 1000000;								
		for ( ; ; s++) // increment the sector for read_sector
		{	
			// create a buffer to read in to
			char buf[512];
			// read the ide sector to our buffer
			read_ide_sector (s, buf);				
			for (int j = 0; j < 512; j++) {
				// if we are at the end of file quit
				if (buf[j] == '\0') goto eof;
				// check if we are currently in a word			
				bool this_alpha = isalpha((unsigned char) buf[j]) != 0;
				//
				/this will increment the number of words at the beginning of each word          
				words += ~inword & this_alpha;	//both bool. 
				// update inword
				inword = this_alpha;					
			}
		}
			
		EOF:
			words += inword;
			display (words);								
	}

	// Another possibility: we could take out the repeating routine as a function
	void wait_for_ready(void) {
		while (inb(0x1F7) & 0xC0) != 0x40) // busy wait
			continue;
	}

Memory Mapped Display

Each pixel on the screen consists of two bytes. The first byte is an ASCII text character. The second is the pixel color.

Display:

The first pixel's address is 0xB8000.

Each pixel has 2 bytes, 16 bits. There are 25*80*2 = 4000 bytes total. Low order byte stores the character to display, and the high order byte stores the color.

	void display (int i) {
		//starting position on screen. Write from right to left.
		unsigned char *screen = (unsigned char*) 0xB8000 + (25*80*2/2);	//right center of the screen. 2 bytes, 2 to be in the middle. 
		do {
			screen[0] = (i%10) + '0'; // ASCII, alphabet
			// gray text on black
			screen[1] = 7;
			// writing from back to front              				 
			screen -= 2;                 											
			i /= 10;
			} while (i != 0);
	}

Read_sector for MBR, VBR, and application. Instead of multiple functions, we can have it on BIOS. This was the original intention for BIOS, but this approach has been discarded for the sake of performance.

The slowest part of the program is the wait_for_ready and we will improve it next time.

Improving the Modularity of This Program

Function that needs to be improved:

	void read_ide_sector (int s, char *addr);

What if we want to read from other devices other than a hard drive?

	void read_sector (int s, char *addr) 
	{
		switch (device)
		{
			case IDE: // current code
			case CDROM: // other code
		}
	}

We can also do this:

	// d represents multiple devices
	void read_sector (int d, int s, char *addr);      

If we want to change the number of bytes read or where to start reading? The function is changed from read_sector to read because the amount we read is no longer specific to sectors.

	// o is the byte offset into device. len is the # of bytes to read
	void read (int d, int o, char *addr, int len);      

If there was a hardware failure in reading the text? Change function to int to return if read was successful.

	int read (int d, int o, char *addr, int len);

int is only 32-bit which means we can only read roughly 4GB on the device. Our hard drive is 1TB. How do we fix this?

	// off_t and size_t types are both 64-bit
	int read (int d, off_t o, char *addr, size_t len);