本文的主要内容是Linux下手动/自动创建设备节点。
cdev结构体:描述字符设备的结构体,定义在/linux-4.1.15/include/linux/cdev.h中。
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
使用cdev_init函数初始化cdev结构体成员变量;
void cdev_init(struct cdev *, const struct file_operations *);
第一个参数:要初始化的cdev;
第二个参数:文件操作集。
使用cdev_add函数注册到内核。
int cdev_add(struct cdev *, dev_t, unsigned);
第一个参数: cdev的结构体指针;
第二个参数:设备号;
第三个参数:次设备号的数量。
删除/注销字符设备:
void cdev_del(struct cdev *);
字符设备注册完以后不会自动生成设备节点,需要使用mknod命令创建一个设备节点。
格式如下:
mknod 名称 类型 主设备号 次设备号
这里的类型一般为c,次设备号为0,主设备号是动态分配好的。
本例将使用如下命令进行创建。
mknod /dev/test c 242 0
注意这里的242是我动态生成的主设备号,每个人可能不太一样,执行完可以通过ls /dev/test查看是否已生成相应的文件。
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #define DEVICE_NUMBER 1 //设备数量 #define DEVICE_SNAME "stachardevice" //静态名称 #define DEVICE_DNAME "dynchardevice" //动态名称 #define DEVICE_MINOR_NUMBER 0 //动态请求的第一个次设备号,通常为0 static int major_num,minor_num; struct cdev cdev; module_param(major_num,int,S_IRUSR); //主设备号 module_param(minor_num,int,S_IRUSR); //次设备号 int chrdev_open(struct inode *inode,struct file *file) { printk("chrdev_open!\n"); return 0; } struct file_operations chrdev_ops = { .owner = THIS_MODULE, .open = chrdev_open }; static int hello_init(void) { dev_t dev_num; int ret; if(major_num) //传入了主设备号用静态的方法 { printk("major_num = %d\n",major_num); //打印主设备号 printk("minor_num = %d\n",minor_num); //打印次设备号 dev_num = MKDEV(major_num,minor_num); //主次设备号合成一个dev_t类型 ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME); //静态分配 if(ret < 0) { printk("register_chrdev_region error!\n"); } printk("register_chrdev_region ok!\n"); } else //没有传入主设备号用动态的方法 { ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,DEVICE_NUMBER,DEVICE_DNAME); //动态分配 if(ret < 0) { printk("allo_chrdev_region error!\n"); } printk("allo_chrdev_region ok!\n"); major_num = MAJOR(dev_num); //从dev_t中分离出主设备号 minor_num = MINOR(dev_num); //从dev_t中分离出次设备号 printk("major_num = %d\n",major_num); //打印主设备号 printk("minor_num = %d\n",minor_num); //打印次设备号 } cdev.owner = THIS_MODULE; cdev_init(&cdev,&chrdev_ops); //初始化cdev cdev_add(&cdev,dev_num,DEVICE_NUMBER); //注册到内核 return 0; } static int hello_exit(void) { unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER); //注销,申请几个注销几个 cdev_del(&cdev); //删除cdev printk("unregister_chrdev_region ok!\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
obj-m += cdev.o KDIR:=/linux/linux-4.1.15 PWD?=$(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean
#include "stdio.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "unistd.h" int main(int argc, char *argv[]) { int fd; char buf[64] ={0}; fd = open("/dev/test", O_RDWR); //指定打开/dev/test,打开成功则打印"chrdev_open!" if(fd < 0) { perror("open error!\n"); //相当于printf("open error!\n"); return fd; } return 0; }
开发板接收到驱动文件和编译后的app文件后,先加载驱动动态分配主设备号,这时候执行app文件是不能成功打印的,因为/dev文件夹下还没有名为test的设备,需要使用下面的命令生成。
mknod /dev/test c 242 0
这里的主设备号一定要和动态分配的相同。
然后使用如下命令发现/dev/test已经生成了。
ls /dev/test
再运行app文件,就打印了我们想要的信息了。
注意,驱动卸载后虽然app不能再成功运行了,但是咱们创建的/dev/test文件依然存在,记得删除掉。
这就是手动创建设备节点的过程,我们发现整个流程还是比较麻烦,下面来看自动创建的过程。
创建类的定义在/linux-4.1.15/include/linux/device.h中。
#define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })
删除类:
extern void class_destroy(struct class *cls);
在/sys/class目录下可以查看创建的类。
在类下创建设备:
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
device_create是个可变参数函数。
第一个参数:设备注册在该类下。
第二个参数:父设备,一般为NULL。
第三个参数:设备号。
第四个参数:设备可能会使用的数据,一般为NULL。
第五个参数:设备名字,生成后会在/dev目录下。
其返回值就是创建好的设备。
删除/注销设备:
extern void device_destroy(struct class *cls, dev_t devt);
第一个参数:类名称。
第二个参数:设备号。
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #include <linux/device.h> #define DEVICE_NUMBER 1 //设备数量 #define DEVICE_SNAME "stachardevice" //静态名称 #define DEVICE_DNAME "dynchardevice" //动态名称 #define DEVICE_MINOR_NUMBER 0 //动态请求的第一个次设备号,通常为0 #define DEVICE_CLASS_NAME "chrdev_class" //创建的类名称 #define DEVICE_NODE_NAME "chrdev_node" //创建的节点名称 static int major_num,minor_num; dev_t dev_num; struct cdev cdev; struct class *class; struct device *device; module_param(major_num,int,S_IRUSR); //主设备号 module_param(minor_num,int,S_IRUSR); //次设备号 int chrdev_open(struct inode *inode,struct file *file) { printk("chrdev_open!\n"); return 0; } struct file_operations chrdev_ops = { .owner = THIS_MODULE, .open = chrdev_open }; static int hello_init(void) { int ret; if(major_num) //传入了主设备号用静态的方法 { printk("major_num = %d\n",major_num); //打印主设备号 printk("minor_num = %d\n",minor_num); //打印次设备号 dev_num = MKDEV(major_num,minor_num); //主次设备号合成一个dev_t类型 ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME); //静态分配 if(ret < 0) { printk("register_chrdev_region error!\n"); } printk("register_chrdev_region ok!\n"); } else //没有传入主设备号用动态的方法 { ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,DEVICE_NUMBER,DEVICE_DNAME); //动态分配 if(ret < 0) { printk("allo_chrdev_region error!\n"); } printk("allo_chrdev_region ok!\n"); major_num = MAJOR(dev_num); //从dev_t中分离出主设备号 minor_num = MINOR(dev_num); //从dev_t中分离出次设备号 printk("major_num = %d\n",major_num); //打印主设备号 printk("minor_num = %d\n",minor_num); //打印次设备号 } cdev.owner = THIS_MODULE; cdev_init(&cdev,&chrdev_ops); //初始化cdev cdev_add(&cdev,dev_num,DEVICE_NUMBER); //注册到内核 class = class_create(THIS_MODULE,DEVICE_CLASS_NAME); //注册类 device = device_create(class,NULL,dev_num,NULL,DEVICE_NODE_NAME); //注册设备 return 0; } static int hello_exit(void) { unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER); //注销,申请几个注销几个 cdev_del(&cdev); //注销cdev device_destroy(class,dev_num); //注销设备 class_destroy(class); //注销类 printk("unregister_chrdev_region ok!\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
obj-m += autocdev.o KDIR:=/linux/linux-4.1.15 PWD?=$(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean
#include "stdio.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "unistd.h" int main(int argc, char *argv[]) { int fd; char buf[64] ={0}; fd = open("/dev/chrdev_node", O_RDWR); //这里的chrdev_node设备是在加载时就生成的 if(fd < 0) { perror("open error!\n"); return fd; } return 0; }
开发板接收到驱动文件和编译后的app文件后,先加载驱动。
通过如下命令查看所有类。
ls /sys/class
通过如下命令查看所有设备。
ls /dev
结果如下图。
可以看到我们预先设置的类和设备文件都成功生成了。
运行app文件,也成功打印了我们预设的信息。
卸载驱动后再看类和设备,发现它们都随着驱动的卸载而删除了。
这就是自动创建设备节点的过程,我们发现要比手动创建省事多了!
以上就是Linux下手动/自动创建设备节点的所有内容了,要深刻体会其中的关联与不同!