Linux下手动/自动创建设备节点

文章目录

前言

本文的主要内容是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

这里的主设备号一定要和动态分配的相同。
Linux下手动/自动创建设备节点
然后使用如下命令发现/dev/test已经生成了。

ls /dev/test

再运行app文件,就打印了我们想要的信息了。
注意,驱动卸载后虽然app不能再成功运行了,但是咱们创建的/dev/test文件依然存在,记得删除掉。
Linux下手动/自动创建设备节点
这就是手动创建设备节点的过程,我们发现整个流程还是比较麻烦,下面来看自动创建的过程。


二、自动创建

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

结果如下图。
Linux下手动/自动创建设备节点
可以看到我们预先设置的类和设备文件都成功生成了。
运行app文件,也成功打印了我们预设的信息。
卸载驱动后再看类和设备,发现它们都随着驱动的卸载而删除了。
Linux下手动/自动创建设备节点
这就是自动创建设备节点的过程,我们发现要比手动创建省事多了!


总结

以上就是Linux下手动/自动创建设备节点的所有内容了,要深刻体会其中的关联与不同!

上一篇:Linux之字符驱动认识(一)(二)


下一篇:Linux驱动实践:你知道【字符设备驱动程序】的两种写法吗?