1、驱动结构

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

static int __init helloworld_init(void)    //驱动入口函数
{
    printk(KERN_EMERG "helloworld_init\r\n");//注意:内核打印用printk而不是printf
    return 0;
}

static void __exit helloworld_exit(void)    //驱动出口函数
{
    printk(KERN_EMERG "helloworld_exit\r\n");
}

module_init(helloworld_init);    //注册入口函数
module_exit(helloworld_exit);    //注册出口函数
MODULE_LICENSE("GPL v2");    //同意GPL开源协议
MODULE_AUTHOR("topeet");    //作者信息

驱动必须有的模块

模块加载函数(static int __init helloworld_init(void))

当使用加载驱动模块时,内核会执行模块加载函数,完成模块加载函数中的初始化工作。

模块卸载函数(static void __exit helloworld_exit(void) )

当卸载某模块时,内核会执行模块卸载函数,完成模块卸载函数中的退出工作。

模块许可证声明(MODULE_LICENSE(“GPL v2”))

许可证声明描述了内核模块的许可权限,如果不声明模块许可,模块在加载的时候,会收到“内核被污染(kernel tainted)”的警告。可接受的内核模块声明许可包括“GPL”“GPL v2”。

可选模块

模块参数

​ 模块参数是模块被加载的时候可以传递给它的值。

模块导出符号

​ 内核模块可以导出的符号,如果导出,其他模块可以使用本模块中的变量或函数。

模块作者信息等说明

2、驱动模块传参

驱动模块传参是一种可以随时向内核模块传递、 修改参数的方法。 例如可以传递串口驱动的波特率、 数据位数、 校验位、 停止位等参数, 进行功能的设置, 以此节省编译模块的时间,大大提高调试速度。

#include <linux/init.h>
#include <linux/module.h>
static int number;//定义int类型变量number
static char *name;//定义char类型变量name
static int para[8];//定义int类型的数组
static char str1[10];//定义char类型字符串str1
static int n_para;//定义int类型的用来记录module_param_array函数传递数组元素个数的变量n_para
module_param(number, int, S_IRUGO);//传递int类型的参数number,S_IRUGO表示权限为可读
module_param(name, charp, S_IRUGO);//传递char类型变量name
module_param_array(para , int , &n_para , S_IRUGO);//传递int类型的数组变量para
module_param_string(str, str1 ,sizeof(str1), S_IRUGO);//传递字符串类型的变量str1
static int __init parameter_init(void)//驱动入口函数
{
    static int i;
    printk(KERN_EMERG "%d\n",number);
    printk(KERN_EMERG "%s\n",name);                                                                                                                                                          
    for(i = 0; i < n_para; i++)
    {
        printk(KERN_EMERG "para[%d] : %d \n", i, para[i]);
    }
    printk(KERN_EMERG "%s\n",str1);
    return 0;
}
static void __exit parameter_exit(void)//驱动出口函数
{
    printk(KERN_EMERG "parameter_exit\n");
}
module_init(parameter_init);//注册入口函数
module_exit(parameter_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("topeet"); //作者信息

3、内核模块符号导出

驱动程序编译生成的ko文件是相互独立的,即模块之间变量或者函数在正常情况下无法进行互相访问。而一些复杂的驱动模块需要分层进行设计,这时候就需要用到内核模块符号导出。
内核符号导出指的是在内核模块中导出相应的函数和变量,在加载模块时被记录在公共内核符号表中,以供其他模块调用。符号导出所使用的宏为EXPORT_SYMBOL(sym)和EXPORT_SYMBOL_GPL(sym)。它们定义在 “内核源码/include/linux/export.h”文件中(在module.h文件中已经对export.h进行引用,所以不需要单独引用export.h文件)
以下例子编写Linux下的内核模块符号导出实例代码,总共有两个驱动程序,第一个驱动文件名为mathmodule.c,用来定义参数num和函数add(a, b),第二个驱动文件名为hello.c,会引用mathmodule.c驱动程序中的参数num和数学函数add(a,b),并将相应的参数值和函数返回值打印到串口终端上。

//mathmodule.c
#include <linux/init.h>
#include <linux/module.h>
int num = 10;//定义参数num
EXPORT_SYMBOL(num);//导出参数num

int add(int a, int b)//定义数学函数add(),用来实现加法
{
    return a + b;
}
EXPORT_SYMBOL(add);//导出数学函数add()

static int __init math_init(void)//驱动入口函数
{
    printk("math_moudle init\n");
    return 0;
}

static void __exit math_exit(void)//驱动出口函数
{
    printk("math_module exit\n");
}

module_init(math_init);//注册入口函数
module_exit(math_exit);//注册出口函数

MODULE_LICENSE("GPL");//同意GPL开源协议
MODULE_AUTHOR("topeet");//作者信息

//hello.c
#include <linux/init.h>
#include <linux/module.h>
extern int num;//导入int类型变量num
extern int add(int a, int b);//导入函数add
static int __init hello_init(void)//驱动入口函数
{
    static int sum;
    printk("num = %d\n", num);//打印num值
    sum = add(3, 4);//使用add函数进行3+4的运算                                                                                                                                                                          
    printk("sum = %d\n", sum);//打印add函数的运算值
    return 0;
}

static void __exit hello_exit(void)//驱动出口函数
{
    printk("Goodbye hello module\n");
}

module_init(hello_init);//注册入口函数
module_exit(hello_exit);//注册出口函数

MODULE_LICENSE("GPL");//同意GPL开源协议
MODULE_AUTHOR("topeet");//作者信息

由于 hello.ko依赖于mathmodule.ko,所以mathmodule.ko需要先加载,分别使用以下命令进行模块的加载(加载顺序不能变)
销毁时要先销毁hello.ko,再销毁mathmodule.ko顺序也不能变。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐