Lib库使用学习笔记

转自:http://blog.csdn.net/macky0668/article/details/6044867

技术前沿 2008-03-31 14:21:10 阅读177 评论0   字号:大小 订阅

Chapter 1. 为什么使用库文件

我们在实际编程工作中肯定会遇到这种情况:有几个项目里有一些函数模块的功能相同,实现代码也相同,也是我们所说的重复代码。比如,很多项目里都有一个用户验证的功能。代码段如下:

    //UserLogin.h文件,提供函数声明int IsValidUser(char* username, int namelen);//UserLogin.c文件,实现对用户信息的验证int IsValidUser(char* username, int namelen){int IsValid = 0;/*下面是具体的处理代码,略去*/return IsValid;}

如果每个项目都保存着这两个UserLogin.h和UserLogin.c文件,会有以下几个弊端:

  1. 每个项目里都有重复的模块,造成代码重复。

  2. 代码的重用性不好,一旦IsValidUser的代码发生了变化,为了保持设计的一致性,我们还要手工修改其他项目里的UserLogin.c文件,既费时又费力,还容易出错。 库文件就是对公共代码的一种组织形式。

为了解决上面两个弊端,就提出了用库文件存放公共代码的解决方案,其要点就是把公共的(也就是可以被多次复用的)目标代码从项目中分离出来,统一存放到库文件中,项目要用到这些代码的时候,在编译或者运行的时候从库文件中取得目标代码即可。库文件又分两种:静态库和动态库。

简单的说,如果程序是在编译时加载库文件的,就是使用了静态库,静态库的文件名格式是"lib*.a"。如果是在运行时加载目标代码,就成为动态库,动态库的文件名格式是"lib*.so.*"。换句话说,如果是使用静态库,则静态库代码在编译时就拷贝到了程序的代码段,程序的体积会膨胀。如果使用动态库,则程序中只保留库文件的名字和函数名,在运行时去查找库文件和函数体,程序的体积基本变化不大。

静态库的原则是“以空间换时间”,增加程序体积,减少运行时间;动态库则是“以时间换空间”,增加了运行时间,但减少了程序本身的体积。在附录的PIC部分,也会做一些说明。

从文件的格式角度讲,静态库的本质是一种档案文件(.o文件的集合),其中包含了一个内容索引 (也可以不包含,但没有索引的静态库不能用来链接,在附录的ar 和各个模块既.o文件;而动态库是ELF格式的文件,可以被用来加载和执行,而静态库不可以。

还有一种库文件,共享库。看到网上有些资料说,动态库就是共享库的一种变种, 由于没有使用到,没有详细研究。

有时候,会在目录中看到以".la"或".lo"结尾的文件,这些是GNU的工具libtool生成和使用的文件, 用来说明实际库文件的使用信息和以来关系,详细的内容会在以后automake autoconf and libtool的文档中介绍。

Chapter 3. 静态库的生成和使用

3.1. 制作最简单的静态库文件

编写如下两个文件,放在同一目录中:

  mylib.h  //静态库头文件void test();mylib.c  //静态库实现文件#include <stdio.h>void test(){printf("hello world./n");}

使用下边的命令生成静态库:

  gcc -c mylib.car rc libmy.a mylib.o

这样就生成了静态库libmy.a,注意一定要以lib*.a这样的格式命名,否则链接器ld不能识别。 使用命令"file libmy.a"看看它的格式,是一个档案文件。我们可以使用 nm查看它的内部构成:

  [root @Benson libtest]# nm libmy.alibmy.o:U printf00000000 T test

这表示静态库有模块libmy.o,在使用的时候,gcc会根据需要将函数名得到模块,然后从静态库中 提取出对应的".o"文件的内容,然后用来链接,就是使用单独的".o"文件一样。

4.1. 制作最简单的动态库文件

我们同样使用上边的mylib.h和mylib.c文件,但使用不同的命令生成库文件:

  gcc -fpic -shared libmy.so libmy.c

这样就生成了动态库文件libmy.so,-fpic这个选项指定是否使用 PIC,这个选项的使用需要系统平台的支持,一般建议添加,如果不支持,gcc会报错。

生成libmy.so后,使用"file libmy.so"命令,可以看到是一个ELF格式的文件,这说明共享库的 使用需要通过符号解析和重定位加载入内存才能使用。

动态库有两种使用方法,隐式调用和显示调用。隐式调用的方法跟静态库的使用方法一样,都是通过 gcc的"-I -L"参数指定库文件的路径,如果同一个库文件,在系统中同时存在静态库和动态库,默认情况下 gcc主动链接动态库。但也可以通过gcc的"--static"选项,强制指定使用静态库。

gcc -I./ -o main main.c -L./ -lmy  # 使用动态库生成maingcc main.c ./libmy.so -o main   # 使用动态库生成main,不同的地方在于指定了libmy.so的加载路径,这种用法很少用。详细的内容附录ld.sogcc --static -I./ -o main main.c -L./ -lmy  # 使用静态库生成main

输入"file main"命令,可以看到:

[root @Benson libtest]# file mainmain: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5,dynamically linked (uses shared libs), not stripped

运行程序,报错:

  [root @Benson libtest]# ./main./main: error while loading shared libraries: libmy.so: cannot open shared object file: No such file or directory

没有找到libmy.so这个动态库,我这个库文件就在当前目录下?

前边已经说过动态库需要加载到内存中,才能使用。注意,链接和加载是两码事, 链接发生在生成可执行程序的阶段,加载发生在运行阶段。gcc的"-I -L"参数只是保证在链接阶段 指定文件路径,和加载无关,当然也有办法可以使链接阶段的路径信息在加载阶段起作用, 这些涉及到gcc的详细用法,和可执行文件的格式,附录中会简单介绍一些。运行时的加载的工作 不是由main完成的,而是由/lib/ld.so(不同平台会有些出入)完成。他有自己的查找动态连接库的规则, 所以在不更新ld.so的查找路径的情况下,会出现上边的错误。

根据ld.so的man手册,ld.so共有六种方式查找需要的动态库,这里只介绍常用的三种方式,详细的 内容见附录ld.so

  1. 按照环境变量LD_LIBRARY_PATH的内容查找(setuid类的程序排除)

  2. 在库高速缓存文件ld.so.conf中给出的路径查找

  3. 在/lib,/usr/lib目录下查找

相对应的,可以分别通过设置环境变量LD_LIBRARY_PATH,通过在ld.so.conf文件中添加路径和将 动态库文件复制到/lib、/usr/lib目录下,使动态库可以在加载时被找到。

  [root @Benson libtest]# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/`pwd`[root @Benson libtest]# cat `pwd` >> /etc/ld.so.conf ; ldconfig[root @Benson libtest]# cp libmy.so /lib -f

以上三种方式都可以工作。关于ldconfig的使用详解,见 附录ldconfig

此时再执行main,可以正常运行了。

通过ldd命令,可以看到main程序依赖于libmy.so。

  [root @Benson libtest]# ldd mainlibtest.so => not foundlibc.so.6 => /lib/tls/libc.so.6 (0x42000000)/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

动态库的显示调用,也需要库文件被加载到内存中。但是使用方法和静态库完全不同。需要系统 调用——dlopen dlsym dlclose dlerror 的支持。可以使用同一个libmy.so文件,但是需要新的测试文件 :

/* main.c 测试动态库显式调用的程序 */#include <dlfcn.h>        //用于动态库管理的系统头文件  #include "libmy.h"   //要把函数的头文件包含进来,否则编译时会报错int main(void){/* 声明对应的函数的函数指针 */void (*pTest)();   /* 加载动态库 */void *pdlHandle = dlopen("libtest.so", RTLD_LAZY);/* 错误处理 */if(pdlHandle == NULL ){printf("Failed load library/n");return -1;}char* pszErr = dlerror();if(pszErr != NULL){printf("%s/n", pszErr);return -1;}/* 获取函数的地址 */pTest = dlsym(pdlHandle, "test");pszErr = dlerror();if(pszErr != NULL){printf("%s/n", pszErr);dlclose(pdlHandle);return -1;}/* 实现函数调用 */pTest ();/*程序结束时关闭动态库 */dlclose(pdlHandle);return 0;}

编译时需要加入-ldl选项:

[root @Benson libtest]# gcc -ldl -o main main.c

运行程序,成功。这种方式,不需要在链接阶段指定动态库的位置。在运行阶段,调用要使用的函数

通过ldd命令,可以看到main程序不再依赖于libmy.so,转为 依赖libdl.so

libdl.so.2 => /lib/libdl.so.2 (0x40034000)libc.so.6 => /lib/tls/libc.so.6 (0x42000000)/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

ldconfig用来维护ld.so加载的动态库的路径信息和依赖信息,路径信息存放在/etc/ld.so.conf中 ,/etc/ld.so.cache中存放了/lib /usr/lib 和/etc/ld.conf包含的路径内的所有库信息。 可以通过ldd命令查看动态库的依赖信息。

ldconfig有两种常用的使用方法:

  1. 将需要查找的目录加入/etc/ld.so.conf,然后运行ldconfig更新,更新结果永久有效

  2. ldconfig + 路径,这种方式能直接更新指定的目录,但是结果在下次执行ldconfig 时,将会失效。

ldconfig的选项比较有用的是-v和-n,其他的可参考man手册:

  • -v 或 --verbose 详细的显示扫描的目录及搜索到的动态连接库,还有它所创建的 链接的名字

  • -n 使扫描时,不扫描默认目录/lib、/usr/lib和/etc/ld.so.conf包含的路径

ar命令用来创建归档文件,修改归档文件,和提取归档文件中的模块。这里归档文件就是静态库, 模块就是里边的各个".o"文件。在静态库中每个".o"文件的内容,权限,时间戳,文件所有者,文件所属组 都将被保留,并且可在提取时回复。ar通过在静态库中创建模块列表来维护内容结构。可以通过"nm -s" 命令查看这个列表内容。ar命令的选项很多,可通过查看man手册获取全部内容,这里我举几个例子来说明 它的主要功能:

  1. 创建静态库文件:

      ar -rc libtest.a libmy.o libtest.o

    选项分为"r" 和 "c"。-r 表示将后边的".o"模块加入库文件中;-c 表示当库文件不存在时创建。 同样,向存在的库文件添加新的模块时,只需要-r 选项即可。

  2. 查看静态库中的模块:

      [root @Benson libtest]# ar -t libtest.alibmy.olibtest.o
  3. 删除静态库中的模块:

      ar -d libtest.a libmy.o

    此时在查看库的模块,libmy.o就不存在了。

  4. 提取静态库中的模块:

      [root @Benson libtest]# ls libtest.a[root @Benson libtest]# ar -x libtest.a libmy.o ; lslibtest.a libmy.o

    从libtest.a中提取出了libmy.o文件

nm 命令用来查看目标文件中的符号信息,nm命令对每个符号显示如下内容:

  • 每个符号的值。

  • 当指定-S选项时,显示函数符号对应的函数体的大小。

  • 每个符号的类型,如,U表示该符号没有在库中定义;T表示该符号在库中定义。还有其他的内容涉及 到目标文件的格式,这里就不详细介绍。

  • 每个符号的名字。

nm的选项中,"-s"选项用来显示静态库文件的列表信息。其他选项可查看man手册。

PIC(Position Independent Code),位置无关编码。是一种库文件的编码组织方式, 其特点是方便系统装载。是否支持这种格式的动态库,与硬件系统平台有关。

简单的说,这种格式的库文件包含两个符号GOT(_GLOBAL_OFFSET_TABLE_)和PLT (Procedure Linkage Table)。调用函数实际调用的是PLT里的地址,访问全局变量通过GOT里边的地址 这些地址在未运行时不确定,在加载运行后,才被解析出来,所以可以加载到进程地址空间的任何地方。

摘自网络的一篇文章,含有更多信息:

PIC code radically differs from conventional code in the way it calls functions and operates on data variables.

It will access these functions and data through an indirection table, the "Global Offset Table" (GOT), by software convention accessible using the reserved name "_GLOBAL_OFFSET_TABLE_".

The exact mechanism used for this is hardware architecture dependent, but usually a special machine register is reserved for setting up the location of the GOT when entering a function.

The rationale behind this indirect addressing is to generate code that can be independently accessed of the actual load address.

In a true PIC library without relocations in the text segment, only the symbols exported in the "Global Offset Table" need updating at run-time depending on the current load address of the various shared libraries in the address space of the running process.

Likewise, procedure calls to globally defined functions are redirected through the "Procedure Linkage Table" (PLT) residing in the data segment of the core image. Again, this is done to avoid run-time modifications to the text segment.

The linker-editor allocates the Global Offset Table and Procedure Linkage Table when combining PIC object files into an image suitable for mapping into the process address space. It also collects all symbols that may be needed by the run-time link-editor and stores these along with the image's text and data bits. Another reserved symbol, _DYNAMIC is used to indicate the presence of the run-time linker structures. Whenever _DYNAMIC is relocated to 0, there is no need to invoke the run-time link- editor. If this symbol is non-zero, it points at a data structure from which the location of the necessary relocation- and symbol information can be derived. This is most notably used by the start-up module, crt0, crt1S and more recently Scrt1. The _DYNAMIC structure is conventionally located at the start of the data segment of the image to which it pertains.

On most architectures, when you compile source code to object code, you need to specify whether the object code should be position independent or not. There are occasional architectures which don't make the distinction, usually because all object code is position independent by virtue of the Application Binary Interface (ABI), or less often because the load address of the object is fixed at compile time, which implies that shared libraries are not supported by such a platform). If an object is compiled as position independent code (PIC), then the operating system can load the object at any address in preparation for execution. This involves a time overhead, in replacing direct address references with relative addresses at compile time, and a space overhead, in maintaining information to help the runtime loader fill in the unresolved addresses at runtime. Consequently, PIC objects are usually slightly larger and slower at runtime than the equivalent non-PIC object. The advantage of sharing library code on disk and in memory outweigh these problems as soon as the PIC object code in shared libraries is reused.

PIC compilation is exactly what is required for objects which will become part of a shared library. Consequently, libtool builds PIC objects for use in shared libraries and non-PIC objects for use in static libraries. Whenever libtool instructs the compiler to generate a PIC object, it also defines the preprocessor symbol, `PIC', so that assembly code can be aware of whether it will reside in a PIC object or not.

Typically, as libtool is compiling sources, it will generate a `.lo' object, as PIC, and a `.o' object, as non-PIC, and then it will use the appropriate one of the pair when linking executables and libraries of various sorts. On architectures where there is no distinction, the `.lo' file is just a soft link to the `.o' file.

In practice, you can link PIC objects into a static archive for a small overhead in execution and load speed, and often you can similarly link non-PIC objects into shared archives.

When you use position-independent code, relocatable references are generated as an indirection that use data in the shared object's data segment. The text segment code remains read-only, and all relocation updates are applied to corresponding entries within the data segment.

If a shared object is built from code that is not position-independent, the text segment will usually require a large number of relocations to be performed at runtime. Although the runtime linker is equipped to handle this, the system overhead this creates can cause serious performance degradation.

You can identify a shared object that requires relocations against its text segment using tools such as 'readelf -d foo' and inspect the output for any TEXTREL entry. The value of the TEXTREL entry is irrelevant. Its presence in a shared object indicates that text relocations exist.

/lib/ld.so是系统的动态库加载器,属于Glibc编译生成的,与系统平台密切相关。加载目标代码 涉及的内容很多,但是这里主要围绕ld.so的动态库搜索过程,做全面的解释。

ld.so在加载动态库的时候,分先后顺序,按以下六步搜索:

  1. 可执行文件自身的动态段(.dynamic section)中DT_NEED入口中包含的路径。

    首先应该了解到,每个执行文件的数据是按照段来存储的,有代码段,数据段,BSS段等。那么 这里的动态段也是其中的一部分。我们借助readelf命令来查看这部分信息:

      [root @Benson libtest]# readelf -d mainTag        Type                         Name/Value0x00000001 (NEEDED)                     Shared library: [liba.so]0x00000001 (NEEDED)                     Shared library: [libc.so.6]0x0000000c (INIT)                       0x80483400x0000000d (FINI)                       0x80484e40x00000004 (HASH)                       0x80481280x001000005 (STRTAB)                     0x80482400x00000006 (SYMTAB)                     0x80481700x0000000a (STRSZ)                      172 (bytes)0x0000000b (SYMENT)                     16 (bytes)0x00000015 (DEBUG)                      0x00x00000003 (PLTGOT)                     0x80495fc0x00000002 (PLTRELSZ)                   16 (bytes)0x00000014 (PLTREL)                     REL0x00000017 (JMPREL)                     0x80483300x00000011 (REL)                        0x80483280x00000012 (RELSZ)                      8 (bytes)0x00000013 (RELENT)                     8 (bytes)0x6ffffffe (VERNEED)                    0x80483080x6fffffff (VERNEEDNUM)                 10x6ffffff0 (VERSYM)                     0x80482ec0x00000000 (NULL)                       0x0

    readelf命令,能够查看ELF格式的文件的所有信息,在这里先不详细介绍。对于上边显示的信息 我们注意前两行,类型是NEEDED,后便对应的是内容,这些就是DT_NEED部分的内容。而内容部分的 liba.so和libc.so.6是没有路径信息的。所以ld.so不能根据这里的信息加载liba.so库。

    但如果使用如下的命令生成main程序:

      [root @Benson libtest]# gcc -o main main.o ./liba.so[root @Benson libtest]# readelf -d main0x00000001 (NEEDED)                     Shared library: [./liba.so]0x00000001 (NEEDED)                     Shared library: [libc.so.6]0x0000000c (INIT)                       0x80483400x0000000d (FINI)                       0x80484e40x00000004 (HASH)                       0x80481280x001000005 (STRTAB)                     0x80482400x00000006 (SYMTAB)                     0x80481700x0000000a (STRSZ)                      172 (bytes)0x0000000b (SYMENT)                     16 (bytes)0x00000015 (DEBUG)                      0x00x00000003 (PLTGOT)                     0x80495fc0x00000002 (PLTRELSZ)                   16 (bytes)0x00000014 (PLTREL)                     REL0x00000017 (JMPREL)                     0x80483300x00000011 (REL)                        0x80483280x00000012 (RELSZ)                      8 (bytes)0x00000013 (RELENT)                     8 (bytes)0x6ffffffe (VERNEED)                    0x80483080x6fffffff (VERNEEDNUM)                 10x6ffffff0 (VERSYM)                     0x80482ec0x00000000 (NULL)                       0x0

    此时liba.so就有了路径信息,那么ld.so就会在当前路径下查找liba.so。

  2. 可执行文件自身的动态段(.dynamic section)中DT_RPATH入口中包含的路径(存在的话)。 这种方式的详细信息我没有找到,由于不常用,我将这个问题暂时保留。

  3. 环境变量LD_LIBRARY_PATH路径(setuid类的程序排除)。

  4. 可执行文件自身的动态段(.dynamic section)中LD_RUNPATH入口给出的路径(存在的话)这种方式 的详细信息也暂时没有。但同第二种方式,当有链接选项--ignore-rpath LIST时会把LIST中 的RPATH和RUNPATH信息忽略掉。

  5. 库高速缓存文件ld.so.conf中给出的路径。这里补充一点,在做LFS的时候可能会遇到,ld.so使用 那个缓冲文件,可以通过在编译Glibc的时候,使用命令"echo 'slibdir= 路径' >> configpams"来更改。

  6. 默认系统路径/lib,/usr/lib。

这部分的内容涉及的知识非常的广,属于链接器和加载器这部分的内容。根据我们的工作需要,目前 了解到这种程度已经足够。顺便再补充一点小技巧:通过设置LD_TRACE_LOADED_OBJECTS=y环境变量,可以 跟踪库的加载过程。

在了解了以上的所有内容后,我们可以根据掌握的知识做一个有趣,可怕的试验,因为这可能会 引起一些安全隐患。

首先创建两个库文件liba.so和libb.so。

/* 公用的头文件内容 *//* test.h */void test(void);/*************************************************//* liba.c的内容 */#include <stdio.h>void test(void){printf("hello world/n");}/*************************************************//* libb.c的内容 */#include <stdio.h>void test(void){printf("another say hello world/n");}
[root @Benson libtest]# gcc -fpic -shared -o liba.so liba.c[root @Benson libtest]# gcc -fpic -shared -o libb.so libb.c

编写测试程序main,并链接liba.so:

#include "test.h"int main(void){test();return 0;}
[root @Benson libtest]# gcc -o main main.c -L./ -la[root @Benson libtest]# ./mainhello world

我们尝试这样的命令,然后在运行main,会得到有趣的结果:

[root @Benson libtest]# sed "s#liba/.so#libb.so#" main > main2[root @Benson libtest]# chmod +x main2[root @Benson libtest]# ./main2another say hello world

运行结果好像被篡改了是吧。那么如果将这种方法用在setuid命令上,那不是要天下打乱了?还好 linux系统早就做出了限制,首先,setuid这类命令的权限控制非常严格,其次通过设置ld.so,使 LD_LIBRARY_PATH环境变量对于此类程序不起作用。但是我们自己的程序呢?

Lib库使用学习笔记相关推荐

  1. 【Python-pywt】 小波变化库—Pywavelets 学习笔记

    (转载) [Python ]小波变化库--Pywavelets 学习笔记_nanbei2463776506的博客-CSDN博客 https://blog.csdn.net/nanbei24637765 ...

  2. 《C++标准库》学习笔记 — STL —流

    <C++标准库>学习笔记 - STL -流 一.操控器 1.原理 2.自定义操控器 3.控制输入的宽度 二.自定义 I/O 操作符 1.重载输出操作符 2.输入操作符 三.自定义格式化标志 ...

  3. 线性代数库 Armadillo 学习笔记

    线性代数库 Armadillo 学习笔记 项目环境 Xcode 项目配置 前置代码 矩阵基本操作 全零矩阵 全一矩阵 对角矩阵 打印一个矩阵 修改获取矩阵元素 获取某行某列 获取对角向量 转置矩阵 逆 ...

  4. 点云库PCL学习笔记 -- 点云滤波Filtering -- 3. StatisticalOutlierRemoval 统计滤波器

    点云库PCL学习笔记 -- 点云滤波Filtering -- 3.StatisticalOutlierRemoval 统计滤波器 StatisticalOutlierRemoval 统计滤波器相关简介 ...

  5. Python第三方库pygame学习笔记(一)

    Pygame Python最经典的2D游戏开发第三方库,也支持3D游戏开发 Pygame适合用于游戏逻辑验证.游戏入门及系统演示验证 Pygame是一种游戏开发引擎,基本逻辑具有参考价值 pygame ...

  6. CImg库快速学习笔记

    CImg库快速学习笔记 中山大学,曾坤老师的<计算机视觉>需要. 本文部分翻译自CImg_latest安装包中附带的英文文档的8 Class Documentation部分,以及互联网中的 ...

  7. STM32F407霸天虎HAL库CubeMX学习笔记——DS18B20

    STM32F407霸天虎HAL库CubeMX学习笔记--DS18B20 一.软件准备 二.硬件准备 三.CubeMX配置 四.Keil printf重写 tim.c DS18B20.h DS18B20 ...

  8. linux添加自己的库,Linux学习笔记——例叙makefile 增加自定义共享库

    Linux学习笔记--例说makefile 增加自定义共享库 0.前言 从学习C语言开始就慢慢开始接触makefile,查阅了很多的makefile的资料但总感觉没有真正掌握makefile,如果自己 ...

  9. 《装载、链接与库》学习笔记

    <装载.链接与库>前七章学习笔记 今天偶然翻到前面几章,发现诸多"新鲜点",造成这样的原因有一部分就是读过后没有即时沉淀先来,形成自己的知识体系.我觉得有必要写一下读书 ...

最新文章

  1. 单目深度估计方法综述
  2. MyBatis从缓存查找数据的依据
  3. JAVA基础--final、static区别以及类加载顺序
  4. java里面有控制器吗_mvc中 控制器部分可以使用Javabean完成吗?为什么?
  5. aspx是什么语言_什么是网页
  6. 第一个错误的版本_寻找第一个错误的版本
  7. 盘阵多路径学习(转)
  8. C#中引用第三方ocx控件引发的问题以及解决办法
  9. AutoCAD 2008中文版实用自学手册•机械设计篇
  10. WEB前端工具推荐丨分享6个热门颜色选择器组件
  11. 如何在DOS系统下删除隐藏的文件
  12. astc纹理压缩格式
  13. 金融货币学笔记(米什金)第一章 为什么研究货币、银行与金融市场
  14. 031:verbatim 标签
  15. linux挂移动硬盘命令,linux挂载命令mount及U盘、移动硬盘的挂载
  16. JAVA 中 Redis与ehcache对比与使用
  17. w10投影全屏设置_win10如何让投影仪铺满全屏
  18. 人生最大的危机就是没有危机感
  19. 考研政治——马克思原理唯物论之意识观
  20. 如何通过短信转发在iPad和Mac上发送和接收短信

热门文章

  1. matlab中m_map工具箱绘制大圆航线
  2. php程序员的出路,php程序员有前途吗
  3. 【转】 bio 与块设备驱动
  4. 带省略号的比喻句_带有省略号的句子
  5. mac mini调整屏幕亮度
  6. ubuntu虚拟机与windows主机文件传输(命令行)
  7. ubuntu grub深入剖析个性设置
  8. cad lisp 二次抛物线_用Cad画二次抛物线.doc
  9. [POI2005] SZA-Template
  10. 广电为什么禁止投屏_广电的机顶盒怎么投屏