暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

内核工作队列修炼日记1-task-queue诞生

LinuxKernelStudy 2019-06-27
900

      老衲修炼工作队列已有一段时间,内核代码险恶,致使老衲元气大伤,需要休养。为避免再次受伤,老衲整理成修炼秘籍。方能事后继续修炼,也便与江湖各类门派切磋。

       本系列文章介绍了Linux内核中的工作队列机制,如何从无到有,如何从轻盈到复杂的历史过程,通过这些历史的演进过程,希望能对各位同学对工作队列有更加深入的理解。


        如何能更好的理解一个子系统的原理?浩大的内核海洋里代码越来越复杂,一定是有原因的,新版本的推出一定是发现了前面老版本的不足,加以优化改正,使内核更完善,更稳定,性能更高等。所以为了解整个工作队列的产生的背景,本人追溯了工作队列的整个衍变过程,linus也曾经说过,他并非对每个子系统都很熟悉,所以就有了每个模块的maintainer。好,老衲就喜欢一干到底,干到极致。


闲话少说,下面就来听老衲吹吹牛B。。


步入正题:
       1993年的一个夏天,詹姆斯在他的电脑旁疯狂敲打着代码,没错,他是一名软件攻城狮,确切的说他是一名linux爱好者。这次他有一个新需求,为一个软盘写个驱动,这次他似乎遇到点麻烦,当年的内核还是Linux1.0时代,像个年幼的婴儿。


       詹姆斯发现他本次的任务需要很多异步执行任务的场景,为了不阻塞主业务,他只好启动额外的线程来并发处理,而这些小任务,很快就执行完了,导致线程反复创建、销毁。由于经常需要执行异步任务,他索性将启动了一个常驻的后台任务,专门用来处理异步事物。


       由于linux的开放性吸引了大量的开发者投入,linux迅速活跃,越来越多的开发者遇到了相似的问题,他们抱怨内核并没有一个基础设施可以提供这样的服务,导致每个开发者需要自己额外编写代码,处理各种各样的异步事物。导致内核后台任务越来越多,某些驱动加载后,就会顺带启动一个后台守护线程,这些杂七杂八的线程,导致内核臃肿,难以维护。


     1994年,Kai Petzke提出了一个解决方案,他设计了一种叫task queue的基础服务,task queue解决了异步执行任务的问题,是如何做到的呢。其实很简单,他设计了一个的任务队列机制(就是一个单向链表,用来存放各类工作任务),并提供一些API,开发者通过API向链表添加任务,内核的一些流程中会遍历这些链表,摘下这些任务并执行。


task-queue机制由3个部分组成:

1、任务实体:(struct tq_struct)

struct tq_struct {

        struct tq_struct *next;         /* 指向下一个task  */

        int sync;                              /* must be initialized to zero */

        void (*routine)(void *);       /* 任务函数 */

        void *data;                          /* 函数的参数 */

};

2、任务队列:(tq_timer、tq_immediate等)

任务队列是由内核维护的单向链表,用于存放各类task。用户可以通过queue_task接口来向链表添加任务。


3、运行任务:

内核的run_task_queue接口负责从链表中摘下已存在的工作任务,并执行task的工作函数routine。


      有了上面一套机制,任何人都可以向系统提交工作任务,由内核来完成任务的执行。那么内核是在什么时机执行的呢?


        linux早期并没有使用后台线程的做法,而是提供了4种不同的task_queue全局队列,每种工作队列触发的时机不同,用户可根据任务的紧急程度来决定将任务挂到哪一个队列上。这4种全局队列为:


 tq_timer, tq_immediate, tq_scheduler, tq_disk;   //优先级依次降低


执行时机:

(1)加入tq_timer、tq_immediate队列任务的执行时机是中断发生后,也就是我们熟知的bottom half中断底半步,后来linux2.0版本有了tasklet的概念,这两种队列慢慢演变成了hi_tasklet,且tq_timer优先级大于tq_immediate。

(2)tq_scheduler队列执行时机:当调用schedule函数时会执行(也就是进程睡眠或者进程切换的时候会执行)。

(3)tq_disk的的执行时机较多,run_task_queue(&tq_disk)函数会贯穿到内核的各个角落里,但是都是在文件系统相关的函数中执行,如wait_on_page、generic_file_readahead、bdflush等函数。可见tq_disk队列的执行时间具有极大的不确定性。


        同时内核也支持创建新的全局task_queue链表,由用户自己决定什么时刻来执行任务。


如下示例:


总结一下,早期task-queue的出现提供了异步执行机制,这是工作队列的老祖宗。同时它也借用了linux的底半步思想。


task-queue主要功能:
1、提供4种不同的tqueue全局队列。
2、提供struct tq_struct用于描述任务。
3、开发者使用queue_task接口来投递任务。
4、内核在合适的时机,会运行run_task_queue函数,遍历全局链表,执行task。


      自此之后,开发者只需定义tq_struct结构体来描述任务,在tq_strcut中填入function及参数,然后调用queue_task,就可以将任务委托给内核执行。


      task queue初入茅庐,略显单薄,但它的出现确实解决了异步执行的问题。是不是觉得太简单了,别急,复杂的事物都是由一个个简单的东西拼起来的,刚刚修炼,都要先扎扎马步。想当初,老衲也曾试过直接修炼linux5.0,差点走火入魔,如今不敢再碰。相信我,待时机成熟你定能和它过上几招。


       最后举一个生活中的例子,作为收尾。相信大家都点过外卖吧,从内核机制的角度来看,这也是一种工作队列的实现,想想看,如果是我们自己去店里吃饭,是不是要经过:出门->餐馆->点菜->等待上菜->eating->返程,如果有了美团外卖这个“基础设施”,我们就可以委托给外卖小哥去执行了,我们只需要将我们要吃的菜进行“初始化”(tq_struct),然后调用一下“提交订单”(queue_task),等待手机呼叫就可以了,此时我们可以继续躺在沙发上玩上两局王者,乐此不疲。


    不过,美团这个工作队列和内核还是有点差别,别忘了我们要付跑腿费,而linux是完全免费的,尽情的享用吧!



文章转载自LinuxKernelStudy,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论