Linux进程详细解析

1.操作系统

概念

任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:

 • 内核(进程管理,内存管理,文件管理,驱动管理)

• 其他程序(例如函数库,shell程序等等)

设计操作系统的目的 

• 对下,与硬件交互,管理所有的软硬件资源

• 对上,为用户程序(应用程序)提供⼀个良好的执行环境

核心功能

  1. 进程管理:负责进程的创建、调度、同步、通信及销毁,合理分配 CPU 资源,确保多任务环境下各进程高效运行,提升系统整体效率。
  2. 内存管理:动态分配与回收内存空间,通过虚拟内存技术扩展内存能力,保障进程间地址空间相互独立,避免干扰,提高内存利用率。
  3. 文件系统管理:对文件的创建、删除、读写、权限控制等进行组织与管理,确保数据存储的安全性、一致性,方便用户及应用程序对文件的操作与访问。
  4. 驱动管理:协调与控制外部设备(如输入 / 输出设备、存储设备等),实现设备驱动程序的加载与运行,保障设备与系统的兼容性,提升设备使用效率。

如何理解"管理"

操作系统是怎么管理内部的文件呢?是直接管理资源还是通过一系列的接口来管理资源呢?

资源就像学生一样,学生有着年龄,专业,性别等等的属性,系统内的文件也有着许多属性,如大小,所在路径等等。

在学校里面,我们一般一整个学期都见不到几次校长,所以对于我们来说,是辅导员在直接管理我们,而不是校长直接管理我们。所以管理者和被管理者之间可以不同见面

那校长是怎么间接管理我们的呢?校长可以通过辅导员来获取学生的数据来管理我们,并不用平时亲自管理学生。所以管理者和被管理者可以通过数据进行管理,由于是通过辅导员来获取数据,所以可以由中间层来获取数据

学生的数据就如同数据结构中结点的数据一样。可以将学生的数据输入计算机的链表当中,将日常与学生的管理工作转化为对计算机中链表的管理工作

那么在计算机中,我们可以这样子来解释操作系统的管理过程,操作系统对数据进行建模,转化成数据结构,管理就变成了对数据结构的管理。

所以操作系统的管理重点是:先描述,再组织

总结:计算机管理硬件

1. 描述起来,用struct结构体

2. 组织起来,用链表或其他高效的数据结构

系统调用和库函数概念

• 在开发角度,操作系统对外会表现为⼀个整体,但是会暴露自己的部分接口,供上层开发使用, 这部分由操作系统提供的接口,叫做系统调用。

• 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部 分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行而次开 发。

那么我们为什么需要系统调用呢?

我们可以通过银行的管理模式来解释系统调用,我们在存钱取钱的时候,我们并不是我们自己进入银行金库中自己存钱取钱的,而是通过银行的窗口与工作人员进行交流再进行相应的操作,为什么需要与工作人员进行交流呢,因为银行不相信其他人,只能通过工作人员进行存钱取钱操作,害怕其他人做出出格的事情

系统调用也是如此,操作系统就是银行,系统调用就是工作人员,而我们就是其他人,操作系统不相信我们,我们无法直接与底层资源进行互动,害怕我们操作失误损坏数据,只能允许我们通过系统调用与底层资源进行互动

进程

基本概念与基本操作

• 课本概念:程序的⼀个执行实例,正在执行的程序等

• 内核观点:担当分配系统资源(CPU时间,内存)的实体。

描述进程-PCB

基本概念

• 进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合。

• 课本上称之为PCB(process control block),Linux操作系统下的PCB是:task_struct 。task_struct-PCB的⼀种

• 在Linux中描述进程的结构体叫做task_struct。 

• task_struct是Linux内核的⼀种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

•进程=PCB+自己的代码和数据

进程的大致结构 

struct xxx
{int a;int b;......struct xxx* next;struct mm_struct * node;
}

task_struct的数据结构就类似上面那段代码,只不过struct里面存放的是更加详细的进程信息,然后task_struct通过next指针链接起来形成一个链表。

那么怎么查找正在运行的代码和数据呢?task_struct中存在一个结构体指针struct mm_struct *,如同上面的那段代码相似,然后可以通过node指针找到正在运行的代码和数据。

所以进程的所有信息都可以通过task_struct间接或直接找到,所以对进程的管理就变成了对链表的增删查改

组织进程

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里

查看进程 

我们可以通过ls /proc来查看此时正在运行的进程。

这些数字就是进程号

通过系统调用获取进程标示符 

• 进程id(PID)

• 父进程id(PPID)

我们可以通过两个函数来查看pid和ppid,分别是getpid()和getppid()。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{printf("pid: %d\n", getpid());printf("ppid: %d\n", getppid());return 0;
}

当我们运行上面那段代码后就会显示出我们的进程号和父进程号 

此时我们可能会疑惑,为什么我们的代码为什么还会有父进程,代码并不是通过其他代码来调用的,我们来查看父进程的具体信息

我们可以看出父进程是bash开始运行的,bash就是我们的命令行

我们输入的命令实际上都是以字符串的形式给bash,前面的那段字符是bash打印出来的。后面的绿色光标就是bash等待我们的scanf,所以就停下来了

我们可以通过ll -1 /proc/进程号来查看进程的exe和cwd

我们可x以看到文件后面跟着一串路径,其中cwd是记录文件的所在路径,exe就是文件的实际路径

那这个cwd有什么作用呢?在使用c语言的fopen时不带路径只带文件名,就会在当前目录下自动创建文件,但是这是怎么判断当前目录的,这是由于进程启动时,cwd就会自动记录当前所在路径,程序会读取cwd路径来创建文件

但是我们可以通过一些代码来改变cwd

chdir("/root/a");

此时我们的cwd就会被修改成/root/a

创建子进程

初始fork

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();if(ret < 0){perror("fork");return 1;}else if(ret == 0){ //childprintf("I am child : %d!, ret: %d\n", getpid(), ret);}else{             //fatherprintf("I am father : %d!, ret: %d\n", getpid(), ret);}sleep(1);return 0;
}

我们可以通过这段代码来帮助我们了解fork函数和子进程,代码的运行结果如下

这时候可能会疑惑,fork为什么会有两个返回值?  两个返回值各种给父子如何返回? ⼀个变量怎么能让 if 和 else if 同时成立这个问题

fork函数存在两个返回值的原因是因为fork会跟据父进程和子进程返回不同的结果,fork会给父进程返回子进程的pid,会给子进程返回0

为什么会出现不同的返回值呢:由于父进程:子进程=1:n,一个父进程可以有多个子进程,父进程需要通过不同的pid来区分不同的子进程,子进程不需要获取父进程的pid,本身就可以通过getppid来返回父进程的pid

那为什么fork会返回两次呢:我们可能会陷入一个误区,fork函数不仅会创建子进程,父进程也会运行fork函数,在fork函数内部,可能运行到一半的代码时,子进程可能已经被创建好甚至被调度了,所以在最后return语句时,不仅子进程会返回数据,父进程也会返回数据。

那为什么会输出两行内容呢,因为子进程被创建出来之后没有自己的代码和数据,只会自动执行父进程的代码,从子进程被创建出来的地方开始运行。

ret的值不同的原因涉及到了写时拷贝在子进程创建出来的时候,子进程没有自己的代码和数据,所以它只会执行父进程的数据,所以子进程会指向父进程的代码和数据,此时子进程和父进程就指向了相同的内容,如果遇到修改数据的情况呢?此时系统就会为修改数据的进程重新开辟一个新的空间,将原先的代码和数据拷贝过去,这就是写时拷贝。

进程状态

操作系统对硬件的管理

操作系统如何对硬件资源做管理,先描述,再组织。先创建结构体,将对应硬件资源的数据填写完善,将对硬件资源的管理转化成对链表的管理

操作系统的基础状态

操作系统中存在着三种基础状态:运行,阻塞和挂起。

运行状态:只要进程在调度队列中

阻塞状态:等待某种设备或资源就绪,就像C++的cin等待输入一样,此时进程仍在内存中

挂起状态:进程被暂时停止执行,资源使用被限制或部分释放,可能由用户或系统强制触发

为什么会形成阻塞:当进程未等待到某种设备就绪的时候,操作系统会将进程从运行队列上拿取下来,然后将进程的pcb链入到等待输入的设备的等待队列中,不在运行队列里,就永远不会被调度,就形成了阻塞状态,在等待设备就绪过程中,进程并不能够知道设备时候就绪,而操作系统作为硬件资源的管理者,它能够第一时间反应设备状态发生改变,然后更改设备状态,并且检查设备的等待对列,如果不为空,将等待队列的状态设为运行,并重新将进程链入运行队列中

当计算机内存资源不足的时候,设备链表上不会被调度的进程交换到磁盘上,只有PCB没有代码和数据的进程叫做阻塞挂起

操作系统内部数据结构

为什么操作系统内部会存在多个数据结构,而数据的空间只有一份,那么操作系统是怎么解决的?

操作系统中存在多个类似代码的数据结构

struct list_head
{struct list_head *next *prev;
};

将他放入task_struct中

struct task_struct
{.......struct list_head link;.......
};

 link的next和prev不会指向下一个或后一个的task_struct,而是指向下一个或后一个task_struct的link中的next或prev。

但是指向link的话我们如何去访问task_struct中的内容呢:我们可以通过类似下面代码的方式获取link的偏移量,通过将0强转成task_struct类型并访问其中的links,就可以知道结点的起始地址与links地址之间的偏移量,此时我们就可以访问结点中的任何信息了

list-&((struct task_struct*)0->link)

此时task_struct之间遍可以通过类似下图的形式将单个数据装换成多个数据结构 ,在同一个结点内可以有多个links,可以将结点通过links形成不同的数据结构,这就是为什么操作系统中存在一个结点可以被同时放在硬件的链表和运行队列中

Linux内核源代码状态

static const char *const task_state_array[] = {"R (running)", /*0 */"S (sleeping)", /*1 */"D (disk sleep)", /*2 */"T (stopped)", /*4 */"t (tracing stop)", /*8 */"X (dead)", /*16 */"Z (zombie)", /*32 */
};

在这里我们可以看到,在源代码中,进程状态用类似宏定义的整数表示。

• R运行状态(running):并不意味着进程⼀定在运行中,它表明进程要么是在运行中要么在运行 队列⾥。

• S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。

• D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个 状态的进程通常会等待IO的结束。

• T停止状态(stopped): 可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的 进程可以通过发送SIGCONT信号让进程继续运行。

• X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表里看到这个状态。

•Z僵尸状态(zombie):子进程等待父进程回收

这里我们可以着重了解D,T,Z三种状态

D状态:当进程向磁盘中写入数据,在写入数据的过程中,进程会在原地等待磁盘返回写入结果,但是如果在等待过程中,系统资源严重不足的话,操作系统会直接将进程杀掉,但是在杀掉进程之后,磁盘写入失败想要告诉进程,但是进程不见了,磁盘只能将写入的资源丢失,丢失资源用户不知道,容易造成严重后果,后面设计出D状态给等待磁盘返回结果的进程,表示操作系统不能直接杀掉D状态进程

T:我们可能不理解在什么情况下进程是停止状态,当我们在使用cgdb的断点在停止代码运行时,此时通过查看进程状态,能够发现进程状态为T

Z僵尸状态:我们创建子进程的目的是为了帮助父进程完成某些东西,一旦子进程运行完成后,父进程就得知道子进程的运行结果如何,但是如果父进程一直不回收信息,那么子进程就会一直存在,此时子进程的状态就是僵尸状态,这种状态会导致内存泄漏的问题。

但是如果父进程退出后子进程仍未退出,此时子进程的父进程就会变成1号进程,那么1号进程是什么东西,我们可以把它看成操作系统本身,为什么要领养:如果不被领养,子进程就会进入僵尸状态,从而造成内存泄漏无法解决,需要通过系统来解决问题,但是被1号进程接管后。子进程就会变成后台进程

进程优先级

基本概念

 • cpu资源分配的先后顺序,就是指进程的优先权(priority)。

• 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性 能。

• 还可以把进程运行到指定的CPU上,这样⼀来,把不重要的进程安排到某个CPU,可以大改善 系统整体性能。

• 优先级是一种数字,值越低,优先级越高,反之越低

查看系统进程

我们可以使用ps -l命令显示当前系统中运行的进程信息

我们很容易注意到其中的几个重要信息,有下:

• UID:代表执行者的身份,在系统中并非通过用户名字来识别的拥有者,而是通过id来识别的,使用ls -ln即可查看用户对应的id

所以我们可以了解一下系统怎么知道我们访问文件的时候,是拥有者,所属组还是other?

用命令访问文件本质进程访问文件,进程在启动的时候会记录启动者的id,然后与文件的拥有者,所属组做对比,判断启动者的权限有哪些

• PID:代表这个进程的代号

• PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

• PRI:代表这个进程可被执行的优先级,其值越小越早被执行

• NI:代表这个进程的nice值

PRI and NI 

• PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此 值越小进程的优先级别越高

• 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值

• PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=80(默认优先级)+nice。

• 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快 被执行

• 所以,调整进程优先级,在Linux下,就是调整进程nice值

• nice其取值范围是-20到19,⼀共40个级别。为了保证公平,nice才会存在一定的范围,变化的幅度不会太大,所以优先级的范围在[60,99]

• PRI的默认值为80。

查看进程优先级的命令

可以通过top,nice,renice等命令来修改

概念-竞争、独立、并行、并发

• 竞争性:系统进程数目众多,而CPU资源只有少量,甚至只有1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

• 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰

• 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行

• 并发:多个进程在⼀个CPU下采用进程切换的方式,在⼀段时间之内,让多个进程都得以推进,称 之为并发

进程切换

CPU上下文切换:

其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中,入栈工作完成后就把下⼀个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器,并开始下⼀个任务的运行,这⼀过程就是context switch。

1.寄存器

寄存器就是cpu内部的临时空间

寄存器是一份空间,而寄存器内的数据只是内容,可以随时替换,所以寄存器!=寄存器里面的数据

2.切换进程时的文件去哪了

task_struct中还存在着一个结构体TSS,文件会保存到TSS中

Linux2.6内核进程O(1)调度队列

我们通过这张图片来了解一下进程切换的大致内容

图片中存在队列:queue[140],他表示着140个优先级,其中0-99是实时优先级,100-139才是我们可以操作的优先级,在相应的优先级后面,queue[优先级]会存放着我们将要运行的进程,queue可以理解为一个hash表。

我们可以看到图片中有存在相同的变量我们可以将它理解为一个结构体数组array,分为array[0]和array[1],array[0]是活跃进程,array[1]是过期进程。图片中有两个指针,active指向array[0],expired指向array[1]。

bitmap[5]是通过位图操作来减少queue查找进程的时间复杂度,可以迅速的查找1的位置来锁定接下来要运行的进程,为什么大小是5呢?原因很简单,4过小,5过大。

现在有几个问题

1.遇到死循环进程的话后面的进程还会运行吗?

我们的操作系统采用的是分时操作系统,当死循环程序的时间片用完之后,操作系统就会将死循环进程从active中抓取出来,放到expired中

2.expired中的程序什么时候才会运行?

expired中的进程一般是刚开始的进程和过期进程,只有等待active里面的进程运行完成后,swap(active,expired),然后运行进程

3当我们新来一个进程后,是要放在expired中,与过期进程存放在一起?

这就是进程的就绪状态,但是随着硬件的更新,出来了内核优先级抢占问题,它能够直接将新进程直接插入到active中。

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

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

相关文章

解决两个技术问题后小有感触-QZ Tray使用经验小总结

老朋友都知道&#xff0c;我现在是一家软件公司销售部门的项目经理和全栈开发工程师&#xff0c;就是这么“奇怪”的岗位&#xff0c;大概我是公司销售团队里比较少有技术背景、销售业绩又不那么理想的销售。 近期在某个票务系统项目上驻场&#xff0c;原来我是这个项目的项目…

Centos 7.6安装redis-6.2.6

1. 安装依赖 确保系统已经安装了必要的编译工具和库&#xff1a; sudo yum groupinstall "Development Tools" -y sudo yum install gcc make tcl -y 2. 解压 Redis 源码包 进入 /usr/local/ 目录并解压 redis-6.2.6.tar.gz 文件&#xff1a; cd /usr/local/ sudo ta…

Ejs模版引擎介绍,什么是模版引擎,什么是ejs,ejs基本用法

** EJS 模板引擎**&#xff0c;让你彻底搞明白什么是模板引擎、什么是 EJS、怎么用、语法、最佳实践等等&#xff1a; &#x1f4da; 一、什么是模板引擎&#xff1f; 模板引擎是前后端分离之前的一种服务器端“渲染技术”。它的主要作用是&#xff1a; 将 HTML 页面和后端传递…

2025.4.21-2025.4.26学习周报

目录 摘要Abstract1 文献阅读1.1 模型架构1.1.1 动态图邻接矩阵的构建1.1.2 多层次聚合机制模块1.1.3 AHGC-GRU 1.2 实验分析 总结 摘要 在本周阅读的论文中&#xff0c;作者提出了一种名为AHGCNN的自适应层次图卷积神经网络。AHGCNN通过将监测站点视为图结构中的节点&#xf…

6.1 客户服务:智能客服与自动化支持系统的构建

随着企业数字化转型的加速&#xff0c;客户服务作为企业与用户交互的核心环节&#xff0c;正经历从传统人工服务向智能化、自动化服务的深刻变革。基于大语言模型&#xff08;LLM&#xff09;和智能代理&#xff08;Agent&#xff09;的技术为构建智能客服与自动化支持系统提供…

java Optional

我还没用过java8的一些语法&#xff0c;有点老古董了&#xff0c;记录下Optional怎么用。 从源码看&#xff0c;Optional内部持有一个对象&#xff0c; 有一些api对这个对象进行判空处理。 静态方法of &#xff0c;生成Optional对象&#xff0c; 但这个value不能为空&#…

【Java面试笔记:进阶】24.有哪些方法可以在运行时动态生成一个Java类?

在Java中,运行时动态生成类是实现动态编程、框架扩展(如AOP、ORM)和插件化系统的关键技术。 1.动态生成Java类的方法 1.从源码生成 直接生成源码文件:通过Java程序生成源码并保存为文件。编译源码: 使用ProcessBuilder启动javac进程进行编译。使用Java Compiler API(ja…

基于Jamba模型的天气预测实战

深入探索Mamba模型架构与应用 - 商品搜索 - 京东 DeepSeek大模型高性能核心技术与多模态融合开发 - 商品搜索 - 京东 由于大气运动极为复杂&#xff0c;影响天气的因素较多&#xff0c;而人们认识大气本身运动的能力极为有限&#xff0c;因此以前天气预报水平较低 。预报员在预…

GAMES202-高质量实时渲染(Real-Time Shadows)

目录 Shadow MappingshadowMapping的问题shadow mapping背后的数学PCF&#xff08;Percentage Closer Filtering&#xff09;PCSS&#xff08;Percentage closer soft shadows&#xff09;VSSM&#xff08;Variance Soft Shadow Mapping&#xff09;优化步骤3优化步骤1SAT&…

iphonex uniapp textarea标签兼容性处理过程梳理

嗨&#xff0c;我是小路。今天主要和大家分享的主题是“iphonex uniapp textarea标签兼容性处理过程梳理”。 在uniapp项目中&#xff0c;经常会使用到uniapp原生的textarea标签&#xff0c;但在手机兼容性这块&#xff0c;textarea并不是很好用&#xff0c;会出现一些…

C++ 区分关键字和标识符

1. 关键字&#xff08;Keywords&#xff09; 定义&#xff1a;关键字是编程语言预定义的具有特定意义的单词。它们是语言的一部分&#xff0c;C编译器具有特殊的理解规则&#xff0c;不能作为用户自定义的标识符。作用&#xff1a;关键字用于定义语言结构&#xff0c;如声明变…

杭电oj(1087、1203、1003)题解

DP 即动态规划&#xff08;Dynamic Programming&#xff09;&#xff0c;是一种通过把原问题分解为相对简单的子问题&#xff0c;并保存子问题的解来避免重复计算&#xff0c;从而解决复杂问题的算法策略。以下从几个方面简述动态规划&#xff1a; 基本思想 动态规划的核心在…

一键多环境构建——用 Hvigor 玩转 HarmonyOS Next

引言 在 HarmonyOS Next 的应用开发中&#xff0c;常常需要针对不同环境&#xff08;测试、预发、线上&#xff09;或不同签名&#xff08;调试、正式&#xff09;输出多个 APP/HAP 包。虽然 HarmonyOS 提供了多目标构建&#xff08;Multi-Target Build&#xff09;能力&#…

qt/c++云对象浏览器

简介 本项目为基于QT5和C11的云对象存储可视化管理工具 源码获取 int main(){ printf("源码联系绿泡泡:%s","joyfelic"); return 0; }

【Ubuntu】提升 docker ps -a 输出的可读性:让 Docker 容器状态更清晰

提升 docker ps -a 输出的可读性&#xff1a;让 Docker 容器状态更清晰 当我们使用 docker ps -a 查看所有 Docker 容器时&#xff0c;输出的信息通常会非常多&#xff0c;尤其是在容器数量较多时。默认输出中包含容器 ID、名称、镜像、状态、端口等信息&#xff0c;容易让人眼…

Spring Security自定义身份认证

尽管项目启动时&#xff0c;Spring Security会提供了默认的用户信息&#xff0c;可以快速认证和启动&#xff0c;但大多数应用程序都希望使用自定义的用户认证。对于自定义用户认证&#xff0c;Spring Security提供了多种认证方式&#xff0c;常用的有In-Memory Authentication…

在亚马逊云服务器上部署WordPress服务

在亚马逊云服务器上部署WordPress服务第一步&#xff1a;创建EC2实例第二步&#xff1a;初始设置与安装第三步&#xff1a;配置MySQL与WordPress第四步&#xff1a;配置Apache与WordPress第五步&#xff1a;访问WordPress第六步&#xff1a;测试数据库连接第七步&#xff1a;使…

Web3.0的认知补充(去中心化)

涉及开发技术&#xff1a; Vue Web3.js Solidity 基本认知 Web3.0含义&#xff1a; 新一代互联网思想&#xff1a;去中心化及用户为中心的互联网 数据&#xff1a;可读可写可授权 核心技术&#xff1a;区块链、NFT 应用&#xff1a;互联网上应用 NFT &…

如何修复宝可梦时时刻刻冒险无法正常工作

宝可梦的时时刻刻冒险模式是一项强大的功能&#xff0c;即使应用程序关闭&#xff0c;它也能追踪你的步行距离。它的工作原理是将你的步数与 iOS 上的 Apple Health 或 Android 上的 Google Fit 同步。它对于孵化宝可梦蛋和赚取好友糖果至关重要&#xff0c;但一旦它停止工作&a…

redis常用集合操作命令

在 Redis 的命令行界面&#xff08;redis-cli&#xff09;中&#xff0c; Redis 的集合&#xff08;Set&#xff09;是无序的&#xff0c;且集合中的元素是唯一的。Redis 本身没有直接提供获取集合中某个特定属性的命令&#xff0c;因为集合中的元素是简单的值&#xff0c;而不…