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

[Linux打怪升级之路]-信号的保存和递达

前言

作者小蜗牛向前冲

名言我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

目录

一、信号的保存 

1、信号其他相关常见概念

2、信号在内核中的表示

3、sigset_t

4. 信号集操作函数 

二、 模仿实现内核对信号的保存

1、信号函数

 2、实验代码

三、信号的的捕捉 

1、内核态和用户态

2、信号的捕捉流程 

四、信号的补充知识

 1、sigaction函数

2、可重入函数

3、 volatile关键字


本期学习目标:信号的保存,信号的捕捉,什么是可重入函数和关键字volatile。

一、信号的保存 

1、信号其他相关常见概念

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

2、信号在内核中的表示

信号在内核中的表示示意图

        每个信号都有两个标志位分别表示阻塞(block)未决(pending),还有一个函数指针表示处理动作。

从位图中我们理解pending位图和信号的关系, 操作系统发信号,本质也就是将信号写入到pending位图中。pending位图中的比特位的内容也就表征了是否收到该信号

对于block位图:比特位的位置也是代表,信号的编号,但是比特位的内容表示是否阻塞对应的信号。如果阻塞了就不在执行该信号,除非解除阻塞。 

if((1<<(signo -1)) & pcb->block)
{//signo信号被阻塞,不递达
}
else
{if((1<<(signo -1)) & pcb->pending){//递达该信号}
}

 上面我们写了一份伪代码,来理解内核是如何大致处理信号未决到递达的过程信号阻塞

其中在内核中还有一个handler的数组用来存放信号,其中数组的下标表示信号的编号,数组

下标对应的内容表示信号的内容。

所以:当一个信号没有产生这并不妨碍他可以先被阻塞。

3、sigset_t

        从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。

        因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号 的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。

     阻塞信号集也叫做当 前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

4. 信号集操作函数 

        sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统 实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做 任何解释,比如用printf直接打印sigset_t变量是没有意义的 。

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo); 
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有 效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系 统支持的所有信号。

注意:在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号

这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。 

二、 模仿实现内核对信号的保存

     为了更好的理解,内核如何进行对信号的保存的,下面我们自己写一份代码,去验证信号在内核中的保存。

1、信号函数

sigprocmask函数

功能:可以读取或更改进程的信号屏蔽字(阻塞信号集)。

原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oset)

返回值:若成功则为0,若出错则为-1

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

上面一直说信号屏蔽字,那这到底有上面用?

信号屏蔽字是一个位掩码,用于指定哪些信号被阻塞(屏蔽)而不会被递送给进程。当某个信号被屏蔽时,它将被搁置,直到信号解除屏蔽为止。这允许进程在关键部分屏蔽某些信号,以确保在执行临界区代码时不会被中断。 

对于sigprocmask函数,当假设当前屏蔽字为mask 下表说明了how参数的可选值.

SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
SIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask=se

sigpending 函数

功能:读取当前进程的未决信号集,通过set参数传出

原型:int sigpending(sigset_t *set)

返回值:调用成功则返回0,出错则返回-1。

 2、实验代码

这里我们验证2号信号为例(ctrl+c)

#include<iostream>
#include<vector>
#include<signal.h>
#include<unistd.h>
#include<stdio.h>using namespace std;
#define MAX_SIGNUM 31static vector<int> sigarr = {2};static void show_pending(const sigset_t &pending)
{for(int signo = MAX_SIGNUM; signo >= 1;signo--){if(sigismember(&pending,signo)){cout<< "1";}else{cout << "0";}}cout << endl;
}static void myhandler(int signo)
{cout << signo << " 号信号已经被递达!!" << endl;
}int main()
{for(const auto &sig : sigarr) signal(sig, myhandler);sigset_t block, oblock, pending;//初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//添加要屏蔽的信号for(const auto& sig : sigarr) sigaddset(&block,sig);//开始屏蔽,设置内核sigprocmask(SIG_SETMASK,&block,&oblock);//遍历打印penging信号集int cnt = 10;while(true){//初始化sigemptyset(&pending);//获取sigpending(&pending);//打印show_pending(pending);sleep(1);printf("%d\n",cnt);if(cnt-- == 0){sigprocmask(SIG_SETMASK,&oblock,&block);cout << "恢复对信号的屏蔽,不屏蔽任何信号"<<endl;}}return 0;
}

 

三、信号的的捕捉 

1、内核态和用户态

在理解信号捕捉流程前,我们要明白什么是内核态和用户态:

  1. 内核态(Kernel Mode):

    • 权限高: 内核态拥有系统的最高权限,可以执行特权指令和访问系统的所有资源。
    • 操作系统内核运行: 在内核态下,操作系统的内核代码运行,可以执行对硬件的直接访问和控制。
    • 敏感指令: 内核态可以执行一些敏感指令,如修改全局页表、禁止中断等。
    • 特权级别: 通常,内核态运行在较高的特权级别(Ring 0或Supervisor Mode),这是计算机体系结构(如x86)中的一个常见术语。
  2. 用户态(User Mode):

    • 权限低: 用户态拥有较低的权限,受到更多的限制,无法直接访问底层硬件资源。
    • 用户应用程序运行: 在用户态下,用户应用程序运行,其执行受到操作系统的控制和限制。
    • 受限指令: 用户态下的程序不能直接执行一些特权指令,如修改页表、禁止中断等。
    • 特权级别: 用户态通常运行在较低的特权级别(Ring 3或User Mode)。

       在正常的程序执行中,处理器在用户态和内核态之间进行切换。当应用程序需要执行需要更高权限的操作时(如访问硬件、执行特权指令),会触发一个从用户态到内核态的切换。这通常通过系统调用(system call)来实现,应用程序请求操作系统执行某些特权操作,操作系统会在内核态执行相应的服务例程。 

在进行切换身份进行系统调用的时候,调用的人是进程,但是身份是内核。系统调用是比较占用时间的,所以我们应该尽量频繁的使用系统调用。

理解什么是内核态和用户态

那一个进程,是怎么跑到OS中去执行方法的呢?

 这是因为每个进程都有自己的地址空间(用户独占的空间)内核空间(被映射到了每个进程的3~4G),

这时进程要访问OS的接口,只要在自己的地址空间上跳转就好了

注意:每个进程都会3~4GB地址空间,都会共享一个内核级页表,无论进程如何切换,都吧会更该这个页表

2、信号的捕捉流程 

其实上面的进程捕捉流程,我们可以用倒写的8字来记忆

注意:

  • 默认情况下:我们所以的信号是不被阻塞的
  • 默认情况下:如果一个信号被屏蔽了,该信号就不会被递达

四、信号的补充知识

 1、sigaction函数

功能:读取和修改与指定信号相关联的处理动作

原型: int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

参数:

  • signum 是指定信号的编号
  • 若act指针非空,则根据act修改该信号的处理动作
  • 若oact指针非空,则通过oact传 出该信号原来的处理动作

返回:调用成功则返回0,出错则返回- 1

act和oact指向sigaction结构体 :

将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回 值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信 号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来 的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果 在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需 要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 

下面我们通过代码来理解一下sigaction函数

#include<iostream>
#include<cstdio>
#include<signal.h>
#include<unistd.h>using namespace std;void Count(int cnt)
{while(cnt){printf("cnt: %2d\r", cnt);fflush(stdout);cnt--;sleep(1);}printf("\n");
}void handler(int signo)
{cout << "get a signo: " << signo << "正在处理中..." << endl;Count(20);
}
int main()
{struct sigaction act, oact;act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask); // 当我们正在处理某一种信号的时候,我们也想顺便屏蔽其他信号,就可以添加到这个sa_mask中sigaddset(&act.sa_mask, 3);sigaction(SIGINT, &act, &oact);while(true) sleep(1);return 0;
}

这里我们观察到,我们一直给进程发2号信号,发现他不马上执行所育的2号信号的,而是一个一个的执行。

这里我们就知道了,正在递达某个信号期间,同类信号无法被递达,系统会自动将当前信号加入到进程的信号屏蔽字block,当信号完成捕捉动作,系统又会自动解除对该信号的屏蔽(一般一个信号被解除屏蔽的时候,会自动递达当前屏蔽信号,如果该信号已经被pending的话,就不做任何处理)。

2、可重入函数

为了理解可重写入函数,这里我们有一个链表,我们做如下操作:

这里我们发现node2丢失了, 我们代码也没有写错,而仅仅是在一个mian函数中执行了二个执行流。

这里在mian函数handler中该函数重复进入 ,出了问题,我们就称函数insert为不可以重入函数。

这里在mian函数handler中该函数重复进入 ,没有出问题,我们就称函数insert为可以重入函数。

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准 比特科技 I/O库的很多实现都以不可重入的方式使用全局数据结构

3、 volatile关键字

下面我们看一个现象:

int quit = 0;
void handler(int signo)
{printf("pid: %d, %d 号信号,正在被捕捉!\n", getpid(), signo);printf("quit: %d", quit);quit = 1;printf("-> %d\n", quit);
}int main()
{signal(2,handler);while(!quit);printf("我是正常退出\n");
}

 

这个结果是显而易见。

当我们调整 gcc的优化程度,可用man gcc手册查看

//调整优化gcc
g++ -o $@ $^  -O3 #-std=c++11

在次运行代码 

 发现代码进入了死循环,这是为什么呢?

这是因为main函数中有二个流,编译器认为在main执行流中quit没有改,所以编译器只将物理内存中的值由0变1,但是Cpu中寄存器中0没有变。(编译器的优化)

为了解决编译器的优化这就要用到volatile 关键字

volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作

 volatile int quit = 0;

在次运行代码:问题就解决了

相关文章:

[Linux打怪升级之路]-信号的保存和递达

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、信号的保…...

【科研新手指南3】chatgpt辅助论文优化表达

chatgpt辅助论文优化表达 写在最前面最终版什么是好的论文整体上&#xff1a;逻辑/连贯性细节上一些具体的修改例子 一些建议&#xff0c;包括具体的提问范例1. 明确你的需求2. 提供上下文信息3. 明确问题类型4. 测试不同建议5. 请求详细解释综合提问范例&#xff1a; 常规技巧…...

在应用内维护域名缓存时遇到的问题

近期参与的项目中&#xff0c;依赖DNS服务器来解析外部的业务集群&#xff0c;遇到了一连串的问题。 远端的业务集群基于HTTP/HTTPS协议&#xff0c;提供业务服务&#xff0c;集群中包含了多个业务节点&#xff0c;当前方案中在DNS服务器上配置域名&#xff0c;指向业务集群中的…...

网络支付安全:面临的风险与防范策略

随着电子商务的繁荣和移动支付技术的发展&#xff0c;网络支付已成为全球消费者日常生活中不可或缺的一部分。然而&#xff0c;这种便捷的支付方式也带来了许多安全风险&#xff0c;这些风险可能威胁到用户的财务安全和个人隐私。本文将深入探讨网络支付面临的主要安全风险&…...

『亚马逊云科技产品测评』活动征文|阿里云服务器亚马逊服务器综合评测

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 引言一、亚马逊&阿里云发展历史介绍1.1 亚马逊发展历史1.2…...

javascript原来还可以这样比较两个日期(直接使用new Date)

有个需求是这样的&#xff1a;假设今天是2023/11/15 有一个表格&#xff0c;表格中操作列按钮的展示与隐藏依靠开始结束日期来进行展示&#xff0c;如果当前日期在开始结束日期之间&#xff0c;则进行展示&#xff0c;我一开始做的时候使用new Date转换成时间戳(getTime)进行比…...

[云原生案例2.4 ] Kubernetes的部署安装 【通过Kubeadm部署Kubernetes高可用集群】

文章目录 1. 基本架构及前置准备1.1 基本架构1.2 前置准备 2. 系统初始化操作 ---- 所有节点2.1 关闭防火墙、selinux和swap分区2.1.1 关闭防火墙和selinux2.1.2 关闭交换分区 2.2 修改主机名&#xff0c;添加域名映射2.2.1 修改主机名2.2.2 修改本地hosts文件 2.3 内核升级2.4…...

PP-ChatOCRv2、PP-TSv2、大模型半监督学习工具...PaddleX新特性等你来pick!

小A是一名刚刚毕业的算法工程师&#xff0c;有一天&#xff0c;他被老板安排了一个活&#xff0c;要对一批合同扫描件进行自动化信息抽取&#xff0c;输出结构化的分析报表。OCR问题不大&#xff0c;但是怎么进行批量的结构化信息抽取呢&#xff1f;小A陷入了苦苦思索… 小B是…...

HarmonyOS 学习记录

时光荏苒,岁月如梭,韶华不负,未来可期。转眼间已经30岁了&#xff0c;学习的重要性不言而喻&#xff0c;在接下来的日子里记录下自己学习HarmonyOS的过程。增加一下知识储备&#xff0c;防患于未然嘛 不得不说华为的开发文档写的不错&#xff0c;开发工具直接安装后自动配置环境…...

阿里云 业务集群的冗余、备份、监控方案

1. 请解释什么是业务集群的冗余、备份和监控&#xff1f; 一、冗余方案 硬件冗余&#xff1a;在业务集群中&#xff0c;关键设备如服务器、存储设备等应采用双机热备或集群技术&#xff0c;确保在某台设备出现故障时&#xff0c;其他设备能够自动接管工作&#xff0c;保证业务…...

无人驾驶的未来 后疫情时代如何抵达

作者 | 马冀&#xff0c;澳鹏&#xff08;Appen&#xff09;中国区副总裁 自动驾驶—疫情危难中显身手 2020年&#xff0c;一场突如其来的新冠肺炎肆虐全球, 导致不同国家的人们被迫隔离或保持社交距离&#xff0c;人与人之间的接触变得风险极高。一时间&#xff0c;人们对于…...

(论文阅读31/100)Stacked hourglass networks for human pose estimation

31.文献阅读笔记 简介 题目 Stacked hourglass networks for human pose estimation 作者 Alejandro Newell, Kaiyu Yang, and Jia Deng, ECCV, 2016. 原文链接 https://arxiv.org/pdf/1603.06937.pdf 关键词 Human Pose Estimation 研究问题 CNN运用于Human Pose E…...

【第2章 Node.js基础】2.6 Node.js 的Buffer数据类型

Buffer数据类型 文章目录 Buffer数据类型什么是Buffer数据类型Buffer 的特点 创建Buffer实例Buffer用于编码转换将Buffer 实例转换为JSON 对象Buffer实例基本操作1. 写入Buffer实例&#xff1a;2. 从Buffer实例读取数据&#xff1a;3. Buffer实例合并&#xff1a; 4. Buffer实例…...

reactive和effect,依赖收集触发依赖

通过上一篇文章已经初始化项目&#xff0c;集成了ts和jest。本篇实现Vue3中响应式模块里的reactive方法。 前置知识要求 如果你熟练掌握Map, Set, Proxy, Reflect&#xff0c;可直接跳过这部分。 Map Map是一种用于存储键值对的集合&#xff0c;并且能够记住键的原始插入顺…...

【C#学习】backgroundWorker控件

BackgroundWorker 控件的几个实例&#xff08;C# backgroundworker使用方法&#xff09;&#xff1a; 在 WinForms 中&#xff0c;有时要执行耗时的操作&#xff0c;在该操作未完成之前操作用户界面&#xff0c;会导致用户界面停止响应。 解决的方法就是新开一个线程&#xff…...

Istio学习笔记-部署模型

参考&#xff1a;Istioldie 1.18 / 部署模型 当您将 Istio 用于生产环境部署时&#xff0c;需要确定一系列的问题。 网格将被限制在单个集群中还是分布在多个集群中&#xff1f; 是将所有服务都放置在单个完全连接的网络中&#xff0c;还是需要网关来跨多个网络连接服务&#…...

磁盘调度算法

磁盘调度算法是计算机操作系统中用于管理磁盘上的数据访问的重要组成部分。这些算法有助于优化数据的读写操作&#xff0c;以减少磁盘访问时间&#xff0c;提高系统性能。以下是一些常见的磁盘调度算法&#xff1a; 先来先服务&#xff08;FCFS&#xff0c;First-Come-First-Se…...

力扣题库2. 两数相加

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 开…...

【Linux】第十六站:进程地址空间

文章目录 一、程序地址空间1.内存的分布2.static修饰后为什么不会被释放3.一个奇怪的现象 二、进程地址空间1.前面现象的原因2.地址空间究竟是什么&#xff1f;3.为什么要有进程地址空间4.页表5.什么叫进程&#xff1f;6.进程具有独立性。为什么&#xff1f;怎么做到呢&#xf…...

基于Springboot的影城管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的影城管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 项目介绍…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...