Linux内核品读 /基础组件/ 模块机制快速入门

哈喽,我是杰克吴,继续记录我的学习心得。

一、关于兴趣的几点思考

1. 享受不是兴趣,愿意付出才是:

  • 兴趣很容易跟享受混淆。享受是被动的,无需付出;而兴趣则要求你甘愿为了这件事情付出努力。

2.任何事情,接触皮毛的时候不要谈兴趣:

  • 在我开始公众号写文章之前,只是粗浅地觉得这个事不难我可以尝试一下,而事实上,持续写作的难度和意义超乎大多数人的想象。

  • 任何事情,先做到 60 分,再谈是否喜欢

3. 兴趣和爱好不太一样:

  • 区别在于你是否需要且愿意通过刻意练习以收获这个兴趣,以及这件事是否能给你带来持续的成就感。

  • 吃喝玩乐(旅游,逛街,买买买)是爱好,不是兴趣。纯粹的看电影是爱好,但是认真地写影评(经历了思考与分享)则算是兴趣。表面看上去都是同一件事,但是不同人会发展成不一样的结果

  • 最开始时可能只是爱好,但是随着你的持续思考和投入,可能会发展为你的理想职业

4. 兴趣可以带有功利性:

  • 那些看似功利的标准(例如高考、面试),存在很多偏差的部分,但不可否认,在绝大多数情况下,它们提供了较为高效和正确的努力方向

  • 把自己热爱的事情用来挣钱,非常好。只凭自己的兴致去做,确实会有更多愉悦,但这也是最廉价、最轻易的喜欢了,问题是,你很难真正做得好。你真的喜欢这个事,你会主动争取做好,赢得市场才会给你带来更长久的愉悦感


二、模块机制快速入门 (1)

目录:

1. 内核模块的使用
2. 内核模块的文件格式
3. EXPORT_SYMBOL 是如何实现符号导出的?
4. 相关参考

基于 Linux-4.14 + Arm-v7。

1. 内核模块的使用

最简单的内核模块:

#include <linux/init.h>
#include <linux/module.h>static char *name = "embedded hacker";
module_param(name, charp, S_IRUGO);    // 指定模块可以接收的参数static void print_hello(void)
{printk(KERN_INFO "Hello World, %s\n", name);
}static int __init hello_init(void)
{printk(KERN_INFO "Hello World init\n");print_hello();return 0;
}
module_init(hello_init);static void __exit hello_exit(void)
{printk(KERN_INFO "Hello World exit\n ");
}
module_exit(hello_exit);EXPORT_SYMBOL(print_hello);   // 导出符号 print_hello
MODULE_AUTHOR("es-hacker");   // 指定作者
MODULE_LICENSE("GPL v2");     // 指定 license
MODULE_DESCRIPTION("A simple Hello World Module");  // 指定模块的描述信息
MODULE_ALIAS("a simplest module");  // 指定模块的别名

运行效果:

$ insmod hello.ko   // 加载模块
Hello World init    // 加载模块时,module_init() 里的函数被调用
Hello World, embedded hacker$ rmmod hello       // 卸载模块
Hello World exit    // 卸载模块时,module_exit() 里的函数被调用$ insmod hello.ko name=Jack // 指定模块参数
Hello World init
Hello World, Jack$ rmmod hello
Hello World exit

到此,内核模块的使用方法就介绍完毕了,非常简单易用。

接下来是痛苦的部分:探索一下背后的实现机制

2. 内核模块的文件格式

可以用 file 命令确定一个文件的格式:

$ file hello.ko 
hello.ko: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), BuildID[sha1]=2feb2cb1328c0a9113658d6e90ac20d7e4c56384, not stripped

内核模块的格式为 ELF ( Executable and Linkable Format ):

目前不需要全面了解 ELF 文件格式的所有技术细节,只需要结合 Linux 源码中定义的 ELF 相关数据结构,简单了解一下 ELF 的构造即可。

静态的 ELF 文件视图总体上可分为 3 部分


  • 头部的 ELF header;

  • 中间的 Section;

  • 尾部的 Section header table

1) ELF header 部分:

作用:描述整个 ELF 文件。

组成:Linux 内核里的数据结构定义如下,注释部分为内核模块机制相关的的成员。

typedef struct elf32_hdr{unsigned char e_ident[EI_NIDENT];/* 文件类型 */Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;/* Entry point */Elf32_Addr e_entry;Elf32_Off e_phoff;/* Section header table 在文件中的偏移量 */Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;/* Section header table 中 entry 的大小 */Elf32_Half e_shentsize;/* Section header table 中有多少个 entry */Elf32_Half e_shnum;Elf32_Half e_shstrndx;
} Elf32_Ehdr;

实践:


$ # readelf hello.ko -h       # [-h|--file-header]
ELF Header:Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class:                             ELF32Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              REL (Relocatable file)Machine:                           ARMVersion:                           0x1Entry point address:               0x0Start of program headers:          0 (bytes into file)Start of p headers:          59648 (bytes into file)Flags:                             0x5000000, Version5 EABISize of this header:               52 (bytes)Size of program headers:           0 (bytes)Number of program headers:         0Size of p headers:           40 (bytes)Number of p headers:         52Section header string table index: 51

2) Section 部分:

作用:对应人们常说的各种数据段、代码段等,术语是 p。

组成:ELF 文件的主体,位于文件视图中间部分的一个连续区域中。但是当模块被内核加载时,会根据各自属性被重新分配到新的内存区域。

3) Section header table 部分:

作用:每一个条目(术语叫 entry) 就是一个 Section header,负责描述 Section;

组成:由若干个 Section header entry 组成,Linux 内核里的数据结构定义如下 (注释部分为内核模块机制相关的的成员):

typedef struct elf32_shdr {Elf32_Word sh_name;Elf32_Word sh_type;Elf32_Word sh_flags;/* 对应的 p 在内存中的实际地址。初始值为0,当模块被内核加载时,会被修改为 p 在内存中的实际地址 */Elf32_Addr sh_addr;/* p 在文件视图中的偏移量 */Elf32_Off sh_offset;/* p 在文件视图中的大小 */Elf32_Word sh_size;Elf32_Word sh_link;Elf32_Word sh_info;Elf32_Word sh_addralign;Elf32_Word sh_entsize;
} Elf32_Shdr;

实践:

$ readelf hello.ko -S     # [-S|--p-headers|--ps]
There are 52 p headers, starting at offset 0xe900:Section Headers:[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al[ 0]                   NULL            00000000 000000 000000 00      0   0  0[ 1] .note.gnu.build-i NOTE            00000000 000034 000024 00   A  0   0  4[ 2] .text             PROGBITS        00000000 000058 000000 00  AX  0   0  1[...][ 5] .init.text        PROGBITS        00000000 000070 00001c 00  AX  0   0  4[...][ 7] .exit.text        PROGBITS        00000000 00008c 00000c 00  AX  0   0  4[...][ 9] __ksymtab         PROGBITS        00000000 000098 000008 00   A  0   0  4[...][25] __ksymtab_strings PROGBITS        00000000 0001f1 00000c 00   A  0   0  1[26] __param           PROGBITS        00000000 000200 000014 00   A  0   0  4[27] .rel__param       REL             00000000 00b9e4 000020 08   I 49  26  4[28] __versions        PROGBITS        00000000 000214 000100 00   A  0   0  4[29] .data             PROGBITS        00000000 000314 000004 00  WA  0   0  4[...][48] .ARM.attributes   ARM_ATTRIBUTES  00000000 00b21a 000031 00      0   0  1[49] .symtab           SYMTAB          00000000 00b24c 000520 10     50  75  4[50] .strtab           STRTAB          00000000 00b76c 0001cd 00      0   0  1[51] .shstrtab         STRTAB          00000000 00e6e4 00021b 00      0   0  1Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings)I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)O (extra OS processing required) o (OS specific), p (processor specific)

这里只截取模块加载相关的部分 p header,现在有个初步印象就好,后续使用到了相关的 secition header,再做进一步的研究分析。

内核模块自身并不会使用到上述数据结构 (elf32_hdr、elf32_shdr),它们是给内核模块加载器在加载模块时使用的。

3. EXPORT_SYMBOL() 是如何实现符号导出的?

EXPORT_SYMBOL() 系列宏用来向外界导出一个符号。内核和内核模块通过符号表的形式向外部世界导出符号的相关信息。

为什么要导出符号?

  • 如果没有独立存在的内核模块,作为单一的 Linux 内核映像,就没必要导出符号了。对于静态编译链接而成的内核映像而言,所有的符号引用都会在静态链接阶段完成。

  • 有了内核模块之后,独立编译链接的内核模块要使用到内核提供的基础设施(即调用内核函数,例如 printk)的话,就必须要解决符号引用问题 (unresolved symbol)。

  • 可以用 nm 命令来查看一个模块中出现的未定义符号:

$ nm hello.o -u         # [-u|--undefined-only]U __aeabi_unwind_cpp_pr0U param_ops_charpU printkU __this_module
  • 处理 unresolved symbol 问题的本质是在模块加载期间找到该符号在内存中的实际地址。

从全局上看,EXPORT_SYMBOL 的完整实现包括 3 部分:

  • EXPORT_SYMBOL 的定义部分

  • 链接脚本链接器部分

  • 使用导出符号部分

EXPORT_SYMBOL 的定义:

// include/linux/export.h
#define EXPORT_SYMBOL(sym) __EXPORT_SYMBOL(sym, "")/* For every exported symbol, place a struct in the __ksymtab p */
#define ___EXPORT_SYMBOL(sym, sec)     \extern typeof(sym) sym;      \__CRC_SYMBOL(sym, sec)      \static const char __kstrtab_##sym[]    \__attribute__((p("__ksymtab_strings"), aligned(1))) \= VMLINUX_SYMBOL_STR(sym);     \static const struct kernel_symbol __ksymtab_##sym  \__used        \__attribute__((p("___ksymtab" sec "+" #sym), used)) \= { (unsigned long)&sym, __kstrtab_##sym }

以 hello.ko 为例,EXPORT_SYMBOL(print_hello) 本质上就是定义了 2 个变量:

static const char __kstrtab_print_hello[] = "print_hello"static const struct kernel_symbol __ksymtab_print_hello = {(unsigned long)&print_hello,__kstrtab_print_hello,
};
  • 变量1: char []

    • 用于保存符号名;

    • 被放置在名为 "__ksymtab_strings" 的 p 中;

  • 变量2: struct kernel_symbol

    • 用于保存符号名与地址;

    • 被放置在名为 "___ksymtab+print_hello" 的 p 中;

根据 scripts/module-common.lds 里的定义:

SECTIONS {[...]__ksymtab  0 : { *(SORT(___ksymtab+*)) }[...]
}

"___ksymtab+print_hello" 会被转换为 "__ksymtab",这样就跟我们用 readelf hello.ko -S 查看到的 p 对应上了。

为了让内核可以通过上述 __ksymtab p 找到被导出的符号,链接器必须导出 p 的地址

include/asm-generic/vmlinux.lds.h/* Kernel symbol table: Normal symbols */   \__ksymtab         : AT(ADDR(__ksymtab) - LOAD_OFFSET) {  \VMLINUX_SYMBOL(__start___ksymtab) = .;   \KEEP(*(SORT(___ksymtab+*)))    \VMLINUX_SYMBOL(__stop___ksymtab) = .;   \} /* Kernel symbol table: strings */    \__ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) { \*(__ksymtab_strings)     \} 

在 kernel/module.c 中,可以看到下列声明:

/* Provided by the linker */
extern const struct kernel_symbol __start___ksymtab[];
extern const struct kernel_symbol __stop___ksymtab[];
[...]

这些变量会在内核或者内核模块查找某个符号时被使用。

EXPORT_SYMBOL 和 EXPORT_SYMBOL_GPL 导出符号的可见性


从这里开始重头戏模块加载的分析了,鉴于大多数人的注意力无法在一篇文章里上集中太久,更多的内容将放在后面的文章里。建议大家可以先自行阅读相关书籍,不是自己理解到的东西是消化不了的。

4. 相关参考

  • Linux 设备驱动开发详解,第 4 章节

  • 深入 Linux 设备驱动程序内核机制,第 1 章节

  • 深入 Linux 内核架构,第 7 章节

  • 深入理解 Linux 内核,第20 章节、附录2

5. 更多值得关注的知识点

  • 模块的加载

  • 模块的参数传递机制

  • 模块之间的依赖关系

  • 模块中的版本控制机制

  • ...


三、思考技术,也思考人生

要学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

  推荐阅读:

    专辑|Linux文章汇总

    专辑|程序人生

    专辑|C语言

嵌入式Linux

微信扫描二维码,关注我的公众号 

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

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

相关文章

台式计算机时间不准,每天开机电脑时间都不正确怎么办?试试这个办法!

原标题&#xff1a;每天开机电脑时间都不正确怎么办&#xff1f;试试这个办法&#xff01;上面电脑运用时间长了&#xff0c;经常会出现开机提示时间不正确&#xff0c;在系统上设置好时间后&#xff0c;第二天开机电脑时间还不正确&#xff0c;是什么原因呢&#xff1f;无论是…

c语言从1打印到100再打印到1该如何编写?

我觉得这是一个送分题&#xff0c;奈何知乎人才太多了&#xff0c;给出了各种古怪的写法&#xff0c;如果是做项目的话&#xff0c;我比骄建议一些正常的写法&#xff0c;就是大家都能看得懂的&#xff0c;不要搞什么花里胡哨&#xff0c;不过你要是交流的话&#xff0c;既然是…

有人知道 I3C 吗?

我们知道I2C、SPI、UART、但是应该很少有人知道I3C&#xff0c;不过它确实是存在的。在完善的I2C接口标准之后&#xff0c;I3C即将进入嵌入式市场。I3C标准由MIPI联盟开发&#xff0c;现已扩展到更广泛的市场&#xff0c;该标准将I2C&#xff0c;UART和SPI组合为10Mbit / s&…

清华北大计算机考研报录比,2020年考研,清华北大报考人数对比,占考研总人数14%...

原标题&#xff1a;2020年考研&#xff0c;清华北大报考人数对比&#xff0c;占考研总人数14%2020年考研341万人同进考场&#xff0c;考研报名人数超过300万人&#xff0c;创造历史新高。因此2020年考研被公认为考研难度最大的一年。341万考研人&#xff0c;有多少人报考清华北…

我同学

周六在朋友圈提到的&#xff0c;我有一个在三星工作、从事camera方向研究的同学&#xff0c;他在去三星之前已经做了很多年camera了&#xff0c;之前在知识星球也给大家推荐了他的技术公众号&#xff0c;对喜欢或者从事camera研究的会非常有帮助&#xff0c;建议大家关注一下。…

Uva 201 Squares (暴力 + 枚举)

【题意】 给出 n*n 的 点 H 横向 V 纵向 &#xff08;注意&#xff09; V 想 I,j 相反 问 边 为1 &#xff0c;2 &#xff0c; 3 。。。。 n 的 正方向有几个 【思路】 n 很小 直接暴力 枚举 枚举 两个点&#xff0c; 看看 从 左上角 到右下角 &#xff08;变成为 s的…

自己都不觉得自己值钱,别人怎么觉得你值钱?

今天跟一个同学聊天&#xff0c;他最近正在找工作&#xff0c;找了一家外包企业拿到offer&#xff0c;对方开了薪资&#xff0c;他问我「发哥&#xff0c;这个薪资没有问题吧&#xff1f;」。薪资这个问题&#xff0c;我之前已经给出过建议&#xff0c;新企业的薪资最好能能让你…

ASP.NET AJAX入门系列(10):Timer控件简单使用

本文主要通过一个简单示例&#xff0c;让Web页面在一定的时间间隔内局部刷新&#xff0c;来学习一下ASP.NET AJAX中的服务端Timer控件的简单使用。主要内容Timer控件的简单使用1&#xff0e;添加新页面并切换到设计视图。2&#xff0e;如果页面没有包含ScriptManager控件&#…

全国计算机等级考试暨南大学,9月暨南大学计算机等级考试报名时间通知

全国计算机等级考试暨南大学珠海校区考点2017年下半年(第49次)全国计算机等级考试已经开始报名&#xff0c;以下是小编收集的暨南大学计算机等级考试报名时间通知&#xff0c;希望大家认真阅读!报考事项通知1、网上报名时间为2017年6月15日17:00至30日12:00。广东省全国计算机等…

ANSI是什么编码?

原文&#xff1a;http://www.cnblogs.com/malecrab/p/5300486.html用Notepad创建一个文本文件text.txt&#xff0c;其默认编码格式为ANSI&#xff08;乍看之下&#xff0c;还以为是ASCII呢&#xff09;&#xff0c;输入汉字居然不是乱码&#xff1a;保存为test.txt&#xff0c;…

R语言数据分析过程

R语言数据分析前期&#xff1a;预处理过程&#xff0c;供自己查阅&#xff0c;欢迎大家指正。 利用R语言导入数据&#xff1a; taobao<-read.csv("taobao.csv",stringsAsFactors F) 我最常用到就是read.csv&#xff0c;其中stringsAsFactorsF很重要&#xff0c;不…

苏州,遇见NXP痞子衡

昨天的文&#xff0c;今天发--晚上打球照片&#xff0c;大家都是1.8的&#xff0c;结果拍出来变矮了因为公司项目问题&#xff0c;今天飞苏州&#xff0c;昨晚上就得到了命令&#xff0c;早上直接从家里开拔&#xff0c;这次去苏州提前跟痞子衡说了情况&#xff0c;原因是上次我…

Android中GC的触发时机和条件

本文分析基于Android R(11)源码Java对象的创建由Allocator负责&#xff0c;回收由Collector负责。从Android O开始&#xff0c;对于前台应用默认的GC Collector是CC(Concurrent Copying) Collector&#xff0c;与之相匹配的Allocator则是Region-based Bump Pointer Allocator(w…

【华为出品】物联网全栈开发实战营第2期来啦!送海思开发板

物联网一直在改变我们生活的方方面面。可穿戴设备有助于监控我们的偏好、习惯和健康状况。智能家居设备可提高家居的舒适度、安全性和便利性。城市利用连接的数字设备收集的海量数据(数十亿个)来改善城市规划。制造商使用工业物联网设备来优化工厂车间的操作。据MarketsandMark…

2017年12月计算机一级c,2017年12月计算机二级C语言考试操作题冲刺卷(2)

三. 程序设计题3 [简答题]请编写函数fun&#xff0c;其功能是&#xff1a;移动一维数组中的内容&#xff0c;若数组中有n个整数&#xff0c;要求把下标从0&#xff5e;P(含P&#xff0c;P小于等于n-1)的数组元素平移到数组的最后。例如&#xff0c;一维数组中的原始内容为&…

给楠哥准备的入门单片机

---- 少儿编程前几天&#xff0c;珠海的三哥的老婆微信问我「我想给wending报名这个少儿编程课程&#xff0c;你帮我看看怎么样&#xff1f;」我看了下&#xff0c;除了价格还可以&#xff0c;其他都不怎么样&#xff0c;因为报名了这个课程&#xff0c;大家都知道你们家挺有钱…

应届生昆山offer和上海户口offer要如何选择?

这两天还在苏州&#xff0c;没有感受苏州的什么好地方&#xff0c;天气有点微凉&#xff0c;有秋天的味道&#xff0c;同行的何总是一个很闷的人&#xff0c;我总想让他跟我说说故事&#xff0c;每次他都说「我是一个没有故事的男人」&#xff0c;是的&#xff0c;一个从毕业就…

html5--3.7 input元素(6)

html5--3.7 input元素(6) 学习要点 input元素及其属性input元素 用来设置表单中的内容项&#xff0c;比如输入内容的文本框&#xff0c;按钮等不仅可以布置在表单中&#xff0c;也可以在表单之外的元素使用input元素的属性 type属性&#xff1a;指定输入内容的类型&#xff0c;…

计算机or笔记本,笔记本or台式机?大学生第一个烦恼被它解决了

原标题&#xff1a;笔记本or台式机&#xff1f;大学生第一个烦恼被它解决了台式电脑还是笔记本&#xff1f;许多大学生在选择第一台大学电脑的时候都会面临这个问题。初秋的风带着点点凉意悄然走进了大学校园&#xff0c;开学季来临&#xff0c;又一批莘莘学子即将满怀青春的梦…

把编译时间加入到目标文件

原文&#xff1a;https://www.cnblogs.com/pingwen/p/8183728.html1、问题背景&#xff1a;如何保证发布出去的bin文件是最终测试通过的版本&#xff1f;一般的来讲&#xff0c;代码到了测试后期&#xff0c;master分支就不会频繁的提交了&#xff0c;并且提交也会更加谨慎。但…