文章目录

一、驱动简介(一)关于驱动(二)驱动的分类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 查看内核版本

关于内部编译和外部编译

内部编译,也称为静态编译,是指将内核模块编译进内核中,然后生成内核镜像的过程。 可以在系统启动时一次性加载,避免了模块加载和符号解析的时间,但内核镜像的大小也会相应增加。

外部编译,也称为动态编译,是指将内核模块编译成一个单独的目标文件,然后在运行时通过内核的模块加载器加载到内核中。 在外部编译的过程中,内核和模块是分开的,可以根据需要动态加载和卸载模块,减少内核镜像的大小,但是在运行时需要进行模块加载和符号解析,因此效率较低。