标签:预处理 文件 程序 Hello 地址 人生 P2P 进程 hello
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 1190201324
班 级 1903006
学 生 刁奕宁
指 导 教 师 史先俊
计算机科学与技术学院
2021年5月
摘 要
本文介绍了一个hello.c程序的医生,分析了程序由诞生执行再到消亡的过程。通过对hello程序P2P和020的整体介绍,在linux操作系统下对C语言程序hello.c的运行全过程进行了分析。 分析了从c文件转化为可执行文件过程中的预处理、编译、汇编和链接阶段以及可执行文件执行过程中的进程管理、存储空间管理和I/O管理的原理。
关键词:程序人生;预处理;编译;汇编;链接;进程;异常;虚拟内存;I/O函数
目 录
Windows 10 64位 ;Vmware 14 ;Ubuntu 18 64位
第1章 概述
1.1 Hello简介
在编译器的处理下,hello.c文件经历预处理、编译、汇编、链接,四个步骤,变为可执行文件,然后由shell为其创建一个新的进程并运行它。
1.2 环境与工具
1.2.1 硬件环境 
X64 CPU ;1.80GHZ ;8.00G RAM ;256GHD Disk
1.2.2 软件环境

Windows 10 64位 ;Vmware 14 ;Ubuntu 18 64位
1.2.3 开发工具
Visual Studio 2019 64位 ;CodeBlocks 64位
1.3 中间结果
文件名称 | 文件描述 |
hello.c | hello的c语言代码(源文件) |
hello.i | 预处理后的文件 |
hello.s | 编译后产生的汇编文件 |
hello.o | 可重定位的目标文件 |
hello | 可执行文件 |
1.4 本章小结


第2章 预处理


2.1 预处理的概念与作用
在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下:
1、将源文件中以”include”格式包含的文件复制到编译的源文件中。
预处理名称 | 意义 |
#define | 宏定义 |
#undef | 撤销已定义过的宏名 |
#include | 使编译程序将另一源文件嵌入到带有#include的源文件中 |
#if | #if的一般含义是如果#if后面的常量表达式为true,则编译它与#endif之间的代码,否则跳过这些代码。命令endif标识一个#if块的结束。#else命令的功能有点像c语言中的else,#else建立另一选择(在#if失败的情况下)。#elif命令意义与else if相同,它形成一个if else-if阶梯状语句,可进行多种编译选择。 |
#else | |
#elif | |
#endif | |
#ifdef | 用#ifdef与#ifndef命令分别表示“如果有定义”及“如果无定义”,是条件编译的另一种方法。 |
#ifndef | |
#line | 改变当前行数和文件名称,他们是在编译程序中预先定义的标识符命令的基本形式如下: #line number[“filename”] |
#error | 编译程序时,只要遇到#error就会生成一个编译错误提示信息,并停止编译 |
#pragma | 为实现时定义的命令,它允许向编译程序传送各种指令例如,编译程序可能有一种选择,它支持对程序执行的跟踪。可用#pragma语句指定一个跟踪选择。 |
2.2在Ubuntu下预处理的命令
在控制台输入预处理命令:gcc -E hello.c -o hello.i ,生成hello.i文件 (预处理后的文件)。
2.3 Hello的预处理结果解析
预处理过程中预处理器 (cpp) 识别到#include这种指令就会在环境中搜索该文件并将其递归展开
2.4 本章小结
本章节展示分析了预处理的过程,在预处理过程中,hello.c经过一系列的cpp处理生成hello.i文件。分析了hello.i中文本的含义以及和hello.c的关联。
第3章 编译
3.1 编译的概念与作用
将高级语言所写的源程序翻译成等价的机器语言或汇编语言的目标程序。
将高级语言的文件翻译成机器语言或汇编语言文本,使得机器能够更加容易理解和执行。(从.i文件到.s文件,即预处理后的文件到生成汇编语言程序)
1.将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号。
4.中间代码(语言)使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码,目的:一个前端对多个后端,适应不同平台。
代码生成器:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等
目标代码优化器:选择合适的寻址方式,左移右移代替乘除,删除多余指令。
3.2 在Ubuntu下编译的命令
在控制台输入编译命令:gcc -S hello.i -o hello.s ,生成hello.s文件 (编译后的汇编文件)。
3.3 Hello的编译结果解析
5. .string:声明字符串类型数据,保存了程序运行过程中所需要的字符串。
hello.c的程序中使用到int,字符串以及数组三种数据类型
局部变量存储在寄存器或者栈空间中,hello.c程序中i作为局部变量,在程序中声明,存放在栈空间-4(%rbp)之中,i是4字节的
argc是main函数的第一个参数,不需要声明,直接调用即可。程序中-20(%rbp)对argc进行引用。
数组char *argv[]作为main的第二个参数,没有单独声明,在函数执行时在命令行进行输入。
argc指针指向已经分配好连续的连续空间,起始地址为argv (char* 指针类型)。
程序中使用到argv[1],argv[2]以及argv[3]三个char*数据。
在将argv[3]的值保存在寄存器%rdi之后,执行atoi函数,把字符串转换成整型数。
基本指令 | 效果 |
leaq S, D | D = &S |
INC D | D = D + 1 |
DEC D | D = D - 1 |
NEG D | D = -D |
NOT D | D = ~D |
ADD S, D | D = D + S |
SUB S, D | D = D - S |
IMUL S, D | D = D × S |
特殊指令 | 效果 | 描述 |
imulq S mulq S | R[%rdx]: R[%rax] = S × R[%rax] R[%rdx]: R[%rax] = S × R[%rax] | 有符号全乘法 无符号全乘法 |
idivq S | R[%rdx] = R[%rdx]: R[%rax] mod S R[%rdx] = R[%rdx]: R[%rax] ÷ S | 有符号除法 |
Divq S | R[%rdx] = R[%rdx]: R[%rax] mod S R[%rdx] = R[%rdx]: R[%rax] ÷ S | 无符号除法 |
指令 | 效果 | 描述 |
CMP S1, S2 | S2 - S1 | 比较-设置条件码 |
TEST S1, S2 | S1 & S2 | 测试-设置条件码 |
SET** D | D = ** | 按照设置条件将条件码设置D |
判断argc不等于4,将条件码设置为argc - 3,为下一句跳转指令做准备。
判断i小于8,将条件码设置为i - 8,为下一句跳转指令做准备。
指令 | 跳转条件 | 描述 |
jmp | 1 | 无条件 |
je | ZF | 相等/结果为0 |
jne | ~ZF | 不相等/结果不为0 |
js | SF | 结果为负数 |
jns | ~SF | 结果为非负数 |
jg | ~(SF^OF) & ~ZF | 大于(符号数) |
jge | ~(SF^OF) | 大于等于(符号数) |
jl | (SF^OF) | 小于(符号数) |
jle | (SF^OF) | ZF | 小于等于(符号数) |
ja | ~CF & ~ZF | 大于(无符号数) |
jb | CF | 小于(无符号数) |
cmpl进行了argv与4的比较,并设置条件码,使用je判断ZF标志位,如果为0,则说明argv - 4 == 0,进而说明 argv == 4,进行跳转到.L2,反之顺序执行if内部程序。
i作为进行计数的变量循环8次,在每一次循环开始时,cmpl进行了i与7的比较,并设置条件码,如果i<=7,则跳入.L4中执行for语句内部程序,反之顺序执行for语句外部后续程序。
当程序调用一个函数时,首先将函数所需要传入的参数保存寄存器之中,之后执行一个call指令进行跳转。
函数的返回值一般保存在寄存器%eax之中,当需要设定返回值时,将返回值保存到寄存器%eax中,之后使用ret语句进行跳转返回。
printf将%rdi设置为第一个字符串的首地址 (.LC0),因为只有一个字符串参数,所以call选用puts函数进行输出。
图 3.3.8.1.1.1 hello.s对printf的第一次引用
printf将%rdi设置为第二个字符串的首地址 (.LC1),将%rsi设置为argv[1],将%rdx设置为argv[2],call选用printf函数进行输出。
图 3.3.8.1.2.1 hello.s对printf的第2次引用
先将%rdi设置为argv[3],call调用atoi函数进行字符串到整型数的转换,将转换后的值保存到%edi中,call调用sleep函数。
图 3.3.8.3 hello.s对sleep函数以及atoi函数的引用
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
汇编器将汇编语言 (.s) 翻译成机器语言指令,并将这些指令打包成可重定位目标二进制文件 (.o) 之中。汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
用助记符(Mnemonic)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码。
4.2 在Ubuntu下汇编的命令
在控制台输入汇编命令:as hello.s -o hello.o ,生成hello.o文件 (机器语言二进制程序)。
4.3 可重定位目标elf格式
包含帮助链接器语法分析和解释目标文件的信息,其中包括16字节的表示信息、文件类型类别、机器类型、字节头部表的文件偏移,以及节头部表中条目的大小和数量等信息。
包含了文件中出现的各个节的语义,相关信息包括节的名称、类型、地址、偏移量、对齐等。
从节头部表可以得知hello.o共13个节,初始位置在0x488。
4.4 Hello.o的结果解析
在控制台输入反汇编命令objdump -d -r hello.o,生成反汇编代码
.o文件反汇编之后代码区域并没有太大的差别,而左侧增加了汇编语言所对应的机器语言指令。这里的机器语言就是由0/1所构成的二进制文件,在终端显示时转换为16进制显示。
4.5 本章小结
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并合并成一个单一文件的过程,这个文件可被加载到内存并执行。链接行为可以在编译、汇编、加载、运行时执行。
链接的存在可以将大型的程序分解为更小的更具有模块化的程序,并可以独立的修改、编译这些模块,降低了程序模块化编程的难度,使分离编译成为了可能。
5.2 在Ubuntu下链接的命令
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o
5.3 可执行目标文件hello的格式
描述了各节的信息,从节头部表可以得知hello共28个节,初始位置在0x1980。
5.4 hello的虚拟地址空间
用edb打开hello,在Data Dump窗口中分析hello加载到虚拟内存的情况以及各段信息。
可执行文件中加载的信息从0x00400000处开始存放,hello中存放位置从0x00400000到0x004000c0。
5.5 链接的重定位过程分析
在控制台输入反汇编命令objdump -d -r hello,生成反汇编代码
1. hello的反汇编代码从.init节开始,而hello.o的反汇编代码从.text节开始。
2. hello的反汇编代码中导入了puts、printf、atoi、getchar、sleep等在主程序中使用过的函数,而hello.o的反汇编代码中不包含这些函数。
3. hello的反汇编代码中函数的调用方法同hello.s,使用call + 函数名直接调用,而hello.o的反汇编代码使用call指向下一条语句,并未直接调用函数。
5.6 hello的执行流程
5.7 Hello的动态链接分析
在对hello的readelf分析中得知,.got表的地址为0x0000000000600ff0,通过edb中对Data Dump窗口跳转,定位到GOT表处。
5.8 本章小结
本章分析了程序链接的过程,通过查看ELF的信息分析了解了链接生成可执行文件过程中,程序发生的变化。链接作为程序编写中重要的手段,在程序的修改中扮演着重要的角色。
第6章 hello进程管理
6.1 进程的概念与作用
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
阻塞状态:正在执行的进程发生了某事件(I/O,申请缓存失败等)暂时无法继续执行的状态,此时引起进程调度,把处理机分给另一个进程,它进入阻塞队列。
6.2 简述壳Shell-bash的作用与处理流程
shell是个一个交互型应用及程序,在操作系统中提供了一个用户与系统内核进行交互的界面,代表用户访问操作系统内核服务,基本作用是解释并运行用户的指令。
2. 分析命令行字符串,获取命令行参数,构造传递给execvedargv向量。
5.子进程中,进行步骤2获得参数,调用exceve()执行制定程序。
6.命令行末尾没有&,代表前台作业,shell使用waitpid等待作业终止后返回。
6.3 Hello的fork进程创建过程
终端程序调用fork函数,首先输入./hello指令,shell读入命令。父进程通过fork函数创建新的运行子进程hello。
1. 子进程几乎但不完全与父进程相同,子进程拥有与父进程用户级虚拟地址空间相同但独立的一份副本,包括代码、数据段、堆、共享库以及用户栈。
2. 子进程拥有与父进程任何打开文件描述符相同的副本,意味着子进程可以读写父进程中打开的任何文件。
3. 子进程和父进程最大的差别在于他们有着不同的PID,其中父进程的PID非0,而子进程PID为0。
4. 父进程与子进程使并发独立的进程,内核能够以任意方式交替执行它们的逻辑控制流指令。在子进程执行期间,父进程默认选项使显示等待子进程的完成。
6.4 Hello的execve过程
int main(int argc , char **argv , char *envp);
结合虚拟内存和内存映射过程,可以更详细地说明exceve函数实际上是如何加载和执行程序Hello,需要以下几个步骤:
2.映射私有区域。为Hello的代码、数据、bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时复制的。
3.映射共享区域。比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
6.5 Hello的进程执行
6.6 hello的异常与信号处理
1.中断:异步异常,来自处理器外部的I/O设备。异常处理后会执行下一条指令。
2.陷阱:同步异常,是执行系统调用函数的结果。函数调用结束后会执行下一条指令。
3.故障:同步异常,由错误情况引起,如缺页,浮点异常等等。异常处理成功则重新执行该指令,否则程序终止。
这个操作向进程发送了一个sigtstp信号,让程序暂时挂起,输入ps命令符发现hello进程并没有被关闭。
这个操作向进程发送了一个sigint信号,让进程直接结束,输入ps命令符发现hello进程已经被终止。
pstree用进程树的方法把各个进程用树状图的方式连接起来。
6.7本章小结
本章介绍了程序在shell执行及进程的相关概念,描述了shell是如何在用户和系统内核之间建起一个交互的桥梁。程序在shell中执行是通过fork函数及execve创建新的进程并执行程序。hello程序在进程的作用下已经可以发挥程序的作用,并使得各个进程可以并发执行而不会产生冲突矛盾。
第7章 hello的存储管理
7.1 hello的存储器地址空间
CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。
分段机制下CPU寻址是二维的地址即,段地址:偏移地址,CPU不可能认识二维地址,因此需要转化成一维地址即,段地址*16+偏移地址,这样得到的地址便是线性地址(在未开启分页机制的情况下也是物理地址)。
程序运行由CPU产生的与段相关的偏移地址部分,是描述一个程序运行段的地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
即使,对于转换后线性地址相同的逻辑地址,也因为在不同的任务中,而不同的任务有不同的页目录表和页表把线性地址转换成物理地址,因此,也不会有相同的物理地址冲突。
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
mm_struct(内存描述符):描述了一个进程的整个虚拟内存空间。
vm_area_struct(区域结构描述符):描述了进程的虚拟内存空间的一个区间。
1.创建当前进程的mm_struct,vm_area_struct和页表的原样副本。
3.两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制。
7.7 hello进程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
4.设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)。
接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。
如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。
7.9动态存储分配管理
空闲块通过头部中的大小字段隐含地连接着。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。
首次适配从头开始搜索空闲链表,选择第一个合适的空闲块。下一次适配从上一次查询结束的地方开始。最佳适配检查每个空闲块,选择适合所需请求大小的最小空闲块。
立即合并就是在每次一个块被释放时,就合并所有的相邻块;推迟合并就是等到某个稍晚的时候再合并空闲块。
每个空闲块中,都包含一个pred(前驱)和succ(后继)指针。使用双向链表使首次适配的时间减少到空闲块数量的线性时间。
一种是用后进先出的顺序维护链表,将新释放的块放置在链表的开始处,另一种方法是按照地址顺序来维护链表,链表中每个块的地址都小于它后继的地址。
分离存储:维护多个空闲链表,每个链表中的块有大致相等的大小。将所有可能的块大小分成一些等价类,也叫做大小类。
7.10本章小结
本章介绍了存储器的地址空间,讲述了系统虚拟地址,物理地址,线性地址,逻辑地址概念以及相互之间的关系,动态内存分配的不同策略。本章还讲解了访问内存过程中如何处理却页故障,以及动态存储分配的不同方式。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
1.普通文件(regular file):包含任意数据的文件。
2.目录(directory):包含一组链接的文件,每个链接都将一个文件名映射到一个文件(“文件夹”)。
3.套接字(socket):用来与另一个进程进行跨网络通信的文件
8.2 简述Unix IO接口及其函数
int open(char *filename, int flags, mode_t mode);
open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符。
fd是需要关闭的文件的描述符,close返回操作结果,关闭一个已关闭的描述符会出错。
ssize_t read(int fd,void *buf,size_t n);
read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。
ssize_t wirte(int fd,const void *buf,size_t n)
write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
int dup2(int oldfd, int newfd);
dup2函数复制描述符表表项oldfd到描述符表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd。
8.3 printf的实现分析
printf函数主要调用了vsprintf和write两个外部函数。
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
1.从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
2.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
3.显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux unix I/O设备管理机制,了解了其接口和函数,简单分析了printf和getchar函数的实现方法。
结论
1. 源文件的编写:确定程序内容,写入hello.c中,生成最初始源文件。
2. 预处理:预处理去对hello.c进行预处理过程生成hello.i预处理文件,将源程序中使用到的外部库展开导入程序中。
3. 编译:将预处理后的hello.i文件进行程序语法分析优化生成hello.s汇编文件。
4. 汇编:将汇编文件hello.s翻译成二进制机器更易读懂的机器代码hello.o。
5. 链接:使用链接器将hello.o与其他程序中使用到的库函数文件进行合并链接,生成可执行文件hello。
6. 运行程序:在终端中输入运行命令,shell进程调用fork为hello创建子程序,随后调用execve启动加载器,加映射虚拟内存。
7. 执行指令:CPU为程序分配时间片,在一个时间片中hello使用CPU资源顺序执行控制逻辑流。
8. 内存使用: MMu将程序中使用的虚拟内存地址通过页表映射成物理地址, printf会调用malloc向动态内存分配器申请堆中的内存。
9. 异常处理:hello执行的过程中可能收到来自键盘输入的信号,收到相应信号后调用信号处理程序进行处理。
10. 进程结束:shell父进程回收子进程,内核删除为这个进程创建的所有数据结构。
在贯穿大作业的始终,回顾了整个计算机系统课程学习的课程内容,也复习加深了前置实验中使用过的工具,对整个系统和程序运行过程有了更深入的认识与理解,对Linux系统的操作也更加熟悉与熟练。
附件
文件名称 | 文件描述 |
hello.c | hello的c语言代码(源文件) |
hello.i | 预处理后的文件 |
hello.s | 编译后产生的汇编文件 |
hello.o | 可重定位的目标文件 |
hello | 可执行文件 |
参考文献
[1] 龚奕利. 深入理解计算机系统.北京:机械工业出版社,2016.
https://baike.baidu.com/item/预处理/7833652?fr=aladdin
https://baike.baidu.com/item/编译/1258343?fr=aladdin
https://baike.baidu.com/item/atoi/10931331?fr=aladdin
https://baike.baidu.com/item/汇编程序/298210?fr=aladdin
https://blog.csdn.net/shenhuxi_yu/article/details/71437167
https://blog.csdn.net/zhuangyongkang/article/details/38943863
https://www.cnblogs.com/pianist/p/3315801.html
https://blog.csdn.net/gdj0001/article/details/80135196
标签:预处理,文件,程序,Hello,地址,人生,P2P,进程,hello 来源: https://blog.csdn.net/qq_35425435/article/details/118272340
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。