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

目录

  • 1、内核模块介绍
  • 2、内核模块的结构
    • 2.1 hello world例程
    • 2.2 结构说明
      • 2.2.1 包含库
      • 2.2.2 __init的作用 :
      • 2.2.3 内核是裸机程序,不可以调用C库中printf函数来打印程序信息, Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel), printk不支持浮点数打印。
      • 2.2.4 __exit的作用:
      • 2.2.5 MODULE_LICENSE(字符串常量);
      • 2.2.6 module_init 宏
      • 2.2.7 module_exit宏
      • 2.2.8 内核模块信息宏
      • 2.2.9 模块参数
      • 2.2.10 导出符号
  • 3、常用操作命令
    • 3.1 lsmod
    • 3.2 insmod
    • 3.3 rmmod
    • 3.4 dmesg
    • 3.5 modinfo

1、内核模块介绍

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

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

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

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

2、内核模块的结构

2.1 hello world例程

#include 
#include 

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

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 //包含内核编程最常用的函数声明,如printk
#include //包含模块编程相关的宏定义,如:MODULE_LICENSE

2.2.2 __init的作用 :

int __init my_init(void){};

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

2.2.3 内核是裸机程序,不可以调用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 宏

module_init(my_init);

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

2.2.7 module_exit宏

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为全局变量的模参专用类型

【嵌入式环境下linux内核及驱动学习笔记-(2-linux内核模块)】_第1张图片
perm为对应文件 /sys/module/对应模块名/parameters/变量名的操作权限。 一般赋值0664即可。
关于perm这里多解释一下:模块被加载后,在/sys/module目录下会出现以此模块名命名的目录。当perm不为0时,在此目录下还将出现parameters目录,基中包含一系列以参数名命名的文件节点。这些文件的权限值就是perm的值。

  • 例子
/*模块名:book     编译后文件名为 book.ko   */
#include 
#include 

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 符号名
  • 编译及加载
    【嵌入式环境下linux内核及驱动学习笔记-(2-linux内核模块)】_第2张图片
  • 案例
    下面有两种驱动, 一个export.c用于导出符号,另一个extern.c用于接收使用导出的符号。下面是源码

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

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

对两个驱动进行加载后的输出结果:
【嵌入式环境下linux内核及驱动学习笔记-(2-linux内核模块)】_第5张图片

3、常用操作命令

3.1 lsmod

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

3.2 insmod

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

3.3 rmmod

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

3.4 dmesg

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

3.5 modinfo

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

本篇结束

你可能感兴趣的:(Linux内核与驱动,linux,嵌入式,内核与驱动)