1.  

前言

一直有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)复杂度就能够找到。

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

  1. while (p->next && p->next->jiffies < p->jiffies) {
  2. p->jiffies -= p->next->jiffies;
  3. fn = p ->fn;
  4. p->fn = p->next->fn;
  5. p->next->fn = fn;
  6.  
  7. jiffies = p->jiffies;
  8. p->jiffies = p->next->jiffies;
  9. p->next->jiffies = jiffies;
  10.  
  11. p = p->next;
  12. }

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。

  1. while (p->next && p->next->jiffies < p->jiffies) {
  2. p->jiffies -= p->next->jiffies;
  3. fn = p ->fn;
  4. p->fn = p->next->fn;
  5. p->next->fn = fn;
  6.  
  7. jiffies = p->jiffies;
  8. p->jiffies = p->next->jiffies;
  9. p->next->jiffies = jiffies;
  10.  
  11. p = p->next;
  12. }
  13. if (p->next) {
  14. p->next->jiffies -= p->jiffies;
  15. }

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

  1. if (p->next) {
  2. p->next->jiffies -= p->jiffies;
  3. }

取而代之的是,调用了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. JAVA学习笔记(33-53)

    33:java中的多维数组,以二位为例: 创建方法:int[][] a = new int[2][3]; 建立一个5*5的数组. 或者下面的建立方法也可以: int[][] c = { {1, 2, ...

  2. 解决低版本chrome浏览器不支持es6 Array.find()

     if (!Array.prototype.find) {  Array.prototype.find = function(predicate) {    'use strict';    if ( ...

  3. HTML DOM 对象简单介绍

    文档对象模型(Document Object Model,DOM)是DHTML的基础. 常用对象:1)window对象:表示对象浏览器窗口(选项卡)对象.2)document对象:代表整个网页,是客户 ...

  4. 使用SerialPort 读取外置GPS信息和使用GeoCoordinateWatcher获取内置gps的信息

    简介最近工作中需要读取gps设备的信息,平板本身有内置的gps设备,但是精度不够,就又添加了一个外置的gps.对于外置的gps,我们主要通过SerialPort类来获得串口的信息,然后对接收到的内容进 ...

  5. touches, targetTouches, changedTouches 区别

    1. touches: A list of information for every finger currently touching the screen2. targetTouches: Li ...

  6. virtualbox ubuntu 网络连接 以及 连接 secureCRT

    参考http://luowei828.blog.163.com/blog/static/31031204201263125415257/ 用Host-Only 方案      ip: VirtualB ...

  7. datetimepicker一个不错的日历android特效

    datetimepicker一个不错的日历效,选中和选择日历效果都很不错, 实用的时候直接可以把datetimepicker-library这个引入到项目,调用的地方在实现 TimePickerDia ...

  8. 解决SOAPCLIENT访问WebSerivce外网发布端口

    猫用vs2010写了一个webservice,并写了一个盘点程序客户端,PDA盘点机用C#开发,笔记本用VFP开发,发布在本地局域网IIS服务器,用了两年一直很稳定.后面仓库搬迁,需要外网进行访问,在 ...

  9. Java面试宝典答案详解与感悟(第二天)

    19.构造器 Constructor 是否可被 override? 答案:构造器Constructor不能被继承,因此不能重写Override,但是可以被重载Overload. 解析:构造器:在面向对 ...

  10. Maven安装与配置

    下载: 1.从官网http://maven.apache.org中下载,下载下来的是一个压缩包,解压即可.因为Maven本身也是用Java实现的.2.Maven的目录结构   /bin; maven的 ...