Almost all the files under
/dev are device files. Whereas reading and writing to a regular file stores data on a disk or other filesystem, accessing a device file communicates with a driver in the kernel, which generally in turn communicates with a piece of hardware (a hardware device, hence the name).
There are two types of device files: block devices (indicated by
b as the first character in the output of
ls -l), and character devices (indicated by
c). The distinction between block and character devices is not completely universal. Block devices are things like disks, which behave like large, fixed-size files: if you write a byte at a certain offset, and later read from the device at that offset, you get that byte back. Character devices are just about anything else, where writing a byte has some immediate effect (e.g. it's emitted on a serial line) and reading a byte also has some immediate effect (e.g. it's read from the serial port).
The meaning of a device file is determined by its number, not by its name (the name matters to applications, but not to the kernel). The number is actually two numbers: the major number indicates which driver is responsible for this device, and the minor number allows a driver to drive several devices¹. These numbers appear in the
ls -l listing, where you would normally find the file size. E.g.
brw-rw---- 1 root disk 8, 0 Jul 12 15:54 /dev/sda → this device is major 8, minor 0.
Some device files under
/dev don't correspond to hardware devices. One that exists on every unix system is
/dev/null; writing from it has no effect, and reading from it never returns any data. It's often convenient in shell scripts, when you want to ignore the output from a command (
>/dev/null) or run a command with no input (
</dev/null). Other common examples are
/dev/zero (which returns null bytes ad infinitum)
/dev/urandom (which returns random bytes ad infinitum).
A few device files have a meaning that depends on the process that accesses it. For example,
/dev/stdin designates the standard input of the current process; opening from has the same effect as duplicating standard input². Somewhat similarly,
/dev/tty designates the terminal to which the process is connected. Under Linux, nowadays,
/dev/stdin and friends are not implemented as character devices, but instead as symbolic links to a more general mechanism that allows every file descriptor to be referenced (as opposed to only 0, 1 and 2 under the traditional method); for example
/dev/stdin is a symbolic link to
/proc/self/fd/0. See How does /dev/fd relate to /proc/self/fd/?.
You'll find a number of symbolic links under
/dev. This can occur for historical reasons: a device file was moved from one name to another, but some applications still use the old name. For example,
/dev/scd0 is a symbolic link to
/dev/sr0 under Linux; both designate the first CD device. Another reason for symbolic links is organization: under Linux, you'll find your hard disks and partitions in several places:
/dev/sda1 and friends (each disk designated by an arbitrary letter, and partitions according to the partition layout),
/dev/disk/by-id/* (disks designated by a unique serial number),
/dev/disk/by-label/* (partitions with a filesystem, designated by a human-chosen label); and more. Symbolic links are also used when a generic device name could be one of several; for example
/dev/dvd might be a symbolic link to
/dev/sr0, or it might be a link to
/dev/sr1 if you have two CD readers and the second one is to be the default DVD reader.
Finally, there are a few other files that you might find under
/dev, for traditional reasons. You won't find the same on every system. On most unices,
/dev/log is a socket that programs use to emit log messages.
/dev/MAKEDEV is a script that creates entries in
/dev. On modern Linux systems, entries in
/dev/ are created automatically by udev, obsoleting
¹ This is actually no longer true under Linux, but this detail only matters to device driver writers.
² Duplicating a file descriptor means creating a new file descriptor that accesses the same file as the first. The corresponding system call is
open("/dev/stdin", flags) is the same as
dup(0), except that the new descriptor may be created with different access modes.