【Linux】进程信号的保存 | 自定义捕捉

文章目录

  • 三、信号的阻塞(信号的保存)
    • 1. 信号相关其他常见概念
    • 2. 在内核中的表示
    • 3. sigset_t类型
    • 4. 信号集操作函数
      • 函数列表
      • 注意事项
    • 5. 读取/修改block位图 - sigprocmask
    • 6. 读取pending位图 - sigpending
  • 四、信号捕捉
    • 1. 信号捕捉的初步认识
      • 自定义捕捉
      • 总结思考
    • 2. 再谈进程地址空间
      • 内核空间与用户空间
      • 用户态和内核态
    • 3. 内核如何实现信号的捕捉
    • 4. sigaction函数
  • 五、信号部分的总结


信号产生
信号保存
信号处理

信号的概念和信号如何产生已经在 【Linux】进程信号概念 | 核心转储 | 信号的产生 中介绍了,本文来介绍剩下的信号的保存(阻塞)和信号的捕捉。


三、信号的阻塞(信号的保存)

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

  • 实际执行信号的处理动作,称为信号递达(delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(pending)。
  • 进程可以选择阻塞(block)某个信号,表示该进程当前不想收到这个信号,收到后把它标记为未决,但不处理它,直到解除阻塞。换句话说,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 需要注意的是,阻塞和忽略是不同的,只要信号被阻塞就暂时不会递达,而忽略是在递达之后的一种处理动作

产生一个信号,信号是未决的,这个信号不一定是阻塞的。
一个信号如果被进程阻塞,进程收到该信号之后,一定会标记它为未决。



2. 在内核中的表示

  • 信号在内核中的表示示意图如下:请添加图片描述

  • 对于每个进程的pcb(task_struct对象),其中的每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作:请添加图片描述

  • 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。

  • 在示意图的例子中:

    1. SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
    2. SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
    3. SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它解除阻塞后会执行的处理动作是用户自定义函数sighandler()
  • 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?
    POSIX.1允许系统递送该信号一次或多次。
    Linux是这样实现的:

    • 常规信号在递达之前产生多次只计一次,毕竟未决只能是0或1,不可能记录次数。
    • 实时信号在递达之前产生多次可以依次放在一个队列里,本章不讨论实时信号。

用表格整理信号位图比特位的含义和现象:

信号状态比特位为1 (有效) 成因比特位为0 (有效) 成因运行现象
阻塞进程使用系统调用 sigprocmask 或类似方法设置阻塞标志位进程使用系统调用 sigprocmask 或类似方法解除阻塞标志位阻塞的信号不会被处理,直到解除阻塞
未决信号产生,且未被进程处理进程处理了一个未决信号未决的信号等待被处理,直到进程执行相应信号的处理动作

用流程图整理信号传递的过程:

cluster_final
cluster_reached
cluster_pending
cluster_blocked
cluster_signaled
未决
如果该信号被标记为阻塞
如果该信号未阻塞
解除阻塞
未决
解除未决
操作系统尽快让信号递达
忽略该信号
信号默认处理动作
信号的自定义捕捉
结束
结束
结束
继续运行/终止进程
执行信号处理动作
进程状态更新 - 忽略
进程状态更新 - 默认处理
进程状态更新 - 自定义处理
处理未决信号
清除未决标志
信号被阻塞
信号未决标志仍存在
设置未决标志
信号产生
cluster_unblock

3. sigset_t类型

上文提到,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。

未决和阻塞的标志可以用相同的数据类型sigset_t来存储:

typedef unsigned long sigset_t;

sigset_t就是信号位图,这个类型的每个比特位可以表示对应信号的“有效”或“无效”状态:

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

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

未决标志是在信号产生时,如果该信号没有被阻塞,并且进程没有设置信号的忽略处理方式,内核会将相应信号的未决比特位置1。

阻塞是进程的主动行为,可以通过系统调用(如sigprocmask)来设置阻塞,将特定信号的阻塞比特位置1。



4. 信号集操作函数

在Linux中,sigset_t 类型用一个 bit 表示每种信号的“有效”或“无效”状态。使用者不需要关心该类型内部的数据存储方式,只能通过以下函数来操作 sigset_t 变量,而不应直接解释其内部数据。使用 printf 直接打印 sigset_t 变量是没有意义的。

函数列表

<signal.h> 中的系统调用描述
int sigemptyset(sigset_t *set);初始化 set 所指向的信号集,将其中所有信号的对应 bit 清零,表示该信号集不包含任何有效信号。
int sigfillset(sigset_t *set);初始化 set 所指向的信号集,将其中所有信号的对应 bit 置位,表示该信号集的有效信号包括系统支持的所有信号。
int sigaddset(sigset_t *set, int signo);set 所指向的信号集中添加某个有效信号。
int sigdelset(sigset_t *set, int signo);set 所指向的信号集中删除某个有效信号。
int sigismember(const sigset_t *set, int signo);判断一个信号集的有效信号中是否包含某个信号。若包含则返回 1,不包含则返回 0,出错返回 -1。

使用一下上面的函数:

#include <stdio.h>
#include <signal.h>
#include <stdio.h>int main()
{sigset_t s; // 用户空间定义的变量sigemptyset(&s); // 初始化 `set` 所指向的信号集,将其中所有信号的对应 bit 清零,表示该信号集不包含任何有效信号sigfillset(&s); // 初始化 `set` 所指向的信号集,将其中所有信号的对应 bit 置位,表示该信号集的有效信号包括系统支持的所有信号sigaddset(&s, SIGINT); // 向 `set` 所指向的信号集中添加某个有效信号sigdelset(&s, SIGINT); // 从 `set` 所指向的信号集中删除某个有效信号bool test = sigismember(&s, SIGINT); // 判断一个信号集的有效信号中是否包含某个信号。若包含则返回 1,不包含则返回 0,出错返回 -1if (test)printf("true\n");elseprintf("false\n");return 0;
}结果:false

注意事项

在使用 sigset_t 类型的变量之前,必须调用 sigemptysetsigfillset 进行初始化,以确保信号集处于确定的状态。初始化后,可以调用 sigaddsetsigdelset 在该信号集中添加或删除某个有效信号。

这四个函数都是成功返回 0,出错返回 -1。sigismember 是一个布尔函数,用于判断一个信号集的有效信号中是否包含某个信号,若包含则返回 1,不包含则返回 0,出错返回 -1。



5. 读取/修改block位图 - sigprocmask

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

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

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

使用一下:

#include <iostream>
#include <unistd.h>
#include <signal.h>signed main()
{std::cout << "getpid: " << getpid() << std::endl;sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock);for (int signo = 1; signo <= 31; signo++) // 把1-31信号全写入block{sigaddset(&block, signo); // 在这里只是修改了block变量,没有让OS真正屏蔽signo号信号;}sigprocmask(SIG_BLOCK, &block, &oblock); // 将该进程的block位图替换成我们的block位图while (true){std::cout << "我已经屏蔽了所有的信号,来打我呀!" << std::endl;sleep(1);}
}

使用下面的bash脚本,在另外一个bash下每隔一秒kill一下该进程:

i=1; while :; do echo "send signal:${i}..."; kill -${i} 7422; sleep 1; let i++; done

发现除了9号和19号信号,其他信号都能成功屏蔽:
请添加图片描述

请添加图片描述

印证了之前说的SIGKILL (9号信号)SIGSTOP (19号信号)不能被捕获、阻止或忽略。



6. 读取pending位图 - sigpending

sigpending函数可以用于读取进程的未决信号集,该函数的函数原型如下:

int sigpending(sigset_t *set);

sigpending函数读取当前进程的未决信号集,通过set参数传出。该函数调用成功返回0,出错返回-1。

实验一下:

  1. 先用上述的函数将2号信号进行屏蔽(阻塞)。
  2. 使用kill命令或组合按键向进程发送2号信号。
  3. 此时2号信号会一直被阻塞,并一直处于pending(未决)状态。
  4. 使用sigpending函数获取当前进程的pending信号集进行验证。

代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>void PrintPending(const sigset_t& pending)
{for (int signo = 1; signo <= 32; signo++){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << "\n";
}signed main()
{sigset_t set, oset;sigemptyset(&set);sigemptyset(&oset);//1. 阻塞2号信号sigaddset(&set, 2); //SIGINTsigprocmask(SIG_SETMASK, &set, &oset);//2. 让进程不断获取当前自己的pending位图sigset_t pending;sigemptyset(&pending);while (1){sigpending(&pending); //获取pendingPrintPending(pending); //打印pending位图(1表示未决)sleep(1);}return 0;
}

可以看到,程序刚刚运行时,因为没有收到任何信号,所以此时该进程的pending表一直是全0,而当我们使用kill命令向该进程发送2号信号后,由于2号信号是阻塞的,进程收到但不处理2号信号,因此2号信号一直处于未决状态,所以我们看到pending表中的第二个数字一直是1,且进程不会退出:请添加图片描述

问题:一个信号被递达,pending位图会将该信号的标志位从1改成0,这个修改发生在执行递达动作前还是递达动作执行完成后?


我们可以让进程自定义捕捉2号信号,让handler函数打印pending位图,就可以验证修改pending位图的动作的发生时机:

#include \<iostream>
#include <unistd.h>
#include <signal.h>void PrintPending(const sigset_t& pending)
{std::cout << "\n";for (int signo = 1; signo <= 32; signo++){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << "\n";
}void handler(int signo)
{sigset_t pending;sigpending(&pending);PrintPending(pending);std::cout << "handler of signal " << signo << std::endl;
}signed main()
{signal(2, handler);while (true){sleep(1);}return 0;
}

请添加图片描述

实际上,键盘按下 Ctrl+C 之后,向当前进程发送2号信号,信号执行自定义的递达动作handler之前,OS已经将pending位图由1置0了。



四、信号捕捉

1. 信号捕捉的初步认识

自定义捕捉

实际上当用户按Ctrl+C时,这个键盘输入会产生一个硬件中断,被操作系统获取并解释成信号(Ctrl+C被解释成2号信号),然后操作系统将2号信号发送给目标前台进程,当前台进程收到2号信号后就会退出。

我们可以使用signal函数对2号信号进行捕捉,证明当我们按Ctrl+C时进程确实是收到了2号信号。使用signal函数时,我们需要传入两个参数,第一个是需要捕捉的信号编号,第二个是对捕捉信号的处理方法,该处理方法的参数是int,返回值是一个函数指针,指向原来的信号处理函数:

   #include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

例如,下面的代码中将2号信号进行了捕捉,当该进程运行起来后,若该进程收到了2号信号就会打印出收到信号的信号编号。

#include <stdio.h>
#include <signal.h>
void handler(int sig)
{printf("catch a sig : %d\n", sig);
}
int main()
{signal(2, handler); //信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的,提前了解一下while(1);return 0;
}

请添加图片描述



总结思考

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

  2. 信号的处理是否是立即处理的?
    不是所有信号的处理都是立即进行的,而是在合适的时候处理,“合适的时候”是指进程从内核态返回到用户态的时候。有些信号,例如 SIGKILL,会立即终止进程。但对于其他信号,处理可能会延迟,具体取决于进程的状态以及是否被阻塞。

  3. 信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
    是的,如果信号不能立即处理,需要被暂时记录下来。这通常在进程的 pending 结构体中进行记录,其中包括一个位图用于表示未决信号的状态。

  4. 一个进程在没有收到信号的时候,能否知道自己应该对合法信号作何处理呢?
    进程可以在收到信号之前,通过注册信号处理函数来定义对合法信号的处理方式。这通常通过使用 signal()sigaction() 系统调用来实现。进程可以指定默认的处理动作,或者自定义处理函数。

  5. 如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?
    当发生触发信号的事件时(例如按下 Ctrl+C 产生SIGINT),操作系统会向相应进程发送信号。该信号会被记录在进程的 pending 位图中。如果信号不被阻塞,进程会根据信号的处理方式(默认动作、自定义处理函数等)来执行相应的处理。如果信号被阻塞,信号会在解除阻塞后递达,然后按照相应的处理方式进行处理。



2. 再谈进程地址空间

内核空间与用户空间

每一个进程都有自己的进程地址空间,该进程地址空间由内核空间用户空间组成:请添加图片描述

  • 用户所写的代码和数据位于用户空间,通过用户级页表与物理内存之间建立映射关系。
  • 内核空间存储的实际上是操作系统代码和数据,通过内核级页表与物理内存之间建立映射关系。内核级页表是一个全局的页表,它用来维护操作系统的代码与进程之间的关系。因此:
    • 在每个进程的进程地址空间中,用户空间是属于当前进程的,每个进程看到的代码和数据是完全不同的,只能看到自己的那一份;
    • 内核空间所存放的都是操作系统的代码和数据,所有进程看到的都是一样的内容,OS的代码和数据被所有内存共享。

虽然每个进程都能够看到操作系统,但并不意味着每个进程都能够随时对其进行访问,当进程访问用户空间时进程必须处于用户态,当进程访问内核空间时进程必须处于内核态。



用户态和内核态

用户态:执行用户所写的代码时,就属于 用户态
内核态:执行操作系统的代码时,就属于 内核态

从用户态切换为内核态通常有如下几种情况:

  1. 需要进行系统调用时。
  2. 当前进程的时间片到了,导致进程切换。
  3. 产生异常、中断、陷阱等。

与之相对应,从内核态切换为用户态有如下几种情况:

  1. 系统调用返回时。
  2. 进程切换完毕。
  3. 异常、中断、陷阱等处理完毕。

其中,由用户态切换为内核态我们称之为陷入内核。每当我们需要陷入内核的时,本质上是因为我们需要执行操作系统的代码,比如系统调用函数是由操作系统实现的,我们要进行系统调用就必须先由用户态切换为内核态。

陷入内核和切换回用户态的底层实现:

CPU 中,存在一个 CR3 寄存器,这个 寄存器 的作用就是用来表征当前处于 用户态 还是 内核态

  • 当寄存器中的值为 3 时:表示正在执行用户的代码,也就是处于用户态
  • 当寄存器中的值为 0 时:表示正在执行操作系统的代码,也就是处于 内核态

请添加图片描述



3. 内核如何实现信号的捕捉

当我们在执行主控制流程的时候,可能因为某些情况而陷入内核,当内核处理完毕准备返回用户态时,一定会进行信号pending表的检查。(此时仍处于内核态,有权力查看当前进程的pending位图)

在查看pending位图时,如果发现有未决信号,并且该信号没有被阻塞,那么此时就需要该信号进行处理,因为处理方式有默认和自定义两种情况,我们分情况讨论一下:


  • 情况1:信号的默认处理(如果没有用signal()sigaction()来注册信号的自定义捕捉行为)


    如果待处理信号的处理动作是默认或者忽略,则执行该信号的处理动作后清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,从主控制流程中上次被中断的地方继续向下执行即可。请添加图片描述

  • 情况2:自定义捕捉(用户代码中用signal()sigaction()注册了信号的自定义捕捉行为)


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

上面两次从内核态回到用户态的过程中,都会检查pending表,因为要检查有没有需要处理的信号。

简化一下上面这张图:请添加图片描述



4. sigaction函数

捕捉信号除了用前面用过的signal()函数之外,我们还可以使用sigaction()函数对信号进行捕捉,sigactionsignal 功能更丰富,sigaction()函数的函数原型如下:

NAMEsigaction - examine and change a signal actionSYNOPSIS#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

sigaction结构体:

struct sigaction 
{void     (*sa_handler)(int);	                    //自定义动作void     (*sa_sigaction)(int, siginfo_t *, void *);	//实时信号相关sigset_t   sa_mask;	                                //待屏蔽的信号集int        sa_flags;	                            //一些选项,一般设为 0void     (*sa_restorer)(void);	                    //实时信号相关
};

返回值:成功返回 0,失败返回 -1 并将错误码设置
参数1:待操作的信号
参数2:sigaction 结构体,具体成员如上所示
参数3:保存修改前进程的 sigaction 结构体信息

我们可以像使用signal()一样使用sigaction()

#include <iostream>
#include <signal.h>
using namespace std;void PrintPending(const sigset_t& pending)
{std::cout << " 当前进程的 pending 表为: ";for (int signo = 1; signo <= 32; signo++){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << "\n";
}void handler(int signo)
{sigset_t pending;sigpending(&pending);while (true){PrintPending(pending);sleep(2);}
}int main()
{cout << "当前进程的pid:" << getpid() << endl;struct sigaction act, oact;// 初始化自定义动作act.sa_handler = handler;// 给2号信号注册自定义动作sigaction(2, &act, &oact);while (true);return 0;
}

只对二号信号进行了自定义捕捉,收到二号信号后不断打印pending位图,此时发别的信号依然能正常终止该进程,运行现象和用signal()一样:
请添加图片描述

但是如果设置了sa_mask字段,则当进程递达信号并执行用户自定义动作handler时,可以将部分信号进行屏蔽直到用户自定义动作执行完成

加入对3,4,5信号的屏蔽:

//初始化 屏蔽信号集 
sigaddset(&act.sa_mask, 3); 
sigaddset(&act.sa_mask, 4); 
sigaddset(&act.sa_mask, 5);

完整代码:

#include <iostream>
#include <signal.h>
#include <assert.h>
using namespace std;void PrintPending(const sigset_t& pending)
{std::cout << " 当前进程的 pending 表为: ";for (int signo = 1; signo <= 32; signo++){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << "\n";
}void handler(int signo)
{cout << signo << "号信号递达了" << endl;sigset_t pending;int cnt = 15;while (cnt--){int ret = sigpending(&pending);assert(ret == 0);(void)ret; // 假装用一下ret,欺骗编译器,避免 release 模式中出错PrintPending(pending);sleep(1);}
}int main()
{cout << "当前进程的pid:" << getpid() << endl;struct sigaction act, oact;// 初始化 自定义动作act.sa_handler = handler;//初始化 屏蔽信号集 sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);// 给2号信号注册自定义动作sigaction(2, &act, &oact);while (true);return 0;
}

2 号信号的循环结束(10 秒),3、4、5 信号的 阻塞 状态解除,立即被 递达,进程就被干掉了:请添加图片描述



五、信号部分的总结

信号的知识按照:

信号产生
信号保存
信号处理

的顺序展开:

  • 信号产生阶段:有四种产生方式,包括 键盘键入、系统调用、软件条件、硬件异常。
  • 信号保存阶段:内核中存在三张表,blcok 表、pending 表以及 handler 表,信号在产生之后,存储在 pending 表中。
  • 信号处理阶段:信号在 内核态 切换回 用户态 时,才会被处理,处理方式或默认或忽略或自定义。

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

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

相关文章

A股上市公司绿色化转型指数(2007-2022)

数据来源&#xff1a;上市公司年报、上市公司网站信息、上市公司社会责任报告 时间跨度&#xff1a;2007-2022年 数据范围&#xff1a;中国A股上市公司 数据指标 参考Loughran & Mcdonald&#xff08;2011&#xff09;的研究&#xff0c;利用年报中披露的文本信息测量企业…

心律守护 基于机器学习的心脏病预测

心律守护 基于机器学习的心脏病预测 心律守护 基于机器学习的心脏病预测项目背景与意义项目数据与特征数据分析与预处理机器学习模型建立与评估结语 心律守护 基于机器学习的心脏病预测 在当今数字化时代&#xff0c;机器学习的应用已经渗透到了医疗保健领域的各个层面。其中&…

什么是PAGA系统

PAGA系统是一种公共广播和通用报警系统&#xff0c;它在船舶、海上钻井平台、石油化工、天然气开采等行业的应用非常广泛。当遇到紧急情况或其他特殊情况时&#xff0c;PAGA系统能够在大范围内进行喊话广播或报警。这种系统通过自动电话系统&#xff08;如PABX&#xff0c;即自…

【Python--网络编程之Ping命令的实现】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python开发技术 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Python网络编程之Ping命令的实现 往期内容代码见资源&#xff0c;效果图如下一、实验要求二、协…

巴伦周刊:全球最赚钱对冲基金的成功秘诀是什么?

对冲基金界的明星们过去常常大起大落&#xff0c;有时甚至会实现大满贯式的全面成功&#xff0c;比如约翰•保尔森(John Paulson)在2007-09年金融危机前做空美国房地产市场赚了200亿美元。 但摇摆不定的对冲基金近年来并不是大赢家&#xff0c;相反最好的回报来自于多策略、多…

深度学习系列53:大模型微调概述

参考系列文章&#xff1a;https://zhuanlan.zhihu.com/p/635152813 github链接&#xff1a;https://github.com/liguodongiot/llm-action 1 训练范式 下面这种instructive learning&#xff0c;在模型参数达到80亿后&#xff0c;会发生质的提升&#xff1a; 类似的还有手写pr…

鸿蒙开发系列教程(二十三)--List 列表操作(2)

列表样式 1、设置内容间距 在列表项之间添加间距&#xff0c;可以使用space参数&#xff0c;主轴方向 List({ space: 10 }) { … } 2、添加分隔线 分隔线用来将界面元素隔开&#xff0c;使单个元素更加容易识别。 startMargin和endMargin属性分别用于设置分隔线距离列表侧…

2024HVV | 12款开源渗透测试工具(非常详细)零基础入门到精通,收藏这一篇就够了

回顾过去&#xff0c;黑客入侵异常困难&#xff0c;需要大量手动操作。然而&#xff0c;如今&#xff0c;一套自动化测试工具让渗透测试人员变身“半机械人”&#xff0c;能够比以往任何时候都更轻松地完成更多测试。以下12款开源渗透测试工具&#xff0c;可以帮助渗透测试人员…

微信小程序: 获取accessToken,手机号, 小程序二维码,openId与unionId 公共配置类(核心篇)

全文目录,一步到位 1.前言简介1.1 专栏传送门 2. 微信小程序公用功能2.1 配置准备工作2.1.1 配置文件准备(单体放yml中 微服务放配置中心)2.1.2 获取配置文件中的小程序配置2.1.3 设置redis配置 2.2 创建不同功能工具类2.2.1 创建微信服务工具类WechatServiceUtils2.2.2 创建Re…

Java 基于 SpringBoot+Vue 的酒店管理系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

每日五道java面试题之java基础篇(十)

目录: 第一题 JVM有哪些垃圾回收器&#xff1f;第二题 垃圾回收分为哪些阶段&#xff1f;第三题 线程的⽣命周期&#xff1f;线程有⼏种状态&#xff1f;第四题.ThreadLocal的底层原理第五题.并发、并⾏、串⾏之间的区别 第一题 JVM有哪些垃圾回收器&#xff1f; ● 新⽣代收集…

YOLOv8制作自定义数据集并训练

YOLOv8制作自定义数据集并训练 前言一、制作自定义数据集1、建立相应文件夹2、下载图片3、为图片打标签&#xff08;1&#xff09;安装labelimg&#xff08;2&#xff09;打开labelimg&#xff08;3&#xff09;标记图片 二、按比例移动自定义数据集中的内容三、建立数据集测试…

2024年危险化学品经营单位主要负责人证模拟考试题库及危险化学品经营单位主要负责人理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年危险化学品经营单位主要负责人证模拟考试题库及危险化学品经营单位主要负责人理论考试试题是由安全生产模拟考试一点通提供&#xff0c;危险化学品经营单位主要负责人证模拟考试题库是根据危险化学品经营单位主…

机器学习入门--LSTM原理与实践

LSTM模型 长短期记忆网络&#xff08;Long Short-Term Memory&#xff0c;LSTM&#xff09;是一种常用的循环神经网络&#xff08;RNN&#xff09;变体&#xff0c;特别擅长处理长序列数据和捕捉长期依赖关系。本文将介绍LSTM模型的数学原理、代码实现和实验结果&#xff0c;并…

OpenCV库及在ROS中使用

OpenCV库及在ROS中使用 依赖 cv_bridge image_transport roscpp rospy sensor_msgs std_msgsCMakeLists.txt添加 find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(pub_img_topic ${catkin_LIBRARIES} ${Opencv_LIBS}) C …

基于springboot大学生租房系统源码和论文

伴随着全球信息化发展&#xff0c;行行业业都与计算机技术相衔接&#xff0c;计算机技术普遍运用于各大行业&#xff0c;大学生租房系统便是其中一种。实施计算机系统来管理可以降低大学生租房管理的成本&#xff0c;使整个大学生租房的发展和服务水平有显著提升。 本论文主要面…

Github Copilot是什么?Ai高效编程!一键远程授权…

GitHub Copilot是一款Ai编程插件&#xff0c;由OpenAi和Github联合推出&#xff0c;目前支持主流的IDE编辑器安装使用&#xff0c;包括JetBrains IDEs、VSCode、Visual Studio、Neovim等。 官方地址&#xff1a;https://github.com/features/copilot 官方文档&#xff1a;http…

VBA即用型代码手册之取消隐藏工作表及删除工作表

我给VBA下的定义&#xff1a;VBA是个人小型自动化处理的有效工具。可以大大提高自己的劳动效率&#xff0c;而且可以提高数据的准确性。我这里专注VBA,将我多年的经验汇集在VBA系列九套教程中。 作为我的学员要利用我的积木编程思想&#xff0c;积木编程最重要的是积木如何搭建…

【maya 入门笔记】基本视图和拓扑

1. 界面布局 先看基本窗口布局&#xff0c;基本窗口情况如下&#xff1a; 就基本窗口布局的情况来看&#xff0c;某种意义上跟blender更像一点&#xff08;与3ds max相比&#xff09;。 那么有朋友就说了&#xff0c;玛格基&#xff0c;那blender最下面的时间轴哪里去了&…

使用PaddleNLP UIE模型提取上市公司PDF公告关键信息

项目地址&#xff1a;使用PaddleNLP UIE模型抽取PDF版上市公司公告 - 飞桨AI Studio星河社区 (baidu.com) 背景介绍 本项目将演示如何通过PDFPlumber库和PaddleNLP UIE模型&#xff0c;抽取公告中的相关信息。本次任务的PDF内容是破产清算的相关公告&#xff0c;目标是获取受理…