带有循环缓冲区的字符驱动程序

Posted

技术标签:

【中文标题】带有循环缓冲区的字符驱动程序【英文标题】:Character Driver with Circular buffer 【发布时间】:2015-07-20 11:38:41 【问题描述】:

我正在学习设备驱动程序编程,并创建了一个简单的字符驱动程序,我从用户空间应用程序传递数据并将其存储在内核空间循环缓冲区中,然后再次从另一个用户空间应用程序读取此循环缓冲区。

我使用的是 linux 内核版本 3.3.6

我的驱动代码是:

/* A simple character driver program to pass some data from user space and storing that on kernel circular buffer and reading back it on user space */

#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/vmalloc.h>
#include<linux/fs.h>
#include<linux/major.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<linux/slab.h>
#include<linux/device.h>
#include<linux/types.h>
#include<linux/kdev_t.h>
#include<linux/wait.h>
#include<linux/sched.h>
#include<linux/circ_buf.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("C-DAC");
#define MAX_DEVICE 2
#define KERN_BUFF_SIZE 1
#define DRIVER_NAME "ass3q1" 

int my_open(struct inode *inode, struct file *filp);
ssize_t my_write(struct file *filp, const char __user *usr_buff, size_t count, loff_t *ppos);
ssize_t my_read(struct file *filp, char __user *usr_buf, size_t count, loff_t *ppos);
int my_release(struct inode *inode, struct file *filp);
long my_ioctl(struct file *filp, unsigned int cmd,unsigned long arg);


/* DEFINE A DEVICE SPECIFIC DATA */
typedef struct privatedata1

    int minor_num;
    struct cdev mycdev;
    struct circ_buf KERN_BUFF;
    int KERN_SIZE;
//  char KERN_BUFF[KERN_BUFF_SIZE];
my_privatedata;

my_privatedata devices[MAX_DEVICE];

/* DECLARE THE REQUIRED VARIABLES */
int major;
int minor=1;
dev_t device_num;

struct class *my_class;
struct device *my_device;

/* DEFINE THE FILE OPERATIONS STRUCTURE OF THE CHAR DRIVER */
struct file_operations my_fops=
    .owner      = THIS_MODULE,
    .open       = my_open,
    .write      = my_write,
    .read       = my_read,
    .release    = my_release,
    .unlocked_ioctl = my_ioctl,
;


/* INITIALISATION FUNCTION */
static int __init my_init(void)

    int i;
    int res;
    printk("\nI am in Init Function");
    /* DYNAMICALLY DEVICE NUMBER */
    res = alloc_chrdev_region(&device_num,minor,MAX_DEVICE,DRIVER_NAME);
    if(res<0)
    
        printk("\nRegister Device Num generation failed");
        return -1;
    
    major = MAJOR(device_num);

    my_class = class_create(THIS_MODULE,DRIVER_NAME);
    if(my_class == NULL)
    
        printk("\nClass creation failed");
        return -1;
    

    for(i=0; i<MAX_DEVICE; i++)
    
        device_num = MKDEV(major, minor + i);
        cdev_init(&devices[i].mycdev,&my_fops);     //registration of device
        cdev_add(&devices[i].mycdev,device_num,1);  //attachment of device

        /* CREATE DEVICE NODES IN /dev/ DIRECTORY */
        my_device = device_create(my_class,NULL,device_num,NULL,"sample_cdev%d",i);     
        if(my_device == NULL)
        
            class_destroy(my_class);
            printk("\nDevice creation failed");
            return -1;
        

        devices[i].minor_num = minor+i;
    
    return 0;


static void __exit my_exit(void)

    int i;

    printk("\nI am in Exit Function");
    /* REMOVE DEVICES AND NODES */
    for(i=0; i<MAX_DEVICE; i++)
    
        device_num = MKDEV(major, minor + i);
        cdev_del(&devices[i].mycdev);
        device_destroy(my_class, device_num);
    

    /* DESTROY CLASS */
    class_destroy(my_class);

    /* UNREGISTER DEVICE WITH KERNEL */
    device_num = MKDEV(major, minor);
    unregister_chrdev_region(device_num, MAX_DEVICE);


/* DEVICE OPEN METHOD */
int my_open(struct inode *inode, struct file *filp)

    my_privatedata *dev = container_of(inode->i_cdev, my_privatedata, mycdev);
    filp->private_data = dev;
    dev->KERN_SIZE = 4096;
    printk("\nIn character driver open function device node %d", dev->minor_num);
    return 0;


/* DEVICE WRITE METHOD */
ssize_t my_write(struct file *filp, const char __user *usr_buff, size_t count, loff_t *ppos)

    my_privatedata *dev = filp->private_data;

    if(CIRC_SPACE(dev->KERN_BUFF.head, dev->KERN_BUFF.tail, dev->KERN_SIZE) >= 1)
    
        int i;
        char ch;
        for(i=0; i<count; i++)
        
            get_user(ch, &usr_buff[i]);
            dev->KERN_BUFF.buf[dev->KERN_BUFF.head] = ch;
            printk("\nIn character driver write function and value of KERN_BUFF is: %s", dev->KERN_BUFF.buf);
            dev->KERN_BUFF.head = (dev->KERN_BUFF.head + 1) & (dev->KERN_SIZE-1);
        
       
    else
    
        printk("\nCopy from user to kernel space failed");
        return -EFAULT;
    
    return 0;


/* DEVICE READ METHOD */
ssize_t my_read(struct file *filp, char __user *usr_buf, size_t count, loff_t *ppos)

    my_privatedata *dev = filp->private_data;
    int res;
    printk("\nI am in character driver read function");
    dev->KERN_BUFF.buf = "Bye Bye";
//  usr_buf = dev->KERN_BUFF.buf;

    res = copy_to_user((char *)usr_buf, (char *)dev->KERN_BUFF.buf, strlen(dev->KERN_BUFF.buf)+1);
    printk("\nData '%s' from kernel buffer to user buffer copied successfully with bytes: %d",dev->KERN_BUFF.buf,strlen(dev->KERN_BUFF.buf));

/*  if(res == 0)
    
        printk("\nData '%s' from kernel buffer to user buffer copied successfully with bytes: %d",dev->KERN_BUFF.buf,strlen(dev->KERN_BUFF.buf));
        memset(dev->KERN_BUFF.buf, 0, strlen(dev->KERN_BUFF.buf));
        return strlen(dev->KERN_BUFF.buf);
    
    else
    
        printk("\nCopy from kernel to user failed");
        return -EFAULT;
    
*/
    return 0;


/* DEVICE RELEASE METHOD */
int my_release(struct inode *inode, struct file *filp)

    my_privatedata *dev = filp->private_data;
    printk("\nI am in release function and minor number is %d", dev->minor_num);
    return 0;


/* DRIVER IOCTL METHOD */
long my_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)

    printk("\nIn Driver ioctl function");
    my_privatedata *dev = filp->private_data;

    switch (cmd)
    
        case 1:
            
                int count;
                count = CIRC_CNT(dev->KERN_BUFF.head, dev->KERN_BUFF.tail,dev->KERN_SIZE);
                printk("\nSize of buffer count is %d",count);
                return count;
                break;
            
        case 2:
            
                int space;
                space = CIRC_SPACE(dev->KERN_BUFF.head, dev->KERN_BUFF.tail,dev->KERN_SIZE);
                printk("\nSize of buffer count is %d",space);
                return space;
                break;
            
        default:
            
                printk("\nNothing to show");
                break;
            
    


module_init(my_init);
module_exit(my_exit);

我的用户空间编写器应用是

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

int main()

    int fd;
    char Ubuff[50], Kbuff[50];

    fd = open("/dev/sample_cdev1", O_RDWR);
    if(fd < 0) 
        perror("Unable to open the device file\n");
        return -1;
    

    printf("\nPlease enter a string");
    gets(Ubuff);

    /* Write the data into the device */
    write(fd , Ubuff , strlen(Ubuff) + 1);

    close(fd);  
    return 0;

我的用户空间阅读器应用是

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

int main()

    int fd;
    char Ubuff[50], Kbuff[50];

    fd = open("/dev/sample_cdev1", O_RDWR);
    if(fd < 0) 
        perror("Unable to open the device file\n");
        return -1;
    

    /* Read the data back from the device */
    memset(Kbuff , 0 ,sizeof(Kbuff));
    read(fd , Kbuff , sizeof(Kbuff));
    printf("Data from kernel : %s\n", Kbuff);

    close(fd);
    return 0;

在 my_write 函数中,我在

处收到内核空指针取消引用错误
            dev->KERN_BUFF.buf[dev->KERN_BUFF.head] = ch;
            printk("\nIn character driver write function and value of KERN_BUFF is: %s", dev->KERN_BUFF.buf);
            dev->KERN_BUFF.head = (dev->KERN_BUFF.head + 1) & (dev->KERN_SIZE-1);

my_read 函数似乎不正确,但由于我无法向内核循环缓冲区写入内容,因此无法测试 my_read 函数。

我做错了什么?

【问题讨论】:

我没有检查发布的代码。但是,管道已经进行了这种组织。如果是我,我会用管子。 在用户空间阅读器应用程序中,memset、read、printf序列不应该处于循环中并在读取返回值(当前未检查)指示读取0字节时退出循环还是表示错误? 'gets' 已贬值,很快就会从 C 中淘汰。建议使用 'fgets',因为这样很容易避免缓冲区溢出。 这一行:'dev->KERN_BUFF.buf = "Bye Bye";'只复制一个指针。建议使用:strnlen(dev->KERN_BUFF.buf, "Bye Bye", sizeof("Bye Bye")+1); // +1 所以包括 '\0' 字符串终止符字节。注意:当复制到缓冲区时,始终将复制的字符数限制为缓冲区中当前可用的空间。 但是如何摆脱空指针解引用错误呢?我不明白。 【参考方案1】:

我认为问题是您没有在循环缓冲区中分配任何空间...dev-&gt;KERN_BUFF.buf = kmalloc(...) 不要忘记释放。 此外,您正在注册 2 个 my_private_data,这意味着每个 fd 都会获得自己的 my_private_data。我认为您希望生产者和消费者处理相同的数据。也许你需要一个 spin_lock...

另请阅读:CIRCULAR BUFFERS

【讨论】:

以上是关于带有循环缓冲区的字符驱动程序的主要内容,如果未能解决你的问题,请参考以下文章

STM32F4 UART HAL驱动程序'将字符串保存在变量缓冲区中'

驱动开发中使用安全字符串函数

块设备驱动框架详解

块设备驱动框架

linux内核的块设备驱动框架详解

设备与驱动的关系以及设备号设备文件