Linux驱动学习—字符设备驱动注册详解

1、申请字符类设备号

1.1 字符设备和杂项设备的区别

杂项设备的主设备号号是固定的,固定为10,而字符类设备需要自己或者系统分配主设备号。杂项设备可以自动生成设备节点,字符设备需要套我们自己生成设备节点。

1.2 注册字符类设备号的两个方法

1.2.1 静态分配一个设备号

第一种:静态分配一个设备号,使用的函数是:在include/linux/fs.h

extern int register_chrdev_region(dev_t, unsigned, const char *);//在fs.h
注意:使用该函数需要明确知道系统里面有哪些设备号没有用
参数:
第一个:设备号的起始值。类型是dev_t。
	dev_t在include/linux/types.h
	dev_t是用来保存设备号的,是一个32为数。高12位用来保存主设备号,低12位用来保存次设备号	
第二个:次设备号的个数。
第三个:设备的名称。
返回值:成功返回0,失败返回小于0。

Linux提供了几个宏定义来操作设备号:在include/linux/kdev_t.h

#define MINORBITS	20 //次设备号的位数,一共20位
define MINORMASK	((1U << MINORBITS) - 1) //此设备号的掩码
#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))//在dev_t里面获取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))//在dev_t里面获取次设备号
//将主设备号和次设备号组成一个dev_t类型,第一个参数是主设备号,第二个参数的次设备号。
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

1.2.2 动态分配一个设备号

第二种方法:动态分配,使用的函数是:

extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
使用动态分配会优先使用255到234。
参数:
第一个:保存生成的设备号。
第二个:请求的第一个次设备号,通常是0
第三个:连续申请的设备号个数。
第四个:设备名称。
返回值:成功返回0,失败返回负数。

1.3 如何注销设备号

extern void unregister_chrdev_region(dev_t, unsigned);//在include/linux/fs.h
参数:
第一个:分配设备号的起始地址
第二个:申请的连续设备号的个数

1.4 实验代码

#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/fs.h> //包含了文件操作相关的struct的定义
#include <linux/kdev_t.h>

static int major_num;//主设备号
static int minor_num;//次设备号

module_param(major_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h

module_param(minor_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h

#define DEVICE_NUMBER 1			//定义次设备号的个数
#define DEVICE_SNAME "schrdev"	//静态注册设备号的名称
#define DEVICE_ANAME "achrdev"	//动态注册设备号的名称
#define DEVICE_MINOR_NUMBER 0	//定义次设备号的起始地址

static int hello_init(void)
{
	int ret;
	dev_t dev_num;
	if(major_num && minor_num)
	{
		//静态注册设备号
		printk("major_num = %d\n",major_num);
		printk("minor_num = %d\n",minor_num);
		dev_num = MKDEV(major_num,minor_num);
		ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
		if(ret < 0)
		{
			printk("registr_chrdev_region error\n");
			return ret;
		}
		printk("registr_chrdev_region ok\n");
	}
	else
	{
		//动态注册设备号
		ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,1,DEVICE_ANAME);
		if(ret < 0)
		{
			printk("alloc_chrdev_region error\n");
			return ret;
		}
		major_num = MAJOR(dev_num);
		minor_num = MINOR(dev_num);
		printk("major_num = %d\n",major_num);
		printk("minor_num = %d\n",minor_num);
	}
	return 0;
}

static void  hello_exit(void)
{
	unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
	printk("hello_exit \n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

1.4.1 静态分配的方法

首先需要使用cat /proc/devices需要明确知道系统里面有哪些设备号没有用,发现9可以使用,加载ko的时候使用静态分配的方法向驱动传参:

cat /proc/devices看看设备号是否生成,可以看到已经生成:

1.4.2 动态分配的方法

加载驱动:

cat /proc/devices看看设备号是否生成,可以看到已经生成:

建议使用动态申请设备号。

2、注册字符类设备步骤及实现

2.1 杂项设备回顾

//在include/linux/miscdevice.h
extern int misc_register(struct miscdevice *misc);
extern void misc_deregister(struct miscdevice *misc);

2.2 cdev结构体——描述字符设备的一个结构体

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

2.3 注册一个字符类设备的步骤

2.3.1 步骤一:定义一个cdev结构体

2.3.2 步骤二:使用cdev_init函数初始化cdev结构体成员变量

void cdev_init(struct cdev *, const struct file_operations *);
参数一:要初始化的cdev
参数二:文件操作集
    cdev->ops = fops;//实际就是把文件操作集写给ops

2.2.3 步骤三:使用cdev_add函数注册到内核

int cdev_add(struct cdev *, dev_t, unsigned);
参数一:cdev的结构体指针
参数二:设备号
参数三:次设备号的数量

2.4 注销字符类设备

void cdev_del(struct cdev *);

2.5 实验代码

#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/fs.h> //包含了文件操作相关的struct的定义
#include <linux/kdev_t.h>
#include <linux/cdev.h>
​
static int major_num;//主设备号
static int minor_num;//次设备号
​
module_param(major_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
​
module_param(minor_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
​
#define DEVICE_NUMBER 1         //定义次设备号的个数
#define DEVICE_SNAME "schrdev"  //静态注册设备号的名称
#define DEVICE_ANAME "achrdev"  //动态注册设备号的名称
#define DEVICE_MINOR_NUMBER 0   //定义次设备号的起始地址
​
struct cdev cdev;
​
int chrdev_open(struct inode *inode, struct file *file)
{
    printk("chrdev_open \n");
    return 0;
}
​
struct file_operations chrdev_fops = {
    .owner = THIS_MODULE,
    .open  = chrdev_open
};
​
static int hello_init(void)
{
    int ret;
    dev_t dev_num;
    if(major_num && minor_num)
    {
        //静态注册设备号
        printk("major_num = %d\n",major_num);
        printk("minor_num = %d\n",minor_num);
        dev_num = MKDEV(major_num,minor_num);
        ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
        if(ret < 0)
        {
            printk("registr_chrdev_region error\n");
            return ret;
        }
        printk("registr_chrdev_region ok\n");
    }
    else
    {
        //动态注册设备号
        ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,1,DEVICE_ANAME);
        if(ret < 0)
        {
            printk("alloc_chrdev_region error\n");
            return ret;
        }
        major_num = MAJOR(dev_num);
        minor_num = MINOR(dev_num);
        printk("major_num = %d\n",major_num);
        printk("minor_num = %d\n",minor_num);
    }
​
    cdev.owner = THIS_MODULE;
    //cdev_init函数初始化cdev结构体成员变量
    cdev_init(&cdev,&chrdev_fops);
    //完成字符设备注册到内核
    cdev_add(&cdev,dev_num,DEVICE_NUMBER);
    
    return 0;
}
​
static void  hello_exit(void)
{
    //注销设备号
    unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
    //注销字符设备
    cdev_del(&cdev);
    printk("hello_exit \n");
}
​
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

查看主设备号:cat  /proc/devices/

 注意字符设备注册完并不会自动生成设备节点,设备节点需要手动创建,所以需要用别的方法。要想字符设备生成设备节点,可以使用mknod命令创建一个设备节点,

格式为:

mknod 名称 类型 主设备号 次设备号

举例:
mknod /dev/test c 247 0

可以看到已经生成设备节点了,应用层可以使用文件IO接口对其进行读写等操作。

那怎么使得字符设备驱动自动生成设备节点呢,请看下小节

3、如何自动创建设备节点

在上面的Linux实验中使用insmod命令加载模块后,还需要通过mknod命令来收到创建设备节点,这样使用起来太麻烦了,并且不可能每个设备都去这样操作,Linux系统的存在就是为了方便使用,所以我们来看一下如何实现自动创建设备节点,当加载模块时,在/dev目录下自动创建相应的设备文件。

3.1 怎么自动创建一个设备节点?

在嵌入式linux中使用mdev来实现设备节点文件的自动创建和删除。

3.2 什么是mdev?

mdev是udev的简化版本,是busybox中所带的程序,最适合用在嵌入式系统。

3.3 什么是udev?

udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般用在PC上的linux中,相对mdev来说要复杂些。

3.4 怎么自动创建设备节点?

自动创建设备节点分为两个步骤:

3.4.1 步骤一:使用class_create函数创建一个class的类

3.4.2 步骤二:使用device_create函数在我们创建的类下面创建一个设备。

3.5 创建和删除类函数

在Linux驱动程序中一般通过两个函数来完成设备节点的创建和删除。首先要创建一个class类结构体,class结构体定义在include/linux/device.h里面。class_create是类创建函数。class_create是个宏定义,内容如下:

#define class_create(owner, name)       \
({                      \
    static struct lock_class_key __key; \
    __class_create(owner, name, &__key);    \
})
参数 owner:一般为THIS_MODULE。
参数name:类名字。
返回值:指向结构体class的指针,也就是创建的类。
extern struct class * __must_check __class_create(struct module *owner,
                          const char *name,
                          struct lock_class_key *key);

卸载驱动程序的时候需要删除类,类删除函数为class_destory,函数原型如下:

extern void class_destroy(struct class *cls);

3.6 创建设备函数

当使用上节的函数创建完成一个类后,使用device_create函数在这个类下创建一个设备device_create函数原型如下:

struct device *device_create(struct class *cls, struct device *parent,
                 dev_t devt, void *drvdata,
                 const char *fmt, ...);
可见该函数是一个可变参数的函数。
参数class:就是设备要创建那个类下面;
参数parent:是父设备,一般为NULL,也就是没有父设备;
参数devt:设备号。
参数drvdata:是设备肯呢个会使用的一些数据,一般为NULL.
参数fmt:是设备名字,如果设置fmt=xxx的话,就会生成/dev/xxx这个设备文件。
返回值:创建好的设备。

3.7 删除创建的设备

卸载驱动的时候需要删除掉创建的设备,设备删除函数为device_destory,函数原型如下:

extern void device_destroy(struct class *cls, dev_t devt);
参数cls:要删除的设备所处的类
参数devt:要删除的设备号

3.8 实验代码

#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/fs.h> //包含了文件操作相关的struct的定义
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h> //包含了 device、class 等结构的定义
​
​
static int major_num;//主设备号
static int minor_num;//次设备号
​
module_param(major_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
​
module_param(minor_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
​
#define DEVICE_NUMBER 1         //定义次设备号的个数
#define DEVICE_SNAME "schrdev"  //静态注册设备号的名称
#define DEVICE_ANAME "achrdev"  //动态注册设备号的名称
#define DEVICE_MINOR_NUMBER 0   //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class"
#define DEVICE_NODE_NAME  "chrdev_test" //设备节点名称
​
struct cdev cdev;
struct class *class;
dev_t dev_num;
struct device *device;
​
int chrdev_open(struct inode *inode, struct file *file)
{
    printk("chrdev_open \n");
    return 0;
}
​
struct file_operations chrdev_fops = {
    .owner = THIS_MODULE,
    .open  = chrdev_open
};
​
static int hello_init(void)
{
    int ret;
    if(major_num && minor_num)
    {
        //静态注册设备号
        printk("major_num = %d\n",major_num);
        printk("minor_num = %d\n",minor_num);
        dev_num = MKDEV(major_num,minor_num);
        ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
        if(ret < 0)
        {
            printk("registr_chrdev_region error\n");
            return ret;
        }
        printk("registr_chrdev_region ok\n");
    }
    else
    {
        //动态注册设备号
        ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,1,DEVICE_ANAME);
        if(ret < 0)
        {
            printk("alloc_chrdev_region error\n");
            return ret;
        }
        major_num = MAJOR(dev_num);
        minor_num = MINOR(dev_num);
        printk("major_num = %d\n",major_num);
        printk("minor_num = %d\n",minor_num);
    }
​
    //初始化cdev
    cdev.owner = THIS_MODULE;
    //cdev_init函数初始化cdev结构体成员变量
    cdev_init(&cdev,&chrdev_fops);
    //完成字符设备注册到内核
    cdev_add(&cdev,dev_num,DEVICE_NUMBER);
    //创建类
    class = class_create(THIS_MODULE,DEVICE_CLASS_NAME);//会在/sys/class/目录下生成类名
    //在class类下创建设备
    device = device_create(class,NULL,dev_num,NULL,DEVICE_NODE_NAME);
    
    return 0;
}
​
static void  hello_exit(void)
{
    //注销设备号
    unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
    //删除设备
    cdev_del(&cdev);
    //注销字符设备
    device_destroy(class,dev_num);
    //删除类
    class_destroy(class);
    printk("hello_exit \n");
}
​
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

可以发现不仅在/dev/下生成设备节点。也在/sys/class下生成了类:

4、字符设备和杂项字符设备总结

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值