【问题标题】:Write long to linux char device driver?写长到linux char设备驱动程序?
【发布时间】:2020-09-06 12:43:07
【问题描述】:

我正在尝试在 linux 中编写字符设备驱动程序。不幸的是,它不适用于任何大于 255 的数字。

我希望这个驱动程序专门使用 long 类型的值。每当我输入一个大于 255 的值时,数字就会出错。 256 变为 0 等等。

我编写了一个简单的字符设备驱动程序来显示问题,当我复制完整的驱动程序并删除了几乎所有内容时,可能有很多未使用的包含语句:

chartest.c

#include <linux/init.h>
#include <linux/module.h> /* I mean this is a module after all! */
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/sched.h> /* For current task information */
#include <linux/fs.h> /* For file operations */
#include <linux/types.h> /* dev_t: device number data type */
#include <linux/cdev.h> /* cdev is the module data type that the kernel sees */
#include <asm/uaccess.h> /* For routines to copy data to/from user space */
#include <linux/uaccess.h>
#include <linux/slab.h> /* kmalloc/kfree */


MODULE_LICENSE("GPL");

#define DRIVER_NAME "chartest"

#define MAJOR_NUM 230
#define MINOR_NUM 0

struct cdev *cdev;

int test_device_open(struct inode *inode, struct file *fp) {
    return 0;
}


int test_device_release(struct inode *inode, struct file *fp) {
    return 0;
}


ssize_t test_read(struct file *fp, char __user *buffer, size_t count, loff_t *f_pos) {
    return count;
}

ssize_t test_write(struct file *fp, const char __user *buffer, size_t count, loff_t *fpos) {
    // We must validate the user's buffer and convert it to a long long
    long userOperand;
    unsigned char *userInput = NULL;

    printk(KERN_NOTICE "Write Function Entered.\n");
    printk(KERN_ALERT "Write count: %ld, Write fp: %lld\n", count, *fpos);

    userInput = kmalloc(count, GFP_KERNEL); 
    get_user(*userInput, buffer);

    printk(KERN_NOTICE "Value before cast: %ld\n", (long) *userInput);

    userOperand = (long) *userInput;

    printk(KERN_NOTICE "Value after cast: %ld\n", userOperand);

    // Increment the file position pointer (in our case, always by 8)
    *fpos += count;

    kfree(userInput);
    return count;
}


/*
* Declaration of function for open file operations
*/
static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .read = test_read,
    .write = test_write,
    .open = test_device_open,
    .release = test_device_release,
};



// Initialization function
static int __init test_init(void)
{
    // Register device number:
    int err = 0;
    dev_t device_number = MKDEV(MAJOR_NUM, MINOR_NUM);

    err = register_chrdev_region(device_number, 1, DRIVER_NAME);

    if (err < 0) {
        printk(KERN_ALERT "Could not allocate device number.\n");
        return err;
    }

    cdev = cdev_alloc();
    cdev->owner = THIS_MODULE;
    cdev->ops = &test_fops;

    err = cdev_add(cdev, device_number, 1);
    if (err) {
        printk("Error allocating cdev.\n");
    }

    printk(KERN_ALERT "Test Initialized. Major Number: %d\n", MAJOR_NUM);

    return 0;
}

// Exit function:
static void __exit test_exit(void)
{
    dev_t device_number = MKDEV(MAJOR_NUM, MINOR_NUM);

    // Remove char device */
    cdev_del(cdev);

    /* Unregister Device Number: */
    unregister_chrdev_region(device_number, 1);
    printk(KERN_ALERT "TestDriver %d destroyed.\n", MAJOR_NUM);
}

module_init(test_init);
module_exit(test_exit);

小测试程序:

maintest.c:

#include <unistd.h>
#include <fcntl.h>



int main(void) {
    long input = 256;


    int fd = open("/dev/chartest0", O_RDWR);

    write(fd, &input, sizeof(long));

    close(fd);

    return 0;

}

printk 语句在给定输入 256 的情况下给出以下输出:

Write Eunction Entered.
Write count: 8, Write fp: 0
Value before cast: 0
Value after cast: 0

这也会失败,copy_from_user 给定输入大小为 8 个字节。当一次遍历一个字节并复制数据时,它也会失败。我什么都试过了。

如果您愿意提供帮助,请编译: 生成文件

ifeq ($(KERNELRELEASE),)

    # Assume the source tree is where the running kernel was built
    # You should set KERNELDIR in the environment if it's elsewhere
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    # The current directory is passed to sub-makes as argument
    PWD := $(shell pwd)

modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

.PHONY: modules modules_install clean

else
    # called from kernel build system: just declare what our modules are
    obj-m := chartest.o

endif

然后在同一目录下:

sudo insmod chartest.ko

终于:

sudo mknod -m 777 /dev/chartest0 c 230 0

然后你可以编译maindriver.c并运行它来测试。

有人可以帮我解决这个问题吗?

【问题讨论】:

  • 请注意,在 ints 和 longs 中,字节是相反的。它可能会看到0x 0000 0000 0000 00ff
  • @PaulOgilvie 我看不出用户的 8 字节缓冲区无论如何都不能被复制并将其解释为 long 的任何原因。

标签: c linux character driver device


【解决方案1】:

您错误地使用了宏get_user。

它的第一个参数实际上应该是一个变量名,而不是它的地址。

它的第二个参数应该是typed指向用户空间数据的指针。并且该指针的确切类型用于确定要读取的数据的大小。

正确:

    long userOperand;
    ...
    get_user(userOperand, (const long __user*)buffer);

    printk(KERN_NOTICE "Value written: %ld\n", userOperand);

注意第二个参数的情况:读取long而不是char需要它(因为buffer参数是指向char的指针)。

注意,上面的代码没有使用你的userInput变量,所以你不需要定义它,也不需要分配内存。


注意,.write 方法可以使用任何 count 参数调用,该参数表示用户传递的字节数。由于get_user 总是尝试读取预定义的字节数(在您的情况下,此数字等于long 的大小),因此检查 @987654332 将是用户友好的@ 等于该数字(或至少不小于给定数字):

if (count != sizeof(long)) {
  return -EINVAL;
}

【讨论】:

    【解决方案2】:

    你不能像你那样使用get_user:

    来自get_user doc

    这个宏将一个简单的变量从用户空间复制到内核空间。它支持 char 和 int 等简单类型,但不支持结构或数组等更大的数据类型。
    ptr 必须具有指向简单变量的指针类型,并且取消引用 ptr 的结果必须是可赋值的到 x 没有演员表。

    使用get_user,您将只复制第一个字符。

    你需要使用copy_from_user,这个函数可以复制数组和结构,不仅仅是简单的类型:

    ssize_t test_write(struct file *fp, const char __user *buffer, size_t count, loff_t *fpos) {
        // We must validate the user's buffer and convert it to a long long
        long userOperand;
        unsigned char *userInput = NULL;
    
        userInput = kmalloc(count, GFP_KERNEL); 
    
        printk(KERN_NOTICE "Write Function Entered.\n");
        printk(KERN_ALERT "Write count: %ld, Write fp: %lld\n", count, *fpos);
    
        /* warning, here you should test that count is exactly sizeof userInput */
        copy_from_user(userInput, buffer, count);
    
        userOperand =  *(long*)userInput;
    
        printk(KERN_NOTICE "Value after cast: %ld\n", userOperand);
    
        // Increment the file position pointer (in our case, always by 8)
        *fpos += count;
    
        kfree(userInput);
        return count;
    }
    

    您还可以在copy_from_user 中从char * 复制到long(这种情况下没有内存分配):

    ssize_t test_write(struct file *fp, const char __user *buffer, size_t count, loff_t *fpos) {
        // We must validate the user's buffer and convert it to a long long
        long userOperand;
    
        printk(KERN_NOTICE "Write Function Entered.\n");
        printk(KERN_ALERT "Write count: %ld, Write fp: %lld\n", count, *fpos);
    
        /* warning, here you should test that count is exactly sizeof userOperand */
        copy_from_user(&userOperand, buffer, sizeof userOperand);
    
        printk(KERN_NOTICE "Value after reading: %ld\n", userOperand);
    
        // Increment the file position pointer (in our case, always by 8)
        *fpos += count;
    
        return count;
    }
    

    【讨论】:

    • 感谢您一直以来的帮助。转换为 (long) *userInput 与 *(long *) userInput 之间有什么区别吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多