构建简单字符设备但设备驱动程序文件不会写入或读取

时间:2017-10-29 21:11:17

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

我正在尝试编写一个简单的字符设备/ 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&#34; hello world&#34; &GT;&GT;的/ dev / simple_char_driver

我正在使用的终端崩溃

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

然后终端返回被杀死。

我完全迷失了什么是错的,我一直在寻找解决方案很长一段时间没有成功。如果有人对出了什么问题有任何见解,请告诉我。

编辑:作为建议的用户,我删除了除printk和return之外的读写方法中的所有代码,以确保触发了函数。 当我使用echo时,dmesg显示写入printk被触发,并且设备(我已打开)关闭。当我试图捕获设备文件时,dmesg显示设备重新打开,&#34;准备从设备&#34; printk成功出现,然后设备再次关闭。然而,尽管我已经回应了#Hello; Hello world&#34;然而,echo实际上并没有从设备文件中找到任何东西。之前就进入了它。

修改

最终功能读写函数如下:

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;
}

1 个答案:

答案 0 :(得分:2)

您的代码总体上还有很多不足之处,但我目前看到的是.write实施可能会令人怀疑。有两种可能的错误 - 没有缓冲区边界检查和忽略空终止,这可能导致strlen()的未定义行为。

首先,您知道缓冲区的大小 - BUFFER_SIZE。因此,您应该检查*offset + length < BUFFER_SIZE。它应该是<而不是<=,因为无论如何最后一个字节都应保留用于空终止。因此,如果没有可用空间(else分支或>=),此类检查应立即返回该方法。我无法确定是否应该返回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()调用中使用它。例如,如果用户请求从5字节的偏移量开始写入1021个字节的数据,那么您的驱动程序将只允许写入2个字节的数据 - 例如{ {1}}代替he。此外,返回值应设置为hello,以便调用者能够检测缓冲区空间不足。

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

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()出现问题,但我怀疑您是否需要它。您只需执行strlen()

顺便说一句,我建议以更具描述性的方式命名参数/变量。 *offset += nb_bytes_to_copy是一个眼睛。如果命名为*offset,它看起来会更好。如果您的方法变得庞大,普通读者将不太可能记住*offsetp是指针而不是值。 offset offsetp代表&#34;指针&#34;将来会减轻任何支持你的代码的人的工作。

要把它放在一起,我怀疑你的p实施并建议你重做它。如果其他一些错误仍然存​​在,则需要进一步调试。添加调试打印输出可能会派上用场,但请首先重新审视基本点,例如空终止和缓冲区边界保护。为了让我的答案对你更有用,我提供了section 3.7&#34; Linux设备驱动程序3&#34;的链接。这本书将阐明正在讨论的主题。