Linux系统内核模块函数调用及命名空间

Linux内核模块符号导出机制详解 Linux内核模块(LKM)是操作系统核心功能的动态扩展机制,通过符号导出实现模块间的高效通信。其核心原理是将函数或变量注册到全局符号表,使其他模块能直接调用这些资源。这种机制在驱动开发中尤为重要,比如硬件抽象层导出寄存器操作接口供上层驱动使用。相比用户空间的动态链接库,内核符号表采用全局共享设计,无需复杂重定位过程。通过EXPORT_SYMBOL系列API,开发者可以控制符号的可见性范围,包括基础导出、GPL兼容导出及命名空间隔离等特性。合理使用符号导出能构建模块化驱动架构,但需注意最小化导出原则和版本兼 阅读详情

内核模块是怎么开始和结束的

用户程式通常从函数main()开始,执行一系列的指令并且当指令执行完成后结束程式。内核模块有一点不同。内核模块要么从函数init_module 或是你用宏module_init指定的函数调用开始。这就是内核模块 的入口函数。他告诉内核模块提供那些功能扩展并且让内核准备好在需要时调用他。当他完成这些后,该函数就执行结束了。模块在被内核调用前也什么都不做。

所有的模块或是调用cleanup_module或是你用宏 module_exit指定的函数。这是模块的退出函数。他撤消入口函数所做的一切。 例如注销入口函数所注册的功能。

所有的模块都必须有入口函数和退出函数。既然我们有不只一种方法去定义这两个 函数,我将努力使用“入口函数”和“退出函数”来描述 他们。不过当我只用init_module 和cleanup_module时,我希望你明白我指的是什么。

模块可调用的函数

程式员并不总是自己写所有用到的函数。一个常见的基本的例子就是 printf()你使用这些C标准库,libc提供的库函数。这些函数(像printf()) 实际上在连接之前并不进入你的程式。 在连接时这些函数调用才会指向 你调用的库,从而使你的代码最终能执行。

内核模块有所不同。在hello world模块中你也许已注意到了我们使用的函数 printk() 却没有包含标准I/O库。这是因为模块是在insmod加 载时才连接的目标文件。那些要用到的函数的符号链接是内核自己提供的。 也就是说,你能在内核模块中使用的函数只能来自内核本身。如果你对内核提供了哪些函数符号 链接感兴趣,看一看文件/proc/kallsyms。

需要注意的一点是库函数和系统调用的差别。库函数是高层的,完全运行在用户空间, 为程式员提供调用真正的在幕后完成实际事务的系统调用的更方便的接口。系统调用在内核 态运行并且由内核自己提供。标准C库函数printf()能被看做是个通用的输出语句,但他实际做的是将数据转化为符合格式的字符串并且调用系统调用 write()输出这些字符串。 是否想看一看printf()究竟使用了哪些系统调用? 这非常容易,编译下面的代码。

 

#include
            int main(void)
            { printf("hello"); return 0; }

使用命令gcc -Wall -o hello hello.c编译。用命令 strace hello行该可执行文件。是否非常惊讶?每一行都和一个系统调用相对应。 strace[3] 是个非常有用的程式,他能告诉你程式使用了哪些系统调用和这些系统调用的参数,返回值。这是个极有价值的查看程式在干什么的工具。在输出的末尾,你应该看到这样类似的一行 write(1, "hello", 5hello)。这就是我们要找的。藏在面具printf() 的真实面目。既然绝大多数人使用库函数来对文件I/O进行操作(像 fopen, fputs, fclose)。 你能查看man说明的第二部分使用命令man 2 write. 。man说明的第二部分专门介绍系统调用(像kill()和read())。 man说明的第三部分则专门介绍你可能更熟悉的库函数, (像cosh()和random())。

你甚至能编写代码去覆盖系统调用,正如我们不久要做的。骇客常这样做来为系统安装后门或木马。 但你能用他来完成一些更有益的事,像让内核在每次某人删除文件时输出 “ Tee hee, that tickles!” 的信息。

用户空间和内核空间

内核全权负责对硬件资源的访问,不管被访问的是显示卡,硬盘,还是内存。 用户程式常为这些资源竞争。就如同我在保存这份文件同时本地数据库正在更新。 我的编辑器vim进程和数据库更新进程同时需求访问硬盘。内核必须使这些请求有条不紊的进行,而不是随用户的意愿提供计算机资源。 为方便实现这种机制, CPU 能在不同的状态运行。不同的状态赋予不同的你对系统操作的自由。Intel 80836 架构有四种状态。 Unix只使用了其中 的两种,最高级的状态(操作状态0,即“终极状态”,能执行所有操作)和最低级的状态 (即“用户状态”)。

回忆以下我们对库函数和系统调用的讨论,一般库函数在用户态执行。 库函数调用一个或几个系统调用,而这些系统调用为库函数完成工作,不过在终极状态。 一旦系统调用完成工作后系统调用就返回同时程式也返回用户态。

命名空间

如果你只是写一些短小的C程式,你可为你的变量起一个方便的和易于理解的变量名。 不过,如果你写的代码只是许多其他人写的代码的一部分,你的全局一些就会和其中的全局变量发生冲突。 另一个情况是个程式中有太多的难以理解的变量名,这又会导致变量命名空间污染 在大型项目中,必须努力记住保留的变量名,或为独一无二的命名使用一种统一的方法。

当编写内核代码时,即使是最小的模块也会同整个内核连接,所以这的确是个令人头痛的问题。最佳的解决方法是声明你的变量为static静态的并且为你的符号使用一个定义的非常好的前缀。传统中,使用小写字母的内核前缀。如果你不想将所有的东西都声明为static静态的, 另一个选择是声明一个symbol table(符号表)并向内核注册。我们将在以后讨论。

文件/proc/kallsyms保存着内核知道的所有的符号,你能访问他们, 因为他们是内核代码空间的一部分。

代码空间

内存管理是个非常复杂的课题。O’Reilly的《Understanding The Linux Kernel》绝大部分都在 讨论内存管理!我们 并不准备专注于内存管理,但有一些东西还是得知道的。

如果你没有认真考虑过内存设计缺陷意味着什么,你也许会惊讶的获知一个指针并不指向一个确切的内存区域。当一个进程建立时,内核为他分配一部分确切的实际内存空间并把他交给进程,被进程的代码,变量,堆栈和其他一些计算机学的专家才明白的东西使用[4]。这些内存从$0$ 开始并能扩展到需要的地方。这些内存空间并不重叠,所以即使进程访问同一个内存地址,例如0xbffff978,真实的物理内存地址其实是不同的。进程实际指向的是一块被分配的内存中以0xbffff978 为偏移量的一块内存区域。绝大多数情况下,一个进程像普通的"Hello, World"不能访问别的进程的 内存空间,尽管有实现这种机制的方法。我们将在以后讨论。

内核自己也有内存空间。既然一个内核模块能动态的从内核中加载和卸载,他其实是共享内核的 内存空间而不是自己拥有独立的内存空间。因此,一旦你的模块具有内存设计缺陷,内核就是内存设计缺陷了。 如果你在错误的覆盖数据,那么你就在破坏内核的代码。这比目前听起来的还糟。所以尽量小心谨慎。

顺便提一下,以上我所指出的对于所有单整体内核的操作系统都是真实的[5]。 也存在模块化微内核的操作系统,如 GNU Hurd 和 QNX Neutrino。

一种内核模块是设备驱动程式,为使用硬件设备像电视卡和串口而编写。 在Unix中,所有设备都被当作路径/dev 的设备文件处理,并通过这些设备文件提供访问硬件的方法。 设备驱动为用户程式访问硬件设备。举例来说,声卡设备驱动程式es1370.o将会把设备文件 /dev/sound同声卡硬件Ensoniq IS1370联系起来。这样用户程式像 mp3blaster 就能通过访问设备文件/dev/sound 运行而不必知道那种声卡硬件安装在系统上。

Major and Minor Numbers

让我们来研究几个设备文件。这里的几个设备文件代表着一块主IDE硬盘上的头三个分区:

 

# ls -l /dev/hda[1-3]
            brw-rw---- 1 root disk 3, 1 Jul 5 2000 /dev/hda1
            brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2
            brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3

注意一下被逗号隔开的两列。第一个数字被叫做主设备号,第二个被叫做从设备号。 主设备号决定使用何种设备驱动程式。每种不同的设备都被分配了不同的主设备号; 所有具有相同主设备号的设备文件都是被同一个驱动程式控制。上面例子中的 主设备号都为3,表示他们都被同一个驱动程式控制。

从设备号用来差别驱动程式控制的多个设备。上面例子中的从设备号不相同是因为他们被识别为几个设备。

设备被大概的分为两类:字符设备和块设备。差别是块设备有缓冲区,所以他们能对请求进行优化排序。 这对存储设备尤其重要,因为读写相邻的文件总比读写相隔非常远的文件要快。另一个差别是块设备输入和输出 都是以数据块为单位的,不过字符设备就能自由读写任意量的字节。大部分硬件设备为字符设备,因为他们 不必缓冲区和数据不是按块来传输的。你能通过命令ls -l输出的头一个字母识别一个 设备为何种设备。如果是’b’ 就是块设备,如果是’c’就是字符设备。以上你看到的是块设备。这儿更有一些字符设备文件(串口):

 

crw-rw---- 1 root dial 4, 64 Feb 18 23:34 /dev/ttyS0
            crw-r----- 1 root dial 4, 65 Nov 17 10:26 /dev/ttyS1
            crw-rw---- 1 root dial 4, 66 Jul 5 2000 /dev/ttyS2
            crw-rw---- 1 root dial 4, 67 Jul 5 2000 /dev/ttyS3

如果你想看一下已分配的主设备号都是些什么设备能看一下文件 /usr/src/linux/Documentation/devices.txt。

系统安装时,所有的这些设备文件都是由命令mknod建立的。去建立一个新的名叫 coffee’,主设备号为12和从设备号为2的设备文件,只要简单的 执行命令mknod /dev/coffee c 12 2。你并不是必须将设备文件放在目录 /dev中,这只是个传统。Linus本人是这样做的,所以你最佳也不例外。不过,当你测试一个模块时,在工作目录建立一个设备文件也不错。 只要确保完成后将他放在驱动程式找得到的地方。

我还想声明在以上讨论中隐含的几点。当系统访问一个系统文件时, 系统内核只使用主设备号来差别设备类型和决定使用何种内核模块。系统 内核并不必知道从设备号。内核模块驱动本身才关注从设备号,并用之来 差别其操纵的不同设备。

另外,我这儿提到的硬件是比那种能握在手里的PCI卡稍微抽象一点的东西。看一下下面的两个设备文件:

 

% ls -l /dev/fd0 /dev/fd0u1680
            brwxrwxrwx  1 root floppy  2, 0 Jul 5 2000 /dev/fd0
            brw-rw----  1 root floppy  2, 44 Jul 5 2000 /dev/fd0u1680

你目前即时明白这是快设备的设备文件并且他们是有相同的驱动内核模块来操纵 (主设备号都为2))。你也许也意识到他们都是你的软盘驱动器,即使你实际上只有一个软盘驱动器。为什么是两个设备文件?因为他们其中的一个代表着 你的1.44 MB容量的软驱,另一个代表着你的1.68 MB容量的,被某些人称为“终极格式化”的软驱。 这就是个不同的从设备号代表着相同硬件设备的例子。请清晰的意识到我们提到的硬件有时可能是非常抽象的。

Linux 网络命名空间的奥秘:深入解析struct net与内核模块编译陷阱 struct net通过清晰的层次划分实现复杂功能,通过精心控制的API边界保证稳定性。理解这样的编译陷阱背后的设计意图,能帮助开发者更深入地掌握内核开发范式。当遇到类似问题时,记住:内核模块只能访问公开API网络参数属于对应的协议层结构体条件编译是内核保护内部实现的常用手段遵循这些原则,你将能编写出更健壮、兼容性更好的内核代码,在Linux网络开发的深水中游刃有余。 阅读详情

相关推荐

linux 内核模块调用实例

此例为linux 内核模块相互调用实例,亲自编写,开发linux 驱动理解内核工作原理必备

Linux 进程管理之命名空间_linux设置一个新的命名空间(3)

要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

2401_83739727的博客 1075

Linux安装操作管理及命令指南

Linux的安装和基本的操作以及常用的管理和Linux的命令。同时包含了目录索引

关于Linux系统调用,内核函数

<br /><br />早上听人说到某个程序的一部分是内核态,另一部分是用户态,需要怎么怎么。当时突然想知道,用户的程序可以直接调用内核函数吗?(现在突然发觉这问题有点可笑,若是可以随便调,那系统岂不是乱套了)从网上找到下面这篇文章,讲的还算透彻。<br />现在自己的理解是,用户程序不可用直接调用内核函数,除非通过系统调用接口。如果想调用哪个内核函数(或自己写的内核函数),怎么办?增加一个系统调用就行了。<br />原文如下:<br />Linux系统调用<br />   顾名思意,系统调用说的是操作系统

ubuntulover的专栏 1万+

Python爬取腾讯视频m3u8加密流实战(附完整代码)

本文提供了一份详细的Python实战教程,指导如何通过抓包分析、模拟请求头与载荷,破解腾讯视频的m3u8加密流防盗链机制,并附上完整代码实现视频片段的下载与合并。教程涵盖从侦察到代码实现的完整流程,并包含参数动态化、多线程下载等高级技巧与避坑指南,旨在帮助开发者掌握流媒体下载的核心技术。

weixin_29090089的博客 1604

Aurora8b10b光口通信实战:复位时序与IP核配置详解

本文深入解析了Aurora 8B10B协议在FPGA高速光口通信中的核心实践,重点剖析了确保链路稳定启动的关键复位时序与IP核配置细节。通过详解全双工与单工模式下的复位流程、Vivado中IP核的物理层与链路层参数设置,并结合用户逻辑设计实例,为工程师实现可靠的高速数据传输提供了清晰的实战指南。

weixin_29280219的博客 177

Linux 内核开发中模块间函数调用问题

需要在提供函数的模块中使用 `EXPORT_SYMBOL` 导出函数,在使用函数的模块中通过 `extern` 声明函数,或者包含头文件。通过上述方法,可以在同一个文件中维护两个模块的代码,并直接在一个模块中使用另一个模块的函数,而无需导出和声明。在同一个文件中定义两个模块时,函数无需导出即可使用,函数的定义和声明在同一文件中可见,可以直接调用。可以在同一个文件中定义两个模块,但它们仍然需要作为独立的模块进行编译和加载。4. 在同一个文件中定义两个模块,函数还需要导出才能使用吗?

fz2891897770的博客 456

NVIDIA Jetson Orin系列 - 使用SDK Manager安装JetPack全流程解析

本文详细解析了使用NVIDIA官方SDK Manager为Jetson Orin系列开发板安装JetPack系统的完整流程。从环境准备、设备进入恢复模式,到SDK Manager图形化界面的分步操作指南,提供了新手友好的全流程解析,并分享了安装后的验证方法及常见问题解决方案,帮助开发者高效搭建稳定的边缘AI开发环境。

nn3456的博客 406

linux 内核常用的函数

set a device name 在 dev 目录下面 有 event.... 等这样的 设备文件 dev_set_name(&evdev->dev, "event%d", dev_no);

weixin_42546931的博客 854

告别‘打架’的检测头:手把手教你用PyTorch实现YOLOv11解耦头(附完整代码)

本文详细解析了YOLOv11解耦检测头的创新设计与PyTorch实现,通过解耦分类和回归任务,显著提升目标检测性能。文章包含完整的代码实现、架构设计解析及工程调优技巧,帮助开发者深入理解并应用这一先进技术。

weixin_29031057的博客 301

FreeRTOS面试题汇总

如果一个低优先级的任务持有一个信号量,高优先级的任务想要用这个信号量,造成的死锁怎么解决。12.FreeRTOS的多任务之间如何去进行通信的,什么方法,它实现原理是什么。任务间数据传输一共有三种形式,用的最多的是消息队列,其次是全局变量和信号量。简历上写了了解RTOS,问了rtos的问题,用过哪些函数。讲解FreeRtos原理,调度机制,底层原理,代码调用。FreeRTOS,你的三个任务控制,请问你是怎么实现的。freertos中EventBits_t是干啥的?freertos使任务切换的方式有哪些?

weixin_45636780的博客 1万+

Linux内核模块函数调用及命名空间(3)

系统安装时,所有的这些设备文件都是由命令mknod建立的。去建立一个新的名叫 coffee',主设备号为12和从设备号为2的设备文件,只要简单的 执行命令mknod /dev/coffee c 12 2。你并不是必须将设备文件放在目录 /dev中,这只是一个传统。Linus本人是这样做的,所以你最好也不例外。但是,当你测试一个模块时,在工作目录建立一个设备文件也不错。 只要保证完成后将它放在驱动程

佑邦的专栏 891

H3C交换机Console口连接全攻略:从接线到PuTTY配置(附常见问题排查)

本文详细介绍了H3C交换机Console口的连接与配置方法,包括硬件线缆选择、驱动安装、PuTTY参数设置及常见问题排查。特别针对Console转USB线缆的兼容性和驱动安装难点提供实用建议,帮助网络工程师快速掌握串联调试技巧,解决连接中的黑屏、乱码等典型问题。

weixin_42549451的博客 674

Linux内核模块函数调用及命名空间(1)

内核模块是如何开始和结束的     用户程序通常从函数main()开始,执行一系列的指令并且当指令执行完成后结束程序。内核模块有一点不同。内核模块要么从函数init_module 或是你用宏module_init指定的函数调用开始。这就是内核模块 的入口函数。它告诉内核模块提供那些功能扩展并且让内核准备好在需要时调用它。当它完成这些后,该函数就执行结束了。模块在被内核调用前也什么都不做。

佑邦的专栏 1354

Unity序列帧动画输出全攻略:Recorder+Timeline实战与透明通道处理

序列帧动画是游戏开发、影视动画和UI动效中的基础技术,指将连续动作分解为一系列静态图像并按序播放,形成动态视觉效果。其原理是通过时间轴精确控制每一帧的内容与时机,实现动画的逐帧呈现。在实时渲染领域,序列帧输出技术价值显著,它能够将动态内容高质量地转换为静态图像序列,便于跨平台复用、后期合成与资源分发。应用场景广泛,包括角色动画导出、技能特效制作、宣传片素材生成等。本文聚焦Unity引擎,深入解析如何利用Recorder与Timeline工具链,高效输出带Alpha透明通道的PNG序列帧,并重点攻克透明通道处

weixin_31556767的博客 327

BME688传感器原理图设计,已量产(环境传感器)

在便携式空气质量监测、可穿戴健康设备这类低功耗场景中,BME688 这类集成了 VOC、温湿度、气压的四合一 MEMS 传感器越来越受欢迎 —— 它体积小、功耗低,刚好匹配便携设备的需求。但这类小信号传感器对电源噪声、电平匹配的要求很细,今天结合实际项目里的一款 BME688 接口电路,聊聊硬件设计的关键细节。

不脱发的程序猿 515

linux内核模块编程指南

The Linux Kernel Module Programming GuidePeter Jay SalzmanMichael BurianOri PomerantzCopyright © 2001 Peter Jay SalzmanThe Linux Kernel Module Programming Guide is a free book; you may reproduce and/o

Make Progress Everyday! 2342

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

LXC/LXD容器化终极指南:Linux内核模块深度支持揭秘

想要在容器化环境中实现完整的系统虚拟化?LXC/LXD容器技术正是你需要的解决方案!作为轻量级容器技术的代表,LXC/LXD通过Linux内核模块的强大支持,为你提供接近原生性能的容器体验。本文将从内核模块的角度,为你揭示LXC/LXD容器化背后的技术奥秘。😊 ## 什么是LXC/LXD容器化? LXC(Linux Containers)是一种操作系统级别的虚拟化技术,允许你在单一Linux

gitblog_00878的博客 1141

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Linux内核模块函数调用及命名空间(2)

命名空间     如果你只是写一些短小的C程序,你可为你的变量起一个方便的和易于理解的变量名。 但是,如果你写的代码只是许多其它人写的代码的一部分,你的全局一些就会与其中的全局变量发生冲突。 另一个情况是一个程序中有太多的难以理解的变量名,这又会导致变量命名空间污染 在大型项目中,必须努力记住保留的变量名,或为独一无二的命名使用一种统一的方法。     当编写内核代码时,即使是最小的模块也会同

佑邦的专栏 1167

Linux内核模块编程

 摘要Linux内核模块编程的资料有些纷繁复杂,有的过于简单,有的过于庞杂,我试图用笔记的形式想读者展示怎样来进程Linux模块编程,力图做到简明扼要,这篇文章也是作为本人备忘的资料,所以有些地方过于简略是难免的。本来这篇文章的目的就是让用户知其然,至于所以然还是请参考相应的资料,其实最好的资料莫过于Linux Kernel Source。 适用范围: Linux Kernel

技术资料库 2545

操作系统实验——内核模块设计

(1)设计一个模块,要求列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID。 (2)设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号、进程状态。

CPromise的博客 1916

驱动以模块编译进linux内核,Linux 驱动开发之内核模块开发

8种机械键盘轴体对比本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?Linux 驱动开发之内核模块开发一、内核模块的概念1、什么是模块? ​ 内核模块是一些可以让操作系统内核在需要时载入和执行的代码,同时在不需要的时候可以卸载。这是一个好的功能,扩展了操作系统的内核功能,却不需要重新启动系统,是一种动态加载的技术。特点:动态加载,随时载入,随时卸载,扩展功能2、内核模块的加载作用 ​ ...

weixin_31255215的博客 300
上一篇: kernel网桥代码分析
下一篇: linux网络设置命令
linuxc2008
博客等级 码龄18年 15粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值