linux驱动开发(预配知识篇)

本文深入介绍了Linux驱动开发,包括设备驱动在应用程序和硬件之间的桥梁作用,驱动的三个主要考虑因素:选项、开发时间和程序复杂性。内核功能涉及进程管理、内存管理、文件系统、设备管理和网络功能。设备被分为字符模块、块模块和网络模块。装载和卸载模块通过insmod和rmmod等工具完成。文章还提及内核符号表、模块初始化清理函数以及一些关键的内核头文件和API。此外,讨论了预处理标记如__init和__exit在模块生命周期中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

驱动开发

通俗话来说讲

设备驱动可以看作是应用程序和实际设备之间的软件层,驱动程序的这种特权角色客人白那些这选择如何展现设备特性,也就是说,即使对与相同的设备。不同的驱动程序可能提供不同的功能。实际 的驱动程序设计应该在许多要考虑的因素之间做出平衡。

驱动开发综合考虑以下三个方面

1 提供给用户尽量多的选项,

2 编写驱动程序要占用的时间

3 保持程序简单二不至于错误丛生

内核功能划分

内核功能库分为以下几个部分

进程管理

​ 进程管理功能负责创建和销毁进程,并处理它们和外部世界之间的链接(输入输出)。内核处理管理不同进程之间的通信,如何共享CPU的调度器也是进程管理的一部分。内核进程管理活动就是在单个或多个CPU上实现多个进程的抽象

内存管理

​ 内存是计算机的主要资源之一,用来管理内存的策略是决定系统性能一个关键因素。内核在有限可用资源之上伪每个进程都创建一个虚拟地址空间。

文件系统

​ Unix中每个对象几乎都可以当作文件看待,内核在没有结构的硬件上构造结构话的文件系统,Linux支持多文件系统类型,也就是在物理介质上组织数据的不同方式

设备管理

​ 所有设备控制操作都由与被控制设备相关代码来完成,这段代码就叫做驱动程序。内核必须伪系统中的没见外设嵌入相关的驱动程序,

网络功能

​ 网络功能页必须由操作系统管理,大部分网络操作和具体进程无关,数据包的传入是异步事件。,在某个进程处理这些数据包之间必须收集、标识和分发这些数据包。系统负责在应用程序和网络接口之间传递数据包,并根据网络活动控制程序的执行。所有的路由和地址解析问题都市由内核处理

设备和模块分类

Linux系统将设备分为三种基本类型

字符模块

​ 字符设备时个能够像字节流一样被访问的设备、由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少实现open、close、read和write系统调用。字符终端(/dev/console)和串口(/dev/ttys0)就是字符设备。他们能够很好地说明六这种抽象概念。字符设备可用通过文件系统节点来访问。比如/dev/tty1和/dev/lp0,这些设备文件和普通文件之间的唯一区别在与对普通文件的访问可用前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道。页存在具有数据区特性的字符设备,

块模块

​ 和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备(例如磁盘)上能够容纳文件系统。在大多数书Unix系统中,进行I/O操作时快设备每次只能传输一个或多个完整的块。,而每个块包括512字节。Linux可以让应用程序像字符设备的区别仅仅在于内核内部管理数据的方式。

网络模块

​ 任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备,许多网络连接式面向流的,但网络设备却围绕数据包的传输和接收而设计,网络驱动程序不需要知道各个连接的相关信息,他只要处理数据包即可。

装载和卸载模块

​ 在构造模块之后,下一步就是将模块装入内核。insmod完成装载insmod程序,它将模块的代码和数据装入内核,任何使用内核符号表解析模块中任何为解析的符号。内核不会修改模块的磁盘文件,而仅仅修改内核中的副本

​ 内核如何支持insmod工作

实际上它依赖与定于在kernel/module.c中的系统调用。函数sys_init_module给模块分配内核内存(函数vmalloc负责内核分配)以便装载模块 -------> 该系统调用将模块正文复制到内存区域,并通过内核符号表解析模块中的内核引用。------>调用模块的初始化函数

注: 只有系统调用名字前带有sys_前缀

内核符号表

​ 了解了insmod使用公共内核符号表来解析模块为定义的符号。公告内核符号表中包含了所有的全局内核项(函数和变量)的地址,这式实现模块化驱动程序所必需的。当模块被装入内核后,它所到处的任何符号都会变成内核符号表的一部分。

模糊层叠技术

​ 模糊层叠技术在复杂的项目中非常有用,如果以设备驱动程序的形式实现一个新的软件抽象,则可以伪硬件相关的实现提供一个插头

​ Linux内核头文件提供了一个方便的方法管理符号对模块外部的可见性,从而减少了可能造成的名字空间污染(名字空间中的名称可能会和内核其他地方定义的名称发生冲突)并且适当隐藏信息,

如果一个模块需要从其他模块导出符号,则应该使用下main的宏。

  EXPORT_SYMBOL(name);
  EXPORT_SYMBOL_GPL(name);

​ 这两个宏均用将给定的符号导出模块外部。_GPL版本使得要导出的模块只能被GPL许可证下的模块使用。符号必须在模块文件的全局 部分导出,不能再函数中导出。这式因为上面这两个宏将被扩展为一个特殊变量的声明,而该变量必须式全局 的。该变量将在模块可执行文件的特殊部分中保存,在装载时,内核通过这个段来寻找模块导出的变量 (看不懂)

预备知识

​ 所有模块代码中都包含下面两行代码:

#include<linux/module.h>   //包含可装载模块需要的大量符号和函数 
#include<linux/init.h>     //init.h的目的时指定初始化和清除函数,
尽管不是严格要求的,但模块应该指定代码所使用的许可证。为此,我们只需要包含MODULE_LICENSE行:
    MODULE_LICENSE("GPL");
内核能够识别的许可证由
    “GPL”(任一版本的GUN通用公共许可证)、
    “GPL v2”(GPL 版本2)
    “GPL and additional rights”(GPL及附加权力)
    “Dual BSD/GPL”(BSD/GPL双重许可证)
    “Proprietary”(专有)
    如果一个模块没有显式地标记为上述内核可识别的许可证,则会被假定式专有的,而内核装载这种模块就会被污染。如同我们在第一章"许可证条款"中提到的,内核开发者不太愿意帮助因为装载专有模块而遇到问题的用户
    

在内核源代码中可能还遇到__devinit和__devinitdata只有在内核未被配置为支持热插拔设备的情况下,这两个标记才会被翻译为__init 和 __initdata

案例解析
insmod
modrprobe
rmmod
		//用来装载模块到正运行的内核和移除模块的用户空间工具
#include<linux/init.h>
module_init(init_function);
module_exit(cleanup_function);
		//用于指定模块的初始化和清除函数的宏
__init
__initdata
__exit
__exitdata
		//仅用于模块初始化或清除阶段的函数(__init和__exit)和数据(__initdata和__exitdata)标记。标记为初始化的项目会在初始化结束后丢弃;而退出项目在内核未被配置未可卸载模块的情况下被丢弃。内核通过将相应的目标对象防止在可执行文件的特殊ELF段中而让这些标记器作用
#include<linux/cshed.h>
    最重要的头文件之一。该文件包括驱动使用的大量内核API定义,包括睡眠函数以及各种变量声明。
struct task_struct *current;
//当前进程    
current->pid //进程id
current->comm  //进程命令名
obj-m   //由内核构造系统使用的makefile符号,用来确定在当前目录中应构造哪些模块。

/sys/module	   //  /sys/module是sysfs目录层次构造中包含当前已装载模块信息的目录
/proc/modules  //  只在单个文件中包括这些信息,其中包含了模块名称、每个模块使用的计数等。
vermagic.o     //  内核源代码目录中的一个目标文件,它描述了模块的构造环境。
#include<linux/module.h>  //必需的头文件,他必须包含在模块源代码中
#include<linux/version.h>  //包含所构造内核版本信息的头文件。
Linux_VERSION_CODE       //整数宏,在处理依赖问题的预处理语句非常有用
EXPORT_SYSBOL(symbol)    //用来导出单个符号到内核的宏
EXPORT_SYSBOL_GPL(symbol)  //用于导出符号的使用仅限于GPL许可证下的模块
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
//在目标文件中添加关于模块的文档信息
#include<linux/moduleparam.h>
module_param(variable,type,param);
#include<linux/kernel.h>
int printk(const char * fmt,...);
//函数printf的内核代码。
    
    
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值