linux专题Linux kernel专题

linux 驱动开发 - 内核模块

2019-08-28  本文已影响0人  Mr_Michael

一、Linux内核简介

1.宏内核与微内核

内核分为四大类:单内核(宏内核);微内核;混合内核;外内核。

image

2.Linux体系架构

从两个层次上来考虑操作系统

image

划分原因

3.Linux的内核结构

Linux内核是整体式结构(宏内核),各个子系统联系紧密,作为一个大程序在内核空间运行。

系统调用接口(system call interface,SCI)提供了某些机制执行从用户空间到内核的函数调用。

image
1)Linux内核组成(子系统)

4.内核模块

Linux内核是模块化组成的,它允许内核在运行时动态地向其中插入或删除代码。

二、内核模块结构

1.头文件

内核模块头文件<linux/module.h>和<linux/init.h>是必不可少的 ,不同模块根据功能的差异,所需要的头文件也不相同 。

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

2.模块初始化

模块的初始化负责注册模块本身 ,只有已注册模块的各种方法才能够被应用程序使用并发挥各方法的实际功能。

模块并不是内核内部的代码,而是独立于内核之外,通过初始化,能够让内核之外的代码来替内核完成本应该由内核完成的功能,模块初始化的功能相当于模块与内核之间衔接的桥梁,告知内核已经准备好模块了。

内核模块初始化函数

//模块初始化函数一般都需声明为 static
//__init 表示初始化函数仅仅在初始化期间使用,一旦初始化完毕,将释放初始化函数所占用的内存
static int __init module_init_func(void)
{
    初始化代码
}
module_init(module_init_func);
//module_init宏定义会在模块的目标代码中增加一个特殊的代码段,用于说明该初始化函数所在的位置。

当使用 insmod 将模块加载进内核的时候,初始化函数的代码将会被执行。

3.模块退出

模块的退出相当于告知内核“我要离开了,将不再为您服务了”。

内核模块退出函数

//模块退出函数没有返回值;
//__exit 标记这段代码仅用于模块卸载;
static void __exit module_exit_func(void)
{
    //模块退出代码
}
module_exit(module_exit_func);
//没有 module_exit 定义的模块无法被卸载

当使用 rmmod 卸载模块时,退出函数的代码将被执行。

注意:如果模块被编译进内核,而不是动态加载,则__init的使用会在模块初始化完成后丢弃该函数并回收所占内存, _exit宏将忽略“清理收尾”的函数。

4.模块许可证声明

Linux 内核是开源的,遵守 GPL 协议,所以要求加载进内核的模块也最好遵循相关协议。

为模块指定遵守的协议用 MODULE_LINCENSE 来声明 :

MODULE_LICENSE("GPL");

5.模块导出符号 【可选】

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

6.模块描述 [可选]

模块编写者还可以为所编写的模块增加一些其它描述信息,如模块作者、模块本身的描述或者模块版本等

MODULE_AUTHOR("Abing <Linux@zlgmcu.com>");
MODULE_DESCRIPTION("ZHIYUAN ecm1352 beep Driver");
MODULE_VERSION("V1.00");

模块描述以及许可证声明一般放在文件末尾。

三、向Linux内核添加新内核模块

1.添加模块驱动文件

在linux/drivers/下新建目录hello,并且在hello/目录下新建hello.c、Makefile、Kconfig三个文件。

1)内核模块程序hello.c
/* hello world module */

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

static int __init hello_init(void)
{
  printk(KERN_INFO "Hello, I'm ready!\n");
  return 0;
}
static void __exit hello_exit(void)
{
  printk(KERN_INFO "I'll be leaving, bye!\n");
}
module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("michael");
MODULE_DESCRIPTION("hello world module");
2)Kconfig
menu "HELLO TEST Driver "
comment "HELLO TEST Driver Config"
 
config HELLO
    tristate "hello module test"
    default m
    help
    This is the hello test driver.
 
endmenu
3)Makefile
obj-$(CONFIG_HELLO) += hello.o

可用于动态模块外部编译的写法

2.修改上一级目录的Kconfig和Makefile

进入linux/drivers/

3.make menuconfig配置和编译

image

如果选择编译成动态模块<m>

如果选择编译进内核<y>

4.动态模块加载和卸载

加载模块使用 insmod 命令,卸载模块使用 rmmod 命令。

$ insmod hello.ko
$ rmmod hello.ko
#加载和卸载模块必须具有 root 权限 。

对于可接受参数的模块,在加载模块的时候为变量赋值即可,卸载模块无需参数。

$ insmod hello.ko num=8
$ rmmod hello.ko

四、带参数的内核模块

模块参数必须使用 module_param 宏来声明,通常放在文件头部。

module_param 需要 3个参数:变量名称、类型以及用于 sysfs 入口的访问掩码。

static int num = 5;
module_param(num, int, S_IRUGO);

能够接收参数的模块范例

#include <linux/module.h>
#include <linux/init.h>
// moduleparam.h 文件已经包含在 module.h 文件中

static int num = 3;
static char *whom = "master";

module_param(num, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);

static int __init hello_init(void)
{
  printk(KERN_INFO "%s, I get %d\n", whom, num); //KERN_INFO 表示这条打印信息的级别
  return 0;
}

static void __exit hello_exit(void)
{
  printk("I'll be leaving, bye!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("luxiaodai");
MODULE_DESCRIPTION("this is my first module");
上一篇 下一篇

猜你喜欢

热点阅读