ImageVerifierCode 换一换
格式:DOCX , 页数:18 ,大小:145.95KB ,
资源ID:1729553      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bingdoc.com/d-1729553.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(linux设备驱动读书笔记 3.docx)为本站会员(b****2)主动上传,冰点文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰点文库(发送邮件至service@bingdoc.com或直接QQ联系客服),我们立即给予删除!

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

1、linux设备驱动读书笔记 3linux设备驱动读书笔记(3)转2009年08月30日 星期日 13:26原子变量: atomic_t. 所有原子量操作必须使用如下函数atomic_t v = ATOMIC_INIT(0): 声明并初始化atomic变量void atomic_set(atomic_t *v, int i): 设置值int atomic_read(atomic_t *v): 读值void atomic_add(int i, atomic_t *v): 值加ivoid atomic_sub(int i, atomic_t *v): 值减ivoid atomic_inc(atomi

2、c_t *v): 值+1void atomic_dec(atomic_t *v): 值-1int atomic_inc_and_test(atomic_t *v): 值+1, 并测试值是否为0, 若为0返回真, 否则返回假int atomic_dec_and_test(atomic_t *v): 值-1, 并测试值是否为0, 若为0返回真, 否则返回假int atomic_sub_and_test(int i, atomic_t *v): 值-i, 并测试值是否为0, 若为0返回真, 否则返回假int atomic_add_negative(int i, atomic_t *v): 值+i,

3、并测试值是否为负, 若为负返回真, 否则返回假int atomic_add_return(int i, atomic_t *v): 值+i, 并返回值int atomic_sub_return(int i, atomic_t *v): 值-i, 并返回值int atomic_inc_return(atomic_t *v): 值+1, 并返回值int atomic_dec_return(atomic_t *v): 值-1, 并返回值位操作: 位操作依赖于体系结构. nr 参数(描述要操作哪个位)常常定义为 int, 也可能是 unsigned long; 要修改的地址常常是一个 unsigned

4、 long 指针, 但是几个体系使用 void * 代替. void set_bit(nr, void *addr): 在 addr 指向的数据项中将第 nr 位设置为1void clear_bit(nr, void *addr): 在 addr 指向的数据项中将第 nr 位设置为0void change_bit(nr, void *addr): 对 addr 指向的数据项中的第 nr 位取反test_bit(nr, void *addr): 返回 addr 指向的数据项中的第 nr 位int test_and_set_bit(nr, void *addr): 在 addr 指向的数据项中将第

5、 nr 位设置为1, 并返回设置前的值int test_and_clear_bit(nr, void *addr): 在 addr 指向的数据项中将第 nr 位设置为0, 并返回设置前的值int test_and_change_bit(nr, void *addr): 对 addr 指向的数据项中的第 nr 位取反, 并返回取反前的值Mark: 大部分现代代码不使用位操作,而是使用自选锁seqlock: 2.6内核提供的, seqlock_t lock1 = SEQLOCK_UNLOCKED;seqlock_t lock2;seqlock_init(&lock2);读存取通过在进入临界区入口获

6、取一个(无符号的)整数序列来工作. 在退出时, 那个序列值与当前值比较; 如果不匹配, 读存取必须重试. 结果是, 读者代码象下面的形式:unsigned int seq;do seq = read_seqbegin(&the_lock);/* Do what you need to do */ while read_seqretry(&the_lock, seq);这个类型的锁常常用在保护某种简单计算, 需要多个一致的值. 如果这个计算最后的测试表明发生了一个并发的写, 结果被简单地丢弃并且重新计算.如果你的 seqlock 可能从一个中断处理里存取, 你应当使用 IRQ 安全的版本来代替:

7、unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);写者必须获取一个排他锁来进入由一个 seqlock 保护的临界区. 为此, 调用:void write_seqlock(seqlock_t *lock); 写锁由一个自旋锁实现, 因此所有的通常的限制都适用. 调用:void write_sequnlock(seqlock_t *lo

8、ck); 来释放锁. 因为自旋锁用来控制写存取, 所有通常的变体都可用:void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);void write_seqlock_irq(seqlock_t *lock);void write_seqlock_bh(seqlock_t *lock);void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);void write_sequnlock_irq(seqlock_t *lock);void wri

9、te_sequnlock_bh(seqlock_t *lock);还有一个 write_tryseqlock 在它能够获得锁时返回非零.读-拷贝-更新(Read-Copy-Update)针对很多读但是较少写的情况. 所有操作通过指针.读: 直接操作写: 将数据读出来, 更新数据, 将原指针指向新数据(这一步需要另外做原子操作的保护)作为在真实世界中使用 RCU 的例子, 考虑一下网络路由表. 每个外出的报文需要请求检查路由表来决定应当使用哪个接口. 这个检查是快速的, 并且, 一旦内核发现了目标接口, 它不再需要路由表入口项. RCU 允许路由查找在没有锁的情况下进行, 具有相当多的性能好处.

10、 内核中的 Startmode 无线 IP 驱动也使用 RCU 来跟踪它的设备列表.在读这一边, 使用一个 RCU-保护的数据结构的代码应当用 rcu_read_lock 和 rcu_read_unlock 调用将它的引用包含起来. RCU 代码往往是象这样:struct my_stuff *stuff; rcu_read_lock();stuff = find_the_stuff(args.);do_something_with(stuff);rcu_read_unlock();rcu_read_lock 调用是很快的: 它禁止内核抢占但是没有等待任何东西. 在读锁被持有时执行的代码必须是原

11、子的. 在对 rcu_read_unlock 调用后, 没有使用对被保护的资源的引用.需要改变被保护的结构的代码必须进行几个步骤: 分配一个新结构, 如果需要就从旧的拷贝数据; 接着替换读代码所看到的指针; 释放旧版本数据(内存). 在其他处理器上运行的代码可能仍然有对旧数据的一个引用, 因此不能立刻释放. 相反, 写代码必须等待直到它知道没有这样的引用存在了. 因为所有持有对这个数据结构引用的代码必须(规则规定)是原子的, 我们知道一旦系统中的每个处理器已经被调度了至少一次, 所有的引用必须消失. 这就是 RCU 所做的; 它留下了一个等待直到所有处理器已经调度的回调; 那个回调接下来被运行

12、来进行清理工作.改变一个 RCU-保护的数据结构的代码必须通过分配一个 struct rcu_head 来获得它的清理回调, 尽管不需要以任何方式初始化这个结构. 通常, 那个结构被简单地嵌入在 RCU 所保护的大的资源里面. 在改变资源完成后, 应当调用:void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);给定的func在安全的时候调用来释放资源全部 RCU 接口比我们已见的要更加复杂; 它包括, 例如, 辅助函数来使用被保护的链表. 详细内容见相关的头文件阻塞 I/O运行在原子上下文时不能睡眠.

13、持有一个自旋锁, seqlock, RCU 锁或中断已关闭时不能睡眠. 但在持有一个旗标时睡眠是合法的不能对醒后的系统状态做任何的假设, 并且必须检查来确保你在等待的条件已经满足确保有其他进程会做唤醒动作明确的非阻塞 I/O 由 filp-f_flags 中的 O_NONBLOCK/O_NDELAY 标志来指示. 只有 read, write, 和 open 文件操作受到非阻塞标志影响下列情况下应该实现阻塞如果一个进程调用 read 但是没有数据可用(尚未), 这个进程必须阻塞. 这个进程在有数据达到时被立刻唤醒, 并且那个数据被返回给调用者, 即便小于在给方法的 count 参数中请求的数量

14、.如果一个进程调用 write 并且在缓冲中没有空间, 这个进程必须阻塞, 并且它必须在一个与用作 read 的不同的等待队列中. 当一些数据被写入硬件设备, 并且在输出缓冲中的空间变空闲, 这个进程被唤醒并且写调用成功, 尽管数据可能只被部分写入如果在缓冲只没有空间给被请求的 count 字节.若需要在open中实现对O_NONBLOCK的支持,可以返回-EAGAINDECLARE_WAIT_QUEUE_HEAD(name);定义并初始化一个等待队列init_waitqueue_head(wait_queue_head_t *name);初始化一个等待队列wait_event(queue,

15、condition)wait_event_interruptible(queue, condition)wait_event_timeout(queue, condition, timeout)wait_event_interruptible_timeout(queue, condition, timeout)queue: 是要用的等待队列头. 注意它是通过值传递的, 而不是指针condition: 需要检查的条件, 只有这个条件为真时才会返回. 注意条件可能被任意次地求值, 因此它不应当有任何边界效应(side effects, 按我的理解就是当外界条件未改变时,每次计算得到的结果应该相同)

16、timeout: 超时值. 表示要等待的 jiffies 数, 是一个相对时间值. 如果这个进程被其他事件唤醒, 它返回以 jiffies 表示的剩余超时值上述4个函数带interruptible的是可被中断的void wake_up(wait_queue_head_t *queue);void wake_up_interruptible(wait_queue_head_t *queue);wake_up 唤醒所有的在给定队列上等待的进程. wake_up_interruptible限制只唤醒因wait_event_interruptible/wait_event_interruptible_

17、timeout睡眠的进程wake_up_nr(wait_queue_head_t *queue, int nr); wake_up_interruptible_nr(wait_queue_head_t *queue, int nr); 类似 wake_up, 但它们能够唤醒nr个互斥等待者, 而不只是一个. 注意: 0被解释为请求所有的互斥等待者都被唤醒wake_up_all(wait_queue_head_t *queue); wake_up_interruptible_all(wait_queue_head_t *queue); 唤醒所有的进程, 不管它们是否进行互斥等待(尽管可中断的类型

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

19、.1. 一个进程如何睡眠放弃处理器是最后一步, 但是要首先做一件事: 你必须先检查你在睡眠的条件. 做这个检查失败会引入一个竞争条件; 如果在你忙于上面的这个过程并且有其他的线程刚刚试图唤醒你, 如果这个条件变为真会发生什么? 你可能错过唤醒并且睡眠超过你预想的时间. 因此, 在睡眠的代码下面, 典型地你会见到下面的代码:if (!condition)schedule();通过在设置了进程状态后检查我们的条件, 我们涵盖了所有的可能的事件进展. 如果我们在等待的条件已经在设置进程状态之前到来, 我们在这个检查中注意到并且不真正地睡眠. 如果之后发生了唤醒, 进程被置为可运行的不管是否我们已真正

20、进入睡眠.如果在if判断之后,schedule之前,有其他进程试图唤醒当前进程, 那么当前进程就会被置为可运行的(但可能会到下次调度才会再次运行), 所以这个过程是安全的手动睡眠DEFINE_WAIT(my_wait);定义并初始化一个等待队列入口项init_wait(wait_queue_t*);初始化等待队列入口项void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state); 添加你的等待队列入口项到队列, 并且设置进程状态. queue: 等待队列头wait: 等待队列入口项state: 进程的

21、新状态; TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE调用prepare_to_wait之后, 需再次检查确认需要等待, 便可调用schedule释放CPU在schedule返回之后需要调用下面的函数做一些清理动作void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait); 在此之后需要再次检查是否需要再次等待(条件已经满足还是被其他等待的进程占用?)互斥等待当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项添加到开始.wake

22、_up调用在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止(即进行互斥等待的进程一次只以顺序的方式唤醒一个). 但内核仍然每次唤醒所有的非互斥等待者.用函数void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);代替手动睡眠中的prepare_to_wait即可实现互斥等待老版本的函数void sleep_on(wait_queue_head_t *queue);void interruptible_sleep_on(wait_queue_head_t *

23、queue);这些函数无法避免竞争(在条件判断和sleep_on之间会存在竞争). 见上面6.2.5.1. 一个进程如何睡眠的分析时间HZ: 1秒产生的tick. 是一个体系依赖的值. 内核范围此值通常为100或1000, 而对应用程序,此值始终是100jiffies_64: 从系统开机到现在经过的tick, 64bit宽jiffies: jiffies_64的低有效位, 通常为unsigned long. 编程时需要考虑jiffies隔一段时间会回绕(重新变成0)的问题u64 get_jiffies_64(void);得到64位的jiffies值int time_after(unsigned

24、 long a, unsigned long b);若a在b之后,返回真int time_before(unsigned long a, unsigned long b);若a在b之前,返回真int time_after_eq(unsigned long a, unsigned long b);若a在b之后或a与b相等,返回真int time_before_eq(unsigned long a, unsigned long b);若a在b之前或a与b相等,返回真unsigned long timespec_to_jiffies(struct timespec *value);void jiff

25、ies_to_timespec(unsigned long jiffies, struct timespec *value);unsigned long timeval_to_jiffies(struct timeval *value);void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);struct timeval 和 struct timespec 与 jiffies 之间的转换rdtsc(low32,high32);rdtscl(low32);rdtscll(var64);read tsc/read

26、tsc low/read tsc long long: 读取短延时(很容易回绕), 高精度的时间值. 不是所有平台都支持cycles_t get_cycles(void); 类似rdtsc, 但每个平台都提供. 若CPU为提供相应功能则此函数一直返回0注意:它们在一个 SMP 系统中不能跨处理器同步. 为保证得到一个一致的值, 你应当为查询这个计数器的代码禁止抢占短延时(很容易回绕), 高精度不是所有平台都支持unsigned long mktime (unsigned int year, unsigned int mon, unsigned int day, unsigned int hou

27、r, unsigned int min, unsigned int sec); 转变一个墙上时钟时间到一个 jiffies 值void do_gettimeofday(struct timeval *tv);获取当前时间struct timespec current_kernel_time(void);获取当前时间. 注意: 返回值是个结构体不是结构体指针延时长延时之忙等待(cpu密集型进程会有动态降低的优先级)while (time_before(jiffies, j1)cpu_relax();让出CPU(无法确定什么时候重新得到CPU, 即无法确定什么时候循环结束, 也即无法保证延时精度)

28、while (time_before(jiffies, j1) schedule();long wait_event_timeout(wait_queue_head_t q, condition, long timeout);long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);timeout表示要等待的 jiffies 数, 是相对时间值, 不是一个绝对时间值. 如果超时到, 这些函数返回 0; 如果这个进程被其他事件唤醒, 它返回以 jiffies 表示的剩余超时值. set_

29、current_state(TASK_INTERRUPTIBLE);signed long schedule_timeout(signed long timeout);不需要等待特定事件的延时注意: 上述几个函数在超时事件到与被调度之间有一定的延时短延时之忙等待void ndelay(unsigned long nsecs);void udelay(unsigned long usecs);void mdelay(unsigned long msecs);这几个函数是体系特定的. 每个体系都实现 udelay, 但是其他的函数可能或者不可能定义获得的延时至少是要求的值, 但可能更多void m

30、sleep(unsigned int millisecs);unsigned long msleep_interruptible(unsigned int millisecs);void ssleep(unsigned int seconds)使调用进程进入睡眠给定的毫秒数. 如果进程被提早唤醒(msleep_interruptible), 返回值是剩余的毫秒数内核定时器定时器是基于中断的(通常来讲是通过软中断), 所以会缺少进程上下文. 定时器Timer运行在非进程上下文中. 通常只能提供tick为单位的精度struct timer_list /* . */ unsigned long ex

31、pires; void (*function)(unsigned long); unsigned long data; ;expires: 超时时间function: 时间到时需要回调的函数data: function的参数void init_timer(struct timer_list *timer);初始化一个timerstruct timer_list TIMER_INITIALIZER(_function, _expires, _data);定义并初始化一个timervoid add_timer(struct timer_list * timer);将timer加入调度. 每次这个timer被调度后, 就会从调度链表中去掉, 所以如果需要反复运行,需要重新add_timer或者用mod_timerint del_timer(struct timer_list * timer); 将timer从调度链表中去除int mod_timer(struct timer_list *timer, unsigned long expires); 更新一个定时器的超时时间(并将其重新加入到调度链表中去). mod_timer也可代替add_timer

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

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