【Linux】信号的处理

信号篇终章

文章目录

  • 前言
  • 一、信号的处理
  •         1.可重入函数
  •         2.volatile关键字
  •         3.SIGCHLD信号
  • 总结


前言

在前两篇linux文章中我们详细的讲解了信号的产生和信号的保存,今天来到最后一个重点信号的处理,对于信号的处理我们会重新引入进程地址空间的知识,并且我们会详细的介绍内核是如何对信号进行捕捉的以及什么是可重入函数。


一、信号的处理

上一篇我们讲过,信号是可以不用被立即处理的,如果一个信号之前被block了,当他解除阻塞状态的时候信号会立即被递达,这是因为信号的产生是异步的,当前进程可能正在做着更重要的事情,那么什么时候再处理之前没处理的信号呢?是在当进程从内核态切换回用户态的时候,进程会在操作系统的指导下,进行信号的检测与处理,这里有三种方式:1.默认 2.忽略 3.自定义捕捉

而用户态的意思是:执行用户所写的代码的时候进程所处的状态。

内核态:执行操作系统代码的时候,进程所处的状态。而一般什么时候切换状态呢?1.进程时间片到了,需要切换,就要执行进程切换逻辑。  2.系统调用也需要转为内核态

下面我们引入进程地址空间来讲解一下内核态:

 如上图所示:进程首先找到对应的进程地址空间的地址,然后由页表映射到物理内存,而这里的空间都是用户空间【0,3G】,下面我们加入【3G,4G】的内核空间:

 我们的系统代码是放在内核空间的,CPU加载系统代码也需要先在进程地址空间的内核空间中经过内核级页表找到操作系统在物理内存中存放的数据才加以运行。

1.所有的进程【0,3GB】是不同的,每一个进程都要有自己的用户级页表。

2.所有的进程【3GB,4GB】是一样的,每一个进程都可以看到同一张内核级页表,所有进程都可以通过统一的窗口,看到同一个操作系统。

3.操作系统运行的本质,其实都是在进程的地址空间内运行的。也就是说,无论进程如何切换,【3GB,4GB】是不变的,看到操作系统的内容与进程切换无关。

4.所谓的系统调用的本质,其实就如同调用.so中的方法,在自己的地址空间中进行函数跳转并返回即可。

那么如何从用户态切换到内核态呢?如下图:

 在CPU中有一个CR3寄存器,当这个寄存器里面存的是0时代表正在运行的进程执行级别是内核态,当这个寄存器里面存的是3时代表正在运行的进程执行级别是用户态。所以我们在使用系统调用接口的时候,内部在正式执行调用逻辑的时候会去修改执行级别为内核态才可以。

那么操作系统的本质是什么呢?操作系统本质是一个软件,本质上是一个死循环。并且操作系统有一个时钟硬件,每隔很短的时间向操作系统发送时钟中断,然后操作系统要执行对应的中断处理方法,而进程被调度就是时间片到了,然后将进程对应的上下文等进行保存并切换,然后再选择合适的进程。

下面我们讲解信号的处理过程:

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

 如上图所示,上面是用户态下面是内核态,有一个进程正在运行自己的代码,当时间片到了后就切换为内核态,陷入内核态后处理完我们的任务,而在操作系统内部会包含进程的PCB的相关结构的,这个时候操作系统会检查PCB里面的相关信号的信息,看哪些信号需要被处理,有三种处理方法我们上面已经说过了,我们就将最麻烦的用户自定义方法,如果操作系统发现信号中用户自定义对这个信号的使用比如上图中的handler方法,当操作系统识别到这个方法是被自定义捕捉的就跳转到对应的自定义方法中,当信号捕捉完成后要返回到信号处理的过程中然后回到用户态执行后续的代码,当我们识别到信号没有被block跳转到我们自己写的方法中时,我们用的是内核态还是用户态来执行这个方法呢?这里当然是用户态了,如果是内核态的话系统代码很容易被修改。

由于上面的过程太过复杂,下面我们用一个简单的方式让大家理解信号捕捉过程:

 大家理解的时候可以将上图理解为无穷符号,以上就是我们自定义信号捕捉的简略图。

 下面我们学习一下sigaction这个接口:

 sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1signum是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。actoact指向sigaction结构体.

sa_handler 赋值为常数 SIG_IGN 传给 sigaction 表示忽略信号 , 赋值为常数 SIG_DFL 表示执行系统默认动作, 赋值为一个函数指针表示用自定义函数捕捉信号 , 或者说向内核注册了一个信号处理函 数 , 该函数返回值为void, 可以带一个 int 参数 , 通过参数可以得知当前信号的编号 , 这样就可以用同一个函数处理多种信号。显然, 这也是一个回调函数 , 不是被 main 函数调用 , 而是被系统所调用.
当某个信号的处理函数被调用时 , 内核自动将当前信号加入进程的信号屏蔽字 , 当信号处理函数返回时自动恢复原来的信号屏蔽字, 这样就保证了在处理某个信号时 , 如果这种信号再次产生 , 那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时, 除了当前信号被自动屏蔽之外 , 还希望自动屏蔽另外一些信号 , 则用 sa_mask 字段说明这些需要额外屏蔽的信号, 当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flflags 字段包含一些选项 , 本章的代码都把sa_flflags 设为 0,sa_sigaction 是实时信号的处理函数。
下面我们使用一下这个函数:
int main()
{
    struct sigaction act,oldact;
    memset(&act,0,sizeof(act));
    memset(&oldact,0,sizeof(oldact));
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigaction(2,&act,&oldact);
    return 0 ;
}

下面我们将代码运行起来:

 代码运行起来我们可以看到确实如signal函数一样可以对信号进行捕捉,上面的解释说sa_mask会自动恢复原来的信号屏蔽字,下面我们就写个代码来验证一下:

我们可以看到第一次处理了2号信号后,2号信号被block了也就是说内核确实自动将2号信号加入进程的信号屏蔽字。 

1.可重入函数

我们先用一张图来引出现象:

 有一个insert插入函数,每次进行头插,但是当有多个结点都要插入到头节点的下一个位置时,就会出现节点丢失,内存泄漏的问题,如图4原先链接node2但是当另一个函数重入后,head连接上了node1,这个时候node2这个节点就丢失了,导致内存泄漏的问题。

main 函数调用 insert 函数向一个链表 head 中插入节点 node1, 插入操作分为两步 , 刚做完第一步的时候 , 因为硬件中断使进程切换到内核, 再次回用户态之前检查到有信号待处理 , 于是切换到 sighandler 函数,sighandler 也调用 insert 函数向同一个链表 head 中插入节点 node2, 插入操作的两步都做完之后从sighandler返回内核态 , 再次回到用户态就从 main 函数调用的 insert 函数中继续往下执行 , 先前做第一步之后被打断, 现在继续做完第二步。结果是 ,main 函数和 sighandler 先后向链表中插入两个节点 , 而最后只有一个节点真正插入链表中了。
像上例这样 ,insert 函数被不同的控制流程调用 , 有可能在第一次调用还没返回时就再次进入该函数 , 这称为重入,insert 函数访问一个全局链表 , 有可能因为重入而造成错乱 , 像这样的函数称为不可重入函数 , 反之 ,如果一个函数只访问自己的局部变量或参数, 则称为可重入 (Reentrant) 函数。
也就是说可重入函数的概念就是:不同的执行流中同一个函数被重复进入,如果进入没有问题,该函数被称为可重入函数,如果进入有问题,那么该函数被称为不可重入函数。那么如何知道一个函数是否可以重入呢? 第一:调用了malloc或者free函数,因为malloc是用全局链表来管理堆的,所以调用malloc和free一定不可以重入。第二:调用了标准IO库函数,标准IO库的很多实现都以不可重入的方式使用全局数据结构。

2.volatile关键字

volatile关键字我们在学C/C++的时候应该都认识,这个关键字是防止编译器对我们的代码做优化。

下面我们用一份简单的代码用信号来引出volatile的原理。首先创建两个文件,一个.c文件一个makefile。

#include <stdio.h>
#include <signal.h>


int quit = 0;

void handler(int signo)
{
     printf("change quit from 0 to 1\n");
     quit = 1;
}

int main()
{
    signal(2,handler);
    //注意这里我们故意没有携带while的代码块,故意让编译器认为在main中,quit只会被检测
    while (!quit);
    printf("main quit  正常\n");
    return 0;
}

上面这段代码的意思是:首先我们将2号信号捕捉让其执行我们自己的handler方法,然后我们有一个全局变量quit,进入自定义方法后先打印quit即将由0变成1,然后将quit变成1,然后进入main函数中,while循环中是quit的逻辑反,本来循环中quit为真就一直在循环中,取反后变成了只要quit为假也就是0就会一直在死循环,正常情况下不会进入循环直接打印main quit ,下面我们看一下gcc的优化级别:

 我们可以看到有O0,O1,O2,O3这几个优化级别,我们直接用O2这个级别演示一下,只需要在gcc后面加上级别即可:

 运行后我们可以发现程序一直在死循环中,下面我们不用gcc的优化试一下:

 我们可以看到当编译器对我们的代码没有优化的时候反而我们的代码可以正常运行,也就是说编译器的优化并不都是往好的方式优化,下面我们解释一下原理:

 如上图所示:代码是存储在内存中的,而while判断也是一段代码,这段代码也是会被CPU所执行的,而在while循环内部有quit的判断,这个时候会将quit这段代码加载到CPU的寄存器当中,根据寄存器中quit的大小来在while循环中做判断,(具体CPU如何知道执行到哪一句代码是因为CPU中会有一个PC指针,这个指针指向下一段代码的位置)。而正常的情况如下图:

 当我们的信号捕捉的自定义方法将quit改为1时,内存中的quit就变成1了,CPU读取代码将quit=1这段代码加载到寄存器当中然后判断while,这个时候不满足while循环的条件打印一句话并退出即可。但是经过编译器的优化后就变成了一下这样:

由于while是一个循环,每次都会将quit的数据加载到寄存器中判断while是否满足条件,当编译器发现while这个循环内部没有任何更改quit判断语句的代码块,这个时候编译器就会觉得不用每次都将quit加载到寄存器中做判断,所以优化后就变成了:第一次将quit=0加载到寄存器判断while语句后,以后就不再从内存加载quit的数据到寄存器中了,而是直接用第一次加载到寄存器中的quit值来判断while,所以这就是while一直死循环的原因,因为quit在while中判断的时候一直为假!

 那么为了不让编译器给我们的代码优化,我们就要告诉编译器保证每次检测都要尝试着从内存中进行数据读取,不要用寄存器中的数据,让内存数据不可见。

下面我们演示一下:

 这个时候我们的程序就不会被优化,从而可以按照我们的预期来运行了。在这里我们要说明一下,我们理解编译器的优化实际上是:1.编译器优化的本质是在代码上动手脚。2.CPU其实很笨,用户喂给它什么代码它才执行什么代码。

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

3.SIGCHLD信号

我们在进程那一篇文章讲过用 wait waitpid 函数清理僵尸进程 , 父进程可以阻塞等待子进程结束 , 也可以非阻塞地查询是否有子进程结束等待清理( 也就是轮询的方式 ) 。采用第一种方式 , 父进程阻塞了就不能处理自己的工作了 ; 采用第二种方式 , 父进程在处理自己的工作的同时还要记得时不时地轮询一 下, 程序实现复杂。其实, 子进程在终止时会给父进程发 SIGCHLD 信号 , 该信号的默认处理动作是忽略 , 父进程可以自定义 SIGCHLD 信号的处理函数, 这样父进程只需专心处理自己的工作 , 不必关心子进程了 , 子进程终止时会通知父进程 , 父进程在信号处理函数中调用wait 清理子进程即可。
下面我们编写一个程序完成以下功能 : 父进程 fork 出子进程 , 子进程调用 exit(2) 终止 , 父进程自定义 SIGCHLD 信号的处理函数 , 在其中调用wait 获得子进程的退出状态并打印。
事实上 , 由于 UNIX 的历史原因 , 要想不产生僵尸进程还有另外一种办法 : 父进程调用 sigaction SIGCHLD 的处理动作置为SIG_IGN, 这样 fork 出来的子进程在终止时会自动清理掉 , 不会产生僵尸进程 , 也不会通知父进程。系统默认的忽略动作和用户用sigaction 函数自定义的忽略 通常是没有区别的 , 但这是一个特例。此方法对于 Linux 可用 , 但不保证在其它UNIX 系统上都可用。下面我们会编写程序验证这样做不会产生僵尸进程。
下面我们先演示第一种方法:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void handler(int signo)
{
    printf("捕捉到一个信号:%d,who: %d\n",signo,getpid());
}
int main()
{
    signal(SIGCHLD,handler);
    pid_t id = fork();
    if (id==0)
    {
        //child
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    //父进程
    while (1)
    {
        sleep(1);
    }
    return 0;
}

以上代码的意思是:首先创建一个子进程,在子进程中我们设置一个为5的计数器,在这5S内打印子进程的pid和子进程的ppid,为了不要太快我们中途sleep1秒。5秒后直接退出子进程。然后进入父进程,父进程什么也不干一直死循环。当子进程结束会给父进程发SIGCHLD信号,我们将这个信号捕捉一下,如果捕捉到了我们就打印这个信号最终是发给谁的,如果是发给父进程的就打印父进程的pid。下面我们运行起来:(在运行前记得将编译器的优化关闭)

 通过结果我们可以看到确实子进程在退出前会给父进程发送17号SIGCHLD信号,下面我们将进程等待加进去,当捕捉到子进程退出的信号时我们就让父进程等待子进程防止子进程变成僵尸状态:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
pid_t id;
void handler(int signo)
{
    printf("捕捉到一个信号:%d,who: %d\n",signo,getpid());
    sleep(5);
    pid_t res = waitpid(-1,NULL,0);
    if (res>0)
    {
        printf("wait success,res:%d,id:%d\n",res,id);
    }
}
int main()
{
    signal(SIGCHLD,handler);
    id = fork();
    if (id==0)
    {
        //child
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    //父进程
    while (1)
    {
        sleep(1);
    }
    return 0;
}

下面我们在解释一下wait函数的参数:

 我们可以看到当第一个参数为-1时意思是等待任意一个子进程。我们原先第一个参数都是要等待进程的id,现在我们可以用用其他参数。第三个参数设为0意思就是等待方式为阻塞式等待,我们讲过阻塞式等待会让等待的进程什么事也干不了只能等待子进程。第二个参数是退出结果,我们不关心退出结果所以设置为空即可。最后判断一下如果等待成功我们就打印等待成功进程的pid,在这里要注意waitpid的返回值,当我们等待成功默认返回的就是子进程的pid。

 通过以上代码我们可以看到最后成功等待了子进程。也就是说我们现在这样的方法比我们之前让父进程什么也不干只等待子进程退出的效率高多了。

注意:以上代码的要求,如果你的父进程没有事干,那么还是用以前的方法也就是阻塞式等待,因为父进程闲着也是闲着。

如果你的父进程很忙,而且不退出,就可以用上述我们信号的方法。

当然我们以上的代码实际上不完全对,因为遇到多个子进程退出的时候是有问题的,下面我们用代码写出这个问题:

 现在我们会有10个子进程,但是我们之前说过,pending只有一个符号位记录17号信号,第一个子进程退出后pending位变成1,下一次来了还是变成1,那么这就出问题了,因为当父进程忽略子进程的退出信号的时候pending位还是1,这个时候下一个子进程再发信号将pending的位又置为1那么前面那个忽略的信号就丢失了,我们后面想回收之前忽略的信号也没法回收,所以代码的现象是10个进程只能回收几个子进程。

 运行起来后我们发现10个进程才回收了2个子进程,要解决这个情况很简单,只需要让父进程死循环的等待回收所有子进程即可:

 通过运行结果我们可以看到确实将所有的子进程都成功回收了,那么如果我们有10个子进程,但是只有10个子进程需要退出,这样的情况该怎么办呢?我们只需要将等待方式设为非阻塞式等待,也就是WNOHANG

 

下面我们演示第二种方法:父进程调用sigaction将SIGCHLD的处理动作设置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程也不会通知父进程,下面我们写一下代码:

 

 可以看到此方法非常简单,但是除了linux不保证其他系统也可以这样哦。


总结

以上就是我们信号处理的所有内容了,对于信号处理这个章节我们需要理解进程地址空间以及内核是如何进行信号捕捉的,下一篇我们发布的linux文章是线程。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

19c rac环境修改pubic, vip,scan ip步骤

19c rac环境第一次修改public、vip和scan ip&#xff0c;和11g还是稍有不同。首先说明下环境 具体步骤如下 1、修改public地址&#xff0c;关闭实例后使用root用户操作 [rootdb1 ~]# ./oifcfg getif bond0 172.20.30.0 global public bond2 100.100.100.0 global clust…

Vivado 下 IP核之 PLL实验

目录 实验任务&#xff1a;Vivado 下 IP核之 PLL实验 1、实验简介 2、实验环境 3、实验原理 3.1、PLL IP核简介 3.2、MMCM 和 PLL 各自的含义以及两者的区别 3.3、PLL 分频 和 倍频 的工作原理 3.4、实验任务 4、建立工程 4.1、PLL IP 核配置 4.2、模块设计 4.…

计算卸载-论文05-双层优化(无线充电与卸载)

标题&#xff1a;《A Divide-and-Conquer Bilevel Optimization Algorithm for Jointly Pricing Computing Resources and Energy in Wireless Powered MEC》 期刊&#xff1a;IEEE TRANSACTIONS ON CYBERNETICS&#xff0c;2022 一、理论梳理 问题&#xff1a;相比于移动云…

Espresso Sequencer:针对Rollup生态的Decentralized Shared Sequencing Layer

1. 引言 前序博客&#xff1a; Rollup去中心化Rollup DecentralizationAztec 征集 Rollup Sequencer去中心化提案Espresso Sequencer&#xff1a;去中心化RollupsRadius&#xff1a;针对Rollup生态的Trustless Shared Sequencing层 当前的L2 Rollup方案在扩容的同时&#xf…

​AI + 非遗文化传播,人工智能师资培训重磅招募

大语言模型热度空前&#xff0c;诸如文心一言、 ChatGPT 等已经能够与人对话互动、回答问题、协助创作&#xff0c;逐渐应用于人们的工作和生活&#xff0c;也引发了社会热议。为推动大模型及人工智能相关专业人员的培养&#xff0c;同时将人工智能技术融入非遗文化传播&#x…

心血管疾病预测--逻辑回归实现二分类

一、实现效果 实现心血管疾病的预测准确率70%以上 二、数据集介绍 数据共计70000条&#xff0c;其中心血管疾病患者人数为34979&#xff0c;未患病人数为35021。数据特征属性12个分别为如下所示:生理指标(性别、年龄、体重、身高等)、 医疗检测指标(血压、血糖、胆固醇水平等)…

青龙面板使用教程,以及安装

1. 青龙面板使用教程&#xff0c;以及安装 首先青龙面板是在docker里面的&#xff0c;我们要安装一个docker 我这里只有debian 11 安装的教程 如何在debian11上安装docker - 知乎 这个文章不错了&#xff0c;按命令执行就好了&#xff0c;其他操作系统的。去网上搜索安…

Helm方式部署 zookeeper+kafka 集群 ——2023.05

文章目录 版本概况一、添加helm仓库二、安装部署集群2.1 在线安装zookeeperkafka集群2.2 离线安装zookeeperkafka集群 三、验证kafka与zookeeper是否绑定四、测试集群附&#xff1a;可改善地方卸载应用 版本概况 服务版本centos7.9kubernetesv1.20.15helmv3.10.1zookeeper3.8.…

Python通过natcap.invest库调用InVEST模型批处理数据(Carbon Storage and Sequestration模块)

InVEST&#xff08;Integrated Valuation of Ecosystem Servicesand Tradeoffs&#xff09;生态系统服务和权衡的综合评估模型&#xff0c;旨在通过模拟不同土地覆被情景下生态系统物质量和价值量的变化。它提供了多种生态系统服务功能评估&#xff0c;包括了淡水生态系统评估、…

【问题记录】USB monitor抓包工具显示音频数据CRC error

一&#xff0c;简介 在进行UAC2.0调试的过程中&#xff0c;使用USB monitor抓包工具抓取音频流数据出现数据错乱现象&#xff0c;本文对该问题进行分析记录。 二&#xff0c;问题记录及分析过程 2.1 先看下正常的抓包数据是什么样子&#xff1a; 从上图可以看出&#xff0c;…

《The Element of Style》阅读笔记 —— 章节 I Elementary Rules of Usage

前言&#xff1a;本科期间担任科研助理时&#xff0c;有幸从导师那里借来这本书通读&#xff0c;只记得自己当时在本子上做了一些笔记&#xff0c;但是想不起来具体记了什么&#x1f602;前段时间再次从学院的讲座活动中听闻这本书&#xff0c;决定重温一遍&#xff0c;本篇为此…

js原生实现pc端日历组件

业务场景 客户要求日期选择使用pc端的样式&#xff0c;前端在x5平台上&#xff0c;也就是需要用原始html、js、css等实现&#xff0c;笔者使用于 原生javascript封装的PC端日历插件&#xff0c;不依赖任何第三方插件&#xff0c;复制的代码文件较长&#xff0c;可跳到 总结 处…

React 内 JSX了解及使用

目录 jsx基本概念及例子 jsx的基本用法 JSX表达式&#xff08;有执行结果&#xff09; {} 语法嵌入不同的值 所呈现出来的特点 标签内行内样式 自定义组件 驼峰命名 Jsx的全称是Javascript XML&#xff0c;react定义的一种类似XML的JS拓展语法&#xff1a;JSXML&#xf…

Go Etcd 分布式锁实战

1 分布式锁概述 谈到分布式锁&#xff0c;必然是因为单机锁无法满足要求&#xff0c;在现阶段微服务多实例部署的情况下&#xff0c;单机语言级别的锁&#xff0c;无法满足并发互斥资源的安全访问。常见的单机锁如Java的jvm锁Lock、synchronized&#xff0c;golang的Mutex等 对…

类和对象(三)

目录 前言 1.再谈构造函数 1.1 初始化列表 1.2 explicit关键字 2.static成员 2.1 概念 2.2 特性 3.友元 3.1. 友元函数 3.2 友元类 4.内部类 5.匿名对象 6.拷贝对象时的一些编译器优化 前言 今天小编给大家介绍的就是类最后的相关内容&#xff0c;希望大家好好学习理…

什么是可信时间戳?可信时间戳电子取证有效吗?

电子数据具有脆弱性、易变性、隐蔽性、载体多样性等特点&#xff0c;容易被复制、删除、篡改且难以被发现。因此&#xff0c;电子数据在实际的司法认定过程中&#xff0c;很难准确鉴定其生成的时间以及内容的真实性、完整性。可信时间戳是一种公认的技术手段&#xff0c;可为电…

精选 100 种最佳 AI 工具大盘点

为了应对对精简流程和数据分析日益增长的需求&#xff0c;整合人工智能工具在多个领域变得至关重要。 本文精选了2023年可用的100种最佳人工智能工具&#xff0c;旨在提高您的生产力、创造力和效率。 以下是 2023 年排名前 100 的人工智能工具&#xff1a; Aidoc&#xff1a;A…

Python获取酷得music并下载,获得无限听

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 环境使用: Python 3.8 Pycharm 模块使用: requests >>> pip install requests re win R 输入cmd 输入安装命令 pip install 模块名 (如果你觉得安装速度比较慢, 你可以切换国内镜像源) 代码实现步骤 <基…

springCloud使用maven

springCloud项目使用maven集成nexus 一&#xff1a;故事背景二&#xff1a;基础概念2.1 什么是Maven2.2 什么是nexus 三&#xff1a;实操3.1 setting文件配置3.2 项目内pom.xml配置3.3 jar上传3.3.1 maven插件上传3.3.2 mvn命令上传3.3.3 页面上传3.3.4 通过Rest的方式进行上传…

麻了,真的不想做测试了...

前言 有不少技术友在测试群里讨论&#xff0c;近期的面试越来越难了&#xff0c;要背的八股文越来越多了,考察得越来越细&#xff0c;越来越底层&#xff0c;明摆着就是想让我们徒手造航母嘛&#xff01;实在是太为难我们这些测试工程师了。 这不&#xff0c;为了帮大家节约时…