Linux的内核模块机制允许开发者动态的向内核添加功能,我们常见的文件系统、驱动程序等都可以通过模块的方式添加到内核而无需对内核重新编译,这在很大程度上减少了操作的复杂度。模块机制使内核预编译时不必包含很多无关功能,把内核做到最精简,后期可以根据需要进行添加。

而针对驱动程序,因为涉及到具体的硬件,很难使通用的,且其中可能包含了各个厂商的私密接口,厂商几乎不会允许开发者把源代码公开,这就和linux的许可相悖,模块机制很好的解决了这个冲突,允许驱动程序后期进行添加而不合并到内核。OK,下面结合源代码讨论下模块机制的实现。

类似于普通的可执行文件,模块经过编译后得到.ko文件,其本身也是可重定位目标文件,类似于gcc -c 得到的.o目标文件。

关于可重定位目标文件的定义:可重定位目标文件(多位高手回答版,未综合)。

既然是重定位文件,在把模块加载到内核的时候就需要进行重定位,回想下用户可执行文件的重定位,一般如果一个程序的可执行文件总能加载到自己的理想位置,所以对于用户可执行文件,一般不怎么需要重定位;而对于动态库文件就不同了,库文件格式是一致的,但是可能需要加载多个库文件,那么有些库文件必然无法加载到自己的理想位置,就需要进行重定位。而内核模块由于和内核共享同一个内核地址空间,更不能保证自己的理想地址不被占用,所以一般情况内核模块也需要进行重定位。在加载到内核时,还有一个重要的工作即使解决模块之间的依赖,模块A中引用了其他模块的函数,那么在加载到内核之前其实模块A并不知道所引用的函数地址,因此只能做一个标记,在加载到内核的时候在根据符号表解决引用问题!这些都是在加载内核的核心系统调用sys_init_module完成。

Linux内核模块

Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合。之所以提供模块机制,是因为Linux本身是一个单内核。单内核由于所有内容都集成在一起,效率很高,但可扩展性和可维护性相对较差,模块机制可弥补这一缺陷。

Linux模块可以通过静态或动态的方法加载到内核空间,静态加载是指在内核启动过程中加载;动态加载是指在内核运行的过程中随时加载。一个模块被加载到内核中时,就成为内核代码的一部分。模块加载入系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号添加到内核符号表中,以便模块间的通信。

从代码的特征上来看,模块就是可以完成一个独立功能的一组函数的集合,但以特殊的方法来编译,从而使之可以在需要时随时安装,在不需要时随时卸载。它们扩展了操作系统内核功能却不需要重新编译内核、启动系统。

准确的说,模块就是一个已经编译但未经连接的可执行文件。

一个Linux 内核模块主要由以下几个部分组成:

  • 模块加载函数(必须):当通过insmod命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块相关初始化工作;
  • 模块卸载函数(必须):当通过rmmod命令卸载模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能;
  • 模块许可证声明(必须):模块许可证(LICENCE)声明描述内核模块的许可权限,如果不声明LICENCE,模块被加载时将收到内核被污染的警告。大多数情况下,内核模块应遵循GPL 兼容许可权。Linux2.6 内核模块最常见的是以MODULE_LICENSE(“Dual BSD/GPL”)语句声明模块采用BSD/GPL 双LICENSE;
  • 模块参数(可选):模块参数是模块被加载的时候可以被传递给他的值,它本身对应模块内部的全局变量;
  • 模块导出符号(可选):内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模块中的变量或函数;
  • 模块作者等信息声明(可选)。

一个内核模块至少包含两个函数,模块被加载时执行的初始化函数init_module()和模块被卸载时执行的析构函数delete_module()。前者为内核工作进行必要的硬件及软件的初始化工作;后者用来对内核的卸载做内存的释放等一些扫尾工作。在最新内核稳定版本2.6 中,两个函数可以起任意的名字,通过宏module_init()和module_exit()注册调用要编译内核模块,把代码嵌进内核空间,首先要获取内核源代码,且版本必需与当前正在运行的版本一致。

模块的内核描述

每一个内核模块在内核中都对应一个数据结构module,所有的模块通过一个链表维护。所以有些恶意模块企图通过从链表摘除结构来达到隐藏模块的目的。部分成员列举如下:

struct module
{enum module_state state;                                //状态/* Member of list of modules */struct list_head list;                                //所有的模块构成双链表,包头为全局变量modules/* Unique handle for this module */char name[MODULE_NAME_LEN];                        //模块名字,唯一,一般存储去掉.ko的部分/* Sysfs stuff. */struct module_kobject mkobj;struct module_attribute *modinfo_attrs;const char *version;const char *srcversion;struct kobject *holders_dir;/* Exported symbols *//**/const struct kernel_symbol *syms;                    //导出符号信息,指向一个kernel_symbol的数组,有num_syms个表项。const unsigned long *crcs;                        //同样有num_syms个表项,不过存储的是符号的校验和unsigned int num_syms;/* Kernel parameters. */struct kernel_param *kp;unsigned int num_kp;/* GPL-only exported symbols. */unsigned int num_gpl_syms;/                        /具体意义同上面符号,但是这里只适用于GPL兼容的模块const struct kernel_symbol *gpl_syms;const unsigned long *gpl_crcs;#ifdef CONFIG_UNUSED_SYMBOLS/* unused exported symbols. */const struct kernel_symbol *unused_syms;const unsigned long *unused_crcs;unsigned int num_unused_syms;/* GPL-only, unused exported symbols. */unsigned int num_unused_gpl_syms;const struct kernel_symbol *unused_gpl_syms;const unsigned long *unused_gpl_crcs;
#endif#ifdef CONFIG_MODULE_SIG/* Signature was verified. */bool sig_ok;
#endif/* symbols that will be GPL-only in the near future. */const struct kernel_symbol *gpl_future_syms;const unsigned long *gpl_future_crcs;unsigned int num_gpl_future_syms;/* Exception table */unsigned int num_exentries;struct exception_table_entry *extable;/* Startup function. */int (*init)(void);                            //模块初始化函数指针/* If this is non-NULL, vfree after init() returns */void *module_init;                            /如果该函数不为空,则init结束后就可以调用进行适当释放/* Here is the actual code + data, vfree'd on unload. */void *module_core;                            //核心数据和代码部分,在卸载的时候会调用/* Here are the sizes of the init and core sections */unsigned int init_size, core_size;            //对应于上面的init和core函数,决定各自占用的大小/* The size of the executable code in each section.  */unsigned int init_text_size, core_text_size;/* Size of RO sections of the module (text+rodata) */unsigned int init_ro_size, core_ro_size;......#ifdef CONFIG_MODULE_UNLOAD/*模块间的依赖关系记录*//* What modules depend on me? */struct list_head source_list;/* What modules do I depend on? */struct list_head target_list;/* Who is waiting for us to be unloaded */struct task_struct *waiter;                    //等待队列,记录那些进程等待模块被卸载/* Destruction function. */void (*exit)(void);                            //卸载退出函数,模块中定义的exit函数......
};

依赖关系

模块间的依赖关系通过两个节点source_list和target_list记录,前者记录那些模块依赖于本模块,后者记录本模块依赖于那些模块。节点通过module_use记录,module_use如下(定义在include/linux/module.h中):

struct module_use {struct list_head source_list;struct list_head target_list;struct module *source, *target;
};

每个module_use记录一个映射关系,注意这里把source和target放在一个一个结构里,因为一个关系需要在源模块和目标模块都做记录。如果模块A依赖于模块B,则生成一个module_use结构,其中source_list字段链入模块B的module结构的source_list链表,而source指针指向模块A的module结构。而target_list加入到模块A中的target_list链表,target指针指向模块B的模块结构,参考下面代码:

static int add_module_usage(struct module *a, struct module *b)
{struct module_use *use;pr_debug("Allocating new usage for %s.\n", a->name);use = kmalloc(sizeof(*use), GFP_ATOMIC);if (!use) {printk(KERN_WARNING "%s: out of memory loading\n", a->name);return -ENOMEM;}use->source = a;use->target = b;list_add(&use->source_list, &b->source_list);list_add(&use->target_list, &a->target_list);return 0;
}

符号信息

内核模块几乎不会作为完全独立的存在,均需要引用其他模块的函数,而这一机制就是由符号机制保证的。参考前面的module数据结构,在module结构体中:

const struct kernel_symbol *syms;                //导出符号信息,指向一个kernel_symbol的数组,有num_syms个表项。
const unsigned long *crcs;                        //同样有num_syms个表项,不过存储的是符号的校验和
unsigned int num_syms;

syms指针指向一个符号数组,也可以称之为符号表,不过是局部的符号表。看下kernel_symbol结构:

struct kernel_symbol
{unsigned long value;const char *name;
};

结构很简单,value记录符号地址,而name自然就是符号名字了。

参考文章:Linux下的内核模块机制。

内核描述

每当内核需要使用这个模块提供的功能,就会到链表modules中寻找这个模块,并调用模块使用export修饰的功能函数。

module中,成员state为模块当前的状态。它是一个枚举类型的变量,可取的值为MODULE_STATE_LIVE、MODULE_STATE_COMING、MODULE_STATE_GOING,分为当前正常使用中(存活状态)、模块当前正在被加载和模块当前正在被卸载三种状态。

  • 当模块向内核加载时,insmod调用内核的模块加载函数,该函数在完成模块的部分创建工作后,将模块的状态置为MODULE_STATE_COMING。接着内核将调用内核模块初始化函数,并在完成所有的初始化工作之后(包括将模块加入模块注册表,调用模块本身的初始化函数),将模块状态设置为MODULE_STATE_LIVE。
  • 当使用rmmod命令卸载模块时,内核将调用系统调用delete_module,并将模块的状态置为MODULE_STATE_GOING。

Linux模块的实现机制及其管理

由前面的内容可知,从代码的特征上来看,模块是可完成一项独立功能的一组函数的集合;从使用特征上来看,它在需要时可以随时被安装,而在不需要时又可以随时被卸载。

从用户的角度上看,模块时内核的一个外挂的配件:需要时可将其挂接到内核上,以完成用户所要求的任务;不需要时即可将其删除。它给用户提供了扩充内核的手段;从内核的角度上看,模块由在运行时可连接并删除的、包括了至少2个函数的代码块。这个代码块一旦被连接到内核,它就可以是内核的一部分。总之,模块是一个为内核或其他内核模块提供使用功能的代码块。

在某种意义上来说,从可安装模块的角度来看,内核也是一个模块,只不过是大一些。所以说,模块就是一个已编译但未连接的可执行文件。既然把模块安装到了内核这个模块上并向内核提供服务,那么这些可模块就必须有与内核交互的手段。实现两个模块之间交互的最简单的手段就是实现双方的变量和函数的共享。

为了使被模块知道内核的哪些变量和函数是模块可以使用的,linux内核以“可移出”符号的形式提供了可供其他模块共享的变量和函数名称。这里所谓的“可移出”,是指外部可以引用,即是暴露在内核外面的符号。而模块在需要引用内核的一个“可移出”符号时,要把该符号用extern声明为外部引用。

为了使加载的各模块之间也可通过可移出符号进行交互,模块也可声明自己的移出符号,以供其他模块使用。所以这些移出符号可看做是内核与模块以及模块之间的信号通路,模块之间就是通过这些内核或模块的可移出符号实现交互的

但需要注意的是,模块可以引用内核及其他模块的可移出符号,而内核不能引用模块的可移出符号。也就是说,内核与模块之间的互连是一种“单向”的互连。 模块与内核之间连接示意图如图所示:

一个简单的内核模块程序

例程要求:编写一个模块,该模块含有一个初始化函数init_hello_module()和析构函数exit_hello_module()。在两个函数中,分别打印字符串。(注意:内核中打印语句时printk(),而不是printf()!)

hello.c模块代码

#include <linux/module.h>
#include <linux/kernel.h>static int __init init_hello_module(void)                //__init进行注明
{printk("***************Start***************\n");printk("Hello World! Start of hello world module!\n");return 0;
}static void __exit exit_hello_module(void)              //__exit进行注明
{printk("***************End***************\n");printk("Hello World! End of hello world module!\n");
}MODULE_LICENSE("GPL");                             //模块许可证声明(必须要有)
module_init(init_hello_module);                    //module_init()宏,用于初始化
module_exit(exit_hello_module);                    //module_exit()宏,用于析构

Makefile文件

#Makefile**********************************************************
obj-m := hello.oKERNELBUILD:=/lib/modules/ $ (shell uname -r) /build 

在Makefile中,在obj-m := hello.o这句中,.o的文件名要与编译的.c文件名一致。

模块加载和卸载的情况

  • .在Makefile及helloworld.c所在目录下,直接make,成功后查看当前目录下有无helloworld.ko文件产生,有则内核模块生成成功;
  • 使用insmod命令,把此内核模块程序加载到内核中运行。结合lsmod及管道命令,查看内核模块程序在内核中是否正确运行;
  • 查看此内核模块程序打印的信息,另开一个终端,输入tail -n /var/log/messages;
  • 使用rmmod命令把之前加载的内核模块卸载掉,然后再次执行第2步,即可看到此内核模块程序打印的信息。

【Linux】Linux基础知识(Linux模块)相关推荐

  1. linux文件基础知识,linux文件系统基础知识

    利用直接指针.单级间接指针.二级间接指针.三级间接指针可保存的最大文件大小为: 1024*12+1024*256+1024*256*256+1024*256*256*256=16843020 KB,约 ...

  2. Linux驱动 简单的Linux驱动基础知识

    Linux驱动 简单的Linux驱动基础知识 一.简述         记--Linux驱动学习笔记. Linux驱动程序初始化硬件设备,并提供硬件控制接口给更上一层的应用调用. 例如使用QT应用程序 ...

  3. [基础知识]Linux新手系列之三

    2019独角兽企业重金招聘Python工程师标准>>> [基础知识]Linux新手系列之三 给Linux新手 [系列之三] Linux相关资料由兄弟连分享 OK,从哪里得到Linux ...

  4. 权限认证php,2016年Linux认证基础知识:php做权限管理

    2016年Linux认证基础知识:php做权限管理 在学习Linux认证过程中,每个人会遇到每个人不同的问题,或小或大,那么你知道在Linux下,php怎么做权限管理?下面跟yjbys小编来看看最新的 ...

  5. Linux系统基础知识

    Linux系统基础知识 1.在Linux系统中,以文件方式访问设备.   2. Linux内核引导时,从文件 /etc/fstab中读取要加载的文件系统. 3. Linux文件系统中每个文件用 ino ...

  6. Kali Linux渗透基础知识整理(四):维持访问

    Kali Linux渗透基础知识整理系列文章回顾 维持访问 在获得了目标系统的访问权之后,攻击者需要进一步维持这一访问权限.使用木马程序.后门程序和rootkit来达到这一目的.维持访问是一种艺术形式 ...

  7. Linux操作系统基础知识学习

    Q1.什么是GNU?Linux与GNU有什么关系? A: 1)GNU是GNU is Not Unix的递归缩写,是自由软件基金会(Free Software Foundation,FSF)的一个项目, ...

  8. Linux常用基础知识必备三之常用指令及操作

    Linux常用基础知识必备三之常用指令及操作 1.vi和vim vi如何使用 vi几种模式下的操作指令 命令模式进入编辑模式 命令模式下的快捷键 底行模式(指按了esc键之后输入按键:后出现) 字符串 ...

  9. linux系统下io的过程,Linux系统基础知识:IO调度

    Linux系统基础知识:IO调度 IO调度发生在Linux内核的IO调度层.这个层次是针对Linux的整体IO层次体系来说的.从read()或者write()系统调用的角度来说,Linux整体IO体系 ...

  10. Linux入门基础知识

    注:内容系兄弟连Linux教程(百度传课:史上最牛的Linux视频教程)的学习笔记. Linux入门基础知识 1. Unix和Linux发展历史 二者就像父子关系,当然Unix是老爹.1965年,MI ...

最新文章

  1. 深入理解Java的接口和抽象类
  2. 使用 MarkDown DocFX 升级 Rafy 帮助文档
  3. BZOJ4318: OSU! (概率DP)
  4. CSS3中的圆角边框属性详解(border-radius属性)
  5. java menu字体_Java开发网 - 请问如何让菜单字体变宋体?
  6. Virtual.Lab模拟钢板冲击声
  7. Debug Android with Android phone.
  8. 【转载】 深入浅出Windows的Dll文件
  9. PHP RSA2 签名算法
  10. oracle删除导入库,oracle数据库删除和导入方法
  11. WebService的知识总结(一)
  12. DNS服务器常见的攻击方式
  13. UEBA案例分析系列之数据泄露检测
  14. 毕业即月薪上万,但这才是悲催人生的开始
  15. 我的Blog东一个西一个!
  16. 怎么将某个页面,保存成一个长图?
  17. 系统设计:在线支付系统的需求分析报告
  18. curl php 宝塔 开启_宝塔安装php失败
  19. python如何拼读英语单词-q开头的英语单词
  20. Artifact is being deployed, please wait…

热门文章

  1. 使用Py2neo连接Neo4j失败解决方案/Py2neo安装
  2. c语言实现的小学生心算抢答系统
  3. 简单的合成图片实现‘盖章’
  4. selenium获取某网站工作岗位信息(含火狐浏览器的驱动下载安装步骤)
  5. ❤爆肝nodejs爬虫❤puppeteer带你破解e小天微信机器人授权
  6. 基于JavawWeb的体育馆预定管理系统,java+vue实现体育场管理啊啊啊啊啊
  7. postman查看response_Postman教程——响应
  8. 关于u8 u16 u32的含义
  9. javascript sm2 sm3 sm4 国密库
  10. android webview滚动到底部,Android WebView实现网页滚动截图