嵌入式linux驱动学习-用cdev代替register_chrdev()

2023-11-07 14:50

本文主要是介绍嵌入式linux驱动学习-用cdev代替register_chrdev(),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

​上回说到字符设备驱动程序的注册与销毁register_chrdev()和unregister_chrdev()这是有缺陷的。

嵌入式lnux驱动学习-2.一个驱动程序的流程

现在用另外一个更好的方法代替,我们先来看看register_chrdev()实际上是调用了

__register_chrdev(major, 0, 256, name, fops);

static inline int register_chrdev(unsigned int major, const char *name,          const struct file_operations *fops){  return __register_chrdev(major, 0, 256, name, fops);}

这个256其实就是申请的次设备号个数。

还记得怎么创建设备节点吗,无论是手动创建还是自动创建都是用主设备号加上次设备号,一个驱动程序可以有很多不同的设备节点

一个驱动程序有自己对应的file_operations结构体,A驱动对应A_fop结构体,用register_chrdev()后,A驱动的256个设备节点都对应A_fop结构体。

用另外一种方法,可以指定次设备号个数,举例:

用register_chrdev()后,A驱动主设备号254,B驱动主设备号就不能用254,不然就冲突了。

而另外一种方法,A驱动主设备号254,次设备号申请0-2,3个,B驱动主设备号仍然可以用254,次设备号只要不用0-3就行,主设备号相同也不会产生冲突。

实际上另外一种方法就是把register_chrdev()展开:

1.分配主次设备号

#define LED_MAJOR 0#define DEVICE_NUM 1static int major = LED_MAJOR;static int __init myled_init(void){  int ret;  dev_t devno = MKDEV (major,0);  if (major)    ret = register_chrdev_region(devno, DEVICE_NUM, "myled");  else {    ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;    major = MAJOR(devno); }

dev_t devno定义了完整设备号, 为 32 位, 其中 12 位为主设备号, 20 位为次设备号。 

使用如下宏可以从 dev_t 获得主设备号和次设备号:

MAJOR (dev_t dev)
MINOR (dev_t dev)

使用如下宏从主、次设备号获得完整的设备号

MKDEV (major,minor)

register_chrdev_region()函数用于已知起始设备的设备号的情况, 而alloc_chrdev_region() 用于设备号未知, 向系统动态申请未被占用的设备号的情况,可以自动避开设备号重复的冲突。

DEVICE_NUM为我们要申请的次设备号个数,这里设置了1个。

2.初始化 cdev 结构体

在 Linux 内核中, 使用 cdev 结构体描述一个字符设备, cdev 结构体的定义如下:

struct cdev {  struct kobject kobj; /* 内嵌的 kobject */  struct module *owner; /* 所属模块 */  struct file_operations *ops; /* 文件操作结构体 */  struct list_head list;  dev_t dev; /* 设备号 */  unsigned int count;};

cdev 结构体里有一个重要成员 file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。

绑定file_operations结构体在cdev结构体初始化中完成

为了精简就写个什么都没有的open函数。

static int led_open (struct inode *node, struct file *filp){    return 0;}static struct file_operations myled_oprs = {  .owner = THIS_MODULE,  .open  = led_open,};static int __init myled_init(void){  int ret;  dev_t devno = MKDEV (major,0);  if (major)    ret = register_chrdev_region(devno, DEVICE_NUM, "myled");  else {    ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;    major = MAJOR(devno);  }    cdev_init(&cdev_myled, &myled_oprs);//初始化  ...... }

3.添加驱动

很简单,就是在初始化后加一句

cdev_add (&cdev_myled, devno, DEVICE_NUM);

4.删除驱动和设备号

在驱动出口使用

  cdev_del(&cdev_myled);  unregister_chrdev_region(MKDEV (major, 0), DEVICE_NUM);

5.完整测试

自动创建设备节点的方式是一样的,我们只申请一个次设备号0,但是用次设备号0和1,创建两个设备节点myled0,myled1。

然后写一个简单的应用程序,功能只是打开设备节点,如果是一个myled0能打开,myled1打不开即正常。

驱动:

#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <linux/cdev.h>#include <linux/device.h>#define LED_MAJOR 0#define DEVICE_NUM 1static int major = LED_MAJOR;static struct class *led_class;static struct cdev cdev_myled;static int led_open (struct inode *node, struct file *filp){    return 0;}static struct file_operations myled_oprs = {  .owner = THIS_MODULE,  .open  = led_open,};static int __init myled_init(void){  int ret;  dev_t devno = MKDEV (major,0);  if (major)    ret = register_chrdev_region(devno, DEVICE_NUM, "myled");  else {    ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;    major = MAJOR(devno);  }    cdev_init(&cdev_myled, &myled_oprs);  cdev_add (&cdev_myled, devno, DEVICE_NUM);  led_class = class_create(THIS_MODULE, "myled");  device_create(led_class, NULL, MKDEV(major, 0),NULL,"myled0");     device_create(led_class, NULL, MKDEV(major, 1),NULL,"myled1");   return 0;}static void __exit myled_exit(void){  cdev_del(&cdev_myled);  unregister_chrdev_region(MKDEV (major, 0), DEVICE_NUM);  device_destroy(led_class, MKDEV(major, 0));  device_destroy(led_class, MKDEV(major, 1));    class_destroy(led_class);}module_init(myled_init);module_exit(myled_exit);MODULE_LICENSE("GPL");

应用:

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>int main(int argc,char **argv){  int fd;  if(argc != 2) {    printf("usage:%s num",argv[0]);    return 0;  }  fd = open(argv[1],O_RDWR);  if(fd < 0) {    printf("can't open!\n");  } else    printf("can open\n");  return 0;}

makefile:

KERN_DIR = /usr/src/linux-headers-4.8.0-36-genericall:  make -C $(KERN_DIR) M=`pwd` modules  gcc -o led_test led_test.cclean:  make -C $(KERN_DIR) M=`pwd` modules clean  rm -rf modules.order  rm -f led_nothingobj-m += led_nothing.o

文件传入linux系统,make命令编译,insmod命令加载驱动:

结果:

测试成功

6.测试2

两个驱动用同一个主设备号,看看系统能不能识别

如下代码,第一个主设备号由系统自动分配后,第二个主设备号就用和第一个一样的,两个驱动对应不同的open函数,分别生成两个设备,对应设备成功打开时,用printk输出信息。

如果打开不同节点时,输出信息不同,说明成功。

应用程序和上文完全相同

#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <linux/cdev.h>#include <linux/device.h>#define LED_MAJOR 0#define DEVICE_NUM 2static int major = LED_MAJOR;static struct class *led_class;static struct cdev cdev_myled;static struct cdev cdev_myled2;static int led_open (struct inode *node, struct file *filp){    printk("myled0/1 open\n");  return 0;}static struct file_operations myled_oprs = {  .owner = THIS_MODULE,  .open  = led_open,};static int led_open2 (struct inode *node, struct file *filp){    printk("myled2/3 open\n");  return 0;}static struct file_operations myled_oprs2 = {  .owner = THIS_MODULE,  .open  = led_open2,};static int __init myled_init(void){  int ret;  dev_t devno = MKDEV (major,0);  if (major)    ret = register_chrdev_region(devno, DEVICE_NUM, "myled");  else {    ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;    major = MAJOR(devno);  }  cdev_init(&cdev_myled, &myled_oprs);  cdev_add (&cdev_myled, devno, DEVICE_NUM);    register_chrdev_region(MKDEV (major,2), DEVICE_NUM, "myled2");    cdev_init(&cdev_myled2, &myled_oprs2);  cdev_add (&cdev_myled2, MKDEV (major,2), DEVICE_NUM);  led_class = class_create(THIS_MODULE, "myled");  device_create(led_class, NULL, MKDEV(major, 0),NULL,"myled0");   device_create(led_class, NULL, MKDEV(major, 1),NULL,"myled1");     device_create(led_class, NULL, MKDEV(major, 2),NULL,"myled2");   device_create(led_class, NULL, MKDEV(major, 3),NULL,"myled3");   return 0;}static void __exit myled_exit(void){  cdev_del(&cdev_myled);  unregister_chrdev_region(MKDEV (major, 0), DEVICE_NUM);  device_destroy(led_class, MKDEV(major, 0));  device_destroy(led_class, MKDEV(major, 1));    cdev_del(&cdev_myled2);  unregister_chrdev_region(MKDEV (major, 2), DEVICE_NUM);  device_destroy(led_class, MKDEV(major, 2));  device_destroy(led_class, MKDEV(major, 3));    class_destroy(led_class);}module_init(myled_init);module_exit(myled_exit);MODULE_LICENSE("GPL");

编译后insmod装载驱动,cat /proc/devices查看一下

两个主设备号相同的驱动程序出现了

四个设备节点也都打开成功,用dmseg命令查看内核打印信息

成功。可见cdev方法虽然多了几步,但是更加灵活。在今后的讲解中为了精简代码,还是用register_chrdev()。

更多内容与参考资料:大叔的嵌入式小站:
嵌入式linux驱动学习-3.用cdev代替register_chrrdev

这篇关于嵌入式linux驱动学习-用cdev代替register_chrdev()的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/364347

相关文章

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

linux下shell脚本启动jar包实现过程

《linux下shell脚本启动jar包实现过程》确保APP_NAME和LOG_FILE位于目录内,首次启动前需手动创建log文件夹,否则报错,此为个人经验,供参考,欢迎支持脚本之家... 目录linux下shell脚本启动jar包样例1样例2总结linux下shell脚本启动jar包样例1#!/bin

Linux之platform平台设备驱动详解

《Linux之platform平台设备驱动详解》Linux设备驱动模型中,Platform总线作为虚拟总线统一管理无物理总线依赖的嵌入式设备,通过platform_driver和platform_de... 目录platform驱动注册platform设备注册设备树Platform驱动和设备的关系总结在 l

linux批量替换文件内容的实现方式

《linux批量替换文件内容的实现方式》本文总结了Linux中批量替换文件内容的几种方法,包括使用sed替换文件夹内所有文件、单个文件内容及逐行字符串,强调使用反引号和绝对路径,并分享个人经验供参考... 目录一、linux批量替换文件内容 二、替换文件内所有匹配的字符串 三、替换每一行中全部str1为st