Tues, Dec. 5
First, let’s look at the hw3 (no preemption case) output, and then see how it changes for hw5. Note that one char takes about 1 ms to be output at 9600 baud, the baudrate of systems 1-10. Systems 11-14 have much slower processors, so not recommended for testing sched.
Recall that process 1 runs like this,
where ●
denotes the
computation loop: aaaaaaaaaazzz●AAAAAAAAAA
main
2 runs like this: bbbbbbbbbbb
main 3 runs like this: ccccccccccc
hw3
solution, or hw5 provided files: no preemption
------- ------------------------
<--i/o bound
-----●
<--CPU bound
aaaaaaaaaazzzAAAAAAAAAAbbbbbbbbbbbccccccccccc
^ note pause here with no output,
as main1 does CPU loop
^proc 1 starts its CPU loop (3 a’s,
3 z’s in Q)
^proc
1 exit (6 A’s in Q)
^proc 2 exit (6 b’s
in Q)
^proc 3
exit
When the system is i/o bound, every time an output interrupt happens, wakeup unblocks all the user processes (that haven’t yet exited), and they all go promptly back to sleep in ttywrite (one gets to enqueue another char, but this takes very little time). Then process 0 runs for most of the millesecond that the next char takes to output.
I’ve marked the end of the CPU-bound interval with a spot because there’s a lot of CPU used at that point, with no output to show for it.
Preemption of process 1 by process 2 is marked by |(1-2) in the debug log. The tick occurs every 10ms. In the i/o bound case, the tick happens once every 10 chars, or possibly every 9 chars (allowing for some overhead.) We’ve settled on QUANTUM = 4, so at the 4th tick in a CPU-bound process (for example, process 1 of uprog123), preemption occurs.
The following is a possible run for this case. We can see an effect of preemption from watching the run: instead of the system stopping output after zzz, stuck in the computation loop in main1, it keeps doing output from the other processes while doing that computation.
-------
----------
<--system is i/o bound
------●-----●-----●----●-●
<--system is CPU bound
aaaaaaaaaazzzbbbbbbbbbbccccccccccAAAAAAAAAA
^proc 1 starts running CPU loop, with 3 a’s and 3 z’s in Q
^preempt proc 1, proc 2 runs, enq’s
6 b’s,
blocks
^proc 1 runs again, others in i/o
wait
^proc 1 preempted again,
proc 2 finishes output
^proc 1 runs again,
…
Although the “CPU bound” bar above the output is longer in this picture than the last one, it represents the same length of elapsed time. In the preemptive case, output is allowed during the CPU bound execution. Since the 6 chars take 6 ms to output, and the loop in main1 runs 40 ms before being preempted, there are (about) 34 ms of CPU expended at the spots on the CPU-bound bar, just before the 2 preemptions, and another spot where the loop is finishing up.
Now let’s assume the tick handler debug-logs *1 when interrupting process 1, *2 when interrupting process 2, and *3 or *0 similarly. Then we might see the following in the debug log:
*0~a~a~a~a~a~a~a~a~a~a*1~z~z~z~S*1*1*1|(1-2)~b~b~b~b~b~b~S*1*1*1*1|(1-2)~b~b~b~b~c~c~c~S*1*1*1*1%|(1-3)
~c~c~c~c~c~c~S*1*1*1*1|(1-3)~c~S*1*1*1*1|(1-1)*1*1*1*1|(1-1)*1*1*1*1 ...
|(1-1)*1*1*1~A~A*0~A~A*0~A*0~A~A~A~A~A~S*0*0*0*0*0*0*0*0*0*0
Here we see that when the system is CPU-bound running in proc 1, the interrupts are from proc 1, whereas when the system is i/o bound, it is mostly running proc 0, so the interrupts will mostly be from proc 0. We hardly ever see an interrupt from process 2 or proc 3 because they use hardly any CPU—they are mostly blocked for output. Remember this is uprog123.lnx. The other sched-project user program, sequences.lnx, with 2 long computations, will be quite different.
Remote gdb: more tricks, power gdb
Suppose you’re seeing no interrupts during the CPU loop in main1. Want to check if IF=1 there.
One way: add to main1 code:
kprintf(“eflags
= %x\n”,get_eflags());
Using gdb: after “tar rem /dev/remgdb8” or whatever—don’t forget you need to use system #5 or higher.
gdb) b
main1
gdb) c
<hits breakpoint>
gdb) p/x $ps
<---note gdb
calls eflags “ps”, for
processor status
$1
= 0x202
<---IF=1 here, bit 9 of
0x202 = 0010 0000 0010
Although eflags is called ps, eip is called eip, not pc, the generic name. To restart at the beginning:
gdb) set $eip
= 0x100100
gdb) c
You can see all the registers with “i reg”, but ps will be printed only in decimal, a fairly useless way.
To check if interrupts are enabled in the COM2 UART, we need to inb from port 0x2f9, which we could do in Tutor by “pd 2f9”, but how can we do it in remote gdb? Use the power of gdb to execute functions in the program, here inpt:
gdb) p/x inpt(0x2f9)
$2
= 0x1
This 0x1 means receiver ints are enabled. A value of 3 means receiver and transmitter ints are enabled.
Similarly, to check the mask register in the PIC, we need to look at port 0x21:
gdb) p/x inpt(0x21)
$3
= 0xe1
0xe1 = 1110 0001, so we see that (hw3 case) the timer at bit 0 is masked off, whereas IRQs 1, 2, 3, and 4 are not masked. IRQ1 is the keyboard, 2 is for cascading the second chip, 3 and 4 are for the two UARTs.
(It is actually less confusing to look at this port via remote gdb than Tutor, because gdb arranges execution of this function (inpt) as normal program execution, whereas with Tutor “pd 0x21”, Tutor itself is both using this register to get control of the system and trying to report on its use by the user program.)
Looking at kernel data structures
gdb) p proctab[1]
look at process 1’s PEntry
gdb) p proctab
look at the whole array
gdb) p ttytab
look at both tty
structs
In here we see the transmit buffer contents, but some of what we see may be old data, not still in the queue. How can we find out how many chars are actually in the tbuf for COM2? We can call queuecount. COM2 has the second entry, ttytab[1], in which lies the Queue tbuf, and queuecount takes a pointer to the Queue:
gdb) p queuecount(&ttytab[1].tbuf)
How can we find the current process number? Make gdb compute it from curproc:
gdb) p curproc
- proctab
You can even call sleep and wakeup from gdb to see how they work, but don’t expect your kernel to continue executing correctly after that! Just use the p command, even if the function returns void.
Block vs. char devices (mainly a UNIX idea)
Each device under UNIX has a special file, also known as “device node”. Tan., pg. 734 example is “/dev/lp” for a line printer device. Classically, device nodes were kept in directory /dev. They are not ordinary files, but rather filenames and associated information about a device.
When you display them with “ls –l”, you see a “c” for char device or “b” for block device as the first character of the listing, as you would see a directory marked “d”. For example a line printer would be a char device:
ls
–l /dev/lp
crw-rw-rw 1 root ...
/dev/lp
On Solaris, the devices have been reorganized into many subdirectories by device type, and with symbolic links to other names, so it’s a bit hard to find the actual device nodes. For example, on ulab, we have /dev/board1, the serial line to mtip system 1. We have to follow two symbolic links before we find the device node:
ulab(1)% ls
-l /dev/board1
lrwxrwxrwx 1 root
10
ulab(2)% ls
-l /dev/ttyrf
lrwxrwxrwx 1 root
30
ulab(3)% ls
-l /devices/pseudo/ptsl@0:ttyrf
crw-rw-rw- 1
root
26, 47
Nov 22
The c shows that we finally found the device node.
How about a disk? We do “df –l” to see local disks (the disk-free command), and then trace through the symbolic links:
% df
-l
/proc
(/proc
):
0 blocks
1860 files
/
(/dev/dsk/c0t0d0s0 ):
1035474 blocks 360590
files
/dev/fd
(fd
):
0 blocks
0 files
/tmp
(swap
): 1133152 blocks
12888 files
/disk/sd0h
(/dev/dsk/c0t0d0s3 ):
6753190 blocks 480037
files
/cdrom/990129_1230
(/vol/dev/dsk/c0t2d0/990129_1230):
0
blocks
0 files
/courses/cs241
(/disk/sd0h/courses/cs241): 6753190 blocks
480037 files
ulab(6)% ls
-l /dev/dsk/c0t0d0s3
lrwxrwxrwx 1 root
46
ulab(7)% ls
-l /devices/pci@1f,0/pci@1,1/ide@3/dad@0,0:d
brw------- 1 root
136, 3
There’s the b for block device. If we add a * on the end of the ls command, we see a companion device node known as its “raw device node.”
ulab(8)% ls
-l /devices/pci@1f,0/pci@1,1/ide@3/dad@0,0:d*
brw------- 1 root
136, 3
crw------- 1 root
136, 3
Jul 6
This “raw device node” provides a way to access the same disk without going through the filesystem, a capability used by some database systems and utility programs. Note the heavy protection on these nodes, necessary for system security, since these nodes can give access to all the data on the disk.
Special files can be used like filenames in open. Devices are folded into the filesystem name space, so that open, read, write, close can be used with devices just as if they were files.
For Tan’s simple device node, we could
fd =
open(“/dev/lp”,
...)
write(fd,
“testing”, 7);
close(fd);
And write the message on the line printer. Since the cp command is doing such an open (possibly through fopen()), the cp command can be used with the device node too:
cp file
/dev/lp
This is not recommended for normal use, however, as it “jumps the queue” set up by lpr. You can try it on your home Linux system.
This is part of the device-independent i/o story. We can do the same to a floppy drive, /dev/fd, although it will not be in a filesystem on the floppy disk this way. We are treating the floppy disk as a sequential stream of bytes.
We can even do it with a hard drive, though it will wipe out any filesystem on that partition. The other direction is much safer:
more
/dev/hda0
Where /dev/hda0
stands for the first partition on drive hda,
the
first IDE drive on a Linux system.
Of course you need to run as root to do this successfully
on a normally
protected system.
Windows: for list of device names for disk devices use “mountvol”. This includes floppy and CD drives. Here’s the volume name of my floppy disk:
\\?\Volume{cb15a4be-b302-11d7-a922-806d6172696f}\
We’re at Tan., pg. 279. Quick review of material we already looked at near the start of the term
Note corr. to pg. 280: line 8, “interrupt vector” should be “interrupt vector number”, so that “interrupt vector” can be saved for use as the address of the interrupt handler, held in the interrupt gate on x86, in IDT[nn], where nn is the interrupt vector number.
Precise Interrupts—we’ve covered this already, but reread.