CS360 Midterm Exam #1. March 28, 2019. James S. Plank
Answers and Grading
Each question was worth 25 points.
Question 1
Part A: You are making 2n system calls in this program. In other words,
for each value of i, you are calling write() twice. Suppose n
is 1,000,000, and each system call is 10 microseconds. That's 20 seconds!
Part B: The approach is to have a buffer to reduce the number of
system calls. Suppose its size is b, and
that b is a multiple of 12. Then, you can write b/3 ints and doubles
with exactly one write() call. What you'll do is fill in the buffer
with ids[i] and vals[i] alternatively. When the buffer is full, you write() it.
Part C:
Here's some code that does the trick. You need to use memcpy() rather than, for
example, pointers to ints and doubles, because the doubles will be unaligned in
the buffer.
int write_double_array(int fd, int *ids, double *vals, int n)
{
int index, i;
char buffer[12000];
index = 0;
for (i = 0; i < n; i++) {
memcpy(buffer+index*12, ids+i, sizeof(int));
memcpy(buffer+index*12+4, vals+i, sizeof(double));
index++;
if (index % 1000 == 0) {
if (write(fd, buffer, 12000) != 12000) return -1;
index = 0;
}
}
if (index > 0) {
if (write(fd, buffer, index*12) != index*12) return -1;
}
return 0;
}
|
Part D:
This happens when write() fails. You can focus on each of the arguments to write():
- You give it a bad file descriptor for fd. Then, the write() call will
return -1. As a corollary, you can give it a good file descriptor, but one that was not opened
for writing.
- You give it a bad memory address for ids or for vals.
- You give it a value of n which is so large that ids+n or vals+n
becomes an illegal address.
A lot of you said things like "too many open files" or "not writable file." Although it
may not seem irrelevant to you, it is, because this would be an error in an open()
call, not in a write() call. Write() cares about what the file descriptor
is, not how it was created.
Grading
- Part A: 5 points. You have to mention system calls.
- Part B: 5 points.
- Part C: 10 points. You lost 3 points when you didn't interleave ids and vals.
- Part D: 5 points -- 2.5 points per correct answer.
Question 2
This is a garden-variety stat() program. The approach is this:
- Call stat on f so that you can record its inode.
- Traverse the directory, and for each file there, construct the correct filename,
call stat() on it, and check its inode to see if it matches f's.
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
int hleqf(char *f, char *d)
{
char *fn; /* The constructed file name for each file. */
int l; /* The length of f, so I don't have to call strlen a lot. */
int total; /* The total number of hard links. */
DIR *dir; /* The opened directory. */
struct dirent *de; /* Each directory entry. */
struct stat fbuf; /* The stat buf for f. I'll check each inode against its inode. */
struct stat buf; /* The stat buf for each file in the directory. */
/* Store the stat buf for f. */
if (stat(f, &fbuf) != 0) return -1;
/* Prepare the file name for each file in the directory by
allocating it and copying the directory there. */
l = strlen(d);
fn = (char *) malloc(sizeof(char) * (strlen(d)+257));
if (fn == NULL) return -1;
strcpy(fn, d);
/* Open the directory and set the total to zero. */
dir = opendir(d);
if (dir == NULL) return -1;
total = 0;
/* Traverse the directory, construct each file name, and then call stat() to check the inode. */
for (de = readdir(dir); de != NULL; de = readdir(dir)) {
sprintf(fn+l, "/%s", de->d_name);
if (stat(fn, &buf) != 0) return -1;
if (buf.st_ino == fbuf.st_ino) total++;
}
/* Clean up and return. */
closedir(dir);
free(fn);
return total;
}
int main(int argc, char **argv)
{
printf("%d\n", hleqf(argv[1], argv[2]));
return 0;
}
|
Grading
- Store the stat/inode information for f: 4 points.
- Return -1 if the stat() call fails: 1 point.
- Open the directory: 4 points.
- Return -1 if the opendir() call fails: 1 point.
- Traverse the directory with readdir: 3 points
- Construct the filename correctly: 4 points.
- Compare the two inodes and increment total when they equal each other: 4 points
- Clean up (call closedir(), and free() if you called malloc()): 2 points
- Return the total: 2 points
- There were various deductions
Question 3
Here are the important chunks of memory that you can derive from the text
that says, "we know the following things":
- The free list head pointer is stored at location 0x4000, and its value is 0x5100.
- The heap goes from 0x5000 to 0x5268, and its composition is as follows:
- Chunk 1: 0x100 bytes starting at 0x5000, and is allocated.
- Chunk 2: 0x80 bytes starting at 0x5100 and is free.
- Chunk 3: ???? bytes starting at 0x5180 and is allocated. Max size = 0x80..
- Chunk 4: 0x68 bytes starting at 0x5200 and is allocated.
- There can be more chunks if chunk 3 is smaller than 0x80.
That allows us to answer the questions in part A:
- A-1: There are 0x268 bytes in the heap.
- A-2: There are 0x80 bytes on the free list.
- A-3: The minimum number of allocated chunks on the heap is three, which happens when chunk 3
above has a size of exactly 0x80.
- A-4: Any malloc() call will require us to call sbrk() if it cannot
be satisfied from the free list. There is one chunk on the free list, whose size is 0x80.
So, I find the smallest value of x such athat malloc(x) needs a chunk that is
greater than 0x80 bytes. The answer is 0x79. This value, when rounded up to a multiple of 8
is 0x80. Then when you add 8 for bookkeeping, you can a size greater than 0x80. So the answer
is 0x79, or 121.
Part B: When you call free(0x5208), it will put the chunk starting at 0x5200
onto the head of the free list. That will make the following changes:
- The four bytes at 0x4000 will be set to 0x5200.
- The four bytes at 0x5204 will be set to 0x5100 (this is the new node's flink).
- The four bytes at 0x5208 will be set to 0 (this is the new node's blink).
- The four bytes at 0x5108 will be set to 0x5200 (this is the original free chunk's blink).
Grading
- A-1: 4 points
- A-2: 3 points
- A-3: 3 points
- A-4: 3 points
- The four bytes at 0x4000 will be set to 0x5200: 3 points
- The four bytes at 0x5108 will be set to 0x5200: 3 points
- The four bytes at 0x5204 will be set to 0x5100: 3 points
- The four bytes at 0x5208 will be set to 0x0: 3 points
- I gave pretty liberal partial credit.
Question 4
a:
ld [fp+16] -> %r0 / This is b
mov #4 -> %r1
mul %r0, %r1 -> %r0 / This is 4*b
ld [fp+12] -> %r1 / This is p
add %r0, %r1 -> %r0 / This is &(p[b])
ld [r0] -> %r0 / This is p[b]
ret
b:
push #4 / Allocate d
st %r2 -> [sp]-- / Spill r2, because you'll need it
mov #4 -> %r0 / Push the argument 4 on the stack
st %r0 -> [sp]--
jsr e / Call e
pop #4 / Pop the argument off the stack
mov %r0 -> %r2 / Store r0 to r2, so that f() won't mess with it
mov #6 -> %r0 / Push 5 and 6 on the stack in reverse order
st %r0 -> [sp]--
mov #5 -> %r0
st %r0 -> [sp]--
jsr f / Call f
pop #8 / Pop the two arguments off the stack
add %r0, %r2 -> %r0 / Add the two return values
st %r0 -> [fp] / Store in d
ld ++[sp] -> %r2 / Unspill
ret
g:
push #80 / Allocate h
mov #5 -> %r0 / The compiler knows exactly where h[10] is: [fp-36]
st %r0 -> [fp-36]
mov #-36 -> %r0 / It has to calculate &h[10] as (fp-36)
add %fp, %r0 -> %r0
ret
Grading
- a: Load b (ld [fp+16] -> %r0): 1 point
- a: Multiply b by 4: 1 point
- a: Load p (ld [fp+12] -> %r0): 1 point
- a: Calculate &p[b]: 1 point
- a: Calculate p[b]: 2 points
- b: Push 4 to allocate d: 0.5 points
- b: Spill r2 (st %r2 -> [sp]--): 2 points
- b: Push the value 4 onto stack: 0.5 points
- b: Jsr e: 0.5 points
- b: Pop #4: 0.5 points
- b: Move the return value to r2: 2 points
- b: Push 6 onto the stack: 0.5 points
- b: Push 5 onto the stack: 0.5 points
- b: Push them in reverse order: 2 points
- b: Jsr f: 0.5 points
- b: Pop #8: 0.5 points
- b: Add the return values of the two procedure calls: 1 point
- b: Store the sum into d: 1 point
- b: Unspill r2: 1 point
- g: Push #80 to allocate h: 1 point
- g: Store five to h[10], which is [fp-36]: 3 points
- g: Calculate fp-36 to return &(h[10]): 2 points