一、open和release
1.打开设备(open)
作用:提供给驱动程序以初始化的能力,为以后的操作完成初始化的准备
工作流程:
①检查设备特定的错误(eg:设备未就绪或硬件问题)。
②若设备是首次打开,则对其进行初始化。
③若有必要,更新f_op指针。
④分配并填写置于filp->private_data里的的数据结构。
原型:
int (*open) (struct inode *inode, struct file *filp);
//
1.inode函数在i_cdev字段(即先前设置的cdev结构)中包含了我们所需要的信息。
2.struct file对象
此函数获得的是cdev结构本身。
获得包含cdev的scull_dev机构函数:
container_of(pointer, container_type, container_field);
这个宏可用来找到适当的设备结构
struct scull_dev *dev;
dev=container_of(inode->i_cdev,struct scull_dev,cdev);
filp->private_data=dev; //scull将指针保存至该字段中
当寻找到scull_dev结构后,scull将一个指针保存到file结构的pvivate_data字段中。
scull_open( )代码:
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev;
dev=container_of(inode->i_cdev,struct scull_dev,cdev);
filp->privare_date=dev;
if(file->f_flags & O_ACCMODE) ==O_WRONLY){ //现在如果open是write_only,则将设备的长度修剪为0
scull_trim(dev); //忽略错误
}
return 0;
}
注意:若user使用静态注册设备,需利用(检查保存在inode结构的次设备号)来打开设备,方法为:使用iminor宏从inode结构中获得次设备号。
2.release方法
工作流程:
①释放由open分配的、保存在filp->private_data的中内容。
②在最后一次关闭操作时关闭操作。
注:scull_open为每种设备都替换了不通的filp->f_op,所以不同的设备由不通的函数关闭。
函数:
int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}
//
只有真正释放设备数据结构的close才会调用这个方法。只有在file结构的计数归为0时,
close系统调用才会执行release方法(在删除结构时发生)。对于每个open驱动程序只
会看到对应的一次release调用。
二、scull的内存使用
1.kmalloc( )
申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB
void *kmalloc(size_t size, int flags);
//
分配size个字节大小的内存,其返回值指向该内存的指针,分配失败时返回NULL。
flags参数描述分配内存的方法。
GFP_ATOMIC:分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;
GFP_KERNEL:正常分配内存
GFP_DMA:给DMA控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)
2.kfree( )
void kfree(void *ptr);
三、scull设备布局
在scull中,每个设备都是一个指针链表,每个指针都指向一个scull_qset结构。每一个这样的结构通过一个中间指针数组最多可引用4000000个字节。发布的源代码使用了1000个指针的数据,每个指针指向4000字节的区域。我们把一个内存区称为一个量子,二这个指针数组称为量子集。
问题:修改scull设备的驱动程序的量子和量子集的数值。
在编译时:修改scull.h中的宏SCULL_QUANTUM和SCULL_QSET
在模块加载时:设置scull_quantum和scull_qset的整数值。
在运行时:使用ioctl修改当前值及默认值。
实际数据的处理结构:
struct scull_qset{
void **data;
struct scull_qset *next;
}
四、read和write
read(拷贝数据到应用程序空间)
write(从应用程序空间拷贝数据)
ssize_t read(struct file *filp, char _ _user *buff, size_count, loff_t *offp);
ssize_t write(struct file *filp, const char _ _user *buff, size_t count, loff_t *offp);
//
1.filp是文件指针
2.参数count是请求传输的数据长度。
3.参数buff是指向用户空间的缓冲区,这个缓冲区保存要写入的数据,
或者是一个存放新读入数据的空缓存区。
4.offp是一个指向long offset type(长偏移量类型)对象的指针,
这个对象指明在文件中进行存取操作的位置。
注意:read和write方法的buff参数是用户空间的指针。内核代码不能直接引用其中的内容。
原因:
①运行架构或配置不同,用户空间指针无效,地址无法映射到内核空间。
②用户空间内存是分页的,在系统调用被调用时,涉及到的内存可能根本就不在RAM中
③提供指针的程序可能存在缺陷。
利用以下函数来实现在用户空间和内核空间之间进行整段数据的拷贝
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);
ssize_t scull_read(struct file *filp , char __user *buf, size_t count, loff_t *f_pos)
{
ssize_t retval = 0;
if (*f_pos >= size)
goto out;
if (*f_pos + count > size)
count = size - *f_pos;
if (copy_to_user(buf, store + *f_pos , count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
return retval;
}
ssize_t scull_write(struct file *filp , const char __user *buf,size_t count ,loff_t *f_pos)
{
ssize_t retval = -ENOMEM; /* value used in "goto out"statements */
if (*f_pos >= MAX_SIZE)
goto out;
if (*f_pos + count > MAX_SIZE)
count = MAX_SIZE - *f_pos;
if (copy_from_user(store + *f_pos , buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
/* update the size */
if (size < *f_pos)
size = *f_pos;
out:
return retval;
}
五、readv和writev
readv调用可以用于将指定数量的数据一次读入每个缓冲区。
writev把各个缓冲区的内容收集起来,并将它们在一次写入操作中进行输出。
struct iovec
{
void _ _user *iov_base;
_ _kernel_size_t iov_len;
};
//
每个iovec结构都描述了一个用于传输的数据块,这个数据块起始位置在iov_base(用户空间中),
长度为iov_len字节。
ssize_t (*readv) (struct file *filp, const struct iovec *iov,
unsigned long count, loff_t *ppos);
ssize_t (*writev) (struct file *filp, const struct iovec *iov,
unsigned long count, loff_t *ppos);
//
函数中的count参数指明要操作多少个iovec结构,这些结构由应用程序创建,而内核在调用驱动
程序之前会把它们拷贝到内核空间中。