Linux:进程信号(二.信号的保存与处理、递达、volatile关键字、SIGCHLD信号)

上次介绍了:(Linux:进程信号(一.认识信号、信号的产生及深层理解、Term与Core))[https://blog.csdn.net/qq_74415153/article/details/140624810]


文章目录

  • 1.信号保存
    • 1.1递达、未决、阻塞等概念
    • 1.2再次理解信号产生与保存
    • 1.3信号集操作函数
      • sigset_t类型
      • sigprocmask系统调用
      • sigpending系统调用
  • 2.信号的处理/递达
    • 2.1信号处理时机与过程
    • 2.2用户态和内核态
    • 2.3再看进程地址空间
      • 谁来运行OS
    • 2.4信号的捕捉—sigaction()函数
  • 3.补充知识
    • 3.1可重入函数
    • 3.2volatile关键字
    • 3.3 SIGCHLD信号


1.信号保存

1.1递达、未决、阻塞等概念

  1. 信号未决(Pending):当信号产生时,会首先进入未决状态,即信号还没有被进程处理。此时,信号被标记为未决状态,等待进程处理。

  2. 信号递达(Delivery):当进程解除对信号的阻塞时,信号才会被递达,即信号被传递给进程的信号处理函数进行处理

    三种信号处理方式:

    1. 默认处理(Default Handling):每个信号都有一个默认的处理方式,当信号递达时,操作系统会执行默认的信号处理动作,传入SIG_DFL

    2. 自定义处理(Custom Handling):进程可以通过设置信号处理函数(一般是handler)来自定义对信号的处理方式。当信号递达时,操作系统会调用进程设置的信号处理函数来处理信号

    3. 忽略处理(Ignore Handling):进程还可以选择忽略某个信号,即在信号递达时不做任何处理。通过将信号处理函数设置为 SIG_IGN,进程可以忽略某个信号

  3. 阻塞信号:进程可以选择阻塞某个或多个信号,使其在未决状态下等待。被阻塞的信号不会递达,保持在未决状态,直到进程解除对此信号的阻塞

1.2再次理解信号产生与保存

在操作系统中,进程信号相关的"Pending位图"和"Block位图"是两种数据结构,用于跟踪进程当前挂起/未决(pending)的信号和已经阻塞(blocked)的信号

  1. Pending位图

    • 作用:Pending位图用于记录当前对进程发送但尚未被处理的信号。当操作系统向进程发送信号时,如果进程当前不能立即处理该信号(比如正在处理其他信号或忙于执行其他任务),该信号会被添加到进程的Pending位图中。
    • 操作:操作系统会定期检查进程的Pending位图,并根据信号处理方式(默认处理、自定义处理、忽略处理)来决定如何处理挂起的信号。
  2. Block位图

    • 作用:Block位图用于记录当前被阻塞的信号。进程可以选择阻塞某些信号,使得这些信号被阻塞不会被递送给进程。
    • 操作:当信号被阻塞时,该信号会被添加到进程的Block位图中。被阻塞的信号不会被递送给进程,直到解除阻塞。
    • 特点:Block位图记录了进程当前被阻塞的信号,帮助进程控制哪些信号可以递送到进程。

在这里插入图片描述

其中信号的阻塞与否,跟是否收到信号毫无关系

对应信号在进程的信号未决位图中的比特位会在信号递达前被设置为1,表示信号需要处理,而在信号被处理完后会被清零,即改为0

是先清0,再进行递达

在这里插入图片描述

而进程能识别信号,也是因为早在未收到信号之前,我们就已经知道是否堵塞,怎么处理了(利用上述三个表)

  • 信号处理表:在进程创建时,内核会为其分配一个信号处理表,用于记录每个信号对应的信号处理函数(Signal Handler)。当进程收到一个信号时,内核会根据信号处理表中对应信号的处理函数来执行相应的操作。
  • 信号未决位图:在进程接收到一个信号时,内核会更新进程的信号未决位图,用于记录当前未被屏蔽的信号。这个位图帮助进程确定是否有信号需要处理。
  • 信号挂起位图:当一个信号被进程接收但尚未处理时,内核会将这个信号标记为挂起,即更新进程的信号挂起位图。这个位图帮助进程确定哪些信号需要等待处理。

这三个表是操作系统内核为了管理进程信号处理而设计的数据结构,它们在进程创建时被初始化并与进程关联,帮助进程识别和处理信号

1.3信号集操作函数

sigset_t类型

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次(只表示收到否信号,对于信号的数量没办法也没必要),阻塞标志也是这样表示的。

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

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

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

对于我们使用者来说,应该将sigset_t类型看作一个抽象的信号集合,而不需要关心其内部的具体实现细节。sigset_t类型的具体表示方式可能会因系统而异,可能是一个位图、一个数组或其他数据结构,但这些细节对于使用者来说并不重要。

我们使用者应该通过系统提供的函数来操作sigset_t变量,比如sigemptyset、sigfillset、sigaddset、sigdelset等函数来对信号集进行操作。这些函数会根据系统的具体实现来正确处理信号集的操作,确保其正确性和可移植性。

因此,直接打印sigset_t变量是没有意义的,因为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在该信号集中添加或删除某种有效信号

sigprocmask系统调用

sigprocmask是一个系统调用,用于检查或修改当前进程的信号屏蔽集(signal mask)。信号屏蔽集是一个用来指定哪些信号在进程处理信号时应该被阻塞的集合。通过操作信号屏蔽集,进程可以控制哪些信号可以被接收和处理,哪些信号应该被暂时屏蔽。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明:

  • how:表示对信号屏蔽集的操作方式,有三种取值:
    • SIG_BLOCK:将set中指定的信号添加到当前信号屏蔽集中。
    • SIG_UNBLOCK:从当前信号屏蔽集中移除set中指定的信号。
    • SIG_SETMASK:将当前信号屏蔽集设置为set中指定的信号集。
  • set:指向一个sigset_t类型的指针,用于指定要操作的信号集
  • oldset:指向一个sigset_t类型的指针,用于存储之前的信号屏蔽集

返回值:

  • 如果函数调用成功,返回0;如果出现错误,返回-1,并设置errno变量来指示错误类型。

功能:

  • sigprocmask函数允许进程检查或修改当前进程的信号屏蔽集。
  • 通过how参数指定的操作,可以添加、移除或替换信号屏蔽集中的信号。
  • 如果oldset参数不为NULL,则会将之前的信号屏蔽集存储到oldset中。

sigpending系统调用

sigpending是一个系统调用,用于获取当前进程挂起/未决(pending)的信号集。挂起的信号是指已经发送给进程但尚未被处理的信号。通过sigpending函数,进程可以查询当前有哪些信号处于挂起状态,以便进一步处理这些信号。

#include <signal.h>
int sigpending(sigset_t *set);

参数说明:

  • set:指向一个sigset_t类型的指针,用于存储当前进程挂起的信号集。

返回值:

  • 如果函数调用成功,返回0;如果出现错误,返回-1,并设置errno变量来指示错误类型。

功能:

  • sigpending函数允许进程获取当前进程挂起的信号集。
  • 通过set参数返回当前进程挂起的信号集,可以进一步对这些信号进行处理。

2.信号的处理/递达

在信号处理中,一般情况下有三种处理方式,分别是:

  1. 忽略信号(Ignore):进程可以选择忽略某些信号,这样当该信号到达时,系统不会采取任何操作,也不会调用任何信号处理函数。一些信号(比如SIGKILL和SIGSTOP)是不能被忽略的,它们具有特殊的含义和作用。
  2. 执行默认操作(Default Action):每个信号都有一个默认的处理方式,当进程接收到信号时,系统会执行该信号的默认操作。比如,当进程接收到SIGINT信号(通常由Ctrl+C触发),系统会默认终止进程的执行。
  3. 捕捉信号并执行处理函数(Signal Handling):进程可以捕捉信号并注册相应的信号处理函数,当接收到信号时,系统会调用该处理函数来处理信号。进程可以自定义信号处理函数,根据需要对信号进行处理,比如记录日志、关闭文件、释放资源等。

2.1信号处理时机与过程

我们之前只是泛泛的讲:进程会在合适时候进行对信号的处理,那什么是合适的时候?——进程从内核态切换会用户态的时候,信号会被检测并处理

每次进程从内核态切换到用户态时,操作系统会依次检查进程是否有未处理的信号。如果有未处理的信号,操作系统会根据信号的处理方式(比如忽略、捕获、默认处理等)来进行相应的处理。如果信号没有被阻塞,操作系统会执行信号处理程序来处理该信号,然后继续执行用户态程序。

在这里插入图片描述

在第三步我们讨论的是自定义处理,如果是默认和忽略呢?

  • 默认:更改PCB的状态即可观在是内核身份,直接杀掉进程
  • 忽略:处理这个信号什么都不做,直接把pending表对应比特位置为0

为什么在第四步里,特地回到用户态执行自定义处理函数:操作系统不相信任何人,不会轻易执行用户的代码,因为用户代码可能包含恶意代码或错误代码,可能会导致系统崩溃、数据泄露等安全问题

2.2用户态和内核态

用户态和内核态是操作系统中的两种运行模式,用于区分程序的权限和访问级别。下面是它们的主要特点和区别:

  1. 用户态(User Mode):

    • 用户态是指程序在执行时所处的一种权限较低的状态,程序在用户态下只能访问受限的资源和执行受限的操作。
    • 在用户态下,程序运行在用户空间,只能访问自己的内存空间和受限的系统资源,不能直接访问操作系统内核或其他进程的内存空间(内核空间)。
    • 用户态下的程序通常是普通应用程序,如文本编辑器、浏览器等,它们无法直接执行特权指令或访问系统底层资源。
  2. 内核态(Kernel Mode):

    • 内核态是指程序在执行时所处的一种权限较高的状态,程序在内核态下具有更多的权限和访问系统资源的能力。
    • 在内核态下,程序运行在内核空间,可以直接访问系统内核和底层资源,执行特权指令和进行敏感操作。
    • 内核态下的程序通常是操作系统内核的一部分,如设备驱动程序、系统调用处理程序等,它们负责管理系统资源、处理中断、执行特权操作等。

我们不同的状态主要是不同的权限:通过改变CPU内的执行权限,设置了寄存器内的特定标志位,来改变状态

2.3再看进程地址空间

在这里插入图片描述

进程无论如何切换,总能找到OS:我们访问OS,本质就是通过进程的地址空间的[3,4]GB的内核空间来访问的

调用系统调用也是在地址空间内进行的

在操作系统内核中,通常会有一个系统调用表(System Call Table)用于存储系统调用号与对应系统调用处理程序的映射关系。当用户进程发起系统调用时,会将系统调用号放入特定寄存器中,CPU根据系统调用号找到对应的系统调用处理程序在系统调用表中的位置,然后跳转到该函数的地址进行调用。

在这个过程中,操作系统内核会确保系统调用表的起始虚拟地址是已知的,并且系统调用号与处理程序的映射关系是正确的。通过这种方式,CPU能够根据系统调用号正确地找到对应的系统调用处理程序,并执行相应的操作。

谁来运行OS

在这里插入图片描述

2.4信号的捕捉—sigaction()函数

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字。如果我们处理完对应的信号,该信号默认也会从信号屏蔽字中进行移除——不想让信号,嵌套式进行捕捉处理(正在处理时你又来了,那就又去调用处理函数)

sigaction()函数是用于设置和修改信号处理程序的系统调用函数。通过sigaction()函数,进程可以指定在接收到特定信号时应该执行的处理程序。这个处理程序可以是系统默认的处理方式,也可以是用户自定义的处理函数。

sigaction()函数的原型如下:

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

其中,signum参数指定了要设置的信号的编号,act参数指定了新的信号处理方式,oldact参数用于保存之前的信号处理方式。

二者都是struct sigaction类型的,对于struct sigaction

在这里插入图片描述

  1. void (*sa_handler)(int):这是一个函数指针,用于指定信号处理函数的地址。当接收到信号时,系统会调用这个函数来处理信号。函数接受一个整型参数,表示接收到的信号编号。如果将sa_handler设置为SIG_IGN,表示忽略该信号;将其设置为SIG_DFL,表示使用系统默认的信号处理方式。

  2. void (*sa_sigaction)(int, siginfo_t *, void *):这也是一个函数指针,用于指定扩展的信号处理函数的地址。与sa_handler不同的是,sa_sigaction函数接受三个参数:第一个参数是信号编号,第二个参数是一个指向siginfo_t结构体的指针,其中包含了关于信号的更多信息,第三个参数是一个指向void类型的指针。(一般用于实时信号,我们不管这个

  3. sigset_t sa_mask:这是一个信号集合,用于指定在信号处理函数执行期间需要屏蔽的信号。如果有信号在sa_mask指定的信号集合中,则这些信号会被阻塞,直到信号处理函数执行完毕。

  4. int sa_flags:用于指定信号处理的行为。可以是以下几个标志的组合:

  • SA_RESTART:表示系统调用在接收到信号后会自动重启。
  • SA_NOCLDSTOP:子进程暂停和继续时不会产生SIGCHLD信号。
  • SA_NODEFER:不会在执行信号处理函数期间阻止同一信号的传递。
  • SA_SIGINFO:表示使用sa_sigaction字段指定的信号处理函数。(我们一般设置为0就行了
  1. void (*sa_restorer)(void):这是一个保留字段,已经废弃,不再使用

返回值为0表示函数调用成功,返回-1表示函数调用失败。在函数调用失败的情况下,可以通过errno全局变量获取具体的错误信息。

通过sigaction()函数,进程可以设置信号的处理方式为以下几种之一:

  • 忽略信号(SIG_IGN
  • 执行默认处理方式(SIG_DFL
  • 指定自定义的信号处理函数
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signum)
{cout << "收到了信号:" << signum << endl;
}int main()
{struct sigaction act, oldact;act.sa_handler = handler;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(2, &act, &oldact); // 进行信号捕捉while (true)sleep(2);return 0;
}

3.补充知识

3.1可重入函数

被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,函数有可能因为重入而造成错乱,像这样的函数称为不可重入函数

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

可重入函数(Reentrant Function)也称为可重入代码(Reentrant Code)或重入函数(Reentrant Routine),是指在并发执行环境中,能够被多个线程同时调用的函数。这种函数能够在任何时候被中断,并在之后从中断点恢复执行,而不会导致数据错误或系统崩溃。

为了实现可重入性,可重入函数必须满足以下条件:

  1. 不使用静态(全局)非常量数据:静态或全局非常量数据可能在多个线程之间共享,如果一个线程修改了这些数据,其他线程可能无法正确地读取或写入这些数据,导致数据错误。
  2. 不调用不可重入函数:如果一个函数调用了另一个不可重入的函数,那么它本身也将是不可重入的。
  3. 不返回指向静态(全局)非常量数据的指针:与第一条类似,返回这样的指针可能导致其他线程错误地修改或读取数据。
  4. 使用局部变量:局部变量存储在函数的栈帧中,每个函数调用都有自己的栈帧,因此局部变量是线程私有的,不会被其他线程干扰。
  5. 对共享资源的访问进行保护:如果函数需要访问共享资源(如文件、数据库、共享内存等),则需要使用适当的同步机制(如互斥锁、信号量等)来保护这些资源,防止数据竞争和冲突。

3.2volatile关键字

volatile 关键字在 C 和 C++ 语言中是一个类型限定符,它告诉编译器不要对访问该关键字声明的变量的代码进行优化,即每次都需要从内存中读取变量的值,而不是使用存储在寄存器中的副本。这是为了确保多线程环境或者硬件中断等场景下,对该变量的访问总是最新的、未被其他线程或硬件修改过的值。

有时因为编译器优化的原因,会导致我们代码出错

#include <stdio.h>
#include <unistd.h>
#include <signal.h>int g_flag = 0;void changeflag(int signo)
{(void)signo;printf("将g_flag,从%d->%d\n", g_flag, 1);g_flag = 1;
}int main()
{signal(2, changeflag);while(!g_flag); // 故意写成这个样子, 编译器默认会对我们的代码进行优化//因为,g_flag一直都没使用过printf("process quit normal\n");return 0;
}

这里,如果编译器进行优化,会把内存里的g_flag拷贝一份到寄存器里,那下一次判断直接从寄存器里拿。不用再去内存里拿,收到信号2后我们更改的是内存里的g_flag,但是我们while判断的是寄存器里的g_flag——寄存器屏蔽了内存

3.3 SIGCHLD信号

SIGCHLD信号是在Linux系统中用于进程间通信的一种机制。具体来说,当子进程终止或停止时,子进程会向其父进程发送SIGCHLD信号。这个信号是子进程状态改变时发送给父进程的信号,用于通知父进程其子进程的状态已经发生了变化。

父进程可以捕获这个信号,并通过调用如wait()或waitpid()等函数来获取子进程的退出状态、终止原因等信息。SIGCHLD信号常用于以下几种情况:

  1. 子进程终止,父进程需要回收子进程的资源。
  2. 父进程需要等待子进程的状态改变,比如子进程终止或停止。
  3. 父进程需要在子进程终止后进行一些操作。

处理SIGCHLD信号时,通常会在信号处理函数中循环调用waitpid()函数来非阻塞等待子进程状态改变,以避免僵尸进程的产生。

有可能:有100个子进程,有50个退出了,50个还没有。那么在循环到51次时,waitpid会一直堵塞住,父进程就一直卡在那里,所以不能堵塞等待

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>void CleanupChild(int signum)
{while (true){pid_t rid = waitpid(-1, nullptr, WNOHANG); // -1 : 回收任意一个子进程;这里非堵塞if (rid > 0)//等待成功{std::cout << "wait child success: " << rid << std::endl;}else if (rid <= 0)break;}
}int main()
{signal(SIGCHLD, CleanupChild);for (int i = 0; i < 100; i++){pid_t id = fork();if (id == 0){// childint cnt = 5;while (cnt--){std::cout << "I am child process: " << getpid() << std::endl;sleep(1);}std::cout << "child process died" << std::endl;exit(0);}}// fatherwhile (true)sleep(1);return 0;
}

事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用

  signal(SIGCHLD, SIG_IGN);//直接这样就行

好了今天就到这里啦

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

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

相关文章

Pytorch深度学习实践(9)卷积神经网络

卷积神经网络 全连接神经网络 神经网络中全部是线性模型&#xff0c;是由线性模型串联起来的 全连接网络又叫全连接层 卷积神经网络 在全连接神经网络中&#xff0c;由于输入必须是一维向量&#xff0c;因此在处理图像时必须要对图像矩阵进行拉伸成一维的形式&#xff0c;…

【算法】布隆过滤器

一、引言 在现实世界的计算机科学问题中&#xff0c;我们经常需要判断一个元素是否属于一个集合。传统的做法是使用哈希表或者直接遍历集合&#xff0c;但这些方法在数据量较大时效率低下。布隆过滤器&#xff08;Bloom Filter&#xff09;是一种空间效率极高的概率型数据结构&…

【NPU 系列专栏 2.8 -- 特斯拉 FDS NPU 详细介绍 】

请阅读【嵌入式及芯片开发学必备专栏】 文章目录 特斯拉 NPU 芯片介绍FSD(Full Self-Driving)芯片 简介FSD主要特点FSD 详细参数FSD 应用场景特斯拉 Hardware 3.0 芯片 简介Hardware 3.0主要特点Hardware 3.0 详细参数Hardware 3.0应用场景特斯拉自研 NPU 的优势优化设计高度…

【数学建模】——matplotlib简单应用

目录 1.绘制带有中文标签和图例的正弦和余弦曲线 2. 绘制散点图 1.修改散点符号与大小 2.修改颜色 3.绘制饼状图 4.在图例中显示公式 5.多个图形单独显示 6.绘制有描边和填充效果的柱状图 7.使用雷达图展示学生成绩 8.绘制三维曲面 9.绘制三维曲线 10.设置…

定制化即时通讯企业级移动门户解决方案,WorkPlus IM系统让工作事半功倍

随着移动设备的普及和移动办公的兴起&#xff0c;企业越来越需要一种定制化的即时通讯企业级移动门户解决方案来提高工作效率和团队协作效果。WorkPlus IM系统作为一种创新的解决方案&#xff0c;为企业提供了一个个性化定制、高度安全和高效便捷的移动门户平台。本文将对定制化…

BFF:优化前后端协作设计模式

BFF&#xff1a;优化前后端协作设计模式 BFF是什么 BFF即 Backends For Frontends (服务于前端的后端)。是一种介于前端和后端之间一种重要的通信设计模式。它旨在解决前端与后端协作中的复杂性问题。 背景 行业背景&#xff1a;传统前端应用&#xff08;如Web应用、移动应…

微服务-MybatisPlus下

微服务-MybatisPlus下 文章目录 微服务-MybatisPlus下1 MybatisPlus扩展功能1.1 代码生成1.2 静态工具1.3 逻辑删除1.4 枚举处理器1.5 JSON处理器**1.5.1.定义实体****1.5.2.使用类型处理器** **1.6 配置加密&#xff08;选学&#xff09;**1.6.1.生成秘钥**1.6.2.修改配置****…

网络安全防御【IPsec VPN搭建】

目录 一、实验拓扑图 二、实验要求 三、实验思路 四、实验步骤&#xff1a; 修改双机热备的为主备模式&#xff1a; 2、配置交换机LSW6新增的配置&#xff1a; 3、防火墙&#xff08;FW4&#xff09;做相关的基础配置&#xff1a; 4、搭建IPsec VPN通道 &#xff08;1…

Java代码基础算法练习-求杨辉三角第n行的值-2024.07.27

任务描述&#xff1a; 给定一个非负整数n&#xff0c;生成「杨辉三角」的第n行。&#xff08;1<n<10&#xff09;在「杨辉三角」中&#xff0c;每 个数是它左上方和右上方的数的和。 &#xff08;提示&#xff0c;第一列数值为1&#xff0c;如数组下标用i,j表示&#xf…

独占电脑资源来执行一个应用

1. 背景 在人工智能时代&#xff0c;随着神经网络的发展&#xff0c;训练人工智能模型需要越来越多的硬件资源&#xff0c;例如&#xff0c;利用10万条棋局数据、使用一台PC电脑、完整地训练一次确定性神经网络五子棋模型&#xff0c;需要花费一年半的时间。随着训练数据的增长…

APP逆向 day23司小宝逆向

一.前言 今天也是讲最后一个基础知识点了&#xff0c;ptrace占坑&#xff0c;这个也算是一个坑&#xff0c;今天通过这个案例和大家讲一下&#xff0c;今天这个案例我们来整验证码登录&#xff0c;版本选择4.7.8 二.抓包分析 抓包发现&#xff0c;请求头里的东西通过改包发现…

Spring Boot:图书管理系统(一)

1.编写用户登录接口 代码&#xff1a; package com.example.demo;import jakarta.servlet.http.HttpSession; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotatio…

技术成神之路:设计模式(九)备忘录模式

介绍 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为设计模式&#xff0c;它允许在不破坏封装性的前提下捕获和恢复对象的内部状态。通过备忘录模式&#xff0c;可以在程序运行过程中保存和恢复对象的某个状态&#xff0c;从而实现“撤销”等功能。 1.定义 备忘…

【BUG】已解决:UnicodeDecodeError: ‘utf-8’ codec can’t decode bytes in position 10

UnicodeDecodeError: ‘utf-8’ codec can’t decode bytes in position 10 目录 UnicodeDecodeError: ‘utf-8’ codec can’t decode bytes in position 10 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#x…

使用python内置的虚拟环境

在一台机器上安装了太多的第三方python库&#xff0c;它们依赖相同的库可能版本不同&#xff0c;就会造成某些第三方库崩溃&#xff0c;之前可以使用的库可能就会坏掉不能用了&#xff0c;所以可以使用虚拟环境运行不同的程序&#xff0c;python有内置的虚拟环境&#xff1b; …

前端八股文 promise async await 的理解

promise是什么 Promise 是异步编程的一种解决方案&#xff0c;比传统的解决方案——回调函数和事件——更合理和更强大。 目的 解析 吴优编程 &#xff08;解决异步编程中的嵌套问题的&#xff0c;将嵌套的格式 用peomise 写成同步&#xff09; promise.then() 是成功后继…

Cocos Creator2D游戏开发(4)-飞机大战(2)-编辑器界面

编辑器几个重要板块 参考: https://docs.cocos.com/creator/3.8/manual/zh/editor/ (1) 场景编辑器: 仅看2D视图: 按钮作用依次是: 平移, 旋转,缩放,矩形变换,增量吸附工具,最后三个,前俩是变换工具,最后一个是布局组件 矩形变换: 中心点和锚点切换 以后用到慢慢整吧! (2)层…

AI服务器产业链研究分析

AI服务器产业链初探 一、AI服务器的技术架构与构成 AI服务器的主要构成包括&#xff1a; 芯片种类丰富&#xff0c;包括X86、ARM、MIPS等架构的CPU&#xff0c;以及GPU、FPGA、ASIC和NPU等。 内存&#xff1a;DRAM、HBM&#xff08;高带宽存储&#xff09;。 本地存储&#…

前端开发调试工具推荐分类整理

具体前往&#xff1a;前端调试工具分类整理汇总

黑马Java零基础视频教程精华部分_6_字符串

系列文章目录 文章目录 系列文章目录前言一、API是什么&#xff1f; API帮助文档案例&#xff1a;API文档练习Step1&#xff1a;查找文档中Scanner内容。Step2&#xff1a;学习文档中Scanner内容。 二、字符串String类1、String概述总结&#xff1a; 创建String对象的两种方式2…