Skip to content

编写第一份驱动代码

一、介绍

本部分编写一段 Linux 内核 C 程序,实现最简单的打印输出功能。

在 Linux 内核中,驱动程序可以看作一个 模块(module),它是可以被动态装载(load)与卸载(unload)的。当模块被加载时,系统会自动调用 模块初始化函数;当模块被卸载时,系统会自动调用 模块退出函数。

内核使用两个宏来实现这一机制:module_init() 和 module_exit()。 这两个宏定义在头文件:

linux-5.4/include/linux/module.h

它们的作用是:

  • 将模块的初始化函数和退出函数注册到内核的全局初始化与退出表中;
  • 从而在 insmod 和 rmmod 操作时自动调用对应函数。

只需要把你的初始化函数和退出函数的函数指针作为参数传给这两个宏即可。

二、编写代码

代码示例:

~/work/d1s/linux-5.4/drivers/misc目录下新建fcode.c文件:

c
// SPDX-License-Identifier: GPL-2.0
/*
 * My first linux kernel code.
 *
 * Copyright (C) 2025 by DuRuofu
 *
 * This module serves no practical purpose and is solely for testing purposes.
/*
*/

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

static int __init fcode_init(void)
{
	pr_info("=============>%s runing<=============\n", __func__);
	return 0;
}

static void __exit fcode_exit(void)
{
	pr_info("=============>%s runing<=============\n", __func__);
}

module_init(fcode_init);
module_exit(fcode_exit);

MODULE_AUTHOR("duruofu");
MODULE_DESCRIPTION("This is my first linux kernel code for testing.");
MODULE_LICENSE("GPL");

代码说明:

头注释部分:

c
// SPDX-License-Identifier: GPL-2.0
/*
 * My first linux kernel code.
 *
 * Copyright (C) 2025 by DuRuofu
 *
 * This module serves no practical purpose and is solely for testing purposes.
*/

// SPDX-License-Identifier: GPL-2.0 表示该源码的许可证是 GPL-2.0。Linux 内核强制要求每个源码文件都包含 SPDX 标识符,否则编译会有警告。

头文件部分

c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
  • <linux/kernel.h>:提供printk() / pr_info()等内核日志函数。
  • <linux/module.h>:包含 module_initmodule_exitMODULE_LICENSE 等宏的定义(任何模块都要包含它)。
  • <linux/types.h>:定义内核的标准类型(如 u8, u16, u32, size_t 等)。

模块入口和卸载函数:

c
static int __init fcode_init(void)
{
	pr_info("=============>%s runing<=============\n", __func__);
	return 0;
}

static void __exit fcode_exit(void)
{
	pr_info("=============>%s runing<=============\n", __func__);
}

模块初始化与退出注册:

c
module_init(fcode_init);
module_exit(fcode_exit);

这两个宏告诉内核:

  • 当模块被加载时,调用 fcode_init()
  • 当模块被卸载时,调用 fcode_exit()

模块元信息:

MODULE_AUTHOR("duruofu");
MODULE_DESCRIPTION("This is my first linux kernel code for testing.");
MODULE_LICENSE("GPL");

三、格式检查

写完代码进行,代码格式检查,使用命令:

bash
 ../../scripts/checkpatch.pl -f fcode.c

可以看到这里给出了一个警告,说注释的格式不对:

仔细观察发现是注释多写了一点没用的东西,如下:

删除后重新进行格式检查:

可以看到就没问题了。

四、编写Makefile和Kconfig

打开当前目录下的Makefile,如图:

把我们编写的文件放到文件最后面,格式参考前面写的即可:

obj-$(CONFIG_HISI_HIKEY_USB)	+= hisi_hikey_usb.o
obj-$(CONFIG_SUNXI_RFKILL)      += sunxi-rf/
obj-$(CONFIG_SUNXI_ADDR_MGT)    += sunxi-addr/
obj-$(CONFIG_SUNXI_BOOTEVENT)   += sunxi-bootevent/
obj-y                           += sunxi-gorilla/
obj-$(CONFIG_POWER_KEY)		    += powerkey.o
obj-$(CONFIG_FIRST_CODE)		+= fcode.o

最后一行是我们添加了,注意这里定义了CONFIG_FIRST_CODE这个宏

还需要修改Kconfig,同样是当前目录下的Kconfig文件:

在最后添加下面的内容(格式也是和前面的一致):

config FIRST_CODE
        tristate "FIRST CODE SUPPORT"
        default n
        help
                This driver for key first code.

FIRST CODE SUPPORT 就是之后会在menuconfig里显示的内容。

五、配置和编译

在menuconfig里配置对应的选项,使我们的代码加入编译:

cd linux-5.4
export ARCH=riscv
export CROSS_COMPILE=/home/duruofu/work/d1s/riscv64-wangzai-linux-gnu-gcc/bin/riscv64-unknown-linux-gnu-
make wangzai_d1s_rtl8723ds_defconfig
make menuconfig

选中我们编写的模块:

保存后,在根目录下的.config文件里就勾选了这个选项,我们的模块已经被加入编译了。

如果我们想把这个配置存储下来,或者作为一个默认编译配置,应该如何做:

首先使用命令,保存默认配置:

make savedefconfig

目录下就会生成一个defconig文件:

把这个defconfig拷贝到对应的目标平台的目录下,比如

 cp defconfig arch/riscv/configs/wangzai_d1s_first_code_defconfig

注意,这个文件的最后一个单词一定要是defconfig ,下次我们就可以使用下面的命令来加载这个默认配置:

make wangzai_d1s_first_code_defconfig

然后进行编译:

make -j12

六、烧录程序

将Image和wangzai_d1s.dtb文件拷贝到“bootcard”目录下:

bash
cp ~/work/d1s/linux-5.4/arch/riscv/boot/dts/sunxi/wangzai_d1s.dtb ~/work/d1s/bootcard/
cp ~/work/d1s/linux-5.4/arch/riscv/boot/Image ~/work/d1s/bootcard/

制作启动卡,然后烧录到SD卡即可,烧录操作可以参考:环境搭建和程序烧录

通过串口线观察开发板启动时的内核输出:

可以看到我们编写的模块成功运行了。