Linux字符设备驱动模型(二)

在上一节 中讨论了字符设备的基本模型,本节在上一节的基础上继续完善,本节将增加file_operations中的readwritellseek三个方法。

read和write

read和write方法完成的任务是相似的,即,拷贝数据到应用程序空间,或反过来从应用程序空间拷贝数据到内核空间,它们的原型也很相似,如下:

ssize_t read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos);
ssize_t write(struct file *file, const char __user *buffer, size_t count, loff_t* ppos);

对于这两个方法的参数解读如下:

  • filep: 文件指针;
  • count: 请求传输的数据长度;
  • buffer: 指向用户空间的缓冲区,该地址是用户空间的地址,在内核不能直接使用,拷贝数据需要通过copy_from_usercopy_to_user两个函数来操作,其原型如下:
unsigned long copy_to_user(void __user* to, const void* from, unsigned long count);
unsigned long copy_from_user(void* to, const void __user* from, unsigned long count);

这两个函数在拷贝是会对地址做检查,返回实际拷贝的字节数。

  • ppos: 指明用户在文件中进行存取操作的位置。即,readwrite都要在偏移*ppos的基础上对文件进行操作。需要注意的是在读取或者写入文件内容后需要更新*ppos,之后内核会将文件位置的改变传播回file结构。
llseek

llseek的作用就是重新定位文件访问(read或者write)的偏移,函数原型为:

loff_t char_driver_llseek(struct file *filp, loff_t offset, int whence);
  • filp: 文件指针;
  • offset: 需要偏移的距离,可以是正数,也可以是负数;
  • whence: 从何处开始偏移,一共有三种方式,分别是:
      a.从文件头部偏移;
      b.从当前访问位置偏移;
      c.从文件末尾偏移。

至此,本节的内容差不多了,下面给出完整的代码:
char_driver.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MEM_SIZE 4096

struct mem_dev                                     
{                                                        
	char *buf;                      
	unsigned long buf_size;  
	//struct cdev cdev;      
};

struct mem_dev *mem_devp;

static int char_driver_open(struct inode *inode, struct file *filp)
{
	filp->private_data = mem_devp;		// 将全局变量mem_devp赋给filp->private_data,减少对全局变量的依赖

	printk("<0> open dev:%d\n", iminor(inode));

	return 0;
}

static ssize_t  char_driver_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
{
    int read_size;
    struct mem_dev *my_dev;
    
    my_dev = filp->private_data;
	
	if (count > my_dev->buf_size - *ppos) {
		read_size = my_dev->buf_size - *ppos;
	} else {
		read_size = count;
	}

	copy_to_user(buffer, my_dev->buf + *ppos, read_size);
	*ppos += read_size;

	//printk("<0> char_read to user:%s\n", buffer);
	return read_size;
}

static ssize_t  char_driver_write(struct file *filp, const char __user *buffer, size_t count, loff_t* ppos)
{
    struct mem_dev *my_dev;
	int write_size;

	my_dev = filp->private_data;

	if (count > my_dev->buf_size - *ppos) {
		write_size = my_dev->buf_size - *ppos;
	} else {
		write_size = count; 
	}

	copy_from_user(my_dev->buf + filp->f_pos, buffer, write_size);
	*ppos += count;
	
	//printk("<0> char_write from user:%s ppos:%d\n", (struct mem_dev*)(file->private_data)->buf, (int)*ppos);
	return write_size;
}

static loff_t char_driver_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;
    struct mem_dev *my_dev = filp->private_data;

    switch(whence) {
    case 0: /* SEEK_SET */
        newpos = offset;
        break;
    case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;
        break;
    case 2: /* SEEK_END */
        newpos = my_dev->buf_size -1 + offset;
        break;
    default: /* can't happen */
        return -EINVAL;
    }

    if ((newpos < 0) || (newpos > MEM_SIZE)) {
		return -EINVAL;
    }
        
    filp->f_pos = newpos;

    return newpos;
}

static int  char_driver_release(struct inode *inode, struct file *filp)
{
	printk(KERN_EMERG"close dev:%d\n", MINOR(inode->i_rdev));

	return 0;
}

static struct file_operations char_driver_fops = {
	.owner =	THIS_MODULE,
	.open =		char_driver_open,
	.write =	char_driver_write,	
	.read =		char_driver_read,	
	.llseek =	char_driver_llseek,
	.release =	char_driver_release,
};

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "char_driver",
	.fops = &char_driver_fops,
};

static struct mem_dev* alloc_mem_dev(void)
{
	struct mem_dev* devp = kmalloc(sizeof(struct mem_dev), GFP_KERNEL);
	if (!devp) {	/*申请失败*/
		return NULL;
	}   

	memset(devp, 0, sizeof(struct mem_dev));

	devp->buf = kmalloc(MEM_SIZE, GFP_KERNEL);
	if (NULL == devp->buf) {
		kfree(devp);
		return NULL;
	}

	devp->buf_size = MEM_SIZE;

	return devp; 
}

static void release_mem_dev(struct mem_dev** devp)
{
	if (NULL == devp) {
		return;
	}

	kfree((*devp)->buf);
	(*devp)->buf = NULL;

	kfree(*devp);
	*devp = NULL;
}

static int __init char_driver_init(void)
{
	int ret;

	mem_devp = alloc_mem_dev();
	if (!mem_devp)    /*申请失败*/
	{
		printk(KERN_EMERG"alloc mem_dev error!\n");
		return - ENOMEM;
	}

	ret = misc_register(&misc);
	printk(KERN_EMERG"hello driver dev_init!\n");

    return ret;
}

static void __exit char_driver_exit(void)
{
	release_mem_dev(&mem_devp);

	misc_deregister(&misc);
	printk(KERN_EMERG"hello driver dev_exit!\n");
}

MODULE_LICENSE("GPL");
module_init(char_driver_init);
module_exit(char_driver_exit);

app.c

#include 
#include 
#include 
#include 
#include 
#include  
#include  

int main(int argc, char *argv[])
{
    int fd;
    char buf[128] = {0};
    
    fd = open("/dev/char_driver", O_RDWR);
    if(0 > fd){
    	perror("can not open 6410_led:");
    }

    write(fd, "hello driver!", 13);

    lseek(fd, SEEK_SET, 0);

    read(fd, buf, 5);
    printf("%s\n", buf);

    memset(buf, 0, sizeof(buf));

    read(fd, buf, 10);
    printf("%s\n", buf);

    close(fd);   

    return 0;
}

Makefile和上一节的一样,本节不再列出。

模块测试

编译加载模块的过程不再列出来了,分别为一下步骤:

#make
#insmod char_driver.ko

测试驱动功能
由于本驱动模拟的是一个文件的读写,那么接下来看看是否能实现功能:
在这里插入图片描述
从上图结果可知,首先我写入了“hello driver!”13个字符,分两次读取,读取结果和预期相同,至于第二次读取的内容打印出来后面有乱码,是由于我在驱动中没有记录实际写入的字节数,如果有兴趣的话可以自己尝试实现。

你可能感兴趣的:(Linux驱动)