马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
计算机体系
大作业
题 目 步调人生-Hello’s P2P
专 业 医学类1
学 号 2022110242
班 级 2252002
学 生 谢全荣
指 导 教 师 史先俊
计算机科学与技能学院
2024年5月
摘 要
本文通过追踪一个简单的"hello"步调的传奇一生,深入探究了它在当代计算机体系中的完整生命周期。我们从源代码编写开始,具体解析了hello步调如何通过编译、链接等步骤转化为可实行文件,并进一步探究了它在操作体系中的加载、实行以及终极的终止过程。
起首,我们先容了hello步调的源代码编写,它通常包含一个简单的输出语句。随后,我们具体描述了hello.c步调如何通过编译器(如GCC)转化为目标文件,并进一步链接成可实行文件。在这一阶段,我们强调了编译器在词法分析、语法分析、语义分析、优化以及代码生成等方面的关键作用。
接下来,我们探究了hello步调在操作体系中的加载和实行过程。我们分析了Shell如何创建子历程,并通过execve体系调用加载并运行hello步调。同时,我们深入探究了捏造内存管理、物理内存加载以及CPU如何实行hello步调的指令。
最后,我们讨论了hello步调的终止过程,包括Shell如何回收子历程,以及操作体系如何删除相干数据结构并释放资源。
通过对hello步调的传奇一生的分析,我们揭示了当代计算机体系的复杂性和精妙之处。hello步调固然简单,但它却充实体现了计算机体系的基本构成和工作原理,为深入理解计算机体系提供了重要的窗口。
关键词:链接;可实行文件;函数调用;捏造内存;历程管理 ;IO管理
目 录
第1章 概述
1.1 Hello简介
1.2 环境与工具
1.3 中心结果
文件
作用
hello.c
源文件
hello.i
预处置惩罚后生成的文件
hello.s
编译后生成的文件
hello.o
汇编后生成的可重定位目标步调
hello
终极生成的可实行目标文件
asm.txt
hello.o反汇编生成的文件
elf.txt
hello.o的elf格式文件
asm1.txt
hello反汇编生成的文件
elf1.txt
hello的elf格式文件
表1-1
1.4 本章小结
第2章 预处置惩罚
2.1 预处置惩罚的概念与作用
2.2在Ubuntu下预处置惩罚的命令
2.3 Hello的预处置惩罚结果解析
2.4 本章小结
第3章 编译
3.1 编译的概念与作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.4 Hello.o的结果解析
4.5 本章小结
第5章 链接
5.1 链接的概念与作用
5.2 在Ubuntu下链接的命令
5.3 可实行目标文件hello的格式
5.4 hello的捏造地点空间
5.5 链接的重定位过程分析
5.6 hello的实行流程
5.7 Hello的动态链接分析
5.8 本章小结
第6章 hello历程管理
6.1 历程的概念与作用
6.2 简述壳Shell-bash的作用与处置惩罚流程
6.3 Hello的fork历程创建过程
6.4 Hello的execve过程
6.5 Hello的历程实行
6.6 hello的异常与信号处置惩罚
6.7本章小结
第7章 hello的存储管理
7.1 hello的存储器地点空间
7.2 Intel逻辑地点到线性地点的变换-段式管理
7.3 Hello的线性地点到物理地点的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello历程fork时的内存映射
7.7 hello历程execve时的内存映射
7.8 缺页故障与缺页中断处置惩罚
7.9动态存储分配管理
7.10本章小结
第8章 hello的IO管理
8.1 Linux的IO装备管理方法
8.2 简述Unix IO接口及其函数
8.3 printf的实现分析
8.4 getchar的实现分析
8.5本章小结
结论
附件
参考文献
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机体系的术语来简述Hello的P2P(From Program to Process)和020(From Zero to Zero)的整个过程时,可以将其分解为以下步骤:
一、Hello的P2P(From Program to Process)过程:
即为源文件到可实行步调文件的转化。在这里gcc编译驱动步调读取源文件hello.c,并将其翻译成一个可实行目标文件hello。编译过程包括四个阶段(如图1所示:
①预 图1
①预处置惩罚阶段。预处置惩罚器(cpp)根据#命令,修改原始的c步调,比方#include<stdio.h>命令告诉预处置惩罚器读取体系头文件stdio.h的内容,并把它直接插入步调文本中。结果就得到了另一个C步调,通常是以.i主作次文件扩展名。
②编译阶段。编译器(ccl)将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言步调。该步调包含函数 main 的定义,汇编语言黑白常有用的,因为它为不同高级语言的不同编译器提供了通用的输出语言。
③汇编阶段。接下来,汇编器(as)将 hel1o.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标步调(relocatable object program)的格式,并将结果保存在目标文件hello.o中。
④链接阶段。请注意,hel1o步调调用了 printf函数,它是每个C编译器都提供的标准C库中的一个函数。printf函数存在于一个名为 printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的 hello.o步调中。链接器(Id)就负责处置惩罚这种合并。结果就得到 hello文件,它是一个可实行目标文件。
当我们运行hello时,在shell中利用fork()函数创建子历程,再用execve加载hello步调,这时,hello就由步调(program)变成了一个历程(process),完成了hello的P2P的过程。
二、Hello的020(From Zero to Zero)过程
即为历程从无到有产生,操作体系加载实行并终极回收资源的过程。
From Zero:初始状态。在步调开始实行之前,它在体系中是不存在的,即处于“零”状态。没有为其分配任何资源(如内存、CPU时间等)。
实行过程:
资源分配:当步调开始实行时,操作体系会为其分配必要的资源。这包括在内存中为其分配空间以存储步调和数据,以及为其分配CPU时间片以实行步调中的指令。
实行指令:步调开始实行其指令,完成预定的功能。在这个过程中,步调会不停地与操作体系和其他硬件装备进行交互,以获取必要的资源和服务。
To Zero:
资源回收:当步调实行完毕后,操作体系会回收为其分配的资源。这包括释放步调在内存中占用的空间,以及回收其利用的CPU时间片等。通过这种方式,体系可以确保资源的有效利用和管理的公平性。
结束状态:在资源回收完成后,步调在体系中不再存在,即回到了“零”状态。此时,步调已经完成了其预定的功能并退出了实行。
综上所述,Hello的P2P和020过程涉及到了从编写源代码到编译成目标文件,再到操作体系加载实行并终极回收资源的整个过程。这个过程展示了计算机体系中步调实行的基本流程和资源管理的重要性。
1.2 环境与工具
硬件环境:X64 CPU;3.20GHz;16.0G RAM;256GHD Disk 以上
软件环境:Windows11 64位以上;VirtualBox/Vmware 17;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;
开辟工具:CodeBlocks 64位;Visual Studio 2022 64位; vi/vim/gedit+gcc。
1.3 中心结果
文件
| 作用
| hello.c
| 源文件
| hello.i
| 预处置惩罚后生成的文件
| hello.s
| 编译后生成的文件
| hello.o
| 汇编后生成的可重定位目标步调
| hello
| 终极生成的可实行目标文件
| asm.txt
| hello.o反汇编生成的文件
| elf.txt
| hello.o的elf格式文件
| asm1.txt
| hello反汇编生成的文件
| elf1.txt
| hello的elf格式文件
|
|
| 表1-1
1.4 本章小结
本章重要先容了hello的P2P和020的过程,接着先容了完本钱次大作业所需的的软硬件环境及开辟工具。同时还列出了完成大作业过程中产生的结果文件。
第2章 预处置惩罚
2.1 预处置惩罚的概念与作用
预处置惩罚的概念:预处置惩罚是指预处置惩罚器(cpp)根据以字符#开头的命令,修改原始的C步调,获得一个后缀为.i的文件,在本例中hello.c与处置惩罚后生成hello.i,预处置惩罚后的文件仍为文本文件。
预处置惩罚的作用:
(1)能够完成头文件的包含,将包含的文件插入到步调文本中(#include);
(2)可以进行宏替换,用现实的常量替换它的符号;
(3)删除源步调中所有的注释部分;
(4)实现特殊控制指令(如#error)。
2.2在Ubuntu下预处置惩罚的命令
在ubuntu下预处置惩罚指令为 gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i,该命令表示hello.c文件经预处置惩罚生成hello.i文件
图2-1
2.3 Hello的预处置惩罚结果解析
在进行预处置惩罚操作之后,打开hello.i文件后发现,相较于hello.c文件内容明显增长,由初始几行增长到3061行,并且由最后几行可找到初始hello步调,与之前hello.c中的源步调存在差异,重要包括以下几个方面:
(1)hello.c中的注释和多余空缺均被删除;
(2)替换宏定义,cpp识别到#include这种指令就会在环境中搜寻该头文件并将其递归睁开;
(3)#include中包含的几个文件插入到hello.i中
图2-2
图2-3
2.4 本章小结
本章重要先引入预处置惩罚的概念及作用,包括宏定义、注释、包含等方面,再实如今Ubuntu上的预处置惩罚指令,将hello.c文件转化生成hello.i文件,并对其结果解析比对。
第3章 编译
3.1 编译的概念与作用
汇编的概念:编译器(ccl)将预处置惩罚生成的文件hello.i翻译成汇编文件文件hello.s。 它包含一个汇编语言步调。
汇编的作用:将高级语言编译为相应的机器语言,这里将C语言转化为intel x86汇编指令。
3.2 在Ubuntu下编译的命令
在ubuntu下编译的命令为gcc -S hello.i -o hello.s,该命令表示hello.i文件通过编译器翻译生成hello.s汇编文件
图3-1
3.3 Hello的编译结果解析
图3-2
3.3.1数据
1.常量
1).字符串
图3-3
划线为字符串常量,此中英文与数字可直接显示,汉字则被编码为UTF-8 格式,一个汉字占3个字节。
分别对应.c文件中的以下两部分
图3-4
2)立刻数 在汇编语言中,以$n情势给出,如下为:$5,$1
图3-5
通常保存在寄存器或堆栈中,本函数中包括以下三种:i、argc以及argv。
1)int i局部变量
图3-6
2)int argc 作为main函数的第一个参数,保留在堆栈里
3)char*agrv[ ]为一个指针数组,作为main函数的第二个参数,此处采用偏移量寻址,在访存时,利用%rax寄存器间接访存。
图3-7
3.3.2赋值操作
步调中绝大部分由mov指令完成,比方下图
图3-8
3.3.3算术操作
划线部分表示,%rax的值+24
图3-9
划线部分为变量i的自增操作
图3-10
3.3.4关系判定和控制转移
1)argc!=5,将argc的值存在-20(%rbp) 的位置后与5比较大小,再判定是否跳转到L2
图3-11
2)i<10,比较9与-4(%rbp)的值,若相等则跳转到L4实行功能否则跳出循环
图3-12
2.控制转移
1)if(argc!=5),将5与-20(%rbp)的值即argc比较,若相等则跳转到L2
图3-11
2).for循环语句,for(i=0,i<10,i++),比较9与-4(%rbp)的值,若相等则跳转到L4实行功能
图3-12
3.3.5数组/指针/结构操作
由前面可知char*argv作为一个指针数组,其首地点起首传入%rax,之后偏移量寻址方式,被存储到即-32(%rbp),完成后%rax对应argv[1],同理,以下指令完成后,argv[2]也被存储到%rax中
图3-13
3.3.6函数操作
汇编语言中,利用call指令进行函数调用,包括puts,exit,printf,atoi,sleep,getchar等函数的调用,ret指令表示函数返回
比方对printf函数调用,编译器调用puts函数优化,把数据放在%rdi寄存器中作为函数参数,call指令调用函数,其他函数同理。
图3-14
3.3.7类型转换
比如在此汇编文件中,sleep函数的参数为int型,但argv为字符串数组,在汇编时用atoi将字符串转化成int型,atoi函数表示逼迫处置惩罚该类型转换。
图3-15
3.4 本章小结
本章重要讲编译,先引入其概念与作用,再实现Ubuntu下由hello.i到hello.s的编译实现,之后对编译文件和源步调文件进行对比分析,重要包括数据类型、操作类型、函数调用与类型转换方面,此中数据类型包括常量和局部变量,操作包括赋值、算术、关系、控制等。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编器将汇编语言翻译为机器语言指令,比方汇编器as将hello.s文件翻译为hello.o文件的过程,此中hello.o文件为一个可重定位目标步调,包含步调的指令编码。
汇编的作用:将高级语言转化为机器能直接识别并实行的机器语言二进制代码,这个二进制机器代码是步调在本机器上的机器语言的表示。
4.2 在Ubuntu下汇编的命令
命令为:gcc hello.s -c -o hello.o
图4-1
4.3 可重定位目标elf格式
实行命令readelf -a hello.o >elf.txt
图4-2
ELF头:以一段16字节的序列开始,描述了构成该序列体系的字的大小和字节顺序,包含了目标文件的类型、体系架构信息、节头大小和数量等信息。具体见下图:
图4-3
节头部表:包含了文件中出现的各个节的语义、类型、位置和大小等信息,比方.text、.data、.bss、rodata等的名称、类型、地点、偏移量等都存储在节头部表中。此中代码可实行但不可写,数据段与只读数据段均不可实行,且只读数据段不可写。由图可知,hello步调中有14个节。下面先容此中的节:
图4-4
.text
| 已编译步调的机器代码
| .data
| 已初始化的全局变量和静态变量
| .bss
| 未初始化或所有被初始化为0的全局变量和静态变量,不占据现实空间,仅为占位符
| .rodata
| 记录只读数据,如开关语句中的跳转表
| .comment
| 显示版本控制信息
| .note
| 注释节具体描述
| .eh_frame
| 处置惩罚异常
| .symtab
| 存放符号表
| .strtab
| 字符串表
| .shstrtab
| 包含节区名称
|
图4-5
重定位节:是可重定位目标文件的重要构成部分,显示各段引用的外部符号、外部函数等,重定位节可以在链接时对其进行重定位操作,修改其地点,由下图可知elf文件中进行重定位的信息有.rodata的模式串,puts,exit,printf,atoi,sleep,getchar等符号。
图4-6
符号表:.symtab节,由汇编器生成,重要存放了一些步调中的定义并引用的全局变量和步调中定义的局部变量的类型和需要重定位函数等信息,由下图可知,存储了value,size type等信息。
图4-7
4.4 Hello.o的结果解析
实行命令objdump -d -r hello.o >asm.txt 将hello.o的反汇编文件存储在asm.txt文件中。
hello.s asm.txt
图4-8
由上图对比hello.s和asm.txt可知
1)数字利用进制不同,hello.s利用十进制,而反汇编文件asm.txt利用十六进制
2)分支转移不同,hello.s利用段名.L,而反汇编文件asm.txt直接利用跳转地点
3)函数调用不同,hello.s在call指令后直接引用函数名,而反汇编文件asm.txt在call指令后利用引用函数的首地点
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
4.5 本章小结
本章重要讲汇编,起首引入汇编的概念和作用,接着实现linux体系下的汇编操作,将hello.s转化为hello.o文件,并利用elf命令生成hello.o的可重定位目标文件,先容其包含的信息与功能,如ELF头、节头部表、重定位节和符号表,最后解析hello.o文件的反汇编文件asm.txt,同时与hello.s文件对比分析。
第5章 链接
5.1 链接的概念与作用
接的概念:链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并实行。
链接的作用:链接器在软件开辟中饰演着一个关键的脚色,使得分离编译成为可能,不消再将一个大型的应用步调构造为一个巨大的源文件,而是把它分解为更小、更好管理的模块,可以独立的修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链策应用,而不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
链接命令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu//crtn.o
图5-1
5.3 可实行目标文件hello的格式
利用命令:readelf -a hello >elf1.txt
较elf.txt,在保持原有elf头、节头、符号表等的底子上,新增长了步调头、dynamic section,并新增共享库等信息。
elf头:与elf.txt相比,由REL(可重定向目标文件)变为EXEC(可实行文件),并且步调头入口点地点变为0x4010f0,另外步调和节的起始位置和大小改变,节数变为了27个。
图5-2
节头部表(较长,截取部分):内容较前面elf.txt基本稳定,但变为27节。
图5-3
符号表(较长,截取部分):存储信息基本稳定,但内容较前面elf.txt增多,增长了一些外部函数和变量。
图5-4
步调头:重要在步调实行时利用,作用如下:
1)定义了文件在内存中的结构。它告诉操作体系哪些部分应该被加载到历程的地点空间中,以及这些部分在内存中的位置。
2)描述了文件的各个段,如代码段、数据段、动态链接信息等。
3)步调加载时,操作体系会读取步调头来确定哪些段需要被加载到内存中。步调头还包含了动态链接器所需的信息,以便在运行时解析符号和重定位地点。
图5-5
Dynamic section:与动态链接直接相干,提供必要链接信息,包括共享库、动态链接符号表的位置等。同时,它可能包含指示动态链接器进行立刻重定位的标志等。
图5-6
5.4 hello的捏造地点空间
这里我们在Ubuntu中利用edb加载hello,能够查看本历程的捏造地点空间各段信息。在edb的data dump中可以看到关于elf文件的二进制信息,此中非常明显的便是ELF头在0x400000处的16字开始序列。而在5.3中步调头中可以看到INTERP标志性的/lib64/ld-linux-x86-64.so.2,同时注意到其VirtAddr为0x400200,因此data dump中找到00400200,它对应着INTERP的捏造地点。同理,关于步调头中标出的各个段的捏造地点在datadump中都有对应。
由下图hello文件的捏造地点空间是从0x0000558c929b6000-0x0000558c929b7000,
,二者相互对应
图5-7
5.5 链接的重定位过程分析
实行命令:objdump -d -r hello >asm1.txt
图5-8
查看hello文件的反汇编文件asm1.txt,发现较asm.txt而言,
- 内容增多,
- 同时在main函数之前增长了.init,.plt,_start的反汇编内容和节中相干函数如puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等,
- 另外由于hello无需重定位,其反汇编代码中地点为捏造地点,
- 且无需重定位条目,因为在链接过程中已完成重定位。
图5-9
图5-10
链接过程:
1)合并相同节:合并相同类型的节成为一个新节,比方合并所有文件的.plt成为一个新的.plt节,即为hello文件的新的.plt节
2)确定地点:链接器ld分配内存地点给新节以及符号。确定地点后全局变量,指令等具有唯一的运行时地点
asm.txt:
图5-11
asm1.txt:
图5-12
圈出部分表明hello中对其重定位方式分别为立刻数、控制转移、函数调用进行重定位
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.6 hello的实行流程
从加载hello到_start,到call main,以及步调终止的所有过程(重要函数)调用与跳转的各个子步调名或步调地点如下:
图5-13
5.7 Hello的动态链接分析
got中存放函数量标地点,plt利用got中地点跳转到目标函数。二者信息如下图 ,由图可知got其地点在0x403ff0
图5-14
图5-15
在gdb中定位0x403ff0地点,并在_start前后设置断点,结果如图5-15所示。由结果可知在dl_start前后,0x4010f0处8bit数据分别由00007ffff7fd0100和00007ffff7fde1630变为了00007ffff7ff2684和00007ffff7f564bd,说明动态链接项目都发生了厘革。
5.8 本章小结
本章重要讲链接,起首引入链接的概念和作用,再实现Ubuntu下的链接命令,同时分析了hello对应elf1.txt,以及与elf.txt的异同,之后先容并用edb查看了hello的捏造地点空间,通过对比asm.txt与asm1.txt更加深入解析了重定位过程和链接过程,同时利用edb对动态链接进行进一步分析概述,至此链接过程结束,之后hello将作为一个历程。
第6章 hello历程管理
6.1 历程的概念与作用
历程的概念:
历程由步调、数据和历程控制块三部分构成。是计算机中的步调关于某数据集合上的一次运行活动,是体系进行资源分配的基本单位,也是操作体系结构的底子。它是动态产生,动态灭亡的。任何历程都可以同其他历程一起并发实行。
另外历程是一个能独立运行的基本单位,同时也是体系分配资源和调治的独立单位。但由于历程间的相互制约,使历程具有实行的间断性。
历程的作用:历程提供给应用步调一个独立的逻辑控制流;一个私有的地点空间。在Shell中运行hello步调时,Shell会调用fork函数创建一个子历程。应用步调也可创建新历程;操作体系为hello历程分配必要的体系资源,如CPU时间、内存空间等。调治器负责将CPU时间分配给不同的历程,确保hello历程能够得到实行的机会。
6.2 简述壳Shell-bash的作用与处置惩罚流程
Shell-bash的作用:
Shell是一个与操作体系交互的命令行表明器,用户可以将一系列命令编写成脚本文件,通过bash表明实行,bash将用户的指令翻译给OS内核,让内核来进行处置惩罚,并把处置惩罚的结果反馈给用户。
Shell-bash的处置惩罚流程:
1)从终端、文件或管道中读取用户输入的命令。
2)对读取到的命令进行解析,识别出命令名、参数、选项等。
3)查找命令:根据命令名在体系的PATH环境变量中查找对应的可实行文件或脚本文件。
4)分析命令:
①输入为内置命令,直接表明实行
②输入为非内置命令,则调用相应函数分配子历程实行
③如果找不到命令,则返回错误信息。
期待命令实行完毕:bash会期待命令实行完毕并收集命令的返回值。如果命令实行成功,返回值一般为0;如果失败,则返回非零值。
5)输出当前参数后继续进行处置惩罚下一参数,直到全部处置惩罚完为止
6.3 Hello的fork历程创建过程
当用户在Shell中输入并实行hello命令时,bash解析此命令是否为内置命令,在当前状态查找并实行hello,此时bash调用fork函数来创建一个新的子历程。子历程在创建时,会复制父历程的PCB(历程控制块)结构体的部分内容,包括父历程的代码段、数据段、堆和栈等,fork函数返回两次,但在父历程中返回新创建的子历程的PID,而在子历程中返回0。
图6-1
跟踪hello历程
图6-2
6.4 Hello的execve过程
hello步调调用函数fork创建新的子历程之后,子历程会调用execve函数,execve函数能够在当前历程的上下文中加载并运行一个新步调,同时携带传递准备好的文件路径名、参数列表和环境变量给内核。在execve函数加载了hello之后,它调用加载器删除子历程现有的捏造内存段,并创建一组新的代码、数据、堆和栈段(初始化为零),但是,历程ID(PID)和一些其他属性(如父历程ID、会话ID等)会保持稳定。通过映射关系,将hello中的代码和数据初始化在新的代码和数据,然后跳转到_start函数的地点,调用main函数。
因此,execve没有返回值。但当新步调实行完毕后,它会返回一个退出状态给其父历程(通过wait或waitpid等体系调用获取)。
6.5 Hello的历程实行
1)上下文信息
历程上下文信息包括了历程实行时所需的所有状态信息,如CPU寄存器的值、步调计数器、栈信息、内存管理信息、打开文件描述符等。这些信息在历程切换时会被保存和恢复,确保历程在被调治返来时能够继续实行。
图6-3
2)时间片
历程时间片是操作体系分配给每个历程实行的时间片段。当历程的时间片用完时,操作体系会暂停该历程的实行,并将其状态信息保存到历程上下文中,然后调治另一个历程实行。通过这种方式,操作体系可以实现多个历程的并发实行。
3)历程调治
历程调治是操作体系决定哪个历程在何时获得CPU资源的过程。调治算法会思量多个因素,如历程的优先级、时间片的利用环境、I/O操作等。在hello历程实行时,操作体系会根据调治算法来决定何时将其放入停当队列,并在得当的机遇将其调治到CPU上实行。抢占历程时包括保存前历程的上下文;恢复实行新历程的上下文;把控制给新恢复历程实现上下文切换。
4)用户态和核心态
表示CPU的两种实行状态。用户模式下,历程只能实行用户空间的代码,不能直接访问体系资源或实行特权指令。大部分的应用步调代码都在用户模式下实行。内核模式下,历程可以实行任何指令,访问任何内存地点,控制所有体系资源。操作体系的内核代码在内核模式下实行。
当hello历程需要实行体系调用(如execve来加载并实行步调)时,会触发从用户态到核心态的转换。这个过程通常通过中断或异常来实现。一旦进入核心态,操作体系会实行相应的内核代码来处置惩罚体系调用,如加载hello步调到内存中并实行。当体系调用完成后,操作体系会将控制权返回给hello历程,并触发从核心态到用户态的转换,使hello历程继续在用户态下实行。
图6-4
6.6 hello的异常与信号处置惩罚
Hello的几种异常:中断、陷阱、故障和终止
1)中断:异步的,由处置惩罚器外部的I/O装备产生的。敲键盘后,键盘控制器向处置惩罚器的中断引脚发送信号来触发中断,同时将异常号放到体系总线上,cpu调用相应步调来处置惩罚中断,完毕后返回继续实行下一条指令。
2)陷阱:同步的,是CPU实行当前指令的结果。是一种故意触发异常,重要为用户步调和操作体系内核之间提供类似函数的接口,当要读取文件或创建新历程时,向内核提供服务,处置惩罚器提供syscall指令,内核调用陷阱处置惩罚步调来实行体系调用。
3)故障:同步的,是CPU实行当前指令的结果。由错误环境引起,有可能被故障处置惩罚步调修复,发生故障时,处置惩罚器将控制转移给故障处置惩罚步调,若能修正,则返回故障指令并重新实行,若无法修正,则终止该步调。比方缺页异常,
假设当前指令引用了一个捏造地点,但与其对应的物理页面并不在内存中,此事须从磁盘中读取数据到内存,此时当前指令引发故障,之后步调从磁盘加载对应页面到内存,将控制返回给引起故障的指令,之后指令可以继续实行而不引发故障。
- 终止:同步的,是CPU实行当前指令的结果。是由不可恢复的致命错误导致的,通常为硬件错误,比方DRAM或SRAM的存储位被损坏时,会导致奇偶校验错误,终止处置惩罚步调从不将控制返回给应用步调,而是直接终止步调。
产生的信号类型:SIGINT、SIGKILL、SIGSEGV、SIALARM、SIGCHLD等
步调的运行
1)正常运行
图6-5
2)运行过程中不绝乱按
图6-6
3)运行过程中回车
图6-7
4)运行过程中键入Ctrl-Z
图6-8
5)运行过程中键入Ctrl-C
图6-9
- Ctrl-z后可以运行ps jobs pstree fg kill 等命令
ps打印各历程PID
图6-10
jobs打印被挂起的hello的信息
图6-11
pstree打印历程树
图6-12
fg继续进行
图6-13
kill杀死历程
图6-14
6.7本章小结
本章重要讲hello历程,起首引入其概念和作用,接着先容shell-bash的作用和处置惩罚流程,之后先容如何利用fork函数创建新历程,以及hello的exceve过程,然后先容hello的历程实行,包括上下文,时间片,历程调治,用户态和核心态等信息,最后针对异常和信号处置惩罚,表明了异常的几种类型以及信号处置惩罚流程。
第7章 hello的存储管理
7.1 hello的存储器地点空间
逻辑地点:步调在编写时所利用的地点,也称为捏造地点或相对地点。通常与步调的源代码或中心代码(如汇编代码)中的内存引用相对应。在多用户或多任务操作体系中,逻辑地点空间是私有的,每个历程或任务都有本身的逻辑地点空间。在步调实行前需要转换为物理地点。
线性地点:是逻辑地点到物理地点转换过程中的一个中心步骤。在某些架构中(如x86的保护模式),逻辑地点起首被转换为线性地点,然后再转换为物理地点。线性地点空间是全局的,所有历程共享同一个线性地点空间。线性地点通常与物理地点的大小相同(比方,在32位体系中为4GB)。
捏造地点:它和逻辑地点在很多上下文中是相似的,并且经常可以互换利用。
它是私有的,并且每个历程都有本身的捏造地点空间。在当代操作体系中,内存管理单位(MMU)负责将捏造地点转换为物理地点。通过利用捏造地点,操作体系可以实现内存保护、地点空隔断离和捏造内存等功能。
物理地点:物理地点是内存中的现实硬件地点,也称为机器地点或实地点。
处置惩罚器利用物理地点来访问内存单位。在没有内存管理硬件(如MMU)的体系中,步调直接利用物理地点。在具有内存管理的体系中,捏造地点(或逻辑地点)通过地点转换机制(如分页或分段)转换为物理地点。
7.2 Intel逻辑地点到线性地点的变换-段式管理
概念:为了让步调在内存中能自由浮动而又不影响它的正常实行,CPU将内存分别成逻辑上的段来给步调利用。与段有关的信息需要8个字节来描述,为了存放这些段描述符,就在内存中开辟了一段空间,所有的段描述符以数组的情势存放在一起,这就构成了一个段描述表(Descriptor Table)。全局描述符表(GDT)是最重要的描述符表,它在任何时刻都可以利用。在进入保护模式前,必须要先定义全局描述表的内容。
变换过程:
逻辑地点由两个16位的地点分量构成,一个为段选择符(Segment Selector),另一个为偏移量。段选择符是为了找到段描述符。起首检查段选择符的TI字段,决定段描述符保存在哪一个描述表中(GDT或LDT),此中TI=0表示利用GDT,TI=1表示利用LDT。从段选择符的index字段计算段描述符的地点,index字段的值乘以8(描述符大小),这个结果与gdtr或ldtr寄存器的内容相加。把逻辑地点的偏移量与段描述符base字段的值相加,就得到了线性地点。
比方假设GDT在内存地点0x00020000,且由段选择符所指定的索引号为2。相应的段描述符地点是:0x00020000 + (2 * 8) = 0x00020010。如果逻辑地点的偏移量是0x1234,那么线性地点就是段描述符的base字段值(从0x00020010处读取)加上0x1234。
图7-1
7.3 Hello的线性地点到物理地点的变换-页式管理
概念:页式管理是一种内存空间存储管理的技能,它将各历程的捏造空间分别成若干个长度相等的页(page),同时把内存空间也按页的大小分别成片或页面(page frame)。然后,页式管理通过建立捏造地点与物理地点之间的逐一对应页表,并利用相应的硬件地点变换机构,来办理离散地点变换问题。
变换过程:
1)起首将其与CPU内部的TLB(Translation Lookaside Buffer)中的条目进行比较。若TLB中存在匹配的条目(即线性地点的高位与TLB中某个条目的线性页面值相等),则可以直接从TLB中获取对应的物理页面值,并与线性地点的页内偏移量组合,形成物理地点。
2)若TLB中没有匹配的条目,体系会利用常驻于存储器中的页目录表和页表来进行转换。
起首,线性地点的高10位(页目录索引)会被用来在页目录表中查找对应的页目录项。通常将页目录索引乘以4(因为每个页目录项通常占用4个字节)并与页目录表基址相加,从找到的页目录项中,体系会读取页表地点指针(高20位)和页目录项的属性(低12位)。接着,线性地点的中心10位(页表索引)会被用来在页表中查找对应的页表项。同样将页表索引乘以4并与页表地点指针相加。
从找到的页表项中,体系会读取物理页地点指针(高20位)和页表项的属性(低12位)。最后,将物理页地点指针与线性地点的低12位(页内偏移量)相加,就得到了对应的物理地点。
3)当通过页目录表和页表进行转换得到物理地点后,体系会把这次转换的信息(线性页面值及对应的物理页面值)用来更新TLB,通常是替换掉TLB中迩来较少利用的条目。这样可以加速未来的地点转换过程,因为TLB的访问速率通常远快于内存中的页目录表和页表。
图7-2
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:全称Translation Lookaside Buffer,即旁路转换缓冲或地点转换后备缓冲,是一种特殊的高速缓存,用于加速捏造地点到物理地点的转换过程。它存储了迩来利用的页表项,当CPU需要访问某个内存地点时,起首会在TLB中查找该地点的映射关系。
四级页表:当今操作体系普遍采用64位架构,CPU最大寻址能力固然到达了64位,但现实上只利用48位进行寻址。其内存管理采用了9-9-9-9-12的分页模式,表示物理地点拥有四级页表,依次为PXE(Page Directory Pointer Table Entry)、PPE(Page Directory Entry)、PDE(Page Table Entry)和PTE(Page Table Entry)。
TLB与四级页表共同支持了从捏造地点到物理地点的高效变换过程。TLB通过缓存迩来利用的页表项来加速地点转换,而四级页表则提供了从捏造地点到物理地点的准确映射。
VA到PA的变换过程
1)TLB查找:当CPU需要访问某个捏造地点(VA)时,起首会在TLB中查找该地点的映射关系。如果TLB命中(TLB hit),即TLB中存在该VA对应的VA-PA映射关系,则可以直接从TLB中获取物理地点(PA),从而加速内存访问的速率。
2)页表查找(如果TLB未命中):如果TLB未命中(TLB miss),即TLB中没有该VA对应的VA-PA映射关系,CPU需要到页表中查找。根据四级页表的结构,CPU会依次查找PXE、PPE、PDE和PTE,直到找到对应的PTE项。PTE项中存储了VA到PA的映射关系,包括物理页地点指针(高20位)和页表项的属性(低12位)。
3)计算物理地点:
将PTE项中的物理页地点指针与VA的页内偏移量(低12位)相加,得到完整的物理地点(PA)。
图7-3
7.5 三级Cache支持下的物理内存访问
运作原理是利用较快速的储存装置保留一份从慢速储存装置(如物理内存)中所读取的数据并进行拷贝。当CPU需要再次访问这些数据时,它可以先从三级缓存中读取,从而避免了直接从物理内存中读取数据所带来的延长。
三级缓存的过程:
当CPU需要读取数据时,它会起首检查一级缓存(L1 Cache),如果数据在一级缓存中命中,则直接读取。
如果一级缓存未命中,CPU会检查二级缓存(L2 Cache)。如果数据在二级缓存中命中,则读取二级缓存中的数据。
如果二级缓存也未命中,CPU会检查三级缓存(L3 Cache)。如果数据在三级缓存中命中,则读取三级缓存中的数据。
如果三级缓存也未命中,CPU则必须从物理内存中读取数据。
通过增长缓存的层级,尤其是三级缓存,CPU可以更有效地从缓存中读取数据,从而减少对物理内存的访问次数。这不仅降低了内存延长,还提高了大数据量计算时处置惩罚器的性能。
图7-4
7.6 hello历程fork时的内存映射
基本概念:fork是Unix和类Unix操作体系中创建新历程的重要方法。当一个历程调用fork时,它会创建一个与当前历程几乎完全相同的副本,包括父历程的内存结构、环境变量、打开的文件描述符等。
内存映射:在fork时,子历程会继续父历程的内存映射。这意味着父历程的代码段、数据段、堆和栈都会被复制到子历程中。然而,这种复制是“写时复制”(Copy-On-Write, COW)的,即只有当子历程或父历程尝试修改内存中的某个页面时,该页面才会被现实复制。这种机制有效地减少了不必要的内存拷贝,提高了性能。父历程和子历程在fork后拥有独立的捏造地点空间,但它们映射到相同的物理内存页面(在写时复制之前)。这允许它们各自独立地访问和修改本身的内存区域,而不会互相干扰。
7.7 hello历程execve时的内存映射
基本概念:execve是一个体系调用,用于实行一个新的步调。当一个历程调用execve时,它会用新的步调完全替换本身的内存映像,包括代码、数据、堆和栈。这意味着原有的内存映射会被丢弃,而新的步调文件(通常是可实行文件)会被加载到内存中。
内存映射:在execve时,操作体系会丢弃当前历程的内存映射,并根据新的步调文件创建新的内存映射。这通常包括将步调的代码段映射到捏造地点空间的某个区域,并为其分配堆和栈空间。此外,任何须要的文件或库也会被映射到捏造地点空间中。在execve后,历程的捏造地点空间会被完全重新构造,以反映新的步调结构。原有的内存映射会被丢弃,而新的内存映射会根据新的步调文件来创建。
图7-4
7.8 缺页故障与缺页中断处置惩罚
缺页故障(Page Fault,又名硬错误、硬中断、等):当软件试图访问已映射在捏造地点空间中,但是并未被加载在物理内存中的一个分页时,由中央处置惩罚器的内存管理单位(MMU)所发出的中断。是操作体系(如Microsoft Windows和各种类Unix体系)中常见且有必要的机制,用于利用捏造内存来增长步调可用内存空间。
处置惩罚流程如下:
①处置惩罚器将捏造地点发送给MMU;
②MMU生成PTE地点(PTEA),并从高速缓存/主存请求得到;
③高速缓存/主存向MMU返回PTE;
④PTE有效位为0, MMU触发缺页异常,传递CPU中的控制到操作体系内核的缺页异常处置惩罚步调;
⑤缺页处置惩罚步调确定出物理内存中的捐躯页 ,若页面被修改,则把它写回到磁盘中。
⑥缺页处置惩罚步调调入新的页面,并更新内存中的PTE;
⑦缺页处置惩罚步调返回到原历程,再次实行导致缺页的指令,CPU将VA重新送给MMU并实行相应访问操作,之后将不会再出现缺页环境。
图7-5
7.9动态存储分配管理
基本方法:
步调利用malloc(或calloc/realloc)这些函数来请求分配指定大小的内存块。这些函数返回指向分配的内存的指针,或者在内存不足时返回NULL。分配内存后,步调可以通过返回的指针来访问和修改这块内存。当步调不再需要某块内存时,它应该利用free函数来释放这块内存。这样可以避免内存走漏。
基本策略:
1) 内存池
为了减少内存分配和释放的开销,一些步调会利用内存池来预先分配一大块内存,并在需要时从这块内存中分配和释放小块内存。这可以减少与操作体系的交互次数,提高性能。
2)引用计数
用于跟踪内存块被引用的次数。每当内存块被引用时,计数加1;每当引用被释放时,计数减1。当计数为0时,内存块可以被安全地释放。这种方法可以主动处置惩罚内存走漏,但不适用于存在循环引用的环境。
3)垃圾回收
主动管理内存,它可以识别并释放不再被步调利用的内存块。垃圾回收器会定期扫描内存,找出所有不再被引用的对象,并将它们占用的内存释放。这种方法可以大大简化内存管理,但可能会增长步调的运行时间。
4)分区
将内存分别为多个固定大小的分区,每个分区都可以存储一个对象。当需要分配内存时,步调会选择一个足够大的分区来存储对象。这种方法可以简化内存管理,但可能导致内存利用率降低。
5)伙伴体系
用于动态内存分配,它将内存块构造成一系列不同大小的“伙伴对”。当一个内存块被释放时,它会被与其大小的伙伴合并成一个更大的内存块。当需要分配内存时,步调会找到最接近请求大小的伙伴对,并从中拆分出所需大小的内存块。这种方法可以提高内存利用率,但可能导致内存碎片。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
本章重要讲hello步调存储管理的相干内容。起首先容了逻辑地点、线性地点、捏造地点以及物理地点的概念,再通过段式管理和页式管理报告了从逻辑地点到线性地点,再到物理地点的变换,和在TLB与四级页表支持下的捏造地点到物理地点的变换,三级cache支持下的物理内存访问操作。最后先容了hello历程的调用fork和execve时的内存映射以及缺页故障和缺页中断处置惩罚的方式及动态分配管理的基本方法与策略。
第8章 hello的IO管理
8.1 Linux的IO装备管理方法
装备的模型化:文件
一个linux文件就是一个m字节的序列,有平凡文件如文本文件和二进制文件,包含链接的目录文件,用来与另外一个历程跨网络通讯的套接字文件以及其他文件如命名通道、符号链接、字符和块装备
装备管理:unix io接口,使得输入输出统逐一致
Linux 的 I/O 装备管理方法将每个装备都被视为一个特殊的文件,通过文件描述符进行访问和控制。可以利用标准的文件 I/O 操作(如 read、write、open)来进行输入输出,Linux 通过文件体系的统一性,将装备的访问、控制和管理与平凡文件一致化。此外,采用装备驱动模型,通过装备驱动步调管理各类硬件,将硬件底层细节与上层应用步调隔离,使得应用步调更专注于高层
装备的模型化:文件
装备管理:unix io接口
8.2 简述Unix IO接口及其函数
unix io接口:在计算机体系中用于毗连和互换信息的通道,重要用于数据传输和控制信号传递,是毗连CPU与外设之间的部件,它完成CPU与外界的信息传送。还包括辅助CPU工作的外围电路,如中断控制器、DMA控制器、定时器、高速cache。
1)打开文件:通过要求内核打开相应的文件,来宣告它想要访问一个I/O装备。
2)改变当前文件的位置:对于每个文件,内核保持着文件位置k、初始为0。该文件位置即为从文件开头起始的字节偏移量。可通过实行seek操作,显示地设置文件的当前位置为k。
3)读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增长到k+n。写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
4)关闭文件:完成文件访问,通知内核关闭该文件。
I/O接口函数:
1)打开文件:open 函数,打开一个文件,并返回一个文件描述符(file descriptor)。int open(const char *pathname, int flags, ...);此中pathname:要打开的文件的路径名。flags:用于指定打开文件的模式(如只读、只写、读写等)以及其他选项(如创建文件、追加写等)。...:如果flags中包含了O_CREAT,则还需要一个额外的参数mode来指定新文件的权限。
2)关闭文件:close 函数,关闭一个已打开的文件描述符。int close(int fd);参数:fd 是要关闭的文件描述符。
3)读取文件:read 函数,从文件中读取数据到缓冲区。ssize_t read(int fd, void *buf, size_t count);fd:要读取的文件描述符。buf:指向用于存储读取数据的缓冲区的指针。count:要读取的字节数。
4)写入文件:write 函数,将数据从缓冲区写入文件。ssize_t write(int fd, const void *buf, size_t count);fd:要写入的文件描述符。buf:指向包含要写入数据的缓冲区的指针。count:要写入的字节数。
5)文件定位:lseek 函数,移动文件的读写指针到指定位置。off_t lseek(int fd, off_t offset, int whence);fd:要移动读写指针的文件描述符。offset:相对于whence的偏移量。whence:指定偏移量的起始位置(如文件开头、当前位置、文件结尾等)。
8.3 printf的实现分析
printf的函数体如下:
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
之后printf利用了可变参数的模式,在函数体内它调用了vsprintf函数,vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数厘革的参数进行格式化,产生格式化输出。
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}
接着调用write底层体系函数,将数据从缓冲区写入标准输出。此时,在 Linux 中,用户空间步调通过软中断或体系调用(int 0x80 或 syscall 指令)切换到内核空间实行体系调用。
字符显示驱动子步调:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
8.4 getchar的实现分析
getchar函数体如下:
int getchar(void){
static char buf[BUFSIZ];
static char *bb = buf;
static int n = 0;
if(n == 0)
{
n = read(0, buf, BUFSIZ);
bb = buf;
}
return(--n >= 0)?(unsigned char) *bb++ : EOF;
}
重要调用read函数,将缓冲区都读到buf里面,n将缓冲区的长度赋值给n,如果n>0,那么返回buf的第一个元素,
异步异常-键盘中断的处置惩罚:键盘中断处置惩罚子步调。接受按键扫描码转成ascii码,保存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章重要讲hello的IO管理,起首引入linux下的IO装备管理方法,接着报告了unix IO接口及相干函数,表明其流程与几种常见的函数如open,close,read,lseek等,并结合以上函数分析了printf和getchar的实现方式
结论
起首由hello的P2P、020的引入,对hellode整个生命周期建立团体框架,hello总结如下:
1、编写:利用C语言编写源步调hello.c,hello今后诞生。
2、预处置惩罚:预处置惩罚器(cpp)对源步调hello.c文件进行处置惩罚,生成hello.i文件。
3、编译:编译器(cc1)将hello.i文件进行编译,得到汇编文件hello.s。将源代码转换成机器码。
4、汇编:汇编器(as)将汇编文件hello.s转化为可重定位目标文件hello.o。
5、链接:链接器(ld)将可重定位目标文件hello.o和其他目标文件链接生成可实行目标文件hello。
6、在shell输入./hello 2022110242 谢全荣 17793884284 4运行hello步调。
7、创建历程:shell调用fork函数为hello创建子历程。
8、运行历程:调用execve函数运行hello,加载映射捏造内存,在当前历程的上下文中加载运行hello。
9、访问内存:在运行hello时会涉及访存操作,这就需要通过MMU将需要访问的捏造地点转化为物理地点即进行段式及页式管理并访问。
10、信号与异常:hello运行过程中可能会产生各种异常与信号,体系对其异常处置惩罚和信号处置惩罚等一系列操作
13、hello运行结束,终极被父历程或init历程回收所占资源
感悟:
在完本钱次大作业的工程中,我不仅温习到了册本中所讲到的知识,同时将理论用于实践,对所学有了更深入的理解,另外,本次大作业着眼于hello步调的一系列操作,将每章的琐碎的知识点串联在一起,使得我对整本书有了一个更加清楚的架构,同时他也让我从一个步调员的角度相识了各函数各流程的利用,让我有了更深入的思考。
附件
hello.c: hello的C语言源步调
hello.i: ASCII码的中心文件(预处置惩罚器产生),用于分析预处置惩罚过程。
hello.s: ASCII汇编语言文件,用于分析编译的过程。
hello.o:可重定位目标步调(汇编器产生),用于分析汇编的过程。
hello:可实行目标文件(链接器产生),用于分析链接的过程.
elf.txt:hello.o的通用的可实行、可链接和可共享的文件格式
elf1.txt:hello的通用的可实行、可链接和可共享的文件格式
asm.txt:hello.o的objdump反汇编文件,用于分析可重定位目标文件hello.o。
asm1.txt:hello的objdump反汇编文件,用于分析可实行目标文件hello。
参考文献
[1] Gopher大威,通过hello步调的生命周期理解计算机体系,csdn计算机体系专栏,2020.5.9
[2] kkbca,Linux------历程的fork()详解,csdn,2024.1.16
[3] zmrlinux ,动态链接详解,csdnc/c++专栏,2015.8.21
[4] 南方铁匠,操作体系内存管理——分区、页式、段式管理、段页式 csdn操作体系栏目,2017.11.13
[5] ZY-JIMMY,Linux历程管理 | 替换历程映像exec系列函数,csdnLinux底子、网络与内核专栏,2019.2.23
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |