Linux模块编程框架

Linux模块编程框架

Linux是单内核系统,可通用计算平台的外围设备是频繁变化的,不可能将所有的(包括将来即将出现的)设备的驱动程序都一次性编译进内核,为了解决这个问题,Linux提出了可加载内核模块(Loadable Kernel Module,LKM)的概念,允许一个设备驱动通过模块加载的方式,在内核运行起来之后"融入"内核,加载进内核的模块和本身就编译进内核的模块一模一样。
一个程序在编译的地址的相对关系就已经确定了,运行的时候只是进行简单的偏移,为了使模块加载进内核后能够被放置在正确的地址,并正确的调用系统的运行的导出符号表,编译模块的时候必须要使用系统的编译地址,并调用系统编译出得静态的导出符号表。即模块必须使用系统的配置环境:Makefile+.config,一旦这两个文件任意一个发生了变化,都很可能导致模块的编译地址与系统的编译地址不匹配,造成运行时的错误甚至宕机。

导出符号表

从提供系统运行效率的角度,一个模块不是也不应该是完全独立的,即一个模块往往会调用其他模块提供的功能来实现自己的功能,这样做能更好实现系统的分工并提高效率。Linux为了实现模块间的相互调用,设计了导出符号表,每个模块都可以将自己的一个私有的标号导出到系统层级,以使该标号对其他模块可见,系统在编译一个模块的时候会自动导出这个模块的导出符号表到modules.syms文件(如果没有导出任何符号,可以为空),并在加载一个模块的时候会自动将该模块的导出符号表与系统自身的导出符号表合并。一个系统的源码的导出符号表一般在源码顶层目录的modules.syms文件中,查看正在运行的系统导出符号表使用cat /proc/kallsyms。注意,正如前面解释的,我们的模块之所以能够正常运行,一个重要原因就是编译我们模块使用的符号地址就是编译内核时使用的符号地址,所以运行起来虽然地址会有偏移,但是模块中相关的符号的地址也会和内核地址一起偏移,也就还能找得到。基于这种思想,我们也可以直接查看系统当前运行的地址,将地址赋值给一个函数指针并使用,也是没有问题的,当然,这只是阐述原理,并不建议这么写模块。
下面这个例子可以看出编译出的地址和运行时的地址是不一样的:

导出符号表可以大大的提高系统的运行效率,这也是只有开源系统才能提供的一个强大的功能,但是,导出符号表的引入会导致一个小小的麻烦--模块的依赖,当我们使用lsmod的时候,就可以查看系统当前的模块,其最后两列分别是该模块被引用的次数以及引用该模块的内核模块,当一个模块被其他模块引用时,我们是不能进行卸载的,同样,如果模块A依赖于模块B,那么如果模块B不加载的时候模块A也加载不了。在编写多模块的时候尤其要注意这个问题,可以写一个脚本管理多个依赖模块。Linux内核使用两个宏来导出一个模块的符号

EXPORT_SYMBOL(符号名)
EXPORT_SYMBOL_GPL(符号名)

模块编译方法

借助内核的Makefile,编译出的XXX.ko(Kernel Object)就是可加载到该内核的外部模块,为了利用内核的Makefile,我们可以将编译外部模块的Makefile写成如下的格式:

ifneq ($(KERNELRELEASE),)
    export-objs = demo.o
    obj-m = extern.o
else
    KERNELDIR :=  /lib/modules/$(shell uname -r)/build
    PWD       := $(shell pwd)
endif
all:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
     .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c

这个简单的Makfile是利用ubuntu主机的源码Makefile来编译模块,学习模块编程的开始阶段在主机进行编译调试更方便一点,下面我解释一下这个Makefile,首先,我们的思路还是通过内核的Makefile来准备我们的模块,而内核的Makefile一旦执行,就会给KERNELRELEASE这个变量赋值,所以第一次进入我们这个Makfile的时候,这个变量还是空,所以执行else的部分——给相关的变量赋值,make默认编译第一个目标allmake -C $(KERNELRELEASE)就是进入到KERNELRELEASE指定的目录并执行里面的Makefile,显然,这就是我们内核源码的顶层Makefile,接下来的选项M=$(PWD) modules都是传入这个顶层Makefile的参数,表示我要编译一个模块,这个模块位于M指定的目录,所以内核会进行相关的配置并最终进入到"这个模块所在的目录",此时,我们的这个Makefile会再被进入一次,这一次是从内核Makefile中跳入这里的,,KERNELRELEASE已经被定义过,内核Makefile想要的就是obj-m后面指定的要编译的目标文件,所以内核Makfile就会找到我们写的模块源文件进行编译。如此我们就得到了能在ubuntu下执行的xxx.ko文件,如果需要在开发板上运行,只需要将内核路径改成开发板运行系统的源码路径即可,同时记得要导出相关的环境变量( ARCH, CROSS_COMPILE )

注册/注销模块

Linux为每个模块都预留了相应的地址,注册模块即让该模块对内核可见,这也是模块工作的先决条件。注册之后,我们就可以通过查看内核输出信息dmesg命令来查看模块的运行情况。经常使用内核函数printk()来输出系统信息进行打印调试。使用insmod XXX.ko加载一个模块,使用rmmod XXX.ko卸载一个模块,使用lsmod查看当前系统中的模块及其引用情况
insmod使用的是init_module()系统调用,这个系统调用的实现是sys_init_module()
rmmod使用delete_module()系统调用,这个系统调用的实现是sys_delete_module()

模块的程序框架

#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>

/* 构造/析构函数 */
static int __init mydemo_init(void)
{
    //构造设备/驱动对象
    //初始化设备/驱动对象
    //注册设备/驱动对象
    //必要的硬件初始化
}
static void __exit mydemo_exit(void)
{
    //回收资源
    //注销设备/驱动对象
}   

/* 加载/卸载模块 */
module_init(mydemo_init);
module_exit(mydemo_exit);                   

/* 授权 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XJ");
MODULE_DESCRIPTIPON("mymydemo");

/* 导出符号 */
EXPORT_SYMBOL(data);

注意这里的授权是必须的,如果一个模块没有授权,那么很多需要该授权的函数甚至都不能使用,同理,不合适的授权也会导致模块运行或加载的错误,所以初学者一定不要忽视这个授权,相关授权的选项在"linux/module.h"中,这里我把相关的说明贴出来供大家参考

/*
 * The following license idents are currently accepted as indicating free
 * software modules
 *
 *  "GPL"               [GNU Public License v2 or later]
 *  "GPL v2"            [GNU Public License v2]
 *  "GPL and additional rights" [GNU Public License v2 rights and more]
 *  "Dual BSD/GPL"          [GNU Public License v2
 *                   or BSD license choice]
 *  "Dual MIT/GPL"          [GNU Public License v2
 *                   or MIT license choice]
 *  "Dual MPL/GPL"          [GNU Public License v2
 *                   or Mozilla license choice]
 *
 * The following other idents are available
 *
 *  "Proprietary"           [Non free products]
 */

另一个细节是Linux内核源码的默认头文件路径是顶层目录的include目录,所以包含头文件的时候include可以省略,

第一个Linux模块

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>

static int __init demo_init(void)
{
    printk(KERN_INFO"demo_init:%s,%s,%d"__FILE__,__func__,__LINE__);
    return 0;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO"demo_exit:%s,%s,%d"__FILE__,__func__,__LINE__);
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");

执行insmod xjDemo.ko,查看执行结果

模块传参

我们编写的模块还可以在insmod的时候传入参数,Linux提供了几个宏(函数)用于接收外部的参数。模块内部使用这些函数,只需执行insmod xjDemo.ko num=2insmod mydemo.ko i=10insmod mydemo.ko extstr="hello" 等命令就可以将参数传入模块

module_param(num,type,perm);    //接收一个传入的int数据
module_param(num,type,perm);    //接收一个传入的charp数据
module_param_array(num,type,nump,perm);               //接收一个数组
module_param_string(name,string,len,perm);            //接收一个字符串
MODULE_PARAM_DESC("parameter description");

分类: Lv3_LinuxKernel_Analysis

标签: Linux模块编程框架

时间: 2024-07-31 10:38:22

Linux模块编程框架的相关文章

(46)LINUX应用编程和网络编程之一Linux应用编程框架

3.1.1.应用编程框架介绍 3.1.1.1.什么是应用编程 (1)整个嵌入式linux核心课程包括5个点,按照学习顺序依次是:裸机.C高级.uboot和系统移植.linux应用编程和网络编程.驱动. (2)典型的嵌入式产品就是基于嵌入式linux操作系统来工作的.典型的嵌入式产品的研发过程就是:第一步让linux系统在硬件上跑起来(系统移植工作),第二步基于linux系统来开发应用程序实现产品功能. (3)基于linux去做应用编程,其实就是通过调用linux的[系统API]来实现应用需要完成

Linux - 模块编程初试

计算机网络的课程设计要做防火墙,老师没有限制在什么系统上面做,所以决定在Linux上实现.找了一下相关的资料,发现其实Linux有提供Netfilter/Iptables,为用户提供防火墙的功能,稍微看了一下,使用Iptables能够很方便地配置用户想要的防火墙,但是好像只能做过滤.数据报修改以及网络地址转换,好像不能做获取其中信息的功能,而且看了一下网上其他人的提问或者博客,好像想做类似的功能还是需要直接使用Netfilter.而如果想要使用Netfiler的话,需要编写hook函数,这个过程

linux网络编程框架

OSI七层模型与TCP四层模型 OSI七层模型与TCP四层模型 BS和CS服务器架构 (1)CS架构介绍(client server,客户端服务器架构)(2)BS架构介绍(broswer server,浏览器服务器架构) TCP协议 (1)建立连接需要三次握手(2)建立连接的条件:服务器listen时客户端主动发起connect(3)关闭连接需要四次握手(4)服务器或者客户端都可以主动发起关闭 注:这些握手协议已经封装在TCP协议内部,socket编程接口平时不用管 TCP如何保证可靠传输(1)

3.9.1.linux网络编程框架

参考:https://blog.csdn.net/guoguo527/article/details/52078962 本节讲述网络编程的框架,分层思想和TCP/IP协议的介绍,BS架构和CS架构的介绍等. 3.9.1.1.网络是分层的 (1)OSI 7层模型 传输层-应用层:定义应用程序的功能, 剩下三层主要通过网络的端到端的数据流.OSI七层模型是一个理论模型,更多的则是把它作为分析.评判各种网络技术的依据. 物理层 为数据链路层提供物理链接,在其串行传送比特流(传送数据的单位比特). 数据

linux module 模块编程

转载自:http://blog.csdn.net/eroswang/archive/2008/09/13/2924875.aspx 摘要Linux内核模块编程的资料有些纷繁复杂,有的过于简单,有的过于庞杂,我试图用笔记的形式想读者展示怎样来进程Linux模块编程,力图做到简明扼要,这篇文章也是作为本人备忘的资料,所以有些地方过于简略是难免的.本来这篇文章的目的就是让用户知其然,至于所以然还是请参考相应的资料,其实最好的资料莫过于Linux Kernel Source. 适用范围: Linux K

初探linux内核编程,参数传递以及模块间函数调用

一.前言                                  我们一起从3个小例子来体验一下linux内核编程.如下: 1.内核编程之hello world 2.模块参数传递 3.模块间函数调用 二.准备工作                           首先,在你的linux系统上面安装linux头文件,debian系列: 1 $:sudo apt-get install linux-headers-`uname -r` 安装后,在你的/lib/modules/目录下有你刚

【转】初探linux内核编程,参数传递以及模块间函数调用

http://www.cnblogs.com/yuuyuu/p/5119891.html ZC: 疑问,最后的 模块kernel_mod 调用 模块kernel_fun的函数fun,是成功的OK的.但是 模块kernel_mod 怎么就知道 它调用的就是 模块kernel_fun的fun函数?如果 又有一个 模块kernel_fun01它也导出了fun函数,此时 模块kernel_mod调用fun的话调用的是哪一个模块的fun函数? (ZC: 测试了一下,两个模块 有相同的导出函数的话,在 加载

跨平台网络通信与server编程框架库(acl库)介绍

一.描写叙述 acl project是一个跨平台(支持LINUX,WIN32,Solaris,MacOS,FreeBSD)的网络通信库及server编程框架,同一时候提供很多其它的有用功能库.通过该库,用户能够很easy地编写支持多种模式(多线程.多进程.非堵塞.触发器.UDP方式)的server程序,WEB 应用程序,数据库应用程序.此外,该库还提供了常见应用的client通信库(如:HTTP.SMTP.ICMP.memcache.beanstalk),常见流式编解码库:XML/JSON/MI

跨平台网络通信与服务器编程框架库(acl库)介绍

一.描述 acl 工程是一个跨平台(支持LINUX,WIN32,Solaris,MacOS,FreeBSD)的网络通信库及服务器编程框架,同时提供更多的实用功能库.通过该库,用户可以非常容易地编写支持多种模式(多线程.多进程.非阻塞.触发器.UDP方式)的服务器程序,WEB 应用程序,数据库应用程序.此外,该库还提供了常见应用的客户端通信库(如:HTTP.SMTP.ICMP.memcache.beanstalk),常见流式编解码库:XML/JSON/MIME/BASE64/UUCODE/QPCO