Linux 设备驱动编写及设备节点自动生成 (cdev)

系列文章目录

第一章 Linux 中内核与驱动程序
第二章 Linux 设备驱动编写 (misc)
第三章 Linux 设备驱动编写及设备节点自动生成 (cdev)
第四章 Linux 平台总线platform与设备树
第五章 Linux 设备树中pinctrl与gpio(lichee nano pi)



前言

前两节提到misc_device是一种特殊的字符设备,主设备号都是10。字符设备的设备号就需要我们自己去分配设备号,而且字符设备不能自动生成设备文件(节点),这个也是需要我们去生成的。这两两点可以说是misc设备和字符设备的区别。


一、分配设备号

设备号是由主设备号+次设备号组成,两个一块构成dev_t类型的设备号。

dev_t类型在<linux/tppe.h>头文件中有定义,是一个32位数。高12位是用来保存主设备号,低20位是用来保存次设备的号

分配设备号有两种方式:

1.1 静态分配一个设备号

我们使用的是:

register_chrdev_region(dev_t, unsigned, const char *); 

需要明确知道我们的系统里面那些设备号没有用。
参数:
第一个:设备号的起始值。类型是dev_t类型
第二个:次设备号的个数。
第三个:设备的名称
返回值:成功返回0,失败返回负数

cat /proc/device  //查看设备中那个设备号被占用

Linux在<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里面获取我们的次设备号
#define MKDEV(ma,mi)(.(ma)<< MINORBITS) | (mi))
将我们的主设备号和次设备号组成一个dev_t类型。第一个参数是主设备号,第二个参数是次设备号

1.2 动态分配

alloc_chrdev_region(dev_t *. unsigned, unsigned, const char *);

参数:
第一个:保存生成的设备号
第二个:我们请求的第一个次设备号,通常是0
第三个:连续申请的设备号的个数。
第四个:设备名称
返回值:成功返回0,失败返回负数

1.3 注销设备号

我们使用的是

unregister_chrdev_region(dev_t, unsigned);

参数:
第一个:分配设备号的起始地址
第二个:申请的连续设备号的个数

1.4 例程结果

本文编译了一个x86的在自己电脑上跑了一下,用dmesg查看内核日志;
在这里插入图片描述
设备名字也对应的上。
在这里插入图片描述


二、字符设备驱动注册

2.1 一:定义一个cdev结构体

cdev结构体定义如下
在这里插入图片描述

2.2 使用cdev_init函数初始化cdev结构体成员变量

void cdev_init(struct cdev *, const struct file_operations *);

参数:
第一个:要初始化的cdev
第二个:文件操作集
cdev->ops = fops; //实际就是把文件操作集写给ops

2.3 使用cdev_add函数注册到内核

int cdev_add(struct cdev *, dev_t, unsigned);

参数:
第一个:cdev的结构体指针
第二个:设备号
第三个:次设备号的数量

2.4 cdev注销

void cdev_del(struct cdev *);

2.5 例程结果


三、生成设备节点

字符设备注册完以后自动生成设备节点。

3.1 手动生成设备节点

我们需要在insmod模块之后,使用mknod命令创建一个设备节点,这样才能在应用层对设备进行操作。(要不没设备文件你操作个啥)
格式:mknod 名称 类型 主设备号 次设备号
举例:

mknod  /dev/test  c  247 0

3.2 自动生成设备节点

1.什么是mdev?
mdev是udev的简化版本,是busybox中所带的程序,最适合用在嵌入式系统。
2.什么是udev?
udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般用在PC 上的 linux中,相对mdev来说要复杂些。
3.怎么自动创建设备节点?
自动创建设备节点分为俩个步骤:
步骤一:使用class_create函数创建一个class的类。
步骤二:使用device_create函数在我们创建的类下面创建一个设备。
4.创建和删除类函数
在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);
})
struct class *_class_create(struct module *owner, const char *name,
struct lock_class_key *key)

class_create一共有两个参数,参数owner一般为THIS_MODULE,参数name是类名字。返回值是个指向结构体class的指针,也就是创建的类。
下面命令可以查看类

ls /sys/class/

在这里插入图片描述

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

void class_destroy(struct class *cls);

参数cls就是要删除的类。
5.创建设备函数
当使用上节的函数创建完成一个类后,使用device_create函数在这个类下创建一个设备。
device_create函数原型如下:

struct device  *device_create(struct class *class,struct device *parent,
dev_t devt,
void *drvdata,
const char*fmt,...)

device create是个可变参数函数,
参数class就是设备要创建哪个类下面;
参数parent是父设备,一般为NULL,也就是没有父设备;
参数devt是设备号;
参数drvdata是设备可能会使用的一些数据,一般为NULL;
参数 fmt是设备名字,如果设置fmt=xxx的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。

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

void device_destroy(struct class *class, dev_t devt)

参数 class是要删除的设备所处的类,参数devt是要删除的设备号。

3.3 例程结果

在这里插入图片描述
结果生成了my_cdev_nod


附一张 法师 的思维导图
请添加图片描述


例程代码

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>   /* printk() */
#include <linux/cdev.h> /* cdev... */
#include <linux/fs.h>       /* everything... */
#include <linux/types.h>    /* size_t */
#include <linux/uaccess.h>
#include <linux/kdev_t.h>



#define DEVICE_NUMBER 1 //需要注册设备的数量

#define DEVICE_SNAME "s_my_cdev" //动态分配的设备名称
#define DEVICE_ANAME "a_my_cdev" //静态分配的设备名称

#define DEVICE_MINOR_NUMBER 0
#define DEVICE_CLASS_NAME "my_cdev_class"//类的名称
#define DEVICE_NODE_NAME "my_cdev_nod" //要生成的设备节点的名称


static int major_num,minor_num;


module_param(major_num,int,S_IRUSR);//驱动传参,不了解的搜一下,比较简单
module_param(minor_num,int,S_IRUSR);


int cedv_open(struct inode *inode,struct file *file)
{
    printk( "open cedv\n");
    return 0;
}

struct file_operations cedv_fops={
  .owner   = THIS_MODULE,
  .open    = cedv_open,
};

dev_t dev_num;//设备号

struct cdev cdev ;       //字符设备结构体
struct class *class ;  //类相关的结构体
struct device *device ;  //设备节点相关的结构体

static int cdevice_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);

        ret=register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
        if(ret < 0)
        {
            printk( "register_chrdev_region is failed\n");

            return -1;
        }
        printk( "register_chrdev_region is succeed\n");

    }
    else
    {
        ret =alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
        if(ret < 0)
        {
            printk( "alloc_chrdev_region is failed\n");

            return -1;
        }
        printk( "alloc_chrdev_region is succeed\n");

        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 ,&cedv_fops);

    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 void cedvice_exit(void)
{
    unregister_chrdev_region(MKDEV(major_num,minor_num), DEVICE_NUMBER);
    cdev_del(&cdev);
    device_destroy(class, dev_num);
    class_destroy(class);

    printk(KERN_ALERT "Goodbye,cdev\n");
}

/* register the init and exit routine of the module */
module_init( cdevice_init );
module_exit( cedvice_exit );

MODULE_LICENSE("GPL");

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 编写 Linux 字符设备驱动程序需要熟悉 Linux 内核,并且具备 C 语言编程能力。首先,需要找到对应的设备文件,并确定对应的设备类型。然后根据设备的硬件特性,编写字符设备驱动程序,并实现相关操作函数,注册设备驱动,最后编写用户空间程序以便操作设备。 ### 回答2: 编写一个Linux字符设备驱动需要遵循以下步骤: 1. 包含必要的头文件:在驱动程序的源文件中,你需要包含一些必要的头文件。这些头文件包括“linux/module.h”、“linux/kernel.h”和“linux/fs.h”。 2. 定义设备结构体:在驱动程序中,你需要定义一个结构体来表示设备。这个结构体通常包含设备名称设备号和其他需要的属性。 3. 实现打开和关闭设备的函数:在驱动程序中,你需要实现打开设备和关闭设备的函数。这些函数通常用于初始化设备和释放相关资源。 4. 实现读取和写入设备的函数:在驱动程序中,你需要实现读取设备和写入设备的函数。这些函数通常用于从设备中读取数据和向设备中写入数据。 5. 实现设备操作的函数:在驱动程序中,你需要实现一些设备操作的函数。这些函数通常包括设备的初始化、设备的释放和设备的控制等操作。 6. 注册驱动程序:在驱动程序的初始化函数中,你需要调用适当的函数来注册驱动程序。这个函数通常是“register_chrdev”函数。 7. 编译和加载驱动程序:将驱动程序的源文件编译成模块的形式,然后使用“insmod”命令将其加载到内核中。 8. 测试驱动程序:使用“cat”命令或其他读取文件的方式来测试驱动程序。 编写一个Linux字符设备驱动需要掌握Linux内核编程的知识和相关的驱动开发技术。同时,还需要理解设备驱动的工作原理和相关的API。要确保驱动程序的正确性和稳定性,还需要进行充分的测试。 ### 回答3: 编写一个Linux字符设备驱动需要涵盖以下几个步骤: 1. 头文件和模块初始化: 首先需要创建一个头文件,并包含必要的Linux内核头文件,定义驱动程序所需的宏、结构体和函数。在模块初始化函数中,需要完成设备的注册和申请主设备号。 2. 设备的文件变量和方法: 在驱动程序中,需要定义设备特定的结构体来保存设备的状态和数据。此外,需要定义open、release、read、write等方法来处理设备文件的打开、释放和读写操作。 3. 设备的字符设备自动创建: 在驱动程序中,可以通过cdev结构体和相应函数来自动创建一个字符设备,并将该设备与上述方法进行关联。 4. 主设备号的分配与释放: 在模块初始化函数中,需要通过调用register_chrdev_region函数来分配一个独特的主设备号,并在模块注销函数中调用unregister_chrdev_region函数来释放占用的主设备号。 5. 设备文件的创建与删除: 可以通过调用cdev_add函数将设备与相应的字符设备驱动关联起来,从而创建设备文件。在驱动程序中,也可以调用cdev_del函数来删除设备文件。 6. 内核与用户空间的数据传输: 可以通过copy_to_user和copy_from_user等函数在内核和用户空间之间传输数据。 7. 用于驱动模块的Makefile文件: 创建Makefile文件,用于编译和构建驱动模块,并链接所需的头文件和库文件。 通过以上步骤,就可以编写一个基本的Linux字符设备驱动编写完成后,可以使用gcc编译驱动程序源代码,通过insmod命令将其插入内核并加载,然后使用mknod命令创建设备文件,在用户空间中通过open、read、write等系统调用进行设备的操作和数据传输。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请叫我7plus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值