整体框架(基于开发版本的调试方式):
1、写好的驱动程序,编译成.ko文件。
2、在开发板上 insmod xx.ko,加载驱动。
3、加载驱动后就会进入驱动的入口函数,调用module_init(入口函数名)。在卸载驱动的时候会调用module_exit(出口函数名)。
4、在入口函数中注册杂项设备或字符设备(比如调用misc_register(),函数入参是一个字符设备结构体的地址),就能在内核中生成设备节点。设备结构体中有名为fops的成员变量,该成员变量是一个file_operation类型的结构体,file_operation类型结构体中有open、read、write、ioctl、release等函数指针类型的成员变量,需要在内核中实现对应的函数并赋值给对应的函数指针,我们一般称这些函数为“钩子函数”。
5、设备注册成功并实现了对应的钩子函数后,我们就可以在应用层对设备节点进行访问、操作,通过调用open()、read()等函数就可以触发驱动中设备节点file_operation中对应的钩子函数。
总体来说,设备节点是沟通应用层和底层驱动的桥梁
在 Linux 内核中实现 ioctl
通信需要以下步骤:
-
定义设备驱动:创建一个字符设备驱动。
-
实现
ioctl
回调函数:在内核模块中实现ioctl
的处理逻辑。 -
注册设备:将设备驱动注册到内核。
-
用户态程序调用
ioctl
:通过设备文件与内核通信。
下面是一个完整的示例,展示如何在内核中实现 ioctl
通信,并附带详细的中文注释。
1. 内核模块代码(C语言)
代码实现
#include <linux/module.h> // 内核模块相关的头文件
#include <linux/kernel.h> // 内核打印函数 printk 的头文件
#include <linux/fs.h> // 文件系统相关的头文件(用于设备驱动)
#include <linux/uaccess.h> // 用户空间和内核空间数据拷贝的头文件
#include <linux/cdev.h> // 字符设备相关的头文件
#define DEVICE_NAME "mydevice" // 设备名称
#define MY_IOCTL_CMD _IOR('k', 1, int) // 定义 ioctl 命令
static int major_number; // 主设备号
static struct cdev my_cdev; // 字符设备结构体
static char kernel_buffer[256]; // 内核缓冲区
// 设备打开函数
static int device_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device opened\n");
return 0;
}
// 设备关闭函数
static int device_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device closed\n");
return 0;
}
// 设备读函数
static ssize_t device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset)
{
int bytes_to_copy;
// 计算需要拷贝的字节数
bytes_to_copy = min(length, sizeof(kernel_buffer));
// 将内核缓冲区的数据拷贝到用户空间
if (copy_to_user(buffer, kernel_buffer, bytes_to_copy)) {
return -EFAULT; // 拷贝失败,返回错误码
}
printk(KERN_INFO "Read %d bytes from kernel\n", bytes_to_copy);
return bytes_to_copy;
}
// 设备写函数
sta