CS644, Thurs., May 6
Handout on Memory Management Basics
Last class: Xinu TTY driver, output side, then input side, seeing two cases of modified producer-consumer, one semaphore each, to block the system call code. The interrupt handler can’t block, so it can’t wait on a semaphore. Instead, it shuts down transmitter interrupts in the output case, when obuff is full, and discards input that doesn’t fit in ibuff.
There are echoes to handle as well, so the full picture looks like this, in cooked mode with echoes, the default CONSOLE state, and the default standard input/output setup for UNIX*:
<input syscall: ttygetc> <output syscall: ttyputc>
^ |
| v
ibuff ebuff obuff
^ ^ \ |
| / \ |
v v
<input int. handler> <output int. handler>
This picture shows the data flow in tty driver, including echoes. Each input char is put in both ibuff and ebuff, the echo-char buffer, in ttyiin. In ttyoin, the code looks first in ebuff for a char to output, and failing that, in obuff.
UNIX Device Drivers
Not much in Love about this, unfortunately. A little on pg. 235 about character devices vs. block devices. Let’s concentrate on char devices.
Older UNIX kernels had a big table for char devices much like devtab. Linux and other current UNIX systems allow dynamic loading of device drivers, so a monolithic table is no longer the best way. Instead, the individual devsw struct equivalents are made available via a hash table for fast lookup by major numbers.
In Linux, these devsw structs equivalents are struct file_operations, listed in Love on pg. 227. Each device driver has one of these, but only has to fill out part of it. For example, the simple chardev.c device driver in the Linux Kernel Module Guide has:
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
This is using a new capability of C (C99) for struct initialization, discussed in Love on pg. 340.
Back at user level, to open a device, you use the open system call with the device’s “device file”, for example
int fd = open(“/dev/lp”, ...)
We see that the devices are represented by device files, or “special files” or “device nodes”, which are not ordinary files, and usually reside in /dev or its subdirectories. Instead of normal file contents, the device file records the major and minor device numbers, which locate the driver inside the kernel, and say which instance of that device is in use.
You can see the major and minor device numbers with “ls –l”. On Solaris you need to follow several links to get to the actual device file from the /dev/remgdb14 or whatever. On Linux, more are right there in /dev. Try “ls –l /dev/ttya*” on sf06, for example.
sf06.cs.umb.edu$ ls -l ttya*
crw-rw-rw- 1 root tty 3, 176 Nov 24 10:09 ttya0
crw-rw-rw- 1 root tty 3, 177 Nov 24 10:09 ttya1
crw-rw-rw- 1 root tty 3, 178 Nov 24 10:09 ttya2
...
shows all have major device# 3 and minor dev # 176, 177, etc.
The c at the beginning of each line shows these are character devices (a b means a block device.)
As in Xinu, a typical Linux device driver has an interrupt handler, as we have already covered, and implementations for at least some of the file_operations functions, which are called from system calls open, close, read, write, etc.
Memory Management: our final topic. See handout.