简述新建站点的步骤,笑话类网站用什么做,苏州seo排名公司,网站建设行业如何哈喽#xff0c;我是杰克吴#xff0c;继续记录我的学习心得。一、关于兴趣的几点思考1. 享受不是兴趣#xff0c;愿意付出才是#xff1a;兴趣很容易跟享受混淆。享受是被动的#xff0c;无需付出#xff1b;而兴趣则要求你甘愿为了这件事情付出努力。2.任何事情#x… 哈喽我是杰克吴继续记录我的学习心得。一、关于兴趣的几点思考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.hstatic 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 nameJack // 指定模块参数
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 table1) 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: 2s 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_hellostatic const struct kernel_symbol __ksymtab_print_hello {(unsigned long)print_hello,__kstrtab_print_hello,
};
变量1: char []用于保存符号名;被放置在名为 __ksymtab_strings 的 p 中;变量2: struct kernel_symbol用于保存符号名与地址;被放置在名为 ___ksymtabprint_hello 的 p 中;根据 scripts/module-common.lds 里的定义SECTIONS {[...]__ksymtab 0 : { *(SORT(___ksymtab*)) }[...]
}
___ksymtabprint_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 章节、附录25. 更多值得关注的知识点模块的加载模块的参数传递机制模块之间的依赖关系模块中的版本控制机制...三、思考技术也思考人生要学习技术更要学习如何生活。你和我各有一个苹果如果我们交换苹果的话我们还是只有一个苹果。但当你和我各有一个想法我们交换想法的话我们就都有两个想法了。 推荐阅读 专辑|Linux文章汇总 专辑|程序人生 专辑|C语言嵌入式Linux微信扫描二维码关注我的公众号