Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >linux动态库和静态库的使用_静态库的使用

linux动态库和静态库的使用_静态库的使用

作者头像
全栈程序员站长
发布于 2022-11-11 07:49:14
发布于 2022-11-11 07:49:14
4.9K00
代码可运行
举报
运行总次数:0
代码可运行

文章目录

动静态库的基本原理

动静态库的本质是可执行程序的“半成品”。

我们都知道,一堆源文件和头文件最终变成一个可执行程序需要经历以下四个步骤:

  1. 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
  2. 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
  3. 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
  4. 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。

例如,用test1.c、test2.c、test3.c、test4.c以及main1.c形成可执行文件,我们需要先得到各个文件的目标文件test1.o、test2.o、test3.o、test4.o以及main1.o,然后再将这写目标文件链接起来,最终形成一个可执行程序。

如果我们在另一个项目当中也需要用到test1.c、test2.c、test3.c、test4.c和项目的main2.c或者main3.c分别形成可执行程序,那么可执行程序生成的步骤也是一样的。

而实际上,对于可能频繁用到的源文件,比如这里的test1.c、test2.c、test3.c、test4.c,我们可以将它们的目标文件test1.o、test2.o、test3.o、test4.o进行打包,之后需要用到这四个目标文件时就可以之间链接这个包当中的目标文件了,而这个包实际上就可以称之为一个库。

实际上,所有库本质都是一堆目标文件(xxx.o)的集合,库的文件当中并不包含主函数而只是包含了大量的方法以供调用,所以说动静态库本质是可执行程序的“半成品”。

认识动静态库

Linux下创建文件编写以下代码,并生成可执行程序。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>

int main()
{ 
   
	printf("hello world\n"); //库函数
	return 0;
}

这是最简单的代码,运行结果大家也都知道,就是hello world。

下面我们就通过这份简单的代码来认识一下动静态库

在这份代码当中我们可以通过调用printf输出hello world,主要原因是gcc编译器在生成可执行程序时,将C标准库也链接进来了。

在Linux下,我们可以通过ldd 文件名来查看一个可执行程序所依赖的库文件。

这其中的libc.so.6就是该可执行程序所依赖的库文件,我们通过ls命令可以发现libc.so.6实际上只是一个软链接。

实际上该软链接的源文件libc-2.17.solibc.so.6在同一个目录下,为了进一步了解,我们可以通过file 文件名命令来查看libc-2.17.so的文件类型。

此时我们可以看到,libc-2.17.so实际上就是一个共享的目标文件库,准确来说,这还是一个动态库。

  • 在Linux当中,以.so为后缀的是动态库,以.a为后缀的是静态库。
  • Windows当中,以.dll为后缀的是动态库,以.lib为后缀的是静态库。

这里可执行程序所依赖的libc.so.6实际上就是C动态库,当我们去掉一个动静态库的前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字。

而gcc/g++编译器默认都是动态链接的,若想进行静态链接,可以携带一个-static选项。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos testlib]$ gcc -o mytest-s mytest.c -static

此时生成的可执行程序就是静态链接的了,可以明显发现静态链接生成的可执行程序的文件大小,比动态链接生成的可执行程序的文件大小要大得多。

静态链接生成的可执行程序并不依赖其他库文件,此时当我们使用ldd 文件名命令查看该可执行程序所依赖的库文件时就会看到以下信息。

此外,当我们分别查看动静态链接生成的可执行程序的文件类型时,也可以看到它们分别是动态链接和静态链接的。

动静态库各自的特征

静态库

静态库是程序在编译链接的时候把库的代码复制到可执行文件当中的,生成的可执行程序在运行的时候将不再需要静态库,因此使用静态库生成的可执行程序的大小一般比较大。

优点:

  • 使用静态库生成可执行程序后,该可执行程序就可以独自运行,不再需要库了。

缺点:

  • 使用静态库生成可执行程序会占用大量空间,特别是当有多个静态程序同时加载而这些静态程序使用的都是相同的库,这时在内存当中就会存在大量的重复代码。

动态库

动态库是程序在运行的时候才去链接相应的动态库代码的,多个程序共享使用库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。

在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接。动态库在多个程序间共享,节省了磁盘空间,操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

优点:

  • 节省磁盘空间,且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码。

缺点:

  • 必须依赖动态库,否则无法运行。

静态库的打包与使用

为了更容易理解,下面演示动静态库的打包与使用时,都以下面的四个文件为例,其中两个源文件add.csub.c,两个头文件add.hsub.h

add.h当中的内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once

extern int my_add(int x, int y);

add.c当中的内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "add.h"

int my_add(int x, int y)
{ 
   
	return x + y;
}

sub.h当中的内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once

extern int my_sub(int x, int y);

sub.c当中的内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "sub.h"

int my_sub(int x, int y)
{ 
   
	return x - y;
}

代码内容都非常简单,我就不做过多的阐述了。

打包

下面我们就利用这四个文件打包生成一个静态库:

第一步:让所有源文件生成对应的目标文件

第二步:使用ar命令将所有目标文件打包为静态库

ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar命令的-r选项和-c选项进行打包。

  • -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
  • -c(create):建立静态库文件。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos static]$ ar -rc libcal.a add.o sub.o

此外,我们可以用ar命令的-t选项和-v选项查看静态库当中的文件。

  • -t:列出静态库中的文件。
  • -v(verbose):显示详细的信息。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos static]$ ar -tv libcal.a

第三步:将头文件和生成的静态库组织起来

当我们把自己的库给别人用的时候,实际上需要给别人两个文件夹,一个文件夹下面放的是一堆头文件的集合,另一个文件夹下面放的是所有的库文件。

因此,在这里我们可以将add.hsub.h这两个头文件放到一个名为include的目录下,将生成的静态库文件libcal.a放到一个名为lib的目录下,然后将这两个目录都放到mathlib下,此时就可以将mathlib给别人使用了。

使用Makefile

当然,我们可以将上述所要执行的命令全部写到Makefile当中,后续当我们要生成静态库以及组织头文件和库文件时就可以一步到位了,不至于每次重新生成的时候都要敲这么多命令,这也体现了Makefile的强大。

编写Makefile后,只需一个make就能生成所有源文件对应的目标文件进而生成静态库。

一个make output就能将头文件和静态库组织起来。

使用

创建源文件main.c,编写下面这段简单的程序来尝试使用我们打包好的静态库。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <add.h>

int main()
{ 
   
	int x = 20;
	int y = 10;
	int z = my_add(x, y);
	printf("%d + %d = %d\n", x, y, z);
	return 0;
}

现在该目录下就只有main.c和我们刚才打包好的静态库。

方法一:使用选项

此时使用gcc编译main.c生成可执行程序时需要携带三个选项:

  • -I:指定头文件搜索路径。
  • -L:指定库文件搜索路径。
  • -l:指明需要链接库文件路径下的哪一个库。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos project]$ gcc main.c -I./mathlib/include -L./mathlib/lib -lcal

此时就可以成功使用我们自己打包的库文件并生成可执行程序。

说明一下:

  1. 因为编译器不知道你所包含的头文件add.h在哪里,所以需要指定头文件的搜索路径。
  2. 因为头文件add.h当中只有my_add函数的声明,并没有该函数的定义,所以还需要指定所要链接库文件的搜索路径。
  3. 实际中,在库文件的lib目录下可能会有大量的库文件,因此我们需要指明需要链接库文件路径下的哪一个库。库文件名去掉前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字。
  4. -I-L-l这三个选项后面可以加空格,也可以不加空格。

方法二:把头文件和库文件拷贝到系统路径下

既然编译器找不到我们的头文件和库文件,那么我们直接将头文件和库文件拷贝到系统路径下不就行了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos project]$ sudo cp mathlib/include/* /usr/include/ 
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos project]$ sudo cp mathlib/lib/libcal.a /lib64/

需要注意的是,虽然已经将头文件和库文件拷贝到系统路径下,但当我们使用gcc编译main.c生成可执行程序时,还是需要指明需要链接库文件路径下的哪一个库。

此时才能成功使用我们自己打包的库文件并生成可执行程序。

为什么之前使用gcc编译的时候没有指明过库名字?

因为我们使用gcc编译的是C语言,而gcc就是用来编译C程序的,所以gcc编译的时候默认就找的是C库,但此时我们要链接的是哪一个库编译器是不知道的,因此我们还是需要使用-l选项,指明需要链接库文件路径下的哪一个库。

扩展: 实际上我们拷贝头文件和库文件到系统路径下的过程,就是安装库的过程。但并不推荐将自己写的头文件和库文件拷贝到系统路径下,这样做会对系统文件造成污染。

动态库的打包与使用

打包

动态库的打包相对于静态库来说有一点点差别,但大致相同,我们还是利用这四个文件进行打包演示:

第一步:让所有源文件生成对应的目标文件

此时用源文件生成目标文件时需要携带-fPIC选项:

  • -fPIC(position independent code):产生位置无关码。

说明一下:

  1. -fPIC作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
  2. 如果不加-fPIC选项,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的拷贝,并且每个拷贝都不一样,取决于这个.so文件代码段和数据段内存映射的位置。
  3. 不加-fPIC编译出来的.so是要在加载时根据加载到的位置再次重定位的,因为它里面的代码BBS位置无关代码。如果该.so文件被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。
  4. 我们总是用-fPIC来生成.so,但从来不用-fPIC来生成.a。但是.so一样可以不用-fPIC选项进行编译,只是这样的.so必须要在加载到用户程序的地址空间时重定向所有表目。

第二步:使用-shared选项将所有目标文件打包为动态库

与生成静态库不同的是,生成动态库时我们不必使用ar命令,我们只需使用gcc的-shared选项即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos dynamic]$ gcc -shared -o libcal.so add.o sub.o

第三步:将头文件和生成的动态库组织起来

与生成静态库时一样,为了方便别人使用,在这里我们可以将add.hsub.h这两个头文件放到一个名为include的目录下,将生成的动态库文件libcal.so放到一个名为lib的目录下,然后将这两个目录都放到mlib下,此时就可以将mlib给别人使用了。

使用Makefile

当然,生成动态库也可以将上述所要执行的命令全部写到Makefile当中,后续当我们要生成动态库以及组织头文件和库文件时就可以一步到位了。

编写Makefile后,只需一个make就能生成所有源文件对应的目标文件进而生成动态库。

一个make output就能将头文件和动态库组织起来。

使用

我们还是用刚才使用过的main.c来演示动态库的使用。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <add.h>

int main()
{ 
   
	int x = 20;
	int y = 10;
	int z = my_add(x, y);
	printf("%d + %d = %d\n", x, y, z);
	return 0;
}

现在该目录下就只有main.c和我们刚才打包好的动态库。

说明一下,使用该动态库的方法与刚才我们使用静态库的方法一样,我们既可以使用 -I-L-l这三个选项来生成可执行程序,也可以先将头文件和库文件拷贝到系统目录下,然后仅使用-l选项指明需要链接的库名字来生成可执行程序,下面我们仅以第一种方法为例进行演示。

此时使用gcc编译main.c生成可执行程序时,需要用-I选项指定头文件搜索路径,用-L选项指定库文件搜索路径,最后用-l选项指明需要链接库文件路径下的哪一个库。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos project]$ gcc main.c -I./mlib/include -L./mlib/lib -lcal

与静态库的使用不同的是,此时我们生成的可执行程序并不能直接运行。

需要注意的是,我们使用-I-L-l这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁,但是当生成的可执行程序生成后就与编译器没有关系了,此后该可执行程序运行起来后,操作系统找不到该可执行程序所依赖的动态库,我们可以使用ldd命令进行查看。

可以看到,此时可执行程序所依赖的动态库是没有被找到的。

解决该问题的方法有以下三个:

方法一:拷贝.so文件到系统共享库路径下

既然系统找不到我们的库文件,那么我们直接将库文件拷贝到系统共享的库路径下,这样一来系统就能够找到对应的库文件了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos project]$ sudo cp mlib/lib/libcal.so /lib64

可执行程序也就能够顺利运行了。

方法二:更改LD_LIBRARY_PATH

LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos project]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/cl/BasicIO/testlib/project/mlib/lib

此时我们再用ldd命令查看该可执行程序就会发现,系统现在就可以找到该可执行程序所依赖的动态库了。

现在我们也就能正常运行该可执行程序了。

方法三:配置/etc/ld.so.conf.d/

我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题,/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了。

首先将库文件所在目录的路径存入一个以.conf为后缀的文件当中。

然后将该.conf文件拷贝到/etc/ld.so.conf.d/目录下。

但此时我们用ldd命令查看可执行程序时,发现系统还是没有找到该可执行程序所依赖的动态库。

这时我们需要使用ldconfig命令将配置文件更新一下,更新之后系统就可以找到该可执行程序所依赖的动态库了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[cl@VM-0-15-centos project]$ sudo ldconfig

而此时我们也就可以正常运行该可执行程序了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/187400.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年9月30日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux之动态库和静态库
我们了解了动态库和静态库的相关概念,但是我们还是不理解库是个什么东西。 假设,我们做了一个小程序,只希望提供给用户小程序的功能,不希望暴露我们的源码。我们可以选择给用户提供我们的.o可重定位目标二进制文件(gcc -c 文件)与头文件。让用户使用我们提供的.o文件和.h文件进行链接即可。(在编译时,只需要把源文件编译成.o文件,再将其链接即可形成一个可执行程序,因此我们可以直接提供,o文件)。 文件add.c
摘星
2023/10/15
7020
Linux之动态库和静态库
Linux静态库与动态库加载
        关于库相比大家之前肯定使用过,比如C/C++里面的标准库,STL里面的各种库,我们在调用STL里的容器时都需要使用库,那么库到底是什么呢?
咬咬
2024/06/12
3260
Linux静态库与动态库加载
详解动态库和静态库
在C、C++中我们使用过标准库,比如在使用strerror、vector、string等时,都只是调用了这些函数接口,这些都是需要具体的实现。
南桥
2024/06/03
2470
详解动态库和静态库
动态库与静态库
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
二肥是只大懒蓝猫
2023/03/30
2.4K0
动态库与静态库
【Linux】动静态库(超详细)
💢 由于CentOS 8系统2021年12月31日已停止维护服务,CentOS 7系统将于2024年06月30日停止维护服务。CentOS官方不再提供CentOS 9及后续版本,不再支持新的软件和补丁更新。CentOS用户现有业务随时面临宕机和安全风险,并无法确保及时恢复。
IsLand1314
2024/11/19
970
【Linux】动静态库(超详细)
【Linux】静态库和动态库
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
YoungMLet
2024/03/01
9170
【Linux】静态库和动态库
【Linux】掌握库的艺术:我的动静态库封装之旅
在计算机编程中,库(Library)是一个预先编写的代码集合,包含了可以被其他程序调用的函数、类、变量和资源。库的主要目的是为了简化编程过程,提供常用功能的实现,促进代码重用,从而减少开发时间和提高软件的可靠性。
Yui_
2024/11/19
990
【Linux】掌握库的艺术:我的动静态库封装之旅
深入浅出动静态库
  当你在Linux系统上编写和运行程序时,动态库和静态库是两个非常重要的概念。它们不仅影响着程序的编译和执行效率,还直接关系到程序的可移植性和灵活性
用户11029129
2024/06/04
1500
深入浅出动静态库
【库函数】Linux下动态库.so和静态库.a的生成和使用
我在路径/root/host/my_program/asoc/include下创建四个文件
SarPro
2024/04/25
2.1K0
【库函数】Linux下动态库.so和静态库.a的生成和使用
【Linux】动静态库
hello,大家好,今天我们继续学习Linux中的动静态库,我们将从不同的角度来学习如何使用,并如何制作一个可供他人使用的动静态库文件,并试着探究一下动态库加载问题。那我们就开始学习吧!!
破晓的历程
2024/06/24
1430
【Linux】动静态库
linux 动态库 静态库_静态库里面包含动态库
那么这三个时间有什么作用呢? 我们在使用自动化构建工具Makefile时,如果连续make会发现:
全栈程序员站长
2022/11/10
7.5K0
linux 动态库 静态库_静态库里面包含动态库
【Linux】静态库和动态库
系统层面上有.和…硬链接指向目录。假设我们是超级用户,允许给目录建立硬链接,给根目录建立硬链接,从根目录开始查找,当查找硬链接的时候就是根目录,这时候递归式查找,形成了环路查找,最后导致软件无法正常进行查找工作!所以不允许普通用户给目录建立硬链接。
平凡的人1
2023/10/15
4620
【Linux】静态库和动态库
linux动态库和静态库
http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520101023104745738/
bear_fish
2018/09/20
12.6K0
【Linux】动静态库
库分为 静态库(.a)和动态库(.so) 库的命名 以c++的库为例 输入 ls /lib64/libstdc++* 以lib开头要去除 库的真实名字为 stdc++
lovevivi
2023/05/10
2.5K0
【Linux】动静态库
Linux基础——gcc编译、静态库与动态库(共享库)
1、静态库的命名格式 lib + 库的名字 + .a 例:libMyTest.a (MyTest为静态库的名字)
全栈程序员站长
2022/11/10
8.9K0
Linux基础——gcc编译、静态库与动态库(共享库)
【Linux】基础IO --- 软硬链接、acm时间、动静态库制作、动静态链接、动静态库加载原理…
2. 软链接文件soft_file.link有自己独立的inode,可以被当作独立文件看待。 而硬链接文件没有自己独立的inode,无论改变myfile.txt什么内容,hard_file.link都会随着一起改变,所以建立硬链接,实际上根本没有创建新文件,因为没有给硬链接分配独立的inode。
举杯邀明月
2023/04/12
3.7K0
【Linux】基础IO --- 软硬链接、acm时间、动静态库制作、动静态链接、动静态库加载原理…
动态库与静态库优缺点比较分析_c静态库和动态库的区别
我们在编写一个C语言程序的时候,经常会遇到好多重复或常用的部分,如果每次都重新编写固然是可以的,不过那样会大大降低工作效率,并且影响代码的可读性,更不利于后期的代码维护。我们可以把他们制作成相应的功能函数,使用时直接调用就会很方便,还可以进行后期的功能升级。
全栈程序员站长
2022/11/11
3.3K0
【Linux】动态库与静态库的底层比较
我们前两篇文章讲解了如何建立动静态库与如何使用动静态库。 接下来我们就来深入聊聊动静态库。
叫我龙翔
2024/05/13
2910
【Linux】动态库与静态库的底层比较
【Linux系统IO】六、动静态库
​ 我们之前学 gcc 的时候也有接触过一点动静态库的知识,现在要把它单独拿出来讲,主要是因为我们后面肯定在自己开发的时候需要包装自己的库,此时就需要有动静态库的原理知识和使用知识!
利刃大大
2025/03/11
910
【Linux系统IO】六、动静态库
c++动态库和静态库的区别_静态库里面包含动态库
C++静态库与动态库
全栈程序员站长
2022/11/11
2K0
相关推荐
Linux之动态库和静态库
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验