本章节包含字符设备较为全面的知识,主要包括如下:内核模块、字符设备基础、并发与竞争、IO模型、平台总线、设备树、设备树插件、设备模型、中断
内核模块主要包含的知识点有:模块传参、符号导出、图形化配置、模块编译进内核
字符设备基础包括:字符设备驱动注册、实现较为全面的读写接口(打开、关闭、读写、定位、ioctl)
并发与竞争主要包括:原子操作、自旋锁、信号量、互斥锁
IO模型包括:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO
平台总线:平台总线下字符设备驱动
设备树:设备树的工作原理和常见用法(单独章节解释)
中断:中断的原理与常见用法(单独章节解释)
设备模型:参考迅为电子中的设备模型章节
内核模块传参:
#include <linux/module.h>
#include <linux/kernel.h>
static int number;
static char*name;
static int para[8];
static char str1[10];
static int n_para;
module_param(number, int, S_IRUGO);
module_param(name, charp, S_IRUGO);
module_param_array(para, int, &n_para, S_IRUGO);
module_param(str1, charp, S_IRUGO);
static int __init paraeter_init(void)
{
printk(KERN_INFO "Hello, world!\n");
static int i;
printk(KERN_INFO "number = %d\n", number);
printk(KERN_INFO "name = %s\n", name);
printk(KERN_INFO "para = ");
for(i = 0; i < n_para; i++)
{
printk(KERN_CONT "%d ", para[i]);
}
printk(KERN_EMERG "str1 = %s\n", str1);
return 0;
}
static void __exit paraeter_exit(void)
{
printk(KERN_EMERG "paraeter exit!\n");
}
module_init(paraeter_init);
module_exit(paraeter_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ErHu");
MODULE_DESCRIPTION("A simple Hello, World! module");
实现效果如下

这里遇到了一个问题就是使用网络加载的根文件系统无法删除文件,解决方法
| 参数 | 含义 |
|---|---|
rw | 读写权限(必须有) |
sync | 同步写入磁盘 |
no_root_squash | 允许 root 用户拥有完整权限(开发板默认以 root 运行) |
no_subtree_check | 禁用子树检查,提高性能 |
sudo vi /etc/exports进行如下修改:/home/eh/linux/nfs/xuexi/rootfs *(rw,sync,no_root_squash,no_subtree_check)之后重新导出:sudo exportfs -ra
然后再重新加载根文件系统即可以解决该问题
make menuconfig常用命令

接下来将较图形化配置相关知识
图形化配置主要使用Kconfig语法来实现在menuconfig图形配置界面下配置相关的选项,只需要注意相关的语法即可,详细可以参考这篇文章:https://erhudiy.cn/wp-admin/post.php?post=142&action=edit


注意在NXP的kernel目录下没有固定的Kconfig文件,这是 NXP 官方内核源码的正常结构。标准 Linux 内核在 kernel/ 下有一个 Kconfig,但 NXP 的 IMX6ULL 源码把这部分拆分成了多个 Kconfig.* 分片文件,由顶层 Kconfig 直接 source 引用,一个是需要创建一个新的Kconfig文件,另一个是需要在上一层的Kconfig文件中进行引用
接下来就是重点介绍将驱动模块编译进内核的方法
第一步需要在将你的驱动文件、新的Kconfig文件、makfile文件放在Linux特定的目录下面

config HELLO
tristate “hello world”
default y
help
hello hello
上述的HELLO会在.config文件中显示为CONFIG_HELLO,hello word会在menuconfig界面中进行显示


makefile文件内容
obj-$(CONFIG_HELLO) += moudle_test.o
此外还需要对它上一级的目录中makefile文件和Kconfig文件进行补充修改


之后需要重新编译内核,重新加载内核就可以看到驱动模块被加载到了Linux内核中了
接下来补充几个知识点1.vim编辑器使用技巧。2.定向输出文件和其它的一些技巧
vim编辑器使用技巧参考这篇文章:,这里先补充晚上遇到的关键词搜索功能:
| 命令 | 作用 |
|---|---|
/关键词 | 从光标位置向下查找 |
?关键词 | 从光标位置向上查找 |
n | 跳到下一个匹配项 |
N | 跳到上一个匹配项 |
# 区分大小写查找
/keyword
# 忽略大小写查找
/\ckeyword
# 查找完整单词(不匹配子串)
/\<keyword\>
# 高亮所有匹配项
:set hlsearch
# 关闭高亮
:nohlsearch
# 清除上次高亮(快捷方式)
:noh
# 替换当前行第一个匹配项
:s/old/new
# 替换当前行所有匹配项
:s/old/new/g
# 替换文件中所有匹配项
:%s/old/new/g
# 替换前确认每个
:%s/old/new/gc
再编译内核时遇到了一些问题,由于编译内核时输出信息很多,需要将输出信息定向输出到指定文件中,再从文件中检索错误所在
make -j4 2>&1 | tee build.log
部分 含义
make -j4 编译内核,-j4 表示 4 个任务并行编译(加快速度)
2>&1 将标准错误输出(stderr) 重定向到标准输出(stdout),合并两者
| 管道,把 make 的输出同时传给下一个命令
tee build.log 读取管道输入,一边打印到终端,一边写入文件 build.log
为什么用 2>&1
1 = 标准输出(stdout,正常输出)
2 = 标准错误(stderr,错误信息)
2>&1 = 把错误信息也合并到标准输出中,这样管道能捕获所有输出
再加载内核后需要查看驱动模块在加载时候的打印信息
# 查看所有启动消息
dmesg
# 分页查看
dmesg | less
# 搜索包含特定关键词的消息
dmesg | grep -i "error"
dmesg | grep -i "usb"
dmesg | grep -i "network"
# 显示最近的消息(常用于查看刚插入设备的消息)
dmesg | tail -20
# 高亮显示关键词
dmesg | grep --color=auto -i "error"
# 从头搜索关键词
dmesg | head -100
二、实时监控新消息
# 实时监控新启动消息(相当于 tail -f)
dmesg -w
# 实时监控并过滤
dmesg -w | grep -i "error"
三、查看系统日志文件
# 通用 Linux 日志目录
ls /var/log/
# 查看系统启动日志(需要 root)
cat /var/log/boot.log
# 查看系统消息日志
cat /var/log/messages | grep -i "error"
# 查看内核日志
cat /var/log/kern.log
# journalctl(systemd 系统)
journalctl -b # 本次启动日志
journalctl -b -k # 内核日志
journalctl -b -k | grep -i "error"
journalctl -f # 实时监控
这个文件讲解了日志要如何设置,可以用来参考
接下来要关注的内容:字符设备驱动注册