《LINUX设备驱动程序》读书笔记---------字符设备(二)

1.scull内存使用

1.1 内存管理函数
定义在 <linux/slab.h>

void *kmalloc(size_t size,int flags);
void kfree(void *ptr);

kmalloc:size代表的是分配空间的大小,flags代表分配方式(第八章讲细节),先默认它的值为GFP_KERNEL。
kfree:用来释放内存,除了kmalloc返回的地址和NULL之外,传入其他指针式非法的。

1.2 scull设备结构
在这里插入图片描述
整体结构类似一个链表,每个指针指向下一个scull_qset结构。
每个scull_qset结构中都有data指针指向一个数组,数组中包含1000个指针,每个指针指向一块4000byte大小的空间(称为一个Quantum量子),所以每个scull_qset都有1000*4000bytes大小的空间。

我们并不限制死量子的大小和量子集中量子的数目,这些都可以根据需求来改变,有一下几种方法可以改变这两个的值。

  • 编译阶段:改变SCULL_QUANTUM 和 SCULL_QSET来改变,不过这样其实还是限制死了。
  • 模块加载阶段:通过之前《模块接口》笔记中模块参数的方式,通过加载命令insmod传入参数,改变scull_quantum和scull_qset的值。
  • 运行阶段:可以通过ioctl来设置

1.3 相关代码

  • scull_dev结构体
    在上一份笔记字符设备(一)中有书写,在这里在回顾一下。

    struct scull_dev{
    	struct scull_qset *data;	/*指向第一个scull_qset的指针*/
    	int quantum;				/*量子大小*/
    	int qset;					/*量子集中量子个数*/
    	unsigned long size;			/*存储的数据量*/
    	unsigned int access_key;	/*used by sculluid and scullpriv*/
    	struct semaphore sem;		/*互斥信号量*/
    	struct cdev cdev;			/*字符设备结构体*/
    }
    
  • scull_qset 结构体

    struct scull_qset{
    	void **data;
    	struct scull_qset *next;
    }
    
  • scull_trim 释放数据区函数
    这个函数在文件写权限打开时,会被scull_open调用,用于释放整个数据区域。同样在清除函数中也会调用它来归还内存资源。

    int scull_trim(struct scull_dev *dev)
    {
    	struct scull_qset *next,*dptr;
    	int qset = dev->qset;	
    	int i;
    	for(dptr = dev->data;dptr;dptr = next)/*遍历链表,访问每个scull_qset节点*/
    	{
    		if(dptr->data)
    		{
    			for(i=0;i<qset;i++)		/*遍历量子集,释放每个量子*/
    				kfree(dptr->data[i]);
    			kfree(dptr->data);		/*释放量子集*/
    			dptr->data = NULL;			
    		}
    		next = dptr->next;
    		kfree(dptr);		/*释放scull_qset节点*/
    	}
    	dev->size = 0;
    	dev->quantum = scull_quantum;
    	dev->qset = scull_qset;
    	dev->data = NULL;		/*初始化scull_dev*/
    	return 0;
    }
    
2.read and write
ssize_t read (struct file *filp, char __user *buff, size_t count, loff_t *offp);
ssize_t write (struct file *filp, const char __user *buff, size_t count, loff_t *offp);

其中,filp是指向file结构体的指针。count是请求数据传输的大小。buff是指向用户空间的缓冲区。offp是指向“long offset type”(就是long long)对象的指针,这个对象指明了当前用户访问文件的位置。
在这里插入图片描述
需要注意的是,buff是用户空间的指针,所以我们不能直接引用它来读写数据,这样是不安全的。需要利用内核支持的函数来完成这一功能,函数定义在<asm/uaccess.h>。

static inline int copy_to_user(void __user volatile *to, const void *from,
				unsigned long n);
static inline int copy_from_user(void *to, const void __user volatile *from,
				 unsigned long n);

在有了上面的工具之后,就可以写出scull的read和write的实现,代码中down_interruptible和up先不用在意,这个会在下一章讲到,其实凭借函数名称也能把它俩的功能猜出来。

  • scull_read

    ssize_t scull_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
    {
    	struct scull_dev *dev = filp->private_data;   /*在open中,我们把dev保存在了file的private_data域*/
    	struct scull_qset *dptr;
    	int quantum = dev->quantum, qset = dev->qset; /*quantum是量子大小,qset是一个量子集有多少量子*/
    	int itemsize = quantum * qset;				/*计算链表中一个scull_qset节点 指向的空间大小*/
    	int item,s_pos,q_pos,rest;
    	ssize_t retval = 0;
    	
    	if(down_interruptible(&dev->sem)) 		
    		return -ERESTARTSYS;
    	if(*f_pos >= dev->size)			/*判断文件当前位置是否超出设备区域*/
    		goto out;
    	if(*f_pos + count > dev->size)	/*判断当前位置+写入的字节是否会超出设备区域*/
    		count = dev->size - *f_pos;
    	item = (long)*f_pos / itemsize;	/*计算是第几个scull_qset节点*/
    	rest = (long)*f_pos % itemsize;	
    	s_pos = rest / quantum; /*计算是量子集中第几个量子*/
    	q_pos = rest % quantum; /*计算在量子中的偏移*/
    	
    	
    	dptr = scull_follow(dev,item); /*移动指针到相应scull_qset节点*/
    	if(dptr == NULL || !dptr->data || !dptr->data[s_pos]) 
    		goto out;
    	if(count > quantum - q_pos) /*一个量子中放不下要写入的数据,那么只写入能写下的*/
    		count = quantum-q_pos;
    	if(copy_to_user(buf,dptr->data[s_pos] + q_pos,count)) /*写入*/
    	{
    		retval = -EFAULT;
    		goto out;
    	}
    	*f_pos += count;  /*更新文件位置指示*/
    	retval = count;	  /*更新实际写入字节*/
    out:
    	up(&dev->sem);
    	return retval;
    }
    
  • scull_write
    scull_write 与 scull_read相似,仅对不同的地方进行了注释说明。

    ssize_t scull_write(struct file *flip,char __user *buf,size_t count,loff_t *f_pos)
    {
    	struct scull_dev *dev = filp->private_data;
    	struct scull_qset *dptr;
    	int quantum = dev->quantum,qset = dev->qset;
    	int itemsize = quantum * qset;
    	int item,s_pos,q_pos,rest;
    	ssize_t retval = -ENOMEM 
    	
    	if(down_interruptible(&dev->sem)) 		
    		return -ERESTARTSYS;
    	item = (long)*f_pos / itemsize;	
    	rest = (long)*f_pos % itemsize;	
    	s_pos = rest / quantum; 
    	q_pos = rest % quantum; 
    	/*在此之前都与read相同,
    		不同的地方在于,写数据的时候要为量子集和量子开辟空间
    	*/
    	dptr = scull_follow(dev,item); 
    	if(dptr == NULL ) 
    		goto out;
    	if(!dptr->data)/*为量子集开辟空间*/
    	{
    		dptr->data = kmalloc(qset * sizeof(char *),GFP_KERNEL);
    		if(!dptr->data)
    			goto out;
    		memset(dptr->data,0,qset * sizeof(char *));
    	}
    	if(!dptr->data[s_pos]) /*为量子开辟空间*/
    	{
    		dptr->data[s_pos] = kmalloc(quantum,GFP_KERNEL);
    		if(!dptr->data[s_pos])
    			goto out;
    	}
    	if(count > quantum - q_pos)
    		count = quantum - q_pos;
    	if(copy_from_user(dptr->data[s_pos]+q_pos,buf,count))
    	{
    		retval = -EFAULT;
    		goto out;
    	}
    	*f_pos += count;
    	retval = count;
    	/*更新设备size大小*/
    	if(dev->size < *f_pos)
    		dev->size = *f_pos;
    	
    out:
    	up(&dev->sem);
    	return retval;	
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值