Linux Kernel Character Module (Part II)
1. Registering Device Numbers
In the Linux kernel, device numbers are the core identifiers for character devices and block devices, consisting of a major device number and a minor device number. Device number management is fundamental to driver development, and below I will analyze the Linux device number registration mechanism in detail.
1.1 Device Number Structure
typedef u32 dev_t; // 32-bit device number• Major device number: high 12 bits (0-4095), identifies device type/driverMinor device number: low 20 bits (0-1,048,575), identifies specific device instance
#define MAJOR(dev) ((dev) >> 20) // Extract major device number
#define MINOR(dev) ((dev) & 0xfffff) // Extract minor device number
#define MKDEV(ma,mi) (((ma) << 20) | (mi)) // Combine device number
1.2 Device Number Registration Methods
Static Registration
Applicable Scenarios: Specify device number when the available device number is known.
int register_chrdev_region(dev_t first, unsigned count, const char *name);
Parameter Description:
- •
<span>first</span>: Starting device number (generated using<span>MKDEV</span>). - •
<span>count</span>: Number of consecutive device numbers. - •
<span>name</span>: Device name (displayed in<span>/proc/devices</span>).
Workflow:
- 1. Check if the device number range is available.
- 2. Add the device number to the kernel device number mapping table.
- 3. Create an entry in
<span>/proc/devices</span><span>.</span>
Dynamic Registration
Applicable Scenarios: Automatically allocate, suitable for avoiding device number conflicts.
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
Parameter Description:
- •
<span>dev</span>: Output parameter, returns the allocated device number. - •
<span>baseminor</span>: Requested starting minor device number. - •
<span>count</span>: Number of device numbers. - •
<span>name</span>: Device name.
Allocation Strategy:
- 1. Attempt to allocate from the range 234-255 (traditional dynamic allocation area).
- 2. If it fails, try the entire range 1-4095.
- 3. Use a red-black tree for efficient searching of free device numbers.
2. Kernel Implementation Mechanism
1. Device Number Management Data Structure
struct char_device_struct {
struct list_head list; // List node
dev_t dev; // Device number
unsigned int major; // Major device number
unsigned int baseminor; // Starting minor device number
int minorct; // Number of minor device numbers
const char *name; // Device name
struct file_operations *fops; // File operations
struct cdev *cdev; // Associated cdev structure
};
2. Core Logic of Registration Process
static int __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) {
struct char_device_struct *cd;
// Check device number range validity
if (major >= CHRDEV_MAJOR_MAX)
return -EINVAL;
// Dynamic allocation: find free major device number
if (major == 0) {
for (m = CHRDEV_MAJOR_DYN_END; m > 0; m--) {
if (!find_chrdev_region(m, baseminor, minorct))
break;
}
// ... allocation logic
}
// Create new device entry
cd = kmalloc(sizeof(*cd), GFP_KERNEL);
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
cd->name = name;
// Add to global list
list_add(&cd->list, &chrdevs);
return 0;
}
3. Device Number Lifecycle Management
1. Registration Phase
dev_t dev;
int ret;
// Static registration
dev = MKDEV(250, 0);
ret = register_chrdev_region(dev, 1, "mydev");
// Dynamic registration
ret = alloc_chrdev_region(&dev, 0, 1, "mydev");
major = MAJOR(dev);
minor = MINOR(dev);
2. Unregistration Phase
void unregister_chrdev_region(dev_t from, unsigned count) {
struct char_device_struct *cd = find_chrdev_region(from);
if (cd) {
// Remove from list
list_del(&cd->list);
// Free associated cdev
if (cd->cdev)
cdev_del(cd->cdev);
// Free memory
kfree(cd);
}
}
4. Usage Methods
- 1. Device Number Selection Strategy:
- • Static Allocation: Preferably use 240-254 (IANA recommended dynamic allocation area).
- • Avoid Using: 0 (special meaning), 1-9 (traditional devices).
ret = register_chrdev_region(dev, 1, "mydev");
if (ret < 0) {
// EBUSY: Device number already occupied
// EINVAL: Invalid parameter
pr_err("Registration failed: %d\n", ret);
return ret;
}
- 3. Multi-Device Management:
// Register multiple consecutive device numbers
alloc_chrdev_region(&dev, 0, NUM_DEVICES, "multi_dev");
for (i = 0; i < NUM_DEVICES; i++) {
cdev_init(&cdev[i], &fops);
cdev_add(&cdev[i], MKDEV(major, minor + i), 1);
}
Code
#include <linux/init.h> // Module initialization and exit macros
#include <linux/module.h> // Basic kernel module functionality
#include <linux/moduleparam.h> // Module parameter support
#include <linux/fs.h> // File system operations, including character device registration functions
#include <linux/kdev_t.h> // Device number operation macros (e.g., MKDEV/MAJOR/MINOR)
static int major = 0; // Static declaration of major device number (default 0 indicates dynamic allocation)
static int minor = 0; // Static declaration of minor device number (usually 0 indicates a single device)
dev_t dev_num; // Store the final generated device number (32 bits, high 12 bits major device number, low 20 bits minor device number)
// Declare module parameters (users can pass parameters via insmod)
module_param(major, int, S_IRUGO); // Register readable and writable major device number parameter (S_IRUGO indicates user-readable)
module_param(minor, int, S_IRUGO); // Register readable and writable minor device number parameter
// Module initialization function
static int __init module_param_init(void)
{
int ret; // Used to store function return value
// Check if static registration is used (major non-0)
if (major) {
// Combine user-specified major/minor device numbers into device number (dev_t type)
dev_num = MKDEV(major, minor);
// Print user-specified device number information (kernel log)
printk("major:%d\n", major);
printk("minor:%d\n", minor);
// Static register device number range (register 1 device)
ret = register_chrdev_region(dev_num, 1, "chardev_name");
if (ret) {
printk("register chardev region error: %d\n", ret);
return ret; // Return error code on registration failure
}
printk("register chrdev is ok!\n");
}
else { // Dynamic allocation of device number (major is 0)
// Request kernel to dynamically allocate device number (starting minor device number is minor, quantity is 1)
ret = alloc_chrdev_region(&dev_num, minor, 1, "alloc_chardev");
if (ret) {
printk("alloc register chrdev region error: %d\n", ret);
return ret; // Return error on allocation failure
}
else {
printk("alloc register chrdev is ok!\n");
// Extract major/minor device numbers from allocated device number
major = MAJOR(dev_num);
minor = MINOR(dev_num);
printk("alloc register major %d minor %d\n", major, minor);
}
}
return 0; // Initialization successful
}
// Module exit function
static void __exit module_param_exit(void)
{
// Release registered device number (regardless of static/dynamic registration, uniformly released)
unregister_chrdev_region(dev_num, 1);
printk("----chrdev end----\n");
}
// Specify module initialization and exit functions
module_init(module_param_init);
module_exit(module_param_exit);
// Module metadata
MODULE_LICENSE("GPL"); // Declare GPL license
MODULE_AUTHOR("XSX"); // Declare author
MODULE_DESCRIPTION("Module parameter example"); // Module functionality description
MODULE_VERSION("V1.0"); // Module version
Explanation
- 1. Device Number Management Mechanism:
- • Static Allocation: Specify device number via
<span>insmod module.ko major=250 minor=0</span>. - • Dynamic Allocation: When
<span>major=0</span>, allocated automatically by the kernel (check via<span>cat /proc/devices</span>).
- •
<span>register_chrdev_region()</span>: Pre-register known device numbers (ensure they are not occupied). - •
<span>alloc_chrdev_region()</span>: Dynamically request device numbers (avoid conflicts). - •
<span>unregister_chrdev_region()</span>: Uniformly release device number resources.
- •
<span>MKDEV(major, minor)</span>: Combine major and minor device numbers →<span>dev_t</span>. - •
<span>MAJOR(dev)</span>: Extract major device number from<span>dev_t</span>. - •
<span>MINOR(dev)</span>: Extract minor device number from<span>dev_t</span>.
- • All
<span>printk</span>outputs to the kernel log (check via<span>dmesg</span>). - • Clearly distinguish between success/failure information for static/dynamic registration.
Usage Examples
- 1. Dynamic Allocation of Device Number:
sudo insmod module.ko Output example: alloc register major 246 minor 0 ----chrdev end---- - 2. Static Allocation of Device Number:
sudo insmod module.ko major=250 minor=0 Output example: register chrdev is ok! ----chrdev end---- - 3. Unload Module:
sudo rmmod module Output example: ----chrdev end---- - 2. Device Model
The existence of the Linux device model is to address the critical challenges encountered by early kernels in managing and handling increasingly complex hardware and software components. Its core goal is to provide a unified, abstract, dynamic, and scalable way to describe and manage all devices and their relationships in the system.
Background
Unified Device Representation and Management
- • Problem: In early kernels, different types of devices (such as PCI, USB, platform devices, virtual devices) used their own independent mechanisms for registration, discovery, and management. This led to code duplication, structural confusion, and difficulty in implementing common functionality across device types.
- • Solution: The device model introduces core concepts (such as
<span>struct device</span>,<span>struct device_driver</span>,<span>struct bus_type</span>,<span>struct class</span>, and<span>struct kobject</span>) to provide a unified abstraction layer for all hardware devices (physical or virtual) and software entities (such as DMA engines). Kernel subsystems (such as power management, hot-plugging) can work based on these common interfaces without concern for the specific type of device.
Support for Dynamic Device Discovery and Hot-Plugging
- • Problem: Modern computer systems (especially servers and mobile devices) need to support hot-plugging of devices (such as USB, Thunderbolt, PCIe). The kernel needs a mechanism to dynamically perceive the addition and removal of devices and correspondingly load/unload drivers, allocate resources, and notify user space.
- • Solution: The device model is the foundation for hot-plugging support. Bus drivers are responsible for detecting device insertion/removal events and sending events (uevents) to user space (such as udev) through the core mechanisms of the device model (such as
<span>kobject_uevent</span><code><span>). User space tools (udev) dynamically load drivers, create device nodes, set permissions, etc., based on event information (device type, attributes, etc.). The device tree structure maintained by the device model allows the kernel to clearly know which devices are affected.</span>
Providing User Space Interfaces
- • Problem: User space programs (such as configuration tools, monitoring tools, udev) need a standardized way to query device information, status, configuration, and receive device event notifications.
- • Solution: The sysfs virtual file system is the direct embodiment of the device model in user space. It exposes kernel objects such as devices, drivers, buses, classes, and their attributes (such as device ID, power state, driver name, interrupt number, etc.) in the form of files and directories under
<span>/sys</span><span>. User space programs can obtain information or perform configurations by reading and writing these files. The uevent mechanism provides event notifications.</span>
Related Functions
Linux device management is mainly implemented through two structures: struct device and struct class. These two structures can be simply illustrated: if there are multiple ways to control LEDs in a development process, such as high/low level, PWM, I2S, etc., and each method is placed in different paths in the kernel, it is clearly not easy to manage. A unified folder named LED can be created during driver initialization to place different implementation methods, allowing the system to directly find the LED folder to manage different driver devices. The generation of this folder is implemented with the struct class structure, while the different driver implementation methods are implemented through struct device. The detailed analysis of these two structures is as follows:
struct class
- • Function: Represents a class of devices with similar functions or attributes.
- • Core Responsibilities: Group functionally similar devices (for example, all network interface cards belong to the
<span>net</span>class, all block devices belong to the<span>block</span>class, all TTY devices belong to the<span>tty</span>class, all input devices belong to the<span>input</span>class). - • Unified User Space Interface: Creates a directory for each class under
<span>/sys/class/</span><span> (e.g., </span><code><span>/sys/class/net/</span><span>). Each device belonging to this class will create a symbolic link in this directory pointing to its actual location under </span><code><span>/sys/devices/</span><span>. This provides a unified view for user space to access devices categorized by function, without concern for the specific bus location or physical connection of the device.</span> - • Class-Level Attributes: Attribute files can be defined in the class directory. These attributes will be “inherited” by all devices of that class. When user space reads or writes these attribute files, the kernel will call the callback functions provided by the class (
<span>class_attr_show</span>and<span>class_attr_store</span>). Drivers can also add device-specific attribute files in the symbolic links of devices in the class directory. - • Device Management: Maintains a list of devices belonging to that class.
- • Device Hot-Plug Events: When devices belonging to that class are added or removed, the class can define corresponding callback functions (such as
<span>dev_uevent</span>) to generate standard<span>uevent</span>events to notify user space (such as<span>udev</span>) to create device nodes, set permissions, or load firmware, etc. - • Key Members (Simplified Example):
struct class {
const char *name; // Class name (appears under /sys/class/)
struct module *owner; // Module owning this class
struct class_attribute *class_attrs; // Array of attributes for the class itself
const struct attribute_group **dev_groups; // Default attribute groups added to each device in the class
struct kobject *dev_kobj; // Points to kobject under /sys/dev/ (for creating device nodes)
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); // uevent callback
char *(*devnode)(struct device *dev, umode_t *mode); // Callback for creating device node path
void (*class_release)(struct class *class); // Class release callback
void (*dev_release)(struct device *dev); // Device release callback (can override device->release)
// ... other members, such as namespace support, parent class pointer, etc ...
};
Definition: The driver developer defines an instance of <span>struct class</span> (usually static) and fills in the necessary members, especially <span>name</span>.
Registration: Call <span>class_register()</span>. This step creates a directory named after <span>name</span> under <span>/sys/class/</span><span>.</span>
Create Device Interface (Optional but Common): Use <span>class_create()</span> macro or function. This is a convenience function that allocates a <span>struct class</span>, sets <span>name</span>, and calls <span>class_register()</span>. This is the most common way to create a class.
Associate Devices: When the driver registers a device (<span>struct device</span>), if the class pointer of that device is set (usually set during device initialization or driver probing), the kernel will automatically add that device to the device list of this class and create a symbolic link under <span>/sys/class/<classname>/</span><span> pointing to </span><code><span>/sys/devices/</span><span> ...</span>
Add Attributes: Use <span>class_create_file()</span> or <span>class_attr_create()</span> to add attribute files in the class directory. Use <span>device_create_file()</span> to add device-specific attribute files in the symbolic links of devices in the class directory.
Create Device Nodes (Optional): Classes usually work with <span>udev</span> (or <span>mdev</span>). When a device is registered and triggers a <span>uevent</span>, <span>udev</span> will create device nodes under <span>/dev/</span><span> based on rules (possibly using the </span><code><span>devnode</span> callback provided by the class). The driver can also directly call <span>device_create()</span> (the modern version of the internal call to <span>class_device_create()</span>), which will:– Create a device (<span>struct device</span>).– Associate the device with the specified class.– Trigger a <span>uevent</span> to let <span>udev</span> create device nodes.– Create a symbolic link for the device under <span>/sys/class/<classname>/</span><span>.</span>
Unregister: When the module is unloaded, call <span>class_unregister()</span> or use <span>class_destroy()</span> (paired with <span>class_create()</span>) to unregister the class. This will delete the class directory and all its contents under <span>/sys/class/</span><span>.</span>
struct device
- • Function: Represents a specific physical or logical device.
- • Core Responsibilities:
- • Device Abstraction: Represents a specific device instance in the kernel. This device can be physically present (such as USB devices, PCI devices, I2C devices, platform devices) or virtual (such as DMA channels, clock sources).
- • Device Hierarchy: Establishes parent-child relationships between devices through the
<span>parent</span>pointer (for example, a USB mouse is a child device of a USB port, which is a child device of a USB controller). This reflects the physical or logical connection relationships. - • Device Driver Binding: Contains a pointer to
<span>struct device_driver</span>(<span>driver</span>), indicating the driver currently controlling this device. - • Sysfs Interface: Each registered
<span>struct device</span>will create a corresponding directory under<span>/sys/devices/</span><span> in the kernel. This directory contains various attribute files of the device (such as </span><code><span>uevent</span><span>, </span><code><span>dev</span><span>, </span><code><span>subsystem</span><span>, </span><code><span>driver</span><span>, </span><code><span>power</span><span>, etc.), allowing user space to query device status, configure, or trigger events (such as hot-plugging).</span> - • Device Resource Management: Can associate resource information of the device (such as memory-mapped I/O addresses, interrupt numbers, DMA channels, etc.).
- • Device Lifecycle Management: Provides interfaces for registration (
<span>device_register()</span><span> / </span><code><span>device_add()</span><span>) and unregistration (</span><code><span>device_unregister()</span><span> / </span><code><span>device_del()</span><span>) to manage the creation and destruction of devices.</span> - • Power Management: Participates in system power management operations (such as suspend, resume).
- • Device Namespace: Supports the concept of device namespaces.
- • Key Members (Simplified Example):
struct device {
struct device *parent; // Pointer to parent device
struct device_private *p; // Internal private data (includes kobject, driver pointer, etc.)
struct kobject kobj; // Embedded kobject, provides sysfs representation and reference counting
const char *init_name; // Initial name of the device
const struct device_type *type; // Device type
struct bus_type *bus; // Bus type to which the device belongs (platform, PCI, USB, I2C, SPI, etc.)
struct device_driver *driver; // Driver bound to this device
void *platform_data; // Platform-specific data
void *driver_data; // Driver private data pointer
struct device_node *of_node; // Associated device tree node (Open Firmware)
// ... many other members, such as power management, DMA operations, class pointer, etc ...
};
Allocation: Typically, a <span>struct device</span> (or its more specific substructures, such as <span>struct platform_device</span>, <span>struct pci_dev</span>, etc.) is allocated by the bus core, platform code, or specific driver.
Initialization: Set necessary fields such as <span>parent</span>, <span>bus</span>, <span>init_name</span>, or <span>of_node</span> (for device trees).
Registration: Call <span>device_register()</span> or <span>device_add()</span>. This step is crucial:– Adds the device to the kernel’s device list.– Creates corresponding directories and attribute files under <span>/sys/devices/</span><span>.</span><span>- Triggers the bus matching (</span><code><span>bus->match()</span><span>) process to try to find and bind a suitable driver (</span><code><span>struct device_driver</span><span>) for this device.</span><span>- If the device belongs to a class (</span><code><span>class</span>), it will also be added to the device list of that class, and a symbolic link will be created under <span>/sys/class/<classname>/</span><span> pointing to </span><code><span>/sys/devices/...</span><span>.</span>
Associate Devices: When the driver registers a device (<span>struct device</span>), if the class pointer of that device is set (usually set during device initialization or driver probing), the kernel will automatically add that device to the device list of this class and create a symbolic link under <span>/sys/class/<classname>/</span><span> pointing to </span><code><span>/sys/devices/...</span><span>.</span>
Add Attributes: Use <span>class_create_file()</span> or <span>class_attr_create()</span> to add attribute files in the class directory. Use <span>device_create_file()</span> to add device-specific attribute files in the symbolic links of devices in the class directory.
Create Device Nodes (Optional): Classes usually work with <span>udev</span> (or <span>mdev</span>). When a device is registered and triggers a <span>uevent</span>, <span>udev</span> will create device nodes under <span>/dev/</span><span> based on rules (possibly using the </span><code><span>devnode</span> callback provided by the class). The driver can also directly call <span>device_create()</span> (the modern version of the internal call to <span>class_device_create()</span>), which will:– Create a device (<span>struct device</span>).– Associate the device with the specified class.– Trigger a <span>uevent</span> to let <span>udev</span> create device nodes.– Create a symbolic link for the device under <span>/sys/class/<classname>/</span><span>.</span>
Unregister: When the module is unloaded, call <span>class_unregister()</span> or use <span>class_destroy()</span> (paired with <span>class_create()</span>) to unregister the class. This will delete the class directory and all its contents under <span>/sys/class/</span><span>.</span>
3. Basic File Operations for Character Devices
In the Linux kernel, everything is a file, and reading and writing to character modules is equivalent to reading and writing files. The related operations for character devices in Linux can be implemented through the struct cdev structure. Below is the implementation of read and write functionality for character modules, with the overall process illustrated in the following diagram.
struct cdev
In the Linux kernel, character devices (such as serial ports, keyboards, etc.) are represented by <span>struct cdev</span>. Its main functions include:
- • Binding Device Numbers to Device Operation Functions:
<span>struct cdev</span>associates device numbers with a specific set of device operation functions, allowing the kernel to manage devices through these operation functions. - • Managing Character Device Registration and Unregistration:
<span>struct cdev</span>provides functionality for registering and unregistering character devices, ensuring lifecycle management of the device in kernel space. - • Providing Device Operation Interfaces: Through the
<span>file_operations</span>pointer, the kernel can access related file operation functions, such as open, read, write, etc. - • Core Representation of Character Devices in the Kernel:
<span>struct cdev</span>is the core data structure for managing character devices in the kernel. - • Simplified Version of struct cdev
struct cdev {
struct kobject kobj; // Embedded kobject for device model and reference counting
struct module *owner; // Pointer to the module owning the device (usually THIS_MODULE)
const struct file_operations *ops; // Pointer to the set of file operation functions
struct list_head list; // List node for adding the device to the character device list
dev_t dev; // Device number (major device number + minor device number)
unsigned int count; // Number of associated minor device numbers
};
Each character device requires a <span>struct cdev</span> to represent its state and operations in the kernel. The various fields in this structure work together to ensure that character devices can be correctly registered, managed, and provide necessary functional interfaces.
Key Members Explained
- • struct kobject kobj
Function: Provides representation of the device in sysfs and reference counting.Implement Device Lifecycle Management (Reference Counting): Used for device creation, deletion, and reference counting.Create Device Attributes in sysfs: Creates device attribute nodes in <span>/sys</span><span>.</span><strong><span>Support Device Hot-Plug Event Notifications</span></strong><span>: Responds to device addition and removal events.</span>
- • struct module owner
Function: Points to the module owning the device.Prevents Module from Being Unloaded While in Use: When the device file is opened, the reference count of the module is increased to prevent accidental unloading of the module.Module Reference Counting: The module can only be safely unloaded when all devices are closed.
const struct file_operations ops
- • Core Function: Defines the set of operation functions supported by the device.
- • Typical Operations:
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 *); // Other operations: ioctl, mmap, poll, etc. };
struct list_head list
Function: List node for adding the device to the global character device list.Management: The kernel manages all registered character devices through this list.
dev_t dev
Function: Stores the device number (major device number + minor device number).Operation Macros:
- •
<span>MAJOR(dev_t dev)</span>: Get the major device number. - •
<span>MINOR(dev_t dev)</span>: Get the minor device number. - •
<span>MKDEV(int major, int minor)</span>: Combine major and minor device numbers.
unsigned int countFunction: Indicates the number of consecutive minor device numbers used by this device.Example: If a driver manages 3 identical devices, set <span>count</span> to 3.
Lifecycle Management of struct cdev
Initialization
- • Static Initialization (Recommended):
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; } - • Dynamic Allocation (Less Common):
struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; }
Add to System
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
// 1. Set device number range
p->dev = dev;
p->count = count;
// 2. Add to global character device list
kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
// 3. Create device in sysfs
kobject_add(&p->kobj, &p->owner->mkobj.kobj, "%d:%d",
MAJOR(dev), MINOR(dev));
return 0;
}
Remove from System
void cdev_del(struct cdev *p)
{
// 1. Remove from global character device list
cdev_unmap(p->dev, p->count);
// 2. Delete device from sysfs
kobject_put(&p->kobj);
}
Code
#include <linux/init.h> // Module initialization and exit macros
#include <linux/module.h> // Basic kernel module support
#include <linux/moduleparam.h> // Module parameter support
#include <linux/fs.h> // File system related, including file operation structure definitions
#include <linux/kdev_t.h> // Device number related macros (e.g., MKDEV)
#include <linux/cdev.h> // cdev related structures and functions
#include <linux/device.h> // Device class related functions (class_create, device_create)
// Device number parameters
static int major = 0; // Major device number, 0 indicates dynamic allocation
static int minor = 0; // Minor device number
dev_t dev_num; // Complete device number (major + minor)
// Character device structure
struct cdev cdev_test; // Character device instance
// Device model structure
struct class *class; // Device class pointer
struct device *device; // Device node pointer
// Device open function
static int cdev_test_open(struct inode *inode, struct file *file)
{
printk("cdev file open!\r\n");
return 0;
}
// Device read function
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk("read cdev file ....\r\n");
return 0; // Return 0 indicates reaching the end of the file (EOF)
}
// Device write function
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk("write cdev file ....\r\n");
return size; // Return the number of bytes written, indicating success
}
// Device close/release function
static int cdev_test_release(struct inode *inode, struct file *file)
{
printk("release cdev file ....\r\n");
return 0;
}
// File operation structure - defines the operations supported by the device
static struct file_operations cdev_test_ops = {
.owner = THIS_MODULE, // Module owning this structure
.open = cdev_test_open, // Open device
.read = cdev_test_read, // Read device
.write = cdev_test_write, // Write device
.release = cdev_test_release // Close device
// Note: Actual drivers need to implement more operations like ioctl, llseek, etc.
};
// Module initialization function
static int module_param_init(void)
{
int ret;
// 1. Dynamically allocate device number (major device number allocated by the kernel)
ret = alloc_chrdev_region(&dev_num, // Return allocated device number
minor, // Requested starting minor device number
1, // Number of devices
"alloc_chardev"); // Device name
if (ret) {
printk("alloc register chrdev region error: %d\n", ret);
return ret;
}
printk("alloc register chrdev is ok!\n");
major = MAJOR(dev_num); // Extract major device number from device number
minor = MINOR(dev_num); // Extract minor device number from device number
printk("alloc register major %d minor %d\n", major, minor);
// 2. Initialize character device structure
cdev_test.owner = THIS_MODULE; // Set owning module
cdev_init(&cdev_test, // cdev structure to initialize
&cdev_test_ops); // File operation function set
// 3. Add character device to the system
ret = cdev_add(&cdev_test, // cdev to add
dev_num, // Device number
1); // Number of devices
if (ret) {
printk("cdev_add failed\n");
goto fail_cdev_add; // Jump to error handling
}
// 4. Create device class (create directory under /sys/class/)
class = class_create(THIS_MODULE, // Owning module
"test"); // Class name
if (IS_ERR(class)) {
ret = PTR_ERR(class);
printk("class_create failed\n");
goto fail_class_create; // Jump to error handling
}
// 5. Create device node (create device file under /dev/)
device = device_create(class, // Owning class
NULL, // Parent device (none)
dev_num, // Device number
NULL, // Driver data (none)
"test"); // Device name
if (IS_ERR(device)) {
ret = PTR_ERR(device);
printk("device_create failed\n");
goto fail_device_create; // Jump to error handling
}
return 0; // Initialization successful
// Error handling (release resources in reverse order of creation)
fail_device_create:
class_destroy(class); // Destroy device class
fail_class_create:
cdev_del(&cdev_test); // Delete character device
fail_cdev_add:
unregister_chrdev_region(dev_num, 1); // Release device number
return ret;
}
// Module exit function
static void module_param_exit(void)
{
// 1. Destroy device node (delete device file under /dev/)
device_destroy(class, dev_num);
// 2. Destroy device class (delete class directory under /sys/class/)
class_destroy(class);
// 3. Remove character device from the system
cdev_del(&cdev_test);
// 4. Release device number
unregister_chrdev_region(dev_num, 1);
printk("----chrdev end----\n");
}
// Specify module's initialization and exit functions
module_init(module_param_init);
module_exit(module_param_exit);
// Module information
MODULE_LICENSE("GPL"); // Module license
MODULE_AUTHOR("XSX"); // Author
MODULE_DESCRIPTION("Module parameter example"); // Description
MODULE_VERSION("V1.0"); // Version
Overall Process Diagram
Module Loading Phase
Device Operation Phase
Module Unloading Phase
Kernel Component Interaction Relationships
Kernel Data Interaction
Data operations between the user layer and kernel layer in Linux are mainly performed through the functions copy_from_user() and copy_to_user().
copy_from_user(): Copies data from user space to kernel space (user -> kernel).
copy_to_user(): Copies data from kernel space to user space (kernel -> user).
These two interfaces are used to transfer buffer data between the kernel and user processes and must be used with caution to avoid memory leaks, kernel crashes, or permission issues.
Prototype
#include <linux/uaccess.h>
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
Returns the number of bytes not copied. 0 indicates success, non-zero is usually considered an error (the kernel often returns -EFAULT).
Example
char *kbuf = kmalloc(len, GFP_KERNEL);
if (!kbuf) return -ENOMEM;
if (copy_from_user(kbuf, ubuf, len)) { kfree(kbuf); return -EFAULT; }
/* ... */
if (copy_to_user(ubuf, kbuf, len)) { kfree(kbuf); return -EFAULT; }
kfree(kbuf);
Code
Driver File
#include <linux/init.h> /* module init/exit macros */
#include <linux/module.h> /* module related (MODULE_*) */
#include <linux/moduleparam.h> /* module parameters (if needed) */
#include <linux/fs.h> /* file_operations, struct file, etc. */
#include <linux/kdev_t.h> /* MKDEV, MAJOR, MINOR */
#include <linux/cdev.h> /* cdev related */
#include <linux/device.h> /* class_create / device_create */
#include <linux/uaccess.h> /* copy_to_user / copy_from_user */
#include <linux/string.h> /* strlen, if using string functions in kernel */
/* Global variables: major and minor device numbers and device structure */
static int major = 0;
static int minor = 0;
dev_t dev_num; /* Used to save allocated device number */
struct cdev cdev_test; /* Character device structure */
struct class *class; /* Device class (for udev/sysfs) */
struct device *device; /* Device object (for creating /dev/test) */
/* open callback */
static int cdev_test_open(struct inode *inode, struct file *file)
{
printk("cdev file open!\r\n"); /* Simple log */
return 0; /* Return 0 indicates success */
}
/* read callback */
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk("read cdev file ....\r\n");
return 0; // Return 0 indicates reaching the end of the file (EOF)
}
/* write callback */
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk("write cdev file ....\r\n");
return size; // Return the number of bytes written, indicating success
}
/* release (close) callback */
static int cdev_test_release(struct inode *inode, struct file *file)
{
printk("release cdev file ....\r\n");
return 0;
}
/* file_operations structure, exported to kernel file system calls */
static struct file_operations cdev_test_ops = {
.owner = THIS_MODULE,
.open = cdev_test_open,
.read = cdev_test_read,
.write = cdev_test_write,
.release = cdev_test_release
// Note: Actual drivers need to implement more operations like ioctl, llseek, etc.
};
/* module initialization: register character device and create device node */
static int module_param_init(void)
{
int ret;
/* Allocate major/minor device number (dynamic allocation) */
ret = alloc_chrdev_region(&dev_num, minor, 1, "alloc_chardev");
if (ret) {
printk("alloc register chrdev region error: %d\n", ret);
return ret;
}
printk("alloc register chrdev is ok!\n");
major = MAJOR(dev_num);
minor = MINOR(dev_num);
printk("alloc register major %d minor %d\n", major, minor);
/* Initialize cdev and add to system */
cdev_test.owner = THIS_MODULE;
cdev_init(&cdev_test, &cdev_test_ops);
ret = cdev_add(&cdev_test, dev_num, 1);
if (ret) {
printk("cdev_add failed: %d\n", ret);
unregister_chrdev_region(dev_num, 1);
return ret;
}
/* Create device class and device node /dev/test (for udev to create device file) */
class = class_create(THIS_MODULE, "test");
if (IS_ERR(class)) {
pr_err("class_create failed\n");
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(class);
}
device = device_create(class, NULL, dev_num, NULL, "test");
if (IS_ERR(device)) {
pr_err("device_create failed\n");
class_destroy(class);
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(device);
}
return 0;
}
/* module exit: destroy device and unregister device number */
static void module_param_exit(void)
{
device_destroy(class, dev_num);
class_destroy(class);
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
printk("----chrdev end----\n");
}
module_init(module_param_init);
module_exit(module_param_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XSX");
MODULE_DESCRIPTION("Module parameter example");
MODULE_VERSION("V1.0");
Application Code
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
/* Simple device test program:
* - Open /dev/test (read/write)
* - Read fixed size data and print
* - Write data to the device
*/
int main(int argc, char *argv[])
{
/* Open device file, O_RDWR means read/write */
int fd = open("/dev/test", O_RDWR);
if (fd < 0) {
/* Use perror to print standard error message for easier problem localization */
perror("open /dev/test");
return fd;
}
/* Read buffer: here using fixed size read example */
char read_buf[32] = {0}; /* Initialize to 0, ensure NUL termination */
ssize_t r = read(fd, read_buf, sizeof(read_buf) - 1); /* Leave 1 byte for NUL */
if (r < 0) {
perror("read");
close(fd);
return 1;
}
/* r is the actual number of bytes read, ensure string is NUL terminated */
read_buf[r] = '\0';
printf("read data: %s (bytes=%zd)\n", read_buf, r);
/* Write buffer: only write actual string length, not the entire array */
char write_buf[32] = "helloworld!";
siz