在LINUX RS485的使用过程中,由于各种原因,最后不得不使用中断底半部机制的方法来进行实现此功能。先讲两个小故事来描述一下,遇到的问题。也是因为自己对底半部机制理解得不透彻。这些故事的前提都是在串口中断中,一定条件后去完成某件事情,但时间上不能超过5ms。

 故事一,最开始想到的是用workqueue。印象中workqueue 就是用来做这种事的,并且还记得可以延时一段时间再来做。




点击(此处)折叠或打开


  1. INIT_WORK(&my_wq,(void (*) (void*))my_wq_func);
  2. schedule_work(&my_wq);
  3. //schedule_delayed_work(&my_wq,delay);


最终实现的结果是,my_wq_func 的执行是在中断响应后,但响应时间不确定。短的时候是1毫秒以内,长得的时候出现过几十个毫秒。这样就达不到我们的要求。为什么出现这种时间不确定的问题呢?等故事讲完再一起分析。schedule_delayed_work 延时执行的时间为最小一个jiffies,显然不能用在我们这种情况,我们要求小于5ms。


故事二,工作队列不行后,感觉底半部机制就实现不了,满足不了我们的要求。上网翻了一些资料,觉得任务队列时效性应该比工作队列更好。就像买药的做广告一样,抱着试一试的态度尝试了一下。



点击(此处)折叠或打开


  1. tasklet_init(&my_task0,my_wq_func,(unsigned long) my_wq_arg);
  2. tasklet_schedule(&my_task0);
  3. tasklet_hi_schedule(&my_task0);

最终这种方法实现了。但过程也是相当曲折。tasklet_schedule时效性可以达到,hi_schedule 更是完美,感觉会牺牲系统性能。那么过程曲折在哪呢?刚开始以为搞好了,回家睡大觉,等我九点半到家,同事打电话说不行,出问题了。单个串口没有问题,多个串口同时用的时候,前面打开的串口对应的rs485 都不能正常使用。GPIO拉高后,就不低。而my_wq_func就是实现GPIO拉低的动作。最后的原因是my_wq_func被多次调用,而其只响应最后一次。这个地方还得感谢这位老兄,tasklet 是一个特殊的函数, 它在软中断上下文被调度。它可能被调度运行多次,但是tasklet调度不累积,也就是即使在tasklet被执行之前请求了多次来执行该tasklet,它也只运行一次。不会有同一个tasklet的多个实例同时运行。但是tasklet可以与SMP系统上的其他tasklet并行运行。因此, 如果多个tasklet会使用相同的资源, 它们必须采取某类加锁来避免彼此冲突。除非tasklet重新激活自己,否则每次tasklet激活只会运行一次。最后的解决方法就是将my_wq_func一个函数可以实现的内容,复制成了四个函数,问题就解决了。 

 故事讲完了,这时候该来分析分析理论上的底半部机制。前面的曲折,主要是因为自己对底半部机制的一知半解。这里来着重分析一下任务队列,工作队列的区别,同时也COPY一些别人对软中断的理解,以备后续查看


工作队列,任务队列,软中断

工作队列:Linux kernel中将工作推后执行的一种机制。这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。工作队列是2.6内核开始引入的机制,在2.6.20之后,工作队列的数据结构发生了一些变化。



点击(此处)折叠或打开


  1. DECLARE_WORK(struct work_struct , work_func_t func);
  2. INIT_WORK(struct work_struct *work, work_func_t func);
  3. INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);
  4. int schedule_work(struct work_struct *work);
  5. int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
  6. int schedule_delayed_work_on(struct delayed_work *work, unsigned long delay);
  7. struct workqueue_struct *create_workqueue(const char *name);
  8. int queue_work(struct workqueue_struct *wq, struct work_struct *work);
  9. int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, 
  10. unsigned long delay);
  11. void flush_scheduled_work(void);
  12. void flush_workqueue(struct workqueue_struct *wq);
  13. int cancel_delayed_work(struct delayed_work *work);
  14. void destroy_workqueue(struct workqueue_struct *wq);


任务队列:是一个由系统决定的安全时刻在软件中断上下文被调度运行的特殊函数。注意tasklet只会运行一次,即使在激活tasklet的运行之前重复请求该tasklet的运行也是这样。但是他可以与其他tasklet并行的运行在对称多处理器(SMP)系统上。





点击(此处)折叠或打开


  1. DECLARE_TASKLET(name, func, data);
  2. void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
  3. void tasklet_schedule(struct tasklet_struct *t);
  4. void tasklet_hi_schedule(struct tasklet_struct *t);
  5. void tasklet_disable(struct tasklet_struct *t);
  6. void tasklet_disable_nosync(struct tasklet_struct *t);
  7. void tasklet_enable(struct tasklet_struct *t);
  8. void tasklet_kill(struct tasklet_struct *t);


软中断:利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和"信号"有些类似,同时,软中断又是和硬中断相对应的,"硬中断是外部设备对CPU的中断","软中断通常是硬中断服务程序对内核的中断","信号则是由内核(或其他进程)对某个进程的中断"




点击(此处)折叠或打开


  1. void open_softirq(int nr, void (*action)(struct softirq_action *));
  2. void raise_softirq(unsigned int nr);
  3. asmlinkage void do_softirq(void) ;


他们之间的差异做了一个对比

linux 中断底半部机制对比(任务队列,工作队列,软中断)--由linux RS485引出的血案【转】_工作队列

 

 


通过上面的表格也就能明白,这三种分别适应的场合。以下原则

1,需要睡眠,阻塞的,只能用工作队列。

2,短时间内中断数量很多的,任务队列,软中断会更好。例如网络。

3,对性能要求很高的话,软中断最好。

4,使用任务队列时,应该注意同一个任务被多次调用,同一个函数被多个任务队列注意。

5,软中断要注意SMP,函数的重入。


【作者】张昺华

欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.