【Linux】进程信号全攻略(一)

news2024/11/13 21:56:55

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

目录

  • 一:🔥 信号的概念
  • 二:🔥 信号产生的方式
    • 🦋 使用键盘
    • 🦋 系统调用函数
    • 🦋 软件条件
    • 🦋 进程异常
  • 三:🔥 关于term和core
  • 四:🔥 信号处理
    • 🦋 信号处理的三种方式
  • 五:🔥 信号保存
    • 🦋 信号其他相关常见概念
    • 🦋 在内核中的表示
  • 六:🔥 信号处理
    • 🦋 信号集sigset_t
    • 🦋 信号集操作函数
    • 🦋 sigprocmask(操作block表的函数)
    • 🦋 sigpending
    • 🦋 代码样例
  • 七:🔥 共勉

一:🔥 信号的概念

🦁 信号是 Linux 系统提供的一种向指定进程发送特定事件的方式,进程会对信号进行识别和处理。

信号的产生是异步的,即一个进程不知道自己何时会收到信号,在收到信号之前进程只能一直在处理自己的任务

  • 使用 kill -l 指令查看信号(1-30号信号为普通信号,31-64号信号为实时信号
root@hcss-ecs-a9ee:~/code/linux/112# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

🍉 具体的信号采取的动作和详细信息可查看:man 7 signal

进程内核数据结构中有位图,例如 uint32_t signalbits。一共32位,除去0号位,刚好对应1-32号普通信号

0000 0000 0000 0000 0000 0000 0000 0000

例如给进程发送2号信号,则修改位图为:0000 0000 0000 0000 0000 0000 0000 0100

所以给进程发送信号,本质是修改进程内核数据结构中的位图数据

二:🔥 信号产生的方式

🦋 使用键盘

例如 ctrl+c 产生是2号信号(终止进程)、ctrl+\ 产生的是3号信号(终止进程)
注意:

  1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个 & 可以放到后台运行, 这样 Shell 不必等待进程结束就可以接受新的命令, 启动新的进程。
  2. Shell 可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
  3. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous) 的

🦋 系统调用函数

(1) kill 函数,用于向指定进程发送信号

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
  • pid指定进程pid,如果 pid 是负数,信号将被发送到与 pid 的绝对值相同的进程组中的所有进程。
  • sig指定的信号编号
    返回值:成功返回 0,失败返回 -1 并设置 errno
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <signal.h>

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " signumber processid" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    int signumber = std::stoi(argv[1]);
    pid_t id = std::stoi(argv[2]);

    int n = ::kill(id, signumber);
    if (n < 0)
    {
        perror("kill");
        exit(2);
    }
    exit(0);
    return 0;
}

在这里插入图片描述

(2) raise 函数,向自己发送信号

#include <signal.h>

int raise(int sig);
  • sig发送给当前进程的信号编号
    返回值:成功返回 0,失败返回 -1并设置 errno

(3) abort 函数,立即终止当前进程(本质发送的是6号信号SIGABRT)

#include <stdlib.h>

void abort(void);

6号信号SIGABRT可以被自定义捕捉处理,但是捕捉后仍然会立即退出进程,比较特殊

9号信号 SIGKILL 无法被捕捉,否则如果所有的信号都被捕捉,那么进程将无法退出

🦋 软件条件

\qquad 🦁 使用管道通信时,当读端关闭,但是写端一直写,操作系统就会给写端进程发送13号信号SIGPIPE,终止进程。SIGPIPE 就是一种由软件条件产生的信号。

现在要介绍的是 alarm 函数

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
  • alarm 函数用于设置一个定时器,在指定时间后向进程发送14号信号SIGALRM,终止进程
  • seconds指定定时器的时间,单位为秒。如果这个值是 0,则会取消之前设置的闹钟
  • 返回值:alarm 函数返回自上次调用的 alarm 闹钟剩余的秒数。如果之前没有设置定时器,或者定时器已经触发,返回 0。
#include <iostream>
#include <signal.h>
#include <unistd.h>

int main()
{
    alarm(1);//1秒后终止进程
    int cnt = 0;
    while(true)
    {
        std::cout << cnt << std::endl;
        ++cnt;
    }
    return 0;
}

🦋 进程异常

当进程进行非法操作、访问等就会崩溃,操作系统就给崩溃的进程发送相应的信号终止进程

  • 例如进程将0作为除数时,进程崩溃,操作系统会给进程发送8号信号 SIGFPE,终止进程
root@hcss-ecs-a9ee:~/code/linux/112/lesson24/3.signal/work# ./process 
Floating point exception
  • 例如进程中进行野指针访问时,进程崩溃,操作系统会给进程发送11号信号 SIGSEGV,终止进程
root@hcss-ecs-a9ee:~/code/linux/112/lesson24/3.signal/work# ./process 
Segmentation fault

收到这些信号,进程必须退出吗?不是,可以捕捉以上的异常信号,但是我们推荐终止进程,为什么呢?

  1. 除0问题

关于进程中的计算问题,一般都是交由 cpu 完成的,在计算的过程中,难免会出现错误的计算,比如说除0,那么 cpu 又是如何知道的呢?

🐮 这就要提到 cpu 中的寄存器了,cpu 中是有很多的寄存器的,其中有一个寄存器:EFLAGS 寄存器(状态寄存器)。该寄存器中有很多状态标志:这些标志表示了算术和逻辑操作的结果,如溢出(OF)、符号(SF)、零(ZF)、进位(CF)、辅助进位(AF)和奇偶校验(PF)。

除 0 操作就会触发溢出,就会标定出来运算在 cpu 内部出错了。OS 是软硬件资源的管理者!OS 就会处理这种硬件问题,向目标进程发送信号,默认终止进程。

我们要知道 cup 内部是只有一套寄存器的,寄存器中的数据是属于每一个进程的,是需要对进程上下文进行保存和恢复的。

如果进程因为除0操作而被操作系统标记为异常状态,但没有被终止,那么它可能会被挂起,等待操作系统的进一步处理。

当操作系统决定重新调度这个进程时,会进行上下文切换,即将当前进程的上下文保存到其PCB(进程控制块)中,并加载异常进程的上下文到CPU寄存器中。

上下文切换是一个相对耗时的过程,包括保存和恢复寄存器、堆栈等信息。当切换回这个进程的时候,溢出标志位的错误信息同样会被恢复,会频繁的导致除0异常而触发上下文切换,会大大增加系统的开销。

为什么推荐呢?因为要释放进程上下文的数据,包括溢出标志数据或其他的异常数据。

  1. 空指针解引用(野指针)问题

这个问题就与页表,MMU及CR2,CR3寄存器有关联了。

🐮 MMU 和 页表 是操作系统实现虚拟内存管理和内存保护的关键机制,它们通过虚拟地址到物理地址的转换来确保程序的正确运行和内存安全。CR2 和 CR3 寄存器在内存管理和错误处理中扮演着重要角色。CR3 寄存器用于切换不同进程的页表,而 CR2 寄存器则用于存储引起页错误的虚拟地址,帮助操作系统定位和处理错误。

CR2 寄存器用于存储引起页错误的线性地址(即虚拟地址)。当 MMU 无法找到一个虚拟地址对应的物理地址时(例如,解引用空指针或野指针),会触发一个页错误(page fault)。此时,CPU会将引起页错误的虚拟地址保存到 CR2 寄存器中,并产生一个异常,此时就会向进程发送11号信号。

三:🔥 关于term和core

term 和 core 是某些信号默认动作的一种表示。它们之间的区别如下:

默认动作:

  • term:这是“terminate”的缩写,表示信号的默认动作是终止进程。例如,-- SIGTERM(编号15)信号的默认操作就是请求进程正常退出。这给了进程一个机会来清理并正常终止。

  • core:这个动作表示在终止进程的同时,还会生成一个 core dump 文件。这个文件包含了进程在内存中的信息,通常用于调试。例如,SIGQUIT(编号3)和 SIGSEGV(编号11)等信号的默认动作就是终止进程并生成 core dump。

文件生成:

  • 当一个进程因某个信号而 term(终止)时,通常不会生成额外的文件。
  • 但当进程因某个信号而 core(终止并 核心转储,这个动作在云服务器下是被默认关掉的)时,会生成一个 core dump 文件。这个文件包含了进程在内存中的状态信息,对于程序员来说是非常有用的调试工具。

使用场景:

  • term 动作通常用于请求进程正常退出,比如当你想要优雅地关闭一个服务时。
  • core 动作则更常用于在进程崩溃时生成调试信息,帮助程序员找出崩溃的原因。(以gbd为例,先使用gdb打开目标文件,然后将core文件加载进来,就直接可以定位到错误在哪一行)
    在这里插入图片描述

信号示例:

  • SIGTERM(编号15):默认动作为term,即请求进程正常退出。
  • SIGQUIT(编号3)和SIGSEGV(编号11):默认动作为core,即终止进程并生成core dump。
    当进程退出时,如果core dump为0就表示没有异常退出,如果是1就表示异常退出了。

在这里插入图片描述

在这里插入图片描述

eg:关于core dump的演示:
如果你是云服务器,那么就需要手动的将core dump功能打开
在这里插入图片描述

在这里插入图片描述

注意:如果是ubuntu系统的话需要在配置文件/etc/sysctl.conf 后加入如下两行指令
在这里插入图片描述
然后在执行一下这条指令 让其生效

sudo sysctl -p

此时就形成了 core_process 文件
在这里插入图片描述

四:🔥 信号处理

🦋 信号处理的三种方式

信号处理有三种方式:

  1. 默认处理(通常为终止、暂停、忽略等)

  2. 忽略处理

  3. 自定义处理(信号捕捉)

先介绍信号处理函数:signal

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
  • signum:指定信号的编号

  • func:函数指针,该函数将在接收到sig信号时被调用。这个函数必须接受一个 int 参数(信号编号),并且返回类型为 void。

  • 返回值:返回值为一个函数指针,指向之前的信号处理器;如果之前没有信号处理器,则返回 SIG_ERR

(1) 默认处理
如果signal函数的 func 参数为 SIG_DFL,则系统将使用默认的信号处理动作。

指令man 7 signal查看信号的默认处理方式,Action列即为信号的默认处理方式

Core、Term即为进程终止,Stop为进程暂停……

(Core终止进程同时还会形成一个debug文件,Term仅终止进程)

在这里插入图片描述

(2) 忽略处理
如果signal函数的 func 参数为 SIG_IGN,则系统将忽略该信号。

将pending表中被忽略的信号置为0

(3) 自定义处理(信号捕捉)
信号自定义处理,其实是对信号进行捕捉,然后让信号执行自定义的方法

信号的捕捉,一次捕捉,一直有效

#include <iostream>
#include <signal.h>
#include <unistd.h>

void hander(int sig)
{
    std::cout << "get a sig: " << sig << std::endl;
}
int main()
{
    //信号捕捉
    ::signal(2, hander);  //当进程收到2号1信号时,执行hander函数
    while(true)
    {
        std::cout << "my pid is: " << getpid() << std::endl;
        ::sleep(1);
    }
    return 0;a
}

五:🔥 信号保存

🦋 信号其他相关常见概念

  • 实际执行信号的处理动作称为 信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

🦋 在内核中的表示

信号在内核中的表示示意图
在这里插入图片描述
在这个阶段有以下几种情况:

  • 信号未决:信号产生后,在未被处理之前,处于未决状态。这意味着信号已经被发送,但目标进程尚未对其作出响应。操作系统会检查目标进程的Pending表,确定哪些信号处于未决状态。(每个进程都有一个Pending位图,用于记录哪些信号处于未决状态。这个位图由32个比特位组成,分别代表32个不同的信号,如果对应的比特位为1,表示该信号已经产生但尚未处理。)

  • 信号阻塞:如果目标进程阻塞了某些信号,那么这些信号会保持在未决状态,直到进程解除对这些信号的阻塞。(与Pending位图类似,Block位图用于记录哪些信号被进程阻塞。当信号被阻塞时,对应的比特位会被设置为1。)

  • handler表:是一个函数指针数组,每个下标都是一个信号的执行方式(有31个普通信号,信号的编号就是数组的下标,可以采用信号编号,索引信号处理方法!)如signal函数在进行信号捕捉的时候,其第二个参数就是,提供给handler的

  • 如果进程选择阻塞某个信号,操作系统会在block表中设置对应信号的比特位为1。此时,即使信号已经产生(pending表中对应比特位为1),进程也不会立即处理该信号。

  • 被阻塞的信号将保持在pending表中,直到进程解除对该信号的阻塞(即block表中对应比特位被重置为0)。

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

信号阻塞和未决的区别

  • 信号阻塞(Blocking):是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。它使得系统暂时保留信号留待以后发送。阻塞只是暂时的,通常用于防止信号打断敏感的操作。
  • 信号未决(Pending):是一种状态,指的是从信号的产生到信号被处理前的这一段时间。信号产生后,如果未被处理且没有被阻塞,则处于未决状态,等待被处理。

六:🔥 信号处理

🦋 信号集sigset_t

在这里插入图片描述

前面我们了解到,每个信号只有一个 bit 的未决标志,非 0 即 1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型 sigset_t 来存储, sigset_t 称为信号集, 这个类型可以表示每个信号的 “有效” 或 “无效” 状态,在阻塞信号集中 “有效” 和 “无效” 的含义是该信号是否被阻塞, 而在未决信号集中 “有效” 和 “无效” 的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字 (Signal Mask) ,这里的 “屏蔽” 应该理解为阻塞而不是忽略。(该类型只在 Linux 系统上有效,是 Linux 给用户提供的一个用户级的数据类型)

🦋 信号集操作函数

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(操作block表的函数)

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

#include <signal.h>

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

返回值:若成功则为0,若出错则为-1

how指定对信号屏蔽集的操作方式,有以下几种方式:

  • SIG_BLOCK将 set 所指向的信号集中包含的信号添加到当前的信号屏蔽集中,即信号屏蔽集和set信号集进行逻辑或操作。
  • SIG_UNBLOCK将 set 所指向的信号集中包含的信号从当前的信号屏蔽集中删除,即信号屏蔽集和set信号集的补集进行逻辑与操作。
  • SIG_SETMASK将 set 的值设定为新的进程信号屏蔽集,即 set 直接对信号屏蔽集进行了赋值操作。
  • set指向一个 sigset_t 类型的指针,表示需要修改的信号集合。如果只想读取当前的屏蔽值而不进行修改,可以将其置为 NULL。
  • oldset指向一个 sigset_t 类型的指针,用于存储修改前的内核阻塞信号集。如果不关心旧的信号屏蔽集,可以传递 NULL。

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

🦋 sigpending

(检查pending信号集,获取当前进程pending位图)

#include <signal.h>  

int sigpending(sigset_t *set);
  • 参数:set 是一个指向 sigset_t 类型的指针,用于存储当前进程的未决信号集合。
  • 返回值:函数调用成功时返回 0,失败时返回 -1,并设置 errno 以指示错误原因。

🦋 代码样例

基于上面的操作方法我们来做一个实验:我们把2号信号block对应的位图置为1,那么2号信号就会被屏蔽掉了,此时我们给当前进程发送2号信号,但2号信号已经被屏蔽了,2号信号永远不会递达,发完之后我们再不断的获取当前进程的pending表,我们就能肉眼看见2号信号被pending的效果:验证

1.第一步实现对2号信号的屏蔽

int main()
{
    sigset_t block_set,old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set,SIGINT);//向block_set信号集中添加SIGINT信号(编号为2)。
    //1.屏蔽2号信号
        // 1.1 设置进入进程的Block表中
    sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!
    while(true) sleep(1);
}

当我们运行程序的时候,对进程发送2号信号是没有作用的,因为2号信号此时已经被屏蔽了。
在这里插入图片描述

  1. 下一步我们打印pending表,之后我们给该进程发送2号信号
void PrintPending(sigset_t &pending)
{
    std::cout << "curr process[" << getpid() << "]pending: ";
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))//如果存在就返回1
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << "\n";
}
int main()
{
    sigset_t block_set,old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set,SIGINT);//向block_set信号集中添加SIGINT信号(编号为2)。
    //1.屏蔽2号信号
        // 1.1 设置进入进程的Block表中
    sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!
 
    while(true)
    {
        //2.获取当前进程的pending信号集
        sigset_t pending;
        sigpending(&pending);
 
        //3.打印pending信号集
        PrintfPending(pending);
        sleep(1);
    }
}

对该进程发送2号信号,pending表对应位置被置为1
在这里插入图片描述

  1. 解除对2号信号的屏蔽,并且捕捉2号信号,我们来看一下现象:
#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))//如果存在就返回1
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << "\n";
}
void handler(int signo)
{
    std::cout << signo << " 号信号被递达!!!" << std::endl;
 
    std::cout << "-------------------------------" << std::endl;
    sigset_t pending;
    sigpending(&pending);
    PrintPending(pending);
    std::cout << "-------------------------------" << std::endl;
}
int main()
{
    // 0. 捕捉2号信号
    signal(2, handler); // 自定义捕捉
    sigset_t block_set,old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set,SIGINT);//向block_set信号集中添加SIGINT信号(编号为2)。
    //1.屏蔽2号信号
        // 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);
 
        //4.解除对2号信号的屏蔽
        cnt--;
        if(cnt==0)
        {
            std::cout << "解除对2号信号的屏蔽!!!" << std::endl;
            //使用直接重置的方法
            //我们之前是保存了old_set,老的屏蔽字,直接使用就行了
            sigprocmask(SIG_SETMASK, &old_set, &block_set);
        }
        sleep(1);
        
    }
}

我们不难发现,解除屏蔽后,信号会立即递达,pending对应位置由1置为0(这个过程,是在执行handler方法之前完成的,也就是在信号递达之前,位图就由1转为0了)。

在这里插入图片描述

七:🔥 共勉

以上就是我对 【Linux】进程信号(一) 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

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

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

相关文章

selenium+chromedriver下载与安装

安装selenium 使用pip安装selenium&#xff1a; pip install selenium安装成功&#xff1a; 安装WebDriver 根据你使用的浏览器下载相应的 WebDriver。 Chrome&#xff1a;下载地址Firefox&#xff1a;下载地址Edge&#xff1a;下载地址Safari&#xff1a;下载地址 1、c…

前端环境配置

对于换公司的小伙伴来讲&#xff0c;重新安装环境&#xff0c;百度或许稍微有点麻烦&#xff0c;本文章让你无脑式直接操作&#xff0c;保证环境畅通无阻。 1.安装nvm-setup 该插件是一款管理nodeJs的包&#xff0c;无需你单独下载nodeJs去安装&#xff0c;只需要下载安装此…

python(自用查看版)

目录 1.注意事项 1.1 python的除法不是整除&#xff0c;得到的是浮点数 1.2算术符号基于数学的算术优先级。具体可自行查看。 1.3注释 1.4缩进 1.5换行 1.6常见关键字 1.7续行符 1.8报错 1.9赋值 1.10比较运算符 2.常量和表达式 3.变量 4.数据类型 4.1整型int …

基于Prometheus的client_golang库实现应用的自定义可观测监控

文章目录 1. 安装client_golang库2. 编写可观测监控代码3. 运行效果4. jar、graalvm、golang编译运行版本对比 前文使用javagraalvm实现原生应用可观测监控&#xff1a; prometheus client_java实现进程的CPU、内存、IO、流量的可观测&#xff0c;但是部分java依赖包使用了复杂…

Docker网络概述

1. Docker 网络概述 1.1 网络组件 Docker网络的核心组件包括网络驱动程序、网络、容器以及IP地址管理&#xff08;IPAM&#xff09;。这些组件共同工作&#xff0c;为容器提供网络连接和通信能力。 网络驱动程序&#xff1a;Docker支持多种网络驱动程序&#xff0c;每种驱动程…

新160个crackme - 094-TheBigMan-crackme6

运行分析 需破解Name和Serial PE分析 LCC win32程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 ida搜索字符串&#xff0c;进入关键函数 ida动调&#xff0c;发现关键判断函数func_1 进入后&#xff0c;发现Name长度需满足一定要求&#xff0c;且func_2返回值不能…

大数据-214 数据挖掘 机器学习理论 - KMeans Python 实现 算法验证 sklearn n_clusters labels

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

【Django】视图函数

【Django】视图函数 视图函数的本质是Python中的函数&#xff0c;视图函数负责处理用户的请求并返回响应&#xff0c;该响应可以是网页的HTML内容、重定向、404错误、XML文档、图像或者任何东西&#xff0c;一般在应用中的views.py编写&#xff0c;示例代码如下&#xff1a; …

kafka+zookeeper的搭建

kafka从2.8版本开始&#xff0c;就可以不用配置zookeeper了&#xff0c;但是也可以继续配置。我目前使用的kafka版本是kafka_2.12-3.0.0.tgz&#xff0c;其中前面的2.12表示是使用该版本的scala语言进行编写的&#xff0c;而后面的3.00才是kafka当前的版本。 通过百度网盘分享…

了解bootstrap改造asp.net core MVC的样式模板

我们都知道&#xff0c;在使用默认的asp.net core MVC模板建立项目的时候&#xff0c;里面的样式是已经事先被写好了的。一般来说都在css目录下的site.css和bootstrap.css及下面的bootstrap.min.css中。我们打开bootstrap这些样式文件&#xff0c;里面有大量的样式类的定义&…

HTMLCSS:旋转的动态卡片

效果演示 这段代码创建了一个具有动态背景和渐变效果的卡片。卡片背景有一个无限循环的旋转动画&#xff0c;增加了视觉吸引力。这种效果可以用于展示个人信息、项目介绍或其他需要吸引用户注意的内容。 HTML <div class"card"><h3>前端Hardy</h3&…

深入Pillow:处理图像下载中的意外挑战

在当今数字化时代&#xff0c;获取和处理图像数据已经成为了许多应用程序的核心功能。从社交媒体到电子商务&#xff0c;图像的获取和处理对于用户体验至关重要。下载图片不仅能够丰富我们的内容&#xff0c;还能够通过分析图像数据为我们的应用提供更多价值。然而&#xff0c;…

管理 Elasticsearch 变得更容易了,非常容易!

作者&#xff1a;来自 Elastic Ken Exner Elasticsearch 用户&#xff0c;我们听到了你的心声。管理 Elasticsearch 有时会变得很复杂&#xff0c;面临的挑战包括性能调整、问题检测和资源优化。我们一直致力于简化你的体验。今天&#xff0c;我们宣布了自收购 Opster 以来的一…

Linux挖矿病毒(kswapd0进程使cpu爆满)

一、摘要 事情起因:有台测试服务器很久没用了&#xff0c;突然监控到CPU飙到了95以上&#xff0c;并且阿里云服务器厂商还发送了通知消息&#xff0c;【阿里云】尊敬的xxh: 经检测您的阿里云服务&#xff08;ECS实例&#xff09;i-xxx存在挖矿活动。因此很明确服务器中挖矿病毒…

【go从零单排】迭代器(Iterators)

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 在 Go 语言中&#xff0c;迭代器的实现通常不是通过语言内置的迭代器类型&#x…

(混乱版)数据冒险-ld,sub和and

第一张图没有数据转发 从这张图来看&#xff0c;如果没有数据转发机制&#xff0c;流水线的执行会出现更多的停顿。这种情况下&#xff0c;数据依赖只能通过**插入停顿周期&#xff08;stalls&#xff09;**来解决。具体分析如下&#xff1a; 指令序列 ld r1, 0(r2)&#xf…

成都睿明智科技有限公司抖音电商服务效果如何?

在这个短视频风起云涌的时代&#xff0c;抖音电商以其独特的魅力&#xff0c;成为了众多商家竞相追逐的新蓝海。而在这片波澜壮阔的商海中&#xff0c;成都睿明智科技有限公司犹如一艘稳健的航船&#xff0c;引领着无数企业驶向成功的彼岸。今天&#xff0c;就让我们一起揭开成…

ssm071北京集联软件科技有限公司信息管理系统+jsp(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;北京集联软件科技有限公司信息管理系统 \ 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本信息…

shodan[3](泷羽sec)

声明 学习视频来自B站UP主 泷羽sec,如涉及侵泷羽sec权马上删除文章。 笔记只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 这节课旨在扩大自己在网络安全方面的知识面&#xff0c;了解网络安全领域的见闻&#xff0c;了…

【OD-支持在线评测】数字涂色(100分)

📎 在线评测链接 https://app5938.acapp.acwing.com.cn/contest/11/problem/OD1081 🍓 OJ题目截图 🍿 最新机试E卷,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测,专栏文章质量平均 94 分 🌍 评测功能需要 ⇒ 订阅专栏 ⇐ 后私信联系解锁~ 文章目录 📎…