首页 > 系统相关 >嵌入式Linux中内核模块的基本框架

嵌入式Linux中内核模块的基本框架

时间:2024-06-03 17:13:23浏览次数:20  
标签:函数 嵌入式 内核 模块 Linux hello 内核模块

在Linux系统中,驱动程序属于内核态程序,可以认为它是介于操作系统和硬件实体之间的一层,对上负责与操作系统交流,对下负责控制硬件设备。 即,驱动程序对操作系统通过软件接口进行沟通,对芯片硬件通过读写寄存器进行控制。Linux系统的驱动由内核模块(Loadable Kernel Module,简称LKM)的形式来呈现,它实现了一种在内核运行期间动态加载一组目标代码来实现某个特定功能的机制。

模块是具有独立功能的程序,它可以被单独编译,但不能独立运行,在运行时它被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不一样的。模块由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序和其他内核上层功能。内核模块经过编译,最终形成以.ko为后缀的ELF(Excutable And Linking Format)格式文件。

ELF是一种普通的可重定位目标文件,其中包含了代码和数据,可以用来链接成为可执行文件、共享目标文件或静态链接库文件等。内核模块文件通过insmod命令插入到内核中,通过rmmod命令移除。

下面看一个简单的内核模块程序示例,其功能为,当把它插入内核时,会在终端打印一句Hello World;当把它从内核移除时,会打印一句GoodBye。虽然简单,但它却是内核态程序的一个基本框架,可在野火STM32MP157开发板上实现。下面的代码保存为一个名为hello.c的文件。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
        printk(KERN_EMERG"Hello World!\n");
        return 0;
}
static void __exit hello_exit(void)
{
        printk("Goodbye!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Delphi");
MODULE_ALIAS("Alias");
MODULE_DESCRIPTION("Hello World Module");

内核模块属于内核态程序,即它是Linux内核的一部分,所以应该把它放入到Linux内核中去编译,因此,一方面需要提供目标系统的Linux源码(此处使用野火STM32MP157开发板的内核源码),另一方面,还需要写一个配套的Makefile文件参与编译,其内容如下。

KERNEL_DIR=/opt/ebf_linux_kernel_mp157_depth1/build_image/build/
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := hello.o
all:
  $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules

上面第一行中的KERNEL_DIR即为指向内核源码的目录。注意最后一行要以Tab键起头,因为它是一条命令。执行make命令进行编译,完成后会在当前目录下产生出很多文件,找到一个名为hello.ko的文件,即为需要的内核模块文件。把该文件拷贝到NFS共享目录下,在开发板的挂载目录找到它,并执行insmod hello.ko,会在终端上打印出一句Hello World!。再执行lsmod命令查看一下当前插入的内核模块,会发现有hello的名称。最后执行rmmod hello,会在终端上打印出一句GoodBye!。全部过程如下图所示。

上图中先使用命令modinfo查看了hello.ko文件的信息,其中可见模块的用途说明,别名,作者,所遵循的协议等信息。接下来使用file命令查看了该文件的属性,从中可以看出,hello.ko文件属于32位的ELF格式文件,为ARM格式文件。

下面就来详细解释一下以上内核模块代码的工作原理。

首先是包含内核模块所需要的头文件,他们位于Linux内核源码的include目录下。其中:
#include <linux/module.h>:包含内核模块信息声明的相关函数及宏定义,如MODULE_LICENSE()等。
#include <linux/init.h>:包含了module_init()和module_exit()函数的声明。
#include <linux/kernel.h>:包含内核提供的各种函数,如printk()等。

前面说过,内核模块属于Linux内核的一部分,当把它动态地插入到正在运行的Linux内核中去时,Linux系统就增加了该模块提供的某种功能,当Linux内核不再需要它时,又可以动态地从正在运行的Linux内核中移除,此时Linux系统就散失了模块提供的功能。这就是Linux的内核模块机制,在插入内核模块时,使用Linux提供的insmod命令,移除时使用rmmod命令。同时,在Linux系统中,模块之间可能存在一些依赖,即在插入模块A时,可能还会需要模块B的支持。为了解决这些依赖问题,Linux还提供了modprobe命令来代替insmod命令进行插入操作,modprobe是一个处理层叠模块的工具,它相当于多次执行了insmod命令,只不过在执行它之前,需要先执行depmod命令进行模块间依赖关系的建立。

内核模块程序其本身并不会执行,在它内部一般都会定义一个入口函数(如上例中的hello_init函数)和一个出口函数(如上例中的hello_exit函数),通过某种条件来触发这些函数执行。为了指定触发的条件,Linux系统中规定,当执行模块插入命令(insmod或modprobe)时,会触发一个名为module_init的宏。而当执行移除命令(rmmod)时,会触发一个名为module_exit的宏。在这两个宏中,可以指定触发后要执行的函数。对比来看,上面内核模块代码中的module_init(hello_init)一句,就指明了当Linux执行insmod(或modprobe)命令时,会去调用hello_init函数,所以一般称其为入口函数。同样,上面内核模块代码中的module_exit(hello_exit)一句,就指明了当Linux执行rmmod命令时,会去调用hello_exit函数,所以一般称其为出口函数。

明白了这一层,再来看上面的内核模块代码就清爽多了。在执行insmod命令时要打印Hello world,只需要把打印语句放在入口函数中就可以了。同样,在执行rmmod命令时要打印GoodBye,只需要把打印语句放在出口函数中就可以了。只不过,所定义的入口函数和出口函数还需要专门“修饰”一下,此外,打印语句也要换成printk。

由于内核模块的代码是内核代码的一部分,如果在内核模块中定义的函数名称和内核源代码中的某个函数重复了,那在编译时就会因冲突而报错。因此,一般会给内核模块中的函数加上static的前缀进行修饰,以指明该函数的作用域被限制在本模块中,这样就可以有效避免名称冲突而产生错误。

此外,函数中带有__init的修饰符,表示该函数将被放到ELF文件的__init节区中,该节区的内容只适用于模块的初始化阶段,初始化阶段执行完毕之后,这部分内容就会从内存中释放掉。同样,函数中带有__exit的修饰符,表示将该函数放在ELF文件的__exit节区,当执行完模块卸载阶段之后,就会自动释放该区域的内存空间。一般地,__init和__exit用于修饰函数,__initdata和__exitdata用于修饰变量,这部分定义位于内核源码/linux下的init.h头文件中。被__init修饰过的入口函数在内核模块中做与初始化相关的工作,返回值为0表示模块初始化成功,并在/sys/module目录下会自动建立一个以模块名为名的目录(见上面的图片),返回值为非0时表示模块初始化失败。而被__exit修饰过的出口函数的返回值则是void类型,即不需要返回。

上面的代码中,在入口函数和出口函数中都调用了printk函数。由于在内核模块运行的过程不依赖于C库支持,所以用不了printf函数,因此在需要使用单独的内核打印输出时,换成了printk函数,它的用法与printf函数类似,只不过加入了打印的优先级别。查看当前系统printk打印等级可在开发板上执行cat /proc/sys/kernel/printk,从左到右依次对应当前控制台日志级别、默认消息日志级别、最小的控制台级别、默认控制台日志级别。

最后,内核模块程序还必须声明本模块所遵循的许可证协议,这里设置为GPL协议。除此以外,内核模块许可证还有“GPL v2”,“GPL and additional rights”,“Dual SD/GPL”,“DualMPL/GPL”,“Proprietary”等。另外,还可以在模块中加入作者、别名、用途等信息(非必要)。特别注意一点,如果要给模块起别名(如代码中的MODULE_ALIAS(“Alias”);),在使用该模块的别名时,需要将该模块复制到/lib/modules/内核源码/目录下(本例为/lib/modules/4.19.94-stm-r1/),并使用depmod命令更新模块的依赖关系方才可以。  

标签:函数,嵌入式,内核,模块,Linux,hello,内核模块
From: https://www.cnblogs.com/fxzq/p/18228717

相关文章

  • 应急响应之Linux下进程隐藏
    概述当黑客获取系统root权限时,为了实现持久化控制往往会创建隐藏恶意进程,这给应急响应人员取证的时候带来了难度,隐藏进程的方法分为两类,一类是用户态隐藏,另一类是内核态隐藏。用户态常使用的方法有很多,例如劫持预加载动态链接库,一般通过设置环境变量LD_PRELOAD或者/etc/l......
  • 泛微e9阿里云linux服务器部署迁移总结
    1.基础部分,linux基础命令打开指定目录:cd[目录名称]创建目录:mkdir[目录名称]查看目录大小:du-sh[目录名称]启动停止服务:进入目录:cd/weaver/resin4/bin/启动服务:./startresin.sh停止服务:./stopresin.sh编辑文件vim【文件......
  • enum4linux一键查询SMB信息(KALI工具系列十六)
    目录1、KALILINUX简介 2、enum4linux工具简介 3、在KALI中使用enum4linux3.1目标主机IP(win)​编辑3.2KALI的IP  4、操作示例4.1运行工具 4.2列出用户名4.3提取用户名4.4使用自定义RID范围4.5列出组4.6列出共享文件夹4.7获取操作系统信息5、总结......
  • Linux ffmpeg 离线安装
    linux版本下载地址:http://www.ffmpeg.org/releases/4.3.1 下载地址:http://ffmpeg.org/releases/ffmpeg-4.3.1.tar.gz 配套组件yasm下载http://www.tortall.net/projects/yasm/releases/http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz yasm安......
  • 每天一个 Linux 命令(2):od
    功能简介od(OctalDump)命令用于将指定文件内容以八进制、十进制、十六进制、浮点格式或ASCII编码字符方式显示,通常用于显示或查看文件中不能直接显示在终端的字符。od命令系统默认的显示方式是八进制。常见的文件为文本文件和二进制文件。od命令主要用来查看保存在二进制文件中的......
  • 云服务器Linux 时间与本地时间不一致
     云服务器Linux时间与本地时间不一致问题解释:云服务器和本地计算机之间的时间不一致可能是因为它们使用的时间同步服务不同,或者云服务器没有配置自动对时。解决方法: 手动同步时间:可以使用date命令查看当前时间,使用ntpdate命令从网络时间协议(NTP)服务器同步时......
  • CentOS 7基础操作07_Linux复制、删除、移动目录和文件
    1、cp——复制(Copy)文件或目录        cp命令用于复制文件或目录,将需要复制的文件或目录(源)重建一份并保存为新的文件或目录(可保存到其他目录中)。cp命令的基本使用格式如下:cp[选项]...源文件或目录...目标文件或目录        需要复制多个文......
  • Ubuntu server 24 (Linux) IPtables 双网卡 共享上网NAT 安装配置DHCP
    一 开启路由转发功能sudovim/etc/sysctl.confnet.ipv4.ip_forward=1sudosysctl-p二 安装DHCP#更新软件包列表:sudoaptupdate#安装DHCP服务器sudoaptinstallisc-dhcp-server#修改监听网卡,根据实际修改sudovi/etc/default/isc-dhcp-serverINTERFACESv4=......
  • 深入理解Linux文件系统
    目录inode和block概述block(块)indoe(索引节点)   inode的内容Linux系统文件三个主要的时间属性inode文件结构所以,当用户在Linux系统中试图访问一个文件时,系统会先根据文件名去查找它对应的inode,看该用户是否具有访问这个文件的权限。如果有,就指向相对应的数据bloc......
  • Linux -- 环境变量与文件查找
    提示:制作不易看完点个关注和收藏哦前言提示:要解释环境变量,得先明白变量是什么,准确的说应该是Shell变量,所谓变量就是计算机中用于记录一个值(不一定是数值,也可以是字符或字符串)的符号,而这些符号将用于不同的运算处理中。通常变量与值是一对一的关系,可以通过表达式读取它的......