一.线程/进程
进程:资源管理的最小单位
线程:执行任务的最小单位
内核线程:内核空间创建,类似用户进程,但运行期间不能被抢断
用户线程:用户空间创建,拥有进程上下文,可调度可睡眠
二.内核线程的同步原因
@中断:几乎可以在任何时刻发生,随时打断当前执行的代码;
@睡眠和与用户空间的同步:内核线程的睡眠,会唤醒调度程序,从而导致新用户进程执行;
@对称对处理:多个cpu可以同时执行代码;
@内核抢断:2.6以后,内核具有抢占性,内核中的任务可以被另一任务抢占;
三.工作队列和tasklet的选择
需要睡眠的任务—wq
需要延时的任务—wq
需要在1个tick内处理的任务—tasklet
对执行时间没有需求的任务—wq
工作队列的本质就是将任务交给内核线程处理,它实现了内核线程的封装。
一个工作队列对应拥有n个(cpu个数)内核线程,每个线程对应运行在单独的cpu上。
一个tasklet只能运行在一个cpu上,不同的tasklet可以运行在不同的cpu上。
四.信号量和自旋锁的选择
低开销加锁—spinlock优先
短期加锁—spinlock优先
长期加锁---mutex/semp
中断上下文加锁—spinlock
持有锁者需要调度/睡眠—mutex/semp
五.利用其它机制来保证非重入
1.利用电源管理机制来实现操作的不可重入
2.原子操作;
电源管理休眠/唤醒流程:
涉及到的文件:
linux_source/kernel/power/main.c
linux_source/kernel/power/earlysuspend.c
linux_source/kernel/power/wakelock.c
1. 用户空间写入mem或者standby到SYSFS节点/sys/power/state,kernel调用state_store(),进入以后会调用request_suspend_state(),紧接着early_suspend_work会被调用,进入early_suspend状态;
2. 在early_suspend()里面,检查现在的请求状态是否仍然还是suspend状态,防止用户取消suspend请求;如果还是suspend状态,就会把各个驱动注册的early_suspend回调函数执行一遍,然后sys_sync(),在此时,用户进程还在运行;最后释放main_wake_lock,准备进入Linux kernel的suspend;
3. 当进入到suspend_prepare()中以后,它会给suspend分配一个虚拟终端来输出信息,然后广播一个系统要进入suspend的Notify,关闭掉用户态的helper进程,然后一次调用suspend_freeze_processes()冻结所有的进程,这里会保存所有进程当前的状态,也许有一些进程会拒绝进入冻结状态,当有这样的进程存在的时候,会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程。
4. 现在,所有的进程(也包括workqueue/kthread)都已经停止了,内核态任务有可能在停止的时候握有一些信号量,所以如果这时候在外设里面去解锁这个信号量有可能会发生死锁,所以在外设的suspend()函数里面作lock/unlock锁要非常小心,这里建议设计的时候就不要在suspend()里面等待锁。最后会调用suspend_devices_and_enter()来把所有的外设休眠,在这个函数中,如果平台注册了suspend_pos(通常是在板级定义中定义和注册),这里就会调用suspend_ops->begin(),然后driver/base/power/main.c中的 device_suspend()->dpm_suspend()会被调用,他们会依次调用驱动的suspend() 回调来休眠掉所有的设备。当所有的设备休眠以后,suspend_ops->prepare()会被调用,这个函数通常会作一些准备工作来让板机进入休眠。
5. 接下来Linux,在多核的CPU中的非启动CPU会被关掉,通过注释看到是避免这些其他的CPU造成race condion,接下来的以后只有一个CPU在运行了。suspend_ops是板级的电源管理操作,通常注册在文件 arch/xxx/mach-xxx/pm.c中。接下来,suspend_enter()会被调用,这个函数会关闭arch irq,调用 device_power_down(),它会调用suspend_late()函数,这个函数是系统真正进入休眠最后调用的函数,通常会在这个函数中作最后的检查。如果检查没问题,接下来休眠所有的系统设备和总线,并且调用 suspend_pos->enter() 来使CPU进入省电状态。这时候,就已经休眠了,代码的执行也就停在这里了。
2.为什么在sensor的各个操作函数里面增加mutex保护?
Ans:MTK的sensor驱动架构设计是使用hwm来实现的,所有的sensor驱动是一个子模块,挂载在hwmsen这个虚拟的input设备上,在enable sensor之后,它启动一个timer,在timer的回调里面使用work队列来读取sensordata。enable/disable/setdelay和read sensordata等各个功能调用在不同的线程上执行,具体sensor的sensor_operaton()实现会在不同的线程被同时调用。
另外,如问题一所述,在suspend/resume执行之前,所有的进程已经被冻结,所以suspend/resume操作不必考虑重入问题。
相关的一些知识点:
1. spinlock只能有一个所有者,semaphore可以有多个所有者;
2. spinlock获取到之前,会一直处于忙等待状态,semaphore则会排队等待,进入睡眠;
3. 同一个tasklet/timer不会在2个cpu上运行,即不会重入;
4. 不同的tasklet/timer如果要共享数据,必须使用spinlock来避免重入;
5. 在中断之间共享数据的话,必须使用spin_lock_irq()来做保护;
6. 不要在中断和用户上下文里使用同一个spinlock;
7. 不要在回调/hook函数里面加锁;
8. 不要在中断/持有spinlock的时候调用运行休眠的函数;
9. 不能在不同线程里释放当前线程获取的mutex,即获取与释放必须配对;
10. 在MP(多核)环境下,work队列是可以重入的;
11. kernel3.0以后,中断线程化以后,中断也是可以重入的;
12. suspend/resume;early_suspend/late_resume; wakelock概念理解
13. HZ/tick/jiffies 概念
14. 和应用(java)层有关的一些东西:Timer和TimerTask在系统休眠以后会停掉,若需要可以使用系统的AlarmService来执行轮询(功耗);Socket长连接系统休眠以后会断开,如需要可以使用PARTIAL_WAKE_LOCK(功耗);插着usb线调试时候,系统不会进入休眠状态。
15. 一个出了问题的进程是不会影响其它的进程的,因为它们分别在不同的进程空间进行自己的操作。同一个进程中的某个线程的故障可以影响其它的线程,因为所有的线程共享同一个虚拟内存空间以及其他资源。