Add Book to My BookshelfPurchase This Book Online

Chapter 5 - Files and Directories

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

The UNIX Filesystem
UNIX was not the first operating system to use a hierarchical filesystem, nor is it the last. Almost every modern operating system has some type of hierarchical filesystem.
When it was first developed, the UNIX filesystem was different from other filesystems of the day, however. Unlike most systems, in which hardware devices were accessed by using their own special abstractions, UNIX folded everything into the filesystem. Instead of using a special set of system calls to print a file on a printer or write data on a tape drive, the UNIX programmer could access these devices simply by opening a file and then writing data to it. The centrality of the filesystem is one of the things that has made UNIX one of the most popular operating systems in the world.
The remainder of this section discusses the different types of objects provided by the UNIX filesystem.
Basic File Types
There are three basic file types in the UNIX filesystem: regular files, special files, and directories.
Regular files
The simplest object in the filesystem is a regular file. This object can contain whatever data the user chooses to place there; the operating system does not interpret it in any way. Unlike some other operating systems, which have several different types of files such as sequential, random access, fixed-length records, and so on, UNIX does not impose any format on a regular file at all. Instead, the file is simply interpreted as a string of bytes, and these bytes may be read and written in any way the user chooses. Certain programs, of course, expect this string of bytes to have a specific format. For example, the assembler generates an object file that must be in a particular format (header, followed by executable code, followed by initialized data) to be understood by the linker. But these formats are imposed by user-level programs, not the operating system. As far as UNIX is concerned, there is no difference whatsoever between a program's source code, its object code, its input, and its output. They're all just regular files, each of which contains a string of bytes.
Special files
Special files, also called device files, are one of the most unusual aspects of the UNIX filesystem. Each I/O device connected to the computer system (disk drive, tape drive, serial port, printer, and so on) is associated with at least one such file. To access a device, a program opens the special file associated with the device and then reads data from or writes data to the device as if it were a regular file. The difference between special files and regular files is that when reads and writes are performed on special files, the devices connected to the computer system do things. For example, reading from the special file associated with a tape drive causes the tape to spin, the drive to transfer data from the tape and into the computer's memory, and so forth. Writing to the special file associated with a printer causes the print head to move, the hammers to strike the ribbon, and letters to appear on the page.
There are two types of special files: character-special files, also called “raw” devices, and block-special files. The character-special file is the most like a regular file, because it simply transfers data between a program and a device in whatever units the program cares to use. For example, if a program reads one character at a time from the character-special file associated with a tape drive, the tape drive literally transfers a character at a time to the computer. If the program writes in blocks of several sizes to the tape drive, then the tape contains an assortment of different block sizes. A block-special file, on the other hand, is buffered by the operating system. If a program reads one character at a time from the block-special file associated with a tape drive, the operating system tells the tape drive to transfer a block of data (usually some multiple of 512 bytes) to memory and then satisfies the program's read request from this buffer. After the program reads enough data to exhaust the buffer, another buffer is requested from the tape drive. Similarly, if a program writes in several different quantities to the tape drive, the operating system buffers that data, resulting in a tape with a uniform block size.
Directories
Directories provide the mapping between the names of files and the files themselves, thus imposing a structure on the filesystem as a whole. A directory contains some number of files, and it can also contain other directories. You can open and read a directory just like any other file; it is simply a stream of bytes with a meaningful format. However, a program cannot open a directory for writing, because all writes to a directory are handled by the operating system itself.
The operating system maintains one special directory for each filesystem, called the root directory. This directory serves as the root of the filesystem hierarchy. Every other file or directory in the filesystem is subordinate to the root directory. Any file in the filesystem can be located by specifying a path through a chain of directories starting at the root.
Each file in the filesystem is identified by a pathname, which is a sequence of filenames separated by slash (/) characters, for example, /dir/subdir/file. All names in a pathname, except for the one following the last slash character, must be directories. If the pathname begins with a slash character, it is called an absolute pathname and specifies the path to the file beginning from the root directory. If the pathname does not begin with a slash character, it is called a relative pathname and specifies the path to the file from the program's current working directory (see below). As limiting cases, the pathname / refers to the root directory, and a null filename (for example, /a/b/ ) refers to the directory whose name precedes the last slash. Multiple slashes (/// ) are interpreted as a single slash.
A directory always has two entries, named . (dot) and .. (dotdot). The special name . in a directory refers to the directory itself; this enables a program to open its current working directory for reading, without knowing its pathname, by opening the . file. The special name .. refers to the parent directory of the directory in which it appears; that is, the directory one level up in the hierarchy. A program can move from its current directory, regardless of where it is located in the hierarchy, to the root directory by repeatedly changing to the directory .. until the root directory is reached. As a limiting case, in the root directory the .. name is a circular link.
Removable Filesystems
In its simplest case, the filesystem is a single directory hierarchy contained on a single storage device. There is a single root directory, and under that directory are files and directories. These directories in turn contain more files and directories, and so on. What happens, though, when the storage device runs out of room, and more storage space must be added to the system? Since a filesystem is a single directory hierarchy on a single device, does this mean that the existing disk must be replaced with a larger one, and that no filesystem can be larger than the largest capacity disk currently manufactured?
Fortunately, no. To explain this requires that you use the term filesystem to describe two different things. The first definition is that a filesystem is the directory hierarchy that exists on a single storage device, composed of a root directory, files, and subdirectories, as described in the previous paragraph. The second definition is a recursive one: a filesystem is a directory hierarchy composed of a root directory, files, subdirectories, and other filesystems. This second definition is achieved by telling the operating system that whenever a reference is made to a specific directory, the system should move its frame of reference from the directory hierarchy stored on the first disk to the hierarchy stored on some other disk.
This is best explained by an example. Suppose that you have a single disk on your system, and it contains the entirety of the UNIX filesystem: /, /etc, /usr, and so forth. Assume that users' home directories, in which they keep all their personal files, are stored in the directory /home, with names such as /home/joe, /home/mary, and so on. Now suppose that your disk is running out of space, and you just purchased a second disk. You would like to leave the system files on the first disk, but move all the user files to the new disk. There are four steps to this process:
 1.You use the newfs command to create a filesystem on the new disk. This process involves initializing a number of new data structures on the disk and creating a root directory to serve as the base of the directory hierarchy. For a discussion of the data structures that are actually placed on the disk in this step, see Appendix B, Accessing Filesystem Data Structures.
 2.You use the mount command to mount the new directory hierarchy into the filesystem, using the /mnt directory as a mount point. The mounting process tells the operating system that whenever a reference is made to a file whose pathname from the root includes the directory /mnt, the system should look in the directory hierarchy stored on the second disk. The process of mounting a filesystem hierarchy on /mnt causes any previous contents of /mnt to be hidden until the filesystem is again unmounted.
 3.Using any of a variety of tools, you copy the contents of the /home directory (on the old disk) to the /mnt directory (on the new disk). Then you delete the contents of the /home directory, removing the data from the old disk.
 4.Finally, you unmount the new disk's filesystem from /mnt and mount it on /home instead. Now, whenever a file whose absolute pathname contains the /home directory is referenced, the operating system knows to look for the file on the new disk, instead of the old one.
The filesystem hierarchy created on the second disk is called a removable filesystem. It can be mounted or unmounted, and the system will still operate correctly. However, the files in /home are accessible only when the hierarchy is mounted. Otherwise, /home is just an empty directory. It doesn't have to be empty, but it makes little sense to store things there, because they are inaccessible whenever the /home filesystem is mounted.
Filesystems can be mounted on directories at any level in the filesystem hierarchy. For example, you could have mounted the new disk on /home/mary. In this case, Joe's home directory (/home/joe) is stored on the old disk, but Mary's home directory (/home/mary) is stored on the new disk. You can also nest mounts; for example, you could have one filesystem mounted on /home, and another filesystem mounted on /home/mary. To do this, you must mount the filesystems in a particular order: mounting the /home/mary disk before the /home disk does not produce the desired result.
Device Numbers
Each special file in the filesystem has two device numbers associated with it. The major device number tells the operating system which device driver to use when the device is referenced. For example, a disk drive might have major device number 23, and a tape drive might have major device number 47. Whenever a reference is made to a file on the disk, the operating system looks up number 23 in a table and then uses the disk device driver to access the requested data. The minor device number is passed to the device driver. This number tells the device driver which physical device to use in the case of a driver that handles multiple devices, or how to access a device, in the case of devices like tape drives that support multiple densities. Several devices (such as all of the disks connected to the system) can have the same major device number, since they are all accessed with the same device driver, but they each need a different minor device number.
I-numbers, the I-list, and I-nodes
As mentioned earlier, directories provide the mapping between the names of files and the files themselves. Each directory file contains a series of structures that perform this mapping. Each structure contains the name of a file and a pointer to the file itself. The pointer is in the form of an integer called an i-number (for index number). When a file is accessed, the i-number is used as an index into a system table (the i-list) where the entry for the file (the i-node) is stored. The i-node contains all the information about a file:
 •The user ID and group ID of the file's owner
 •The protection bits for the file, specifying who can access it and in what modes
 •The physical disk addresses of the data blocks that contain the file's contents
 •The size of the file, in bytes
 •The last time the file was modified (written), and the last time the file was accessed (read)
 •The last time the file's i-node was changed (for example, the last time the permission bits were changed)
 •A tag indicating the file's type (regular file, directory, character special file, and so on)
One piece of information about a file is not stored in the i-node: the file's name. This information is stored in the directory file for the directory that contains the file, and nowhere else.
The operating system maintains a separate i-list for each mounted filesystem. I-numbers are unique within each removable filesystem, but when several filesystems are mounted, the i-number alone is not enough to distinguish a file uniquely.
Recall that each special file has two device numbers associated with it: a major device number and a minor device number. Because a filesystem is associated with a disk drive, it is also associated with a special file. Each disk drive is unique, so it must have a unique major and minor device number pair. Therefore, you can use three numbers (major device number, minor device number, i-number) to uniquely specify each file in the overall filesystem.
Other File Types
There are several other file types available in the UNIX filesystem besides the three basic types already presented.
Hard links
It is possible to have more than one name refer to the same file by making a hard link to that file. The link is created by making a new entry in a directory file with the new name and the i-number for the file. There can be any number of links to the same file; every link has a different name, but the same i-number. However, because a hard link only uses the i-number of the file, it is impossible to make a hard link across two filesystems. Hard links must all reside on the same filesystem. It is possible, though, for the links to reside in different directories on that filesystem.
Symbolic links
In 4.2BSD, a new type of file, called a symbolic link, was introduced to solve the problem of linking across filesystem boundaries. A symbolic link is a special file type that contains the pathname of the file to which the link points. The pathname can either be an absolute pathname, in which case the link's target is located from the root of the filesystem, or a relative pathname, in which case the link's target is located relative to the directory that contains the link's source. Because i-numbers are not involved in symbolic links, they can be used to make links across filesystem boundaries.
FIFOs
A FIFO (first-in, first-out), also called a named pipe, is a special type of file used for interprocess communication. A program creates a FIFO in the filesystem using a special library routine. After the FIFO is created, other processes can open it, read from it, and write to it just as if it were a regular file. However, whenever a read is performed, the data is transferred from the process owning the FIFO, not from the disk. Whenever a write is performed, the data are transferred to the process owning the FIFO, not to the disk. When the process that created the FIFO exits, the FIFO can no longer be opened or used. However, it remains as an entry in the filesystem until it is explicitly removed. FIFOs were introduced in System V UNIX and are often not available in BSD-derived systems.
UNIX-domain sockets
A UNIX-domain socket serves more or less the same function as a FIFO, in that it is created by a process and results in an entry in the filesystem. After the socket is created, other programs can communicate with the process that created the socket. However, unlike a FIFO, which preserves the open/read/write conventions of regular files, UNIX-domain sockets require a special set of system calls (the same set of system calls used for intermachine communication over Internet-domain sockets). UNIX-domain sockets were introduced in BSD UNIX and are often not available in System V-derived systems.

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback