我们在前面讲到了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
#include
#include
#include
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四个函数已经学完,下面我们来看一下四个函数使用时,到底经历了一个怎样的过程:
注:箭头方向是从调用的一方指向受作用的一方