I/O Odds and Ends There are a few I/O topics that don't really fit into the categories we've talked about so far, but they are worth knowing about. Device Files and the Device Switch Table Device files in HP-UX are represented by two numbers: a major number and a minor number. Within the kernel, these two numbers are concatenated to form a single 32-bit value known as a dev_t. The first 8 bits of the dev_t are the major number, and the remaining 24 bits are the minor number. The major number is associated with a driver through the device switch tables, which we talked about earlier. The minor number is passed as an argument to that device driver's routines. As an example, let's say we want to do an open() system call to a tape device called /dev/rmt/c0t3d0BESTnb. The major number for this device is 205 (decimal), and the minor number is 0x0030c0. Within the kernel, this is a dev_t value of 0xcd0030c0. The kernel handles this open request by looking in the device switch table and finding entry 205, then calling the routine pointed to by the d_open field in that entry: the function stape_open(). The first argument to stape_open() is the dev_t that we're opening. In this example the SCSI tape driver is part of the WSIO CDIO, and as such it gets access to common SCSI services. One of the first things that stape_open() does is call scsi_lun_open(), again passing the dev_t. scsi_lun_open() decodes the dev_t into card, target, and logical unit numbers (LUNs), and does what it has to do to open the correct target and LUN. stape_open() now interprets the remaining bits in the dev_t in its own way and determines that this is a no-rewind Berkeley tape device file. The ioconfig File The ioconfig file is a persistent version of the iotree. That is, it is a place on disk where I/O configuration can be stored so that it will exist from one boot of the machine to the next. There are actually two copies of the ioconfig file on a system one in /etc/ioconfig and another in /stand/ioconfig. The copy in /etc/ioconfig is the authoritative copy, and the one in /stand is a backup in case the /etc directory is unavailable at boot time. During system startup, the ioinit program is run from /sbin/ioinitrc (which in turn gets started by init). ioinit compares the two files to see if they match. If they don't, the one in /etc is written over the top of the one in /stand. Then ioinit reads the configuration information from the file and uses that to rebuild the iotree entries from the previous boot of the system. This ensures that instance numbers and major numbers won't change between one boot and the next. Instance Numbers So, what are these instance numbers that the ioconfig is keeping for us? An instance number is just a way of uniquely identifying a particular card. Card types are assigned a class by the driver that claims them. This class might be tty for a serial interface, lan for a LAN interface, or ext_bus for a device like a SCSI card that supports multiple remote devices. Within these categories, each card is given an instance number, so for example, we might have ext_bus numbers 0, 1, and 2 in a particular system. Each number corresponds to a particular card. The numbers are assigned by the GIO when they are bound to a driver, and they need not be sequential. They must be unique within a class but not across classes, so you can have only one ext_bus0, but you can also have a lan0 and a tty0. These numbers are generally represented as a part of the minor number of the device file. In our SCSI tape example the ext_bus instance was 0 (for SCSI card 0) and is represented by the c0 part of the device filename and by the 0x00 in the upper byte of the minor number. I/O Merging It is important that these instance numbers not change once they've been discovered, which is why we have the ioconfig file. If we reassigned the instance numbers at each boot, then removing a card or adding a new card could cause the instance numbers to change, meaning that the device files would now be pointing to different devices. I/O merging is a technique to improve disk throughput by gathering together contiguous disk accesses. When a new disk I/O is started, rather than just tacking it on the end of the queue, the driver looks through the queue of I/Os that are waiting to be started to see if any of them are scheduled to access adjacent data on the disk. If so, the new I/O is combined with the old so that the system will do one larger I/O rather than two smaller ones. This takes advantage of the disk device's ability to read ahead and cache data, and also of the physical nature of disks. A disk access is much quicker if the heads don't have to be moved. When the I/O is completed, there is a corresponding process that unmerges the I/O back to the original requests. |