我正在学习这里的一个教程。
我有以下代码:
#include <linux/init.h> // Macros used to mark up functions e.g. __init __exit
#include <linux/module.h> // Core header for loading LKMs into the kernel
#include <linux/device.h> // Header to support the kernel Driver Model
#include <linux/kernel.h> // Contains types, macros, functions for the kernel
#include <linux/fs.h> // Header for the Linux file system support
#include <linux/uaccess.h> // Required for the copy to user function
#define DEVICE_NAME "ebbchar" ///< The device will appear at /dev/ebbchar using this value
#define CLASS_NAME "ebb" ///< The device class -- this is a character device driver
MODULE_LICENSE("GPL"); ///< The license type -- this affects available functionality
MODULE_AUTHOR("Derek Molloy"); ///< The author -- visible when you use modinfo
MODULE_DESCRIPTION("A simple Linux char driver for the BBB"); ///< The description -- see modinfo
MODULE_VERSION("0.1"); ///< A version number to inform users
static int majorNumber; ///< Stores the device number -- determined automatically
static char message[256] = {0}; ///< Memory for the string that is passed from userspace
static short size_of_message; ///< Used to remember the size of the string stored
static int numberOpens = 0; ///< Counts the number of times the device is opened
static struct class* ebbcharClass = NULL; ///< The device-driver class struct pointer
static struct device* ebbcharDevice = NULL; ///< The device-driver device struct pointer
static int dev_open(struct inode *, struct file *);
static int dev_release(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
static ssize_t dev_write(struct file *, const char *, size_t, loff_t *);
static struct file_operations fops =
{
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
};
static int __init ebbchar_init(void){
printk(KERN_INFO "EBBChar: Initializing the EBBChar LKM\n");
// Try to dynamically allocate a major number for the device -- more difficult but worth it
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber<0){
printk(KERN_ALERT "EBBChar failed to register a major number\n");
return majorNumber;
}
printk(KERN_INFO "EBBChar: registered correctly with major number %d\n", majorNumber);
// Register the device class
ebbcharClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(ebbcharClass)){ // Check for error and clean up if there is
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to register device class\n");
return PTR_ERR(ebbcharClass); // Correct way to return an error on a pointer
}
printk(KERN_INFO "EBBChar: device class registered correctly\n");
// Register the device driver
ebbcharDevice = device_create(ebbcharClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(ebbcharDevice)){ // Clean up if there is an error
class_destroy(ebbcharClass); // Repeated code but the alternative is goto statements
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(ebbcharDevice);
}
printk(KERN_INFO "EBBChar: device class created correctly\n"); // Made it! device was initialized
return 0;
}
static void __exit ebbchar_exit(void){
device_destroy(ebbcharClass, MKDEV(majorNumber, 0)); // remove the device
class_unregister(ebbcharClass); // unregister the device class
class_destroy(ebbcharClass); // remove the device class
unregister_chrdev(majorNumber, DEVICE_NAME); // unregister the major number
printk(KERN_INFO "EBBChar: Goodbye from the LKM!\n");
}
static int dev_open(struct inode *inodep, struct file *filep){
numberOpens++;
printk(KERN_INFO "EBBChar: Device has been opened %d time(s)\n", numberOpens);
return 0;
}
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset){
int error_count = 0;
// copy_to_user has the format ( * to, *from, size) and returns 0 on success
error_count = copy_to_user(buffer, message, size_of_message);
if (error_count==0){ // if true then have success
printk(KERN_INFO "EBBChar: Sent %d characters to the user\n", size_of_message);
return (size_of_message=0); // clear the position to the start and return 0
}
else {
printk(KERN_INFO "EBBChar: Failed to send %d characters to the user\n", error_count);
return -EFAULT; // Failed -- return a bad address message (i.e. -14)
}
}
static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){
sprintf(message, "%s(%zu letters)", buffer, len); // appending received string with its length
size_of_message = strlen(message); // store the length of the stored message
printk(KERN_INFO "EBBChar: Received %zu characters from the user\n", len);
return len;
}
static int dev_release(struct inode *inodep, struct file *filep){
printk(KERN_INFO "EBBChar: Device successfully closed\n");
return 0;
}
module_init(ebbchar_init);
module_exit(ebbchar_exit);我有一个小的测试文件以及教程。问题是,当测试代码运行时,该进程将被终止。日志文件说,这是由于主管模式访问和页面错误异常抛出。
经过一些研究和查看日志文件,它归结为与主管模式访问预防的兼容性问题,其中内核代码不能访问用户代码,因为一些CPU的新的SMAP特性。
在启动时使用nosmap选项禁用SMAP之后,测试代码就可以正常工作了。
我正在寻找一种方法,以禁用/规避SMAP正确地在模块代码。由于这个应用程序可以在多个CPU上运行,所以我不认为更改CR4寄存器是正确的方法。
我认为copy_to_user()函数是一个很好的线索。当调用写时,问题就出现了。有人能告诉我,为这个模块编写write()函数的正确方法是什么?
发布于 2020-04-13 23:18:01
如果您有问题,禁用SMAP不会解决它,它只会隐藏它。SMAP杀死您的进程的事实是好的,它应该保持这种状态,这是Linux内核的一种安全措施,不应该仅仅为了使一个be的模块工作而禁用它。
您的错误在于:
sprintf(message, "%s(%zu letters)", buffer, len);您正在从内核空间读取用户空间内存,这是错误的,SMAP可以防止这产生错误。
您应该使用copy_from_user(),因为您正在处理的是用户空间缓冲区:
static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset){
unsigned long remaining;
// NEVER copy more than your message buffer size.
if (len > 256)
len = 256;
// Ensure that the additional string fits (XXX because len is at most 3 chars).
if (len + strlen(" (XXX letters)") >= 256) {
pr_info("User buffer is too big (%zu).\n", len);
return -EINVAL;
}
remaining = copy_from_user(message, buffer, len);
if (remaining > 0) {
pr_info("Failed to copy %lu characters from the user.\n", remaining);
return -EFAULT;
}
sprintf(message + len, " (%zu letters)", len);
size_of_message = len + strlen(message + len);
pr_info("Received %zu characters from the user.\n", len);
return len;
}还有一些其他的建议:
error_count应该是unsigned long而不是int,因为copy_to_user()返回该类型。dev_read()和dev_write()函数从用户空间获取指针。当内核函数接受来自用户空间的指针时,应该使用__user注释声明该指针,就像我在上面的函数中所做的那样。pr_info()而不是printk(KERN_INFO ...),就像我上面所做的那样。EBBChar:),只需重新定义pr_fmt宏,如下所示:
//这将使任何pr_*函数(pr_cont除外)将模块名放在每条消息的前面。// KBUILD_MODNAME是在构建时自动生成的,是模块名。//把这个放在模块的顶部。#ifdef pr_fmt #undef pr_fmt #endif #define pr_fmt( fmt ) KBUILD_MODNAME ":https://stackoverflow.com/questions/61196203
复制相似问题