CPSC 213 Assignment 7 jump tables solution

$24.99

Original Work ?
Category: You will Instantly receive a download link for .ZIP solution file upon Payment

Description

5/5 - (4 votes)

Goal This assignment has two goals. The first is to explore the use of jump tables to implement switch statements. You will examine snippet B, read a mystery assembly file, and transform C code that uses switch statements into code that uses jump tables and gotos. The second goal is to examine asynchronous I/O programming. You are given a C file that models a simplified disk with an asynchronous read operation, simulated DMA, and interrupts. You are also given two programs that perform a sequence of disk reads in different ways. The first of these programs is completely implemented. It access the disk sequentially, waiting for each request to finish before moving on. The other programs is partly implemented, with the rest left to you. It improves on the sequential version using event-driven programming. You will gain some experience with this style of programming and then compare the runtime performance of the two alternatives. You’ll continue to with this example in Assignment 8, where you will implement a third alternative that uses threads. Groups You may do this assignment in groups of two if you like. If you do, be sure that you both contribute equally to the assignment and that you each work on every part of the assignment. Do not split up the assignment in such a way that one of you does one part and the other does another part. Keep in mind that the core benefit to you of doing the assignment is the learning that happens while you do it. Each assignment is worth only around 1.8% of your grade, but what you learn while doing the assignment goes a long way to determining the other 85%. If you choose to do the entire assignment with a partner, submit one solution via handin and list both of your names, student numbers, and computer-science login ids in the README file. Alternatively, you can also choose to collaborate with another student for a portion of the assignment and have your work marked individually. Do do this, simply submit your own version via handin and list the other student as a collaborator in your README file. Just don’t do this if you and your partner are turning in identical solutions, as we would like to realize marking efficiency where possible. You may have at most one collaborator for the assignment; i.e., you
can not collaborate with one student for one part of the assignment and another student for another part. Code Provided this Week The file www.ugrad.cs.ubc.ca/~cs213/cur/Assignments/a7/code.zip contains the following files that you will use with this assignment • SB-switch.{c,s} and SB_switch.java • A7-1.s • A7-2.c • max.s • test.s • Makefile • disk.{c,h}, queue.{c,h}, uthread.{ch}, and uthread_util.h • sRead.c and aRead.c Part 1: Switch Statements Part 1a: Snippet SB Examine the code for SB-switch. Carefully read the three different implementations in the C file. Run the assembly version in the simulator and observe what happens. Unlike previous assignments you are not required to document anything here. Doing this will help you with Parts 1b and 1c. You are not required to turn in anything for Part 1a. Part 1b: Reading Assembly Code The file A7-1.s contains uncommented assembly code that corresponds to a single C procedure starting at address 0x300 and another procedure that sets up the stack and calls the first procedure. Read this code and run it in the simulator. Comment every line and write an equivalent C program that is a likely candidate for the program the compiler used to generate this assembly file (with the exception of the stack-setup code, which is only for the .s file). Call this program A7-1.c. Note that A7-1.s uses the directive .address instead of .pos; they are equivalent. Part 1c: Implement Switch Statements with Jump Tables The file A7-2.c is a C implementation of CPU.java; i.e., it is a sm213 virtual machine. It has been setup to load and execute two assembly programs include in code.zip: max.s and test.s. It does this by loading the machine representation of the instructions since this program does not include an assembler. It is easy to get the translation from assembly to machine code from the Java simulator (its just to the left of the assembly code, line by line).
Take a look at this so you understand what is going on, but don’t worry about the details of the instructions. They are stored in A7-2.c as constants (i.e., loadMax() and loadTest()). The difference between max.s and test.s is that max does something interesting (it computes the maximum value of an array of integers) and test simply executes each instruction in the ISA once (in order of their op code). For max, the virtual machine prints the value stored in memory at address 0 after max finishes; this is the maximum value of the list that starts at address 4. Notice that it works. Modify the integer array in loadMax so that it computes a different maximum. Your task is to write an equivalent C program called A7-2-jumptable.c that replaces the two switch statements in exec() with gotos using a jump table and label pointers as shown in class. Note that label pointers are a gnu C extension and so some compilers may not support them (all versions of gcc and llvm (the Mac compiler) do support this extension). So, you may need to move to a lab computer to test this your program. Leave cases marked “NYI” empty; i.e., they are cases with an empty body so just implement them that way. To test your program it is sufficient to show either that it computes the correct value for max or that loadTest runs okay. Start with loadMax. If it works; you are done. If not, then switch to loadTest and change every case arm to include a print statement that prints the value of the case label (e.g., “case 2: printf (“2\n”); …”). This will simplify debugging greatly. Then if you show that you print the case labels in the correct order, you are done. Part 2: Asynchronous Programming Background The Simulated Disk The simulated disk is implemented in the file disk.c and its public interface is in disk.h. A disk contains a collection of disk blocks named by a block number. This disk simulates a fixed, per-access read latency of 10 ms (1 ms is 10-3 seconds), which is about the average access time of real disks. This means that it takes the disk 10 ms to process a single read request. However, the disk can process multiple requests in parallel. When it completes a request, it does what a real disk does, it uses a direct-memory transfer (DMA) to copy the disk data into main memory and it delivers an interrupt to the CPU. In this case, of course, the DMA is just a memory-tomemory copy between parts of your program’s memory. And the interrupt is delivered by calling a specified handling procedure you register when you initialize the disk. To initialize the disk using an interrupt handler called interruptServiceRoutine, you call the following procedure at the beginning of your program (all of the provided files already do this). disk_start (interruptServiceRoutine)
To request that the first nbytes of the content of the disk block numbered blockno be transferred into buf you call. disk_scheduleRead (buf, nbytes, blockno) This procedure returns immediately. Then 10ms later, the target data is copied into buf and an interrupted is delivered to you by calling interruptServiceRoutine. The disk completes reads in request order, and calls interruptServiceRoutine for each completion. Now, since the simulated disk doesn’t really store data, it does not transfer anything interesting into buf other than one thing. It write the integer blockno into the buffer so that you can test your code to be sure you have the right disk block. For example if you make the call char buf [100]; disk_scheduleRead (buf, 100, 37); And wait the 10ms for the interrupt, and then examine buf, you will see it unchanged except for the beginning, which will store the number 37. Thus the following expression evaluates to true. *((int*) buf) == 37 Note — and this is important — the interrupt operates just like a regular interrupt. It will interrupt your program at some arbitrary point to run the handler, continuing your program when the handler returns. There are some potentially difficult (and I mean horrible) issues that arise if your program and the handler access any data structures in common or if the handle calls any non-reentrant procedure (e.g., malloc or free). You are provided with the implementation of a reentrant, thread-safe queue that is safe to access from the handler and your program. It is also okay to access the target data buffer (buf) in the handler. Do not access any other data structures in the handler and do not call free from the handler (its okay to call dequeue). You will learn more about this issue in a couple of lectures, but it is not a concern for this assignment (other than to avoid the problem). The disk provides one addition operation that you can call to wait until all pending reads have completed. disk_waitForReads() The Queue The files queue.c and queue.h provide you with a thread-safe, re-entrant implementation of a queue. If you need to enqueue information in your program that is dequeued in the interrupt handler, use this queue. To create a queue named something like prq, for example, you declare the variable like this. queue_t prq; Then before you use the queue you need to initialize it, in main, for example like this. queue_init (&prq);
To add an item pointed to by the variable item (of any type) you do this: queue_enqueue (&prq, item); To get an item (e.g., of type struct Item*), you do this: struct Item* item = queue_dequeue (&prq); Makefiles The provided code contains a file called Makefile that describes how this week’s program should be built. In general, most C projects have a makefile like this. To compile the program sRead, for example, type the following at the command line: make sRead To compile every program just type this: make To remove executables, object files and other derived files type this: make clean Timing the Execution of a Program The UNIX command time can be prepended to any command to time its execution. When the command finishes you get a report of three times: the total elapsed clock time, the time spent in user-mode (i.e., your program code) and the time spent system-mode (i.e., the operating system). The format is otherwise a bit different on different platforms. For example if you type this: time ./sRead 100 You will get something like this on Mac OS when sRead completes: 1.074u 0.010s 0:01.08 100.0% 0+0k 0+0io 0pf+0w And on Linux: real 0m1.100s user 0m1.084s sys 0m0.000s Ignore the user (u) and sys (s) times; they really are approximations. Pay attention only to the real, elapsed time, which is 1.08 s in the Mac example and 1.1 s in the Linux example. You will use this command to assess and compare the runtime performance of the three alternative programs.
Part 2a: Synchronous Reads Examine the program sRead.c, compile it by typing make sRead and run it. You run it from the command line with one argument, the number of reads to perform. For example, to perform 100 reads, you type this: ./sRead 100 Temporarily modify handleRead to assert that the value at the beginning of buf is something other than blockno to confirm that this is really working. For example assert (*((int*) buf) == blockno-1) Now, restore the assert statement and temporarily add a printf statement to handleRead to print number at the beginning of every the buffer something this: printf (“buf = %d, blockno = %d\n”, *((int*) buf), blockno); Again, this is just to convince yourself that this works. Now, remove the printf. Finally, time the execution of the program for executions that read various numbers of blocks. For example time ./sRead 10 time ./sRead 100 time ./sRead 1000 Knowing how the simulated disk and this program perform you should be able to explain why you see the runtime performance you do. Do so, in README.txt; this is QUESTION 1. Part 2b: Asynchronous Reads Now open aRead.c in an editor and examine it carefully. This version of the read program each read should not wait for the disk to complete, as the synchronous version does. Instead, it should schedule each read in run and call handleRead for each of them when their interrupt is delivered. You will receive interrupts in the same order as calls to disk_scheduleRead. So, for example, if you schedule reads for blockno values of 0, 1, and 2, in that order, the first time interruptServiceRoutine runs it will be to tell you that the read for block 0 has completed. The second call will be for block 1 and so on. But note, that the interrupt is just an interrupt. It does not transmit any values; the interrupt service routine has no parameters. So, you will need to remember these somehow. Remember the queue? Compile and test your program the same way that you did for sRead. Measure the runtime performance of aRead for executions that read different numbers of disk blocks as you did with sRead and so that you can directly compare their performance. There is some experimental error and so you should run each case at least three times and take the minimum. The reason for taking the minimum is that this is the most reliable way to factor out
extraneous events that you see as noise in your numbers. Obviously, this is not a good way to determine the expected behaviour of the programs; its just a good way to compare them. Now you know more and have more data. Record the time values you observe for both aRead and sRead for a few different numbers of reads. Be sure to choose meaningful values for this parameter; e.g., at least a small one of around 10 and a large one of around 1000 (though if your system is too slow to run either of these for 1000, choose a smaller number). Explain what you observe: both what and why. The why part is important: carefully explain why one of these is faster than the other. Record your observations and data in README.txt. This is QUESTION 2. Requirements Here is a summary of the requirements for each part of this assignment. 1. Examine the execution of Snippet B. There is nothing to hand in for this step. 2. Add comments to A7-1.s and write A7-1.c. 3. Write A7-2-jumptable.c. 4. Run sRead as described in Part 2a and then measure its runtime performance. Answer QUESTION 1. 5. Complete the implementation of aRead.c. Run it as described in Part 2b and then measure its runtime performance. Answer QUESTION 2. What to Hand In Use the handin program. The assignment directory is a7. 1. A single file called “README.txt” that includes your name, student number, four- or fivedigit cs-department undergraduate id (e.g., the one that’s something like a0b1), and all written material required by the assignment as listed below. 2. If you had a partner for the entire assignment, turn in only one copy of your joint solution under one of your ids and list both student’s complete information in the README.txt file and include the text “PARTNER – MARK JOINTLY”. 3. If, on the other hand, you collaborated with another student for a portion of the assignment, but your solutions are not identical and you want them marked individually, each of you should include the other student’s complete information in your README.txt file, include the text “COLLABORATOR – MARK SEPARATELY”, and turn in copies separately via handin. 4. The files A7-1.s and A7-1.c. 5. The file A7-2-jumptable.c. 6. Your answer to QUESTION 1 in the README.txt file. 7. The file aRead.c. 8. Your answer to QUESTION 2 in the README.txt file.