【Linux】万字解读<进程控制>:创建&中止&等待&替换

news2024/11/13 13:00:56

前言

大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过Linux的老铁
主要内容含:
在这里插入图片描述

欢迎订阅 YY滴C++专栏!更多干货持续更新!以下是传送门!

  • YY的《C++》专栏
  • YY的《C++11》专栏
  • YY的《Linux》专栏
  • YY的《数据结构》专栏
  • YY的《C语言基础》专栏
  • YY的《初学者易错点》专栏
  • YY的《小小知识点》专栏
  • YY的《单片机期末速过》专栏
  • YY的《C++期末速过》专栏
  • YY的《单片机》专栏
  • YY的《STM32》专栏
  • YY的《数据库》专栏
  • YY的《数据库原理》专栏

目录

  • 一.进程创建
    • 1.fork函数
      • 【1】fork函数与其返回值
      • 【2】fork函数中的写时拷贝
    • 2.fork函数/进程创建的场景——常规用法
  • 二.进程终止
    • 1.进程退出的场景
    • 2.进程应对不同退出场景的退出方法
      • 前置知识:echo $? 查看进程退出码
      • 前置知识:错误码VS退出码
      • 【1】正常退出————通过退出码判断
        • 1.exit函数 与 _exit函数
          • 验证是否自动刷新缓冲区
        • 2.从main返回——return退出——等同于正常退出的exit
      • 【2】异常退出(程序崩溃)——操作系统转换成信号——进程被操作系统杀掉
        • ※ctrl + c也能强制退出——信号终止
  • 三.进程等待
    • 1.进程等待基本介绍
    • 2.进程等待的必要性
    • 3.如何进行等待(wait&waitpid)
      • 【1】wait函数参数与返回值介绍&演示
      • 【2】waitpid函数参数与返回值介绍&演示
      • 【3】输出型参数-status的原理演示&status规则&现象演示(要点)
  • 四.进程程序替换
    • 1.进程替换概念
      • 【1】进程替换概念
      • 【2】进程替换的机制&情景演示
    • 2.进程替换用到的exec类函数解释&命名规则&使用演示
      • 【1】exec类函数
      • 【2】exec类函数命名规则
      • 【3】exec类函数使用演示
        • 1. execl
        • 2. execlp
        • 3. execv和execvp
      • 【4】exec类函数原理

一.进程创建

1.fork函数

【1】fork函数与其返回值

  • 它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
  • fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器
    决定。
#include <unistd.h>
pid_t fork(void);

返回值:在子进程中返回0
       在父进程中返回子进程id,出错返回-1

【2】fork函数中的写时拷贝

  • 通常,父子代码共享,父子再不写入时, 数据也是共享的
  • 当任意一方试图写入,便以写时拷贝的方式各自一份 副本 (蓝色部分) ,具体见下图:
  • 注意,其发生写时拷贝时,权限也会相应变化
    在这里插入图片描述

2.fork函数/进程创建的场景——常规用法

  1. 一个父进程希望复制自己,使父子进程同时执行不同的代码段。 例如:父进程等待客户端请求,生成子进程来处理请求。
  2. 一个进程要执行一个不同的程序。例如:子进程从fork返回后,调用exec函数(后文进程替换会提到)

二.进程终止

1.进程退出的场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

2.进程应对不同退出场景的退出方法

前置知识:echo $? 查看进程退出码

  • ?:保存的是最近一个子进程执行完毕时的退出码
  • 情况1:?= 0 ,表示成功
  • 情况2:?!=0 , 可以用1,2,3,4,5不同的数字表示不同的错误原因
echo $?  ------指令
10 -------结果

前置知识:错误码VS退出码

  • **错误码:**通常是衡量一个库函数或者是一个系统调用一个 函数 的调用情况

  • **退出码:**通常是一个 进程 退出的时候,他的退出结果

  • **error错误码:**C语言提供的 全局变量 ,调用函数时如果出错了,他就会被设置成出错原因对应的数字

  • **strerror:**再把数字转换成描述出来的具体错误原因

#include <errno.h>
-- 测试调用失败情况
FILE *fp = fopen("./log.txt","r");

strerror():
printf("after: %d, error string : %s\n, errno, strerror(errno)");

【1】正常退出————通过退出码判断

1.exit函数 与 _exit函数
  • 任意地点调用exit,表示进程退出,不进行后续执行
  1. exit( 退出码 ) 是 库函数 ——头文件:stdlib.h
  2. _exit( 退出码 ) 是 系统调用 ——头文件:unistd.h
  • exit终止进程的时候, 会自动刷新缓冲区 exit终止进程的时候, 不会自动刷新缓冲区
验证是否自动刷新缓冲区
  • exit函数自动刷新缓冲区
// 对比下面两个程序,一个带\n,一个不带\n
// \n是行刷新,刷新到显示器上
int main()
{
    printf("you can see me!\n"); 
    sleep(6);
    exit(1);
}现象:遇到刷新条件,立刻刷新显示you can see me,6s后,程序退出

int main()
{
    printf("you can see me!"); 
    sleep(6);
    exit(1);
}现象:不立刻显示,6s后程序退出,强制刷新显示
  • _exit函数 不会 自动刷新缓冲区
// 对比下面两个程序,一个带\n,一个不带\n
// \n是行刷新,刷新到显示器上
int main()
{
    printf("you can see me!\n"); 
    sleep(6);
    _exit(1);
}现象:遇到刷新条件,立刻刷新显示you can see me,6s后,程序退出

int main()
{
    printf("you can see me!"); 
    sleep(6);
    _exit(1);
}现象:不立刻显示,6s后程序退出,也不会刷新显示
2.从main返回——return退出——等同于正常退出的exit
  • return是一种更常见的退出进程方法
  • 执行return n 等同于 执行exit(n)
  • 因为调用main的运行时,函数会将 main函数 的返回值当做 exit 的参数。
  • 换句话说, main函数 的退出码是可以被父进程获取的,用来判断子进程的运行结果

【2】异常退出(程序崩溃)——操作系统转换成信号——进程被操作系统杀掉

  • 我们输入kill -l 可以看到有许多信号
  • 崩溃时就是执行了-9信号,有时也会显示其他信号
  • 信号具体内容可以看YY后续信号博客
    在这里插入图片描述
※ctrl + c也能强制退出——信号终止

三.进程等待

1.进程等待基本介绍

  • 通过wait/waitpid的方式,让父进程(一般)对子进程进行 资源回收 的等待过程

2.进程等待的必要性

  1. 子进程退出,父进程如果不管不顾,就可能造成 ‘僵尸进程’的问题,进而造成内存泄漏——进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的 kill -9 也无能为力 ,因为谁也没有办法杀死一个已经死去的进程。
  2. 我们需要知道父进程派给子进程的 任务完成的如何 ,如:子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过 进程等待 的方式,回收子进程资源,获取子进程退出信息
  1. 有时候进程也会 等待硬件资源 ,利用wait进程等待把自己挂起

3.如何进行等待(wait&waitpid)

【1】wait函数参数与返回值介绍&演示

  • 返回值pid_t:成功返回 被等待进程pid ,失败返回-1。
  • 参数status: 输出型参数 ,获取子进程退出状态, 不关心则可以设置成为NULL
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

演示:

  • 下方有一段程序,我们将参数设置成NULL即可
void Worker(int number)
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d, number: %d\n", getpid(), getppid(), cnt--);//蓝色部分
        sleep(1);
    }
}
int main()
{
    pid_t id = fork();
    if(id == 0) {//child
        Worker();
        exit(0);
    }
    else{//father橙色部分
       pid_t rid2 = wait(NULL);//wait函数
       if(rid == id)
       {
           printf("wait success, pid:%d\n",getpid());
       }
    return 0;
}
  • 执行结果如下
  • 逐步打印完蓝色部分后(子进程),执行橙色部分(父进程)
  • 本质:子进程和父进程同时存在——>子进程变成 僵尸状态 ——>等待结束时, 子进程消失 ,只剩下父进程
    在这里插入图片描述

【2】waitpid函数参数与返回值介绍&演示

返回值:

  1. 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
  2. 如果option设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  3. 如果调用中出错,则返回-1 ,这时errno会被设置成相应的值以指示错误所在;

参数:

  • pid:
    Pid=-1, 等待任一一个子进程 。与wait等效。
    Pid>0.等待其 进程ID与pid相等 的子进程。
  • status:
    WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    无需其他操作,设置成NULL即可
  • options:
  1. WNOHANG: 非阻塞等待。若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
  2. option设置为0,表示阻塞等待
pid_ t waitpid(pid_t pid, int *status, int options);

演示:

  • 下方有一段程序,我们将参数中,pid设置成fork出的子进程id,status设置成NULL,而option设置成0——表示阻塞就等待的设置
void Worker(int number)
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d, number: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}
int main()
{
    pid_t id = fork();
    if(id == 0)
    {//child
        Worker();
        exit(0);
    }
    else{//father
       printf("wait before\n");
       pid_t rid = waitpid(id,NULL,0);//waitpid函数
       printf("wait after\n");
       if(rid == id)
       {
           printf("wait success, pid:%d\n",getpid());
       }
    return 0;
}
  • 执行结果如下
  • 先执行橙色部分(父进程)中的打印, 逐步打印完蓝色部分后(子进程) 【先后顺序与操作系统调度有关,不确定谁先开始,但一般是父进程最后退出】
  • 本质:子进程和父进程同时存在——>子进程变成 僵尸状态 ——>等待结束时, 子进程消失 ,只剩下父进程
    在这里插入图片描述

【3】输出型参数-status的原理演示&status规则&现象演示(要点)

pid_ t waitpid(pid_t pid, int *status, int options);
  • status参数 整形指针 类型,是 输出型参数 —— status用于接受子进程的退出码
  • 因为进程具有独立性,父进程无法直接获得子进程的退出信息
  • 我们也可以打印status观察它的值,但是要 注意status的规则 ! —— 意味着不能对status整体使用

原理:

  1. 我们需要定义一个整型变量 status
  2. 把 status 的地址,即我们的参数给内核;内核再 把这个地址指向区域上的值给回用户层变量中

status规则:

  • status是一个int的整数,一共32bit,只研究低16位
  • status用于接受子进程的退出码
  • 所以一共记录三种信息,1.退出状态 2.core dump标志 3.终止信号
  • 但是并 不是每个信息都会用到 下图分别是穷举2个场景的例子——正常终止以及被信号所杀时的区域划分情况
    在这里插入图片描述

现象演示:

  • 把子进程退出码改成10exit(10);,通过status把子进程的退出信息返回
void Worker(int number)
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d, number: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}
int main()
{
    pid_t id = fork();
    if(id == 0)
    {//child
        Worker();
        exit(10);//设置成10
    }
    else{//father
       printf("wait before\n");
       
       int status =0; //定义一个整型变量 status
       pid_t rid = waitpid(id,&status,0);//通过status把子进程的退出信息返回
       printf("wait before\n");
       if(rid == id)
       {
           printf("wait success, pid:%d\n",getpid(),status);//打印status指向的区域的内容
       }
    return 0;
}
  • 执行结果为
2560  //输出结果为2560,而不是10
  • 分析:该情况为正常退出,10作为退出状态填到9-15位的区域中,而打印是打印整体,也就是2560
    在这里插入图片描述

四.进程程序替换

1.进程替换概念

【1】进程替换概念

  • 我们所创建的所有的子进程,执行的代码,都是父进程代码的一部分
  • 如果我们想让子进程 执行新的程序呢???-----进程替换:执行全新的代码和访问全新的数据,不再和父进程有瓜葛
  • 注意:进程替换不创建子进程—— 目标程序的进程不变pid不变,只是改变代码和数据
  • 如果直接替换代码和数据区,耦合可能同时影响父子进程,要维持进程的独立性——写时拷贝解决;我们有结论: 程序替换也存在写时拷贝

【2】进程替换的机制&情景演示

机制:

  • 进程替换成功: 子进程执行新的程序了, 剩下的代码不会执行了,被覆盖了。
  • 进程替换失败: 继续执行子进程剩余内容。 只有失败才有返回值

演示:

  • 当程序替换成功时,打印语句begin和执行替换后的语句,不打印语句end
  • 当程序替换失败时,打印语句begin和语句end
//注:我们只要知道下面execl函数是 起到进程替换的作用就行
//即执行ls -a -l -n 指令,具体使用细节看下文exec类函数模块
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("pid: %d, exec command begin\n",getpid());//语句begin
        execl("/usr/bin/ls", "lsaaa", "-a", "-l", "-n", NULL);
        printf("pid: %d, exec command end\n", getpid());//语句end
        exit(1);
    }
    else{
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

2.进程替换用到的exec类函数解释&命名规则&使用演示

【1】exec类函数

exec类函数有如下几种:都是为了满足各种调用的场景

  • 通过man exec可查看
    在这里插入图片描述

注意事项:

  1. 所有的exec类函数以 NULL结尾 表示完成
  2. 参数中后的…表示 可变参数
  3. exec用于程序替换,使用要满足:(1)必须找到可执行程序(2)必须要告诉exec*,怎么执行 ,下面会有具体演示

【2】exec类函数命名规则

  • l(list) : 表示参数采用 列表——execl ,execlp,execle
  • v(vector) : 参数用 数组——execv,execvp,execve
  • p(path) : 有p 自动搜索环境变量PATH——execvp
  • e(env) : 表示自己维护 环境变量——execve

【3】exec类函数使用演示

  • exec用于程序替换,使用要满足:(1)必须找到可执行程序(2)必须要告诉exec*,怎么执行 ,下面会有具体演示
1. execl

在这里插入图片描述

#include <unistd.h>
int main()
{
 //第一个参数找到可执行程序
 //后面的参数以列表形式(带l)告诉exec怎么执行
 //以NULL结尾
 execl("/bin/ps", "ps", "-ef", NULL);
 execl("/usr/bin/top", "top", NULL);
 execl("/usr/bin/pwd", "pwd", NULL);
 execl("/usr/bin/bash", "bash","test.sh", NULL);
 exit(0);
 }
2. execlp

在这里插入图片描述

#include <unistd.h>
int main()
{
 //第一个参数,execlp带p的,可以使用环境变量PATH,无需写全路径
 //后面的参数以列表形式(带l)告诉exec怎么执行 
 //以NULL结尾
 execlp("ps", "ps", "-ef", NULL);
 execlp("ls", "ls", "-a","-l", NULL);
 exit(0);
}
3. execv和execvp

在这里插入图片描述
在这里插入图片描述

#include <unistd.h>
int main()
{
 //第一个参数,execv和execvp带v的,使用数组传参
 //后面的参数以列表形式(带l)告诉exec怎么执行 
 //以NULL结尾
 char *const argv[] = {"ps", "-ef", NULL};
 execv("/bin/ps", argv);
 
 // 带p的,可以使用环境变量PATH,无需写全路径
 execvp("ps", argv);
 exit(0);
}

【4】exec类函数原理

  • 事实上, 只有execve是真正的系统调用 ,其它五个函数最终都调用 execve
  • 所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2119216.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年【甘肃省安全员C证】报名考试及甘肃省安全员C证考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年甘肃省安全员C证报名考试为正在备考甘肃省安全员C证操作证的学员准备的理论考试专题&#xff0c;每个月更新的甘肃省安全员C证考试总结祝您顺利通过甘肃省安全员C证考试。 1、【多选题】下列选用起重机钢丝绳时…

2 模拟——258. 各位相加 ★

2 模拟 258. 各位相加 ★ 给定一个非负整数 num&#xff0c;反复将各个位上的数字相加&#xff0c;直到结果为一位数。返回这个结果。 示例 1: 输入: num 38 输出: 2 解释: 各位相加的过程为&#xff1a; 38 --> 3 8 --> 11 11 --> 1 1 --> 2 由于 2 是一位…

RK3562 NPU供电要求

在《RK3562 NPU跑飞问题处理》文档中,有提及PWM_NPU网络对应的IO域电平,之前使用1.8V(PMUIO1电平),对应可以测量到VDD_NPU_P的电压为0.8V(上电后不工作状态),而最近硬件配置PMUIO1改为了3.3V,VDD_NPU_P的电压为0.6V,虽然部分AI模型能跑起来,但也存在NPU识别出错的情…

基于人工智能的实时交通监控系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 实时交通监控系统通过人工智能技术对交通流量进行监测和分析&#xff0c;能够帮助城市管理者优化交通流、减少拥堵、提升道路安全。本…

xilinx sdk退出Debug模式返回C开发布局模式

点击甲壳虫进入Debug窗口模式&#xff0c;退出方法是在最右上角quick access旁点击窗口布局的格子(open perspective)点击第一个default即可回到原先布局。

【python篇】——python基础语法一篇就能明白,快速理解

前言&#xff1a;本文章是在已经学习了C语言之后作者角度下进行书写的一篇关于python当中基础的语法说明。 文章目录 快速深入理解python当中的语法基本数据类型算数运算符类型的查看与转换 变量的输入与输出条件语句比较运算符和逻辑运算符if、elif、else三目运算符 循环语句w…

Qt:自制白噪声播放器

前言 由于本人觉得听着白噪声学习效果才会更佳&#xff0c;所以做了一个白噪声播放器。先看效果吧 我在里面添加了一些音乐 运行视频 whiteMusicDemo 中间区域原本想画个图像的&#xff0c;但是不知道要画啥&#xff0c;就搞了张图片贴上去&#xff0c;如果有想法的朋友可以…

《Attention Is All You Need》论文导读

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl论文背景 《Attention Is All You Need》这篇具有里程碑意义的论文,彻底改变了自然语言处理(NLP)的研究和应用格局。在此之前,循环神经网络(RNN)及其变体,如长短期记忆网络(LSTM),是处理…

首例开源的自动驾驶混合运动规划框架,手握“规划可解释”和“决策准确”两张王牌!

导读&#xff1a; 本文开发了一种新的混合运动规划方法&#xff0c;将环境和预测信息集成在Frenet坐标系中&#xff0c;提升了运动规划能力。本文将传统运动规划算法的可预测性和稳定性与RL的动态适应性相结合&#xff0c;从而形成了一个能够有效管理复杂情况并适应不断变化的环…

C#高级:递归3-根据ID连续递归生成一颗递归树

目录 一、需求&#xff1a; 二、实现代码 三、递归代码 四、思考 一、需求&#xff1a; 给小明一家生成族谱树 二、实现代码 using Newtonsoft.Json;class Person {public int Id { get; set; }public int? ParentID { get; set; }public string Name { get; set; } }c…

四数相加 II--力扣454

四数相加 II 题目思路C代码 题目 思路 我们需要统计元组的个数&#xff0c;并且不需要排序和去重&#xff0c;所以选择哈希表unordered_map。 要使得abcd0&#xff0c;我们首先利用map在前两个数组中统计ab的值以及该值出现的次数。 接下来我们去后两个数组中查找0-(cd)&…

每日一练:除自身以外数组的乘积

一、题目要求 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在…

windows手工杀毒-寻找可疑进程之句柄

上篇回顾&#xff1a;windows手工杀毒-寻找可疑进程之内存-CSDN博客 上篇中我们介绍了如果通过进程的内存分析进程是否是可疑进程&#xff0c;主要是通过查看是否有可写可执行的内存页。也可以通过查看内存内容&#xff0c;看是否是可疑内容&#xff0c;不过这个可能需…

如何取消密码?打印加密的PDF文件?

在日常生活和工作中&#xff0c;我们时常会遇到需要处理加密PDF文件的情况。这些文件可能包含敏感信息&#xff0c;如合同、报告或私人文档&#xff0c;因此被设置了密码保护。然而&#xff0c;有时我们出于工作需要或其他原因&#xff0c;需要打印这些加密的PDF文件。本文将详…

管道检测机器人市场前景:预计2030年全球市场规模将达到18.9亿美元

用于检查管道内堆积的残留物的机器人被称为管道内检查机器人。这种机器人有能力从地面向机器人的控制器发送视频反馈信息。通过这种方式&#xff0c;用户可以验证管道的实际状况&#xff0c;也可以测量管道壁上的沉积物厚度。 据QYResearch调研团队最新报告“全球管道检测机器人…

苹果手机照片被删除?如何通过不同的方法来恢复照片

手机已经成为我们生活中不可或缺的一部分&#xff0c;它不仅仅是通讯工具&#xff0c;更是我们记录生活点滴的重要工具之一。然而&#xff0c;正如其他任何设备一样&#xff0c;iPhone上存储的照片有时也会不小心被删除或丢失。 别担心&#xff0c;即使你误删了重要的照片&…

未来的去中心化网络:Web3与AI的深度融合探讨

在数字技术的快速发展下&#xff0c;Web3和人工智能&#xff08;AI&#xff09;正逐步成为未来互联网的核心支柱。Web3作为一种去中心化的网络架构&#xff0c;致力于重塑互联网的基本结构&#xff0c;而AI则通过智能化技术提升了数据处理和决策的能力。二者的深度融合不仅推动…

数学建模笔记—— 模糊综合评价

数学建模笔记—— 模糊综合评价 模糊综合评价1. 模糊数学概述2. 经典集合和模糊集合的基本概念2.1 经典集合2.2 模糊集合和隶属函数1. 基本概念2.模糊集合的表示方法3. 模糊集合的分类4. 隶属函数的确定方法 3. 评价问题概述4. 一级模糊综合评价模型典型例题 5. 多层次模糊综合…

系统分析师8:项目管理

文章目录 1 内容提要2 范围管理3 时间管理3.1 时间管理-关键路径法3.1.1 时间管理-前导图法&#xff08;单代号网络图&#xff0c;PDM&#xff09;3.1.2 时间管理-箭线图法(双代号网络图&#xff0c;ADM) 4 时间管理-甘特图&#xff08;Gantt&#xff09;5 成本管理6 软件质量管…

超声波眼镜清洗机有用吗? 非常好用的超声波清洗机

随着科技的飞速跃进&#xff0c;现代人的生活质量显著提升&#xff0c;众多日常用品已成为生活的必需品&#xff0c;例如频繁佩戴的眼镜。尽管常规的眼镜布能抹去镜片上的尘埃&#xff0c;但隐匿于细微之处的细菌往往逃过人眼的监察。这些潜在的细菌若不被及时清除&#xff0c;…