0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux新字符设备驱动开发方式

CHANBAEK 来源:嵌入式攻城狮 作者:安迪西 2023-04-14 12:02 次阅读

Linux字符设备驱动开发模板中介绍了旧版本的驱动开发模板,其需要手动分配设备号后,再进行注册,驱动加载成功后还需要手动创建设备节点,比较麻烦。 目前Linux内核推荐的新字符设备驱动API函数,可以自动分配设备号、创建设备节点,使得驱动的使用更加方便

1. 新字符设备驱动原理

1.1 分配和释放设备号

旧字符设备驱动开发中使用register_chrdev函数注册字符设备时,需要事先确定好主设备号,并且注册成功后,会将该设备号下的所有次设备号都使用掉

而新字符设备驱动API函数很好的解决了这个问题,使用设备号时再向内核申请,需要几个就申请几个,由内核分配设备可以使用的设备号

设备号申请函数:没有指定设备号

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 
//dev:保存申请到的设备号
//baseminor:次设备号起始地址,一般为0
//count:要申请的设备号数量
//name:设备名字

设备号申请函数:指定了主次设备号

int register_chrdev_region(dev_t from, unsigned count, const char *name) 
//from:要申请的起始设备号
//count:要申请的设备号数量
//name:设备名字

设备号释放函数:统一使用下面函数释放

void unregister_chrdev_region(dev_t from, unsigned count) 
//from:要释放的设备号
//count:表示从from开始,要释放的设备号数量

因此新字符设备驱动中,设备号分配代码通常按如下示例编写:

int major;     /* 主设备号 */
int minor;     /* 次设备号 */
dev_t devid;   /* 设备号 */

if (major) {   /* 定义了主设备号 */
    devid = MKDEV(major, 0);    /*大部分驱动次设备号都选择0*/
    register_chrdev_region(devid, 1, "test");
} else {      /* 没有定义设备号 */
    alloc_chrdev_region(&devid, 0, 1, "test"); /*申请设备号*/
    major = MAJOR(devid);       /* 获取分配号的主设备号 */
    minor = MINOR(devid);       /* 获取分配号的次设备号 */
}

1.2 注册字符设备

Linux中使用cdev表示字符设备,在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结构体变量

cdev_init函数:定义好cdev变量后,用该函数进行初始化

cdev_add函数:向系统添加字符设备(cdev结构体变量)

struct cdev testcdev;
/* 设备操作函数 */
static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    /* 其他具体的初始项 */
};

testcdev.owner = THIS_MODULE;
cdev_init(&testcdev, &test_fops);  /* 初始化cdev结构体变量 */
cdev_add(&testcdev, devid, 1);     /* 添加字符设备 */

cdev_del函数:卸载驱动时从内核中删除相应的字符设备

void cdev_del(struct cdev *p)
//p:要删除的字符设备

1.3 自动创建设备节点

旧字符设备驱动开发中,驱动程序加载成功后还需要使用mknod命令手动创建设备节点,十分麻烦。

而新字符设备驱动开发中,Linux通过udev用户程序来实现设备文件的自动创建与删除。 udev会检测系统中硬件设备状态,并根据硬件设备状态来创建或者删除设备文件。

使用busybox构建根文件系统时,busybox会创建一个udev的简化版本mdev。 因此,在嵌入式开发中使用mdev来实现设备节点文件的自动创建与删除。 Linux系统中的热插拔事件也由mdev管理,在/etc/init.d/rcS文件中有如下语句:

echo /sbin/mdev > /proc/sys/kernel/hotplug

创建类:自动创建设备节点的工作是在驱动程序入口函数中完成的,一般在cdev_add之后添加相关代码

struct class *class_create (struct module *owner, const char *name)
//owner 一般为 THIS_MODULE
//name 是类名字
//返回值是个指向结构体class的指针,也就是创建的类

删除类:卸载驱动程序时需要删除类

void class_destroy(struct class *cls)
//cls 就是要删除的类

创建设备:类创建好后还不能实现自动创建设备节点,还需要在该类下创建一个设备

struct device *device_create(struct class *class, 
                             struct device *parent, 
                             dev_t devt,   
                             void *drvdata,   
                             const char *fmt, ...) 
//class 设备创建在哪个类下
//parent 父设备,一般为NULL
//devt 设备号
//drvdata 设备可能会使用的数据,一般NULL
//fmt 设备名字
//返回值是创建好的设备

删除设备:卸载驱动时需要删除创建的设备

void device_destroy(struct class *class, dev_t devt)
//class 是要删除的设备所处的类
//devt 是要删除的设备号

1.4 设置文件私有数据

每个硬件设备都有一些属性,比如主设备号、类、设备、开关状态等等,在编写驱动时可将这些属性封装成一个结构体。 并在编写驱动open函数时将设备结构体作为私有数据添加到设备文件中:

/*设备结构体*/ 
struct test_dev{ 
    dev_t         devid;     /*设备号*/ 
    struct cdev   cdev;      /*cdev*/ 
    struct class  *class;    /*类*/ 
    struct device *device;   /*设备*/ 
    int           major;     /*主设备号*/ 
    int           minor;     /*次设备号*/ 
}; 

struct test_dev testdev; 
/*open函数*/ 
static int test_open(struct inode *inode, struct file *filp) 
{ 
    filp->private_data = &testdev; /*设置私有数据*/ 
    return 0; 
}

综上所述,新字符设备驱动开发流程如下图所示:

图片

2. 新字符设备驱动开发实验

新字符设备驱动开发实验是在Linux字符设备驱动开发模板一文的基础上进行修改,只更改了驱动的编写方式,与应用程序无关,因此只修改驱动程序即可

2.1 驱动程序编写

添加定义:宏及字符设备定义

#define CHRDEVBASE_CNT   1            //设备号个数
#define CHRDEVBASE_NAME  "chrdevbase" //名字
/*newchr设备结构体 */
struct newchr_dev{
    dev_t devid;             //设备号
    struct cdev cdev;        //cdev 
    struct class *class;     //类   
    struct device *device;   //设备 
    int major;               //主设备号
    int minor;               //次设备号
};

struct newchr_dev chrdevbase; //自定义字符设备

修改open函数:设置私有数据

static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("chrdevbase open!\\r\\n");
    filp->private_data = &chrdevbase; //设置私有数据
    return 0;
}

修改init函数

static int __init chrdevbase_init(void)
{
    /* 注册字符设备驱动 */
    //1、创建设备号
    if (chrdevbase.major) /* 定义了设备号 */
    {
        chrdevbase.devid = MKDEV(chrdevbase.major, 0);
        register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
    } 
    else /* 没有定义设备号 */
    {
        alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT, CHRDEVBASE_NAME); /* 申请设备号 */
        chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取分配号的主设备号 */
        chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取分配号的次设备号 */
    }
    printk("chrdevbase major=%d,minor=%d\\r\\n",chrdevbase.major, chrdevbase.minor);  
    //2、初始化cdev
    chrdevbase.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevbase.cdev, &chrdevbase_fops); 
    //3、添加一个cdev
    cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);
    //4、创建类
    chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.class)) 
    {
     return PTR_ERR(chrdevbase.class);
     }
    //5、创建设备
    chrdevbase.device = device_create(chrdevbase.class, NULL, chrdevbase.devid, NULL, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.device)) 
    {
        return PTR_ERR(chrdevbase.device);
    }
    
    printk("chrdevbase init done!\\r\\n");
    return 0;
}

修改exit函数

static void __exit chrdevbase_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&chrdevbase.cdev);/*  删除cdev */
    unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT); /* 注销设备号 */

    device_destroy(chrdevbase.class, chrdevbase.devid);
    class_destroy(chrdevbase.class);
    
    printk("chrdevbase exit done!\\r\\n");
}

2.2 程序编译

程序编译包括驱动程序和应用程序编译两个部分:

驱动程序编译:创建Makefile文件,使用make命令,编译驱动程序

KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := newchrdev.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

应用程序编译:无需内核参与,直接编译即可

arm-linux-gnueabihf-gcc newchrdevApp.c -o newchrdevApp

2.3 运行测试

为了方便,选择通过TFTP从网络启动,并使用NFS挂载网络根文件系统。 确保开发板能正常启动,在Ubuntu中将驱动和测试文件复制到modules/4.1.15目录中

在开发板中输入如下指令加载驱动模块

depmod                    //第一次加载驱动的时候需要运行此命令
modprobe newchrdev.ko     //加载驱动

驱动加载成功后,可以看到自动申请到的主设备号和次设备号

图片

使用ls /dev/chrdevbase -l命令验证该设备节点文件是否存在,而旧驱动方式需要额外使用mknod指令来手动创建该设备节点文件

图片

驱动加载成功后,测试APP程序,如下

图片

测试完使用rmmod指令卸载驱动

图片

以上可见Linux新字符设备驱动开发方式可以自动分配设备号、创建设备节点,使得驱动的使用更加方便、便捷。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11292

    浏览量

    209328
  • API
    API
    +关注

    关注

    2

    文章

    1499

    浏览量

    61962
  • 字符
    +关注

    关注

    0

    文章

    233

    浏览量

    25199
  • 函数
    +关注

    关注

    3

    文章

    4327

    浏览量

    62571
  • 驱动开发
    +关注

    关注

    0

    文章

    130

    浏览量

    12072
收藏 人收藏

    评论

    相关推荐

    i.MX6ULL驱动开发1—字符设备开发模板

    本篇介绍了嵌入式Linux驱动开发中的基础驱动——字符驱动
    的头像 发表于 03-17 09:13 3311次阅读
    i.MX6ULL<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>1—<b class='flag-5'>字符</b><b class='flag-5'>设备</b><b class='flag-5'>开发</b>模板

    i.MX6ULL驱动开发2—新字符设备开发模板

    上篇文章介绍了字符设备开发模板,但那是一种旧版本的驱动开发模式,设备
    的头像 发表于 03-17 09:11 3174次阅读
    i.MX6ULL<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>2—<b class='flag-5'>新字符</b><b class='flag-5'>设备</b><b class='flag-5'>开发</b>模板

    Linux驱动开发:字符设备驱动开发理论

    大部分学习者的最终目的就是学习 Linux驱动开发Linux中的外设驱动可以分为:字符
    发表于 10-26 09:53 1128次阅读

    Linux字符设备驱动开发框架介绍

    字符设备Linux驱动中最基本的一类设备驱动字符
    发表于 04-15 11:52 1363次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>字符</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b><b class='flag-5'>开发</b>框架介绍

    ArmSoM系列板卡 嵌入式Linux驱动开发实战指南 之 字符设备驱动

    字符设备驱动 本章,我们将学习字符设备使用、字符设备
    的头像 发表于 04-10 09:53 1049次阅读
    ArmSoM系列板卡 嵌入式<b class='flag-5'>Linux</b><b class='flag-5'>驱动</b><b class='flag-5'>开发</b>实战指南 之 <b class='flag-5'>字符</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>

    「正点原子Linux连载」第四十二章新字符设备驱动实验

    1)实验平台:正点原子Linux开发板2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》关注官方微信号公众号,获取更多资料:正点原
    发表于 03-18 15:12

    字符设备驱动开发流程

    做嵌入式linux驱动开发,首先要搞明白大致框架。linux驱动通常分为字符
    发表于 12-24 08:30

    嵌入式Linux字符设备驱动的设计与应用

    描述了基于嵌入式Linux字符设备驱动程序的设计方法和实现过程。以电机、数码管、串口和mini键盘的驱动设计为例,详细阐述了嵌入式
    发表于 02-23 15:45 24次下载

    嵌入式Linux字符设备驱动的设计与应用

    描述了基于嵌入式Linux字符设备驱动程序的设计方法和实现过程。以电机、数码管、串口和mini键盘的驱动设计为例,详细阐述了嵌入式
    发表于 07-14 17:31 31次下载

    基于linux系统的字符设备驱动研究与设计_王森

    基于linux系统的字符设备驱动研究与设计_王森
    发表于 03-18 09:23 1次下载

    Linux设备驱动开发字符设备驱动编程分析

    1.字符设备驱动编写流程 设备驱动程序可以使用模块的方式动态加载到内核中去。加载模块的
    发表于 10-18 17:33 1次下载
    <b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b><b class='flag-5'>开发</b>之<b class='flag-5'>字符</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>编程分析

    Linux设备驱动开发详解》第6章、字符设备驱动

    Linux设备驱动开发详解》第6章、字符设备驱动
    发表于 10-27 11:46 23次下载
    《<b class='flag-5'>Linux</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b><b class='flag-5'>开发</b>详解》第6章、<b class='flag-5'>字符</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>

    基于PXA255开发板外围字符设备的嵌入式Linux字符设备驱动设计与应用

    驱动程序和应用程序的需求在成倍增长。本文通过实现对 PXA255开发板外围字符设备(电机、数码管、串口和 mini键盘)的操作和控制,详细讨论了嵌入式
    发表于 08-21 10:19 1185次阅读
    基于PXA255<b class='flag-5'>开发</b>板外围<b class='flag-5'>字符</b><b class='flag-5'>设备</b>的嵌入式<b class='flag-5'>Linux</b><b class='flag-5'>字符</b><b class='flag-5'>设备</b><b class='flag-5'>驱动</b>设计与应用

    Linux字符设备架构是如何实现的

    一、Linux设备分类Linux系统为了管理方便,将设备分成三种基本类型:字符设备
    的头像 发表于 12-24 18:12 708次阅读

    i.MX6ULL驱动开发3—GPIO寄存器配置原理

    介绍了字符设备驱动的两种新旧开发方式,并使用一个虚拟的字符驱动来学习
    的头像 发表于 03-18 08:17 2763次阅读
    i.MX6ULL<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>3—GPIO寄存器配置原理