linux 驱动学习笔记04--简单驱动

时间:2023-03-08 20:49:10
linux 驱动学习笔记04--简单驱动

首先贴代码helloworld.c和Makefile

/*************************************************************************
> File Name: helloworld.c
> Author: hailin.ma
> Mail: mhl2018@126.com
> Created Time: Wed 15 Jul 2015 02:39:35 PM CST
************************************************************************/ #include <linux/init.h>
#include <linux/module.h> static char* mod_name = "hello";
static int mod_num = ; static int __init hello_init(void)
{
printk(KERN_INFO"hello world init! mod_name = %s,mod_num = %d\n",mod_name,mod_num);
return ;
} static void __exit hello_exit(void)
{
printk(KERN_INFO"hello world exit!\n");
} module_init(hello_init);
module_exit(hello_exit); module_param(mod_name,charp,S_IRUGO);
module_param(mod_num,int,S_IRUGO);
MODULE_AUTHOR("hailin.ma <mhl2018@126.com>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("A simple hello world module");
MODULE_ALIAS("simple driver test");

Makefile

#CFLAGS = -g
KVERS = /lib/modules/$(shell uname -r)/build obj-m += hello.o
hello-objs:=helloworld.o all:
make -C $(KVERS) M=$(PWD) modules
@rm *.o clean:
make -C $(KVERS) M=$(PWD) clean

make 执行后将生成hello.ko文件,命令:insmod hello.ko 即可加载模块到内核。加载 hello.ko 后,内核中将包含
/sys/module/hello 目录,该目录下又包含一个 refcnt 文件和一个 sections 目录,在/sys/module/hello
目录下运行“ tree –a” 得到如下目录树:

linux 驱动学习笔记04--简单驱动

modinfo <模块名 >命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持
的参数以及 vermagic:

linux 驱动学习笔记04--简单驱动

insmod  加载模块

lsmod  查看已加载模块, lsmod 命令实际上是读取并分析“/proc/modules” 文件。

rmmod  卸载模块

tail /var/log/messages 查看printk打印消息

一个 Linux 内核模块主要由如下几个部分组成。
( 1 )模块加载函数(一般需要)。
当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数会自动被内核执行,完成
本模块的相关初始化工作。

static int _ _init initialization_function(void)      //初始化函数
{
/* 初始化代码 */
}
module_init(initialization_function);    //指定模块初始化函数

模块加载函数必须以“ module_init(函数名 )” 的形式被指定。它返回整型值,若初始化成功,
应返回 0。而在初始化失败时,应该返回错误编码。在 Linux 内核里,错误编码是一个负值,在
<linux/errno.h>中定义,包含-ENODEV、 -ENOMEM 之类的符号值。总是返回相应的错误编码是
种非常好的习惯,因为只有这样,用户程序才可以利用 perror 等方法把它们转换成有意义的错误
信息字符串。
在 Linux 2.6 内核中,可以使用 request_module(const char *fmt, …)函数加载内核模块,驱动开
发人员可以通过调用
request_module(module_name);

request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));
这种灵活的方式加载其他内核模块。
在 Linux 中,所有标识为_ _init 的函数在连接的时候都放在.init.text 这个区段内,此外,所有
的_ _init 函数在区段.initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针
调用这些_ _init 函数,并在初始化完成后,释放 init 区段(包括.init.text、 。initcall.init等)。

( 2)模块卸载函数(一般需要)。
当通过 rmmod 命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数
相反的功能。

static void _ _exit cleanup_function(void)    //模块卸载函数
{
/* 释放代码 */
}
module_exit(cleanup_function);      //指定模块卸载函数

模块卸载函数在模块卸载的时候执行,不返回 任何值,必须以“ module_exit(函数名 )” 的形
式来指定。
通常来说,模块卸载函数要完成与模块加载函数相反的功能, 如下所示。
! 若模块加载函数注册了 XXX,则模块卸载函数应该注销 XXX。
! 若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。
! 若模块加载函数申请了硬件资源(中断、 DMA 通道、 I/O 端口和 I/O 内存等)的占用,
则模块卸载函数应释放这些硬件资源。
! 若模块加载函数开启了硬件,则卸载函数中一般要关闭之。
和_ _init 一样, _ _exit 也可以使对应函数在运行完成后自动回收内存。实际上, _ _init 和_ _exit
都是宏,其定义分别为:
#define _ _init _ _attribute_ _ (( _ _section_ _ (".init.text")))

#ifdef MODULE
#define _ _exit _ _attribute_ _ (( _ _section_ _(".exit.text")))

#else
#define _ _exit _ _attribute_used_ _attribute_ _ (( _ _section_ _(".exit.text")))
#endif
数据也可以被定义为_ _initdata 和_ _exitdata,这两个宏分别为:
#define _ _initdata _ _attribute_ _ (( _ _section_ _ (".init.data")))

#define _ _exitdata _ _attribute_ _ (( _ _section_ _(".exit.data")))

( 3)模块许可证声明(必须)。
许可证( LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE,模块被加载时,
将收到内核被污染 ( kernel tainted)的警告。
在 Linux 2.6 内核中,可接受的 LICENSE 包括“GPL”、“GPL v2”、“GPL and additional rights”、
“ Dual BSD/GPL”、 “ Dual MPL/GPL” 和“ Proprietary”。
大多数情况下,内核模块应遵循 GPL 兼容许可权。 Linux 2.6 内核模块最常见的是以
MODULE_LICENSE( "Dual BSD/GPL" )语句声明模块采用 BSD/GPL 双 LICENSE。
( 4)模块参数(可选)。
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。

我们可以用 “ module_param(参数名 ,参数类型,参数读/写权限)” 为模块定义一个参数,例如下
列代码定义了 1 个整型参数和 1 个字符指针参数:

static char *book_name = " dissecting Linux Device Driver ";
static int num = ;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);

在装载内核模块时,用户可以向模块传递参数,形式为“ insmode(或 modprobe)模块名 参
数名 =参数值”,如果不传递,参数将使用模块内定义的缺省值。
参数类型可以是 byte、 short、 ushort、 int、 uint、 long、 ulong、 charp(字符指针)、 bool 或 invbool
(布尔的反),在模块被编译时会将 module_param 中声明的类型与变量定义的类型进行比较,判断
是否一致。
模块被加载后,在/sys/module/目录下将出现以此模块名命名的目录。当“参数读/写权限” 为 0
时,表示此参数不存在 sysfs 文件系统下对应的文件节点,如果此模块存在“参数读/写权限” 不为 0
的命令行参数,在此模块的目录下还将出现 parameters 目录,包含一系列以参数名命名的文件节点,
这些文件的权限值就是传入 module_param()的“参数读/写权限”,而文件的内容为参数的值。
除此之外,模块也可以拥有参数数组,形式为“module_param_array(数组名,数组类型,数组长,参数
读/写权限)”。从 2.6.0~2.6.10 版本, 需将数组长变量名赋给“数组长”,从 2.6.10 版本开始, 需将数组
长变量的指针赋给“数组长”,当不需要保存实际输入的数组元素个数时,可以设置“数组长” 为 NULL。
运行 insmod,不同参数用空格隔开

linux 驱动学习笔记04--简单驱动

( 5)模块导出符号(可选)。
内核模块可以导出符号( symbol,对应于函数或变量),这样其他模块可以使用本模块中的变
量或函数。