内核定时器3-用户空间定时器
用户空间定时器与内核定时器的关系
虽然用户空间定时器和内核定时器在实现上各自独立,但用户空间定时器通常依赖于内核定时器提供的基础设施。以下是具体关系:
- 依赖性
- 用户空间定时器的实现基于内核定时器。
- 例如,POSIX 定时器使用内核的
hrtimer机制。 alarm()和setitimer()使用内核低精度定时器。
- 例如,POSIX 定时器使用内核的
- 内核通过用户态 API(如
timerfd和clock_nanosleep())将定时器功能暴露给用户空间。
- 用户空间定时器的实现基于内核定时器。
- 精度
- 用户空间定时器的精度取决于内核定时器的支持:
- 在启用高分辨率定时器(
CONFIG_HIGH_RES_TIMERS=y)时,用户空间定时器可以实现亚毫秒级精度。 - 如果系统只支持低分辨率定时器,用户空间定时器的精度会受到限制(分辨率基于
HZ和jiffies)。
- 在启用高分辨率定时器(
- 用户空间定时器的精度取决于内核定时器的支持:
- 隔离性
- 内核定时器运行在内核态,直接管理硬件资源,对用户态透明。
- 用户空间定时器运行在用户态,通过系统调用访问内核资源。
- 性能
- 内核定时器的执行效率更高,因为它直接操作硬件并在内核上下文中运行。
- 用户空间定时器的执行效率相对较低,因其需要从用户态切换到内核态并依赖系统调用。
用户空间定时器
在 Linux 中,用户空间有多种方式可以使用定时器来执行定时任务或管理延时操作。以下是常见的方法及其详细说明:
1. 使用 **alarm()** 函数
- 功能:
alarm()用于设置一个定时器,定时结束时会向当前进程发送SIGALRM信号。
- 用法:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>void alarm_handler(int sig) {printf("Alarm triggered!\n");
}int main() {signal(SIGALRM, alarm_handler); // 设置信号处理函数alarm(5); // 设置定时器,5秒后触发pause(); // 等待信号return 0;
}
- 特点:
- 简单易用。
- 只能设置秒级的定时器。
- 只能设置一个定时器。
- 定时器到期后,如果是多线程,会选择一个当前非阻塞的线程来处理信号,处理线程并不确定。
- 依赖的内核机制
- 依赖于**内核 jiffies,**即低分辨率定时器。
- 内核定时器队列(
timer_list)会管理这些定时器,触发时发送信号。
2. 使用 **setitimer()** 函数
- 功能:
setitimer()可以设置定时器,以精确控制一次性或周期性的定时任务。- 当定时时间到达时,会向调用进程发送信号(通常是
SIGALRM)。
- 用法:
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>void timer_handler(int sig) {printf("Timer triggered!\n");
}int main() {struct itimerval timer;signal(SIGALRM, timer_handler); // 设置信号处理函数// 定时器间隔为1秒timer.it_value.tv_sec = 1;timer.it_value.tv_usec = 0;timer.it_interval.tv_sec = 1;timer.it_interval.tv_usec = 0;setitimer(ITIMER_REAL, &timer, NULL); // 设置定时器while (1); // 无限循环等待定时器触发return 0;
}
- 特点:
- 支持周期性定时器。
- 提供微秒级别的精度。
- 依赖的内核机制
- 本身最初是设计为基于低分辨率定时器,但在现代内核中,如果高分辨率定时器启用,它可以利用高分辨率定时器来提高精度。
- 内核定时器队列(
timer_list)会管理这些定时器,触发时发送信号。
3. 使用 POSIX 定时器 (**timer_create()**** 和 **timer_settime()**)**
- 功能:
- POSIX 定时器提供了灵活的定时功能,可以通过多种方式通知(如信号或回调)。
- 可以创建多个定时器。
- 使用
timer_create()创建一个定时器,时间到达时触发信号或调用特定的回调函数。
- 用法:
**timer_create**:- 用于创建一个 POSIX 定时器。
- 可以选择通知方式,包括发送信号(
SIGEV_SIGNAL)和使用线程回调(SIGEV_THREAD)。
**timer_settime**:- 用于设置定时器的到期时间 (
it_value) 和周期性时间 (it_interval)。
- 用于设置定时器的到期时间 (
- 定时器的通知方式:
SIGEV_SIGNAL: 定时器超时时发送指定信号(如SIGALRM)。SIGEV_THREAD: 定时器超时时调用用户定义的回调函数。
// gcc -o posix_timer posix_timer.c -lrt -pthread#include <time.h>
#include <signal.h>
#include <stdio.h>void timer_handler(union sigval sv) {printf("POSIX timer triggered![%ld]\n", pthread_self());
}int main() {timer_t timerid;struct sigevent sev;struct itimerspec ts;sev.sigev_notify = SIGEV_THREAD; // 使用线程通知方式sev.sigev_value.sival_ptr = &timerid;sev.sigev_notify_function = timer_handler;sev.sigev_notify_attributes = NULL;timer_create(CLOCK_REALTIME, &sev, &timerid);ts.it_value.tv_sec = 2; // 初始触发时间ts.it_value.tv_nsec = 0;ts.it_interval.tv_sec = 2; // 周期触发时间ts.it_interval.tv_nsec = 0;timer_settime(timerid, 0, &ts, NULL);while (1); // 等待定时器触发return 0;
}
可以看到有两个线程,一个main线程,一个回调线程。
$ #ps -eLf | grep posix_timer
congchp 1965725 1138860 1965725 99 2 11:11 pts/4 00:02:27 ./posix_timer
congchp 1965725 1138860 1965726 0 2 11:11 pts/4 00:00:00 ./posix_timer
congchp 1966341 939085 1966341 0 1 11:13 pts/2 00:00:00 grep --color=auto posix_timer
- 特点:
- 支持多个定时器。
- 支持多种通知方式(如线程回调或信号)。
- 通知方式如果使用SIGEV_THREAD,则自动创建一个回调线程。
- 依赖的内核机制
- 内核使用
hrtimer(高精度定时器)实现。 hrtimer基于内核的高精度时间子系统,支持纳秒级别的精度。- 事件触发通过
softirq或中断机制实现。
- 内核使用
4. 使用 **sleep()** **nanosleep()**``**clock_nanosleep()** 函数
- 功能
- 这些函数用于让进程挂起一定时间。
sleep()提供秒级精度,而nanosleep()和clock_nanosleep()提供纳秒级精度。- 可以被信号(如Ctrl+C)打断。
※Ctrl+C后,先发送SIGINT,如果SIGINT没有被处理,则发送SIGKILL强制终止进程。
- 用法:
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <stdlib.h>void handle_sigint(int sig) {printf("Received SIGINT (Ctrl+C). Exiting...\n");
}int main() {struct timespec req, rem;req.tv_sec = 2; // Sleep for 2 secondsreq.tv_nsec = 500000000; // Plus 500 milliseconds// 设置自定义信号处理函数signal(SIGINT, handle_sigint);printf("Sleeping for 2.5 seconds...\n");if (nanosleep(&req, &rem) == -1) {perror("nanosleep interrupted");printf("%ld.%ld remain\n", rem.tv_sec, rem.tv_nsec);return 1;}printf("Sleep complete!\n");return 0;
}
- 依赖的内核机制
- 使用hrtimer(高精度定时器)进行时间跟踪。
- 内核会将进程标记为睡眠状态,定时时间到后通过唤醒机制让进程继续运行。
5. **使用 ****poll()**/select()/epoll()的超时功能
- 功能
- 提供 I/O 多路复用时的超时功能(timeout),用于等待文件描述符的状态变化。
- 这些函数可以设置超时时间,如果没有检测到IO,让调用线程挂起指定时间。
- 用法:
#include <stdio.h>
#include <poll.h>
#include <unistd.h>int main() {struct pollfd fds[1];fds[0].fd = STDIN_FILENO; // Monitor standard inputfds[0].events = POLLIN;int timeout = 5000; // 5-second timeoutprintf("Waiting for input (5 seconds timeout)...\n");int ret = poll(fds, 1, timeout);if (ret == -1) {perror("poll failed");} else if (ret == 0) {printf("Timeout occurred!\n");} else if (fds[0].revents & POLLIN) {char buffer[100];read(STDIN_FILENO, buffer, sizeof(buffer));printf("Input received: %s", buffer);}return 0;
}
- 特点:
- 高效且支持事件驱动(如结合
epoll)。 - 适用于文件描述符管理的场景。
- 高效且支持事件驱动(如结合
- 依赖的内核机制
- 高分辨率定时器启用的情况下,使用hrtimer;否则使用tick-based定时器,取决于内核配置和硬件支持。
- 内核会将调用线程加入等待队列,并在超时到期或事件发生时唤醒线程。
6. 使用 **timerfd** 接口
- 功能
**timerfd**: 通过文件描述符(FD)暴露定时器事件,适合与epoll一起使用。- read()函数读取出来的,自上一次读取以来的所有未处理的超期次数。
- 用法:
#include <sys/timerfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>int main() {int tfd = timerfd_create(CLOCK_REALTIME, 0);struct itimerspec ts;ts.it_value.tv_sec = 2; // 初始触发时间ts.it_value.tv_nsec = 0;ts.it_interval.tv_sec = 2; // 周期触发时间ts.it_interval.tv_nsec = 0;timerfd_settime(tfd, 0, &ts, NULL);uint64_t expirations;while (1) {read(tfd, &expirations, sizeof(expirations)); // 阻塞等待printf("Timer expired %lu times\n", expirations); // expirations = 1}close(tfd);return 0;
}
- 特点:
- 高效且支持事件驱动(如结合
epoll)。 - 适用于大量定时器、文件描述符管理的场景。
- 高效且支持事件驱动(如结合
- 依赖的内核机制
timerfd基于内核的 hrtimer 实现。- 内核将定时器事件封装为文件描述符事件,通过 VFS 层传递给用户空间。
对比各种方法
| 方法 | 多定时器支持 | 周期性定时器 | 精度 | 内核机制 | 易用性 | 推荐用途 |
|---|---|---|---|---|---|---|
alarm() | 否 | 否 | 秒级 | 低分辨率定时器 | 简单 | 单一的短时任务 |
setitimer() | 否 | 是 | 微秒级 | 低分辨率定时器 | 较简单 | 简单的周期性任务 |
POSIX 定时器 | 是 | 是 | 纳秒级 | 高分辨率定时器 | 较复杂 | 多任务,灵活的通知方式 |
sleep()/ nanosleep() | 否 | 否 | 秒级/纳秒 | 低/高分辨率定时器 | 简单 | 简单的延时任务 |
poll()/select()/epoll() | 是 | 是 | 毫秒级 | 高分辨率定时器 | 较复杂 | 高效的文件描述符管理场景 |
timerfd | 是 | 是 | 纳秒级 | 高分辨率定时器 | 较复杂 | 高效的文件描述符管理场景 |
根据需求选择合适的定时器接口,可以满足不同的用户空间定时任务需求。
timerfd+IO多路复用组合使用
※※※ 推荐的方法,是**timerfd**+**IO多路复用**组合使用的方法。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sched.h>#define MAX_EVENTS 10
#define WORKER_COUNT 2
#define QUEUE_NAME "/task_queue"// 任务结构
typedef struct {int task_id;
} Task;// 线程信息
typedef struct {mqd_t mqd;pthread_t thread;int id;
} WorkerThread;WorkerThread workers[WORKER_COUNT];// 任务回调
void task_callback(int task_id) {printf("Worker processing task %d\n", task_id);sleep(1); // 模拟任务执行
}// 设定线程优先级
void set_thread_priority(pthread_t thread, int priority) {struct sched_param param;param.sched_priority = priority;pthread_setschedparam(thread, SCHED_FIFO, ¶m);
}// 工作线程函数
void *worker_thread(void *arg) {WorkerThread *worker = (WorkerThread *)arg;Task task;while (1) {if (mq_receive(worker->mqd, (char *)&task, sizeof(Task), NULL) > 0) {task_callback(task.task_id);}}
}// 选择负载最轻的工作线程
WorkerThread *get_least_busy_worker() {struct mq_attr attr;WorkerThread *best_worker = &workers[0];for (int i = 1; i < WORKER_COUNT; i++) {mq_getattr(workers[i].mqd, &attr);struct mq_attr best_attr;mq_getattr(best_worker->mqd, &best_attr);if (attr.mq_curmsgs < best_attr.mq_curmsgs) {best_worker = &workers[i];}}return best_worker;
}// 定时器检测线程
void *timer_thread(void *arg) {set_thread_priority(pthread_self(), 90);int epfd = epoll_create1(0);int timerfd = timerfd_create(CLOCK_REALTIME, 0);struct itimerspec timer_value = {.it_value = {2, 0}, .it_interval = {3, 0} };timerfd_settime(timerfd, 0, &timer_value, NULL);struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = timerfd;epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev);while (1) {struct epoll_event events[MAX_EVENTS];int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);for (int i = 0; i < nfds; i++) {if (events[i].data.fd == timerfd) {uint64_t exp;read(timerfd, &exp, sizeof(uint64_t));printf("Timer expired! Dispatching task...\n");static int task_id = 0;Task task = {task_id++};WorkerThread *worker = get_least_busy_worker();mq_send(worker->mqd, (char *)&task, sizeof(Task), 0);}}}
}// 主程序
int main() {struct mq_attr attr = {.mq_flags = 0,.mq_maxmsg = 10,.mq_msgsize = sizeof(Task),.mq_curmsgs = 0};// 初始化工作线程for (int i = 0; i < WORKER_COUNT; i++) {workers[i].id = i;workers[i].mqd = mq_open(QUEUE_NAME, O_CREAT | O_RDWR | O_NONBLOCK, 0644, &attr);pthread_create(&workers[i].thread, NULL, worker_thread, &workers[i]);set_thread_priority(workers[i].thread, 80);}// 启动定时器检测线程pthread_t timer_tid;pthread_create(&timer_tid, NULL, timer_thread, NULL);pthread_join(timer_tid, NULL);for (int i = 0; i < WORKER_COUNT; i++) {pthread_join(workers[i].thread, NULL);mq_close(workers[i].mqd);}mq_unlink(QUEUE_NAME);return 0;
}
还有一些定时器数据结构设计方法,参考之前另一篇定时器文章:《定时器实现》。
相关文章:
内核定时器3-用户空间定时器
用户空间定时器与内核定时器的关系 虽然用户空间定时器和内核定时器在实现上各自独立,但用户空间定时器通常依赖于内核定时器提供的基础设施。以下是具体关系: 依赖性 用户空间定时器的实现基于内核定时器。 例如,POSIX 定时器使用内核的 h…...
知识管理系统助力企业信息共享与创新思维的全面提升研究
内容概要 知识管理系统的引入极大地改变了企业内部的信息流程与创新机制。通过有效整合与管理组织内的知识资源,这些系统不仅降低了信息孤岛的现象,还提升了员工之间的协作能力。企业在信息共享方面,通过知识管理系统构建了一个透明、高效的…...
LLM - 基于LM Studio本地部署DeepSeek-R1的蒸馏量化模型
文章目录 前言开发环境快速开始LM Studio简单设置模型下载开始对话 模型选择常见错误最后 前言 目前,受限于设备性能,在本地部署的基本都是DeepSeek-R1的蒸馏量化模型,这些蒸馏量化模型的表现可能并没有你想象的那么好。绝大部分人并不需要本…...
本地部署 DeepSeek-R1:简单易上手,AI 随时可用!
🎯 先看看本地部署的运行效果 为了测试本地部署的 DeepSeek-R1 是否真的够强,我们随便问了一道经典的“鸡兔同笼”问题,考察它的推理能力。 📌 问题示例: 笼子里有鸡和兔,总共有 35 只头,94 只…...
实现网站内容快速被搜索引擎收录的方法
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/6.html 实现网站内容快速被搜索引擎收录,是网站运营和推广的重要目标之一。以下是一些有效的方法,可以帮助网站内容更快地被搜索引擎发现和收录: 一、确…...
浅谈量化感知训练(QAT)
1. 为什么要量化? 假设你训练了一个神经网络模型(比如人脸识别),效果很好,但模型太大(比如500MB),手机根本跑不动。于是你想压缩模型,让它变小、变快。 最直接的压缩方法…...
对象的实例化、内存布局与访问定位
一、创建对象的方式 二、创建对象的步骤: 一、判断对象对应的类是否加载、链接、初始化: 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化…...
制造业设备状态监控与生产优化实战:基于SQL的序列分析与状态机建模
目录 1. 背景与挑战 2. 数据建模与采集 2.1 数据表设计 设备状态表(记录设备实时状态变更)...
OpenAI推出Deep Research带给我们怎样的启示
OpenAI 又发新产品了,这次是面向深度研究领域的智能体产品 ——「Deep Research」,貌似被逼无奈的节奏… 在技术方面,Deep Research搭载了优化后o3模型并通过端到端强化学习在多个领域的复杂浏览和推理任务上进行了训练。因没有更多的技术暴露…...
K8S学习笔记-------1.安装部署K8S集群环境
1.修改为root权限 #sudo su 2.修改主机名 #hostnamectl set-hostname k8s-master01 3.查看网络地址 sudo nano /etc/netplan/01-netcfg.yaml4.使网络配置修改生效 sudo netplan apply5.修改UUID(某些虚拟机系统,需要设置才能生成UUID)#…...
【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户登录
🧸安清h:个人主页 🎥个人专栏:【Spring篇】【计算机网络】【Mybatis篇】 🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。 目录 🎯1.登录-持久层 &…...
【Deep Seek本地化部署】模型实测:规划求解python代码
目录 前言 一、实测 1、整数规划问题 2、非线性规划问题 二、代码正确性验证 1、整数规划问题代码验证 2、非线性规划问题代码验证 三、结果正确性验证 1、整数规划问题结果正确性验证 2、非线性规划问题正确性验证 四、整数规划问题示例 后记 前言 模型ÿ…...
虚幻基础17:动画蓝图
能帮到你的话,就给个赞吧 😘 文章目录 animation blueprint图表(Graph): 编辑动画逻辑。变量(Variables): 管理动画参数。函数(Functions): 自定义…...
【游戏设计原理】98 - 时间膨胀
从上文中,我们可以得到以下几个启示: 游戏设计的核心目标是让玩家感到“时间飞逝” 游戏的成功与否,往往取决于玩家的沉浸感。如果玩家能够完全投入游戏并感受到时间飞逝,说明游戏设计在玩法、挑战、叙事等方面达到了吸引人的平衡…...
C语言基础系列【1】第一个C程序:Hello, World!
C语言的历史与特点 历史背景 C语言起源于20世纪70年代,最初是由美国贝尔实验室的Dennis Ritchie和Ken Thompson为了开发UNIX操作系统而设计的一种编程语言。在UNIX系统的开发过程中,他们发现原有的B语言(由Thompson设计)在功能和…...
【LLM】DeepSeek-R1-Distill-Qwen-7B部署和open webui
note DeepSeek-R1-Distill-Qwen-7B 的测试效果很惊艳,CoT 过程可圈可点,25 年应该值得探索更多端侧的硬件机会。 文章目录 note一、下载 Ollama二、下载 Docker三、下载模型四、部署 open webui 一、下载 Ollama 访问 Ollama 的官方网站 https://ollam…...
go-zero学习笔记(三)
利用goctl生成rpc服务 编写proto文件 // 声明 proto 使用的语法版本 syntax "proto3";// proto 包名 package demoRpc;// golang 包名(可选) option go_package "./demo";// 如需为 .proto 文件添加注释,请使用 C/C 样式的 // 和 /* ... */…...
C# 修改项目类型 应用程序程序改类库
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 源码指引:github源…...
普罗米修斯监控服务搭建位置全解析:权衡与抉择
在数字化时代,监控系统对于企业的稳定运营和业务发展至关重要。普罗米修斯作为一款备受青睐的开源监控和告警工具,其搭建位置的决策绝非小事,它紧密关联着监控系统的性能、可靠性与安全性,如同为整座大厦奠定基石。接下来…...
为什么“记住密码”适合持久化?
✅ 特性 1:应用重启后仍需生效 记住密码的本质是长期存储用户的登录凭证(如用户名、密码、JWT Token),即使用户关闭应用、重启设备,仍然可以自动登录。持久化存储方案: React Native 推荐使用 AsyncStorag…...
地址查询API接口:高效查询地址信息,提升数据处理效率
地址查询各省市区API接口 地址查询是我们日常生活中经常遇到的一个需求,无论是在物流配送、地图导航还是社交网络等应用中,都需要通过地址来获取地理位置信息。为了满足这个需求,我们可以使用地址查询API接口来高效查询地址信息,提…...
2021版小程序开发5——小程序项目开发实践(1)
2021版小程序开发5——小程序项目开发实践(1) 学习笔记 2025 使用uni-app开发一个电商项目; Hbuidler 首选uni-app官方推荐工具:https://www.dcloud.io/hbuilderx.htmlhttps://dev.dcloud.net.cn/pages/app/list 微信小程序 管理后台:htt…...
元音字母(模拟)
给定一个由大小写字母、空格和问号组成的字符串。 请你判断字符串中的最后一个字母是否是元音字母。 我们认为元音字母共有 66 个,分别为:AA、EE、II、OO、UU、YY(当然还有它们的小写)。 输入格式 一个由大小写字母、空格和问…...
如何处理 Typecho Joe 主题被抄袭或盗版的问题
在开源社区中,版权保护是一个非常重要的话题。如果你发现自己的主题(如 Joe 主题)被其他主题(如子比主题)抄袭或盗版,你可以采取以下措施来维护自己的权益。 一、确认侵权行为 在采取任何行动之前…...
将markdown文件和LaTex公式转为word
通义千问等大模型生成的回答多数是markdown类型的,需要将他们转为Word文件 一 pypandoc 介绍 1. 项目介绍 pypandoc 是一个用于 pandoc 的轻量级 Python 包装器。pandoc 是一个通用的文档转换工具,支持多种格式的文档转换,如 Markdown、HTM…...
自动化测试框架搭建-封装requests-优化
目的 1、实际的使用场景,无法避免的需要区分GET、POST、PUT、PATCH、DELETE等不同的方式请求,以及不同请求的传参方式 2、python中requests中,session.request方法,GET请求,只支持params传递参数 session.request(me…...
Smart contract -- 钱包合约
在区块链的世界里,钱包是存储和管理加密货币的基本工具。今天,我们将通过 Solidity 智能合约来创建一个简单的以太坊钱包。这个钱包将允许用户存入和取出以太坊主币(ETH),并且只有管理员(合约的创建者&…...
模拟实战-用CompletableFuture优化远程RPC调用
实战场景 这是广州某500-900人互联网厂的面试原题 手写并发优化解决思路 我们要调用对方的RPC接口,我们的RPC接口每调用一次对方都会阻塞50ms 但是我们的业务要批量调用RPC,例如我们要批量调用1k次,我们不可能在for循环里面写1k次远程调用…...
图 、图的存储
图的基本概念: 图g由顶点集v和边集e组成,记为g(v,e) 用|v|表示图g中顶点的个数,也称图g的阶,用|e|表示图g中边的条数 线性表可以是空表,树可以是空树,但图不可以是空&…...
快速提升网站收录:利用网站新闻发布功能
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/63.html 利用网站新闻发布功能快速提升网站收录是一个有效的策略。以下是一些具体的建议,帮助你更好地利用这一功能: 一、保持新闻更新频率 搜索引擎尤其重视网站的…...
