目录

  • 引言
  • Union File System
    • 定义
    • 那么这么做有什么好处?
  • overlay2运作方式
  • overlay2实操
    • 修改lowerdir的文件
    • 删除lowerdir的文件
  • overlay2与Docker
  • overlay和overlay2
    • 为什么说overlay比overlay2消耗更多的inode
  • 参考

引言

之前大一看《鸟哥的Linux私房菜》时,了解到mount命令。

大概是这么个意思:将一个块设备挂载到目录上,使得访问该目录时相当于访问该设备,挂载时,原目录的内容将暂时被隐藏,而不会消失。看来Linux的虚拟文件系统还是很“海纳百川”的嘛。

那么下面也说说我这一年对mount命令基础用法的一些实践:

  • 插U盘时,我们可以整一些小花样,不通过Linux的一些图形化文件管理器打开它时(使用这些管理器时实际上会自动帮我们挂载到run目录),通过lsblk命令查看块设备,再手动挂载到VFS上。
  • 修复grub时,为了让修复的是宿主机的grub,需要将宿主机的各个目录挂载上来(其实我们开机时,内核也会自动帮我们挂载/etc/fstab的内容),再chroot进去,对grub进行修复。
  • 之后还了解到mount和loop设备的用法,使我们对待普通文件也可以像对待块设备那样,也觉得很奇妙。

最近在学习docker的实现时看到这么一个概念:Union File System,先让我们来介绍介绍它。

Union File System

定义

让我们先来看看它的介绍:

联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。

主要有两个细节:

  • 可以将不同目录挂载到同一个虚拟文件系统下:
    这就意味着一个文件系统被挂载时不再只能有一个目录下的内容,而是多个。
  • 支持对文件系统的修改作为一次提交来一层层的叠加:
    这点其实有一点像git的工作方式,每次的commit就相当于一次增量,上层的commit由下层的一层层commit,以增量的方式组织在一起。

那么这么做有什么好处?

我们知道,镜像可以理解为容器的模板,一套镜像可以衍生出多个容器。

那么多个容器势必有相同的镜像层,如果我们每次拷贝一份,那么对存储空间的要求的相当大的,并且也不利于后续的整合和发布,因为这样意味着当别人需要你的容器时,你commit的就是一整份镜像,这好比使用git时,当你需要获取origin的更新资源时,需要pull一整份代码下来。

因此,通过镜像层增量的方式来组织,使不同的docker容器可以共享相同的镜像层,再加上自己的改动进行发布。

而docker就支持多种UnionFS,如aufs、overlay2等等。

overlay2运作方式

overlay2主要由merged、lowerdir、upperdir、workdir组成。

其中,lowerdir对应底层文件系统,也就是那一层层“commit”的内容,它是能被上层文件系统upperdir所共享的只读层。workdir则可以理解为overlay2运作的一个工作目录,用于完成copy-on-write等操作。copy-on-write这点我们会在实操中谈到它。

overlay2运作时,会将lowerdir、upperdir和workdir联合挂载到merged目录,为使用者提供一个“统一视图”。
这张其实是docker官网中的overlay的原理示意图,不过也能帮助我们理解overlay2的运作方式。这里要注意的是,overlay2中lowerdir可以有很多层,这里只画了一层,我们可以脑补它为很多层。overlay和overlay2的区别可以参考下面。

那么当我们想删除或者修改lowerdir中内容时,overlay2是如何处理的?不是说它是只读层吗,只读意味着无法修改,但我们又确实可能需要修改它,那lowerdir的出现都无法满足我们的使用需求了吗?

下面一起实操来看看overlay2是怎么解决这些问题的吧。

overlay2实操

下面是man mount中对overlay2的描述。

Mount options for overlaySince Linux 3.18 the overlay pseudo filesystem implements a union mount for other filesystems.An overlay filesystem combines two filesystems - an upper filesystem and a lower filesystem.  When a name exists in both filesystems, theobject  in  the  upper  filesystem  is  visible while the object in the lower filesystem is either hidden or, in the case of directories,merged with the upper object.The lower filesystem can be any filesystem supported by Linux and does not need to be writable.  The lower filesystem can even be anotheroverlayfs.   The  upper  filesystem will normally be writable and if it is it must support the creation of trusted.* extended attributes,and must provide a valid d_type in readdir responses, so NFS is not suitable.A read-only overlay of two read-only filesystems may use any filesystem type.  The options lowerdir and  upperdir  are  combined  into  amerged directory by using:mount -t overlay  overlay  \-olowerdir=/lower,upperdir=/upper,workdir=/work  /mergedlowerdir=directoryAny filesystem, does not need to be on a writable filesystem.upperdir=directoryThe upperdir is normally on a writable filesystem.workdir=directoryThe workdir needs to be an empty directory on the same filesystem as upperdir.

我们照葫芦画瓢,创建好对应的目录和文件。

nigo@DESKTOP-95TV8LK ~/overlay2> tree
.
├── lower1
├── lower2
├── merged
├── upper
└── work5 directories, 0 file
nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file1, belong to lower1' > lower1/file1.txt
nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file2, belong to lower2' > lower2/file2.txt
nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file3, belong to upper' > upper/file3.txt

现在lowerdir和upperdir都有它们各自的文件。

nigo@DESKTOP-95TV8LK ~/overlay2> tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
├── upper
│   └── file3.txt
└── work5 directories, 3 files

可以看到,我们成功挂载了这些目录。

nigo@DESKTOP-95TV8LK ~/overlay2 [1]> mount | grep overlaynigo@DESKTOP-95TV8LK ~/overlay2 [0|1]> sudo mount -t overlay overlay -olowerdir=lower1:lower2,upperdir=upper,workdir=work merged/
[sudo] password for nigo:nigo@DESKTOP-95TV8LK ~/overlay2> mount | grep overlay
overlay on /home/nigo/overlay2/merged type overlay (rw,relatime,lowerdir=lower1:lower2,upperdir=upper,workdir=work)

而merged中也出现了我们期待的内容。

nigo@DESKTOP-95TV8LK ~/overlay2> sudo tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── upper
│   └── file3.txt
└── work└── work6 directories, 6 filesnigo@DESKTOP-95TV8LK ~/overlay2> cat merged/*
I'm file1, belong to lower1
I'm file2, belong to lower2
I'm file3, belong to upper

改动属于upperdir的内容肯定会映射到upperdir中,毕竟upperdir是属于当前层的可读写层。而上面提到的lowerdir呢?让我们验证一下它吧。

修改lowerdir的文件

修改lowerdir1中的file1。

nigo@DESKTOP-95TV8LK ~/overlay2> echo 'I\'m file1, belong to lower1' > lower1/file1.txtnigo@DESKTOP-95TV8LK ~/overlay2> echo 'file1 has been changed' > merged/file1.txtnigo@DESKTOP-95TV8LK ~/overlay2> cat merged/*
file1 has been changed
I'm file2, belong to lower2
I'm file3, belong to uppernigo@DESKTOP-95TV8LK ~/overlay2> cat lower1/file1.txt
I'm file1, belong to lower1nigo@DESKTOP-95TV8LK ~/overlay2> sudo tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── upper
│   ├── file1.txt
│   └── file3.txt
└── work└── work6 directories, 7 files

可以看到,merged中的file1.txt确实被我们修改了,但lowerdir中的内容仍然不变,而是在upperdir中生成了一个file1.txt,这就是copy-on-write。这也验证了lowerdir是只读层这一点。

copy-on-write,即写时复制,概念类似于linux fork后尝试对父子进程共享的页表进行修改时,内核为子进程重新复制一份页表。在这里,overlay2在upperdir中生成了一份file1.txt。

删除lowerdir的文件

试着删除file2.txt。

nigo@DESKTOP-95TV8LK ~/overlay2> rm merged/file2.txt
nigo@DESKTOP-95TV8LK ~/overlay2> sudo tree
.
├── lower1
│   └── file1.txt
├── lower2
│   └── file2.txt
├── merged
│   ├── file1.txt
│   └── file3.txt
├── upper
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
└── work└── work6 directories, 7 filesnigo@DESKTOP-95TV8LK ~/overlay2> cat merged/*
file1 has been changed
I'm file3, belong to uppernigo@DESKTOP-95TV8LK ~/overlay2> cat lower2/file2.txt
I'm file2, belong to lower2nigo@DESKTOP-95TV8LK ~/overlay2> ll upper/file2.txt
c--------- 1 root root 0, 0 Apr 22 14:44 upper/file2.txt

从结果来看,file2.txt确实被我们“删掉”了。但在upperdir中,我们看到生成了一个file2.txt的特殊的字符设备文件。

而overlay2在联合挂载时,看到这个特殊的字符设备文件,会选择性的忽略lowerdir中对应的内容。

而在aufs(另一种UnionFS)中表现为whiteout文件(可以查一下这个词,意为临时性失明,挺形象的emm)。感兴趣的同学可以搜一下,目前网上的大多数实验也是关于aufs的。

overlay2与Docker

进行实验前,请确保你的docker使用overlay2的驱动方式。
可以使用docker info,或者查看/etc/docker/daemon.json中的内容。
具体请参考:Use the OverlayFS storage driver | Docker Documentation

nigo@DESKTOP-95TV8LK ~> docker info | grep StorageStorage Driver: overlay2

我们以ubuntu为例子,pull下来三层镜像层。

nigo@DESKTOP-95TV8LK ~> docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
a70d879fa598: Pull complete
c4394a92d1f8: Pull complete
10e6159c56c0: Pull complete
Digest: sha256:3c9c713e0979e9bd6061ed52ac1e9e1f246c9495aa063619d9d695fb8039aa1f
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

在/var/lib/docker/overlay2中我们可以成功的找到它们,由于一些目录比较深,我们可以通过-L参数来指定tree访问的深度。

root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# tree -L 2
.
├── 809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04
│   ├── committed
│   ├── diff
│   └── link
├── ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l├── 2FMRPFC5X2PFHGEZII4KC47JF4 -> ../ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff├── EVRLLRGLJ5K5374Z6B32BREDLT -> ../809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff└── UXEF4DBCSIKECRM2J2IPAD4WY5 -> ../adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff12 directories, 7 files

但在这里多出了一个l目录,里面存放的指向各层的软链接,而且软链接的名字显然被缩短过。根据docker官网的说明,这些软链接用于避免达到 mount命令对页面参数的页面大小限制。

在各层目录下还存在着link文件,这些目录中放的就是l目录中那些被缩短过的软链接名称。

root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# cat adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/link
UXEF4DBCSIKECRM2J2IPAD4WY5

使用docker inspect,在GraphDriver这里找到关于该ubuntu镜像的信息。

"GraphDriver": {"Data": {"LowerDir": "/var/lib/docker/overlay2/ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff:/var/lib/docker/overlay2/809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff","MergedDir": "/var/lib/docker/overlay2/adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/merged","UpperDir": "/var/lib/docker/overlay2/adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff","WorkDir": "/var/lib/docker/overlay2/adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/work"},"Name": "overlay2"}

从inspect返回的信息中可以看到,各层的diff目录就是该层“different”于下层的内容,对于下层镜像,它是只读层(lowerdir),而对于上层,它是可读写层(upperdir),它们也是和workdir被联合挂载到mergeddir的。

上面没有提到的commit文件则是记录这每个层的相关commit信息。

最后让我们来尝试一下创建容器,以及commit对该目录的影响。

nigo@DESKTOP-95TV8LK ~> docker run -itd --name myubuntu ubuntu
d6adc07566d205f1554b2db9534c76713f830a7705e9f41ab30f9c0f4d118b1d
root@DESKTOP-95TV8LK:/var/lib/docker/overlay2# tree -L 2
.
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778
│   ├── diff
│   ├── link
│   ├── lower
│   ├── merged
│   └── work
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04
│   ├── committed
│   ├── diff
│   └── link
├── ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l├── 2FMRPFC5X2PFHGEZII4KC47JF4 -> ../ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff├── EVRLLRGLJ5K5374Z6B32BREDLT -> ../809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff├── M32S6F25IVIE4YRIR2BYQX5S4H -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init/diff├── RPNEL4XVFQMLG5Q4BF7BXUYY5C -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/diff└── UXEF4DBCSIKECRM2J2IPAD4WY5 -> ../adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff

运行容器后生成了两个新的层,其中一个为init层,这是用来存储和容器环境相关内容的只读层,由于这些环境在每台机器上都可能不同,docker的策略是放在init层,每个镜像生成容器时去生成环境相关的配置。我们在docker commit时,不提交init层的内容。

写入新的文件,执行docker commit来观察结果。

nigo@DESKTOP-95TV8LK ~> docker start myubuntu
myubuntunigo@DESKTOP-95TV8LK ~> docker exec -it myubuntu /bin/bash
root@d6adc07566d2:/# touch hello-overlay2.txtroot@d6adc07566d2:/# exitroot@DESKTOP-95TV8LK:/var/lib/docker/overlay2# tree -L 2
.
├── 0991cf894ea2ed9bb2b8313331ac9eb72c3678c26dc0152241e6228105df25f2
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── 809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04
│   ├── committed
│   ├── diff
│   └── link
├── ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
├── adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278
│   ├── committed
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l├── 2FMRPFC5X2PFHGEZII4KC47JF4 -> ../ab0963faec278aa7c9c40c79642774451b2a5ecd9142706d7b6165864d55ad59/diff├── DD5NLJQIUJRFR45OMBJIF26CQ3 -> ../0991cf894ea2ed9bb2b8313331ac9eb72c3678c26dc0152241e6228105df25f2/diff├── EVRLLRGLJ5K5374Z6B32BREDLT -> ../809e4cfaa089d57ba81faea4570d6689cf6fe9a424b982ba6859b094340eef04/diff├── M32S6F25IVIE4YRIR2BYQX5S4H -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778-init/diff├── RPNEL4XVFQMLG5Q4BF7BXUYY5C -> ../6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/diff└── UXEF4DBCSIKECRM2J2IPAD4WY5 -> ../adfcb936bd0fac351f71721610abbec97b7309c1ae8323ebc6795c5c96ac0278/diff24 directories, 15 files

果然,新的镜像层生成了!

nigo@DESKTOP-95TV8LK ~> mount | grep overlay
overlay on /var/lib/docker/overlay2/6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/M32S6F25IVIE4YRIR2BYQX5S4H:/var/lib/docker/overlay2/l/UXEF4DBCSIKECRM2J2IPAD4WY5:/var/lib/docker/overlay2/l/2FMRPFC5X2PFHGEZII4KC47JF4:/var/lib/docker/overlay2/l/EVRLLRGLJ5K5374Z6B32BREDLT,upperdir=/var/lib/docker/overlay2/6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/diff,workdir=/var/lib/docker/overlay2/6c287e2696d9a1593ae358045b511d95e6dc5f1bbe021a4f72a7892f3a8c5778/work)

而这些被联合挂载的目录,也是l目录中那些被缩短过的软链接。

overlay和overlay2

由于内核支持的原因,这点我并没有做过具体的实验,下面是道听途说的结论

  • 不同点:
    overlay的lowdir只有一层,每层只读层都通过硬链接共享文件,因此每层只读层都有一套完整的增量。
    overlay2的只读层是独立的个体,容器启动时统一挂载到merged。
  • 相同点:
    都有work目录用于完成copy-on-write等工作,启动时挂载到merged目录。
    都支持页缓存,同一镜像的容器有机会使用同一文件,内存消耗更小。

具体请参考:overlay和overlay2的区别 - ElNinoT - 博客园
实验之前也请同样的修改docker的Storage Driver。

为什么说overlay比overlay2消耗更多的inode

相信大家之前都学习过硬链接和软链接的知识,我们知道,软链接好比windows的快捷方式,它是单独的文件,具有单独的inode号码,只不过文件的内容是一个路径,而硬链接的inode号码是相同的。
那为什么说使用inode处理只读层的overlay会消耗更多的inode呢?
原因在于外层目录是新建的,有不同的inode号码
既然联合文件系统是一层套一层的,那每层必然有自己独到的内容,假如连外层目录都是同一个inode号码,那么所有父层是不是就变成一样的了?

参考

Use the OverlayFS storage driver | Docker Documentation
联合文件系统 · DOCKER · 看云
Overlay Filesystem — The Linux Kernel documentation
UnionFS-维基百科
也可以顺便用用大神写的测试套件:叠加层测试·amir73il / overlayfs Wiki

Docker:overlay2浅析相关推荐

  1. DOCKER OVERLAY2占用大量磁盘空间解决办法

    1.首先找到OVERLAY2目录 cd /var/lib/docker/overlay2 2.查看文件的大小 du -h --max-depth=1 3.查看占用空间的PID,以及对应的容器名称 do ...

  2. 【已解决】docker overlay2占用大量磁盘空间处理方法

    在使用docker容器的时候遇到了容量上的问题,做一个记录 处理方式1: 在使用docker时,往往会出现磁盘空间不足,导致该问题的通常原因是因为docker中部署的系统输出了大量的日志内容. 此时, ...

  3. readlink /var/lib/docker/overlay2/l: invalid argument报错解决

    用stack起服务的时候个别服务没有起来,用docker stack ps查看之后发现redis的报错信息是"readlink /var/lib/docker/overlay2/l: inv ...

  4. 解决docker启动错误 error creating overlay mount to /var/lib/docker/overlay2

    原文 最近在centos7.1使用docker运行redis镜像,出现下面的错误: /usr/bin/docker-current: Error response from daemon: error ...

  5. docker overlay2占用大量磁盘空间处理方法

    在使用docker时,往往会出现磁盘空间不足,导致该问题的通常原因是因为docker中部署的系统输出了大量的日志内容. 此时,可通过手动或定时任务进行清除. 针对/var/lib/docker/ove ...

  6. Docker物理机重启后,docker报错 error creating overlay mount to /var/lib/docker/overlay2

    昨晚公司运维说机房要停电,然后今天上午来公司发现,之前docker起的容器启动不起来了,报错 Error response from daemon: Cannot restart container ...

  7. Error response from daemon: readlink /var/lib/docker/overlay2/l/OEK3ESNVLXTTUOL6PIEXF2S6VF: invalid

    删除/var/lib/docker 后,同步其他服务器上的/var/lib/docker文件重启docker 后虽然有些容器没问题,但是有些还是出现了问题 Error response from da ...

  8. 【Error response from daemon: mkdir /var/lib/docker/overlay2/413d0090b35b07401e25a5cc80538da5b43f6a9】

    一.服务器磁盘空间不足 使用docker重启重启失败 [root@oracle ~]# docker start mongo Error response from daemon: mkdir /va ...

  9. 记一次测试环境的灾难修复(排错及思路)-Docker Overlay2的出错;

    公司有个开发大佬,说要看某个docker里面的文件夹分层,在网上找了个软件,然后进去各种操作猛如虎,完后跟我说公司的测试环境无法编译,有可能是他不小心删了什么东西,让我看下是什么问题?随后自己高高兴兴 ...

最新文章

  1. 通过jQuery调用ASP.NET的AJAX
  2. Docker-Compose 使用简介
  3. POJ 2186 挑战 --牛红人 强连通分量——Tarjan
  4. 每个前端工程师都应该懂的前端性能优化总结:
  5. win10怎么设置默认浏览器_vscode如何设置默认打开的浏览器为Chrome?
  6. MySQL高级知识(九)——慢查询日志
  7. 北京楼市前十个月少卖832亿元 销售创6年新低
  8. java分页 Struts+Ibatis
  9. mysql编译方式查询_源码编译mysql及其各种查询总结
  10. Iconfont 替代品网站 图标网站推荐
  11. 亿级流量 即时通讯IM系统 设计详解(全)
  12. 《海边的卡夫卡》读后感
  13. 华为和H3C无线AP上线DHCP参数配置
  14. 01- 机器学习经典流程 (中国人寿保费项目) (项目一)
  15. 【Android必备】构建一个App小部件(24)【代码块异常】
  16. http://windowsandroid.cn.uptodown.com/download
  17. linux 读取png图片大小,读取 png 图片的宽高信息
  18. 端粒效应《The Telemere Effect》程序员的养生指南(一)压力、端粒与衰老
  19. 渗透工程师日常探测漏洞全流程 初学者必看
  20. imagecreatefromjpeg(),imagecreatefrompng()打开不同格式的图片报错误

热门文章

  1. 视觉SLAM十四讲第二章学习与课后题与随笔日记
  2. Linux中bash文档翻译
  3. 金山毒霸技术预览版1.0 beta【云沙箱 三引擎】发布(毒霸体验团队可优先测试)...
  4. windows下修改黑苹果config_黑苹果家用PC安装苹果Mac OS操作系统经验(下)工具和资源...
  5. HAC集群状态检查、切换、数据同步验证方法
  6. 腾讯企业邮箱api java_腾讯企业邮全新API,五大新接口上线_腾讯企业邮箱服务中心...
  7. 美国计算机专业的大学,美国计算机专业什么大学好
  8. 他一跳槽就裁员4.5万,却让快倒闭的IBM涅槃重生
  9. 隐秘而伟大!知名互联网公司都在使用哪些数据库?
  10. 外边距塌陷原因和解决方式