5.设备驱动程序

5. 设备驱动程序

  • Linux 内核是一个比较庞大的系统,深入理解内核可以减少在系统移植中的障碍。在系统移植中设备驱动开发是一项很复杂的工作,由于 Linux 内核提供了一部分源代码,同时还提供了对某些公共部分的支持,例如, USB 驱动对读写 U 盘、键盘、鼠标等设备提供了通用驱动程序,一般情况可以直接使用内核提供的驱动。但是对于复杂的 USB 设备没有现成的驱动,就需要读者对驱动开发过程有一定的认识,必要时参考 Linux 源码重新开发驱动程序。

  • 设备驱动,实际上是硬件功能的一个抽象。针对同一个硬件不同的驱动可以将硬件封装成不同的功能。设备驱动是硬件层和应用程序(或者操作系统)的媒介,能够让应用程序或者操作系统使用硬件。

  • 在 Linux 操作系统下有 3 类主要的设备文件类型:块设备、字符设备和网络设备。设备驱动程序是指管理某个外围设备的一段代码,它负责传送数据控制特定类型的物理设备的操作,包括开始和完成 I/O 操作,检测和处理设备出现的错误。

1. 字符设备驱动程序

  • 字符设备是一种能像字节流一样进行串行访问的设备,对设备的存取只能按顺序、按字节存取,不能随机访问。字符设备没有请求缓冲区,必须按顺序执行所有的访问请求。常见的字符设备有鼠标、键盘、串口、控制台等。

  • 应用程序对字符设备的访问是通过字符设备结点来完成的。字符设备是 Linux 中最简单的设备,可以像文件一样访问。应用程序使用标准系统调用打开、读、写和关闭字符设备,完全可以把它们当做普通文件一样进行操作,甚至被 PPP 守护进程使用,用于将一个 Linux系统连接到网上的 modem,也被看做一个普通文件。

  • 当字符设备初始化时,它的设备驱动程序向 Linux 内核注册,向 chrdevs 向量表中增加一个 device_struct 数据结构项。

  • 通常一种类型设备的主设备标识符是固定的,例如 tty 设备是 4。设备的主设备标识符,用作chrdevs 向量表的索引。

  • 向量表中的每一项(即 device_struct 数据结构)包括两个元素:

    • 一个是指向登记的设备驱动程序名字的指针
    • 另一个是指向一组文件操作的指针。这组文件操作本身位于这个设备的字符设备驱动程序中,每一个都处理一个特定的文件操作(如打开、读、写和关闭)。
  • 用户进程通过设备文件对硬件进行访问,对设备文件的操作方式通过一些系统调用来实现,如 openreadwriteclose 等。下面通过一个关键的数据结构 file_operations将系统调用和驱动程序关联起来:

    struct file_operations {int (*seek) (struct inode *struct file *off_tint);int (*read) (struct inode *struct file *charint);int (*write) (struct inode *struct file *off_tint);int (*readdir) (struct inode *struct file *struct dirent *int);int (*select) (struct inode *struct file *int , select_table *);int (*ioctl) (struct inode *struct file *, unsined intunsigned long);int (*mmap) (struct inode *struct file *struct vm_area_struct *);int (*open) (struct inode *struct file *);int (*release) (struct inode *struct file *);int (*fsync) (struct inode *struct file *);int (*fasync) (struct inode *struct file *int);int (*check_media_change) (struct inode *struct file *);int (*revalidate) (dev_t dev);
    };
    
    • 该结构中每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如 read/write 操作时,系统调用根据设备文件的主设备号找到对应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。
  • 编写驱动程序就是针对上面相应的函数编写具体的实现,然后将它们对应上。编写完驱动后,把驱动程序嵌入内核。驱动程序可以采用两种方式进行编译:一种是编译进内核,驱动被静态加载;另一种是编译成模块(modules),驱动模块需要动态加载。

  • 在模块被调入内存时, init()函数向系统的字符设备表登记了一个字符设备:

    int __init chr_dev_init(void)
    {if (devfs_register_chrdev(CHR_MAJOR,"chr_name",&chr_fops))printk("unable to get major %d for chr devs\n", MEM_MAJOR);...return 0;
    }
    
  • cleanup_chr_dev()函数被调用时,它释放字符设备 chr_name 在系统字符设备表中占有的表项:

    void cleanup_chr_dev(void)
    {unregister_chrdev(CHR_MAJOR, "chr_name");
    }
    

2. 块设备驱动程序

  • 块设备具有请求缓冲区,从块设备读取数据时,可以从任意位置读取任意长度,即块设备支持随机访问而不必按照顺序存取数据。例如,可以先存取后面的数据,然后再存取前面的数据,字符设备则不能采用该方式存取数据。 常见的块设备有各种硬盘、 flash 磁盘、 RAM 磁盘等。Linux 下的磁盘设备均为块设备,应用程序访问 Linux 下的块设备结点是通过文件系统及其高速缓存来访问块设备的,并非直接通过设备结点读写块设备上的数据。

  • 块设备既可以用做普通的裸设备存放任意数据,也可以将块设备按某种文件系统类型的格式进行格式化,然后根据该文件系统类型的格式进行读取。无论使用哪种方式,访问设备上的数据都必须通过调用设备本身的方法实现。两者的区别在于前者直接调用块设备的操作方法,而后者则间接(通过文件系统)调用块设备的操作方法。

  • 块设备用与字符设备类似的方法进行设备的注册与释放。块设备使用 register_blkdev()函数和 block_device_operations结构的指针,其中定义的 openreleaseioctl 方法和字符设备的对应方法相同,但没有对 read 和 write 操作定义,因为所有涉及块设备的 I/O 通常由系统进行缓冲处理。

  • 块驱动程序最终必须提供完成实际块 I/O 操作的机制,在 Linux 中,用于这些 I/O 操作的方法称为 request(请求)。

  • 注册块设备时,通过 blk_init_queue 来完成对 request 队列的初始化, blk_init_queue 函数创建队列,并将该驱动程序的 request 函数关联到队列。在模块的清除阶段,调用 blk_cleanup_queue 函数。

  • 初始化块设备的时候,将块设备注册到内核中,下面为块设备的注册函数 mtdblock_release()的实现:

    int register_blkdev(unsigned int major, const char *name)
    {struct blk_major_name **n, *p;int index, ret = 0;mutex_lock(&block_class_lock);/*为块设备指定主设备号,如果指定为 0 则表示由系统来分配*/if (major == 0) {for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {if (major_names[index] == NULL)break;}if (index == 0) {printk("register_blkdev: failed to get major for %s\n", name);ret = -EBUSY;goto out;}major = index;ret = major;}/*为块设备名字分配空间*/p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);if (p == NULL) {ret = -ENOMEM;goto out;}p->major = major;strlcpy(p->name, name, sizeof(p->name));p->next = NULL;index = major_to_index(major);for (n = &major_names[index]; *n; n = &(*n)->next) {if ((*n)->major == major)break;}if (!*n)*n = p;elseret = -EBUSY;if (ret < 0) {printk("register_blkdev: cannot get major %d for %s\n",major, name);kfree(p);}
    out:mutex_unlock(&block_class_lock);return ret;
    }
    
  • 块设备被注册到系统后,访问硬件的操作 open 和 release 等就能够被对应的系统调用指针所绑定,应用程序使用系统调用就可以对硬件进行访问了。下面是块设备主要的操作函数 open()和 release():

    • 块设备 open()操作函数:

      static int mtdblock_open(struct mtd_blktrans_dev *mbd)
      {struct mtdblk_dev *mtdblk;struct mtd_info *mtd = mbd->mtd;int dev = mbd->devnum;DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open\n");if (mtdblks[dev]) {/*如果设备已经打开,则只需要增加其引用计数*/mtdblks[dev]->count++;return 0;}/*为设备创建 mtdblk_dev 对象保存 mtd 设备的信息*/mtdblk = kzalloc(sizeof(struct mtdblk_dev), GFP_KERNEL);if (!mtdblk)return -ENOMEM;mtdblk->count = 1;mtdblk->mtd = mtd;mutex_init(&mtdblk->cache_mutex);mtdblk->cache_state = STATE_EMPTY;if ( !(mtdblk->mtd->flags & MTD_NO_ERASE) && mtdblk->mtd->erasesize){mtdblk->cache_size = mtdblk->mtd->erasesize;mtdblk->cache_data = NULL;}mtdblks[dev] = mtdblk;DEBUG(MTD_DEBUG_LEVEL1, "ok\n");return 0;
      }
      
    • 块设备 release()操作函数:

      static int mtdblock_release(struct mtd_blktrans_dev *mbd)
      {int dev = mbd->devnum;struct mtdblk_dev *mtdblk = mtdblks[dev];DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n");mutex_lock(&mtdblk->cache_mutex);write_cached_data(mtdblk);mutex_unlock(&mtdblk->cache_mutex);if (!--mtdblk->count) { mtdblks[dev] = NULL;	/*用户计数递减为 0 时释放设备*/if (mtdblk->mtd->sync)mtdblk->mtd->sync(mtdblk->mtd);vfree(mtdblk->cache_data);kfree(mtdblk);}DEBUG(MTD_DEBUG_LEVEL1, "ok\n");return 0;
      }
      

3. 网络设备驱动程序

  • 网络设备是面向数据报文的、不支持随机访问, 也没有请求缓冲区。在 Linux里网络设备也可以被称为网络接口,如 eth0,应用程序是通过 Socket(套接字),而不是设备结点来访问网络设备,在系统中不存在网络设备结点。

  • 网络设备用来与其他设备交换数据,它可以是硬件设备,也可以是纯软件设备,如loopback 接口。网络设备由内核中的网络子系统驱动,负责发送和接收数据包,但它不需要了解每项事务如何映射到实际传送的数据包。 许多网络连接(如TCP连接)是面向流的,但网络设备围绕数据包的传输和接收设计。

  • 网络驱动程序不需要知道各个连接的相关信息,它只需处理数据包。字符设备和块设备都有设备号,而网络设备没有设备号,只有一个独一无二的名字,例如 eth0、 eth1 等,这个名字也无须与设备文件结点对应。

  • 内核利用一组数据包传输函数与网络设备驱动程序进行通信,它们不同于字符设备和块设备的 read()和 write()方法。

  • Linux 网络设备驱动程序从下到上分为 4 层,依次为”网络设备与媒介层、设备驱动功能层、网络设备接口层和网络协议接口层“。

  • 在设计具体的网络设备驱动程序时,需要完成的主要工作是编写设备驱动功能层的相关函数以填充 net_device 数据结构的内容,并将net_device 注册入内核。

  • 下面以 DM9000 代码为例说明网络设备驱动的注册、注销等主要过程。

    • 驱动的注册(在设备初始化时被调用)

      static int __init dm9000_init(void)
      {printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME,DRV_VERSION);return platform_driver_register(&dm9000_driver);
      }int platform_driver_register(struct platform_driver *drv)
      {drv->driver.bus = &platform_bus_type;if (drv->probe)drv->driver.probe = platform_drv_probe;if (drv->remove)drv->driver.remove = platform_drv_remove;if (drv->shutdown)drv->driver.shutdown = platform_drv_shutdown;if (drv->suspend)drv->driver.suspend = platform_drv_suspend;if (drv->resume)drv->driver.resume = platform_drv_resume;return driver_register(&drv->driver);
      }
      
    • 驱动的注销(在设备被清除时被调用,其中包括将设备从系统中移除和将驱动从总线上移除。 )

      static void __exit dm9000_cleanup(void)
      {platform_driver_unregister(&dm9000_driver);
      }void platform_driver_unregister(struct platform_driver *drv)
      {driver_unregister(&drv->driver);
      }void driver_unregister(struct device_driver *drv)
      {driver_remove_groups(drv, drv->groups);bus_remove_driver(drv);
      }static void driver_remove_groups(struct device_driver *drv, struct attribute_group **groups)
      {int i;if (groups)for (i = 0; groups[i]; i++)sysfs_remove_group(&drv->p->kobj, groups[i]);
      }void bus_remove_driver(struct device_driver *drv)
      {if (!drv->bus)return;remove_bind_files(drv);driver_remove_attrs(drv->bus, drv);driver_remove_file(drv, &driver_attr_uevent);klist_remove(&drv->p->knode_bus);pr_debug("bus: '%s': remove driver %s\n", drv->bus->name,drv->name);driver_detach(drv);module_remove_driver(drv);kobject_put(&drv->p->kobj);bus_put(drv->bus);
      }
      
      • 有关网络设备驱动的详细接口函数解析和驱动移植将在后面的章节中叙述。

4. 内存与I/O操作

  • 一般来说,在系统运行时,外设的 I/O 内存资源的物理地址是已知的,由硬件的设计决定。但是 CPU 通常并没有为这些已知的外设 I/O 内存资源的物理地址,预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问 I/O 内存资源,只能先将它们映射到内核的虚拟地址空间内(通过页表),然后才能根据映射的内核虚拟地址范围访问这些 I/O 内存资源。

  • Linux 在 io.h 头文件中声明了函数 ioremap()iounmap(),分别用来将 I/O 内存资源的物理地址映射和解映射到核心虚拟地址空间(3GB~4GB)中,原型如下:

    void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
    void iounmap(void * addr);
    
  • 在将 I/O 内存资源的物理地址映射成内核的虚拟地址后,就可以像读写 RAM 那样直接读写 I/O 内存资源了。但为了保证驱动程序跨平台的可移植性,应该使用 Linux 中特定的函数访问 I/O 内存资源,而不是通过指向内核虚拟地址的指针直接访问。如在 ARM平台上,读写 I/O 的函数如下:

    #define __raw_base_writeb(val,base,off) __arch_base_putb(val,base,off)
    #define __raw_base_writew(val,base,off) __arch_base_putw(val,base,off)
    #define __raw_base_writel(val,base,off) __arch_base_putl(val,base,off)#define __raw_base_readb(base,off) __arch_base_getb(base,off)
    #define __raw_base_readw(base,off) __arch_base_getw(base,off)
    #define __raw_base_readl(base,off) __arch_base_getl(base,off)
    
  • 驱动程序中 mmap()函数的实现原理是,用 mmap 映射一个设备,表示将用户空间的一段地址关联到设备内存上,这样当程序在分配的地址范围内进行读取或者写入时,实际上就是对设备的访问。这一映射原理类似于 Linux 下 mount 命令,将一种类型的文件系统或设备挂载到另外一个文件系统或者目录下时,挂载成功后,对挂载点的任何操作实际上是对被挂载的文件系统和设备的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/715198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法:堆

朋友们大家好啊&#xff0c;本篇文章来到堆的内容&#xff0c;堆是一种完全二叉树&#xff0c;再介绍堆之前&#xff0c;我们首先对树进行讲解 树与堆 1.树的介绍1.1节点的分类 2.树的存储结构3.二叉树的概念和结构3.1 二叉树的特点3.2 特殊的二叉树3.3二叉树的存储结构 4.堆的…

Acwing---1460. 我在哪?

我在哪&#xff1f; 1.题目2.基本思想3.代码实现 1.题目 农夫约翰出门沿着马路散步&#xff0c;但是他现在发现自己可能迷路了&#xff01; 沿路有一排共 N N N 个农场。 不幸的是农场并没有编号&#xff0c;这使得约翰难以分辨他在这条路上所处的位置。 然而&#xff0c;…

Mybatis | 动态SQL

目录: 动态SQL中的 “元素” :\<if>元素\<choose>、\<when>、\<otherwise>元素\<where>、\<trim>元素\<set>元素\<foreach>元素\<bind>元素 作者简介 &#xff1a;一只大皮卡丘&#xff0c;计算机专业学生&#xff0c;正…

单细胞Seurat - 降维与细胞标记(4)

本系列持续更新Seurat单细胞分析教程&#xff0c;欢迎关注&#xff01; 非线形降维 Seurat 提供了几种非线性降维技术&#xff0c;例如 tSNE 和 UMAP&#xff0c;来可视化和探索这些数据集。这些算法的目标是学习数据集中的底层结构&#xff0c;以便将相似的细胞放在低维空间中…

__vueParentComponent和__vue__获取dom元素上的vue实例

vue2: 使用__vue__ const el document.querySelector(.xxx); const vueInstance el.__vue__;vue3: 使用 __vueParentComponent const el document.querySelector(.xxx); const vueInstance el.__vueParentComponent;

Python错题集-4:NameError:(变量名错误)

1问题描述 Traceback (most recent call last): File "D:\pycharm\projects\1-可视化学习\8.3更改小提琴图的中位数、均值、颜色等.py", line 8, in <module> violin_parts plt.violinplot(data, showmediansTrue, showmeansTrue) …

代码随想录算法训练营第四十四天 完全背包 、零钱兑换 II 、组合总和 Ⅳ

代码随想录算法训练营第四十四天 | 完全背包 、零钱兑换 II 、组合总和 Ⅳ 完全背包 题目链接&#xff1a;题目页面 (kamacoder.com) 解释一、01背包 一维 &#xff1a;为什么要倒序遍历背包&#xff1f; 首先要明白二维数组的递推过程&#xff0c;然后才能看懂二维变一维的…

【MATLAB源码-第150期】基于matlab的开普勒优化算法(KOA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 开普勒优化算法&#xff08;Kepler Optimization Algorithm, KOA&#xff09;是一个虚构的、灵感来自天文学的优化算法&#xff0c;它借鉴了开普勒行星运动定律的概念来设计。在这个构想中&#xff0c;算法模仿行星围绕太阳的…

项目风险:测试大佬结合实例告诉你如何应对!

项目有风险 今天下午15点&#xff0c;团队成员D向他的主管Z反馈他测试的项目有风险&#xff1a;项目在测试周期内&#xff0c;但在用例评审时发现有一处功能逻辑有争议&#xff0c;需要产品经理跟业务方确认&#xff0c;可能出现的情况有&#xff1a; 1 不变更需求&#xff0…

【技巧】SpringCloud Gateway实现多子域(单个应用开放多个端口)

0. 目录 1. 需求背景2. 实现3. 额外 - 其它Servlet容器实现3.1 Undertow3.2 Tomcat 4. 相关 1. 需求背景 浏览器针对单个网站地址(ipport)存在“6个请求”限制&#xff1b;通过多子域配置可以突破这个限制&#xff0c;增加网站的响应效率&#xff0c;尤其是针对三维服务这类大…

【深入了解设计模式】组合设计模式

组合设计模式 组合模式是一种结构型设计模式&#xff0c;它允许你将对象组合成树状结构来表现“整体-部分”关系。组合模式使得客户端可以统一对待单个对象和组合对象&#xff0c;从而使得代码更加灵活和易于扩展。 概述 ​ 对于这个图片肯定会非常熟悉&#xff0c;上图我们可…

Carla自动驾驶仿真九:车辆变道路径规划

文章目录 前言一、关键函数二、完整代码效果 前言 本文介绍一种在carla中比较简单的变道路径规划方法&#xff0c;主要核心是调用carla的GlobalRoutePlanner模块和PID控制模块实现变道&#xff0c;大体的框架如下图所示。 一、关键函数 1、get_spawn_point(),该函数根据指定r…

c语言字符串函数之strcpy函数,strnpy函数

strcpy函数 语法格式 strcpy(字符数组1,字符串2&#xff09; 它的作用是把字符串2复制到字符数组1里面 #include<stdio.h> #include<string.h> int main() {char c[]"河南";char d[]"安徽";char d[];printf("%s\n",strcpy(c,d));…

力扣hot100题解(python版41-43题)

41、二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示例…

【C语言结构体】用户自定义类型--结构体,结构体传参,位段,联合体和枚举【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言结构体】用户自定义类型--结构体&#xff0c;结构体传参&#xff0c;位段&#xff0c;联合体和枚举【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 上一篇&#xff08;ht…

GO—函数

Go 语言支持普通函数、匿名函数和闭包&#xff0c;从设计上对函数进行了优化和改进&#xff0c;让函数使用起来更加方便。 Go 语言的函数属于“一等公民”&#xff08;first-class&#xff09;&#xff0c;也就是说&#xff1a; 函数本身可以作为值进行传递。支持匿名函数和闭…

Leetcode.2369 检查数组是否存在有效划分

题目链接 Leetcode.2369 检查数组是否存在有效划分 rating : 1780 题目描述 给你一个下标从 0 0 0 开始的整数数组 n u m s nums nums &#xff0c;你必须将数组划分为一个或多个 连续 子数组。 如果获得的这些子数组中每个都能满足下述条件 之一 &#xff0c;则可以称其为…

推荐6款SSH远程连接工具

1、Xshell 介绍&#xff1a; xshell是一个非常强大的安全终端模拟软件&#xff0c;它支持SSH1, SSH2, 以及Windows平台的TELNET 协议。Xshell可以在Windows界面下用来访问远端不同系统下的服务器&#xff0c;从而比较好的达到远程控制终端的目的。 业界最强大的SSH客户机 官…

数据分析-Pandas数据的直方图探查

数据分析-Pandas数据的直方图探查 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&…

农产品质量追溯系统—功能介绍(2)

储藏管理 储藏信息管理对需要储藏的农产品,记录储藏的相关信息,如储藏开始时间、存放仓库、操作人员、储藏原因等; 仓库信息管理物流管理 物流公司管理对相关的物流公司信息进行登记,以便于管理和追溯; 车辆管理