zoukankan      html  css  js  c++  java
  • 由unix socket想到的部分文件系统问题

    一、unix socket
    这种套接口感觉在文件系统和套接口中都是一种异类,就好像蝙蝠是兽中的鸟、鸟中的兽一样。它的特点在于一个地址是否被占用是通过一个文件是否存在来确定。这其实是一个比较危险的操作,因为这个文件的存在是持久性创建的文件,会给底层的文件系统造成持久的影响。现在假设说一个程序非正常结束,例如遇到段错误、被管理员通过SIGKILL杀死等原因而异常退出,那么在下次启动的时候,这个文件还是存在的,这就意味着当前即使没有任何进程在使用(侦听)这个套接口,依然没有任何程序可以使用这个地址。
    通常程序在使用相同的unix套接口的时候,都会在bind该套接口之前对套接口执行unlink来删除这个文件,这一点在syslog-ng和fastcgi的实现模块中都是这么做的。
    迄今为止,还是没有什么问题。但是对于syslog-ng来说,按照linux下syslog函数的实现规则,它们都是打印到/dev/log套接口文件中,然后系统的日志管理工具来侦听这个套接口。同样滴、在执行bind功能之前,日志工具会删除这个文件,然后自己bind的时候在创建这个文件。迄今为止、还是没有问题。同一个系统可能使用多个不同系统日志收集工具,例如syslogd、rsyslogd、syslog-ng,按照syslog的协议,它们都会尝试在/dev/log文件上进行侦听,为了避免绑定失败,它们都会在bind前删除文件。根据linux文件系统的惯例,如果一个文件正在被使用,那么删除是可以的,但是它只是从内存中删除,而不会通过文件系统立即删除。所以每个日志工具启动的时候,之前的日志管理工具同样还是使用之前自己绑定的/dev/log文件,而新的日志工具在unlink之后通过bind再次创建一个新的/dev/log设备文件。
    这看起来也没有什么问题。
    在C库的syslog实现中,它会在openlog的时候打开这个侦听的套接口,之后每次执行syslog的时候都是使用这个已经打开的文件进行发送操作,只要客户端不重启,它就一直使用第一次打开时使用的文件描述符。同样只要之前的系统日志工具没有重启,它们之间的连接就会一直有效,而此后新启动的任务将会使用新的侦听进程。这就会出现日志记录不一致的问题。
    为了说明这个问题,看一下我的系统的一个输出:
    [root@Harry bash-4.1]# ps aux | grep syslog
    root      5090  0.0  0.0   4220   692 pts/1    S+   22:48   0:00 grep syslog
    root     22276  0.0  0.1  45688  1244 ?        Sl   18:16   0:01 rsyslogd
    root     23186  0.0  0.0   3564   712 ?        S    18:21   0:00 supervising syslog-ng                                              
    root     23187  0.0  0.2   7452  2668 ?        Ss   18:21   0:00 /home/tsecer/Downloads/syslog-ng-3.2.3/syslog-ng/.libs/lt-syslog-ng
    root     23303  0.0  0.0   3564   704 ?        S    18:22   0:00 supervising syslog-ng                                              
    root     23304  0.0  0.2   7452  2660 ?        Ss   18:22   0:00 /home/tsecer/Downloads/syslog-ng-3.2.3/syslog-ng/.libs/lt-syslog-ng
    root     27058  0.0  0.0   3564   712 ?        S    18:44   0:00 supervising syslog-ng                                              
    root     27059  0.0  0.2   7452  2668 ?        Ss   18:44   0:00 /home/tsecer/Downloads/syslog-ng-3.2.3/syslog-ng/.libs/lt-syslog-ng
    root     27199  0.0  1.5  28580 16008 pts/5    S+   18:44   0:00 gdb /home/tsecer/Downloads/syslog-ng-3.2.3/syslog-ng/.libs/lt-syslog-ng
    root     27233  0.0  0.0      0     0 ?        Z    18:45   0:00 [lt-syslog-ng] <defunct>
    root     27265  0.0  0.0   3564   576 ?        S    18:45   0:00 supervising syslog-ng                                              
    root     27266  0.0  0.2   7452  2644 ?        Ss   18:45   0:00 /home/tsecer/Downloads/syslog-ng-3.2.3/syslog-ng/.libs/lt-syslog-ng
    [root@Harry bash-4.1]# netstat -anp | grep /dev/log
    unix  2      [ ]         DGRAM                    830241 27266/lt-syslog-ng  /dev/log
    unix  3      [ ]         DGRAM                    917054 22276/rsyslogd      /dev/log
    unix  2      [ ]         DGRAM                    829315 27059/lt-syslog-ng  /dev/log
    unix  2      [ ]         DGRAM                    815520 23304/lt-syslog-ng  /dev/log
    unix  2      [ ]         DGRAM                    815017 23187/lt-syslog-ng  /dev/log
    [root@Harry bash-4.1]# 
    这里可以看到,对于同一个/dev/log文件,其中对应了六个不同的inode编号,也就是说系统的内存中当前存在6个不同的进程在侦听不同的系统日志输入。
    二、文件延迟删除可能导致的一致性问题
    linux中正在使用的文件可以被删除,解决可能一些流氓程序长运行而拒绝被杀死的问题,但是这样的操作可能带来文件系统的不一致。和内存泄露一样,可能会存在inode的泄露。在文件系统中,目录中的目录项dentry就相当于指针,而inode相当于指向的数据结构头,文件内容则相当于数据体。在用文件被删除的时候,相当于该文件在文件系统中的dentry会被优先删除,删除之后该文件就从文件系统中消失,从而可以在文件系统中相同文件夹创建新的同名文件。而该文件使用的inode在磁盘上依然处于占用状态,以保证文件的真正内容在磁盘上一直存在到文件使用周期结束。
    假设在这个时候文件系统发生断电,此时文件系统就可能出现不一致的情况。因为文件已经被从文件系统中删除,inode没有,相当于出现了文件泄露,inode本身在磁盘上占据的空间并不多,但是文件本身可能占用了大量的磁盘空间,再加上如果这样的文件很多,那么文件系统就相当于浪费了大量的存储空间。
    为了验证这个问题,我在一个minix文件系统中进行了测试。为了使用这么不常见的文件系统呢?当然不是怀旧,更不是为了装那啥,而是在busybox中自带的mkfs中没有ext2的格式化工具,只有dos和minix的,由于ext文件系统是基于minix基础上开发的,所以两者本质上是一样的。

     
     
    由unix socket想到的部分文件系统问题 - Tsecer - Tsecer的回音岛
     其中minix是一个干净的minix分区,然后把busybox拷贝到该文件夹下,后台执行sleep命令以确保该文件长期运行,然后从磁盘中删除该可执行文件。可以看到,删除文件之后,文件系统中的inode使用数量并没有减少,也就是说可执行文件的索引已经从硬盘中删除,而inode没有回收,此时如果断电系统,那么这个不一致将会一直保存。
    之后我断电(不是重启)了系统,然后重启,再次挂载该文件系统的时候,minix系统会有下面提示:
    MINIX-fs: mounting unchecked file system,running fsck is recommended
    也就是说文件系统并没有默默的接收这一错误,而是在挂载文件的时候给出了善意的提示。看了一下ext2文件系统的实现,同样的操作步骤也会造成ext的这样提示。
    三、文件系统如何做到这种检测
    搜索一下内核中关于这个提示的位置,其位置位于
    static int minix_fill_super(struct super_block *s, void *data, int silent)
        if (!(s->s_flags & MS_RDONLY)) {
            if (sbi->s_version != MINIX_V3) /* s_state is now out from V3 sb */
                ms->s_state &= ~MINIX_VALID_FS;如果不是以只读形式挂载的文件系统,则在文件系统挂载的时候将硬盘超级块的状态在内存中复制一份,然后无条件清除硬盘中超级块的状态为不一致状态
            mark_buffer_dirty(bh);
        }
        if (!(sbi->s_mount_state & MINIX_VALID_FS))如果硬盘中上次保存的状态为不一致状态,给出提示
            printk("MINIX-fs: mounting unchecked file system, "
                "running fsck is recommended ");
         else if (sbi->s_mount_state & MINIX_ERROR_FS)
            printk("MINIX-fs: mounting file system with errors, "
                "running fsck is recommended ");
    那么硬盘中的这种状态是在什么时候清除的呢?对应的(不是明显地)清除是在超级块的删除,也就是文件系统的卸载中修改为一致状态。
    static void minix_put_super(struct super_block *sb)

        int i;
        struct minix_sb_info *sbi = minix_sb(sb);

        if (!(sb->s_flags & MS_RDONLY)) {
            if (sbi->s_version != MINIX_V3)     /* s_state is now out from V3 sb */
                sbi->s_ms->s_state = sbi->s_mount_state;
            mark_buffer_dirty(sbi->s_sbh);
        }
    在卸载文件系统的时候,挂载前文件系统中保存的状态将会被重新写回到硬盘中。由于新的硬盘在格式化之后是一致状态,如果之后每次挂载和卸载一一对应,那么任意多次挂在和卸载之后,文件系统的状态应该和起始状态一致,那就是出于合法状态。但是如果某此挂载之后没有正常卸载,那么之后硬盘中就会记录这种不一致状态。
    四、df如何实现
    通过strace可以看到,df是通过fstatfs工具来获得文件系统的特征的,包括inode的总量和在用量。这里需要提醒的是,这个df是disk filesystem的意思,而不是disk format,所以大家不用担心这个工具会格式化分区。
    minix文件系统的底层实现比较简单
    static int minix_statfs(struct dentry *dentry, struct kstatfs *buf)
    {
        struct minix_sb_info *sbi = minix_sb(dentry->d_sb);
        buf->f_type = dentry->d_sb->s_magic;
        buf->f_bsize = dentry->d_sb->s_blocksize;
        buf->f_blocks = (sbi->s_nzones - sbi->s_firstdatazone) << sbi->s_log_zone_size;
        buf->f_bfree = minix_count_free_blocks(sbi);
        buf->f_bavail = buf->f_bfree;
        buf->f_files = sbi->s_ninodes;
        buf->f_ffree = minix_count_free_inodes(sbi);
        buf->f_namelen = sbi->s_namelen;
        return 0;
    }
    五、简单流程整理
    1、文件删除是目录项删除
    ext2_unlink--->>ext2_delete_entry
        if (pde)
            from = (char*)pde - (char*)page_address(page);
        lock_page(page);
        err = mapping->a_ops->prepare_write(NULL, page, from, to);
        BUG_ON(err);
        if (pde)
            pde->rec_len = cpu_to_le16(to-from);dentry内容被合并入前一dentry中
        dir->inode = 0;应用的inode清零
    此时在iput的时候文件应用不为零,所以文件不能删除。
    2、文件删除
    当使用该文件的进程退出之后,关闭文件描述符,内存inode引用为零,并且nlink为零,所以需要从文件系统中删除该文件,大致流程为
    minix_delete_inode--->>minix_free_inode
        bh = sbi->s_imap[ino];
        lock_kernel();
        if (!minix_test_and_clear_bit(bit, bh->b_data))
            printk("minix_free_inode: bit %lu already cleared ", bit);
    其中sbi->imap就是statfs中minix_count_free_inodes中扫描的数据结构位图。
  • 相关阅读:
    汇编Ring 3下实现 HOOK API
    软件调试之INT 3讲解
    Delphi逆向
    XoftSpy 4.13的注册算法分析
    反调试技术揭秘
    jmp && call && ret 特权级转移 & 进程调度
    PHP Warning: Module 'modulename' already loaded in Unknown on line 0
    PhpStorm和PHPstudy配置调试参数(Xdebug),问题描述Error. Interpreter is not specified or invalid. Press “Fix” to edit your project configuration.
    php 安装xdebug进行调试(phpstorm)
    Windows下PHP多线程扩展pthreads的安装
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487447.html
Copyright © 2011-2022 走看看