Question 3 - write_ints()
It's too slow because of system call overhead, and system calls are slow. It is writing 4 bytes at a time, so, for example, when write_ints should be writing 1000 bytes overall, it is doing so with 250 system calls rather than one.
bit_set is a simple program with bit arithmetic. In typical C fashion, we don't test
to see if index is a bad value. We just do what we're told.
int bit_set(const unsigned char *to_include, int index)
{
int word, bit;
word = index/8;
bit = index%8;
if (to_include[word] & (1 << bit)) return 1;
return 0;
}
|
write_ints needs to have a buffer. Since our boss is allowing a global variable
with 1024 bytes, that's what we'll use. We need to fill the buffer, and when it gets
full, we write it. At the end of the procedure, if it's not empty, we have to write
the remainder.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int BUF[256];
void write_ints(int fd, int size, const int *ints, const unsigned char *to_include)
{
int i;
int elts;
elts = 0;
for (i = 0; i < size; i++) {
if (bit_set(to_include, i)) {
BUF[elts] = ints[i];
elts++;
if (elts == 256) { /* I know I should use #defines and sizeof(int), but it's a test. */
write(fd, BUF, 1024);
elts = 0;
}
}
}
if (elts != 0) write(fd, BUF, elts * sizeof(int));
}
|
Grading
- Why is it slow? 6 points. You had to say both system call overhead and the fact that
you're making system calls to small pieces of data.
- bit_set(): 9 points. Here are some answers that I received, and their point values:
1 -- for (i = 0; i < index; i++) if (to_include[i]) return 1; return 0;
1 -- return ((*to_include) & 1);
1 -- return (index != 0)
2 -- return (fread(to_include, index) == 0);
2 -- return (strcmp(to_include[index], "0"))
2 -- return (to_include[index] == '1')
4 -- return (to_include[index] & 0x1)
4 -- return (to_include[index] & 0xff)
4 -- return (to_include[index])
6 -- return ((to_include >> index) & 0x1);
6 -- return (to_include & (1 << index))
|
- write_ints(): 10 points.
Question 4
Obviously, these can come in any order:
- Line 13 - we don't test the return value of opendir. Since readdir() is a
library call, it will segfault if opendir() fails and returns NULL. Fix this by
testing to see if d is NULL.
- Line 16 or 17 - we don't check to see if de->d_name is "." or "..". That means
we are going to go into infinte recursion at some point. Fix it by checking and not
executing lines 16 through 19 if de->d_name is "." or "..".
- Line 16: If we're in a recursing call, the remove() command is not including the
directory with the filename. The result is that the "a.out" file in that directory will
not be deleted. Fix it by building the whole pathname using the directory
(see the prsize lecture notes for an example).
- Line 18: Same problem in the recursive call as the previous problem. Fix in the same way.
- Line 18: If we do too much recursion, we will run out of open files, because the recursion
is inside the opendir()/closedir() calls. Solve this by storing directories into
a list, and then after calling closedir(), go through the list and do the recursion.
Grading
3 points per part. I gave partial credit to some other answers.
Question 5
This question comes from the lecture entitled "Files, Links and Inodes": http://web.eecs.utk.edu/~jplank/plank/classes/cs360/360/notes/Links/lecture.html. In the beginning of this lecture it says:
When you create a file in Unix, there are quite a few things that happen. In this lecture, we are going to focus on three components of a file in Unix:
- The bytes of the file itself.
- The metadata of the file.
- The links to the file that are relative to a directory.
I then show what happens when you do:
UNIX> echo "This is f1.txt" > f1.txt
|
Here, we're doing the same thing, only we're doing:
UNIX> echo "My name is Harvey" > f1.txt
This changes the following three things on disk:
- It has put the bytes "My name is Harvey\n" into the contents of the file.
- It has created the file f1.txt, and as such, puts a mapping of the name f1.txt to its inode into the "d1" directory.
- It has created the metadata for the file and stored it onto an inode on disk.
Grading
I meant for this question to be a free 9 points for everyone. This is the main takeaway
from the "Links" lecture.
As it turns out, since I hadn't asked a question like this on an exam, a lot of students
didn't "study" it.
Three points per part.
Question 6
This came from a bank. I'm answering with the command line:
UNIX> ./a.out staph rust bo
|
Line 2: The memcpy copies the bytes "staph" with no null character. The answer is 01234staph012345.
Line 3: The memcpy copies the bytes "rust" with the null character. Since it uses ip+1,
it starts at the fourth byte. The answer is 0123rust.
Line 4: This is printing the string "8901" as an integer, so little endian: 0x31303938.
Line 5: i is '5', which is 0x35 and j is '2', which is 0x32. So: 0x3532.
Line 6: i is '7'. If you view it as an array of four characters, then it is "7***", where the asterisks are
the null character. The strcat() will simply put "bo" at the end, and there's still a null
character: "7bo*" - the answer is 7bo.
Grading
3 points per question. I did give partial credit.
Question 7
- Line 1: The smallest byte at 0x5b89fcac: 0xc8.
- Line 2: p[10] will be the four bytes starting at 0x5b89fcd4: 0xd5.
- Line 3: q+3 is 0x5b89fcb8, so q[3] is 0x5b89fcb4. So q[3]+12 is 0x5b89fce4,
and the first byte of q[3][12] is: 0xef.
- Line 4: q[0] is 0x5b89fcc8. So q[0]+3 is 0x5b89fcd4,
and the first byte of q[0][3] is: 0xd5.
- Line 5: 0x5b.
- Line 6: 0xbc.
- Line 7: t+2 is 0x5b89fcb4, so t[2] is 0x5b89fcb0. So t[2]+8 is 0x5b89fcb8,
and the byte there is: 0xb4.
- Line 8: t+1 is 0x5b89fcb0, so t[1] is 0x5b89fcbc. So t[1]+16 is 0x5b89fccc,
and the byte there is: 0xa3.
2 points per question. I did give partial credit.