zoukankan      html  css  js  c++  java
  • 【Linux开发】内核模块简介

    一. 摘要

    这篇文章主要介绍了Linux内核模块的相关概念,以及简单的模块开发过程。主要从模块开发中的常用指令、内核模块程序的结构、模块使用计数以及模块的编译等角度对内核模块进行介绍。在Linux系统开发过程中,以模块的形式开发其重要性不言自明,而在嵌入式设备驱动开发中将驱动程序以模块的形式发布,更是极大地提高了设备使用的灵活性——用户只需要拿到相关驱动模块,再插入到用户的内核中,即可灵活地使用你的设备。

    二. 文章提纲

    1. 摘要

    2. 文章提纲

    3. 概述

    4. 模块开发常用的指令

    5. 内核模块程序结构

    6. 模块使用计数

    7. 模块的编译

    8. 使用模块绕开GPL

    9. 总结

    三.概述

    Linux内核整体结构已经很庞大,包含了很多的组件,而对于我们工程师而言,有两种方法将需要的功能包含进内核当中。

             一:将所有的功能都编译进Linux内核。

             二:将需要的功能编译成模块,在需要的时候动态地添加。

    上述两种方式优缺点分析:

             第一种:

                       优点:不会有版本不兼容的问题,不需要进行严格的版本检查

                       缺点:生成的内核会很大;要在现有的内核中添加新的功能,则要编译整个内核

             第二种:

    优点:模块本身不编译进内核,从而控制了内核的大小;模块一旦被加载,将和其它的部分完全一样。

    缺点:可能会有内核与模块版本不兼容的问题,导致内核崩溃;会造成内存的利用率比较低。

     

    四.模块开发常用的指令

    在内核模块开发的过程中常用的有以下指令。

    1)  insmod: 将模块插入内核中,使用方法:#insmod XXX.ko

    2)  rmmod: 将模块从内核中删除,使用方法:#rmmod XXX.ko

    3)  lsmod: 列表显示所有的内核模块,可以和grep指令结合使用。使用方法:#lsmod | grep XXX

    4)  modprobe: modprobe可载入指定的个别模块,或是载入一组相依赖的模块。modprobe会根据depmod所产生的依赖关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。依赖关系是通过读取 /lib/modules/2.6.xx/modules.dep得到的。而该文件是通过depmod 所建立。

    5)  modinfo: 查看模块信息。使用方法:#modinfo XXX.ko

    6)  tree –a: 查看当前目录的整个树结构。使用方法:#tree -a

     

    五.内核模块程序结构

    1)  模块加载函数(一般需要)

    在用insmod或modprobe命令加载模块时,该函数被执行。完成模块的初始化工作。

    Linux内核的模块加载函数一般用__init标识声明,模块加载函数必须以module_init(函数名)的形式被指定。该函数返回整型值,如果执行成功,则返回0,初始化失败时则返回错误编码,Linux内核当中的错误编码是负值,在<linux/errno.h>中定义。

    在Linux中,标识__init的函数在连接时放在.init.text这个区段,而且在.initcall.init中保留一份函数指针,初始化的时候内核会根据这些指针调用初始化函数,初始化结束后释放这些init区段(包括前两者)。

    代码清单:

    复制代码
     1 static int __init XXX_init(void)
     2 
     3 {
     4 
     5          return 0;
     6 }
     7 
     8  
     9 
    10 moudle_init(XXX_init);
    复制代码

     

    2)  模块卸载函数(一般需要)

    在用rmmod或modprobe命令卸载模块时,该函数被执行。完成与加载相反的工作。

    模块的卸载函数和模块加载函数实现相反的功能,主要包括

    1. 若模块加载函数注册了XXX,则模块卸载函数注销XXX
    2. 若模块加载函数动态分配了内存,则模块卸载函数释放这些内存
    3. 若模块加载函数申请了硬件资源,则模块卸载函数释放这些硬件资源
    4. 若模块加载函数开启了硬件资源,则模块卸载函数一定要关闭这些资源

    代码清单:

    复制代码
    1 static void __exit XXX_exit(void)
    2 
    3 {
    4 
    5 }
    6 
    7  
    8 
    9 moudle_exit(XXX_exit);
    复制代码

     

    3)  模块许可证声明(必须)

    如果不声明,则在模块加载时会收到内核被污染的警告,一般应遵循GPL协议。

    代码清单:

    1 MODULE_LICENSE("GPL");

     

    4)  模块参数(可选)

    模块在被加载时传递给模块的值,本身应该是模块内部的全局变量。

    示例程序book.c

    复制代码
     1 #include <linux/init.h>
     2 
     3 #include <linux/module.h>
     4 
     5  
     6 
     7 static char *bookName = "Good Book.";
     8 
     9 static int bookNumber = 100;
    10 
    11  
    12 
    13 static int __init book_init(void)
    14 
    15 {
    16 
    17          printk(KERN_INFO "Book name is %s
    ", bookName);
    18 
    19          printk(KERN_INFO "Book number is %d
    ", bookNumber);
    20 
    21          return 0;
    22 
    23 }
    24 
    25  
    26 
    27 static void __exit book_exit(void)
    28 
    29 {
    30 
    31          printk(KERN_INFO "Book module exit.
    ");
    32 
    33 }
    34 
    35  
    36 
    37 module_init(book_init);
    38 
    39 module_exit(book_exit);
    40 
    41 module_param(bookName, charp, S_IRUGO);
    42 
    43 module_param(bookNumber, int, S_IRUGO);
    44 
    45  
    46 
    47 MODULE_LICENSE("GPL");
    复制代码

     

    在向内核插入模块的时候可以用以下方式,并且可以在内核日志中看到模块加载以后变量已经有了值。

     

     

    5)  模块导出符号(可选)

    使用模块导出符号,方便其它模块依赖于该模块,并使用模块中的变量和函数等。

    在Linux2.6的内核中,/proc/kallsyms文件对应着符号表,它记录了符号和符号对应的内存地址。对于模块而言,使用下面的宏可以导出符号。

    1 EXPORT_SYMBOL(符号名);

    1 EXPORT_GPL_SYMBOL(符号名);

     

    6)  模块信息(可选)

    模块信息则是指模块的作者信息等。

     

    六.模块使用计数

    Linux内核提供了MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT宏来管理模块使用计数。但是对于内核模块而言,一般不会自己管理使用计数。

     

    七.模块的编译

    将下面的Makefile文件放在book.c同级的目录下,然后使用#make命令或者#make all命令编译即可生成book.ko模块文件。

    对应的Makefile:

    复制代码
     1 ifneq ($(KERNELRELEASE),)
     2 
     3 mymodule_objs := book.o
     4 
     5 obj-m := book.o
     6 
     7 else
     8 
     9 PWD := $(shell pwd)
    10 
    11 KVER ?= $(shell uname -r)
    12 
    13 KDIR := /usr/src/linux-headers-2.6.38-8-generic
    14 
    15  
    16 
    17 all:
    18 
    19          $(MAKE) -C $(KDIR) M=$(PWD)
    20 
    21 clean:
    22 
    23          rm -rf *.mod.c *.mod.o *.ko *.o *.tmp_versions *.order *symvers
    24 
    25 endif
    复制代码

     

    八.使用模块绕开

    如果功能不编译成模块,则无法绕开GPL,编译成模块后公司发布产品则只需要发布模块,而不需要发布源码。为了Linux系统能够支持模块,需要做以下的工作:

    1. 内核编译时选择“可以加载模块”,嵌入式产品一般都不需要卸载模块,则可以不选择“可卸载模块”
    2. 将我们的ko文件放在文件系统中
    3. Linux系统实现了insmod、rmmod等工具
    4. 使用时可以用insmod手动加载模块,也可以修改/etc/init.d/rcS文件,从而在系统启动的时候就加载模块。

     

    九.总结

    本文主要介绍内核模块的概念和基本编程方法,内核模块主要由加载、卸载函数功能函数等一系列声明组成。它可以被传入参数,也可以导出符号,供其它的模块使用。

  • 相关阅读:
    排序希尔排序
    Sentinel 限流
    排序基数排序
    【ProToolkit范例之三】遍历模型下的所有Feature
    【ProToolkit范例之四】创建udf
    【ProToolkit范例之五】输出Asm的架构
    鼠标滚轮改变文本框值的jQuery插件cutePsWheel发布
    自己做手做“分享到”小工具
    深入理解jQuery AJAX调用页面内Web服务
    没错,你没有看错,博客园Android客户端开源了!
  • 原文地址:https://www.cnblogs.com/huty/p/8518620.html
Copyright © 2011-2022 走看看