10.linux驱动之helloworld - 指南
1.printk打印问题
驱动调试时,需要打印log,内核调试,打印使用printk打印。
当使用ssh这种登录方式时,无法将printk的内容输出到终端上。
核心原因
- SSH 终端并非系统 “物理控制台”:Linux 的 “控制台” 默认指物理显示器(tty1-tty6),而 SSH 连接属于伪终端(pty)。内核的 printk 默认仅向物理控制台输出,不会主动转发到伪终端。
- 日志级别匹配问题:即使 SSH 终端能接收日志,若 printk 的日志级别≥当前控制台日志级别(如你截图中的 4),仍不会在终端打印,仅存入内核环形缓冲区。
- syslog 服务未转发日志:若系统未运行 klogd、syslogd 或 rsyslogd 等日志服务,内核日志无法从环形缓冲区转发到 SSH 终端对应的输出流。
2.降低控制台日志打印级别
printk 有 8 个日志级别(数值 0~7,数值越小级别越高),控制台也有对应的日志级别(可通过cat /proc/sys/kernel/printk查看,第一个数值即为当前控制台日志级别)。当printk 的日志级别数值 < 控制台日志级别数值时,会在控制台打印。
首先确认,是否有printk打印:
dmesg
如图所示:

内核环形缓冲区中,有printk打印的信息。
/proc/sys/kernel/printk,该文件四个数值分别对应控制台日志级别、默认消息日志级别、最低控制台日志级别、默认控制台日志级别。
查看当前控制台日志级别:
cat /proc/sys/kernel/printk
修改控制台日志级别:
echo 7 > /proc/sys/kernel/printk
3.驱动调试方法
1. 编写驱动调试源文件
编写最简单的:
#include
#include
static int __init helloworld_init(void)
{
printk(KERN_EMERG "helloworld_init\r\n");
return 0;
}
static void __exit helloworld_exit(void)
{
printk(KERN_EMERG "helloworld_exit\r\n");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("carrylee");
2. 编写makefile
#已经编译过的内核源码路径
KERNEL_DIR = /home/uisrc/uisrc-lab-xlnx/sources/kernel
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
#当前路径
CURRENT_DIR = $(shell pwd)
MODULE = helloworld
all :
#进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
rm -rf *.symvers *.order *.o *.mod.o *.mod.c
ifneq ($(APP), )
$(CROSS_COMPILE)gcc $(APP).c -o $(APP)
endif
clean :
make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
rm $(APP)
#指定编译哪个文件
obj-m += $(MODULE).o
3. 编译
将源文件拷贝到home目录下。

make
生成.ko文件

4. 加载到目标电路板

将生成的ko文件拷贝出来。

通过ssh 网络连接电路板,通过TFTP将文件扔进去。
5. 验证
5.1. 加载内核
sudo insmod helloworld.ko

5.2. 移除模块
sudo rmmod helloworld
5.3. 确保模块被移除
lsmod | grep helloworld

4.注册字符设备
#include
#include
#include // 文件操作相关头文件
#include // 字符设备相关头文件
#include // 用户空间与内核空间数据拷贝
#define DEVICE_NAME "helloworld_dev" // 设备名称
#define MAJOR_NUM 0 // 主设备号(0表示由内核自动分配)
#define MINOR_NUM 0 // 次设备号
#define DEVICE_COUNT 1 // 设备数量
static dev_t dev_num; // 设备号(主设备号+次设备号)
static struct cdev helloworld_cdev; // 字符设备结构体
// 设备打开操作
static int helloworld_open(struct inode *inode, struct file *file) {
printk(KERN_EMERG "helloworld device opened\r\n");
return 0;
}
// 设备关闭操作
static int helloworld_release(struct inode *inode, struct file *file) {
printk(KERN_EMERG "helloworld device released\r\n");
return 0;
}
// 文件操作结构体
static struct file_operations helloworld_fops = {
.owner = THIS_MODULE,
.open = helloworld_open,
.release = helloworld_release,
};
static int __init helloworld_init(void) {
int ret;
// 1. 分配设备号
if (MAJOR_NUM == 0) {
ret = alloc_chrdev_region(&dev_num, MINOR_NUM, DEVICE_COUNT, DEVICE_NAME);
} else {
dev_num = MKDEV(MAJOR_NUM, MINOR_NUM);
ret = register_chrdev_region(dev_num, DEVICE_COUNT, DEVICE_NAME);
}
if (ret < 0) {
printk(KERN_ERR "Failed to allocate device number\r\n");
return ret;
}
printk(KERN_EMERG "helloworld_init: major=%d, minor=%d\r\n",
MAJOR(dev_num), MINOR(dev_num));
// 2. 初始化字符设备并注册
cdev_init(&helloworld_cdev, &helloworld_fops);
helloworld_cdev.owner = THIS_MODULE;
ret = cdev_add(&helloworld_cdev, dev_num, DEVICE_COUNT);
if (ret < 0) {
printk(KERN_ERR "Failed to add character device\r\n");
unregister_chrdev_region(dev_num, DEVICE_COUNT);
return ret;
}
return 0;
}
static void __exit helloworld_exit(void) {
// 注销字符设备并释放设备号
cdev_del(&helloworld_cdev);
unregister_chrdev_region(dev_num, DEVICE_COUNT);
printk(KERN_EMERG "helloworld_exit\r\n");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("carrylee");
如图所示,创建成功,将主设备号(241)和次设备号(0)打印了出来。

然后执行:
sudo mknod /dev/helloworld_dev c 241 0
可以看到,设备安装成功。

浙公网安备 33010602011771号