文章目录
一、驱动简介(一)关于驱动(二)驱动的分类1. 字符设备驱动2. 块设备驱动3. 网络设备驱动
二、Linux内核模块(一)linux内核模块三要素1. 入口2. 出口:3. 许可证
(二)linux内核模块链接脚本文件
三、外部编译关于内部编译和外部编译
一、驱动简介
(一)关于驱动
什么是驱动? 驱动是在Linux中运行在内核空间的一段控制设备的代码,它充当计算机操作系统和硬件设备之间的接口,向上提供接口,向下控制硬件。 Linux驱动分为字符设备驱动、块设备驱动、网络设备驱动
如何理解linux中一切皆文件? linux中将设备、管道、套接字等抽象为文件的形式,使得用户可以通过统一的接口(如文件操作命令或系统调用)来访问它们。
Linux为何要分为内核层和应用层? 为了提高系统的稳定性和可维护性。 内核层是操作系统的核心,它负责管理系统的各种资源,内核层运行在最高的特权级别下,具有访问系统资源的最高权限。 应用层是建立在内核层之上的,应用层的代码通常由第三方开发者编写,因此其质量和可靠性难以保证。 将应用层与内核层分离,既可以提高操作系统可维护性,而且一个应用程序出现问题,不会影响整个系统的稳定性。
(二)驱动的分类
1. 字符设备驱动
按照 字节流 来访问的,只能 顺序 不能无序访问的设备
注:流,强调顺序
LED、鼠标、键盘、触摸屏、LCD(真缓存)
2. 块设备驱动
按照 block(512字节) 来访问,可以 顺序或无序 访问
访问最小单位就是一个block,即使一次操作仅需一个字节,也是直接读取512字节。
eMMC、U盘、NAND Flash
3. 网络设备驱动
借助网络协议栈,负责网络数据收发工作的代码 网络设备没有设备节点(设备文件)
注:完成的是数据链路和物理层的工作没有设备节点
RTL8211网卡、DM9000、CS8900
二、Linux内核模块
(一)linux内核模块三要素
入口函数和出口函数实现了模块的启动和关闭过程。 许可证则是模块的授权和约束,它定义了模块的使用和分发规则。
1. 入口
安装驱动时执行的函数,在入口分配资源 当驱动模块被加载时,内核会调用入口函数执行初始化工作。 入口函数通常用于注册设备等
2. 出口:
卸载驱动时执行的函数,在出口做资源释放工作 当驱动模块被卸载时,内核会调用出口函数执行清理工作。
3. 许可证
许可证用于定义模块的开放程度,限制模块的使用和分发。 常见的许可证包括GPL、LGPL、BSD等。
linux内核是开源的,所以基于linux内核编写的驱动也必须开源,要遵从GPL开源协议
GNU(组织):gnu is not unix GPL(开源协议):General Public License
(二)linux内核模块
#include
#include
//static:限定作用域
//__init:注意此处是两个下划线,他是linux内核中的一个宏,
// 这个宏的作用是告诉编译器将入口函数放在.init.text
// #define __init __section(".init.text")
static int __init demo_init(void){
return 0;
}
//__exit:也是linux内核中的一个宏
static void __exit demo_exit(void){
}
module_init(demo_init); //告知内核入口函数地址
module_exit(demo_exit); //告知内核出口函数地址
MODULE_LICENSE("GPL"); //许可证
注:内核中错误码是负数,无错误返回0入口函数有返回值,出口函数没有返回值 理解:将入口函数和出口函数分别放在不同的代码段,当需要调用某个驱动程序的入口或出口函数时,只需要到对应的代码段位置去寻找调用即可。因此如果不将其放在对应的代码段,可能会找不到想要调用的函数
链接脚本文件
这个文件是一个汇编文件,用于指导链接器如何将编译后的目标文件(.o文件)和库文件链接成最终的内核映像(vmlinux)
链接脚本:将存储位置分为各个段,告诉编译器该代码存放在指定的位置
链接脚本文件vmlinux.lds.S通常位于内核源代码树的arch/xxx/kernel/目录下,其中xxx代表CPU的架构
三、外部编译
make -C 源码目录 M=$(shell pwd) modules
@# -C 切换到指定目录下,读取Makefile文件,并对此Makefile文件执行make
@# make modules 模块化编译(单纯执行该命令会编译内核模块下的所有模块,M默认为.)
@# M 指定编译模块的路径
obj-m:=$(modname).o
@# 执行make modules时,编译依赖的文件是由obj-m变量指定的,因此外部编译时需要指定obj-m变量的值
使用linux-5.10.61源码目录的路径下编译的.ko文件 使用ubuntu虚拟机目录下的Makefile文件编译的.ko文件
注:当前ubuntu源码路径 KERNELDIR := /lib/modules/$(shell uname -r)/build/
注:uname -r 查看内核版本
关于内部编译和外部编译
内部编译,也称为静态编译,是指将内核模块编译进内核中,然后生成内核镜像的过程。 可以在系统启动时一次性加载,避免了模块加载和符号解析的时间,但内核镜像的大小也会相应增加。
外部编译,也称为动态编译,是指将内核模块编译成一个单独的目标文件,然后在运行时通过内核的模块加载器加载到内核中。 在外部编译的过程中,内核和模块是分开的,可以根据需要动态加载和卸载模块,减少内核镜像的大小,但是在运行时需要进行模块加载和符号解析,因此效率较低。