【Linux】信号--信号初识/信号的产生方式/信号的保存

文章目录

  • 一、信号初步理解
    • 1.生活角度的信号
    • 2.技术应用角度的信号
  • 二、信号的产生方式
    • 1.通过终端按键产生信号
    • 2.调用系统函数向进程发信号
    • 3.硬件异常产生信号
    • 4.由软件条件产生信号
    • 5.进程退出时的核心转储问题
  • 三、信号的保存
    • 1.信号其他相关常见概念
    • 2.信号在内核中的表示
    • 3.sigset_t
    • 4.信号集操作函数

一、信号初步理解

1.生活角度的信号

我们生活中有许许多多的信号,比如我们手机的电量低于20%的时候会提醒我们电量低,红绿灯,以及QQ消息提醒等。我们以红绿灯为例,我们是能够识别红绿灯的,是我们知道红绿灯是什么并且应该产生的对应行为,比如红灯停,绿灯行。那为什么我们能够识别红绿灯呢,这是因为有人教育过你,让你在大脑中记住了对应的红绿灯的属性或行为。当绿灯亮的时候,我们不一定要过马路,因为我们可能此时在跟朋友告别等其他更重要的事情,所以当信号灯到来的时候,我们不一定立马处理这个信号,信号可以随时产生(异步),我们可能做着更重要的事情。信号的到来和信号被处理的时间段,我们称为时间窗口,但是在此期间,我们必须要记住这个信号。对于绿灯亮的时候,我们可以过马路,也可以在路边跳了一段舞之后再过马路,也有可能你根本就不是在等绿灯,此时就会忽略绿灯的亮起,所以对于信号,我们有三种处理方式:默认动作,自定义动作和忽略动作

2.技术应用角度的信号

现在我们把上面的概念迁移到操作系统中。进程是如何识别信号的呢?认识+动作。进程本身是被程序员编写的属性和逻辑的集合即由程序员编码完成的。当进程收到信号的时候,进程可能正在执行更重要的代码,所以信号不一定会被立即处理,所以进程本身必须有对信号的保存能力。进程在处理信号的时候,一般有三种动作:默认,自定义,忽略

如果一个信号是发给进程的,而进程需要保存,那么应该保存在哪里呢,答案是task_struct中,那么如何保存呢,结构体中包含了信号的一个字段

struct task_struct
{......unsigned int signal;
}

我们使用kill -l 命令可以查看所有的信号:

在这里插入图片描述

其中[1,31]为普通信号,[34,64]为实时信号

那么一个unsigned int如何保证31个信号呢。答案是采用位图的方式,用31个比特位表示31个信号,其中比特位的位置,代表信号编号,比特位的内容,代表是否收到该信号,0没有,1表示有

发生信号的本质是修改PCB中的信号位图。PCB是内核维护的数据结构对象,PCB的管理者是OS,那么就只有OS有权利修改PCB中的内容,所以无论是哪一种发生信号的方式,本质都是通过OS向目标进程发送信号,所以OS一定要提供发送信号处理信号的相关系统调用,所以我们之前使用的kill 命令,底层一定调用了对应的系统调用

二、信号的产生方式

1.通过终端按键产生信号

当我们的程序正在运行的时候,我们可以使用ctl + c的方式中断进程

#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;int main()
{// 1. 通过键盘发送信号while (true){cout << "hello world" << endl;sleep(1);}return 0;
}

在这里插入图片描述

ctl + c是一个组合键,操作系统将ctl + c解释为2号信号–SIGINT。SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程

注意事项:

1.Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。

2.Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。

3.前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。

我们也可以使用kill指令来终止程序

#include <iostream>
#include <sys/types.h>
#include <unistd.h>// 我写了一个将来会一直运行的程序,用来进行后续的测试
int main()
{while (true){std::cout << "我是一个正在执行的进程,pid:" << getpid() << std::endl;sleep(1);}return 0;
}

在这里插入图片描述

2.调用系统函数向进程发信号

1.kill函数可以向任意进程发送任意信号

#include <signal.h>
int kill(pid_t pid, int signo);
成功返回0,错误返回-1
#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;static void Usage(const string &proc)
{cout << "\nUsage:"<< proc << "pid signo\n"<< endl;
}// ./mysignal pid signo
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}pid_t pid = atoi(argv[1]);int signo = atoi(argv[2]);int n = kill(pid, signo);if (n != 0){perror("kill");}return 0;
}

在这里插入图片描述

这样我们就可以使用一个进程来向另外一个进程发送信号了

2.raise() 给自己 发送 任意信号 kill(getpid(), 任意信号)

#include <signal.h>
int raise(int signo);
成功返回0,错误返回-1
#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;int main()
{// 2. 系统调用向目标进程发送信号int cnt = 0;while (cnt <= 10){cout << "cnt:" << cnt++ << "pid" << getpid() << endl;sleep(1);if (cnt >= 5){raise(9);}}return 0;
}

在这里插入图片描述

3.abort() 给自己 发送 指定的信号SIGABRT, kill(getpid(), SIGABRT)

#include <stdlib.h>
void abort(void);
就像exit函数一样,abort函数总是会成功的,所以没有返回值。
#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;int main()
{// 2. 系统调用向目标进程发送信号int cnt = 0;while (cnt <= 10){cout << "cnt:" << cnt++ << "pid" << getpid() << endl;sleep(1);if (cnt >= 5){abort();}}return 0;
}

在这里插入图片描述

注意事项:

kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)

关于信号处理的行为的理解:有很多的情况,进程收到大部分的信号,默认处理动作都是终止进程
信号的意义:信号的不同,代表不同的事件,但是对事件发生之后的处理动作可以一样!

3.硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

我们知道,大多数的处理结果都是终止程序,但是我们也可以自动的控制接收到某种信号之后OS的处理行为

信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的.下面我们介绍signal 函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数功能:改变OS接收到信号之后的行为
参数
signum:信号的编号
handler:处理方法的函数指针
返回值:signal() 返回信号处理程序的先前值,如果出错则返回 SIG_ERR。在发生错误的情况下,会设置errno以指示错误的原因。

1.除0错误

我们知道,一旦程序出现了除0错误之后,程序就会直接崩溃,从而导致进程退出。那么,为什么除0错误会终止程序,答案是当前进程会收到来自操作系统的信号–SIGFPE

int main()
{while (true){std::cout << "我在运行中...." << std::endl;sleep(1);int a = 10;a /= 0;}
}

在这里插入图片描述

现在我们对信号进行自定义捕捉

void catchSig(int signo)
{cout << "获取到一个信号,信号编号是: " << signo << endl;sleep(1);
}
int main()
{signal(SIGFPE, catchSig);while (true){std::cout << "我在运行中...." << std::endl;sleep(1);int a = 10;a /= 0;}
}

在这里插入图片描述

OS如何得知应该给当前进程发送8号信号的-- OS怎么知道我除0了呢

这是因为在CPU中有一个状态寄存器,里面有一个溢出标记位,当我们进行除0的时候,数据发生了溢出,CPU发生了运算异常,此时溢出标记位就被置为了1,因为操作系统是软硬件资源的管理者,操作系统就发现了运算异常,就会给对应的进程发送8号信号

我们看下面的代码,我们把除0放在循环的外面:

void catchSig(int signo)
{cout << "获取到一个信号,信号编号是: " << signo << endl;sleep(1);
}
int main()
{signal(SIGFPE, catchSig);int a = 10;a /= 0;while (true){std::cout << "我在运行中...." << std::endl;sleep(1);}
}

在这里插入图片描述

我们发现,我们明明只有一次除0了,为什么""获取到一个信号,信号编号是8"还是循环打印呢,这是因为,后面的代码还没有结束,程序继续执行,但是CPU中的溢出标记位还是1,所以就会继续接收到8号信号,所以就会一直打印。

受到信号,不一定会引起进程退出 – 没有退出,有可能还会被调度,CPU内部的寄存器只有一份,但是寄存器中的内容,属于当前进程的上下文!,你没有能力或者动作修正这个问题当进程被切换的时候,就有无数次状态寄存器被保存和回复的过程所以每一次恢复的时候,就让OS识别到了CPU内部的状态寄存器中的溢出标志位是1

2.野指针

int main()
{signal(11, catchSig);while (true){std::cout << "我在运行中...." << std::endl;sleep(1);int *p = nullptr;*p = 100;}
}

在这里插入图片描述

为什么 野指针 就会崩溃呢?因为OS会给当前进程发送指定的11号信号

MMU因为越界访问,发生了异常,告知操作系统之后,OS系统给指定的进程发生11号信号

4.由软件条件产生信号

1.管道

当我们把管道的读端关闭之后,OS系统就给写的进程发送SIGPIPE信号,写端就不再写了,这是由软件条件触发的

2.alarm函数 和SIGALRM信号

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动
作是终止当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

int main()
{alarm(1);while(true){cout << "我在运行: " << getpid() <<endl;}
}

在这里插入图片描述

这里我们就可以只有alarm计算我们的计算机能够将数据累计多少次!

int cnt = 0;
int main()
{alarm(1);while (true){cout << "cnt:" << cnt++<< endl;}return 0;
}

在这里插入图片描述

nt cnt = 0;void catchSig(int signo)
{cout << "cnt: " << cnt << endl;exit(1);
}int main()
{signal(SIGALRM, catchSig);alarm(1);while (true){cnt++;}return 0;
}

在这里插入图片描述

我们从上面的对比可以看出,IO其实很慢

任何一个进程,都可以通过alarm系统调用在内核中设置闹钟,OS中可能会存在着很多闹钟,那么OS系统要不要管理这些闹钟呢,答案是要,管理方法是先描述,再组织

操作系统会为闹钟定义类似于一下的数据结构

struct alarm
{uint64_t when;//未来的超时时间int type;//闹钟的类型,一次性的还是周期性的task_struct *p;struct alarm* next;
};

这样我们就可以对闹钟的数据结构使用一个链表来链接起来,对闹钟的管理。就变成了对链表的管理,OS系统会周期性的检测这些闹钟,超时时就会发送SIGALARM给对应的进程,也可以使用更加高效的数据结构来进行管理,比如优先级队列。

总结:

上面所说的所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者

信号的处理是否是立即处理的?在合适的时候

信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?

一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?

如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?

5.进程退出时的核心转储问题

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2

这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal

在这里插入图片描述

2号和3号信号都是终止进程,但是他们的action一个是Term,一个是Core.

Term会直接终止进程,但是Core终止进程之后会用户空间内存数据全部 保存到磁盘上。在云服务器上,默认如果进程是core退出的,我们暂时看不大不存数据的现象,因为云服务器默认关闭了core file选项,如果需要查看,就需要打开云服务器的core file选项

我们可以使用ulimit - a选项进行查看

ulimit -a

在这里插入图片描述

我们可以使用 ulimit -c 1024进行设置

ulimit -c size
int main()
{while (true){std::cout << "我在运行中...." << std::endl;sleep(1);int *p = nullptr;*p = 100;}
}

在这里插入图片描述

core dumped就称为核心转储:当进程出现异常的时候,我们将进程对应的时刻,在内存中的有效数据转储到磁盘中

首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c1024

在这里插入图片描述

其中文件后缀的数字为进程的pid

保存的数据可以支持我们进行调试

在这里插入图片描述

三、信号的保存

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

实际执行信号的处理动作称为信号递达(Delivery)

信号从产生到递达之间的状态,称为信号未决(Pending)。

进程可以选择阻塞 (Block )某个信号。

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

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

2.信号在内核中的表示

在这里插入图片描述

在这里插入图片描述

内核中为信号设置了pending位图和block位图,对于pending位图来说,比特位的位置,代表信号的编号,比特位的内容,表示是否收到了对应的信号,而block位图,比特位的位置表示信号的编号,比特位的内容表示是否阻塞了该信号,其次还维护了一个函数指针数组,数组的下标表示信号的编号,数组下标对应的内容,表示对应信号的处理方法。

需要注意的是,如果一个信号没有产生,并不妨碍它可以先被阻塞。

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。

在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

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

sigprocmask

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
返回值:若成功则为0,若出错则为-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值

在这里插入图片描述

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

sigpending

#include <signal.h>
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1

下面我们写一个程序,先屏幕2号信号,然后发送2号信号,我们可以看到block位图中第二个比特位从0置为1的过程

#include <iostream>
#include <unistd.h>
#include <signal.h>#define BLOCK_SIGNO 2void show_pending(const sigset_t &pending)
{for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo))std::cout << "1";elsestd::cout << "0";}std::cout << "\n";
}int main()
{// 1.先尝试屏蔽指定信号sigset_t block, oblock, pending;// 1.1 初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);// 1.2 添加要屏蔽的信号sigaddset(&block, BLOCK_SIGNO);// 1.3 开始屏蔽sigprocmask(SIG_SETMASK, &block, &oblock);// 2.遍历打印pending信号集int cnt = 10;while (true){// 2.1初始化sigemptyset(&pending);// 2.2获取它sigpending(&pending);// 2.3打印它show_pending(pending);sleep(1);}
}

在这里插入图片描述

下面我们更改我们的代码,让10s之后,解除对2号信号的屏蔽

int main()
{// 1.先尝试屏蔽指定信号sigset_t block, oblock, pending;// 1.1 初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);// 1.2 添加要屏蔽的信号sigaddset(&block, BLOCK_SIGNO);// 1.3 开始屏蔽sigprocmask(SIG_SETMASK, &block, &oblock);// 2.遍历打印pending信号集int cnt = 10;while (true){// 2.1初始化sigemptyset(&pending);// 2.2获取它sigpending(&pending);// 2.3打印它show_pending(pending);sleep(1);if (cnt-- == 0){sigprocmask(SIG_SETMASK, &oblock, &block);std::cout << "恢复对信号的屏蔽,不屏蔽任何信号" << std::endl;}}
}

在这里插入图片描述

我们发现,解除对2号屏蔽之后,我们最后的打印语句也没有执行,这是因为一旦对特定的信号进行解除屏蔽,一般OS要至少立马递达一个信号,此时2号信号递达,OS采取默认的行为,直接在内核态将进程退出,并没有返回到用户态,所以就没有打印

下面我们对代码进行更改,使得可以看到打印语句,并且可以看到比特位从1置为0的过程。

#include <iostream>
#include <unistd.h>
#include <signal.h>#define BLOCK_SIGNO 2void show_pending(const sigset_t &pending)
{for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo))std::cout << "1";elsestd::cout << "0";}std::cout << "\n";
}static void handler(int signo)
{std::cout << signo << " 号信号已经被递达" << std::endl;
}int main()
{signal(BLOCK_SIGNO, handler);// 1.先尝试屏蔽指定信号sigset_t block, oblock, pending;// 1.1 初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);// 1.2 添加要屏蔽的信号sigaddset(&block, BLOCK_SIGNO);// 1.3 开始屏蔽sigprocmask(SIG_SETMASK, &block, &oblock);// 2.遍历打印pending信号集int cnt = 10;while (true){// 2.1初始化sigemptyset(&pending);// 2.2获取它sigpending(&pending);// 2.3打印它show_pending(pending);sleep(1);if (cnt-- == 0){sigprocmask(SIG_SETMASK, &oblock, &block);std::cout << "恢复对信号的屏蔽,不屏蔽任何信号" << std::endl;}}
}

在这里插入图片描述

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

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

相关文章

ubuntu debian mini安装系统 有线选项消失或ens33 ethernet 未托管解决方法

nmcli device status#修改NetworkManager.conf如下 sed s/false/true/ /etc/NetworkManager/NetworkManager.confsed -i s/false/true/ /etc/NetworkManager/NetworkManager.conf#重启生效systemctl restart NetworkManager

智能优化算法应用:基于蝠鲼觅食算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蝠鲼觅食算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蝠鲼觅食算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蝠鲼觅食算法4.实验参数设定5.算法结果6.…

phpMyAdmin的常见安装位置

nginx的日志显示有人一直在尝试访问phpMyAdmin的setup.php&#xff0c;用了各种位置。 其实我只有一个nginx&#xff0c;别的什么也没有。 47.99.136.156 - - [01:44:37 0800] "GET http://abc.com:80/phpMyAdmin/scripts/setup.php HTTP/1.0" 404 162 "-"…

生成树基本实验

背景 某公司的二层交换网络中&#xff0c;为了提高网络可靠性&#xff0c;故在二层交换网络中增加冗余链路。为了阻 止冗余链路可能带来的广播风暴&#xff0c;MAC地址漂移等负面影响&#xff0c;需要在交换机之间部署生成树 协议。 实验 一.配置stp en 开启 stp en stp …

PPINN Parareal physics-informed neural network for time-dependent PDEs

论文阅读&#xff1a;PPINN Parareal physics-informed neural network for time-dependent PDEs PPINN Parareal physics-informed neural network for time-dependent PDEs简介方法PPINN加速分析 实验确定性常微分方程随机常微分方程Burgers 方程扩散反应方程 总结 PPINN Par…

R语言【rgbif】——什么是多值传参?如何在rgbif中一次性传递多个值?多值传参时的要求有哪些?

rgbif版本&#xff1a;3.7.8.1 什么是多值传参&#xff1f; 您是否在使用rgbif时设想过&#xff0c;给某个参数一次性传递许多个值&#xff0c;它将根据这些值独立地进行请求&#xff0c;各自返回独立的结果。 rgbif支持这种工作模式&#xff0c;但是具体的细节需要进一步地…

新版Spring Security6.2 - Digest Authentication

前言&#xff1a; 书接上文&#xff0c;上次翻译basic的这页&#xff0c;这次翻译Digest Authentication这页。 摘要认证-Digest Authentication 官网的警告提示&#xff1a;不应在应用程序中使用摘要式身份验证&#xff0c;因为它不被认为是安全的。最明显的问题是您必须以…

IDEA中Terminal配置为bash

简介 我们日常命令行都是使用Linux的bash指令&#xff0c;但是我们的开发基本都是基于Windows上的IDEA进行开发的&#xff0c;对此我们可以通过将IDEA将终端Terminal改为git bash自带的bash.exe解决问题。 配置步骤 安装GIT 这步无需多说了&#xff0c;读者可自行到官网下载…

大模型时代-从0开始搭建大模型

开发一个简单模型的步骤&#xff1b; 搭建一个大模型的过程可以分为以下几个步骤&#xff1a; 数据收集和处理模型设计模型训练模型评估模型优化 下面是一个简单的例子&#xff0c;展示如何使用Python和TensorFlow搭建一个简单的大模型。 数据收集和处理 首先&#xff0c;我…

Python接口自动化 —— Json 数据处理实战(详解)

简介   上一篇说了关于json数据处理&#xff0c;是为了断言方便&#xff0c;这篇就带各位小伙伴实战一下。首先捋一下思路&#xff0c;然后根据思路一步一步的去实现和实战&#xff0c;不要一开始就盲目的动手和无头苍蝇一样到处乱撞&#xff0c;撞得头破血流后而放弃了。不仅…

作业12.11

1 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&…

数据结构(超详细讲解!!)第二十七节 查找

1.查找的基本概念 1、查找表——由同一类型的数据元素&#xff08;或记录&#xff09;构成的集合称为查找表。 2、对查找表进行的操作&#xff1a; 查找某个特定的数据元素是否存在&#xff1b; 检索某个特定数据元素的属性&#xff1b; 在查找表中插入一个数据元素&#x…

Stable Diffusion AI绘画系列【25】:3D可爱风格系列图片

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

微信小程序---自定义组件

目录 1.局部引用组件 2.全局引用组件 3.组件和页面的区别 4.自定义组件样式 5.properties属性 6.data和properties的区别 7.数据监听器 8.纯数据字段 9.自定义组件-组件的生命周期 lifetimes节点 10.组件所在的页面的生命周期 pageLifetimes节点 11.插槽 &#x…

Redis设计与实现之简单的动态

目录 一、内部数据结构 二、简单动态字符串 1、sds的用途 实现字符串对象 将sds代替C默认的char*类型 2、Redis中的字符串 sds的实现 3、优化追加操作 4、sds 模块的 API 三、Redis动态字符串的内存分配和释放是如何进行的&#xff1f; 四、Redis动态字符串的扩容策略…

PDF控件Spire.PDF for .NET【转换】演示:将 PDF 转换为线性化

PDF 线性化&#xff0c;也称为“快速 Web 查看”&#xff0c;是一种优化 PDF 文件的方法。通常&#xff0c;只有当用户的网络浏览器从服务器下载了所有页面后&#xff0c;用户才能在线查看多页 PDF 文件。然而&#xff0c;如果 PDF 文件是线性化的&#xff0c;即使完整下载尚未…

IntelliJ IDEA2023学习教程

详细介绍idea开发工具及使用技巧 1. 2023版安装1.1删除老版本1.2 下载及安装 3.快捷技巧4. 创建各种model 1. 2023版安装 1.1删除老版本 如果以前装有idea需要先删除&#xff0c;以避免冲突&#xff0c;在idea安装目录/bin/Uninstall.exe双击1.2 下载及安装 最新版本 https:/…

Unity Web 浏览器-3D WebView中有关于CanvasWebViewPrefab

一、CanvasWebViewPrefab默认设置 这个是在2_CanvasWebViewDemo示例场景文件中可以可以查看得到&#xff0c;可以看出CanvasWebViewPrefab的默认配置如下。 二、Web 浏览器网页和Unity内置UI的渲染顺序 1、如果你勾选了以下这个Native 2D Mode选项的话&#xff0c;那么Unit…

计算机设计大赛信息可视化设计的获奖经验剖析解读—基于本专栏文章助力4C大赛【全网最全万字攻略-获奖必读】

文章目录 一.中国大学生计算机设计大赛1.1赛道解读1.2 信息可视化设计小类介绍1.2 小类区别解读 二.信息可视化设计赛道获奖经验2.1 四小类作品预览2.1.1 数据可视化小类-优秀参赛作品展览2.1.2 信息图形设计小类-优秀参赛作品展览2.1.3 动态信息影像&#xff08;MG动画&#x…

记录Oracle Exadata X8M-2 存储服务器告警灯亮的处理过程(/SYS/MB/P0PCIE7)

文章目录 概要调查流程处理方式&#xff1a; 概要 现场服务器告警灯亮&#xff0c;其他服务器正常&#xff0c;磁盘灯正常&#xff0c;所以从整体来看应是内部部件抛出的异常问题&#xff0c;需要登录机器确认&#xff1a; 调查流程 通过ILOM web界面查看服务器状态进行信息…