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() 函数的定义:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static inline int copy_from_user(void *to, const void __user volatile *from,  
  2.                  unsigned long n)  
  3. {  
  4.     __chk_user_ptr(from, n);  
  5.     volatile_memcpy(to, from, n);  
  6.     return 0;  
  7. }  
  8.   
  9. static inline int copy_to_user(void __user volatile *to, const void *from,  
  10.                    unsigned long n)  
  11. {  
  12.     __chk_user_ptr(to, n);  
  13.     volatile_memcpy(to, from, n);  
  14.     return 0;  
  15. }  
可以看到两个函数均是调用了 _memcpy() 函数

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static void volatile_memcpy(volatile char *to, const volatile char *from,   
  2.                 unsigned long n)  
  3. {  
  4.     while (n--)  
  5.         *(to++) = *(from++);  
  6. }  

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

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

     

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)  
  2.   
  3. 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表示操作出错。当然也可以自己决定。


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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static ssize_t hello_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)  
  2. {  
  3.     if(len>64)  
  4.     {  
  5.         len =64;  
  6.     }  
  7.     if(copy_to_user(buf,temp,len))  
  8.     {  
  9.         return -EFAULT;  
  10.     }     
  11.     return len;  
  12. }  
  13. static ssize_t hello_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)  
  14. {  
  15.     if(len>64)  
  16.     {  
  17.         len = 64;  
  18.     }  
  19.   
  20.     if(copy_from_user(temp,buf,len))  
  21.     {  
  22.         return -EFAULT;  
  23.     }  
  24.     printk("write %s\n",temp);  
  25.     return len;  
  26. }  
测试程序:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <sys/types.h>  
  2. #include <sys/stat.h>  
  3. #include <fcntl.h>  
  4. #include <stdio.h>  
  5.   
  6.   
  7. char buf[]="111232342342342";  
  8. char temp[64]={0};  
  9. main()  
  10. {  
  11.     int fd,len;  
  12.   
  13.     fd = open("/dev/hello",O_RDWR);  
  14.     if(fd<0)  
  15.     {  
  16.         perror("open fail \n");  
  17.         return ;  
  18.     }  
  19.   
  20.     write(fd,buf,strlen(buf));  
  21.   
  22.     len=read(fd,temp,sizeof(temp));  
  23.   
  24.     printf("len=%d,%s \n",len,temp);  
  25.   
  26.     close(fd);  
  27. }  


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

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

    

    

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值