Linux操作系统以其强大的内核模块支持而闻名。内核模块允许开发者扩展Linux内核的功能,以支持新的硬件设备或实现特定的功能。本文将通过一个简单的虚拟字符设备示例,展示如何在Linux内核模式下开发和调试内核模块。将使用MicrosoftVisual Studio作为集成开发环境(IDE),以利用其丰富的编码和调试特性简化开发过程。
在开始之前,请确保已经安装了以下软件:
确保Windows机器可以通过串行端口访问Linux机器。推荐使用带有虚拟COM端口的虚拟机,因为它速度快且方便。
启动Visual Studio并创建一个新的VisualKernel模块项目。按照向导步骤创建项目,在最后一页指定想要使用的调试方法。如果不使用VMware,请指定可以连接到虚拟机的COM端口。
现在,已经创建了一个基础的“Hello, World”项目。使用Ctrl-Shift-B构建项目,并使用F5开始调试。
为了创建设备对象并使其对用户模式应用程序可见,需要执行以下四个步骤:
为了使示例代码更紧凑,将省略完整的错误处理,并使用BUG_ON()语句来检查设备注册是否成功。
#include
#include
#include
struct file_operations demo_operations = {
.owner = THIS_MODULE,
};
static dev_t device_major_number;
static struct cdev character_device;
static struct class *class_object;
static struct device *device_object;
static int __init LinuxKernelModule1_init(void) {
int status;
const char *device_name = "MyCharacterDevice";
cdev_init(&character_device, &demo_operations);
printk("Allocating a major number...\n");
status = alloc_chrdev_region(&device_major_number, 0, 1, device_name);
BUG_ON(status < 0);
printk("Adding a character device with number %d...\n", device_major_number);
status = cdev_add(&character_device, device_major_number, 1);
BUG_ON(status < 0);
printk("Creating a device class \"%s\"...\n", device_name);
class_object = class_create(THIS_MODULE, device_name);
BUG_ON(IS_ERR(class_object));
printk("Creating a device object \"%s\"...\n", device_name);
device_object = device_create(class_object, NULL, device_major_number, NULL, device_name);
BUG_ON(IS_ERR(device_object));
printk("Device object created!");
return 0;
}
static void __exit LinuxKernelModule1_exit(void) {
device_destroy(class_object, device_major_number);
class_destroy(class_object);
cdev_del(&character_device);
unregister_chrdev_region(device_major_number, 1);
}
构建项目。注意,由于现在使用的是仅限GPL的API,需要将模块许可证更改为GPL。在初始化函数内设置断点,并使用F5开始调试项目。逐步执行初始化过程,观察变量值以了解发生了什么:
打开SSH会话窗口,尝试使用'cat'工具从设备读取一些数据:
cat /dev/MyCharacterDevice
Linux将报告一个'Invalid Argument'错误。这是因为没有提供一个read()函数,每次有人尝试从设备读取数据时,该函数将被调用。将在下一步中实现它。
可以通过实现一个read处理函数来控制当用户模式应用程序从设备读取时提供给用户模式应用程序的数据。将实现一个非常简单的函数,返回一个固定的消息:
static ssize_t demo_read_function(struct file *pFile, char __user *pBuffer, size_t size, loff_t *pOffset) {
static const char message[] = "Hello, User-mode\n";
int todo = sizeof(message) - 1 - *pOffset;
if (todo < 0) todo = 0;
todo = min(todo, size);
if (todo > 0) copy_to_user(pBuffer, message + *pOffset, todo);
*pOffset += todo;
return todo;
}
现在只需要在file_operations结构中注册这个函数:
struct file_operations demo_operations = {
.owner = THIS_MODULE,
.read = demo_read_function
};