说到linux程序,我们会条件反射地想到用户空间跟内核空间,两者有很多的相似之处,使用到的资源保护,数据同步的方法也类似。如用户空间用open去打开一个文件,内核里面有file_open的操作等。但他们之间还是有需要遵循的规则,如内核空间跟用户空间直接的数据拷贝,需要用到copy_from_user和copy_to_user;这也是为什么我们平时听到的在32位机上,linux进程都可以独立地访问4G的空间,(内核空间3-4G,用户空间0-3G),也是通过这些规则来实现的。
这里说线程轮询,之所以讲到上一段的内容,是因为轮询中会涉及到用户空间跟内核空间的数据交互。
说回轮询,可能好多人会有疑惑,为什么不适用中断,中断的方式占用资源更少、实时性更强、效率更高。其主要的原因有几点:
1、芯片的资源是有限的,也就是中断GPIO有限,有时候我们把中断GPIO用完了,可以用轮询来替代。
2、有些时候我们需要使用轮询来读取某些数据,如串口数据,又话者是通过i2c去读取某一个外部器件的寄存器值。
3、第三点也是最重要的一点,中断是被动的,也就是中断需要外部条件触发,中断条件来了,进入中断函数处理一下就完事了,最多后面还跟着一个schedule。而轮询是我们主动发出的,而且处理逻辑也更为复杂特别是多线程中对临界资源的访问,需要非常注意。
对于线程轮询,个人觉得可以分为三种:
一、 线程轮询阻塞,由外部条件唤醒
对于这种方式,我们常见的是select和poll的方式,常见的方式是读取串口或者系统input类事件的时候使用到,一般的用法是:
readThread{ while(TRUE){ select(•••); //poll(•••); //数据处理 } }
这也是线程常见的处理轮询的方式,线程回调函数进来,进入while循环,然后进行阻塞,让出cpu,由特定的条件来唤醒。对于select和poll也是一样,不过这种方式是被动的,必须有外部数据来唤醒,如果我们想采用主动唤醒线程的方式呢?那么就看第二点。
二、 线程轮询阻塞,由其他线程按照一定的逻辑形式唤醒
线程进入阻塞,是为了让进程进入休眠,一方面不会占用cpu资源;另一方面线程也没有可以处理的任务,无需一直在跑。但线程不能一直处于阻塞的状态,终究要唤醒的,所以第二点是讲一下怎样去主动唤醒阻塞的进程。pthread_cond_wait是用户空间常用的一种主动进入阻塞的方式,这种方式下,阻塞可以通过pthread_cond_signal和pthread_cond_broadcast两个函数来唤醒,这两个函数的具体用法这里不多说,值得注意的是,pthread_cond_wait是需要在一个互斥锁的保护下被检查,才能确保调用pthread_cond_wait的线程被依次的执行,而且在一些资料显示,对pthread_cond_wait有这样的描述:阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。所以总结,pthread_cond_wait的使用方式:
readThread{ while(TRUE){ pthread_mutex_lock(); while (condition_is_false) pthread_cond_wait(); pthread_mutex_unlock(); //数据处理 } }
好,这样就实现了主动阻塞和主动唤醒的目的,这也是进程间通信,数据同步的一种手段。上文说到linux用户空间跟内核空间的编程很相似,对于这种做法,在内核里面也是用同样的方式可以实现的,具体是wait_event_interruptible和wake_up_interruptible的使用,详细不说,用法是大概相似的。也可以使用这种方法来实现内核空间跟用户空间的数据同步。
以上是线程主动进入阻塞,并由其他线程主动唤醒的方式。还有一种方式就是线程主动轮询,并且是每隔一段时间轮询一次,这方面就需要用到系统调度来实现了。情况第三点情况。
三、 线程轮询阻塞,由系统调度唤醒
线程阻塞的方式好多,不过根本原因是调用能引起调度的函数即可,其他的工作就让系统调度来完成。第三点的做法也是一样,看下面的用法:
readThread{ while(TRUE){ sleep(3); //数据处理 } }
最简单的就是使用sleep函数让线程进入休眠。等休眠结束后,线程由“阻塞状态”进入“就绪状态”,期间就需要系统根据不同线程间的nice值来评判运行的优先级。显然这种方式的实时性不高,而且还可能被系统中断打断的可能。不过对于我们而言,这种状态已经足够了。因为现在的系统都是微秒级别,基本差别不到差别。而且对于使用这种方式的用户,也不需要很高的实时性要求。足够我们使用了。
再次说到,内核空间的用法跟用户空间很相似。这种使用方法,在内核也有相应的办法实现的,除了像上面的使用方法外,内核还可以借助schedule的系统调度来完成,这种方式直接使用系统调度,更为简洁。而且对于开一个调度和开一线程,对于内核而言,原理性都是一样的。所以在内核里面的做法,是建议使用schedule的方式,而且有需要的话,还可以使用schedule_work_on和schedule_delayed_owrk_on来指定执行的CPU,资源由我们自动分配。