在Linux的文件查找命令中,mlocate提供的locate命令在单纯进行路径名名查找时有着显著的效率优势,因为mlocate预先对磁盘文件进行扫描并存储到一个数据库文件中,查找时只需要检索数据库而即可。本文主要对mlocate工具数据库的更新(updatedb)进行分析。

基础知识

locate命令需要安装mlocate来获得

locate命令基础用法:点此链接

mlocate的配置:点此链接。这里特别说一下 PURNE_BIND_MOUNTS,大部分文章只说这是限制搜索,没说具体意思,其实PURNE_BIND_MOUNTS=yes会跳过bind mount,至于什么是bind mount,使用过docker的同学应该知道一个容器通常会产生一个挂载卷,在df -h中可以看到,通常如下图

这就是一个bind mount,它是对本地目录的重复挂载,没有必要多索引一次,所以 PURNE_BIND_MOUNTS 保留yes就好。

updatedb的执行策略

mlocate是通过执行updatedb命令来建立/更新数据库的,除了手动执行外,操作系统会每日进行更新,但在各用户的crontab里是看不到的,因为updatedb的定时执行使用了anacron实现。

anacron介绍参见这里,updatedb的执行定义在/etc/cron.daily/mlocate里:

  1. # vim /etc/cron.daily/mlocate
  2.  
  3. #! /bin/bash
  4.  
  5. set -e
  6.  
  7. [ -x /usr/bin/updatedb.mlocate ] || exit 0
  8.  
  9. if which on_ac_power >/dev/null 2>&1; then
  10. ON_BATTERY=0
  11. on_ac_power >/dev/null 2>&1 || ON_BATTERY=$?
  12. if [ "$ON_BATTERY" -eq 1 ]; then
  13. exit 0
  14. fi
  15. fi
  16.  
  17. # See ionice(1)
  18. if [ -x /usr/bin/ionice ] &&
  19. /usr/bin/ionice -c3 true 2>/dev/null; then
  20. IONICE="/usr/bin/ionice -c3"
  21. fi
  22.  
  23. # See nocache(1)
  24. NOCACHE=
  25. if [ -x /usr/bin/nocache ]; then
  26. NOCACHE="/usr/bin/nocache"
  27. fi
  28.  
  29. flock --nonblock /run/mlocate.daily.lock $NOCACHE $IONICE nice /usr/bin/updatedb.mlocate

即在updatedb命令没有运行,且插入电源的情况下,首先设定了io优先级(-c3表示只在其他程序无磁盘io时执行),然后以默认优先级执行updatedb命令。

updatedb过程分析

mlocate相较于前辈slocate实现了增量更新。总的来说,mlocate基于对目录的mtime和ctime是否发生了变更的判断,来决定是否要进入目录内进一步索引内容,流程图如下:

下面结合源码说明,从main函数开始(部分代码):

  1. unlink_init ();
  2. new_db_open ();
  3. dir_state_init (&scan_dir_state);
  4. if (chdir (conf_scan_root) != 0)
  5. error (EXIT_FAILURE, errno, _("can not change directory to `%s'"),
  6. conf_scan_root);
  7. if (lstat (".", &st) != 0)
  8. error (EXIT_FAILURE, errno, _("can not stat () `%s'"), conf_scan_root);
  9. cwd_fd = -1;
  10. scan (conf_scan_root, &cwd_fd, &st, ".");

main函数在进行了一系列初始化和检测后,就进入了scan函数;全部代码太长,这里只截取必要的部分:

  1. 1 /* "relative" may now become a symlink to somewhere else. So we use it only
  2. 2 in safe_chdir (). */
  3. 3 entries_mark = obstack_alloc (&scan_dir_state.data_obstack, 0);
  4. 4 dir.path = path;
  5. 5 time_get_ctime (&dir.time, &st);
  6. 6 time_get_mtime (&mtime, &st);
  7. 7 if (time_compare (&dir.time, &mtime) < 0)
  8. 8 dir.time = mtime;
  9. 9 while (old_dir.path != NULL && (cmp = dir_path_cmp (old_dir.path, path)) < 0)
  10. 10 {
  11. 11 old_dir_skip ();
  12. 12 old_dir_next_header ();
  13. 13 }
  14. 14 did_chdir = false;
  15. 15 have_subdir = false;
  16. 16 if (old_dir.path != NULL && cmp == 0
  17. 17 && time_compare (&dir.time, &old_dir.time) == 0
  18. 18 && (dir.time.sec != 0 || dir.time.nsec != 0))
  19. 19 {
  20. 20 res = copy_old_dir (&dir);
  21. 21 if (res != -1)
  22. 22 {
  23. 23 have_subdir = res;
  24. 24 old_dir_next_header ();
  25. 25 goto have_dir;
  26. 26 }
  27. 27 }
  28. 28 if (time_is_current (&dir.time))
  29. 29 {
  30. 30 /* The directory might be changing right now and we can't be sure the
  31. 31 timestamp will be changed again if more changes happen very soon, mark
  32. 32 the timestamp as invalid to force rescanning the directory next time
  33. 33 updatedb is run. */
  34. 34 dir.time.sec = 0;
  35. 35 dir.time.nsec = 0;
  36. 36 }
  37. 37 did_chdir = true;
  38. 38 if (safe_chdir (cwd_fd, relative, &st) != 0)
  39. 39 goto err_chdir;
  40. 40 res = scan_cwd (&dir);
  41. 41 if (res == -1)
  42. 42 goto err_chdir;
  43. 43 have_subdir = res;
  44. 44 have_dir:
  45. 45 write_directory (&dir);
  46. 46 if (have_subdir != false)
  47. 47 {
  48. 48 if (did_chdir == false)
  49. 49 {
  50. 50 did_chdir = true;
  51. 51 if (safe_chdir (cwd_fd, relative, &st) != 0)
  52. 52 goto err_entries;
  53. 53 }
  54. 54 scan_subdirs (&dir, &st);
  55. 55 }

这里涉及到了dir,它是一个directory类型,其定义如下:

  1. /* A directory in memory, using storage in obstacks */
  2. struct directory
  3. {
  4. struct time time;
  5. void **entries; /* Pointers to struct entry */
  6. size_t num_entries;
  7. char *path; /* Absolute path */
  8. };

由此可见,directory中记录了该path下含有哪些子目录/文件(entries),以及包含的总数(num_entries),以及最重要的path本身。基本满足了locate命令查询时所需的必要信息。

回到scan函数,它传入了一个path参数,函数首先获取这个path的ctime和mtime,取其中最大者为directory结构体的time,因为这可以反映该path最后一次被修改的时间,而后它将old_dir.path和path进行了一个对比。

old_dir来自于old_db,它的数据结构是Obstack,本质是一个栈,在项目中发挥的作用类似一个内存数据库,储存着上次磁盘扫描的结果。简要地理解,整个磁盘的路径字符串以深度遍历的顺序依次存储在其中,old_dir.path就是old_db的游标当前指向的路径,而path为此次扫描程序正在读取的当前路径。两个路径比较结果小于0则把old_db游标指向下一个路径;比较函数为dir_path_cmp,这个函数定义在lib.c中:

  1. /* Initialize dir_path_cmp_table */
  2. void
  3. dir_path_cmp_init (void)
  4. {
  5. size_t i;
  6. unsigned char val;
  7.  
  8. dir_path_cmp_table[0] = 0;
  9. dir_path_cmp_table['/'] = 1;
  10. val = (unsigned char)2;
  11. for (i = 1; i < ARRAY_SIZE (dir_path_cmp_table); i++)
  12. {
  13. if (i != '/')
  14. {
  15. dir_path_cmp_table[i] = val;
  16. val++;
  17. }
  18. }
  19. assert (val == 0);
  20. }
  21.  
  22. /* Compare two path names using the database directory order. This is not
  23. exactly strcmp () order: "a" < "a.b", so "a/z" < "a.b". */
  24. int
  25. dir_path_cmp (const char *a, const char *b)
  26. {
  27. while (*a == *b && *a != 0)
  28. {
  29. a++;
  30. b++;
  31. }
  32. {
  33. verify (sizeof (int) > sizeof (unsigned char)); /* To rule out overflow */
  34. }
  35. return ((int)dir_path_cmp_table[(unsigned char)*a]
  36. - (int)dir_path_cmp_table[(unsigned char)*b]);
  37. }

可以看出,dir_path_cmp返回负值的条件是,path的ascii字典序大于old_dir.path('/'字典序被定义为1),这包含了两种情况:

  1. path是old_dir.path的一个子目录
  2. path与old_dir.path不存在上下级关系(同级或不同级),且path的字典序大于old_dir.path,比如/a和/b这种

由于scan的路径扫描是深度优先的(后面会讲到),所以当scan扫到了path时,其父目录一定被扫描过了,因而old_dir.path可以跳过;对于情况2,由于old数据库中的路径是严格按照字典序排列的,那这种情况的出现只可能由两种原因导致:

  1. 旧数据库中的游标滞后了,所以需要跳过来赶上当前扫描的进度(情况1就是它的一个情境)
  2. 旧数据库中的path已经不存在了(删除或改名),自然可以跳过

继续往后走,对于old_db中存在的路径,用time_compare比较了下最后修改时间(综合了mtime和ctime,上面有说明),如果时间也一致就不对这个目录内的文件进一步扫描了,直接copy_old_dir把old_db里上次记录的信息拿过来,这使得mlocate极大的减少了updatedb时的时间开销;但子目录还要遵照正常的深度优先顺序遍历(25行goto have_dir),因为子目录里的修改并不会反映在父目录的ctime和mtime上。

对于old_db中不存在的路径,即是上次扫描后新增(广义)的文件/目录,于是scan_cwd (&dir)扫一遍目录里面,然后同样走到have_dir,have_dir这里先write_directory (&dir)把已获得的路径信息(不管是扫描得到的还是从旧数据库拷贝来的)写入一个新数据库里,如果有子目录的话,则要scan_subdirs (&dir, &st)进入扫描,基本是上述过程的重复;而scan_subdirs是一个递归函数(严格来说是scan_subdirs调用了scan,算间接递归),这里就理解了为什么说扫描是一个深度优先的过程。

总结

本文分析了mlocate工具中的updatedb。一言以蔽之,updatedb就是按acsii字符序深度优先遍历整个磁盘,建立了一个顺序表,并定时更新。与直觉不同的是,updatedb不是在旧数据库的基础上更新,而是重建数据库,只是对于未修改目录利用旧数据库里的信息节省了部分磁盘扫描时间。另外值得注意的是,updatedb的扫描并不会影响文件和目录的atime,因为其对文件使用了C函数lstat,对目录实现了opendir_noatime。

有机会的话,下篇文章会讲讲mlocate的检索(locate)原理。

附:一个locate命令使用技巧

当你想查找路径包含"ponny"的目录/文件时,locate ponny可以满足你要求,但一旦关键字里混进了空格,比如"James Lee",你会发现即便是locate "James Lee"也找不出想要的结果。此时,在关键字前后各加一个'*',即locate "*James Lee*"即可。

Linux mlocate源码分析:updatedb的更多相关文章

  1. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...

  2. Linux内核源码分析 day01——内存寻址

    前言 Linux内核源码分析 Antz系统编写已经开始了内核部分了,在编写时同时也参考学习一点Linux内核知识. 自制Antz操作系统 一个自制的操作系统,Antz .半图形化半命令式系统,同时嵌入 ...

  3. linux内存源码分析 - 零散知识点

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 直接内存回收中的等待队列 内存回收详解见linux内存源码分析 - 内存回收(整体流程),在直接内存回收过程中, ...

  4. linux内存源码分析 - 内存回收(整体流程)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 当linux系统内存压力就大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文 ...

  5. linux内存源码分析 - 内存压缩(同步关系)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 最近在看内存回收,内存回收在进行同步的一些情况非常复杂,然后就想,不会内存压缩的页面迁移过程中的同步关系也 ...

  6. linux内存源码分析 - 内存压缩(实现流程)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 本文章最好结合linux内存管理源码分析 - 页框分配器与linux内存源码分析 -伙伴系统(初始化和申请 ...

  7. linux内存源码分析 - SLUB分配器概述

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ SLUB和SLAB的区别 首先为什么要说slub分配器,内核里小内存分配一共有三种,SLAB/SLUB/SLOB ...

  8. linux内存源码分析 - SLAB分配器概述【转】

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请 ...

  9. linux内存源码分析 - 伙伴系统(初始化和申请页框)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前的文章已经介绍了伙伴系统,这篇我们主要看看源码中是如何初始化伙伴系统.从伙伴系统中分配页框,返回页框于伙伴系 ...

随机推荐

  1. .NET平台系列10 .NET统一平台愿景

    系列目录     [已更新最新开发文章,点击查看详细] 2019年,微软分享了[统一的.NET堆栈和生态系统的愿景].给开发者带来的价值是,将能够使用一组API,语言和工具来针对广泛的应用程序类型,包 ...

  2. [bug] powerdesigner 设置id 自增 Properties中没有identity

    参考 https://blog.csdn.net/qq_37924509/article/details/105215719

  3. Ansible_实施处理程序

    一.Ansible配置处理程序 1.处理程序 1️⃣:处理程序是响应由其他任务触发的通知的任务 2️⃣:仅当任务在受管主机上更改了某些内容时,任务才通知其处理程序 3️⃣:每个处理程序具有全局唯一的名 ...

  4. [论文阅读笔记] Community aware random walk for network embedding

    [论文阅读笔记] Community aware random walk for network embedding 本文结构 解决问题 主要贡献 算法原理 参考文献 (1) 解决问题 先前许多算法都 ...

  5. redux 源码浅析

    redux 源码浅析 redux 版本号: "redux": "4.0.5" redux 作为一个十分常用的状态容器库, 大家都应该见识过, 他很小巧, 只有 ...

  6. 解决idea查不到插件

    http://127.0.0.1:1080

  7. ZooKeeper学习笔记一:集群搭建

    作者:Grey 原文地址:ZooKeeper学习笔记一:集群搭建 说明 单机版的zk安装和运行参考:https://zookeeper.apache.org/doc/r3.6.3/zookeeperS ...

  8. 如何在Linux中自动删除或清理/tmp文件夹内容?

    每个Linux系统都有一个名为的目录/tmp,该目录已挂载了单独的文件系统. 它具有称为tmpfs的特殊文件系统.这是一个虚拟文件系统,操作系统将在系统引导时自动挂载/tmp挂载点. 如果要根据应用程 ...

  9. CVD和ALD薄膜沉积技术应用领域

    CVD和ALD薄膜沉积技术应用领域 显示 用于OLED.QD-OLED.甚至未来QLED的薄膜封装,通过有机/无机叠层结构的保护,水汽渗透率WVTR可降至10-5g/m2/day,保证OLED或者量子 ...

  10. 摄像头与毫米波雷达(Radar)融合

    摄像头与毫米波雷达(Radar)融合 Input: (1)图像视频分辨率(整型int) (2)图像视频格式 (RGB,YUV,MP4等) (3)毫米波雷达点云信息(点云坐标位置x,y,浮点型float ...