1. long host2netl_asm (long param)
Convert the parameter param from host byte order
to network byte order (always Big Endian). The code for this function should be entirely written
in x86 assembly. You should not use in-line assembly (i.e.,
do not use asm("...")). You can assume that the size of long
is 4 bytes and the byte order of the host machine is Little Endian.
To investigate the assembly code generated by the compiler, you can use the tool objdump
-d <___.o> to disassemble an object file. The object
files reside in the compile/ directory within the main Xinu directory (xinu-spring2013-x86). You can also see some of
the *.S files in the system/ directory for reference.
2. void printsegaddress()
Print the address of
the end of the text, data, and bss segments of the Xinu OS.
Also print the 4 bytes (in hexadecimal) preceding the end of the
three segment boundaries, and similarly for the 4 bytes following
the segment boundaries. This function can be written
in C.
3. void printtos()
Using the create() system call, create a XINU
process that runs a simple program that you have written in C.
Call this program "myprogA()" which returns a value of type integer.
The number of arguments and their type is up to you to decide.
Your program myprogA() should contain at least one function, call it
myfuncA().
When calling create(), set the stack size limit to 1024 and
process priority to 20.
Note that programs in XINU are represented as
functions (not files containing
main() as in UNIX/Linux, Windows) that are spawned as independent processes
by calling create() with the function pointer (i.e., name of program)
from within XINU's main().
Hence although both myprogA() and myfuncA() are coded as C functions,
myprogA() becomes a process because it is activated through the create()
system call, whereas myfuncA() remains a regular function (not a
separate process). This implies that myprogA() is allocated a private
run-time stack, but myfuncA(), when invoked by myprogA(), is managed as
a regular function call by pushing a stack frame in the run-time stack
of process myprogA().
Thus although from a programming language perspective myprogA() and
myfuncA() appear similar, they are night and day when viewed from
an operating systems perspective.
Lastly, since XINU's create() system call, after creating a process,
immediately suspends it (the process exists but it is hybernating in
a frozen state), the resume() system call must be invoked
to get XINU's process scheduler to allocate
CPU cycles to the process at some point in the future.
Print the address
of the top of the run-time stack when myprogA() is created but
before myfuncA() is called by myprogA().
Print the contents of up to five stack locations at
and below the top of the stack (the five or fewer items that have been the
most recently pushed, if any).
Remember that stack elements are 32 bits wide,
and be careful to perform pointer arithmetic accurately. Note that
there are local variables, arguments, etc. on the stack as
discussed in class.
See the hints for Problem 4 below, especially
pertaining to stacktrace.c and process.h.
printtos() can be written entirely in C, or you may use in-line assembly
if you prefer.
Repeat the above after myfuncA() is called by myprogA() but before
myfuncA() returns.
4. void printprocstks()
Spawn three processes running the same program myprogA().
For each process running
myprogA(), print the stack base, stack size, stack
limit, stack pointer
and the process id. An example output might look
something like:
Proc [prnull]. Pid = 0.
Stack:
Base = 4095996
Len = 0
Limit = 4091904
StkPtr = 4095820
Proc [main]. Pid = 49.
Stack:
Base = 4091896
Len = 4096
Limit = 4087804
StkPtr = 4091872
To aid with this task, please
look into process.h in the include/ directory. The proctab[]
array holds key information about processes and the prstkptr member of
the procent structure holds the saved stack pointer.
In a multiprogramming OS where a CPU is shared among multiple
processes, the CPU is dynamically allocated to processes before they
are completed (in contrast to batch mode). Thus when a CPU is reassigned
from a process A to another process B then process A's run-time state
must be saved so that it can be restored when A is allocated the CPU
again in the future. We called this bookkeeping operation context
switching between processes.
Hence although the stack pointer need not be saved when performing a
function call within a process that pushes a stack frame in the process's
run-time stack, when context switching between processes
occurs the stack pointer value must be saved.
The saved stack pointer field in the process table
entry of a process serves this purpose.
A consequence of this is that
the currently executing process (note that in a uniprocessor system
only one process runs at any time with all other processes context
switched out and in a state of limbo)
has a stack pointer whose value is different
from saved stack pointer value (which contains the stack pointer
value when it was last context switched out).
In order to help you get the stack
pointer of the currently executing process, study stacktrace.c
in the system/ directory. The register %esp
(AT&T assembly syntax)
denotes the stack pointer. You may use in-line assembly (i.e., asm("..."))
to carry out this part.
With (at least) three concurrent processes running in XINU, it is
useful to know how XINU's default process scheduler, resched(),
works. When invoked, resched() always picks the highest priority "ready"
process to run. "Ready" means that a process is not blocking (i.e.,
waiting on an event, such as message arrival on a network interface)
but can immediately
make use of the CPU if allocated to execute. Since in lab1
all processes are created with equal priority 20, a round-robin
policy is implemented to pick the next process to run (i.e., allocate
CPU).
What prevents a process from hogging the CPU forever
is the notion of a time budget, called time slice or quantum, that the
operating system keeps track of. This time budget is configured in
the kernel constant QUANTUM which is defined in the header file
include/kernel.h. When a process is created, it is allocated this initial
budget. The kernel keeps track of how much CPU time a process has
consumed by performing bookkeeping in the clock interrupt handler
system/clkint.S. When a process that's running expends all of its
time slice -- this event is detected by clkint.S -- it calls
XINU's process scheduler, resched(). Since all processes in lab1 are
assigned equal priority, resched() picks the next process in line
and runs this process until it also exhausts its budget.
The process that exhausted its time slice and was context switched out
is made to wait at the end of the line (hence round robin).
When its turn comes up again because all other processes have spent their
time budgets and have been put behind in the line, the process's time
slice is replenished back to QUANTUM and the dynamics repeats again.
When a process relinquishes the CPU because its time slice has
depleted, we say that relinquishing the CPU was mandatory. When a
process gives up the CPU because it has to wait on an event
(such as message arrival) before it can proceed (a process may
also just decide to sleep for a while by making a sleep() system
call), then we say that
a process relinquished the CPU voluntarily.
The above is but a conceptual description of how the default XINU process
scheduler works. In CS 354, reading the kernel source code
resched() and clkint.S is required to understand the workings of
the operating system. In lab2, you will be asked to modify XINU's
scheduler to enhance its capabilities similar to those of UNIX/Linux
and Windows.
As finished and useful software output, the four functions described
in Problems 1 to 4 should be implemented as libraries that are
included (in source code format) as separate files in the system/
directory. Update Makefile accordingly to reflect this change.
Name the files after the functions they implement with C
files having the .c extension and assembly files having the
.S extension. For example, the file that holds
void printsegaddress()
should be named printsegaddress.c,
and the file that holds
long host2netl_asm(long param)
should be named host2netl_asm.S.
If you require a header file, please name it lab1.h.
When evaluating your lab assignment, the TAs will (1) examine the results
from your own test cases including the source code, and
(2) add new test cases to gauge
correctness and performance of your code.
Electronic turn-in instructions:
i) Go to the xinu-spring2013-x86/compile directory and do "make clean".
ii) Go to the directory of which your xinu-spring2013-x86 directory is a subdirectory. (NOTE: please do not rename xinu-spring2013-x86, or any of its subdirectories.)
e.g., if /homes/jsr/xinu-spring2013-x86 is your directory structure, go to /homes/jsr
iii) Type the following command
turnin -c cs354 -p lab1 xinu-spring2013-x86
Important: You can write code in main.c to test your procedures,
but please note that when we test your programs we will replace the main.c
file! Therefore, do not put any functionality in the main.c file.
ALL debugging output should be turned off before you submit your code.