Linux任务管理与守护进程
一、任务管理
(一)进程组、作业、会话概念
(1)进程组概念:进程组是由一个或多个进程组成的集合,这些进程在某些方面具有关联性。在操作系统中,进程组是用于对进程进行分组管理的一种机制。每个进程组有一个唯一的进程组 ID 来标识它,通常情况下,进程组 ID 是其第一个加入该组的进程的进程 ID。
(2)作业概念:作业是用户一次提交给计算机系统进行处理的任务集合。它通常包括一个或多个进程,以及这些进程的相关资源(如输入文件、输出文件、环境变量等)。作业是用户与操作系统交互的基本单位,反映了用户想要完成的特定任务。例如,用户在命令行中输入一个命令(如gcc -o program source.c
),这个命令及其相关的输入文件(source.c
)、输出文件(program
)和编译过程中的临时文件等,共同构成了一个作业。
(3)会话的概念:会话是一个或多个进程组的集合。它提供了一种机制,用于将相关进程组织在一起,并管理它们与终端设备的交互。
会话组成部分:
- 控制终端:会话可以有一个控制终端,这是用户与系统交互的设备。控制终端可以是物理终端(如本地登录的终端设备),也可以是伪终端(如通过SSH登录的终端)。控制终端是会话与用户交互的接口,用户通过控制终端输入命令和接收输出。
- 控制进程:建立与控制终端连接的会话首进程被称为控制进程。控制进程是会话的第一个进程,通常是一个登录Shell。控制进程负责初始化会话,并管理会话中的其他进程。
- 前台进程组:每个会话中有一个前台进程组。前台进程组是当前与控制终端直接交互的进程组。前台进程组可以接收来自控制终端的输入,并将输出发送到控制终端。例如,当用户在终端中按下
Ctrl+C
时,信号会被发送到前台进程组中的所有进程。 - 后台进程组:会话中可以有多个后台进程组。后台进程组不会直接与控制终端交互,它们在后台运行。后台进程组可以通过其他方式(如文件、网络)与用户或其他进程通信,但不会直接接收控制终端的输入。
(二)进程组、作业、会话关系
- 会话:是最高层次的组织单位,包含一个或多个进程组。
- 进程组:是会话中的一个子集,包含一个或多个相关进程。
- 作业:是用户提交的任务集合,通常对应一个进程组。作业可以是前台作业或后台作业。
- 作业 是从用户的角度定义的,它关注的是用户提交的任务。一个作业可以包含一个或多个进程组。例如,一个复杂的作业可能涉及多个步骤,每个步骤由一个进程组完成。
- 当作业中的某个进程创建子进程时,子进程默认会继承父进程的进程组ID。因此,子进程最初属于父进程所在的进程组。然子进程属于父进程所在的进程组,但它并不自动成为原作业的一部分。作业的范围通常由Shell管理,而Shell并不自动将子进程纳入原作业的管理范围。
为什么子进程不属于原作业
- 作业的管理范围:作业是由Shell管理的,Shell通常将作业定义为用户直接提交的任务及其直接相关的进程。如果作业中的某个进程创建了子进程,这些子进程可能超出了Shell对作业的管理范围。
- 资源管理的独立性:子进程可能需要独立于原作业进行资源管理和信号处理。将子进程排除在原作业之外,可以避免作业结束时对子进程产生不必要的影响。
- 作业结束:当作业运行结束时,Shell会将自己提到前台,以便用户可以继续输入新的命令。
- 子进程的处理:如果原前台作业中的某个子进程仍然存在且未终止,它将自动变为后台进程组。这是因为作业的结束并不一定意味着所有相关进程的结束,而Shell需要重新接管前台以便用户可以继续输入新的命令。
这些进程组的控制终端相同,它们同属于一个会话,当用户在控制终端输入特殊的控制键(如Ctrl+C产生SIGINT,Ctrl+\产生SIGQUIT,Ctrl+Z产生SIGTSTP),内核就会发送相应的信号给前台进程组中的所有进程。
[1] 787011
其中[1]是作业的编号,如果同时运行多个作业可以用这个编号进行区分,787011是该作业中某个进程的id(一个作业可以由多个进程组成)。
(三)任务管理相关命令
(1)jobs:查看当前会话有哪些作业
(2)fg + 作业号:可以将某个作业提至前台运行,如果该作业正在后台运行则直接提至前台运行,如果该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续运行并提至前台。
将一个前台进程放到后台运行可以使用Ctrl+Z,但使用Ctrl+Z后该进程就会处于停止状态
(3)bg + 作业号:可以让某个停止的作业在后台继续运行(Running),本质就是给该作业的进程组的每个进程发SIGCONT信号。
二、守护进程
(一)守护进程定义
- 守护进程是一种在后台运行的特殊进程,它独立于用户终端,并且通常在系统启动时启动,在系统关闭时才终止。守护进程的主要功能是周期性地执行某些任务,或者等待并处理某些事件,而不需要用户直接干预。
(二)守护进程特点
- 独立于终端:守护进程没有控制终端,它不会与任何用户终端关联,因此不会因用户注销而终止。
- 后台运行:守护进程在后台运行,不会占用前台的终端资源,也不会干扰用户的正常操作。
- 周期性或事件驱动:守护进程通常会周期性地执行某些任务(如定时备份、日志清理等),或者等待某些事件的发生(如网络请求、文件系统变化等)。
- 系统服务:守护进程通常是系统服务的一部分,为系统或其他应用程序提供支持,例如网络服务、定时任务调度等。
(三)守护进程作用
提供系统服务:许多系统服务都是以守护进程的形式运行,例如:
- 网络服务:如 Web 服务器(
httpd
)、FTP 服务器(ftpd
)、DNS 服务器(named
)等。 - 定时任务调度:如作业规划进程(
crond
),用于执行定时任务。 - 系统日志服务:如日志守护进程(
syslogd
),负责收集和记录系统日志。
系统管理:守护进程可以执行一些系统管理任务,例如:
- 磁盘空间监控:定期检查磁盘空间,防止磁盘满导致系统故障。
- 系统备份:定期备份重要数据,确保数据安全。
用户服务:守护进程也可以为用户提供服务,例如:
- 邮件服务:如邮件传输代理(
sendmail
),负责邮件的发送和接收。 - 打印服务:如打印守护进程(
lpd
),管理打印任务。
(四)查看守护进程
我们可以用ps axj
命令查看系统中的进程:
- 参数a表示不仅列出当前用户的进程,也列出所有其他用户的进程。
- 参数x表示不仅列出有控制终端的进程,也列出所有无控制终端的进程。
- 参数j表示列出与作业控制相关的信息。
凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程:
(1)内核线程
定义:内核线程是运行在内核空间的线程,完全由内核创建和管理,没有用户空间代码,因此不会出现在用户空间的进程列表中。
特点:
- 名字通常以
k
开头,例如kworker
、kswapd
、ksoftirqd
等。- 在
ps
或top
命令的COMMAND
列中,内核线程的名字会被方括号[]
括起来,以区分普通进程。- 内核线程没有用户空间的上下文,因此没有程序文件名和命令行参数。
(2)常见的守护进程
守护进程是运行在后台的特殊进程,独立于控制终端,通常以
d
结尾的名字来标识。以下是文章中提到的几个常见守护进程及其功能:
udevd
:
- 功能:负责维护
/dev
目录下的设备文件。- 作用:当系统检测到硬件设备的插入或移除时,
udevd
会动态创建或删除对应的设备文件,确保用户和程序能够正确访问硬件设备。
acpid
:
- 功能:负责电源管理。
- 作用:监听电源事件(如电池电量低、AC适配器插拔等),并根据配置执行相应的操作(如关机、休眠等)。
syslogd
:
- 功能:负责维护
/var/log
下的日志文件。- 作用:收集系统日志信息,将其记录到指定的日志文件中,便于系统管理员进行故障排查和系统监控。
(3)守护进程的命名:守护进程通常以
d
结尾,例如httpd
(Web服务器)、sshd
(SSH服务器)、crond
(定时任务守护进程)等。这种命名方式是为了方便区分守护进程和其他普通进程。
(五)守护进程创建
(1)设置文件掩码为0:
umask(0);
目的:将文件掩码设置为0,确保后续守护进程创建文件具有预期的权限
(2)第一次fork后终止父进程,子进程创建新会话
if (fork() > 0) {exit(0); // 父进程退出
}
setsid(); // 子进程创建新会话,脱离终端
目的:调用setsid()创建新会话;使得进程自成会话,与当前bash脱离关系。要求调用进程不是进程
组组长,因此先fork创建子进程,由子进程调用setsid。
为什么要求调用进程不是进程组组长?
根据 POSIX 标准,
setsid()
的行为有以下限制:
- 如果调用
setsid()
的进程已经是某个进程组的组长,则setsid()
会失败,并返回错误码EPERM
。- 这是因为如果允许进程组组长调用
setsid()
,可能会导致会话和进程组的管理混乱,尤其是在终端管理方面。具体原因:
避免会话管理混乱:
- 如果允许进程组组长调用
setsid()
,可能会导致该进程既属于旧的进程组,又成为新的会话首进程,这会使得会话和进程组的关系变得复杂,难以管理。- 通过要求调用进程不是进程组组长,可以确保会话的创建过程是清晰和一致的。
避免终端管理问题:
- 当一个进程调用
setsid()
时,它会断开与控制终端的关联。如果该进程是进程组组长,那么它可能正在管理其他进程对终端的访问。- 通过要求调用进程不是进程组组长,可以避免因终端管理权限问题导致的混乱。
(3)忽略SIGCHLD信号
signal(SIGCHLD, SIG_IGN);
目的:忽略SIGCHLD信号,防止父进程退出时触发信号,避免僵尸进程。
(4)第二次fork,终止父进程,保持子进程不是会话首进程
if (fork() > 0) {exit(0); // 父进程退出
}
目的:在 Linux 系统中,只有会话首进程(Session Leader)才有能力打开终端设备。会话首进程是通过 setsid()
创建的,它成为新会话的领导者。如果一个进程不是会话首进程,它将无法打开终端设备。通过第二次 fork()
,孙子进程不再是会话首进程,因此它无法打开终端。这确保了守护进程不会意外地重新连接到某个终端,从而避免了终端资源的占用。
防御性编程:
虽然在大多数情况下,守护进程不会主动尝试打开终端,但进行第二次
fork()
是一种防御性编程的手段。它确保即使守护进程的代码中存在某些意外情况(例如错误地尝试打开终端),守护进程也不会成功打开终端,从而避免潜在的问题。
(5)更改工作目录为根目录:
chdir("/");
守护进程将工作目录设置为根目录后:
- 绝对路径:仍然从根目录开始解析,与工作目录无关。例如,
/home/user/file.txt
会从根目录开始解析,不受当前工作目录的影响。 - 相对路径:从根目录开始解析。例如,
./file.txt
实际上指的是/file.txt
,而不是其他目录下的file.txt
。
将守护进程的工作目录设置为根目录主要是为了避免守护进程占用特定的文件系统资源。如果守护进程的工作目录位于某个可卸载的文件系统中(例如,网络挂载目录或可移动存储设备),当该文件系统被卸载时,守护进程可能会受到影响。通过将工作目录设置为根目录,可以避免这种情况。此外,这也减少了对特定目录的依赖,确保守护进程能够独立运行
(6)将标准输入、标准输出、标准错误重定向到/dev/null中
close(0);
int fd = open("/dev/null", O_RDWR);
dup2(fd, 1);
dup2(fd, 2);
目的:将标准输入、输出、错误重定向到/dev/null
,避免守护进程输出信息到终端。
守护进程不能直接和用户交互,也就是说守护进程已经与终端去关联了,因此一般我们会将守护进程的标准输入、标准输出以及标准错误都重定向到/dev/null
,/dev/null
是一个字符文件(设备),通常用于屏蔽/丢弃输入输出信息。(该操作不是必须的)
代码如下:
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<fcntl.h>int main()
{//1.设置权限掩码umask(0);//2.创建子进程创建新会话if(fork()>0) exit(0);setsid();//3.忽略SIGCHLD信号signal(SIGCHLD,SIG_IGN);//4.防御性编程if(fork()>0) exit(0);//5.更改工作目录为根目录chdir("/");//6.将标准输入、标准输出、标注你错误重定向到/dev/null中close(0);int fd=open("/dev/null",O_RDWR);dup2(fd,1);dup2(fd,2);//代替服务器while(1);return 0;
}
运行代码,用ps命令查看该进程,发现该进程TPGID为-1,TTY显示为?,说明该进程已经与终端去关联了。
其次就是我们还可以看见该进程的PID与其PGID和SID不同,也就是说该进程既不是组长进程也不是会话首进程。
此外,我们还可以看到该进程的SID与bash进程的SID是不同的,即它们不属于同一个会话。
通过ls /proc/进程pid -al命令,可以看到该进程的工作目录设置为根目录
通过ls /proc/进程pid/fd -al命令,可以看见该进程的标准输入、标准输出以及标准错误成功重定向到/dev/null上。
(六)调用daemon函数创建守护进程
函数 | 返回值 | 参数 | |
int daemon(int nochdir, int noclose); | 如果成功,返回 如果失败,返回 |
|
调用daemon函数创建的守护进程与我们原生创建的守护进程差距不大,唯一区别就是daemon函数创建出来的守护进程,既是组长进程也是会话首进程。也就是说系统实现的daemon并没有防止守护进程打开终端,系统并没有进行防御性编程
(七)模拟实现daemon函数
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>void my_daemon(int nochdir, int noclose)
{// 1.设置权限掩码umask(0);// 2.创建子进程创建新会话if (fork() > 0)exit(0);setsid();// 3.忽略SIGCHLD信号signal(SIGCHLD, SIG_IGN);// 4.防御性编程if (fork() > 0)exit(0);// 5.更改工作目录为根目录if (nochdir == 0)chdir("/");// 6.将标准输入、标准输出、标注你错误重定向到/dev/null中if (noclose == 0){close(0);int fd = open("/dev/null", O_RDWR);dup2(fd, 1);dup2(fd, 2);}
}
相关文章:

Linux任务管理与守护进程
一、任务管理 (一)进程组、作业、会话概念 (1)进程组概念:进程组是由一个或多个进程组成的集合,这些进程在某些方面具有关联性。在操作系统中,进程组是用于对进程进行分组管理的一种机制。每个…...

C#里与嵌入式系统W5500网络通讯(2)
在嵌入式代码里,需要从嵌入式的MCU访问W5500芯片。 这个是通过SPI通讯来实现的,所以要先连接SPI的硬件通讯线路。 接着下来,就是怎么样访问这个芯片了。 要访问这个芯片,需要通过SPI来发送数据,而发送数据又要有一定的约定格式, 于是芯片厂商就定义下面的通讯格式: …...

EMQX开源版安装指南:Linux/Windows全攻略
EMQX开源版安装教程-linux/windows 因最近自己需要使用MQTT,需要搭建一个MQTT服务器,所以想到了很久以前用到的EMQX。但是当时的EMQX使用的是开源版的,在官网可以直接下载。而现在再次打开官网时发现怎么也找不大开源版本了,所以…...

【计算机视觉】OpenCV实战项目:GraspPicture 项目深度解析:基于图像分割的抓取点检测系统
GraspPicture 项目深度解析:基于图像分割的抓取点检测系统 一、项目概述项目特点 二、项目运行方式与执行步骤(一)环境准备(二)项目结构(三)执行步骤 三、重要逻辑代码解析(一&#…...

MySQL 数据库备份与还原
作者:IvanCodes 日期:2025年5月18日 专栏:MySQL教程 思维导图 备份 (Backup) 与 冗余 (Redundancy) 的核心区别: 🎯 备份是指创建数据的副本并将其存储在不同位置或介质,主要目的是在发生数据丢失、损坏或逻辑错误时进…...

Kubernetes控制平面组件:Kubelet详解(四):gRPC 与 CRI gRPC实现
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)Kubernetes控…...

javax.servlet.Filter 介绍-笔记
1.javax.servlet.Filter 简介 javax.servlet.Filter 是 Java Servlet API 中的一个核心接口,用于在请求到达目标资源(如 Servlet 或 JSP)之前或响应返回给客户端之前执行预处理或后处理操作。它常用于实现与业务逻辑无关的通用功能ÿ…...
从40秒到11毫秒:TiDB环境下一次SQL深潜优化实战
作者: meathill 原文来源: https://tidb.net/blog/edb6061b 在数据库应用中,慢SQL是常见的性能瓶颈。本文将详细记录一次针对TiDB Cloud v7.5.2环境中复杂评论查询的SQL优化过程,如何通过分析执行计划、添加索引、改写SQL&…...

Win 11开始菜单图标变成白色怎么办?
在使用windows 11的过程中,有时候开始菜单的某些程序图标变成白色的文件形式,但是程序可以正常打开,这个如何解决呢? 这通常是由于快捷方式出了问题,下面跟着操作步骤来解决吧。 1、右键有问题的软件,打开…...

入门OpenTelemetry——应用自动埋点
埋点 什么是埋点 埋点,本质就是在你的应用程序里,在重要位置插入采集代码,比如: 收集请求开始和结束的时间收集数据库查询时间收集函数调用链路信息收集异常信息 这些埋点数据(Trace、Metrics、Logs)被…...

C语言链表的操作
初学 初学C语言时,对于链表节点的定义一般是这样的: typedef struct node {int data;struct node *next; } Node; 向链表中添加节点: void addNode(Node **head, int data) {Node *newNode (Node*)malloc(sizeof(Node));newNode->dat…...

芯片生态链深度解析(二):基础设备篇——人类精密制造的“巅峰对决”
【开篇:设备——芯片工业的“剑与盾”】 当ASML的EUV光刻机以每秒5万次激光脉冲在硅片上雕刻出0.13nm精度的电路(相当于在月球表面精准定位一枚二维码),当国产28nm光刻机在华虹产线实现“从0到1”的突破,这场精密制造…...

C语言指针深入详解(二):const修饰指针、野指针、assert断言、指针的使用和传址调用
目录 一、const修饰指针 (一)const修饰变量 (二)const 修饰指针变量 二、野指针 (一)野指针成因 1、指针未初始化 2、指针越界访问 3、指针指向的空间释放 (二)如何规避野指…...

【unity游戏开发——编辑器扩展】使用EditorGUI的EditorGUILayout绘制工具类在自定义编辑器窗口绘制各种UI控件
注意:考虑到编辑器扩展的内容比较多,我将编辑器扩展的内容分开,并全部整合放在【unity游戏开发——编辑器扩展】专栏里,感兴趣的小伙伴可以前往逐一查看学习。 文章目录 前言常用的EditorGUILayout控件专栏推荐完结 前言 EditorG…...

Linux基础第三天
系统时间 date命令,date中文具有日期的含义,利用该命令可以查看或者修改Linux系统日期和时间。 基本格式如下: gecubuntu:~$ date gecubuntu:~$ date -s 日期时间 // -s选项可以设置日期和时间 文件权限 chmod命令,是英文…...

MoodDrop:打造一款温柔的心情打卡单页应用
我正在参加CodeBuddy「首席试玩官」内容创作大赛,本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 起心动念:我想做一款温柔的情绪应用 「今天的你,心情如何?」 有时候&#x…...

接口——类比摄像
最近迷上了买相机,大疆Pocket、Insta Go3、大疆Mini3、佳能50D、vivo徕卡人像大师(狗头),在买配件的时候,发现1/4螺口简直是神中之神,这个万能接口让我想到计算机设计中的接口,遂有此篇—— 接…...
【上位机——WPF】布局控件
布局控件 常用布局控件Panel基类Grid(网格)UniformGrid(均匀分布)StackPanel(堆积面板)WrapPanel(换行面板)DockerPanel(停靠面板)Canvas(画布布局)Border(边框)GridSplitter(分割窗口)常用布局控件 Grid:网格,根据自定义行和列来设置控件的布局StackPanel:栈式面板,包含的…...
深入解析Spring Boot与Kafka集成:构建高性能消息驱动应用
深入解析Spring Boot与Kafka集成:构建高性能消息驱动应用 引言 在现代分布式系统中,消息队列是实现异步通信和解耦的重要组件。Apache Kafka作为一种高性能、分布式的消息系统,被广泛应用于大数据和实时数据处理场景。本文将详细介绍如何在…...

二十、案例特训专题3【系统设计篇】web架构设计
一、前言 二、内容提要 三、单机到应用与数据分离 四、集群与负载均衡 五、集群与有状态无状态服务 六、ORM 七、数据库读写分离 八、数据库缓存Memcache与Redis 九、Redis数据分片 哈希分片如果新增分片会很麻烦,需要把之前数据取出来再哈希除模 一致性哈希分片是…...

【数据结构与算法】ArrayList 与顺序表的实现
目录 一、List 接口 1.1 List 接口的简单介绍 1.1 常用方法 二、顺序表 2.1 线性表的介绍 2.2 顺序表的介绍 2.3 顺序表的实现 2.3.1 前置条件:自定义异常 2.3.2 顺序表的初始化 2.3.2 顺序表的实现 三、ArrayList 实现类 3.1 ArrayList 的两种使用方式 3.2 Array…...
处理金融数据,特别是股票指数数据,以计算和分析RSRS(相对强度指数)
Python脚本,用于处理金融数据,特别是股票指数数据,以计算和分析RSRS(相对强度指数)指标。以下是代码的逐部分解释: 1. **导入库**: - `pandas`:用于数据处理和CSV文件操作。 - `numpy`:用于数值计算。 - `ElasticNet`:来自`sklearn.linear_model`,用于线性…...

【图像处理基石】OpenCV中都有哪些图像增强的工具?
OpenCV 图像增强工具系统性介绍 OpenCV 提供了丰富的图像增强工具,主要分为以下几类: 亮度与对比度调整 线性变换(亮度/对比度调整)直方图均衡化自适应直方图均衡化(CLAHE) 滤波与平滑 高斯滤波中值滤波双…...

WPS PPT设置默认文本框
被一个模板折磨了好久,每次输入文本框都是很丑的24号粗体还有行标,非常恶心,我甚至不知道如何描述自己的问题,非常憋屈,后来终于知道怎么修改文本框了。这种软件操作问题甚至不知道如何描述问题本身,非常烦…...

PostGIS实现矢量数据转栅格数据【ST_AsRaster】
ST_AsRaster函数应用详解:将矢量数据转换为栅格数据 [文章目录] 一、函数概述 二、函数参数与分组说明 三、核心特性与注意事项 四、示例代码 五、应用场景 六、版本依赖 七、总结 一、函数概述 ST_AsRaster是PostGIS中用于将几何对象(如点、线…...

FAST-DDS源码分析PDP(一)
准备开一个FAST-DDS源码分析系列,源码版本FAST-DDS 1.1.0版本。 FAST-DDS这种网络中间件是非常复杂的,所以前期先去分析每个类的作用是什么,然后在结合RTPS DOC,FAST-DDS DEMO,以及FAST-DDS的doc去串起来逻辑。 Builtin Discovery…...

python打卡day29@浙大疏锦行
知识点回顾 类的装饰器装饰器思想的进一步理解:外部修改、动态类方法的定义:内部定义和外部定义 作业:复习类和函数的知识点,写下自己过去29天的学习心得,如对函数和类的理解,对python这门工具的理解等&…...

【数据结构】2-3-1单链表的定义
数据结构知识点合集 知识点 单链表存储结构 优点:不要求大片连续空间,改变容量方便;缺点:不可随机存取,要耗费一定空间存放指针 /*单链表节点定义*/ typedef struct LNode{ElemType data;struct LNode *next; }LNo…...

贝塞尔曲线原理
文章目录 一、 低阶贝塞尔曲线1.一阶贝塞尔曲线2. 二阶贝塞尔曲线3. 三阶贝塞尔曲线 一、 低阶贝塞尔曲线 1.一阶贝塞尔曲线 如下图所示, P 0 P_0 P0, P 1 P_1 P1 是平面中的两点,则 B ( t ) B ( t ) B(t) 代表平面中的一段线段。…...

3D个人简历网站 4.小岛
1.模型素材 在Sketchfab上下载狐狸岛模型,然后转换为素材资源asset,嫌麻烦直接在网盘链接下载素材, Fox’s islandshttps://sketchfab.com/3d-models/foxs-islands-163b68e09fcc47618450150be7785907https://gltf.pmnd.rs/ 素材夸克网盘&a…...