Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2088213
  • 博文数量: 288
  • 博客积分: 10594
  • 博客等级: 上将
  • 技术积分: 3469
  • 用 户 组: 普通用户
  • 注册时间: 2006-10-27 19:27
文章分类

全部博文(288)

文章存档

2012年(4)

2011年(30)

2010年(40)

2009年(32)

2008年(71)

2007年(79)

2006年(32)

分类: LINUX

2009-02-20 15:44:37

/*pipe.c*/

/*=========================================*/

/*驱动功能分析*/

本驱动使用环形缓冲作为scull设备的的具体实现,类似于pipe.

其中实现了阻塞的I/O读写和非同步通知.

/*=========================================*/

/*主函数流程分析*/

1.定义scull_pipe设备机构体(){/*由于其实现阻塞I/O,所以主要其与scull_dev的区别*/

     用于阻塞I/O读写休眠的等待队列:wait_queue_head_t

     环形缓冲相关

     记录打开阻塞I/O的设备进程的数量

     阻塞I/O的非同步通知:fasync_struct

     锁机制和字符设备

}

2.初始化模块module_init(scull_p_init){

     (1).根据主次设备号生成dev_t,并调用register_chrdev_region向内核注册设备

     (2).scull_pipe设备结构体在内核空间分配,初始化内存:kmalloc,memset

     (3).初始化每个设备空间

         初始化等待队列头:init_waitqueue_head

         初始化互斥锁:init_MUTEX

         初始化字符设备:scull_p_setup_cdev

              定义设备的fpos,并于设备关联,定点设备所以着,然后添加字符设备./*基本同scull_dev*/

     (4).建立procdebug机制

     (5).返回值是成功初始化的设备个数

}

3.退出并注销模块module_exit(scull_p_cleanup){

     (1).注销proc调试机制

     (2).删除注册的字符设备,并清空字符设备已经分配的空间:cdev_del&kfree

     (3).清空设备结构体空间注,并销注册的scull_pipe设备:kfree&unregister_chrdev_region

         /*注意对释放后的指针依然赋值为NULL*/

}

4.设备相关文件操作函数(fpos)的定义(){

     (1)open(scull_p_open){

         通过inode->i_cdev获得scull_pipe结构体的入口地址

         在互斥锁的保护下初始化设备的环形缓冲区和其他一些变量:

              分配缓冲区空间,计算缓冲区大小,首末地址,读写位置

              根据进程对文件的读写要求,分别记录进程读写打开设备的次数dev->nreaders,dev->nwriters

              /*f_mode表示进程的读写等模式,f_flag表示文件本身的一些设定*/

         由于open不支持seeking,所以使用nonseekable_open ,并且指定fops->llseek=no_llseek

     }

     (2)release(scull_p_release){

         关闭本设备的异步通知:scull_p_fasync(-1, filp, 0);

         在互斥锁的保护下处理打开进程记录和环形缓冲

              dev->nreaders,dev->nwriters分别递减

              如果两者和为0,即无进程打开设备,则清空设备的环形缓冲

     }

     (3)read(){/*阻塞读,若无可读数据进入休眠,使用环形缓冲,最后还唤醒休眠中的写进程*/

         a.通过filp->private_data获得设备结构体的入口指针

         b.加锁down_interruptible(&dev->sem)

         c.若缓冲区为空,则进入以下循环,以实现阻塞读等待

              解锁up (&dev->sem)/*由于阻塞等待有数据写入,首先要解锁数据区*/

              判断是否为阻塞打开:filp->f_flags & O_NONBLOCK

              进入睡眠等待在写函数中的唤醒:wait_event_interruptible(dev->inq, (dev->rp != dev->wp)/*若等待函数非正常唤醒则返回值为非0,这时候应该返回-ERESTARTSYS,以通知内核重新调用该系统调用*/

              再次加锁:down_interruptible(&dev->sem)

         d.通过rp,wp的关系计算实际能读取的数据长度,注意环形缓冲wp反转后的处理,即返回rp到指针结束的数据

         e.将内核缓冲区的数据转移到用户区:copy_to_user(buf, dev->rp, count)

         f.处理环形缓冲的读写指针,具体是读指针加count,若写指针反转,则读指向开始.

         g.解锁up (&dev->sem)

         h.唤醒处于睡眠中写进程:wake_up_interruptible(&dev->outq);

     }

     (4)write(){/*read不同,本处在无数据可写进入休眠运用的是手动实现休眠的方法,并在结尾发出非同步通知*/

         a.获得设备机构体指针

         b.加锁

         c.判断缓冲区是否有空间可写,调用:scull_getwritespace(dev, filp),其中实现了手动休眠以阻塞等待可写

              判断有多少空间可用,调用:spacefree(dev),注意其中可写空间为空空间-1.若没有空间可写,则进入下面循环

                   使用手动休眠的方式等待有空间可写,其实就是等待读进程返回,在休眠前依然需要解锁

                   判断是否由信号唤醒了我们的等待:if (signal_pending(current)),如果是,则返回-ERESTARTSYS重新调用信号前的那个系统调用,即我们的休眠

                   加锁

         d.spacefree(dev)获得总的可写空间,再通过rp,wp计算实际本次可写数据的长度

         e.copy_from_user(dev->wp, buf, count)将用户数据写到内核缓冲

         f.处理wp现在的实际位置

         g.解锁up (&dev->sem)

         h.唤醒处于睡眠中读进程:wake_up_interruptible(&dev->inq);

         i.保留用于非同步通知,具体参考收获3       

     }

     (5)poll(){/*用户空间poll/select方法驱动里面的实现*/

         原型:unsigned int (*poll) (struct file *filp, poll_table *wait);

         理论实现步骤:

              将读/写调用的进程进入休眠,等待可读/可写的唤醒

              根据具体情况返回掩码,表示可读/可写

         具体实现:

              加锁

              将读写等待队列进入休眠:poll_wait(filp, &dev->inq,wait)&poll_wait(filp, &dev->outq,wait);

              唤醒后通过环形缓冲的wp,rp判断可读可写

              可读返回:POLLIN | POLLRDNORM  可写:POLLOUT | POLLWRNORM

              解锁

     }

     (6)fasync(){/*非同步通知的实现*/

         具体可以参考收获3

     }

             

}

5.proc调试方法函数(){

     在锁的保护下,将虚拟设备结构体中的相关信息输出到proc指定的内核缓冲区

     调用scullp_proc_offset处理offset问题,注意其中的算法

}

/*=========================================*/

/*收获*/

/*--------------------------------*/

1.如何使用环形缓冲(){

     1.定义环形缓冲相关变量

         首尾:char *begin, *end; 大小:int buffersize;

         当前读写位置: char *rp, *wp; 

     2.初始这些变量

         begin一般通过动态分配内存空间获得

         end=beigin+buffersize

         rpwp一般相等且指向begin

     3.具体应用

         rp==wp:表示缓冲为空

         rp>wp && rp-wp=1:说明唤醒缓冲已经满,注意这时候wp所在空间不可写入数据,也就是说,若缓冲大小为size且为空,写最多范围为size-1.

         wp>rp: 说明有新写入数据,而且没有反转,

         rp>wp: 有新写入数据,而且有反转,需要具体处理

         如缓冲不为空时候,计算可写空间大小的算法:(dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1      

}

/*--------------------------------*/

2.手动实现进程休眠(){/*区别与自动的wait_event_XXXX*/

     a.初始化相关等待队列入口,两个方法宏定义或者动态指定

         DEFINE_WAIT(my_wait);

         /*or*/

         wait_queue_t my_wait;

         init_wait(&my_wait);

     b.将等待队列入口添加到系统队列中

         prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);/*参数分别为已经定义的等待队列,本次的等待入口,是否支持中断TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE*/

     c.进程放弃cpu与调度

         检查休眠的条件是否依然成立

         schedule()/*程序进入休眠,将由唤醒queue的函数唤醒*/

     d.休眠后一些处理工作,比如说时间清除

         finish_wait(wait_queue_head_t *queue, wait_queue_t *wait)

     e.使用signal_pending(current)检查休眠是否是由信号处理函数唤醒,如果是返回-ERESTARTSYS重新调用休眠

}

/*--------------------------------*/

3.驱动中非同步通知的实现(){

     理论三个步骤:

         0.定义非同步通知列表 struct fasync_struct

         1.在以FASYSNC打开设备的时候FS调用fasysnc的实现,其功能是将一个fd文件添加到非同步通知列表,也就是作为非同步信号的接受者

         2.在驱动的其他地方发出一个非同步操作

     具体实现:

         0.struct fasync_struct *async_queue

         1.static int XXX_fasync(int fd, struct file *filp, int mode){

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

         }

         2.kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

     /*对应用户空间如何调用*/

         0.设定信号处理函数:signal(SIGIO, &handler);

         1.指定fd文件的所有者fcntl(fd, F_SETOWN, getpid( ));

         2.使非同步使能,即调用非同步oflags = fcntl(fd, F_GETFL)&fcntl(fd, F_SETFL, oflags | FASYNC);

}

/*=========================================*/
阅读(1380) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~