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

内核内存回收关键隐藏变量之page引用计数

在分析内核内存回收源码时,page引用计数并不显眼,但是page引用计数对page的内存回收至关重要。本文基于linux-4.18.0-240版本内核源码,总结下文件页page的引用计数的相关细节。首先是get_page()和put_page()函数,分别令page引用计数加1和减1.

  1. //page引用计数加1
  2. static inline void get_page(struct page *page)
  3. {
  4.     page_ref_inc(page);
  5. }
  6. //page引用计数减1
  7. static inline void put_page(struct page *page)
  8. {
  9.     if (put_page_testzero(page))
  10.         __put_page(page);
  11. }

以read系统调用读文件为例,最后执行到generic_file_buffered_read函数,先page_cache_alloc()分配一个文件页page,此时的page引用计数是0。

  1. static ssize_t generic_file_buffered_read(struct kiocb *iocb,
  2.         struct iov_iter *iter, ssize_t written)
  3. {
  4.     page = page_cache_alloc(mapping);
  5.     error = add_to_page_cache_lru(page, mapping, index,mapping_gfp_constraint(mapping, GFP_KERNEL));
  6. }

然后执行add_to_page_cache_lru函数把page添加到radix/xrray tree,接着把page添加到lru缓存和lru链表

  1. int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
  2.                 pgoff_t offset, gfp_t gfp_mask)
  3. {
  4.     //page添加到radix/xrray tree时令page引用计数加1
  5.     ret = __add_to_page_cache_locked(page, mapping, offset,gfp_mask, &shadow);
  6.     //page添加到lru缓存时令page引用计数加1,把pagelru缓存移动到lru链表时再令page引用计数减1
  7.     lru_cache_add(page);
  8. }

在把page添加到radix/xrray tree时令page引用计数加1

  1. static int __add_to_page_cache_locked(struct page *page,
  2.                       struct address_space *mapping,
  3.                       pgoff_t offset, gfp_t gfp_mask,
  4.                       void **shadowp)
  5. {
  6.     XA_STATE(xas, &mapping->i_pages, offset);
  7.     .........
  8.     //page引用计数加1
  9.     get_page(page);
  10.     page->mapping = mapping;
  11.     page->index = offset;
  12.     .........
  13.     old = xas_load(&xas);
  14.     xas_store(&xas, page);
  15.     mapping->nrpages++;
  16.     .........
  17. }

然后执行lru_cache_add函数page添加到lru缓存时令page引用计数加1pagelru缓存移动到lru链表时再令page引用计数减1,函数流程是lru_cache_add->__lru_cache_add->__pagevec_lru_add->release_pages,关键函数如下:

把page添加到lru缓存时令page引用计数加1

  1. static void __lru_cache_add(struct page *page)
  2. {
  3.     struct pagevec *pvec = &get_cpu_var(lru_add_pvec);
  4.     //page引用计数加1
  5.     get_page(page);
  6.     if (!pagevec_add(pvec, page) || PageCompound(page))
  7.         __pagevec_lru_add(pvec);
  8.     put_cpu_var(lru_add_pvec);
  9. }
  10. //page添加到lru缓存
  11. static inline unsigned pagevec_add(struct pagevec *pvec, struct page *page)
  12. {
  13.     pvec->pages[pvec->nr++] = page;
  14.     return pagevec_space(pvec);
  15. }

接着把page添加到lru链表时令page引用计数减1

  1. void __pagevec_lru_add(struct pagevec *pvec)
  2. {
  3.     pagevec_lru_move_fn(pvec, __pagevec_lru_add_fn, NULL);
  4. }
  5. static void __pagevec_lru_add_fn(struct page *page, struct lruvec *lruvec,
  6.                  void *arg)
  7. {
  8.     SetPageLRU(page);
  9.     //page添加到lru链表
  10.     add_page_to_lru_list(page, lruvec, lru);
  11. }
  12. void release_pages(struct page **pages, int nr)
  13. {
  14.     for (i = 0; i < nr; i++) {
  15.         struct page *page = pages[i];
  16.         //page引用计数减1。如果之后page引用计数是0说明没有进程使用该page了,然后执行free_unref_page_list()page释放回伙伴系统。
  17.         if (!put_page_testzero(page))
  18.             continue;
  19.         .........  
  20.         list_add(&page->lru, &pages_to_free);
  21.     }
  22.     free_unref_page_list(&pages_to_free);
  23. }

如果是write系统调用对文件页page有写操作,则还要为page分配buffer_head(即bh),然后建立文件页page和bh的联系,令page引用计数加1。源码流程如下(以ext4文件系统为例):vfs_write->new_sync_write->ext4_file_write_iter->__generic_file_write_iter->generic_perform_write->ext4_da_write_begin->__block_write_begin_int->create_page_buffers->create_empty_buffers->attach_page_buffers,

  1. static inline void attach_page_buffers(struct page *page,
  2.         struct buffer_head *head)
  3. {
  4.     //page引用计数加1
  5.     get_page(page);
  6.     //标记pageprivate属性
  7.     SetPagePrivate(page);
  8.     //建立pagebh的联系,本质是page->private=bh
  9.     set_page_private(page, (unsigned long)head);
  10. }

OK,此时page的引用计数是2。接着来到page的内存回收,执行shrink_inactive_list()函数,这里把该函数的关键源码列下:

  1. static unsigned long shrink_inactive_list(unsigned long nr_to_scan, struct lruvec *lruvec,
  2.              struct scan_control *sc, enum lru_list lru)
  3. {
  4.     spin_lock_irq(&pgdat->lru_lock);
  5.     //根据nr_to_scan数目从inactive lru链表隔离page符合条件的pagepage_list链表,同时都令这些page的引用计数加1
  6.     nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &page_list,&nr_scanned, sc, isolate_mode, lru);
  7.     spin_unlock_irq(&pgdat->lru_lock);
  8.     ..........
  9.     nr_reclaimed = shrink_page_list(&page_list, pgdat, sc, 0,&stat, false);
  10.     ..........
  11.     spin_lock_irq(&pgdat->lru_lock);
  12.     //没有成功内存回收的page再移动回 active/inactive lru链表,page引用计数减1。如果page引用计数是0说明没人用了,再移动回page_list
  13.     putback_inactive_pages(lruvec, &page_list);
  14.     spin_unlock_irq(&pgdat->lru_lock);
  15.     ..........
  16.     //释放page_list上引用计数是0page
  17.     free_unref_page_list(&page_list);
  18. }

看下隔离page执行的isolate_lru_pages函数,主要是page引用计数加1

  1. static unsigned long isolate_lru_pages(unsigned long nr_to_scan,
  2.         struct lruvec *lruvec, struct list_head *dst,
  3.         unsigned long *nr_scanned, struct scan_control *sc,
  4.         isolate_mode_t mode, enum lru_list lru)
  5. {
  6.         //page符合内存回收条件则清理pagePageLRU属性,并令page引用计数加1,返回0,否则返回负数
  7.         switch (__isolate_lru_page(page, mode)) {
  8.         case 0:
  9.             ........
  10.             //把符合内存回收条件的pagelru链表移动到dst临时链表
  11.             list_move(&page->lru, dst);
  12.             break;
  13.         case -EBUSY:
  14.             list_move(&page->lru, src);
  15.             continue;
  16.         }
  17. }
  18. int __isolate_lru_page(struct page *page, isolate_mode_t mode)
  19. {
  20.     int ret = -EINVAL;
  21.     //关键点,如果page已经从lru链条剔除,page隔离失败
  22.     if (!PageLRU(page))
  23.         return ret;
  24.     ret = -EBUSY;
  25.     //page引用计数不是0则加1并返回true。否则说明page应用计数是0,返回false,这种page已经没进程在使用了,已经不在LRU链表了
  26.     if (likely(get_page_unless_zero(page))){
  27.         //page将要从activeinactive lru链表移除,于是清理pagePageLRU属性
  28.         ClearPageLRU(page);
  29.         ret = 0;
  30.     }
  31.     return ret;
  32. }

注意,隔离page时,在对spin_locklru_lock加锁后,要令page引用计数加1,这个非常重要。此时其他进程就无法释放这个page了!如果在隔离page前,这个page可能被其他进程释放回伙伴系统,那page将没有LRU属性,此时__isolate_lru_page函数里的if (!PageLRU(page))将起到作用,导致隔离page失败。如果隔离page时没有对page引用计数加1,那page将可能并发被其他进程释放回伙伴系统,或者被释放回伙伴系统并且被新的进程分配并加入新的lru链表。这种情况下,page->mapping将发生变化,与原始的mapping就不一样了,可以据此判断出这种异常。

好的,page引用计数此时是3,接着来到shrink_page_list()函数对page进行真正的内存回收。

  1. static unsigned long shrink_page_list(struct list_head *page_list,
  2.                       struct pglist_data *pgdat,
  3.                       struct scan_control *sc,
  4.                       enum ttu_flags ttu_flags,
  5.                       struct reclaim_stat *stat,
  6.                       bool force_reclaim)
  7. {
  8.     while (!list_empty(page_list)) {
  9.         ............
  10.         //page有映射的bh
  11.         if (page_has_private(page)) {
  12.             //pagebh解除联系,并且令page引用计数减1
  13.             if (!try_to_release_page(page, sc->gfp_mask))
  14.                 goto activate_locked;
  15.         }
  16.         ...........
  17.         //pageradix treeaddress_space 剔除,如果page引用计数是2则清0,返回1page可以释放。否则page还再被其他进程使用,返回0,不能释放
  18.         else if (!mapping || !__remove_mapping(mapping, page, true))
  19.             goto keep_locked;
  20. free_it:
  21.         nr_reclaimed++;
  22.         list_add(&page->lru, &free_pages);
  23.         continue;
  24. activate_locked:
  25.         //重新设置page active
  26.         SetPageActive(page);
  27. keep_locked:
  28.         unlock_page(page);
  29. keep:
  30.         //到这里,page本轮不能回收,暂存ret_pages链表然后再移回activeinactive lru链表
  31.         list_add(&page->lru, &ret_pages);
  32.     }
  33.     //释放free_pages上的page到伙伴系统
  34.     free_unref_page_list(&free_pages);
  35.     ...............
  36. }

如果page有bh则if (page_has_private(page))成立,然后执行try_to_release_page解除page和bh的联系,并page引用计数减1,源码流程是try_to_release_page->ext4_releasepage->try_to_free_buffers->drop_buffers->__clear_page_buffers,

  1. static void __clear_page_buffers(struct page *page)
  2. {
  3.     //解除pagebh的联系
  4.     ClearPagePrivate(page);
  5.     set_page_private(page, 0);
  6.     //page引用计数减1
  7.     put_page(page);
  8. }

此时page的引用计数是2,然后执行到__remove_mapping()函数。

  1. static int __remove_mapping(struct address_space *mapping, struct page *page,
  2.                 bool reclaimed)
  3. {
  4.     refcount = 2;
  5.     //page引用计数是2则对page引用计数清0,并返回true,这个page可以释放了。否则page引用计数不是2则保持引用计数并返回false,这个page不能释放
  6.     if (!page_ref_freeze(page, refcount))
  7.         goto cannot_free;
  8.     .............
  9.     //pageradix tree 剔除
  10.     __delete_from_page_cache(page, shadow);
  11.     return 1;
  12. cannot_free:
  13.     return 0;  
  14. }

主要作用是:把page从radix tree、address_space 剔除,如果page引用计数是2则清0,返回1page可以释放。否则page还再被其他进程使用,返回0,不能释放

好的,正常情况page引用计数此时就是0了,然后就可以释放掉这个page了。如果page因为是脏页、writeback页等导致page回收失败,page就要暂存在page_list链表。shrink_page_list()函数执行后,再执行putback_inactive_pages()函数把page移动回lru链表,源码如下:

  1. static void putback_inactive_pages(struct lruvec *lruvec, struct list_head *page_list)
  2. {
  3.     struct zone_reclaim_stat *reclaim_stat = &lruvec->reclaim_stat;
  4.     struct pglist_data *pgdat = lruvec_pgdat(lruvec);
  5.     LIST_HEAD(pages_to_free);
  6.     while (!list_empty(page_list)) {
  7.         struct page *page = lru_to_page(page_list);
  8.         int lru;
  9.         lruvec = mem_cgroup_page_lruvec(page, pgdat);
  10.         //page要添加到inactive lru,设置LRU属性
  11.         SetPageLRU(page);
  12.         lru = page_lru(page);
  13.         //page添加到lru链表,并增加lru链表page
  14.         add_page_to_lru_list(page, lruvec, lru);
  15.         //page引用计数减1,减1后如果是0就说明page没人用了,可以释放了
  16.         if (put_page_testzero(page)) {
  17.             //清理pagelRUactive属性
  18.             __ClearPageLRU(page);
  19.             __ClearPageActive(page);
  20.             //pagelru链表剔除,并减少lru链表的page
  21.             del_page_from_lru_list(page, lruvec, lru);
  22.             //page再移动到pages_to_free链表,之后就直接释放掉
  23.             list_add(&page->lru, &pages_to_free);
  24.     }
  25.     list_splice(&pages_to_free, page_list);
  26. }

这里对page引用计数减1,因为之前隔离收该page时令page引用计数加1了,二者对冲掉。

OK,本文到这里基本就结束了。page引用计数可能有点复杂,简单说,当page要启用一个新功能时,就要对page引用计数加1,而回收page时要一一对应对page引用计数减1。水平有限,如有错误请指出。

相关文章:

内核内存回收关键隐藏变量之page引用计数

在分析内核内存回收源码时&#xff0c;page引用计数并不显眼&#xff0c;但是page引用计数对page的内存回收至关重要。本文基于linux-4.18.0-240版本内核源码&#xff0c;总结下文件页page的引用计数的相关细节。首先是get_page()和put_page()函数&#xff0c;分别令page引用计…...

数据结构---链表的基本操作

头插法遍历链表尾插法头删法尾删法按位置插入数据按位置删除数据直接插入排序 链表翻转快慢指针 linklist.c #include <stdio.h> #include <stdlib.h> #include "./linklist.h"linklist* create_linklist(void) {linklist* head (linklist*)malloc(siz…...

异步框架Celery在Django中的运用

参考博客&#xff1a;https://www.cnblogs.com/pyedu/p/12461819.html 参考视频&#xff1a;01 celery的工作机制_哔哩哔哩_bilibili 定义&#xff1a;简单灵活、处理大量消息的分布式系统&#xff0c;专注于实时处理异步队列&#xff0c;支持任务调度 主要架构&#xff1a; …...

YOLOv5代码解读[02] models/yolov5l.yaml文件解析

文章目录 YOLOv5代码解读[02] models/yolov5l.yaml文件解析yolov5l.yaml文件检测头1--->耦合头检测头2--->解耦头检测头3--->ASFF检测头Model类解析parse_model函数 YOLOv5代码解读[02] models/yolov5l.yaml文件解析 yolov5l.yaml文件 # YOLOv5 &#x1f680; by Ult…...

智能搬运机器人|海格里斯将如何持续推进工业和物流的智能化升级与发展?

存取、搬运、分拣是物流行业中的通用功能&#xff0c;但具体到每个行业又十分不同&#xff0c;例如&#xff1a;新能源电池领域&#xff0c;它所搬运的东西是电池&#xff0c;50KG~200KG&#xff1b;快递行业领域&#xff0c;所要处理的物料是那种扁平件和信封等等&#xff0c;…...

linux之前后端项目部署与发布

目录 前言 简介 一、安装Nginx 二、后端部署 2.1多个tomcat负载均衡 2.2 负载均衡 2.3 后端项目部署 三、前端部署 1.解压前端 2.Nginx配置文件修改 3.IP域名映射 4.重启Nginx服务 前言 上篇博主已经讲解过了单机项目的部署linux之JAVA环境配置JDK&Tomcat&a…...

Python 高级语法:一切皆对象

1 “一切皆对象”是一种核心设计哲学 在编程领域&#xff0c;特别是面向对象编程&#xff08;OOP&#xff09;中&#xff0c;“一切皆对象”是一种核心设计哲学。这种哲学主张&#xff0c;无论是数据、函数、还是更复杂的结构&#xff0c;都可以被视为对象&#xff0c;并赋予…...

python jupyter notebook打开页面方便使用

如果没安装jupyter, 请安装&#xff1a; pip install jupyter notebook 运行jupyter notebook jupyter-notebook...

音视频开发之旅(69)-SD图生图

目录 1. 效果展示 2. ControlNet介绍 3. 图生图流程浅析 4. SDWebui图生图代码流程 5. 参考资料 一、效果展示 图生图的应用场景非常多&#xff0c;比较典型的应用场景有风格转化&#xff08;真人与二次元&#xff09;、线稿上色、换装和对图片进行扩图等&#xff0c;下面…...

數據集成平台:datax將hive數據步到mysql(全部列和指定列)

數據集成平台&#xff1a;datax將hive數據步到mysql&#xff08;全部列和指定列&#xff09; 1.py腳本 傳入參數&#xff1a; target_database&#xff1a;數據庫 target_table&#xff1a;表 target_columns&#xff1a;列 target_positions&#xff1a;hive列的下標&#x…...

pikachu靶场-File Inclusion

介绍&#xff1a; File Inclusion(文件包含漏洞)概述 文件包含&#xff0c;是一个功能。在各种开发语言中都提供了内置的文件包含函数&#xff0c;其可以使开发人员在一个代码文件中直接包含&#xff08;引入&#xff09;另外一个代码文件。 比如 在PHP中&#xff0c;提供了&…...

[今天跟AI聊聊职场] ~你能接受你的直接领导能力不如你,年纪还比你小很多吗?

知乎问题&#xff1a; 弟弟今年35岁&#xff0c;刚换了一份工作&#xff0c;直接领导小A比他小5岁&#xff0c;各方面经验没有他成熟。难的工作都是弟弟在做&#xff0c;功劳都被直接领导小A抢走了&#xff0c;有时候还要被直接领导小A打压。弟弟感觉升职加薪无望。现在找工作不…...

网络原理TCP之“三次握手“

TCP内核中的建立连接 众所周知,TCP是有连接的. 当我们在客户端敲出socket new Socket(serverIp,severPort)时,就在系统内核就在建立连接 真正建立连接是在系统内核中建立的,我们程序员只是调用相关的api. 在此处,我们把TCP的建立连接称为三次握手. 系统在内核建立连接时如上…...

990-03产品经理与程序员:什么是 IT 与业务协调以及为什么它很重要?

What is IT-business alignment and why is it important? 什么是IT-业务一致性&#xff1f;为什么它很重要&#xff1f; It’s more important than ever that IT and the business operate from the same playbook(剧本). So why do so many organizations struggle to ach…...

Java Web(七)__Tomcat(二)

Tomcat工作模式 Tomcat作为Servlet容器&#xff0c;有以下三种工作模式。 1&#xff09;独立的Servlet容器&#xff0c;由Java虚拟机进程来运行 Tomcat作为独立的Web服务器来单独运行&#xff0c;Servlet容器组件作为Web服务器中的一部分而存在。这是Tomcat的默认工作模式。…...

【项目实战】帮美女老师做一个点名小程序(Python tkinter)

前言 博主有一个非常漂亮的老师朋友&#x1f60d;。最近&#xff0c;她急需一个能够实现随机点名的小程序&#xff0c;而博主正好擅长这方面的技术&#x1f90f;。所以&#xff0c;今天博主决定为她制作一个专门用于点名的小程序&#x1f4aa;。 博主在美女老师面前吹完牛皮之…...

Elasticsearch 去重后求和

标题的要求可以用如下 SQL 表示 select sum(column2) from (select distinct(column1),column2 from table)t 要如何用 DSL 实现呢&#xff0c;先准备下索引和数据 PUT test_index {"mappings": {"properties": {"column1": {"type"…...

考研数学——高数:函数与极限(3)

函数的连续性与间断点 函数的连续性 左连续 右连续 区间上的连续性 在xo处连续 函数的间断点 第一类间断点(左右极限都存在) 可去间断点: f(xo-0)= f(xo+0) 跳跃间断点: f(xo-0)≠ f(xo+0) 第二类间断点(震荡间断点、无穷间断点)...

LeetCode49 字母异位词分组

LeetCode49 字母异位词分组 在这篇博客中&#xff0c;我们将探讨 LeetCode 上的一道经典算法问题&#xff1a;字母异位词分组。这个问题要求将给定的字符串数组中的字母异位词组合在一起&#xff0c;并以任意顺序返回结果列表。 问题描述 给定一个字符串数组 strs&#xff0…...

【Python】Windows本地映射远程Linux服务器上的端口(解决jupyter notebook无法启动问题)

创作日志&#xff1a; 学习深度学习不想在本地破电脑上再安装各种软件&#xff0c;我就用实验室的服务器配置环境&#xff0c;启动jupyter notebook时脑子又瓦特了&#xff0c;在自己Windows电脑上打开服务器提供的网址&#xff0c;那肯定打不开啊&#xff0c;以前在其它电脑上…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

Unity UGUI Button事件流程

场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...

CppCon 2015 学习:Time Programming Fundamentals

Civil Time 公历时间 特点&#xff1a; 共 6 个字段&#xff1a; Year&#xff08;年&#xff09;Month&#xff08;月&#xff09;Day&#xff08;日&#xff09;Hour&#xff08;小时&#xff09;Minute&#xff08;分钟&#xff09;Second&#xff08;秒&#xff09; 表示…...