How major and minor device numbers worked in V7 Unix

July 13, 2020

Unix people who've been around for a while know that Unix devices have device numbers, and that device numbers are divided into major and minor device numbers. When you do 'ls -l /dev/null' and one of the fields that ls prints is two comma separated numbers, those are the major and minor numbers (on Linux, they are '1, 3'; this varies by Unix). Device numbers and their split into major and minor parts go back a long way, to before Research Unix V7, but V7 makes a convenient point to look at what they meant and how they worked in the original Unixes.

As various sources will tell you, the major number tells you (and the Unix kernel) what sort of device it is and thus what device driver to use to talk to it, while the minor number tells the device driver what specific bit of hardware it's responsible for that you want to talk to. Sometimes the minor number also determines some bit of functionality. Because V7 was a deliberately simple and brute force system and kernel, major device numbers had a very simple implementation. We can see it in the generated V7 kernel configuration file c.c:

 struct bdevsw bdevsw[] =
 {
   nulldev, nulldev, rkstrategy, &rktab, /* rk = 0 */
   nodev, nodev, nodev, 0, /* rp = 1 */
   [...]
   nodev, nodev, nodev, 0, /* hp = 6 */
   htopen, htclose, htstrategy, &httab, /* ht = 7 */
   nodev, nodev, nodev, 0, /* rl = 8 */
   0
 };

What we're seeing here is that V7 literally had an array of bdevsw structures indexed by the major (block) device number, with various function that were called when you did things like open a device (in fio.c). There was a similar array for character devices, the cdevsw array. In both of them, what driver functions were listed here instead of stubbed out were determined by simple configuration files (here) that said what devices you had (among other things).

(The c.c file was generated by a program. The particular c.c file in the TUHS V7 tree was built with only two block devices configured, the RK disk driver and TJU16 tape driver 'ht'.)

In V7 the minor device number was only interpreted by the device driver, as far as I can see. Device drivers used this for a variety of purposes. For instance, the mem character driver implemented /dev/null as minor device 2, to go along with access to physical memory and kernel memory. The rl disk driver used the minor device number to decide what physical disk it was talking to (it supported up to four of them). Once V7 started getting out in the world, other people wrote drivers for it (such as the RX02 floppy disk driver) that used minor device numbers both to select what to talk to and control what features to use.

(There's also the kl KL/DL-11 serial and console driver, which seems to deal with three different sets of hardware control registers based on the minor number.)

The /dev/tty character device was implemented in a clever and very short way in sys.c. In V7, there were no pseudo-ttys and no hot-plugged devices, so your underlying physical terminal device always existed and was recorded in your u area (see user.h) The general tty driver simply used this recorded device number of your controlling tty to call its open, read, write, and ioctl functions through the cdevsw array. As far as I can tell, this driver paid no attention at all to the minor device number; as long as /dev/tty had major number 7, the minor number was irrelevant.

PS: Note that V7 device drivers tended to be a little relaxed about error checking for their minor device numbers (and other things). For instance, as far as I can tell the mem driver actually only distinguishes between minor number 2, minor number 1, and 'everything else', which is treated as minor number 0, giving access to physical memory.


Comments on this page:

By Warner Losh at 2020-07-16 16:54:29:

Yes, the minor number used to be 100% the business of the device driver to interpret. V7 didn't always check for valid ranges on them because /dev/MAKEDEV wouldn't create invalid ones, so the issue rarely arose. But those were more-trusting times, almost laughably permissive. Though in fairness, root would have to create the bogus node on purpose, so when you're in a bind for space checking "can't happen w/o root's complicity" conditions just wasted bytes that could be used for something else.

Written on 13 July 2020.
« Running servers and Fred Brooks on transforming programs to products
Link: The Anatomy of a PromQL Query »

Page tools: View Source, View Normal.
Search:
Login: Password:

Last modified: Mon Jul 13 22:53:13 2020
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.