【Linux初阶】信号入门2 | 信号阻塞、捕捉、保存

news2024/11/24 17:37:28

文章目录

  • ☀️前言
  • ☀️一、信号阻塞
    • 🌻1.信号其他相关常见概念
    • 🌻2.信号在内核中的表示
  • ☀️二、信号捕捉(重点)
    • 🌻1.用户态 & 内核态
    • 🌻2.如何判断进程处于用户态或内核态
    • 🌻3.OS接口的访问方法
    • 🌻4.信号的捕捉过程
  • ☀️三、信号保存1
    • 🌻1.sigset_t
    • 🌻2. 信号集操作函数
    • 🌻3.sigprocmask
    • 🌻4.sigpending
    • 🌻5.代码示例
  • ☀️四、信号保存2
    • 🌻1.sigaction
    • 🌻2.代码示例 - sigaction
    • 🌻3.可重入函数
    • 🌻4.volatile关键字
  • ☀️五、信号总结
  • ☀️结语


☀️前言

通过我们上一篇文章的学习,我们知道信号的生命周期包括四个阶段:预备、信号产生、信号保存、信号处理。同时我们还接触到了信号的保存位置:信号被保存在进程的 task_struct中。知道了信号发送的本质就是修改进程 task_struct中的位图结构

在本片文章中,我将带领大家更加深入学习信号阻塞、捕捉、保存的知识。


☀️一、信号阻塞

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

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

对于信号的发送我们还要树立两点共识:1.信号发送是以操作系统为载体,向目标进程发送信号的。2.因为我们的信号不会被立即处理,因此信号产生和信号递达之间就会产生一个简单的时间窗口,在这个时间窗口中,信号已经收到了但是没有被立即处理,因此我们需要将信号保存起来。

🌻2.信号在内核中的表示

图示1:
在这里插入图片描述

  • task_struct中有两张位图和一个指针,它们分别是 pending位图block位图指向 hander函数指针数组的指针
  • pending位图默认为0,它可以表示为32个比特位,比特位的位置表示信号编号,比特位的内容(0 or 1)表示的是是否收到该信号
  • block位图默认也为0,它也可表示32个比特位,比特位的位置表示信号编号,比特位的内容表示 是否阻塞该信号
  • 指针指向 hander函数指针数组,我们可以把它简称为 hander表,数组的下标表示信号的编号,数组下标对应的函数内容表示 对应信号的处理方法
  • 图中右上角为信号的递达的伪代码,它告诉我们:如果一个信号阻塞了,信号就不会递达
  • 图的最上方有一个 signal函数,我们可以通过信号内核表示加以理解:signo代表信号编号,handler表示修改函数数组对应信号的处理方法。

总结:1.如果一个信号没有产生,并不妨碍它被阻塞。2.进程为什么能识别信号?因为每个信号都有自己对应的 pending位图、block位图 和 hander表。

图示2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKN04IS3-1690967125451)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230730232240040.png)]


☀️二、信号捕捉(重点)

通过上面的学习,我们知道:信号产生的时候,不会被立即处理,而是会在合适的时候被处理。

那么问题来了,究竟合适的时候是什么时候呢?答案是:从内核态返回用户态的时候,进行处理

🌻1.用户态 & 内核态

  • 进程在运行时,有两种状态(运行级别),它们分别是:用户态内核态
  • 用户态:运行我们在电脑上自己写的代码(包括数据结构等),都是在用户态下完成的。
  • 内核态:运行系统调用,就是在内核态完成的。
  • 用户为了访问某些资源(OS or 硬件),必须通过系统调用完成,因此在访问过程中需要 状态改变
  • 系统调用比较费时间,因此我们应尽量避免频繁调用系统调用。举一个简单的例子:当我们使用 vector进行扩容的时候,计算机往往会为我们多申请一些空间,这就是为了避免频繁调用系统调用。

🌻2.如何判断进程处于用户态或内核态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ev86QH5C-1690967125453)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230730231112099.png)]

  • CPU中有很多可见和不可见的寄存器,它们保存有当前运行进程的上下文数据。
  • CPU有专门的寄存器可以指向进程的 task_struct(PCB),和页表(用户级页表 & 内核级页表)的起始地址。
  • CPU内有一个名为 CR3的寄存器,它表征当前进程的运行级别:0-内核态,3-用户态

🌻3.OS接口的访问方法

在这里插入图片描述

  • 进程的 task_strcut有指向该进程的地址空间(mm_strcut)的指针,地址空间分为内核空间(1G)和用户空间(3G),因此页表也有两个:用户级页表内核级页表
  • 内核级页表指向 内核对应的虚拟地址空间。
  • 每个进程都有自己的地址空间,由于不同进程共用同一个内核,每个进程的地址空间中的内核空间都是同一个,因此内核级页表只要有一份就够了,它指向同一份虚拟地址空间和物理内存。
  • 进程要访问OS的接口,因为每个进程的地址空间中都带有同一个内核空间,因此只需要在地址空间中自行跳转到内核空间访问即可

总结:1.用户访问OS的过程:运行到特定代码 -> 系统调用(起始位置会更改CR3寄存器)-> 查看CR3寄存器(确认运行状态) -> 跳转到内核空间进行访问 -> 访问完成 -> 更改CR3寄存器 -> 返回并继续执行下一行代码。

🌻4.信号的捕捉过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2m3h8QD-1690967125455)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230731231739555.png)]

  • 在有需要的时候陷入内核用户态 -> 内核态)。
  • 由于陷入内核会会产生一定的成本(系统调用比较费时间),因此内核处理完对应的工作或异常的时候,不会立即返回运行下一条代码,而是会以内核的身份处理一些只有内核才能完成的额外的工作。
  • 内核处理完对应的工作后会进行信号检测和递达处理
  • 信号处理分为3种:默认、忽略、自定义。大部分信号的处理方式为终止对应进程,忽略即不需要处理,自定义就是如同 signal函数一样执行我们定义的方法。
  • OS会检测是否收到某一信号、该信号是否阻塞、处理方法是哪个。
  • 如果确认收到某一信号,该信号没有阻塞,且为自定义处理方法,则:回到用户态,执行对应的自定义处理方法。(内核态 -> 用户态
  • 注意:我们不能用用户态执行内核的代码(权限不足),也不能用内核态执行用户态的代码(避免用户对内核的恶意访问)。
  • 执行完自定义处理方法之后,不能直接跳转回代码部分运行下一条代码。这是因为在我们使用系统调用时,我们的部分数据(代码运行位置)是由OS保存的,因此我们需要使用OS的身份进行恢复,再跳转回去运行下一条代码。
  • 执行完自定义处理方法之后,需要重新回到内核态,恢复数据用户态 -> 内核态)。
  • 再使用特定的系统调用,回到代码运行的地方内核态 -> 用户态)。
  • 至此,完成了信号捕捉的全过程,然后继续运行下一条代码。

信号捕捉巧记图:红色圆圈代表操作,绿色圆圈代表状态切换(4个操作 + 4次状态切换),如果信号的执行方法为默认或者忽略,则不会再沿图示路径进行下去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YIEIQDXR-1690967125456)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230731235922188.png)]

通过学习信号的捕捉过程,我们就可以更加深入理解到本节开头时候的话:信号产生的时候,不会被立即处理,而是会在合适的时候被处理,即从内核态返回用户态的时候


☀️三、信号保存1

综合我们学习的知识,我们可以得出:信号产生之后不会立即递达,而是会在合适的时候递达,因此我们的信号在这个时间周期内需要被保存。信号被保存在进程的 task_struct中,信号发送(保存)的本质就是修改进程 task_struct中的位图结构

这里我们再复习一下信号递达和信号未决的知识点,方便后面的学习:

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)

🌻1.sigset_t

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NQ8SwR98-1690967125456)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230730232240040.png)]

  • 从上图来看,每个信号只有一个bit的未决标志(判断是否收到该信号),非0即1,不记录该信号产生了多少次,阻塞标志(判断信号是否阻塞)也是这样表示的。
  • 因此,未决和阻塞标志可以用相同的数据类型 sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态
  • 下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

🌻2. 信号集操作函数

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清零,表示该信号集不包含 任何有效信号。
  • 函数 sigfifillset初始化 set所指向的信号集,使其中所有信号的对应bit置为1,表示该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用 sigemptysetsigfifillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用 sigaddsetsigdelset在该信号集中添加或删除某种有效信号。
  • 这四个函数都是成功返回0,出错返回-1。
  • sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

🌻3.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参数的可选值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UoEX5Jk-1690967125457)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230801105602021.png)]

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

🌻4.sigpending

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

总结:sigprocmask - 修改block位图(阻塞信号集/信号屏蔽字),sigpending - 获取pending位图(未决信号集),signal - 修改信号处理方法。

🌻5.代码示例

  • 下面代码讲述的是如何调整信号屏蔽字
#include <iostream>
#include <vector>
#include <signal.h>
#include <unistd.h>

// #define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31

using namespace std;

// static vector<int> sigarr = {2,3};
static vector<int> sigarr = { 2 };

static void show_pending(const sigset_t& pending)
{
    for (int signo = MAX_SIGNUM; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
        {
            cout << "1";
        }
        else cout << "0";
    }
    cout << "\n";
}

static void myhandler(int signo)
{
    cout << signo << " 号信号已经被递达!!" << endl;
}

int main()
{
    for (const auto& sig : sigarr) signal(sig, myhandler);

    // 1. 先尝试屏蔽指定的信号
    sigset_t block, oblock, pending;
    // 1.1 初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    sigemptyset(&pending);
    // 1.2 添加要屏蔽的信号
    for (const auto& sig : sigarr) sigaddset(&block, sig);
    // 1.3 开始屏蔽,设置进内核(进程)
    sigprocmask(SIG_SETMASK, &block, &oblock);

    // 2. 遍历打印pengding信号集
    int cnt = 10;
    while (true)
    {
        // 2.1 初始化
        sigemptyset(&pending);
        // 2.2 获取它
        sigpending(&pending);
        // 2.3 打印它
        show_pending(pending);
        // 3. 慢一点
        sleep(1);
        if (cnt-- == 0)
        {
            sigprocmask(SIG_SETMASK, &oblock, &block); // 一旦对特定信号进行解除屏蔽,一般OS要至少立马递达一个信号!
            cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";
        }
  • 运行结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxSWfAj0-1690967125459)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230801225551420.png)]

总结:我们可以通过信号集操作函数初始化信号集,并将需要屏蔽的信号加入屏蔽信号集中,然后用 sigprocmask函数将信号集内容射入内核,然后通过 sigpending函数查看 pending信号集。上面的示例显示,当我们屏蔽2号信号之后,我们输入 ctrl+C 后会将信号存储于 pending信号集中,而不会递达,即不会执行 signal函数中的 myhandler方法。


☀️四、信号保存2

🌻1.sigaction

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

知识点1:

  • sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。
  • signo是指定信号的编号。
  • act指针非空,则根据act修改该信号的处理动作。
  • oact指针非 空,则通过oact传出该信号原来的处理动作。
  • act和oact指向sigaction结构体
  • sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

知识点2:

  • 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止
  • 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
  • sa_flflags/sa_flags字段包含一些选项,本章的代码都把sa_flflags设为0,sa_sigaction是实时信号的处理函数,本章不详细解释这两个字段,有兴趣的同学可以在了解一下。

🌻2.代码示例 - sigaction

  • 下述代码用于验证:某个信号在递达时,该信号会被屏蔽。
 #include <iostream>
 #include <cstdio>
 #include <signal.h>
 #include <unistd.h>

 using namespace std;

 void Count(int cnt)
 {
     while(cnt)
     {
         printf("cnt: %2d\r", cnt);
         fflush(stdout);
         cnt--;
         sleep(1);
     }
     printf("\n");
 }

 void handler(int signo)
 {
     cout << "get a signo: " << signo << "正在处理中..." << endl;
     Count(20); //调用计时程序
 }

 int main()
 {
     struct sigaction act, oact; 
     act.sa_handler = handler;
     act.sa_flags = 0;
     sigemptyset(&act.sa_mask); // 当我们正在处理某一种信号的时候,我们也想顺便屏蔽其他信号,就可以添加到这个sa_mask中
     sigaddset(&act.sa_mask, 3); //对3号信号也添加屏蔽
     sigaction(SIGINT, &act, &oact); //SIGINT为2号信号

     while(true) sleep(1);

     return 0;
 }
  • 运行结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SstA1NV6-1690967793303)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230801233636876.png)]

总结:代码运行时,在第一个信号递达过程中(计数器开始计时),我们再向该进程发送2号信号则无法递达,第二次发送的2号信号将被保存在 pending位图中,等待第一次发送的信号递达完成之后才会执行对应方法,第3、4…次的信号发送均会失效/丢失。

🌻3.可重入函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPuHK8xW-1690967125460)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20230802001418612.png)]

  • main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步(如上图insert代码所示),刚做完第一步的 时候,因为硬件中断(该进程的时间片到了)使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
  • main 执行流 和 handler执行流(信号捕捉执行流)是两个不同的执行流,它们之间相互独立
  • 像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

🌻4.volatile关键字

  • 该关键字在C当中我们已经有所涉猎,今天我们站在信号的角度重新理解一下
[ldx@localhost code_test]$ cat sig.c
#include <stdio.h>
#include <signal.h>

int flag = 0;

void handler(int sig)
{
	printf("chage flag 0 to 1\n");
	flag = 1;
}

int main()
{
	signal(2, handler);
	while (!flag);
	printf("process quit normal\n");
	return 0;
}

[ldx@localhost code_test]$ cat Makefile
sig : sig.c
gcc -o sig sig.c #-O2      #使用#号屏蔽优化,02为优化级别
.PHONY : clean
clean :
rm - f sig

[ldx@localhost code_test]$ ./sig
^ Cchage flag 0 to 1
process quit normal

标准情况下,键入 Ctrl-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 , while 条件不满足,退出循环,进程退出

[ldx@localhost code_test]$ cat sig.c
#include <stdio.h>
#include <signal.h>

int flag = 0;

void handler(int sig)
{
	printf("chage flag 0 to 1\n");
	flag = 1;
}

int main()
{
	signal(2, handler);
	while (!flag);
	printf("process quit normal\n");
	return 0;
}

[ldx@localhost code_test]$ cat Makefile
sig : sig.c
gcc -o sig sig.c -O2       #放开屏蔽,设置优化级别02
.PHONY : clean
clean :
rm - f sig

[ldx@localhost code_test]$ ./sig
^ Cchage flag 0 to 1
^ Cchage flag 0 to 1
^ Cchage flag 0 to 1

我们的代码在编译过程中,编译器会对其进行优化,优化有不同级别,优化情况下,键入 Ctrl-C ,2号信号被捕捉,执行自定义动作,修改 flag=1 ,但是 while 条件依旧满足,进程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?很明显while 循环检查的flag,并不是内存中最新的flag,这就存在了数据二异性的问题。 while检测的flag其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?很明显需要 volatile

  • 优化过程中,编译器认为while循环中的flag不会被修改,因此它默认提前将flag的值加载到cup中去了,然后让出资源执行其他代码去了,即对于 flag只做了检测,没有做修改。
  • handler中修改的值是内存中的flag值,和已经 load到cpu中的 flag数据并不相同,只要cpu(寄存器)中的flag不变,那么循环就会一直进行下去。
[ldx@localhost code_test]$ cat sig.c

#include <stdio.h>
#include <signal.h>

volatile int flag = 0; //在全局变量前加volatile关键字

void handler(int sig)
{
	printf("chage flag 0 to 1\n");
	flag = 1;
}

int main()
{
	signal(2, handler);
	while (!flag);
	printf("process quit normal\n");
	return 0;
}

[ldx@localhost code_test]$ cat Makefile
sig : sig.c
gcc - o sig sig.c - O2
.PHONY : clean
clean :
rm - f sig

[ldx@localhost code_test]$ . / sig
^ Cchage flag 0 to 1
process quit normal

volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作

我们需要根据实际应用场景(优化级别比较高且存在需要更新的判断变量),判断我们是否需要添加volatile 关键字。

☀️五、信号总结

  • 下面是我在前面的信号文章中给出的信号生命周期图

在这里插入图片描述

  • 下面是信号的知识点汇总,方便大家对应回顾

  • 信号的预备,信号的基本概念。

  • 信号的产生,信号的产生方法,发送本质。

  • 信号捕捉(用户态内核态 & OS接口的访问方法 & 捕捉过程)

  • 信号的保存,保存位置,保存方法,未决与递达的概念,信号阻塞,信号集及其操作,修改信号屏蔽字的方法,查看pending位图的方法,多次发送同一信号的现象。

  • 信号处理,信号递达。


☀️结语

🌹🌹 信号阻塞 & 信号捕捉 & 信号的保存 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

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

相关文章

板子接线图

1.ST-LINK V2接线 2.对抗板子刷蓝牙固件 接USB转TTL&#xff0c;用镊子短接两个孔 2.对抗板子用串口测试蓝牙AT命令 短接白色箭头&#xff0c;接TX&#xff0c;RX&#xff0c;电源

【已解决】:该该虚拟机似乎正在使用中。如果该虚拟机未在使用,请按“获取所有权(T)“按钮获取它的所有权。否则,请按“取消(C)“按钮以防损坏。

现象 启动VMware虚拟机&#xff0c;无法正常打开并出现以下信息&#xff1a; 原因 这是因为当运行一个“虚拟系统”时&#xff0c;为防止该系统被另外一个VMware程序打开&#xff0c;导致数据被修改或损坏&#xff0c;VMware会自动在该“虚拟系统”所在的文件夹下&#xff0c…

微信小程序 工具使用(HBuilderX)

微信小程序 工具使用:HBuilderX 一 HBuilderX 的下载二 工具的配置2.1 工具 --> 设置 --> 运行配置2.1.1 微信开发者工具路径2.1.2 node 运行配置 2.2 插件 工具 --> 插件安装2.2.1 下载插件 三 微信小程序端四 同步运行五 BUG5.1 nodemon在终端无法识别 一 HBuilderX…

前端面试题: 请解释什么是函数的作用域?

今天做到了一道题&#xff1a;请解释什么是函数的作用域&#xff1f; 我给的答案是&#xff1a; 函数的作用域是指函数执行到内部后创建的数据空间&#xff0c;在函数的作用域内&#xff0c;let定义的变量的有效期为函数作用域 AI觉得我答得比较简单&#xff1a;回答基本正确&…

使用 OpenWRT 设置 Banana Pi BPI-R3开源硬件路由器

这里介绍如何使用 pfSense 将ZimaBoard 216设置为防火墙的链接。我们正在为小型板计算机建立一个专用网络&#xff0c;这样当我开始教授 Linux 时&#xff0c;我就可以显示和共享它的 IP。 现在为我的网络设置一条到 pfSense 后面的路由。这是为了提供另一个级别的安全性。我有…

Ubuntu22.04 vnc远程黑屏

一、原因 原因是Ubuntu22.04使用的gnome启用了Wayland。vnc、teamviewer、向日葵、todesk等均无法使用或者远程黑屏等。 简单的说vnc、teamviewer、向日葵、todesk等均基于xorg实现&#xff08;xorg太流行&#xff09;&#xff0c;并不兼容Wayland&#xff0c;所以vnc无法正常…

权限提升Linux篇

提权工具 https://github.com/liamg/traitor https://github.com/AlessandroZ/BeRoot https://github.com/rebootuser/LinEnum https://github.com/mzet-/linux-exploit-suggester https://github.com/sleventyeleven/linuxprivchecker https://github.com/jondonas/linux…

DirectX12架构理解

无它&#xff0c;一图而已

Python - FastAPI 实现 get、post 请求

目录 一.引言 二.FastAPI Server 构建 1.get - read_items 2.post - create_item 3.uvicorn - run_app 三.Postman 请求 1.post - create_item 2.get - read_items 四.Requests 请求 1.post - create_item 2.get - read_items 五.总结 一.引言 前面介绍了 LLM 的相…

前端解决页面访问总是自动弹出 浏览器的 翻译此页 问题

今天克隆了一个项目下来 结果 浏览器自带的 翻译此页总会默认弹出 还一直以为是js设置的 结果最后才发现 这是个基础设置 在于 html标签的 lang 属性 这里设置了 en 他就会每次启动都弹出来 只需要改成langzh-CN 再次运行 就不会在弹出来了

恒合仓库 - 商品管理模块、上传照片、添加采购单、添加出库单、商品分类

商品管理模块 文章目录 商品管理模块一、分页查询商品1.1 实体类1.1.1 Store 仓库1.1.2 Brand 品牌1.1.3 ProductType 商品分类1.1.4 Supply 供应商1.1.5 Place 产地1.1.6 Unit 单位1.1.7 Product 商品 1.2 查询所有仓库1.2.1 Mapper1.2.2 Service1.2.3 Controller1.2.4 效果图…

基于宏基因组的功能挖掘:碳水化合物活性酶(CAZymes)注释

基于宏基因组数据&#xff0c;可以通过NR数据库进行物种注释&#xff0c;还可以进行功能挖掘&#xff0c;比如通过KEGG数据库来挖掘代谢通路、根据COG注释结果对蛋白进行功能归类&#xff0c;通过CAzyme可以得到碳化合物合成、代谢、转运等酶的分类和相关信息&#xff0c;通过P…

Echarts散点图筛选新玩法dataZoom

目录 前言 一、引入Echarts5.4.3 二、新建index.html 三、绑定Echarts展示元素 四、初始数据绑定 五、option设置 六、效果展示 七、参数说明 总结 前言 如果您在日常的工作当中也会遇到如下场景&#xff0c;需要在线对已经展示出来的图表进行进一步的筛选&#xff0c…

ASL集睿致远代理商,CS5366芯片,typec转HDMI 4k60Hz带PD快充方案

CS5366是一款Type-C/DP1.4到HDMI2.0的显示协议转换芯片&#xff0c;内部集成了PD3.0及DSC decoder, 并能按客户需求配置成不同的功能组合&#xff0c; 是目前集成度与功耗最小的一颗芯片。 CS536x主要特性 1. 业界最低功耗的Type-C to HDMI2.0 4K60产品 (<300mW)。 2. 集成D…

R读写parquet文件

什么是parquet文件 Apache Parquet是一个开源的&#xff0c;列存储的数据文件格式。 https://parquet.apache.org/ 在R里面&#xff0c;我们可以通过arrow包来读写它。 我们先安装一下arrow包&#xff0c;并加载它。 install.packages("arrow") library(arrow)读写…

[CISCN2019 华东南赛区]Web11 SSTI

这道SSTI 差点给我渗透的感觉了 全是API 我还想去访问API看看 发现这里读取了我们的ip 我们抓包看看是如何做到的 没有东西 我们看看还有什么提示 欸 那我们可不可以直接修改参数呢 我们传递看看 发现成功了 是受控的 这里我就开始没有思路了 于是看了wp 说是ssti 那我们看…

buuctf-[WUSTCTF2020]朴实无华

打开环境就这么一句话 先打开index.php,但是没有什么 查看了下网络 看到gzip和php 我试了试www.zip 还有index.phps&#xff0c;也是一样的&#xff0c;都没找到文件 于是我想到用御剑扫&#xff0c;但是我好像线程太长了&#xff0c;一个没扫到&#xff0c;我就想到用dirsea…

阿里云ACE认证的含金量高吗?如何通过ACE认证考试?

在当今的社会中&#xff0c;想要获得一份好工作、得到丰厚的报酬&#xff0c;唯一的方法就是证明自己优秀&#xff0c;能给公司创造价值&#xff0c;是大多数公司想要看到的。 那么在面试过程中&#xff0c;怎么样才能让面试官一眼就记住呢&#xff1f;那一定是有一份足够优秀…

c语言基础知识+OS+数据结构

c语言&#xff1a; memory section&#xff1a; .bss .data .text C语言编译流程&#xff1a; pre-compiler: compiler: 检查语法问题 link: 将symbol转化为实际函数/变量地址&#xff0c;map file里面可以看到 预编译在做什么&#xff1a; &#xff08;#define&#x…

Unity3D C# 反射与特性的配合使用

需求分析 情况&#xff1a; 假如我们是一个动物园的管理员&#xff0c;我们需要统计园内的所有动物和动物的行为。 举例&#xff1a; 现在园区内有猫、狗和鸡。猫对应的行为是喵喵喵和卖萌&#xff0c;狗对应狗吠和干饭&#xff0c;鸡对应篮球和打鸣那么这时候我要统计这些&a…