前言

一直有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的。

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

Linux内核完全注释阅读笔记1:O(1)时间复杂度查找timeout定时器的更多相关文章

  1. Linux内核0.11体系结构 ——《Linux内核完全注释》笔记打卡

    0 总体介绍 一个完整的操作系统主要由4部分组成:硬件.操作系统内核.操作系统服务和用户应用程序,如图0.1所示.操作系统内核程序主要用于对硬件资源的抽象和访问调度. 图0.1 操作系统组成部分 内核 ...

  2. 读《linux内核完全注释》的FAQ

    以下只是个人看了<linux内核完全注释>的一点理解,如果有错误,欢迎指正! 1 eip中保存的地址是逻辑地址.线性地址还是物理地址? 这个应该要分情况.eip保存的是下一条要执行的指令地 ...

  3. ubuntu下linux内核源码阅读工具和调试方法总结

    http://blog.chinaunix.net/uid-20940095-id-66148.html 一 linux内核源码阅读工具 windows下当然首选source insight, 但是l ...

  4. linux内核参数注释与优化

    目录 1.linux内核参数注释 2.两种修改内核参数方法 3.内核优化参数生产配置 参数解释由网络上收集整理,常用优化参数对比了网上多个实际应用进行表格化整理,使查看更直观. 学习linux也有不少 ...

  5. 《Linux内核分析》读书笔记(四章)

    <Linux内核分析>读书笔记(四章) 标签(空格分隔): 20135328陈都 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行 ...

  6. 赵炯博士《Linux内核完全注释》

    赵炯:男,1963年10月5日出生,江苏苏州人,汉族. 同济大学机械工程学院机械电子教研室副教授,从事教学和科研工作. 现在主要为硕士和博士研究生开设<计算机通信技术>.<计算机控制 ...

  7. LINUX内核完全注释

    学习教材:LINUX内核完全注释,内核版本0.11,修正版V3.0 赵炯编著 参考教材:UNIX操作系统设计--M. J. Bach, programming the 80x86  --John H. ...

  8. (转)linux内核参数注释与优化

    linux内核参数注释与优化 原文:http://blog.51cto.com/yangrong/1321594 http://oldboy.blog.51.cto.com/2561410/13364 ...

  9. linux内核代码注释 赵炯 第三章引导启动程序

    linux内核代码注释 第三章引导启动程序 boot目录中的三个汇编代码文件   bootsect.s和setup.s采用近似intel的汇编语法,需要8086汇编器连接器as86和ld86 head ...

随机推荐

  1. Js获取图片原始宽高

    如果我们页面看到的图片都是缩略图,那就需要做个图片点击放大效果,那么怎样获取图片的原始宽高呢?方法如下: //获取图片原始宽度 function getNaturalWidthAndHeight(im ...

  2. Dev tdxDBTreeView

    可以快速的用tree展示层次结构,无需任何编码;对tree的操作会自动post到数据集:对数据集的操作会 在tree上表现 一.关键 设置 datasource displayField:节点的   ...

  3. Java日志——2016年5月31日

    1. 三元运算符(A?B:C)属于运算符,表达式必须具有返回值,则A必须是boolean类型值,B和C必须是一个具有返回值的表达式. 2. switch...case本质上只支持int类型的选择判断, ...

  4. ssh访问控制,多次失败登录即封掉IP,防止暴力破解

    ssh访问控制,多次失败登录即封掉IP,防止暴力破解 一.系统:Centos6.3 64位 二.方法:读取/var/log/secure,查找关键字 Failed,例如(注:文中的IP地址特意做了删减 ...

  5. Modified Least Square Method and Ransan Method to Fit Circle from Data

    In OpenCv, it only provide the function fitEllipse to fit Ellipse, but doesn't provide function to f ...

  6. BAT的面试经验_摘抄

    一.心态 心态很重要! 心态很重要! 心态很重要! 重要的事情说三遍,这一点我觉得是必须放到前面来讲. 找工作之前,有一点你必须清楚,就是找工作是一件看缘分的事情,不是你很牛逼,你就一定能进你想进的公 ...

  7. 45、Docker 加 tensorflow的机器学习入门初步

    [1]最近领导天天在群里发一些机器学习的链接,搞得好像我们真的要搞机器学习似的,吃瓜群众感觉好神奇呀. 第一步 其实也是最后一步,就是网上百度一下,Docker Toolbox,下载下来,下载,安装之 ...

  8. jq之ajax以及json数据传递

    <html> <head><meta http-equiv="Content-Type" content="text/html; chars ...

  9. [Tomcat 源码分析系列] (一) : Tomcat 启动脚本-startup.bat

    概述 我们通常使用 Tomcat 中的 startup.bat 来启动 Tomcat. 但是这其中干了一些什么事呢? 大家都知道一个 Java 程序需要启动的话, 肯定需要 main 方法, 那么这个 ...

  10. Android客户端稳定性测试——Monkey

    修改时间 修改内容 修改人 2016.6.20 创建 刘永志 2016.6.29 完成 刘永志 Monkey简介: Android SDK自带的命令行测试工具,向设备发送伪随机事件流,对应用程序进行进 ...