CS 140 Final Exam Sample Solutions Summer 2008 Bob Lantz
Transcription
CS 140 Final Exam Sample Solutions Summer 2008 Bob Lantz
CS 140 Final Exam Sample Solutions Summer 2008 Bob Lantz 1. Synchronization (20 points) This is actually a kind of interesting problem, and many implementations are possible ensuring correctness, deadlock-free operation, and fairness can be hard! For this question, you just had to ensure that the various requirements were met. Essentially write_lock() must lock out writers and readers, and read_lock() must lock out writers, yet guarantee that writers are not starved. However, it’s OK for your answer to give priority to writers (and possibly starve readers.) Here’s a straightforward implementation using yield() : typedef struct { sema mutex; int readers, writers; } mlock; void mlock_init(mlock *m) { sema_init(&m->mutex, 1); m->readers = 0; m->writers = 0; } void read_lock(mlock *m) { do { P(&m->mutex); if (m->writers == 0) break; V(&m->mutex); yield(); } while (1); m->readers++; V(&m->mutex); } void read_unlock(mlock *m) { P(&m->mutex); m->readers--; V(&m->mutex); } void write_lock(mlock *m) { P(&m->mutex); m->writers++; while (m->readers != 0) { V(&m->mutex); yield(); P(&m->mutex); } } void write_unlock(mlock *m) { m->writers--; V(&m->mutex); } A version without using yield() might look something like this: typedef struct { sema read, write, count; int readers; } mlock; void mlock_init(mlock *m) { sema_init(&m->read, 1); sema_init(&m->write, 1); sema_init(&m->count, 1); m->readers = 0; } void write_lock(mlock *m) { P(&m->read); P(&m->write); } void write_unlock(mlock *m) { V(&m->read); V(&m->write); } void read_lock(mlock *m) { P(&m->read); P(&m->count); if (m->readers == 0) P(&m->write); m->readers++; V(&m->read); V(&m->count); } void read_unlock(mlock *m) { P(&m->count); m->readers--; if (m->readers == 0) V(&m->write); V(&m->count); } 2. Virtual Memory (25 points) a) Page Directory - this can be one of two things: first, it is what intel calls the top level of the page table hierarchy (so its function is reducing page table size by using a level of indirection) ; second, it is what Pintos calls the per-process page table; in this sense, it abstracts the x86 page table and manages virtual-physical address mappings. SPT - per-process structure storing additional information that is not in the page table/ directory, such as: whether a page is swapped out, zero filled, code page, mapped page, shared page, etc.; used to determine how to fill a page on a page fault, and also possibly for page sharing and for deallocating pages when a process exits or unmaps a file. Not actually necessary if you store this information in the page dir. FT - global structure to track of all physical pages (i.e. frames) in the system, who is using them, etc.; used to allocate and evict physical pages. Swap Table - in Pintos, this may just be a bitmap; keeps track of allocated swap pages. b) VMM needs to keep track of PPN -> MPN mappings for each VM. Doesn’t require another level of indirection, because the TLB is actually a cache of the shadow page table, which maps VPNs directly to MPNs. c) Since the rates are per access, we can calculate AMAT as a weighted average as follows: Cache: ! TLB: ! ! ! ! ! ! .9 * 1 ns + .1 * 10 ns = 1.9 ns 500 cycles / (2e9 cycles/second ) = 250 ns .01 * 250 ns = 2.5 ns (note there’s no TLB hit penalty) Page Faults: !.020 seconds * (1e9 ns/second) = 2e7 ns ! ! .001 * 2e7 ns = 2e4 ns = 20,000 ns AMAT = 20,004.4 ns or 20.0044 µs Even if we only make one memory access per instruction, we’ll be limited to something like 50 KIPS, because of the excessive page faults. The interesting thing to note is that even though our rates for TLB misses and page faults are much lower than our cache miss rate, they have a much larger contribution to the memory latency of this (somewhat broken) computer system! But page faults are dominating everything. Clearly the best way to improve the performance in this system is to reduce the time spent servicing page faults, most likely by getting more memory! Alternately you could get a faster disk, defragment the swap file, shrink the workload, increase the page cache relative to buffer cache size, or improve the page replacement algorithm. d) For each of these (mostly lousy) replacement algorithms, we can easily achieve 1 page fault per reference. You could either list the address references, e.g. 0x1000 0x2000 0x3000 0x4000 … or the page references, e.g. 1 2 3 4 5 2. LRU: something like ! 3. MRU: something like! 4. MFU: something like! 5. FIFO: 123456123456… 6. 123456565656565…. 123456123456! 123456161616! 1 2 3 4 5 6 1 6 1 6 1 6!! achieves 1 PF/ref achieves 1 PF/ref also achieves 1 PF/ref e) 1. Simple: just requires add (map) and compare (check); fast: add and compare can be done in parallel 2. The biggest problem with extent allocation is external fragmentation (though internal fragmentation can also be an issue, since you can only release regions on the edge of an extent!) 3. They might have tried periodic compaction and/or swapping. 3. File Systems (20 Points) b) FFS splits files greater than 1M across cylinder groups, which are spaced across the disk. It does this to leave space for file growth or additional, smaller files within the same cylinder group (e.g. to preserve locality by co-locating these files’ inodes, data blocks, and directory entries, thereby improving prefetching and reducing seeks during file access and directory enumeration.) b) Stanford students (and everyone else) tend to have large media files. These files are both (a) larger than 1 MB (typically at least 3-4MB for an MP3) and (b) files that require reasonably fast sequential access for smooth playback. While MP3s can easily be cached in memory (cf. iPods) movies have much more data. Typical media players (e.g. WMP or QuickTime player) don’t handle this well, as you can observe whenever your file system is full or fragmented on Windows or OS X. If they buffered more data for smooth playback, this could reduce the memory available to other programs on the system, increasing paging, reducing caching, etc.. c) Sequential or random write performance is great because the log can be written sequentially. Read performance is a bit dicey if the file was written randomly, but sequential read performance is great. FFS, as we’ve noted above, splits files, so even sequential writes and reads of large files require seeks. Basically LFS’s claim to fame is being able to max out write performance of hard drives. However, for most people read performance is more important than write performance, which is perhaps why LFS itself hasn’t caught on, although write-ahead logging has. d) Deleting files and/or modifying files leads to fragmentation, both of the log (i.e. gaps in the log) as well as the files themselves; when the log can no longer be written sequentially, the performance benefits of sequential logging (and writes) are lost. e) What might LFS do to mitigate this problem? LFS has (or had) a defragmenter/ compressor program which runs continuously to coalesce fragmented files and guarantee that there is always sequential free space on disk for the log. (Also the log/ filesystem was divided into multiple contiguous segments which could be defragmented/coalesced independently, and one segment could be copied/ coalesced into another, empty segment). 4. Networking (20 points) b) This is fairly straightforward, and is very similar to what was on the lecture slide!! B A 1 R1 3 2 1 2 R2 3 1 R3 2 C D R1 (B,3) (C,3) (D,2) (A,1) R2 (B,1) (C,3) (A,2) (D,2) R3 (C,2) (B,1) (A,1) (D,1) c) Very similar; note inbound paths are just the reverse of the outbound paths, so a real network using this design could just store the single routes and look them up backwards as well as forwards! However, for this question I wanted you to explicitly write both routes. (Note this is a bit different than networks like ATM where the virtual circuit ID is a link-local identifier. This is sort of a hybrid to show that the two kinds of networks aren’t necessarily as far apart as you might imagine.) B A 1 R1 3 2 1 2 R2 3 1 R3 2 C D R1 (1,1,3) (3,1,1) (1,2,3) (3,2,1) (1,3,2) (2,3,1) R2 (2,1,1) (1,1,2) (2,3,3) (3,3,2) R3 (1,2,2) (2,2,1) d) No, this doesn’t invalidate the end-to-end argument, because a) it’s really a workaround for TCP (or its implementations) backing off too much in the face of loss, and b) it actually demonstrates the end-to-end argument because non-TCP protocols (e.g. UDP) still end up getting retransmitted, which may be exactly the wrong things for protocols like VoIP or streaming media, which prefer fast, lossy transmission to delayed, retransmitted transmission. (Better to have a brief click or duplicated frame than steadily increasing delay, for example.) d) I sort of gave this one away, but I wanted you to consider more possibilities!! There are many things that can go wrong with Stanford’s (and the RIAA’s) assumptions, for example: User ID/auth: could be someone else logged in as you (or your roommate using your machine); also, what about multiuser systems (e.g. cluster machines! - Stanford just ignores these complaints because you can’t tell which user on a multiuser system made the connection; even on a cluster machine, an old program started by someone else might still be running when you are logged in, etc.) OS: maybe someone hacked into your system! I’ve definitely seen malware run filesharing programs. IP: Maybe else was using the same IP address! This happens frequently. With roaming, IP addresses are dynamic. Some OS’s cache DHCP leases and don’t properly reconfigure; erroneous static configurations are also possible. Network security: Maybe someone is spoofing your IP address on purpose! Or just using a random one because they want to get on the network. Or because they want to hide their tracks. (I’ve seen this for file-sharers.) MAC address: MAC addresses are often reconfigurable/rewritable. This is a useful feature (e.g. upgrade your network card, keep same MAC), but it can also be used to subvert MAC address-based authentication or user/machine tracking. I’ve seen multiple bogus MAC addresses, as well as very unlucky people who’ve had their MAC addresses spoofed and gotten kicked off Stanford’s network for file sharing and/or malware. Naming/Content: There’s no guarantee that a file (e.g. crazy.mp3) is what you think it is (e.g. the song by Gnarls Barkley). It might not even be an mp3. In one instance, a professor’s recorded lectures resulted in a DMCA complaint. In a similar instance, hackers tried to download the pictures of people named “bill” expecting that they would contain billing information. 5. Security (20 points) a) This was a problem because it enabled any local user to run any program as root, or to get a root shell, etc.. This could be cited as an example of several sorts of problems, including: - Local privilege escalation: while it was not directly remotely exploitable (you had to be logged on to the machine, and possibly logged on to the console as well), it was an easy way of getting root access on a cluster machine, e.g. in Tresidder or a dorm cluster. Local privilege escalations are problematic because they can often be used by remote attacks: for example, a remote exploit can get local non-root access and then make use of a local root exploit; or a local non-root user can be tricked into executing a program which uses the local root exploit, or a buggy program (e.g. web browser) can be compromised and can then take advantage of the local flaw. - (Unexpected) Feature interaction: consider that Mac OS X includes features from Mac OS classic (e.g. AppleScript, AppleEvents) and UNIX (setuid programs, shell scripts) - Ambient authority: unfortunately when executing the “do shell script” event, ARDAgent made use of its ambient (i.e. setuid root) authority, rather than using the authority/privileges of the requesting user - Confused Deputy: exec(“/usr/bin/evil/whatever”) has no idea who is making the request; is it a trusted user, or some random unprivileged user? Some things that could be done to mitigate the problem include: 1. Disable AppleEvents 2. Require authentication for AppleEvents 3. Do not allow unprivileged programs/users to send events to privileged/setuid programs 3. Disable the “do shell script” AppleEvent - for ARDAgent, for setuid programs, or for the entire system 4. Implement MAC and/or capabilities 5. Drop privilege before executing shell script or Apple Event (make sure this doesn’t open up other security holes! I believe Apple what actually ended up doing (you can check this on-line) was disabling loading of scripting extensions (and certain other extensions?) in setuid programs. b) (Perhaps you should have started worrying when you noticed SecureWareSoftCorp was not actually a corporation [as the name seems to imply], but was actually a Limited Liability Company!) It’s not entirely clear that 65,536-bit encryption based on a very small password is going to be any more secure than 128-bit encryption. Is everyone going to enter in some 65,536 bit secret key from a USB device or something? Or perhaps multi-factor authentication involving a USB key and a password? That might be a bit more secure, and harder to crack passwords. But even if it is, making passwords harder to crack is not really solving the major security problems with operating systems. For example, all of the above problems (local root exploits, ambient authority, confused deputy) and various security flaws we discussed in class (TOCTTOU, etc.) have nothing to do with cracking passwords. Most Windows/Linux/OS X flaws are not related to weak password (or other) encryption. If SecurePintos encrypts everything using your secret key, the key must still be kept secret (e.g. if it’s on a USB key, or in memory, or written down, or on disk, you may still be vulnerable.) c) Changing the name of the root user to “administrator” will not really make the system more secure. However, having finer-grained permissions might, so you can perhaps argue some marginal security benefit. Getting rid of setuid() will actually make it harder for administrator programs to drop privileges - this could make things more secure (e.g. admin programs won’t leak information to lower privilege levels), but it also might mean that they would run longer with higher privileges and be exploitable for longer periods. Moreover, now it’s not clear how programs like login will work. Not inheriting permissions across fork() or exec() is also good, but this might also break useful programs… like the shell. Root/administrator shells are security holes, but they’re also incredibly useful - they’re the way you get things done. This would likely result in a more secure, but very difficult to use system, as most privileges which can be assigned dynamically on a Unix system (via setuid() + fork()/exec ()!) Possibly privileges would have to be assigned statically at startup (e.g. the kernel could start up user shells based on a configuration file), or perhaps login would be part of the kernel. Making it harder to do things at user level might increase kernel complexity and decrease reliability or security. d) It might be true if the VMM runs on top of SecurePintos and interposes on guest OS activities. Moreover, the VMM might introspect into or monitor the encapsulated OS for compromise or malicious behavior. It might not be any improvement at all if SecurePintos and the other guest OS run at the same level in the system. In this case, security bugs in (Windows, Linux, etc.) would still have the same severity. 6. Virtual machines (25 points) a) Here are some straightforward implementations (note there is a spurious word “registers” in the declaration of R which should not be there, so you could call the register array either R or registers): /* Add source registers and store result in destination register */ void add(int dest_reg, int source_reg, int source_reg1) { R[dest_reg] = R[source_reg] + R[source_reg1]; } /* Load a word at (source_reg + offset) into dest_reg */ void load(int dest_reg, int source_reg, short offset) { uint32 paddr = translate_virtual(R[source_reg] + offset); R[dest_reg] = *((int*)&MEM[paddr]); } /* Call: push the PC onto the stack, and jump to the destination */ void call(int routine_addr) { SP -= 4; *((int*)&MEM[translate_virtual(SP)]) = PC; PC = addr; } /* Return: return to the function that called us; note this routine has to be renamed to compile in C */ void Return(void) { PC = *((int*)&MEM[translate_virtual(SP)]); SP += 4; } b) Binary translation/just-in-time compilation could batch instructions together, avoiding the overhead of call/return for each instruction; also register values can be cached and reused, and the BT/JIT can possibly dynamically optimize the (source and/or translated) code. c) A VMM can achieve dramatically improved performance by running these instructions directly on the CPU. Much faster fetch/decode/execute/commit. These are all user-level instructions, so they will run at full speed on a VMM. d) Traps! Traps require at least two hardware traps (i.e. context switches) and also require software emulation. System calls are a prime example. Privileged instructions also require hardware traps/context switches as well as software emulation. e) Traps and privileged instructions can actually run faster on an interpreter or binary translator, because no hardware traps/mode switches/context switches are required. They basically call the emulation functions directly. There’s a paper from vmware comparing binary translation to hardware virtualization that describes this in detail. 7. The Big Picture (10 points) a) No, OS is not only a subset of HCI, because operating systems increase functionality as well as usability of computer systems. You can really do what you couldn’t (practically) do before. Perhaps your HCI friend would argue that everything’s a Turing machine, or that you could program the hardware directly, but in general HCI focuses on regular users rather than programmers. OSes do make computer systems easier to program, however. There are many ways which OSes make machines more usable. Consider: responsiveness: more responsive systems are more usable reliability: a system is not usable if it crashes all the time, or if it loses your data security: a system is not usable if you can’t trust it not to disclose your data; a system is not usable if hackers can take control of it and slow it down for you while they use it for nefarious purposes. Adding more features to the OS can possibly make a system easier to perform certain tasks, or you could possibly use it to do something which you couldn’t do before. The former is usability, while the latter is more functionality. b) You can argue either way. Certainly there are many ways in which computers have benefited humanity; improving communication is perhaps the main contribution: certainly e-mail, the web, word processing, desktop publishing - these are revolutionary technologies which have enabled people to communicate at a distance, to share information, to write books, to create newspapers, etc.. Games and computer entertainment seem frivolous but can actually make people happier, and can improve hand-eye coordination for surgeons, for example. Robots can complete dangerous, boring, difficult, or exhausting tasks, freeing humans for other activities. Computers can reduce the potential for human error (e.g. computational/ transcription errors, the reason why Babbage invented his Difference Engine!) Computational capabilities augment human intelligence: for example, computational biology can help to cure disease, etc.. But what specifically about Operating System design can help humanity at large? I believe physicist Peter Feibelman said that research (and, by extension, engineering) can be interesting if it solves a particular scientific or technical problem or discovers some previously unknown principle, but it becomes compelling if it can be shown to bear on some important human need in the real world. There are many examples of compelling features of well-designed operating systems, for example: 1. Responsiveness/performance! By reducing the time which people wait for their computers, OS designers can save hundreds (thousands?) of human-years of wasted time. Also, unresponsive/slow computers slow people down in general and decrease efficiency (not to mention increasing unhappiness!) 2. Power efficiency! By using components effectively, powering them down when they’re not in use, and synchronizing power usage, an OS can reduce the overall power usage of a system. This could help reduce rolling blackouts in California, and could also reduce overall emission of greenhouse gases, possibly reducing global warming. 3. Real-time systems. Computers can make decisions faster than humans, but they have to be correct decisions (the application’s job) and reliable in terms of time. A reliable real-time OS can guarantee that a program will be given adequate CPU time (assuming the application requests the correct amount) to complete its task (e.g. putting the brakes on in your car, or activating a critical piece of medical equipment) and meet a critical deadline. Other possibilities: a well-designed OS can make it easier to write all sorts of programs (e.g. communication programs.) A more secure/reliable OS can also make it less likely that your personal data will be stolen, lost, damaged, etc..