文章目录
前言
本文的主要内容是Linux下手动/自动创建设备节点。
一、手动创建
1.cdev结构体的简要介绍
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查看是否已生成相应的文件。
2.代码文件
1>.cdev.c文件
#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");
2>.Makefile文件
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
3>.app.c文件
#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;
}
3.执行结果
开发板接收到驱动文件和编译后的app文件后,先加载驱动动态分配主设备号,这时候执行app文件是不能成功打印的,因为/dev文件夹下还没有名为test的设备,需要使用下面的命令生成。
mknod /dev/test c 242 0
这里的主设备号一定要和动态分配的相同。
然后使用如下命令发现/dev/test已经生成了。
ls /dev/test
再运行app文件,就打印了我们想要的信息了。
注意,驱动卸载后虽然app不能再成功运行了,但是咱们创建的/dev/test文件依然存在,记得删除掉。
这就是手动创建设备节点的过程,我们发现整个流程还是比较麻烦,下面来看自动创建的过程。
二、自动创建
1.创建类
创建类的定义在/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);
第一个参数:类名称。
第二个参数:设备号。
2.代码文件
1>.autocdev.c文件
#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");
2>.Makefile文件
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
3>.app.c文件
#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;
}
3.执行结果
开发板接收到驱动文件和编译后的app文件后,先加载驱动。
通过如下命令查看所有类。
ls /sys/class
通过如下命令查看所有设备。
ls /dev
结果如下图。
可以看到我们预先设置的类和设备文件都成功生成了。
运行app文件,也成功打印了我们预设的信息。
卸载驱动后再看类和设备,发现它们都随着驱动的卸载而删除了。
这就是自动创建设备节点的过程,我们发现要比手动创建省事多了!
总结
以上就是Linux下手动/自动创建设备节点的所有内容了,要深刻体会其中的关联与不同!