• Docker


    Docker

    理论上讲:

    1型虚拟化一般对硬件虚拟化功能进行了特别优化,性能上比2型要高;

    2型虚拟化因为基于普通的操作系统,会比较灵活,比如支持虚拟机嵌套。嵌 套意味着可以在KVM虚拟机中再运行KVM。

    容器(系统虚拟化)位于前两者之上,以进程的形式存在于操作系统之中

         容器为应用程序提供了隔离的运行空间:每个容器内都包含一个独享的完整用户环境空间, 并且一个容器内的变动不会影响其他容器的运行环境(其实一个容器呢就像是我们学习namespace一样,namespace是网络的隔离空间,不同的namespace它里面可以有自己的网络设备,比如网桥,路由表等等,容器也是类似于namespace一样,做的资源的隔离)。为了能达到这种效果,容器技术使用了一系列的系统级别的机制诸如利用Linux namespaces 来进行空间隔离,通过文件系统的 挂载点来决定容器可以访问哪些文件,通过 cgroups 来确定每个容器可以利用多少资源(比如说我的宿主机原来是8G,通过cgroups可以管理我运行的容器,我可以让你这个容器运行指定的内存大小)。此 外容器之间共享同一个系统内核(指的是宿主机的系统内核),这样当同一个库被多个容器使用时,内存的使用效率会得到提升。

    docker是一个技术的类别,docker就像云计算里的openstack一样,它是虚拟化里的一款软件。

      容器( container-based )虚拟化方案,充分利用了操作系统本身已有的机制和特性,以实现轻量级的虚拟化(每个虚拟机安装的不是完整的虚拟机), 甚至有人把他称为新一代的虚拟化技术, Docker 无疑就是其中的佼佼者。

    在一台服务器上同时运行上百个虚拟机,肯定会被认为是痴人说梦,而在一台机器上同时运 行一千个 Docker 容器,这已成为现实。

    Docker 的目标

      Build,Ship and Run Any App,Anywhere— 即通过对应用组件的封装 (Packaging), 发布(Distribution), 运行 (runing) 等生命周期的管理 , 达到应用组件级别的”一次封装 , 到处运行”。(就像是集装箱一样,我把我的操作系统打包好了,有一天别人想用我这个操作系统,我直接把我容器发给他,那他轻而易举的拥有了跟我一样的系统环境。很方便,不用你手动的去布。)

    Doker的特点

    优点:

    1、启动快,资源占用小 , 资源利用高,快速构建标准化运行环境 。

    2、创建分布式应用程序时快速交付和部署,更轻松的迁移和扩展,更简单的更新管理。

    局限:

    1、Docker 是基于 Linux 64bit 的,无法在 windows/unix (是Linux系统的鼻祖)或 32bit 的 linux环境下使用。

    2、LXC 是基于 cgroup 等 linux kernel 功能的,因此 container 的 guest 系统只能是 linux。

    3、隔离性相比 KVM(因为kvm是完全的虚拟化,它中间有hypervirson独立的虚拟化层来管理上层的kvm虚拟机,但是容易是寄托于宿主机里面的进程,它就是宿主机里的一个进程,假如说你宿主机坏了,那么你这个容器也就不能用了,但是kvm的宿主机坏了,可以把kvm的文件copy出来还可以使用) 之类的虚拟化方案还是有些欠缺,所有 container 公用一部分的运行库 。

    4、管理相对简单,主要是基于 namespace 隔离。

    5、cgroup 的 cpu 和 cpuset 提供的 cpu 功能相比 KVM 的等虚拟化方案相比难以度量 ( 所以 dotcloud 主要是按内存收费 ) 。

    6、docker 对 disk 的管理比较有限 。

    7、container 随着用户进程的停止而销毁, container 中的 log 等用户数据不便收集。

    Docker与虚拟机比较

    1、启动快比虚拟机 , 可以秒级启动,像虚拟机启动,需要有开机的流程。

    2、对资源占用小 , 宿主机上可运行千台容器。

    3、方便用户获取 , 分布 , 和更新应用镜像(生成容器的基础,就像我们给虚拟机安装操作系统一样,你得有一个iso的基础镜像,你才能装操作系统,容易也一样,但容器的镜像不是iso文件) , 指令简单 , 学习费用低。

    4、通过 Dockerfile 配置文件来灵活的自动创建和部署镜像 & 容器 , 提高工作效率。

    5、Docker 除了运行其中应用外 , 基本不消耗其他系统资源 , 保证应用性能同时 , 尽量减小系统开销(如果我们在宿主机里运行一台kvm虚拟机,那么这个kvm虚拟机的操作系统本身也会占用宿主机一定的资源,你如果再在kvm上部署一个mysql或者apache,那么他俩也会占用宿主机的系统资源,但是容器不一样,如果你这个容器运行的是mysql,那么只有mysql会占用你系统的资源,容器本身是不占用的)。

    Docker重要网站

    容器的官网:https://hub.docker.com/

    容器的官方手册:https://docs.docker.com/

    容器中文的官方手册:http://docs.docker-cn.com/

    Docker的安装

    yum安装

    vim /etc/resolv.conf    
    
    #添加如下内容: nameserver
    192.168.254.251
    wget http:
    //download2.yunwei.edu/shell/yum-repo.sh
    mkdir -p /etc/yum.repos.d/old && mv /etc/yum.repos.d/* /etc/yum.repos.d/old/
    sh yum-repo.sh
    yum -y install docker
    systemctl start docker.service
    systemctl status docker

    二进制安装(就像我们windows系统里会安装很多的软件,比如qq、微信,有一天有个朋友说,你把你的微信这个程序拷给我一下,我有两种选择,一是把微信安装好的目录都拷给他,二是把微信的安装包拷给他,第一种方法就相当于二进制安装,第二种就相当于yum install)

    #先将此包上传至/root目录下
    tar xzf docker.tar.gz
    
    cd docker
    
    sh docker.sh #这是docker安装脚本
    #验证docker是否启动
    systemctl status docker
    #查看docker 运行的程序
    docker ps #没有容器时会显示下图输出

    Docker镜像

      镜像(docker image) --- Docker 运行容器之前需要本地存在镜像,若本地不存在,那么 Docker 会找默认镜像仓库( Docker Hub 公共注册服务器中的仓科)下载,用户也可以通过配置使用自己的镜像库。

    列出当前宿主机里所有的镜像

    docker images

    (1)下载镜像 (你下载的镜像也是有版本区分的)

    docker pull   NAME:TAG 

    下载某个被打上了某个标签的叫 ×× 名字 的镜像

    eg: docker   pull    ubuntu:latest

    若在下载时你未指定标签(就是版本号),默认追加 latest标签,表示库中最新镜像

    镜像在下载过程中是分层的,并且在每层前会有各层的 ID号,层(Layer ) 是 AUFS (联合文件系统)的重要概念,是实现增量保存与更新的基础。

     

    实际上面的命令的原型是

     docker pull registry.hub.docker.com/centos:7

    即从默认的注册服务器下载指定的镜像当然,以此你也可以用这种格式到其他仓库下载你所 需要的镜像了!!!

    (2)查看本地镜像相关信息

    docker images 

    显示结果:

     

    REPOSITORY:一方面指的镜像的名字,一方面指的是镜像源自哪个仓库。 如果镜像名字前面没有“/”的话,它默认都是从docker官网下载的。

    TAG:标签信息   ---用于区分同一个仓库的同名镜像可用 docker tag仓库名: 标签名  新库名:新标签。

    IMAGE ID:镜像ID

    CREATED:创建时间(指的不是镜像的下载时间,指的是在人家官网里这个镜像最新上传的时间)

    SIZE:镜像大小

    (3)重新给一个镜像定义名字

    拿httpd举例,我们httpd的信息如下

     

    比如我哪天在此基础上给httpd做了一些更新,用于区分,所以要重新命名

    docker tag httpd:latest httpd:latestv1

    我们在检索一下镜像列表

     

    如果想运行指定版本httpd,要加版本号

    docker run –it httpd:latestv1

    (4)搜索库中满足需求的镜像,以此下载符合需求的镜像文件 (就是说在命令行中,你下载一个镜像,你不知该下载哪个,可以搜索)

    docker  search  镜像名字   

    显示结果:

     

    name:哪个库/镜像名字。  

    DESCRIPTION:绝对路径,库中的带有搜索关键字的镜像。  

    STARS:描述。

    OFFICIAL:官方。

    AUTOUMATED:是否自动创建。

    (5)删除本地镜像  

    docker  rmi  镜像的名字:版本号/镜像的ID   

      当有别名镜像时,是同一个ID 号多个名字 ,删除任一个对互相无伤害,直至删除最后一个,镜像被彻底删除用ID号删除,默认会删除同一个ID的多个镜像,除非该镜像创建的容器存在, 那么镜像能够不被删除,所以在删除时正确顺序是先删除使用镜像的容器,再删除镜像。

    Docker镜像分层

    docker镜像的分层结构:支持通过扩展现有镜像,创建新的镜像。

    就像刚才咱们下载httpd的时候,它每层都会输出像ID号一样的东西,如下图:

     

    其实这就是单独的一层,它为什么要做分层呢?看下图:

     

    这个就是我们最新下载的镜像,它底层运行的kernel(内核),还有bootfs(开启引导项的一个文件系统)这是我们的基础镜像层,第二层是怎么来的,有一天对这个镜像做了一个更改,比如说我想以后我启动的这个容器/root目录下都有相同的一个文件,那我得对这个镜像做更改,不更改的话,我以后生成的容易,就没有我想要的这个文件了。我做的这个修改会在二层之上累加一层,如图把我对这个原来的镜像所作的操作变化、新添加的东西都保存在最上层,你每做的一次更改,它都会往高的累加。我现在/root添加了一个文件,那么我还想在/root创建一个目录,那么它会在上一个状态的基础上又多一层,新状态的最上层,记录了我创建这个目录的记录,如图

    可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。

    分层结构的优势:

    共享资源:

    有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base (基础)镜像;同时内存中也只需加载一份 base 镜像,就可以为所 有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。

    这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc 是否也会被修改?

    答案是不会!修改会被限制在单个容器内。这就是我们接下来要学习的容器 Copy-on-Write 特性。

           比如我只在我电脑里保存了一份base镜像,那么有一天,我要通过这个base镜像在里面添加一个文件,这样底层镜像不用保存,只保存你改变的那一层即可,我再修改的话,也是保存修改的。

    可写的容器层:

     

      当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被 称作“容器层”,“容器层”之下的都叫“镜像层”。

    所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。

    只有容器层是可写的,容器层下面的所有镜像层都是只读的。

        所以说你通过容器启动了一个镜像以后呢,别人也想用这个镜像,那么别人也会基于下面的镜像层在去起一个容器层,我们这些容器层是相互独立的,不会对底层的镜像层产生修改影响。

      镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a, 也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。

    添加文件   

    在容器中创建文件时,新文件被添加到容器层中。

    读取文件   

    在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文 件。一旦找到,打开并读入内存。

    修改文件   

    在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找 此文件。一旦找到,立即将其复制到容器层,然后修改之。

    删除文件    在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。 找到后,会在容器层中记录下此删除操作。

    只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见, 容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。

    这样就解释了我们前面提出的问题:容器层记录对镜像的修改,所有镜像层都 是只读的,不会被容器修改,所以镜像可以被多个容器共享。

    Docker镜像的创建

    创建镜像的三种方法

    (1)基于修改后的容器创建:docker commit 命令

    (2)基于本地模板导入

    (3)基于  Dockerfile 文件创建

     

    (1)基于修改后的容器创建

    docker run -it  httpd:latest   /bin/bash
    mkdir /testdir 
    
    exit

    此时容器较镜像已经发生改变,我们以此生成新镜像

     docker commit 9a748f24b831 http1:v1  

    顺利的话会返回一个新的 ID  号

    docker  images 

    查看镜像列表,会看到本地多了一个名为新名字的镜像

     

    (2)基于模板导出和导入镜像

    导出:将镜像保存为本地文件   

    docker  save -o  httpd.tar.gz   httpd:latest    
    
    ls 

     

    删除一个镜像必须是在没有容器使用此镜像的情况下,要是有容器使用此镜象还是要删除此镜像,得删除此容器。

    先查看在线容器

    docker ps 

    在查看所有容器

    docker ps –a

    删除容器

    docker rm 容器ID

    删除镜像

    docker rmi 镜像   ID

    导入 :将压缩包文件导入到本地镜像列表   

    docker  load  --input  centos.tar    或者    
    
    docker  load  -i  centos.tar    
    
    docker rmi  ID    
    
    docker images

    简单来说,容器是镜像的运行实体。所不同的是它带有额外的可写层

    (3)基于  Dockerfile 文件创建

    Dockerfile 是一个文本文件,用来配置 image,记录了镜像构建的所有步骤。

    Docker 根据 该文件生成二进制的 image 文件。

    如何可以生成 image 文件? 如果你要推广自己的软件,势必要自己制作 image 文件。

    下面通过一个实例,演示如何编写 Dockerfile 文件:

    1、用 Dockerfile 创建上节点的 centos-with-vi,其内容则为:

    [root@cicd ~]# cat /root/dockerfile 
    
    FROM centos:
    7
    RUN yum install
    -y vim
    [root@cicd docker]# docker build
    -t centostest /root/dockerfile
    或者:docker build
    -t centostest .

    build:创建镜像 -t:为镜像指定名字

    .:指明 build context 为当前目录,我们也可以通过 -f 参数指定 Dockerfile 的位置

     

      从这步开始就是镜像真正的构建过程。首先 Docker 将 build context 中的所有 文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。Dockerfile 中的 ADD、COPY 等 命令可以将 build context 中的文件添加到镜像。此例中,build context 为当前 目录 /root/docker,该目录下的所有文件 和子目录都会被发送给 Docker daemon。     

    所以,使用 build context 就得小心 了,不要将多余文件放到 build context, 特别不要把 /、/usr 作为 build context,否则构建过程会相当缓慢甚至失败。

    查看构建过程:

    docker history 容器ID

     

    Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。

    在前面的 Dockerfile 中添加一点新内容,往镜像中复制一个文件:

    [root@cicd docker]# ls 
    
    dockerfile  hello 
    
    [root@cicd docker]# cat dockerfile FROM centos:7 RUN yum install -y vim copy 
    
    hello /root/

     

    下面列出了 Dockerfile 中最常用的指令,完整列表和说明可参看官方文档。

    FROM:指定 base 镜像。

    MAINTAINER:设置镜像的作者,可以是任意字符串。

    COPY:将文件从 build context 复制到镜像。           

    COPY 支持两种形式:

    COPY src dest    
    
    COPY ["src", "dest"] 

    注意:src 只能指定 build context 中的文件或目录。

    ADD:与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会 被自动解压到 dest。

    ENV:设置环境变量,环境变量可被后面的指令使用。例如:

    ... 
    
    ENV MY_VERSION 1.3 
    
    RUN apt-get install -y mypackage=$MY_VERSION
    
    ...

    EXPOSE:指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。我们会在容器网络部分详细讨论。

    VOLUME:将文件或目录声明为 volume。

    WORKDIR:为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。

    RUN:在容器中运行指定的命令,RUN 指令通常用于安装应用和软件包。

    CMD:容器启动时运行指定的命令。

    Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。

    ENTRYPOINT:设置容器启动时运行的命令,可让容器以应用程序或者服务的形式运行。 Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。

    完整的dockerfile:

    [root@cicd docker2]# ls
    
     dockerf  test.tar.gz  tmpfile2
    
    [root@cicd docker2]# cat dockerfile 
    
    #my dokerfile 
    
    FROM centos:7 
    
    MAINTAINER steven 
    
    WORKDIR /testdir 
    
    RUN touch tmpfile1 
    
    COPY ["tmpfile2","."] 
    
    ADD ["test.tar.gz","."]
    
     ENV WELCOME "you are im my container.welcome!"
    
    [root@cicd docker2]# docker build -t my-imge .

    构建image过程:      

     

    运行一个容器:

     

    Docker使用

    (1)创建容器,create命令是创建并不启动容器

    docker creat  -it  centos:7 

    (2)查看所有状态的容器

    docker ps -a 
    
    docker ps 

    (3)启动停止的容器

    docker  start  ID 

    (4)运行一个容器

    创建并运行一个容器 ,此时你会直接被切换到容器中

    docker run -ti docker.io/centos:7 /bin/bash 
    
    ctrl+d//exit退出,并且容器也退出 
    
    ctrl+p+q退出容器,并且保持容器up的状态 
    
    -t 分配一个伪终端 
    
    -i 让容器的标准输入持续打开 
    
    用/bin/bash环境显示

    (5)以后台守护进程(Daemonized)形态运行      用-d参数实现

    docker run -dti docker.io/centos:7 
    
    docker ps -a 

    (6)终止容器

    docker  stop  NAME/ID     
    
    docker  kill  NAME/ID 
    
    docker  ps -a 

    (7)重启容器

    docker  restart  NAME/ID 
    
    docker  ps  -a 需要注意,非持久后台守护状态的虚拟机,在重启后当你查看时还是关闭状态 

    (8)删除容器

    语法: docker rm  参数  NAME  NAME …

    docker  -rm   -f   NAME/ID 

    -f 强制删除处于运行中的容器

    -l  删除链接,保留容器

    -v 删除挂载的数据卷

    (9)进入容器 docker attach 和 docker exec 两种方式:

    docker  attach直接进入容器 启动命令的终端,不会启动新的进程 
    
    docker run -d centos /bin/bash -c “while true; do sleep 1;echo I_am_docker;done” 
    
    docker  attach  NAME/ID
    
    docker  exec则是在容器中打开新的终端,并且可以启动新的进程
    
    docker  exec -it NAME/ID /bin/bash

    (10)导入和导出容器

    a. 导出容器:导出一个已经创建的容器到文件,不管容器是否运行
    
    docker ps -a 假设当前系统中有两个处于运行和停止状态的容器 
    
    docker export  ID   >   *.tar 
    
    ls
    b.将容器导入系统成为镜像
    
    cat  *.tar  | docker  import  - test

    (11)查看容器日志 显示容器启动进程的控制台输出

    docker logs -f NAME/ID

    (12)暂停容器

    docker pause NAME/ID

    (13)取消暂停继续运行容器

    docker unpause NAME/ID

    下面是容器的常用操作命令:

     

    Docker镜像仓库

    仓库分为公共仓库和私有仓库 DockerHub的官方仓库  

    https://hub.docker.com DockerPool社区仓库  

    https://dl.dockerpool.com Dockern Pool (http://dockerpool.com)是国内专业的Docker社区,目前 也提供了官方镜像的下载管理服务

    仓库  --- 创建自己的私有仓库

    (1)
     docker run -d -p 5000:5000 -v /var/lib/registry:/var/lib/registry --restart=always --name registry registry:2 

     

    (2)docker客户端设置

    2.1. 修改配置文件将私有库地址加入进去(针对yum安装的容器)

    vim /etc/sysconfig/docker  
    
    line
    4: add Docker-Registry's
    URL OPTIONS='--insecure-registry 192.168.210.128:5000

     

    注意:有时会遇到如下报错:

     

    这是由于Registry为了安全性考虑,默认是需要https证书支持的,可利用2.2 提到的方法解决。

    2.2. 或者修改/etc/docker/daemon.json文件,加入如下内容: (这是二进制安装容器)

    "insecure-registries": ["192.168.210.128:5000"],

     

    (3)重新启动 docker服务

    systemctl  restart  docker

    (4)更换 my-imge:latest 一个新的名称标签

     docker tag my-imge:latest 192.168.210.128:5000/myimge1:latest

    (5)上传 my-imge1:latest

    docker push  192.168.210.128:5000/my-imge1:latest

    (6)#删除本地原有的busybox 镜像,然后向私有库192.168.254.162重新 下载

    docker rmi 192.168.210.128:5000/my-imge1:latest 
    
    docker pull
    192.168.210.128:5000/my-imge1:latest

     

    Docker底层依赖的核心技术

    进程实现服务,服务要遵守协议,端口来标识服务或者协议。

    命名空间 (Namespaces)

    控制组 (Control Groups)

    联合文件系统 (Union File System)

    Linux 虚拟网络支持:本地和容器内创建虚拟接口

    (1)命名空间 (Namespaces):实现了容器间资源的隔离 每个容器拥有自己独立的命名空间 , 运行其中的应用就像是运行在独立的操作系统中一样 , 我们都可以看到文件系统,网卡等资源保证了容器之间互不影响, namespaces管理进程号 , 每个进程命名空间有一套自己的进程号管理方法 , 进程命名空间是一个父子关系的结构 , 子空间中的进程对于父空间是可见的。

         网络命名空间—有了 PID 命名空间 , 那么每个名字中的进程就可以相互隔离 , 但是 , 网络端口还是共享本地的端口(我们在一个环境当中可以启动很多的容器,它里面有自己的IP,网卡,但是它的端口是共享本地宿主机的端口,也就是说我一个容器占用了本地80端口,那么另一个容器再想用80端口是不可以的。). 网络命名空间就是为进程提供一个完全独立的网络协议栈的视图包括 : 网络设备接口 ,IPv4 和 IPv6 协议栈 ,IP 路由表 , 防火墙规则 ,sockets 等等 ...

    目前容器所使用的命名空间提供5种不同的类型 :

     

    (2)控制组功能 cgroups: 控制组 (Control groups)--CGroups 是 Linux 内核的一个特性 ,主要用来对 共享资源进行隔离、限制、审计等 。cgroups 允许对于进程或进程组公平 ( 不公平 ) 的分配 CPU 时间、内存分配和 I/O 带宽。

         容器通过 cgroups 来得到所能够管理资源的分配和使用。因此容器所获得资源仅为所有系统 资源的一个部分。(就是可以通过cgroups来控制我这个容器使用多大的资源,等等)

    1、资源限制 : 内存子系统为进程组设置内存使用上限,内存达到上限后再申请内存,就会发                   出 Out of Memory

    2、优先级 : 通过优先级让一些组得到更多 CPU 等资源

    3、资源审计 : 用来统计系统上实际把多少资源用到适合的目的上 , 可以使用 cpuacct 子系统                  记录某个进程组使用的 CPU 时间

    4、隔离 : 为组隔离名字空间 , 这样一个组不会看到其他组的进程 .网络连接和文件系统

    5、控制 : 挂起 . 恢复和启动等操作

    (3)联合文件系统 (Union FS) docker 中使用AUFS(another Union File System 或 v2 版本以后的 Advanced multi-layered Unification File System) 控制为每一个成员目 录设定只读 / 读写 / 写出权限 , 同时 AUFS 有一个类似分层的概念 , 对只读权 限的分支可以逻辑上进行增量的修改

          轻量级的高性能分层文件系统 , 它支持将文件系统中的修改信息作为一次提交 , 并层层叠加 , 并且可以将不同目录挂载到同一个虚拟文件系统下docker 目前支持的联合文件系统包括AUFS/btrfs/vfs/DeviceMappe。

  • 相关阅读:
    插入排序
    JavaMail学习笔记
    汉诺塔问题
    使用Three.js绘制一个虚拟城市
    jquery flotcharts使用简介
    用CSS hack技术解决浏览器兼容性问题.
    IE条件注释详解.
    让IE6也认识!important
    代码重构(转)
    模版+数据分离渲染方式的设计与实现
  • 原文地址:https://www.cnblogs.com/shuaiyin/p/11061947.html
Copyright © 2020-2023  润新知