<Linux>进程概念-下

news2024/9/22 21:37:28

文章目录

目录

前言

一、环境变量

1. PATH

2. HOME

3. 其他环境变量

系统调用接口--getenv 

4. 命令行参数

4.1 双参数main

4.2 三参数main

5. 设置环境变量

5.1 本地环境变量

 5.1.1 内建命令

5.2 固定环境变量

6. 取消环境变量 

7. 小总结

二、程序地址空间 

1. 空间划分

2. 进程地址空间

3. 缺页中断

4. 进程地址空间的意义


前言

进程切换

为什么函数返回值会被外部拿到?

        return a -->  mov eax 10,是通过CPU寄存器实现的

系统如何得知我们的进程当前执行到哪行代码了?

        程序计数器pc,eip:记录当前进程正在执行指令的下一行指令的地址

寄存器大致分类:

        通用寄存器:eax,ebx,ecx,edx

        栈帧:ebp,esp,eip

        状态寄存器:status

寄存器有很多,那么寄存器扮演着什么角色?

        1. 提高效率,有关进程的高频访问数据放入寄存器中

        2. CPU中寄存器保存的是进程相关的数据,即进程的临时数据——进程的上下文数据,CPU中有大量的进程上下文数据

        由于进程切换与时间片概念,是进程切换基于时间片轮转的调度算法,可以在同一时间,让多个进程得以推进,称之为并发。

        所以进程在从CPU上离开的时候,要将自己进程的上下文数据保存好带走(一些临时数据,ebp、esp、eip等),如果不保存这些临时数据,进程轮转之后新的进程数据会覆盖原先的进程上下文数据,所以保存的目的是为了下次轮转到自己时快速恢复进程上下文数据,这些数据保存到PCB中(真正的是软硬件合作保存)

进程切换时:

        1. 保存上下文       

        2. 回复上下文


一、环境变量

基本概念:
        环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

        例如,我们编写的C/C++代码,在各个目标文件进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。

        环境变量通常具有某些特殊用途,并且在系统当中通常具有全局特性。

常见环境变量:

        PATH : 指定命令的搜索路径
        HOME : 指定用户的主工作目录 ( 即用户登陆到 Linux 系统中时 , 默认的目录 )
        SHELL : 当前 Shell, 它的值通常是 /bin/bash
查看环境变量方法:
        echo $name     //name:环境变量名称
        env         //显示全部的环境变量

1. PATH

echo $PATH
必须要$,不然会被echo识别为字符串

        这些路径用冒号进行连接,在调用指令时,系统默认从左到右,按指定路径进行搜索。如果不在指定的路径中,例如我们写的./mycmd,如果不写 ./ 只是简单的输入mycmd,会报错,指令找不到(找的操作是shell进行的)

        如果将mycmd所在路径添加到PATH中,那么我们直接输入mycmd不加./,也可以直接执行,所以输入 ./ 是为了确定路径

PATH=$PATH:/home/ljs/XXXXX
#等号左右两侧不能有空格
//如果不写$PATH:那么PATH会被覆写,如果被覆写后,可以重启xshell
//因为环境变量是被存储在Linux配置文件中,每次启动后会加载到内存中

        指令也是可执行程序,执行前shell要先找到路径,所以shell会维护PATH环境变量,来保存指令搜索路径。

        可以知道,which指令就是从PATH环境变量中搜索路径。 

2. HOME

        不同的用户,$HOME不同。

        在xshell中,不同用户登陆后,由xshell分配bash,命令行解释器进程,此时会默认的cd到自己的家目录路径下,而HOME环境变量就是 shell 用来保存各个用户的家目录路径。

3. 其他环境变量

和环境变量相关的命令
  • echo: 显示某个环境变量值
  • export: 设置一个新的环境变量
  • env: 显示所有环境变量
  • unset: 清除环境变量
  • set: 显示本地定义的shell变量和环境变量
env

  • HOSTNAME:主机名
  • HISTSIZE:保存到是历史命令被保存下来的条数(history指令可以查历史指令,由histsize变量控制条数)
  • SSH_TTY:终端名称,我们在xshell中可以打开多个终端,每个终端互不干扰,只在指定的终端输出就是依靠终端名分别终端
  • USER:用户名
  • LS_COLORS:配色方案
  • PATH:可执行程序的路径
  • PWD:当前进程所在路径
  • LOGNAME:当前登录用户
  • OLD_PWD:当前路径的上一个路径(cd - 等同于 cd $OLDPWD)
系统调用接口--getenv 

        系统调用接口,getenv 可以根据传入的实参字符串,返回对应的环境变量

printf("PATH: %s\n", getenv("PATH"); 

        环境变量:是系统提供的一组形如 name = value形式的变量,不同的环境变量由不同的用户,通常具有全局属性

4. 命令行参数

        我们初学c语言时,可能看到过main函数有参数

int main(int argc, char* argv[])
{
    

    return 0;
}

         其中,argv是指针数组,argc是数组内元素个数。main函数并不是第一个函数,它是被CRTStartup()调用的,那么这两个参数是干什么用的呢?

        它是为指令、工具、软件等提供命令行选项的支持!

4.1 双参数main

        我们在输入 ./mycmd 时,如果后面追加 -a -b -c -d这一串字符串,那么

        由于argv指针数组的最后一个数据的下一个位置默认设置为nullptr,所以我们也可以改变for循环内的条件。

        我们输入的 ./mycmd 在bash看来就是一个字符串,bash将空格作为分隔符,第一个字符串为指令,剩下的字符串是指令选项,bash可以得到小的字符串,根据小字符串的数量赋予argc变量值,argv数组中每个数据存放的是这些字符串首字符的地址。 

        那么我们可以根据这个原理,来设计带选项的指令,根据argv[1]中指向的字符串来判断携带的是什么选项

        这就是指令为什么可以有选项的原因,例如 ls -l 、ls -a等等。所以命令行参数它是为指令、工具、软件等提供命令行选项的支持!根据不同的选项,表现不同的功能

        所以在学c语言时,没有使用该知识点是因为当时我们不使用命令行,所以不需要。

4.2 三参数main

main函数除了这两个参数类型外,还重载的有三参数类型

int main(int argc, char* argv[], char* env[])
{

    return 0;
}

        我们使用env指针数组,也实现了输出全部环境变量的效果

综上,main函数有两张核心的向量表

        1. 命令行参数表

        2. 环境变量表

        即一个进程的创建不仅仅只是将我们的程序加载到内存就结束了,而是需要被调用main函数并完成两张核心表的建立

        我们所运行的进程,都是子进程,bash本身在启动时,会从操作系统的配置文件中读取环境变量信息,而子进程会继承父进程的环境变量,所以我们所有的进程环境变量几乎相同,这就体现了环境变量的全局性

        进程之间具有独立性,如果子进程对环境变量有修改,那么就会发生写时拷贝

        除了env、和三参数的main函数可以获取环境变量,我们还可以使用第三方变量environ来获取环境变量,可以使用man命令查看用法

        libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明
#include <stdio.h>
int main(int argc, char *argv[])
{
     extern char **environ;
     int i = 0;
     for(; environ[i]; i++)
     {
         printf("%s\n", environ[i]);
     }
     return 0;
}

5. 设置环境变量

5.1 本地环境变量
MY_VALUE=12345678

         如果再命令行处直接定义,它会成为本地环境变量,在env中查找不到

        本地环境变量不会被子进程继承,它只在本bash处有效

set
查看本地环境变量

 

        其中的ps1环境变量表示对是命令行提示符的格式,\u为用户名,\h为主机名、\w为当前工作目录,$为提示符。如果是root用户,那么提示符为#,这就表明了本地环境变量只在本bash内有效

        ps2是命令行次提示符,当一行指令还没输完,可以用 \ 续行

ls \
> -a
 5.1.1 内建命令

问题:既然本地变量只在本bash内部有效,那么为什么echo能够输出本地变量的值呢?(echo是指令,我们之前认为任何指令都会创建进程,该进程是bash的子进程),也就是说bash的子进程为什么能输出bash的本地变量?

指令分为两类

  • 常规命令 -- 通过创建子进程完成
  • 内建命令 -- bash不创建子进程,而是由自己完成,类似于bash调用了自己写的或是系统提供的函数,即echo在bash内部有代码实现,所以不创建子进程

那么类似的内建命令还有export、cd等命令,cd并不是一个新的bash的子进程,而是直接由bash操作,因为如果是bash子进程,那么它修改的就是子进程的当前工作目录,修改不了父进程工作目录,我们可以简单的实现以下cd功能,cd命令依靠的是chdir系统调用接口。

终端1:
./mycmd /

终端2:
ps axj | head -1 && ps axj | grep mycmd    从而找到pid
ls /proc/pid/cwd -l    显示当前工作目录
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
 
int main(int argc, char* argv[], char* env[])
 {
 
     sleep(20);
     printf("change beign\n");
     //判断argv的值,就是命令行参数是否为两个字符串
     if (argc == 2)
     {
         chdir(argv[1]);
     }
     printf("change end\n");
     sleep(20);

    return 0;
}

        这样就可以观察到mycmd进程确实更改了路径,将mycmd名称更改为bash,那么这就是cd的简答代码

5.2 固定环境变量
export MY_VALUE=12345678

        再进行grep查找

        此时我们再次执行我们的程序,即可证明环境变量是会被子进程继承的,因为有我们新设置的MY_VALUE

6. 取消环境变量 

unset MY_VALUE

        再查找就查找不到了,即一旦bash的环境变量改变,子进程继承的环境变量也会改变

7. 小总结

  1. 程序运行需要命令行参数表、环境变量表,这两张表是系统维护的,可以简单理解为bash(因为bash广义上也是操作系统的一部分)
  2. main函数不要简单的被语言方面局限了,main也是函数,它也需要被传参调用(被系统调用),传入两张表

二、程序地址空间 

1. 空间划分

1. 首先,我们来验证一下各区地址的大小情况

        可以看到代码区、字符常量区、已初始化全局变量、为初始化全局变量、堆区、栈区地址依次从低到高 

2. 我们再来验证堆、栈各自地址的生长情况

        可以看到,堆区向上生长,地址数为七位,栈区向下生长,地址数位12位,两者相对,中间空缺的部分为动静态库

        如果将变量a前加上static修饰,那么a的地址数将由12位更改为7位故static修饰的局部变量编译的时候已经被编译到全局数据区,只是作用域只在该函数内部,而生命周期是全局的

2. 进程地址空间

我们再来看进程方面

问:为什么同一个变量,同一个地址,同时读取,却读到了不同的内容?

        结论:该地址一定不是物理地址,如果是物理地址,那么一定不会出现上面的现象,这种地址被称为线性地址或虚拟地址

        任何一个进程都需要PCB内核数据结构进程虚拟地址表,进程虚拟地址表的地址在PCB结构体中保存,并且有一张页表来保存映射虚拟地址到物理地址

        父进程创建子进程后,子进程继承父进程的大部分PCB数据并进行修改,同时也完全拷贝父进程的虚拟地址表和页表,此时因为页表相同,所以父子进程同时指向同一块的物理地址(数据和代码都相同)当子进程检测到g_val被修改时,操作系统会先创建一个新的g_val空间,并拷贝父进程的数据,在根据子进程修改的数据进行修改,再修改页表的映射关系,原先的虚拟地址不变,修改映射的物理地址

        前提是该进程正在被CPU执行

地址空间:我们的计算机地址总线排列组合形成地址范围【0,2^32】(在32位的计算机中,有32位的地址和数据总线)

        所谓进程地址空间,本质是一个描述进程可视范围的大小。地址空间内一定要存在各种区域划分,对线性地址进行start,end描述。

        任何一个进程都要有PCB和进程地址空间

        地址空间本质是内核的一个数据结构对象,和PCB一样,也是要被操作系统管理,进行先描述,再组织

struct mm_struct
{
    long code_start, code_end;

    long readonly_start, readonly_end;
    
    long init_start, init_end;

    long uninit_start, uninit_end;

    long heap_start, heap_end;

    long stack_start, stack_end;
}

        每一个task_struct都要指向自己的 mm_struct结构体,32位系统默认内存为4GB,那么每个进程都在该物理内存中规划自己的进程地址

        进程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个区域,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度,

        在结构体mm_struct当中,各个边界刻度之间的每一个刻度都代表一个虚拟地址,这些虚拟地址通过页表映射与物理内存建立联系。由于虚拟地址是由0x00000000到0xffffffff线性增长的,所以虚拟地址又叫做线性地址。

        32位操作系统有4GB内存,每个进程的进程地址空间可分得一部分内存大小,但是不能全部获取4GB大小内存

页表:每一个进程都要在CPU上被调度,在CPU上有一个cr3寄存器,存放的是页表的起始地址(物理地址),本质上是进程的硬件上下文,所以在进程轮转时,该数据会被保存,下一次被调度时再次加载 。当CPU需要访问物理地址的数据时,会根据cr3寄存器找到页表,从而找到物理地址

        此时我们页表有三个字段,分别为进程虚拟地址、物理地址、标志位,标志位的意义在于我们查找到数据所在的物理地址后,我们应该区分该地址的数据是可读写还是只可读,这就是标志位字段的意义,标记该地址处的数据可读写情况

        所以为什么代码、字符常量是只读的?因为代码加载到磁盘就是写入,而你说了磁盘的代码处是只读的,这里的关键就是页表的第三个字段——标志位,因为标志位是r,所以是只读!!

3. 缺页中断

        我们也玩过大几十G的游戏,我们知道进程是要将自己的代码和数据加载到内存中的,但是内存大多是都是16或32GB,怎么能容纳几百G的游戏呢?

        此时操作系统的解决方案就是对大文件实现分批加载,先加载并运行一小部分,之后再加载剩余的代码和数据,这个行为就是惰性加载。此时页表的映射关系中,虚拟地址是全填的,但是会缺少一部分的物理地址,因为此时后面的代码和数据还没有加载到内存,当CPU根据虚拟地址找到内存的物理地址,发现该物理地址处还没有加载,那么就会申请空间,将对应剩余的代码和数据加载到内存中,该机制被称为缺页中断

        页表的第四个字段也是标志位,表示物理地址此处对应的代码和数据是否已经加载到内存,从而使CPU根据这一标志来判断是否触发缺页中断机制,将在磁盘中剩余的代码和数据自动加载到内存中,并构建号页表内剩余的映射关系。

        所以,进程在被创建的时候是先构建内核数据结构(PCB、虚拟地址空间、页表),再慢慢加载对应的可执行程序,从而填充页表。

        写时拷贝就是根据缺页中断实现的

那么进程之间具有独立性更具体的理解:

        首先,每一个进程都已各自的PCB、进程地址空间、页表,这些都是互相独立的

        其次是程序的代码和数据层面,因为页表的存在,即使是父子进程,代码指向相同,即使虚拟地址相同,但是它们所映射的物理地址不同,这就使得它们的数据层面解耦合,互相独立,这就是进程之间的独立性根源

4. 进程地址空间的意义

综上所述,为什么要设计进程地址空间?

  • 让进程以统一的视角看待内存。如果没有进程地址空间,那么进程PCB需要记录代码和数据的物理地址,一旦进程换出再换入时,PCB还要修改内容,而且还有越界访问的风险,所以我们需要中转站,即进程地址空间这一结构,此时进程只需要根据相应的映射即可找到代码和数据
  • 增加转换过程(页表),在转换过程中可以对我们的寻址请求进行审查。一旦异常访问,直接拦截,该请求不会到达物理内存,从而保护了物理内存
  • 因为有地址空间和页表的存在,将进程管理模块(PCB、进程地址空间、页表)和内存管理模块(页表和物理内存)进行解耦合,只负责各自的工作

 进程 = 内核数据结构(task_struct && mm_struct && 页表)+ 程序的代码和数据

所以,进程切换的时候,只用切换进程上下文数据即可,因为task_struct中有指针指向进程地址空间,而cr3寄存器属于进程上下文数据,cr3寄存器存放的是页表的起始地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2041998.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

haproxy负载均衡(twenty-eight day)

官网&#xff1a; https://www.haproxy.com/ 自由及开放源代码软件 HAPrOxy是一个使用C语言编写的自由及开旅酒代码软性&#xff0c;其提供高可用性、负我均衡&#xff0c;以及基于TCP和HTTP的应用程座代理 HAProxy特别适用于那些负载特大的webi些站点通常又需要会话保挂或七层…

单片机中时钟源(Clock Source)和时基源(Timebase Source)和的联系和区别

问题描述 在单片机中&#xff0c;时钟源&#xff08;Clock Source&#xff09;和时基源&#xff08;Timebase Source&#xff09;是两个与时间相关的基本概念&#xff0c;它们在单片机的时钟系统设计中扮演着重要角色。 区别与联系 1.区别 1.1定义 时钟源&#xff1a;是单片机…

【C语言篇】编译和链接以及预处理介绍(上篇)

文章目录 前言翻译环境和运行环境翻译环境编译预处理&#xff08;预编译&#xff09;编译词法分析语法分析语义分析 汇编 链接 运行环境预处理&#xff08;预编译&#xff09;详解预定义符号#define定义常量#define定义宏带有副作用的宏参数宏替换的规则宏和函数的对比 写在最后…

【git】gitee 提交错误,如何回退

文章目录 查看提交记录设定退回到位置提交 查看提交记录 git log git log如下图所示共2次提交记录 最近一次是错误提交&#xff08;笔者提交是在错误的工作路径上传了&#xff09; 设定退回到位置 git reset --hard hash值 git reset --soft 83fcc380d5250599eca********…

rabbit消息队列

一&#xff1a;消息队列简介 1&#xff1a;主流的消息队列 目前主流的几大消息队列有&#xff1a;RabitMQ、ActiveMQ、RocketMQ、Kafka、ZeroMQ等&#xff0c;也有一些小众的比如Beanstalk&#xff0c;当然我们之前学过的Redis也可以实现消息队列的功能。 &#xff08;1&…

Android全面解析之Context机制(一) :初识Android context

什么是Context 回想一下最初学习Android开发的时候&#xff0c;第一用到context是什么时候&#xff1f;如果你跟我一样是通过郭霖的《第一行代码》来入门android&#xff0c;那么一般是Toast。Toast的常规用法是&#xff1a; Toast.makeText(this, "我是toast", To…

详解【网路编程】之Socket套接字编程

谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注 没错&#xff0c;说的就是你&#xff0c;不用再怀疑&#xff01;&#xff01;&#xff01; 希望我的文章内容能对你有帮助&#xff0c;一起努力吧&#xff01;&#xff01;&#xff01; 1、Socket套接字 Socket 是一个…

4G 和 5G 中的单域注册(VoLTE和VoNR适用)VoNR 中的 CSRetry

目录 1. 4G 和 5G 中的单域注册&#xff08;VoLTE和VoNR适用&#xff09; 1.1 主要内容 1.2 什么是 4/5G 网络中的单域注册 1.3 为什么需要单域注册 1.4 单域注册主要参数之&#xff1a;Dual-Registration-5G-Indicator 1.5 单域注册主要参数之&#xff1a;DualRegistrat…

基于微信小程序地图实现点位标注、覆盖物、地图聊天

目录 小程序部分map标签的使用获取用户经纬度并转换地址地图点击事件覆盖物标注点击并实现弹窗交互数据库及接口部分数据库表结构设计API搭建小程序接口使用注意事项wx.getLocation深入控制地图小程序部分 map标签的使用 创建小程序的步骤这里不再重复赘述,在wxml页面中放一个…

EtherCAT运动控制器PT/PVT实现用户自定义轨迹规划

ZMC408CE硬件介绍 ZMC408CE是正运动推出的一款多轴高性能EtherCAT总线运动控制器&#xff0c;具有EtherCAT、EtherNET、RS232、CAN和U盘等通讯接口&#xff0c;ZMC系列运动控制器可应用于各种需要脱机或联机运行的场合。 ZMC408CE支持8轴运动控制&#xff0c;最多可扩展至32轴…

美团到店面经

redis中大key引起的问题 1、阻塞请求 Big Key对应的value较大&#xff0c;我们对其进行读写的时候&#xff0c;需要耗费较长的时间&#xff0c;这样就可能阻塞后续的请求处理。Redis的核心线程是单线程&#xff0c;单线程中请求任务的处理是串行的&#xff0c;前面的任务完不成…

【RabbitMQ】 相关概念 + 工作模式

本文将介绍一些MQ中常见的概念&#xff0c;同时也会简单实现一下RabbitMQ的工作流程。 MQ概念 Message Queue消息队列。是用来存储消息的队列&#xff0c;多用于分布式系统之间的通信。 系统间调用通常有&#xff1a;同步通信和异步通信。MQ就是在异步通信的时候使用的。 同…

Go 1.19.4 错误处理-Day 11

1. 错误处理机制 1.1 先看一段代码&#xff0c;引出Golang错误处理机制 看到了上面的代码后&#xff0c;思考两个问题&#xff1f; &#xff08;1&#xff09;有没有办法&#xff0c;能让test()执行出错了&#xff0c;它下面的fmt代码依然能够运行。 &#xff08;2&#xff09…

service 管理 web 管理插件

clusterIP 资源清单文件 [rootmaster ~]# kubectl create service clusterip websvc --tcp80:80 --dry-runclient -o yaml 解析域名 创建后端应用 负载均衡 固定 IP 服务 端口别名 nodePort 对外发布服务 Ingress 对外发布服务 查询 ingress 控制器类名称 kubectl g…

一个功能强大、易于使用的开源WEB表单构建工具Formbricks

大家好&#xff0c;今天给大家分享的是一个功能强大、易于使用的表单构建工具Formbricks&#xff0c;能够帮助开发者和非开发者快速构建各种类型的表单。 项目介绍 Formbricks 是一个开源的、基于 Web 的表单构建器&#xff0c;旨在帮助开发者和非开发人员轻松创建复杂的表单&…

大数据技术——Hadoop运行环境搭建

目录 一、 Hadoop运行环境搭建 1.1 模板虚拟机环境准备 1.2 克隆虚拟机 一、 Hadoop运行环境搭建 1.1 模板虚拟机环境准备 0&#xff09;安装模板虚拟机&#xff0c;IP地址192.168.10.100、主机名称hadoop100、内存4G、硬盘50G 具体操作参照下列文档 大数据技术之模板虚…

8.15日学习打卡---Spring Cloud Alibaba(三)

8.15日学习打卡 目录&#xff1a; 8.15日学习打卡为什么需要服务网关Higress是什么安装DockerCompose部署Higress创建网关微服务模块Higress路由配置Higress策略配置-跨域配置Higress解决如何允许跨域Higress策略配置之什么是HTTP认证Higress策略配置-Basic 认证什么是JWT认证J…

腾讯云AI代码助手 —— 编程新体验,智能编码新纪元

阅读导航 引言一、开发环境介绍1. 支持的编程语言2. 支持的集成开发环境&#xff08;IDE&#xff09; 二、腾讯云AI代码助手使用实例1. 开发环境配置2. 代码补全功能使用&#x1f4bb;自动生成单句代码&#x1f4bb;自动生成整个代码块 3. 技术对话3. 规范/修复错误代码4. 智能…

C++ | stack/queue

前言 本篇博客讲解cSTL中的stack/queue &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大…

LMDeploy 量化部署实践闯关任务-50%的A100跑的过程

基础任务&#xff08;完成此任务即完成闯关&#xff09; 使用结合W4A16量化与kv cache量化的internlm2_5-7b-chat模型封装本地API并与大模型进行一次对话&#xff0c;作业截图需包括显存占用情况与大模型回复&#xff0c;参考4.1 API开发(优秀学员必做)使用Function call功能让…