// typical low level code for reading sector void main(void){ long long words = 0; // initialize number of words to 0 bool in_word = false; size_t sizeOfBuff; char * buf[sizeOfBuff]; int s = 100000; // location of sector for(;;s++){ read_ide_sector(s, buf); for(int j = 0; j < sizeOfBuff; j++) if(!buf[j]){ write_out(words); } bool this_alpha = isAlpha(buf[j]); // check to see if the character is alpha words+=this_alpha&(~in_word); // add 1 to words if this_alpha=TRUE and // in_word = FALSE in_word = is_alpha; }
void write_out (long long n){ char *p = (char *) (0x6800 + 80*25*2/2 - 20); // assume 80 by 25 screen with 2 bites // 20 is a value we felt is close to the center do{ int lsd = n%10; // get the least significant digit of N *--p =7; //this adds color 7 which is setting by first byte since a pixel is2bytes *--p = lsd + ‘0’; // this will get the ascii value of the lsd by adding it to char ‘0’ n/= 10; }while(n); }
how do we improve performance when we boot fast but I/O or CPU is idle:
typical scenario:
Now if we want to move away from standalone programs to enable multiple programs to be able to use read_ide_sector() function, we need to change a couple things. One way to allow other programs the ability to use the function is to put the function in the PROM(Programmable Read Only Memory) part of RAM. Although this works, there are some drawbacks.
Drawbacks of this approach:
To generalize the read_ide_sector function:
// 's' is the sector number we want to read, 'p' is where we read to void read_ide_sector (int s, char* p); ↓ // modified to void* p so that we can read any type of data void read_ide_sector (int s, void* p); ↓ // there is a limit to the largest number we can represent as an integer, so // we define a new type 'off_t' which allow us to access larger number of sectors void read_ide_sector (off_t s, void* p); ↓ // we add a new parameter ‘buffSize’ to our function which will be the block // size. This allows us flexibility to use our code on different systems even if // their block sizes are different. We use the ssize_t return type to add room for // knowing about errors when reading the sector. It will be negative if we get an // error. ssize_t read_ide_sector (off_t s, void* p, size_t buffSize);
We have modified our read_ide_sector() to look very similar to the pread() Linux command. The ‘pread’ command looks like:
// 'int fd' is a file descriptor and 'off_t s' is a byte location. All other // parameters are the same as we have already discussed. ssize_t pread (int fd, off_t s, void* p, size_t buffSize);
The ‘pread’ command was further modified to the ‘read’ command as follows:
// we don’t keep track of how much we have read in the file. The OS helps keep // track of that for us. ssize_t read (int fd, void* p, size_t buffSize);
We also have the ‘lseek’ command which returns the location a new location of the read pointer to a file. The command looks like:
// We can move the file pointer from either the beginning of the file, the end // of the file or the current position any way we want by providing the ‘flag’ and // 's' parameters. The ‘s’ parameter signifies how much we move the read pointer. off_t lseek (int fd, off_t s, int flag);
The flag parameter takes the following values:
When a function is called, we need to store its local variable, return address, etc... We do it by using a stack. A stack grows from bottom to top. An example of the stack of a nested function calls is below:
Recursive factorial example:
int fact (int n) { if (!n) { return 1; } return n * fact(n-1); }
Assembly code (unoptimized version, note that the number at the end is not part of the code):
fact: pushl %ebp (1) movl $1, %eax (2) movl %esp, %ebp (3) subl $8, %esp (4) movl %ebx, -4(&ebp) (5) movl 8(%ebp), %ebx (6) testl %ebx, %ebx (7) jne .L5 (8)
The stack after each step is described below (%ebx contains n):
(7): Test if value %ebx = 0
(8): If the above condition is wrong (jne = jump not equal), then jump to L5 (recursive case)
.L5: leal -1(%ebx), %eax(compute effective address, just means eax = eax -1) movl %eax, (%esp) call fact imull %ebx, %eax jmp .L1
L1 is call if n = 0 (base case):
.L1: movl -4(%ebp), %ebx (restore ebx) movl (%ebp), %esb (restore stack pointer) popl %ebp ret
Refer to lecture27 for few more examples about C and assembly code
Things that can go wrong with the recursive approach for factorial:
This is an example of soft modularity (each module stays on their side, relies on communication for correct action)
We want hard modularity (which works even with unreliable caller /callee)