The article below was written in 1998 - a lot has changed since then. Linux was barely on my radar then, so this article doesn't mention MAKEDEV. Perhaps more important is that dynamic device creation isn't mentioned either. Still, the basic concepts do remain the same. Just ignore the OS specific stuff.
If you are new to unix, you may not yet understand that the files in the /dev directory are a little different from anything you may be used to in other operating systems.
The article SCO Device Drivers goes into this in more depth for the technically inclined, so I'll just cover the highlights here.
This article references SCO Unix specifically, but the general concepts of devices are the same on Linux or any Unix system. Device numbers are likely to be different, and commands like "idmknod" may not exist, but the rest is completely applicable.
The very first thing to understand is that these files are NOT the drivers for the devices. Drivers are in the kernel itself (/unix or /xenix or /stand/unix), and the files in /dev do not actually contain anything at all: they are just pointers to where the driver code can be found in the kernel. There is nothing more to it than that. These aren't programs, they aren't drivers, they are just pointers.
That also means that if the device file points at code that isn't in the kernel, it obviously is not going to work. Existence of a device file does not necessarily mean that the device code is in the kernel, and creating a device file (with mknod) does NOT create kernel code.
Unix actually even shows you what the pointer is. When you do a long listing of a file in /dev, you may have noticed that there are two numbers where the file size should be:
brw-rw-rw- 2 bin bin 2, 64 Dec 8 20:41 fd0
That "2,64" is a pointer into the kernel. I'll explain more about this in a minute, but first look at some more files:
brw-rw-rw- 2 bin bin 2, 64 Dec 8 20:41 fd0 brw-rw-rw- 2 bin bin 2, 48 Sep 15 16:13 fd0135ds15 brw-rw-rw- 2 bin bin 2, 60 Feb 12 10:45 fd0135ds18 brw-rw-rw- 1 bin bin 2, 16 Sep 15 16:13 fd0135ds21 brw-rw-rw- 2 bin bin 2, 44 Sep 15 16:13 fd0135ds36 brw-rw-rw- 3 bin bin 2, 36 Sep 15 16:13 fd0135ds9
A different kind of device would have a different major number. For example, here are the serial com ports:
crw-rw-rw- 1 bin bin 5,128 Feb 14 05:35 tty1A crw-rw-rw- 1 root root 5, 0 Dec 9 13:13 tty1a crw-rw-rw- 1 root sys 5,136 Nov 25 07:28 tty2A crw-r--r-- 1 uucp sys 5, 8 Nov 25 07:16 tty2a
Notice that each of these files shares the "5" part of the pointer, but that the other number is different. The "5" means that the device is a serial port, and the other number tells exactly which com port you are referring to. In Unix parlance, the 5 is the "major number" and the other is the "minor number".
These numbers get created with a "mknod" command. For example, you could type "mknod /dev/myfloppy b 2 60" and then "/dev/myfloppy" would point to the same driver code that /dev/fd0135ds18 points to, and it would work exactly the same.
This also means that if you accidentally removed /dev/fd0135ds18, you could instantly recreate it with "mknod".
But if you didn't know that the magic numbers were "2,60", how could you find out?
It turns out that it's not hard.
First, have a look at "man idmknod". The idmknod command wipes out all non-required devices, and then recreates them. Sounds scary, but this gets called every time you answer "Y" to that "Rebuild Kernel environment?" question that follows relinking. Actually, on 5.0.4 and on, the existing /dev files don't get wiped out; the command simply recreates whatever it has to.
idmknod requires several arguments, and you'd need to get them right to have success. You could make it easier by simply relinking a new kernel and answering "Y" to the "Rebuild" question, but that's using a fire hose to put out a candle.
A less dramatic method would be to look at the files that idmknod uses to recreate the device nodes. These are found in /etc/conf/node.d
In this case, the file you want would be "fd". A quick look at part of that shows:
fd fd0 b 64 bin bin 666 fd fd0135ds36 b 44 bin bin 666 fd fd0135ds21 b 16 bin bin 666 fd fd0135ds18 b 60 bin bin 666 fd fd0135ds15 b 48 bin bin 666 fd fd0135ds9 b 36 bin bin 666 fd fd048 b 4 bin bin 666
This gives you *almost* everything you need to know about the device nodes in the "fd" class. The only thing it doesn't tell you is the major number, but you can get that just by doing an "l" of any other fd entry:
brw-rw-rw- 1 bin bin 2, 60 Feb 5 09:45 fd096ds18
this shows you that the major number is "2".
Armed with these two pieces of information, you can now do
mknod /dev/fd0135ds18 b 2 60 chown bin /dev/fd0135ds18 chgrp bin /dev/fd0135ds18 chmod 666 /dev/fd0135ds18
If you examined the node file closely, you would also notice that /dev/rfd0135ds18 and /dev/fd0135ds18 differ only in that the "r" version is a "c" or character device and the other is "b" or block. If you had already known that, you wouldn't have even had to look at the node file; you'd simply have looked at an "l" of the /dev/rfd0135ds18 and recreated the block version appropriately.
There are other fascinating things that can be learned from the node files. For example, fd096ds18 is also minor number 60, and can be used in the same way with identical results. In other words, if you z'd out (were momentarily innattentive, not CTRL-Z in a job control shell) and dd'd an image to /dev/fd096ds18, it would write to your hd floppy without incident.
If you have a SCSI tape drive, notice what happens when you set it to be the "default" tape drive. It creates device files that have different names (rct0, etc.) but that have the same major and minor numbers.
Knowing that it's easy to recreate missing device files also means that you can sometimes capture the output of programs that write directly to a device. For example, suppose some application prints directly to /dev/lp but you need to capture this to a file. In most situations, you can simply "rm /dev/lp" (after carefully noting its current ownership, permissions and, of course, major/minor numbers), and then "touch /dev/lp" to create an ordinary file. You'll need to chmod it for appropriate permissions, and then run your app. Unless the app has tried to do ioctl calls on the device, the output will be there for your use. This can be particularly useful for examining control characters that the app is sending.
One question that comes up fairly often is "what's the difference between a block and a character device and when should I use one rather than the other?". To answer that question fully is hard, but I'm going to try to at least get you started here.
The real difference lies in what the kernel does when a device file is accessed for reading or writing. If the device is a block device, the kernel gives the driver the address of a kernel buffer that the driver will use as the source or destination for data. Note that the address is a "kernel" address; that's important because that buffer will be cached by the kernel. If the device is raw , then the address it will use is in the user space of the process that is using the device. A block device is something you could make a filesystem on (a disk). You can move forward and backward, from the beginning of a block device to its end, and then back to the beginning again. If you ask to read a block that the kernel has buffered, then you get data from the buffer. If you ask for a block that has not yet been buffered, the kernel reads that block (and probably a few more following it) into the buffer cache. If you write to a block device, it goes to the buffer cache (eventually to the device, of course). A raw (or character) device is often something that doesn't have a beginning or end; it just gives a stream of characters that you read. A serial port is an excellent example- however, it is not at all unusual to have character (raw) drivers for things that do have a beginning and an end- a tape drive, for example. And many times there are BOTH character and block devices for the same physical device- disks, for example. Nor does using a raw device absolutely mean that you can't move forward and back, from beginning to end- you can move wherever you want with a tape or /dev/rfd0.
And that's where the differences get confusing. It seems pretty reasonable that you'd use the block device to mount a disk. But which do you use for format? For fsck? For mkfs?
Well, if you try to format /dev/fd0135ds18, you'll be told that it is not a formattable device. Does that make any sense? Well, the format process involves sequential access- it starts at the beginning and just keeps on going, so it seems to make sense that it wouldn't use the block device. But you can run "mkfs" on either the block or character device; it doesn't seem to care. The same is true for fsck. But although that's true for those programs on SCO OSR5, it isn't necessarily going to be true on some other UNIX, and the "required" device may make sense to whover wrote the program, but it may not make sense to you.
You'd use a block device when you want to take advantage of the caching provided by the kernel. You'd use the raw device when you don't, or for ioctl operations like "tape status" or "stty -a".
Got something to add? Send me email.
More Articles by Tony Lawrence © 2012-08-30 Tony Lawrence
Being able to break security doesn’t make you a hacker anymore than being able to hotwire cars makes you an automotive engineer. (Eric Raymond)