Linux驱动编程 step-by-step (五)主要的文件操作方法实现

转载 2012年03月21日 10:17:13

主要的文件操作方法实现

文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用时候会调用到这些操作
  1. struct file_operations {  
  2.     ...  
  3.     loff_t (*llseek) (struct file *, loff_t, int);  
  4.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
  5.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  
  6.     int (*open) (struct inode *, struct file *);  
  7.     int (*release) (struct inode *, struct file *);  
  8.     ...  
  9. };  

以上只列出了主要的操作,下面会依次介绍:
本次的测试代码上传在:char_step2


结构体:

首先 我们会模拟写一个不操作任何设备,而仅仅是存储的一个驱动。
定义自己的一个结构体为:
  1. struct simple_dev{  
  2.     char data[MAX_SIMPLE_LEN];  
  3.     loff_t count;  
  4.     struct semaphore semp;  
  5. };  
data 保存数据, count表示文件的数据有效的位置, semp是一个信号量锁,在以后的编程中使用,
之后的程序中结构体也会做相应的变化,以适应linux编写驱动的习惯

open方法:

打开设备并进一步初始化工作,在没有定义open方法时内核以一种默认的方式打开设备,保证每次都能正确打开。
open方法中有有struct inode参数,包含了设备号,程序中可以使用次设备号得到正操作的设备
在struct file中主要的操作是private_data指针,他可以传递任何自己创建的结构。
总得说来open方法的作用有3
1、获得操作的设备(通过设备号)
2、进一步的初始化设备
3、初始化file结构体的private_data

  1. static int simple_open(struct inode *inodp, struct file *filp)  
  2. {  
  3.     struct simple_dev *temp_dev = NULL;  
  4.     int minor = 0;  
  5. #if SIMPLE_DEBUG  
  6.     printk(KERN_INFO "In %s \n", __func__);  
  7. #endif  
  8.     minor = iminor(inodp);//获得操作的设备的次设备号  
  9.     if(minor > DEV_COUNT-1){  
  10.         printk(KERN_ERR "the char dev in invalid \n");  
  11.         return -ENODEV;  
  12.     }  
  13. #if SIMPLE_DEBUG  
  14.     printk(KERN_INFO "the minor is  %d \n", minor);  
  15. #endif  
  16.       
  17.     temp_dev = &char2_dev[minor];//获得真正操作的设备  
  18.     /* 进一步 初始化设备 因为是操作一个模拟的设备 故省去*/  
  19.     filp->private_data = temp_dev; //初始化 private_data  
  20.   
  21.     return 0;  
  22. }  

release方法:

主要是对open进一步初始化的操作的反操作
比如open时候分配了内存,在release时就需要释放它等
例子中因为操作内存设备,故在release时无需做什么事

read方法:

read 是把设备中的数据传递给调用者
主要步骤
1、检测偏移量有效(有些设备驱动不需要检测)
2、检测用户空间地址有效
3、将数据传给用户(在此步骤中调用的函数可能会自己检测步骤2)
4、调整偏移量
5、返回读到的数据长度
(read write 用法相对灵活,不要依赖上边的步骤,设备驱动程序要根据设备特性去设计此方法)
这里先介绍一个会检测用户空间地址是否有效的copy函数
用户调用read读设备,而在内核空间就是将数据传给用户,是一个to的操作

  1. unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)  
__must_check表述必须检测其返回值,操作成功返回0,不成功返回负的错误码
to是用户空间指针 也就是read函数传入的用户空间的指针,
from指向设备要传送的数据

n标识传入长度

上图是 摘自LDD3上的经典视图, 应该比较能说明read的方法

  1. static ssize_t simple_read(struct file *filp, char __user *userstr, size_t count, loff_t *loff)  
  2. {  
  3.       
  4.     struct simple_dev *dev = NULL;  
  5.     int data_remain = 0;  
  6.     int err;  
  7. #if SIMPLE_DEBUG  
  8.     printk(KERN_INFO "In %s \n", __func__);  
  9. #endif  
  10.       
  11.     dev         = filp->private_data;  
  12.     data_remain = dev->count - *loff;  
  13.     if(MAX_SIMPLE_LEN < *loff)//检测偏移量  
  14.     {  
  15.         printk(KERN_ERR "the offset is illegal in func %s \n",__func__ );  
  16.         return -EINVAL;  
  17.     }  
  18.     else if(data_remain <= 0)  
  19.     {  
  20.         printk(KERN_WARNING "there was not much data in the device\n");  
  21.         return 0;  
  22.     }  
  23.     else  
  24.     {  
  25.         if(count > data_remain)  
  26.         {  
  27. #if SIMPLE_DEBUG  
  28.             printk(KERN_INFO "the data is less than the user want to read\n");  
  29. #endif  
  30.             count = data_remain;  
  31.         }  
  32.         else  
  33.         {  
  34.           
  35.         }  
  36.     }  
  37.     err = copy_to_user(userstr, (dev->data)+(*loff), count); //调用内核函数进行数据拷贝,它会检测用户地址是否有效  
  38.     if(err != 0)  
  39.     {  
  40.         printk(KERN_ERR "an error occured when copy data to user\n");  
  41.         return err;  
  42.     }  
  43.     else  
  44.     {  
  45.       
  46. #if SIMPLE_DEBUG  
  47.         printk(KERN_INFO "data copy to user OK\n");  
  48. #endif  
  49.         *loff = *loff + count; //调整偏移量  
  50.         return count; //返回写入的数据量  
  51.     }  
  52. }  

write方法:

与read类似 它是从用户传数据给设备驱动
从内核空间看就是一个从用户空间取数据 是一个from操作

  1. long __must_check strncpy_from_user(char *dst, const char __user *src, long count)  
dst 驱动保存数据的地址
src 用户空间传入的数据
count 标识数据长度
  1. static ssize_t simple_write(struct file *filp, const char __user *userstr, size_t count, loff_t *loff)  
  2. {  
  3.     struct simple_dev *dev = NULL;  
  4.     int err;  
  5.     int remain_space = 0;  
  6. #if SIMPLE_DEBUG  
  7.     printk(KERN_INFO "In %s\n",__func__);  
  8. #endif  
  9.   
  10.     dev          = filp->private_data;  
  11.     if(MAX_SIMPLE_LEN <= *loff) //检测偏移量  
  12.     {  
  13.         printk(KERN_ERR "the offset is illegal in func %s\n", __func__);  
  14.         return -EINVAL;  
  15.     }  
  16.     else  
  17.     {  
  18.         remain_space = MAX_SIMPLE_LEN - *loff;  
  19.         if(count > remain_space)  
  20.         {  
  21. #if SIMPLE_DEBUG  
  22.             printk(KERN_WARNING "the data is to long to write to the device\n");  
  23. #endif  
  24.             count = remain_space;  
  25.         }  
  26.         else  
  27.         {  
  28.               
  29.         }  
  30.     }  
  31.     err = copy_from_user((dev->data)+(*loff),userstr,count);//取得数据  
  32.     if(err != 0)  
  33.     {  
  34.         printk(KERN_ERR "an error occured when copy data from user\n");  
  35.         return err;  
  36.     }  
  37.     else  
  38.     {  
  39.       
  40. #if SIMPLE_DEBUG  
  41.         printk(KERN_INFO "data copy from user OK\n");  
  42. #endif  
  43.         *loff = *loff + count; //跳着偏移  
  44.         if(*loff > dev->count)  
  45.         {  
  46.             dev->count = *loff;  
  47.         }  
  48.         else  
  49.         {  
  50.           
  51.         }  
  52.         return count; //返回写入的数据量  
  53.     }  
  54. }  

lseek方法:

根据用户传入的参数调整文件偏移
mode

SEEK_SET 从文件起始处开始偏移
SEEK_CUR 从文件当前位置计算偏移
SEEK_END 从文件末尾计算偏移
file结构的f_pos保存了文件的偏移量
在调整文件偏移后需要 更新file中得f_pos成员
  1. static loff_t simple_llseek(struct file *filp, loff_t loff, int mode)  
  2. {  
  3.     struct simple_dev *dev = NULL;  
  4.     loff_t tmp_len;  
  5. #if SIMPLE_DEBUG  
  6.     printk(KERN_INFO "In %s\n",__func__);  
  7. #endif  
  8.   
  9.     dev          = filp->private_data;  
  10.   
  11.     switch ( mode )  
  12.     {  
  13.         case SEEK_SET:  
  14.             if( loff < 0 )  
  15.             {  
  16.                 printk(KERN_ERR "can't move above file line %d \n", __LINE__);  
  17.                 return -1;  
  18.             }  
  19.             else if(loff > dev->count)  
  20.             {  
  21.                 printk(KERN_ERR "offset is too long line %d\n", __LINE__);  
  22.                 return -1;  
  23.             }  
  24.             else  
  25.             {  
  26.                 filp->f_pos = loff;  
  27.             }  
  28.             break;  
  29.         case SEEK_CUR:  
  30.             if((tmp_len = filp->f_pos+loff) < 0)  
  31.             {  
  32.                 printk(KERN_ERR "can't move above file line %d \n", __LINE__);  
  33.                 return -1;  
  34.             }  
  35.             else if(tmp_len > dev->count)  
  36.             {  
  37.                 printk(KERN_ERR "offset is too long line %d\n", __LINE__);  
  38.                 return -1;  
  39.             }  
  40.             else  
  41.             {  
  42.                 filp->f_pos = tmp_len;  
  43.             }  
  44.             break;  
  45.         case SEEK_END:  
  46.             if((tmp_len = dev->count+loff ) < 0)  
  47.             {  
  48.                 printk(KERN_ERR "can't move above file line %d \n", __LINE__);  
  49.                 return -1;  
  50.             }  
  51.             else if(tmp_len > dev->count)  
  52.             {  
  53.                 printk(KERN_ERR "offset is too long line %d\n", __LINE__);  
  54.                 return -1;  
  55.             }  
  56.             else  
  57.             {  
  58.                 filp->f_pos = tmp_len;  
  59.             }  
  60.             break;  
  61.         default :  
  62.             printk(KERN_INFO "illigal lseek mode! \n");  
  63.             return -1;  
  64.             break;  
  65.     }  
  66.     return filp->f_pos;  


Linux驱动编程 step-by-step (五)

2011-11-11 00:35 3206人阅读 评论(13) 收藏 举报 主要的文件操作方法实现 文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用...
  • huzm08
  • huzm08
  • 2011年11月15日 08:57
  • 130

Linux驱动编程 step-by-step (五)

主要的文件操作方法实现 文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用时候会调用到这些操作 struct file_operations { ... loff_...

Linux驱动编程 step-by-step (一)

第三次看了LDD3了(虽然现在已经是kernel3.0但从这本书商还是能学到很多) 每次都有一些收获 现在终于能够些一些代码了 驱动程序的作用: 简单来说 驱动程序就是使计算机与设备通信的特殊的代码,...

Linux驱动编程 step-by-step (二)

简单字符设备驱动 1、主次设备号 主设备号标识设备连接的的驱动,此设备好由内核使用,标识在相应驱动下得对应的设备 在linux中设备号是一个32位的dev_t类型 typedef __...
  • huzm08
  • huzm08
  • 2011年11月15日 08:53
  • 166

Linux驱动编程 step-by-step (四)

似乎每一章介绍的内容比较少,但学习是一个循序渐进的过程,不在于一天学多少,重要的一天能真正的学懂多少,所以我主张一步一步来,从多个渠道去学习知识,实现互补。 本节测试代码传到此处了:char_ste...
  • xphh
  • xphh
  • 2011年11月26日 21:48
  • 163

Linux驱动编程 step-by-step (七)

分类: C/C++ Linux 2011-11-17 00:24 2589人阅读 评论(4) 收藏 举报 并发 竞态 (信号量与自旋锁) 代码传至并发竞态控制 ...

Linux驱动编程 step-by-step (六) .

说点上节没有讲完的话题 用户地址检测 简单模块调试 以及一些杂项 检测用户空间地址的有效性 上一节中提到在read write时候要检测用户空间传递的参数地址是否是有效地址,有的内核函数会自...
  • huzm08
  • huzm08
  • 2011年11月15日 08:58
  • 140

Linux驱动编程 step-by-step (一)

转载于: http://blog.csdn.net/jshazk1989/article/details/6908472 驱动程序的作用: 简单来说 驱动程序就是使计算机与设备通...

Linux驱动编程 step-by-step (七)

并发 竞态 (信号量与自旋锁) 代码传至并发竞态控制 并发进程 导致竞态的一个例子 前面所述的字符驱动都是没有考虑并发竟态的情况,想象一下 一个进程去读一个字符设备,另一个进程在同...

Linux驱动编程 step-by-step (八)

阻塞型字符设备驱动 前面说到了 如何实现read write 等操作,但如果设备缓冲已满,现在想而用户此时又想写入设备,此请求就无法立即执行,那怎么办呢? 第一种情况是:驱动程序想用户返回请求失败...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux驱动编程 step-by-step (五)主要的文件操作方法实现
举报原因:
原因补充:

(最多只允许输入30个字)