深入探讨Linux驱动开发:字符设备驱动开发与测试_linux 驱动开发设备号(2)


参数说明:


* dev:用于返回分配的设备号。
* firstminor:要分配的第一个次设备号,它常常是0。
* count:要分配的设备号数量。
* name:设备名字。


动态分配的缺点是你无法提前创建设备节点,因为分配给你的主设备号会发生变化,对于驱动的正常使用这不是问题,但是一旦编号分配了,只能通过 查看 `/proc/devices`文件才能知道它的值,然后再创建设备节点。


#### ④释放主次设备号


注销字符设备后要释放设备号,设备号释放函数如下:



void unregister_chrdev_region(dev_t from, unsigned count)


使用该函数可以将之前通过 register\_chrdev\_region() 函数注册的设备号从系统中取消,以便于其他设备可以使用这些设备号。通常在模块的卸载函数`xxx__exit`中调用该函数,释放模块使用的设备号资源。


#### ⑤手动创建设备节点


如果我们需要创建该文件,则需要使用mknod命令创建。当然我们也可以在驱动里调用相应的函数,来通知应用程序空间自动创建该设备文件。



mknod /dev/chrdevbase c 251 0


* “mknod”是创建节点命令
* “/dev/chrdevbase”是要创建的节点文件
* “c”表示这是个字符设备
* “251”是设备的主设备号
* “0”是设备的次设备号


例如 chrdevbaseAPP 想要读写 chrdevbase 设备,直接对`/dev/chrdevbase`进行读写操作即可。相当于`/dev/chrdevbase`这个文件是 chrdevbase 设备在用户空间中的实现。


#### ⑥自动创建设备节点


在Linux中,可以通过udev(userspace dev)来实现自动创建设备节点,当然前提条件是用户空间移植了udev。udev是Linux系统中的一个动态设备管理机制,它负责在系统启动时检测硬件设备的插拔并创建相应的设备节点。


当udev检测到新设备插入时,它会执行一系列规则(rules)来确定设备应该如何创建,这些规则定义了设备节点的名称、权限等信息,并使用mknod系统调用来创建设备节点。这些规则通常存储在/etc/udev/rules.d/目录中的文件中,每个文件包含一组规则。


class\_create():在调用device\_create()前要先用class\_create()创建一个类(设备依附的类)。类这个概念在Linux中被抽象成一种设备的集合。类在`/sys/class`目录中。class\_create()这个函数使用非常简单,在内核中是一个宏定义,定义在`/include/linux/device.h`中:



#define class_create(owner, name)
({
static struct lock_class_key __key;
__class_create(owner, name, &__key);
})


此函数有两个参数:


* owner:struct module结构体类型的指针,一般赋值为THIS\_MODULE。
* name:char类型的指针,类名。


device\_create():用于创建设备节点,其定义在`/include/linux/device.h`中,函数原型如下:



struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, …)
{
va_list vargs;
struct device *dev;

va\_start(vargs, fmt);
dev = device\_create\_vargs(class, parent, devt, drvdata, fmt, vargs);
va\_end(vargs);

return dev;
}


* class:该设备依附的类
* parent:父设备
* devt:设备号(此处的设备号为主次设备号)
* drvdata:私有数据
* fmt:设备名


device\_create能自动创建设备文件是依赖于udev这个应用程序,使用udev后,在/dev目录下就只包含系统中真正存在的设备。


#### ⑦删除设备节点


device\_destroy()函数用于销毁一个设备节点,并释放相关的资源。定义在`/include/linux/device.h`中:



void device_destroy(struct class *class, dev_t devt)
{
struct device *dev;

dev = class\_find\_device(class, NULL, &devt, exact_match);
if (dev)
    device\_destroy(dev);

}


参数说明:


* class:指向struct class类型的指针,表示设备节点所属的设备类。
* devt:表示设备号。


函数class\_destroy()用于从Linux内核系统中删除设备。此函数执行的效果是删除函数class\_create()在/sys/class目录下创建的节点对应的文件夹,定义在`/include/linux/device.h`中:



void class_destroy(struct class *cls)
{
if ((cls == NULL) || (IS_ERR(cls)))
return;

class\_unregister(cls);

}


参数说明:


* cls:struct class结构体类型的变量,代表通过class\_create创建的设备的节点。


自动创建节点示例:



static struct class *my_class;
static struct device *my_device;
static int __init my_driver_init(void)
{
/* 创建设备类 */
my_class = class_create(THIS_MODULE, “my_class”);
/* 创建设备节点 */
my_device = device_create(my_class, NULL, MKDEV(MAJOR_NUM, MINOR_NUM), NULL, “my_device”);
return 0;
}
static void __exit my_driver_exit(void)
{
/* 销毁设备节点 */
device_destroy(my_class, MKDEV(MAJOR_NUM, MINOR_NUM));
/* 销毁设备类 */
class_destroy(my_class);
}
module_init(my_driver_init);
module_exit(my_driver_exit);


## 三、字符设备注册


### 1.cdev结构体


内核在内部使用类型struct cdev的结构体来代表字符设备。在内核调用你的设备操作之前,你必须分配一个这样的结构体并注册给linux内核,在这个结构体里有对于这个设备进行操作的函数,具体定义在file\_operation结构体中。该结构体定义在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;
};


* kobj:kobject结构体,用于表示cdev对象在内核中的内存管理等方面的信息。
* owner:指向内核模块的指针,表示注册这个cdev结构体的内核模块。
* ops:指向字符设备驱动的file\_operations结构体。
* list:链表节点,用于将多个cdev结构体链接在一起。
* dev:字符设备的设备号。
* count:用于表示同一设备实例的数量,通常为1。


在内核编程中,我们可以使用两种方法获取结构体。  
 一是运行时想获取一个独立的cdev结构:



struct cdev *chrtest;
if(NULL == chrtest = cdev_alloc())
{
printk(KERN_ERR “S3C %s driver can’t alloc for the cdev.\n”, DEV_NAME);
unregister_chrdev_region_region(devno, dev_vount);
return -ENOMEM;
}

chrtest->ops = &my_fops;


但是,偶尔你会想将cdev结构体嵌入一个你自己的设备特定结构。这样的情况下你需要初始化已经分配的结构体:



cdev_init(struct cdev *dev, struct file_operations *fops);


struct cdev有一个拥有者成员,应当设置为THIS\_MODULE,一旦cdev结构建立,最后的步骤就是告诉内核。


### 2.注册cdev到内核


分配到cdev结构体后,我们将它初始化,并将对该设备驱动所支持的系统调用函数存放在file\_operations结构体添加进来。然后用途cdev\_add函数将它注册到内核,从而完成一个完整的Linux设备注册过程。


cdev\_add函数原型如下:



int cdev_add(struct cdev *p, dev_t dev, unsigned int count);


其中,参数含义如下:


* p:指向要添加的字符设备对象的 cdev 结构体指针。
* dev:指定要添加的设备号。
* count:指定添加的设备号的数量。


字符设备驱动cdev的分配和注册示例:



static struct file_operations chrtest_fods =
{
.owner = THIS_MODULE,
.open = chrtest_open,
.release = chrtest_release,
.unlocked_ioctl = chrtest_ioctl,
};

struct cdev *chrtest_cdev;
if(NULL == (led_cdev = cdev_alloc))
{
printk(KERN_ERR “S3C %s driver can’t alloc for the cdev.\n”, DEV_NAME);
unregister_chdev_region(devno, dev_count);
return -ENOMEM;
}

led_cdev->owner = THIS_MODULE;
cdev_init(led_cdev, &led_fops);

result = cdev_add(led_cdev, devno, dev_count);

if(0 != result)
{
printk(KERN_INFO “S3C %s drive can’t register cdev:result = %d\n”, DEV_NAME, result);
goto ERROR;
}


## 三、字符设备驱动开发与测试


### 1.字符设备驱动



/*********************************************************************************
* Copyright: © 2023 Deng Yonghaodengyonghao2001@163.com
* All rights reserved.
*
* Filename: chrdevbase.c
* Description: This file is character device base
*
* Version: 1.0.0(2023年04月10日)
* Author: Deng Yonghao dengyonghao2001@163.com
* ChangeLog: 1, Release initial version on “2023年04月10日 11时30分10秒”
*
********************************************************************************/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/*确定主设备号*/
//#define DEV_MAJOR 79
#ifndef DEV_MAJOR
#define DEV_MAJOR 0
#endif
int dev_major = DEV_MAJOR;//主设备号

#define DEV_NAME “chrdev”//设备名称

static struct cdev *chrtest_cdev;//cdev结构体

//static struct class *chrdev_class; //定义一个class用于自动创建类

static char kernel_buf[1024];

#define MIN(a,b) (a < b ? a : b)

/*实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t chrtest_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
printk(“%s %s line %d\n”, __FILE__, FUNCTION, __LINE__);
err = copy_to_user(buf, kernel_buf, MIN(1024, size));//内核空间的数据到用户空间的复制
return MIN(1024, size);
}

static ssize_t chrtest_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
printk(“%s %s line %d\n”, __FILE__, FUNCTION, __LINE__);
err = copy_from_user(kernel_buf, buf, MIN(1024, size));//将用户空间的buf复制到内核空间缓冲区kernel_buf中,因为用户空间内存不能直接访问内核空间的内存
return MIN(1024, size);
}

static int chrtest_drv_open(struct inode *node, struct file *file)
{
printk(“%s %s line %d\n”, __FILE__, FUNCTION, __LINE__);
return 0;
}

static int chrtest_drv_close(struct inode *node, struct file *file)
{
printk(“%s %s line %d\n”, __FILE__, FUNCTION, __LINE__);
return 0;
}

/*定义自己的file_operations结构体*/
static struct file_operations chrtest_fops = {
.owner = THIS_MODULE,
.open = chrtest_drv_open,
.read = chrtest_drv_read,
.write = chrtest_drv_write,
.release = chrtest_drv_close,
};

/*把file_operations结构体告诉内核:register_chrdev*/
/*注册驱动函数:写入口函数,安装驱动程序时就会调用这个入口函数*/
static int __init chrdev_init(void)
{
int result;
dev_t devno;/*定义一个dev_t变量来表示设备号*/

printk("%s %s line %d\n", \_\_FILE\_\_, __FUNCTION__, \_\_LINE\_\_);

/\*字符设备驱动注册流程第二步:分配主次设备号,这里即支持静态指定,也支持动态申请\*/
if(0 != dev_major)
{
	devno = MKDEV(dev_major, 0);
	result = register\_chrdev\_region(devno, 1, DEV_NAME);//"/proc/devices/chrdev"
}
else
{
	result = alloc\_chrdev\_region(&devno, 0, 1, DEV_NAME);
	dev_major = MAJOR(devno);
}

/\*自动分配设备号失败\*/
if(result < 0)
{
	printk(KERN_ERR " %s driver can't use major %d\n", DEV_NAME, dev_major);
	return -ENODEV;
}
printk(KERN_DEBUG " %s driver can't use major %d\n", DEV_NAME, dev_major);

/\*字符设备驱动注册流程第三步:分配cdev结构体,我们这里使用动态申请的方式\*/
if(NULL == (chrtest_cdev = cdev\_alloc()))
{
	printk(KERN_ERR " %s driver can't alloc for the cdev\n", DEV_NAME);
	unregister\_chrdev\_region(devno, 1);
	return -ENOMEM;
}

/\*字符设备驱动注册流程第四步:分配cdev结构体,绑定主次设备号、fops到cdev结构体中,并注册给Linux内核\*/
chrtest_cdev->owner = THIS_MODULE;//.owner表示谁拥有你这个驱动程序
cdev\_init(chrtest_cdev, &chrtest_fops);//初始化设备
result = cdev\_add(chrtest_cdev, devno, 1);
if(0 != result)
{
	printk(KERN_INFO "%s driver can't register cdev:result=%d\n", DEV_NAME, result);
	goto ERROR;
}
printk(KERN_INFO "%s driver can register cdev:result=%d\n", DEV_NAME, result);

/\*自动创建设备类型与/dev设备节点\*/
#if 0
chrdev_class = class\_create(THIS_MODULE, DEV_NAME);//创建设备类型 sys/class/chrdevbase
if(IS\_ERR(chrdev_class))
{
	result = PTR\_ERR(chrdev_class);
	goto ERROR;
}
device\_create(chrdev_class, NULL, MKDEV(dev_major, 0), NULL, DEV_NAME);// /dev/chrdev 注册这个设备节点
#endif


return 0;

ERROR:
printk(KERN_ERR" %s driver installed failure.\n", DEV_NAME);
cdev_del(chrtest_cdev);
unregister_chrdev_region(devno, 1);
return result;

}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void __exit chrdev_exit(void)
{
printk(“%s %s line %d\n”, __FILE__, FUNCTION, __LINE__);

/\*注销设备类型与/dev设备节点\*/
#if 0
device\_destroy(chrdev_class, NKDEV(dev_major, 0));//注销此设备节点
class\_destroy(chrdev_class);//删除这个设备类型
#endif

cdev\_del(chrtest_cdev);//注销字符设备
unregister\_chrdev\_region(MKDEV(dev_major, 0), 1);//释放设备号
												 

printk(KERN_ERR"%s driver version 1.0.0 removed!\n", DEV_NAME);
return;

}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_LICENSE(“Dual BSD/GPL”);
MODULE_AUTHOR(“DengYonghao dengyonghao2001@163.com”);


### 2.字符设备测试程序



/*********************************************************************************
* Copyright: © 2023 Deng Yonghaodengyonghao2001@163.com
* All rights reserved.
*
* Filename: chrdevbaseAPP.c
* Description: This file character driver test APP.
*
* Version: 1.0.0(2023年04月12日)
* Author: Deng Yonghao dengyonghao2001@163.com
* ChangeLog: 1, Release initial version on “2023年04月12日 09时58分17秒”
*
********************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

img-FPZEmNgv-1715758946133)]

[外链图片转存中…(img-VMIYKZYz-1715758946134)]

[外链图片转存中…(img-MAjyvxXx-1715758946134)]

[外链图片转存中…(img-2uFbUv1D-1715758946135)]

[外链图片转存中…(img-94TieV3t-1715758946136)]

[外链图片转存中…(img-3quBTtWz-1715758946136)]

[外链图片转存中…(img-BYBzf3KI-1715758946137)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值