一文让你彻底了解Linux内核文件系统(大总结)

2023-11-01

一,文件系统特点

  • 文件系统要有严格的组织形式,使得文件能够以块为单位进行存储。
  • 文件系统中也要有索引区,用来方便查找一个文件分成的多个块都存放在了什么位置。
  • 如果文件系统中有的文件是热点文件,近期经常被读取和写入,文件系统应该有缓存层。
  • 文件应该用文件夹的形式组织起来,方便管理和查询。
  • Linux内核要在自己的内存里面维护一套数据结构,来保存哪些文件被哪些进程打开和使用。

总体来说,文件系统的主要功能梳理如下:

二,EXT系列的文件系统的格式

2.1,inode与块的存储

硬盘分成相同大小的单元,我们称为块(Block)。一块的大小是扇区大小的整数倍,默认是4K。在格式化的时候,这个值是可以设定的。

一大块硬盘被分成了一个个小的块,用来存放文件的数据部分。这样一来,如果我们像存放一个文件,就不用给他分配一块连续的空间了。我们可以分散成一个个小块进行存放。这样就灵活得多,也比较容易添加、删除和插入数据。

inode就是文件索引的意思,我们每个文件都会对应一个inode;一个文件夹就是一个文件,也对应一个inode。

inode数据结构如下:

struct ext4_inode {
    __le16  i_mode;     /* File mode */
    __le16  i_uid;      /* Low 16 bits of Owner Uid */
    __le32  i_size_lo;  /* Size in bytes */
    __le32  i_atime;    /* Access time */
    __le32  i_ctime;    /* Inode Change time */
    __le32  i_mtime;    /* Modification time */
    __le32  i_dtime;    /* Deletion Time */
    __le16  i_gid;      /* Low 16 bits of Group Id */
    __le16  i_links_count;  /* Links count */
    __le32  i_blocks_lo;    /* Blocks count */
    __le32  i_flags;    /* File flags */
......
    __le32  i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
    __le32  i_generation;   /* File version (for NFS) */
    __le32  i_file_acl_lo;  /* File ACL */
    __le32  i_size_high;
......
};

inode里面有文件的读写权限i_mode,属于哪个用户i_uid,哪个组i_gid,大小是多少i_size_io,占用多少个块i_blocks_io,i_atime是access time,是最近一次访问文件的时间;i_ctime是change time,是最近一次更改inode的时间;i_mtime是modify time,是最近一次更改文件的时间等。

所有的文件都是保存在i_block里面。具体保存规则由EXT4_N_BLOCKS决定,EXT4_N_BLOCKS有如下的定义:

#define    EXT4_NDIR_BLOCKS        12
#define    EXT4_IND_BLOCK          EXT4_NDIR_BLOCKS
#define    EXT4_DIND_BLOCK         (EXT4_IND_BLOCK + 1)
#define    EXT4_TIND_BLOCK         (EXT4_DIND_BLOCK + 1)
#define    EXT4_N_BLOCKS           (EXT4_TIND_BLOCK + 1)

在ext2和ext3中,其中前12项直接保存了块的位置,也就是说,我们可以通过i_block[0-11],直接得到保存文件内容的块。

但是,如果一个文件比较大,12块放不下。当我们用到i_block[12]的时候,就不能直接放数据块的位置了,要不然i_block很快就会用完了。

那么可以让i_block[12]指向一个块,这个块里面不放数据块,而是放数据块的位置,这个块我们称为间接块。如果文件再大一些,i_block[13]会指向一个块,我们可以用二次间接块。二次间接块里面存放了间接块的位置,间接块里面存放了数据块的位置,数据块里面存放的是真正的数据。如果文件再大点,那么i_block[14]同理。

这里面有一个非常显著的问题,对于大文件来讲,我们要多次读取硬盘才能找到相应的块,这样访问速度就会比较慢。

为了解决这个问题,ext4做了一定的改变。它引入了一个新的概念,叫作Extents。比方说,一个文件大小为128M,如果使用4k大小的块进行存储,需要32k个块。如果按照ext2或者ext3那样散着放,数量太大了。但是Extents可以用于存放连续的块,也就是说,我们可以把128M放在一个Extents里面。这样的话,对大文件的读写性能提高了,文件碎片也减少了。

资料直通车:https://docs.qq.com/doc/DTlRYeVVuTFV1UGZk

学习地址:https://ke.qq.com/course/4032547?flowToken=1040236

2.2,Exents是一个树状结构:

每个节点都有一个头,ext4_extent_header可以用来描述某个节点:

struct ext4_extent_header {
    __le16  eh_magic;   /* probably will support different formats */
    __le16  eh_entries; /* number of valid entries */
    __le16  eh_max;     /* capacity of store in entries */
    __le16  eh_depth;   /* has tree real underlying blocks? */
    __le32  eh_generation;  /* generation of the tree */
};

eh_entries表示这个节点里面有多少项。这里的项分两种,如果是叶子节点,这一项会直接指向硬盘上的连续块的地址,我们称为数据节点ext4_extent;如果是分支节点,这一项会指向下一层的分支节点或者叶子节点,我们称为索引节点ext4_extent_idx。这两种类型的项的大小都是12个byte。

/*
 * This is the extent on-disk structure.
 * It's used at the bottom of the tree.
 */
struct ext4_extent {
    __le32  ee_block;   /* first logical block extent covers */
    __le16  ee_len;     /* number of blocks covered by extent */
    __le16  ee_start_hi;    /* high 16 bits of physical block */
    __le32  ee_start_lo;    /* low 32 bits of physical block */
};
/*
 * This is index on-disk structure.
 * It's used at all the levels except the bottom.
 */
struct ext4_extent_idx {
    __le32  ei_block;   /* index covers logical blocks from 'block' */
    __le32  ei_leaf_lo; /* pointer to the physical block of the next *
                 * level. leaf or next index could be there */
    __le16  ei_leaf_hi; /* high 16 bits of physical block */
    __u16   ei_unused;
};

如果文件不大,inode里面的i_block中,可以放得下一个ext4_extent_header和4项ext4_extent。所以这个时候,eh_depth为0,也即inode里面的就是叶子节点,树高度为0。

如果文件比较大,4个extent放不下,就要分裂成为一棵树,eh_depth>0的节点就是索引节点,其中根节点深度最大,在inode中。最底层eh_depth=0的是叶子节点。

除了根节点,其他的节点都保存在一个块4k里面,4k扣除ext4_extent_header的12个byte,剩下的能够放340项,每个extent最大能表示128MB的数据,340个extent会使你的表示的文件达到42.5GB。

2.3,inode位图和块位图

inode的位图大小为4k,每一位对应一个inode。如果是1,表示这个inode已经被用了;如果是0,则表示没被用。block的位图同理。

在Linux操作系统里面,想要创建一个新文件,会调用open函数,并且参数会有O_CREAT。这表示当文件找不到的时候,我们就需要创建一个。那么open函数的调用过程大致是:要打开一个文件,先要根据路径找到文件夹。如果发现文件夹下面没有这个文件,同时又设置了O_CREAT,就说明我们要在这个文件夹下面创建一个文件。

创建一个文件,那么就需要创建一个inode,那么就会从文件系统里面读取inode位图,然后找到下一个为0的inode,就是空闲的inode。对于block位图,在写入文件的时候,也会有这个过程。

2.4,文件系统的格式

数据块的位图是放在一个块里面的,共4k。每位表示一个数据块,共可以表示

个数据块。如果每个数据块也是按默认的4K,最大可以表示空间为

个byte,也就是128M,那么显然是不够的。这个时候就需要用到块组,数据结构为ext4_group_desc,这里面对于一个块组里的inode位图bg_inode_bitmap_lo、块位图bg_block_bitmap_lo、inode列表bg_inode_table_lo,都有相应的成员变量。

这样一个个块组,就基本构成了我们整个文件系统的结构。因为块组有多个,块组描述符也同样组成一个列表,我们把这些称为块组描述符表。

我们还需要有一个数据结构,对整个文件系统的情况进行描述,这个就是超级块ext4_super_block。里面有整个文件系统一共有多少inode,s_inodes_count;一共有多少块,s_blocks_count_lo,每个块组有多少inode,s_inodes_per_group,每个块组有多少块,s_blocks_per_group等。这些都是这类的全局信息。

最终,整个文件系统格式就是下面这个样子

默认情况下,超级块和块组描述符表都有副本保存在每一个块组里面。防止这些数据丢失了,导致整个文件系统都打不开了。

由于如果每个块组里面都保存一份完整的块组描述符表,一方面很浪费空间;另一个方面,由于一个块组最大128M,而块组描述符表里面有多少项,这就限制了有多少个块组,128M * 块组的总数目是整个文件系统的大小,就被限制住了。

因此引入Meta Block Groups特性。

首先,块组描述符表不会保存所有块组的描述符了,而是将块组分成多个组,我们称为元块组(Meta Block Group)。每个元块组里面的块组描述符表仅仅包括自己的,一个元块组包含64个块组,这样一个元块组中的块组描述符表最多64项。

我们假设一共有256个块组,原来是一个整的块组描述符表,里面有256项,要备份就全备份,现在分成4个元块组,每个元块组里面的块组描述符表就只有64项了,这就小多了,而且四个元块组自己备份自己的。

根据图中,每一个元块组包含64个块组,块组描述符表也是64项,备份三份,在元块组的第一个,第二个和最后一个块组的开始处。

如果开启了sparse_super特性,超级块和块组描述符表的副本只会保存在块组索引为0、3、5、7的整数幂里。所以上图的超级块只在索引为0、3、5、7等的整数幂里。

三,目录的存储格式

其实目录本身也是个文件,也有inode。inode里面也是指向一些块。和普通文件不同的是,普通文件的块里面保存的是文件数据,而目录文件的块里面保存的是目录里面一项一项的文件信息。这些信息我们称为ext4_dir_entry。

在目录文件的块中,最简单的保存格式是列表,每一项都会保存这个目录的下一级的文件的文件名和对应的inode,通过这个inode,就能找到真正的文件。第一项是“.”,表示当前目录,第二项是“…”,表示上一级目录,接下来就是一项一项的文件名和inode。

如果在inode中设置EXT4_INDEX_FL标志,那么就表示根据索引查找文件。索引项会维护一个文件名的哈希值和数据块的一个映射关系。

如果我们要查找一个目录下面的文件名,可以通过名称取哈希。如果哈希能够匹配上,就说明这个文件的信息在相应的块里面。然后打开这个块,如果里面不再是索引,而是索引树的叶子节点的话,那里面还是ext4_dir_entry的列表,我们只要一项一项找文件名就行。通过索引树,我们可以将一个目录下面的N多的文件分散到很多的块里面,可以很快地进行查找。

四,Linux中的文件缓存

4.1ext4文件系统层

对于ext4文件系统来讲,内核定义了一个ext4_file_operations

const struct file_operations ext4_file_operations = {
......
    .read_iter  = ext4_file_read_iter,
    .write_iter = ext4_file_write_iter,
......
}

ext4_file_read_iter会调用generic_file_read_iter,ext4_file_write_iter会调用__generic_file_write_iter

ssize_t
generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
......
    if (iocb->ki_flags & IOCB_DIRECT) {
......
        struct address_space *mapping = file->f_mapping;
......
        retval = mapping->a_ops->direct_IO(iocb, iter);
    }
......
    retval = generic_file_buffered_read(iocb, iter, retval);
}


ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
......
    if (iocb->ki_flags & IOCB_DIRECT) {
......
        written = generic_file_direct_write(iocb, from);
......
    } else {
......
        written = generic_perform_write(file, from, iocb->ki_pos);
......
    }
}

generic_file_read_iter和__generic_file_write_iter有相似的逻辑,就是要区分是否用缓存。因此,根据是否使用内存做缓存,我们可以把文件的I/O操作分为两种类型。

第一种类型是缓存I/O。大多数文件系统的默认I/O操作都是缓存I/O。对于读操作来讲,操作系统会先检查,内核的缓冲区有没有需要的数据。如果已经缓存了,那就直接从缓存中返回;否则从磁盘中读取,然后缓存在操作系统的缓存中。对于写操作来讲,操作系统会先将数据从用户空间复制到内核空间的缓存中。这时对用户程序来说,写操作就已经完成。至于什么时候再写到磁盘中由操作系统决定,除非显式地调用了sync同步命令。

第二种类型是直接IO,就是应用程序直接访问磁盘数据,而不经过内核缓冲区,从而减少了在内核缓存和用户程序之间数据复制。

如果在写的逻辑__generic_file_write_iter里面,发现设置了IOCB_DIRECT,则调用generic_file_direct_write,里面同样会调用address_space的direct_IO的函数,将数据直接写入硬盘。

带缓存的写入操作

我们先来看带缓存写入的函数generic_perform_write。

ssize_t generic_perform_write(struct file *file,
                struct iov_iter *i, loff_t pos)
{
    struct address_space *mapping = file->f_mapping;
    const struct address_space_operations *a_ops = mapping->a_ops;
    do {
        struct page *page;
        unsigned long offset;   /* Offset into pagecache page */
        unsigned long bytes;    /* Bytes to write to page */
        status = a_ops->write_begin(file, mapping, pos, bytes, flags,
                        &page, &fsdata);
        copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
        flush_dcache_page(page);
        status = a_ops->write_end(file, mapping, pos, bytes, copied,
                        page, fsdata);
        pos += copied;
        written += copied;


        balance_dirty_pages_ratelimited(mapping);
    } while (iov_iter_count(i));
}

循环中主要做了这几件事:

  • 对于每一页,先调用address_space的write_begin做一些准备;
  • 调用iov_iter_copy_from_user_atomic,将写入的内容从用户态拷贝到内核态的页中;
  • 调用address_space的write_end完成写操作;
  • 调用balance_dirty_pages_ratelimited,看脏页是否太多,需要写回硬盘。所谓脏页,就是写入到缓存,但是还没有写入到硬盘的页面。

对于第一步,调用的是ext4_write_begin来说,主要做两件事:

第一做日志相关的工作

ext4是一种日志文件系统,是为了防止突然断电的时候的数据丢失,引入了日志(Journal)模式。日志文件系统比非日志文件系统多了一个Journal区域。文件在ext4中分两部分存储,一部分是文件的元数据,另一部分是数据。元数据和数据的操作日志Journal也是分开管理的。你可以在挂载ext4的时候,选择Journal模式。这种模式在将数据写入文件系统前,必须等待元数据和数据的日志已经落盘才能发挥作用。这样性能比较差,但是最安全。

另一种模式是order模式。这个模式不记录数据的日志,只记录元数据的日志,但是在写元数据的日志前,必须先确保数据已经落盘。这个折中,是默认模式。

还有一种模式是writeback,不记录数据的日志,仅记录元数据的日志,并且不保证数据比元数据先落盘。这个性能最好,但是最不安全。

第二调用
grab_cache_page_write_begin来,得到应该写入的缓存页。

struct page *grab_cache_page_write_begin(struct address_space *mapping,
                    pgoff_t index, unsigned flags)
{
    struct page *page;
    int fgp_flags = FGP_LOCK|FGP_WRITE|FGP_CREAT;
    page = pagecache_get_page(mapping, index, fgp_flags,
            mapping_gfp_mask(mapping));
    if (page)
        wait_for_stable_page(page);
    return page;
}

在内核中,缓存以页为单位放在内存里面,每一个打开的文件都有一个struct file结构,每个struct file结构都有一个struct address_space用于关联文件和内存,就是在这个结构里面,有一棵树,用于保存所有与这个文件相关的的缓存页。

对于第二步,调用

iov_iter_copy_from_user_atomic。先将分配好的页面调用kmap_atomic映射到内核里面的一个虚拟地址,然后将用户态的数据拷贝到内核态的页面的虚拟地址中,调用kunmap_atomic把内核里面的映射删除。

size_t iov_iter_copy_from_user_atomic(struct page *page,
        struct iov_iter *i, unsigned long offset, size_t bytes)
{
    char *kaddr = kmap_atomic(page), *p = kaddr + offset;
    iterate_all_kinds(i, bytes, v,
        copyin((p += v.iov_len) - v.iov_len, v.iov_base, v.iov_len),
        memcpy_from_page((p += v.bv_len) - v.bv_len, v.bv_page,
                 v.bv_offset, v.bv_len),
        memcpy((p += v.iov_len) - v.iov_len, v.iov_base, v.iov_len)
    )
    kunmap_atomic(kaddr);
    return bytes;
}

第三步中,调用ext4_write_end完成写入。这里面会调用ext4_journal_stop完成日志的写入,会调用block_write_end->__block_commit_write->mark_buffer_dirty,将修改过的缓存标记为脏页。可以看出,其实所谓的完成写入,并没有真正写入硬盘,仅仅是写入缓存后,标记为脏页。

第四步,调用

balance_dirty_pages_ratelimited,是回写脏页

/**
 * balance_dirty_pages_ratelimited - balance dirty memory state
 * @mapping: address_space which was dirtied
 *
 * Processes which are dirtying memory should call in here once for each page
 * which was newly dirtied.  The function will periodically check the system's
 * dirty state and will initiate writeback if needed.
  */
void balance_dirty_pages_ratelimited(struct address_space *mapping)
{
    struct inode *inode = mapping->host;
    struct backing_dev_info *bdi = inode_to_bdi(inode);
    struct bdi_writeback *wb = NULL;
    int ratelimit;
......
    if (unlikely(current->nr_dirtied >= ratelimit))
        balance_dirty_pages(mapping, wb, current->nr_dirtied);
......
}

在balance_dirty_pages_ratelimited里面,发现脏页的数目超过了规定的数目,就调用balance_dirty_pages->wb_start_background_writeback,启动一个背后线程开始回写。

另外还有几种场景也会触发回写:

  • 用户主动调用sync,将缓存刷到硬盘上去,最终会调用wakeup_flusher_threads,同步脏页;
  • 当内存十分紧张,以至于无法分配页面的时候,会调用free_more_memory,最终会调用wakeup_flusher_threads,释放脏页;
  • 脏页已经更新了较长时间,时间上超过了设定时间,需要及时回写,保持内存和磁盘上数据一致性。

4.2,带缓存的读操作

看带缓存的读,对应的是函数generic_file_buffered_read。

static ssize_t generic_file_buffered_read(struct kiocb *iocb,
        struct iov_iter *iter, ssize_t written)
{
    struct file *filp = iocb->ki_filp;
    struct address_space *mapping = filp->f_mapping;
    struct inode *inode = mapping->host;
    for (;;) {
        struct page *page;
        pgoff_t end_index;
        loff_t isize;
        page = find_get_page(mapping, index);
        if (!page) {
            if (iocb->ki_flags & IOCB_NOWAIT)
                goto would_block;
            page_cache_sync_readahead(mapping,
                    ra, filp,
                    index, last_index - index);
            page = find_get_page(mapping, index);
            if (unlikely(page == NULL))
                goto no_cached_page;
        }
        if (PageReadahead(page)) {
            page_cache_async_readahead(mapping,
                    ra, filp, page,
                    index, last_index - index);
        }
        /*
         * Ok, we have the page, and it's up-to-date, so
         * now we can copy it to user space...
         */
        ret = copy_page_to_iter(page, offset, nr, iter);
    }
}

在generic_file_buffered_read函数中,我们需要先找到page cache里面是否有缓存页。如果没有找到,不但读取这一页,还要进行预读,这需要在page_cache_sync_readahead函数中实现。预读完了以后,再试一把查找缓存页。

如果第一次找缓存页就找到了,我们还是要判断,是不是应该继续预读;如果需要,就调用
page_cache_async_readahead发起一个异步预读。

最后,copy_page_to_iter会将内容从内核缓存页拷贝到用户内存空间。


好文推荐:

2022年嵌入式开发想进互联网大厂,你技术过硬吗?

从事十年嵌入式转内核开发(23K到45K),给兄弟们的一些建议

腾讯T6-9首发“Linux内核源码嵌入式开发进阶笔记”,差距不止一点点哦

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文让你彻底了解Linux内核文件系统(大总结) 的相关文章

  • Mono 和 WebRequest 速度 - 测试

    在 mono 4 6 2 linux 中 我注意到 wget 下载文件的速度与webclient DownloadString 所以我做了一个小测试来调查 为什么 wget 明显比 C 快 根据我自己的实验 使用 wget 下载 手动读取文
  • 跟踪 pthread 调度

    我想做的是创建某种图表 详细说明 Linux 中 两个 线程的执行情况 我不需要查看线程的作用 只需查看它们何时被安排以及持续多长时间 基本上是一条时间线 在过去的几个小时里 我一直在互联网上搜索跟踪 pthread 调度的方法 不幸的是
  • jpackage linux 创建的桌面文件不足

    我刚刚开始使用 jpackage 它是一个非常棒的工具 只要迈出一步 我的肩上的工作就减轻了很多 我对看起来硬编码且无法定制的东西越感到惊讶 JPackage 自动生成启动器 lib
  • 使用 C++ 输出字符串覆盖 Linux 终端上的最后一个字符串

    假设我有一个命令行程序 有没有办法让我说的时候 std cout lt lt stuff 如果我不做std cout lt lt n 在另一个之间std cout lt lt stuff 东西的另一个输出将覆盖同一行上的最后一个东西 清理行
  • 无法为 Python 3.4 创建工作虚拟环境

    I 安装Python 3 4 2 https docs python org 3 using unix html building python和我的 Linux Mint 17 1 中的 Virtualenv 12 0 5 然后我尝试创建
  • 如何使用libaudit?

    我试图了解如何使用 libaudit 我想接收有关使用 C C 的用户操作的事件 我不明白如何设置规则 以及如何获取有关用户操作的信息 例如 我想获取用户创建目录时的信息 int audit fd audit open struct aud
  • 使用 sed 将 old-link-url 替换为 new-link-url

    我正在 bash 中编写一个脚本 将 old link url 替换为 new link url 我的问题是 sed 由于斜杠而无法替换 url 如果我只输入一些文字就可以了 my code sed e s old link new lin
  • 原生 Linux 应用程序可像 ResHacker 一样编辑 Win32 PE

    我想运行自动修改 dll服务 用户提交特定的 dll 我在服务器上修改它 然后用户可以下载 dll的修改版本 是否有任何本机 Linux 应用程序提供常见的 Win32 PE 修改功能 例如图标 字符串 加速器 对话等 至少提供命令行或脚本
  • 将 stdout 作为命令行 util 的文件名传递?

    我正在使用一个命令行实用程序 该实用程序需要传递文件名以将输出写入 例如 foo o output txt 它唯一写入的东西stdout是一条消息 表明它运行成功 我希望能够通过管道传输写入的所有内容output txt到另一个命令行实用程
  • 如何从“git log”中查看 Git 中的特定版本?

    My git log显示为 enter code here git trial git log commit 4c5bc66ae50780cf8dcaf032da98422aea6e2cf7 Author king lt email pro
  • 如何在两个不同帐户之间设置无密码身份验证

    我们可以在两台机器的两种不同用途之间设置无密码身份验证吗 例如 计算机A有用户A 计算机B有用户B 我们可以设置密码 ssh 以便计算机 A 上的用户 A 使用其用户帐户 A 登录计算机 B 谢谢你 如果我理解你的问题 你能设置一下吗ssh
  • 如何仅将整个嵌套目录中的头文件复制到另一个目录,在复制到新文件夹后保持相同的层次结构

    我有一个目录 其中有很多头文件 h 和其他 o 和 c 文件以及其他文件 这个目录里面有很多嵌套的目录 我只想将头文件复制到一个单独的目录 并在新目录中保留相同的结构 cp rf oldDirectory newDirectory将复制所有
  • Windows 与 Linux 文本文件读取

    问题是 我最近从 Windows 切换到 Ubuntu 我的一些用于分析数据文件的 python 脚本给了我错误 我不确定如何正确解决 我当前仪器的数据文件输出如下 Header 有关仪器等的各种信息 Data 状态 代码 温度 字段等 0
  • 为什么 ld 无法从 /etc/ld.so.conf 中的路径找到库?

    我想添加 opt vertica lib64进入系统库路径 所以我执行以下步骤 1 添加 opt vertica lib64 into etc ld so conf 然后运行ldconfig 2 检查 bash ldconfig p gre
  • Bash 脚本 - 迭代 find 的输出

    我有一个 bash 脚本 其中需要迭代 find 命令输出的每一行 但似乎我正在迭代 find 命令中的每个单词 以空格分隔 到目前为止我的脚本看起来像这样 folders find maxdepth 1 type d for i in f
  • 无关的库链接

    我有一个可能有点愚蠢的问题 因为我很确定我可能已经知道答案了 假设你有静态库A 动态共享库B和你的linux下的程序C 假设库 A 调用库 B 中的函数 并且您的程序调用库 A 中的函数 现在假设 C 在 A 中调用的所有函数都不使用 B
  • 如何通过代理将套接字连接到http服务器?

    最近 我使用 C 语言编写了一个程序 用于连接到本地运行的 HTTP 服务器 从而向该服务器发出请求 这对我来说效果很好 之后 我尝试使用相同的代码连接到网络上的另一台服务器 例如 www google com 但我无法连接并从网络中的代理
  • CentOS目录结构是树形的吗?

    CentOS 上有相当于树的东西吗 如果你的 Centos 系统上没有安装 tree 无论如何我通常建议服务器设置使用最小安装磁盘 你应该在命令行中输入以下内容 yum install tree y 如果没有安装 那是因为您没有正确的存储库
  • 无法仅在控制台中启动 androidstudio

    你好 我的问题是下一个 我下载了Android Studio如果我去 路径 android studio bin 我执行studio sh 我收到以下错误 No JDK found Please validate either STUDIO
  • 如何查明 Ubuntu 上安装了哪个版本的 GTK+?

    我需要确定 Ubuntu 上安装了哪个版本的 GTK 男人似乎不帮忙 这个建议 https stackoverflow com a 126145 会告诉您安装了哪个 2 0 的次要版本 不同的主要版本将具有不同的包名称 因为它们可以在系统上

随机推荐

  • spring的定时任务@Scheduled简单实用

    方式一 使用注解 Component EnableScheduling 可以在启动类上注解也可以在当前文件 public class TestJob Scheduled cron 0 10 public void runfirst Syst
  • 正则表达式匹配不包含某些字符串的技巧

    经常我们会遇到想找出不包含某个字符串的文本 程序员最容易想到的是在正则表达式里使用 hede 来过滤 hede 字串 但这种写法是错误的 我们可以这样写 hede 但这样的正则表达式完全是另外一个意思 它的意思是字符串里不能包含 h e d
  • 【深度学习】 Python 和 NumPy 系列教程(廿五):Matplotlib详解:3、多子图和布局:subplot()函数

    目录 一 前言 二 实验环境 三 Matplotlib详解 1 2d绘图类型 2 3d绘图类型 3 多子图和布局 1 subplot 函数 简单示例 一 前言 Python是一种高级编程语言 由Guido van Rossum于1991年创
  • Blender安装Babylon插件

    参考链接 https doc babylonjs com extensions Exporters Blender 安装步骤图示 首先去这个网站下载此文件 https github com BabylonJS BlenderExporter
  • 区块链安全————区块链技术安全讨论

    0x00 背景介绍 区块链技术是金融科技 Fintech 领域的一项重要技术创新 作为分布式记账 Distributed Ledger Technology DLT 平台的核心技术 区块链被认为在金融 征信 物联网 经济贸易结算 资产管理等
  • 浏览器出现无法访问此页面的提示的解决办法

    部分地区与网络会出现该问题 本人查询论坛后找到的有效解决办法为 控制面板 网络和internet internet选项 连接 局域网设置 在 为LAN使用代理服务器 这一栏打上勾 点击应用退出 刷新一下就可以 下来也有可能是hosts文件里
  • Kotlin高阶函数概念

    一 高阶函数的基本概念 1 传入或者返回函数的函数 传入是函数 返回也是函数 2 函数引用最常见的方式 println 3 带有接收者Receiver的引用pdfPrinter println 二 看一下入门的例子 package net
  • 腾讯员工收入曝光,我顿悟了一个成人世界的残酷事实

    最近 一张腾讯员工的收入证明火了 收入证明上显示 这位员工的职位是腾讯 成都 某游戏客户端开发 已入职9年 而在他的税后年收入那一栏 显示着251多万元 包括工资 奖金和津贴等 平均月收入约20万 算下来 税前大概是450万 这张图在网上流
  • android壁纸显示逻辑

    所有文章仅限自己备忘 并无他用 壁纸主要分为两类 锁屏壁纸和桌面壁纸 一 壁纸服务的启动 壁纸服务WallpaperManagerService中 有一个内部类LifeCycle继承自SystemService SystemServer在启
  • 数据结构——C++中实现栈链(含完整代码)

    栈链相关代码 1 向栈顶插入元素 2 删除栈顶元素 3 判断栈是否为空 4 读取栈顶元素 0 退出程序 栈其实就是一个特殊的线性表 输入输出只能在一端 基于这一特性完成栈链的相关操作 注意事项 由于插入和删除操作只可以在一端 链表头部 所以
  • Atcoder Beginner Contest 044

    C C Tak and Cards 我一开始想的是先从小到大排个序 然后分情况 先从左往右一个数一个数枚举 如果等于ave 1 就res 如果大于ave 1 就说明1个数的没有了 然后从左到右两个数两个数枚举 如果等于ave 2 就res
  • 游戏外挂怎么做?

    文章目录 1 什么是游戏外挂 2 外挂的分类及实现原理 2 1 辅助类外挂 2 2 专用插件类外挂 2 3 通用工具 2 4 内存修改器 2 5 变速器 2 6 按键精灵 2 7 模拟器 2 8 破解版 转载自 Anti Cheat Exp
  • java TRC20

    直接上代码 创建地址 离线 private static SecureRandom random new SecureRandom 具体方法 public static Map
  • 15-数据结构-二叉树的遍历,递归和非递归

    简介 本文主要是代码实现 二叉树遍历 递归和非递归 用栈 主要为了好理解 直接在代码处 加了详细注释 方便复习和后期默写 主要了解其基本思想 为后期熟练应用打基础 遍历的意义 就是为了实现在二叉树上 进行各种操作 给每个结点都光顾到位 到根
  • C语言判断是否到达文件末尾

    血的教训 判断文件是否读到末尾的时候 使用 while fgets 不要用 while feof fgets 不然回车符弄死人
  • Pixhawk之姿态解算篇(2)_mahony算法分析

    一 开篇 还是没能进入到源码部分研究 对姿态解算过程太过于模糊 所以主要开始研究一下关于姿态解算的过程和实现 本篇博文主要是以mahony的算法为基础理解姿态解算的过程 主要参考的论文就是William Premerlani and Pau
  • linux创建文件的方法

    linux创建文件可以通过以下六个方式来完成 gt 标准重定向符允许我们创建一个 0KB 的空文件 touch 如果文件不存在的话 touch 命令将会创建一个 0KB 的空文件 echo 通过一个参数显示文本的某行 printf 用于显示
  • python基础学习--基础

    一 基础知识 1 1 基础语法及编码规范 1 定义变量加if Python 通常是一行写完一条语句 但如果语句很长 我们可以使用反斜杠 来实现多行语句sad total item one item two item three item f
  • 二极管(一):反向恢复时间

    一 反向恢复过程 在图1所示的二极管电路中 加入一个如图2所示的输入电压 即在 0 时间内 输入为 二极管导通 电路中有电流流过 假设二极管的正向压降为 当 远大于 时 可忽略不计 如果在 时刻 输入 突然从 变为 在理想情况下 二极管将即
  • 一文让你彻底了解Linux内核文件系统(大总结)

    一 文件系统特点 文件系统要有严格的组织形式 使得文件能够以块为单位进行存储 文件系统中也要有索引区 用来方便查找一个文件分成的多个块都存放在了什么位置 如果文件系统中有的文件是热点文件 近期经常被读取和写入 文件系统应该有缓存层 文件应该