当前位置: 首页 > news >正文

【Linux】进程的程序替换

文章目录

  • 1. 程序替换
    • 1.创建子进程的目的是什么?
    • 2.了解程序是如何进行替换的
    • 3. 程序替换的基本原理
      • 当创建进程的时候,先有进程数据结构,还是先加载代码和数据?
      • 程序替换是整体替换,不是局部替换
      • execl 返回值
    • 4. 替换函数
      • 1. execl
      • 2. execv
      • 3. execlp
      • 4. execvp
      • 5. execle
  • 2. 自定义shell
      • 缓冲区问题
      • fgets 使用出现空格问题
      • 完整代码
      • 动图演示

1. 程序替换

1.创建子进程的目的是什么?

目标:为了让子进程帮父进程执行特定的任务

  • 具体做法:1. 让子进程执行父进程的一部分代码

红框中的代码实际上是父进程的代码,在没有执行fork之前代码就有了,在没有创建子进程之前,父进程的代码加载到内存了,子进程被创建出来是没有独立的代码,这个代码是父进程的代码,父进程通过if判断分流让子进程去跑了

  • 2.创建一个子进程不执行父进程的代码,而是让子进程在磁盘当中执行全新的程序,这种操作称之为进程的程序替换

2.了解程序是如何进行替换的

程序替换函数 execl
输入 man execl 查看程序替换接口

int execl(const char *path, const char *arg, …);
括号内部的 . . . 称为 可变参数列表,可以给c函数传递任意个数的参数
第一个参数为 要执行什么命令
第二个参数 为 要怎样执行程序
最后以NULL结尾表示参数传完了


创建test.c文件并输入以下内容

#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("begin......\n");execl("/bin/ls", "ls", "-a", "-l", NULL);printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");}

运行可执行程序发现,只有begin 以及执行 ls -l -a显示的指令


再次修改test.c文件内容如下

#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("我已经是一个进程啦,我的PID:%d\n",getpid());                                                                                                                                                                      execl("/bin/ls", "ls", "-a", "-l", NULL);printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");}

test.c 经过编译形成mytest可执行程序,./可执行程序就变成进程了,CPU调度进程 ,打印出代码中的打印语句,同时调用程序替换execl,将ls程序执行起来了


[yzq@VM-8-8-centos nn]$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c8ada1f7095f6b2bb7ddc848e088c2d615c3743e, stripped

使用的 /bin/ls 命令 实际上是一个可执行程序,所以ls程序是在磁盘上的


前面执行的是自己代码的一部分,当调用execl时,将磁盘中可执行程序替换当前进程的代码和数据
后半部分就不执行自己的代码了,执行ls所对应的代码 ,这个现象就叫做程序替换

程序替换就是让一个进程去执行另一个在磁盘中的程序,让一个进程把一个新的程序运行起来

3. 程序替换的基本原理

在这里插入图片描述
当前的进程执行当前代码时,如果执行了函数execl等接口,就会根据你所传入的程序的路径以及你要执行的名称及选项,把磁盘当中的一个其他的程序加载到对应的内存,
用新程序的代码替换当前进程的代码段,用当前进程的数据替换老进程的数据段


站在进程的角度
进程的程序替换有没有创建新的进程呢?
没有,只是将新的程序加载到当前进程的代码段和数据段,用CPU去调度当前进程就可以跑起来了


站在程序的角度
程序被加载了内存中,就可以称程序替换的接口(execl) 为加载器

当创建进程的时候,先有进程数据结构,还是先加载代码和数据?

修改test.c文件为以下内容

#include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4    int main()5    {6        execl("/bin/ls", "ls", "-a", "-l", NULL);                                                                                                                                          7   }

此时运行可执行程序,自己就写了一个ls命令


创建子进程,让子进程调用execl,在调用execl把代码和数据加载到内存
所以当创建进程的时候,先有进程数据结构,再加载代码和数据

程序替换是整体替换,不是局部替换

修改test.c文件内容如下

 #include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4 #include<sys/wait.h>5    int main()6    {7      pid_t id=fork();8      if(id==0)9      {10        //child11        printf("我是子进程:%d\n",getpid());12        execl("/bin/ls", "ls", "-a", "-l", NULL);                        13      }sleep(5);14      printf("我是父进程:%d\n",getpid());15      waitpid(id,NULL,0);                                                                                                                                                                16   }  

查看子进程完成替换后会不会影响父进程,如果影响父进程,就不应该打印父进程的这句话了


过了5秒钟,父进程结果打印出来,说明父进程不受子进程影响


程序替换只会影响调用进程,进程具有独立性
父子进程都有自己独立的PCB 地址空间 页表 也会自己的映射关系
虽然代码有可能是跟父进程共享,当子进程进行程序替换的时候,子进程会加载新进程的代码和数据
操作系统会发生写时拷贝,将代码和数据进行区分 ,使子进程形成新的映射关系,从而使子进程不会影响到父进程

execl 返回值

如果出错了,execl返回值为-1


修改test.c文件内容如下

#include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4 #include<sys/wait.h>5    int main()6    {7      pid_t id=fork();8      if(id==0)9      {10        //child11        printf("我是子进程:%d\n",getpid());12       int n=execl("/bin/lsss", "lsss", "-a", "-l", NULL);//lsss命令不存在     13        printf("you can see me:%d\n",n);14        exit(0);15      }16      sleep(5);17      printf("我是父进程:%d\n",getpid());                                                                                                                                                18      waitpid(id,NULL,0);                                                                                                                            19   }                                                                                                                                                 20      

输入的lsss命令不存在,查询报错后的execl的返回值


程序替换只要成功,就会跑去执行新程序,失败了就会继续向后运行
所以execl程序替换成功不会有返回值——>如果替换失败,一定有返回值——>如果失败了,必定返回——>只要有返回值就失败了
说明不用对execl函数的返回值进行判断,只要继续向后运行一定失败

4. 替换函数

1. execl

int execl(const char *path, const char *arg, …);
l 代表 list 链表
path:代表你想执行谁 (需要带路径)
执行一个程序最基本的原则为:找到它,加载执行它
arg:你想怎么执行它(若想执行ls指令,是只执行ls,还是执行ls- l 、ls -l -a指令
在命令行怎么执行这个命令,就把参数一个一个的传递给execl就可以了
最终以NULL结尾


具体的实现以及返回值问题上面在演示程序替换时已经使用过啦

2. execv

int execv(const char *path, char *const argv[]);
v代表vector 容器
path:代表你想执行谁 (需要带路径)
把原来需要一个一个传的参数放在argv[]数组中


修改test.c文件内容

1    #include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4 #include<sys/wait.h>5    int main()6    {7      pid_t id=fork();8      if(id==0)9      {10        //child11        printf("我是子进程:%d\n",getpid());12        char *const myargv[]={"ls", "-l", "-a",NULL};
13       execv("/bin/ls",myargv);14        exit(0);15      }16      sleep(5);17      printf("我是父进程:%d\n",getpid());18      waitpid(id,NULL,0);19   }20                                                                                                                                                                                       

执行可执行程序,依旧可以执行ls指令

3. execlp

int execlp(const char *file, const char *arg, …);
带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找
file: 不需要传路径,只需要把在PATH环境变量的指令传过来
最后以NULL结尾


   #include<stdio.h>  2    #include<stdlib.h>  3   #include<unistd.h>  4 #include<sys/wait.h>  5    int main()  6    {  7      pid_t id=fork();  8      if(id==0)  9      {  10        //child  11        printf("我是子进程:%d\n",getpid());  12        execlp("ls", "ls", "-l", "-a",NULL);13        exit(0);14      }15      sleep(5);                                                                                                                                                                          16      printf("我是父进程:%d\n",getpid());                                                              17      waitpid(id,NULL,0);                                                                              18   }   

执行可执行程序,依旧可以执行ls指令


4. execvp

int execvp(const char *file, char *const argv[]);
带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找
v代表vector 容器


     #include<stdio.h>    #include<stdlib.h>    #include<unistd.h>    #include<sys/wait.h>    int main()    {    pid_t id=fork();    if(id==0)    {    //child    printf("我是子进程:%d\n",getpid());    char* const myargv[]={"ls", "-l", "-a",NULL};    execvp("ls", myargv);    exit(0);    }    sleep(5);    printf("我是父进程:%d\n",getpid());    waitpid(id,NULL,0);                                                                                                                                                                  }    

在这里插入图片描述

5. execle

int execle(const char *path, const char *arg,
…, char * const envp[]);
path:代表你想执行谁 (需要带路径)
envp[]:代表自定义环境变量
如果调用程序替换时,若不想让子进程使用父进程的环境列表,想自定义环境变量,就可以自己传一个环境变量


在另一个目录中创建other.cc (以cc为结尾说明是一个c++程序),并输入以下内容

#include <iostream>    
#include <unistd.h>    
#include<stdlib.h>    
using namespace std;    int main()    
{    for(int i = 0; i < 5; i++)    {    cout<< "我是另一个程序,我的pid是:"<< getpid()<<endl;                                                                                                                                 cout << " MYENV: " << (getenv("MYENV")==NULL?"NULL":getenv("MYENV")) << endl;    sleep(1);                                        }                                                    return 0;                                            
}               

修改test.c文件为以下内容

     #include<stdio.h>    #include<stdlib.h>    #include<unistd.h>    #include<sys/wait.h>    int main()    {    pid_t id=fork();    if(id==0)    {    //child    printf("我是子进程:%d\n",getpid());    
W>       char*const myenv[]={"MYENV=YouCanSeeMe", NULL};    execle("/home/mydir/my/mm/myother", "myother", NULL,myenv);    exit(0);    }    sleep(1);    printf("我是父进程:%d\n",getpid());    int  status=0;    waitpid(id,&status,0);    return 0;                                                                                                                                                                            }                                                                                                                                                                   

在这里插入图片描述

第一个路径为other.cc生成的可执行程序 myother 所在的 绝对路径



2. 自定义shell

编写极简版本的shell(bash)
目标:为了深刻的理解shell的运行原理


输入 ps ajx |grep bash ,发现bash就是一个进程

在这里插入图片描述


由于shell是一个进程,所以用while死循环

缓冲区问题


在这里插入图片描述
正常来说,运行可执行程序会显示命令行,但是由于没有\n刷新缓冲区,也没有使用相关的刷新库函数,所以命令行会一直在缓冲区中 直到 程序结束才显示,但是这是个死循环,所以什么都不会显示


在这里插入图片描述


在这里插入图片描述


执行可执行程序后即可显示命令行

fgets 使用出现空格问题

fgets 标准输入 按行获取
char *fgets(char *s, int size, FILE *stream);
从特定的标准输入当中获取对应的命令行输入,把对应的数据放在缓冲区中




执行可执行程序后,发现在命令行中输入 ls -a ,就会打印出 ls -a,但是输入时会多出一个空行


在这里插入图片描述

正常来说,都会使用回车来到下一行,而这个回车被fgets读取到了


在这里插入图片描述
将最后的回车符替换成’\0’


在这里插入图片描述
此时就没有空格出现了

完整代码

: mybash.c ? ?                                                                                                                                                                ?? buffers #include<stdio.h>#include<unistd.h>#include<string.h>#include<assert.h>#include<sys/types.h>#include<sys/wait.h>#include<stdlib.h>#define MAX 1024#define ARGC 64#define SEP " "int split (char*commandstr,char*argv[]){assert(commandstr);assert(argv);argv[0]=strtok(commandstr,SEP);//在commandstr以空格作为分割符if(argv[0]==NULL)//切割失败{return -1;}int i=1;while(1){argv[i]=strtok(NULL,SEP);//继续切割空格if(argv[i]==NULL){break; }i++;                                                                                                                                                                                 }
W>}void print(char*argv[]){int i=0;for(i=0;argv[i];i++){                                                                                                                                                                                       printf("%d:%s\n",i,argv[i]);}}int main(){   while(1){char commandstr[MAX]={0};char*argv[ARGC]={NULL};printf("[yzq@mymachine currpath]# ");fflush(stdout);char*s= fgets(commandstr,sizeof(commandstr),stdin);assert(s);(void)s;//保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用commandstr[strlen(commandstr)-1]='\0';//将最后的回车符用'\0'覆盖掉int n= split(commandstr,argv); //从命令行中获取的字符串传入argv中if(n!=0)continue;//切割失败就什么都不做// print(argv);//打印字符串pid_t id=fork();assert(id>=0);(void)id;if(id==0){//childexecvp(argv[0],argv);exit(1);}int status=0;waitpid(id,&status,0);}}

动图演示

在这里插入图片描述

相关文章:

【Linux】进程的程序替换

文章目录1. 程序替换1.创建子进程的目的是什么&#xff1f;2.了解程序是如何进行替换的3. 程序替换的基本原理当创建进程的时候&#xff0c;先有进程数据结构&#xff0c;还是先加载代码和数据&#xff1f;程序替换是整体替换&#xff0c;不是局部替换execl 返回值4. 替换函数1…...

【C++】模板(上)

文章目录1、泛型编程2、函数模板函数模板的实例化模板参数的匹配原则3、 类模板类模板的实例化1、泛型编程 void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp left;left …...

express框架利用formidable上传图片

express框架&#xff0c;在上传图片功能方面&#xff0c;用formidable里面的incomingform功能&#xff0c;很方便。很多功能都已经封装好了&#xff0c;非常好用&#xff0c;简单&#xff0c;不需要写更深层次的代码了。确实不错。 下面是我自己跟着黑马教程的博客系统的部分&…...

测试背锅侠?入职软件测试后大d佬给我丢了这个bug分类分析,至今受益匪浅......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 刚成为入职&#xf…...

STM32 OTA应用开发——通过内置DFU实现USB升级(方式1)

STM32 OTA应用开发——通过内置DFU实现USB升级&#xff08;方式1&#xff09; 目录STM32 OTA应用开发——通过内置DFU实现USB升级&#xff08;方式1&#xff09;前言1 硬件介绍2 环境搭建2.1 Keil uVsion2.2 zadig2.3 STM32CubeProgrammer2.4 安装USB驱动3 OTA升级结束语前言 …...

基于MFC的JavaScript进行网页数据交互

目录 前言 一、创建html对话框工程 二、使用步骤 1.引入JavaScript接口代码 2.重写相关接口 3.在html网页中添加C/C调用的接口 4.在MFC工程中添加调用接口 5.设置确认按键触发调用 6.运行结果 总结 前言 如何快速的进行MFC开发,这里我介绍一种JavaScript与C/C交互的…...

AUTOSAR-Fee

Fee模块 全称Flash EEPROM Emulation Module,属于ECU抽象层 Fee模块本身是脱离硬件的,但是Fee模块可能会引用的Fls模块定制API,所以只能算半抽象. FEE模块应从设备特定的寻址方案和分段中抽象出来,并为上层提供虚拟寻址方案和分段(virtual addressing scheme and segment…...

Linux基本命令——操作演示

Linux基本命令——操作演示Linux的目录结构Linux命令入门目录切换相关命令&#xff08;cd/pwd&#xff09;相对路径、绝对路径和特殊路径符创建目录命令&#xff08;mkdir&#xff09;文件操作命令part1 (touch、cat、more)文件操作命令part2 (cp、mv、rm&#xff09;查找命令 …...

【Linux】目录和文件的权限

Linux中的权限有什么作用Linux权限管理文件访问者的分类文件类型和访问权限&#xff08;事物属性&#xff09;**文件权限值的表示方法**文件访问权限的相关设置方法chmodchownchgrpumaskumask使用 sudo分配权限目录的权限Linux中的权限有什么作用 Linux下有两种用户&#xff1…...

Unity 优化之Player Setting

Quality SettingPixel Light Count 使用前向渲染时最大像素光源数。也是性能关键。数量越大消耗越多。Texture Quality&#xff1a;贴图质量&#xff0c;可以选择Half Res&#xff0c;这样速度会更快&#xff0c;但是贴图质量会轻微下降。Anisotropic Textures 纹理各向异形Ant…...

Qt——通过一个简单的程序例程熟悉使用Qt Creator软件进行项目搭建的基本流程(新建项目、项目的文件组成、修改ui文件、编译运行与调试)

【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C++语言开发基础总结》 《从0到1学习嵌入式Linux开发》 《QT开发实战》 《Android开发实战》...

Linux 如何使用 git | 新建仓库 | git 三板斧

文章目录 专栏导读 一、如何安装 git 二、注册码云账号 三、新建仓库 配置仓库信息 四、克隆远端仓库到本地 五、git 三板斧 1. 三板斧第一招&#xff1a;git add 2. 三板斧第二招&#xff1a;git commit 解决首次 git commit 失败的问题 配置机器信息 3. 三…...

3.springcloud微服务架构搭建 之 《springboot自动装配ribbon》

1.springcloud微服务架构搭建 之 《springboot自动装配Redis》 2.springcloud微服务架构搭建 之 《springboot集成nacos注册中心》 ribbon工作原理自己网上百度&#xff0c;说的都很详细 目录 1.项目引入openfeign和ribbon配置 2.新建lilock-ribbon-spring-boot-starter 3…...

【一】进程到底是个啥?

1. 什么是进程 进程&#xff08;process&#xff09;&#xff1a;一个运行起来的程序&#xff0c;就是进程&#xff01;&#xff0c;我们可以在任务管理中看到进程。 进程是操作系统进行资源分配的基本单位 2. 进程的管理 所谓的进程管理&#xff0c;其实就是分为两步&…...

[蓝桥杯] 双指针、BFS和DFS与图论问题

文章目录 一、日志统计 1、1 题目描述 1、2 题解关键思路与解答 二、献给阿尔吉侬的花束 2、1 题目描述 2、2 题解关键思路与解答 三、红与黑 3、1 题目描述 3、2 题解关键思路与解答 3、2、1 dfs题解代码 3、2、2 bfs题解答案 四、交换瓶子 4、1 题目描述 4、2 题解关键思路与…...

编译原理陈火旺版第四章课后题答案

下面答案仅供参考&#xff01; 1.考虑下面文法G1: (1) 消去 Q 的左递归。然后&#xff0c;对每个非终结符&#xff0c;写岀不带回溯的递归子程序。 (2) 经改写后的文法是否是LL(1)的&#xff1f;给出它的预测分析表。 2.对下面的文法G: P→(E)lalblΛ (1)计算这个文法的每个非…...

【LeetCode】剑指 Offer(25)

目录 题目&#xff1a;剑指 Offer 49. 丑数 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 49. 丑数 - 力扣&…...

【数据结构】链表OJ

Yan-英杰的主页 悟已往之不谏 知来者之可追 目录 ​编辑 ​编辑二、分享&#xff1a;OJ调试技巧 ​编辑三、链表的中间结点 ​编辑四、链表中倒数第k个结点 一、移除链表元素 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,…...

电子工程师必须掌握的硬件测试仪器,你确定你都掌握了?

目录示波器示例1&#xff1a;测量示波器自带的标准方波信号输出表笔认识屏幕刻度认识波形上下/左右移动上下/左右刻度参数调整通道1的功能界面捕获信号设置Menu菜单触发方式触发电平Cursor按钮捕捉波形HLEP按钮参考资料频谱分析仪器信号发生器示波器 示例1&#xff1a;测量示波…...

高速PCB设计指南系列(四)

第二篇 抗干扰3&#xff08;部分&#xff09; 3 提高敏感器件的抗干扰性能 提高敏感器件的抗干扰性能是指从敏感器件这边考虑尽量减少对干扰噪声 的拾取&#xff0c;以及从不正常状态尽快恢复的方法。 提高敏感器件抗干扰性能的常用措施如下&#xff1a; &#xff08;1&…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

xmind转换为markdown

文章目录 解锁思维导图新姿势&#xff1a;将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件&#xff08;ZIP处理&#xff09;2.解析JSON数据结构3&#xff1a;递归转换树形结构4&#xff1a;Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙

WebGL&#xff1a;在浏览器中解锁3D世界的魔法钥匙 引言&#xff1a;网页的边界正在消失 在数字化浪潮的推动下&#xff0c;网页早已不再是静态信息的展示窗口。如今&#xff0c;我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室&#xff0c;甚至沉浸式的V…...