CS444 Class 7
Handout: hw1 expected
script using testio.sh, an mtip shell script
Short demo: running
testio.sh to see the automated input happening.
Check your directory
structure: The
grading script will assume your tty
files are in hw1_part2, and will build testio.lnx and run it, so leave
all the supplied files there too.
Check
with du (disk usage utility), ignoring the numbers of blocks in the first
column--
cd cs444
du
hw1_part1
hw1_part2/queue
hw1_part2
Note
you can rename directories on UNIX with the mv command, for example “mv hw1
hw1_part2”.
Debugging Points
Use kprintf generously to
report what’s happening.
Common Problem: system “goes
away”, after printing some of your kprintf messages.
~r gets you going again, but
memory info is lost.
Use kprintf generously to
report what’s happening, blow by blow. kprintf is your spy—it runs in
between any two program lines, and runs immediately, no delay like we’re
putting into write().
Another idea is to use the
debugger, remote gdb. It works within interrupt handlers as well as in
ttyread and ttywrite.
Dataflow pictures, very rough here: those stick things are
supposed to be arrows showing data flow.
read
---> ttyread : upper half code, dequeues from input Q
^
|
input Q
^
|
input interrupt handler: enqueues into input Q
write
--> ttywrite: upper half code, enqueues to output Q
|
v
output Q
|
v
output interrupt handler: dequeues from output Q
Echoes
How about echoes? They
are generated when a char is processed in the input interrupt handler.
One copy of the char is enqueued in the input Q and another in an output echo
Q, separate from the ordinary output Q. If we put the above pictures next
to each other we can draw in the echo Q next to the output Q and an arrow from
the input int handler to the echo Q, and another arrow from the echo Q to the
output int handler.
When the output int handler
runs, it should first look in the echo Q, and output a char from there, or if
there are no chars there, output one from the ordinary output Q. This way
users see echoes as soon as possible.
What if TX interrupts are off
when we generate an echo—should we turn on transmitter ints then?
Answer: Yes, there needs to
be code in the receiver interrupt handler for this.
Note: the UART has only one
IRQ for transmitter and receiver interrupts. That means there is one
interrupt handler that has to handle both input and output interrupts.
You can check for receiver data-ready, and if that’s on, do the input interrupt
handling, and then, if the transmitter is ready, do the output interrupt handling.
Not covered in class but
may be useful for hw1:
Note: don’t confuse & and
&&. When we’re working with bits we need &, the bit-wise AND,
and also |, the bit-wise OR. These operators works the same in C and
Java, but are not as commonly used in Java. Reread K&R, pp. 48-49 and
52-53 if you need review on this.
One interrupt handler: how do we tell which kind of
interrupt is happening?
The simple-UART treatment
expected in hw1 uses the LSR to discriminate between receiver and transmitter
interrupts. After all, the UART is interrupting to tell us it’s
data-ready (DR on) or transmit-ready (THRE on), where DR and THRE are bits in
the LSR.
if
(inpt(COM2_BASE + UART_LSR) & UART_LSR_DR) … receiver int case, COM2
Of course, in tty.c you need
to use a variable for the base register. Also note that this action does
not acknowledge the interrupt, so you need to read the char in (DR on), or if
THRE is on, transmit another char or turn off transmit interrupts.
Back to Tanenbaum to
finish Chap 1.
UNIX system calls—we looked
at write and exit earlier, now look at
fork(), the amazing parameterless UNIX process-creation
system call.
Here is a trivial fork
program, using the return value from fork to tell the parent and child
apart: The parent gets the child pid (always !=0) from fork, while the
child gets back 0.
int
main()
{
if (fork())
printf(“hello from
parent\n”);
else printf(“hello from child\n”);
}
When you run this program, you
will see one message and then the other, in either order. Each terminates
by executing the exit syscall in the startup module.
<Drawing of one address
space, then copied second address space for the child process.>
After fork, while both
processes exist, there is a parent-child relationship between them. If
the parent exits first, the child is an orphan, and gets assigned to process 1
as a child. Being an orphan doesn’t affect how it runs, only how it gets
cleaned up at the end.
Fork clones the whole process
image, even the stack, so we can put in local variables and they will be
copied:
int x_ext = 2;
/* external var, in data segment */
int main()
{
int
x_loc=6; /* local (automatic) var, on stack */
if
(fork()) {
x_loc++;
x_ext++;
printf(“hello
from parent, x_loc=%d, x_ext = %d\n”, x_loc, x_ext);
}
else
printf(“hello from child, x_loc=%d, x_ext = %d\n”, x_loc, x_ext);
}
<Drawing of one address
space, then copied second address space for the child process.>
<x_ext is in the data
segment, x_loc is on the stack>
Here x_loc, a local or
automatic variable, will be on the stack, = 6 in the original program image,
and thus =6 in the copied image as well. In the parent, x_loc will be
incremented to 7, but this has no effect on the child.
Similarly, x_ext is an external variable = 2 at the fork, so still 2 in the child’s report.
So you will see “hello from
parent, x_loc=7, x_ext=3\n” and “hello from child, x_loc=6, x_ext=2\n” output,
in either order.
Next time: exec system call,
UNIX vs Windows system calls. Start on Chap. 2