6.5 Special Devices

Linux also provides several character devices that don't correspond to hardware devices. These entries all use the major device no. 1, which is associated with the Linux kernel's memory device instead of a device driver.

6.5.1 /dev/null

The entry /dev/null, the null device, is very handy. It serves two purposes; you are probably familiar at least with the first one:

·         Linux discards any data written to /dev/null. A common trick is to specify /dev/null as an output file in some context where the output is unwanted.

For example, to run a command and discard its standard output (without printing it or writing it to a file), redirect standard output to /dev/null:

% verbose_command > /dev/null 

·         Reading from /dev/null always results in an end-of-file. For instance, if you open a file descriptor to /dev/null using open and then attempt to read from the file descriptor, read will read no bytes and will return 0. If you copy from /dev/null to another file, the destination will be a zero-length file:

·                % cp /dev/null empty-file 
·                % ls -l empty-file 
-rw-rw----    1 samuel   samuel          0 Mar  8 00:27 empty-file 

6.5.2 /dev/zero

The device entry /dev/zero behaves as if it were an infinitely long file filled with 0 bytes. As much data as you'd try to read from /dev/zero, Linux "generates" enough 0 bytes.

To illustrate this, let's run the hex dump program presented in Listing B.3 in Section B.1.4, "Reading Data," of Appendix B. This program prints the contents of a file in hexadecimal form.

% ./hexdump /dev/zero 
0x000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x000020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x000030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
. . . 

Hit Ctrl+C when you're convinced that it will go on indefinitely.

Memory mapping /dev/zero is an advanced technique for allocating memory. See Section 5.3.5, "Other Uses for mmap," in Chapter 5, "Interprocess Communication," for more information, and see the sidebar "Obtaining Page-Aligned Memory" in Section 8.9, "mprotect: Setting Memory Permissions," in Chapter 8, "Linux System Calls," for an example.

6.5.3 /dev/full

The entry /dev/full behaves as if it were a file on a file system that has no more room. A write to /dev/full fails and sets errno to ENOSPC, which ordinarily indicates that the written-to device is full.

For example, you can try to write to /dev/full using the cp command:

% cp /etc/fstab /dev/full 
cp : /dev/full : No space left on device 

The /dev/full entry is primarily useful to test how your program behaves if it runs out of disk space while writing to a file.

6.5.4 Random Number Devices

The special devices /dev/random and /dev/urandom provide access to the Linux kernel's built-in random number–generation facility.

Most software functions for generating random numbers, such as the rand function in the standard C library, actually generate pseudorandom numbers. Although these numbers satisfy some properties of random numbers, they are reproducible: If you start with the same seed value, you'll obtain the same sequence of pseudorandom numbers every time. This behavior is inevitable because computers are intrinsically deterministic and predictable. For certain applications, though, this behavior is undesirable; for instance, it is sometimes possible to break a cryptographic algorithm if you can obtain the sequence of random numbers that it employs.

To obtain better random numbers in computer programs requires an external source of randomness. The Linux kernel harnesses a particularly good source of randomness: you! By measuring the time delay between your input actions, such as keystrokes and mouse movements, Linux is capable of generating an unpredictable stream of high-quality random numbers. You can access this stream by reading from /dev/random and /dev/urandom. The data that you read is a stream of randomly generated bytes.

The difference between the two devices exhibits itself when Linux exhausts its store of randomness. If you try to read a large number of bytes from /dev/random but don't generate any input actions (you don't type, move the mouse, or perform a similar action), Linux blocks the read operation. Only when you provide some randomness does Linux generate some more random bytes and return them to your program.

For example, try displaying the contents of /dev/random using the od command. [4] Each row of output shows 16 random bytes.

[4] We use od here instead of the hexdump program presented in Listing B.4, even though they do pretty much the same thing, because hexdump terminates when it runs out of data, while od waits for more data to become available. The -t x1 option tells od to print file contents in hexadecimal.

% od -t x1 /dev/random 
0000000 2c 9c 7a db 2e 79 3d 65 36 c2 e3 1b 52 75 1e 1a 
0000020 d3 6d 1e a7 91 05 2d 4d c3 a6 de 54 29 f4 46 04 
0000040 b3 b0 8d 94 21 57 f3 90 61 dd 26 ac 94 c3 b9 3a 
0000060 05 a3 02 cb 22 0a bc c9 45 dd a6 59 40 22 53 d4 

The number of lines of output that you see will vary—there may be quite a few—but the output will eventually pause when Linux exhausts its store of randomness. Now try moving your mouse or typing on the keyboard, and watch additional random numbers appear. For even better randomness, let your cat walk on the keyboard.

A read from /dev/urandom, in contrast, will never block. If Linux runs out of randomness, it uses a cryptographic algorithm to generate pseudorandom bytes from the past sequence of random bytes. Although these bytes are random enough for many purposes, they don't pass as many tests of randomness as those obtained from /dev/random.

For instance, if you invoke the following, the random bytes will fly by forever, until you kill the program with Ctrl+C:

% od -t x1 /dev/urandom 
0000000 62 71 d6 3e af dd de 62 c0 42 78 bd 29 9c 69 49 
0000020 26 3b 95 bc b9 6c 15 16 38 fd 7e 34 f0 ba ce c3 
0000040 95 31 e5 2c 8d 8a dd f4 c4 3b 9b 44 2f 20 d1 54 

Using random numbers from /dev/random in a program is easy, too. Listing 6.1 presents a function that generates a random number using bytes read from in /dev/random. Remember that /dev/random blocks a read until there is enough randomness available to satisfy it; you can use /dev/urandom instead if fast execution is more important and you can live with the potential lower quality of random numbers.

Listing 6.1 (random_number.c) Function to Generate a Random Number Using /dev/random
#include <assert.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <fcntl.h> 
#include <unistd.h> 
/* Return a random integer between MIN and MAX, inclusive.  Obtain 
   randomness from /dev/random.  */ 
int  random_number  (int min, int max) 
  /*  Store a file descriptor opened to /dev/random in a static 
      variable.  That way, we don't need to open the file every time 
      this function is called.  */ 
  static int dev_random_fd  =  -1; 
  char* next_random_byte; 
  int bytes_to_read; 
  unsigned random_value; 
  /* Make sure MAX is greater than MIN. */ 
  assert (max > min); 
  /* If this is the first time this function is called, open a file 
     descriptor to /dev/random. */ 
  if (dev_random_fd == -1) {
    dev_random_fd = open ("/dev/random", O_RDONLY); 
    assert (dev_random_fd != -1); 
  /* Read enough random bytes to fill an integer variable. */ 
  next_random_byte = (char*) &random_value; 
  bytes_to_read = sizeof (random_value); 
  /* Loop until we've read enough bytes. Because /dev/random is filled 
     from user-generated actions, the read may block and may only 
     return a single random byte at a time. */ 
  do {
    int bytes_read; 
    bytes_read = read (dev_random_fd, next_random_byte, bytes_to_read); 
    bytes_to_read -= bytes_read; 
    next_random_byte += bytes_read; 
  } while (bytes_to_read > 0); 
  /* Compute a random number in the correct range. */ 
  return min + (random_value % (max - min + 1)); 

6.5.5 Loopback Devices

A loopback device enables you to simulate a block device using an ordinary disk file. Imagine a disk drive device for which data is written to and read from a file named disk-image rather than to and from the tracks and sectors of an actual physical disk drive or disk partition. (Of course, the file disk-image must reside on an actual disk, which must be larger than the simulated disk.) A loopback device enables you to use a file in this manner.

Loopback devices are named /dev/loop0, /dev/loop1, and so on. Each can be used to simulate a single block device at one time. Note that only the superuser can set up a loopback device.

A loopback device can be used in the same way as any other block device. In particular, you can construct a file system on the device and then mount that file system as you would mount the file system on an ordinary disk or partition. Such a file system, which resides in its entirety within an ordinary disk file, is called a virtual file system.

To construct a virtual file system and mount it with a loopback device, follow these steps:

1.       Create an empty file to hold the virtual file system. The size of the file will be the apparent size of the loopback device after it is mounted.

One convenient way to construct a file of a fixed size is with the dd command. This command copies blocks (by default, 512 bytes each) from one file to another. The /dev/zero file is a convenient source of bytes to copy from.

To construct a 10MB file named disk-image, invoke the following:

% dd if=/dev/zero of=/tmp/disk-image count=20480 
20480+0 records in 
20480+0 records out 
%  ls  -l /tmp/disk-image 
-rw-rw----    1 root     root     10485760 Mar  8 01:56 /tmp/disk-image 

2.       The file that you've just created is filled with 0 bytes. Before you mount it, you must construct a file system. This sets up the various control structures needed to organize and store files, and builds the root directory.

You can build any type of file system you like in your disk image. To construct an ext2 file system (the type most commonly used for Linux disks), use the mke2fs command. Because it's usually run on a block device, not an ordinary file, it asks for confirmation:

% mke2fs -q /tmp/disk-image 
mke2fs 1.18, 11-Nov-1999 for EXT2 FS 0.5b, 95/08/09 
disk-image is not a block special device. 
Proceed anyway? (y,n) y 

The -q option suppresses summary information about the newly created file system. Leave this option out if you're curious about it.

Now disk-image contains a brand-new file system, as if it were a freshly initialized 10MB disk drive.

3.       Mount the file system using a loopback device. To do this, use the mount command, specifying the disk image file as the mount device. Also specify loop=loopback-device as a mount option, using the -o option to mount to tell mount which loopback device to use.

For example, to mount our disk-image file system, invoke these commands. Remember, only the superuser may use a loopback device. The first command creates a directory, /tmp/virtual-fs, to use as the mount point for the virtual file system.

% mkdir /tmp/virtual-fs 
% mount -o loop=/dev/loop0 /tmp/disk-image /tmp/virtual-fs 

Now your disk image is mounted as if it were an ordinary 10MB disk drive.

% df -h /tmp/virtual-fs 
Filesystem            Size  Used Avail Use% Mounted on 
/tmp/disk-image       9.7M   13k  9.2M   0% /tmp/virtual-fs 

You can use it like any other disk:

% cd /tmp/virtual-fs 
% echo 'Hello, world!' > test.txt 
% ls -l 
total 13 
drwxr-xr-x    2 root     root        12288 Mar  8 02:00 lost+found 
-rw-rw----    1 root     root           14 Mar  8 02:12 test.txt 
% cat test.txt 
Hello, world! 

Note that lost+found is a directory that was automatically added by mke2fs. [5]

[5] If the file system is ever damaged, and some data is recovered but not associated with a file, it is placed in lost+found.

When you're done, unmount the virtual file system.

% cd /tmp 
% umount /tmp/virtual-fs 

You can delete disk-image if you like, or you can mount it later to access the files on the virtual file system. You can also copy it to another computer and mount it there—the whole file system that you created on it will be intact.

Instead of creating a file system from scratch, you can copy one directly from a device. For instance, you can create an image of the contents of a CD-ROM simply by copying it from the CD-ROM device.

If you have an IDE CD-ROM drive, use the corresponding device name, such as /dev/hda, described previously. If you have a SCSI CD-ROM drive, the device name will be /dev/scd0 or similar. Your system may also have a symbolic link /dev/cdrom that points to the appropriate device. Consult your /etc/fstab file to determine what device corresponds to your computer's CD-ROM drive.

Simply copy that device to a file. The resulting file will be a complete disk image of the file system on the CD-ROM in the drive—for example:

% cp /dev/cdrom /tmp/cdrom-image 

This may take several minutes, depending on the CD-ROM you're copying and the speed of your drive. The resulting image file will be quite large—as large as the contents of the CD-ROM.

Now you can mount this CD-ROM image without having the original CD-ROM in the drive. For example, to mount it on /mnt/cdrom, use this line:

% mount -o loop=/dev/loop0 /tmp/cdrom-image /mnt/cdrom 

Because the image is on a hard disk drive, it'll perform much faster than the actual CD-ROM disk. Note that most CD-ROMs use the file system type iso9660.