CS444 Class 22

Last time: Mechanism of Page Faults (PFs)

Examples of PFs

 

  1. First ref to data page—page contents are in executable file.  PF handler blocks.
  2. First ref to BSS page (uninitialized data)—no blocking, just assign page from free list.
  3. Ref that extends the user stack—same as 2.
  4. First ref to text page (code)—as in 1, or if this program is in use by another process, arrange sharing of code page already in memory.
  5. Reref after pageout to swap space—block while read in from swap space.
  6. Ref to address outside of program image: fails validity test in step 4 above, causes “segmentation violation” in Solaris, usually kills process.
  7. Ref to malloc’d memory (heap): malloc itself only allocates swap space, not real memory, so the memory is added by PFs, like #2.

 

Note that a PF is more like a system call than an interrupt.  They are both exceptions or “traps.”  When the kernel is executing after a trap, it is executing on behalf of the current process, so the process entry and process image are relevant and usable.  No problem in blocking.  An interrupt is quite different.  Interrupt handlers execute as guests of a “random” process.  They normally don’t access process data, only kernel global data relevant to their device.

 

 

Tan, pg. 758 Linux memory management.

Discussion of process image regions—don’t forget DLLs too now.

 

pg. 758 pic. showing 2 processes in memory sharing their code pages.  But note that only one of these is “current” in use of the CPU (unless there are multiple CPUs.)  The other one is in memory but not scheduled at the moment.  On the x86, the current one has the CPU register CR3 pointing at its page directory, which makes its whole process image mapped in.  Other CPUs have similar master registers for the top-level of their paging support.

 

Memory-mapped files. Fig. 10-13, pg. 761

A region of a file can be mapped into a process image.  Then writes to that part of VA space cause corresponding writes to the file pages.  If two processes map the same region of a file, they end up with shared memory with data that persists in the filesystem.  However, this is not commonly used in applications, partly because the solution is not portable, and partly because there are subtle issues such as exactly when the file writes occur. 

 

Often, the memory-mapping mechanism is used for shared memory, ignoring the file itself.  You can use /dev/zero, the OS-supplied effectively infinite file of zeroes, instead of a real file.  See mmap_nofile.c.

 

Memory-related system calls.

Most paging actions are done without memory-specific system calls, but malloc does need a system call to get memory assigned to the process. The underlying system call is brk.  Memory-mapped

 

Finished with memory management coverage. Skipping Chap 4, on to Chap. 5, already partly covered.

 

Intro to hw4—go over handout

 

semaphores—new syscalls, but that should be no problem: new code in ulib.s, sysup, sysdown, etc. in kernel

See Tanenbaum pg. 128, discussed in class 12 for definitions of up and down.

Need sem struct in kernel, with sem count and a list of waiting processes or equivalent

—can use intqueue, supplied queue of ints (pids), or use proctab[i].waitcode values. (with a unique waitcode for each semaphore)

sysdown(int semid): decrement semid’s count if it’s positive, sleep if appropriate

sysup(int semid): increment semid’s count as appropriate, call wakeup_one

 

For testing with testmutex, is not a full test because it uses only one waiter.  Prodcons is a better test once testmutex is working. 

Semaphore Implementation

 

To user programs, these provide mutex and process synchronization, but to the kernel, they are software objects with certain properties.

 

Luckily, we can support semaphores without preemption, so this project is independent of optional part preemptive scheduler.  Note: use Tanenbaum’s definition of up and down, pg. 128.

 

The kernel data structures needed for semaphores: an array of Semaphore structs, one for each possible semaphore, with sem. count and list of processes (pids) waiting on it (using intqueue, provided), or equivalently, a decision to scan proctab[i] looking for the waiters on a certain semaphore when necessary.

 

With the array, we have a simple way of keeping track of each semaphore: just use the index of its semaphore struct in the array.  Thus we have sem #0, sem #1, and so on.  We can return these id’s to the user.

 

Pseudocode for sysup:

 

Psuedocode for sysdown:

 

For example, suppose processes 2 and 3 are waiting on sem #2, and then process 1 does an up on sem 2.  The code in sysup can decide that process 3 should unblock and call wakeup_one(3).  Then in sysdown, where process 2 and 3 are in sleep calls, process 3 returns from sleep, but process 2 continues to sleep.

 

Recall from hw3 that it’s fine to call sleep with IF=0, in fact, it’s required.  But the kernel mutex only lasts until another process is chosen to run, since that process will restore IF to 1 when it finishes its system call and returns to user (if not sooner.)  Thus the call to sleep represents a “mutex hole”, and the global variables need to be in a consistent state at this point.

 

You can define a new field of PEntry to record a semaphore waiter’s semaphore, for example p_sem. This is only valid if the process is blocked in semaphore wait (need a new waitcode for this).  This is the simplest way to know who the waiters are. Alternatively, maintain a queue of waiter-pids in the semaphore struct.

 

 

Remote gdb: more tricks, power gdb

 

Suppose your program is crashing—where??

Just run it under remote gdb and see the backtrace after the crash.

 

From gdb.script in $pcex:

------mtip window-----------------------------------------

ulab(1)% mtip -b 6 -f test.lnx

For command help, type ~?

For help on args, rerun without args

Code starts at 0x100100

Using board # 6

 

Tutor>                 <----user did <CR> to see Tutor prompt.  If necessary,

                                do ~r here to reset the board.

~downloading test.lnx   <---user typed ~d here to download test.lnx

Calling loadprog()

.......Done.

 

Download done, setting EIP to 100100.

Tutor> gdb

Setting gdb dev to COM1, starting gdb (CTRL-C to abort).

                                 <---mtip session "hangs" here, that's

                                        normal--it's waiting on commands

                                        from its gdb connection.

------gdb window-----------------------------------------

ulab(1)%

ulab(1)% i386-gdb test.lnx

GDB is free software and you are welcome to distribute copies of it

 under certain conditions; type "show copying" to see the conditions.

There is absolutely no warranty for GDB; type "show warranty" for details.

GDB 4.15.1 (sparc-sun-sunos4.1.3 --target i386-linuxaout),

Copyright 1995 Free Software Foundation, Inc...

(gdb) tar rem /dev/remgdb6           <---remote gdb line for board 6

Remote debugging using /dev/remgdb6

0x100100 in /groups/ulab/devhost/lib/startup0.opc ()

(gdb) set $eip = 0x100100             <----set EIP (PC) at start of program

(gdb) c

Continuing.

<crash>

(gdb) where

see backtrace

 

Second Example

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. 772 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