Linux——进程信号

目录

一、信号的理解

二、信号的种类

2.1 标准信号 (1-31)

2.2 实时信号 (通常是34及以上)

三、信号的产生

3.1 用户通过终端产生信号

3.1.1 signal 函数

 3.1.2 demo 测试

3.1.3 demo 现象

3.2 通过系统函数产生信号

3.2.1 demo 测试

3.3 由软件条件产生信号

3.3.1 alarm 函数

3.3.2 demo 测试

3.3.3 demo 现象 

3.4 硬件异常产生信号

3.4.1 demo 测试 

3.4.2 demo 现象 

3.5 进程间通信产生信号

3.5.1 SIGCHLD 信号

3.5.1 demo 测试

3.5.2 demo 现象

四、信号的保存

4.1 信号的产生和递达

4.2 信号的状态

4.3 信号的存储结构

4.4 信号的处理

4.5 sigset_t 类型的信号集

4.5.1 sigprocmask:更改或检查进程的信号屏蔽字。

4.5.2 sigpending:检查当前进程的未决信号集。

4.5.3 sigemptyset:将信号集初始化为空集,即清除信号集中所有的信号,使其不包含任何信号。

4.5.4 sigfillset:将信号集初始化为满集,即包含所有可能的信号。

4.5.5 sigaddset:将指定的信号添加到信号集中。 

 4.5.6 sigdelset:将指定的信号从信号集中删除。

函数原型

4.5.7 sigismember:检查指定的信号是否在信号集中。

4.6 测试 demo

4.7 demo 现象 

五、捕捉信号

5.1 内核态与用户态

用户态(User Mode)

特点

安全性

性能

内核态(Kernel Mode)

特点

安全性

性能

5.2 OS的存储

5.3 信号捕捉的过程


一、信号的理解

想象你在家里等待一个快递包裹的到来:

  1. 快递员到达通知

    • 当快递员到达你家楼下,他给你打电话或者发短信,告诉你快递到了。这就像一个信号,通知你有一个事件(快递到达)需要处理。
    • 在Linux中,这类似于信号的产生和发送。例如,当你按下Ctrl+C时,系统会产生一个SIGINT信号,发送给当前正在运行的前台进程。
  2. 你正在忙碌

    • 如果你正在忙着做其他事情,比如在看电影,你会告诉快递员放进快递驿站,这段时间你知道有快递到了,但还没有去取。
    • 在Linux中,这相当于信号被产生但被屏蔽了,暂时不会立即处理。信号被保留在一个“待处理”的状态,直到你准备好处理它。
  3. 处理快递

    • 当你处理完手头的事情,你决定去取快递。你有几种选择:
      1. 默认动作:直接去拿快递,打开包裹,使用里面的物品。
      2. 自定义动作:你可以决定先不打开包裹,而是把它存放在一个特定的地方,稍后再处理。
      3. 忽略:你可以选择忽略这个快递,不去取它。
    • 在Linux中,进程可以对信号有不同的处理方式:执行默认操作、自定义处理函数、或者忽略信号。例如,SIGINT的默认操作是终止进程,但你可以自定义一个处理函数来执行其他动作。

二、信号的种类

可以在终端使用 kill -l 查看信号的种类:

2.1 标准信号 (1-31)

标准信号是由POSIX标准定义的,所有Unix和Linux系统都支持这些信号。每个信号都有一个固定的编号和对应的宏定义名称。

信号在Linux系统中是一种用于进程间通信和事件通知的机制。信号的产生可以由多种途径触发,具体包括以下几种方式:

  1. SIGHUP (1):挂起信号,通常在终端断开时发送给会话控制的进程。
  2. SIGINT (2):中断信号,通常由用户按下Ctrl+C产生,用于终止前台进程。
  3. SIGQUIT (3):退出信号,通常由用户按下Ctrl+\产生,用于终止进程并产生核心转储(core dump)。
  4. SIGILL (4):非法指令信号,进程执行非法、无效的机器指令时产生。
  5. SIGABRT (6):进程异常终止信号,通常由abort()函数产生。
  6. SIGFPE (8):浮点异常信号,如除以零等算术错误。
  7. SIGKILL (9):杀死信号,无法捕捉或忽略,用于立即终止进程。
  8. SIGSEGV (11):段错误信号,进程非法访问内存时产生。
  9. SIGPIPE (13):管道破裂信号,进程写入无读端的管道时产生。
  10. SIGALRM (14):闹钟信号,由alarm()函数设定的计时器到期时产生。
  11. SIGTERM (15):终止信号,默认用于请求进程终止。

这些信号编号在1到31之间,涵盖了大多数常见的进程控制和错误处理机制。

2.2 实时信号 (通常是34及以上)

实时信号的编号范围通常从34开始,根据具体的Linux实现可能有所不同。这些信号是POSIX实时扩展的一部分,提供了更高的优先级和实时性特性。

这里不做过多说明。

三、信号的产生

3.1 用户通过终端产生信号

用户可以通过在终端按特定的键来产生信号。例如:

  • SIGINT (信号编号2):当用户按下Ctrl+C时,系统会产生一个SIGINT信号并发送给当前运行的前台进程。这个信号的默认处理动作是终止进程。
  • SIGQUIT (信号编号3):当用户按下Ctrl+\时,系统会产生一个SIGQUIT信号,默认处理动作是终止进程并产生一个核心转储(core dump)​

3.1.1 signal 函数

在Linux和其他类Unix操作系统中,signal是一个函数,用于设置进程对特定信号的处理方式。信号(signal)是进程间通信的一种机制,用于通知进程某个事件的发生。

signal函数用于指定某个信号的处理函数。当进程接收到该信号时,操作系统会调用指定的处理函数。通过这个函数,程序可以定义自定义的行为来响应信号,而不仅仅是执行默认的处理动作(比如终止进程)。

其函数原型如下:

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
  • sig: 指定的信号编号(例如 SIGINT 表示中断信号)。
  • func: 指向信号处理函数的指针。处理函数接受一个整数参数,该参数是信号的编号。

 3.1.2 demo 测试

以下是一个demo,可以用来测试用户通过终端产生信号 。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigint(int sig) 
{printf("Caught signal %d (SIGINT). Exiting...\n", sig);_exit(0);
}int main() 
{// 设置对 SIGINT 信号的处理函数signal(SIGINT, handle_sigint);// 无限循环,等待信号到来while (1) {printf("Running... Press Ctrl+C to stop.\n");sleep(1);}return 0;
}
/*signal(SIGINT, handle_sigint): 设置对 SIGINT 信号的处理函数为 handle_sigint。当进程接收到 SIGINT 信号时,将调用 handle_sigint 函数。
handle_sigint 函数:自定义的信号处理函数,打印接收到的信号编号。*/

demo 解释:

  1. signal(SIGINT, handle_sigint): 设置对 SIGINT 信号的处理函数为 handle_sigint。当进程接收到 SIGINT 信号时,将调用 handle_sigint 函数。
  2. handle_sigint 函数:自定义的信号处理函数,打印接收到的信号编号。

3.1.3 demo 现象


可见,CTRL + c 就是信号 SIGINT ,按下 CTRL + c 后,系统进入handle函数,执行了 exit

如果对 demo 进行修改,取消掉 exit 后,再按下 CTRL + c 后,程序就不会终止了。

产生这种现象的原因是 signal 函数改变了对 CTRL + c 这种信号的处理方式,把退出的处理方式修改成了 handle_sigint 函数中的方式。

3.2 通过系统函数产生信号

程序可以通过调用系统提供的函数来产生信号,例如:

  • kill()函数:可以用于向指定进程发送信号。调用形式如kill(pid, signo),其中pid是目标进程的进程ID,signo是要发送的信号编号。
  • raise()函数:用于向当前进程自身发送信号,相当于kill(getpid(), signo)
  • abort()函数:使当前进程接收到SIGABRT信号并异常终止​​。

3.2.1 demo 测试

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigusr1(int sig) 
{printf("Caught signal %d (SIGUSR1)\n", sig);_exit(0);
}int main() 
{// 设置对 SIGUSR1 信号的处理函数signal(SIGUSR1, handle_sigusr1);pid_t pid = fork();if (pid == 0) {// 子进程sleep(2);kill(getppid(), SIGUSR1); // 向父进程发送 SIGUSR1 信号_exit(0);} else {// 父进程while (1) {printf("Waiting for SIGUSR1 from child process...\n");sleep(1);}}return 0;
}

3.2.2 demo 现象

通过上述 demo ,子进程在休息2s后会对父进程发送信号然后退出,父进程进入一个无限循环,等待 SIGUSR1 信号。当信号到达时,信号处理函数 handle_sigusr1 将被调用。 

当 handle_sigusr1 被调用时,会打印出 Caught signal %d (SIGUSR1)\n ,然后执行 _exit ,退出程序。

3.3 由软件条件产生信号

一些信号是由特定的软件条件触发的,例如:

  • SIGPIPE (信号编号13):当进程尝试向一个没有读端的管道或套接字写入数据时,系统会产生SIGPIPE信号。
  • SIGALRM (信号编号14):通过alarm(seconds)函数设定一个闹钟,在指定的秒数后系统会向当前进程发送SIGALRM信号​​。

3.3.1 alarm 函数

函数原型

#include <unistd.h>unsigned int alarm(unsigned int seconds);
  • seconds:指定计时器的秒数。设定计时器在 seconds 秒之后发送 SIGALRM 信号。如果 seconds 为 0,表示取消任何现有的计时器。
  • 返回先前设定的闹钟时间还剩余的秒数。如果没有设定过闹钟,则返回 0。

3.3.2 demo 测试

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigalrm(int sig) 
{printf("Caught signal %d (SIGALRM). Time's up!\n", sig);
}int main() 
{// 设置对 SIGALRM 信号的处理函数signal(SIGALRM, handle_sigalrm);alarm(5);  // 设定闹钟在 5 秒后触发 SIGALRM 信号// 无限循环,等待信号到来while (1) {printf("Sleeping... Waiting for alarm...\n");sleep(1);}return 0;
}

3.3.3 demo 现象 

首先使用 alarm 函数设定了一个5s后的闹钟,程序会在5s后接收到 SIGALRM 信号,同时使用signal 函数重新设计了 SIGALRM 信号的处理方式,所以执行程序后会看到以下现象:

在Unix和类Unix系统中,alarm函数只支持设置一个定时器。如果在一个进程中设置了两个alarm调用,后面的调用会覆盖前面的调用。

具体来说,当你第二次调用alarm时,它会取消前一个定时器并重新设定一个新的定时器。因此,第一个定时器所关联的SIGALRM信号将不会被发送,只有最后一次调用alarm设置的定时器到期时,才会发送SIGALRM信号。

3.4 硬件异常产生信号

当进程执行非法操作(如除以0或者页表对应失败(数组越界、野指针...))时,硬件会产生异常,内核将这些异常转换为信号并发送给进程,例如:

  • SIGSEGV (信号编号11):当进程访问非法内存地址时,系统会产生SIGSEGV信号,通常导致进程异常终止。
     
  • SIGFPE (信号编号8):当进程执行非法的算术操作(如除以零)时,系统会产生SIGFPE信号​​

3.4.1 demo 测试 

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigfpe(int sig) 
{printf("Caught signal %d (SIGFPE). Division by zero!\n", sig);_exit(1);
}int main() 
{// 设置对 SIGFPE 信号的处理函数signal(SIGFPE, handle_sigfpe);int x = 1;int y = 0;int z = x / y;  // 这将导致 SIGFPE 信号printf("Result: %d\n", z);return 0;
}

3.4.2 demo 现象 

3.5 进程间通信产生信号

父进程和子进程之间可以通过信号进行通信。例如,当子进程终止时,会向父进程发送SIGCHLD信号。父进程可以捕捉并处理该信号,以便执行相应的清理工作,避免产生僵尸进程​​。

3.5.1 SIGCHLD 信号

SIGCHLD 是一个特定的信号,用于通知父进程其子进程的状态变化。通常,当一个子进程终止或停止时,系统会向父进程发送 SIGCHLD 信号。父进程可以通过捕捉和处理 SIGCHLD 信号来得知其子进程的终止或停止状态,并进行相应的处理,如清理资源或重新启动子进程。

以下是一个子进程对应一个父进程时,子进程退出向父进程发出 SIGCHLD 信号。

3.5.1 demo 测试

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigchld(int sig) 
{pid_t pid;int status;while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {printf("Child %d terminated\n", pid);}
}int main() 
{// 设置对 SIGCHLD 信号的处理函数signal(SIGCHLD, handle_sigchld);if (fork() == 0) {// 子进程printf("Child process: %d\n", getpid());sleep(2);_exit(0);} else {// 父进程while (1) {printf("Parent process doing some work...\n");sleep(1);}}return 0;
}

3.5.2 demo 现象

这里对SIGCHLD信号进行处理,在处理方式中设置了 waitpid 的方法,同时,其中设置了WNOHANG 的方式,防止子进程一部分退出另一部分不退出造成的进程堵塞,这样也会导致父进程无法进行自己的操作。

同时,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用 sigaction 将SIGCHLD 的处理动作置为 SIG_IGN ,这样 fork 出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略:

signal(SIGCHLD, SIG_IGN);

四、信号的保存

4.1 信号的产生和递达

  • 信号产生:当某个事件发生时(例如用户按下Ctrl+C、调用kill函数、硬件异常等),内核会为目标进程产生一个信号。
  • 信号递达:信号递达指的是信号被实际传送到目标进程并触发相应的处理动作。递达的时机可以是立即的,也可以是当进程解除信号阻塞时。

4.2 信号的状态

信号在一个进程的生命周期中可以有三种状态:

  • 未决(Pending):信号已经产生,但由于某种原因(例如信号被阻塞)尚未递达。
  • 递达(Delivered):信号已经传递到进程,并触发了相应的处理动作。
  • 阻塞(Blocked):进程设置了信号屏蔽字,暂时阻止某些信号的递达。

4.3 信号的存储结构

每个进程都有两个重要的数据结构用于信号的管理:

  • 未决信号集(Pending Signals Set):记录当前进程所有未决的信号。通常用一个位图来表示,每个比特位对应一个信号,置1表示该信号未决。
  • 信号屏蔽字(Signal Mask):记录当前进程哪些信号被阻塞。也用一个位图来表示,每个比特位对应一个信号,置1表示该信号被阻塞。

4.4 信号的处理

当一个信号递达时,内核会根据以下步骤处理信号:

  1. 检查信号屏蔽字:如果信号被阻塞(在信号屏蔽字中置位),信号不会立即递达,而是保持未决状态。
  2. 更新未决信号集:将信号添加到未决信号集中。
  3. 检查信号处理方式
    • 默认处理:执行默认的处理动作,例如终止进程。
    • 忽略信号:信号被丢弃,不做任何处理。
    • 自定义处理函数:调用用户定义的信号处理函数。

4.5 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);

4.5.1 sigprocmask:更改或检查进程的信号屏蔽字。

函数原型

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how:指定如何修改信号屏蔽字(如 SIG_BLOCK 阻塞信号)。
  • set:指向要设置的信号集。
  • oldset:如果不为 NULL,保存先前的信号屏蔽字。

4.5.2 sigpending:检查当前进程的未决信号集。

函数原型

int sigpending(sigset_t *set);
  • set:指向保存未决信号集的信号集。

4.5.3 sigemptyset:将信号集初始化为空集,即清除信号集中所有的信号,使其不包含任何信号。

函数原型

int sigemptyset(sigset_t *set);
  • set:指向要初始化的信号集。
  • 成功时返回 0;出错时返回 -1。

4.5.4 sigfillset:将信号集初始化为满集,即包含所有可能的信号。

函数原型

int sigfillset(sigset_t *set);
  • set:指向要初始化的信号集
  • 成功时返回 0;出错时返回 -1。

4.5.5 sigaddset:将指定的信号添加到信号集中。 

函数原型

int sigaddset(sigset_t *set, int signo);
  • set:指向要修改的信号集。
  • signo:要添加到信号集中的信号编号。
  • 成功时返回 0;出错时返回 -1。

 4.5.6 sigdelset:将指定的信号从信号集中删除。

函数原型
int sigdelset(sigset_t *set, int signo);
  • set:指向要修改的信号集。
  • signo:要从信号集中删除的信号编号。
  • 成功时返回 0;出错时返回 -1。

4.5.7 sigismember:检查指定的信号是否在信号集中。

函数原型

int sigismember(const sigset_t *set, int signo);
  • set:指向要检查的信号集。
  • signo:要检查的信号编号。
  • 如果信号在信号集中,返回 1;如果不在信号集中,返回 0;出错时返回 -1。

4.6 测试 demo

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>void PrintPending(sigset_t &pending)//打印“位图”
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){std::cout << 1;}else{std::cout << 0;}}std::cout << "\n";
}void handler(int signo)
{std::cout << signo << " 号信号被递达!!!" << std::endl;
}int main()
{// 0. 捕捉2号信号signal(2, handler); // 自定义捕捉signal(2, SIG_IGN); // 忽略一个信号signal(2, SIG_DFL); // 信号的默认处理动作// 1. 屏蔽2号信号sigset_t block_set, old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set, SIGINT);// 1.1 设置进入进程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!int cnt = 10;while (true){// 2. 获取当前进程的pending信号集sigset_t pending;sigpending(&pending);// 3. 打印pending信号集PrintPending(pending);cnt--;// 4. 解除对2号信号的屏蔽if (cnt == 0){std::cout << "解除对2号信号的屏蔽!!!" << std::endl;sigprocmask(SIG_SETMASK, &old_set, &block_set);}sleep(1);}
}

4.7 demo 现象 


五、捕捉信号

通过前面的学习,已经了解了我们可以自定义信号处理的方式,当对某信号进行自定义处理时,系统就要去找自定义的 handler 处理方法,但是,系统拥有最高的权限,它的这种身份被称作内核态,普通用户则被成为用户态。系统会以内核态的方式直接去执行自定义的 handler 函数吗?很显然是不行的。

这样如果某个用户钻了漏子,借用系统内核态的身份完成一些用户态不可以完成的事情,就会惹到麻烦。操作系统在这时就存在着身份的转换。

5.1 内核态与用户态

下面以32位机器为例:

4G的内存中,0-3G是供用户使用的,3-4G是操作系统的所有代码和数据。当用户想访问[3-4]G的地址时,只能使用系统调用!

用户态(User Mode)

特点
  • 受限访问:在用户态下,进程只能访问受限的内存区域和受限的硬件资源,不能直接执行可能影响系统稳定性的指令。
  • 应用程序运行:大多数应用程序(如文本编辑器、浏览器等)都在用户态运行。
  • 系统调用:当用户态进程需要执行特权操作(如文件读写、内存管理等)时,它必须通过系统调用请求内核的帮助。
安全性
  • 用户态运行的代码无法直接访问硬件和系统关键资源,防止应用程序错误或恶意代码直接影响系统的稳定性。
性能
  • 用户态进程的执行速度较慢,因为它们不能直接访问硬件,需要通过系统调用进行间接访问。

内核态(Kernel Mode)

特点
  • 完全访问权限:在内核态下,代码可以访问所有的内存区域和所有的硬件资源,可以执行任何CPU指令。
  • 操作系统内核运行:操作系统的内核及其核心服务(如设备驱动程序、文件系统、网络栈等)都在内核态运行。
  • 系统调用处理:内核态负责处理来自用户态的系统调用请求,并执行相应的操作。
安全性
  • 内核态运行的代码有最高权限,因此必须确保内核代码的正确性和安全性,避免系统崩溃或安全漏洞。
性能
  • 内核态进程的执行速度较快,因为它们可以直接访问硬件和系统资源。

5.2 OS的存储

操作系统也是一个软件,它是第一个加载到内存的软件,它的页表只会维护一份,所以当从用户级换到内核级时,无论在哪个进程,相应的系统调用会访问内核地址空间,映射到同一个内核级页表,进而每个进程进入的OS内部都是相同的!

系统调用访问内核地址空间:无论哪个进程发起系统调用,都会进入相同的内核地址空间,访问相同的内核数据结构和代码。

映射到相同的内核级页表:每个进程在进入内核态时,使用的都是相同的内核级页表。这确保了内核环境的一致性和简化了内存管理。

统一的OS内部环境:由于共享相同的内核地址空间和内核级页表,每个进程进入内核态时,看到的OS内部环境是相同的。

5.3 信号捕捉的过程

  1. 信号的产生(进入内核态)

    • 当某个事件(如用户按下Ctrl+C或硬件异常)触发信号时,内核会生成该信号并将其标记为待处理状态。此时,进程会从用户态切换到内核态。
    • 如果信号是由系统调用(如killraise)产生的,同样会引发进程进入内核态。
  2. 信号的检查与处理准备(进入用户态)

    • 内核检查当前进程的信号屏蔽字和信号处理设置,确定该信号是否需要处理。
    • 如果信号未被阻塞,内核会准备将信号处理函数(用户自定义的或默认的)加入进程的执行上下文中。这将导致进程从内核态返回到用户态。
  3. 信号处理函数的执行(进入内核态)

    • 当信号处理函数被调用时,进程再次从用户态切换到内核态,以便内核进行必要的处理(例如,保存当前的进程上下文)。
    • 内核将控制权交给信号处理函数,此时进程切换回用户态,执行用户定义的信号处理函数。
  4. 信号处理函数的完成(进入内核态并返回用户态)

    • 当信号处理函数执行完毕后,进程再次进入内核态,以便内核恢复先前保存的进程上下文。
    • 最终,内核将控制权返回给进程的正常执行流,进程回到用户态,继续执行未完成的工作。
用户态 (User Mode)|  | (事件触发,如 Ctrl+C)V
内核态 (Kernel Mode)|  | (信号生成,标记待处理)V
内核态 (Kernel Mode)|  | (准备信号处理)V
用户态 (User Mode)|  | (执行信号处理函数)V
内核态 (Kernel Mode)|  | (信号处理函数执行完毕)V
用户态 (User Mode)

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

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

相关文章

面向浏览器端免费开源的三维可视化编辑器,包含BIM轻量化,CAD解析预览等特色功能。

ES 3DEditor &#x1f30d;Github地址 https://github.com/mlt131220/ES-3DEditor &#x1f30d;在线体验 https://editor.mhbdng.cn/#/ 基于vue3与ThreeJs&#xff0c;具体查看Doc 主要功能&#xff1a; 模型导入展示&#xff0c;支持OBJ、FBX、GLTF、GLB、RVT、IFC、SEA、3…

如何将Docker容器打包并在其他服务器上运行

如何将Docker容器打包并在其他服务器上运行 我会幻想很多次我们的相遇&#xff0c;你穿着合身的T恤&#xff0c;一个素色的外套&#xff0c;搭配一条蓝色的牛仔裤&#xff0c;干净的像那天空中的云朵&#xff0c;而我&#xff0c;还是一个的傻傻的少年&#xff0c;我们相识而笑…

有没有适合女性做的副业?盘点9个适合女生做的赚钱兼职副业

亲爱的女神们&#xff0c;你们是否也想在忙碌的生活中寻找一些额外的乐趣和收入呢&#xff1f;今天&#xff0c;就为大家揭秘九种特别适合女性的副业&#xff0c;让你在追求美丽的同时&#xff0c;也能轻松赚取零花钱&#xff0c;秒变“小金库”&#xff01; 一、宅富社任务赚钱…

BGP策略实验

BGP策略实验 1.拓扑 2.要求 1.使用配用preva1策略&#xff0c;确保R4通过R2到达192.168.10.0/24 2.用AS Path策略&#xff0c;确保R4通过R3到达192.168.11.0/24 3.配置MED策略&#xff0c;确保R4通过R3到达192.168.12.0/24 4.使用Local Preference策略&#xff0c;确保R1通…

Sentinel的隔离和降级

文章目录 1、概念简介2、FeignClient整合Sentinel2.1、修改配置&#xff0c;开启sentinel功能2.2、编写失败降级逻辑2.3、总结 3、线程隔离&#xff08;舱壁模式&#xff09;3.1、线程隔离的实现方式3.2、sentinel的线程隔离1&#xff09;配置隔离规则2&#xff09;Jmeter测试 …

南加州大学字节提出MagicPose,提供逼真的人类视频生成,实现生动的运动和面部表情传输,以及不需要任何微调的一致的野外零镜头生成。

MagicPose可以精确地生成外观一致的结果&#xff0c;而原始的文本到图像模型(如Stable Diffusion和ControlNet)很难准确地保持主体身份信息。 此外&#xff0c;MagicPose模块可以被视为原始文本到图像模型的扩展/插件&#xff0c;而无需修改其预训练的权重。 相关链接 论文链…

k8s pv 一直是release状态

如下图所示&#xff0c;pv 一直是release状态 这个时候大家可能就会想到现在我的 PVC 被删除了&#xff0c;PV 也变成了 Released 状态&#xff0c;那么我重建之前的 PVC 他们不就可以重新绑定了&#xff0c;事实并不会&#xff0c;PVC 只能和 Available 状态的 PV 进行绑定。…

如何远程连接默认端口?

远程连接是指通过网络实现两个或多个计算机之间的连接和通信。在进行远程连接时&#xff0c;使用的端口号是一个重要的参数。端口号是计算机上正在运行的特定应用程序的标识符。每个应用程序都会监听一个或多个特定的端口号&#xff0c;以便接收来自其他计算机的连接请求&#…

Android正向开发实现客户端证书认证

前言 如果第三方模块被混淆,那hook方式均不能生效。这时就需要根据系统包去定位校验的函数,因此需要对安卓开发者是如何实现客户端证书校验的有一定了解,接下来就介绍这部分内容。 开发者实现客户端证书校验的本质是:证书/密钥 + 代码。 在形式上有:证书校验、公钥校验和…

【Linux】:进程优先级

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux进程优先级的知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到…

LSTM实例解析

大家好&#xff0c;这里是七七&#xff0c;今天带给大家的实例解析。以前也用过几次LSTM模型&#xff0c;但由于原理不是很清楚&#xff0c;因此不能清晰地表达出来&#xff0c;这次用LSTM的时候&#xff0c;去自习研究了原理以及代码&#xff0c;来分享给大家此次经历。 一、简…

JAVA 中 HTTP 基本认证(Basic Authentication)

目录 服务端这么做服务端告知客户端使用 Basic Authentication 方式进行认证服务端接收并处理客户端按照 Basic Authentication 方式发送的数据 客户端这么做如果客户端是浏览器如果客户端是 RestTemplat如果客户端是 HttpClient 其它参考 服务端这么做 服务端告知客户端使用 …

AWS CloudWatch日志组中关于中文关键字的查询

问题 在AWS CloudWatch日志组中&#xff0c;想要查询出包含中文关键字的错误日志&#xff0c;结果&#xff0c;AWS说语法错误。 最开始&#xff0c;使用如下查询&#xff0c;查询可能的错误日志&#xff1a; 查询语句&#xff0c;如下&#xff1a; {($.log %ERROR%) }具体效…

实战Java虚拟机-高级篇

一、GraalVM 什么是GraalVM GraalVM是Oracle官方推出的一款高性能JDK&#xff0c;使用它享受比OpenJDK或者OracleJDK更好的性能。GraalVM的官方网址&#xff1a;https://www.graalvm.org/官方标语&#xff1a;Build faster, smaller, leaner applications。 更低的CPU、内存…

js实现鼠标拖拽多选功能

实现功能 在PC端的H5页面中&#xff0c;客户拖动鼠标可以连选多个选项 效果展示 具体代码如下 <!DOCTYPE html> <html><head><title>鼠标拖拽多选功能</title><script src"https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js&quo…

ClickHouse配置与使用

静态IP配置 # 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33# 修改文件内容 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic IPADDR192.168.18.128 NETMASK255.255.255.0 GATEWAY192.168.18.2 DEFROUTEyes IPV4_FAILURE_FATALno IPV6INIT…

【408真题】2009-12

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…

招人啦~数通售后、云计算和云服务的岗位需求

小伙伴们大家好&#xff0c;小誉的就业推荐又来咯。想要跳槽晋升找工作的朋友们&#xff0c;千万不要错过机会哦~ 北京集成商数通售后 薪资:12-18k 1、负责公司系统集成项目的网络技术实施工作&#xff0c;包括项目的网络架构的规划、设计、调整、性能优化; 2、负责从项目开展…

零基础HTML教程(35)--网站的本地部署

文章目录 1. 背景2. 网站的本地部署3. 本地部署的步骤4. 服务器软件介绍5. 本地部署实操5.1 开发一个网站5.2 下载服务器软件5.3 将网站复制到服务器软件下5.4 启动服务器软件5.5 通过Http协议访问网站 6. 小结 1. 背景 我们之前开发的网页&#xff0c;都是编写完成后&#xf…

Day22:Leetcode:654.最大二叉树 + 617.合并二叉树 + 700.二叉搜索树中的搜索 + 98.验证二叉搜索树

LeetCode&#xff1a;654.最大二叉树 1.思路 解决方案&#xff1a; 单调栈是本题的最优解&#xff0c;这里将单调栈题解本题的一个小视频放在这里 单调栈求解最大二叉树的过程当然这里还有leetcode大佬给的解释&#xff0c;大家可以参考一下&#xff1a; 思路很清晰&#xf…