├── jx └── README ├── zhaoxiao └── test ├── HZero ├── README ├── 3.memory ├── 2.char └── 1.kernel module ├── jack--li └── chapter01 ├── Sprint05 └── test.txt ├── README.md ├── stron ├── 1557643423066.png ├── 1557656859923.png ├── 1557762840867.png ├── 1558280607511.png ├── 1558777428404.png ├── 1558778085928.png ├── 1558779843508.png ├── 1559357813413.png ├── 1559379796680.png ├── 1559382558201.png ├── 1559384672396.png ├── 1559437888743.png ├── 1559439057012.png ├── 1559439170767.png └── 深入linux设备驱动程序内核机制.md ├── fitzyu ├── chapter01 │ └── 内核模块.md └── chapter02 │ └── 字符设备驱动程序.md ├── jeff ├── chapter01-modules.md └── chapter02-chardev.c └── cheng └── chapter01-modules └── modules.md /jx/README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /zhaoxiao/test: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /HZero/README: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /jack--li/chapter01: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sprint05/test.txt: -------------------------------------------------------------------------------- 1 | sprint05 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kernel-note 2 | Linux内核机制笔记 3 | -------------------------------------------------------------------------------- /stron/1557643423066.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1557643423066.png -------------------------------------------------------------------------------- /stron/1557656859923.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1557656859923.png -------------------------------------------------------------------------------- /stron/1557762840867.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1557762840867.png -------------------------------------------------------------------------------- /stron/1558280607511.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1558280607511.png -------------------------------------------------------------------------------- /stron/1558777428404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1558777428404.png -------------------------------------------------------------------------------- /stron/1558778085928.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1558778085928.png -------------------------------------------------------------------------------- /stron/1558779843508.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1558779843508.png -------------------------------------------------------------------------------- /stron/1559357813413.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1559357813413.png -------------------------------------------------------------------------------- /stron/1559379796680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1559379796680.png -------------------------------------------------------------------------------- /stron/1559382558201.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1559382558201.png -------------------------------------------------------------------------------- /stron/1559384672396.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1559384672396.png -------------------------------------------------------------------------------- /stron/1559437888743.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1559437888743.png -------------------------------------------------------------------------------- /stron/1559439057012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1559439057012.png -------------------------------------------------------------------------------- /stron/1559439170767.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-lugoo/kernel-note/HEAD/stron/1559439170767.png -------------------------------------------------------------------------------- /HZero/3.memory: -------------------------------------------------------------------------------- 1 | 3.内存分配-1 概述 http://naotu.baidu.com/file/be7da6455235d4a6ebec1a94e6f92f5a?token=bed42023c317d633 2 | 3.内存分配-2 基本对象 http://naotu.baidu.com/file/4c797aa7048780e09fea7a136d7b5286?token=33b1f8ac526d5e6a 3 | 3.内存分配-5 per_cpu变量-DECLARE_PER_CPU宏 http://naotu.baidu.com/file/180321f7391b046b22008b16f330ee63?token=49ceb02a8b3a00e2 4 | -------------------------------------------------------------------------------- /fitzyu/chapter01/内核模块.md: -------------------------------------------------------------------------------- 1 | # 第2章 字符设备驱动程序 2 | 3 | ## 函数 4 | ### 1.struct cdev *cdev_alloc(void) 5 | 动态分配 struct cdev对象 6 | 7 | ### 2.cdev_init 初始化cdev对象 8 | ###3.register_chrdev_region 9 | 这个函数要完成的主要功能是将当前设备驱动程序要使用的设备号记录到chardevs数组中 10 | 11 | 12 | 13 | ## 结构体 14 | ### 1 .struct file_operations 15 | 字符设备的编写都是为了实现该结构体中的函数指针 16 | 17 | ### 2. struct cdev 18 | 内核为字符设备抽象出的具体结构 19 | struct kobject kobj 内嵌的内核对象 20 | struct module *owner 字符设备驱动程序所在的内核模块对象指针 21 | 22 | const struct file_operations *ops 在应用程序通过文件系统接口呼叫到设备驱动程序中实现的文件操作类函数的过程中,ops指针起着桥梁纽带的作用。 23 | 24 | struct list_head list 用来将系统中的字符设备形成链表 25 | 26 | dev_t dev 字符设备的设备号,由主设备号和次设备号构成。 27 | 28 | unsigned int count 同属于一个主设备号的次设备号的个数 29 | 30 | ## 变量 31 | 32 | ### 1.dev_t 类型变量来标识一个设备号,这个32位的无符号整数,低20位表示次设备号,高12位宝石主设备号 33 | 34 | ### 2.chrdevs 内核用于设备号分配和管理的核心元素 35 | -------------------------------------------------------------------------------- /fitzyu/chapter02/字符设备驱动程序.md: -------------------------------------------------------------------------------- 1 | # 第2章 字符设备驱动程序 2 | 3 | ## 函数 4 | ### 1.struct cdev *cdev_alloc(void) 5 | 动态分配 struct cdev对象 6 | 7 | ### 2.cdev_init 初始化cdev对象 8 | 9 | ### 3.register_chrdev_region 10 | 这个函数要完成的主要功能是将当前设备驱动程序要使用的设备号记录到chardevs数组中 11 | 12 | ### 4.alloc_chrdev_region 13 | 该函数是由系统协助分配设备号,而3是自己设定设备号 14 | 15 | ### 5.cdev_add 16 | 把一个字符设备添加到系统中 17 | 18 | ### 6.cdev_del 19 | 把字符设备从系统中移除 20 | 21 | ## 结构体 22 | ### 1 .struct file_operations 23 | 字符设备的编写都是为了实现该结构体中的函数指针 24 | 25 | ### 2. struct cdev 26 | 内核为字符设备抽象出的具体结构 27 | struct kobject kobj 内嵌的内核对象 28 | struct module *owner 字符设备驱动程序所在的内核模块对象指针 29 | 30 | const struct file_operations *ops 在应用程序通过文件系统接口呼叫到设备驱动程序中实现的文件操作类函数的过程中,ops指针起着桥梁纽带的作用。 31 | 32 | struct list_head list 用来将系统中的字符设备形成链表 33 | 34 | dev_t dev 字符设备的设备号,由主设备号和次设备号构成。 35 | 36 | unsigned int count 同属于一个主设备号的次设备号的个数 37 | 38 | ## 变量 39 | 40 | ### 1.dev_t 类型变量来标识一个设备号,这个32位的无符号整数,低20位表示次设备号,高12位宝石主设备号 41 | 42 | ### 2.chrdevs 内核用于设备号分配和管理的核心元素 43 | -------------------------------------------------------------------------------- /HZero/2.char: -------------------------------------------------------------------------------- 1 | 2.字符设备驱动-1 概述 http://naotu.baidu.com/file/f89de7daa19c9b1fcda74986c4bf65c7?token=fe2699359e914c98 2 | 2.字符设备驱动-2 基本对象 http://naotu.baidu.com/file/6807ba17bacb0847da462e56b0204f6e?token=94211d4c69d9a665 3 | 2.字符设备驱动-3 字符设备分配-cdev_alloc http://naotu.baidu.com/file/aff08ed6c45e5fcc725bf44265946c23?token=b37b7a223aa82f0d 4 | 2.字符设备驱动-3 字符设备初始化-cdev_init http://naotu.baidu.com/file/b5f671b604867940237ff6eec14497fb?token=30db4d022b733c5a 5 | 2.字符设备驱动-4设备号的分配-register_chrdev_region http://naotu.baidu.com/file/53e7ed4556132c666e4b891acb57823e?token=32a92fdd0140b165 6 | 2.字符设备驱动-4 设备号的分配 unregister_chrdev_region http://naotu.baidu.com/file/0cbb1395c5b407f86d1778abe4a540c8?token=8bcbba7fa46c4741 7 | 2.字符设备驱动-4 设备号的分配- alloc_chrdev_region http://naotu.baidu.com/file/b1fb0ec276f3d22a2ac7bf44ba91425e?token=6b7aae589b3f2663 8 | 2.字符设备驱动-5字符设备的注册 http://naotu.baidu.com/file/961ca617acb519e873c6f80d1d658438?token=7143c20ad98b5d10 9 | 2.字符设备驱动-6 字符设备的注销 http://naotu.baidu.com/file/ef83d8738017a8103a9c0ca2cc19b035?token=ccd76090377b5ae1 10 | 2.字符设备驱动-7 设备文件节点生成 http://naotu.baidu.com/file/b4c68d27b31dc477ef79fddad38e8619?token=698ccbe783cace7b 11 | 2.字符设备驱动-8 字符设备的打开 http://naotu.baidu.com/file/aa1811de5b26f613e67acadf75bdf6a7?token=7eef5c6d32d79adc 12 | 2.字符设备驱动-9 字符设备的关闭 http://naotu.baidu.com/file/b7919e526755a6396533bb42d74ad6f1?token=2f74c90e6021fe25 13 | 2.字符设备驱动-10 小结 http://naotu.baidu.com/file/fca94229ff61ba37f7f8db13bdab86e3?token=bb38bfa8c75de05f 14 | -------------------------------------------------------------------------------- /HZero/1.kernel module: -------------------------------------------------------------------------------- 1 | 1.1. 内核模块中的文件格式 2 | 分享链接:http://naotu.baidu.com/file/e3f8a1fc429e2492399101bd667b3d48?token=546f9c33bd297e1d 3 | 4 | 1.2 EXPORT_SYMBOL的内核实现 5 | 1.内核模块-1.2 EXPORT_SYMBOL的内核实现-1基本概念: http://naotu.baidu.com/file/ba7d470020a64b5853877a827e1e888e?token=25d9fa679ef3953f 6 | 1.内核模块-1.2 EXPORT_SYMBOL的内核实现-2 EXPORT_SYMBOL宏:http://naotu.baidu.com/file/159aff33c14238ef777c7ad3dba9f0b1?token=d1475d973a2ede66 7 | 8 | 1.3 模块的加载过程 9 | 1.内核模块-1.3 模块的加载过程-1.概述 http://naotu.baidu.com/file/8278e4c29c32553a256715427dc64c7b?token=9c8bbe43f734526d 10 | 1.内核模块-1.3 模块的加载过程-2.主要对象http://naotu.baidu.com/file/13c8dfc125a9bb849ce66d6c2bfbb867?token=b3e0341783bbf807 11 | 1.内核模块-1.3 模块的加载过程-3 insmod流程分析http://naotu.baidu.com/file/33e60220dc97e80cf9482f8fd3601ebe?token=ca438e608418b956 12 | 1.内核模块-1.3 模块的加载过程-4 find_symbol http://naotu.baidu.com/file/8e1afec36a7a71f1c28bb3827dcbaf5c?token=b855391fe5e0438a 13 | 1.内核模块-1.3 模块的加载过程-5 模块参数-1module_param宏 http://naotu.baidu.com/file/25ed79d6cc73feadcb52c9d31be2bc18?token=d2bee65af019d99b 14 | 1.内核模块-1.3 模块的加载过程-7.模块的版本控制 http://naotu.baidu.com/file/881918021d598325ebf76ee4845a0417?token=f88431deac99d46d 15 | 1.内核模块-1.3 模块的加载过程-8.模块信息 http://naotu.baidu.com/file/0f40ea8fc5fcbe651db977b570a5f68e?token=39092ca1f8bdb099 16 | 1.内核模块-1.3 模块的加载过程-9.模块的Lincense-MODULE_LICENSE宏 http://naotu.baidu.com/file/454c0dae252dfb708577f5cf0c60d339?token=dd4611390f11614e 17 | 1.内核模块-1.3 模块的加载过程-10.模块的Vermagic http://naotu.baidu.com/file/acc9541a40020204495d144e0e13aed8?token=75c98766c4d2645b 18 | 1.内核模块-1.3 模块的加载过程-11rmmod流程分析 http://naotu.baidu.com/file/f51be5f46d0f3936a6d52a13b21b3878?token=54834a91d3df83c6 19 | -------------------------------------------------------------------------------- /jeff/chapter01-modules.md: -------------------------------------------------------------------------------- 1 | # 第一章:内核模块 2 | 3 | 4 | ``` 5 | 6 | 7 | #define __EXPORT_SYMBOL(sym, sec) \ 8 | extern typeof(sym) sym; \ 9 | __CRC_SYMBOL(sym, sec) \ 10 | static const char __kstrtab_##sym[] \ 11 | __attribute__((section("__ksymtab_strings"), aligned(1))) \ 12 | = MODULE_SYMBOL_PREFIX #sym; \ 13 | static const struct kernel_symbol __ksymtab_##sym \ 14 | __used \ 15 | __attribute__((section("__ksymtab" sec), unused)) \ 16 | = { (unsigned long)&sym, __kstrtab_##sym } 17 | 18 | #define EXPORT_SYMBOL(sym) \ 19 | __EXPORT_SYMBOL(sym, "") 20 | 21 | #define EXPORT_SYMBOL_GPL(sym) \ 22 | __EXPORT_SYMBOL(sym, "_gpl") 23 | 24 | #define EXPORT_SYMBOL_GPL_FUTURE(sym) \ 25 | __EXPORT_SYMBOL(sym, "_gpl_future") 26 | static const char * __kstrtab_my_exp_function = "my_exp_funxtion" 27 | static const struct kernel_symbol __kstrtab_my_exp_function 28 | = { (unsigned long)&my_exp_function, __kstrtab_my_exp_function }\ 29 | 30 | struct kernel_symbol 31 | { 32 | unsigned long value; /* my_exp_function 放在 __ksymtab 段 */ 33 | const char *name; /* __kstrtab_my_exp_function 放在 __ksymtab_strings */ 34 | }; 35 | ``` 36 | 37 | **找段名称字符串表的基地址:** 38 | 通过hdr中的e_shstrndx 对应到entry数组的下标直接索引到字符串表的entry,再通过entry中的 sh_offset找到字符串表的基地址,基地址使用secstrings变量保存。 39 | 40 | **找符号名称字符串表基地址:** 41 | 遍历数组entry中的各个entry,找到entry数组结构中sh_type类型为SHT_SYMTAB的entry,通过这个entry结构中的sh_link对应到entry数组结构下标的entry,从这个entry的sh_offset找到对应的section的基地址,即是符号名称字符串表基地址,使用strtab表示。 42 | 43 | 获取某一段的名称 44 | 假设该段在Section header table中的索引为i,那么secstrings + entry[i].sh.name即是该段的名字。 45 | 获取符号名称的基地址: 46 | 遍历整个符号名称字符串表,因为这个段是struct Elf_Sym数组构成,遍历数组中买一个struct Elf_Sym结构,匹配到与查找名称相同的Struct Elf_Sym.再从Struct Elf_Sym取出符号名称的基地址。 47 | 48 | 符号表中的结构体定义: 49 | struct Elf_Sym { 50 | Elf32_Word st_name; /* 符号名在符号名称字符串表中的索引 */ 51 | Elf32_Addr st_value; /* 符号所在的内存地址 */ 52 | Elf32_Word st_size; 53 | unsigned char st_info; 54 | unsigned char st_other; 55 | Elf32_Half st_shndx; /* 符号所在的段在section header table中的索引值 */ 56 | }; 57 | 内核中未定义的符号 st_shndx会设置成SHN_UNDEF. 58 | 59 | 内核中对用EXPORT_SYSBOL导出的符号,模块在编译的时候会为这个ELF文件生成一个段 .rel__ksymtab,它专门用于对__ksymtab段的重定位,称为relocation section. 60 | 这个段里面都是由 struct elf32_rel数组构成。 61 | typedef struct elf32_rel { 62 | ELf32_Addr r_offset; 63 | Elf32_Word r_info; 64 | }Elf32_Rel 65 | 系统根据r_offset得到需要修改的导出符号struct kernel_symble中的value所在的内存地址。 66 | r_info获取得到符号表中的偏移量,即可得到struct Elf_Sym 结构,从结构中获取基地址,把这个基地址给上面的value,最终导致导出的符号地址被修改。 67 | 68 | ``` 69 | __visible struct module __this_module 70 | __attribute__((section(".gnu.linkonce.this_module"))) = { 71 | .name = KBUILD_MODNAME, 72 | .init = init_module, 73 | #ifdef CONFIG_MODULE_UNLOAD 74 | .exit = cleanup_module, 75 | #endif 76 | .arch = MODULE_ARCH_INIT, 77 | }; 78 | ``` 79 | 用.gnu.linkonce.this_module段表示struct module结构,在内核中 80 | 有这样定义: 81 | extern struct module __this_module; 82 | #define THIS_MODULE (&__this_module) 83 | 所以THIS_MODULE就指向了.gnu.linkonce.this_module段中的struct module 结构。 84 | 85 | 86 | -------------------------------------------------------------------------------- /cheng/chapter01-modules/modules.md: -------------------------------------------------------------------------------- 1 | # 第一章 内核模块 2 | 3 | #### ELF介绍 4 | ![ELF](https://images0.cnblogs.com/blog/417887/201309/10163329-3b81c167e3e34681b4d30f19953ccc81.jpg) 5 | 6 | 7 | #### 1. 模块的加载过程 8 | 9 | * insmod 读取模块文件数据到用户空间,然后执行系统调用sys_init_module处理模块加载 10 | 11 | * sys_init_module调用load_module, 将内核空间利用vmalloc分配一块大小等于模块的地址空间, 将用户空间的数据复制到内核空间 12 | 13 | * load_module通过如下计算就可以获得section名称字符串表的基地址secstrings和符号名称字符串表的基地址strtab.  14 | 计算section名称的方法 15 | >    char *secstrings = (char *)hdr + entry[hdr->e_shstrndx].sh_offset. 16 | >计算符号表名称字符串表的基地址的方法 17 | > 18 | >  遍历section header table中的所有entry, 找一个entry[i].sh_type = SHT_SYMTAB的entry, SHT_SYMTAB 表明这个entry所对应的section是一个符号表。 19 | >entry[i].sh_link是符号名称字符串表在section在section header table中的索引, 所以基地址就为 char *strtab = (char*)hdr+entry[entry[i].sh_link].sh_offset 20 | * 开始遍历section header table中所有的entry, 将entry的sh_addr指向HDR中的实际地址。 21 | 22 | * layout_sections函数中, 内核遍历HDR视图中的每一个section, 对每一个标记有HF_ALLOC的section,将其划分到两大类的section当中:CORE和INIT. 23 | 24 | * 对“未解决的引用”符号的处理 25 | simplify_symbols函数的代码实现, 函数首先通过一个for循环遍历符号表中的所有符号, 根据Elf_sym的st_shndx查找到SHN_UNDEF, 这就是未解决的引用, 调用solve_symbol来处理, 后者会调用find_symbol来查找符号, 找到了就将它在内存中的实际值赋值给st_value, 这样未解决的引用就有了正确的内存地址。 26 | * 重定位 27 | 重定位主要用来解决静态链接时的符号引用与动态加载时实际符号地址不一致的问题. 28 | 如果模块有用EXPORT_SYMBOL导出符号, 那么这个模块的elf文件会生成一个独立的section:".rel_ksymtab", 它专门用于对"__ksymtab" section的重定位, 称为relocation section. 29 | * 模块成功加载到系统之后, 表示模块的struct module变量mod加入到modules中(一个全局的链表变量,记录系统中的模块) 30 | 31 | #### 2. 模块如何引用内核或者其他模块中的函数和变量 32 | 33 | 34 | 通过find_symbol函数查找,第一部分在内核导出的符号表中查找对应的符号, 找到就返回对应的符号信息, 否则,再进行第二部分查找,在系统已加载的模块中查找. 35 | 36 | 37 | #### 3. 模块的参数传递机制 38 | 模块必需使用module_param宏声明模块可以接收的参数, 如module_param(dolphin, int, 0), 内核模块加载器对参数的构造过程发生在模块初始化函数之前, 所以在模块构造函数中就可以得到命令行传过来的实际参数。 39 | 命令行的参数值复制到模块的参数过程中, module_param宏定义的"__param"section起到桥梁的作用, 通过"__param"section, 内核找到模块中定义参数所在内存地址, 然后用命令行中参数值修改之。 40 | 41 | #### 4. 模块间的依赖关系 42 | struct list_head source_list和struct list_head target_list 用来构建有信赖关系模块的链表, 对链表的使用要结合数据结构struct module_use: 43 | 44 | 如果有依赖关系, 在solve_symbol函数中将会导出这"未解决的引用"符号模块记录在变量struct module *owner中, 调用ref_module(mod, owner)在模块mod和owner之间建立信赖关系。 ref_module做过检查后调用add_module_usage(mod, owner)在mod和owner模块间建立信赖关系. 45 | 46 | #### 5. 模块的版本控制机制 47 |  linux系统对此的解决方案是使用接口的校验和,也叫接口crc校验码, 为了确保这种机制能够正常运行, 内核必须启用CONFIG_MODVERSIONS宏, 模块编译时也需启用此宏, 否则加载不成功。 48 | 49 | __kcrctab_my_exp_function用来保存__crc_my_exp_function变量地址并将其放在一个名为__kcrctab的section中. 可见如果内核启用了CONFIG_MODVERSIONS宏, 每一个导出 50 | 的符号都会生成一个crc校验码check_version用一个for循环在__versions section中进行遍历, 对每一个struct modversion_info元素和找到的符号名symname进行匹配, 如果匹配成功就进行接口的校验码比较。 51 | 52 | 独立编译的内核模块,如果引用到了内核导出一符号, 在模块编译的过程中, 工具链会到内核源码所在目录下查找Module.symvers文件, 将得到的printk的crc校验码记录到模块的__versions section中。 53 | 54 | 55 | EXPORT_SYMBOL的宏定义 56 | ```c 57 | #ifdef CONFIG_MODVERSIONS 58 | /* Mark the CRC weak since genksyms apparently decides not to 59 | * generate a checksums for some symbols */ 60 | #define __CRC_SYMBOL(sym, sec) \ 61 | extern void *__crc_##sym __attribute__((weak)); \ 62 | static const unsigned long __kcrctab_##sym \ 63 | __used \ 64 | __attribute__((section("__kcrctab" sec), unused)) \ 65 | = (unsigned long) &__crc_##sym; 66 | #else 67 | #define __CRC_SYMBOL(sym, sec) 68 | #endif 69 | 70 | /* For every exported symbol, place a struct in the __ksymtab section */ 71 | #define __EXPORT_SYMBOL(sym, sec) \ 72 | extern typeof(sym) sym; \ 73 | __CRC_SYMBOL(sym, sec) \ 74 | static const char __kstrtab_##sym[] \ 75 | __attribute__((section("__ksymtab_strings"), aligned(1))) \ 76 | = MODULE_SYMBOL_PREFIX #sym; \ 77 | static const struct kernel_symbol __ksymtab_##sym \ 78 | __used \ 79 | __attribute__((section("__ksymtab" sec), unused)) \ 80 | = { (unsigned long)&sym, __kstrtab_##sym } 81 | 82 | #define EXPORT_SYMBOL(sym) \ 83 | __EXPORT_SYMBOL(sym, "") 84 | 85 | #define EXPORT_SYMBOL_GPL(sym) \ 86 | __EXPORT_SYMBOL(sym, "_gpl") 87 | 88 | #define EXPORT_SYMBOL_GPL_FUTURE(sym) \ 89 | __EXPORT_SYMBOL(sym, "_gpl_future") 90 | ``` 91 | 字符串表是elf文件中的一个section, 用来保存elf文件中各个section的名称或符号表名. 92 |     计算section名称的方法 93 |     char *secstrings = (char *)hdr + entry[hdr->e_shstrndx].sh_offset. 94 |     计算符号表名称字符串表的基地址的方法 95 |      96 |     .遍历section header table中的所有entry, 找一个entry[i].sh_type = SHT_SYMTAB的entry, SHT_SYM 97 |     TAB    表明这个entry所对应的section是一个符号表。 98 |      99 |     .entry[i].sh_link是符号名称字符串表在section在section header table中的索引, 所以基地址就为 100 |     char *strtab = (char*)hdr+entry[entry[i].sh_link].sh_offset 101 | 102 |     至此load_module通过以上计算就可以获得section名称字符串表的基地址secstrings和符号名称字符串表 103 |     的基地址strtab. 104 | 105 | ### 参考文档 106 | ELF规范:    [Executable and Linking Format Specification V1.2]( 107 | http://www.sco.com/developers/gabi/latest/contents.html) 108 | 参考文档 - [ELF-64 Object File Format](https://www.uclibc.org/docs/elf-64-gen.pdf) 109 | [参考blog] 110 | (https://www.cnblogs.com/feng9exe/p/6899351.html) 111 | (https://blog.csdn.net/dyron/article/details/9022629) 112 | -------------------------------------------------------------------------------- /jeff/chapter02-chardev.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static struct cdev chr_dev; 8 | 9 | struct cdev { 10 | struct kobject kobj; 11 | /* 用于驱动模型 */ 12 | struct module *owner; 13 | /* 当前字符设备所属模块 */ 14 | const struct file_operations *ops; 15 | struct list_head list; 16 | /* 用来将系统中的字符设备形成链表。*/ 17 | dev_t dev; 18 | /* 字符设备的设备号,由主设备和次设备构成 */ 19 | unsigned int count; 20 | /* 棣属于同一主设备号的次设备号的个数,用于表示当前设备驱动程序控制的设备数量 */ 21 | }; 22 | /* 23 | * struct cdev仅仅作为一个内嵌的数据结构内嵌于实际的字符设备驱动结构中 24 | */ 25 | 26 | static dev_t ndev; 27 | 28 | static int chr_open(struct *inode nd, struct file *filp) 29 | { 30 | int major = MAJOR(nd->i_rdev); 31 | int minor = MINOR(nd->i_rdev); 32 | printk("chr_open,major = %d, minor = %d", major, minor); 33 | return 0; 34 | } 35 | 36 | static ssize_t char_read(struct file *f, char __user *u, size_t sz, loff_t *off) 37 | { 38 | printk("In the chr_read() function\n"); 39 | return 0; 40 | } 41 | 42 | struct file_operation chr_ops = 43 | { 44 | .owner = THIS_MODULE, 45 | .open = chr_open, 46 | .read = chr_read, 47 | }; 48 | 49 | 50 | static int demo_init(void) 51 | { 52 | int ret; 53 | cdev_init(&chr_dev, &chr_ops); 54 | ret = alloc_chrdev_region(&ndev, 0 , 1,"chr_dev"); 55 | if (ret < 0) 56 | return ret; 57 | ret = cdev_add(&chr_dev, ndev, 1); 58 | if (ret < 0) 59 | return ret; 60 | return 0; 61 | } 62 | 63 | 64 | statict void demo_exit(void) 65 | { 66 | cdev_del(&chr_dev); 67 | unregister_chrdev_region(ndev, 1); 68 | } 69 | 70 | 71 | 72 | /* 73 | * struct file_operations 中有个 struct module *owner; 74 | * owner指向第一章中提到的.gnu.linkonce.this_module段的struct module结构, 75 | * 作用就是file_operations中的函数被调用时不要卸载当前模块。 76 | * 如果设备驱动编译进内核不编译成模块,THIS_MODULE就是空指针,可以 77 | * 从内核代码中看到这一段。 78 | */ 79 | #ifdef MODULE 80 | extern struct module __this_module; 81 | #define THIS_MODULE (&__this_module) 82 | #else 83 | #define THIS_MODULE ((struct module *)0) 84 | #endif 85 | 86 | 87 | 88 | static struct char_device_struct { 89 | struct char_device_struct *next; 90 | /* 这个next是用来连接主设备号哈希值相同的情况,比如MKDEV(2,0)和MKDEV(257,0) */ 91 | unsigned int major; 92 | unsigned int baseminor; 93 | 次设备号 94 | int minorct; 95 | 次设备号个数 96 | char name[64]; 97 | struct cdev *cdev; /* will die */ 98 | /* 这里的will die在linux5.0版本中都还在,有点搞笑 */ 99 | } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; 100 | #define CHRDEV_MAJOR_HASH_SIZE 255 101 | 102 | /* 这个结构跟踪每一个字符设备主设备号;*/ 103 | 104 | int cdev_add(struct cdev *p, dev_t dev, unsigned count) 105 | { 106 | int error; 107 | 108 | p->dev = dev; 109 | p->count = count; 110 | 111 | return kobj_map(cdev_map, dev, count, NULL, 112 | exact_match, exact_lock, p); 113 | } 114 | /* 参数p为要加入系统的字符设备对象的指针,dev为设备号,count表示 115 | 从次设备号开始连续的设备数量。 */ 116 | 117 | static struct kobj_map *cdev_map; 118 | struct kobj_map { 119 | struct probe { 120 | struct probe *next; 121 | /* 散列冲突链表的下一个元素 */ 122 | dev_t dev; 123 | /* 设备号范围的初始设备号(主、次设备号) */ 124 | unsigned long range; 125 | /* 设备号范围的大小 */ 126 | struct module *owner; 127 | /* 指向实现设备驱动程序模块的指针(编译进内核就是空指针) */ 128 | kobj_probe_t *get; 129 | /* 探测谁拥有这个设备号范围 */ 130 | int (*lock)(dev_t, void *); 131 | /* 增加设备号范围内拥有者的引用计数器 */ 132 | void *data; 133 | /* 用来指向 struct cdev */ 134 | } *probes[255]; 135 | struct mutex *lock; 136 | }; 137 | /* 138 | * 用来跟踪struct cdev,与struct char_device_struct跟踪设备号是一个原理. 139 | * inode->i_fop = &def_chr_fops, 140 | * file->f_op = &dev_chr_fops; 141 | * file->f_op->open(inode, file); 142 | */ 143 | 144 | /* 145 | * Called every time a character special file is opened 146 | */ 147 | static int chrdev_open(struct inode *inode, struct file *filp) 148 | { 149 | struct cdev *p; 150 | struct cdev *new = NULL; 151 | int ret = 0; 152 | 153 | spin_lock(&cdev_lock); 154 | p = inode->i_cdev; 155 | if (!p) { 156 | struct kobject *kobj; 157 | int idx; 158 | spin_unlock(&cdev_lock); 159 | kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); 160 | if (!kobj) 161 | return -ENXIO; 162 | new = container_of(kobj, struct cdev, kobj); 163 | spin_lock(&cdev_lock); 164 | /* Check i_cdev again in case somebody beat us to it while 165 | we dropped the lock. */ 166 | p = inode->i_cdev; 167 | if (!p) { 168 | inode->i_cdev = p = new; 169 | list_add(&inode->i_devices, &p->list); 170 | new = NULL; 171 | } else if (!cdev_get(p)) 172 | ret = -ENXIO; 173 | } else if (!cdev_get(p)) 174 | ret = -ENXIO; 175 | spin_unlock(&cdev_lock); 176 | cdev_put(new); 177 | if (ret) 178 | return ret; 179 | 180 | ret = -ENXIO; 181 | filp->f_op = fops_get(p->ops); 182 | if (!filp->f_op) 183 | goto out_cdev_put; 184 | 185 | if (filp->f_op->open) { 186 | ret = filp->f_op->open(inode, filp); 187 | if (ret) 188 | goto out_cdev_put; 189 | } 190 | 191 | return 0; 192 | 193 | out_cdev_put: 194 | cdev_put(p); 195 | return ret; 196 | } 197 | 198 | 199 | /* 200 | * 主要的作用是通过cdev_map找到对应的struct cdev,然后把cdev中的ops 201 | * 赋值给打开文件的f_op,然后调用cdev中的open,打完收工。 202 | */ 203 | -------------------------------------------------------------------------------- /stron/深入linux设备驱动程序内核机制.md: -------------------------------------------------------------------------------- 1 | ## 第一章 内核模块 2 | 3 | #### 目的 4 | 5 | - 模块的加载过程 6 | - 模块如何引入用内核或者其他模块中的函数与变量 7 | - 模块本身导出的函数与变量如何被别的内核模块使用 8 | - 模块的参数传递机制 9 | - 模块之间的依赖关系 10 | - 模块中的版本控制机制 11 | 12 | #### 1.1内核模块文件格式 13 | 14 | 内核模块是一种普通的可重定位目标文件。ELF(Executable and Linkable Format)格式 15 | 16 | ![1557643423066](1557643423066.png) 17 | 18 | #### 1.2 EXPORT_SYMBOL的内核实现 19 | 20 | ​ 对于静态编译链接而成的内核映像而言,所有的符号引用都将在静态链接阶段完成。 21 | 22 | ​ 作为独立编译链接的内核模块,要解决这种静态链接无法完成的符号引用问题(在模块elf文件中,这种引用称为“未解决的引用”) 23 | 24 | ​ 处理"未解决引用"问题的本质是在模块加载期间找到当前“未解决的引用”符号在内存中的实际目标地址。 25 | 26 | ​ 内核和内核模块通过符号表的形式向外部世界导出符号的相关信息。方式:EXPORT_SYMBOL,经三部分达成:EXPORT_SYMBOL宏定义部分,链接脚本连接器部分和使用到处符号部分。 27 | 28 | ​ 在链接脚本告诉链接器把目标文件中名为`__ksymtab` 和`__ksymtab_strings`的section放置在最终内核(或内核模块)映像文件的名为 `__ksymtab`和`__ksymtab_strings`的section中, 然后用EXPORT_SYMBOL 来导处符号,实际上是要通过 struct kernel_symbol的一个对象告诉外部世界关于这个符号的符号名称(`__ksymtab_strings`中)和地址(`__ksymtab`中),这个地址会在模块加载过程中由模块加载器负责修改该成员以反映出符号在内存中最终的目标地址,这个过程也就是"重定位"过程. 29 | 30 | #### 1.3 模块的加载过程 31 | 32 | ##### 1.3.2 struct module 33 | 34 | load_module返回值时struct module。是内核用来管理系统中加载模块时使用的,一个struct module对象代表着现实中一个内核模块在linux系统中的抽象。 35 | 36 | 重要成员: 37 | 38 | ```c 39 | struct module{ 40 | ... 41 | enum module_state; /*记录模块加载过程中不同阶段的状态*/ 42 | struct list_head list;/*用来将模块链接到系统维护的内核模块链表中*/ 43 | char name[MODULE_NAME_LEN];/*模块名称*/ 44 | const struct kernel_symbol *syms;/*内核模块导出的符号所在起始地址*/ 45 | const unsigned long *crcs;/*内核模块导出符号的校验码所在起始地址*/ 46 | struct kernel_param *kp;/*内核模块参数所在的起始地址*/ 47 | ini (*init)(void);/*指向内核模块初始化函数的指针,在内核模块源码中由module_init宏指定*/ 48 | struct list_head source_list; 49 | struct list_head target_list; /*用来再内核模块间建立依赖关系*/ 50 | ... 51 | }; 52 | ``` 53 | 54 | ##### 1.3.3 load_module 55 | 56 | - 模块ELF静态的内存视图 57 | 58 | ![1557656859923](1557656859923.png) 59 | 60 | - 字符串表(string table) 61 | 62 | 在驱动模块所在ELF文件中,有两个字符串表section,一个用来保存各section名称的字符串,另一个用来保存符号表中每个符号名称的字符串。 63 | 64 | section名称字符串表的基地址:char *secstrings=(char *)hdr+entry[hdr->e_shstrndx]​.sh_offset. 其中e_shstrndx是段名称字符串表在段首部表中的索引. 想获得某一section的名称,通过索引i可得地址secstrings+entry[i].sh_name. 65 | 66 | 符号名称字符串表的基地址:char *strtab=(char *)hdr+entry[entry[i].sh_link].sh_offset. 即首先遍历Section header table中所有的entry,找一个entry[i].sh_type=SHT_SYMTAB的entry,SHT_SYMTAB表明这个entry所对应的section是一符号表。这种情况下,entry[i].sh_link是符号名称字符串表section在Section header table中的索引值。 67 | 68 | - HDR视图的第一次改写 69 | 70 | 获取到section名称字符串表基地址secstrings和符号名称字符串表基地址strtab后,函数开始遍历part3的所有entry,将sh_addr改为entry[i].sh_addr= (size_t)hdr+entry[i].offset。 71 | 72 | - find_sec函数 73 | 74 | 寻找某一section在Section header table中的索引值 75 | 76 | static unsigned int find_sec(Elf_Ehdr *hdr, Elf_Shdr *sechdrs, const char *secstrings, const char *name); 77 | 78 | 遍历Section header table中的entry,通过secstrings找到对应的section名称和name比较,相等后返回对应索引值。 79 | 80 | HDR视图第一次改后,然后查找此三个section(".gun,linkonce.this_module","__versions",".modinfo")对应的索引值赋给modindex,versindex,infoindex 81 | 82 | - struct module类型变量 mod初始化 83 | 84 | 模块的构造工具为我们安插了一个".gun,linkonce.this_module" section,并初始化了其中一些成员如.init(即init_module别名(initfn)(即module_init(initfn)中的initfn),和.exit。在模块加载过程中load_module函数利用这个section中的数据来初始mod变量。次section在内存中的地址即为:mod=(void *)sechdrs[modindex].sh_addr。 85 | 86 | - HDR视图的第二次改写 87 | 88 | 哪些section需要移动?移动到什么位置?layout_sections负责 89 | 90 | 有标记SHF_ALLOC的section(分两大类CORE和INIT) 91 | 92 | - CORE section 93 | 94 | 遍历Section header table,section name不是".init"开始的section划为CORE section。改对应entry的sh_sh_entsize,用来记录当前section在CORE section中的偏移量。 95 | 96 | entry[i].sh_entsize=mod->core_size; 97 | 98 | > sh_entsize: 99 | > 有些节的内容是一张表,其中每一个表项的大小是固定的,比如符号表;对于这种表来说,该字段指明这种表中的每一个表项的大小;如果该字段的值为0,则表示该节的内容不是这种表格; 100 | 101 | mod->core_size+=entry[i].sh_size;//记录当前正在操作的section为止,CORE section的空间大小 102 | 103 | code section用 module结构中的core_text_size来记录 104 | 105 | - INIT section 106 | 107 | section name 必须以".init"开始,内核用struct module结构中的init_size来记录当前INIT section空间大小 108 | 109 | mod->init_size+=entry[i].sh_size; 110 | 111 | code section 用module的init_text_size来记录 112 | 113 | 114 | 115 | CONFIG_KALLSYMS启用后会在内核模块中保留所有符号,在ELF符号表section中,由于没有SHF_ALLOC标志,layout_sections不会移动符号表section,所以用layout_symtab将其移动到CORE section中。 116 | 117 | 之后内核用vmalloc为CORE section和INIT section分配对应内存空间,基地址分别在mod->module_core和mod->module_init中。接着就搬移CORE和INIT section到最终位置。之后更新其中各section对应table中entry的sh_addr. 118 | 119 | ".gun.linkonce.this_module" section带有SHF_ALLOC标志,也移到CORE section,所以要更新 120 | 121 | mod=(void *)entry[modindex].sh_addr; 122 | 123 | 为什么要移动? 124 | 125 | 模块加载过程结束时,系统会释放掉HDR视图所在的内存区域,模块初始化工作完成后,INIT section所在内存区域也会被释放,所以最终只会留下CORE section中的内容到整个模块存活期。 126 | 127 | ![1557762840867](1557762840867.png) 128 | 129 | - 模块导出符号 130 | 131 | 内核模块会把导出符号放到_ksymtab、_ksymtab_gpl和_ksymtab_gpl_future section中。 132 | 133 | 这些section都带SHF_ALLOC标志,在模块加载过程中会被搬移到CORE section区域去, 134 | 135 | 在搬移CORE section和INIT section后,内核通过HDR视图中Section header table查找获取,keymtab,ksymtab_gpl和ksymtab_gpl_future section在CORE section中的地址,分别记录到mod->syms,mod->gpl_syms和mod->gpl_future_syms中,内核即刻通过这些变量得到模块导出的符号的所有信息。 136 | ![1558280607511](1558280607511.png) 137 | 138 | - find_symbol函数 139 | 140 | 用来查找一个符号 141 | 142 | struct symsearch用来对应要查找的每一个符号表section. enum型成员licence,NOT_GPL_ONLY由EXPORT_SYMBOL导出,GPL_ONLY由EXPORT_SYMBOL_GPL导出,WILL_BE_GPL_ONLY由EXPORT_SYMBOL_GPL_FUTURE导出。 143 | 144 | struct find_symbol_arg用做查找符号的标识参数,成员kernel_symbol是一个用以表示内核符号构成的数据结构。 145 | 146 | find_symbol函数首先构造被查找模块的标志参数fsa,然后通过each_symbol来查找符号。分两部分:第一部分:在内核导出的符号表中查找对应的符号,未找到,再进行第二部分:在系统中已加载模块(在全局变量modules的链表上)的导出符号中查找对应的符号。 147 | 148 | 第一部分:在内核导出的符号表中查。查找时先构造 struct symsearch类型的数组arr。然后调用each_symbol_in_section查询内核的导出符号表,即遍历arr数组定义的各section,然后在每个section中依次通过find_symbol_in_section查找每个内核符号结构(kernel_symbol) 149 | 150 | find_symbol_in_section函数首先比较kernel_symbol结构体中的name与fsa中的name(正在查找的符号名,即要加载的内核模块中出现的"未解决的引用"的符号).不匹配返回false。fsa->gplok=true表明这是个GPL module,否则就是non-GPL module,对于后者,他不能使用内核导出的属于GPL_ONLY的那些符号,会直接返回查找失败。 151 | 152 | 第二部分:在已加载模块导出的符号表中查。加载一个内核模块时需完成两点,第一,模块成功加载进系统之后,需将该模块的struc module类型变量mod加入到modules全局链表中去,记录所有已加载模块。第二,模块导出的符号信息记录在mod相关成员变量中。 153 | 154 | each_symbol用来再已加载模块导出的符号中查找指定符号。与在内核导出的符号表中查找符号的唯一区别在于 构造的arr数组:在全局链表modules中遍历所有已加载的内核模块,对其中每一模块用其导出符号表构造一个新的arr数组,然后查找特点符号。 155 | 156 | - 对"未解决的引用"符号的处理 157 | 158 | 所谓"未解决的引用"符号,就是模块的编译工具链在对模块进行链接生成最终的.ko文件时,对于模块中调用的一些函数,如printk,无法在该模块的所有目标文件中找到这个函数的具体指令码。所以就将这个符号标记为"未解决的引用"。在内核模块被加载时,就是通过在内核或者其他模块导出的符号中找到这个"为解决就的引用"符号,从而找到该符号在内存中的地址,最终形成正确的函数调用。 159 | 160 | simplify_symbols函数实现此功能,用来为当前正在加载的模块中所有"未解决的引用"符号产生正确的目标地址。函数首先通过一个for循环遍历符号表中的所有符号,对于每个符号都会更具符号的st_shndx值分情况处理,通常情况下,该值表示符号所在section的索引值,称一般符号,它的st_value在ELF文件中的值时从section起始处算起的一个偏移。先得到符号所在section的最终内存地址,再加上它在section中的偏移量,就得到了符号最终内存地址。有些符号的st_shndx有特殊含义,SHN_ABS表明符号具有绝对地址,无需处理,SHN_UNDEF表明该符号是"undefined symbol即"未解决的引用",此时会调用resolve_symbol函数继而调用find_symbol函数去查找该符号,找到后把它在内存中的实际地址赋值给st_value。 161 | 162 | - 重定位 163 | 重定位主要用来解决静态链接时的符号引用与动态加载时的实际符号地址不一致的问题。 164 | 165 | 如果模块有EXPORT_SYMBOL导出的符号,那么模块的编译工具链会为这个模块的ELF文件生成一个独立的特殊section: ".rel__ksymtab",它专门用于对"__ksymtab" section的重定位,称为relocation section。 166 | 167 | 重定位的section,在Section header table中的entry的sh_type值为SHT_REL或SHT_RELA. 168 | 遍历Section header table的entry时发现sh_type=SHT_REL的section,就调用apply_relocate执行重定位,根据重定位元素的r_offset以及entry中的sh_info得到需要修改的导出符号struct kernel_symbol中的value所在的内存地址。然后根据r_info获得需要重定位的符号在符号表中的偏移量。也就是说 根据导出符号所在的section的relocation section,结合导出符号表section,修改导出符号的地址为在内存中最终的地址值,如此,内核模块导出符号的地址在系统执行重定位之后被更新为正确的值。 169 | 170 | - 模块参数 171 | 172 | 内核模块在源码中必须用module_param宏声明模块可以接受的参数。 173 | 174 | ```c 175 | ... 176 | int dolpin; 177 | int bobcat; 178 | module_param(dolpin,int,0); 179 | module_param(bobcat,int,0); 180 | ... 181 | ``` 182 | 183 | 内核模块加载器对模块参数的初始化过程在模块初始化函数的调用之前。 184 | 185 | 存在模块的"__param" section中,对应一个人relocation section".rel__param",用来重定位。 186 | 187 | ![1558777428404](1558778085928.png) 188 | 189 | 在把命令行参数值复制到模块的参数这个过程中,module_param宏定义的"__param"section起了桥梁作用,通过"__param" section内核可以找到模块中定义的参数所在的内存地址,继而可以用命令行的值改写之。 190 | 191 | - 模块间的依赖关系 192 | 193 | 当一个模块引用到另一个模块中导出的符号时,这两个模块间就建立了依赖关系,只存在于模块之间,模块与内核间不构成依赖关系。 194 | 195 | 依赖关系的建立发生在当前模块对象mod被加载时,模块加载函数调用resolve_symbol函数来解决"未解决的引用"符号,如果成功的找到了指定的符号,就将那个模块记录在struct module *owner中,再调用ref_module(mod,owner)在mod和owner间建立依赖关系。 196 | 197 | ![1558779843508](1558779843508.png) 198 | 199 | - 模块的版本控制 200 | 201 | 编译内核模块时,启用了CONFIG_MODEVERSIONS,则模块最终的ELF文件中会生成一个"__versions" section,会将模块中所有“未解决的引用”符号名和对应的校验码放入其间。 202 | 203 | 接口验证时机是在resolve_symbol函数里调用check_version函数完成,遍历“__versions” section对每个struct modversion_info元素和找到的符号名symname进行批评,成功,再进行校验码比较,相同则表明接口一致 204 | 205 | - 模块的license 206 | 207 | 在模块源码中以MODULE_LICENSE宏引出 208 | 209 | 内核模块加载过程中,会调用license_is_gpl_compatible来确定license是否与GPL兼容。 210 | 211 | ##### 1.3.4 sys_init_module (第二部分) 212 | 213 | - 调用模块的初始化函数 214 | 215 | 内核模块可以不提供模块初始化函数,如果提供了,将在do_one_initcall函数内部被调用。 216 | 217 | - 释放INIT section所占用的空间 218 | 219 | 模块别加载成功,HDR视图和INIT section所占用的内存区域将不再会被用到,释放它,HDR视图释放实际上发生在load module函数的最后 220 | 221 | - 呼叫模块通知链 222 | 223 | 通知链(notifier call chain)module_notiy_list,通过通知链模块或者其他的内核组件可以向其感兴趣的一些内核时间进行注册,当该事件发生时,这些模块或组件注册的回调函数将会被调用。 224 | 225 | #### 模块的卸载 226 | 227 | ​ rmmod通过系统调用sys_delete_module来完成卸载工作 228 | 229 | - find_module函数 230 | 231 | sys_delete_module函数首先将来自用户工具欲卸载模块名用strncpy_from_user复制到内核空间,然后调用find_module在内核维护的模块链表modules中利用name来找要卸载的模块。 232 | 233 | - 检查模块依赖关系 234 | 235 | 检查是否有别的模块依赖于当前要卸载的模块,检查模块的source_list链表是否为空,判断依赖关系 236 | 237 | - find_module函数 238 | 239 | sys_delete_module函数最后会调用free_module函数做清理工作,将卸载模块从modules链表中移除,将模块占用的CORE section空间释放,释放冲用户空间接受参数所占用的空间。 240 | 241 | 242 | 243 | 244 | 245 | ## 第二章 字符设备驱动程序 246 | 247 | - linux系统设备驱动分三大类:字符设备、块设备、网路设备 248 | 249 | - 字符设备在I/O传输过程中以字符为单位,传输速率慢(无缓存机制),如鼠标键盘打印机。 250 | 251 | ![1559357813413](1559357813413.png) 252 | 253 | #### 目标 254 | 1. 构成字符设备驱动程序的内核设施的幕后机制。 255 | 2. 应用程序如何使用字符设备驱动程序提供的服务。 256 | 257 | #### 2.1 应用程序与设备驱动程序互动实例 258 | 259 | 1. 编写编译字符设备驱动模块 260 | 1. cdev_init(&chr_dev,&chr_ops);//初始化字符设备对象,和file_operations挂钩 261 | 2. alloc_chrdev_region(&ndev,0,1,"chr_dev");//分配设备号 262 | 3. cdev_add(&chr_dev,ndev,1);//将字符设备对象chr_dev注册进系统。 263 | 2. insmod加载内核模块到系统 264 | 3. mknod创建设备文件节点 265 | 4. 应用程序通过读写设备文件节点来调用设备驱动程序提供的服务。 266 | 267 | #### 2.2 struct file_operations 268 | 269 | - struct file_operations的成员变量几乎全是函数指针。 270 | 271 | - owner成员,表示当前file_operations对象所属的内核模块,基本都赋值THIS_MODULE. 272 | 273 | #define THIS_MODULE (&__this_module),__this_nodule是内核模块的编译工具链为当前模块产生的struct module类型对象,所以THIS_MODULE实际上是当前内核模块对象的指针。owner可以避免file_operations种的函数正在被调用时,其所属的模块从系统中卸载掉。 274 | 275 | #### 2.3 字符设备的内核抽象 276 | 277 | - 字符设备struct cdev 278 | 279 | ```c 280 | 281 | struct cdev { 282 | struct kobject kobj;//内嵌的内核对象 283 | struct module *owner;//字符设备驱动程序所在的内核模块对象指针 284 | const struct file_operations *ops;//应用程序通过文件系统接口呼叫到字符设备驱动程序种实现的文件操作类函数的过程种,ops指针起着桥梁纽带作用。 285 | struct list_head list;//用来将系统中的字符设备形成链表。 286 | dev_t dev;//字符设备的设备号,由主设备号和次设备号构成。 287 | unsigned int count;//隶属于同一主设备号的次设备号的个数,即当前设备驱动程序控制的实际同类设备的数量 288 | }; 289 | ``` 290 | 291 | 两种方式产生struct cdev对象 292 | 293 | 1. 静态定义 294 | 295 | static struct cdev chr_dev; 296 | 297 | 2. 动态分配 298 | 299 | static struct cdev *p = kmalloc(sizeif(struct cdev),GFP_KERNEL); 300 | 301 | - cdev_alloc 302 | 专门用于动态分配struct cdev对象。分配对象,并且进行初始化。但通常cdev会作为内嵌成员出现在实际的设备数据结构中,如: 303 | 304 | ```c 305 | struct my_keypad_dev{ 306 | int a; 307 | int b; 308 | struct cdev cdev; 309 | }; 310 | ``` 311 | 312 | 此时动态分配就不能用cdev_alloc.而是: 313 | 314 | ​ static struct my_keypad_dev *p = kzalloc(sizeof(struct real_char_dev),GFP_KERNEL); 315 | 316 | - cdev_init 317 | 318 | 初始化一个cdev对象 319 | 320 | ```c 321 | 322 | void cdev_init(struct cdev *cdev,const struct file_operations *fops) 323 | { 324 | memset(cdev,0,sizeof *cdev); 325 | INIT_LIST_HEAD(&cdev->list); 326 | kobject_init(&cdev->kobj,&ktype_cdev_default); 327 | cdev->ops=fops; 328 | } 329 | ``` 330 | 331 | #### 2.4 设备号的构成与分配 332 | 333 | ##### 2.4.1 设备号的构成 334 | 335 | ​ Linux用dev_t类型变量来标识一个设备号,是一个32位的无符号整数。低20为表示次设备号,高12位表示主设备号,但不排除后续会改变,因此建议使用MAJOR、MINOR、MKDEV来操作设备号,只修改MINORBITS即可。 336 | 337 | ```c 338 | 339 | #define MAJOR(dev) ((unsigned int)((dev)>>MINORBITS)) 340 | #define MINOR(dev) ((unsigned int)((dev) & MINORMASK)) 341 | #define MKDEV(ma,mi) (((ma)< 362 | static struct char_device_struct{ 363 | struct char_device_stuct *next, 364 | unsigned int major; 365 | unsigned int baseminor; 366 | int minorct; 367 | char namep[64]; 368 | struct cdev *cdev; 369 | } *chardevs[CHRDEV_MAJOR_HASH_SIZE]; 370 | ``` 371 | 372 | ​ 函数首先分配一个struct char_device_struct类型对象cd,然后初始化,之后开始以哈希表的形式搜索chrdevs数组,获取散列关键值i=major%255,此后函数对chrdevs[i]元素管理的链表进行扫描,若chrdevs[i]上已经有了链表节点,表面之前有别的设备驱动程序使用的主设备号散列到了chrdevs[i]上。当前正在操作的设备号不与已经在使用的设备号发生冲突就可以将cd加入到chrdevs[i]领衔的链表中成为一个新的节点。 373 | 374 | ​ 只有在主设备相同的情况下,次设备号范围和已经加入的设备号有重叠,才会冲突。 375 | 376 | ![1559379796680](1559379796680.png) 377 | 378 | - alloc_chrdev_region函数 379 | 380 | int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name); 381 | 382 | 该函数由系统协助分配设备号,范围1~254 383 | 384 | 核心调用也是__register_chrdev_region,第一个参数为0,会从chrdevs数组最后一项依次向前扫描,若某项chrdevs[i]为NULL就把该项对应索引值i作为分配的主设备号返回给驱动程序,同时生成一个char_device_struct节点,并加入到chrdevs[i]对应的哈希链表中。分配成功后通过*dev=MKDEV(cd->major,cd->baseminor)返回给函数调用者。 385 | 386 | - 释放设备号 void unregister_chrdev_region(dev_t from,unsigned count); 387 | 388 | #### 2.5 字符设备注册 389 | 390 | 把一个字符设备加入到系统中使用cdev_add函数,核心功能通过kobj_map函数实现,其通过全局变量cdev_map来把设备(*p)加入到其中的哈希表中 391 | 392 | ```c 393 | /* p为要加入系统的字符设备对象的指针 394 | * dev为该设备的设备号 395 | * count表示从次设备号开始连续的设备数量 396 | */ 397 | int cdev_add(struct cdev *p,dev_t dev,unsigned count) 398 | { 399 | p-dev=dev; 400 | p->count=count; 401 | return kobj_map(cdev_map,dev,count,NULL,exaxt_match,exact_lock,p); 402 | } 403 | ``` 404 | 405 | ```c 406 | 407 | static struct kobj_map *cdev_map; //系统启动期间由chrdev_init函数初始化 408 | 409 | 410 | struct kobj_map{ 411 | struct probe{ 412 | struct probe *next; 413 | dev_t dev; 414 | unsigned long range; 415 | struct module *owner; 416 | kobj_probe_t *get; 417 | int (*lock)(dev_t,void *); 418 | void *data; 419 | }*probes[255]; 420 | struct mutex *lock; 421 | } 422 | ``` 423 | kobj_map函数中哈希表的实现原理与前面注册分配设备号种的几乎完全一样, 424 | 设备驱动程序通过cdev_add把它所管理的设备对象指针嵌入到一个类型为struct probe的节点之中,然后再把该节点加入到cdev_map所实现的哈希链表中。 425 | ![1559382558201](1559382558201.png) 426 | 427 | 模块卸载函数应负责调用cdev_del(struct cdev *p)函数来将所管理的设备对象从系统中移除。 428 | 429 | #### 2.6 设备文件节点的生成 430 | 431 | 设备文件是沟通用户空间程序和内核空间驱动程序的桥梁。 432 | 433 | 当前的Linux内核在挂载完根文件系统之后,会在这个根文件系统的/dev目录上重新挂载一个新的文件系统devtmpfs,后者是个基于系统RAM的文件系统实现。 434 | 435 | 静态创建 mknod /dev/demodev c 2 0 436 | 437 | ​ mknod最终调用mknod函数,参数为设备文件名(“/dev/demodev”)和设备号(mkdev(30,0)),通过系统调用sys_mknod进入内核空间。 438 | 439 | ![1559384672396](1559384672396.png) 440 | 441 | #### 2.7 字符设备文件的打开操作 442 | 443 | 用户空间open函数原型 int open(const char *filename,int flags,mode_t mode); 返回int型的文件描述符fd 444 | 445 | 内核空间open函数原型int (*open)(struct inode *,struct file *); 驱动程序中的demodev_read,demodev_write,demodev_ioctl等函数第一个参数都是struct file *filp. 446 | 447 | 内核需要在打开设备文件时为fd和filp建立某种联系,其次是为filp与驱动程序中的fops建立关联。 448 | 449 | ![1559439057012](1559439057012.png) 450 | 451 | ```c 452 | 453 | struct file{ 454 | ... 455 | const struct file_operations *f_op; 456 | atomic_long_t f_count;//对struct file对象的使用计数,close文件时,只有f_count为0才真正执行关闭操作 457 | unsigned int f_flags;//记录当前文件被open时所指定的打开模式 458 | void *private_data;//记录设备驱动程序自身定义的数据,在某个特定文件视图的基础上共享数据? 459 | ... 460 | } 461 | ``` 462 | 463 | 进程为文件操作维护一个文件描述符表(current->files->fdt),对设备文件的打开操作返回的文件描述符fd,作为进程维护的文件描述符表(指向struct file *类型数组)的索引值,将之前新分配的struct file空间地址赋值给它: 464 | 465 | current->files->fdt->pfd[fd]=filp; 466 | 467 | ![1559437888743](1559437888743.png) 468 | 469 | ![1559439170767](1559439170767.png) 470 | 471 | 用户空间close函数 int close(unsigned int fd);从fd得到filp,然后调用int filp_close(struct file *filp,fl_owner_t id) 472 | 473 | ​ flush函数,若设备驱动定义就调用,函数为了确保在把文件关闭前缓存在系统中的数据被真正的写回到硬件,字符设备很少有定义,多用在块设备上,比如SCSI硬盘和系统进行大数据传输,为此内核设计了高速缓存机制,在文件关闭前将高速缓存数据写回到磁盘中 474 | 475 | ​ relase函数,只有在当前文件的所有副本都关闭后,relase函数才会被调用。 476 | 477 | --------------------------------------------------------------------------------