纸翼 · 加载中
1987 words
10 minutes
第一个驱动程序

第一个驱动程序#

一、#

linux-4.9/
├── arch/ # 架构专属代码(核心:arm/32位ARM、arm64/64位ARM64)
├── include/ # 全局头文件目录(驱动编译核心依赖,含linux/、asm/、asm-generic/)
│ ├── linux/ # 内核通用头文件(如module.h、init.h、types.h,驱动必包含)
│ ├── asm/ # 架构专属头文件(软链接,32位指向asm-arm,64位指向asm-arm64)
│ └── asm-generic/ # 跨架构通用头文件(兜底兼容,解决架构头文件缺失)
├── kernel/ # 内核核心功能代码(进程、调度等,驱动编译间接依赖)
└── scripts/ # 编译脚本、工具链(驱动编译的Makefile依赖此目录脚本)

三、Linux驱动核心框架(字符设备驱动,全文实操的驱动类型)#

全文实现的是 Linux最基础、最通用的字符设备驱动,是嵌入式驱动开发入门核心框架,完整分层+核心要素如下:

1. 驱动整体分层(用户层 ↔ 内核层)#

应用程序(APP) → 系统调用 → Linux内核 → 字符设备驱动 → 硬件
  • 应用层:通过 open/read/write/ioctl 等标准接口操作驱动,无需关心硬件细节;
  • 内核层:驱动作为内核模块,注册字符设备节点,承接应用层调用,实现硬件操作逻辑;
  • 核心特点:驱动与应用解耦、驱动与内核版本绑定、以模块(.ko)形式动态加载到内核

2. 字符设备驱动核心框架(必实现4大核心函数+2大入口出口)#

(1)2个核心入口/出口(驱动模块生命周期)#

  • 加载入口:module_init(驱动初始化函数) → 驱动insmod加载时执行,完成设备注册、资源申请;
  • 卸载出口:module_exit(驱动卸载函数) → 驱动rmmod卸载时执行,完成设备注销、资源释放。

(2)4个核心设备操作函数(应用层接口映射)#

驱动需实现 file_operations 结构体,绑定应用层接口对应的内核处理函数,核心必实现:

.open // 应用层open()调用时执行(设备打开,初始化硬件/状态)
.read // 应用层read()调用时执行(硬件→内核→应用,数据读取)
.write // 应用层write()调用时执行(应用→内核→硬件,数据写入)
.release// 应用层close()调用时执行(设备关闭,释放临时资源)

(3)驱动核心宏定义(必加,标识驱动信息)#

MODULE_LICENSE("GPL"); // 声明开源协议(必须,否则内核加载警告)
MODULE_AUTHOR("XXX"); // 驱动作者(可选)
MODULE_DESCRIPTION("XXX");// 驱动描述(可选)

四、全文实操:驱动代码 + 应用程序代码#

1. 核心驱动代码(my_drv.c,字符设备驱动最简实现)#

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
// 设备号、设备名定义
#define DEV_NAME "my_char_dev"
static int major;
// 1. 设备打开函数
static int my_drv_open(struct inode *inode, struct file *filp) {
printk("my_drv open success!\n");
return 0;
}
// 2. 设备读取函数
static ssize_t my_drv_read(struct file *filp, char __user *buf, size_t size, loff_t *off) {
char data[] = "Hello Driver!";
copy_to_user(buf, data, sizeof(data)); // 内核→用户层数据拷贝
return sizeof(data);
}
// 3. 设备写入函数
static ssize_t my_drv_write(struct file *filp, const char __user *buf, size_t size, loff_t *off) {
char kbuf[128] = {0};
copy_from_user(kbuf, buf, size); // 用户层→内核数据拷贝
printk("APP write data: %s\n", kbuf);
return size;
}
// 4. 设备关闭函数
static int my_drv_release(struct inode *inode, struct file *filp) {
printk("my_drv close success!\n");
return 0;
}
// 绑定操作函数集
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_drv_open,
.read = my_drv_read,
.write = my_drv_write,
.release = my_drv_release,
};
// 驱动加载入口
static int __init my_drv_init(void) {
// 注册字符设备,自动分配主设备号
major = register_chrdev(0, DEV_NAME, &fops);
if (major < 0) {
printk("driver register failed!\n");
return major;
}
printk("driver register success! major = %d\n", major);
return 0;
}
// 驱动卸载出口
static void __exit my_drv_exit(void) {
unregister_chrdev(major, DEV_NAME); // 注销字符设备
printk("driver unregister success!\n");
}
module_init(my_drv_init);
module_exit(my_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huangzy");
MODULE_DESCRIPTION("A133 Simple Char Driver");

2. 配套应用程序代码(app.c,操作驱动的用户层程序)#

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd;
char r_buf[128] = {0};
char w_buf[] = "Hello APP to Driver!";
// 1. 打开驱动设备(需先手动创建设备节点:mknod /dev/mydev c 主设备号 0)
fd = open("/dev/mydev", O_RDWR);
if (fd < 0) {
perror("open dev failed");
return -1;
}
// 2. 读取驱动数据
read(fd, r_buf, sizeof(r_buf));
printf("read from driver: %s\n", r_buf);
// 3. 向驱动写入数据
write(fd, w_buf, strlen(w_buf));
// 4. 关闭驱动设备
close(fd);
return 0;
}

五、全文核心Makefile#

驱动Makefile(32位ARM,适配A133开发板)#

obj-m := my_drv.o # 要编译的驱动文件(xxx.c → xxx.o → xxx.ko)
KERNELDIR := /home/huangzy/桌面/a133-linux/kernel/linux-4.9 # 内核源码路径
PWD := $(shell pwd) # 当前驱动目录路径
ARCH := arm # 指定32位ARM架构
CROSS_COMPILE := arm-linux-gnueabi- # 32位ARM交叉编译器
# 编译驱动模块(固定规则)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
# 清理编译产物
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean

六、全文所有核心指令(编译、加载、运行、调试,按流程排序)#

【第一阶段】驱动编译指令(驱动目录执行,生成.ko文件)#

Terminal window
make clean && make # 直接执行Makefile,生成my_drv.ko

【第二阶段】应用程序编译指令(应用程序目录执行,生成可执行文件)#

Terminal window
arm-linux-gnueabi-gcc app.c -o app # 生成32位APP可执行文件

【第三阶段】开发板端核心指令(驱动加载、运行、调试,关键流程)#

1. 驱动加载(将.ko模块动态加载到内核)#

Terminal window
# 驱动加载
insmod my_drv.ko

2. 查看驱动加载状态(验证是否加载成功)#

Terminal window
lsmod | grep my_drv # 过滤驱动名,显示则加载成功
dmesg | tail -10 # 查看内核打印信息,确认驱动注册状态

3. 创建设备节点(应用层操作驱动的唯一入口,必须执行)#

Terminal window
# 格式:mknod /dev/设备名 c 主设备号 次设备号(次设备号填0即可)
mknod /dev/mydev c 240 0 # 主设备号从dmesg打印的日志中获取

4. 运行应用程序(操作驱动,测试读写功能)#

Terminal window
chmod +x app # 赋予执行权限
./app # 运行APP,触发驱动的open/read/write操作

5. 驱动卸载(从内核中移除驱动模块)#

Terminal window
rmmod my_drv # 卸载驱动(直接写驱动名,无需加.ko)

七、遇到问题#

驱动加载失败(未解决)#

Terminal window
root@kickpi:/mnt/host_share# insmod my_drv.ko
insmod: ERROR: could not insert module my_drv.ko: Invalid module format

可能原因#

  1. 虚拟机使用x86.
  2. 虚拟机用arm32编译(开发板是arm64),但是官方给的是32.
  3. 改用64之后缺少头文件。
  4. 还有一些小的文件配置问题重新编译一下就能解决。

其他问题#

虚拟机内存不足#

解决虚拟机内(Ubuntu)的空间不足问题,需要先扩展虚拟机磁盘容量,再在Ubuntu中分配新增空间,步骤如下:

步骤1:在VMware中扩展虚拟机磁盘的最大容量#

  1. 关闭Ubuntu虚拟机。
  2. 打开VMware,选中虚拟机 → 点击「编辑虚拟机设置」→ 选择「硬盘」→ 点击「扩展」。
  3. 在弹出的窗口中,设置新的最大磁盘容量(比如设为50GB),点击「扩展」(此操作不会立刻占用主机对应空间)。

步骤2:在Ubuntu中分配新增的磁盘空间#

需要用gparted工具(图形化分区工具)将新增空间合并到当前系统分区:

  1. 安装gparted 启动Ubuntu,打开终端执行:

    Terminal window
    sudo apt install gparted
  2. 打开gparted并识别磁盘 执行sudo gparted启动工具,在右上角选择虚拟机的磁盘(通常是/dev/sda)。

  3. 调整分区

    • 先删除磁盘末尾的“未分配空间”前的“扩展分区”(若存在);
    • 右键点击当前系统分区(通常是/dev/sda1)→ 选择「调整大小/移动」;
    • 拖动分区的右侧滑块,将未分配空间全部纳入系统分区,点击「调整大小」;
    • 点击工具栏的「√」(应用所有操作),等待完成。

步骤3:验证空间#

操作完成后,打开终端执行df -h,即可看到系统分区的可用空间已增加。

第一个驱动程序
https://blog.huangzy.xyz/posts/第一个驱动程序/
Author
纸翼
Published at
2025-12-28
License
CC BY-NC-SA 4.0

Some information may be outdated