Questions
and Answers
System Calls
Q1: Why must kernel programmers be especially careful when
implementing system calls?
System calls with poor argument checking or implementation can result
in a malicious or buggy program crashing, or taking-over the machine.
Q2: Why is recursion or large arrays of local variables
avoided by kernel programmers?
The kernel stack is usually a limited resource. A stack overflow
crashes the entire machine.
Q3: How does the 'C' function calling convention relate to the system
call interface between the application and the kernel? At minimum,
what additional information is required beyond that passed to the
system-call wrapper function?
The 'C' function calling convention must always be preserved after any
system-call wrapper function completes. The preservation of
preserved registers (e.g., s0-s8, ra) can be done in the
wrapper function itself or by the kernel (the latter case is what's
done in OS/161).
The interface between the system-call wrapper function and the
kernel can be defined to provide additional information beyond that
passed to the wrapper function. At minimum, the wrapper function must
add the system call number to the arguments passed to the wrapper
function. It's usually added by set an agreed-to register to the value
of the system call number.
Q4: In the example given in lectures, the library procedure
read invoked the read system call. Is it essential that both have the same name? If not, which name is important?
System calls do not really have names, other than in a documentation sense.
When the library procedure read traps to the kernel, it puts the number of the
system call in a register or on the stack. This number is used to index into a
table. There is really no name used anywhere. On the other hand, the name
of the library procedure is very important, since that is what appears in the
program.
Q5: To a programmer, a system call looks like any other call
to a library procedure. Is it important that a programmer know which
library procedures result in system calls? Under what circumstances and why?
As far as program logic is concerned it does not matter whether a call
to a library procedure results in a system call. But if performance is
an issue, if a task can be accomplished without a system call the
program will run faster. Every system call involves overhead time in
switching from the user context to the kernel context. Furthermore, on
a multiuser system the operating system may schedule another process
to run when a system call completes, further slowing the progress in
real time of a calling process.
Processes and Threads
Q6: In the three-state process model, what do each
of the three states signify? What transitions are possible between each
of the states, and what causes a process (or thread) to undertake such a
transition?
The three states are: Running , the process is currently
being executed on the CPU; Ready, the process is ready to
execute, but has not yet been selected for execution by the
dispatcher; and Blocked where the process is not runnable as
it is waiting for some event prior to continuing execution.
Possible transitions are Running to Ready, Ready to
Running, Running to Blocked, and Blocked to
Ready.
Events that cause transitions:
- Running to Ready: timeslice expired, yield, or higher
priority process becomes ready.
- Ready to Running: Dispatcher chose the next thread to
run.
- Running to Blocked: A requested resource (file, disk
block, printer, mutex) is unavailable, so the process is blocked
waiting for the resource to become available.
- Blocked to Ready: a resource has become available, so all
processes blocked waiting for the resource now become ready to
continue execution.
Q7: The following segment of code is similar (but much simpler)
to the main task that the daemon inetd performs. It accepts connections
on a socket and forks a process to handle the connection.
This is not guaranteed to be compilable. Use the man command if you want to
investigate what all the system calls are doing.
0001 xxx(int socket){
0002
0003 while ((fd = accept(socket, NULL, NULL)) >= 0) {
0004 switch((pid = fork())) {
0005 case -1:
0006 syslog(LOG_WARN, "%s cannot create process: %s",
0007 progname, sys_error(errno));
0008 continue;
0009 case 0:
0010 close(0);
0011 close(1);
0012 dup(fd);
0013 dup(fd);
0014 execl("/usr/sbin/handle_connection",
0015 "handle_connection", NULL);
0016 syslog(LOG_WARN, "%s cannot exec handle_connection\
0017 helper : %s", progname, sys_error(errno));
0018 _exit(0);
0019 default:
0020 waitpid(pid, &status, 0);
0021 if (WIFEXITED(status) && WIFEXITSTATUS(status) == 0)
0022 continue;
0023 syslog(LOG_WARN, "handle_connection failed:\
0024 exit status +%d\n", status);
0025 }
0026 }
0027 }
-
Identify which lines of code are executed by the parent process.
-
Identify which lines of code are invoked by the child process.
-
Under what circumstances does the child terminate?
The parent process can execute 0001 - 0004 (fork()), 0005 - 0008 (if
the fork() fails) whereupon it continues to wait for a connection on the socket,
and 0019 - 0026 if the fork() succeeds. By calling waitpid(), the parent
suspends execution until the child terminates. It exits the
while loop when the accept() fails (see "man exec" for failure modes).
The child returns at line 004, and then executes 0009 - 0015, only executing
0016 - 0018 if the execl() fails.
The child terminates when the program /usr/sbin/handle_connection terminates
(assuming the exec was successful), but otherwise if the exec fails.
Q8: A web server is constructed such that it is
multithreaded. If the only way to read from a file is a normal
blocking read system call, do you think user-level threads or
kernel-level threads are being used for the web server? Why?
A worker thread within the web server will block when it has to read a
Web page from the disk. If user-level threads are being used, this
action will block the entire process, destroying the value of
multithreading. Thus it is essential that kernel threads are used to
permit some threads to block without affecting the others.
Q9: Compare reading a file using a single-threaded file
server and a multithreaded file server. Within the file server, it
takes 15 msec to get a request for work and do all the necessary
processing, assuming the required block is in the main memory disk
block cache. A disk operation is required for one third of the
requests, which takes an additional 75 msec during which the thread
sleeps. How many requests/sec can a server handled if it is single
threaded? If it is multithreaded?
In the single-threaded case, the cache hits take 15 msec and cache misses take
90 msec. The weighted average is 2/3 × 15 + 1/3 × 90. Thus the mean
request takes 40 msec and the server can do 25 per second. For a multithreaded
server, all the waiting for the disk is overlapped, so every request
takes 15 msec, and the server can handle 66 2/3 requests per second.
|