So what exactly is the difference between a char device and this filesystem node?
I interpret this question as "what is the difference between a character device driver and the character device file?"
A character device driver is kernel software that operates on a stream of bytes, usually to communicate with some piece of hardware that also operates on a stream of bytes.
A character device file is a file on the filesystem. Device files have metadata associated with them that the kernel uses to know the character device driver with which the file is associated. Character device files (actually, all device files) have two pieces of metadata: the major and minor device numbers. You can see the major/minor numbers when you look the output of ls -l
. Consider, for example, the character device file /dev/null
:
$ ls -l /dev/null
crw-rw-rw- 1 root root 1, 3 Jun 6 14:30 /dev/null
Notice here the 1, 3
after the second root -- that's the major device number (1) and the minor device number (3). When a process interacts a device file, the kernel uses the major device number to know which kernel device driver handles I/O against the file. Character devices with major number 1 are associated with memory devices; see major.h
:
./include/uapi/linux/major.h:#define MEM_MAJOR 1
A single device driver can often "drive" multiple devices; the minor device number tells the kernel the specific device on which the user is operating. For example, the following character device files all have the same major number, but different minor numbers:
# ls -l /dev/zero /dev/mem /dev/null /dev/full /dev/random /dev/urandom /dev/kmsg
crw-rw-rw- 1 root root 1, 7 Jun 6 14:30 /dev/full
crw-r--r-- 1 root root 1, 11 Jun 6 14:30 /dev/kmsg
crw-r----- 1 root kmem 1, 1 Jun 6 14:30 /dev/mem
crw-rw-rw- 1 root root 1, 3 Jun 6 14:30 /dev/null
crw-rw-rw- 1 root root 1, 8 Jun 6 14:30 /dev/random
crw-rw-rw- 1 root root 1, 9 Jun 6 14:30 /dev/urandom
crw-rw-rw- 1 root root 1, 5 Jun 6 14:30 /dev/zero
The following source snippets come from Linux 5.4.32, file drivers/char/mem.c
.
From the ls
output above, we observe that all of those files have major device number 1. From that we know that the same kernel device driver responds to I/O requests to any process opening/reading/writing those files. From the kernel sources we see that the memory device driver is responsible for handling I/O against all those files:
static const struct memdev {
const char *name;
umode_t mode;
const struct file_operations *fops;
fmode_t fmode;
} devlist[] = {
#ifdef CONFIG_DEVMEM
[1] = { "mem", 0, &mem_fops, FMODE_UNSIGNED_OFFSET },
#endif
#ifdef CONFIG_DEVKMEM
[2] = { "kmem", 0, &kmem_fops, FMODE_UNSIGNED_OFFSET },
#endif
[3] = { "null", 0666, &null_fops, 0 },
#ifdef CONFIG_DEVPORT
[4] = { "port", 0, &port_fops, 0 },
#endif
[5] = { "zero", 0666, &zero_fops, 0 },
[7] = { "full", 0666, &full_fops, 0 },
[8] = { "random", 0666, &random_fops, 0 },
[9] = { "urandom", 0666, &urandom_fops, 0 },
#ifdef CONFIG_PRINTK
[11] = { "kmsg", 0644, &kmsg_fops, 0 },
#endif
};
Notice that the array indices --- the numbers in brackets --- match up with the minor device numbers on the associated files.
Now, let's consider an example where a process uses one of the character device files. If we have a shell script that contains:
echo "hello" > /dev/null
Then the script open()
s the character device file /dev/null
. The kernel knows that /dev/null
is a character device and examines the major and minor device number associated with the file. It sees major number 1, so it routes the open()
request to the character device driver that handles operations on major number 1 (the memory device). That ends up in the the function in the memory device driver that handles open calls:
static int memory_open(struct inode *inode, struct file *filp)
{
int minor;
const struct memdev *dev;
minor = iminor(inode);
if (minor >= ARRAY_SIZE(devlist))
return -ENXIO;
dev = &devlist[minor];
if (!dev->fops)
return -ENXIO;
filp->f_op = dev->fops;
filp->f_mode |= dev->fmode;
if (dev->fops->open)
return dev->fops->open(inode, filp);
return 0;
}
The memory_open()
function then uses the minor device number to index into the devlist
array that we saw earlier. If that device has a special open()
function, then it calls that, otherwise it just returns 0; for the null
device there is no special open()
function.
Eventually, the process will call write()
to write "hello" to the file descriptor associated with the open file. Again, the kernel knows that the open file is associated with a character device with major number 1 and minor number 3, so it routes the write()
to the driver for major device type 1 (the memory device). The device with minor number 3 has a set of functions registered to handle I/O (here, null_fops
):
[3] = { "null", 0666, &null_fops, 0 },
The null_fops
struct contains the following pointers to functions:
static const struct file_operations null_fops = {
...
.write = write_null,
...
};
So a write()
to a character device file with major number 1, minor number 3 will result in a call to write_null()
. The implementation of that function is:
static ssize_t write_null(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
return count;
}
The write_null()
function does nothing, and returns count
to indicate that count
bytes were successfully written (the behavior that we expect from writing to /dev/null
).
To summarize, character device files contain metadata: the major and minor device numbers. When processes perform I/O on character device files, the kernel uses that metadata to find the right character device driver in the kernel to handle the I/O requests made against the file.
Char devices are accessed by means of filesystem nodes
Just as files are accessed by filename. – Eduardo Trápani Jun 21 '20 at 17:24/dev/ttyS0
and/dev/tty0
seem so similar. How is one a char device while the other is a filesystem node? What is the point of difference between them? So I can'topen()
a char device but canopen()
a filesystem node? I mean both of them seem to offer almost exactly the same interface to userspace applications – Suraaj K S Jun 21 '20 at 17:35