ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2021-10-30 15:09:28  阅读:193  来源: 互联网

标签:手动 dev chrdev num cdev Linux DEVICE 节点 设备


文章目录

前言

本文的主要内容是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下手动/自动创建设备节点的所有内容了,要深刻体会其中的关联与不同!

标签:手动,dev,chrdev,num,cdev,Linux,DEVICE,节点,设备
来源: https://blog.csdn.net/weixin_42570192/article/details/121044148

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有