本文参考:
https://www.cnblogs.com/lifexy/p/7506277.html
http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/
驱动学习2中每次都要手工创建设备节点,大家肯定也会觉得这样做太麻烦了。
可以使用自动创建设备节点,Linux有udev、mdev的机制,而我们的ARM开发板上移植的busybox有mdev机制,然后mdev机制会通过class类来找到相应类的驱动设备来自动创建设备节点 (前提需要有mdev)
接下来使用insmod自动创建设备节点, rmmod自动注销设备节点
(1)首先创建一个class设备类,class是一个设备的高级视图,它抽象出低级的实现细节,然后在class类下,创建一个class_device,即类下面创建类的设备:(在C语言中class就是个结构体)
同时设置了类名、设备节点名
#define DEVICE_NAME "hello" ///< The device will appear at /dev/ebbchar using this value #define CLASS_NAME "ebb" ///< The device class -- this is a character device driver
(2)在hello_init入口函数、hello_exit出口处修改:
static struct class* ebbcharClass = NULL; ///< The device-driver class struct pointer static struct device* ebbcharDevice = NULL; ///< The device-driver device struct pointer static int majorNumber; ///< Stores the device number -- determined automatically //register_chrdev作用:在VFS虚拟文件系统中找到字符设备,然后通过主设备号找到内核数组里对应位置,最后将设备名字和fops结构体填进去 static int hello_init(void) { /*如果设置major为0,表示由内核动态分配主设备号,函数的返回值是主设备号*/ majorNumber =register_chrdev (0, DEVICE_NAME, &hello_fops); //创建类,它会在sys/class目录下创建CLASS_NAME这个类 ebbcharClass = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(ebbcharClass)){ // Check for error and clean up if there is unregister_chrdev(majorNumber, DEVICE_NAME); printk(KERN_ALERT "Failed to register device class "); return PTR_ERR(ebbcharClass); // Correct way to return an error on a pointer } printk(KERN_INFO "EBBChar: device class registered correctly "); // Register the device driver ebbcharDevice = device_create(ebbcharClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME); if (IS_ERR(ebbcharDevice)){ // Clean up if there is an error class_destroy(ebbcharClass); // Repeated code but the alternative is goto statements unregister_chrdev(majorNumber, DEVICE_NAME); printk(KERN_ALERT "Failed to create the device "); return PTR_ERR(ebbcharDevice); } printk(KERN_INFO "EBBChar: device class created correctly "); // Made it! device was initialized return 0; } static void hello_exit(void) { device_destroy(ebbcharClass, MKDEV(majorNumber, 0)); // remove the device class_unregister(ebbcharClass); // unregister the device class class_destroy(ebbcharClass); // remove the device class //释放设备号、注销设备 unregister_chrdev(majorNumber,DEVICE_NAME); printk(KERN_EMERG "hello dev has been exit! "); //卸载驱动, 将major填入即可 }
(3)验证
重新编译insmod后,会发现在/dev下自动的创建了hello设备节点
其中在sys/class里有各种类的设备, 比如sys/class/ebb下就有hello设备
附完整代码:
//参考内核源码:gpio.c #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/fs.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("pp"); #define DEVICE_NAME "hello" ///< The device will appear at /dev/ebbchar using this value #define CLASS_NAME "ebb" ///< The device class -- this is a character device driver /////////////////////////dev////////////////////////////////////// static int hello_open(struct inode *inode, struct file *file){ printk(KERN_EMERG "hello open "); return 0; } static int hello_release(struct inode *inode, struct file *file){ printk(KERN_EMERG "hello release "); return 0; } /*设备文件的读函数中,参数filp为文件结构体指针,buf为用户空间内存地址,count为要读取的字节数,ppos为写的位置相对于文件开头的偏移*/ //copy_to_user 完成用户空间缓冲区到内核空间的复制 static ssize_t hello_read(struct file *filp, char __user *buf, size_t len,loff_t *ppos) { printk("hello_read "); return 0; } /*设备文件的写函数中,参数filp为文件结构体指针,buf为用户空间内存地址,count为要写入的字节数,ppos为写的位置相对于文件开头的偏移*/ //copy_from_user 完成内核空间到用户空间缓冲区的复制 static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t * ppos) { printk("hello_write "); return 0; } //ioctl函数 static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ printk("cmd is %d, arg is %d ", cmd, arg); return 0; } static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .read = hello_read, .write = hello_write, .release = hello_release, .unlocked_ioctl = hello_ioctl, }; static struct class* ebbcharClass = NULL; ///< The device-driver class struct pointer static struct device* ebbcharDevice = NULL; ///< The device-driver device struct pointer static int majorNumber; ///< Stores the device number -- determined automatically //register_chrdev作用:在VFS虚拟文件系统中找到字符设备,然后通过主设备号找到内核数组里对应位置,最后将设备名字和fops结构体填进去 static int hello_init(void) { /*如果设置major为0,表示由内核动态分配主设备号,函数的返回值是主设备号*/ majorNumber =register_chrdev (0, DEVICE_NAME, &hello_fops); //创建类,它会在sys/class目录下创建CLASS_NAME这个类 ebbcharClass = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(ebbcharClass)){ // Check for error and clean up if there is unregister_chrdev(majorNumber, DEVICE_NAME); printk(KERN_ALERT "Failed to register device class "); return PTR_ERR(ebbcharClass); // Correct way to return an error on a pointer } printk(KERN_INFO "EBBChar: device class registered correctly "); // Register the device driver ebbcharDevice = device_create(ebbcharClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME); if (IS_ERR(ebbcharDevice)){ // Clean up if there is an error class_destroy(ebbcharClass); // Repeated code but the alternative is goto statements unregister_chrdev(majorNumber, DEVICE_NAME); printk(KERN_ALERT "Failed to create the device "); return PTR_ERR(ebbcharDevice); } printk(KERN_INFO "EBBChar: device class created correctly "); // Made it! device was initialized return 0; } static void hello_exit(void) { device_destroy(ebbcharClass, MKDEV(majorNumber, 0)); // remove the device class_unregister(ebbcharClass); // unregister the device class class_destroy(ebbcharClass); // remove the device class //释放设备号、注销设备 unregister_chrdev(majorNumber,DEVICE_NAME); printk(KERN_EMERG "hello dev has been exit! "); //卸载驱动, 将major填入即可 } module_init(hello_init); module_exit(hello_exit);
应用程序测试代码:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <sys/ioctl.h> int main(int argc, char *argv[]) { int fd; printf("enter driver test %s %s ", argv[1], argv[2]); char *hello = "/dev/hello"; if((fd = open(hello, O_RDWR | O_NOCTTY | O_NDELAY)) < 0) { printf("open %s failed ", hello); } else { printf("%s fd is %d ", hello, fd); ioctl(fd, atoi(argv[1]), atoi(argv[2])); } close(fd); return 1; }