这个作业是来自英国的关于调式并查看内存中程序的操作系统代写
COMP0019 Alternative assessment
TURN OVER
WeensyOS process. Suppose the C function prototype for this new WeensyOS system
call is as follows:
int debug_get_long(int processid, long *targetaddr, long *result);
where:
•
•
•
processid is the WeensyOS process ID of the target process within whose virtual
address space an 8-byte value should be retrieved;
targetaddr is the virtual address within the target process’s virtual address space
from which an 8-byte value should be retrieved;
result is a pointer to a long in the calling process’s virtual address space, where
the system call will store the requested value retrieved from the virtual address space
of the target process;
•
the int return value indicates whether the system call has completed successfully
or with an error.
Design debug get long() for the WeensyOS kernel. Give your design in C code,
and comment your code thoroughly, explaining each significant step taken in the code.
Assume that there is already a symbolic constant SYS DEBUG GET LONG that can be
used in the relevant switch statement in the WeensyOS kernel source to dispatch to
your code when an application invokes the debug get long() system call. Provide
the code for the system call starting with the relevant case statement. (You may find it
useful to recall how you added support for fork() to the WeensyOS kernel in CW4.)
Hints: You will need to refamiliarize yourself with how process and kernel page ta-
bles work in WeensyOS to solve this problem. For full marks, you will need to include
error-handling code that returns an error and avoids crashing the kernel if the call to
debug get long() provides an invalidtargetaddr and processid, or an in-
valid combination of the two.
We will mark your answer by evaluating the correctness and completeness of your design
as expressed in code. This is a design exercise and not a programming task: we won’t
try to compile and run your code, or test it. We will not deduct marks for minor syntax
errors. Don’t try to compile and run your code as you work on your solution; we are only
asking you to write out the code to make your design concrete, as a way of demonstrating
your understanding of virtual memory and how WeensyOS supports it.
[Question 1: Total 20 marks]
COMP0019
2
CONTINUED
Recall the notion of a file pointer in the Linux/UNIX file input/output (I/O) system calls,
which tracks the current byte offset within a currently open Linux/UNIX file—where in
the bytes of the file to read or write next, when the next read() or write() system
call is issued. As discussed in 0019, when a process has a file descriptor for a currently
open file, modern Linux and UNIX OSes track the file pointer for that file using state held
in the kernel’s memory.
Note: throughout this question, we are referring to the UNIX file I/O system calls, such
as read() and write(), and not the C library’s buffered standard I/O file I/O calls,
such as fread() and fwrite().
By contrast, in an early version of UNIX, the various UNIX file I/O system calls main-
tained the file pointer for a currently open file in user-level memory within the process’s
virtual address space. When the file pointer advanced (after a read() or write() sys-
tem call on the file, using the exact same API for these calls as in modern UNIX), the
kernel would update the file’s file pointer value in the user-level memory of the process
that called read() or write().
Note that in all versions of UNIX (the early version discussed above and modern ones),
output to a terminal always appends to the end of the terminal, regardless of the file
pointer value for the open file for that terminal. Terminals were originally teletypes, one-
character-at-time physical printers onto paper, and modern terminals are windows in a
windowing system; in both cases, each successive write() always adds to the end of
the terminal’s output. When a shell is interactive (when it accepts commands from the
user interactively), its output goes to a terminal, and thus interactive shells always append
their output to the end of the terminal, regardless of the file pointer value for the open file
for that terminal.
Suppose you run a fully implemented sh0019 shell (that fully solves 0019 CW5) ac-
cording to the following scenario:
•
You start an interactive command-line sh0019 shell. At its command prompt, you
run a second instance of your sh0019 shell and have it run the commands in the
above shell script by typing the command:
sh0019 -q script > outfile
a. Suppose you run sh0019 in the above scenario on the aforementioned early version
of UNIX. Assume that sh0019 compiles and runs on this early version of UNIX,
but experiences the early UNIX version’s different file pointer implementation de-
scribed above.
COMP0019
3
TURN OVER
On this early version of UNIX that maintains the file pointer in memory within a
process’s address space, what output results (and in what file)? Justify your answer
by describing the sequence of process creation, program execution, and file I/O
system calls that occurs, and how the file pointer influences the output.
[10 marks]
b. What output does the same command give (and in what file) when run on a modern
version of Linux, which maintains file pointers as described in the 0019 lectures and
in CS:APP/3e? Justify your answer by explaining what is different about modern
Linux’s implementation of UNIX file I/O operations, and how this implementation
difference gives rise to the output.
[10 marks]
[Question 2: Total 20 marks]
COMP0019
4
CONTINUED
3
. C Compilation and x86-64 Assembly
Consider the below disassembly of the compiled x86-64 object code for the C function
mystery(), as output by objdump -S:
Disassembly of section .text:
0000000000000000 <mystery>:
0
1
4
6
8
a:
c:
f:
1:
4:
7:
a:
c:
d:
:
:
:
:
:
55
pushq %rbp
48 89 e5
85 d2
7e 14
89 d0
31 c9
8b 14 8e
01 d2
01 14 8f
48 ff c1
48 39 c8
75 f0
5d
movq
%rsp,%rbp
testl %edx,%edx upmpff ꢁxꢂeerr apaꢂ0
jle
1c <mystery+0x1c>
movl
xorl
movl
addl
addl
incq
cmpq
jne
%edx,%eax
%ecx,%ecx
(%rsi,%rcx,4),%edx ꢅrv ꢄefꢆꢇf(gn
%edx,%edx
%edx,(%rdi,%rcx,4)
%rcx
%rcx,%rax
c <mystery+0xc>
eo ꢀxoo00000000…i.i=0
gꢁꢁꢄ)or ꢁx
1
1
1
1
1
1
cxꢀ i
ꢂrrpgigfnfff=ꢃꢃ ꢄgx
popq
retq
%rbp
c3
a. How many arguments does the C function mystery() take, and what evidence in
the disassembly supports this conclusion?
,,fofꢈvrꢂv e,gꢄip ꢅ ꢈoꢄifeoꢄꢄef,ꢄꢁf,ꢄꢁd) ꢁd)
[2 marks]
b. Below is a skeleton for the C code that when compiled yields the above assembly
code. In your answers document, write out the skeleton and fill in the missing code
(where there are blanks in the skeleton) that matches the above assembly code (2
marks per blank):
void mystery(___n_t_[_,]_,_n_t_ꢀ_,]_,_og__g_c____________________________)
{
eft _o
____ i;
=
=c
for (i = 0; i _____; i++)
[
]f]ꢉ2
a[i] += ________________________;
}
[8 marks]
[Question 3 Total: 10 marks]
COMP0019
5
TURN OVER
4
. Concurrency with pthreads on x86-64
a. Consider the following C code, which starts four pthreads, each of which increments
each element of an array of unsigneds ten million times, and uses C11 atomics
for synchronization. Include files have been omitted in the interest of brevity.
void *threadfunc(void *arg)
{
_Atomic unsigned **x = (_Atomic unsigned **) arg;
for (int i = 0; i != 10000000; i++) {
for (int j = 0; j < 10; j++) {
atomic_fetch_add(x[j], 1);
}
}
}
int main()
{
pthread_t th[4];
_
_
Atomic unsigned n[10];
Atomic unsigned *pn[10];
for (int i = 0; i < 10; i++) {
n[i] = ATOMIC_VAR_INIT(0);
pn[i] = &(n[i]);
}
for (int i = 0; i < 4; i++) {
pthread_create(&th[i], NULL, threadfunc, (void *) pn);
}
for (int i = 0; i != 4; i++) {
pthread_join(th[i], NULL);
}
for (int i = 0; i < 10; i++) {
printf(“%u\n”, n[i]);
}
}
Rewrite the above code to instead use spin locks for synchronization. You may
not place a spin lock outside the outer for() loop in threadfunc(), as doing
so reduces concurrency among threads too much. Subject to that prohibition, you
must place spin lock(s) so as to ensure correct synchronization, and to minimize the
overhead of locking.
Omit include files. Use the spin lock API discussed in 0019 lecture, summarized
below for your reference. You need not provide code for the implementation of
spin_lock() or spin_unlock().
/*
allocate and initialize spin lock */
COMP0019
6
CONTINUED
atomic_flag spinlock = ATOMIC_FLAG_INIT;
int spin_lock(atomic_flag *lock); // acquire spin lock
int spin_unlock(atomic_flag *lock); // release spin lock
[6 marks]
b. How does the C compiler enforce atomicity on the x86-64 CPU instructions that
implement C11 atomic operations?
[2 marks]
c. Which version of the code discussed in this exam question requires more C11 atomic
operations for correct synchronization: the one in the exam question paper, which
directly invokes C11 atomics, or your answer that uses spin locks? Justify your
answer by referring to the relevant aspects of these two versions of the code.
[2 marks]
[Question 4 Total: 10 marks]
COMP0019