Linux Kernel Module(LKM) Init、Delete Code Principle Learning

目录

1. Linux模块(LKM)简介
2. 使用Linux模块
3. LKM模块加载原理
4. LKM模块卸载原理

1. Linux模块(LKM)简介

模块是一种向linux内核添加"设备驱动程序"、"文件系统"、"其他组件"的有效方法,而无须重新编译内核或重启系统,这消除了许多限制,同时带来了很多的优点

1. 通过使用模块,内核程序员能够预先编译大量驱动程序,而不会致使内核映像的尺寸发生膨胀。在自动检测硬件或用户提示后,安装例程会选择适当的模块并将其添加到内核中
2. 内核开发者可以将试验性的代码打包到模块中,模块可疑卸载,修改代码或重新打包后再重新加载,这使得可以快速测试新特性,无需每次都重启系统
3. 模块(LKM)可疑无缝地插入到内核中,同时模块也可以导出一些函数,可以由其他核心模块(以及持久编译到内核中的代码)使用。在模块代码需要卸载时,模块和内核剩余部分之间的关联会自动终止

0x1: 模块的依赖关系和引用

如果模块B使用了模块A提供的函数,那么模块A和模块B之间就存在关系,可以从两个方面来看这种关系

1. 模块B依赖模块A
除非模块A已经驻留在内核内存,否则模块B无法装载

2. 模块B引用模块A
除非模块B已经移除,否则模块A无法从内核移除,在内核中,这种关系称之为"模块B使用模块A"

"struct module_use"和"struct module->module_which_use_me"这两个结果共同组合和保证了内核模块中的依赖关系。
如果模块B使用了模块A提供的函数,那么模块A和模块B之间就存在关系,可以从两个方面来看这种关系

1. 模块B依赖模块A
除非模块A已经驻留在内核内存,否则模块B无法装载

2. 模块B引用模块A
除非模块B已经移除,否则模块A无法从内核移除,在内核中,这种关系称之为"模块B使用模块A"

对每个使用了模块A中函数的模块B,都会创建一个module_use结构体实例,该实例将被添加到模块A(被依赖的模块)的module实例中的modules_which_use_me链表中,modules_which_use_me指向模块B的module实例

我们在编写并加载LKM模块的时候,一定要注意模块间的依赖关系,有时候还需要分步骤单独进行指定模块的加载,才能保证模块间的依赖关系的正确连接

2. 使用Linux模块

0x1: 模块的添加

从用户的角度来看,模块可以通过以下两个"指令”进行添加

1. modprobe
它考虑了各个模块之间可能出现的依赖性(在一个模块依赖于一个或多个合作者模块的功能时),modprobe在识别出目标模块所依赖的模块之后,在内核也会使用insmod(即modprobe只是对insmod的一个包装)

2. insmod
insmod只加载一个单一的模块到内核中,且该模块只信赖内核中已经存在的代码(不管是通过模块动态加载的、还是持久编译到内核中的)

从内核系统的角度来看,模块的加载可以通过以下方法完成

1. init_module()
init_module()是一个系统调用,用户空间的工具只需要提供二进制数据,所有其他工作(重定位、解决引用问题)由内核自身完成

2. request_module()
request_module()不是系统调用,它用于从内核端加载模块,它不仅用于加载模块,还用于实现热插拔功能

0x2: 模块的移除

从用户的角度来看,模块可以通过以下"指令”进行删除

1. rmmod

从内核系统的角度来看,模块的卸载可以通过以下方法完成

1. delete_module()
delete_module()是一个系统调用,它用于从内核移除一个模块,前提是该模块代码不再使用,且其他模块也不再使用该模块导出的函数(即不能有依赖关系)

3. LKM模块加载原理

LKM模块的加载的大部分逻辑都在init_module()中,ring3的insmod、modprobe仅仅负责传递一个二进制数据,本文只关注init_module这个系统调用的代码逻辑,关于整个insmod模块加载过程的原理分析,请参阅

http://files.cnblogs.com/LittleHann/Modultils%E5%B7%A5%E5%85%B7%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E2%80%94%E2%80%94insmod%E7%AF%87.pdf

0x1: 代码流程

init_module()系统调用是用户空间和内核之间用于装载新模块的接口,它的大致流程如下

0x2: 内核代码分析

\linux-2.6.32.63\kernel\module.c
SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs)
    1. *umod
    指向用户地址空间中的区域,表示模块的名字
    2. len
    该区域的长度
    3. *uargs
    指向字符串的指针,指定了模块的参数

\linux-2.6.32.63\kernel\module.c

/* This is where the real work happens */
SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs)
{
    struct module *mod;
    int ret = 0;

    /*
    Must have permission
    确保有插入和删除模块不受限制的权利,并且模块没有被禁止插入或删除
    */
    if (!capable(CAP_SYS_MODULE) || modules_disabled)
    {
        return -EPERM;
    }

    /* Only one module load at a time, please */
    if (mutex_lock_interruptible(&module_mutex) != 0)
        return -EINTR;

    /*
    Do all the hard work
    分配,加载模块,并创建相关的sysfs文件
    */
    mod = load_module(umod, len, uargs);
    if (IS_ERR(mod))
    {
        mutex_unlock(&module_mutex);
        return PTR_ERR(mod);
    }

    /* Drop lock so they can recurse */
    mutex_unlock(&module_mutex);

    /*
    通知内核通知链module_notify_list上的监听者,模块状态变为MODULE_STATE_COMING
    关于module的状态信息,请参阅http://www.cnblogs.com/LittleHann/p/3865490.html,搜索: struct module
         enum module_state
        {
            MODULE_STATE_LIVE,    //模块当前正常使用中(存活状态)
            MODULE_STATE_COMING,    //模块当前正在被加载
            MODULE_STATE_GOING,    //模块当前正在被卸载
        };
    */
    blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_COMING, mod);

    //调用本模块的所有构造器
    do_mod_ctors(mod);
    /*
    Start the module
    调用模块的init方法
    */
    if (mod->init != NULL)
        ret = do_one_initcall(mod->init);
    if (ret < 0)
    {
        /* Init routine failed: abort.  Try to protect us from
                   buggy refcounters. */
        mod->state = MODULE_STATE_GOING;
        synchronize_sched();
        module_put(mod);
        blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod);
        mutex_lock(&module_mutex);
        free_module(mod);
        mutex_unlock(&module_mutex);
        wake_up(&module_wq);
        return ret;
    }
    if (ret > 0)
    {
        printk(KERN_WARNING
"%s: ‘%s‘->init suspiciously returned %d, it should follow 0/-E convention\n"
"%s: loading module anyway...\n",
               __func__, mod->name, ret,
               __func__);
        dump_stack();
    }

    /*
    Now it‘s a first class citizen!  Wake up anyone waiting for it.
    */
    mod->state = MODULE_STATE_LIVE;
    //唤醒module_wq 队列上等待本模块初始化的所有任务
    wake_up(&module_wq);
    //通知内核通知链module_notify_list上的监听者,模块状态变为MODULE_STATE_LIVE
    blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_LIVE, mod);

    /*
    We need to finish all async code before the module init sequence is done
    等待所有的异步函数调用完成
    */
    async_synchronize_full();

    //获得module_mutex锁,module_mutex作用之一就是保护全局的模块链表
    mutex_lock(&module_mutex);
    /* Drop initial reference. */
    module_put(mod);
    trim_init_extable(mod);
#ifdef CONFIG_KALLSYMS
    mod->num_symtab = mod->core_num_syms;
    mod->symtab = mod->core_symtab;
    mod->strtab = mod->core_strtab;
#endif
    /*
    释放与模块初始化相关的节区所占的内存
    这点和windows下的驱动加载是类似的,仅仅用于驱动加载的那部分"派遣初始化函数"被分配到"可换页内存"区域中,当驱动加载完毕后就立即释放
    */
    module_free(mod, mod->module_init);
    mod->module_init = NULL;
    mod->init_size = 0;
    mod->init_text_size = 0;
    mutex_unlock(&module_mutex);

    return 0;
}

Relevant Link:

http://bbs.chinaunix.net/thread-2194837-1-1.html
http://blog.csdn.net/wuhui_gdnt/article/details/5316616
http://blog.csdn.net/muge0913/article/details/7518568
http://blog.csdn.net/ganggexiongqi/article/details/6823960

4. LKM模块卸载原理

我们输入指令rmmod,最终在系统内核中需要调用sys_delete_module进行实现

SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags)
    1. name_user
    待卸载的模块名称
    2. flags

\linux-2.6.32.63\kernel\module.c

SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags)
{
    struct module *mod;
    char name[MODULE_NAME_LEN];
    int ret, forced = 0;

    //确保有插入和删除模块不受限制的权利,并且模块没有被禁止插入或删除
    if (!capable(CAP_SYS_MODULE) || modules_disabled)
        return -EPERM;

    //获得从用户空间传递到内核空间的模块名字
    if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)
        return -EFAULT;
    name[MODULE_NAME_LEN-1] = ‘\0‘;

    /* Create stop_machine threads since free_module relies on
     * a non-failing stop_machine call. */
    ret = stop_machine_create();
    if (ret)
        return ret;

    //获得module_mutex锁
    if (mutex_lock_interruptible(&module_mutex) != 0) {
        ret = -EINTR;
        goto out_stop;
    }

    //得到要卸载的模块的指针
    mod = find_module(name);
    if (!mod) {
        ret = -ENOENT;
        goto out;
    }

    /*
    检查,确认没有其他模块依赖要卸载的模块
    关于linux下模块间的依赖性以及相关数据结构,请参阅另一篇文章
    http://www.cnblogs.com/LittleHann/p/3865490.html,搜索: struct module_use
    */
    if (!list_empty(&mod->modules_which_use_me)) {
        /* Other modules depend on us: get rid of them first. */
        ret = -EWOULDBLOCK;
        goto out;
    }

    /*
    Doing init or already dying?
    检查模块的状态是否是 MODULE_STATE_LIVE
    */
    if (mod->state != MODULE_STATE_LIVE) {
        /* FIXME: if (force), slam module count and wake up
                   waiter --RR */
        DEBUGP("%s already dying\n", mod->name);
        ret = -EBUSY;
        goto out;
    }

    /* If it has an init func, it must have an exit func to unload */
    if (mod->init && !mod->exit) {
        forced = try_force_unload(flags);
        if (!forced) {
            /* This module can‘t be removed */
            ret = -EBUSY;
            goto out;
        }
    }

    /*
    Set this up before setting mod->state
    设置等待本模块退出 的进程为current
    */
    mod->waiter = current;

    /* Stop the machine so refcounts can‘t move and disable module. */
    ret = try_stop_module(mod, flags, &forced);
    if (ret != 0)
        goto out;

    /*
    Never wait if forced.
    等待模块的引用计数变为0
    */
    if (!forced && module_refcount(mod) != 0)
    {
        wait_for_zero_refcount(mod);
    } 

    //释放module_mutex锁
    mutex_unlock(&module_mutex);
    /*
    Final destruction now noone is using it.
    调用模块本身的exit函数
    */
    if (mod->exit != NULL)
    {
        mod->exit();
    }
    //告诉通知链module_notify_list上的监听者,模块状态 变为 MODULE_STATE_GOING
    blocking_notifier_call_chain(&module_notify_list, MODULE_STATE_GOING, mod);
    //等待所有的异步函数调用完成
    async_synchronize_full();
    mutex_lock(&module_mutex);
    /* Store the name of the last unloaded module for diagnostic purposes */
    strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module));
    free_module(mod);

 out:
    mutex_unlock(&module_mutex);
out_stop:
    stop_machine_destroy();
    return ret;
}

Copyright (c) 2014 LittleHann All rights reserved

Linux Kernel Module(LKM) Init、Delete Code Principle Learning,布布扣,bubuko.com

时间: 2024-10-15 08:57:06

Linux Kernel Module(LKM) Init、Delete Code Principle Learning的相关文章

linux kernel module

#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h> static int hello_init(void){ printk(KERN_ALERT "hello world!\n"); return 0;} static void hello_exit(void){ printk(KERN_ALERT "exit ok!\n");} mo

Linux Kernel Synchronization &amp;&amp; Mutual Exclusion、Linux Kernel Lock Mechanism Summarize(undone)

目录 1. 同步与互斥 2. 锁定内存总线原子操作 3. 信号量 4. 自旋锁 5. RCU机制 6. PERCPU变量 1. 同步与互斥 在多任务操作系统中,多个进程按照不可预测的顺序进行,因为多个进程之间常常存在相互制约或者相互依赖的关系,这些关系可以被划分为同步和互斥的关系 从本质上来说,同步和互斥也可以理解为进程/线程间同步通信的一种机制,只是这里传递的是一种"争用关系",关于Linux进程间通信和同步.以及不同和互斥的相关知识,请参阅另一篇文章 http://www.cnbl

CentOS启动流程、Grub legacy配置、linux kernel模块管理、伪文件系统介绍

写在前面: 博客书写牢记5W1H法则:What,Why,When,Where,Who,How. 本篇主要内容: ● 启动相关基础概念汇总 ● 启动流程 ● init程序类型     /etc/rc.d/rc     chkconfig     /etc/rc.d/rc.sysinit ● GRUB legacy     命令行接口     配置文件 ● Linux Kernel     内核模块查看与管理         lsmod         modinfo         modprob

Linux Kernel(Android) 加密算法总结(cipher、compress、digest)

1. Linux内核支持哪些加密算法 ? 内核支持的加密算法很多,包括: 对称加密算法,如AES: 摘要算法,如sha1,md5: 压缩算法,如deflate. 不过内核好像不支持非对称加密算法. 2. 加密算法源文件位置 这些算法作为加密函数框架的最底层,提供加密和解密的实际操作.这些函数可以在内核crypto文件夹下,相应的文件中找到. 3.  配置编译选项将加密算法作为模块编入内核 Cryptographic options 加密选项 Cryptographic API 提供核心的加密AP

Linux Kernel sys_call_table、Kernel Symbols Export Table Generation Principle、Difference Between System Calls Entrance In 32bit、64bit Linux(undone)

目录 1. sys_call_table:系统调用表 2. 内核符号导出表.kallsyms_lookup_name 3. Linux 32bit.64bit下系统调用入口的异同 1. sys_call_table:系统调用表 Relevant Link: 2. 内核符号导出表.kallsyms_lookup_name Relevant Link: 3. Linux 32bit.64bit下系统调用入口的异同 以sys_execve.sys_socketcall.sys_init_module这

#26 Linux kernel(内核)详解与uname、lsmod、modinfo、depmod、insmod、rmmod、modprobe...命令用法

Linux kernel: 内核设计流派: 单内核设计,但是充分借鉴了微内核体系设计的优点,为内核引入了模块化机制,内核高度模块化: 内核被模块化之后,一些最为基本最为重要的内容,被编译到内核核心:而其他更多的功能则以模块的方式来提供:而且支持动态装载和卸载各内核模块: 内核的组成部分: kernel:内核核心文件,一般为bzimage,经过压缩处理的镜像文件:通常内核核心文件保存在/boot/目录下,名称为vmlinuz-version-release kernel object(ko):内核

在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6564592 在前一篇文章提到,从源代码树下载下来的最新Android源代码,是不包括内核代码的,也就是Android源代码工程默认不包含Linux Kernel代码,而是使用预先编译好的内核,也就是prebuilt/android-arm/kernel/kernel-qemu文件.那么,如何才能DIY自己的内核呢?这篇文章一一道来. 一. 首选

Linux Kernel - Debug Guide (Linux内核调试指南 )

http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环

linux kernel 字符设备详解

有关Linux kernel 字符设备分析: 参考:http://blog.jobbole.com/86531/ 一.linux kernel 将设备分为3大类,字符设备,块设备,网络设备. 字符设备是指只能一个字节一个字节读写的设备, 常见的外设基本上都是字符设备. 块设备:常见的存储设备,硬盘,SD卡都归为块设备,块设备是按一块一块读取的. 网络设备:linux 将对外通信的一个机制抽象成一个设备, 通过套接字对其进行相关的操作. 每一个字符设备或块设备都在/dev目录下对应一个设备文件.l