LDD3源码学习日记<七>

这篇是学习关于阻塞IO的源码内容,源代码在examples/scull/pipe.c  examples/scull/main.c中,下面开始分析源代码:

一、代码分析

scullpipe使用一个进程来产生数据并唤醒读取进程,类似的,利用读取进程来唤醒等待缓冲区可用的写入进程,scullpipe的主体实现在examples/scull/pipe.c中,但是也利用了examples/scull/main.c中的一些代码。
分析scullpipe的起点在main.c中,首先看main.c中的如下代码:

        /* 初始化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);
	}

        /* 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);
首先,在module_init函数里,调用了MKDEV宏来为scullpip生成设备号,scull_ninor等于0,scull_nr_devs等于四,在这里,就是为了生成一个主设备号随机产生,次设备号为4的scullpip设备号,再利用这个设备号作为参数,传递给scull_p_init函数,在这个函数里面,调用了register_chrdev_region函数,申请从dev开始的scull_p_nr_devs个设备编号,使得scullpip0~3的次设备号分别为4、5、6、7、8.下面是 scull_p_init的源代码:

int scull_p_init(dev_t firstdev)
{
	int i, result;

	result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp");
	if (result < 0) {
		printk(KERN_NOTICE "Unable to get scullp region, error %d\n", result);
		return 0;
	}
	scull_p_devno = firstdev;
	scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL);
	if (scull_p_devices == NULL) {
		unregister_chrdev_region(firstdev, scull_p_nr_devs);
		return 0;
	}
	memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe));
	for (i = 0; i < scull_p_nr_devs; i++) {
		init_waitqueue_head(&(scull_p_devices[i].inq));  //休眠在scullpip设备上等待写入的进程
		init_waitqueue_head(&(scull_p_devices[i].outq)); //休眠在scullpip设备上等待被读的进程
		init_MUTEX(&scull_p_devices[i].sem);
		scull_p_setup_cdev(scull_p_devices + i, i);
	}
#ifdef SCULL_DEBUG
	create_proc_read_entry("scullpipe", 0, NULL, scull_read_p_mem, NULL);
#endif
	return scull_p_nr_devs;
}

这个函数的实现和scull_init十分类似,注册设备号,然后初始化设备结构体里面的各个成员。下面看看scull_p_set函数和scull_pip结构体:

struct scull_pipe {
        wait_queue_head_t inq, outq;       /* read and write queues */
        char *buffer, *end;                /* begin of buf, end of buf */
        int buffersize;                    /* used in pointer arithmetic */
        char *rp, *wp;                     /* where to read, where to write */
        int nreaders, nwriters;            /* number of openings for r/w */
        struct fasync_struct *async_queue; /* asynchronous readers */
        struct semaphore sem;              /* mutual exclusion semaphore */
        struct cdev cdev;                  /* Char device structure */
};

scull_p_setup_cedv的源码:

static void scull_p_setup_cdev(struct scull_pipe *dev, int index)
{
	int err, devno = scull_p_devno + index;
    
	cdev_init(&dev->cdev, &scull_pipe_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk(KERN_NOTICE "Error %d adding scullpipe%d", err, index);
}
主要实现了调用cdev_init函数初始化dev->cdev,指定设备操作函数集是scull_pipe_fops。调用cdev_add函数将dev->cdev注册到系统中, devno是对应的设备编号。

static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct scull_pipe *dev = filp->private_data;

	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	while (dev->rp == dev->wp) { /* nothing to read,当读指针dev->rp与写指针 dev->wp相等时,*/
				/*说明缓冲区中没有数据可读。这种情况下,要根据用户指定的标志位决定是进入休眠等待还是直接返回。 */
		up(&dev->sem); /* release the lock */
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
		/*调用wait_event_interruptible函数休眠在dev->inq等待队列上,
		注意被唤醒的条件是(dev->rp != dev->wp),即缓冲区中有数据可读。
		由这一句也可以看出,用户空间的read进程对应的等待队列是dev->inq。
		另外,wait_event_interruptible的返回值有两种,返回0表示缓冲区中有数据可读,被唤醒。
		返回非0值表示休眠被某个信号中断,这时,132行直接返回-ERESTARTSYS。*/
		if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
			return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
		/* otherwise loop, but first reacquire the lock */
		/*休眠被唤醒后,首先重新获得互斥锁,         */
		/*然后返回到while循环开始处判断是否有数据可读*/
		if (down_interruptible(&dev->sem))
			return -ERESTARTSYS;
	}
	/* ok, data is there, return something */
	/*确认有数据可读后,退出while循环,    */
	/*根据读写指针的位置位及count值,决定要读多少数据*/
	if (dev->wp > dev->rp)
		count = min(count, (size_t)(dev->wp - dev->rp));
	else /* the write pointer has wrapped, return data up to dev->end */
		count = min(count, (size_t)(dev->end - dev->rp));
		
		/*读数据到用户空间*/
	if (copy_to_user(buf, dev->rp, count)) {
		up (&dev->sem);
		return -EFAULT;
	}
	/*更新读指针的位置*/
	dev->rp += count;
	if (dev->rp == dev->end)
		dev->rp = dev->buffer; /* wrapped */
	/*释放互斥锁*/
	up (&dev->sem);

	/* finally, awake any writers and return */
	/*读取结束后,就有缓冲区空间可以进行写操作了。*/
	/*所以唤醒所有休眠在dev->outq上的写进程,唤醒函数是wake_up_interruptible。*/
	wake_up_interruptible(&dev->outq);
	PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
	/*返回读取的字节数*/
	return count;
}
write函数,看注释(与scull_p_read采用简单休眠不同,scull_p_write采用了手工休眠的方式):

static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct scull_pipe *dev = filp->private_data;
	int result;
	/*获得互斥锁*/
	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	/* Make sure there's space to write */
	/*调用scull_getwritespace函数*/
	result = scull_getwritespace(dev, filp);
	if (result)
		return result; /* scull_getwritespace called up(&dev->sem) */

	/* ok, space is there, accept something */
	/*计算可写入数据的大小。*/
	count = min(count, (size_t)spacefree(dev));
	if (dev->wp >= dev->rp)
		count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */
	else /* the write pointer has wrapped, fill up to rp-1 */
		count = min(count, (size_t)(dev->rp - dev->wp - 1));
	PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf);
	/*将数据从用户空间拷贝到内核空间*/
	if (copy_from_user(dev->wp, buf, count)) {
		up (&dev->sem);
		return -EFAULT;
	}
	/*调整写入指针的位置*/
	dev->wp += count;
	if (dev->wp == dev->end)
		dev->wp = dev->buffer; /* wrapped */
	/*释放互斥锁*/
	up(&dev->sem);

	/* finally, awake any reader */
	wake_up_interruptible(&dev->inq);  /* blocked in read() and select() */

	/* and signal asynchronous readers, explained late in chapter 5 */
	if (dev->async_queue)
		/*异步通知相关的操作*/
		kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
	PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count);
	return count;
}
scull_getwritespace函数,该函数定义如下:

static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
	while (spacefree(dev) == 0) { /* full */
		DEFINE_WAIT(wait);
		
		up(&dev->sem);
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		PDEBUG("\"%s\" writing: going to sleep\n",current->comm);
		prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
		if (spacefree(dev) == 0)
			schedule();
		finish_wait(&dev->outq, &wait);
		if (signal_pending(current))
			return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
		if (down_interruptible(&dev->sem))
			return -ERESTARTSYS;
	}
	return 0;
}	
使用spacefree函数判断是否有空间可写,该函数定义如下

/* How much space is free? */
/*如果spacefree函数返回值为0,说明现在scullpipe设备缓冲区已满,没有空间可以写入。*/
/*写进程需要根据调用者设置的标志位决定是否休眠等待。*/
static int spacefree(struct scull_pipe *dev)
{
	if (dev->rp == dev->wp)
		return dev->buffersize - 1;
	return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1;
}
接下来是测试,具体信息如图:



左侧执行cat命令,会被阻塞,此时在右侧向scullpipe0设备输入信息,会在左侧被打印出来;

另外,参考博客:http://blog.youkuaiyun.com/liuhaoyutz/article/details/7395057,可以找出LDD3源码中的一个BUG,实验已做,就不放图了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值