【SOC】【Linux】基于全志F133+Linux内核的嵌入式Linux开发的学习过程记录

前言

首先,保证我们具有一定的C语言基础和单片机基础,然后,可以根据B站的视频可以快速上手Linux的学习,这个过程当中除了亲自上手使用或者编写模块驱动代码时,一定要学习到能够独立学习Linux源码以及驱动开发的独立学习能力。

肯定在最开始是记不住里面所有的Linux现有的模块API,那么至少需要知道其中那些会经常用到的API的所属的模块有哪些,至少形成编写代码时会知道Linux里有哪些类型模块(最好了解有Linux内核源码的大致框架图),同时,再进行使用AI快速帮助我们了解或者理解这个模块里的API的特性以及其使用场景(在Linux源码里可找到对应的头文件以及源文件,使用sourceinsight可以快速查找,一般在/linux-5.4/include/linux里的头文件里有介绍)。

后续,在经常使用当中进行逐步深刻记忆(就像学习单片机外设的过程一样)。学习旺哥的Linux开发方法后,可以再通过学习正电原子的补充Linux底层汇编裸机知识,同时再通过学习100ask的Linux应用层方面,形成正式的Linux学习框架,搭建起一个系统的认知,后续就是补充完成以及持续精进了(大部分情况是需要自行学习Linux驱动开发)。

注:(如有侵权,请联系删除)

(1)参考【搞linux的旺仔】【ubuntu系统环境搭建】:windows wsl安装ubuntu_哔哩哔哩_bilibili

(2)注意学习过程中一定要连续性的学习,否则会很快的遗忘。

(3)uboot也内容稍微大一些,可以单独学习。

(4)如果只想使用adb口,在adb shell进入后安装ko文件后使用dmesg可查看打印信息。

嵌入式Linux开发:C代码初步实战

/home/zky/ProjectsHub/Linux_Projects/pingdichan/linux-5.4/drivers/misc

在该目录下编写一个文件使用模块注册函数,注册一个打印模块在启动Linux可见

注意:

顶行证书

文件注释

Linux必要的头文件

注册/退出函数

模块注册/退出函数

模块作者 描述 协议类型

tab缩进(8个空格宽)

以及Linux代码风格

复制代码
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * my first linux code
 *
 * Copyright (C) 2010 xxxx Electronics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
 /*
  * 设备树描述:
  *     pkey:pkey {
  *         compatible = "wangzai,powerkey";
  *         poweroff-gpios = <&pio PG 14 GPIO_ACTIVE_HIGH>;
  *     };
  */
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>

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

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

module_init(fcode_init);
module_exit(fcode_exit);

MODULE_AUTHOR("zky");
MODULE_DESCRIPTION("my first linux code");
MODULE_LICENSE("GPL");

写完后

bash 复制代码
../../scripts/checkpatch.pl -f my_linux_c.c


vim Makefile

obj-$(CONFIG_FCODE)		+= my_linux_c.o




vim kconfig

config FCODE
        tristate "my first linux code"
        default n
        help
                This driver for my first linux code.



export ARCH=riscv
export CROSS_COMPILE=/home/zky/ProjectsHub/Linux_Projects/pingdichan/riscv64-wangzai-linux-gnu-gcc/bin/riscv64-unknown-linux-gnu-
make wangzai_d1s_rtl8723ds_defconfig



make menuconfig

vim .config


make savedefconfig


cp defconfig arch/riscv/configs/wangzai_d1s_rtl8723ds_defconfig

cp arch/riscv/boot/Image /home/zky/ProjectsHub/Linux_Projects/pingdichan/bootcard

cp arch/riscv/boot/dts/sunxi/wangzai_d1s.dts /home/zky/ProjectsHub/Linux_Projects/pingdichan/bootcard



ls /dev/sd*

sudo umount /dev/sdb1

sudo ./mkbootcard /dev/sdb

一、动态模块加载

MakeFile

复制代码
CROSS_PREFIX := /home/zky/ProjectsHub/Linux_Projects/pingdichan/riscv64-wangzai-linux-gnu-gcc/bin/riscv64-unknown-linux-gnu-
KERNEL_DIR := /home/zky/ProjectsHub/Linux_Projects/pingdichan/linux-5.4/
arch := riscv
obj-m := ko.o

cc := $(CROSS_PREFIX)gcc
ld := $(CROSS_PREFIX)ld

.PHONY:all
all:
	make ARCH=$(arch)  CC=$(cc) LD=$(ld) -C $(KERNEL_DIR) M=$(shell pwd) modules V=1

.PHONY:clean
clean:
	make ARCH=$(arch) CC=$(cc) LD=$(ld) -C $(KERNEL_DIR) M=$(shell pwd) clean

ko.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * my first linux code
 *
 * Copyright (C) 2010 xxxx Electronics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
 /*
  * 设备树描述:
  *     pkey:pkey {
  *         compatible = "wangzai,powerkey";
  *         poweroff-gpios = <&pio PG 14 GPIO_ACTIVE_HIGH>;
  *     };
  */
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>

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

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

module_init(ko_init);
module_exit(ko_exit);

MODULE_AUTHOR("zky");
MODULE_DESCRIPTION("my first linux code");
MODULE_LICENSE("GPL");

首先内核必须编译过,再编译模块代码

或是使用menuconfig 按下m,可以将内核代码单独编译为.ko模块代码

二、idr

idr.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 *  Idr test code.
 *
 *  Copyright (C) 2025 zky
 *
 *  This module for test idr.
 */

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

static int __init idr_test_init(void)
{
	char *s1 = "parm1", *s2 = "parm2", *s3 = "parm3", *st = "parm_tmp";
	int id1, id2, id3, idt;
	struct idr idr;
	char *ret_s;

	/*
	 * Init idr
	 */
	idr_init(&idr);

	/*
	 * Add element to idr
	 */
	id1 = idr_alloc(&idr, s1, 0, 10, GFP_KERNEL);
	id2 = idr_alloc(&idr, s2, 0, 10, GFP_KERNEL);
	id3 = idr_alloc(&idr, s3, 0, 10, GFP_KERNEL);

	/*
	 * Find idr element
	 */
	ret_s = idr_find(&idr, id1);
	pr_info("id1 [%d] = %s\n", id1, ret_s);

	ret_s = idr_find(&idr, id2);
	pr_info("id2 [%d] = %s\n", id2, ret_s);

	ret_s = idr_find(&idr, id3);
	pr_info("id3 [%d] = %s\n", id3, ret_s);

	/*
	 * Delete idr element.
	 */
	idr_remove(&idr, id2);

	/*
	 * Replace the idr parameter.
	 */
	idr_replace(&idr, st, id1);

	/*
	 * Check if the idr is empty.
	 */
	if (idr_is_empty(&idr))
		pr_info("idr empty\n");
	else
		pr_info("idr not empty\n");

	/*
	 * Compile each parameter of the idr in a loop.
	 */
	idr_for_each_entry(&idr, ret_s, idt)
		pr_info("id [%d] = %s\n", idt, ret_s);

	/*
	 * Free idr
	 */
	idr_destroy(&idr);

	return 0;
}

static void __exit idr_test_exit(void)
{
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("This is idr test code");

module_init(idr_test_init);
module_exit(idr_test_exit);

三、链表操作

linklist.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * link list test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/slab.h>

/*
 * 定义一个链表结构体
 */
struct element {
	struct list_head list;
#define NAME_LEN 64
	char name[NAME_LEN];
};

/*
 *申请链表元素
 */
static struct element *alloc_ele(char *name)
{
	struct element *ele;

	ele = kmalloc(sizeof(struct element), GFP_KERNEL);
	if (!ele)
		return NULL;
	strcpy(ele->name, name);

	return ele;
}

static void free_ele(struct element *ele)
{
	kfree(ele);
}

static int __init link_list_init(void)
{
	struct element hele;
	struct element *ele, *tmp_ele;
	struct element *ele1, *ele2, *ele3;

	/*
	 * 初始化链表头
	 */
	INIT_LIST_HEAD(&hele.list);

	/*
	 * 申请链表
	 */
	ele1 = alloc_ele("p1");
	if (!ele1)
		return -ENOMEM;

	/*
	 * 将链表添加到链表链中
	 */
	list_add_tail(&ele1->list, &hele.list);

	ele2 = alloc_ele("p2");
	if (!ele2) {
		free_ele(ele1);
		return -ENOMEM;
	}
	list_add_tail(&ele2->list, &hele.list);

	ele3 = alloc_ele("p3");
	if (!ele3) {
		free_ele(ele2);
		free_ele(ele1);
		return -ENOMEM;
	}
	list_add_tail(&ele3->list, &hele.list);

	/*
	 * 遍历删除链表中特定元素
	 * 这里使用list_for_each_entry_safe()而非list_for_each_entry()原因是
	 * 由于在循环链表中要删除元素,而list_for_each_entry()不支持此种操作
	 */
	pr_info("start delete ele\n");
	list_for_each_entry_safe(ele, tmp_ele, &hele.list, list) {
		pr_info(" ===========> ele->name = %s\n", ele->name);
		if (!strcmp(ele->name, "p3")) {
			list_del(&ele->list);
			free_ele(ele);
		}
	}

	pr_info("start get ele\n");
	list_for_each_entry_safe(ele, tmp_ele, &hele.list, list) {
		pr_info(" ===========> ele->name = %s\n", ele->name);
		list_del(&ele->list);
		free_ele(ele);
	}

	if (list_empty(&hele.list))
		pr_info(" ===========> link list is empty\n");
	else
		pr_info(" ===========> link list not empty\n");

	return 0;
}

static void __exit link_list_exit(void)
{
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is link list test");

module_init(link_list_init);
module_exit(link_list_exit);

四、rbtree(红黑树)

rbtree.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * rbtree test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/slab.h>

/*
 * 定义一个红黑树结构体
 */
struct par {
	u32		id;
	char		*name;
	struct rb_node	node;
};

/*
 * 通过id查找对应的结构体
 */
static struct par *rbtree_find_by_id(struct rb_root *root, u32 id)
{
	struct rb_node *node;
	struct par *par;

	node = root->rb_node;
	while (node) {
		par = rb_entry(node, struct par, node);
		if (par->id < id)
			node = node->rb_right;
		else if (par->id > id)
			node = node->rb_left;
		else
			return par;
	}
	return NULL;
}

/*
 * 往红黑树中插入元素
 */
static void rbtree_insert(struct rb_root *root, struct par *par)
{
	struct rb_node **new_node, *parent_node = NULL;
	struct par *curr_par;

	new_node = &(root->rb_node);
	while (*new_node) {
		parent_node = *new_node;
		curr_par = rb_entry(parent_node, struct par,
					node);
		if (curr_par->id > par->id) {
			new_node = &((*new_node)->rb_left);
		} else if (curr_par->id < par->id) {
			new_node = &((*new_node)->rb_right);
		} else {
			pr_info("%u already in tree\n", par->id);
			return;
		}
	}
	rb_link_node(&par->node, parent_node, new_node);
	rb_insert_color(&par->node, root);
}

/*
 * 删除红黑树中对应的元素
 */
static void rbtree_delete(struct rb_root *root, struct par *par)
{
	rb_erase(&par->node, root);
}

/*
 * 删除红黑树中所有元素
 */
static void rbtree_delete_all(struct rb_root *root)
{
	struct rb_node *node, *next;
	struct par *curr_par;

	for (node = rb_first(root);
		next = node ? rb_next(node) : NULL, node != NULL;
			node = next) {
		curr_par = rb_entry(node, struct par, node);
		rbtree_delete(root, curr_par);
	}
}

static int __init rbtree_init(void)
{
	struct rb_root root = {};

	struct par par1 = {
		.id = 1,
		.name = "one",
	};
	struct par par2 = {
		.id = 2,
		.name = "two",
	};
	struct par par3 = {
		.id = 3,
		.name = "three",
	};

	rbtree_insert(&root, &par1);
	rbtree_insert(&root, &par2);
	rbtree_insert(&root, &par3);

	pr_info("find name = %s\n", rbtree_find_by_id(&root, 1)->name);
	pr_info("find name = %s\n", rbtree_find_by_id(&root, 2)->name);
	pr_info("find name = %s\n", rbtree_find_by_id(&root, 3)->name);

	rbtree_delete_all(&root);

	return 0;
}

static void __exit rbtree_exit(void)
{
	pr_info("%s\n", __func__);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is rbtree test");

module_init(rbtree_init);
module_exit(rbtree_exit);

Makefile(后续使用时注意更改文件名称)

bash 复制代码
CROSS_PREFIX := /home/zky/ProjectsHub/Linux_Projects/pingdichan/riscv64-wangzai-linux-gnu-gcc/bin/riscv64-unknown-linux-gnu-
KERNEL_DIR := /home/zky/ProjectsHub/Linux_Projects/pingdichan/linux-5.4/
arch := riscv
obj-m := rbtree.o

cc := $(CROSS_PREFIX)gcc
ld := $(CROSS_PREFIX)ld

.PHONY:all
all:
	make ARCH=$(arch)  CC=$(cc) LD=$(ld) -C $(KERNEL_DIR) M=$(shell pwd) modules V=1

.PHONY:clean
clean:
	make ARCH=$(arch) CC=$(cc) LD=$(ld) -C $(KERNEL_DIR) M=$(shell pwd) clean

env.sh(后续使用时注意更改文件名称)

bash 复制代码
#!/bin/bash

export ARCH=riscv
export CROSS_COMPILE=/home/zky/ProjectsHub/Linux_Projects/pingdichan/riscv64-wangzai-linux-gnu-gcc/bin/riscv64-unknown-linux-gnu-

echo "ARCH: $ARCH"
echo "CROSS_COMPILE: $CROSS_COMPILE"

# /home/zky/ProjectsHub/Linux_Projects/pingdichan/linux-5.4/scripts/checkpatch.pl -f linklist.c

# 定义checkpatch.pl路径(方便维护)
CHECKPATCH_PATH="/home/zky/ProjectsHub/Linux_Projects/pingdichan/linux-5.4/scripts/checkpatch.pl"
# 要检查的文件
CHECK_FILE="rbtree.c"

echo -e "\n========== 开始检查 $CHECK_FILE 代码规范 =========="
"$CHECKPATCH_PATH" -f "$CHECK_FILE"

# 检查执行结果,给出友好提示
if [ $? -eq 0 ]; then
    echo -e "========== 完成检查 $CHECK_FILE 代码规范 =========="
    echo -e "\n✅ $CHECK_FILE 代码规范检查通过!"
else
    echo -e "========== 完成检查 $CHECK_FILE 代码规范 =========="
    echo -e "\n⚠️ $CHECK_FILE 代码规范检查发现问题,请根据提示修改!"
    # 非0退出码,但不终止脚本(可选,根据你的需求调整)
    # exit 1
fi

使用方法:

bash 复制代码
# 使用ubuntu终端
. ./ex.sh

make

adb devices

adb push ./rbtree.ko /root

# 后面使用板子的串口终端
root

cd /root

ls

lsmod

insmod rbtree.ko

rmmod rbtree.ko

注:空行不能有tab;变量定义和执行代码间需要空行隔开;

五、bitmap

bitmap.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * bitmap test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

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


/*
 * 定义bitmap
 */
#define BITMAP_LEN 128
static DECLARE_BITMAP(test_bitmap1, BITMAP_LEN);
static DECLARE_BITMAP(test_bitmap2, BITMAP_LEN);

static int __init bitmap_test_init(void)
{
	int i, len, start, align_mask;

	for (i = 0; i < 10; i++) {
		/*
		 * 设置特定的bit
		 */
		bitmap_set(test_bitmap1, i, 1);
	}

	/*
	 * 检查bitmap是否为全1
	 */
	if (bitmap_full(test_bitmap1, BITMAP_LEN))
		pr_info("bitmap full\n");
	else
		pr_info("bitmap not full\n");

	/*
	 * 将所有bit设为0
	 */
	bitmap_zero(test_bitmap1, BITMAP_LEN);

	/*
	 * 将所有bit设为1
	 */
	bitmap_fill(test_bitmap2, BITMAP_LEN);

	/*
	 * 将test_bitmap1与test_bitmap2的每个bit进行与操作
	 */
	bitmap_and(test_bitmap1, test_bitmap1, test_bitmap2, BITMAP_LEN);

	/*
	 * 检查test_bitmap1的所有bit是否为0
	 */
	if (bitmap_empty(test_bitmap1, BITMAP_LEN))
		pr_info("bitmap empty\n");
	else
		pr_info("bitmap not empty\n");

	/*
	 * 将所有bit设为0
	 */
	bitmap_zero(test_bitmap1, BITMAP_LEN);

	/*
	 * 将所有bit设为1
	 */
	bitmap_fill(test_bitmap2, BITMAP_LEN);

	/*
	 * 将test_bitmap1与test_bitmap2的每个bit进行或操作
	 */
	bitmap_or(test_bitmap1, test_bitmap1, test_bitmap2, BITMAP_LEN);

	/*
	 * 检查test_bitmap1的所有bit是否为0
	 */
	if (bitmap_full(test_bitmap1, BITMAP_LEN))
		pr_info("bitmap full\n");
	else
		pr_info("bitmap not full\n");

	/*
	 * 将所有bit设为0
	 */
	bitmap_zero(test_bitmap1, BITMAP_LEN);

	for (i = 0; i < 10; i++) {
		/*
		 * 设置特定的bit
		 */
		bitmap_set(test_bitmap1, i, 1);
	}

	/*
	 * bitmap_find_next_zero_area - find a contiguous aligned zero area
	 * @map: The address to base the search on
	 * @size: The bitmap size in bits
	 * @start: The bitnumber to start searching at
	 * @nr: The number of zeroed bits we're looking for
	 * @align_mask: Alignment mask for zero area
	 *
	 * The @align_mask should be one less than a power of 2; the effect is that
	 * the bit offset of all zero areas this function finds is multiples of that
	 * power of 2. A @align_mask of 0 means no alignment is required.
	 */
	/*
	 * 从start的bit处查找不需要对齐的连续2个为0的区域标签
	 */
	len = 2;
	start = 0;
	align_mask = 0;
	i = bitmap_find_next_zero_area(test_bitmap1, BITMAP_LEN,
				       start, len, align_mask);
	pr_info("idx = %d\n", i);

	return 0;
}

static void __exit bitmap_test_exit(void)
{
	pr_info("%s\n", __func__);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is bitmap test");

module_init(bitmap_test_init);
module_exit(bitmap_test_exit);

六、kfifo

kfifo.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * kfifo test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

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

#define KFIFO_SIZE 256

static int __init kfifo_test_init(void)
{
	char buffer[8], val[2];
	struct kfifo kfifo;
	int ret = 0;

	/*
	 * 申请一个深度为KFIFO_SIZE的kfifo
	 */
	ret = kfifo_alloc(&kfifo, KFIFO_SIZE, GFP_KERNEL);
	if (ret) {
		pr_info("Kfifo mallo fail\n");
		return -EINVAL;
	}

	/*
	 * 往kfifo中压入元素
	 */
	buffer[0] = 1;
	buffer[1] = 2;
	kfifo_in(&kfifo, buffer, 2);
	buffer[0] = 3;
	buffer[1] = 4;
	kfifo_in(&kfifo, buffer, 2);

	pr_info("currtent kfifo len = %d\n", kfifo_len(&kfifo));
	/*
	 * 获取kfifo的元素
	 */
	if (kfifo_out(&kfifo, &val, 2) != 2) {
		ret = -EINVAL;
		goto fail;
	}

	pr_info("first get val = %d, %d\n", val[0], val[1]);
	/*
	 * 判断kfifo是否为空
	 */
	if (kfifo_is_empty(&kfifo))
		pr_info("current kfifio is empty\n");
	else
		/*
		 * 如果kfifo不为空,则获取kfifo的长度
		 */
		pr_info("current kfifo len = %d\n", kfifo_len(&kfifo));

	if (kfifo_out(&kfifo, &val, 2) != 2) {
		ret = -EINVAL;
		goto fail;
	}

	pr_info("second get val = %d, %d\n", val[0], val[1]);
	if (kfifo_is_empty(&kfifo))
		pr_info("current kfifio is empty\n");
	else
		pr_info("current kfifo len = %d\n", kfifo_len(&kfifo));

fail:
	/*
	 * 释放kfifo
	 */
	kfifo_free(&kfifo);

	return 0;
}

static void __exit kfifo_test_exit(void)
{
	pr_info("%s\n", __func__);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is kfifo test");

module_init(kfifo_test_init);
module_exit(kfifo_test_exit);

七、栈回溯(调试方面)

noinline 防止内联优化

dump_stack.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * dump stack test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

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

static noinline void function_1(void)
{
	dump_stack();
}

static noinline void function_2(void)
{
	function_1();
}

static noinline void function_3(void)
{
	function_2();
}

static int __init dump_stack_test_init(void)
{

	function_3();

	return 0;
}

static void __exit dump_stack_test_exit(void)
{
}

MODULE_AUTHOR("Wangzai <1587636487@qq.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is dump stack test");

module_init(dump_stack_test_init);
module_exit(dump_stack_test_exit);

dmesg

由于编译环境上下文的问题会出现下面的警告,但是能正常安装使用.ko文件,不要管这个警告了

八、锁

8.1 互斥锁

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * mutex test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static struct task_struct *thread1, *thread2;
static DEFINE_MUTEX(mutexlock);
static int shared_resource;

/*
 * 线程处理函数,thread1和thread2共用同一个线程处理函数。
 */
static int thread_handler(void *arg)
{
	pr_info("thread running\n");

	while (!kthread_should_stop()) {

		/*
		 * 互斥锁,如果没抢到锁,就会被调度走。
		 */
		mutex_lock(&mutexlock);

		shared_resource = 0;
		if (shared_resource == 1)
			pr_info("shared_resource verfiy to 1\n");

		shared_resource = 1;
		if (shared_resource == 0)
			pr_info("shared_resource verfiy to 0\n");
		mutex_unlock(&mutexlock);
	}

	return 0;
}

static int __init mutex_test_init(void)
{
	/*
	 * 申请两个线程,实现两个线程共同操作同一个变量。
	 */
	thread1 = kthread_run(thread_handler, (void *)1, "thread1");
	if (IS_ERR(thread1)) {
		pr_info("thread1 create fail\n");
		return -1;
	}
	pr_info("thread1 create success\n");

	thread2 = kthread_run(thread_handler, (void *)2, "thread2");
	if (IS_ERR(thread2)) {
		kthread_stop(thread1);
		pr_info("thread2 create fail\n");
		return -1;
	}
	pr_info("thread2 create success\n");

	return 0;
}

static void __exit mutex_test_exit(void)
{
	kthread_stop(thread1);
	kthread_stop(thread2);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is mutex test");

module_init(mutex_test_init);
module_exit(mutex_test_exit);

8.2 自旋锁

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * spinlock test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static struct task_struct *thread1, *thread2;
static DEFINE_SPINLOCK(spinlock);
static int shared_resource;

/*
 * 线程处理函数,thread1和thread2共用同一个线程处理函数。
 */
static int thread_handler(void *arg)
{
	unsigned long flags;

	pr_info("thread running\n");

	while (!kthread_should_stop()) {

		/*
		 * 自旋锁,如果没抢到锁,就会陷入自旋。
		 */
		spin_lock_irqsave(&spinlock, flags);
		shared_resource = 0;
		if (shared_resource == 1)
			pr_info("shared_resource verfiy to 1\n");
		spin_unlock_irqrestore(&spinlock, flags);

		spin_lock_irqsave(&spinlock, flags);
		shared_resource = 1;
		if (shared_resource == 0)
			pr_info("shared_resource verfiy to 0\n");
		spin_unlock_irqrestore(&spinlock, flags);

	}

	return 0;
}

static int __init spinlock_test_init(void)
{
	/*
	 * 申请两个线程,实现两个线程共同操作同一个变量。
	 */
	thread1 = kthread_run(thread_handler, (void *)1, "thread1");
	if (IS_ERR(thread1)) {
		pr_info("thread1 create fail\n");
		return -1;
	}
	thread2 = kthread_run(thread_handler, (void *)2, "thread2");
	if (IS_ERR(thread2)) {
		kthread_stop(thread1);
		pr_info("thread2 create fail\n");
		return -1;
	}

	return 0;
}

static void __exit spinlock_test_exit(void)
{
	kthread_stop(thread1);
	kthread_stop(thread2);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is spinlock test");

module_init(spinlock_test_init);
module_exit(spinlock_test_exit);

8.3 信号量

semaphore.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * semaphore test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/semaphore.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static struct task_struct *thread1, *thread2;
static struct semaphore sem;
static int shared_resource;

/*
 * 线程处理函数,thread1和thread2共用同一个线程处理函数。
 */
static int thread_handler(void *arg)
{
	pr_info("thread running\n");

	while (!kthread_should_stop()) {

		/*
		 * 尝试获取信号量。
		 */
		if (down_interruptible(&sem)) {
			pr_err("Interrupted while waiting for semaphore\n");
			return -ERESTARTSYS;
		}

		shared_resource = 0;
		if (shared_resource == 1)
			pr_info("shared_resource verfiy to 1\n");

		shared_resource = 1;
		if (shared_resource == 0)
			pr_info("shared_resource verfiy to 0\n");

		/*
		 * 释放信号量。
		 */ 
		up(&sem);
	}

	return 0;
}

static int __init semaphore_test_init(void)
{
	/*
	 * 初始化信号量,并设置信号量计数器的初始值为1。
	 */
	sema_init(&sem, 1);

	/*
	 * 申请两个线程,实现两个线程共同操作同一个变量。
	 */
	thread1 = kthread_run(thread_handler, (void *)1, "thread1");
	if (IS_ERR(thread1)) {
		pr_info("thread1 create fail\n");
		return -1;
	}
	thread2 = kthread_run(thread_handler, (void *)2, "thread2");
	if (IS_ERR(thread2)) {
		kthread_stop(thread1);
		pr_info("thread2 create fail\n");
		return -1;
	}

	return 0;
}

static void __exit semaphore_test_exit(void)
{
	kthread_stop(thread1);
	kthread_stop(thread2);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is semaphore test");

module_init(semaphore_test_init);
module_exit(semaphore_test_exit);

rwsem.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * rwsem test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/rwsem.h>
#include <linux/delay.h>

static struct rw_semaphore rwsem;
struct task_struct *threads[4];
static int shared_data;

/*
 * 读线程函数。
 */
static int read_handler(void *arg)
{
	pr_info("read thread running\n");

	while (!kthread_should_stop()) {

		/*
		 * 获取读锁。
		 */
		down_read(&rwsem);
	
		pr_info("read shared_data = %d\n", shared_data);
	
		/*
		 * 释放读锁。
		 */
		up_read(&rwsem);
	}

	return 0;
}

/*
 * 写线程函数。
 */
static int write_handler(void *arg)
{	
	pr_info("write thread running\n");

	while (!kthread_should_stop()) {

		/*
		 * 获取写锁。
		 */
		down_write(&rwsem);
	
		shared_data += 1;
		pr_info("write shared_data = %d\n", shared_data);
	
		/*
		 * 释放写锁。
		 */
		up_write(&rwsem);
	}

	return 0;
}

static int __init rwsem_test_init(void)
{
	int i, j;

	/*
	 * 初始化读写信号量。
	 */
	init_rwsem(&rwsem);

	/*
	 * 创建2个写线程。
	 */
	for (i = 0; i < 2; i++) {
		threads[i] = kthread_run(write_handler, NULL, "write_thread");
		if (IS_ERR(threads[i]))
			goto ERR;
	}
	/*
	 * 创建2个读者线程。
	 */
	for (i = 2; i < 4; i++) {
		threads[i] = kthread_run(read_handler, NULL, "read_thread");
		if (IS_ERR(threads[i]))
			goto ERR;
	}

	return 0;

ERR:
	/*
	 * 处理申请线程失败。
	 */
	for (j = 0; j < i; j++)
		kthread_stop(threads[j]);

	return -1;
}

static void __exit rwsem_test_exit(void)
{
	for (int i = 0; i < 4; i++)
		kthread_stop(threads[i]);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is rwsem test");

module_init(rwsem_test_init);
module_exit(rwsem_test_exit);

8.4 顺序锁

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * seqlock test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/seqlock.h>
#include <linux/delay.h>

static seqlock_t seqlock;
struct task_struct *threads[4];
static int shared_data;

/*
 * 读线程函数。
 */
static int read_handler(void *arg)
{
	unsigned int seq;
	int val;

	pr_info("read thread running\n");

	while (!kthread_should_stop()) {

		do {
			seq = read_seqbegin(&seqlock);
			val = shared_data;
		} while (read_seqretry(&seqlock, seq));

		pr_info("read shared_data = %d\n", shared_data);
	}

	return 0;
}

/*
 * 写线程函数。
 */
static int write_handler(void *arg)
{
	pr_info("write thread running\n");

	while (!kthread_should_stop()) {

		/*
		 * 获取写锁。
		 */
		write_seqlock(&seqlock);

		shared_data += 1;
		pr_info("write shared_data = %d\n", shared_data);

		/*
		 * 释放写锁。
		 */
		write_sequnlock(&seqlock);
	}

	return 0;
}

static int __init seqlock_test_init(void)
{
	int i, j;

	/*
	 * 初始化seqlock。
	 */
	seqlock_init(&seqlock);

	/*
	 * 创建2个写线程。
	 */
	for (i = 0; i < 2; i++) {
		threads[i] = kthread_run(write_handler, NULL, "write_thread");
		if (IS_ERR(threads[i]))
			goto ERR;
	}
	/*
	 * 创建2个读者线程。
	 */
	for (i = 2; i < 4; i++) {
		threads[i] = kthread_run(read_handler, NULL, "read_thread");
		if (IS_ERR(threads[i]))
			goto ERR;
	}

	return 0;
ERR:
	/*
	 * 处理申请线程失败。
	 */
	for (j = 0; j < i; j++)
		kthread_stop(threads[j]);

	return -1;
}

static void __exit seqlock_test_exit(void)
{
	int i;

	for (i = 0; i < 4; i++)
		kthread_stop(threads[i]);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is seqlock test");

module_init(seqlock_test_init);
module_exit(seqlock_test_exit);

8.5 原子操作

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * atomic test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/atomic.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static atomic_t counter = ATOMIC_INIT(0); // 初始化原子变量

static int __init atomic_test_init(void)
{
	/*
	 * 原子设置counter的值为10。
	 */
	atomic_set(&counter, 10);
	pr_info("counter val = %d\n", atomic_read(&counter));
	/*
	 * 原子将counter加1。
	 */
	atomic_inc(&counter);
	pr_info("counter val = %d\n", atomic_read(&counter));

	/*
	 * 原子将counter加5。
	 */
	atomic_add(5, &counter);
	pr_info("counter val = %d\n", atomic_read(&counter));

	/*
	 * 原子将counter减2。
	 */
	atomic_sub(2, &counter);
	pr_info("counter val = %d\n", atomic_read(&counter));

	return 0;
}

static void __exit atomic_test_exit(void)
{
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is atomic test");

module_init(atomic_test_init);
module_exit(atomic_test_exit);

九、线程操作

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * kthread test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>

int val = 1;

/*
 * 线程处理函数
 */
static int kernel_thread(void *arg)
{
	int *val = (int *)arg;

	while (!kthread_should_stop()) {
		msleep(1000);
		pr_info("test_threads_handler: arg = %d\n", *val);
	}

	return 0;
}

static int __init kthread_test_init(void)
{
	struct task_struct *thread;

	/*
	 * 创建内核线程
	 */
	thread = kthread_create(kernel_thread, &val, "test_thread");
	if (IS_ERR(thread)) {
		pr_info("Failed to create waiting thread.\n");
		return PTR_ERR(thread);
	}

	/*
	 * 唤醒内核线程
	 */
	wake_up_process(thread);

	msleep(5000);
	/*
	 * 停止内核线程
	 */
	kthread_stop(thread);

	return 0;
}

static void __exit kthread_test_exit(void)
{
	pr_info("%s\n", __func__);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is create kthread test");

module_init(kthread_test_init);
module_exit(kthread_test_exit);

十、工作队列Wait Queue

工作队列部分类似于线程池的某些方面

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * workqueue test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

struct work_struct work1;
struct work_struct work2;
static struct workqueue_struct *work_queue;

static void work_func1(struct work_struct *work)
{
	pr_info("my_work_func 1 exec\n");
}

static void work_func2(struct work_struct *work)
{
	pr_info("my_work_func 2 exec\n");
}

static int __init workqueue_test_init(void)
{

	/*
	 * 创建一个工作队列
	 */
	work_queue = create_workqueue("wangzai-workqueue");

	/*
	 * 初始化work
	 */
	INIT_WORK(&work1, work_func1);
	INIT_WORK(&work2, work_func2);


	/*
	 * 将work调度运行
	 */
	queue_work(work_queue, &work1);
	queue_work(work_queue, &work2);

	return 0;
}

static void __exit workqueue_test_exit(void)
{
	destroy_workqueue(work_queue);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is workqueue test");

module_init(workqueue_test_init);
module_exit(workqueue_test_exit);

十一、tasklet

Linux tasklet 是内核中用于处理中断的下半部任务的轻量级延迟执行机制,基于软中断实现。

Linux中断分为上半部和下半部

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * tasklet test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

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

static struct tasklet_struct tasklet;

/*
 * tasklet处理函数
 */
static void tasklet_handler(unsigned long data)
{
	pr_info("%s running, data = %ld\n", __func__, data);
}

static int __init tasklet_test_init(void)
{
	unsigned long data = 12;

	/*
	 * 初始化tasklet
	 */
	tasklet_init(&tasklet, tasklet_handler, data);

	/*
	 * 使能tasklet
	 */
	tasklet_enable(&tasklet);

	/*
	 * 调度tasklet
	 */
	tasklet_schedule(&tasklet);

	/*
	 * 失能tasklet
	 */
	tasklet_disable(&tasklet);
	/*
	 * 调度tasklet
	 */
	tasklet_schedule(&tasklet);

	return 0;
}

static void __exit tasklet_test_exit(void)
{
	tasklet_kill(&tasklet);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is tasklet test");

module_init(tasklet_test_init);
module_exit(tasklet_test_exit);

十二、等待队列

详细执行步骤:

  1. 模块初始化(waitqueue_test_init)

    • 初始化等待队列头 wq
    • 创建内核线程 waitqueue_test_thread,线程开始执行。
  2. 内核线程执行

    • 定义等待节点 wait → 调用 prepare_to_wait 挂到 wq,设为可中断休眠;
    • 打印 kernel thread start wait → 调用 schedule(),线程休眠。
  3. 主线程继续执行

    • 调用 msleep(3000),主线程休眠 3 秒;
    • 3 秒后打印 main process wake up kernel thread → 调用 wake_up_interruptible(&wq) 唤醒线程。
  4. 内核线程被唤醒

    • 调度器将线程设为运行状态,线程从 schedule() (之前在这里休眠,现在唤醒继续)处继续执行;
    • 调用 finish_wait 清理等待节点 → 打印 kernel thread wake up → 线程返回 0,退出。
  5. 主线程收尾

    • 调用 kthread_stop 停止线程(此时线程已退出)→ 模块初始化完成。
cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * waitqueue test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>

wait_queue_head_t wq;

/*
 * 线程处理函数
 */
static int waitqueue_test_thread(void *arg)
{
	/*
	 * 初始化wait
	 */
	DEFINE_WAIT(wait);
	/*
	 * 等待前准备
	 */
	prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE);

	pr_info("kernel thread start wait\n");

	/*
	 * 利用schedule()将任务调度走
	 */
	schedule();

	finish_wait(&wq, &wait);
	pr_info("kernel thread wake up\n");

	return 0;
}

static int __init waitqueue_test_init(void)
{
	struct task_struct *thread;

	/*
	 * 初始化waitqueue
	 */
	init_waitqueue_head(&wq);

	/*
	 * 创建内核线程
	 */
	thread = kthread_run(waitqueue_test_thread, NULL, "waitqueue_test_thread");
	if (IS_ERR(thread)) {
		pr_info("Failed to create waiting thread.\n");
		return PTR_ERR(thread);
	}

	msleep(3000);

	pr_info("main process wake up kernel thread\n");

	/*
	 * 唤醒等待的waitqueue
	 */
	wake_up_interruptible(&wq);

	/*
	 * 停止内核线程
	 */
	kthread_stop(thread);

	return 0;
}

static void __exit waitqueue_test_exit(void)
{
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is waitqueue test");

module_init(waitqueue_test_init);
module_exit(waitqueue_test_exit);

十三、完成量complete

在 Linux 中,完成量可以看成是一种与信号量相关的数据结构,在某些情况下,完成量可以替代

信号量的使用。

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * complete test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static struct completion completion;

/*
 * 线程处理函数
 */
static int complete_test_thread(void *arg)
{
	pr_info("kernel thread running\n");
	msleep(3000);
	pr_info("kernel threads complete\n");
	/*
	 * 利用complete()通知主线程完成
	 */
	complete(&completion);

	return 0;
}

static int __init complete_test_init(void)
{
	struct task_struct *thread;

	init_completion(&completion);
	/*
	 * 创建线程
	 */
	thread = kthread_run(complete_test_thread, NULL, "complete_test_thread");
	if (IS_ERR(thread)) {
		pr_info("Failed to create waiting thread.\n");
		return PTR_ERR(thread);
	}

	pr_info("Main thread: Waiting for completion\n");
	/*
	 * 利用complete机制等待其他线程完成
	 */
	wait_for_completion(&completion);

	if (completion_done(&completion))
		pr_info("Main thread: Detected completion done\n");

	return 0;
}

static void __exit complete_test_exit(void)
{
	pr_info("%s\n", __func__);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is complete test");

module_init(complete_test_init);
module_exit(complete_test_exit);

十四、通知链notifier chain

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * notifier chain test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/notifier.h>
#include <linux/ktime.h>

static BLOCKING_NOTIFIER_HEAD(test_notifier_head);

#define ACTION_A 1
#define ACTION_B 2

/*
 * 内核通知链回调函数
 */
static int test_nb1_notifier_call(struct notifier_block *self,
				  unsigned long action, void *data)
{
	if (action == ACTION_A) {
		pr_info("%s :action = ACTION_A, data = %d\n", __func__,
			*(unsigned int *)data);
	} else if (action == ACTION_B) {
		pr_info("%s :action = ACTION_B, data = %d\n", __func__,
			*(unsigned int *)data);
	} else
		pr_info("%s :unkown action\n", __func__);

	return NOTIFY_DONE;
}

/*
 * 内核通知链结构体
 */
static struct notifier_block test_nb1 = {
	/*
	 * 通知链函数指针
	 */
	.notifier_call = test_nb1_notifier_call,
	/*
	 * 通知链优先级
	 */
	.priority = INT_MIN + 1,
};

static int test_nb2_notifier_call(struct notifier_block *self,
				  unsigned long action, void *data)
{
	if (action == ACTION_A) {
		pr_info("%s :action = ACTION_A, data = %d\n", __func__,
			*(unsigned int *)data);
	} else if (action == ACTION_B) {
		pr_info("%s :action = ACTION_B, data = %d\n", __func__,
			*(unsigned int *)data);
	} else
		pr_info("%s :unkown action\n", __func__);

	return NOTIFY_DONE;
}

static struct notifier_block test_nb2 = {
	.notifier_call = test_nb2_notifier_call,
	.priority = INT_MIN + 2,
};

static int __init notifier_chain_test_init(void)
{
	unsigned long action;
	unsigned int data;

	/*
	 * 注册内核通知链
	 */
	pr_info("notifier chain register\n");
	blocking_notifier_chain_register(&test_notifier_head, &test_nb1);
	blocking_notifier_chain_register(&test_notifier_head, &test_nb2);

	/*
	 * 调用内核通知链
	 */
	pr_info("notifier chain call acton = ACTION_A\n");
	action = ACTION_A;
	data = 10;
	blocking_notifier_call_chain(&test_notifier_head, action, &data);

	pr_info("notifier chain call acton = ACTION_B\n");
	action = ACTION_B;
	data = 1;
	blocking_notifier_call_chain(&test_notifier_head, action, &data);

	return 0;
}

static void __exit notifier_chain_test_exit(void)
{
	/*
	 * 注销内核通知链
	 */
	blocking_notifier_chain_unregister(&test_notifier_head, &test_nb1);
	blocking_notifier_chain_unregister(&test_notifier_head, &test_nb2);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is notifier chain test");

module_init(notifier_chain_test_init);
module_exit(notifier_chain_test_exit);

十五、定时器

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * timer test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

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

static struct timer_list timer;

/*
 * 定时器回调函数
 */
void timer_callback(struct timer_list *timer)
{
	pr_info("timer expired!\n");
	/*
	 * 重新启动定时器
	 */
	mod_timer(timer, jiffies + msecs_to_jiffies(1000));
}

static int __init timer_test_init(void)
{
	/*
	 * 初始化定时器
	 */
	timer_setup(&timer, timer_callback, 0);

	/*
	 * 启动定时器,1秒后到期
	 */
	mod_timer(&timer, jiffies + msecs_to_jiffies(1000));

	return 0;
}

static void __exit timer_test_exit(void)
{
	del_timer(&timer);
	pr_info("%s\n", __func__);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is timer test");

module_init(timer_test_init);
module_exit(timer_test_exit);

十六、字符设备char

app.c

cpp 复制代码
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <stdint.h>
#include <sys/mman.h>

int main(int argc, char *argv[])
{
	int devfd, val, size = 4096;
	unsigned int ioctl_val = 0;
	char *src = "hello world\n";
	char dst[128];

	devfd = open("/dev/chrdev_dev", O_RDWR);
	if (devfd < 0) {
		printf("open /dev/chrdev fail\n");
		return -1;
	}
	
	write(devfd, src, strlen(src));
	read(devfd, dst, strlen(src));

	ioctl_val = 1;
	ioctl(devfd, 1111, &ioctl_val);
	ioctl_val = 3;
	ioctl(devfd, 2222, &ioctl_val);

	close(devfd);

	printf("dst  = %s\n", dst);

	return 0;
}

chrdev.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * chrdev test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define CHARDEV_DEV_CNT (1)
#define CHARDEV_DEV_NAME "chrdev_dev"
#define CHARDEV_CLASS_NAME "chrdev_class"
#define CHARDEV_CDEV_NAME "chrdev_chrdev"
#define MALLOC_LEN (128)

#define CHRDEV_DBG pr_info("func = %s, line = %d\n", __func__, __LINE__)

struct chrdev_dev {
	dev_t         devid;
	struct cdev   *cdev;
	struct class  *class;
	struct device *dev;
};

static struct chrdev_dev chrdev;

/*
 * 字符设备open()函数
 */
static int chrdev_open(struct inode *inode_p, struct file *file_p)
{
	char *mem = NULL;

	CHRDEV_DBG;

	/*
	 * 申请一段内存并保存到文件的私有数据中。
	 */
	mem = kmalloc(MALLOC_LEN, GFP_KERNEL);
	if (!mem)
		return -1;
	file_p->private_data = mem;

	return 0;
}

/*
 * 字符设备release()函数,即关闭函数
 */
static int chrdev_release(struct inode *inode_p, struct file *file_p)
{
	char *mem = (char *)file_p->private_data;

	CHRDEV_DBG;

	/*
	 * 通过kfree()函数释放在chrdev_open()函数中申请的内存。
	 */
	kfree(mem);

	return 0;
}

/*
 * 字符设备读函数
 */
static ssize_t chrdev_read(struct file *file_p, char __user *buf, size_t size,
			   loff_t *ppos)
{
	char *mem = (char *)file_p->private_data;
	int rc;

	CHRDEV_DBG;

	/*
	 * 通过copy_to_user()函数将内核空间的数据(mem)拷贝到应用程序空间(buf)。
	 */
	rc = copy_to_user(buf, mem, size);
	if (rc)
		return -1;

	return size;
}

/*
 * 字符设备写函数
 */
static ssize_t chrdev_write(struct file *file_p, const char __user *buf,
			    size_t size, loff_t *ppos)
{
	char *mem = (char *)file_p->private_data;
	int rc;

	CHRDEV_DBG;

	/*
	 * 通过copy_from_user()函数将应用程序空间的数据(buf拷贝到内核空间(mem)。
	 */
	rc = copy_from_user(mem, buf, size);
	if (rc)
		return -1;

	return size;
}

/*
 * 字符设备iotctl()函数,通过cmd传递命令,通过arg传递数据
 */
static long chrdev_unlocked_ioctl(struct file *file_p, unsigned int cmd,
				  unsigned long arg)
{
	unsigned int val = 0;
	int rc;

	CHRDEV_DBG;
	rc = copy_from_user((void *)&val, (const char __user *)arg, sizeof(val));
	if (rc)
		return -1;
	pr_info("cmd = %d, val = %d\n", cmd, val);

	return 0;
}

/*
 * 文件操作结构体
 */
static const struct file_operations chrdev_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = chrdev_unlocked_ioctl,
	.read = chrdev_read,
	.write = chrdev_write,
	.open = chrdev_open,
	.release = chrdev_release,
};

static int __init chrdev_test_init(void)
{
	int major = 0, minor = 0;

	/*
	 * 注册一个字符设备。
	 * 本质是创建一组与设备号绑定的文件读写接口。
	 *
	 * 第一个参数为传入的major,当major = 0时,自动获取major。
	 */
	major = register_chrdev(0, CHARDEV_CDEV_NAME, &chrdev_fops);
	if (!major)
		return -1;
	chrdev.devid = MKDEV(major, minor);

	/*
	 * 创建一个class。
	 */
	chrdev.class = class_create(THIS_MODULE, CHARDEV_CLASS_NAME);
	if (IS_ERR(chrdev.class))
		goto UNREG_CHRDEV;

	/*
	 * 创建一个class下面的设备,并通过设备号与字符设备绑定,这个设备是 chrdev_dev
	 */
	chrdev.dev = device_create(chrdev.class, NULL, chrdev.devid,
				   NULL, CHARDEV_DEV_NAME);
	if (!chrdev.dev)
		goto CLASS_DESTORY;

	return 0;

CLASS_DESTORY:
	class_destroy(chrdev.class);
UNREG_CHRDEV:
	unregister_chrdev(major, CHARDEV_CDEV_NAME);

	return -1;
}

static void __exit chrdev_test_exit(void)
{
	/*
	 * 释放设备。
	 */
	device_destroy(chrdev.class, MAJOR(chrdev.devid));
	/*
	 * 释放class。
	 */
	class_destroy(chrdev.class);
	/*
	 * 释放字符设备。
	 */
	unregister_chrdev(MAJOR(chrdev.devid), CHARDEV_CDEV_NAME);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is chrdev test");

module_init(chrdev_test_init);
module_exit(chrdev_test_exit);

/home/zky/ProjectsHub/Linux_Projects/pingdichan/riscv64-wangzai-linux-gnu-gcc/bin/riscv64-unknown-linux-gnu-gcc app.c -o app

需要单独编译app,再在板子里执行

十七、杂设备

/home/zky/ProjectsHub/Linux_Projects/pingdichan/linux-5.4/include/linux/miscdevice.h

杂设备是简化封装后的字符设备

app.c

cpp 复制代码
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <stdint.h>
#include <sys/mman.h>
int main(int argc, char *argv[])
{
	char *str = "hello world\n";
	char buf[4096];
	int devfd;

	devfd = open("/dev/my_miscdevice", O_RDWR);
	if (devfd < 0) {
		printf("open /dev/my_miscdevice fail\n");
		return -1;
	}
	
	write(devfd, str, strlen(str));
	read(devfd, buf, strlen(str));

	close(devfd);

	printf("buf = %s\n", buf);

	return 0;
}

miscdevice.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * miscdevice test
 *
 * Copyright (C) 2025 Wangzai 《搞linux的旺仔》
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>

#define MISCDEVICE_DEV_NAME "my_miscdevice"
#define MALLOC_LEN (4096)

/*
 * 杂设备open()函数
 */
static int miscdevice_open(struct inode *inode, struct file *filp)
{
	char *buf = NULL;

	/*
	 * 申请一段内存并保存到文件的私有数据中。
	 */
	buf = kmalloc(MALLOC_LEN, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	filp->private_data = buf;

	return 0;
}

/*
 * 杂设备release()函数,即关闭函数
 */
static int miscdevice_release(struct inode *inode, struct file *filp)
{
	char *buf = (char *)filp->private_data;

	kfree(buf);

	return 0;
}

/*
 * 杂设备读函数
 */
static ssize_t miscdevice_read(struct file *filp, char __user *ptr, size_t len,
			       loff_t *off)
{
	char *buf = (char *)filp->private_data;

	/*
	 * 通过copy_to_user()函数将内核空间的数据(buf)拷贝到应用程序空间(ptr)。
	 */
	if (copy_to_user(ptr, buf, len))
		return -EFAULT;

	return len;
}

/*
 * 杂设备写函数
 */
static ssize_t miscdevice_write(struct file *filp, const char __user *ptr,
				size_t len, loff_t *off)
{
	char *buf = (char *)filp->private_data;

	/*
	 * 通过copy_from_user()函数将应用程序空间的数据(ptr拷贝到内核空间(buf)。
	 */
	if (copy_from_user(buf,  ptr, len))
		return -EFAULT;

	return len;
}

/*
 * 杂设备iotctl()函数,通过cmd传递命令,通过arg传递数据
 */
static long miscdevice_ioctl(struct file *filp,
			     unsigned int cmd,
			     unsigned long arg)
{
	return 0;
}

/*
 * 文件操作结构体
 */
static const struct file_operations miscdevice_fops = {
	.owner = THIS_MODULE,
	.open = miscdevice_open,
	.release = miscdevice_release,
	.read = miscdevice_read,
	.write = miscdevice_write,
	.unlocked_ioctl = miscdevice_ioctl,
};

/*
 * 杂设备结构体
 */
static struct miscdevice my_miscdevice = {
	.name   = MISCDEVICE_DEV_NAME,
	.minor  = MISC_DYNAMIC_MINOR,
	.fops   = &miscdevice_fops,
};

static int __init miscdevice_test_init(void)
{
	/*
	 * 注册杂设备
	 */
	return misc_register(&my_miscdevice);
}

static void __exit miscdevice_test_exit(void)
{
	/*
	 * 注销杂设备
	 */
	misc_deregister(&my_miscdevice);
}

MODULE_AUTHOR("Wangzai <1587636487@qq.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is miscdevice test");

module_init(miscdevice_test_init);
module_exit(miscdevice_test_exit);

十八、固件加载

load_firmware.c

cpp 复制代码
// SPDX-License-Identifier: GPL-2.0
/*
 * load firmware test
 *
 * Copyright (C) 2025 zky
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>

/*
 * 在根文件系统的/lib/firmware/下要放一个名字为LOAD_IMAGE_NAME所定义的文件
 */
#define LOAD_IMAGE_NAME "test.firmware"

static struct miscdevice load_firmware_misc_dev = {
	.name   = "load_firmware_test",
	.minor  = MISC_DYNAMIC_MINOR,
};

static int __init load_firmware_test_init(void)
{
	const struct firmware *fw;
	int ret;

	/*
	 * 注册一个杂设备
	 */
	ret = misc_register(&load_firmware_misc_dev);
	if (ret)
		return ret;

	/*
	 * 利用request_firmware()加载文件系统下的固件
	 */
	ret = request_firmware(&fw, LOAD_IMAGE_NAME, load_firmware_misc_dev.this_device);
	if (ret) {
		misc_deregister(&load_firmware_misc_dev);
		return ret;
	}

	pr_info("loading firmware = %s\n", fw->data);

	/*
	 * 释放固件
	 */
	release_firmware(fw);

	return 0;
}

static void __exit load_firmware_test_exit(void)
{
	/*
	 * 注销杂设备
	 */
	misc_deregister(&load_firmware_misc_dev);
}

MODULE_AUTHOR("zky");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("This is load firmware test");

module_init(load_firmware_test_init);
module_exit(load_firmware_test_exit);

十九、platform驱动/设备

待更新

二十、设备树

相关推荐
重生之绝世牛码2 小时前
Linux软件安装 —— Flink集群安装(集成Zookeeper、Hadoop高可用)
大数据·linux·运维·hadoop·zookeeper·flink·软件安装
艾莉丝努力练剑2 小时前
【QT】Qt 从零上手:Hello World、项目文件与实战避坑指南
linux·运维·开发语言·c++·qt·继承·qt5
小趴菜不能喝2 小时前
Linux 搭建SVN服务
linux·运维·svn
可爱又迷人的反派角色“yang”2 小时前
k8s(七)
java·linux·运维·docker·云原生·容器·kubernetes
optimistic_chen2 小时前
【Docker入门】namespace 空间隔离
linux·运维·docker·容器·空间隔离
骇客野人2 小时前
Linux通过自动脚本自动化推送k8s Docker镜像
linux·kubernetes·自动化
探序基因2 小时前
CentOS Stream release 9的Rstudio安装
linux·运维·centos
HABuo2 小时前
【linux进程控制(二)】进程等待-->死亡的子进程是如何被父进程等待回收的?
linux·运维·服务器·c语言·c++·ubuntu·centos
wheeldown2 小时前
【Linux网络基础】Linux 网络基础与 TCP 协议
linux·网络·tcp/ip