精华内容
下载资源
问答
  • 一个运行中的Linux内核是您不想烦恼的事情之一。 毕竟,内核是驱动计算机执行所有操作的软件。 考虑到必须在实时系统上同时管理多少个细节,最好让内核以尽可能少的干扰来完成其工作。 但是,如果在不重新启动整个...

    linux如何卸载内核模块

    本文摘自Manning出版的《 Linux in Action》第15章。

    Linux使用内核模块管理硬件外围设备。 这是这样的。

    一个运行中的Linux内核是您不想烦恼的事情之一。 毕竟,内核是驱动计算机执行所有操作的软件。 考虑到必须在实时系统上同时管理多少个细节,最好让内核以尽可能少的干扰来完成其工作。 但是,如果在不重新启动整个系统的情况下无法对计算环境进行很小的更改,则插入新的网络摄像头或打印机可能会给您的工作流程带来痛苦。 每次添加设备时都必须重新启动,以使系统识别它几乎没有效率。

    为了在稳定性和可用性的对立优点之间取得有效的平衡,Linux隔离了内核,但允许您通过可加载的内核模块(LKM)快速添加特定的功能。 如下图所示,您可以将模块视为一个软件,它告诉内核在哪里可以找到设备以及如何使用该设备。 反过来,内核使设备可供用户和进程使用,并监督其运行。

    Kernel modules

    内核模块充当设备和Linux内核之间的转换器。

    没有什么可以阻止您编写自己的模块以完全按照您希望的方式支持设备的,但是为什么要麻烦呢? Linux模块库已经非常强大,以至于通常不需要自己动手。 在绝大多数情况下,Linux会自动加载新设备的模块,而您甚至都不知道它。

    尽管如此,由于某些原因,有时它并不是单独发生的。 (您不想让招聘经理不耐烦地等待您的笑脸加入视频会议工作面试时间太长。)为了帮助您完成工作,您需要对内核模块有更多的了解,尤其是,如何找到将运行外围设备的实际模块,然后如何手动激活它。

    查找内核模块

    按照公认的惯例,模块是扩展名为.ko(内核对象)的文件,位于/lib/modules/目录下。 但是,在完全导航到这些文件之前,您可能必须做出选择。 因为在启动时可以从版本列表中加载一个选项,所以支持选择的特定软件(包括内核模块)必须存在于某个地方。 好吧, /lib/modules /就是其中之一。 在该目录中,您可以找到每个可用Linux内核发行版的模块填充目录; 例如:

    $ ls /lib/modules
    4.4.0-101-generic
    4.4.0-103-generic
    4.4.0-104-generic

    就我而言,活动内核是具有最高发行版本号(4.4.0-104通用)的版本,但不能保证对您而言会是相同的(内核会经常更新)。 如果要对要在实时系统上使用的模块进行某些工作,则需要确保您拥有正确的目录树。

    uname -r-r从通常会显示的系统信息中指定内核发行版号):
    $ uname -r
    4.4.0-104-generic

    有了这些信息,您可以使用称为命令替换的过程将uname合并到文件系统引用中。 例如,要导航到正确的目录,请将其添加到/lib/modules 要告诉Linux“ uname”不是文件系统位置,请将uname部分括在反引号中,如下所示:

    $ ls /lib/modules/`uname -r`
    build   modules.alias        modules.dep      modules.softdep
    initrd  modules.alias.bin    modules.dep.bin  modules.symbols
    kernel  modules.builtin      modules.devname  modules.symbols.bin
    misc    modules.builtin.bin  modules.order    vdso

    您将在kernel/目录下的子目录中找到大多数模块。 花几分钟时间浏览这些目录,以了解事物的排列方式和可用的内容。 文件名通常可以使您很好地了解所要查找的内容。

    $ ls /lib/modules/`uname -r`/kernel
    arch  crypto  drivers  fs  kernel  lib  mm
    net  sound  ubuntu  virt  zfs

    这是定位内核模块的一种方法。 实际上,这是快速而肮脏的方法。 但这不是唯一的方法。 如果要获取完整的集合,可以使用lsmod列出所有当前加载的模块以及一些基本信息。 截断的输出的第一列(此处列出的内容太多了)是模块名称,然后是文件大小和编号,然后是每个模块所依赖的其他模块的名称:

    $ lsmod
    [...]
    vboxdrv          454656  3 vboxnetadp,vboxnetflt,vboxpci
    rt2x00usb        24576  1 rt2800usb
    rt2800lib        94208  1 rt2800usb
    [...]

    多少太多? 好吧,让我们再次运行lsmod ,但这一次将输出管道传输到wc -l以获得行数:

    $ lsmod | wc -l
    113

    这些是已加载的模块。 总共有多少个可用? 运行modprobe -c并计算行数将得到该数字:

    $ modprobe -c | wc -l
    33350

    共有33,350个可用模块! 多年来,似乎有人一直在努力为我们提供运行物理设备的软件。

    注意:在某些系统上,您可能会遇到在/etc/modules文件中以其唯一条目引用的定制模块,或者将其作为配置文件保存到/etc/modules-load.d/ 这样的模块很可能是本地开发项目的产品,可能涉及尖端的实验。 无论哪种方式,最好都对正在查看的内容有所了解。

    这就是找到模块的方式。 您的下一个工作是弄清楚如果由于某种原因它不是独立发生的,则如何手动加载不活动的模块。

    手动加载内核模块

    在加载内核模块之前,逻辑要求您必须确认它的存在。 在执行此操作之前,您需要知道它的名称。 有时候,获得这一部分需要同等的魔力和运气,以及在线文档作者的辛勤工作中的一些帮助。

    我将通过描述一段时间后遇到的问题来说明该过程。 晴朗的一天,由于仍然无法摆脱我的原因,笔记本电脑上的WiFi接口停止工作。 就这样 也许是软件更新将其淘汰了。 谁知道? 我运行了lshw -c network ,并得到了以下非常奇怪的信息:

    network UNCLAIMED
        AR9485 Wireless Network Adapter

    Linux识别了该接口(Atheros AR9485),但将其列为无人认领。 就像他们说的那样:“当艰难时,艰难地搜索互联网。” 我搜索了atheros ar9 linux模块 ,在浏览了5年甚至10年历史的页面并建议我写自己的模块或放弃时,我终于发现了这一点(使用Ubuntu 16.04,至少)存在一个工作模块。 它的名字是ath9k。

    是! 战斗和胜利一样好! 向内核添加模块比听起来容易得多。 要再次检查它是否可用,可以针对模块的目录树运行find ,指定-type f告诉Linux您正在寻找文件,然后添加字符串ath9k以及glob星号以包括所有以开头的文件名您的字符串:

    $ find /lib/modules/$(uname -r) -type f -name ath9k*
    /lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_common.ko
    /lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k.ko
    /lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_htc.ko
    /lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_hw.ko

    仅需一步,即可加载模块:

     # modprobe ath9k 
    

    而已。 没有重启。 别大惊小怪。

    这是另一个示例,向您展示如何使用已损坏的活动模块。 曾经有一段时间,将我的Logitech网络摄像头与特定软件一起使用会使该摄像头无法被任何其他程序访问,直到下次启动系统为止。 有时,我需要在其他应用程序中打开相机,但没有时间关闭并重新启动。 (我运行了许多应用程序,在启动后将它们全部安装到位需要一些时间。)

    因为此模块可能处于活动状态,所以使用lsmod搜索单词video应该给我有关相关模块名称的提示。 实际上,它总比提示好:用video一词描述的唯一模块是uvcvideo (如下所示):

    $ lsmod | grep video
    uvcvideo               90112  0
    videobuf2_vmalloc      16384  1 uvcvideo
    videobuf2_v4l2         28672  1 uvcvideo
    videobuf2_core         36864  2 uvcvideo,videobuf2_v4l2
    videodev              176128  4 uvcvideo,v4l2_common,videobuf2_core,videobuf2_v4l2
    media                  24576  2 uvcvideo,videodev

    我可能已经控制了导致崩溃的某些事情,我想我可能已经更深入地研究了一下是否可以正确地解决问题。 但是你知道这是怎么回事。 有时您不关心理论,只希望设备正常工作。 因此,我使用rmmod杀死了uvcvideo模块,并使用modprobe再次重新启动了它:

    # rmmod uvcvideo
    # modprobe uvcvideo

    再次:没有重启。 没有顽固的血迹。

    翻译自: https://opensource.com/article/18/5/how-load-or-unload-linux-kernel-module

    linux如何卸载内核模块

    展开全文
  • Linux内核模块

    千次阅读 2018-05-16 14:43:33
    内核模块Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大...

    最近学习到了这里,也记录一下

    内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。

    模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。

    一般是在设备驱动程序、文件系统等地方使用模块,而对Linux内核中极为重要的地方,如进程管理和内存管理等,无法通过模块来实现,必须直接对内核进行修改来完成。

    利用内核模块的动态装载性具有如下优点:
      ·将内核映象的尺寸保持在最小,并具有最大的灵活性;
      ·便于检验新的内核代码,而不需重新编译内核并重新引导。
    但是,内核模块的引入也带来了如下问题:
      ·对系统性能和内存利用有负面影响;
      ·装入的内核模块和其他内核部分一样,具有相同的访问权限,因此,差的内核模块会导致系统崩溃;
      ·为了使内核模块访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改这些符号表;
      ·有些模块要求利用其他模块的功能,因此,内核要维护模块之间的依赖性。
      ·内核必须能够在卸载模块时通知模块,并且要释放分配给模块的内存和中断等资源;
      ·内核版本和模块版本的不兼容,也可能导致系统崩溃,因此,严格的版本检查是必需的。
      尽管内核模块的引入同时也带来不少问题,但是模块机制确实是扩充内核功能一种行之有效的方法,也是在内核级进行编程的有效途径。

    就比如说操作系统的驱动程序需要代码和缓冲空间。如果某驱动程序(或其他操作系统服务)不常使用,可以不必在内存中保留该代码和数据,这部分空间可以用于其他目的,这类代码有的时候称为暂时操作系统代码,它们会根据需要去调入或者是调出,因此使用这样的代码,可以在程序执行的时候去动态的改变操作系统的大小

    编写一个简单的内核模块

    • 1、编译源文件
    vi helloworld.c

    这里写图片描述

    程序中的MODULE_LICENSE(“GPL”)用于声明模块的许可证,可以将模块定义为获得GPL Version 2或更新版本许可的模块,根据GPLv2授权要求,衍生版本也就是修改后的Linux内核源代码必须公开

    • 2、编译 gcc –c helloworld.c,我们也可以去使用gcc –DMODULE –c helloworld.c,参数–DMODULE通知编译程序这是一个模块而不是一个普通文件,不过我们在代码中已声明是模块,所以可以直接用gcc –c helloworld.c

    这里需要注意的是,在Linux当中如果我们需要自己去写内核模块的话,我们就要去包含linux/module.h这个头文件,这个文件包含了对模块的结构定义以及模块的版本控制,而函数init_module()和函数cleanup_module()是模块编程中最基本的也是必须的两个函数。init_module()向内核注册模块提供新功能;cleanup_module负责注销所有由模块注册的功能,以及printk函数是由Linux内核定义的,功能与用户态下的printf函数相似。字符串<1>表示消息的优先级,printk对于不同优先级的消息进行不同的处理,在这儿使用高优先级是因为默认优先级的消息可能不能显示在控制台上

    • 3、安装内核模块insmod helloworld.o,如果遇到版本不匹配问题,可使用 insmod -f helloworld.o

    • 4、查看内核模块的安装情况,验证安装成功,使用使用 lsmod 或者 cat /proc/modules 命令
      这里写图片描述

    如果我们需要卸载内核模块则使用 rmmod helloworld,可以看到下面有Goodbye!的输出

    这里写图片描述

    展开全文
  • Linux内核模块详解

    万次阅读 多人点赞 2019-08-26 09:22:36
    内核模块Linux操作系统中一个比较独特的机制。通过这一章学习,希望能够理解Linux提出内核模块这个机制的意义;理解并掌握Linux实现内核模块机制的基本技术路线;运用Linux提供的工具和命令,掌握操作内核模块的...

    内核模块

    实验目的

    内核模块是Linux操作系统中一个比较独特的机制。通过这一章学习,希望能够理解Linux提出内核模块这个机制的意义;理解并掌握Linux实现内核模块机制的基本技术路线;运用Linux提供的工具和命令,掌握操作内核模块的方法。

    实验内容

    针对三个层次的要求,本章安排了3个实验。

    第一个实验,编写一个很简单的内核模块。虽然简单,但它已经具备了内核模块的基本要素。与此同时,初步阅读编制内核模块所需要的Makefile。

    第二个实验,演示如何将多个源文件,合并到一个内核模块中。上述实验过程中,将会遇到Linux为此开发的内核模块操作工具lsmod、insmod、rmmod等。

    第三个实验,考察如何利用内核模块机制,在/proc文件系统中,为特殊文件、设备、公共变量等,创建节点。它需要自主完成,本书只交待基本思路和部分源代码。程序的完善,以及调试工作,留给大家完成。

     

    实验指导

    1. 什么是内核模块

    Linux操作系统的内核是单一体系结构(monolithic kernel)的。也就是说,整个内核是一个单独的非常大的程序。与单一体系结构相对的是微内核体系结构(micro kernel),比如Windows NT采用的就是微内核体系结构。对于微内核体系结构特点,操作系统的核心部分是一个很小的内核,实现一些最基本的服务,如创建和删除进程、内存管理、中断管理等等。而文件系统、网络协议等其它部分都在微内核外的用户空间里运行。

    这两种体系的内核各有优缺点。使用微内核的操作系统具有很好的可扩展性而且内核非常的小,但这样的操作系统由于不同层次之间的消息传递要花费一定的代价所以效率比较低。对单一体系结构的操作系统来说,所有的模块都集成在一起,系统的速度和性能都很好,但是可扩展性和维护性就相对比较差。

    据作者理解,正是为了改善单一体系结构的可扩展性、可维护性等,Linux操作系统使用了一种全新的内核模块机制。用户可以根据需要,在不需要对内核重新编译的情况下,模块能动态地装入内核或从内核移出。

    模块是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是其代码可以在运行时链接到系统中作为内核的一部分运行或从内核中取下,从而可以动态扩充内核的功能。这种目标代码通常由一组函数和数据结构组成,用来实现一种文件系统,一个驱动程序,或其它内核上层的功能。模块机制的完整叫法应该是动态可加载内核模块(Loadable Kernel Module)或 LKM,一般就简称为模块。与前面讲到的运行在微内核体系操作系统的外部用户空间的进程不同,模块不是作为一个进程执行的,而像其他静态连接的内核函数一样,它在内核态代表当前进程执行。由于引入了模块机制,Linux的内核可以达到最小,即内核中实现一些基本功能,如从模块到内核的接口,内核管理所有模块的方式等等,而系统的可扩展性就留给模块来完成。

     

    1.1 内核模块的特点

    使用模块的优点:

    1. 使得内核更加紧凑和灵活
    2. 修改内核时,不必全部重新编译整个内核,可节省不少时间,避免人工操作的错误。系统中如果需要使用新模块,只要编译相应的模块然后使用特定用户空间的程序将模块插入即可。
    3. 模块可以不依赖于某个固定的硬件平台。
    4. 模块的目标代码一旦被链接到内核,它的作用和静态链接的内核目标代码完全等价。 所以,当调用模块的函数时,无须显式的消息传递。

    但是,内核模块的引入也带来一定的问题:

    1. 由于内核所占用的内存是不会被换出的,所以链接进内核的模块会给整个系统带来一定的性能和内存利用方面的损失。
    2. 装入内核的模块就成为内核的一部分,可以修改内核中的其他部分,因此,模块的使用不当会导致系统崩溃。
    3. 为了让内核模块能访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改符号表。
    4. 模块会要求利用其它模块的功能,所以,内核要维护模块之间的依赖性。

    模块是和内核在同样的地址空间运行的,模块编程在一定意义上说也就是内核编程。但是并不是内核中所有的地方都可以使用模块。 一般是在设备驱动程序、文件系统等地方使用模块,而对Linux内核中极为重要的地方,如进程管理和内存管理等,仍难以通过模块来实现,通常必须直接对内核进行修改。

    在Linux内核源程序中,经常利用内核模块实现的功能,有文件系统,SCSI高级驱动程序,大多数的SCSI驱动程序,多数CD-ROM驱动程序,以太网驱动程序等等。

     

    1.2 编写一个简单的内核模块

    看了这些理论概念,你是不是有点不耐烦了:“我什么时候才能开始在机子上实现一个模块啊?” 好吧,在进一步介绍模块的实现机制以前,我们先试着写一个非常简单的模块程序,它可以在2.6.15的版本上实现,对于低于2.4的内核版本可能还需要做一些调整,这儿就不具体讲了。

    helloworld.c

    #include <linux/module.h>        /* Needed by all modules */
    #include <linux/kernel.h>          /* Needed for KERN_INFO */
    
    int init_module(void)
    {
        printk(KERN_INFO “Hello World!\n”);
        return 0;
    }
    
    void cleanup_module(void)
    {
        printk(KERN_INFO “Goodbye!\n”);
    }
    
    MODULE_LICENSE(“GPL”);

    说明:

    1. 任何模块程序的编写都需要包含linux/module.h这个头文件,这个文件包含了对模块的结构定义以及模块的版本控制。文件里的主要数据结构我们会在后面详细介绍。
    2. 函数init_module()和函数cleanup_module( )是模块编程中最基本的也是必须的两个函数。init_module()向内核注册模块所提供的新功能;cleanup_module()负责注销所有由模块注册的功能。
    3. 注意我们在这儿使用的是printk()函数(不要习惯性地写成printf),printk()函数是由Linux内核定义的,功能与printf相似。字符串KERN_INFO表示消息的优先级,printk()的一个特点就是它对于不同优先级的消息进行不同的处理。

     

    接下来,我们就要编译和加载这个模块了。在前面的章节里我们已经学习了如何使用gcc,现在还要注意的一点就是:确定你现在是超级用户。因为只有超级用户才能加载和卸载模块。在编译内核模块前,先准备一个Makefile文件:

    TARGET = helloworld
    
    KDIR = /usr/src/linux
    
    PWD = $(shell pwd)
    
    obj-m += $(TARGET).o
    
    default:
           make -C $(KDIR) M=$(PWD) modules

    然后简单输入命令make:

    #make

    结果,我们得到文件“helloworld.ko”。然后执行内核模块的装入命令:

    #insmod  helloworld.ko

    Hello World!

    这个时候,生成了字符串“Hello World!”,它是在init_module()中定义的。由此说明,helloworld模块已经加载到内核中了。我们可以使用lsmod命令查看。lsmod命令的作用是告诉我们所有在内核中运行的模块的信息,包括模块的名称,占用空间的大小,使用计数以及当前状态和依赖性。

    root# lsmod

    Module    Size    Used  by

    helloworld  464    0   (unused)

    最后,我们要卸载这个模块。

    # rmmod helloworld

    Goodbye!

     

    看到了打印在屏幕上的“Goodbye!”,它是在cleanup_module()中定义的。由此说明,helloworld模块已经被删除。如果这时候我们再使用lsmod查看,会发现helloworld模块已经不在了。

    关于insmod和rmmod这两个命令,现在只能简单地告诉你,他们是两个用于把模块插入内核和从内核移走模块的实用程序。前面用到的insmod, rmmod和lsmod都属于modutils模块实用程序。

    好了,你已经成功地在机子上实现了一个最简单的模块程序。我们再接再厉,进行下一个阶段的学习。

    1. 模块实现机制

    2.1内核模块和应用程序的比较

    在深入研究模块的实现机制以前,我们有必要了解一下内核模块与我们熟悉的应用程序之间的区别。

    最主要的一点,我们必须明确,内核模块是在“内核空间”中运行的,而应用程序运行在“用户空间”。内核空间和用户空间是操作系统中最基本的两个概念,也许你还不是很清楚它们之间的区别,那么我们先一起复习一下。

    操作系统的作用之一,就是为应用程序提供资源的管理,让所有的应用程序都可以使用它需要的硬件资源。然而,目前的常态是,主机往往只有一套硬件资源;现代操作系统都能利用这一套硬件,支持多用户系统。为了保证内核不受应用程序的干扰,多用户操作系统都实现了对硬件资源的授权访问,而这种授权访问机制的实现,得益于在CPU内部实现不同的操作保护级别。以INTEL的CPU为例,在任何时候,它总是在四个特权级当中的一个级别上运行,如果需要访问高特权级别的存储空间,必须通过有限数目的特权门。 Linux系统就是充分利用这个硬件特性设计的,它只使用了两级保护级别(尽管i386系列微处理器提供了四级模式)。 在Linux系统中,内核在最高级运行。在这一级,对任何设备的访问都可以进行。而应用程序则运行在最低级。在这一级,处理器禁止程序对硬件的直接访问和对内核空间的未授权访问。所以,对应于在最高级运行的内核程序,它所在的内存空间是内核空间。而对应于在最低级运行的应用程序,它所在的内存空间是用户空间。Linux通过系统调用或者中断,完成从用户空间到内核空间的转换。执行系统调用的内核代码在进程上下文中运行,它代表调用进程完成在内核空间上的操作,而且还可以访问进程的用户地址空间的数据。但对中断来说,它并不存在于任何进程上下文中,而是由内核来运行的。

    好了,下面我们可以比较具体地分析内核模块与应用程序的异同。让我们看一下表6-1。

    表6-1 应用程序和内核模块程序编程方式的比较

                        C语言普通应用程序                 模块程序

     入口               main()                         init_module()

     出口               无                            cleanup_module()

     编译               gcc –c                         编制专用Makefile,并调用gcc

     连接               gcc                           insmod

     运行               直接运行                      insmod

     调试               gdb                           kdbug, kdb, kgdb等内核调试工具

     

    这个表里,我们看到内核模块必须通过init_module()函数告诉系统,“我来了”;通过cleanup_module()函数告诉系统,“我走了”。这也就是模块最大的特点,可以被动态地装入和卸载。insmod是内核模块操作工具集modutils中,把模块装入内核的命令,我们会在后面详细介绍。因为地址空间的原因,内核模块不能像应用程序那样自由地使用在用户空间定义的函数库如libc,例如printf();模块只能使用在内核空间定义的那些资源受到限制的函数,例如printk()。应用程序的源代码,可以调用本身没有定义的函数,只需要在连接过程中用相应的函数库解析那些外部引用。应用程序可调用的函数printf(),是在stdio.h中声明,并在libc中存在目标可连接代码。然而对于内核模块来说,它无法使用这个打印函数,而只能使用在内核空间中定义的printk()函数。printk()函数不支持浮点数的输出,而且输出数据量受到内核可用内存空间的限制。

    内核模块的另外一个困难,是内核失效对于整个系统或者对于当前进程常常是致命的,而在应用程序的开发过程中,缺段(segment fault)并不会造成什么危害,我们可以利用调试器轻松地跟踪到出错的地方。所以在内核模块编程的过程中,必须特别的小心。

    好了,下面我们可以具体地看一看内核模块机制究竟是怎么实现的。

     

    2.2 内核符号表

    首先,我们来了解一下内核符号表这个概念。内核符号表是一个用来存放所有模块可以访问的那些符号以及相应地址的特殊的表。模块的连接就是将模块插入到内核的过程。模块所声明的任何全局符号都成为内核符号表的一部分。内核模块根据系统符号表从内核空间中获取符号的地址,从而确保在内核空间中正确地运行。

    这是一个公开的符号表,我们可以从文件/proc/kallsyms中以文本的方式读取。在这个文件中存放数据地格式如下:

    内存地址      属性      符号名称        【所属模块】

    在模块编程中,可以利用符号名称从这个文件中检索出该符号在内存中的地址,然后直接对该地址内存访问从而获得内核数据。对于通过内核模块方式导出的符号,会包含第四列“所属模块”,用来标志这个符号所属的模块名称;而对于从内核中释放出的符号就不存在这一列的数据了。

    内核符号表处于内核代码段的_ksymtab部分,其开始地址和结束地址是由C编译器所产生的两个符号来指定:__start___ksymtab和__stop___ksymtab。

     

    2.3 模块依赖

    内核符号表记录了所有模块可以访问的符号及相应地址。一个内核模块被装入后,它所声明的符号就会被记录到这个表里,而这些符号当然就可能会被其他模块所引用。这就引出了模块依赖这个问题。

    一个模块A引用另一个模块B所导出的符号,我们就说模块B被模块A引用,或者说模块A装载到模块B的上面。如果要链接模块A,必须先要链接模块B。否则,模块B所导出的那些符号的引用就不可能被链接到模块A中。这种模块间的相互关系就叫做模块依赖。

     

    2.4 内核代码分析

    内核模块机制的源代码实现,来自于Richard Henderson的贡献。2002年后,由Rusty Russell重写。较新版本的Linux内核,采用后者。

     

    2.4.1 数据结构

    跟模块有关的数据结构存放在include/linux/module.h中,当然,首推struct module,

    include/linux/module.h
    
    232  struct module
    
    233  {
    
    234        enum module_state state;
    
    235
    
    236        /* Member of list of modules */
    
    237        struct list_head list;
    
    238
    
    239        /* Unique handle for this module */
    
    240        char name[MODULE_NAME_LEN];
    
    241
    
    242        /* Sysfs stuff. */
    
    243        struct module_kobject mkobj;
    
    244        struct module_param_attrs *param_attrs;
    
    245        const char *version;
    
    246        const char *srcversion;
    
    247
    
    248        /* Exported symbols */
    
    249        const struct kernel_symbol *syms;
    
    250        unsigned int num_syms;
    
    251        const unsigned long *crcs;
    
    252
    
    253        /* GPL-only exported symbols. */
    
    254        const struct kernel_symbol *gpl_syms;
    
    255        unsigned int num_gpl_syms;
    
    256        const unsigned long *gpl_crcs;
    
    257
    
    258        /* Exception table */
    
    259        unsigned int num_exentries;
    
    260        const struct exception_table_entry *extable;
    
    261
    
    262        /* Startup function. */
    
    263        int (*init)(void);
    
    264
    
    265        /* If this is non-NULL, vfree after init() returns */
    
    266        void *module_init;
    
    267
    
    268        /* Here is the actual code + data, vfree'd on unload. */
    
    269        void *module_core;
    
    270
    
    271        /* Here are the sizes of the init and core sections */
    
    272        unsigned long init_size, core_size;
    
    273
    
    274        /* The size of the executable code in each section.  */
    
    275        unsigned long init_text_size, core_text_size;
    
    276
    
    277        /* Arch-specific module values */
    
    278        struct mod_arch_specific arch;
    
    279
    
    280        /* Am I unsafe to unload? */
    
    281        int unsafe;
    
    282
    
    283        /* Am I GPL-compatible */
    
    284        int license_gplok;
    
    285       
    
    286        /* Am I gpg signed */
    
    287        int gpgsig_ok;
    
    288
    
    289  #ifdef CONFIG_MODULE_UNLOAD
    
    290        /* Reference counts */
    
    291        struct module_ref ref[NR_CPUS];
    
    292
    
    293        /* What modules depend on me? */
    
    294        struct list_head modules_which_use_me;
    
    295
    
    296        /* Who is waiting for us to be unloaded */
    
    297        struct task_struct *waiter;
    
    298
    
    299        /* Destruction function. */
    
    300        void (*exit)(void);
    
    301  #endif
    
    302
    
    303  #ifdef CONFIG_KALLSYMS
    
    304        /* We keep the symbol and string tables for kallsyms. */
    
    305        Elf_Sym *symtab;
    
    306        unsigned long num_symtab;
    
    307        char *strtab;
    
    308
    
    309        /* Section attributes */
    
    310        struct module_sect_attrs *sect_attrs;
    
    311  #endif
    
    312
    
    313        /* Per-cpu data. */
    
    314        void *percpu;
    
    315
    
    316        /* The command line arguments (may be mangled).  People like
    
    317          keeping pointers to this stuff */
    
    318        char *args;
    
    319  };
    
    

    在内核中,每一个内核模块信息都由这样的一个module对象来描述。所有的module对象通过list链接在一起。链表的第一个元素由static LIST_HEAD(modules)建立,见kernel/module.c第65行。如果阅读include/linux/list.h里面的LIST_HEAD宏定义,你很快会明白,modules变量是struct list_head类型结构,结构内部的next指针和prev指针,初始化时都指向modules本身。对modules链表的操作,受module_mutex和modlist_lock保护。

     

    下面就模块结构中一些重要的域做一些说明。

     

    234 state表示module当前的状态,可使用的宏定义有:

    MODULE_STATE_LIVE

    MODULE_STATE_COMING

    MODULE_STATE_GOING

    240 name数组保存module对象的名称。

    244 param_attrs指向module可传递的参数名称,及其属性

    248-251 module中可供内核或其它模块引用的符号表。num_syms表示该模块定义的内核模块符号的个数,syms就指向符号表。

    1. 300  init和exit 是两个函数指针,其中init函数在初始化模块的时候调用;exit是在删除模块的时候调用的。

    294 struct list_head modules_which_use_me,指向一个链表,链表中的模块均依靠当前模块。

     

    在介绍了module{}数据结构后,也许你还是觉得似懂非懂,那是因为其中有很多概念和相关的数据结构你还不了解。例如kernel_symbol{} (见include/linux/module.h)

    struct kernel_symbol

    {

           unsigned long value;

           const char *name;

    };

    这个结构用来保存目标代码中的内核符号。在编译的时候,编译器将该模块中定义的内核符号写入到文件中,在读取文件装入模块的时候通过这个数据结构将其中包含的符号信息读入。

    value定义了内核符号的入口地址

    name指向内核符号的名称

     

     

    2.4.2 实现函数

    接下来,我们要研究一下源代码中的几个重要的函数。正如前段所述,操作系统初始化时,static LIST_HEAD(modules)已经建立了一个空链表。之后,每装入一个内核模块,则创建一个module结构,并把它链接到modules链表中。

    我们知道,从操作系统内核角度说,它提供用户的服务,都通过系统调用这个唯一的界面实现。那么,有关内核模块的服务又是怎么做的呢?请参看arch/i386/kernel/syscall_table.S,2.6.15版本的内核,通过系统调用init_module装入内核模块,通过系统调用delete_module卸载内核模块,没有其它途径。这下,代码阅读变得简单了。

    kernel/module.c
    
    1931 asmlinkage long
    
    1932 sys_init_module(void __user *umod,
    
    1933              unsigned long len,
    
    1934              const char __user *uargs)
    
    1935 {
    
    1936       struct module *mod;
    
    1937       int ret = 0;
    
    1938
    
    1939       /* Must have permission */
    
    1940       if (!capable(CAP_SYS_MODULE))
    
    1941             return -EPERM;
    
    1942
    
    1943       /* Only one module load at a time, please */
    
    1944       if (down_interruptible(&module_mutex) != 0)
    
    1945              return -EINTR;
    
    1946
    
    1947       /* Do all the hard work */
    
    1948       mod = load_module(umod, len, uargs);
    
    1949       if (IS_ERR(mod)) {
    
    1950             up(&module_mutex);
    
    1951             return PTR_ERR(mod);
    
    1952       }
    
    1953
    
    1954       /* Now sew it into the lists.  They won't access us, since
    
    1955         strong_try_module_get() will fail. */
    
    1956       stop_machine_run(__link_module, mod, NR_CPUS);
    
    1957
    
    1958       /* Drop lock so they can recurse */
    
    1959       up(&module_mutex);
    
    1960
    
    1961       down(&notify_mutex);
    
    1962       notifier_call_chain(&module_notify_list, MODULE_STATE_COMING, mod);
    
    1963       up(&notify_mutex);
    
    1964
    
    1965       /* Start the module */
    
    1966       if (mod->init != NULL)
    
    1967             ret = mod->init();
    
    1968       if (ret < 0) {
    
    1969             /* Init routine failed: abort.  Try to protect us from
    
    1970               buggy refcounters. */
    
    1971             mod->state = MODULE_STATE_GOING;
    
    1972             synchronize_sched();
    
    1973             if (mod->unsafe)
    
    1974                   printk(KERN_ERR "%s: module is now stuck!\n",
    
    1975                         mod->name);
    
    1976             else {
    
    1977                   module_put(mod);
    
    1978                   down(&module_mutex);
    
    1979                   free_module(mod);
    
    1980                   up(&module_mutex);
    
    1981             }
    
    1982             return ret;
    
    1983       }
    
    1984
    
    1985       /* Now it's a first class citizen! */
    
    1986       down(&module_mutex);
    
    1987       mod->state = MODULE_STATE_LIVE;
    
    1988       /* Drop initial reference. */
    
    1989       module_put(mod);
    
    1990       module_free(mod, mod->module_init);
    
    1991       mod->module_init = NULL;
    
    1992       mod->init_size = 0;
    
    1993       mod->init_text_size = 0;
    
    1994       up(&module_mutex);
    
    1995
    
    1996       return 0;
    
    1997 }
    
    

    函数sys_init_module()是系统调用init_module( )的实现。入口参数umod指向用户空间中该内核模块image所在的位置。image以ELF的可执行文件格式保存,image的最前部是elf_ehdr类型结构,长度由len指示。uargs指向来自用户空间的参数。系统调用init_module( )的语法原型为:

    long sys_init_module(void *umod, unsigned long len, const char *uargs);

     

    1940-1941 调用capable( )函数验证是否有权限装入内核模块。

    1944-1945 在并发运行环境里,仍然需保证,每次最多只有一个module准备装入。这通过down_interruptible(&module_mutex)实现。

    1948-1952 调用load_module()函数,将指定的内核模块读入内核空间。这包括申请内核空间,装配全程量符号表,赋值__ksymtab、__ksymtab_gpl、__param等变量,检验内核模块版本号,复制用户参数,确认modules链表中没有重复的模块,模块状态设置为MODULE_STATE_COMING,设置license信息,等等。

    1956      将这个内核模块插入至modules链表的前部,也即将modules指向这个内核模块的module结构。

    1966-1983 执行内核模块的初始化函数,也就是表6-1所述的入口函数。

    1987      将内核模块的状态设为MODULE_STATE_LIVE。从此,内核模块装入成功。

    /kernel/module.c
    
    573  asmlinkage long
    
    574  sys_delete_module(const char __user *name_user, unsigned int flags)
    
    575  {
    
    576        struct module *mod;
    
    577        char name[MODULE_NAME_LEN];
    
    578        int ret, forced = 0;
    
    579
    
    580        if (!capable(CAP_SYS_MODULE))
    
    581              return -EPERM;
    
    582
    
    583        if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)
    
    584              return -EFAULT;
    
    585        name[MODULE_NAME_LEN-1] = '\0';
    
    586
    
    587        if (down_interruptible(&module_mutex) != 0)
    
    588               return -EINTR;
    
    589
    
    590        mod = find_module(name);
    
    591        if (!mod) {
    
    592              ret = -ENOENT;
    
    593              goto out;
    
    594        }
    
    595
    
    596        if (!list_empty(&mod->modules_which_use_me)) {
    
    597              /* Other modules depend on us: get rid of them first. */
    
    598              ret = -EWOULDBLOCK;
    
    599              goto out;
    
    600        }
    
    601
    
    602        /* Doing init or already dying? */
    
    603        if (mod->state != MODULE_STATE_LIVE) {
    
    604               /* FIXME: if (force), slam module count and wake up
    
    605                 waiter --RR */
    
    606              DEBUGP("%s already dying\n", mod->name);
    
    607              ret = -EBUSY;
    
    608              goto out;
    
    609        }
    
    610
    
    611        /* If it has an init func, it must have an exit func to unload */
    
    612        if ((mod->init != NULL && mod->exit == NULL)
    
    613            || mod->unsafe) {
    
    614                forced = try_force_unload(flags);
    
    615                if (!forced) {
    
    616                    /* This module can't be removed */
    
    617                    ret = -EBUSY;
    
    618                    goto out;
    
    619              }
    
    620        }
    
    621
    
    622        /* Set this up before setting mod->state */
    
    623        mod->waiter = current;
    
    624
    
    625        /* Stop the machine so refcounts can't move and disable module. */
    
    626        ret = try_stop_module(mod, flags, &forced);
    
    627        if (ret != 0)
    
    628             goto out;
    
    629
    
    630        /* Never wait if forced. */
    
    631        if (!forced && module_refcount(mod) != 0)
    
    632             wait_for_zero_refcount(mod);
    
    633
    
    634        /* Final destruction now noone is using it. */
    
    635        if (mod->exit != NULL) {
    
    636              up(&module_mutex);
    
    637              mod->exit();
    
    638              down(&module_mutex);
    
    639        }
    
    640        free_module(mod);
    
    641
    
    642  out:
    
    643        up(&module_mutex);
    
    644        return ret;
    
    645  }

    函数sys_delete_module()是系统调用delete_module()的实现。调用这个函数的作用是删除一个系统已经加载的内核模块。入口参数name_user是要删除的模块的名称。

     

    580-581 调用capable( )函数,验证是否有权限操作内核模块。

    583-585 取得该模块的名称

    590-594 从modules链表中,找到该模块

    597-599 如果存在其它内核模块,它们依赖该模块,那么,不能删除。

    635-638 执行内核模块的exit函数,也就是表6-1所述的出口函数。

    640     释放module结构占用的内核空间。

     

    源代码的内容就看到这里。kernel/module.c文件里还有一些其他的函数,如果你有兴趣可以自己尝试着分析一下,对于模块机制的实现会有更深的理解。

    1. 使用内核模块

    3.1 模块的加载

    系统调用当然是将内核模块插入到内核的可行方法。但是毕竟太底层了。此外,Linux环境里还有两种方法可达到此目的。一种方法稍微自动一些,可以做到需要时自动装入,不需要时自动卸载。这种方法需要执行modprobe程序。我们待一会介绍modprobe。

    另一种是用insmod命令,手工装入内核模块。在前面分析helloworld例子的时候,我们提到过insmod的作用就是将需要插入的模块以目标代码的形式插入到内核中。注意,只有超级用户才能使用这个命令。insmod的格式是:

    # insmod  [path]modulename.ko

     

    insmod其实是一个modutils模块实用程序,当我们以超级用户的身份使用这个命令的时候,这个程序完成下面一系列工作:

    1. 从命令行中读入要链接的模块名,通常是扩展名为“.ko”,elf格式的目标文件。
    2. 确定模块对象代码所在文件的位置。通常这个文件都是在lib/modules的某个子目录中。
    3. 计算存放模块代码、模块名和module对象所需要的内存大小。
    4. 在用户空间中分配一个内存区,把module对象、模块名以及为正在运行的内核所重定位的模块代码拷贝到这个内存里。其中,module对象中的init域指向这个模块的入口函数重新分配到的地址;exit域指向出口函数所重新分配的地址。
    5. 调用init_module(),向它传递上面所创建的用户态的内存区的地址,其实现过程我们已经详细分析过了。
    6. 释放用户态内存, 整个过程结束。

     

    3.2 模块的卸载

    要卸载一个内核模块使用rmmod命令。rmmod程序将已经插入内核的模块从内核中移出,rmmod会自动运行在内核模块自己定义的出口函数。它的格式是:

    # rmmod  [path]modulename

     

    当然,它最终还是通过delete_module()系统调用实现的。

     

    3.3 模块实用程序modutils

    Linux内核模块机制提供的系统调用大多数都是为modutils程序使用的。可以说,是Linux的内核模块机制和modutils两者的结合提供了模块的编程接口。modutils(modutils-x.y.z.tar.gz)可以在任何获得内核源代码的地方获得, 选择最高级别的patchlevel x.y.z等于或者小于当前的内核版本,安装后在/sbin目录下就会有insmod、rmmod、ksyms、lsmod、modprobe等等实用程序。当然,通常我们在加载Linux内核的时候,modutils已经被装入了。

     

    1. lsmod的使用

    调用lsmod 程序将显示当前系统中正在使用的模块信息。 实际上这个程序的功能就是读取/proc文件系统中的文件/proc/modules中的信息。所以这个命令和cat  /proc/modules等价。它的格式就是:

    # lsmod

     

    1. ksyms

    显示内核符号和模块符号表的信息,可以读取/proc/kallsyms文件。

     

    1. modprobe的使用

    modprobe是由modutils提供的根据模块之间的依赖性自动插入模块的程序。前面讲到的按需装入的模块加载方法会调用这个程序来实现按需装入的功能。举例来讲,如果模块A依赖模块B,而模块B并没有加载到内核里,当系统请求加载模块A时,modprobe程序会自动将模块B加载到内核。

    与insmod类似,modprobe程序也是链接在命令行中指定的一个模块,但它还可以递归地链接指定模块所引用到的其他模块。从实现上讲,modprobe只是检查模块依赖关系,真正的加载的工作还是由insmod来实现的。那么,它又是怎么知道模块间的依赖关系的呢? 简单的讲,modprobe通过另一个modutils程序depmod来了解这种依赖关系。而depmod是通过查找内核中所有的模块并把它们之间的依赖关系写入/lib/modules/2.6.15-1.2054_FC5目录下,一个名为modules.dep的文件。

     

    1. kmod的实现

    在以前版本的内核中,模块机制的自动装入通过一个用户进程kerneld来实现,内核通过IPC和内核通信,向kerneld发送需要装载的模块的信息,然后kerneld调用modprobe程序将这个模块装载。 但是在最近版本的内核中,使用另外一种方法kmod来实现这个功能。kmod与kerneld比较,最大的不同在于它是一个运行在内核空间的进程,它可以在内核空间直接调用modprobe,大大简化了整个流程。

    1. 实例

    为了便于更直观地认识内核模块的功能,下面用实例来说明模块单元是怎样与系统内核交互的。

    4.1 内核模块的make文件

    首先我们来看一看模块程序的make文件应该怎么写。自2.6版本之后,Linux对内核模块的相关规范,有很大变动。例如,所有模块的扩张名,都从“.o”改为“.ko”。详细信息,可参看Documentation/kbuild/makefiles.txt。针对内核模块而编辑Makefile,可参看Documentation/kbuild/modules.txt

    我们练习“helloworld.ko”时,曾经用过简单的Makefile:

    TARGET = helloworld
    
    KDIR = /usr/src/linux
    
    PWD = $(shell pwd)
    
    obj-m += $(TARGET).o
    
    default:
    
           make -C $(KDIR) M=$(PWD) modules

    $(KDIR)表示源代码最高层目录的位置。

    “obj-m += $(TARGET).o”告诉kbuild,希望将$(TARGET),也就是helloworld,编译成内核模块。

    “M=$(PWD)”表示生成的模块文件都将在当前目录下。

     

    4.2 多文件内核模块的 make文件

    现在,我们把问题引申一下,对于多文件的内核模块该如何编译呢?同样以“Hello,world”为例,我们需要做以下事情:

    在所有的源文件中,只有一个文件增加一行#define __NO_VERSION__。这是因为module.h一般包括全局变量kernel_version的定义,该全局变量包含模块编译的内核版本信息。如果你需要version.h,你需要自己把它包含进去,因为定义了 __NO_VERSION__后module.h就不会包含version.h。

     

    下面给出多文件的内核模块的范例。  

    Start.c

    /* start.c
    
    *
    
     * "Hello, world" –内核模块版本
    
    * 这个文件仅包括启动模块例程
    
     */
    
       
    
    /* 必要的头文件 */
    
    
    
    /* 内核模块中的标准 */
    
    #include <linux/kernel.h>   /*我们在做内核的工作 */
    
    #include <linux/module.h> 
    
    
    
    /*初始化模块 */
    
    int init_module()
    
    {
    
      printk("Hello, world!\n");
    
    
    
    /* 如果我们返回一个非零值, 那就意味着
    
    * init_module 初始化失败并且内核模块
    
    * 不能加载 */
    
      return 0;
    
    }

    stop.c

    /* stop.c
    
    *"Hello, world" -内核模块版本
    
    *这个文件仅包括关闭模块例程
    
     */
       
    
    /*必要的头文件 */
    /*内核模块中的标准 */
    #include <linux/kernel.h>   /*我们在做内核的工作 */
    #define __NO_VERSION__     
    #include <linux/module.h>  
    #include <linux/version.h>   /* 不被module.h包括,因为__NO_VERSION__ */
    
    /* Cleanup - 撤消 init_module 所做的任何事情*/
    
    void cleanup_module()
    
    {
    
      printk("Bye!\n");
    
    }
    
    /*结束*/

    这一次,helloworld内核模块包含了两个源文件,“start.c”和“stop.c”。再来看看对于多文件内核模块,该怎么写Makefile文件

    Makefile

    TARGET = helloworld
    
    KDIR = /usr/src/linux
    
    PWD = $(shell pwd)
    
    obj-m += $(TARGET).o
    
    $(TARGET)-y := start.o stop.o
    
    default:
    
        make -C $(KDIR) M=$(PWD) modules

     

     

    相比前面,只增加一行:

    $(TARGET)-y := start.o stop.o

     

    实验思考

    内核模块机制,和/proc文件系统,都是Linux系统中具有代表性的特征。可否利用这些便利,为特殊文件、设备、公共变量等,创建/proc目录下对应的节点?答案当然是肯定的。

    这块实验需要自主完成,本书只交待基本思路和部分源代码。程序的完善,以及调试工作,留给大家完成。

    内核模块与内核空间之外的交互方式有很多种,/proc文件系统是其中一种主要方式。

    本书有专门章节介绍/proc文件系统,在这里我们再把一些基本知识复习一下。文件系统是操作系统在磁盘或其它外设上,组织文件的方法。Linux支持很多文件系统的类型:minix,ext,ext2,msdos,umsdos,vfat,proc,nfs,iso9660,hpfs,sysv,smb,ncpfs等等。与其他文件系统不同的是,/proc文件系统是一个伪文件系统。之所以称之为伪文件系统,是因为它没有任何一部分与磁盘有关,只存在内存当中,而不占用外存空间。而它确实与文件系统有很多相似之处。例如,以文件系统的方式为访问系统内核数据的操作提供接口,而且可以用所有一般的文件工具操作。例如我们可以通过命令cat,more或其他文本编辑工具察看proc文件中的信息。更重要的是,用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc是动态从系统内核读出所需信息并提交的。/proc文件系统一般放在/proc目录下。

    怎么让/proc文件系统反映内核模块的状态呢?我们来看看下面这个稍微复杂一些的实例。

    proc_example.c
    
    …………
    
    int init_module()
    
    {
    
                int rv = 0;
    
       
    
                /* 创建目录 */
    
                example_dir = proc_mkdir(MODULE_NAME, NULL);
    
                if(example_dir == NULL) {
    
                        rv = -ENOMEM;
    
                        goto out;
    
                }
    
                example_dir->owner = THIS_MODULE;
    
               
    
                /* 快速创建只读文件 jiffies */
    
                jiffies_file = create_proc_read_entry("jiffies", 0444, example_dir,
    
                                               proc_read_jiffies, NULL);
    
                if(jiffies_file == NULL) {
    
                        rv  = -ENOMEM;
    
                        goto no_jiffies;
    
                }
    
                jiffies_file->owner = THIS_MODULE;
    
       
    
                /* 创建规则文件foo 和 bar */
    
                foo_file = create_proc_entry("foo", 0644, example_dir);
    
                if(foo_file == NULL) {
    
                        rv = -ENOMEM;
    
                        goto no_foo;
    
                }
    
                strcpy(foo_data.name, "foo");
    
                strcpy(foo_data.value, "foo");
    
                foo_file->data = &foo_data;
    
                foo_file->read_proc = proc_read_foobar;
    
                foo_file->write_proc = proc_write_foobar;
    
                foo_file->owner = THIS_MODULE;
    
                       
    
                bar_file = create_proc_entry("bar", 0644, example_dir);
    
                if(bar_file == NULL) {
    
                        rv = -ENOMEM;
    
                        goto no_bar;
    
                }
    
                strcpy(bar_data.name, "bar");
    
                strcpy(bar_data.value, "bar");
    
                bar_file->data = &bar_data;
    
                bar_file->read_proc = proc_read_foobar;
    
                bar_file->write_proc = proc_write_foobar;
    
                bar_file->owner = THIS_MODULE;
    
                   
    
           /* 创建设备文件 tty */
    
                tty_device = proc_mknod("tty", S_IFCHR | 0666, example_dir, MKDEV(5, 0));
    
                if(tty_device == NULL) {
    
                        rv = -ENOMEM;
    
                        goto no_tty;
    
                }
    
                tty_device->owner = THIS_MODULE;
    
       
    
                /* 创建链接文件jiffies_too */
    
                symlink = proc_symlink("jiffies_too", example_dir, "jiffies");
    
                if(symlink == NULL) {
    
                        rv = -ENOMEM;
    
                        goto no_symlink;
    
                }
    
                symlink->owner = THIS_MODULE;
    
       
    
                /* 所有创建都成功 */
    
                printk(KERN_INFO "%s %s initialised\n",
    
                       MODULE_NAME, MODULE_VERSION);
    
                return 0;
    
        /*出错处理*/
    
        no_symlink:  remove_proc_entry("tty", example_dir);
    
        no_tty:      remove_proc_entry("bar", example_dir);
    
        no_bar:      remove_proc_entry("foo", example_dir);
    
        no_foo:      remove_proc_entry("jiffies", example_dir);
    
        no_jiffies:    remove_proc_entry(MODULE_NAME, NULL);
    
        out:        return rv;
    
        }
    
        …………
    
    

    内核模块proc_example首先在/proc目录下创建自己的子目录proc_example。然后在这个目录下创建了三个proc普通文件(foo,bar,jiffies),一个设备文件(tty)以及一个文件链接(jiffies_too)。具体来说,foo和bar是两个可读写文件,它们共享函数proc_read_foobar和proc_write_foobar。jiffies是一个只读文件,取得当前系统时间jiffies。jiffies_too为文件jiffies的一个符号链接。

    展开全文
  • Linux内核模块编程

    千次阅读 2018-10-24 16:47:13
    Linux内核模块编程 (作者:Baron_wu 禁止转载) 首先,创建一个内核模块并插入Linux内核中。这是实验第一部分 首先查看当前内核模块使用情概况:lsmod Module:模块名 Size:模块大小 Used by:这些模块在哪被使用 ...

    Linux内核模块编程

    (作者:Baron_wu 禁止转载)
    首先,创建一个内核模块并插入Linux内核中。这是实验第一部分
    首先查看当前内核模块使用情概况:lsmod
    在这里插入图片描述
    Module:模块名
    Size:模块大小
    Used by:这些模块在哪被使用
    接下来编写一个simple.c的程序,当加载和卸载内核模块时给出适当的消息。
    代码如下:

     #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    
    /* This function is called when the module is loaded. */
    int simple_init(void)
    {
           printk(KERN_INFO "Loading Module\n");
    
           return 0;
    }
    
    /* This function is called when the module is removed. */
    void simple_exit(void) {
    	printk(KERN_INFO "Removing Module\n");
    }
    
    /* Macros for registering module entry and exit points. */
    module_init( simple_init );
    module_exit( simple_exit );
    
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("Simple Module");
    MODULE_AUTHOR("SGG");
    

    Simple_list (模块入口点) 返回0代表成功 其他值代表失败
    Simple_exit(模块退出点) 无返回值
    两个函数都没有参数
    接下来两个宏指令是注册模块的入口与出口
    Module_init()
    Module_exit()

    printk()

    注意入口点与出口点是如何调用系统函数printk的。Printk()是内核对应的printf(),输出传给内核加载模块,通过使用命令dmesg来查看他的内容。Printk()允许指定优先级标志。它的值在包含文件<linux/printk.h>中给出。
    在这个实例中KERN_INFO是优先的。它就像被定义的一个信号消息。
    最后三行属于软件许可信息,是内核模块开发的标准格式。
    Simple.c用Makefile这个文件来进行编译。在终端输入make进行编译。

    Makefile文件内容

    obj-m += simple.o
    all:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    clean:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
    

    编译后将会生成多个文件, 文件simple.ko代表编译后生成的内核模块。
    首先在含有simple.c 和Makefile的文件目录下执行make命令(我的这两个文件在桌面上)
    在这里插入图片描述
    之后将会生成许多文件如下所示:
    在这里插入图片描述
    == Simple.ko是生成的内核模块 ==
    接下来便是加载和移除内核模块的演示了。
    首先在之前生成的编译模块的目录中执行命令:sudo insmod simple.ko 来加载内核模块
    在这里插入图片描述
    紧接着输入dmesg来查看当前内核加载情况。
    在这里插入图片描述
    在最后一行可以看到加载成功的消息。
    **之后要进行的便是移除内核模块的操作。**在终端输入命令:sudo rmmod simple
    在这里插入图片描述
    接着查看是否移除成功。输入命令:dmesg
    在这里插入图片描述
    可以看到已经成功移除。
    因为内核记录很快就会填满 所以定期清除缓冲区是有必要的。
    执行命令:sudo dmesg -c
    到此,Linux内核模块编程第一部分结束。

    展开全文
  • Linux编写内核模块及文件读写

    万次阅读 2018-01-18 15:44:55
    sysfs是什么sysfs是一个基于内存的文件系统,它的作用是将内核信息以文件的方式提供给用户程序使用。该文件系统的目录层次结构严格按照内核的数据结构组织。除了二进制文件外(只有特殊场合才使用...Linux内核模块是什
  • linux内核模块编译

    千次阅读 2017-09-01 23:39:12
    最近在学习linux内核模块,在初次编译时遇到了不少坑,下面是完整的内核模块编译流程。 写了一个简单的hello_world.c文件作为内核模块学习的第一步,代码如下: #include #include static int __init hello...
  • Linux内核模块编译

    千次阅读 2018-07-25 19:12:52
    Linux内核模块是一种可被动态加载和卸载的可执行程序。通过内核模块可以扩展内核功能,内核模块通常用于设备驱动、文件系统等。如果没有内核模块,需要向内核添加功能就需要自发代码、重新编译内核、安装新内核等...
  • Linux内核模块简介

    千次阅读 2016-08-27 10:22:12
    Linux内核模块简介
  • Linux内核模块

    千次阅读 2007-11-02 08:56:00
    Linux设备驱动会以内核模块的形式出现,因此,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。4.1~4.2节讲解了Linux内核模块的概念和结构,4.3~4.8节对Linux内核模块的各个组成部分进行了展现,4.1~4.2...
  • Linux内核驱动模块

    千次阅读 2013-12-02 10:07:13
    4.1~4.2节讲解了Linux内核模块的概念和结构,4.3~4.8节对Linux内核模块的各个组成部分进行了展现,4.1~4.2与4.3~4.8节是整体与部分的关系。 4.9节说明了独立存在的Linux内核模块的Makefile文件编写方法和模
  • Linux内核模块在编译的时候可以通过命令编译: make -C 内核源码绝对路径 M=模块源码文件所在的绝对路径 modules 此命令的解释: 1)进入到内核源码目录 2)读取内核源码目录的Makefile 3)执行内核源码Makefile...
  • linux 内核模块

    千次阅读 2013-06-05 16:58:03
    内核模块Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的...
  • 编写一个Linux内核模块,其功能是遍历操作系统所有进程。该内核模块输出系统中:每个进程的名字、进程pid、进程的状态、父进程的名字;以及统计系统中进程个数,包括统计系统中TASK_RUNNING、TASK_INTERRUPTIBLE、...
  • 1添加最简单的Linux内核模块

    千次阅读 2020-02-01 13:28:23
    1添加最简单的Linux内核模块 一.实验目的 1 熟练掌握基本的Linux内核模块开发框架和编译方法。 2 熟练掌握Linux内核模块添加流程。 3 理解Linux内核模块代码中的一些常见宏和参数。 4 掌握Linux内核模块程序和应用...
  • linux内核模块加载命令

    千次阅读 2021-01-04 20:25:48
    1、lsmod 列加以挂载的内核模块; lsmod 是列出目前系统中已加载的模块的名称及大小等;另外我们还可以查看 /proc/modules ,我们一样可以知道系统已经加载的模块; [root@localhost beinan]# lsmod 2、modinfo ...
  • Linux 内核模块makefile

    万次阅读 2015-07-17 00:37:16
    = /linux-2.6.29.4/linux-2.6.29.4 #定义内核路径 PWD := $(shell pwd) #获取当前模块路径 modules: #标识符,以冒号结尾,此处表示makefile文件的一个功能选项 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
  • linux内核模块编译 两种编译

    万次阅读 2018-04-09 18:51:27
    linux内核模块的编译方法有两种: 1.放入linux内核源码中编译。 2.独立编译模块。 (1)放入linux内核源码中编译 这里先了解Kconfig和Makefile的作用 Kconfig:对应内核模块的配置菜单。 Makefile:对应内核...
  • /proc是Linux上的一种虚拟文件系统,存储的...现编写一个内核模块,在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world。 实验环境 Ubuntu 18.04.1,源码内核版本:Li...
  • Linux | 内核 | 内核模块(LKM)基础

    千次阅读 2019-04-21 18:29:41
    LKM(Linux kernel module)作为Linux内核的插件,其安装和卸载都很方便(热插拔),可以满足一些需要特殊内核操作而不想重新编译整个内核的场景,在存储和安全厂商的产品中,LKM使用非常广泛。 Demo1 下面以一个...
  • linux内核模块编程

    千次阅读 2010-06-14 11:42:00
    主题: linux内核模块的程序结构--模块加载函数(必须),模块卸载函数(必须),模块许可证声明(必须),模块参数(可选),模块导出符号(可选),模块作者的等信息声明(可选)一个linux内核模块主要由以下几个部分...
  • linux 内核模块函数调用

    千次阅读 2015-01-08 19:57:17
    在编写linux内核模块的时候,有时候我们需要调用一只内核模块里面的函数,然而如果是在不同目录下面编译生成的内核模块,此时A模块去调用B模块的函数时候会出现函数未定义,无法调用的情况。那么以前我是在同一个...
  • linux内核模块调用应用层程序

    千次阅读 2018-11-30 18:15:52
    内核模块代码 #include &lt;linux/module.h&gt; #include &lt;linux/init.h&gt; #include &lt;linux/kernel.h&gt; #include &lt;linux/types.h&gt; #include &lt;linux/kmod....
  • Linux内核模块设计

    千次阅读 2010-11-06 12:53:00
    UNIT 1 Linux内核模块设计             1. 单体内核 VS 微内核 2. Hello,kernel模块实例 3. 内核模块的Makefile 4. 模块的加载与测试 5. 内核...
  • Linux内核模块编程与内核模块LICENSE——《Linux设备驱动开发详解(第3版)》预读
  • 如何编译Linux 内核模块

    万次阅读 2018-11-03 09:44:58
    ubuntu版本:ubuntu-gnome-16.04-desktop-amd64,gnome版 ---------------------------------------------------------...本文主要介绍如何在内核外编译内核模块,即: how to build an out-of-tree kernel module...
  • linux内核模块makefile

    千次阅读 2017-05-26 09:42:13
    #linux内核模块makefile #General Purpose Makefile for Linux Kernel module by guoqingbo KERN_DIR = /home/gqb/development/linux-kernel-2.6.37 #内核源码路径 #KERN_DIR = /usr/src/$(shell uname -r)
  • 今天尝试操作一个最简单的linux内核模块:hello world,也是困难重重。 跪点在于:如何编译 通过各种查看资料,在linux操作系统中,一般是不含有源码的,也就是所说的内核树。那么,如果想要调用源码中的头文件,就...
  • 本文提供一个替换open系统调用的样例代码,使用Linux内核模块实现。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 220,556
精华内容 88,222
关键字:

linux自己的内核模块

linux 订阅