Char Driver Linux: file_operations读写的正确实现是什么?需要进行哪些抵消检查?

Char Driver Linux: What is the correct implementation of file_operations read and write? What are the offset checks needs to be made?

我正在尝试读取和写入字符驱动程序。当我使用 C 程序打开设备文件并读写时,它会出现 SEG 错误。当我对设备文件使用猫时,它会进入无限循环。

1) 我错过了什么,file_operations 中读写的正确实现是什么?

2) 我在原型 read/write 中知道:read(struct file *fp, char *ch, size_t count, loff_t *lofft) 计数是指 read/write 请求的字节数。但是最后一个参数offset是做什么用的,offset需要做哪些检查呢?

3) 对于像 cat /dev/chardriver 这样的多次读取调用,每次读取都会增加偏移量吗?就像计数 = 100 时从 1 到 100 的第一次读取调用偏移量一样,在下一次读取调用中偏移量会从 101 开始吗?或者它会来自任何随机数?

这是我的代码:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/uaccess.h>

    static int device_open(struct inode *, struct file *);
    static int device_release(struct inode *, struct file *);
    static ssize_t device_read(struct file *, char *, size_t, loff_t *);
    static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
    char kernelbuff[1024];

    MODULE_LICENSE("GPL");

    struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
    };


    int device_open(struct inode *inode, struct file *fp)
    {
    printk("device_open called");
    return 0;
    }

    static int device_release(struct inode *inode, struct file *fp)
    {
    printk("device_release called");
    return 0;
    }

    static ssize_t device_read(struct file *fp, char *ch, size_t sz, loff_t *lofft)
    {
    printk("device_read called");      
    copy_to_user(ch, kernelbuff, 1024);
    return sz;
    }

    static ssize_t device_write(struct file *fp, const char *ch, size_t sz, loff_t *lofft)
    {
    printk("device_write called");
    copy_from_user(kernelbuff, ch, 50);
    return 1024;
    }

    static int hello_init(void)
    {
      printk("basicchardriver: module initialized");
      register_chrdev(500, "chr_device", &fops);
      return 0;
    }

    static void hello_exit(void)
    {
      printk("basicchardriver: module exited");
      unregister_chrdev(500, "chr_device");
    }

    module_init(hello_init);
    module_exit(hello_exit);
 }

测试:

sudo mknod -m 666 /dev/chardev c 500 0
echo "Hello World" >> /dev/chardev    ===> Works fine
cat /dev/chardev     ===> Goes to infinite loop

如果我使用 C 程序调用驱动程序,它会给出 SEG 错误:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

int main()
{
  int fd;
  char buff[500];

  fd = open("/dev/chardev", O_RDWR);
  write(fd, "Hello World", 13);

  read(fd, buff, 500);
  printf("Reading data from kernel: \t");
  puts(buff);

  return 0;
}

raj@raj-VirtualBox:~/device-driver/chardriver/read-write$ ./a.out
Reading data from kernel: Hello World
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)

我想我的问题得到了正确的答案:(专家请随意添加您自己的答案/修改)

这里是正确的读法:

static ssize_t device_read(struct file *fp, char *ch, size_t sz, loff_t *lofft)
{
        printk("device_read called");
        if (*lofft > 1024 || sz > 1024) 
        {
          return -EFBIF; // return 0 also works
        }
        if ((*lofft+sz) > 1024)
        {
           sz = 1024 - *lofft;
        }

        copy_to_user(ch, kernelbuff + *lofft, sz);
        *lofft+=sz;
        return sz;
}

(写操作代码如下)

offset的答案可以参考这里:Understanding loff_t *offp for file_operations

关于偏移的一些要点:

  1. 所以,是的,每个连续读取调用的偏移量都需要 在读取功能中调整。

  2. 下一个读取偏移量应该从last_offset + count_of_no_of_bytes_read_in_last_function_call

  3. 读取应该return0,如果offset超过kernel的大小 缓冲区.

已编辑: 读取所需的有效检查可以参考link(@Tsyvarev 建议):

编辑:增加写入功能的改进版本[​​=14=]

static ssize_t device_write(struct file *fp, const char *ch, size_t sz, loff_t *lofft)
{
        printk("device_write called");
        if (((*lofft) > sizeof(kernelbuff)) || (sz > sizeof(kernelbuff)))
        {
        printk("Error: Allocating more than kernel buffer size"); // pr_err( ) can also be used as pointed by @KamilCuk
        return -EFBIG;
        }
        if ((*lofft + sz) > sizeof(kernelbuff))
        {
        printk("Error: Allocating more than kernel buffer size");
        return -EFBIG;
        }

        copy_from_user(kernelbuff + *lofft, ch, sz);
        *lofft+=sz;
        return sz;
}