Linux信号机制
目录
一、信号的概念
二、定时器
1. alarm函数
2. setitimer函数
3.signal和sigaction函数
三、使用SIGCHLD信号实现回收子进程
一、信号的概念
概念:信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式 。所有信号的产生及处理全部都是由内核完成的。
信号处理方式:
1 缺省方式
2 忽略信号
3 捕捉信号
在信号处理中,通常有三种处理方式:
-
缺省方式(Default):这是操作系统针对每种信号定义的默认行为。对于大多数信号,缺省行为是终止进程。例如,
SIGINT
的缺省行为是终止进程。 -
忽略信号(Ignore):这种方式下,进程对收到的信号不做任何响应。这意味着当进程收到指定信号时,不会采取任何动作。这通常用于某些不需要处理的信号,或者是在某些特定情况下临时禁用信号处理器。
-
捕捉信号(Catch):这种方式下,进程定义一个信号处理函数,用于处理特定信号。当进程收到指定信号时,会调用这个信号处理函数。这允许程序员自定义对信号的处理方式,例如,收到
SIGINT
时执行特定的操作而不是终止进程。
在如下代码中使用了第三种方式,即捕捉信号。通过调用 signal(SIGINT, handle)
,定义了一个信号处理函数 handle
,用于处理 SIGINT
信号。当程序收到 SIGINT
信号时,会调用 handle
函数来处理它,而不是按照默认的方式终止进程。
注意typedef void (*sighandler_t)(int);不能少
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>typedef void (*sighandler_t)(int);
sighandler_t oldact;void handle(int sig)
{printf("I cath the SIGINT\n");signal(SIGINT,oldact);
}int main()
{oldact = signal(SIGINT,handle);while(1){sleep(1);}return 0;
}
代码注释:
使用 signal 函数来设置信号处理函数,并捕获 SIGINT 信号(通常由键盘上的Ctrl+C组合键发送)。这段代码的功能是在收到 SIGINT 信号时打印一条消息,并重新设置 SIGINT 信号的处理函数为先前的处理函数。下面是代码的简要解释:signal(SIGINT, handle):这行代码设置了 SIGINT 信号的处理函数为 handle。
在收到 SIGINT 信号时,系统将调用 handle 函数来处理该信号。handle 函数:这是 SIGINT 信号的处理函数。当程序收到 SIGINT 信号时,会调用这个函数,并打印一条消息 "I catch the SIGINT"。
然后,它将信号的处理函数重新设置为 oldact,这样可以恢复先前的信号处理函数。oldact:这是一个全局变量,用于保存先前 SIGINT 信号的处理函数。while(1) 循环:这是一个无限循环,程序在这里持续运行,每次循环睡眠1秒钟。
为什么“它将信号的处理函数重新设置为 oldact,这样可以恢复先前的信号处理函数”?
先前的处理函数,在这里就是系统的默认状态,即CTRL+C可以终止信号。
在这段代码中,oldact 保存了先前 SIGINT 信号的处理函数。
当程序收到 SIGINT 信号时,handle 函数会被调用。
在 handle 函数中,执行了 signal(SIGINT, oldact),这样做的目的是将 SIGINT 信号的处理函数重新设置为先前保存的处理函数 oldact。这样做的原因是为了确保在 handle 函数执行完毕后,再次收到 SIGINT 信号时,会调用先前的处理函数,而不是再次调用 handle 函数。
这样可以恢复程序在接收 SIGINT 信号时的默认行为,或者是由用户自定义的其他行为。
通过在调用 signal 函数时将 oldact 作为第二个参数传递进去,可以获取先前的信号处理函数。然后,你可以在需要的时候使用这个指针来重新设置信号处理函数,从而恢复到先前的处理方式。
在 signal 函数中,oldact 是一个指向先前信号处理函数的指针。signal 函数的原型如下:void (*signal(int signum, void (*handler)(int)))(int);它返回了一个指向先前信号处理函数的指针。
这个指针的类型是 sighandler_t,它是一个函数指针类型,它指向一个接受 int 参数并返回 void 的函数。
-
当程序启动时,通常会有一些默认的信号处理方式。例如,对于
SIGINT
信号(通常由用户按下 Ctrl+C 发送),默认情况下会终止程序。 -
当你调用
signal(SIGINT, handle)
时,你指定了一个自定义的信号处理函数handle
,用于处理SIGINT
信号。在这之前,如果有其他函数被注册为SIGINT
信号的处理函数,那么signal
函数会返回这个先前的处理函数,并将其保存在oldact
变量中。 -
之后,如果你想要恢复先前的处理方式,你可以调用
signal(SIGINT, oldact)
,这样SIGINT
信号会再次被先前的处理函数处理,而不是你自定义的handle
函数。
常用信号:
信号名 | 含义 | 默认操作 |
SIGKILL | 该信号用来结束进程,并且不能被捕捉和忽略 | 终止 |
SIGSTOP | 该信号用于暂停进程,并且不能被捕捉和忽略 | 暂停进程 |
SIGTSTP | 该信号用于暂停进程,用户可键入SUSP字符( 通常是Ctrl-Z)发出这个信号 | 暂停进程 |
SIGCONT | 该信号让进程进入运行态 | 继续运行 |
SIGALRM | 该信号用于通知进程定时器时间已到 | 终止 |
SIGUSR1/2 | 该信号保留给用户程序使用 | 终止 |
SIGCHLD | 是子进程状态改变发给父进程的。 | 忽略 |
信号名 | 含义 | 默认操作 |
SIGHUP | 该信号在用户终端关闭时产生,通常是发给和该 终端关联的会话内的所有进程 | 终止 |
SIGINT | 该信号在用户键入INTR字符(Ctrl-C)时产生,内 核发送此信号送到当前终端的所有前台进程 | 终止 |
SIGQUIT | 该信号和SIGINT类似,但由QUIT字符(通常是 Ctrl-\)来产生 | 终止 |
SIGILL | 该信号在一个进程企图执行一条非法指令时产生 | 终止 |
SIGSEV | 该信号在非法访问内存时产生,如野指针、缓 冲区溢出 | 终止 |
SIGPIPE | 当进程往一个没有读端的管道中写入时产生,代 表“管道断裂” | 终止 |
二、定时器
1. alarm函数
alarm
函数的原型如下:
unsigned int alarm(unsigned int seconds);
它接受一个无符号整数参数 seconds
,表示定时器的超时时间,单位是秒。调用 alarm
函数会设置一个定时器,在指定的秒数之后,会产生 SIGALRM
信号。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void alarm_handler(int signum)
{printf("Alarm signal received!\n");}int main()
{signal(SIGALRM, alarm_handler);alarm(5);printf("Waiting for alarm...\n");while (1) {sleep(1);}return 0;
}
alarm(5)
函数调用设置了一个5秒的定时器。但是,在程序调用 alarm(5)
设置定时器之后,程序会立即继续执行下一条语句,而不会等待5秒钟。这意味着在调用 alarm(5)
之后,立即执行了 printf("Waiting for alarm...\n");
这行代码,所以会立即打印出 "Waiting for alarm..."。然后,程序会进入 while
循环,在那里它会等待5秒钟,直到定时器超时并产生 SIGALRM
信号。
因此,程序执行的步骤是这样的:
- 执行
alarm(5);
设置一代码个5秒的定时器。 - 立即执行
printf("Waiting for alarm...\n");
,打印 "Waiting for alarm..."。 - 进入
while
循环,程序会等待5秒钟。 - 定时器超时,产生
SIGALRM
信号,调用alarm_handler
函数。 - 打印 "Alarm signal received!\n"。
- 程序继续执行
while
循环中的其他代码。
2. setitimer函数
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
功能:定时的发送alarm信号参数:which: ITIMER_REAL:以逝去时间递减。发送SIGALRM信号、ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送SIGVTALRM信号new_value: 负责设定 timout 时间 old_value: 存放旧的timeout值,一般指定为NULLstruct itimerval{struct timeval it_interval; // 闹钟触发周期struct timeval it_value; // 闹钟触发时间};struct timeval{time_t tv_sec; /* seconds */suseconds_t tv_usec; /* microseconds */};
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>void timer_handler(int signum)
{printf("Timer expired!\n");
}int main()
{struct itimerval timer;struct sigaction act;act.sa_handler = timer_handler;act.sa_flags = 0;sigemptyset(&act.sa_mask);timer.it_value.tv_sec = 5; // 5秒后定时器启动timer.it_value.tv_usec = 0;timer.it_interval.tv_sec = 1; // 间隔1秒timer.it_interval.tv_usec = 0;sigaction(SIGALRM, &act,NULL);if (setitimer(ITIMER_REAL, &timer, NULL) == -1){perror("setitimer");exit(EXIT_FAILURE);}while (1){sleep(1);}return 0;
}
定义了一个信号处理函数 timer_handler,用于在定时器到期时打印 "Timer expired!"。创建了一个 struct itimerval 结构体变量 timer,并设置了定时器的参数:it_value 成员设置为 5 秒,表示定时器将在 5 秒后启动。
it_interval 成员设置为 1 秒,表示定时器在启动后每隔 1 秒触发一次。
创建了一个 struct sigaction 结构体变量 act,并设置了其中的成员:sa_handler 成员设置为 timer_handler 函数,表示 SIGALRM 信号的处理函数为 timer_handler。
sa_flags 设置为 0,表示不设置特殊标志。
sa_mask 使用 sigemptyset 函数清空,表示在执行 timer_handler 函数期间不阻塞任何信号。
使用 sigaction 函数将 SIGALRM 信号的处理函数设置为 timer_handler 函数。使用 setitimer 函数设置实时定时器,并传入 timer 结构体,启动定时器。进入一个无限循环,程序将持续运行,每隔 1 秒调用 sleep(1) 函数,等待定时器的触发。总的来说,这段代码实现了一个定时器,在程序启动 5 秒后启动,并且每隔 1 秒触发一次定时器,在定时器触发时打印一条消息。
3.signal和sigaction函数
signal函数:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:捕捉信号执行自定义函数
返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR
参数:signo 要设置的信号类型handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号; 系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。
sigaction函数:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
}
参数:
signum:处理的信号
act,oldact: 处理信号的新行为和旧的行为,是一个sigaction结构体。sigaction结构体成员定义如下:
sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似
sa_sigaction: 另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
re_restorer:是一个已经废弃的数据域
三、使用SIGCHLD信号实现回收子进程
SIGCHLD的产生条件
1子进程终止时
2子进程接收到SIGSTOP信号停止时
3子进程处在停止态,接受到SIGCONT后唤醒时
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
#include<string.h>
#include<errno.h>void handle(int sig)
{wait(NULL);printf("GET sig = %d\n",sig);
}
int main()
{pid_t pid;struct sigaction act;act.sa_handler = handle;act.sa_flags = 0;sigemptyset(&act.sa_mask);pid = fork();if(pid > 0){//wait(NULL);sigaction(SIGCHLD,&act,NULL);while(1){printf("this is father process\n");sleep(1);}}else if(pid == 0){sleep(5);exit(0);}else{perror("fork");return 0;}
}
这段代码创建了一个父子进程,并使用 wait 函数等待子进程结束。
在父进程中,设置了 SIGCHLD 信号的处理函数为 handle 函数,用于处理子进程终止的信号。
具体来说,父进程会周期性地打印一条消息,而子进程在启动后会等待 5 秒后自行退出。下面是这段代码的执行逻辑:程序开始执行,父进程调用 fork 创建了一个子进程。父进程中,如果 fork 成功(返回大于 0 的值),则进入了一个无限循环,不断地打印 "this is father process",并在其中使用 sigaction 函数设置了 SIGCHLD 信号的处理函数为 handle 函数。子进程中,如果 fork 成功(返回 0),则进入了 if (pid == 0) 的分支,子进程会休眠 5 秒后自行退出。如果 fork 出错(返回小于 0 的值),则父进程中输出错误信息并退出。当子进程结束时,父进程会收到 SIGCHLD 信号,进而调用 handle 函数来处理。在 handle 函数中,调用 wait 函数等待子进程结束,并打印 "GET sig = xx" 的信息,其中 xx 是接收到的信号值。总的来说,这段代码通过信号处理机制实现了在父进程中对子进程的终止进行处理。
wait(NULL)
当一个父进程调用 `wait(NULL)` 时,它会发生以下事情:
1. **阻塞父进程**:如果没有任何子进程已经结束,`wait(NULL)` 会阻塞父进程,直到至少有一个子进程结束。
2. **回收子进程**:一旦有子进程结束,`wait(NULL)` 会回收该子进程的资源。这意味着操作系统会清理与该子进程相关的所有资源,比如内存和进程控制块。
3. **不指定子进程**:由于 `wait(NULL)` 不指定等待特定的子进程,它适用于等待任何一个子进程。如果需要等待特定的子进程,可以使用 `waitpid(pid, &status, options)` 函数,其中 `pid` 是特定子进程的进程号。
linux wait(null)函数_wait(null)-CSDN博客https://blog.csdn.net/weixin_44652882/article/details/134363936
相关文章:

Linux信号机制
目录 一、信号的概念 二、定时器 1. alarm函数 2. setitimer函数 3.signal和sigaction函数 三、使用SIGCHLD信号实现回收子进程 一、信号的概念 概念:信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式 。所有信号的产生及处理全部都是由内…...

区块链技术中的共识机制算法:以权益证明(PoS)为例
引言: 在区块链技术的演进过程中,共识机制算法扮演着至关重要的角色。除了广为人知的工作量证明(PoW)外,权益证明(Proof of Stake,PoS)也是近年来备受关注的一种共识算法。 …...

19113133262(微信同号)【征稿进行时|见刊、检索快速稳定】2024年区块链、物联网与复合材料与国际学术会议 (ICBITC 2024)
【征稿进行时|见刊、检索快速稳定】2024年区块链、物联网与复合材料与国际学术会议 (ICBITC 2024) 大会主题: (主题包括但不限于, 更多主题请咨询会务组苏老师) 区块链: 区块链技术和系统 分布式一致性算法和协议 块链性能 信息储存系统 区块链可扩展性 区块…...

Doris:使用表函数explode实现array字段列转行
文章目录 使用场景相关知识点介绍explodesplit_by_stringlateral view 具体实现和SQLlateral view explode列转行SPLIT_BY_STRING拆分字符串为数组element_at获取数据创建视图 使用场景 我们的大数据数据库,由clickhouse换成了doris我们有一张路口指标表࿰…...

原生php单元测试示例
下载phpunit.phar https://phpunit.de/getting-started/phpunit-9.html 官网 然后win点击这里下载 新建目录 这里目录可以作为参考,然后放在根目录下 新建一个示例类 <?phpdeclare(strict_types1);namespace Hjj\DesignPatterns\Creational\Hello;class He…...

计算机毕业设计-springboot+vue前后端分离电竞社交平台管理系统部分成果分享
4.5系统结构设计 本系统使用的角色主要有系统管理员、顾客、接单员,本系统为后台管理系统,游客用户可以经过账号注册,管理员审核通过后,用账号密码登录系统,查看后台首页,模块管理(顾客信息&am…...

Stable Diffusion 详解
整体目标 文本生成图片;文本图片生成图片 网络结构 CLIP的文本编码器和图片生成器组成图像生成器,输入是噪声经过UNet得到图像特征,最后解码得到图像 前向扩散 模型直接预测图片难度比较大,所有让模型预测噪音然后输入-噪音…...

Go函数全景:从基础到高阶的深度探索
目录 一、Go函数基础1.1 函数定义和声明基础函数结构返回值类型和命名返回值 1.2 参数传递方式值传递引用传递 二、Go特殊函数类型2.1 变参函数定义和使用变参变参的限制 2.2 匿名函数与Lambda表达式何为匿名函数Lambda表达式的使用场景 2.3 延迟调用函数(defer&…...

探秘Nutch:揭秘开源搜索引擎的工作原理与无限应用可能(一)
本系列文章简介: 本系列文章将带领大家深入探索Nutch的世界,从其基本概念和架构开始,逐步深入到爬虫、索引和查询等关键环节。通过了解Nutch的工作原理,大家将能够更好地理解搜索引擎背后的原理,并有能力利用Nutch构建…...

MySQL 数据库 下载地址 国内阿里云站点
mysql安装包下载_开源镜像站-阿里云 以 MySQL 5.7 为例 mysql-MySQL-5.7安装包下载_开源镜像站-阿里云...

【25届秋招备战C++】算法篇-贪心算法(Greedy)
【25届秋招备战C】算法篇-贪心算法 一、简介二、解题思路三、应用场景四、模板函数五、参考 一、简介 一种在每次决策时,总是采取在当前状态下的最好选择,从而希望导致结果是最好或最优的算法。通常用于解决一些最优化问题,如找零问题、霍夫…...

scrcpy远程投屏控制Android
下载 下载后解压压缩包scrcpy-win64-v2.4.zip scrcpy连接手机 1. 有线连接 - 手机开启开发者选项,并开启USB调试,连接电脑,华为手机示例解压scrcpy,在scrcpy目录下打开终端,(或添加scrcpy路径为环境变…...

找机厅 洛谷 BFS
P10234 [yLCPC2024] B. 找机厅 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<bits/stdc.h> #define pii pair<int,int> #define fr first #define sc second using namespace std; string maze[2000]; int vis[2000][2000]; char dirs[2005][2005]; st…...

软件无线电系列——模拟无线电、数字无线电、软件无线电
本节目录 一、模拟无线电 二、数字无线电 1、窄带数字无线电 2、宽带数字无线电 三、软件无线电本节内容 一、模拟无线电 20世纪80年代的模拟体制(美国的AMPS/欧洲的TACS)被称为第一代移动通信,简称1G,主要目标是为在大范围内有限的用户提供移动电话服务。最主要的…...

XSS_lab(level11-level18)
level11: 还是url这里,输入:<script>alert(1)</script> 与上一题相似 构建:?t_link1&t_history2&t_sort3&t_ref4 我们发现t_sort是可用的 构建:?t_sort1" type"button" οnclickalert(1) // 把双引号过滤了 这里无法使用实体编码…...

【git】常用操作
基础操作 git init 初始化仓库 要使用 Git 进行版本管理,必须先初始化仓库, 执行了 git init命令的目录下就会生成 .git 目录。这个 .git 目录里存储着管理当前目录内容所需的仓库数据 git status 查看仓库状态 工作树和仓库在被操作的过程中࿰…...

蓝桥杯第十一届电子类单片机组程序设计
目录 前言 单片机资源数据包_2023(点击下载) 一、第十一届比赛原题 1.比赛题目 2.赛题解读 1)计数功能 2)连续按下无效按键 二、部分功能实现 1.计数功能的实现 2.连续按下无效按键的处理 3.其他处理 1)对于…...

Java中文乱码问题解析与解决方案
在日常工作中,我们经常会遇到中文乱码的问题。乱码问题不仅影响用户体验,还可能导致数据丢失或解析错误。因此,了解和掌握中文乱码问题的原因和解决方案,对于Java开发者来说至关重要。本文将分析常见的Java中文乱码场景࿰…...

AIGC笔记--Maya提取和修改FBX动作文件
目录 1--Maya数据解析 2--FBX SDK导出6D数据 3--6D数据映射和Maya可视化 完整项目代码:Data-Processing/FBX_SDK_Maya 1--Maya数据解析 在软件Maya中直接拖入FBX文件,可以播放和查看人体各个骨骼关节点的数据: 对于上图来说,…...

【刷题训练】LeetCode125. 验证回文串
验证回文串 题目要求 示例 1: 输入: s “A man, a plan, a canal: Panama” 输出:true 解释:“amanaplanacanalpanama” 是回文串。 示例 2: 输入:s “race a car” 输出:false 解释:“rac…...

optee默认安全配置
OP-TEE(Open Portable Trusted Execution Environment)是一个开源的可移植的可信执行环境(TEE),用于提供安全和受保护的执行环境。它旨在为基于 ARM 架构的设备提供强大的安全性和隔离能力。 OP-TEE 主要由两部分组成…...

Arcgis新建位置分配求解最佳商店位置
背景 借用Arcgis帮助文档中的说明:在本练习中,您将为连锁零售店选择可以获得最大业务量的商店位置。主要目标是要将商店定位在人口集中地区附近,因为这种区域对商店的需求量较大。设立这一目标的前提是假设人们往往更多光顾附近的商店,而对于距离较远的商店则较少光顾。您…...

【C++初阶】C++入门(上)
C的认识 ①什么是C? C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。 于是1982年,Bjarne Stroustrup(本…...

Vue.js+SpringBoot开发校园疫情防控管理系统
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生2.2 老师2.3 学校管理部门 三、系统展示四、核心代码4.1 新增健康情况上报4.2 查询健康咨询4.3 新增离返校申请4.4 查询防疫物资4.5 查询防控宣传数据 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBoot…...

客服销冠偷偷用的提效神器!无广很实用
近期发现我的同事每天上班必登录的一款软件——客服宝聊天助手,用过才发现:真客服办公的提效神器!感兴趣的小伙伴请往下看~一、客服宝的简介:客服宝聊天助手,是一款跨平台快捷回复工具。自带多种功能,有效帮…...
蓝桥杯刷题|02入门真题
[蓝桥杯 2022 省 B] 刷题统计 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目,周六和周日每天做 b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n 题? 输入格式 输入一行包含三个整数…...

Jenkins cron定时构建触发器
from: https://www.jenkins.io/doc/book/pipeline/syntax/#cron-syntax 以下内容为根据Jenkins官方文档cron表达式部分翻译过来,使用机翻加个人理解补充内容,包括举例。 目录 介绍举例:设置方法方法一:方法二…...

【编程向导】JavaScript-创建对象一期讲解
工厂模式 工厂模式 是用来创建对象的一种最常用的设计模式。工厂模式不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。工厂模式常见于大型项目,例如 jQuery 的 $ 对象,我们创建选择器对…...

【MySQL性能优化】- 一文了解MVCC机制
MySQL理解MVCC 😄生命不息,写作不止 🔥 继续踏上学习之路,学之分享笔记 👊 总有一天我也能像各位大佬一样 🏆 博客首页 怒放吧德德 To记录领地 🌝分享学习心得,欢迎指正ÿ…...

性能测试-Redis
一、测试注意点 1、缓存预热 如果程序初次运行,此时由于数据尚未加载到缓存,则程序的响应时间会明显变长 注意事项: 性能测试的时候 出现 非常不稳定的现象程序刚启动,它的性能 明显 低于 已经运行一段时间的 1.1 测试缓存没…...