register_chrdev深入分析

本文详细解释了Linux内核中字符设备驱动程序的两种注册方法:register_chrdev和__register_chrdev_region的区别及应用场景,通过实例分析了如何高效利用次设备号实现资源最大化利用。

 
register_chrdev
      __register_chrdev(major, 0, 256, name, fops);
            struct char_device_struct *cd;
            cd = __register_chrdev_region(major, baseminor, count, name);
                    cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
                    //如果主设备号是0的话,就会从 char_device_struct数组中选择一个没有分配的char_device_struct结构体
                    //如果找到的话,就以其下标作为主设备号,否则出错返回
                    cd->major = major;
                    cd->baseminor = baseminor;//第一个次设备号
                    cd->minorct = minorct;          //此设备号的个数
                    strlcpy(cd->name, name, sizeof(cd->name));//设置名字
                    i = major_to_index(major);
                          return major % CHRDEV_MAJOR_HASH_SIZE;//只有当指定的主设备号大于255时,i才会不等于major
                    //下面的工作比较难懂,可以参考注释1
            cdev = cdev_alloc(); //分配字符设备结构体
            cdev->owner = fops->owner; //设置字符设备结构体
            cdev->ops = fops;
            kobject_set_name(&cdev->kobj, "%s", name);
            //向上注册,这里用到了cd->major和baseminor
            err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
            
之前我们编写字符设备驱动程序只是一个register_chrdev来完成,通过我们将这个函数分解开来就看出来了,如果使用register_chrdev的话,不能指定此设备号,那么就会默认baseminor=0,count=256。这就造成了浪费!因为一个主设备号下的所有此设备号都只能对应同一个字符设备!如果我们使用__register_chrdev_region的话,情况就变了,我们可以指定baseminor和count,这样就可以让一个主设备号下每一个次设备号都对应字节的字符设备了!

注释1:
我们用个例子来说明__register_chrdev_region做了什么:
比如我们先注册了3个字符设备,主设备号都是2,次设备号分别是0、1~2、3,那么经过__register_chrdev_region这个函数就会形成下面的链表:

register_chrdev深入分析 - 小白 - 小白的博客
 
注意:不好意思,这边有个小错误,那就是主设备号不能是0,要从1开始,而不是0!

如果此时如果再想注册一个字符设备,它的主设备号是257,此设备号是0,那么会形成如下链表:

 register_chrdev深入分析 - 小白 - 小白的博客

这里面有两点是很重要的:
第一:chrdevs的每一项并不是只对应一个与其下表相同的主设备号,而是对应一系列主设备号,这些主设备号=“chrdevs下表+n*255”!
第二:chrdevs的每一项主设备号相同的字符设备此设备号不能有交集,主设备号不同的字符设备此设备号可以有交集!

从而我们也看到了__register_chrdev_region这个函数的功能,其实它也是实现一个互斥访问的功能!
分析下列代码:"#include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/platform_device.h> #include <linux/dmaengine.h> #include <linux/dma-mapping.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/poll.h> #include "common.h" #define ADCDATA_SIZE (1024 * 1024) /*C++驱动开发是一个涉及操作系统、硬件交互高性能编程的复杂领域。以下是对C++驱动开发的详细知识点总结: 1. C++驱动概述 定义与作用:C++驱动程序作为硬件设备与操作系统或用户空间程序的桥梁,负责设备控制数据传输。其高性能对底层资源的控制能力使其适合驱动开发。 优势:执行效率高,支持多线程并发,能直接操作硬件设备。 2. 设备驱动类型 用户态驱动:在用户空间运行,开发相对简单,但权限受限,适用于对性能要求高的设备。 内核态驱动:在内核空间运行,权限高,能直接操作硬件,适合高性能低延迟需求,但开发复杂。 虚拟设备驱动:模拟硬件设备,如虚拟网络接口或虚拟磁盘,用于测试或特定应用场景。 固件驱动:与硬件固件交互,处理低级硬件操作,如设备初始化固件更新。 3. 基本组成 初始化与清理: init 模块初始化函数 exit 模块清理函数,用于资源分配释放。 设备文件操作:实现 open ,  read ,  write 等函数,处理用户空间的I/O请求。 中断处理:注册中断处理函数,处理硬件触发的中断,确保及时响应硬件事件。 同步机制:使用信号量、互斥锁等同步原语,避免竞态条件数据竞争。 内存管理:在内核空间分配管理内存,使用 kmalloc  kfree ,避免内存泄漏。 4. 设备文件创建 设备节点:使用 mknod 命令创建,指定主要次设备号。 设备注册:在驱动中注册设备,使用 register_chrdev 注册字符设备,分配设备号。 内核模块管理:使用 insmod 装载模块, rmmod 卸载, lsmod 查看已加载模块。 Makefile配置:正确配置内核模块Makefile,确保编译选项正确,避免编译错误。 5. 同步与异步操作 同步I/O:即时完成I/O操作,适合实时性要求高的场景。 异步I/O:非阻塞操作,提高系统响应速度,适合高性能高吞吐量需求。 阻塞与非阻塞:阻塞调用等待完成,非阻塞立即返回,适合同应用场景。 轮询与中断:轮询检查设备状态,中断驱动I/O在硬件触发时处理,权衡效率与资源消耗。 6. 内存管理 内核与用户空间:使用 copy_from_user  copy_to_user 传输数据,确保正确性安全性。 内存分配:使用 kmalloc  kfree 管理内核空间内存,避免泄漏双重释放。 7. 调试与测试 内核日志:使用 printk 记录日志,通过 dmesg 查看,帮助调试问题排查。 测试方法:编写测试程序,模拟各种使用场景,确保驱动在边界条件下稳定,避免硬件损坏。 开发工具:使用 gdb 调试内核模块,了解内核调试方法限制。 8. 安全性与稳定性 防止溢出:检查缓冲区操作,防止溢出漏洞,确保用户输入的安全。 竞态条件:使用同步机制防止数据竞争,确保并发操作的正确性。 资源管理:正确处理硬件资源,如I/O端口中断号,避免资源泄漏。 9. 跨平台开发 抽象层:开发硬件抽象层,减少平台依赖,提高代码可移植性。 标准库限制:C++标准库在内核中受限,需谨慎使用,必要时开发自定义函数。 10. 实时系统考虑 实时操作系统:在实时系统中开发驱动,需严格控制延迟响应时间,确保系统实时性。 优先级设置:合理设置任务优先级,避免优先级反转,确保系统稳定。 11. 最佳实践 编码规范:遵循内核编码规范,编写可读性可维护性强的代码。 测试充分:覆盖所有可能的使用场景,确保驱动的稳定性可靠性。 文档详细:编写详细的代码注释用户文档,方便他人理解维护。 性能优化:在影响稳定性的前提下,进行性能优化,避免过度优化带来的问题。 总结 C++驱动开发需要深入理解硬件、操作系统并发编程,实践中需断学习实践,解决实际问题,提升开发能力。*/ enum ERR_TYPE { ERR_ERROR = -1, ERR_OK, ERR_PLATFORM_P, ERR_REST_GPIO, ERR_ENABLE_GPIO, ERR_ALLOC_DMAPARAM_ERR }; typedef struct DmaParam { struct dma_chan *m_pDmaChannel; int m_iBufCount; phys_addr_t m_AdcPhys[32]; void __iomem *m_AdcVirt[32]; int m_iWIndex; int m_iRIndex; } DmaParam; typedef struct DmaData { struct cdev *m_pCDev; struct class *m_pClass; dev_t m_DevNo; struct gpio_desc *m_pRestGpio; struct gpio_desc *m_pEnableGpio; DmaParam *m_pParam; int m_iUsed; int m_iTxFlag; int m_iIrqReport; wait_queue_head_t m_Queue; } DmaData; DmaData *g_pDmaData = NULL; int DmaAdcSetUp(DmaParam *pParam, int index); void ChangeBufIndex(void) { printk("write index %d\r\n", g_pDmaData->m_pParam->m_iWIndex ); g_pDmaData->m_pParam->m_iWIndex = (g_pDmaData->m_pParam->m_iWIndex + 1) % g_pDmaData->m_pParam->m_iBufCount; DmaAdcSetUp(g_pDmaData->m_pParam, g_pDmaData->m_pParam->m_iWIndex ); dma_async_issue_pending(g_pDmaData->m_pParam->m_pDmaChannel); } void DmaAdcCallBack(void *data) { printk("dma adc call back\r\n"); g_pDmaData->m_iIrqReport = 1; if (g_pDmaData->m_iTxFlag == 1) { wake_up_interruptible(&g_pDmaData->m_Queue); ChangeBufIndex(); } } int DmaAdcSetUp(DmaParam *pParam, int index) { struct dma_async_tx_descriptor *desc = NULL; enum dma_ctrl_flags flags; printk("dma set up enter\r\n"); if (NULL == pParam || index < 0 || index >= pParam->m_iBufCount) { printk("param error\r\n"); return ERR_ERROR; } flags = DMA_PREP_INTERRUPT; desc = dmaengine_prep_slave_single(pParam->m_pDmaChannel, pParam->m_AdcPhys[index],ADCDATA_SIZE, DMA_DEV_TO_MEM, flags); if (desc == NULL) { printk("desc error\r\n"); return ERR_ERROR; } if (dmaengine_submit(desc) < 0) { printk("dmaengine submit error\r\n"); return ERR_ERROR; } desc->callback = DmaAdcCallBack; printk("dma set up exit\r\n"); return ERR_OK; } int DmaAdcOpen(struct inode *inode, struct file *file) { printk("dma adc open enter\r\n"); if(g_pDmaData->m_iUsed == 1) { printk("device busy\r\n"); return -EAGAIN; } g_pDmaData->m_iUsed = 1; printk("dma adc open exit\r\n"); return 0; } int DmaAdcRelease(struct inode *inode, struct file *file) { printk("dma adc close enter\r\n"); g_pDmaData->m_iUsed = 0; printk("dma adc close exit\r\n"); return 0; } long DmaAdcIoctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *pUser = NULL; int flag = -1; printk("dma adc ioctl enter\r\n"); pUser = (void __user *)arg; if (pUser == NULL) { printk("arg error\r\n"); return ERR_ERROR; } switch (cmd) { case CMD_START: { if(copy_from_user(&flag, pUser, sizeof(int)) != 0) { return ERR_ERROR; } if (flag != START_FLAG && flag != STOP_FLAG) { printk("flag error\r\n"); return ERR_ERROR; } printk("user cmd flag %d\r\n", flag); if (START_FLAG == flag) { g_pDmaData->m_iTxFlag = 1; g_pDmaData->m_pParam->m_iWIndex = 0; g_pDmaData->m_pParam->m_iRIndex = 0; if (DmaAdcSetUp(g_pDmaData->m_pParam, 0) != ERR_OK) { printk("dam adc setup error\r\n"); return ERR_ERROR; } dma_async_issue_pending(g_pDmaData->m_pParam->m_pDmaChannel); } else { dmaengine_terminate_all(g_pDmaData->m_pParam->m_pDmaChannel); g_pDmaData->m_iTxFlag = 0; } gpiod_set_value_cansleep(g_pDmaData->m_pEnableGpio, flag); } break; case CMD_GETDATA: { if (copy_to_user(pUser, g_pDmaData->m_pParam->m_AdcVirt[g_pDmaData->m_pParam->m_iRIndex], ADCDATA_SIZE) !=0) { printk("get data error\r\n"); g_pDmaData->m_pParam->m_iRIndex = (g_pDmaData->m_pParam->m_iRIndex + 1) % g_pDmaData->m_pParam->m_iBufCount; return ERR_ERROR; } g_pDmaData->m_pParam->m_iRIndex = (g_pDmaData->m_pParam->m_iRIndex + 1) % g_pDmaData->m_pParam->m_iBufCount; } break; default: { printk("cmd error\r\n"); return ERR_ERROR; } } printk("dma adc ioctl exit\r\n"); return ERR_OK; } unsigned int DmaAdcPoll(struct file *pFile, struct poll_table_struct *pTable) { unsigned int mask = 0; printk("hello poll enter\r\n"); poll_wait(pFile, &g_pDmaData->m_Queue, pTable); if (g_pDmaData->m_iIrqReport == 1) { printk("data ok\r\n"); g_pDmaData->m_iIrqReport = 0; mask = POLLIN | POLLRDNORM; } printk("hello poll exit\r\n"); return mask; } const struct file_operations g_fops = { .owner = THIS_MODULE, .open = DmaAdcOpen, .release = DmaAdcRelease, .unlocked_ioctl = DmaAdcIoctl, .poll = DmaAdcPoll, }; int CreateCdev(void) { int iRet = -1; struct device *pDevTmp = NULL; printk("create cdev enter\r\n"); iRet = alloc_chrdev_region(&g_pDmaData->m_DevNo, 0, 1, "dmaadc"); if (iRet != 0) { return ERR_ERROR; } g_pDmaData->m_pCDev = cdev_alloc(); if (NULL == g_pDmaData->m_pCDev) { goto CDEV_ALLOC_ERROR; } cdev_init(g_pDmaData->m_pCDev, &g_fops); iRet = cdev_add(g_pDmaData->m_pCDev, g_pDmaData->m_DevNo, 1); if (iRet != 0) { goto CDEV_ADD_ERROR; } g_pDmaData->m_pClass = class_create(THIS_MODULE, "dmaadc"); if (IS_ERR(g_pDmaData->m_pClass)) { goto CDEV_ADD_ERROR; } pDevTmp = device_create(g_pDmaData->m_pClass, NULL, g_pDmaData->m_DevNo, NULL, "dma_adc_dev"); if (IS_ERR(pDevTmp)) { goto DEVICE_CREATE_ERROR; } printk("create cdev exit\r\n"); return ERR_OK; DEVICE_CREATE_ERROR: device_destroy(g_pDmaData->m_pClass, g_pDmaData->m_DevNo); class_destroy(g_pDmaData->m_pClass); g_pDmaData->m_pClass = NULL; CDEV_ADD_ERROR: cdev_del(g_pDmaData->m_pCDev); g_pDmaData->m_pCDev = NULL; CDEV_ALLOC_ERROR: unregister_chrdev_region(g_pDmaData->m_DevNo, 1); return ERR_ERROR; } void ResetGpio(void) { printk("reset gpio enter\r\n"); gpiod_set_value_cansleep(g_pDmaData->m_pRestGpio, 0); gpiod_set_value_cansleep(g_pDmaData->m_pEnableGpio, 0); gpiod_set_value_cansleep(g_pDmaData->m_pRestGpio, 1); printk("reset gpio exit\r\n"); } int InitDmaParam(struct platform_device *platform) { DmaParam *pTmp = NULL; int count = 0; int i = 0; printk("init dma param enter\r\n"); if (NULL == platform) { printk("platform error\r\n"); return ERR_PLATFORM_P; } //get gpio g_pDmaData->m_pRestGpio = devm_gpiod_get_optional(&platform->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(g_pDmaData->m_pRestGpio )) { printk("reset gpio error\r\n"); return ERR_REST_GPIO; } g_pDmaData->m_pEnableGpio = devm_gpiod_get_optional(&platform->dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(g_pDmaData->m_pEnableGpio )) { printk("enable gpio error\r\n"); return ERR_ENABLE_GPIO; } ResetGpio(); //init dma channel pTmp = devm_kzalloc(&platform->dev, sizeof(DmaParam), GFP_KERNEL); if (NULL == pTmp) { printk("devm malloc dma param error\r\n"); return ERR_ALLOC_DMAPARAM_ERR; } pTmp->m_pDmaChannel = dma_request_chan(&platform->dev, "adc"); if (IS_ERR_OR_NULL(pTmp->m_pDmaChannel)) { printk("request dma channel error\r\n"); return ERR_ERROR; } //get dma buf count if (of_property_read_u32(platform->dev.of_node, "num-buf", &count) != 0) { printk("get num buf error\r\n"); return ERR_ERROR; } if (count > 32) { count = 32; } printk("count %d\r\n", count); pTmp->m_iBufCount = count; //dma buf for(i = 0; i < count; i++) { pTmp->m_AdcVirt[i] = dma_alloc_coherent(&platform->dev, PAGE_ALIGN(ADCDATA_SIZE) , &pTmp->m_AdcPhys[i], GFP_KERNEL); if (pTmp->m_AdcVirt[i] != NULL) { memset(pTmp->m_AdcVirt[i], 0, ADCDATA_SIZE); } else { printk("dma mem error %d\r\n", i); return ERR_ERROR; } } g_pDmaData->m_pParam = pTmp; init_waitqueue_head(&g_pDmaData->m_Queue); printk("init dma param exit\r\n"); return ERR_OK; } int DmaAdcProbe(struct platform_device *platform) { struct device *pDev = NULL; DmaData *pTmp = NULL; int iRet = ERR_ERROR; int i = 0; printk("dma adc probe enter\r\n"); //malloc dmadata memory pDev = &platform->dev; if (pDev == NULL) { printk("platform dev error\r\n"); return -EFAULT; } pTmp = dev_get_platdata(pDev); if (pTmp == NULL) { pTmp = devm_kzalloc(pDev, sizeof(DmaData), GFP_KERNEL); if (pTmp == NULL) { printk("devm kzmalloc error\r\n"); return -ENOMEM; } } platform_set_drvdata(platform, pTmp); g_pDmaData = pTmp; printk("dmadata memory ok\r\n"); //init dma data iRet = InitDmaParam(platform); if (iRet != ERR_OK) { printk("init dma param error\r\n"); return iRet; } iRet = CreateCdev(); if (iRet != ERR_OK) { //free dma memory for (i = 0; i < g_pDmaData->m_pParam->m_iBufCount; i++) { dma_free_coherent(&platform->dev, ADCDATA_SIZE, g_pDmaData->m_pParam->m_AdcVirt[i] , g_pDmaData->m_pParam->m_AdcPhys[i]); } dma_release_channel(g_pDmaData->m_pParam->m_pDmaChannel); return iRet; } printk("dma adc probe exit\r\n"); return 0; } int DmaAdcRemove(struct platform_device *platform) { int i = 0; printk("dma adc remove enter\r\n"); for (i = 0; i < g_pDmaData->m_pParam->m_iBufCount; i++) { dma_free_coherent(&platform->dev, ADCDATA_SIZE, g_pDmaData->m_pParam->m_AdcVirt[i] , g_pDmaData->m_pParam->m_AdcPhys[i]); } dma_release_channel(g_pDmaData->m_pParam->m_pDmaChannel); device_destroy(g_pDmaData->m_pClass, g_pDmaData->m_DevNo); class_destroy(g_pDmaData->m_pClass); g_pDmaData->m_pClass = NULL; cdev_del(g_pDmaData->m_pCDev); g_pDmaData->m_pCDev = NULL; unregister_chrdev_region(g_pDmaData->m_DevNo, 1); printk("dma adc remove exit\r\n"); return 0; } struct of_device_id adc_of_match[] = { {.compatible = "adcdma_demo"}, /*用于与设备树compatible属性值匹配的字符串*/ {} }; struct platform_driver g_driver = { .probe = DmaAdcProbe, .remove = DmaAdcRemove, .driver = { .name = "adcdma_demo", .owner = THIS_MODULE, .of_match_table = adc_of_match, }, }; int DmaAdcModuleInit(void) { int iRet = -1; printk("dma adc init enter\r\n"); iRet = platform_driver_register(&g_driver); if (iRet != 0) { printk("driver register error\r\n"); return iRet; } printk("dma adc init exit\r\n"); return iRet; } void DmaAdcModuleExit(void) { printk("dma adc clean up enter\r\n"); platform_driver_unregister(&g_driver); printk("dma adc clean up exit\r\n"); } module_init(DmaAdcModuleInit); module_exit(DmaAdcModuleExit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("vision"); "
最新发布
08-05
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值