【嵌入式环境下linux内核及驱动学习笔记-(2-linux内核模块)】

1、内核模块介绍

Linux提供了一种 需要时可以被动态加载和移除的代码的机制,这种机制称为模块(Module),内核模块具有以下两个特点:

  • 模块本身不被编译入内核映像,从而使内核映像比较精简。
  • 模块被加载后,其与其它内核进程没有区别。

内核模块的本质:一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。

记住,内核模块的运行是在内核空间里的。

2、内核模块的结构

2.1 hello world例程

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

/***************************************/

static int __init my_init(void){
	printk("Hello Driver initalized!\n")
	return 0;
}

static void __exit my_exit(void){
	printk("Hello Driver exit!\n")
}

module_init(my_init);
module_exit(my_exit);

/***************************************/

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Micher Lee");
MODULE_DESCRIPTION("");
MODULE_ALIAS("");

这是一个典型的内核模块,用注释符分隔成了三个部分。

  • 第一部分为包含的头文件;
  • 第二部分为模块的加载与卸载函数;
  • 第三部分为许可权声明等描述信息。

而实际,只要记住模块三要素:入口函数 、出口函数、 MODULE__LICENSE

2.2 结构说明

2.2.1 包含库

#include <linux/module.h>       //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h>       //包含模块编程相关的宏定义,如:MODULE_LICENSE

2.2.2 __init的作用 :

原型:

int    __init    my_init(void){}
  1. __init 是一个宏,展开后为:attribute ((section (“.init.text”))) 实际是gcc的一个特殊链接标记。
  2. 指示链接器将该函数放置在 .init.text区段。.init.text区段是Linux内核的一种特殊的代码段。该区段中的代码只在内核启动阶段执行一次,启动完成后不再执行,一旦内核启动完成,.init.text节就可以被释放。
  3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置 。
2.2.2.1 补充:内核模块代码段的结构
Linux内核的链接过程与普通程序有所不同。主要有以下几点:

1. 段的种类更丰富。除了普通段(.text、.data、.bss)外,还有.init.text、.exit.text、.rodata等。
2. 段的作用更专门。如.init.text只包含初始化代码,.exit.text只包含退出代码。
3. 段的生命周期不同。如.init.text在启动后被释放,.exit.text在退出前被建立。

Linux内核的主要代码段有:

1. .text - 包含模块的主体函数代码,生命周期与模块相同。
2. .init.text - 包含初始化函数代码,启动后被释放。
3. .exit.text - 包含退出函数代码,退出前被建立。
4. .rodata - 包含常量数据,生命周期与模块相同,内容不可变。
5. .data - 包含可变全局变量和局部静态变量,生命周期与模块相同。
6. .bss - 包含未初始化的全局变量,生命周期与模块相同。
7. .init.data/.exit.data - 包含.init.text/.exit.text对应的变量,生命周期与对应的文本段相同。
除此之外,Linux内核还有其他段如.got等。

链接器在链接内核模块时,会根据代码和数据的标记、属性,将它们放入适当的段中:

1. __init/__exit宏标记的函数放入.init.text/.exit.text。
2. 常量数据放入.rodata。
3. 未初始化数据放入.bss。
4. 其他代码和数据放入.text/.data。
5. 生成段的属性,如.init.text属性为可释放等。
最终链接器生成的ko文件,包含着这些正确分类的代码段,并且这些段的生命周期和访问属性都已正确确定。这为内核执行这些模块的代码提供了必要的信息。

2.2.3 关于printk

内核是裸机程序,不可以调用C库中printf函数来打印程序信息, Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel), printk不支持浮点数打印。

2.2.4 __exit的作用:

void __exit my_exit(void){};

1.__exit是一个宏,展开后为:attribute ((section (“.exit.text”))) 实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置

2.2.5 MODULE_LICENSE(字符串常量);

MODULE_LICENSE(“GPL”);

字符串常量内容为源码的许可证协议 可以是"GPL" “GPL v2” “GPL and additional rights” “Dual BSD/GPL” “Dual MIT/GPL” "Dual MPL/GPL"等, "GPL"最常用

其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证

2.2.6 module_init 宏

原型:

#include <linux/init.h>
#define module_init(x)	__initcall(x);

使用

module_init(my_init);

  1. 用法:module_init(模块入口函数名)
  2. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
  3. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名

2.2.7 module_exit宏

原型:

#include <linux/init.h>
#define module_exit(x)	__exitcall(x);

使用:

module_exit(myhello_exit);

1.用法:module_exit(模块出口函数名)
2.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
3.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名

2.2.8 内核模块信息宏

MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明

MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明

MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名

这些宏用来描述一些当前模块的信息,可选宏

这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:modinfo 模块文件名

2.2.9 模块参数

  • 说明
    模块也可以被传入参数。只是和函数传参的方式不同。其形式如下:
    module_param( name , type , perm);

    module_param_array(数组name, type , 数组长度 , perm);
    其中:
    name为全局变量名
    type为全局变量的模参专用类型

在这里插入图片描述
perm为对应文件 /sys/module/对应模块名/parameters/变量名的操作权限。 一般赋值0664即可。
关于perm这里多解释一下:模块被加载后,在/sys/module目录下会出现以此模块名命名的目录。当perm不为0时,在此目录下还将出现parameters目录,基中包含一系列以参数名命名的文件节点。这些文件的权限值就是perm的值。

  • 例子
/*模块名:book     编译后文件名为 book.ko   */
#include <linux/init.h>
#include <linux/module.h>

static char *book_name = "hello";
module_param(book_name , charp , 0664);

static int book_um = 400;
module_param(book_num , int , 0664);

static init __init my_init(void){
	printk("book name:%s\n", book_name);
	printk("book num :%d\n", book_num);
	return 0
}

static void __exit my_exit(void){

}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

  • 加载
    上面这段代码编译后的文件名为book.ko
    如果用 “ insmod book.ko ”来加载,则没有参数传入, 变量book_name及book_num的值是默认初始值。
    如果用 “ insmod book.ko book_name=“world” book_num=500 ” 语句来加载,则参数被传入。

2.2.10 导出符号

模块是内核的一部分,与内核是一个整体,因此就可以与内核其它程序去共享全局的符号。
Linux的“/proc/kallsyms”文件,对应着整个内核所有的符号表,它记录了符号以及符号所在在内存地址。

对于一个模块,可以通过如下的宏,将自身的符号导出到内核符号表中,可供其它内核进程使用。

  • 导出语法
    EXPORT_SYMBOL(符号名);
    EXPORT_SYMBOL_GPL(符号名);
  • 使用导出符号
    extern 符号名
  • 编译及加载
    在这里插入图片描述
  • 案例
    下面有两种驱动, 一个export.c用于导出符号,另一个extern.c用于接收使用导出的符号。下面是源码

在这里插入图片描述

在这里插入图片描述

对两个驱动进行加载后的输出结果:
在这里插入图片描述

3、常用操作命令

3.1 lsmod

在这里插入图片描述

3.2 insmod

在这里插入图片描述

3.3 rmmod

在这里插入图片描述

3.4 dmesg

在这里插入图片描述

3.5 modinfo

在这里插入图片描述

本篇结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑牛唱剧本

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值