Linux 字符设备驱动开发基础(三)—— read()、write() 相关函数解析

       我们在前面讲到了file_operations,其是一个函数指针的集合,用于存放我们定义的用于操作设备的函数的指针,如果我们不定义,它默认保留为NULL。其中有最重要的几个函数,分别是open()、read()、write()、ioctl(),下面分别对其进行解析

     

一、 打开和关闭设备函数

a -- 打开设备

      int (*open) (struct inode *, struct file *);

在操作设备前必须先调用open函数打开文件,可以干一些需要的初始化操作。当然,如果不实现这个函数的话,驱动会默认设备的打开永远成功。打开成功时open返回0。

b -- 关闭设备      

      int (*release) (struct inode *, struct file *);

当设备文件被关闭时内核会调用这个操作,当然这也可以不实现,函数默认为NULL。关闭设备永远成功。

这两个函数已经讲过,这里不再赘述,主要看下面几个函数


二、read()、write() 函数

        现在把 read()、write() 两个函数放一起讲,因为两个函数非密不可分的,先看一下两个函数的定义 

a -- read() 函数

   函数原型         ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t * p); 
   参数含义

 filp       :为进行读取信息的目标文件,

 buffer  :为对应放置信息的缓冲区(即用户空间内存地址);

 size     :为要读取的信息长度;

 p          :为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,

                 移动的值为要读取信息的长度值

b -- write() 函数

   函数原型         ssize_t (*write) (struct file * filp, const char __user *   buffer, size_t count, loff_t * ppos); 
   参数含义

filp      :为目标文件结构体指针;

buffer :为要写入文件的信息缓冲区;

count  :为要写入信息的长度;

ppos   :为当前的偏移位置,这个值通常是用来判断写文件是否越界

         

       两个函数的作用分别是 从设备中获取数据发送数据给设备,应用程序中与之对应的也有 write() 函数及 read() 函数:

    len = read(fd,buf,len )

    len = write(fd,buf,size)

static ssize_t hello_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)

static ssize_t hello_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)



       我们知道,应用程序工作在用户空间,而驱动工作在内核空间,二者不能直接通信的,那我们用何种方法进行通信呢?下面介绍一下内核中的memcpy---copy_from_user和copy_to_user虽然说内核中不能使用C库提供的函数,但是内核也有一个memcpy的函数,用法跟C库中的一样。

        下面看一下copy_from_user() 及 copy_to_user() 函数的定义:

static inline int copy_from_user(void *to, const void __user volatile *from,
				 unsigned long n)
{
	__chk_user_ptr(from, n);
	volatile_memcpy(to, from, n);
	return 0;
}

static inline int copy_to_user(void __user volatile *to, const void *from,
			       unsigned long n)
{
	__chk_user_ptr(to, n);
	volatile_memcpy(to, from, n);
	return 0;
}
可以看到两个函数均是调用了 _memcpy() 函数

static void volatile_memcpy(volatile char *to, const volatile char *from, 
			    unsigned long n)
{
	while (n--)
		*(to++) = *(from++);
}

       其实在这里,我们可以思考,既然拷贝的功能上面的_memcpy() 函数就可以实现,为什么还要封装成 copy_to_user()和copy_from_user()呢?答案是_memcpy() 函数是有缺陷的,譬如我们在用户层调用函数时传入的不是字符串,而是一个不能访问或修改的地址,那样就会造成系统崩溃

      出于上面的原因, 内核和用户态之间交互的数据时必须要先对数据进行检测,如果数据是安全的,才可以进行数据交互。上面的函数就是memcpy的改进版,在memcpy功能的基础上加上的检查传入参数的功能,防止有些人有意或者无意的传入无效的参数。

     

      现在我们可以审视一下这两个函数了:

static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
用法:

和memcpy的参数一样,但它根据传参方向的不同分开了两个函数。

"to"是相对于内核态来说的。所以,to函数的意思是从from指针指向的数据将n个字节的数据传到to指针指向的数据。

"from"也是相对于内核来说的。所以,from函数的意思是从from指针指向的数据将n个字节的数据传到to指针指向的数据

返回值:函数的返回值是指定要读取的n个字节中还剩下多少字节还没有被拷贝。

注意:

一般的,如果返回值不为0时,调用copy_to_user的函数会返回错误号-EFAULT表示操作出错。当然也可以自己决定。


又到了摆实例的时候了,这里只列出部分代码,看看这两个函数的用法:

static ssize_t hello_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
	if(len>64)
	{
		len =64;
	}
	if(copy_to_user(buf,temp,len))
	{
		return -EFAULT;
	}	
	return len;
}
static ssize_t hello_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)
{
	if(len>64)
	{
		len = 64;
	}

	if(copy_from_user(temp,buf,len))
	{
		return -EFAULT;
	}
	printk("write %s\n",temp);
	return len;
}
测试程序:

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


char buf[]="111232342342342";
char temp[64]={0};
main()
{
    int fd,len;

    fd = open("/dev/hello",O_RDWR);
    if(fd<0)
    {
        perror("open fail \n");
        return ;
    }

    write(fd,buf,strlen(buf));

    len=read(fd,temp,sizeof(temp));

    printf("len=%d,%s \n",len,temp);

    close(fd);
}


      到这里open、close、read、write四个函数已经学完,下面我们来看一下四个函数使用时,到底经历了一个怎样的过程:

注:箭头方向是从调用的一方指向受作用的一方

    

    

   





  • 33
    点赞
  • 116
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值