linux设备驱动读书笔记 3.docx

上传人:b****2 文档编号:1729553 上传时间:2023-05-01 格式:DOCX 页数:18 大小:145.95KB
下载 相关 举报
linux设备驱动读书笔记 3.docx_第1页
第1页 / 共18页
linux设备驱动读书笔记 3.docx_第2页
第2页 / 共18页
linux设备驱动读书笔记 3.docx_第3页
第3页 / 共18页
linux设备驱动读书笔记 3.docx_第4页
第4页 / 共18页
linux设备驱动读书笔记 3.docx_第5页
第5页 / 共18页
linux设备驱动读书笔记 3.docx_第6页
第6页 / 共18页
linux设备驱动读书笔记 3.docx_第7页
第7页 / 共18页
linux设备驱动读书笔记 3.docx_第8页
第8页 / 共18页
linux设备驱动读书笔记 3.docx_第9页
第9页 / 共18页
linux设备驱动读书笔记 3.docx_第10页
第10页 / 共18页
linux设备驱动读书笔记 3.docx_第11页
第11页 / 共18页
linux设备驱动读书笔记 3.docx_第12页
第12页 / 共18页
linux设备驱动读书笔记 3.docx_第13页
第13页 / 共18页
linux设备驱动读书笔记 3.docx_第14页
第14页 / 共18页
linux设备驱动读书笔记 3.docx_第15页
第15页 / 共18页
linux设备驱动读书笔记 3.docx_第16页
第16页 / 共18页
linux设备驱动读书笔记 3.docx_第17页
第17页 / 共18页
linux设备驱动读书笔记 3.docx_第18页
第18页 / 共18页
亲,该文档总共18页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

linux设备驱动读书笔记 3.docx

《linux设备驱动读书笔记 3.docx》由会员分享,可在线阅读,更多相关《linux设备驱动读书笔记 3.docx(18页珍藏版)》请在冰点文库上搜索。

linux设备驱动读书笔记 3.docx

linux设备驱动读书笔记3

linux设备驱动读书笔记(3)转

2009年08月30日星期日13:

26

原子变量:

atomic_t.所有原子量操作必须使用如下函数

atomic_tv=ATOMIC_INIT(0):

声明并初始化atomic变量

voidatomic_set(atomic_t*v,inti):

设置值

intatomic_read(atomic_t*v):

读值

voidatomic_add(inti,atomic_t*v):

值加i

voidatomic_sub(inti,atomic_t*v):

值减i

voidatomic_inc(atomic_t*v):

值+1

voidatomic_dec(atomic_t*v):

值-1

intatomic_inc_and_test(atomic_t*v):

值+1,并测试值是否为0,若为0返回真,否则返回假

intatomic_dec_and_test(atomic_t*v):

值-1,并测试值是否为0,若为0返回真,否则返回假

intatomic_sub_and_test(inti,atomic_t*v):

值-i,并测试值是否为0,若为0返回真,否则返回假

intatomic_add_negative(inti,atomic_t*v):

值+i,并测试值是否为负,若为负返回真,否则返回假

intatomic_add_return(inti,atomic_t*v):

值+i,并返回值

intatomic_sub_return(inti,atomic_t*v):

值-i,并返回值

intatomic_inc_return(atomic_t*v):

值+1,并返回值

intatomic_dec_return(atomic_t*v):

值-1,并返回值

位操作:

位操作依赖于体系结构.nr参数(描述要操作哪个位)常常定义为int,也可能是unsignedlong;要修改的地址常常是一个unsignedlong指针,但是几个体系使用void*代替.

voidset_bit(nr,void*addr):

在addr指向的数据项中将第nr位设置为1

voidclear_bit(nr,void*addr):

在addr指向的数据项中将第nr位设置为0

voidchange_bit(nr,void*addr):

对addr指向的数据项中的第nr位取反

test_bit(nr,void*addr):

返回addr指向的数据项中的第nr位

inttest_and_set_bit(nr,void*addr):

在addr指向的数据项中将第nr位设置为1,并返回设置前的值

inttest_and_clear_bit(nr,void*addr):

在addr指向的数据项中将第nr位设置为0,并返回设置前的值

inttest_and_change_bit(nr,void*addr):

对addr指向的数据项中的第nr位取反,并返回取反前的值

Mark:

大部分现代代码不使用位操作,而是使用自选锁

seqlock:

2.6内核提供的,

seqlock_tlock1=SEQLOCK_UNLOCKED;

seqlock_tlock2;

seqlock_init(&lock2);

读存取通过在进入临界区入口获取一个(无符号的)整数序列来工作.在退出时,那个序列值与当前值比较;如果不匹配,读存取必须重试.结果是,读者代码象下面的形式:

unsignedintseq;

do{

seq=read_seqbegin(&the_lock);

/*Dowhatyouneedtodo*/

}whileread_seqretry(&the_lock,seq);

这个类型的锁常常用在保护某种简单计算,需要多个一致的值.如果这个计算最后的测试表明发生了一个并发的写,结果被简单地丢弃并且重新计算.

如果你的seqlock可能从一个中断处理里存取,你应当使用IRQ安全的版本来代替:

unsignedintread_seqbegin_irqsave(seqlock_t*lock,unsignedlongflags);

intread_seqretry_irqrestore(seqlock_t*lock,unsignedintseq,unsignedlongflags);

写者必须获取一个排他锁来进入由一个seqlock保护的临界区.为此,调用:

voidwrite_seqlock(seqlock_t*lock);

写锁由一个自旋锁实现,因此所有的通常的限制都适用.调用:

voidwrite_sequnlock(seqlock_t*lock);

来释放锁.因为自旋锁用来控制写存取,所有通常的变体都可用:

voidwrite_seqlock_irqsave(seqlock_t*lock,unsignedlongflags);

voidwrite_seqlock_irq(seqlock_t*lock);

voidwrite_seqlock_bh(seqlock_t*lock);

voidwrite_sequnlock_irqrestore(seqlock_t*lock,unsignedlongflags);

voidwrite_sequnlock_irq(seqlock_t*lock);

voidwrite_sequnlock_bh(seqlock_t*lock);

还有一个write_tryseqlock在它能够获得锁时返回非零.

读-拷贝-更新(Read-Copy-Update)

针对很多读但是较少写的情况.所有操作通过指针.

读:

直接操作

写:

将数据读出来,更新数据,将原指针指向新数据(这一步需要另外做原子操作的保护)

作为在真实世界中使用RCU的例子,考虑一下网络路由表.每个外出的报文需要请求检查路由表来决定应当使用哪个接口.这个检查是快速的,并且,一旦内核发现了目标接口,它不再需要路由表入口项.RCU允许路由查找在没有锁的情况下进行,具有相当多的性能好处.内核中的Startmode无线IP驱动也使用RCU来跟踪它的设备列表.

在读这一边,使用一个RCU-保护的数据结构的代码应当用rcu_read_lock和rcu_read_unlock调用将它的引用包含起来.

RCU代码往往是象这样:

structmy_stuff*stuff;

rcu_read_lock();

stuff=find_the_stuff(args...);

do_something_with(stuff);

rcu_read_unlock();

rcu_read_lock调用是很快的:

它禁止内核抢占但是没有等待任何东西.在读"锁"被持有时执行的代码必须是原子的.在对rcu_read_unlock调用后,没有使用对被保护的资源的引用.

需要改变被保护的结构的代码必须进行几个步骤:

分配一个新结构,如果需要就从旧的拷贝数据;接着替换读代码所看到的指针;释放旧版本数据(内存).

在其他处理器上运行的代码可能仍然有对旧数据的一个引用,因此不能立刻释放.相反,写代码必须等待直到它知道没有这样的引用存在了.因为所有持有对这个数据结构引用的代码必须(规则规定)是原子的,我们知道一旦系统中的每个处理器已经被调度了至少一次,所有的引用必须消失.这就是RCU所做的;它留下了一个等待直到所有处理器已经调度的回调;那个回调接下来被运行来进行清理工作.

改变一个RCU-保护的数据结构的代码必须通过分配一个structrcu_head来获得它的清理回调,尽管不需要以任何方式初始化这个结构.通常,那个结构被简单地嵌入在RCU所保护的大的资源里面.在改变资源完成后,应当调用:

voidcall_rcu(structrcu_head*head,void(*func)(void*arg),void*arg);

给定的func在安全的时候调用来释放资源

全部RCU接口比我们已见的要更加复杂;它包括,例如,辅助函数来使用被保护的链表.详细内容见相关的头文件

阻塞I/O

运行在原子上下文时不能睡眠.持有一个自旋锁,seqlock,RCU锁或中断已关闭时不能睡眠.但在持有一个旗标时睡眠是合法的

不能对醒后的系统状态做任何的假设,并且必须检查来确保你在等待的条件已经满足

确保有其他进程会做唤醒动作

明确的非阻塞I/O由filp->f_flags中的O_NONBLOCK/O_NDELAY标志来指示.只有read,write,和open文件操作受到非阻塞标志影响

下列情况下应该实现阻塞

如果一个进程调用read但是没有数据可用(尚未),这个进程必须阻塞.这个进程在有数据达到时被立刻唤醒,并且那个数据被返回给调用者,即便小于在给方法的count参数中请求的数量.

如果一个进程调用write并且在缓冲中没有空间,这个进程必须阻塞,并且它必须在一个与用作read的不同的等待队列中.当一些数据被写入硬件设备,并且在输出缓冲中的空间变空闲,这个进程被唤醒并且写调用成功,尽管数据可能只被部分写入如果在缓冲只没有空间给被请求的count字节.

若需要在open中实现对O_NONBLOCK的支持,可以返回-EAGAIN

DECLARE_WAIT_QUEUE_HEAD(name);

定义并初始化一个等待队列

init_waitqueue_head(wait_queue_head_t*name);

初始化一个等待队列

wait_event(queue,condition)

wait_event_interruptible(queue,condition)

wait_event_timeout(queue,condition,timeout)

wait_event_interruptible_timeout(queue,condition,timeout)

queue:

是要用的等待队列头.注意它是"通过值"传递的,而不是指针

condition:

需要检查的条件,只有这个条件为真时才会返回.注意条件可能被任意次地求值,因此它不应当有任何边界效应(sideeffects,按我的理解就是当外界条件未改变时,每次计算得到的结果应该相同)

timeout:

超时值.表示要等待的jiffies数,是一个相对时间值.如果这个进程被其他事件唤醒,它返回以jiffies表示的剩余超时值

上述4个函数带interruptible的是可被中断的

voidwake_up(wait_queue_head_t*queue);

voidwake_up_interruptible(wait_queue_head_t*queue);

wake_up唤醒所有的在给定队列上等待的进程.wake_up_interruptible限制只唤醒因wait_event_interruptible/wait_event_interruptible_timeout睡眠的进程

wake_up_nr(wait_queue_head_t*queue,intnr);

wake_up_interruptible_nr(wait_queue_head_t*queue,intnr);

类似wake_up,但它们能够唤醒nr个互斥等待者,而不只是一个.注意:

0被解释为请求所有的互斥等待者都被唤醒

wake_up_all(wait_queue_head_t*queue);

wake_up_interruptible_all(wait_queue_head_t*queue);

唤醒所有的进程,不管它们是否进行互斥等待(尽管可中断的类型仍然跳过在做不可中断等待的进程)

wake_up_interruptible_sync(wait_queue_head_t*queue);

正常地,一个被唤醒的进程可能抢占当前进程,并且在wake_up返回之前被调度到处理器.换句话说,调用wake_up可能不是原子的.如果调用wake_up的进程运行在原子上下文(它可能持有一个自旋锁,例如,或者是一个中断处理),这个重调度不会发生.但是,如果你需要明确要求不要被调度出处理器在那时,你可以使用wake_up_interruptible的"同步"变体.这个函数唤醒其他进程,但不会让新唤醒的进程抢占CPU

6.2.5.1.一个进程如何睡眠

放弃处理器是最后一步,但是要首先做一件事:

你必须先检查你在睡眠的条件.做这个检查失败会引入一个竞争条件;如果在你忙于上面的这个过程并且有其他的线程刚刚试图唤醒你,如果这个条件变为真会发生什么?

你可能错过唤醒并且睡眠超过你预想的时间.因此,在睡眠的代码下面,典型地你会见到下面的代码:

if(!

condition)

schedule();

通过在设置了进程状态后检查我们的条件,我们涵盖了所有的可能的事件进展.如果我们在等待的条件已经在设置进程状态之前到来,我们在这个检查中注意到并且不真正地睡眠.如果之后发生了唤醒,进程被置为可运行的不管是否我们已真正进入睡眠.

如果在if判断之后,schedule之前,有其他进程试图唤醒当前进程,那么当前进程就会被置为可运行的(但可能会到下次调度才会再次运行),所以这个过程是安全的

手动睡眠

DEFINE_WAIT(my_wait);

定义并初始化一个等待队列入口项

init_wait(wait_queue_t*);

初始化等待队列入口项

voidprepare_to_wait(wait_queue_head_t*queue,wait_queue_t*wait,intstate);

添加你的等待队列入口项到队列,并且设置进程状态.

queue:

等待队列头

wait:

等待队列入口项

state:

进程的新状态;TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE

调用prepare_to_wait之后,需再次检查确认需要等待,便可调用schedule释放CPU

在schedule返回之后需要调用下面的函数做一些清理动作

voidfinish_wait(wait_queue_head_t*queue,wait_queue_t*wait);

在此之后需要再次检查是否需要再次等待(条件已经满足还是被其他等待的进程占用?

互斥等待

当一个等待队列入口有WQ_FLAG_EXCLUSEVE标志置位,它被添加到等待队列的尾部.没有这个标志的入口项添加到开始.

wake_up调用在唤醒第一个有WQ_FLAG_EXCLUSIVE标志的进程后停止(即进行互斥等待的进程一次只以顺序的方式唤醒一个).但内核仍然每次唤醒所有的非互斥等待者.

用函数

voidprepare_to_wait_exclusive(wait_queue_head_t*queue,wait_queue_t*wait,intstate);

代替手动睡眠中的prepare_to_wait即可实现互斥等待

老版本的函数

voidsleep_on(wait_queue_head_t*queue);

voidinterruptible_sleep_on(wait_queue_head_t*queue);

这些函数无法避免竞争(在条件判断和sleep_on之间会存在竞争).见上面"6.2.5.1.一个进程如何睡眠"的分析

时间

HZ:

1秒产生的tick.是一个体系依赖的值.内核范围此值通常为100或1000,而对应用程序,此值始终是100

jiffies_64:

从系统开机到现在经过的tick,64bit宽

jiffies:

jiffies_64的低有效位,通常为unsignedlong.编程时需要考虑jiffies隔一段时间会回绕(重新变成0)的问题

u64get_jiffies_64(void);

得到64位的jiffies值

inttime_after(unsignedlonga,unsignedlongb);

若a在b之后,返回真

inttime_before(unsignedlonga,unsignedlongb);

若a在b之前,返回真

inttime_after_eq(unsignedlonga,unsignedlongb);

若a在b之后或a与b相等,返回真

inttime_before_eq(unsignedlonga,unsignedlongb);

若a在b之前或a与b相等,返回真

unsignedlongtimespec_to_jiffies(structtimespec*value);

voidjiffies_to_timespec(unsignedlongjiffies,structtimespec*value);

unsignedlongtimeval_to_jiffies(structtimeval*value);

voidjiffies_to_timeval(unsignedlongjiffies,structtimeval*value);

structtimeval和structtimespec与jiffies之间的转换

rdtsc(low32,high32);

rdtscl(low32);

rdtscll(var64);

readtsc/readtsclow/readtsclonglong:

读取短延时(很容易回绕),高精度的时间值.不是所有平台都支持

cycles_tget_cycles(void);

类似rdtsc,但每个平台都提供.若CPU为提供相应功能则此函数一直返回0

注意:

它们在一个SMP系统中不能跨处理器同步.为保证得到一个一致的值,你应当为查询这个计数器的代码禁止抢占

短延时(很容易回绕),高精度

不是所有平台都支持

unsignedlongmktime(unsignedintyear,unsignedintmon,unsignedintday,unsignedinthour,unsignedintmin,unsignedintsec);

转变一个墙上时钟时间到一个jiffies值

voiddo_gettimeofday(structtimeval*tv);

获取当前时间

structtimespeccurrent_kernel_time(void);

获取当前时间.注意:

返回值是个结构体不是结构体指针

延时

长延时之忙等待(cpu密集型进程会有动态降低的优先级)

while(time_before(jiffies,j1))

cpu_relax();

让出CPU(无法确定什么时候重新得到CPU,即无法确定什么时候循环结束,也即无法保证延时精度)

while(time_before(jiffies,j1)){

schedule();

}

longwait_event_timeout(wait_queue_head_tq,condition,longtimeout);

longwait_event_interruptible_timeout(wait_queue_head_tq,condition,longtimeout);

timeout表示要等待的jiffies数,是相对时间值,不是一个绝对时间值.如果超时到,这些函数返回0;如果这个进程被其他事件唤醒,它返回以jiffies表示的剩余超时值.

set_current_state(TASK_INTERRUPTIBLE);

signedlongschedule_timeout(signedlongtimeout);

不需要等待特定事件的延时

注意:

上述几个函数在超时事件到与被调度之间有一定的延时

短延时之忙等待

voidndelay(unsignedlongnsecs);

voidudelay(unsignedlongusecs);

voidmdelay(unsignedlongmsecs);

这几个函数是体系特定的.每个体系都实现udelay,但是其他的函数可能或者不可能定义

获得的延时至少是要求的值,但可能更多

voidmsleep(unsignedintmillisecs);

unsignedlongmsleep_interruptible(unsignedintmillisecs);

voidssleep(unsignedintseconds)

使调用进程进入睡眠给定的毫秒数.

如果进程被提早唤醒(msleep_interruptible),返回值是剩余的毫秒数

内核定时器

定时器是基于中断的(通常来讲是通过软中断),所以会缺少进程上下文.

定时器

Timer运行在非进程上下文中.通常只能提供tick为单位的精度

structtimer_list{

/*...*/

unsignedlongexpires;

void(*function)(unsignedlong);

unsignedlongdata;

};

expires:

超时时间

function:

时间到时需要回调的函数

data:

function的参数

voidinit_timer(structtimer_list*timer);

初始化一个timer

structtimer_listTIMER_INITIALIZER(_function,_expires,_data);

定义并初始化一个timer

voidadd_timer(structtimer_list*timer);

将timer加入调度.每次这个timer被调度后,就会从调度链表中去掉,所以如果需要反复运行,需要重新add_timer或者用mod_timerintdel_timer(structtimer_list*timer);

将timer从调度链表中去除

intmod_timer(structtimer_list*timer,unsignedlongexpires);

更新一个定时器的超时时间(并将其重新加入到调度链表中去).mod_timer也可代替add_timer

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2