LDD3源码学习日记<二>

分析完了最基本的hello.c程序,开始学习下面的scull.c。第三章的内容里并没有涉及到scull中所有要用到的知识,主要介绍了设备驱动的框架,scull的使用,及fileoperation里面的open、read、write的实现。

下面开始分析代码。

最先看的应该是init函数和exit函数,找到他们

int scull_init_module(void)
{
	int result, i;
	dev_t dev = 0;

	if (scull_major) {				//scull_major在scull.h中定义
		dev = MKDEV(scull_major, scull_minor);	//利用宏得出设备号
		result = register_chrdev_region(dev, scull_nr_devs, "scull");
	} else {
		result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
				"scull");
		scull_major = MAJOR(dev);
	}
	if (result < 0) {
		printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
		return result;
	}
	
	/*kmalloc为设备分配空间*/
	scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
	if (!scull_devices) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}
	memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

        /* 初始化scull_nr_devs个设备,也就是设置scull_dev结构体的成员 */
	for (i = 0; i < scull_nr_devs; i++) {
		scull_devices[i].quantum = scull_quantum;	//初始化量子大小
		scull_devices[i].qset = scull_qset;		//初始化量子集
		init_MUTEX(&scull_devices[i].sem);		//初始化互斥量
		scull_setup_cdev(&scull_devices[i], i);		//scull_setup_cdev函数完成对scull设备的cdev成员变量(struct cd																					  //	类型)的初始化和注册
	}

        /* At this point call the init function for any friend device */
	dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
	dev += scull_p_init(dev);
	dev += scull_access_init(dev);

#ifdef SCULL_DEBUG /* only when debugging */
	scull_create_proc();
#endif

	return 0; /* succeed */

  fail:
	scull_cleanup_module();
	return result;
}


    首先判断了scull_major,如果设置了,就是用默认的主次设备号,并调用宏MKDEV得出设备号;如果没有设置,就采用alloc_chrdev_region来动态申请设备号;

    接下来,用kmalloc函数为设备分配空间并调用memset函数将分配得到的内存清零;LDD3里面采用手动创建设备节点的方法,我尝试过使用udev来实现自动生成设备节点,发现不能满足scull的实现(或者说以我的水平很难实现。。),scull的实现是可以对内存的管理,我掌握的udev还不能实现改变设备节点的大小。

    生成设备节点后就是对这scull_nr_devs设备进行初始化了。这里我们关注scull_setup_cdev(&scull_devices[i], i);函数:

static void scull_setup_cdev(struct scull_dev *dev, int index)
{
	/*调用MKDEV宏得到设备编号,注意,4个scull设备的主设备号都是*/
	/*一样的,但次设备号分别是0 - 3*/
	int err, devno = MKDEV(scull_major, scull_minor + index);
    	//cdev结构体在内核中代表一个字符设备。
    	//调用cdev_init函数对cdev结构体进行初始化,指定对应的文件操作函
    	//数集是scull_fops,这个scull_fops必须是前面已经定义实现好的。
	cdev_init(&dev->cdev, &scull_fops); 
	
	dev->cdev.owner = THIS_MODULE;	//行指定所有者是THIS_MODULE
	dev->cdev.ops = &scull_fops;	//多余的
	//调用cdev_add函数将cdev结构体注册到内核,注册成功后,相应的scull设备就“活”了,
	//其它程序就可以访问scull设备的资源。
	err = cdev_add (&dev->cdev, devno, 1);	
	/* Fail gracefully if need be */
	if (err)
		printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
到这里,scull模块的初始化函数就结束了,还要记住几个定义的宏和scull_dev结构体


struct scull_dev {
	struct scull_qset *data;  /* Pointer to first quantum set */
	int quantum;              /* the current quantum size */
	int qset;                 /* the current array size */
	unsigned long size;       /* amount of data stored here */
	unsigned int access_key;  /* used by sculluid and scullpriv */
	struct semaphore sem;     /* mutual exclusion semaphore     */
	struct cdev cdev;	  /* Char device structure	*/
};
struct scull_qset {
	void **data;
	struct scull_qset *next;
};
在scull中,每个设备都是一个指针链表,其中每个指针都指向一个scull_qset结构。LDD发布的源码中,使用了SCULL_QSET个指针

数组,每个指针指向一个SCULL_P_BUFFER字节的区域。每一个内存区称为一个量子,指针数组称为量子集;设备布局如下:



下面看file_operation结构体:

struct file_operations scull_fops = {
	.owner =    THIS_MODULE,
	.llseek =   scull_llseek,
	.read =     scull_read,
	.write =    scull_write,
	.ioctl =    scull_ioctl,
	.open =     scull_open,
	.release =  scull_release,
};
再次,LDD3第三章只讲解了read、write、open和release方法;首先看open函数:

int scull_open(struct inode *inode, struct file *filp)
{
	struct scull_dev *dev; /* device information */
	
	/*调用container_of宏,通过cdev成员得到包含该cdev的scull_dev结构。*/
	dev = container_of(inode->i_cdev, struct scull_dev, cdev);
	
	/*将得到的scull_dev结构保存在filp->private_data中,因为open结束后,后面的read,write等操作使用*/
	/*同一个filp变量,它们即可以从filp->private_data中直接取出scull_dev结构体来使用。*/
	filp->private_data = dev; /* for other methods */

	/* now trim to 0 the length of the device if open was write-only */
	if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
		if (down_interruptible(&dev->sem))  //涉及互斥量,后面章节学习的内容
			return -ERESTARTSYS;
		scull_trim(dev); /* ignore errors */
		up(&dev->sem);
	}
	return 0;          /* success */
}
以上函数的主要动作是dev = container_of(inode->i_cdev, struct scull_dev, cdev)和filp->private_data = dev;此外我们还要关注scull_trim(dev)函数的实现:

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) { /* dev->data指向第一个量子集scull_qset。*/
						    /*所以这个for循环每次循环处理一个scull_qset */
		if (dptr->data) {
			for (i = 0; i < qset; i++)
				kfree(dptr->data[i]);//释放一个量子的空间,注意,dptr->data[i]依然是一个指针
				     		     //记住在scull_qset结构体中,void **data;的定义	
				kfree(dptr->data);   //释放量子集数组占用的内存空间
			dptr->data = NULL;
		}
		next = dptr->next;		//遍历
		kfree(dptr);			//释放cull_qset占用的内存空间
	}
	dev->size = 0;
	dev->quantum = scull_quantum;
	dev->qset = scull_qset;
	dev->data = NULL;
	return 0;
}
open函数结束,release函数什么也不做,这里不列举出来;下面是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; 
	struct scull_qset *dptr;	/* the first listitem */
	int quantum = dev->quantum, qset = dev->qset;
	int itemsize = quantum * qset; /* how many bytes in the listitem */
	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;

	/* find listitem, qset index, and offset in the quantum */
	item = (long)*f_pos / itemsize;		// item代表要读的数据起始点在哪个scull_qset中
	rest = (long)*f_pos % itemsize;		//rest是一个中间值,供下面的计算使用
	s_pos = rest / quantum;			// s_pos代表要读的数据起始点在哪个量子中
	q_pos = rest % quantum;			//q_pos代表要读的数据的起始点在量子的具体哪个位置

	/* follow the list up to the right position (defined elsewhere) */
	/*下面函数的作用是返回参数item指定的scull_qset。*/
	/*如果scull_qset不存在,还要分配内存空间,创建指定的scull_qset。*/
	dptr = scull_follow(dev, item);

	/*如果指定的scull_qset不存在,或者量子指针数组不存在,或者量子不存在,都退出*/
	if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
		goto out; /* don't fill holes */

	/* read only up to the end of this quantum */
	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;		//读取完成后,新的文件指针位置向前移动count个字节
	retval = count;

  out:
	up(&dev->sem);
	return retval;
}
在read函数中,首先进行相关变量的初始化,再计算出要写入的位置,随后调用scull_follow函数判断指定位置处是否有scull_qset结构题存在,如果没有,就创建它。确定要写的位置后,调用copy_to_user,将数据拷贝到用户空间;下面是 scull_follow函数的实现:

struct scull_qset *scull_follow(struct scull_dev *dev, int n)
{
	struct scull_qset *qs = dev->data;

        /* Allocate first qset explicitly if need be */
	if (! qs) {
		qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
		if (qs == NULL)
			return NULL;  /* Never mind */
		memset(qs, 0, sizeof(struct scull_qset));
	}

	/* Then follow the list */
	while (n--) {
		if (!qs->next) {
			qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
			if (qs->next == NULL)
				return NULL;  /* Never mind */
			memset(qs->next, 0, sizeof(struct scull_qset));
		}
		qs = qs->next;
		continue;
	}
	return qs;
}
write函数的实现和read的实现类似,也是先定位要写的位置,不同的是他调用的是copy_from_user函数, 如果指定的量子指针数组不存在,则分配内存空间,创建量子指针数组。

ssize_t scull_write(struct file *filp, const 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; /* value used in "goto out" statements */
	/********以上为初始化工作***************/
	/****************************************************************/
	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	/************定位****************************/
	/* find listitem, qset index and offset in the quantum */
	item = (long)*f_pos / itemsize;
	rest = (long)*f_pos % itemsize;
	s_pos = rest / quantum; q_pos = rest % quantum;
	/*******************************************/

	/* 判断指定位置处是否存在scull_qset结构体 */
	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;
	}
	/* write only up to the end of this quantum */
	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;

        /* update the size */
	if (dev->size < *f_pos)
		dev->size = *f_pos;

  out:
	up(&dev->sem);
	return retval;
}

以就是第三章涉及到的代码的注释,下一步的工作就是先编译通过。

刚刚编译通过了,主要要修改的地方和下面博客链接地址说明的一样,参照他的做法,可以通过编译。


然后烧写到开发板上进行测试:



测试程序将会在下一章里面实现。

参考博客:http://blog.csdn.net/liuhaoyutz/article/details/7383313


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值