『 Linux 』信号的写入与保存

news2024/11/17 10:30:23

文章目录

    • 信号的发送
    • 信号的保存
    • sigset_t 类型与信号集操作函数
    • 阻塞信号集(信号屏蔽字)操作函数
    • 未决信号集操作函数
    • 验证阻塞信号集与未决信号集


信号的发送

请添加图片描述

$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	

普通信号的信号编号由131,是以一个位图的大小来定义的;

task_struct进程内核数据结构中存在一个结构体指针为struct signal_struct *signal用于指向信号处理的结构体;

其中该结构体中包含一个unsigned long类型的数据作为位图;

struct signal_struct {
    ...
    unsigned long pending; // 待处理信号的位图
    ...
};

该数据以位图的形式存在,并存储该进程的待处理信号;

本质上信号的发送指的是操作系统向该位图中对应位置进行写入;

操作系统为进程的管理者,只有操作系统才能够修改task_struct内部属性;


信号的保存

请添加图片描述

信号保存在进程的task_struct内核结构体中;

task_struct内核结构体中包含了三张表表明进程状态,分别为:

  • 阻塞表(阻塞信号集)Blocked Signal Table

    阻塞信号集是以位图形式存在的,表示进程当前选择不接收的信号;

    进程在执行特定代码时,可以通过将某些信号添加到阻塞信号集中来避免被打断;

  • 未决表(待处理信号集)Pending Signal Table

    未决信号集以位图形式存在,记录那些已经发送给进程但尚未被处理的信号;

    这些信号会在进程下一次能够处理信号时被送达;

  • 方法表Handler Table

    方法表以函数指针数组的形式存在,保存每个信号对应的信号处理程序的地址;

    这使得进程能够在接收到特定信号时调用相应的处理函数;

    当用户使用自定义动作时将修改该表的内容将用户自定义动作方法指针覆盖默认动作对应位置完成替换;

信号将以上述表中的阻塞信号集与未决信号集进行保存,三张表意味着信号存在三种状态:

  • 阻塞信号

    阻塞信号是指进程当前接收但不处理的信号;

    在该信号被解除阻塞前进程不会处理这些信号直到解除阻塞后;

    通过阻塞信号,进程可以控制在执行关键操作时不被打扰从而确保操作的完整性;

  • 信号未决

    信号未决是指哪些已经发送给进程但尚未被处理的信号;

    这些信号会被记录在进程的待处理信号集;

    即信号在发送时进程无法立即处理(例如处于阻塞状态),但信号仍会被保存以便在合适的时机进行处理;

    待处理信号集确保了信号不会丢失;

  • 信号递达

    信号递达指已经到达并被处理的信号,当信号处理程序执行完毕后这些信号就会被认定是已抵达的;

    信号递达表示信号已经被处理,进程将继续其正常执行流程或者根据信号类型执行特定行为;

被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞才执行递达动作;

以上图为例三个格子分别代表三种信号;

信号SIGHUP未传入,或是未被阻塞进入Pending表后信号被处理;

信号SIGINT传入,但Block表对应位置设置了阻塞,被阻塞在Pending表中处于未决状态;

信号SIGQUIT未传入,但Block表中对应位置设置了阻塞;

上述表明阻塞与是否收到信号无关;

  • 阻塞与忽略的区别

    阻塞表示已经收到了该信号但不对信号进行处理,使得信号在取消阻塞前都保持未决状态;

    忽略表示已经收到了该信号且已经对信号进行了处理,信号已经递达,其中信号的处理方式为忽略,即不作任何处理;

     // 忽略 SIG_IGN
    
    #define SIG_ERR	((__sighandler_t) -1)		/* Error return.  */
    #define SIG_DFL	((__sighandler_t) 0)		/* Default action.  */
    #define SIG_IGN	((__sighandler_t) 1)		/* Ignore signal.  */
    
    
    /* Type of a signal handler.  */
    typedef void (*__sighandler_t) (int);
    

sigset_t 类型与信号集操作函数

请添加图片描述

信号以位图形式进行标定与保存,但信号保存属于内核信息;

为保证安全,用户无法通过直接修改操作系统内核的位图来修改信号的传递所以封装了一个类型为sigset_t,该类型也被成为信号集;

该类型大致框架为:

typedef struct {
    unsigned long sig[NSIG/ (8 * sizeof(unsigned long))]; // NSIG 是信号数量
} sigset_t;

为保护内核信息并提供用户友好的接口,操作系统定义了sigset_t类型,该类型封装了信号的位图,允许用户程序通过标准化的方式来操作信号集而无需直接与内核数据结构进行交互;

操作系统提供了一系列系统调用,如sigprocmask(),sigaction(),sigemptyset(),sigaddset()等,来操作信号集;

这些接口允许用户程序创建,修改和查询信号集从而实现对信号的控制;

NAME
       sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX signal set operations.

SYNOPSIS
       #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);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigismember():
           _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

RETURN VALUE
       sigemptyset(), sigfillset(), sigaddset(), and sigdelset() return 0 on success and -1 on error.

  • sigfillset()

    int sigemptyset(sigset_t *set);
    

    用于初始化一个信号集,将其设置为一个空集;

    set是指向sigeset_t类型的指针,函数将会修改该信号集;

    #include <stdio.h>
    #include <signal.h>
    
    int main() {
        sigset_t set;
        
        // 初始化一个空的信号集
        if (sigemptyset(&set) != 0) {
            perror("sigemptyset");
            return 1;
        }
    
        // 输出成功
        printf("Initialized empty signal set.\n");
        return 0;
    }
    
  • sigfillset()

    int sigfillset(sigset_t *set);
    

    初始化一个信号集,将其设置为包含所有信号;

    set是指向sigset_t类型的指针,函数将会修改该信号集;

    #include <stdio.h>
    #include <signal.h>
    
    int main() {
        sigset_t set;
        
        // 初始化一个信号集,包含所有信号
        if (sigfillset(&set) != 0) {
            perror("sigfillset");
            return 1;
        }
    
        // 输出成功
        printf("Initialized signal set with all signals.\n");
        return 0;
    }
    
  • sigaddset()

    int sigaddset(sigset_t *set , int signum);
    

    将指定的信号添加到信号集中;

    set是指向sigset_t类型的指针,表示要修改的信号集;

    signum是要添加的信号编号,如SIGINT,SIGQUIT等;

    #include <stdio.h>
    #include <signal.h>
    
    int main() {
        sigset_t set;
        
        // 初始化一个空的信号集
        sigemptyset(&set);
        
        // 添加 SIGINT 信号到信号集中
        if (sigaddset(&set, SIGINT) != 0) {
            perror("sigaddset");
            return 1;
        }
    
        // 输出成功
        printf("Added SIGINT to the signal set.\n");
        return 0;
    }
    
  • sigdelset()

    int sigdelset(sigset_t* set , int signum);
    

    从信号集中删除指定的信号;

    set是指向sigset_t类型的指针,表示要修改的信号集;

    signum是要删除的信号编号;

    #include <stdio.h>
    #include <signal.h>
    
    int main() {
        sigset_t set;
        
        // 初始化一个空的信号集
        sigemptyset(&set);
        
        // 添加 SIGINT 信号到信号集中
        sigaddset(&set, SIGINT);
        
        // 从信号集中删除 SIGINT 信号
        if (sigdelset(&set, SIGINT) != 0) {
            perror("sigdelset");
            return 1;
        }
    
        // 输出成功
        printf("Removed SIGINT from the signal set.\n");
        return 0;
    }
    
  • sigismember()

    int sigismember(const sigset_t* set , int signum);
    

    检查指定的信号是否在信号集中;

    set是指向sigset_t类型的指针,表示要检查的信号集;

    signum是要检查的信号编号;

    #include <stdio.h>
    #include <signal.h>
    
    int main() {
        sigset_t set;
        
        // 初始化一个空的信号集
        sigemptyset(&set);
        
        // 添加 SIGINT 信号到信号集中
        sigaddset(&set, SIGINT);
        
        // 检查 SIGINT 是否在信号集中
        if (sigismember(&set, SIGINT)) {
            printf("SIGINT is a member of the signal set.\n");
        } else {
            printf("SIGINT is not a member of the signal set.\n");
        }
    
        return 0;
    }
    

    返回值分别为0,1,-1;

    0表示当前信号不在信号集中;

    1表示当前信号存在于信号集中;

    -1表示错误;


阻塞信号集(信号屏蔽字)操作函数

请添加图片描述

可调用系统调用接口sigprocmask()对阻塞信号集进行修改从而达到阻塞某个信号的功能;

NAME
       sigprocmask - examine and change blocked signals

SYNOPSIS
       #include <signal.h>

       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sigprocmask(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

RETURN VALUE
       sigprocmask() returns 0 on success and -1 on error.  In the event of an error, errno is set to indicate the cause.

参数为如下:

  • const sigset_t *set

    该参数为将用户定义好的sigset_t类型的信号集传入,从而对阻塞信号集进行特定操作;

  • sigset_t *oset

    该参数为传入一个sigset_t类型的信号集,一般这个信号集为空集;

    set信号集被传进接口函数并对原有阻塞信号集进行操作时将会将原本的阻塞信号集写入*oset空集从而便于恢复;

  • int how

    该参数是一个选择型参数,通过接口所给予的选择通过set信号集来对原有的阻塞信号集进行对应操作;

    假设当前阻塞信号集为mask对应选择结果如下:

    • SIG_BLOCK

      set包含了希望添加到当前阻塞信号集的信号;

      相当于mask = mask | set;

    • SIG_UNBLOCK

      set包含了希望从当前阻塞信号集中解除阻塞的信号;

      相当于mask = mask & ~set;

    • SIG_SETMASK

      设置当前信号屏蔽字为set所指向的值;

      相当于mask = set;


未决信号集操作函数

请添加图片描述

可用系统调用接口sigpending()获取当前进程pending信号集内对应的数据;

NAME
       sigpending - examine pending signals

SYNOPSIS
       #include <signal.h>

       int sigpending(sigset_t *set);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sigpending(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

DESCRIPTION
       sigpending()  returns  the  set  of  signals that are pending for delivery to the calling thread (i.e., the signals
       which have been raised while blocked).  The mask of pending signals is returned in set.

RETURN VALUE
       sigpending() returns 0 on success and -1 on error.  In the event of an error, errno is set to indicate the cause.

参数为一个sigset_t*类型的输出型参数;

功能为向该接口传入一个sigset_t*的输出型参数,调用时将会把当前进程中的pending未决信号集写入至该参数中;

调用成功返回0,调用失败返回-1并设置errno;


验证阻塞信号集与未决信号集

请添加图片描述

利用上文所述接口验证阻塞信号集与未决信号集对应接口以及阻塞是否有效;

2号信号SIGINT为例,大致思路为:

  • 对信号进行屏蔽

    • 声明sigset_t类型信号集

      需要声明两个信号集,分别为bsetoset;

      bset用于设置,oset用于接收阻塞信号集中原本的信号集;

    • 调用sigemptyset()接口初始化信号集为空集

    • 调用sigaddset()接口添加2号信号SIGINT

    • 调用sigprocmask()接口

      调用sigprocmask()接口并使用SIG_SETMASK选项,即覆盖选项;

      传入&bset&oset;

    此处2号信号SIGINT已被阻塞;

  • 重复打印未决信号集

    将未决信号集进行打印验证2号信号SIGINT是否被阻塞,此前应获取未决信号集;

    • 获取未决信号集pending

      声明一个新的sigset_t类型信号集pending并调用接口pending(&pending)使未决信号集写入至pending中;

    • 重复打印未决信号集

      重复打印未决信号集可以调用sigismember();

      当对应信号存在时返回1,不存在返回0,错误则返回-1;

  • 解除信号阻塞

    调用sigprocmask()时已将原有信号集写入oset中;

    设置时间次数限制,当时间达到时调用sigprocmask()并传入oset信号集对阻塞信号集进行覆盖,最后一个参数传入nullptr表示不需要保存(返回)当前现有阻塞信号集;

  • 设置信号捕获

    由于2号信号SIGINT默认动作为终止进程;

    为保证进程不被终止可以往复打印未决信号集以观察到2号信号对应位置由1变回0的状态设置自定义动作;

    当信号阻塞被解除时处理信号时将执行自定义动作以免进程退出;

#include <unistd.h>

#include <csignal>
#include <cstdlib>
#include <iostream>
using namespace std;

void handler(int signum) { printf("func handler get a signum :%d\n", signum); }

int main() {
  // 设置信号处理函数
  signal(SIGINT, handler);

  // 对 SIGINT 信号进行屏蔽
  sigset_t bset;
  if (sigemptyset(&bset)) {
    cerr << "sigemptyset best fail" << endl;
    exit(1);
  }

  if (sigaddset(&bset, SIGINT)) {
    cerr << "sigaddset SIGINT fail" << endl;
    exit(2);
  }

  sigset_t oset;
  if (sigemptyset(&oset)) {
    cerr << "sigemptyset oset fail" << endl;
    exit(3);
  }

  if (sigprocmask(SIG_SETMASK, &bset, &oset)) {
    cerr << "sigprocmask SIG_SETMASK fail" << endl;
    exit(4);
  }

  // 重复打印进程中的未决信号集
  sigset_t pending;
  if (sigemptyset(&pending)) {
    cerr << "sigemptyset pending fail" << endl;
    exit(5);
  }

  int cnt = 0;
  while (1) {
    sigpending(&pending);  // 获取未决信号集
    for (int i = 31; i > 0; --i) {
      // 以01位图的形式打印信号集
      // 调用sigismember函数 信号存在返回1 不存在返回0
      // 忽略查错处理
      if (sigismember(&pending, i)) {
        cout << "1";
      } else {
        cout << "0";
      }
    }
    cout << endl << endl;
    sleep(1);
    ++cnt;
    if (cnt == 4) {
      // 大约4秒左右 SIGINT 信号将被解除阻塞
      // 解除阻塞后发出的 SIGINT 信号将被 signal()捕获
      // 从而执行自定义动作而不默认终止
      sigprocmask(SIG_SETMASK, &oset, nullptr);
    }
  }
  // 发送 SIGINT 信号 --- 可以通过按 Ctrl + C 或使用 kill -2 <pid> 发送
  return 0;
}

运行结果为:

$ ./mysignal 
0000000000000000000000000000000

0000000000000000000000000000000

^C0000000000000000000000000000010

0000000000000000000000000000010

func handler get a signum :2
0000000000000000000000000000000

0000000000000000000000000000000

^\Quit

进程开始时2号信号已经被阻塞;

信号未发出时未决信号集中无信号产生;

2秒过后使用Ctrl + C向进程发送2号信号SIGINT,未决信号集中2号信号位置由0变为1;

4秒过后阻塞被解除,2号信号被signal()捕获执行自定义动作,信号变为递达状态即处理完毕,2号信号对应位置由1变回0;

验证完毕Ctrl + \结束当前进程;

  • 无法被阻塞的信号集

    9号信号SIGKILL19号信号SIGSTOP无法被阻塞,以避免出现无法终止与无法暂停的进程;

    可将上文代码SIGINT位置修改为SIGKILL,SIGSTOP运行,并在另一个窗口中使用kill -9/19 <pid> 命令进行发送信号;

    运行结果如下:

    # 9号信号SIGKILL
    $ ./mysignal
    0000000000000000000000000000000
    
    0000000000000000000000000000000
    
    Killed
    # 19号信号SIGSTOP
    $ ./mysignal
    0000000000000000000000000000000
    
    0000000000000000000000000000000
    
    
    [1]+  Stopped                 ./mysignal
    

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

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

相关文章

sql注入的专项练习 sqlilabs(含代码审计)

在做题之前先复习了数据库的增删改查&#xff0c;然后自己用本地的环境&#xff0c;在自己建的库里面进行了sql语句的测试&#xff0c;主要是回顾了一下sql注入联合注入查询的语句和sql注入的一般做题步骤。 1.获取当前数据库 2.获取数据库中的表 3.获取表中的字段名 一、sql…

Rce漏洞复习(ctfshow29-50)

Rce漏洞简介思维导图 Web29 代码审计&#xff1a; if(!preg_match("/flag/i", $c)){ eval($c); 传参没有flag&#xff08;大小写都没有出现&#xff09; Payload&#xff1a; ?csystem("ls"); ?csystem("tac *lag.php"); Web30 代码…

数据结构——排序大汇总(建议收藏)

这篇文章将为大家详细讲解各大排序的基本思想与实现代码~ 内有动图 首先&#xff0c;我们来看常见的排序有以下几大类&#xff1a; 1.插入排序 插入排序的主要思想是将每个位置的元素插入到前面已具备顺序的数组中 实际中我们玩扑克牌时&#xff0c;就用了插入排序的思想 …

Adobe正通过数字体验改变世界

在当今这个数字化飞速发展的时代&#xff0c;Adobe公司正以其创新的技术和卓越的产品引领着创意设计领域的变革。从Adobe发布的生成式AI工具&#xff08;Adobe Firefly&#xff09;&#xff0c;到Illustrator和Photoshop的新AI功能&#xff0c;再到广受认可的Adobe国际认证&…

【Golang 面试基础题】每日 5 题(七)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

opencv入门(四)

文章目录 一、形态学转换1.1 图像腐蚀1.1.1 erode():用于实现对图像的腐蚀操作1.2 图像膨胀1.2.1 dilate():实现对图像的膨胀操作1.3 图像 开\闭运算、梯度运算、顶帽运算、底帽运算1.3.1 morphologyEx():实现对图像的 开\闭运算、梯度运算、顶帽运算、底帽运算一、形态学转…

DATEDIFF()- Date Functions-SQL函数

DATEDIFF&#xff08;&#xff09;- Date Functions DATEDIFF() 函数是一种用于计算日期差异的常见日期函数。 通常用于比较两个日期之间的时间跨度&#xff0c;以便进行日期计算和分析。 语法 大多数数据库中&#xff0c;DATEDIFF() 函数的语法&#xff1a; DATEDIFF(unit,…

C++ | Leetcode C++题解之第278题第一个错误的版本

题目&#xff1a; 题解&#xff1a; class Solution { public:int firstBadVersion(int n) {int left 1, right n;while (left < right) { // 循环直至区间左右端点相同int mid left (right - left) / 2; // 防止计算时溢出if (isBadVersion(mid)) {right mid; // 答案…

MySQL练习05

题目 步骤 触发器 use mydb16_trigger; #使用数据库create table goods( gid char(8) primary key, name varchar(10), price decimal(8,2), num int);create table orders( oid int primary key auto_increment, gid char(10) not null, name varchar(10), price decima…

Pytorch使用教学6-张量的分割与合并

在使用PyTorch时&#xff0c;对张量的分割与合并是不可避免的操作&#xff0c;本节就带大家深刻理解张量的分割与合并。 在开始之前&#xff0c;我们先对张量的维度进行深入理解&#xff1a; t2 torch.zeros((3, 4)) # tensor([[0., 0., 0., 0.], # [0., 0., 0., 0.…

【PyTorch】基于YOLO的多目标检测项目(二)

【PyTorch】基于YOLO的多目标检测项目&#xff08;一&#xff09; 【PyTorch】基于YOLO的多目标检测项目&#xff08;二&#xff09; YOLO-v3网络由跨距为2的卷积层、跳跃连接层和上采样层组成&#xff0c;没有池化层。网络接收一幅416 * 416的图像作为输入&#xff0c;并提供三…

华为网络模拟器eNSP安装部署教程

eNSP是图形化网络仿真平台&#xff0c;该平台通过对真实网络设备的仿真模拟&#xff0c;帮助广大ICT从业者和客户快速熟悉华为数通系列产品&#xff0c;了解并掌握相关产品的操作和配置、提升对企业ICT网络的规划、建设、运维能力&#xff0c;从而帮助企业构建更高效&#xff0…

数据结构 | LinkedList与链表

前言 ArrayList底层使用连续的空间,任意位置(尤其是0位置下标)插入或删除元素时,需要将该位置后序元素 整体 往前或往后搬移,故时间复杂度为O(N). 优点(给定一个下标,可以快速查找到对应的元素,时间复杂度为O(1))增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗.增容一…

Linux进程——环境变量之二

文章目录 环境变量查看环境变量获取环境变量main()的第三个参数本地变量全局环境变量内建命令与常规命令 环境变量 查看环境变量 在上一篇文章中我们只说了查看某个环境变量的值&#xff0c;那么如何查看所有的环境变量呢 使用指令env即可 例如 这里我们也不需要全部记住&a…

FastAPI(七十四)实战开发《在线课程学习系统》接口开发-- 删除留言

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 之前文章FastAPI&#xff08;七十三&#xff09;实战开发《在线课程学习系统》接口开发-- 回复留言&#xff0c;那么我们这次分享删除留言接口的开发…

从0开始的STM32HAL库学习9

定时器输入捕获测频率 生成待测信号 配置环境 选择如上图所示 代码修改 在main函数中加入 HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); 测量信号频率 配置环境 如图所示打开TIM3定时器 1. 设置TI1FP1为ResetMode,即清空计数 2. 使用内部时钟 3. 通道 1 设置为输…

手机怎么设置不同的ip地址

在数字化日益深入的今天&#xff0c;智能手机已成为我们生活、工作和学习中不可或缺的设备。然而&#xff0c;随着网络应用的广泛和深入&#xff0c;我们有时需要为手机设置不同的IP地址来满足特定需求。比如&#xff0c;避免网络限制、提高网络安全、或者进行网络测试等。本文…

tarojs项目启动篇

TaroJS 是一个开放式跨端开发解决方案&#xff0c;使用 React 语法规范来开发多端应用&#xff08;包括小程序、H5、React Native 等&#xff09;。它可以帮助开发者高效地构建出在不同端上运行一致的应用。以下是启动 TaroJS 项目&#xff08;本来就有的旧项目&#xff09;的步…

经典文献阅读之--LIV-GaussMap(实时3D辐射场地图渲染的LiDAR惯性视觉融合算法)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

一文总结代理:代理模式、代理服务器

概述 代理在计算机编程领域&#xff0c;是一个很通用的概念&#xff0c;包括&#xff1a;代理设计模式&#xff0c;代理服务器等。 代理类持有具体实现类的实例&#xff0c;将在代理类上的操作转化为实例上方法的调用。为某个对象提供一个代理&#xff0c;以控制对这个对象的…