【问题标题】:Building a Simple character device but device driver file will not write or read构建简单字符设备但设备驱动程序文件不会写入或读取
【发布时间】:2018-04-10 20:15:31
【问题描述】:

我正在尝试编写一个可以读取、写入和查找的简单字符设备/LKM。 我在这方面遇到了很多问题,但几周来一直在努力解决它/故障排除,但一直无法让它正常工作。目前,我的模块可以正确制作并正确安装和卸载,但是如果我尝试回显到设备驱动程序文件,终端会崩溃,并且当我尝试使用 cat 从它读取时,它会返回被杀死。

本模块的步骤:

首先,我通过运行 make -C /lib/modules/$(uname -r)/build M=$PWD modules 来制作模块

对于我的内核,uname -r 是 4.10.17newkernel

我使用 sudo insmod simple_char_driver.ko 挂载模块

如果我运行 lsmod,则会列出该模块

如果我运行 dmesg,我的 init 函数“此设备现已打开”中的 KERN_ALERT 会正确触发。

此外,如果我运行 sudo rmmod,“此设备现已关闭”功能 KERN_ALERT 也会正确触发。

该模块在 cat /proc/devices 中也正确显示

我使用 sudo mknod -m 777 /dev/simple_char_driver c 240 0 在 /dev 中创建了设备驱动程序文件

在制作此文件之前,我确保 240 主号码尚未被使用。

我的设备驱动c文件有如下代码:

#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/slab.h>
#include<asm/uaccess.h>

#define BUFFER_SIZE 1024

MODULE_LICENSE("GPL");
//minor nunmber 0;
static int place_in_buffer = 0;
static int end_of_buffer = 1024;
static int MAJOR_NUMBER = 240;
char* DEVICE_NAME = "simple_char_driver";
typedef struct{
    char* buf;
}buffer;

char *device_buffer;
static int closeCounter=0;
static int openCounter=0;

ssize_t simple_char_driver_read (struct file *pfile, char __user *buffer, size_t length, loff_t *offset){
    int bytesRead = 0;
    if (*offset >=BUFFER_SIZE){
        bytesRead = 0;
    }
    if (*offset + length > BUFFER_SIZE){
        length = BUFFER_SIZE - *offset;
    }
    printk(KERN_INFO "Reading from device\n");
    if (copy_to_user(buffer, device_buffer + *offset, length) != 0){
        return -EFAULT;
    }
    copy_to_user(buffer, device_buffer + *offset, length);
    *offset += length;
    printk(KERN_ALERT "Read: %s", buffer);
    printk(KERN_ALERT "%d bytes read\n", bytesRead);
    return 0;
}

ssize_t simple_char_driver_write (struct file *pfile, const char __user *buffer, size_t length, loff_t *offset){
    int nb_bytes_to_copy;
if (BUFFER_SIZE - 1 -*offset <= length)
{
    nb_bytes_to_copy= BUFFER_SIZE - 1 -*offset;
    printk("BUFFER_SIZE - 1 -*offset <= length");
}
else if (BUFFER_SIZE - 1 - *offset > length)
{
    nb_bytes_to_copy = length;
    printk("BUFFER_SIZE - 1 -*offset > length");
}
printk(KERN_INFO "Writing to device\n");
if (*offset + length > BUFFER_SIZE)
{
    printk("sorry, can't do that. ");
    return -1;
}
printk("about to copy from device");
copy_from_user(device_buffer + *offset, buffer, nb_bytes_to_copy);
device_buffer[*offset + nb_bytes_to_copy] = '\0';
*offset += nb_bytes_to_copy;
return nb_bytes_to_copy;
}


int simple_char_driver_open (struct inode *pinode, struct file *pfile)
{
    printk(KERN_ALERT"This device is now open");    
    openCounter++;
    printk(KERN_ALERT "This device has been opened this many times: %d\n", openCounter);    
    return 0;
}

int simple_char_driver_close (struct inode *pinode, struct file *pfile)
{
    printk(KERN_ALERT"This device is now closed");  
    closeCounter++;
    printk(KERN_ALERT "This device has been closed this many times: %d\n", closeCounter);   
    return 0;
}

loff_t simple_char_driver_seek (struct file *pfile, loff_t offset, int whence)
{
    printk(KERN_ALERT"We are now seeking!");
    switch(whence){
        case 0:{
            if(offset<= end_of_buffer && offset >0){
                place_in_buffer = offset;
                printk(KERN_ALERT" this is where we are in the buffer: %d\n", place_in_buffer);
                }
            else{
                printk(KERN_ALERT"ERROR you are attempting to go ouside the Buffer");
                }       
            break;//THIS IS SEEK_SET
        }

        case 1:{
            if(((place_in_buffer+offset)<= end_of_buffer)&&((place_in_buffer+offset)>0)){
                place_in_buffer = place_in_buffer+offset;
                printk(KERN_ALERT" this is where we are in the buffer: %d\n", place_in_buffer);
                }
                else{
                    printk(KERN_ALERT"ERROR you are attempting to go ouside the Buffer");
                    }   
                break;          

        }
        case 2:{//THIS IS SEEK END
            if((end_of_buffer-offset)>=0&& offset>0){
                place_in_buffer = end_of_buffer-offset;
                printk(KERN_ALERT" this is where we are in the buffer: %d\n", place_in_buffer);
                }
                else{
                    printk(KERN_ALERT"ERROR you are attempting to go ouside the Buffer");
                    }   
                break;

        }
        default:{

        }
    }
    printk(KERN_ALERT"I sought %d\n", whence);
    return place_in_buffer;
}

struct file_operations simple_char_driver_file_operations = {

    .owner   = THIS_MODULE,
        .read = simple_char_driver_read,
        .write = simple_char_driver_write,
        .open = simple_char_driver_open,
        .llseek = &simple_char_driver_seek,
        .release = simple_char_driver_close,
};

static int simple_char_driver_init(void)
{   
    printk(KERN_ALERT "inside %s function\n",__FUNCTION__);
    register_chrdev(MAJOR_NUMBER,DEVICE_NAME, &simple_char_driver_file_operations);
    device_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
    return 0;
}

static void simple_char_driver_exit(void)
{
    printk(KERN_ALERT "inside %s function\n",__FUNCTION__);
    unregister_chrdev(MAJOR_NUMBER, DEVICE_NAME);
    kfree(device_buffer);
}

module_init(simple_char_driver_init);
module_exit(simple_char_driver_exit);

正如我之前所说,这个文件可以正常生成,没有错误或警告。 但是,目前如果我尝试回显到设备文件

使用:echo "hello world" >> /dev/simple_char_driver

我使用的终端崩溃了

如果我然后重新打开一个终端,并使用:cat /dev/simple_char_driver

然后终端返回killed。

我完全不知道出了什么问题,并且我一直在寻找解决方案很长时间都没有成功。如果有人对出了什么问题有任何见解,请告诉我。

编辑:正如下面的用户所建议的,我从我的读写方法中删除了除 printk 和 return 之外的所有代码,以确保函数被触发。 然后当我使用 echo 时,dmesg 显示写入 printk 被触发,并且设备(我已经打开)关闭了。然后当我尝试 cat 设备文件时,dmesg 显示设备重新打开,“设备就绪”printk 成功显示,然后设备再次关闭。然而,echo 实际上并没有从设备文件中找到任何要读取的内容,尽管我之前已经在其中回显了“Hello world”。

编辑

最终运行的读写函数如下:

ssize_t simple_char_driver_read (struct file *pfile, char __user *buffer, size_t length, loff_t *offset)
{
     if (*offset > BUFFER_SIZE)
     {
        printk("offset is greater than buffer size");
        return 0;
     }
     if (*offset + length > BUFFER_SIZE)
     {
        length = BUFFER_SIZE - *offset;
     }
     if (copy_to_user(buffer, device_buffer + *offset, length) != 0)
    {
        return -EFAULT;
    }
    *offset += length;
    return length;


}
ssize_t simple_char_driver_write (struct file *pfile, const char __user *buffer, size_t length, loff_t *offset){
    /* *buffer is the userspace buffer where you are writing the data you want to be written in the device file*/
    /* length is the length of the userspace buffer*/
    /* current position of the opened file*/
    /* copy_from_user function: destination is device_buffer and source is the userspace buffer *buffer */
    int nb_bytes_to_copy;
    if (BUFFER_SIZE - 1 -*offset <= length)
    {
        nb_bytes_to_copy= BUFFER_SIZE - 1 -*offset;
        printk("BUFFER_SIZE - 1 -*offset <= length");
    }
    else if (BUFFER_SIZE - 1 - *offset > length)
    {
        nb_bytes_to_copy = length;
        printk("BUFFER_SIZE - 1 -*offset > length");
    }
    printk(KERN_INFO "Writing to device\n");
    if (*offset + length > BUFFER_SIZE)
    {
        printk("sorry, can't do that. ");
        return -1;
    }
    printk("about to copy from device");
    copy_from_user(device_buffer + *offset, buffer, nb_bytes_to_copy);
    device_buffer[*offset + nb_bytes_to_copy] = '\0';
    *offset += nb_bytes_to_copy;
    return nb_bytes_to_copy;
}

【问题讨论】:

  • 首先确保您的.read.write 方法被实际调用。从 .write 方法中删除除printkreturn length; 之外的所有内容。对于 .read 方法 - printkreturn 0;。接下来,用户空间指针只能传递给copy_to_user /copy_from_user 和其他几个*_user 函数。即使strlen 也不应该用于用户缓冲区。
  • 我按照你的建议做了。当我回显到设备并检查 dmesg 时,printk 正确显示。但是,dmesg 显示,该设备随即关闭。然后当我在设备文件上运行 cat 时,dmesg 显示设备文件已打开,触发了 printk“从设备读取”,然后设备再次关闭。设备是否应该像这样关闭/打开。另外,为什么 echo 说设备已写入,但 cat 没有在设备中找到任何可读取的内容。我该如何从这里继续前进?
  • why is it that echo said the device was written to, but cat did not find anything to read in the device. - 命令echocat 只是做.read.write 做的事情。以某种方式实现这些文件操作取决于您,以某种方式读取和写入文件将是一致的。如您所见,实际上调用了您的 .write.read 方法,请尝试将代码行一一添加到其中。这样的方式你会发现,哪条线路会出问题。

标签: linux module linux-kernel linux-device-driver device-driver


【解决方案1】:

您的代码总体上还有很多不足之处,但我现在可以看到您的.write 实现可能是可疑的。有两个可能的错误 - 缺少缓冲区边界检查和无视空终止,这可能导致 strlen() 的未定义行为。

首先,您知道缓冲区的大小 - BUFFER_SIZE。因此,您应该检查*offset + length &lt; BUFFER_SIZE。它应该是&lt; 而不是&lt;=,因为无论如何最后一个字节都应保留用于空终止。因此,如果没有可用空间(else 分支或&gt;=),这样的检查将使方法立即返回。我不能确定您是否应该返回0 来报告没有写入任何内容,或者使用负值来返回错误代码,例如-ENOBUFS-ENOSPC。总之,该方法的返回值为ssize_t,表示可能返回负值

其次,如果您的第一次检查成功,您的方法将计算可用于写入的实际空间。即,您可以使用 MIN(A, B) 宏来执行此操作。换句话说,你最好创建一个变量,比如nb_bytes_to_copy 并像nb_bytes_to_copy = MIN(BUFFER_SIZE - 1 - *offset, length) 一样初始化它,以便稍后在copy_from_user() 调用中使用它。例如,如果用户请求从 1021 字节的偏移量开始写入 5 字节数据,那么您的驱动程序将允许仅写入数据的 2 字节 - 例如,he 而不是 @987654342 @。此外,返回值应设置为nb_bytes_to_copy,以便调用者能够检测到缓冲区空间不足。

最后,不要忘记空终止。一旦你完成了

copy_from_user(device_buffer + *offset, buffer, nb_bytes_to_copy);

你应该注意做类似的事情

device_buffer[*offset + nb_bytes_copy] = '\0';

或者,如果我没记错的话,您可以使用像 strncopy_from_user() 这样的特殊函数来确保复制数据时使用隐式 null 终止。

此外,虽然空终止写入不会导致后续strlen() 出现问题,但我怀疑您是否需要它。你可以简单地做*offset += nb_bytes_to_copy

顺便说一句,我建议以更具描述性的方式命名参数/变量。 *offset 很碍眼。如果命名为*offsetp 会更好看。如果您的方法变得庞大,普通读者不太可能记得offset 是指针而不是值。 offsetp 其中p 代表“指针”,这将使将来支持您的代码的任何人的工作变得轻松。

总而言之,我怀疑您的.write 实现并建议您对其进行重新设计。如果其他一些错误仍然存​​在,您将需要进一步调试它们。添加调试打印输出可能会派上用场,但请先重新审视基本点,例如空终止和缓冲区边界保护。为了让我的回答对您更有用,我提供了指向“Linux 设备驱动程序 3”一书的 section 3.7 的链接,这将阐明正在讨论的主题。

【讨论】:

  • 感谢您的洞察力。我已经调整了写入功能,我相信它现在可以工作了。但是,猫仍然被杀死,所以我不确定。我已经用新代码更新了原始帖子
  • 好吧,首先,尽管您的.write 的新实现有效,但代码看起来仍然很奇怪。基本上,应该只有一项检查:if (*offset &gt;= (BUFFER_SIZE - 1)),结果为return -ENOBUFS;。不需要其他分支。然后nb_bytes_to_copy = MIN(BUFFER_SIZE - 1 - *offset, length)。您编写并简单地返回 nb_bytes_to_copy。调用方将处理返回的值。 .read 方法有什么用,我现在看到 a) 你出于某种奇怪的原因调用 copy_to_user() 两次 并且 b) 你再次使用调试打印输出而不关心 null 终止。
  • @CassieH。在您的.read 中,您应该遵循类似的方案。首先你问自己一个问题:他们希望我从X 开始给他们数据——我能满足这个要求吗?如果X 大于或等于BUFFER_SIZE - 1, - 不,该位置没有可用数据。应该返回错误。接下来你问自己一个问题——他们想让我给他们Y字节的数据。我实际上可以提供多少?要回答,你计算nb_bytes_to_copy = MIN(BUFFER_SIZE - 1 - *offset, length);
  • @CassieH。然后你执行一次if (copy_to_user(buffer, device_buffer + *offset, nb_bytes_to_copy) != 0)(在!= 0的情况下你应该返回一个否定的错误代码)并在成功时将nb_bytes_to_copy传递给调用者。好吧,我需要明确地强调它——你应该始终遵循界面.read回调的接口假设返回的值是读取的字节数。如果无论成功/失败都返回0 - 这是错误的。因为调用者期望这个回调返回读取的字节数或负的错误码。
  • @CassieH。下一个。您有一个调试打印输出试图打印buffer。这是无稽之谈。如果用户请求从5 字节偏移开始读取数据怎么办?这意味着您向他们提供来自buffer + 5 的数据。那为什么你需要完全从buffer而不是buffer + *offset打印一些东西?此外,你为什么需要这个调试打印输出?我的意思是,您可以控制 start 位置,但如果用户只请求读取几个字节,您将如何在打印输出中反映这一点?我建议删除它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多