基于linux的进程信号知识

news2024/11/18 16:42:10

1.前言

生活当中我们无时不刻都在接触外界给予我们各种各样的信号,比如穿越马路时看到红灯就得停下来,在比如听到手机铃声就得接电话,那么生活中如果很多重要的信号同时发生了,你会先做哪个事情?换句化说你会如何处理?我们的操作系统就这种情况给出了一系列的设计

2.系统的介绍信号

2.1什么是linux信号

信号是一种用于通知进程发生某种事件或异常情况的机制。当发生特定的事件或异常时,操作系统会检测到并向目标进程发送一个信号,以便进程能够做出相应的处理。

2.2信号处理的常见方式

1.默认(进程自带的,程序员已经写好的逻辑)

2.忽略

3.自定义动作(捕捉信号)

2.3进程的保存

进程pcb内部保存了信号位图字段,比如说0000 0100 信号收到了就把某一个0改成1

3.信号的产生

3.1由软件条件下产生的信号

在我们讲解这个概念之前先来回顾和学习几个接口函数

//在Linux下,signal函数用于设置信号的处理方式。其原型如下:

#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
下面是对Linux下signal函数的详细解释:

1.signum参数是要设置处理函数的信号的编号,表示要处理的具体信号类型。常见的信号编号可以在 <signal.h> 头文件中找到,例如SIGINT、SIGTERM、SIGSEGV等。

2.handler参数是一个函数指针,指向用户定义的信号处理函数。该函数接收一个整型参数,表示接收到的信号编号。信号处理函数的返回类型为void3.signal函数的返回类型是一个函数指针,它指向之前的信号处理函数。如果之前没有设置过处理函数,则返回值为SIG_DFL(默认处理方式)或SIG_IGN(忽略信号)。
    
如果将handler参数设置为SIG_DFL,表示将信号的处理方式恢复为默认操作。对于大多数信号,这将导致进程终止或生成核心转储文件。

如果将handler参数设置为SIG_IGN,表示忽略接收到的信号。这意味着当进程接收到该信号时,不会采取任何操作。

如果将handler参数设置为用户自定义的信号处理函数,则在接收到信号时,将调用该函数来执行相应的操作。
    
//例子:
 #include <stdio.h>
#include <signal.h>

void sigintHandler(int signum) {
    printf("Received SIGINT signal. Exiting...\n");
    // 执行一些清理操作或其他逻辑
    exit(0);
}

int main() {
    // 注册SIGINT信号的处理函数
    signal(SIGINT, sigintHandler);

    // 进入主循环
    while (1) {
        // 执行主要逻辑
    }

    return 0;
}
 
   在上述示例中,signal(SIGINT, sigintHandler)将SIGINT信号的处理方式设置为sigintHandler函数,该函数在接收到SIGINT信号时打印一条消息并退出程序。
  //在Linux下,alarm函数用于设置定时器信号,即在指定的时间间隔后发送一个SIGALRM信号给当前进程。该函数的原型如下:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

1.seconds参数表示定时器的时间间隔,以秒为单位。当定时器到期时,将发送一个SIGALRM信号给当前进程。

2.alarm函数返回一个无符号整数,表示之前剩余的定时器时间。如果之前已设置了定时器,该值表示剩余的时间;如果之前没有设置定时器,则返回值为0。

tips:
当seconds为0时,表示取消之前设置的定时器。如果之前已设置了定时器,将返回剩余的时间;如果之前没有设置定时器,返回值为0。

只能有一个定时器处于活动状态。如果在调用alarm函数之前已经设置了定时器,新的调用将覆盖前一个定时器,并重新开始计时。

当定时器到期时,会产生一个SIGALRM信号。可以通过注册SIGALRM信号的处理函数来捕获和处理该信号。(表示定时器到期的信号,用于通知进程已经达到了预定的时间间隔。)

其实我们还有很多种与信号相关的函数,当操作系统识别到某种软件(软件是一堆程序,各种逻辑框架是程序员自己写的)条件符合发送某种信号的标准时,操作系统构建信号发送给指定的进程。

3.2硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了

以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了

法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

我们就以上两种情况来具体谈谈这个问题。

1.除以0指令

在计算机中,处理运算的是cpu这个硬件,在cpu内部的状态寄存器(位图)中有对应的状态标记位和溢出标记位,如果溢出标记位为1,那么操作系统会立刻找到当前运行进程的pid,接着操作系统对对应进程发送对应信号。

(正常情况下来讲硬件异常会默认退出)

2.野指针或越界问题

当虚拟地址转换成实际地址的时候需要用到页表和mmu(mmu是硬件),如果越界了或者是非法访问地址,在转换时一定会报错。

总结:所有信号有它的来源,但最终都被操作系统识别解释并发送。

3.3知识点补充----核心转储

在这里插入图片描述

这幅图想必大家都不陌生吧,在我们用函数获取子进程的status(位图)曾经给出了这样的一副图,而我们今天讲解的核心转储的是否启用就和这个core dump标志位有关。

那么究竟什么是核心转储呢?

核心转储(Core Dump)是指在程序运行期间发生严重错误或崩溃时,操作系统将进程的内存状态保存到一个文件中的过程。

一般而言云服务器(生产环境)的核心转储功能是被默认关闭的,因为在生产环境中保持磁盘空间的充足和高效利用是非常重要的,而禁用核心转储功能可以避免不必要的磁盘空间占用,确保系统的正常运行。

4.信号的保存和信号的处理

因为这一块的内容有相似处可以用来进行比较好的对比所以放在一块讲。

4.1信号存储的内容-------位图表的理解

在这里插入图片描述

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

产生时,内核在进程控制块中设置该信号为未决状态(1)以指示进程有一个未决信号等待处理,直到信号递达,并通过函数指针调用所对应的函数进行处理,才开始清除该标志(改成0)。

tips:

(信号掩码(Signal Mask)是一个位图,用于指定进程当前阻塞(屏蔽)哪些信号的递送。通过设置信号掩码,进程可以控制是否接收特定类型的信号。信号标志位中的阻塞部分是进程的运行时状态的一部分,指示某个特定的信号是否被阻塞,这个标志位间接受到信号掩码的影响。)

  1. 当进程接收到一个信号时,如果该信号在信号掩码中被阻塞(signal mask),那么该信号会保持未决状态(1)。
  2. 在信号存储未决标志位为未决状态(阻塞情况下),即使进程接收到相同类型的信号,它们也不会立即处理,而是继续保持未决状态。
  3. 当进程解除对某个信号的阻塞时,内核会将该信号从未决状态转变为"待处理"(ready )状态,并将信号存储未决标志位相应地更新为"非未决"(0)状态。
  4. 一旦信号存储未决标志位被设置为非未决状态,进程就可以根据信号的处理方式(例如,执行默认操作、调用信号处理函数)来处理该信号。
4.2信号集操作函数

前置小知识sigset_t

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

因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号

的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有

**效”和“无效”的含义是该信号是否处于未决状态。**下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当

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

#include <signal.h>
int sigemptyset(sigset_t *set);
``sigemptyset` 函数用于将信号掩码设置为空集,即将所有位都清零,表示不阻塞任何信号。
int sigfillset(sigset_t *set);
``sigfillset` 函数用于将信号掩码设置为满集,即将所有位都置为1,表示阻塞所有信号。
int sigaddset (sigset_t *set, int signo);
``sigaddset` 函数用于将指定的信号添加到信号掩码中,将对应位设置为1,表示阻塞该信号。
int sigdelset(sigset_t *set, int signo);
``sigdelset` 函数用于从信号掩码中移除指定的信号,将对应位设置为0,表示解除对该信号的阻塞。
int sigismember(const sigset_t *set, int signo)
 ``sigismember` 函数用于检查指定的信号是否在信号掩码中。如果该信号被阻塞(信号掩码对应位为1),则返回1;否则,返回0
//sigprocmask 函数用于修改进程的信号掩码(Signal Mask)。它可以用于阻塞或解除阻塞指定的信号,并提供了对信号掩码的原子操作。

//下面是关于 sigprocmask 函数的详细解释:

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

1.//how 参数指定了如何修改信号掩码,它可以采用以下三个值之一:

//SIG_BLOCK:将 set 参数中指定的信号添加到当前信号掩码中,即将这些信号阻塞。在此模式下,新的信号掩码是当前信号掩码和 set 的并集。
//SIG_UNBLOCK:从当前信号掩码中移除 set 参数中指定的信号,即解除对这些信号的阻塞。在此模式下,新的信号掩码是当前信号掩码和 set 的补集。
//SIG_SETMASK:将当前信号掩码替换为 set 参数中指定的信号掩码,即设置新的信号掩码为 set。在此模式下,新的信号掩码直接被 set 替换。
2.set 参数是一个指向 sigset_t 类型的指针,表示要添加、移除或替换的信号集。sigset_t 是一个用于存储信号掩码的数据结构,它通常是一个位图,每个位对应一个特定的信号。

3.oldset 参数是一个指向 sigset_t 类型的指针,用于存储调用 sigprocmask 函数之前的旧信号掩码。如果不需要获取旧的信号掩码,可以将该参数设置为 NULL4.sigprocmask 函数的返回值为整型,表示函数调用的结果。如果成功,返回值为 0,如果出错,返回值为 -1,并设置相应的错误码。

使用 sigprocmask 函数,您可以对当前进程的信号掩码进行修改,控制信号的阻塞和解除阻塞。通过合理设置信号掩码,您可以选择性地阻塞或允许某些信号的递送,以满足程序的需求。

请注意,sigprocmask 函数是一个原子操作,它保证在修改信号掩码期间不会被中断。这是确保在多线程或并发环境中正确处理信号掩码的重要机制之一。
//sigpending 函数用于获取当前被阻塞的未决信号集(Pending Signal Set),即已经发送给进程但由于信号阻塞而暂时未能处理的信号。
 
int sigpending(sigset_t *set);

1.set 参数是一个指向 sigset_t 类型的指针,用于存储被阻塞的未决信号集。sigset_t 是一个用于存储信号掩码的数据结构,它通常是一个位图,每个位对应一个特定的信号。

2.sigpending 函数的返回值为整型,表示函数调用的结果。如果成功,返回值为 0,如果出错,返回值为 -1,并设置相应的错误码。

5.信号的捕捉

5.1前置小知识

首先在了解信号捕捉之前先为大家普及一些额外概念。

正如我们小标题所说在,在这会为大家介绍一些函数来捕捉相应的信号。

想象以下这样一段场景:

我们写了一个程序捕捉了所有的信号,我们是不是就写了一个不会异常不会被杀掉的进程?

显然操作系统考虑到了这样一种问题,kill - 9 命令就能无视这种信号捕捉杀死进程。

5.2状态间变化

在信号进行一系列处理的期间,我们的操作系统会在内核态和用户态直接相互转换。

下面先正式介绍一下这两种状态

  1. 用户态(User Mode):
    • 用户态是指操作系统中的一种执行模式,其中用户程序在较低的特权级别下运行。
    • 在用户态下,用户程序只能执行受限的操作,无法直接访问操作系统核心和底层硬件资源。
    • 用户程序可以执行常规的计算任务、访问用户空间的内存和设备、进行文件操作等。
    • 用户态下的程序执行受到操作系统的保护,无法直接影响其他程序或操作系统本身的稳定性和安全性。
  2. 内核态(Kernel Mode):
    • 内核态是指操作系统中的一种执行模式,其中操作系统内核在较高的特权级别下运行。
    • 在内核态下,操作系统内核具有更高的特权级别和更广泛的访问权限,可以执行特权指令和访问底层硬件资源。
    • 内核态下的操作系统内核可以执行关键的系统任务,如管理内存、调度进程、访问设备驱动程序等。
    • 内核态具有更高的特权级别,可以执行更底层和敏感的操作,但也需要小心处理,以确保操作系统的稳定性和安全性。

实际上在我们的内核状态下使用的也是进程地址空间那一套知识体系,下面我给大家一副图,就能很清晰的得到一个直观理解:在这里插入图片描述

这里我们在提出一个问题:当我公用一个cpu来执行这一份实际内存时,如何知道我应该是内核态的执行权限还是内核态。

实际上cpu存在一套特殊的寄存器,比如CR3,上面记录的信息可以表示当前cpu的执行权限。

5.3信号捕捉具体方案以及内核态,用户态转变

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码

是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行

main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号

SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler

和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返

回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复

main函数的上下文继续执行了。

在这里插入图片描述
在这里插入图片描述

5.4相关函数的使用
//sigaction 函数用于设置和修改信号处理程序,它提供了更为灵活和可靠的信号处理机制。下面是关于 sigaction 函数的详细介绍:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

1.signum 参数是要设置或修改的信号编号,它可以是标准的 POSIX 信号编号(如 SIGINT、SIGTERM)或自定义的信号编号。

2.act 参数是一个指向 struct sigaction 结构的指针,用于指定新的信号处理程序。

3.oldact 参数是一个指向 struct sigaction 结构的指针,用于存储之前的信号处理程序(如果需要备份)。

4.sigaction 函数的返回值为整型,表示函数调用的结果。如果成功,返回值为 0,如果出错,返回值为 -1,并设置相应的错误码。

//struct sigaction 结构定义如下:


struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};

1.sa_handler 是一个函数指针,用于指定简单的信号处理函数。当信号到达时,将调用该函数进行处理。

2.sa_sigaction 是一个函数指针,用于指定复杂的信号处理函数。与 sa_handler 不同,它接收三个参数:信号编号、指向 siginfo_t 结构的指针(包含关于信号的详细信息)和一个指向 ucontext_t 结构的指针(描述进程上下文的结构)。

3.sa_mask 是一个 sigset_t 类型的信号掩码,用于指定在信号处理程序执行期间要阻塞的信号集。阻塞这些信号可以防止它们在处理程序执行期间再次触发。

4.sa_flags 是一个标志位,用于指定附加的行为选项。常见的标志包括 SA_RESTART(指定系统调用在信号处理程序返回后自动重启)和 SA_SIGINFO(指示使用 sa_sigaction 而不是 sa_handler 进行处理)。

5.sa_restorer 是一个指向恢复函数的指针,用于在信号处理程序返回后进行其他处理。在大多数情况下,它被设置为 NULL

6.可重入函数,不可重入函数

在这里插入图片描述

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因

为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函

数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从

sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步

之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只

有一个节点真正插入链表中了。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称

为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,

如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

7.volatile关键字

该关键字在C当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下

[hb@localhost code_test]$ cat sig.c 
#include <stdio.h>
#include <signal.h>
int flag = 0;
void handler(int sig)
{
 printf("chage flag 0 to 1\n");
 flag = 1;
}
int main()
{
 signal(2, handler);
 while(!flag);
 printf("process quit normal\n");
 return 0;
}

[hb@localhost code_test]$ cat Makefile 
sig:sig.c
 gcc -o sig sig.c #-O2
.PHONY:clean
clean:
 rm -f sig
     
[hb@localhost code_test]$ ./sig 
^Cchage flag 0 to 1
process quit normal
[hb@localhost code_test]$ cat sig.c 

标准情况下,键入 CTRL-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 , while 条件不满足,退出循

环,进程退出

[hb@localhost code_test]$ cat sig.c 
#include <stdio.h>
#include <signal.h>
int flag = 0;
void handler(int sig)
{
 printf("chage flag 0 to 1\n");
 flag = 1;
}
int main()
{
 signal(2, handler);
 while(!flag);
 printf("process quit normal\n");
 return 0;
}

[hb@localhost code_test]$ cat Makefile 
sig:sig.c
 gcc -o sig sig.c -O2
.PHONY:clean
clean:
 rm -f sig

[hb@localhost code_test]$ ./sig 
^Cchage flag 0 to 1
^Cchage flag 0 to 1
^Cchage flag 0 to 1

优化情况下,键入 CTRL-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 ,但是 while 条件依旧满足,进

程继续运行!但是很明显flflag肯定已经被修改了,但是为何循环依旧执行?很明显, while 循环检查的flflag,

并不是内存中最新的flflag,这就存在了数据二异性的问题。 while 检测的flflag其实已经因为优化,被放在了

CPU寄存器当中。如何解决呢?很明显需要 volatile

[hb@localhost code_test]$ cat sig.c 
#include <stdio.h>
#include <signal.h>
volatile int flag = 0;
void handler(int sig)
{
 printf("chage flag 0 to 1\n");
 flag = 1;
}
int main()
{
 signal(2, handler);
 while(!flag);
 printf("process quit normal\n");
 return 0;
}
[hb@localhost code_test]$ cat Makefile 
sig:sig.c
 gcc -o sig sig.c -O2
.PHONY:clean
clean:
 rm -f sig
 
[hb@localhost code_test]$ ./sig
^Cchage flag 0 to 1
process quit normal

volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量

的任何操作,都必须在真实的内存中进行操作。

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

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

相关文章

Failed to load the JNI shared library “D:\...\jvm.dll

1.解决办法&#xff1a; 64-bit Eclipse requires a 64-bit JVM, and 32-bit Eclipse requires 32-bit JVM--you can not mix-and-match between 32-bit and 64-bit. 2.问题&#xff1a; 下载了Eclipse4.16&#xff0c;openjdk8&#xff0c;双击安装Eclipse无法启动&#x…

侯捷 C++ STL标准库和泛型编程 —— 3 容器(序列式容器)

3 容器 3.1 容器结构分类 分类&#xff1a;序列式容器 Sequence Container&#xff0c;关联式容器 Associative Container 序列式容器&#xff1a;按照放入的次序进行排列 Array 数组&#xff0c;固定大小Vector 向量&#xff0c;会自动扩充大小Deque 双向队列&#xff0c;双…

每日一题 1333. 餐厅过滤器(中等)

感觉应该算是简单题 思路&#xff1a; 按照他提出的要求进行筛选即可学习了 sort 函数中几个参数的用法&#xff0c; keylambda c:(c[1], c[0]) 的形式可以令在排序中当c[1]相等时按照c[0]大小进行排序&#xff0c;reverseTrue 实现降序排列 class Solution:def filterResta…

基于微信小程序的美食推荐系统设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

element日历插件获取显示的第一天和最后一天

和重置日期内容显示 需求是要传入当前显示的第一天和最后一天来获取范围&#xff0c;再判断某个日期是否有内容标记 已知星期排版是固定的&#xff0c;第一天是星期天&#xff0c;最后一天是星期六。通过当月1号和最后一天再往前推算需要展示上个月几天&#xff0c;和下个月几…

Vue3 + TS 自动检测线上环境 —— 版本热更新提醒

&#x1f414; 前期回顾 编写 loading、加密解密 发布NPM依赖包&#xff0c;并实施落地使用_彩色之外的博客-CSDN博客 目录 &#x1f30d; 问题产生 &#x1f916; 性能效率 &#x1fa82; 新建 autoUpdate.ts &#x1f38b; 在App.vue使用 &#x1f30d; 问题产生 当用…

秋招搞个保底offer再说,我换赛道了。

作者&#xff1a;阿秀 InterviewGuide大厂面试真题网站&#xff1a;https://top.interviewguide.cn 这是阿秀的第「312」篇原创 小伙伴们大家好&#xff0c;我是阿秀。 转测开这个话题已经聊过很多次了&#xff0c;以前也分享过不少测开相关的面经&#xff1a; 后端太卷&#x…

Mac电脑视频处理工具 Topaz Video AI for mac

Topaz Video AI是一款强大而易用的视频处理软件&#xff0c;通过人工智能技术提供高质量的视频增强和编辑功能。它可以帮助用户改善视频的质量、修复缺陷、优化图像&#xff0c;并提供丰富的编辑选项&#xff0c;以满足个性化的视频处理需求。无论是专业摄影师、视频编辑人员&a…

vue+Vant,关闭Popup弹框,遮罩层并没有消失

遇到问题&#xff1a; 点击Popup弹框关闭按钮&#xff0c;弹框的遮罩不能正常关闭&#xff0c;如下图。经研究&#xff0c;排除了popup属性问题&#xff0c;最后只能删除代码排除法。 <!--弹框&#xff1a;选号--><van-popupv-model"showNumber"closeablero…

JS 内存泄漏与垃圾回收机制

一、什么是内存泄漏? 内存泄漏&#xff08;Memory Leak&#xff09;是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费&#xff0c;导致程序运行速度减慢甚至系统崩溃等严重后果。通俗点就是指由于疏忽或者错误造成程序未能释放已…

指定名校牛导|肿瘤科医生赴哈佛大学麻省总医院访学进修

世界排名第一的哈佛大学&#xff0c;美国排名第一的研究型医院-麻省总医院&#xff0c;AIMBE fellow头衔的导师&#xff0c;这些条件均符合T医生要求的名校牛导标准。 T医生背景&#xff1a; 申请类型&#xff1a; 单位公派访问学者 工作背景&#xff1a; 三甲医院医生 教育…

在openwrt dnsmasq DHCP中为客户端分配不同的网关和DNS | 旁路由 禁止上网

环境&#xff1a;openwrt dnsmasq PS4/Switch 问题&#xff1a;为路由器下的设备分配不同的网关和DNS&#xff0c;禁止局域网设备上网 解决办法&#xff1a;修改dnsmasq配置文件 背景&#xff1a;Openwrt 的DHCP服务是使用dnsmasq实现的&#xff0c;他可以给内网的客户端设备…

如何减少爬虫产生的网络负载:爬取间隔和缓存控制策略

在进行Python爬虫开发时&#xff0c;我们需要注意控制爬取频率&#xff0c;以减少对目标网站的网络负载。本文将为您分享两种关键策略&#xff1a;爬取间隔和缓存控制。通过合理设置爬取间隔和使用缓存&#xff0c;您可以有效减少网络负载&#xff0c;同时保证数据的实时性和准…

Spring5应用之AOP动态代理开发

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Spring5应用专栏_Aomsir的博客-CSDN博客 文章目录 前言何为动态代理…

使用Packet Tracer了解网络模型及Lab3 - 2

显示TCP/IP协议套件的元素 这些额外的条目在 TCP/IP 套件中扮演着各种角色。如果列出了地址解析协议 &#xff08;ARP&#xff09;&#xff0c;它将搜索 MAC 地址。DNS 负责将名称&#xff08;例如 www.osi.local&#xff09;转换为 IP 地址。其他 TCP 事件负责连接、商定通信…

DETR纯代码分享(九)transformer.py

一、定义DETR Transformer用于DETR模型 """ DETR Transformer class.Copy-paste from torch.nn.Transformer with modifications:* positional encodings are passed in MHattention* extra LN at the end of encoder is removed* decoder returns a stack of …

趣解设计模式之《当代毕加索小王》

〇、小故事 小王最近对画油画非常的感兴趣&#xff0c;尤其是当他参观完毕加索画展之后&#xff0c;更觉得自己有画画天赋了&#xff0c;“这画我自己也能画啊&#xff01;这以后一幅画随随便便买它2、3个亿&#xff0c;这不发财了&#xff01;”于是&#xff0c;他就开始着手…

Java——》IO

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

12KM02E-V0002 3EGM030300R0002 模块化和加固的边缘计算加速

12KM02E-V0002 3EGM030300R0002 模块化和加固的边缘计算加速 随着边缘人工智能解决方案的兴起&#xff0c;对实时洞察和自主决策的需求显著增长。这也带来了对变革性技术的高度需求&#xff0c;这些技术可以在坚固的边缘支持和提供最佳性能。为了应对技术革命&#xff0c;Prem…

C#开发 降.NET版本问题解决笔记

C#开发 降.NET版本问题记录 以下都是我在使用.NET4.7.2版本开发控件完成后又将控件代码降位使用.NET4.5时所遇到的问题&#xff0c;和解决方式 Struct构造函数&#xff1a;“必须将字段完全分配&#xff0c;然后控制权才能返回给调用者。” 原始的代码和错误如下&#xff1a…