2-内核开发-第一个内核Hello模块开发案例


2-内核开发-第一个内核Hello模块开发案例

目录

2-内核开发-第一个内核Hello模块开发案例

1.安装环境检查及准备

2. 安装依赖项目

3. 开始helloworld module 开发

4.编译

5.安装内核模块

6.卸载 module

7.lsmod 查看内核当前加载模块

8.总结

9.附录


课程简介:
Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础,让他们能够理解和参与到Linux内核的开发过程中。

课程特点:
1. 入门级别:该课程专注于为初学者提供Linux内核开发的入门知识。无论你是否具有编程或操作系统的背景,该课程都将从最基本的概念和技术开始,逐步引导学习者深入了解Linux内核开发的核心原理。

2. 系统化学习:课程内容经过系统化的安排,涵盖了Linux内核的基础知识、内核模块编程、设备驱动程序开发等关键主题。学习者将逐步了解Linux内核的结构、功能和工作原理,并学习如何编写和调试内核模块和设备驱动程序。

3. 实践导向:该课程强调实践,通过丰富的实例和编程练习,帮助学习者将理论知识应用到实际的Linux内核开发中。学习者将有机会编写简单的内核模块和设备驱动程序,并通过实际的测试和调试来加深对Linux内核开发的理解。

4. 配套资源:为了帮助学习者更好地掌握课程内容,该课程提供了丰富的配套资源,包括教学文档、示例代码、实验指导和参考资料等。学习者可以根据自己的学习进度和需求,灵活地利用这些资源进行学习和实践。

无论你是计算机科学专业的学生、软件工程师还是对Linux内核开发感兴趣的爱好者,Linux内核开发入门课程都将为你提供一个扎实的学习平台,帮助你掌握Linux内核开发的基础知识,为进一步深入研究和应用Linux内核打下坚实的基础。

这一讲主要讲述如何开发第一个Linux驱动程序模块,动手开发代码,运行加载卸载模块


课程内容和目标:

  1. 编写实现最简单的内核模块- hello
  2. 如何加载内核模块hello
  3. 如何卸载内核模块hello

先从安装所需依赖、然后以创建基础内核模块hello 进行展开。


1.安装环境检查及准备

Linux 内核开发环境没有搭建好的,需要看第一讲内容 1-内核开发环境ubuntu+virtualbox+mobaXterm搭建-CSDN博客

现在默认你已经搭建好环境。请检查以下项是否已经ready。
    VirtualBox 
    Ubuntu:20.04.1-Ubuntu 
    mobaXterm personal editon 
    Ubuntu网络环境设置好可以访问外部网络,方便 mobaXterm 可以通过ssh 连接

VirtualBox 软件设置详细信息如下:

上节课已经安装好常用软件
   sudo apt install net-tools
   sudo apt install vim
   sudo apt-get install gcc 
   

2. 安装依赖项目

进行内核模块开发,需要安装对应的headers 文件,编译优c,Makefile 文件需要编译环境。
安装build-essential 和 kernel headers,执行以下命令:
$apt-get install  linux-headers-$(uname -r) 

build-essential 软件包提供了在基于 Debian 的系统上编译和构建软件所需的基本工具。

linux-headers-$(uname -r) 软件包提供了当前正在运行的内核的头文件。

为啥需要安装linux-headers?  在进行内核模块开发时需要安装 Linux headers 的原因如下:

  • 内核头文件声明:Linux headers 包含内核中使用的所有数据结构、函数和宏的声明。这些声明对于编写与内核交互的模块至关重要。
  • 编译模块:编译内核模块时,编译器需要知道内核中使用的符号的类型和大小。这些信息在 Linux headers 中提供。
  • 符号解析:当内核加载模块时,它需要解析模块中使用的符号。这些符号在 Linux headers 中定义。
  • 避免内核和模块之间的不匹配:Linux headers 对于确保内核和模块之间的兼容性非常重要。如果内核和模块的版本不匹配,则模块可能无法正常工作或可能导致系统不稳定。

安装header 文件后,安装kernel build 工具,以便进行代码编译。

安装的输出结果如下,说明安装成功

peach@peach-VirtualBox:~$ sudo apt-get install linux-headers-$(uname -r)
Reading package lists... Done
Building dependency tree
Reading state information... Done
linux-headers-5.15.0-91-generic is already the newest version (5.15.0-91.101~20     .04.1).
linux-headers-5.15.0-91-generic set to manually installed.
0 upgraded, 0 newly installed, 0 to remove and 63 not upgraded.

peach@peach-VirtualBox:~$ sudo apt-get install build-essential
[sudo] password for peach:
Reading package lists... Done
Building dependency tree
Reading state information... Done
build-essential is already the newest version (12.8ubuntu1.1).
0 upgraded, 0 newly installed, 0 to remove and 63 not upgraded.

3. 开始helloworld module 开发

Linux 内核模块是一种可加载的代码,它可以动态地添加到正在运行的内核中。模块提供了扩展内核功能的机制,而无需重新编译整个内核。开发模块通常用于以下目的:

  • 添加对新硬件的支持
  • 实现新文件系统
  • 提供网络协议支持
  • 实现安全功能
  • 增强现有功能

本次课程实验,是创建一个最简单的内核模块。首先创建项目目录HelloModule

cd ~
mkdir HelloModule 

编写内核模块,我们需要先了解下编写内核模块的规范以及最佳实践

  • 模块名称:模块名称必须唯一,并且不应与内核中已经存在的任何其他模块名称相同。
  • 目标:目标指定要构建的模块的类型。最常见的目标是 modules,它会创建一个可加载的内核模块。
  • 源文件:源文件指定用于构建模块的源代码文件。
  • 依赖项:依赖项指定模块构建和加载所需的任何其他模块或内核功能。
  • 编译选项:编译选项指定用于编译模块的编译器选项。
  • 安装选项:安装选项指定如何安装模块。

vim hello.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>


MODULE_LICENSE("GPL");
MODULE_AUTHOR("kk swing");
MODULE_DESCRIPTION("A simple hello world module");
MODULE_VERSION("1.0.0");

static int __init hellowrld_init(void)
{
    printk(KERN_INFO "hello world \n");
    return 0;
}


static void __exit helloworld_exit(void)
{
    printk(KERN_INFO "GOODBYE HELLO WORLD!\n");
}


module_init(hellowrld_init);
module_exit(helloworld_exit)

这里就代码重点讲解三点个注意点,其他都是普通的c语言语法程序,相必大家都能够看懂理解。

(1)MODULE_ 宏定义

  • MODULE_LICENSE("GPL"); 指定模块的许可证。GPL(通用公共许可证)是一种流行的开源许可证。
  • MODULE_AUTHOR("kk swing"); 指定模块的作者。
  • MODULE_DESCRIPTION("A simple hello world module"); 提供模块的简要描述。
  • MODULE_VERSION("1.0.0"); 指定模块的版本。

这些宏对于提供有关模块的重要信息非常有用,例如其许可证、作者、用途和版本。此信息在编译、加载和使用模块时都会用到。

注意虽然这些宏是可选的,但是作为一个标准的内核模块,您在开发内核模块时使用它们。有助于使模块更易于被他人理解和使用。

(2)代码中,我们使用了两个宏__init 和 __exit,定义模块初始化函数,__init 和 __exit 宏用于指定模块初始化和清理函数。

__init 宏用于标记模块的初始化函数。该函数将在模块加载时调用。模块初始化函数通常用于分配资源、注册回调函数和执行其他初始化任务。

__exit 宏用于标记模块的清理函数。该函数将在模块卸载时调用。模块清理函数通常用于释放资源、注销回调函数和执行其他清理任务。

static int __init helloworld_init(void) 函数在模块加载时被调用。它将展开为 module_init() 函数。

内核中除了__init __exit 宏,钩子函数外还有很多类似的钩子,后面遇到再进行讲解。

(3)代码中的 KERN_INFO 具体含义是 Linux 内核中使用的日志级别,有点类似与Java 打日志有几种级别。KERN_INFO 是一个宏,它展开为一个整数常量,注意他不是一个函数。KERN_INFO 它表示要打印的信息具有信息性,并且对于用户或系统管理员来说可能是有用的。Linux 内核定义了几个日志级别,每个级别都有一个相关的优先级:

日志级别优先级
KERN_EMERG0
KERN_ALERT1
KERN_CRIT2
KERN_ERR3
KERN_WARNING4
KERN_NOTICE5
KERN_INFO6
KERN_DEBUG7

KERN_INFO 的优先级为 6,这意味着它将打印所有优先级为 6 或更高(即 KERN_NOTICE、KERN_WARNING、KERN_ERR、KERN_CRIT、KERN_ALERT 和 KERN_EMERG)的信息。这里的逻辑和Java 开发工程定义的Log 级别是相似并且一致。printk 打印的输出内容不会在控制台输出。

Makefile


obj-m += hello.o

CFLAGS := -Wall -O2

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

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


注意编写Makefile 需要注意的点

  • Makefile 必须使用 Tab 缩进,而不是空格。上面的 make 签名都是Tab 缩进。如果不确定或想比较好的避免此类问题,可以是哟个vs code 编辑器,在编写Makefile文件时,回车后,按下tab他会自动转成Tab,而不是空格
  • Makefile 的行必须以换行符结尾。
  • Makefile 中的注释是以 # 开头。
  • Makefile 中的变量是以 $ 开头。

modules 命令,说明构建的时一个内核模块。

4.编译

编写好上面的文件后,执行make 进行编译代码

peach@peach-VirtualBox:~/HelloModule$ make
make -C /lib/modules/5.15.0-91-generic/build M=/home/peach/HelloModule modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-91-generic'
  CC [M]  /home/peach/HelloModule/hello.o
  MODPOST /home/peach/HelloModule/Module.symvers
  CC [M]  /home/peach/HelloModule/hello.mod.o
  LD [M]  /home/peach/HelloModule/hello.ko
  BTF [M] /home/peach/HelloModule/hello.ko
Skipping BTF generation for /home/peach/HelloModule/hello.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-91-generic'

执行make进行编译 ,有报错则需要解决相应的报错。编译通过后,会有 ko 模块文件生成。编译后,会看到很多输出内容,其中重点关注的是 ko 文件。

drwxrwxr-x  2 peach peach   4096 4月  20 08:31 ./
drwxr-xr-x 18 peach peach   4096 4月  20 13:31 ../
-rw-rw-r--  1 peach peach    460 4月  20 08:30 hello.c
-rw-rw-r--  1 peach peach 216168 4月  20 08:31 hello.ko
-rw-rw-r--  1 peach peach    228 4月  20 08:31 .hello.ko.cmd
-rw-rw-r--  1 peach peach     33 4月  20 08:31 hello.mod
-rw-rw-r--  1 peach peach    856 4月  20 08:31 hello.mod.c
-rw-rw-r--  1 peach peach    127 4月  20 08:31 .hello.mod.cmd
-rw-rw-r--  1 peach peach 108912 4月  20 08:31 hello.mod.o
-rw-rw-r--  1 peach peach  30838 4月  20 08:31 .hello.mod.o.cmd
-rw-rw-r--  1 peach peach 108744 4月  20 08:31 hello.o
-rw-rw-r--  1 peach peach  29543 4月  20 08:31 .hello.o.cmd
-rw-rw-r--  1 peach peach    159 4月  20 08:30 Makefile
-rw-rw-r--  1 peach peach     33 4月  20 08:31 modules.order
-rw-rw-r--  1 peach peach    153 4月  20 08:31 .modules.order.cmd
-rw-rw-r--  1 peach peach      0 4月  20 08:31 Module.symvers
-rw-rw-r--  1 peach peach    203 4月  20 08:31 .Module.symvers.cmd

hello.ko 这个文件是我们要的文件,这个文件可以安装到内核中。


5.安装内核模块


有了模块文件后,我们就可以进行安装测试了,安装刚才的内核模块

sudo insmod hello.ko  

insmod 执行后,主要经过了以下处理过程。

  • 解析模块文件:内核将解析模块文件以获取模块的信息,例如模块名称、符号表和依赖关系。
  • 检查依赖关系:内核将检查模块的依赖关系以确保它们已加载。如果缺少任何依赖项,内核将尝试加载它们。
  • 分配内存:内核将为模块分配内存。
  • 初始化模块:内核将调用模块的 init_module() 函数来初始化模块。
  • 插入模块:内核将模块插入内核符号表中。这意味着内核现在可以访问模块导出的符号。
  • 执行模块代码:内核将执行模块代码。

为了加载模块,可以使用 insmod 命令。当加载模块时,内核将调用模块的 init 函数。init 函数负责初始化模块并使其准备好使用。

__init 宏表示 init 函数只在模块加载时调用一次。这意味着该函数将在模块加载后从内核中删除。

唉,怎么没有输出内容?,输出在哪里呢?。

原来是使用printk 打印的输出,在kernel log 里面。可以使用dmesg 命令查看,这个命令很有用,查看进程异常退出的时候,很方便,例如java 开发工程师,有时候会看到测试环境的 java 进程跑着跑着,突然不见了,但又不知道啥原因导致进程死掉的,就可以使用这个命令去查看分析, 定位进程为啥原因挂掉。

执行dmesg 命令,看是否可以看到我们自动hello 模块,被内核加载时的printk 语句输出。

$dmesg
[43296.687898] clocksource:                       'tsc' is current clocksource.
[43296.687901] tsc: Marking TSC unstable due to clocksource watchdog
[43296.815155] clocksource: Not enough CPUs to check clocksource 'tsc'.
[43296.815182] clocksource: Switched to clocksource kvm-clock
[43302.845589] e1000: enp0s3 NIC Link is Down
[43302.846080] e1000 0000:00:03.0 enp0s3: Reset adapter
[43304.925835] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
[46724.369501] hello: loading out-of-tree module taints kernel.
[46724.369546] hello: module verification failed: signature and/or required key missing - tainting kernel
[46724.369734] hello world

6.卸载 module

当不需要模块是,需要进行卸载,卸载操作 rmmod 主要执行步骤如下。

  • 查找模块:内核通过检查内核符号表来查找模块。
  • 检查模块是否正在使用:内核检查模块是否正在被其他内核对象(如进程或设备驱动程序)使用。如果模块正在使用,内核将无法卸载它。
  • 调用模块的 cleanup_module() 函数:内核调用模块的 cleanup_module() 函数来清理模块。这包括释放模块分配的任何资源和注销模块导出的任何符号。
  • 从内核符号表中删除模块:内核从内核符号表中删除模块。这意味着内核不再可以访问模块导出的符号。
  • 释放模块内存:内核释放模块分配的内存
  • 打印卸载消息:内核打印以下消息以指示模块已卸载

执行卸载hello 模块操作

sudo rmmod hello

再次执行 使用dmesg 命令查看日志输出,发现exit 函数已经被调用。

$dmesg
[43296.687901] tsc: Marking TSC unstable due to clocksource watchdog
[43296.815155] clocksource: Not enough CPUs to check clocksource 'tsc'.
[43296.815182] clocksource: Switched to clocksource kvm-clock
[43302.845589] e1000: enp0s3 NIC Link is Down
[43302.846080] e1000 0000:00:03.0 enp0s3: Reset adapter
[43304.925835] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
[46724.369501] hello: loading out-of-tree module taints kernel.
[46724.369546] hello: module verification failed: signature and/or required key missing - tainting kernel
[46724.369734] hello world
[46856.430091] GOODBYE HELLO WORLD!

验证了卸载模块成功。


7.lsmod 查看内核当前加载模块

lsmod 可用于查看当前有哪些内核模块。

peach@peach-VirtualBox:~/HelloModule$ sudo insmod hello.ko


peach@peach-VirtualBox:~/HelloModule$ lsmod |grep hello
hello                  16384  0

8.总结

        以上就是一个最简单的内核模块开发完成。这个hello模块没有干什么事情,只是简单的打印了模块加载卸载过程中,输出日志,标识内核已经调用此方法。通过这个实验,我们知道了如何编写内核模块,内核模块Makefile 如何编写,如何加载内核模块,卸载内核模块,如何查找内核模块。

9.附录

有些内核编程规范及风格文档

内核模块编程指南

Linux 内核编码风格

内核模块 Makefile 参考

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值