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:  aaaaaaaaaazzzAAAAAAAAAA

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 eflagsps”, 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.

Chap. 5 I/O

 

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 Jun  7  2000 /dev/board1 -> /dev/ttyrf

ulab(2)% ls -l /dev/ttyrf

lrwxrwxrwx   1 root           30 Feb 16  1999 /dev/ttyrf -> ../devices/pseudo/ptsl@0:ttyrf

ulab(3)% ls -l /devices/pseudo/ptsl@0:ttyrf

crw-rw-rw-   1 root      26,  47 Nov 22 14:14 /devices/pseudo/ptsl@0:ttyrf

 

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 Feb 16  1999 /dev/dsk/c0t0d0s3 ->  ../../devices/pci@1f,0/pci@1,1/ide@3/dad@0,0:d

ulab(7)% ls -l /devices/pci@1f,0/pci@1,1/ide@3/dad@0,0:d

brw-------   1 root     136,   3 Feb 16  1999 /devices/pci@1f,0/pci@1,1/ide@3/dad@0,0:d

 

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 Feb 16  1999 /devices/pci@1f,0/pci@1,1/ide@3/dad@0,0:d

crw-------   1 root     136,   3 Jul  6 11:53 /devices/pci@1f,0/pci@1,1/ide@3/dad@0,0:d,raw

 

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.