您的位置:首页 > 其它

LDD3源码学习笔记之scull_pipe转

2008-10-30 15:57 411 查看
/*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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: