Adhering to high-quality original content, rejecting content piling, if you like it, click the star above to follow, and you will receive updates immediately, thank you for your attention!
Many students may find it confusing when they first encounter character device drivers, as they see different interfaces for character device drivers, some using <span>register_chrdev</span>, others using <span>alloc_chrdev_region</span>, and also <span>cdev_add</span>, <span>cdev_init</span>, etc. The confusion arises from not mastering the framework of character device drivers. In this section, I will organize the relevant concepts of character device drivers, and once you grasp the main framework, there will be no more questions.
Before we start, here is a flowchart:

1. <span>register_chrdev</span> Character Device Driver Framework and Testing Process
As usual, I will first present the complete code framework and testing process to give everyone an overall understanding, and then elaborate in detail later.
This set of code is only used to demonstrate the important components of the character device driver framework, and I do not recommend using it as a template in actual drivers, I will explain the specific reasons later.
In summary, what is the core of this process? It is to prepare the device number, device name, and device operation fops required by the kernel, and finally use the registration interface provided by the kernel to complete the registration, allowing the application layer to operate the device through the device file.
1.1 <span>register_chrdev</span> Driver Framework
This part focuses on two key points:
- Character device driver registration interface
<span>int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);</span>
Parameters:
- major: Device number, passing 0 means the kernel will automatically allocate
- name: Device name (will appear in /proc/devices).
- fops: Pointer to the file_operations structure.
<span>struct file_operations</span>This structure is provided for upper-layer applications to operate device files, and the implementation of the driver’s functionality is done in these interfaces. Since we are demonstrating, we have only added print statements to observe the calling process.
#include <linux/module.h>
#include <linux/string.h>
#define DEV_NAME "corechip_chardev_old"
static int major;
/* Framework functions */
static int corechip_chardev_open(struct inode *inode, struct file *file)
{
pr_info("corechip_chardev_open\n");
return 0;
}
static int corechip_chardev_release(struct inode *inode, struct file *file)
{
pr_info("corechip_chardev_release\n");
return 0;
}
static ssize_t corechip_chardev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
pr_info("corechip_chardev_read\n");
return 0;
}
static ssize_t corechip_chardev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
pr_info("corechip_chardev_write\n");
return 0;
}
static struct file_operations corechip_chardev_fops = {
.owner = THIS_MODULE,
.open = corechip_chardev_open,
.release = corechip_chardev_release,
.read = corechip_chardev_read,
.write = corechip_chardev_write,
};
static int __init corechip_chardev_init(void)
{
major = register_chrdev(0, DEV_NAME, &corechip_chardev_fops); // 0 means dynamically allocate the major device number
if (major < 0) {
pr_err("mychardev_old: register_chrdev failed\n");
return major;
}
pr_info("mychardev_old: loaded, major=%d\n", major);
return 0;
}
static void __exit corechip_chardev_exit(void)
{
unregister_chrdev(major, DEV_NAME);
pr_info("corechip_chardev_old: unloaded\n");
}
module_init(corechip_chardev_init);
module_exit(corechip_chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("corechip");
MODULE_DESCRIPTION("Char device skeleton - old register_chrdev API");
1.2 Application Testing Program
In the application program, we can operate the character device just like operating a file, and the file name is the device node we registered in our driver<span>DEV_NAME</span>.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
char buf[100];
printf("Testing character device driver...\n");
// Open device
fd = open("/dev/corechip_chardev_old", O_RDWR);
if (fd < 0) {
printf("Failed to open device, please load the driver and create the device node first\n");
return -1;
}
// Write data
printf("Writing data\n");
write(fd, "test", 4);
// Read data
printf("Reading data\n");
read(fd, buf, 100);
// Close device
close(fd);
printf("Testing completed\n");
return 0;
}
1.3 Testing Process

As shown in the figure above:
-
<span>insmod register_chrdev.ko</span>, load the character device driver, enter<span>corechip_chardev_init</span>, call<span>register_chrdev</span>, register the character device, bind the<span>fops</span>operation functions, and automatically allocate a device number -
Manually create the device node,
<span>mknod /dev/corechip_chardev_old c 511 0</span>, this command means:
-
Create a device node in the
<span>/dev</span>directory, the device node name must match the one defined in the device driver -
<span>c</span>indicates a character device -
<span>511</span>represents the major device number, which is automatically allocated, and I added print statements in the driver to see that the kernel allocated<span>511</span>for us -
<span>0</span>represents the minor device number
Run the application program, which opens the device node and performs <span>open read write</span> operations, and you can see that it calls the corresponding implementation in our registered <span>fops</span> structure
2. Character Device Driver Framework
After watching the above demonstration, everyone should have an intuitive understanding of the character device driver overall, and next we will explain the character device driver framework in detail.
The so-called framework allows developers to quickly integrate their software into the system without needing to understand the internal principles and details. Just like the code template we demonstrated, if you need to develop a character device driver, you actually do not need to understand the kernel, you just need to implement your <span>open/read/write</span> in the <span>file_operations</span>.
I summarize the key points of character device drivers as follows, and understanding their concepts and relationships will prevent confusion. We will explain from the top down.

2.1 Device Node
We all know that <span>Linux</span> is designed around the concept of files, device nodes are special files in the Linux/Unix file system, usually located in the <span>/dev</span> directory.
Device nodes are what users directly interact with and use, serving as a bridge between user space and the driver, it is the name of the device, and as long as the user knows the name, they can operate the device as a file through system calls <span>open/read/write/ioctl</span>.
2.2 Device Number
If the device node is a person’s name, then the device number is like an ID number. The kernel uses device numbers for driver operations. In Linux, each device node has a device number dev_t, which consists of two parts:
Major Number
-
Identifies which driver program handles this device file.
-
The VFS layer finds the registered
<span>cdev</span>or driver entry through the major number.
Minor Number
-
Identifies a specific device instance or logical sub-device under the same driver.
-
The driver can distinguish different hardware or functions through the minor number.
The kernel defines some major device numbers and device types, for example, the serial port device number is 4, and the SATA disk is 8. When a system has multiple serial ports and disk partitions, we will see <span>/dev/ttyS0</span>, <span>/dev/ttyS1</span>, <span>/dev/sda</span>, <span>/dev/sdb</span>, etc.
Thus, the major device number is used to distinguish device types, allowing the kernel to find the corresponding driver file, while the minor device number corresponds to specific device instances. Additionally, the minor device number can also be used in the driver to distinguish the functionality of devices.
Here, I would like to elaborate a bit, as some students may confuse the concepts of major and minor device numbers. For example, if an <span>I2C</span> bus has various temperature and humidity sensors, then <span>I2C</span> has one major device number, and the devices are assigned minor device numbers.
This understanding is incorrect; it is important to emphasize that everything discussed here is from the user’s perspective, and users will not interact with <span>I2C</span> or <span>SPI</span> controllers; they only need to care about whether the device node is <span>/dev/ttyS0</span> or <span>/dev/sda</span>, without needing to worry about whether the device is a block device or a character device, or how the hardware is connected via <span>USB</span>, <span>PCIe</span>, or <span>I2C</span>.
As for the major and minor device relationship of <span>I2C</span>, we will cover this in the <span>I2C BSP</span> section in the future.
Common macros for device numbers include:
<span>MKDEV(major, minor)</span>: Combine to form a device number <span>dev_t</span>
<span>MAJOR(dev_t)</span>: Extract the major device number
<span>MINOR(dev_t)</span>: Extract the minor device number
2.3 fops
<span>struct file_operations</span> is a structure defined by the kernel that describes all operation function pointers supported by the device file.<span>fops</span> is the core design that allows users to operate devices in the same way as any other file.
When user space performs operations like <span>open/read/write/ioctl</span> on <span>/dev/xxx</span>, the VFS (Virtual File System) finds the driver through the device number and then calls the corresponding function in <span>fops</span>.
Device driver engineers do not need to understand how <span>open/read/write</span> calls the function pointers in <span>fops</span>, they just need to fill in the specific functional implementation functions in <span>.open/.read/.write</span>.
#include <linux/fs.h>
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
// ... many optional functions
};
2.4 Registering Character Devices
When learning about the kernel and BSP, you often encounter the concept of registration, which means registering xxx with the kernel. What does it mean to register with the kernel? It is actually easy to understand; in our daily lives, when registering an account in a system, we need to provide what? Name, phone number, ID number, etc.
In simple terms, it is to let the kernel know how to find the device and what to do once it is found.
The above three points are the core data of the device driver, and finally, they need to be provided to the kernel. The kernel also provides interfaces for registering character device drivers, commonly seen in online materials are two types: <span>register_chrdev</span> and <span>alloc_chrdev_region</span>.
To conclude: Currently, character device drivers are recommended to use <span>alloc_chrdev_region()</span><span> + </span><code><span>cdev_init()</span><span> + </span><code><span>cdev_add()</span><span> interfaces.</span>
Have you noticed that I marked the core position of the architecture diagram with the <span>cdev</span> structure, but we did not involve <span>cdev</span> at all in the demonstration process?
<span>register_chrdev</span> comes from versions of Linux prior to 2.6, while the concept of <span>cdev</span> appeared after version 2.6. The introduction of <span>cdev</span> allows device drivers to integrate with the device model and unbind the device number from the <span>fops</span> registration operation.
For compatibility, the kernel internally implements <span>register_chrdev</span> with the addition of <span>cdev</span>, so <span>register_chrdev</span> actually implicitly uses <span>cdev</span>.

The early <span>register_chrdev()</span> was a one-stop interface that could complete the allocation of the major device number + registration of <span>file_operations</span> at once, making it very simple and intuitive to use, and very suitable for quickly understanding the character device driver framework. Therefore, even though it is no longer recommended, I still choose to explain this interface first.
We will cover the concept of <span>cdev</span> and the specific differences between these two interfaces in the next lesson, and after learning about <span>alloc_chrdev_region</span>, you will have a deeper understanding.
3. Summary
In this lesson, we explained the character device driver framework, and learning should focus on the main points rather than memorizing the process. The core of the process is to prepare the device number, device name, and fops required by the kernel, and provide them to the kernel through the registration interface.
If you find this helpful, please like, share, and recommend; it really means a lot to me, thank you very much!!!
– END –
If you have any questions, feel free to add me on WeChat for discussion. I have created a small group, and I will gradually add some industry leaders I know, all of whom are engineers from top companies at P7 level and above. Any questions related to the industry, work, or job-hopping can be discussed in the group~~

Self-introduction:Previously worked at AMD, currently a senior BSP engineer in a chip department at a major company.Advocating pragmatism, I believe in learning by doing.Series Introduction:The Essential Kernel Skills for BSP Engineers, aimed at explaining kernel knowledge and underlying principles that will be used by driver and BSP engineers in their work.Previous Recommendations:Ubuntu 24.04 + Qemu + ARMV8 + Linux 6.12 Comprehensive Development Environment Setup TutorialExclusive: Tesla BSP Position Interview Process Sharing and Detailed Explanation of Original Test Questions