zoukankan      html  css  js  c++  java
  • Linux内核完全注释阅读笔记1:O(1)时间复杂度查找timeout定时器

    
    

    前言

    一直有Linux kernel情节,之前也一直在看Linux kernel相关的书和代码,但是每次到最后又由于兴趣转变而荒废了。这次终于静下心来想把Linux内核相关的代码好好看看,算是对自己的一个沉淀吧。由于之前工作做的是分布式调度这块的东西,也稍微有过分布式文件系统相关的实习经历,所以阅读Linux内核代码的重心可能会往调度和文件系统这两大块倾斜。

    个人感觉读读Linux kernel还是蛮有必要的,其实现在各种分布式框架、各种云计算,其实很多的思想都是借鉴Linux kernel的。只是现在人比较浮躁,都比较急功近利,都恨不得一下子把什么分布式框架完全研究透,而

    忽略了底层的基础。可是到后面又只是停留在理论,而根本不知道如何深入。

    Linux定时器

    Linux通过系统定时器用以计算流逝的时间,使用全局jiffies用来记录自系统启动以来产生的节拍的总数。由于Linux系统所有的时钟、进程的调度、运行队列的负载均衡都依赖于定时器。

    所以Linux定时器的添加和timeout定时器的查找性能要求尤其重要,所以能够在O(1)查找timeout定时器还是蛮重要的,看了下Linux内核定时器添加这块的逻辑。不得不说,这块的代码

    写得还是蛮好的,而且算法非常简单。

    单纯从timeout定时器的查找来看,可以有以下一些方法:

    1. 最容易想到的,而且也是时间复杂度最高的:维护一个定时器链表,每次插入一个定时器的时候,可以把定时器插入到链表最前面,这样插入时间负责度是O(1),但是查找的时候就必须遍历的,

    时间负责度会O(N)。

    2. 维护一个定时器数组,数组中的每个元素严格按照超时jiffies有序。这样查找的时候可以使用二分查找,查找时间复杂度为O(lgN)。但是,但是对于定时器的插入和删除,时间复杂度却很高,

    必须移动元素,时间复杂度在O(N)。

    3. 使用堆或者红黑树,这样插入、查找和删除时间复杂度都会O(lgN)。还是可以接受的,不过编程稍微复杂些。

    4. Linux内核的实现,插入时间复杂度会O(N),查找和删除时间复杂度都为O(1),性能还是非常好的。而且都是内核的使用场景,插入相对于查找来说肯定频率低很多。比如,一个定时器的

    timeout时间相对于现在有10jiffies,那么就是在这10个jiffies中会涉及10次查找但是只有一次插入和删除。

    Linux定时器插入代码

    看下Linux定时器的插入逻辑,你就会发现它这块在插入的时候估计为查找做了很多优化,就会发现timeout定时器在O(1)复杂度就能够找到。

    前面那些判断的逻辑就不用多说了,这块会讲下核心的代码。首先会判断当前带插入的定时器合不合法,如果合法就插入到链表头部。然后,就是优化。核心代码如下:

    while (p->next && p->next->jiffies < p->jiffies) {
        p->jiffies -= p->next->jiffies;
        fn = p ->fn;
        p->fn = p->next->fn;
        p->next->fn = fn;
    
        jiffies = p->jiffies;
        p->jiffies = p->next->jiffies;
        p->next->jiffies = jiffies;
    
        p = p->next;
    }
    

    p为定时器链表表头指针,代码中比较核心的逻辑是 p->jiffies -= p->next->jiffies; 每个定时器真正的timeout时间是前面所有定时器jiffies值和自身jiffies之和。并且链表的是按照jiffies值有序的。

    但是这块代码中还是有一处问题的,如果仔细想想的话。如果刚开始插入的元素很小的话,后面插入的元素都比较大,这个确实是没有问题。但是,如果之前插入的元素比较大,后面插入的比较小。

    如果,插入1、2、3、4,严格最小的最开始插入式没有问题的,这样最终的链表元素值依次是1、1、1、1。OK,肯定是正常的,每次jiffies到期时,会把第一个元素的值减1,到0就清除掉,

    并且调用对应的注册方法。

    但是,对于后面插入的不是当前链表中最大的值,例如按4、3、2、1或者其他的顺序,就存在问题了。像这个例子,最终链表元素值依次是1、2、3、4。也就是最后一个元素本来是需要4jiffies到期的,现在却需要变成是10jiffies。明显不合理。

    那这个问题怎么解决呢,有一个办法,元素插到确定的位置之后,再把后面那个元素的值减去前面那个元素的值。比如,4已经插好了,并且链表中只有4,再插入3的时候,3插入到4之前,然后把后面的所有的元素值减去前面元素的值。

    所以,3插入到链表后,链表中的值为3、1。插入2之后,链表元素的值为2、3、1,再把2后面的那个元素的值减去见面的,就是2、1、1。插入1之后,链表中的元素值为1、2、1、1,然后再把2减去1,

    所以链表中最终的元素值为1、1、1、1。

    while (p->next && p->next->jiffies < p->jiffies) {
        p->jiffies -= p->next->jiffies;
        fn = p ->fn;
        p->fn = p->next->fn;
        p->next->fn = fn;
    
        jiffies = p->jiffies;
        p->jiffies = p->next->jiffies;
        p->next->jiffies = jiffies;
    
        p = p->next;
    }
    if (p->next) {
        p->next->jiffies -= p->jiffies;
    }

    不知道为什么源代码中反而没有后面的判断,

    if (p->next) {
        p->next->jiffies -= p->jiffies;
    }
    

    取而代之的是,调用了sti()函数,然后add_timer函数就结束了。没细看sti()函数的逻辑,不过感觉Linux内核中肯定做了这件事情,否则肯定是有Bug的。

    不过,不管怎样,这块的代码写的还是蛮好的。可以借鉴,不过在借鉴之前要明白它这么做的场景和原理。

  • 相关阅读:
    HDU 1002 大数A+B
    HDU 2066 一个人的旅行(最短路)
    HDU 1869 六度分离(最短路 floyd)
    HDU 1159 Common Subsequence(LCS)
    POJ 3061 Subsequence(尺取法)
    NYOJ 10 skiing(记忆化搜索)
    dedecms添加全站的rss订阅功能
    dedecms artlist读取全站最新文章
    dedecms的title怎么优化?
    DedeCMS提示Maximum execution time of 30 seconds exceeded in解决办法
  • 原文地址:https://www.cnblogs.com/bitCoin/p/5573511.html
Copyright © 2011-2022 走看看