CS 111 Fall 2009

Lecture Topic: Abstractions and bootstrapping

prepared by: Stefano Emiliozzi, Jason Wang, ChiaCheng Liu

 

OS Goals: What doe we want an OS to do?  What is the big picture?

However, problems can arise according to various issues

  1. Contradictory goals.

    e.g. performance vs. simplicity:
    Bubble sort => Quick sort (trade in simplicity for performance)

  2. Those goal are open ended and defined fuzzily.

Sample scenario:

A paranoid Professor "A" will want to submit a paper and like to make sure if the word count will be accepted. Also, professor want to ensure the security of the paper.

Here is the specification(provided by customer):

As software engineers, we should ask for more specific details about the program:

Q1: How to handle error?
A1: Output a “?” .

Q2: What is the definition of words?
A2: Word can contain [A-Za-z]+.

Q3: What is the machine we use?
A3: 32-bit x86 architecture, VGA output, 1 gigabyte of RAM

Q4: What is the future plan or change you might want to have?
A4: The program should be expandable in the future. e.g. it might be implemented as a full word processor eventually.


Hard disk considerations:

 

 

Hard disk - Disk controller - Bus - CPU

 

Disc Controller commands

In x86 machine, the little endian is used.

Why?

Little endian

 

 
[ 21 67] <-16 bits machine
[00 00 21 67] <- 32 bits machine
1003 1002 1001 1000 <- address

 

Big endian    
[21 67 ] <-16 bits machine
[00 00 21 67] <- 32 bits machine
1003 1002 1001 1000 <- address

 

Under the little endian, the data will not be messed up if the machine is switched from 16 bits to 32 bits.


x86 Boot Procedure:


A small portion of the RAM (Random Access Memory) is marked as read-only and it maintains its status in case of power outage. Such portion is called ROM, acronym for Read Only Memory.

In such area is stored the BIOS, (Basic Input Output System) which can be considered the first program to be loaded and executed. Its basic tasks are:

Due to its special nature, it is often referred as firmware.
Upon its execution, it looks for hard drives and when it finds the main one, copies 512 bytes of data from the Boot Sector 0 (known as Master Boot Record)  into the RAM, thus writes in the Program Counter register  (PC) the starting memory address of the data just copied. We remind here that the x86 architecture we are using for this example relies on the Little Endian notation which means that the LSB (Lowest Significant Byte) is at the lowest memory address. For the sake of clarity, if we suppose a memory with increasing addresses form right to left and the store of the following value, 0x01020304,


we would have (supposing a byte addressing):

Master Boot Record (MBR)

The Master Boot Record is an "OS independent" sector, which resides at position 0 of hard disks. Its less significant two bytes are the signature, which tells us if the sector it’s actually a MBR, by checking if they are equal to 0x55,0xAA. In the MBR, there are 64bytes which contain the partition table. For each partition in the hard disk, there is one entry in the partition table – the information stored are:

The rest of the MBR (446 bytes) contains OS dependant code (x86 in our case) which scans the partition table, looking for a bootable partition. To some extents it mimics the BIOS behavior on a different level.
Once the partition has been found, the program looks for the Volume Boot Record (VBR), which is an OS dependant sector that contains information about the operating system it is meant to load. At this point the program in the MBR loads the 512 bytes of the VBR into memory and updates the program counter to the start address of the data. The code contained in the VBR, takes care of loading the OS kernel into the memory, allowing the system to finally boot. This kind of procedure is often regarded as Chained Loading.


OS Code:

At this point we are ready to implement some basic functions of our limited OS:

void wait_until_ready(void) {
  while (inb(0x1F7) & 0xC0) != 0x40)    //the most significant bits must be 01
  continue;
} 

Implementation of the busy waiting function, for dealing with the disk controller

int read_ide_sector(int s, char *a) {
  wait_until_ready();          //waits for the disk to be ready
  outb(0x1F2, 1);              //prepares to read 1 sector
  outb(0x1F3, s & 0xFF);       //initializes the sector offset
  outb(0x1F4, (s >> 8) & 0xFF); 
  outb(0x1F5, (s >> 16) & 0xFF);
  outb(0x1F6, (s >> 24) & 0xFF);
  outb(0x1F7, 0x20);           //sends the read command
  wait_for_ready();            //waits for data to be ready
  insl(0x1F0, a, 128);         //copies 512 bytes (128 words of 32bit each)to
                               //the location pointed by a
  return 0;                    //no error checking
}

Function that actually reads one sector for the disk

// this is our VBR program, assuming that we have to read 19 sectors
// Note that sector 0 is the VBR
for(i = 1; i < 20; i++)
  read_ide_sector(i, (0x100000 + (i-1)*512);
goto 0x100000; //this is not real C instruction, but the semantic is to 
               //update the Program Counter to the Address 0x100000 

Our VBR Program

int main(void) {
  unsigned int nword = 0;
  bool in_word = false;
  for (int s=50000;;s++) {
    char buf[512];                 //reading buffer
    read_ide_sector(s, buf);       // read one sector
    for (int j = 0; j < sizeof buff; j++) {
      if (!buf[j]){ //any more data?
        write_out(nwords);//prints the result
        return 0;
      }
      bool this_alpha = isalpha((unsigned char) buf[j]);


      // cast is needed
      //because buf[j] is the the range[-128..127] while isalpha accepts
      //values in the range[0..255]
      nwords += ~inword & this_alpha;
      in_word = this_alpha;//updates the marker variable
    }
  }
}

 

Our Main Program

 

void write_out(int n) {
  uint16_t *p=(uint16_t *)0xB8014; //pointer to the video memory  
  do {
    *p--=’0’+n%10+(7<<8);
    //writes in gray, an ascii character from right to left
    n/=10;    
  } while(n)   // n = 0 is the termination condition
}

 

Function that prints on the screen


Let’s make some final considerations. First of all the function read_ide_sector is a primitive one and it should be used that several components (BIOS, MBR program, VBR program, BootLoader, Applications). A good strategy for avoiding multiple copies of the same function could be to use only the copy that resides in the BIOS. For the sake of performance, what happens in most cases is that everyone that needs direct access to disk re-write that function.


Secondly, if we analyze our small operating system with respect to the OS Goals we defined at the beginning of the lesson, we sadly conclude that the only achievement we made was probably the security.