V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tl3shi
V2EX  ›  程序员

你真的知道 du 和 ls 的区别吗?

  •  5
     
  •   tl3shi · 2016-12-13 00:41:30 +08:00 · 7163 次点击
    这是一个创建于 2894 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我又是来拉流量的, 欢迎在文末扫码关注微信公众号.

    原问在 这里

    由一次磁盘告警引发的血案 -- du 和 ls 的区别

    difference-between-du-and-ls

    如果你完全不明白或者完全明白图片含义, 那么你不用继续往下看了. 否则, 这篇文章也许正是你需要的.

    背景

    确切地说,不是收到的自动告警短信或者邮件告诉我某机器上的磁盘满了,而是某同学人肉发现该机器写不了新文件才发现该问题的. 说明我司告警服务还不太稳定 :)

    第一次出现该问题时, 我的处理方式是: 先删了 /tmp/ 目录, 空闲出部分空间, 然后检查下几个常用的用户目录, 最终发现某服务 A 的日志文件(contentutil.log)占用了好几个大 G, 询问相关开发人员后确定该日志文件不需要压缩备份, 所以可直接删除, 于是 rm contentutil.log 之后就天真地认为万事大吉了...(不懂为啥当初没 df 再看看)

    然而, 大约 xx 天后, 发现该机器磁盘又满了, 惊呼奇怪咋这么快又满了. 最终发现是上次 rm contentutil.log 后, 占用好几个大 G 的 contentutil.log 一直被服务 A 的进程打开了, rm 后空间并没有释放. rm 其实是删除该文件名到文件真正保存到磁盘位置的链接, 此时该文件句柄还被服务 A 打开, 因此对应的数据并没有被回收, 其实可以理解为 GC 里面的引用计数, rm 只是减少了引用计数, 并没有真正的进行释放内存, 当引用计数为 0 的时候, OS 内核才会释放空间, 供其他进程使用. 所以当 A 进程停止(文件句柄的引用计数会变为 0)或者重启后, 占用的存储空间才被释放(从某种程度上讲说明该服务一直很稳定, 可以连续跑很久不出故障~ 微笑脸). (tip: 如果不知道具体进程或文件名的话:lsof | grep deleted,这样会查找所有被删除的但是文件句柄没有释放的文件和相应的进程,然后再 kill 掉进程或者重启进程即可).

    后来, 白老板告知可以用修改文件内容的方式在不用重启进程的情况下释放空间.

    du vs ls

    前两天该问题又出现了, 该服务 A 的日志文件(contentutil.log)占用了约 7.6G(请原谅我们没有对该服务的日志做 logrotate)。这一次学聪明了, 直接用echo 'hello' > contentutil.log, 然后 df 确认磁盘空间确实已经释放, 心想着这次可以 Happy 了, 突然手贱执行了下 lsdu, 有了以下结果:

    [root@xxx shangtongdai-content-util]# ls -lah contentutil.log
    -rw-r--r--. 1 root root 7.6G Nov  7 19:36 contentutil.log
    [root@xxx shangtongdai-content-util]# du -h contentutil.log
    2.3M    contentutil.log
    

    反正我看到这样的结果是百思不得其解, 如果你已经明确为什么会产生这样的结果, 那就不用继续往下看了. 可以明确的是, 这里的 lsdu 结果肯定代表不同的含义, 具体原因不详, 在查阅相关资料和咨询强大的票圈后了解到, 这大概与文件空洞和稀疏文件(holes in 'sparse' files)相关.

    ls 的结果是 apparent sizes, 我的理解是文件长度, 就类似文件系统中 file 这个数据结构中的定义文件长度的这个字段, du 的结果 disk usage, 即真正占用存储空间的大小, 且默认度量单位是 block. (apparent sizes 和 disk usage 说法摘自 man du 中的 --apparent-size 部分)

    给出一个具体的示例:

    // Mac OS 10.11.6 (15G1004)
    ➜  _drafts git:(source) ✗ echo -n a >1B.log
    ➜  _drafts git:(source) ✗ ls -las 1B.log
    8 -rw-r--r--  1 tanglei  staff  1 11  9 00:06 1B.log
    ➜  _drafts git:(source) ✗ du 1B.log
    8	1B.log
    ➜  _drafts git:(source) ✗ du -h 1B.log
    4.0K	1B.log
    

    上面示例中, 文件 1B.log 内容仅仅包含一个字母"a", 文件长度为 1 个字节, 前面的 8 为占用的存储空间 8 个 block, (ls -s 的结果跟 du 的结果等价, 都是实际占用磁盘的空间), 为什么 1 个字节的文件需要占用 8 个 block 呢, 可以这样理解, block 为磁盘存储的基本的单位, 方便磁盘寻址等(这里说的基本单位应该是磁盘物理结构单位例如一个扇区 /柱面等, 对应一个物理单位), 而此处的 block 可以理解为一个逻辑单位, 且一个文件除了包括数据外, 还需要存储描述此文件的其他信息, 因此包含 1 个字节的文件实际在磁盘中占用的存储空间不止 1 个字节. 默认情况下, Mac 中 1 个逻辑 block 中是 512 字节, 因此 du -h 结果是 8 * 512 = 4096 = 4.0K.

    If the environment variable BLOCKSIZE is set, and the -k option is not specified, the block counts will be displayed in units of that size block. If BLOCKSIZE is not set, and the -k option is not specified, the block counts will be displayed in 512-byte blocks. (man du)

    因此, 通常情况下, ls 的结果应该比 du的结果更小(都指用默认的参数执行, 调整参数可使其表达含义相同), 然而上面跑服务 A 的机器上 contentutil.log 的对比结果是 7.6G vs. 2.3M, 仍然无法理解了. 沿着 man du 可以看到:

    although the apparent size is usually smaller, it may be larger due to holes in ('sparse') files, internal fragmentation, indirect blocks, and the like

    即因 contentutil.log 是一个稀疏文件, 虽然其文件长度很大, 到 7.6G 了, 然而其中包含大量的holes并不占用实际的存储空间.

    下面用一个具体的例子来复现以上遇到的问题. 注意以下例子为 Linux version 2.6.32 (Red Hat 4.4.7)中运行结果, 且在 Mac 中并不能复现(后文有指出为什么我的 Mac 不能复现).

    // 从标准输入中读取 count=0 个 block, 输出到 sparse-file 中, 
    // 一个 block 的大小为 1k(bs=1k), 输出时先将写指针移动到 seek 位置的地方
    [root@localhost ~]# dd of=sparse-file bs=1k seek=5120 count=0
    0+0 records in
    0+0 records out
    0 bytes (0 B) copied, 1.6329e-05 s, 0.0 kB/s
    // 所以此时的文件长度为: 5M = 5120*1k(1024) = 5242880
    [root@localhost ~]# ls -l sparse-file
    -rw-r--r--. 1 root root 5242880 Nov  8 11:32 sparse-file
    [root@localhost ~]# ls -ls sparse-file
    0 -rw-r--r--. 1 root root 5242880 Nov  8 11:32 sparse-file
    // 而 sparse-file 占用的存储空间为 0 个 block
    [root@localhost ~]# du sparse-file
    0	sparse-file
    [root@localhost ~]# du -h sparse-file
    0	sparse-file
    

    此时若用 vim 打开该文件, 用二进制形式查看 (tip :%!xxd 可以更改当前文件显示为 2 进制形式), 能看到里面的内容全是0. 或者直接用od命令查看 2 进制.

    // vim 二进制查看
    0000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    0000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    ....
    //od -b sparse-file
    0000000   000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
    *
    24000000
    

    实际上, Sparse 文件是并不占用磁盘存储空间的, 那为什么能看到文件里面包含很多 0? 因为当在读取稀疏文件的时候, 文件系统根据文件的 metadata(就是前面所指描述文件的这个数据结构)自动用0填充[ref Wiki]; Wiki 上还说, 现代的不少文件系统都支持 Sparse 文件, 包括 Unix 及其变种和 NTFS, 然而 Apple File System(APFS)不支持, 因此我在我的 Mac 上用 du 查看占用空间与 ls 的结果一致. 传闻指出 Apple 在今年 6 月的 WWWC 上宣称支持 Sparse 文件. (貌似目前我的系统版本还不支持)

    // In Mac
    ➜  ~ dd of=sparse-file bs=1k seek=5120 count=0
    0+0 records in
    0+0 records out
    0 bytes transferred in 0.000024 secs (0 bytes/sec)
    ➜  ~ ls -ls sparse-file
    10240 -rw-r--r--  1 tanglei  staff  5242880 11  9 09:44 sparse-file
    ➜  ~ du sparse-file
    10240	sparse-file
    

    以上是用 dd 等命令创建稀疏文件, 也有同学用 c 代码实现了相同的功能. 其实就是写文件的时候, 改变下当前文件写指针. 前面遇到的问题就应该类似.

    #include <stdio.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main() {
        int fd, result;
        char wbuf[] = "hello";
    
        if ((fd = open("./filetest.log", O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR))
    )  {
                perror("open");
                return -1;
        }
        if ((result = write(fd, wbuf, strlen(wbuf)+1)) < 0) {
                perror("write");
                return -1;
        }
        if ((result = lseek(fd, 1024*1024*10, SEEK_END)) < 0) {
                perror("lseek");
                return -1;
        }
        if ((result = write(fd, wbuf, strlen(wbuf)+1)) < 0) {
                perror("write");
                return -1;
        }
    
        close(fd);
        return 0;
    }
    

    以上先将"hello"写入 filetest.log, 然后改变文件指针到1024*1024*10(相当于文件长度这个字段变大了), gcc 编译后运行结果文件详情如下:

    [root@localhost ~]# ls -ls filetest.log
    8 -rw-------. 1 root root 10485772 Nov  9 17:45 filetest.log
    [root@localhost ~]# du  filetest.log
    8	filetest.log
    [root@localhost ~]# du -h filetest.log
    8.0K	filetest.log
    [root@localhost ~]# ls -lh filetest.log
    -rw-------. 1 root root 11M Nov  9 17:45 filetest.log
    [root@localhost ~]# od -c filetest.log
    0000000   h   e   l   l   o  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
    0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
    *
    50000000  \0  \0  \0  \0  \0  \0   h   e   l   l   o  \0
    50000014
    

    解释下结果: 文件长度应该是 "hello" 加上 "\n" 共 6 个字节*2 = 12, 再加上1024*1024*10个字节, 即为ls产生的结果 10485772 个字节约 11M, 而du的结果为 8 个 block 也为 8k(这台机器上的 block 大小与前面的 Mac 不一样, 这里是 1024).

    Display values are in units of the first available SIZE from --block-size, and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set. (du --help)

    总结

    总结一下: 出现以上问题说明自己对一些基础掌握得尚不牢固, 比如

    1. rm 某文件后, 文件占用的磁盘空间并不是立即释放, 而是其句柄没有被任意一个进程引用时才回收;
    2. ls/du 命令结果的具体含义;
    3. 稀疏文件.

    然而这些知识点都在《 UNIX 环境高级编程》这本书中有讲 (之前走马观花看过不少, 咋对稀疏文件等一点印象都木有!)

    以上内容若有不清楚或不正确的地方, 还望大家指出, 感谢.

    另外, 我最终也开通了一个微信公众号, 欢迎有兴趣的同学扫码关注, 谢谢.

    微信公众号

    参考资料:

    31 条回复    2016-12-14 13:14:55 +08:00
    privil
        1
    privil  
       2016-12-13 00:47:58 +08:00
    你说了那么多,其实最开始的时候你该 df -h 一下 ……
    hanxiV2EX
        2
    hanxiV2EX  
       2016-12-13 00:49:03 +08:00 via iPhone
    总结,清理进程正在访问时的文件不能用 rm, 要用 >.
    Showfom
        3
    Showfom  
       2016-12-13 00:52:51 +08:00
    确实专业,支持下。
    privil
        4
    privil  
       2016-12-13 00:53:14 +08:00
    我去, df 居然也不信,好吧,我光速打自己脸了
    billlee
        5
    billlee  
       2016-12-13 00:53:34 +08:00
    这明显是开发的锅,日志系统不支持 rotate
    privil
        6
    privil  
       2016-12-13 00:57:42 +08:00
    dd of=sparse-file bs=1k seek=5120 count=0 生成的文件 df 也是看不见的,所以你 df 了也没用……
    binux
        7
    binux  
       2016-12-13 01:09:24 +08:00   ❤️ 4
    这个比上次那个免密码拷贝的高级多了。
    msg7086
        8
    msg7086  
       2016-12-13 03:22:06 +08:00
    (请原谅我们没有对该服务的日志做 logrotate)

    这才是问题啊……
    log rotation 怎么能不做呢。
    ynyounuo
        9
    ynyounuo  
       2016-12-13 04:27:22 +08:00 via iPhone
    学习了,看到一半猜到了可能导致的原因,但是以前并不清楚也没有在意过。

    特别大的 log 如果有保存需求我都会定期下载下来然后跑 cmix ……
    然后就再也没用过了……
    大概就是为了体验大文件无损变小的快感吧
    M3ng
        10
    M3ng  
       2016-12-13 08:12:09 +08:00 via iPhone
    学习了。
    shakoon
        11
    shakoon  
       2016-12-13 08:30:15 +08:00
    如果用 rm -rf 删,即便文件正在被另一进程 append ,是不是也不会有问题了呢?
    ppwangs
        12
    ppwangs  
       2016-12-13 09:55:14 +08:00
    真的是涨姿势了!~
    adoyle
        13
    adoyle  
       2016-12-13 10:04:40 +08:00
    好文
    BOYPT
        14
    BOYPT  
       2016-12-13 10:09:38 +08:00
    @shakoon 文件依然存在, append 的依然往磁盘写,但是其他进程已经看不到这个文件了。直到写文件进程释放。
    raptium
        15
    raptium  
       2016-12-13 10:11:53 +08:00 via iPhone
    文中提到了 APFS ,不要和 HFS+ 搞混了。
    kiwi95
        16
    kiwi95  
       2016-12-13 11:00:56 +08:00 via Android
    这篇好多了,学习了,以前还真没看这些细节,稀疏文件倒是了解,没用过
    anubu
        17
    anubu  
       2016-12-13 11:20:09 +08:00
    感觉可以继续讨论一下
    稀疏文件在网络传送时的大小及速率?
    不同文件系统间转移同一稀疏文件有何差异?
    iRiven
        18
    iRiven  
       2016-12-13 11:26:15 +08:00 via Android
    看了好久还是不懂 看来是外行人
    lianghh
        19
    lianghh  
       2016-12-13 11:33:51 +08:00
    貌似是 APUE 第四章的内容
    loading
        20
    loading  
       2016-12-13 12:22:51 +08:00 via Android
    我最喜欢的命令:
    sl
    zhy
        21
    zhy  
       2016-12-13 13:21:33 +08:00
    终于不是争论 linux 好不好用的问题了。。
    bingwenshi
        22
    bingwenshi  
       2016-12-13 13:26:10 +08:00
    确实研究的够深入,不错
    UnknownR
        23
    UnknownR  
       2016-12-13 14:05:14 +08:00
    学习了,赶紧做个笔记
    ashin
        24
    ashin  
       2016-12-13 14:33:38 +08:00
    之前遇到过类似的,测试数据库字段被删了,代码没改, supervisor 的 log 很快打爆磁盘,删了 log 要重启 gunicorn 进程才能释放空间
    langmoe
        25
    langmoe  
       2016-12-13 15:15:51 +08:00
    学习了。。
    tl3shi
        26
    tl3shi  
    OP
       2016-12-13 21:31:41 +08:00
    --------
    (请原谅我们没有对该服务的日志做 logrotate)

    这才是问题啊……
    log rotation 怎么能不做呢。
    -----
    @msg7086 前人留下的坑~ 刚开始并不知道木有。

    @raptium APFS v.s HFS+ 感谢指出.


    另外, 看来从其他地方 copy 到 V2EX 还是有用的, 收藏数量不少。
    不过,我主要目的是推广我的微信公众号啊,新文首发微信公众号, 求关注。
    vuuv
        27
    vuuv  
       2016-12-13 21:44:47 +08:00 via Android
    http 不支持稀疏文件,所以会补 0 传输。
    如果有对静态文件启用压缩,可以降低传输量,但是写入的文件会占据 ls 看到的大小。
    可以搜下 http gzip bomb
    hd7771
        28
    hd7771  
       2016-12-14 02:43:27 +08:00
    一直用 linux ,之前虚拟机开了 100G 装 winxp ,结果用了 10 个 G 不到应该就是这个原因。
    以前貌似看过这头像,学长?
    wweir
        29
    wweir  
       2016-12-14 08:05:38 +08:00 via iPhone
    刚看到开头,就猜到后面的二维码了。

    这套路已经看得太多:先无故作死,再查资料、猜,出现眉目却又引入新坑,最后翻翻官方文档给出结论。
    goodryb
        30
    goodryb  
       2016-12-14 10:09:34 +08:00
    @wweir 这不是标准格式吗


    @tl3shi 不过楼主动不动就说不用往下看了,真是不舒服,虽然我看了开头就知道是什么故事
    tl3shi
        31
    tl3shi  
    OP
       2016-12-14 13:14:55 +08:00
    @goodryb 如果知道怎么回事, 确实不用往下看了啊, 多浪费时间啊。 :)

    @wweir 发文的目的, 第一句我就说了。 O(∩_∩)O 哈哈~

    @hd7771 学弟?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3683 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 04:28 · PVG 12:28 · LAX 20:28 · JFK 23:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.