Linux设备驱动学习(4) 阻塞式PIPE内存“设备” 休眠+异步通知 scullpipe

原创 2012年03月29日 21:23:10
这是scull类设备的阻塞方式,之前的scull设备中的读进程若读到内存空间无数据,则立即返回。
scullpipe包含两个等待队列和一个缓冲区。
写进程在“前面”写,读进程在“后面”读。
在scull_pipe设备中,缓冲区为空,读进程会阻塞自己,直到缓冲区内有数据后由内核唤醒,读完数据后唤醒所有写进程。
写进程也相对应,当缓冲区满的时候阻塞,有空闲空间则被唤醒,写完数据后异步通知读进程。
/*
 * pipe.c -- fifo driver for scull
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 */
 
#include <linux/module.h>
#include <linux/moduleparam.h>

#include <linux/kernel.h>	/* printk(), min() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/proc_fs.h>
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>

#include "scull.h"		/* local definitions */

struct scull_pipe {
        wait_queue_head_t inq, outq;       /* read and write queues *///inq是读设备的进程的等待队列,outq是写设备的进程的等待队列
        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 */
};

/* parameters */
static int scull_p_nr_devs = SCULL_P_NR_DEVS;	/* number of pipe devices */
int scull_p_buffer =  SCULL_P_BUFFER;	/* buffer size */
dev_t scull_p_devno;			/* Our first device number */

module_param(scull_p_nr_devs, int, 0);	/* FIXME check perms */
module_param(scull_p_buffer, int, 0);

static struct scull_pipe *scull_p_devices;

static int scull_p_fasync(int fd, struct file *filp, int mode);
static int spacefree(struct scull_pipe *dev);
/*
 * Open and close
 */


static int scull_p_open(struct inode *inode, struct file *filp)//打开文件
{
	struct scull_pipe *dev;

	dev = container_of(inode->i_cdev, struct scull_pipe, cdev);
	filp->private_data = dev;

	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	if (!dev->buffer) {
		/* allocate the buffer *///若缓冲区不存在,则分配缓冲区,由第一个打开文件的进程执行
		dev->buffer = kmalloc(scull_p_buffer, GFP_KERNEL);
		if (!dev->buffer) {
			up(&dev->sem);
			return -ENOMEM;
		}
	}
	dev->buffersize = scull_p_buffer;
	dev->end = dev->buffer + dev->buffersize;//设置尾指针
	dev->rp = dev->wp = dev->buffer; /* rd and wr from the beginning */

	/* use f_mode,not  f_flags: it's cleaner (fs/open.c tells why) */
	if (filp->f_mode & FMODE_READ)//读进程个数
		dev->nreaders++;
	if (filp->f_mode & FMODE_WRITE)//写进程个数
		dev->nwriters++;
	up(&dev->sem);

	return nonseekable_open(inode, filp);//通知内核,设备不支持llseek操作,默认内核允许设备定位lseek
}



static int scull_p_release(struct inode *inode, struct file *filp)
{
	struct scull_pipe *dev = filp->private_data;

	/* remove this filp from the asynchronously notified filp's */
	scull_p_fasync(-1, filp, 0);//将此文件描述符(进程)从异步通知描述符集合中删除
	down(&dev->sem);
	if (filp->f_mode & FMODE_READ)
		dev->nreaders--;
	if (filp->f_mode & FMODE_WRITE)
		dev->nwriters--;
	if (dev->nreaders + dev->nwriters == 0) {
		kfree(dev->buffer);//设备上没有读写进程了则释放设备文件
		dev->buffer = NULL; /* the other fields are not checked on open */
	}
	up(&dev->sem);
	return 0;
}


/*
 * Data management: read and write
 */

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 */
		up(&dev->sem); /* release the lock */
		if (filp->f_flags & O_NONBLOCK)//
			return -EAGAIN;
		PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
		if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))//条件为真则继续执行,否则进入等待队列dev->inq。读写指针不等即说明缓冲区内有数据
			return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
		/* otherwise loop, but first reacquire the lock *///运行到这里,说明条件为真,缓冲区内有数据,进程取得信号量后读缓冲区
		if (down_interruptible(&dev->sem))
			return -ERESTARTSYS;
	}
	/* ok, data is there, return something */
	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 */
	wake_up_interruptible(&dev->outq);//唤醒写队列中的所有进程
	PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
	return count;
}

/* Wait for space for writing; caller must hold device semaphore.  On
 * error the semaphore will be released before returning. */
static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
	while (spacefree(dev) == 0) { /* full */
		DEFINE_WAIT(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);//准备休眠等待,可能会在下面的schedule()中才休眠
		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))//获得锁之后如果缓冲区已经有写的空间则跳出while循环,函数返回。
			return -ERESTARTSYS;				
	}
	return 0;
}	

/* How much space is free? */
static int spacefree(struct scull_pipe *dev)//返回(真正可用空间-1)的大小
{
	if (dev->rp == dev->wp)
		return dev->buffersize - 1;
	return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1;
}

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 *///有空间写则返回0
	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);//向异步队列发送SIGIO信号,POLL_IN标识设备可读
	PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count);
	return count;
}

static unsigned int scull_p_poll(struct file *filp, poll_table *wait)//设备提供轮询功能,用户态程序可以使用select来轮询设备
{
	struct scull_pipe *dev = filp->private_data;
	unsigned int mask = 0;

	/*
	 * The buffer is circular; it is considered full
	 * if "wp" is right behind "rp" and empty if the
	 * two are equal.
	 */
	down(&dev->sem);
	poll_wait(filp, &dev->inq,  wait);//把读等待队列添加到poll_table wait中
	poll_wait(filp, &dev->outq, wait);//把写等待队列添加到poll_table wait中

	if (dev->rp != dev->wp)
		mask |= POLLIN | POLLRDNORM;	/* readable */
	if (spacefree(dev))
		mask |= POLLOUT | POLLWRNORM;	/* writable */
	up(&dev->sem);
	return mask;
}





static int scull_p_fasync(int fd, struct file *filp, int mode)
{
	struct scull_pipe *dev = filp->private_data;

	return fasync_helper(fd, filp, mode, &dev->async_queue);
}



/* FIXME this should use seq_file */
#ifdef SCULL_DEBUG
static void scullp_proc_offset(char *buf, char **start, off_t *offset, int *len)
{
	if (*offset == 0)
		return;
	if (*offset >= *len) {	/* Not there yet */
		*offset -= *len;
		*len = 0;
	}
	else {			/* We're into the interesting stuff now */
		*start = buf + *offset;
		*offset = 0;
	}
}


static int scull_read_p_mem(char *buf, char **start, off_t offset, int count,
		int *eof, void *data)
{
	int i, len;
	struct scull_pipe *p;

#define LIMIT (PAGE_SIZE-200)	/* don't print any more after this size */
	*start = buf;
	len = sprintf(buf, "Default buffersize is %i\n", scull_p_buffer);
	for(i = 0; i<scull_p_nr_devs && len <= LIMIT; i++) {
		p = &scull_p_devices[i];
		if (down_interruptible(&p->sem))
			return -ERESTARTSYS;
		len += sprintf(buf+len, "\nDevice %i: %p\n", i, p);
/*		len += sprintf(buf+len, "   Queues: %p %p\n", p->inq, p->outq);*/
		len += sprintf(buf+len, "   Buffer: %p to %p (%i bytes)\n", p->buffer, p->end, p->buffersize);
		len += sprintf(buf+len, "   rp %p   wp %p\n", p->rp, p->wp);
		len += sprintf(buf+len, "   readers %i   writers %i\n", p->nreaders, p->nwriters);
		up(&p->sem);
		scullp_proc_offset(buf, start, &offset, &len);
	}
	*eof = (len <= LIMIT);
	return len;
}


#endif



/*
 * The file operations for the pipe device
 * (some are overlayed with bare scull)
 */
struct file_operations scull_pipe_fops = {
	.owner =	THIS_MODULE,
	.llseek =	no_llseek,//特殊的辅助函数,定义在<linux/fs.h>
	.read =		scull_p_read,
	.write =	scull_p_write,
	.poll =		scull_p_poll,
	.ioctl =	scull_ioctl,
	.open =		scull_p_open,
	.release =	scull_p_release,
	.fasync =	scull_p_fasync,
};


/*
 * Set up a cdev entry.
 */
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);
}

 

/*
 * Initialize the pipe devs; return how many we did.
 */
int scull_p_init(dev_t firstdev)//以firstdev开始,为scullpipe设备分配scull_p_nr_devs个设备号,此函数在main.c中被调用。
{
	int i, result;
				//这里连续分配了scull_p_nr_devs个设备号
	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));
		init_waitqueue_head(&(scull_p_devices[i].outq));
		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;
}

/*
 * This is called by cleanup_module or on failure.
 * It is required to never fail, even if nothing was initialized first
 */
void scull_p_cleanup(void)
{
	int i;

#ifdef SCULL_DEBUG
	remove_proc_entry("scullpipe", NULL);
#endif

	if (!scull_p_devices)
		return; /* nothing else to release */

	for (i = 0; i < scull_p_nr_devs; i++) {
		cdev_del(&scull_p_devices[i].cdev);
		kfree(scull_p_devices[i].buffer);
	}
	kfree(scull_p_devices);
	unregister_chrdev_region(scull_p_devno, scull_p_nr_devs);
	scull_p_devices = NULL; /* pedantic */
}


注意:open操作中假设分配了0x1~ox9共9个字节的buffer,那么open之后内存如图所示,注意此时dev->end所指的空间是无效内存。


这里主要理解了read和write函数,read和write函数也是其阻塞实现的核心,其他函数会在后续的学习中来补充理解完整。

scullpipe设备提供了轮询的功能

测试代码如下

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>

#define CLEAR 0x1
int main(int argc, char **argv)
{
	int fd;
	char rd_ch[100];
	fd_set rfds,wfds;//文件描述符集合
  
	fd = open("/dev/my_scullpipe", O_RDONLY | O_NONBLOCK);
	if (fd ==  - 1){
	printf("ERROR:open\n");
	}
	else{
		while(1)
			{
				FD_ZERO(&rfds);//先清空
				FD_ZERO(&wfds);
				FD_SET(fd, &rfds);//将fd加入到读文件描述符集合
				FD_SET(fd, &wfds);//将fd加入到写文件描述符集合
				//检查文件描述符fd,select监视文件描述符集合rfds和wfds
				select(fd + 1, &rfds, &wfds, NULL, NULL);
		
				if(FD_ISSET(fd, &rfds)){//可读
					printf("the device can be read\n");
				}
		
				if(FD_ISSET(fd, &wfds)){//可写
					printf("the device can be written\n");
				}      
			}
	}
	
}


相关文章推荐

07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-按键驱动程序之异步通知机制+原子操作+互斥信号量+阻塞与非阻塞+定时器去抖

一、异步通知机制 从按键的实现方式来说,可以分为以下几种方式 查询方式,极度耗费CPU资源中断方式,平时休眠,按键按下,唤醒休眠poll机制,不需要一直read,根据poll返回值来决定是否rea...

linux 设备驱动异步通知总结

  • 2016年02月18日 15:18
  • 16KB
  • 下载

Linux 设备驱动学习之 异步通知

From:http://blog.csdn.net/ypoflyer/article/details/6131334   异步通知:   使用poll轮询方式的时候,相当于应用程序在需要的时候...

Linux设备驱动之阻塞I/O与异步通知

 阻塞与非阻塞访问是 I/O 操作的两种不同模式,前者在 I/O 操作暂时不可进行时会让进程睡眠,后者则不然。在设备驱动中阻塞 I/O一般基于等待队列来实现,等待队列可用于同步驱动中事件发生的先...

linux设备驱动开发学习之旅--异步通知

[cpp] view plaincopy /**   * Author:hasen   * 参考 :《linux设备驱动开发详解》   * 简介:android小菜...

linux字符设备驱动-异步通知

异步通知关键步骤: 1,应用注册信号处理函数,使用signal函数; 2,谁来发:驱动发送通知信号; 3,发给谁:驱动发送通知给特定的应用程序,驱动需要知道应用程序的PID号; 4,怎么发:驱...

linux设备驱动归纳总结(三):7.异步通知fasync

linux设备驱动归纳总结(三):7.异步通知fasync xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...

Linux设备驱动一 (3)异步通知

异步通知,就是让驱动去告诉应用,底层硬件发生了什么事,而不是应用主动地去查询驱动,这对系统的性能有一个很大的提升。    应用层次         1、要用fcntl对此文件进行设置,      ...

深入浅出 Linux设备驱动异步通知介绍

结合阻塞与非阻塞访问、poll函数可以较好地解决设备的读写,但是如果有了异步通知就更方便了。异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬...
  • ailinty
  • ailinty
  • 2011年09月01日 14:45
  • 320

Linux的异步通知字符设备驱动

Fasync字符驱动: 1、在我们用户程序下所做的工作: ⑴ 注册信号处理函数。 通过signal 或sigaction()实现。 ⑵ 使进程成为该文件的的属主进程。  通过fcntl 的F_SETO...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux设备驱动学习(4) 阻塞式PIPE内存“设备” 休眠+异步通知 scullpipe
举报原因:
原因补充:

(最多只允许输入30个字)