您的位置:首页 > 其它

设备驱动开发详解总结

2014-01-04 11:52 253 查看
unix高级编程:第10章信号(在看设备驱动开发详解第9章的异步通知与异步i/o时重新阅读的):

1、p268(页码为左上角) 图表示信号可以嵌套

2、signal() ->信号绑定

3、pr_mask() ->屏蔽位打印

4、sigsetjmp/siglongjmp 设置跳转点/具体的跳转指令

5、time()->获取系统时间,单位秒

6、sigemptyset(sigset_t) 清空屏蔽

7、sigaddset()添加屏蔽信号

8、sigprocmask() 设置屏蔽位 根据第一个参数有三种方式 或(SIG_BLOCK)、与、设置 有添加,减少等功能。

//取消阻塞时,未被阻塞的未决信号(之前被阻塞)会被传递给该进程

如果希望函数里面先pause()然后再接受以前被阻塞的信号则需要用sigsuspend()

该函数能够使:恢复屏蔽字再休眠在一个原子操作中进行。这样以前的位决的信号就不会丢失(在pause前触发信号,致使进程pause 里醒不来)。该函数运行结束后会自动恢复之前的屏蔽位(该行代码之前)

9、sigaction() 新的信号绑定函数,为向后兼容,改写signal()函数用sigaction实现,通过设置sigaction结构体(包括处理函数与相关选项),来实现信号处理函数中(添加其它或自己)的屏蔽位,信号处理函数后去除屏蔽位的功能。这导致了使用setjmp siglongjmp的跳转后屏蔽位不一致,所以改写为sigsetjmp/siglongjmp

10、sigpending()获取当前被屏蔽的信号中(如果产生该信号将被阻塞)、已经产生但未决的(或称现在阻塞的)的屏蔽字。

11、sigismember()判断屏蔽为里面是否包含某位。

linux:设备驱动第7章并发控制:

1、原子操作:atomic_set()/stomic_read/atomic_add,sub/stomic_inc/atomic_dec

2、基于上面才有了下面的自旋锁(属于忙等锁,相当于一个while判断的循环,把自己旋在一个测试的地方,会占用cpu,同时造成进程的忙等待,石龙轮询机制):

spin_lock_init/()spin—_lock()/spin_unlock()

自旋锁可能被中断、衍生了

spin_lock_irq()/spin_unlock_irq()/spin_lock_irqsave()-屏蔽中断并保存中断/spin_unlock_irqrestore()开启中断,恢复之前的中断

屏蔽中断的自旋锁可能造成死锁。

3、读写时,读之间可并发,写之间不能并发,所以需要读写锁。(写时不能读,读时看系统实现,可能可以中断来写,可能要等读完后再写)

读锁:rwlock_init()/read_lock()/read_lock_irqsave()/read_lock_irq()/read_lock_bn()-pspin_lock_irq()屏蔽底部中断,不屏蔽顶部中断 解锁:加unlock

写锁:write_lock()/write_lock_irqsave/write_lock_irq()/write_lock_bh/write_trylock() 解锁:加unlock

4、顺序锁 write_seqlock()/write_tryseqlock()/write_seqlock_irqsave()/write_seqlock_irq()/write_seqlock_bh

读操作read_seqbegin()/read_seqbegin_irqsave()/read_seqretry()/read_seqretry_irqrestore

使用写方式:write_seqlock();...写相关代码;write_sequnlock()

读使用方式do{read_seqbegin()}while(read_seqretry())

//作用(读写锁的一次改进):读执行单元不会被写单元堵塞,写单元不必等待杜执行单元完才进行写操作,通过重读来确保数据的完整性。

5、读-拷贝-更新(顺序锁的更高级版本)

读锁定与解锁:rcu_read_lock()/rcu_read_lock_bh()/rcu_read_unlock()/rcu_read_unlock_bh()/

读使用模式:rcu_read_lock();....写代码相关;rcu_read_unlock()

写使用模式:list_add_rcu()/list_add_tail_rcu()/list_del_rcu()/list_replace_rcu()/list_for_each_safe_rcu()

/list_for_each_entry_rcu()/hlist_del_rcu()/hlist_for_each_rcu/hlist_for_each_entry_rcu()

其中的删除与替换函数(del,replace)后面要添加挂接回调call_rcu()阻塞写操作知道所有读操作完成。

6、信号量,semaphore与锁的区别是,获取不到信号量,进程不会原地打转(锁机制会忙等待,占用cpu),而是会进入休眠等待状态不占用CPU

初始化:sema_init() 获取:down()/down_trylock()/down_interruptible() 释放:up()

使用模板:sema_init(); down();...临界区;up();

作用:可用于同步(确定先后执行顺序);

7、同步量:completion //应用:比如电梯来了,可以一次有限个同时进入,相当于事件栅栏,详细与综合应用详见:example

初始化:init_completion() 等待完成量:wait_for_completion() 唤醒完成量:complete()-唤醒一个/complete_all()--唤醒全部

8、读写信号量: //相当于睡眠机制的读写锁

作用:允许N个读进程单元同时访问共享资源,最多只有一个写执行单元。

初始化:init_rwsem()

读:down_read()/down_read_trylock() 释放:up_read()

写:down_write()/down_write_trylock() 释放:up_write()

模板:

init_rwsem();down_read();...临界区;up_read();down_write();...临界区;up_write()

9、互斥体 mutex: //完全可以通过信号量实现,也是睡眠机制,实现对临界区的互斥访问。

初始化:mutex_init() 获取:mutex_lock()/mutex_lock_interruptible()/mutex_trylock() 释放:mutex_unlock()/

模板:

mutex_init();mutex_lock();...临界区;mutex_unlock();

调度器、软中断、工作队列、tasklet等的硬件基础及依赖关系:这个可以通过单片机的datasheet了解到相关硬件。

硬件基础:硬件定时器、中断控制器、晶振(提供系统时钟)等。

关于调度:

是通过一个守护进程来调度,包括tasklet,工作队列,等待队列,完成量等。

调度器 等待队列(等待队列及其衍生机制指(等待队列,完成量,工作队列)) 进程基于工作队列,信号量则是基于等待队列,互斥量直接使用锁

(1)工作队列->中断、时钟->定时器(软件)->闹钟、

->信号量

->sleep,阻塞等

工作队列里面调用schedule()函数进行进程的切换。schedule+set_current_state(进程切换)

linux的工作队列初始化:kernel_init()->do_basic_setup()->init_workqueues() 主要是创建工作队列与工作者线程。通常使用下列两种方式来提交

工作项给工作者线程:schedule_work(&work) 一旦其所在的处理器工作者线程被唤醒,该work就会被执行。

schedule_delayed_work(&delay_work,delay) delay_work在指定时钟结拍后才会执行。

默认工作队列名字:keventd_wq 默认工作者线程:events/n (多处理器则有多个工作者线程events/0 events/1 等)

什么情况下要自己写工作队列:通常处理密集型和性能要求严格的任务时,因为默认工作者队列只有一个,而工作者线程在每个CPU上面都有 参考:http://www.linuxidc.com/Linux/2012-01/51726.htm

上面的定时器(软件)可以参考:http://blog.chinaunix.net/uid-24517893-id-120248.html

在linux中,软件定时器最终会依赖硬件定时器来实现。内核在时钟中断发生后检测各定时器是否到期,到期的定时器处理函数会作为软中断在底部执行。具体的中断底部、底部的驱动编写方式详见设备驱动开发详解相关章节

(2)软中断softirq(与一般说的不同)->信号->异步操作。

->中断

软中断的初始化:start_kernel()->_init_softirq_init()

然后软中断号TASKLET_SOFTIRQ的处理函数是tasklet_action 软中断号HI_SOFTIRQ的处理函数是tasklet_hi_action

软中断在以下情况下执行:进程切换、系统调用、进程被调度到低级别的队列去、硬件中断等等之后得到处理。

处理之前,与硬件中断一样先得到一个中断号,(软件中断信号由系统软件产生)。

软件信号产生(比如系统的时钟中断):(1)将软中断信号处理函数连入tasklet_hi_vec或者tasklet_vec中,调用__cpu_raise_softirq产生软件信号。

(2)do_softirq->tasklet_hi_action->bh_task_vec_bh_action->bh_base,工作有bh_base指向的函数完成,

目前就init_bh(TIMER_BH,timer_bh),init_bh(TQUEUE_BH,tqueue_bh),init_bh(IMMEDIATE_BH,immediate_bh);

所以定时器的最终bottom half是timer_bh

软中断的触发:raise_softirq函数相当于信号里面的(线程、进程本身)产生信号操作。

软中断由谁来执行:函数do_softirq()负责执行数组softirq_vec[32]中设置的软中断服务函数。每个CPU都是通过执行这个函数来执行软中断服务的。

参考:http://blog.chinaunix.net/uid-145571-id-2798845.html 搜索:结合上述源码,我们可以看出软中断服务的执行过程如下:

(3)tasklet 这个是基于软中断,同样有tasklet_schedule()这个函数,用于调度。

关于tasklet/软中断与工作队列选择:

推后执行的任务需要休眠/延迟指定的时间再触发/对延迟时间没有任何要求->工作队列 本质:使用一个内核线程处理,可以使用内核线程替换。运行在进程上下文,但不能访问用户空间,在系统调用时,内核会代表用户空间的进程执行,此时此时它才能访问用户空间,此时它才会映射用户空间的内存。

需要在一个tick之内处理,tasklet/软中断,其可以抢占普通进程与内核线程->tasklet/软中断

系统只有多个不同状态的进程队列,通过set_current_state()、schedule()插入与删除。上面说的队列是用户维护的队列,用于用户进行

队列非空。是否有进程在等待等的判断操作。add_wait_queue()/remove_wait_queue()显然只是指针的保存,具体进程现场由系统实现。

驱动开发详解第8章 :

8.1阻塞与非阻塞io:

使用等待队列实现。

初始化,添加,移除:init_waitqueue_head()/add_wait_queue()/remove_wait_queue()/

事件等待:wait_event()/wait_event_interruptible()/wait_event_timeout()/wait_event_interruptible_timeout()

唤醒:wake_up()/wake_up_interruptible()

无事件等待:sleep_on()/interruptible_sleep_on()

8.2轮询:

select()/poll()用于判断是否可以对设备进行无阻塞的访问。两者一样。 最终都是调用驱动中的poll函数。poll函数则用于获取设备资源的

可获取状态。如果无法获得资源可以不再获得,也可以通过轮询的方式进行重复查询。

select(timeout),在等待timeout后如果没有文件描述准备好则返回。

描述符集合操作: FD_ZERO()/FD_SET()/FD_CLR()/FD_ISSET()

(驱动中的poll本身不会阻塞,系统调用中的poll,select则会阻塞的等待文件描述符至少一个可用或者超时)

驱动开发详解第9章:异步通知与异步io

中断是有硬件基础。

信号是在软件层上对中断机制的一种模拟(unix的时候叫做软中断),原理上,一个进程收到一个信号与处理器收到一个中断请求是一样的。使用硬件上面的中断,执行

相应的中断服务程序,检查进程是否有信号,有则处理,没有则不处理。也就是主动检查是否有消息。硬件上面的中断则是管脚或其它的接受

到数字信号,cpu执行相应的中断服务程序。 每个进程有一个信号队列。根据进程相应的屏蔽位进行相应的操作。系统还有一个发送信号队列

,用于待发送的信号的挂接。处理信号是具体的进程内部中断,获得本进程信号,处理本进程信号,结束。参考:

http://blog.chinaunix.net/uid-127037-id-2919544.html

9.1异步通知:

用户进程执行下列命令设置可以接收信号:

1、F_SETOWNIO控制命令设置设备文件的拥有者为本进程

2、F_SETFLIO控制命令设置设备文件支持FASYNC,即异步通知模式

3、通过signal()函数连接信号和信号处理函数。 具体的屏蔽等操作详见unix高级编程的第10章:信号

驱动必须支持一下内容:

1、支持F_SETOWN命令

2、支持F_SETFL命令的处理,该命令最终调用fasync()函数,所以驱动必须实现该函数。调用fasync_helper()函数实现标志改变

3、在设备资源可获得时,调用kill_fasync()函数激发相应的信号。

其中的可获得:

比如别人进行了读操作,那么在读操作后可以调用kill_fasync(POLL_IN)函数产生异步读信号。通知资源可以被读。

9.2异步I/O

GNU C中:aio_read()/aio_write()/aio_error()/aio_return()/aio_sspend()/aio_cancel()/lio_listio() 函数执行不会停在这个地方。可以通过轮询判断是否执行完成。

可以使用信号作为aio的异步通知。 也就是说有异步操作完成时使用信号通知本进程,然后执行信号处理函数。使异步io可以通过睡眠,唤醒进程实现,也可以使用上面的轮询。

异步io的实现方式:

使用信号:示例代码p188

使用回调函数(callback):示例代码:p189

9.4aio与设备驱动:内核中的每个io请求用kiocb表示。

其中的信号通过 softirq实现

其中的回调函数天则通过工作队列实现

以上三章内容的实现基于第10章的中断、时钟:下次有空再总结一下

百度云网盘:linux-时钟-工作队列/linux工作队列_定时器总结.doc

硬中断、软中断、信号、进程状态转化、CPU模式等的一些补充:

1、操作系统的硬件基础:

http://wenku.baidu.com/link?url=fE-hLM-83DdwHgrzGAIlvqJYsJnBQkA3lURyxDdQeEYhZptGZhCP11yAa5spEvxQ7isNUhhWjtJiWrlRxVlRuNQepCWal5DQ0exjguZzoru

2、cpu不同模式(正常模式、管理模式)的转换:

user模式->kernel模式 方法:中断

kernel模式->user模式 方法:设置PSW

3、中断:cpu暂停正在执行的程序,保留现场后转区执行相应

事件的处理程序,处理完成后返回断点继续执行被中断的程序。

中断目的:与外设并行工作问题,实时控制

特点:随机,可恢复,自动处理

4、中断与异常的关系:

|` 中断(外中断):1、i/o中断 2、时钟中断

广义中断:|

|_ 例外、异常(内中断):1、系统调用、缺页异常、断点指令、其它程序性异常(算术溢出)

区别:与正执行指令无关,可以屏蔽,与正执行指令有关,不可屏蔽

5、中断系统组成:1、硬件中断装置、2、软件中断处理程序

5.1硬件中断装置-机制部分:

负责捕获中断源发出的中断请求,以一定的方式相应中断源,然后将控制器的控制权交给特定的中断处理程序。

5.2软件中断处理程序-策略部分:

负责辨别中断类型并作出相应的操作。(一般可以设定系统的中断间隔,越小,实时性越高,因为)

6、硬终端与软中断的例子:

硬中断:键盘按下、时钟(硬件基础:定时器)、等都会产生一个硬件中断输出信号给中断控制器。

unix时软中断(另称信号):比如kill进程命令,解释如下详见:http://blog.chinaunix.net/uid-609765-id-2087511.html/article/4889013.html

7、软中断(信号)的发送与处理时机

发送信号:发送进程把信号送到指定进程的信号域的某一位上。依靠系统调用来设置进程的信号域

对信号的处理时机:进程检查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时; 或者,在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。

注意:操作系统给的周期性(linux为时间片) 调度以及操作系统的优先级调度。所以进程的切换非常的快速。零点几毫秒就进行了多次进程上下文切换。当然也可以使用sleep小段时间来进行信号处理。或者执行pause()。或者执行了系统调用printf也是系统调用。(离开或者进入低优先级队列睡眠(不知道为什么是睡眠)时)

进程状态转换:http://ibicdi.blog.163.com/blog/static/19449410820124276286388/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: