Approach (high level):- Reserve a major/minor via alloc_chrdev_region (or register_chrdev_region for fixed numbers).- Initialize a struct cdev with cdev_init and add it with cdev_add.- Create a sysfs class and device so udev creates /dev/mychardev (class_create + device_create).- Implement struct file_operations with .open, .read, .write, .release (and .unlocked_ioctl if needed).- Use proper locking (mutex or spinlock) and safe memory allocation/free (kmalloc/kfree).- Provide module_init to perform setup and module_exit to undo in reverse order.Minimal skeleton (illustrative):c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#define DEV_NAME "mychardev"
static dev_t devt;
static struct cdev my_cdev;
static struct class *my_class;
static char *kbuf;
static DEFINE_MUTEX(my_lock);
static int my_open(struct inode *inode, struct file *filp){
return 0;
}
static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off){
ssize_t ret = 0;
if (mutex_lock_interruptible(&my_lock)) return -EINTR;
/* copy_to_user from kbuf */
if (copy_to_user(buf, kbuf, min(len, (size_t)strlen(kbuf)))) ret = -EFAULT;
else ret = min(len, (size_t)strlen(kbuf));
mutex_unlock(&my_lock);
return ret;
}
static ssize_t my_write(struct file *filp, const char __user *buf, size_t len, loff_t *off){
ssize_t ret = 0;
if (len > 1024) return -EINVAL;
if (mutex_lock_interruptible(&my_lock)) return -EINTR;
if (copy_from_user(kbuf, buf, len)) ret = -EFAULT;
else ret = len;
mutex_unlock(&my_lock);
return ret;
}
static int my_release(struct inode *inode, struct file *filp){ return 0; }
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_release,
};
static int __init my_init(void){
if (alloc_chrdev_region(&devt, 0, 1, DEV_NAME) < 0) return -1;
cdev_init(&my_cdev, &fops);
if (cdev_add(&my_cdev, devt, 1) < 0) goto unregister;
my_class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(my_class)) goto del_cdev;
device_create(my_class, NULL, devt, NULL, DEV_NAME);
kbuf = kmalloc(1024, GFP_KERNEL);
if (!kbuf) goto destroy_class;
return 0;
destroy_class:
class_destroy(my_class);
device_destroy(my_class, devt);
del_cdev:
cdev_del(&my_cdev);
unregister:
unregister_chrdev_region(devt, 1);
return -ENOMEM;
}
static void __exit my_exit(void){
kfree(kbuf);
device_destroy(my_class, devt);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(devt, 1);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
Key explanations & pitfalls:- Order matters: create device after cdev_add; on unload reverse order and handle partial failures in init.- Memory: always free kmalloc in exit and on init failure paths to avoid leaks; prefer kzalloc for zeroed memory.- Concurrency: protect shared state with mutex for sleeping contexts; use spinlock for IRQ context and be careful with copy_to/from_user (must be called in process context).- Blocking: do not sleep in atomic contexts; avoid calling copy_to_user while holding spinlocks.- User pointers: always validate and use copy_to_user/copy_from_user to avoid page faults.- Reference counts: use module_put/get if exporting symbols; ensure file->private_data cleaned on release.- Device numbers: consider dynamic alloc (alloc_chrdev_region) to avoid conflicts; store major/minor for logging.- Error handling: on init failure undo any partial setup to avoid orphan devices.