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

【Linux从青铜到王者】进程信号

———————————————————————————————————————————

信号入门

在了解信号之前有许多要理解的相关概念

我们可以先通过一个生活例子来初步认识一下信号

1.生活角度的信号

  • 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”。
  • 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成“在合适的时候去取"。
  • 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你“记住了有一个快递要去取"。
  • 当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)。
  • 快递到来的整个过程,你不能准确断定快递员什么时候给你打电话,所以该过程对你来讲是异步的

2.同步与异步,阻塞与非阻塞

同步就是发出一个调用后,当这个调用没有得到结果的时候,该调用就一直不返回

而异步则相反,当发出一个调用后,不管这个调用有没有取得结果,直接就返回了,后面通过状态,和通知来告诉调用者,或者通过回调函数来调用这个调用

阻塞和非阻塞关注的是程序在等待调用结构时的状态

阻塞调用指的是当获得调用结果之前,当前进程会被挂起

非阻塞调用指的是在没有获得调用结果之前,当前进程不会被挂起,会继续执行

举个通俗的例子:
你打电话问书店老板有没有 《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,〞我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了 (不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调

如果是阻塞调用,那么当你问完书店老板以后,你会在电话前一直等,直到书店老板给你回电;而如果是非阻塞调用,那么当你问完以后就会去干其他事情了,例如刷剧打游戏等等

3.进程的注意事项

一个bash只能有一个前台进程,可以有多个后台进程

Ctrl-C 产生的信号只能发给前台进程。只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号

前台进程不能被暂停,一旦被暂停就被自动放到后台进程中去

一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程

前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步的

4.信号的概念

什么叫做信号?信号其实是向目标进程发送通知信息的一种机制

信号的本质:其实就是用软件来模拟中断的过程——软中断

中断是什么呢?本质是电信号。这里涉及部分硬件原理,大概理解一下即可

当你在键盘敲ctrl+c的时候,键盘这个外设就产生了电信号,通过总线发送给中断控制器,再通过8259将电信号转换为中断号(0~n),被OS捕捉

这里外设产生电信号被转换为中断号的过程一般叫硬中断

5.信号介绍

信号分为普通信号和实时信号,本篇主要讨论普通信号

可以通过kill -l命令查看系统定义的命令,每个信号的具体使用可以使用man -7 signal命令

总共有62个信号,1-31为普通信号(大部分为终止进程),其余为实时信号

可以看到每个信号由一个序号+一个名字组成,通过以前所学这里的名字我们可以大概推测出来是宏,类似于#define SIGHUP1

这里的序号就是中断号,而在进程中会存在一张函数指针数组(中断向量表),进程通过序号(数组下标)可以调用不同的函数

这里大概了解一下即可,下文会详解

6.如何全面理解信号

下文将从信号的产生,保存和捕捉处理三个大部分来详解一下

其中保存和捕捉处理十分重要

信号的产生

1.通过终端硬件产生

其实就是上文所讲的通过键盘发送信号,常见的有ctrl+c,向前台进程发送2号新号,ctrl+z(默认暂停进程),ctrl+·默认终止进程

敲下键盘-》外设产生电信号->转变为中断号-》被os拿到发送给进程-》每个进程都有自己的一个中断向量表,中断号与数组下标强相关,通过中断号调用对应的函数

还是下面这张图

注意:这里被os写入进程十分重要,因为os是软硬件的管理者,无论信号的产生有多少种方式,最后只能被os拿到,然后发送给进程

2.通过系统调用产生

kill命令是通过kill函数完成的,kill函数可以给一个指定的进程发送指定的信号

可以通过kill函数来实现自己的,这里需要用到之前学的命令行参数

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<signal.h>void Usage(char*s)
{printf("Usage:%s pid signo\n",s);
}
//kill -9
int main(int argc,char*argv[])
{if(argc!=3){Usage(argv[0]);return 1;}pid_t pid=atoi(argv[1]);int signo=atoi(argv[2]);kill(pid,signo);return 0;
}

除了kill函数,还有raise和abort

int raise(int sig)

raise函数用于给当前进程发送sig信号,成功返回1,不成功返回0

void handler(int signo)
{printf("get a signal:%d\n",signo);
}
int main()
{signal(2,handler);while(1){sleep(1);raise(2);}return 0;
}

void abort(void)

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<signal.h>
#include<unistd.h>void handler(int signo)
{printf("get a signal:%d\n",signo);
}int main()
{signal(6,handler);//对信号自定义捕捉while(1){sleep(1);abort();}return 0;
}

abort函数是一个无参无返回值的函数,就是向进程自己发送6号信号

即使6号信号被自定义捕捉后不退出进程,使用abort函数后总是会退出进程

总结:exit是终止正常结束的进程,abor是终止异常结束的进程,终止方法为向进程发送6号信号,即使6号信号被自定义捕捉后没有执行退出逻辑操作,调用abor函数后总是能退出

3.通过软件条件产生

SIGPIPE信号和闹钟SIGALRM

SIGPIPE信号(13号信号)实际上是一种由软件条件产生的信号,我们都知道管道遵从一定的规则

假如管道的读端关闭,写端还在写数据的时候,此时管道已经没有存在的必要了,写端就会收到SIGPIPE信号从而被终止

unsigned int alarm(unsigned int seconds);

调用alarm函数可以让os在seconds秒之后给当前进程发送SIGALARM信号,SIGALARM信号的默认动作是终止进程

4.通过硬件异常

当进程中出现除零错误或者野指针和越界访问问题,为什么程序会崩溃呢?因为os识别到相关错误向进程发送对应信号使其终止

那么是如何识别除零错误或者野指针和越界访问问题的呢?

先说除零错误。我们知道cpu中有一堆的寄存器,当寄存器进行算术的时候,有些状态寄存器的值也要改变。在这些状态寄存器中肯定有某个寄存器的某个比特位表示除数是否为0,一旦检测出来除数为0,那么对应的硬件信息就会被os所识别到,然后包装成软件信息发送信号给当前进程

野指针和越界访问问题

我们都知道当虚拟地址向物理地址转换的时候,是通过页表转换的,页表属于一种软件映射关系

而实际上从虚拟地址到物理地址映射的时候还有一个硬件叫做MMU,它是一种负责cpu内存访问请求的计算机硬件

当需要进行虚拟地址到物理地址的映射时,我们先将页表的左侧的虚拟地址导给MMU,然后MMU会计算出对应的物理地址,我们再通过这个物理地址进行相应的访问。

既然MMU是硬件,所以就有对应的状态信息。当我们要访问不属于自己的虚拟地址空间的时候,MMU在转换的时候就会出现错误,从而被os识别,发送信号给进程,让进程终止

总结:程序之所以会崩溃,就是进行错误操作的时候一些硬件信息被os捕捉到,然后包装成软件信息向进程发送信号,从而终止进程

信号的保存

首先要理解一下几个概念

实际执行信号的处理动作,称为信号递达
信号从产生到递达之间的状态,称为信号未决

进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
需要注意的是,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后的一种处理动作。

三张表

信号被发送给一个进程之后,进程可能不是立即执行的,那么进程就要保存这个信号,怎么保存呢?通过位图保存

在一个进程中都会存在三张表,block位图(信号屏蔽字,阻塞信号集)表示对应信号是否被阻塞,pending位图表示该信号是否未决,还有一个handler表——函数指针数组,保存默认的处理方法

所以之前说的os发送信号给进程,其实就是向进程对应的位图写入数据

假如我向一个进程发送2号信号,该进程的pending表的二号位置就会变为1,此时2号信号信号未决,直到信号被处理之前,该位置一直为1;如果2号信号被写入pending表但是对应的block也被写入,就是信号被阻塞,此时不执行对应的默认处理方法,直到阻塞被解除

如果是忽略,那么就是对应的pending被写为1,block写为0。先将pending写为0,执行空方法,也就什么都不做

假设在进程在执行其他的信号方法的期间发送多个2号信号,pending的2号位置仍为1,当之前的方法处理完之后,2号的对应方法只被执行一次(其他系统可能不一样)

总结一下:
在block位图中,比特位的位置代表某一个信号,比特位的内容代表该信号是否被阻塞。
在pending位图中,比特位的位置代表某一个信号,比特位的内容代表是否收到该信号。
handler表本质上是一个函数指针数组,数组的下标代表某一个信号,数组的内容代表该信号递达时的处理动作,处理动作包括默认、忽略以及自定义。
block、pending和handler这三张表的每一个位置是一一对应的。

sigset_t及信号集操作函数

sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。

其实就是在系统中被定义的位图,我们直接把其当做c语言中的变量使用即可

修改位图就要修改其中的比特位,必然涉及大量的位操作,对于使用者的体验肯定是不好的,所以就有了信号集操作函数

#include <signal.h>int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set, int signum);int sigdelset(sigset_t *set, int signum);int sigismember(const sigset_t *set, int signum);  

sigemptyset函数:初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
sigfillset函数:初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
sigaddset函数:在set所指向的信号集中添加某种有效信号。
sigdelset函数:在set所指向的信号集中删除某种有效信号。
sigismember函数:判断在set所指向的信号集中是否包含某种信号,若包含则返回1,不包含则返回0,调用失败返回-1。

sigprocmask

sigprocmask函数可以用于读取或更改进程的信号屏蔽字(阻塞信号集),

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数说明:

如果oset是非空指针,则读取进程当前的信号屏蔽字通过oset参数传出。
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。

sigpending

sigpending函数可以用于读取进程的未决信号集,

int sigpending(sigset_t *set);

关于以上接口大家可以自己去用用,这里贴个小实验给大家了解一下大概的用法

先用上面的函数将2号信号进行阻塞,使用kill命令或组合按键向进程发送2号信号,此时2号信号会一直被阻塞,并一直处于未决状态,使用sigpending函数获取当前进程的pending信号集进行验证。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>void printPending(sigset_t *pending)
{int i = 1;for (i = 1; i <= 31; i++){if (sigismember(pending, i)){printf("1 ");}else{printf("0 ");}}printf("\n");
}
void handler(int signo)
{printf("handler signo:%d\n", signo);
}
int main()
{signal(2, handler);sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, 2); //SIGINTsigprocmask(SIG_SETMASK, &set, &oset); //阻塞2号信号sigset_t pending;sigemptyset(&pending);int count = 0;while (1){sigpending(&pending); //获取pendingprintPending(&pending); //打印pending位图(1表示未决)sleep(1);count++;if (count == 20){sigprocmask(SIG_SETMASK, &oset, NULL); //恢复曾经的信号屏蔽字printf("恢复信号屏蔽字\n");}}return 0;
}

信号的捕捉

拿完快递后我们会在合适的时候打开,同理进程也会在合适的时候处理信号,是在什么时候呢?

从内核态返回到用户态的时候,进行信号的检测和处理

在了解什么是内核态和用户态前,我们要先理解一下什么是内核空间和用户空间

用户空间和内核空间

每一个进程都有自己的进程地址空间,该进程地址空间由内核空间(3~4GB)和用户空间(1~3GB)组成

内核空间所存放的都是操作系统的代码和数据,所有进程看到的都是一样的内容

用户空间存放当前进程的代码和数据,每个进程看到的内容是不一样的(父子进程除外)

用户空间通过用户级页表与物理内存之间建立映射关系
内核空间通过内核级页表与物理内存之间建立映射关系

内核级页表是全局的,每个进程都用该页表去物理内存找os的代码和数据

而用户级页表是每个进程一份的,每个进程都用该页表去物理内存找该进程的代码和数据

用户态和内核态

在之前学习权限的时候我们就知道代码的执行是有权限的,假如不给权限你就无法完成一件事情

内核态通常用来执行操作系统的代码,是一种权限非常高的状态。
用户态是一种用来执行普通用户代码的状态,是一种受监管的普通状态

系统调用背后,就包含了进程身份的转变

进程收到信号之后,并不是立即处理信号,而是在合适的时候,这里所说的合适的时候实际上就是指,从内核态切换回用户态的时候

从用户态切换为内核态通常有如下几种情况:

  • 需要进行系统调用时。
  • 当前进程的时间片到了,导致进程切换。
  • 产生异常、中断、陷阱等。

与之相对应,从内核态切换为用户态有如下几种情况:

  • 系统调用返回时。
  • 进程切换完毕。
  • 异常、中断、陷阱等处理完毕。

进程默认是在用户态的,而在调用系统调用的时候,就会从用户态切换成内核态,然后通过在内核空间里的虚拟地址,通过内核级页表和MMU去物理内存中找到相应的代码和数据并执行

当进程收到的信号是默认信号的时候,如果是默认动作,那么把相应的pending表的对应位置置为1 之后,就会去找在内核空间的handler表并执行对应的代码

画圈的地方就是状态切换的地方 

而如果信号被自定义捕捉的话,就要从内核态切换为用户态,去执行自定义的放法,执行完通过系统调用sigreturn返回到内核态

巧记

整体过程就是一个无穷符号!

如果有多个信号要处理,在处理完前面信号返回到内核态的时候,继续进行信号的检测,执行对应的方法,如此循环直到pending表为空,再返回到用户态,继续往下执行代码

为什么不能把自定义捕捉的函数放在内核空间中呢?

因为内核态处于很高的一种状态,有些用户态执行不了的方法它也能执行,为了防止该自定义函数用较高权限乱操作,例如删除数据库等操作,因为内核态的权限足够高可以支持它完成这项操作,所以要将自定义函数放在用户空间中,这样就能防止上面情况发生

os怎么知道该进程当前是处于用户态还是内核态的呢?

cpu中有相应的状态寄存器的某个位置可以标记,该位置可以被os识别并转换信息,例如0为用户态1为内核态,那么根据该位置的值就知道该进程是处于什么状态了

那么问题就来了,如果整个代码没有调用系统调用接口,该进程就不会切换成内核态,就不会进行信号的检测和处理了吗?

当然不是的!进程都是有相应的时间片的,一个进程的时间片到了cpu就要去调度下一个进程了,当前进程的时间片到了,导致进程切换也是要进入内核态的

总结

至此信号的讲解就结束了,本文从三个方面——信号的产生,保存和处理来进行分析,希望大家能对信号有个全面而又清晰的认识

本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流!希望大家多多点赞转发支持一下

相关文章:

【Linux从青铜到王者】进程信号

——————————————————————————————————————————— 信号入门 在了解信号之前有许多要理解的相关概念 我们可以先通过一个生活例子来初步认识一下信号 1.生活角度的信号 你在网上买了很多件商品&#xff0c;再等待不同商品快递的到来…...

MyBatis-Plus 快速入门

介绍 j​​​​​MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 官网&#xff1a;MyBatis-Plus (baomidou.com) 1.…...

iOS调起高德/百度/腾讯/谷歌/苹果地图并使用GCJ02坐标进行导航

使用演示: 2.地图API相关网站 : 高德:...

HarmonyOS Full SDK的安装

OpenHarmony的应用开发工具HUAWEI DevEco Studio现在随着OpenHarmony版本发布而发布,只能在版本发布说明中下载,例如最新版本的OpenHarmony 4.0 Release。对应的需要下载DevEco Studio 4.0 Release,如下图。 图片 下载Full SDK主要有两种方式,一种是通过DevEco Studio下载…...

小程序嵌套H5-真机突然无法使用

今天测试反馈了一个问题&#xff0c;测试环境的小程序突然就登录不了了。我自己拿手机扫码登录是正常的&#xff0c;用其他同事的手机扫描登录也是正常。 下面是排查的路线&#xff1a; 1、其他环境使用测试手机扫码登录是否正常&#xff1f;(正常) 2、H5地址改为本地IP&#…...

自然语言处理 | 语言模型(LM) 浅析

自然语言处理&#xff08;NLP&#xff09;中的语言模型&#xff08;Language Model, LM&#xff09;是一种统计模型&#xff0c;它的目标是计算一个给定文本序列的概率分布&#xff0c;即对于任意给定的一段文本序列&#xff08;单词序列&#xff09;&#xff0c;语言模型能够估…...

全量知识系统问题及SmartChat给出的答复 之13 解析器+DDD+文法型

Q32. DDD的领域概念和知识系统中设计的解析器之间的关系。 那下面&#xff0c;我们回到前面的问题上来。 前面说到了三种语法解析器&#xff0c;分别是 形式语言的&#xff08;机器或计算机语言&#xff09;、人工语言的和自然语言的。再前面&#xff0c;我们聊到了DDD设计思…...

华中某科技大学校园网疑似dns劫持的解决方法

问题 在校园网ping xxx.ddns.net&#xff0c;域名解析失败 使用热点ping xxx.ddns.net&#xff0c;可以ping通 尝试设置windows dns首选dns为114.114.114.114&#xff0c;重新ping&#xff0c;仍然域名解析失败 猜测【校园网可能劫持dns请求】 解决方法 使用加密的dns请求…...

模型部署 - onnx 的导出和分析 -(1) - PyTorch 导出 ONNX - 学习记录

onnx 的导出和分析 一、PyTorch 导出 ONNX 的方法1.1、一个简单的例子 -- 将线性模型转成 onnx1.2、导出多个输出头的模型1.3、导出含有动态维度的模型 二、pytorch 导出 onnx 不成功的时候如何解决2.1、修改 opset 的版本2.2、替换 pytorch 中的算子组合2.3、在 pytorch 登记&…...

【鸿蒙 HarmonyOS 4.0】多设备响应式布局

一、背景 在渲染页面时&#xff0c;需要根据不同屏幕大小渲染出不同的效果&#xff0c;动态的判断设备屏幕大小&#xff0c;便需要采用多设备响应式布局。这种设计方法能够动态适配各种屏幕大小&#xff0c;确保网站在不同设备上都能呈现出最佳的效果。 二、媒体查询&#xf…...

Android ANR 日志分析定位

ANR 是 Android 应用程序中的 "Application Not Responding" 的缩写&#xff0c;中文意思是 "应用程序无响应"。这是当应用程序在 Android 系统上运行时&#xff0c;由于某种原因不能及时响应用户输入事件或执行一个操作&#xff0c;导致界面无法更新&…...

Optional 详解

Optional 详解 1、Optional 介绍2、创建 Optional 对象3、Optional 常用方法1. 判断值是否存在 — isPresent()2. 非空表达式 — ifPresent()3. 设置(获取)默认值 — orElse()、orElseGet()4. 获取值 — get()5. 过滤值 — filter()6. 转换值 — map() 作为一名 Java 程序员&am…...

(科目三)数据库基础知识

1、基本概念 1.1 数据库 1、数据、信息和数据处理 数据是指表达信息的某种物理符号&#xff1b; 信息是对客观事物的反映&#xff0c;是为某一特定目的二提供的决策数据&#xff1b; 数据处理是指将数据转换成信息的过程&#xff0c;是对各类型的数据进行收集、整理、存储、…...

Unity性能优化篇(十) 模型优化之网格合并 Easy Mesh Combine Tool插件使用以及代码实现网格合并

把多个模型的网格合并为一个网格。可以使用自己写代码&#xff0c;使用Unity自带的CombineMeshes方法&#xff0c;也可以使用资源商店的插件&#xff0c;在资源商店搜Mesh Combine可以搜索到相关的插件&#xff0c;例如Easy Mesh Combine Tool等插件。 可大幅度减少Batches数量…...

0.8秒一张图40hx矿卡stable diffusion webui 高质极速出图组合(24.3.3)

新消息是。经过三个月的等待&#xff0c;SD Webui (automatic1111)终于推出了新版本1.8.0&#xff0c;本次版本最大的更新&#xff0c;可能就是pytorch更新到2.1.2, 不过还是晚了pytorch 2.2.2版。 不过这版的一些更新&#xff0c;在forget分支上早就实现了&#xff0c;所以。…...

手写分布式配置中心(四)增加实时刷新功能(长轮询)

上一篇文章中实现了短轮询&#xff0c;不过短轮询的弊端也很明显&#xff0c;如果请求的频率较高&#xff0c;那么就会导致服务端压力大&#xff08;并发高&#xff09;&#xff1b;如果请求的频率放低&#xff0c;那么客户端感知变更的及时性就会降低。所以我们来看另一种轮询…...

03 | 事务隔离:为什么你改了我还看不见?

提到事务&#xff0c;你肯定不陌生&#xff0c;和数据库打交道的时候&#xff0c;我们总是会用到事务。最经典的例子就是转账&#xff0c;你要给朋友小王转 100 块钱&#xff0c;而此时你的银行卡只有 100 块钱。 转账过程具体到程序里会有一系列的操作&#xff0c;比如查询余…...

Jmeter读取与使用Redis数据

Jmeter 作为当前非常受欢迎的接口测试和性能测试的工具&#xff0c;在企业中得到非常广泛的使用&#xff0c;而 Redis 作为缓存数据库&#xff0c;也在企业中得到普遍使用&#xff0c; Redis服务和客户端安装 windows下安装 默认端口&#xff1a;6379 下载地址&#xff1a; …...

flask 支持跨域访问 非常简单的方式 flask_cors

安装 pip install -U flask-cors from flask import Flask from flask_cors import CORSapp Flask(__name__) CORS(app)app.route("/") def helloWorld():return "Hello, cross-origin-world!"参考 https://www.cnblogs.com/anxminise/p/9814326.html …...

Hololens 2应用开发系列(1)——使用MRTK在Unity中设置混合现实场景并进行程序模拟

Hololens 2应用开发系列&#xff08;1&#xff09;——使用MRTK在Unity中进行程序模拟 一、前言二、创建和设置MR场景三、MRTK输入模拟的开启 一、前言 在前面的文章中&#xff0c;我介绍了Hololens 2开发环境搭建和项目生成部署等相关内容&#xff0c;使我们能生成一个简单Ho…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

华为OD机试-最短木板长度-二分法(A卷,100分)

此题是一个最大化最小值的典型例题&#xff0c; 因为搜索范围是有界的&#xff0c;上界最大木板长度补充的全部木料长度&#xff0c;下界最小木板长度&#xff1b; 即left0,right10^6; 我们可以设置一个候选值x(mid)&#xff0c;将木板的长度全部都补充到x&#xff0c;如果成功…...

Chrome 浏览器前端与客户端双向通信实战

Chrome 前端&#xff08;即页面 JS / Web UI&#xff09;与客户端&#xff08;C 后端&#xff09;的交互机制&#xff0c;是 Chromium 架构中非常核心的一环。下面我将按常见场景&#xff0c;从通道、流程、技术栈几个角度做一套完整的分析&#xff0c;特别适合你这种在分析和改…...

k8s从入门到放弃之HPA控制器

k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率&#xff08;或其他自定义指标&#xff09;来调整这些对象的规模&#xff0c;从而帮助应用程序在负…...

算术操作符与类型转换:从基础到精通

目录 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符&#xff1a;、-、*、/、% 赋值操作符&#xff1a;和复合赋值 单⽬操作符&#xff1a;、--、、- 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...