Scribe Notes: Lecture 2
CS111: Spring 2014
By: Amy Tsao
Goal of today's lecture: build something with a real application without an operating system
Problems with Computer Systems
1.
incommensurate
scaling
2.
waterbed
effect
3.
propagation
of effects
4.
Problem of complexity (the biggest one!)
Problem of Complexity
Computer software and architecture is complicated, moreso than every other field. Before computers were built, the single most complicated object was the aircraft carrier. Nowadays, smartphones are more complex than that. We need a way to manage that complexity.
Moore's Law
The number of transistors that you can put on a chip at a minimum cost doubles every two years. Note that this isn't about how fast the chip is, just the number of transistors.
To put it into scale, the number of transistors on a chip was 2 * 104 in 1970. It has since then grown logarithmically to 4 * 109 transistors in 2014.
Kryder's Law
The number of terabytes you can put on a hard drive at a minimum cost
The ability to
build is growing at a growing rate.
We're continuously using our tools to develop a better system.
the
rate of growth of technology is proportional to technology
(note
that the curve is exponential)
Example
1st Univac was designed very conservatively. It was slow and there was error checking galore because they had no way of testing it since it was designed by hand with paper.
2nd Univac used the first Univac for the design, improving on the old design to make it faster and less conservative.
Conclusion
Both hardware and software have become more powerful and complicated. However, sometimes this gets to be too complicated. We want to learn how to be able to build something without an operating system that is specific to our needs (aka standalone or baremetal).
Building a (not) Operating System
In this section, we want to build something with no operating system. Why? Because sometimes you can't always trust the standard operating systems like Linux or Windows because someone can hack into the operating system and mess everything up. In the example, we are going to build something simple -- counting the number of words in a proposal.
Hardware
·
x86 Intel
Core i3-2130
·
3 MiB cache, 3.4 GHz
·
4 GiB RAM (dual channel)
o
DDR3
o
1600 MHz
o
SDRAM3
·
1 TB hard
drive, SATA 7200 RPM
·
Intel HD
integrated graphics
Connections
·
no network
connection
o
air gap
security
o
no wires into
the machine except the power
User Interface
We will have it so that the monitor will
display the word count as soon as the machine is plugged in.
A Little Bit More About Hardware
The hard disk tells its three
platters to read/write some data. However, they don't talk directly -- there is
a disk controller that is connected to the hard drive, with the bus connected
to the disk controller. To tell the disk controller a command:
1.
Wait for it
to become ready (this is busy computer,
it has other things to do with its time)
·
Say the
address you're looking at is 0x1F7. You keep looking at that address until you
see a particular pattern in those 8 bits.
2.
Store number
of sectors you want to read into 0x1F2
·
How many
sectors you want to read I/O in
3.
Store which
sector you want and read into 4-byte number (store in 4 adjacent controller
vector)
·
0x1F3-0x1F6
·
Concatenate
them
·
Little Endian order
4.
Store READ
command into 1x1f7
·
When you
store into the command register computer becomes busy
5.
Wait for it
to be ready (same as the first step)
6.
Slurp data
from disk controller cache through CPU into RAM
·
If you operate
on the data you don't have to pull it out again
Bootstrapping
Bootstrapping is running a program
that doesn't require extra input. The problem with this is trying to run a
program that isn't in main memory. How do you get memory into RAM when the
computer starts without memory in RAM? That's where ROM (read-only memory)
comes in.
You could put the program in ROM;
however, this is highly impractical. This would require us to change the
hardware every time we want a new change, and we can't keep calling up Phoenix
Technologies every time we want a new change.
BIOS - Basic Input/Output System
This system is hardwired into ROM.
The IP (instruction pointer) initially point to BIOS.
BIOS Procedures
1.
Tests itself
to make sure it works
2.
Looks for
devices
·
starts
sending out requests over the bus, "0x1f7, are you ready?"
·
gets a
response
3.
when it finds
a drive that looks like a disk drive, it looks for a special pattern in the
first sector indicating that the device is bootable
·
Need to make
sure this is the right one
·
Special
pattern (specially hard-coded into the ROM)
How to tell if a device is bootable?
Master Boot Record (MBR)
·
Tells you how
to boot the system
·
You look at
the last two bytes, 0x55. If this is right, then we're good to boot
·
OS-agnostic
Word Count
MBR
|
v
=========================================
|boot | /usr | swap | /home |
=========================================
Partition Descriptor
16 bytes that include:
status
(e.g. bootable?)
start
sector 4
bytes
size
(in sectors) 4
bytes
type
(DOS, Linux, etc.)
Last thing that BIO does is jump to
the boot program, which lets the boot program gain control of your system. The
boot program will read the actual program and run it.
BUT
·
Even this doesn't
give us enough flexibility
·
Could be that
some partitions are Windows, and some are Linux (completely different ways of
starting up programs)
·
What can we
do instead?
Volume Boot Record (VBR)
This can be OS-specific. VBR is
essentially two programs: boot program and our program (word count).
Part 2: Translating Everything
into C
mbr.c --> cc --> mbro --> Id --> mbr
executable
The executable should be exactly 512
bytes long (can check with old if=mbr of=/dev/dsk/0 count=1 size=512)
We assume the size of the word count
program is about 10 KiB, which means we will have to
read 20 sectors. We will also assume that the program resides at 0x8000.
for(int i = 0; i < 20; i++)
//subroutine:
what sector we're reading from and where we're reading to
//read_sector(from, sector_number,
to memory location)
read_sector(1+i, , 0x10000 + i*512)
<-- can't put it in 0x8000, need to put it somewhere else
goto 0x10000; //need to put * in front of address if in gcc
Problem?
·
we don't have
the read_sector function
//two parameters: sector number and address
//As long as we do the
right thing so it doesn't really matter if int a or
char *a
static void read_sector(int s, char *a) {
wait_for_ready();
outb(0x1F2, 1); //only want to read one sector
//and then
read 4 sectors
for(int i
= 0x1f6; i >= 0x1f3; i--)
{
outb(i, s & 0xff); <-- & 0xff is actually optional, computer is
smart enough to ignore the top bytes
s
>>= 8;
}
outb(0x1f, 0x20);
wait_for_ready();
//data
is sitting in disk controller cache, pull it out and put into RAM
//insert
long (long=32bits)
//want
128 words out of memory and into a[0] ... a[128]
insl(0x1f0, (int*)a, 128);
//we make this a function because we do this twice: once
for step 1 and another time for step 4
//param: none
static void wait_for_ready(void)
{
//read the
bytes from controller 0x1F7
//inb: give it address, tells the bytes in that address
//only care
about the top 2 bytes because that tells us if it's ready or not
while(inb(0x1F7) & 0xc0) != 0x40) {
continue;
}
//needs to get data out of the device and into the return
register
static unsigned char inb(int a) {
//use actual
assembly language
asm(" inb ... ");
}
Random tidbit that you can look up
on your own for fun: PIO (programmed I/O)
Now
we can do word count!
We assume we've read the word count
program:
void main(void) {
unsigned
long nw = 0; //number of words)
bool inw = false; //true if in a
word, false if not
int s = 21; //start at s=21 because wc = 20
for(;i) {
//local
buffer we're reading into
char
buf[512];
read_sector(s, buf);
}
for(int i = 0; i
< 512; i++) {
if(buf[i] == '\0') { //assume end
of file = '\0'
finish(nw);
return;
//OR
for(;i) and put it into an infinite loop
}
//if
the current character is a letter and the previous character was not, then you
add 1
//if
it's lowercase, it will stay lowercase; if it is uppercase, it will go
lowercase
bool mw1 = 'A' || 'a' <= buf[i] && buf[i] >= 'z' || 'Z';
OR
//lowers it if it's uppercase should
be between 0 and 26
bool inw1 = (buf[i] | 'a' - 'A') - 'a' < 26u;
nw += inw1 & ~inw;
mw
= inw1;
}
}
memory_mapped_output;
This assumes your screen is 24 x 80.
[
80] [80 ] ... [ 80] (24 of these)
16 bits: half tells what character
it is, half is what color to use
memory mapped to: 0xb8014
You start in the middle of the array
and write it backwards. This is because it's easier to compute lower digit and
harder to compute the higher number.
void finish(n) {
short
*p = (short*) 0xb8014;
do
{
*p --= '0' + n%10;
n
/= 10;
}
while (n);
}
End product: stores a neutral black
and white number on the screen.