系统级基础信号知识【Linux】

目录

一,什么是信号 

进程面对信号常见的三种反应概述

二,产生信号

1.终端按键产生信号

signal

2. 进程异常产生信号

核心转储

3. 系统调用函数发送信号

kill

raise

abort

小结:

4. 由软件条件产生

alarm

5. 硬件异常产生信号

三,信号其他概念

1. 进程中储存信号的内核结构

2. sigset_t类型——信号集类型

3. sigpending接口

4. sigprocmask接口

5. 重新理解进程在计算机中的运行

四,捕捉信号

1. 捕捉信号流程

​编辑

2. sigaction

关键字——volatile

SIGCHLD信号


嘿!收到一张超美的风景图,希望你每天都能开心顺心! 

一,什么是信号 

操作系统中的信号是一种在进程间传递信息和通知的机制。它可以用来通知进程发生了某种事件,比如用户按下了某个键盘按键、进程收到了某个信号或者发生了某个错误等。

生活中的例子:

  1. 手机收到新短信或来电时会发出提示音,这就是一种信号,通知用户有新的事件发生。
  2. 交通信号灯会发出红、黄、绿三种不同的信号,指示车辆和行人何时可以通行。
  3. 火灾报警器发出警报声,通知人们有火灾发生,需要立即疏散。
  4. 门铃响起,通知主人有人来访。
  5. 警报器在发现入侵者时会发出警报声,通知屋主有危险。

进程面对信号常见的三种反应概述

可选的处理动作有以下三种 :
1. 忽略此信号。
2. 执行该信号的默认处理动作。
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。

这里我们以:kill  信号为例

我们平时手动结束一个进程: kill -9  PID, 本质上是让操作系统向目标进程发送 9信号,从而结束该进程。下面是kill 相关的信号表:

以上普通信号,我们认识七八个即可

指令:man  7  signal

通过man 7 signal, 我们可以查看信号的详细信息

试问:如何理解键盘中,组合键如何实现其功能? 

答: 首先我们得知道,键盘的工作方式:通过中断的方式产生信息。同时,操作系统中也一定有识别该组合键产生信息的记录表。

一个进程,在接受信号后,必然会对信号进程储存。进程则是通过PCB(task_struct)中位图unsigned  int来记录,信号是否存在。

而PCB又是内核数据结构,能修改PCB的也就只有OS自身。

即:信号发送的本质是,OS对目标进程的PCB中信号位图的修改

回到组合键的问题: 

二,产生信号

1.终端按键产生信号

signal

signum:  捕获该进程信号

handler :  信号处理方法(函数指针)

比如这样:

void signalmain(int signal)
{cout << "信号处理中...  : " << signal << "  gitpid :" << getpid() << endl;
}int main()
{signal(SIGINT, signalmain);while ( 1){cout << "接受信号中...... " << endl; sleep(1);}return 0;
}

运行过程中,我们不断通过,ctrl + c的组合键进行操作。运行结果如下; 

从上面我们可以得出2个点:1.键盘的组合键确实是系统向当前进程发送信号。 2. 可以通过signal注册信号处理函数。

(signal使用须知:signal接口,并不是调用就会触发信号处理方法,它只是提前注册了信号处理函数;只有捕捉到特定信号时才会调用特定方法;  signal接口一般出现在main函数开始。)

2. 进程异常产生信号

核心转储

这个在进程控制,waitpid函数,status参数中提到过:

大概就是这样:在进程发生异常退出时,能进行核心转储,形成一个二进制的特殊文件

解释一下,为什么云服务器核心转储默认是关闭?:因为服务器的管理,是有另外一层服务负责,他们的任务是将异常挂掉的服务进程自动重启如果因为进程老是异常挂掉,这会导致磁盘中存在大量的转储文件,会导致资源浪费。 

我们在终端,再次打开 man  7   signal, 文档中core的意思就是核心转储

3. 系统调用函数发送信号

kill

我们在终端输入kill -9  PID等等信号,在底层是调用了kill系统函数。kill也很简单。

kill : 进程 PID

sig:  信号编号

raise

功能很简单,就是让OS向自身进程发送信号

abort

功能: 终止自身进程(相当于向自身进程发送:kill -6)

小结:

上面这些接口,本质上都是利用系统接口调用,执行OS对应的系统调用代码,OS在PCB中设置或者是修改特定的值,进程对信号进行处理。

4. 由软件条件产生

例子: 

我们以曾经的管道为例,如果读端不仅没读,而且将读端关闭;写端一直在写。作为单向通信,写已经没有意义了,OS会终止写进程,发送SIGPIPE(13),这构成不了软件级的条件。 

alarm

 

 功能:就是过 seconds 后,OS将自动向该进程发送SIGALRM(14)信号。

注意:当进程开始,当alarm被触发后向进程发送信号,但我们要注意,alarm只是发送SIGALRM信号,并不会阻断进程正常进行

5. 硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
例如:当前进程执行了除以0的指令, CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。
void func(int st)
{cout << "接受到信号: " << st << endl;
}int mian()
{signal(SIGFPF, func);int count = 150;z = count / 0;while(1) {sleep(1);}
}

 现象:会一直打印 “接受到信号: 8”

1. 首先我们得思考,这个信号是谁发出的呢?
CPU运算单位异常详细解释:CPU内部有寄存器,其中一个状态寄存器(位图存储信息),有对应的状态标记位,溢出标记位。OS会自动进行计算检测(先检测再计算),如果状态标记位是1(可以理解为是否异常),OS会立即调取进程PID,向目标进程发送信号,然后就是进程会选择合适的时机处理信号。因此, 一些运算,并不都是软件层产生的信号,一些则是硬件层产生的信号。

2,硬件出现异常,进程就一定会退出吗?  

     不一定,如果我们没有捕获信号,那么进程默认退出;而捕获后,我们的就可以控制进程是否退出。

3. 为什么上面代码,会进入死循环??

     在捕获信号后,信号处理中并未退出进程,该进程还在CPU运行队列中,将会被再次调度,当再次调度时OS还会继续检测,会继续发送信号,然后被捕获处理,接着继续在CPU运行队列中。

再比如:当前进程访问了非法内存地址,MMU(硬件)会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

注: 野指针的异常,常常会有段错误: Segmentation fault

void func(int st)
{cout << "接受到信号: " << st << endl;
}int mian()
{signal(SIGSEGV, func);int *count = nullptr;*count  = 100;while(1) {sleep(1);}
}

现象还是:死循环打印 “接受到信号: 11"

1. 如何理解地址访问??

     首先我们访问一个数据目标,我们一定得访问其物理地址。那么中间会有一段虚拟地址转换为物理地址的过程,由页表(并不是软件结构,而是一种硬件) + MMU(Memory Manager  Unit, 是一种硬件) , 当错误的地址被MMU(硬件寄存器)读取后,一定会报错,OS将MMU的报错转换为信号发送给进程,让其退出。

2. 死循环原因??

     因为状态寄存器储存进程的状态,在信号发送完一次后,再次被调度时,检测到进程状态寄存器中的异常,则又会发送信号,然后被切换保存进程上下文,就这样一直继续下去。

三,信号其他概念

实际执行信号的处理动作,称为 信号递达(Delivery)
信号从产生到递达之间的状态,称为 信号未决(Pending)
进程可以选择 阻塞 (Block )某个信号( 一般情况下,进程不会阻塞任何一种信号)。
被阻塞的信号产生时将 保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

1. 进程中储存信号的内核结构

我们可以回想,之前是使用的signal系统接口:

结合上图,可以这么理解:sig就是pending中,对应信号的位置;第二参数位,我们暂时叫func, func就是对handler[sig]中设置处理函数的地址。

上面是对信号的自定义处理

而执行默认处理

signal(SIGSEGV, SIG_DFL); // 对应的下标是0

忽略处理

signal(SIGSEGV, SIG_IGN); // 下标为1

这有一点需要注意的是:当OS检测到进程的一个信号,下标值为signalsum,并不是直接handler[signalsum]直接访问,而是先比较是否是SIG_DEL(0)或者SIG_IGN(1),再比较自定义处理

2. sigset_t类型——信号集类型

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储, sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的 信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。( 总结:block, pending都可以用sigset_t类型表示
sigset_t类型的理解:
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从 使用者的角度是不必关心的,使用者只能 调用函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用 printf直接打印sigset_t变量是没有意义的

信号集处理,相关函数:

#include <signal.h>
int sigemptyset(sigset_t *set);  // 将信号集全设置为0,信号集初始化。
int sigfillset(sigset_t *set);        // 将信号集设置为1
int sigaddset (sigset_t *set, int signo);  // 添加某一信号
int sigdelset(sigset_t *set, int signo);    // 删除某一信号
int sigismember (const sigset_t *set, int signo); //   判断一个信号集的有效信号中是否包含某种 信号, 若包含则返回 1, 不包含则返回 0, 出错返回 -1

3. sigpending接口

功能: 读取当前进程的未决信号集 , 通过 set参数传出 。调用成功则返回 0, 出错则返回 -1 。 

4. sigprocmask接口

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

set: 是我们自定义的一个信号集。

how:  传入的set,对该进程的阻塞信号集进行怎样的操作,比如:添加屏蔽字,删除屏蔽字,覆盖屏蔽字。

oset:  传入一个新set,  储存修改前旧的阻塞信号集

how可选值:

实践:

void cmpshow(const sigset_t& set)
{for (int i = 1; i <= 31; i++){if (sigismember(&set, i)){cout<< "1";}else cout << "0";}cout << endl;
}int main()
{sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, 2); // 目标阻塞信号 2sigpending(&oset);int n = sigprocmask(SIG_BLOCK, &set, &oset);assert(n == 0);  // n 必然==0(void)n;  // 目的是调用一次n,避免在release版本中,n未被调用的警告while (1){sigset_t tmp;sigemptyset(&tmp);sigpending(&tmp);cmpshow(tmp);sleep(1);} return 0;
}

 问:为什么没有设置pending信号集的接口?? 答:没必要,像kill, raise,  abort指令接口都可以修改pending。

小结:我们的进程中的信号,都有各自接口负责管理,处理函数——signal; 信号未决表——sigpending;  阻塞信号集——sigprocmask。

代码知识加餐:

  int n = sigprocmask(SIG_BLOCK, &set, &oset);assert(n == 0);    // n 必然==0(void)n;           // 目的是调用一次n,避免在release版本中,n未被调用的警告

问题1,既然进程可以自己捕捉信号,那我们让进程能捕获任何信号,并且全部阻塞信号,那这样就可以制作一个无法被动退出的进程了吗??

回答:OS的设计者已经考虑到这种情况了,所以解决方法是:kill -9  PID 这个信号是管理者信号无法被阻塞,也无法修改其处理方法(指结束进程)

5. 重新理解进程在计算机中的运行

四,捕捉信号

1. 捕捉信号流程

如果信号的处理动作是用户自定义函数 , 在信号递达时就调用这个函数 , 这称为捕捉信号。由于信号处理函数的代码是在用户空间的, 处理过程比较复杂 , 举例如下 : 用户程序注册了 SIGQUIT 信号的处理函数 sighandler 。 当前正在执行main函数 , 这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的 main 函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复 main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间 , 它们之间不存在调用和被调用的关系, 两个独立的控制流程 sighandler 函数返回后自动执行特殊的系统调用sigreturn 再次进入内核态。 如果没有新的信号要递达 , 这次再返回用户态就是恢复main函数的上下文继续执行了

疑问:为什么在内核层山时,不直接调用信号处理函数呢?

答:如果信号处理函数中存在非法操作,那么贸然让计算机内核进行访问数据,计算机数据安全无法得到保证。

2. sigaction

功能:  检查或者修改信号处理方法。平时用的最多的是signal 用法简单易上手。

sig :  目标信号

struct  sigaction * :一种放多种信息的结构体,其中就包括自定义函数处理方法sa_handler

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>void sigint_handler(int signo) {printf("Caught SIGINT, exiting...\n");exit(1);
}int main() {struct sigaction sa;// 对结构体内数据初始化sa.sa_handler = sigint_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;    if (sigaction(SIGINT, &sa, NULL) == -1) {perror("sigaction");exit(1);}printf("Press Ctrl+C to send SIGINT...\n");while (1) {// Do some work}return 0;
}
当某个信号的处理函数被调用时 , 内核自动将当前信号加入进程的信号屏蔽字 , 当信号处理函数返回时自动恢复原来的信号屏蔽字, 这样就保证了在处理某个信号时 , 如果这种信号再次产生 , 那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时, 除了当前信号被自动屏蔽之外 , 还希望自动屏蔽另外一些信号 , 则用 sa_mask 字段说明这些需要额外屏蔽的信号, 当信号处理函数返回时自动恢复原来的信号屏蔽字。

关键字——volatile

该关键字在C当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下。
示例代码:
int tmp = 0;void func(int sig)
{cout << "tmp: " << tmp << "-->";tmp = 1;cout << tmp << endl;
}int main()
{signal(2, func);while (!tmp);return 0;
}// 编译
signal : signal.ccg++  -std=c++11 -o $@ $^ -O3 -g# -O3 ————编译器对代码进行三级优化./PHONY: clean
clean:	rm -rf signal

现象:进程开始运行后,进行ctrl +  c,进程结束,一切正常。但未来我们的代码会跑在各种各样的编译器上,其中一些优化就会影响这个过程。这里就直接说了,在编译时,添加 -o3  进行三级优化,由于tmp没有进行写入操作,寄存器直接用0代替了tmp,这就会导致我们使用 ctrl  +  c,无法终止循环。而  volatile(易变的)  就是提醒计算机,请不要优化该数据

因此,用  volatile 修饰即可tmp即可。 

SIGCHLD信号

进程一章讲过用wait waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束 也可以非阻塞地查询是否有子进程结束等待清理( 也就是轮询的方式——查看子进程是否发来信号 )
采用第一种方式, 父进程阻塞了就不能处理自己的工作了;
采用第二种方式, 父进程在处理自己的工作的同时还要记得时不时地轮询一 下, 程序实现复杂。
其实, 子进程在终止时会给父进程发 SIGCHLD 信号, 该信号的默认处理动作是忽略, 父进程可以自定义SIGCHLD 信号的处理函数 这样父进程只需专心处理自己的工作, 不必关心子进程了, 子进程终止时会通知父进程, 父进程在信号处理函数中调用wait 清理子进程即可。

下期:多线程

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

WEB服务器介绍

Web服务器是指驻留于因特网上某种类型计算机的程序。当Web浏览器连到服务器上并请求文件时&#xff0c;服务器将处理该请求并将文件发送到该浏览器上&#xff0c;附带的信息会告诉浏览器如何查看该文件&#xff0c;即文WEB服务器件类型。服务器使用HTTP进行信息交流&#xff0c…

Java之异常

一、异常是什么 程序在执行过程中&#xff0c;出现的非正常的情况&#xff0c;最终会导致JVM的非正常停止。 注意&#xff1a;异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行. 二、异常体系 三、异常的分类 &#xff08;一&#xff09;、编译时…

明懿金汇应对气候变化:投资于绿色未来

2023年&#xff0c;面对全球范围内的气候变化和环境保护挑战&#xff0c;明懿金汇积极响应&#xff0c;展现出其在可持续金融领域的领导力。作为一家前沿的金融科技公司&#xff0c;明懿金汇不仅将环保理念融入到其金融产品和服务中&#xff0c;更通过直接的行动和投资&#xf…

【Python】conda镜像配置,.condarc文件详解,channel镜像

1. conda 环境 安装miniconda即可&#xff0c;Miniconda 安装包可以到 http://mirrors.aliyun.com/anaconda/miniconda/ 下载。 .condarc是conda 应用程序的配置文件&#xff0c;在用户家目录&#xff08;windows&#xff1a;C:\users\username\&#xff09;&#xff0c;用于…

用提问的方式来学习:冯·诺伊曼体系结构与操作系统OS

学习冯诺伊曼体系结构之前&#xff0c;我们要本着两个问题来学习&#xff1a; 什么是冯诺伊曼体系结构&#xff1f;为什么要有冯诺伊曼体系结构&#xff1f; 一、冯诺伊曼体系结构 1. 什么是冯诺伊曼体系结构&#xff1f; 那我们就先来回答一下什么是冯诺伊曼体系结构&#x…

Python中的TesserOCR:文字识别的全方位指南

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 文字识别在图像处理领域中起到了至关重要的作用&#xff0c;而TesserOCR&#xff08;Tesseract OCR的Python封装&#xff09;为开发者提供了一个强大的工具&#xff0c;使得文字识别变得更加便捷。本文将通过详细…

Matlab示例-Examine 16-QAM Using MATLAB学习笔记

​工作之余学习16-QAM 写在前面 网上看到许多示例&#xff0c;但一般都比较难以跑通。所以&#xff0c;还是老方法&#xff0c;先将matlab自带的例子研究下。 Examine 16-QAM Using MATLAB Examine 16-QAM Using MATLAB 或者&#xff0c;在matlab中&#xff0c;键入&#x…

C语言数据结构-二叉树的入门

文章目录 0 碎碎念1 二叉树的概念和结构1.1 概念和特点1.2 结构1.3 特殊的二叉树1.4 二叉树的存储与性质1.5 前序、中序和后序 2 简单二叉树的实现2.1 定义数据结构类型2.2 前序、中序和后序接口的实现2.3 二叉树中节点的个数2.4 叶子节点的个数 3 完整代码块3.1 BinaryTree.h3…

「神印王座」皓晨带伙伴参与伊老试炼,12魔神攻打震南关,高能

Hello,小伙伴们&#xff0c;我是拾荒君。 时光匆匆&#xff0c;国漫《神印王座》的第85集已经与大家如约而至。想必各位观众都已经迫不及待地观看了这一集&#xff0c;其中&#xff0c;龙皓晨向光之晨曦团的成员们揭示了永恒之塔的秘密&#xff0c;并带领他们深入其中。 永恒之…

nginx服务前端访问查看无响应的问题

问题 nginx 启动之后&#xff0c;前端访问无数据&#xff0c;F12 查看&#xff0c;提示挂起。 以为是配置问题&#xff0c;查看配置文件&#xff0c;未发现配置的有问题。 原因 通过查看配置文件&#xff0c;发现转发的服务地址为127.0.0.1&#xff0c;手动ping 127.0.0.1&a…

如何部署Portainer容器管理工具+cpolar内网穿透实现公网访问管理界面

文章目录 前言1. 部署Portainer2. 本地访问Portainer3. Linux 安装cpolar4. 配置Portainer 公网访问地址5. 公网远程访问Portainer6. 固定Portainer公网地址 前言 本文主要介绍如何本地安装Portainer并结合内网穿透工具实现任意浏览器远程访问管理界面。Portainer 是一个轻量级…

Unity 关于Rigidbody刚体组件的理解

一、基本了解 刚体Rigidbody因具体物理相关的属性&#xff0c;使得实际应用中更有真实感。应用也多&#xff1a; Rigidbody它可以受到重力、碰撞或者力的作用&#xff0c;所以我们可以用它模拟物体的真实物理行为&#xff0c;如受到重力的作用、与其他刚体对象进行碰撞&#…

ChatGPT Plus重新开启订阅

12月14日凌晨&#xff0c;OpenAI首席执行官Sam Altman在社交平台宣布&#xff0c;终于找到了更多的GPU算力&#xff0c;重新开启订阅ChatGPT Plus。 上个月15日&#xff0c;OpenAI就因为算力不足&#xff0c;以及用户激增等原因暂停了ChatGPT Plus订阅。 Sam表示&#xff0c;在…

【九】python模板方法模式

9.1 模板方法模式概述 模板方法模式是一种行为设计模式&#xff0c;它使用一个抽象的基类定义了一个操作中的算法的骨架&#xff0c;而将一些步骤的实现延迟到子类中。模板方法模式允许子类在不改变算法结构的情况下重新定义算法中的某些步骤。 9.2 代码示例 在Python中使用…

【亚马逊云科技】通过高性能低延迟对象存储 S3实现网站资源托管

本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 前言1 S3 介绍1.1 优点 2 使用步骤2.1 注册账户2.2 创建存储桶2.2.1 打开控制…

智能指针管理“newed对象”

为什么要有智能指针&#xff1f; 指针智能是管理管理动态内存分配对象的一种机制。它提供了自动管理内存&#xff0c;避免常见内存泄漏和悬空指针。 对于上述Func函数的操作&#xff0c;一不小心就会产生很多问题。 p1 new时候抛异常 什么都不做p2 new时候抛异常 p1需要被清理…

深入理解JVM虚拟机第三十篇:详解JVM当中栈帧的一些附加信息以及虚拟机栈的5个面试题

😉😉 欢迎加入我们的学习交流群呀: ✅✅1:这是孙哥suns给大家的福利! ✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 🥭🥭3:QQ群:583783824 📚📚 工作微信:BigTreeJava 拉你进微信群,免费领取! 🍎🍎4:本文章…

如何在Ubuntu的Linux系统上搭建nacos集群

官方给出的集群部署架构图 集群部署说明 (nacos.io)3个或3个以上nacos节点才能构成集群当前示例中包含3个nacos节点&#xff0c;同时一个负载均衡器代理3个nacos&#xff0c;本示例中负载均衡器可使用的是nginx 准备并安装好正常运行的nginx&#xff0c;本示例略准备并安装好正…

Redis权限管理体系(一):客户端名及用户名

在Redis6之前的版本中&#xff0c;因安全认证的主要方式是使用Redis实例的密码进行基础控制&#xff0c;而无法按照不同的应用来源配置不同账号以及更细粒度的操作权限控制来管理。本文先从client list中的信息入手&#xff0c;逐步了解Redis的客户端名设置、用户设置及权限控制…

【Monitor, Maintenance Operation, Script code/prgramme】

Summary of M,M&O,Program JD) Monitor & M&O Symbio信必优) Job chance/opportunities on Dec 12th, 20231.1) Content 招聘JD job description:1.2) suggestions from Ms Liang/Winnie on Wechat app1.3) Java微服务是什么&#xff1f;1.3.1) [URL Java 微服务](…