Linux kernel driver 入门01 写个驱动玩玩儿 加载/卸载 hello world

前言

我们现在就写一个驱动小demo,环境准备:要有个linux系统,我的demo系统是一台老掉牙的笔记本上,装了个Ubunut18.04LTS 版本的系统。uname -a 命令我们查看一下这个系统的内核信息,如下:

内核版本是 4.15.0. x86_64的系统。

这个有什么用呢?就是下载对应的内核源码?我们要查询源码里面的一个文件。文件的目录是:

linux-source-4.15.0/Documentation/kbuild

在这个路径下有个 modulex.txt文件。很重要,我们要用它。

我们的目的就是让这个驱动加载到我们这台Linux机器的系统里面。

入口函数

驱动需要一个入口函数,在加载期间告知内核,这个是入口,(道理类似于应用程序的main函数)。

该函数的类型必须遵循如下的格式:

int  xxxxx_func(void);

注意:

1. 有一个 int 返回值,如果该驱动被成功加载了,那么该接口必须返回0,如果内部出错,可以返回 <0 的值,告诉内核,该模块加载不成功。

2. 一个驱动模块,只能有一个入口函数

3. 告诉内核的方式采用宏 wrap,如下:

module_init(xxxx_funct)

出口函数

如果需要,就添加一个出口函数,如果不需要,那就拉倒。当然,如果没什么资源需要被释放的,那么建议,出口函数就空实现即可。有些驱动,一般被加载,可能不需要被卸载,也就不需要出口函数,但是呢,咱们自己写的时候尽量不要这么干,没什么资源可释放的,那就空实现即可。

函数类型如下:

void xxxx_func(void);

如何无参数,无返回值。告诉内核呢?

module_exit(xxxx_func)

这两个函数,都是固定的,仅仅会在驱动模块被加载或者被卸载的那一刻,被调用一次。

我们现在就自己写一个。

代码

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

MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");

static int __init hello_init(void)
{
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Hello !]===\n",__FILE__, __func__, __LINE__);
    return 0;
}

static void __exit hello_exit(void)
{
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Bye bye...]===\n",__FILE__, __func__, __LINE__);
}

module_init(hello_init);
module_exit(hello_exit);

头文件:

不用想太多,linux 内核的驱动,最基础的两个头文件。

__init  以及  __exit  实际也是宏,利用的 GNU C extension 中的语法,让代码在编译期间分布到指定的段中去。Linux 内核源码,大多数的驱动,都遵循这个原则,也可以不要。当然,对于嵌入式而言,如果交叉编译器不支持这个特性,也是有可能的,那就不要了,也没问题,不影响我们这个驱动的功能。

打印:printk,内核的打印函数,其实前面还有个参数,就是等级,如果省略,那就采用默认的等级去打印。这个地方,我们采用默认的方式打印即可。

MODULE_LICENSE("GPL");

这个玩意儿吧,也不是必须的,没有这一句,编译期间会有一句警告,就很烦,那就加上。

接下来要做的事儿,就是编译了。

驱动程序,可以单独的编译成模块,也可以静态编译到内核中去。

编译

打开前文所述的modules.txt,里面有编译的方法。

所以Makefile 怎么写呢,如下:

KERNDIR:= /lib/modules/`uname -r`/build/

PWD:= $(shell pwd)

obj-m:= hello.o

all:
	make -C $(KERNDIR) M=$(PWD) modules

clean:
	make -C $(KERNDIR) M=$(PWD) clean

注意:最好自己用vim或者其他编辑器,自己敲一遍以上 Makefile,因为tab的问题,要注意,要关闭掉expandtab的功能。也就是说一个tab = 4 空格这样的强制转换功能,要关闭掉。

说明一下:

先定义一下 kernel 的位置。就是上面module.txt里面说的位置。

其次是当前目录,就是 Makefile 跟咱们的驱动代码 hello.c的位置。

obj-m 表示,采用动态模块的方式,编译我们的驱动,与之对应的如果是整体的内核代码编译,需要将驱动编译进内核image中,这个地方就写 obj-y就行了。

modules,就是编译模块。

接下来,我们 make 一下就OK了。

make之前, make过程的输出,make之后的结果。

驱动编译出来,最关键的输出,就是那个 hello.ko。

查看,加载,卸载

内核模块操作的相关的命令:

1. modinfo hello.ko

查看信息

2. lsmod 查看当前系统已经加载的模块。

3. insmod hello.ko  加载模块 hello_init 被调用

这个命令需要 管理员权限。(sudo insmod hello.ko)

4. rmmod hello  卸载模块, hello_exit 被调用

这个命令也需要管理员权限,另外,不需要 .ko了。(sudo rmmod hello)

5. dmesg 查看 printk 输出的内容。

(sudo dmesg -c 清除没用的当前我们还看不懂的那些内核日志,这条命令,最好开始就可以执行一遍)

加载时传参

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

MODULE_DESCRIPTION("Frocheng: Driver for DEMO!");
MODULE_AUTHOR("Frodo Cheng");
MODULE_LICENSE("GPL");

static int irq;
static char* name;

module_param(irq, int, 0664);
module_param(name, charp, 00);

static int __init hello_init(void)
{
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Hello !]===\n",__FILE__, __func__, __LINE__);
	printk("name = %s\n", name);
    return 0;
}

static void __exit hello_exit(void)
{
	printk("irq = %d\n", irq);
    printk("===[frocheng]===[%s]===[%s]===[%d]===[Bye bye...]===\n",__FILE__, __func__, __LINE__);
}

module_init(hello_init);
module_exit(hello_exit);

 module_param(param_name, param_type, param_perm);

如果需要传参,我们需要进行上述的参数声明语法,并且我们需要先定义参数变量。参考内核其他驱动的代码,这个参数都是静态的全局变量。

param_name:变量名

param_type: 类型,注意,不接受浮点类型。

param_perm:权限,当非0时,每当模块加载完成之后,会在 /sys/module 目录下,生成一个跟模块名同名的目录,该目录下有一个parameters目录,此目录下,会针对每一个变量,生成一个同名的文件,可通过修改该文件中的值,去修改变量的值。所以这个权限的表达方式,其实跟文件的权限的表达方式一致。注意,不要给到 x 权限。当为0时,就不会生成该文件。虽然有文件,该文件会一直存在与内存中,所以为了节约内存,需要传参时,尽量给0权限,禁止参数文件的生成。

module_param_hw(),跟硬件相关。

还有数组参数声明,详细,可参考内核源码中的 include/linux/moduleparam.h文件。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值