LDD3源码学习笔记之scull_pipe转

/*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).建立proc的debug机制
     (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
         rp和wp一般相等且指向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);
}
/*=========================================*/



本文来自ChinaUnix博客,如果查看原文请点: http://blog.chinaunix.net/u1/43031/showart_385996.html

你可能感兴趣的:(LDD3源码学习笔记之scull_pipe转)