Linux 驱动的内核适配 - 方法

news2024/10/6 20:32:38

原生与野生

Linux 的驱动代码大致可分为两种:一种是已经进入 mainline 的,当内核 API 变化时,会被同步地修改;还有一种是 out-of-tree 的,需要用一套驱动代码去适配不同版本的内核。由于内核 API 持续变动的特性,进行内核适配就成了做驱动开发绕不过去的一个问题。

材料准备

「适配」简单说就是要编译出针对某个内核版本的 ".ko" 文件,这和普通的驱动编译没什么两样,原材料一个是内核头文件(在 make target 中由 "C" 参数指定路径),一个是驱动源码(由 "M" 参数指定路径)。

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

「内核头文件」是为编译 kernel modules 提供的一组头文件,在 RedHat/CentOS 系统中被命名为 "kernel-devel"【注-1】,因此亦可被称作「内核开发包」。

内核头文件怎么获取呢?

(1) 对于 Ubuntu 系统,以 18.04 (bionic) 版本为例,使用 "apt list linux-headers-unsigned-*-generic" 命令可列出该版本支持的标准内核(4.15, 4.18, 5.0, 5.3, 5.4)的头文件,对于这些标准内核,直接 "apt install" 安装即可。对于其他非标准内核(比如 5.2),可到这个网站下载对应的内核头文件。

(2) 对于 RedHat/CentOS 系统,标准内核(比如 4.18.0-240.el8)的头文件可从镜像网站下载。

UbuntuRedHat/CentOS
内核头文件安装包名称linux-headers-<kernel-version>kernel-devel-<kernel-version>
内核头文件安装位置/usr/src/linux-headers-<kernel-version>/usr/src/kernels/<kernel-version>

Ubuntu 和 RedHat/CentOS 的内核头文件安装路径有一些小的差异,不过相同点是都可以通过 "/lib/modules/build/<kernel-version>" 的软链接指向,所以这个 symlink 成了寻找内核头文件位置的公用路径。

(3) 对于自行编译的内核,需在内核源码目录使用 "make modules_prepare",以生成编译外部 modules 所需的各种文件。

静态与动态

原材料备齐,接下来就可以按照菜谱下锅了。最直观也最简单的适配方法是通过(静态的)版本号判断,在内核头文件 "include/generated/uapi/linux/version.h" 中,有一个记录 Linux 版本号的数字(以 4.18.0 内核为例):

#define LINUX_VERSION_CODE 266752
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))

因为直接使用 KERNEL_VERSION 的 "<a>.<b>.<c>"(分别代表 major, minor, macro 号)来比较大小不方便,所以转换成了一个数字 LINUX_VERSION_CODE (大家可以算一下,"4<<16 + 18<<8 + 0" 是否等于 266752)。

据此,可通过以下的判断来区别使用哪个版本的内核 API:

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0)
/* use API after kernel 4.18 */
#else
/* use API before kernel 4.18 */
#endif

这种方法简捷快速,但在现实的 Linux 江湖里,大部分被使用的都是 RedHat, SUSE, Ubuntu 等厂商提供的 distribution,而这些发行版大都进行了 backporting。比如 RedHat/CentOS-8.2 用的这个 4.18.0-193.28.1 内核,它的 DRM 版本是来源于内核 5.3 的:

直接进行内核版本的比对不能很好地应对这种 backport 的场景(往往需要使用较多的 if/else,造成逻辑复杂),一种更灵活的方式是「探测性编译」。

比如自内核 5.0 之后,"access_ok" 这个用于校验用户空间传入数值的宏,从三个参数变成了两个参数,因此我们可以通过以下一段小程序来测试:

#include <linux/uaccess.h>

int main (void)
{
    access_ok(0, 0);
    return 0;
}

如果编译通过,说明该内核中的 "access_ok" 是两个参数,否则就是三个参数。

那如何捕获这个编译结果呢?在 Autoconf 工具里,有一个 M4 的组件,按照其定义的语法,写出类似这样的一个 "access-ok.m4" 文件:

AC_DEFUN([AC_ACCESS_OK_WITH_TWO_ARGUMENTS], [
	AC_KERNEL_TRY_COMPILE([
		#include <linux/uaccess.h>
	],[
		access_ok(0, 0);
	],[
		AC_DEFINE(HAVE_ACCESS_OK_WITH_TWO_ARGUMENTS, 1,
		    [whether access_ok(x, x) is available])
	])
])

将其加入 autoconf 的配置体系里,就会自动生成一个包含探测性源码的 configure 文件,并最终得到 HAVE_ACCESS_OK_WITH_TWO_ARGUMENTS 为 1 (两个参数)或者为 0(三个参数) 的结果。

所以实际上你只要写一个 m4 文件就可以了,不用自己去写 C 代码然后编译,这些 Autoconf 都可以帮你搞定。

相较于第一种静态版本号判断的方法,这第二种动态探测的方法也并非百利而无一害。对需要判别的 API 生成配置文件再去编译,是有一定时间开销的,当要求编译的驱动版本很多时(比如针对上百个内核),完成一次的总时间就会较长。

所以需要适配的内核版本较少时,应尽量使用第二种方法,否则往往需要做出一定的妥协,混合使用以上两种方法,以兼顾高效和灵活。比如对一个 API 变动先用版本适配法,之后确实因新内核有 backport 造成难以判断(一般只有 20%~30% 的概率),再转为动态探测法。

加载验证

驱动编译成功后,需到对应的内核版本上去验证功能,这又涉及到安装「内核镜像」的过程。具体的方法同前面介绍的内核头文件的安装类似,在此不赘述。

UbuntuRedHat/CentOS
内核镜像安装包名称linux-image-<kernel-version>(旧)kernel-<kernel-version>
(新)kernel-core/modules-<kernel-version>
内核镜像安装位置/boot/vmlinuz-<kernel-version>/boot/vmlinuz-<kernel-version>

安装新内核之后,就该切换内核版本了。这对于 RedHat/CentOS 来说比较好办,使用这篇文章里提到的 grubby 工具即可。而对于 Ubuntu 系统就稍微麻烦一些,需要三步:

  1. 通过 "grep menuentry /boot/grub/grub.cfg" 找到目标内核对应的项。
  2. 将第一步的结果填入 "etc/default/grub" 文件,例如:
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 5.3.0-19-generic"

3. 执行 "update-grub" 命令。

注-1:

对于 RedHat/CentOS,需区别 kernel-devel 和 kernel-headers,后者是提供给用户态的程序编译用的:

原文链接:https://zhuanlan.zhihu.com/p/570218272

(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂

更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! ! 

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

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

相关文章

带你实现react源码的核心功能

React 的几种组件以及首次渲染实现React 更新机制的实现以及 React diff 算法 React 的代码还是非常复杂的&#xff0c;虽然这里是一个简化版本。但是还是需要有不错的面向对象思维的。React 的核心主要有一下几点。 虚拟 dom 对象&#xff08;Virtual DOM&#xff09;虚拟 d…

RabbitMQ_消息确认机制

消息确认机制分为消息发送确认机制与消息消费确认机制 消息发送确认机制 消息发送确认机制&#xff1a;消息由producer发送后&#xff0c;确认其是否到达broker&#xff0c;又是否被exchange转发至对应queue的机制 该机制分为两部分&#xff1a;producer---broker&#xff0c…

Android 性能优化之内存优化——重识内存

我们知道&#xff0c;手机的内存是有限的&#xff0c;如果应用内存占用过大&#xff0c;轻则引起卡顿&#xff0c;重则导致应用崩溃或被系统强制杀掉&#xff0c;更严重的情况下会影响应用的留存率。因此&#xff0c;内存优化是性能优化中非常重要的一部分。但是&#xff0c;很…

66-86-javajvm-堆

66-javajvm-堆&#xff1a; 堆的核心概述 堆与进程、线程 一个进程对应一个JVM实例一个JVM实例对应一个堆空间进程包含多个线程&#xff0c;所以线程之间共享同一个堆空间 对堆的认识 一个JVM实例只存在一个堆内存&#xff0c;堆也是Java内存管理的核心区域。Java堆区在JVM启动…

HashMap原理

在Java编程语言中&#xff0c;最基本的结构就是两种&#xff0c;一种是数组&#xff0c;一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造&#xff0c;HashMap也一样。当程序试图将多个 key-value 放入 HashMap 中时&#xff0c;以如下代码片段为例&#xff1a;…

P1182 数列分段 Section II——二分答案

数列分段 Section II 题目描述 对于给定的一个长度为N的正整数数列 A1∼NA_{1\sim N}A1∼N​&#xff0c;现要将其分成 MMM&#xff08;M≤NM\leq NM≤N&#xff09;段&#xff0c;并要求每段连续&#xff0c;且每段和的最大值最小。 关于最大值最小&#xff1a; 例如一数列…

NCTF web总结与复现

前言 打完NCTF休息了一下&#xff0c;总体感觉还行&#xff0c;学到了很多。 calc 这一题也卡了我很久&#xff0c;因为复现过DASCTF三月赛&#xff0c;一直在想着有没有可以替代反引号或绕过的方法&#xff0c;搞了好久都没出&#xff0c;在学长的提示下学到了一个方法&…

最新出炉的阿里巴巴面试题及答案汇总(513页)

前言 秋招已经结束了&#xff0c;不知道各位有没有拿到自己心仪的offer&#xff1f;最近有不少粉丝去阿里巴巴面试了&#xff0c;回来之后我整理成了一份手册java面试时常用到的面试题&#xff08;附答案&#xff09;那么今天分享给大家&#xff0c;祝愿大家都能找到满意的工作…

HTML期末作业课程设计期末大作业——我的美丽家乡湛江 海鲜之都HTML+CSS+JavaScript

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

python爬虫实战之逆向分析酷狗音乐

文章目录前言一、请求分析二、逆向思路三、全部代码总结前言 声明&#xff1a;本文章只是用于学习逆向知识&#xff0c;仅供学习&#xff0c;未经作者同意禁止转载 对于爬虫而言&#xff0c;不管是什么类型的都会遵循这几个步骤 获取目标url分析请求数据逆向解密数据伪造请求清…

算法日常训练12.5

首先有个很大的进步&#xff0c;看见困难题我没选择做逃兵跑路&#xff0c;这点起码是进步了&#xff0c;虽然算法能力还是那么拉&#xff0c;但是起码敢不自量力地分析一下。。。还能看题解理解下。 先找题解中最简单地一种超时方法开始理解&#xff0c;使用动态规划&#xff…

线程基础概念

1.线程基础 现代软件系统中&#xff0c;除了进程之外&#xff0c;线程也是一个十分重要的概念。特别是随着CPU频率增长开始出现停滞&#xff0c;而开始向多核方向发展。多线程&#xff0c;作为实现软件并发执行的一个重要的方法&#xff0c;也开始具有越来越重要的地位。 什么…

[本人毕业设计] 别踩白块_计算机科学与技术_前端H5游戏毕设

摘 要 本文详细介绍了网页版躲避白色钢琴块音乐游戏的设计和实现。由于游戏软件安装占据较大的空间与安装时间&#xff0c;而且步骤繁琐&#xff0c;用常规的游戏安装方法不能取得便捷的游戏安装体验。网页游戏是一种基于在网络游戏中被广泛应用&#xff0c;网页游戏更具有便捷…

【Tensorflow深度学习】实现手写字体识别、预测实战(附源码和数据集 超详细)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、数据集简介 下面用到的数据集基于IAM数据集的英文手写字体自动识别应用&#xff0c;IAM数据库主要包含手写的英文文本&#xff0c;可用于训练和测试手写文本识别以及执行作者的识别和验证&#xff0c;该数据库在ICDAR1…

对副业的选择无论是自媒体还是 Python接单 ,始终绕不开IT行业。

前言 这个年代&#xff0c;成年人的日子活成了一部苦情戏。十年前&#xff0c;5000块钱工资还能过的自由自在&#xff1b;今天&#xff0c;估计连车贷&#xff0c;房贷&#xff0c;信用卡都不够还。所以一些想要改变现状的朋友&#xff0c;选择了副业这种形式&#xff0c;副业…

【Linux】Shell脚本详解

目录一.概述二.Linux提供的Shell解析器三.Shell入门1.执行一个简单的shell脚本2.脚本常用的执行方法四.变量1.系统预定义变量2.自定义变量3.特殊变量五.运算符六.条件判断1.单条件判断2.多条件判断七.流程控制(重点)1.if判断2.case语句3.for循环4.while循环八.read读取控制台输…

【论文简述】 Point-MVSNet:Point-Based Multi-View Stereo Network(ICCV 2019)

一、论文简述 1. 第一作者&#xff1a;Rui Chen、Songfang Han 2. 发表年份&#xff1a;2019 3. 发表期刊&#xff1a;ICCV 4. 关键词&#xff1a;MVS、深度学习、点云、迭代改进 5. 探索动机&#xff1a;很多传统方法通过多视图光度一致性和正则化优化迭代更新&#xff…

C语言实例|使用C程序优雅地杀掉其它程序进程

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

FPGA 20个例程篇:18.SD卡存放音频WAV播放(中)

第七章 实战项目提升&#xff0c;完善简历 18.SD卡存放音频WAV播放&#xff08;中&#xff09; 如图1所示是WM8731中11个寄存器功能说明概况图&#xff0c;我们需要对照手册&#xff0c;再去深入了解WM8731中的11个寄存器&#xff0c;怎么去配置这些寄存器达到预期的效果&…

了解3dmax坐标系

3dmax具有多种坐标系&#xff0c;其类别如下&#xff1b;默认的是View坐标系&#xff1b; 新建一个茶壶&#xff0c;此时默认是View坐标系&#xff1b; 切换到屏幕坐标系&#xff0c;看一下如下图&#xff1b;要保持视口区域激活&#xff1b; 根据资料&#xff0c;屏幕坐标系&a…