您的位置:首页 > 运维架构 > Linux

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

2014-11-14 22:41 405 查看


主要的文件操作方法实现

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

[cpp] view
plaincopy

struct file_operations {

...

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

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

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

...

};

以上只列出了主要的操作,下面会依次介绍:

本次的测试代码上传在:char_step2


结构体:

首先 我们会模拟写一个不操作任何设备,而仅仅是存储的一个驱动。

定义自己的一个结构体为:

[cpp] view
plaincopy

struct simple_dev{

char data[MAX_SIMPLE_LEN];

loff_t count;

struct semaphore semp;

};

data 保存数据, count表示文件的数据有效的位置, semp是一个信号量锁,在以后的编程中使用,

之后的程序中结构体也会做相应的变化,以适应linux编写驱动的习惯


open方法:

打开设备并进一步初始化工作,在没有定义open方法时内核以一种默认的方式打开设备,保证每次都能正确打开。

open方法中有有struct inode参数,包含了设备号,程序中可以使用次设备号得到正操作的设备

在struct file中主要的操作是private_data指针,他可以传递任何自己创建的结构。

总得说来open方法的作用有3

1、获得操作的设备(通过设备号)

2、进一步的初始化设备

3、初始化file结构体的private_data

[cpp] view
plaincopy

static int simple_open(struct inode *inodp, struct file *filp)

{

struct simple_dev *temp_dev = NULL;

int minor = 0;

#if SIMPLE_DEBUG

printk(KERN_INFO "In %s \n", __func__);

#endif

minor = iminor(inodp);//获得操作的设备的次设备号

if(minor > DEV_COUNT-1){

printk(KERN_ERR "the char dev in invalid \n");

return -ENODEV;

}

#if SIMPLE_DEBUG

printk(KERN_INFO "the minor is %d \n", minor);

#endif

temp_dev = &char2_dev[minor];//获得真正操作的设备

/* 进一步 初始化设备 因为是操作一个模拟的设备 故省去*/

filp->private_data = temp_dev; //初始化 private_data

return 0;

}

release方法:

主要是对open进一步初始化的操作的反操作

比如open时候分配了内存,在release时就需要释放它等

例子中因为操作内存设备,故在release时无需做什么事


read方法:

read 是把设备中的数据传递给调用者

主要步骤

1、检测偏移量有效(有些设备驱动不需要检测)

2、检测用户空间地址有效

3、将数据传给用户(在此步骤中调用的函数可能会自己检测步骤2)

4、调整偏移量

5、返回读到的数据长度

(read write 用法相对灵活,不要依赖上边的步骤,设备驱动程序要根据设备特性去设计此方法)

这里先介绍一个会检测用户空间地址是否有效的copy函数

用户调用read读设备,而在内核空间就是将数据传给用户,是一个to的操作

[cpp] view
plaincopy

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的方法

[cpp] view
plaincopy

static ssize_t simple_read(struct file *filp, char __user *userstr, size_t count, loff_t *loff)

{

struct simple_dev *dev = NULL;

int data_remain = 0;

int err;

#if SIMPLE_DEBUG

printk(KERN_INFO "In %s \n", __func__);

#endif

dev = filp->private_data;

data_remain = dev->count - *loff;

if(MAX_SIMPLE_LEN < *loff)//检测偏移量

{

printk(KERN_ERR "the offset is illegal in func %s \n",__func__ );

return -EINVAL;

}

else if(data_remain <= 0)

{

printk(KERN_WARNING "there was not much data in the device\n");

return 0;

}

else

{

if(count > data_remain)

{

#if SIMPLE_DEBUG

printk(KERN_INFO "the data is less than the user want to read\n");

#endif

count = data_remain;

}

else

{

}

}

err = copy_to_user(userstr, (dev->data)+(*loff), count); //调用内核函数进行数据拷贝,它会检测用户地址是否有效

if(err != 0)

{

printk(KERN_ERR "an error occured when copy data to user\n");

return err;

}

else

{

#if SIMPLE_DEBUG

printk(KERN_INFO "data copy to user OK\n");

#endif

*loff = *loff + count; //调整偏移量

return count; //返回写入的数据量

}

}

write方法:

与read类似 它是从用户传数据给设备驱动

从内核空间看就是一个从用户空间取数据 是一个from操作

[cpp] view
plaincopy

long __must_check strncpy_from_user(char *dst, const char __user *src, long count)

dst 驱动保存数据的地址

src 用户空间传入的数据

count 标识数据长度

[cpp] view
plaincopy

static ssize_t simple_write(struct file *filp, const char __user *userstr, size_t count, loff_t *loff)

{

struct simple_dev *dev = NULL;

int err;

int remain_space = 0;

#if SIMPLE_DEBUG

printk(KERN_INFO "In %s\n",__func__);

#endif

dev = filp->private_data;

if(MAX_SIMPLE_LEN <= *loff) //检测偏移量

{

printk(KERN_ERR "the offset is illegal in func %s\n", __func__);

return -EINVAL;

}

else

{

remain_space = MAX_SIMPLE_LEN - *loff;

if(count > remain_space)

{

#if SIMPLE_DEBUG

printk(KERN_WARNING "the data is to long to write to the device\n");

#endif

count = remain_space;

}

else

{

}

}

err = copy_from_user((dev->data)+(*loff),userstr,count);//取得数据

if(err != 0)

{

printk(KERN_ERR "an error occured when copy data from user\n");

return err;

}

else

{

#if SIMPLE_DEBUG

printk(KERN_INFO "data copy from user OK\n");

#endif

*loff = *loff + count; //跳着偏移

if(*loff > dev->count)

{

dev->count = *loff;

}

else

{

}

return count; //返回写入的数据量

}

}

lseek方法:

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

mode

SEEK_SET从文件起始处开始偏移
SEEK_CUR从文件当前位置计算偏移
SEEK_END从文件末尾计算偏移
file结构的f_pos保存了文件的偏移量

在调整文件偏移后需要 更新file中得f_pos成员

[cpp] view
plaincopy

static loff_t simple_llseek(struct file *filp, loff_t loff, int mode)

{

struct simple_dev *dev = NULL;

loff_t tmp_len;

#if SIMPLE_DEBUG

printk(KERN_INFO "In %s\n",__func__);

#endif

dev = filp->private_data;

switch ( mode )

{

case SEEK_SET:

if( loff < 0 )

{

printk(KERN_ERR "can't move above file line %d \n", __LINE__);

return -1;

}

else if(loff > dev->count)

{

printk(KERN_ERR "offset is too long line %d\n", __LINE__);

return -1;

}

else

{

filp->f_pos = loff;

}

break;

case SEEK_CUR:

if((tmp_len = filp->f_pos+loff) < 0)

{

printk(KERN_ERR "can't move above file line %d \n", __LINE__);

return -1;

}

else if(tmp_len > dev->count)

{

printk(KERN_ERR "offset is too long line %d\n", __LINE__);

return -1;

}

else

{

filp->f_pos = tmp_len;

}

break;

case SEEK_END:

if((tmp_len = dev->count+loff ) < 0)

{

printk(KERN_ERR "can't move above file line %d \n", __LINE__);

return -1;

}

else if(tmp_len > dev->count)

{

printk(KERN_ERR "offset is too long line %d\n", __LINE__);

return -1;

}

else

{

filp->f_pos = tmp_len;

}

break;

default :

printk(KERN_INFO "illigal lseek mode! \n");

return -1;

break;

}

return filp->f_pos;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: