【Linux进程】进程信号(信号的保存与处理)

目录

前言

1. 信号的默认行为

2. 信号的保存

信号集操作函数

sigprocmask 

sigpending 

 3. 信号的处理

 信号的处理过程

思考

4. sigaction

5. SIGCHLD信号

6. 可重入函数

7. volatile

总结


前言

        上文介绍了信号,以及信号的产生,本文继续来聊一聊信号,信号的保存与处理;

在这里插入图片描述

1. 信号的默认行为

使用命令查看:

man 7 signal

 可以查看每种信号默认的行为是什么;但是在使用的时候,好像没发现什么区别啊?

出现异常就直接终止,Core、Term、Ign、Cont又是什么?

Term 信号会让进程有机会在收到信号后进行清理工作,保存数据等操作,然后正常退出;

 Core又是什么?

先来看一个简单示例:信号的详细介绍中,除零错误就是一种Core操作的信号;

        Core 会让进程退出时生成一个以进程 core.pid 的文件在进程运行的目录下,里边会将进程异常退出时核心的进程上下文数据记录下来,转储到磁盘中;

但是进程出现除零错误时也没有core文件? ulimit -a 查看当前shell会话的资源限制;

 云服务器默认的大小都是0;

core file大小限制设置为0会导致系统禁止生成core转储文件;设置core文件大小:

ulimit -c【大小】
# 比如:
ulimit -c unlimited # 没有大小限制
ulimit -c 5000 # 限制大小为5KB

注意:设置之后仅限于当前shell会话;

        再次运行一下除零错误的程序,查看就会有core文件了,观察会发现,core文件相较于测试代码来说还是比较大的;我在Ubuntu系统下测试了一下发现没有,centos 7是有的;

如果想要在Ubuntu系统下也可以测试,可以进行以下操作暂时修改:

执行以下命令以查看当前的核心转储设置:

cat /proc/sys/kernel/core_pattern

 我的输出结果:

|/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E
# 核心转储文件的生成已经被配置为通过 apport 程序进行处理;
# apport 是 Ubuntu 中的一个错误报告工具,它负责捕获崩溃的信息并生成报告,而不是直接生成核心文件

临时修改核心模式

echo "core" | sudo tee /proc/sys/kernel/core_pattern

 解除限制:

ulimit -c unlimited

 测试完恢复默认配置(注意结合实际情况进行修改):

echo "|/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E" | sudo tee /proc/sys/kernel/core_pattern

每次运行出现问题都产生一个core文件;那也是一个较大的存储开销;如果在服务器中,一直运行这样的程序,就会导致磁盘被打满,进而导致计算机挂掉;所以在服务器中一般是不允许生成core文件,core文件可以帮助开发人员分析问题并找出导致进程崩溃的原因;

如何使用core(核心转储)文件?

  1. 编译时添加-g选项,用于debug
  2. 通过gdb打开可执行程序
  3. 命令行输入“core file”

Stop:默认行为是暂停(停止)进程。

Cont:默认行为是继续执行当前已暂停的进程。

Ign:默认行为是忽略该信号。

2. 信号的保存

在此之前先来了解一下,信号处理过程中的状态;

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

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

进程可以选择阻塞(Block )某个信号;未决之后,暂时不递达,直到解除对信号的阻塞;

阻塞是已经收到信号,但一直不处理,让它保持未决状态,是根本就没有处理这个信号;直到解除阻塞;忽略就是一种信号的处理方式(递达);

有三种处理方式:

  1. 信号的忽略
  2. 信号的默认操作
  3. 信号的自定义捕捉

没有收到信号时,可以设置对信号的处理动作吗?当然可以使用signal就是设置信号处理方式
没有收到信号,也可以设置对信号是否阻塞/解除阻塞;

信号的保存与处理机制:

         常规信号在递达之前产生多次只计数一次,而实时信号在递达之前产生多次可以依次放在一个队列里;

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

 内部其实就是一个数组;

信号集操作函数

int sigemptyset(sigset t *set);
// 功能:对指定的信号集进行清空,对比特位进行清零

int sigfillset(sigset t *set);
// 功能:对指定信号集位图全部置 1

int sigaddset (sigset t *set, int signo);
// 功能:把指定信号添加到指定信号集中

int sigdelset(sigset t *set, int signo);
// 功能:删除信号集中指定信号(把指定定位置的bit位置为0)

int sigismember (const sigset t *set, int signo))
// 功能:判断一个信号是否在一个信号集中,包含返回1,不包含返回0,出错返回-1

         使用sigset_t类型的变量之前,一定要调 用sigemptyset 或 sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t 变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信;

这些接口有什么用?可以让一个进程批量的设置对信号的处理;主要用于设置进程对信号的屏蔽机制。这些函数允许你指定哪些信号可以被接收和处理,哪些信号应当被屏蔽;

sigprocmask 

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

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

         如果 oset 是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字;如果 oset 和set 都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。

how的参数:

假设信号集为mask;

how的这么多操作,都是对阻塞位图的修改,如果想要恢复到原位图怎么办?
oset是一个输出型参数,它返回的就是原位图的数据;

实验示例:

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

int main() {  
    sigset_t newset, oldset;  
    
    // 初始化新的信号集合  
    sigemptyset(&newset);  
    sigaddset(&newset, SIGINT);  // 添加 SIGINT 到集合  
    sigaddset(&newset, SIGTERM); // 添加 SIGTERM 到集合  

    // 尝试同时添加和移除信号  
    if (sigprocmask(SIG_BLOCK, &newset, &oldset) == -1) {  
        perror("Failed to block signals");  
    }  
    
    printf("SIGINT and SIGTERM are now blocked. Press Ctrl+C to try...\n");  
    
    // 暂停进程,等待信号  
    sleep(10);  

    // 恢复之前的信号屏蔽字  
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {  
        perror("Failed to restore signals");  
    }  
    
    printf("Signals are unblocked now.\n");  
    
    // 继续运行  
    sleep(5);  
    
    return 0;  
}

sigpending 

#include <signal.h>

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

 3. 信号的处理

 信号在合适的时候被处理——什么时候是合适的时候?

进程从内核态返回到用户态的时候,会进行信号的检查和处理;

        用户态是一种受控状态,能访问的资源有限,内核态是OS的工作状态,能访问大部分系统资源系统调用就包含身份的变换,用户调用系统调用接口时,OS就会从用户态转变为内核态,执行结束后,把内核态在转变为用户态并返回;

如果进程想要访问0S的代码怎么快速访问?

        前边介绍时都是用户空间,还有一部分内核空间没有介绍;OS空间也有一张页表,系统级页表,用于映射OS在物理内存中的代码和数据;进程想要调用系统调用时,只需使用虚拟地址访问内核空间就可以了;

 每个进程使用的OS代码和数据都是一样的,在OS中只需要一份内核页表就够了;

在该体系中,身份又是如何转变的呢?

在CPU中存在一个叫CS的寄存器,寄存器中有两个bit位,主要使用它的两个数字:

  • 1.表示内核、
  • 3.表示用户

         当进程调用系统调用时,是在进程地址空间内进行跳转,默认情况下,是不允许访问内
核空间;调用系统调用时,OS内部需要将CS中的两个bit位由3改为 1;这两个bit位进程是不可以随意修改的,进程调用系统调用时,系统调用会自动将这两个bit位进行修改,调用结束时会进行还原;这样设计,进程所有的代码执行,都可以在自己的地址空间内通过跳转的方式,进行调用和返回;就和之前的库函数调用一样;

CPU中还存在着其他的寄存器比如:CR1、CR3
CR3保存的就是页表的信息,它存储的是页表的物理地址,进程访问一个虚拟地址时,数据可能并没有在内存中,没有虚拟地址的映射,就会引发缺页中断,OS会把需要访问的数据加载到内存,然后建立映射,此时需要找到缺页中断的的虚拟地址,而CR1存储的就是这个虚拟地址;

 信号的处理过程

 使用signal方法捕捉信号,去执行自定义的方法,执行这个方法时是以用户态还是内核态执行?
        用户态,自定义的信号处理方法可能存在非法的操作,为了保证系统安全,在执行时会转换成用户态执行;执行完之后需要返回到内核态;系统调用执行完后继续返回到用户态;

思考

我们平时写的代码不一定就访问内核资源,比如写了一个死循环,不需要访问内核资源
发生Ctrl +C时进程怎么检查处理信号?
        进程在执行时都需要被CPU轮转式调度,进程被调度时时间片到了OS就会把进程从CPU中剥离下来,放到过期队列中,下次被CPU调度时OS需要把进程的上下文数据重新放入到CPU寄存器当中,把数据恢复,让CPU继续执行后续操作;在这个过程中OS需要不停的从内核态转换到用户态去执行进程代码;所以即使不使用系统调用,也会进行多次的状态切换,也就会有很多次机会进行的信号捕捉处理;

4. sigaction

 sigaction函数可以读取和修改与指定信号相关联的处理动作;

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

        调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体;

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

 sa_handler:

        将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

sa_mask:在信号处理期间需要被阻塞的信号集合;当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止;

sa_flags字段包含一些选项,本文的代码都把sa_flags设为0;

sa_sigaction是实时信号的处理函数,这里不进行操作,感兴趣可以去研究一下;

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

void Print(const sigset_t& pending)  
{  
    for (int signo = 31; signo > 0; signo--) // 从 31 循环到 1  
    {  
        if (sigismember(&pending, signo)) // 判断信号 signo 是否存在于信号集中  
        {  
            std::cout << "1"; // 如果存在,打印 1  
        }  
        else  
        {  
            std::cout << "0"; // 如果不存在,打印 0  
        }  
    }  
    std::cout << std::endl; // 最后换行  
}

void handler(int signo)  
{  
    std::cout << "get a sig: " << signo << std::endl;  
    while(1)  
    {  
        sigset_t pending; // 创建一个信号集,用于存储未决信号  
        sigpending(&pending); // 获取当前进程未决信号集合  
        Print(pending); // 假设这是一个函数,用于打印未决信号  
        sleep(1); // 每秒钟打印一次  
    }  
}  

int main()  
{  
    std::cout << "pid: " << getpid() << std::endl;  
    struct sigaction act, oact;  

    // 配置信号处理结构  
    act.sa_handler = handler; // 设置处理函数  
    sigemptyset(&act.sa_mask); // 初始化信号集,清空  
    sigaddset(&act.sa_mask, 3); // 将信号 3 (SIGQUIT) 加入阻塞信号集  
    sigaction(2, &act, &oact); // 将处理动作设置为对 SIGINT 的处理  

    while(1) sleep(1); // 持续运行  
    return 0;  
}

 

         捕获2号信号,然后打印该进程的未决信号集,现象是收到一次2号信号后,后续再收到就会被阻塞;(终止进程可以使用kill命令,在新终端中执行)2号信号正在被处理,但是没有屏蔽其他信号,接收到其他信号时,仍然会处理;不想处理其他信号怎么办?这时候就可以使用sa_mask;

示例中我也添加了3号信号,ctrl + \发送3号信号;

5. SIGCHLD信号

         子进程在退出时需要给父进程发送信号(SIGCHLD);他的默认处理行为是:终止父进程后回收子进程;

验证程序:

void handler(int signo) {  
    std::cout << "signo: " << signo << std::endl;  
}

int main() {  
    signal(SIGCHLD, handler); // 设置 SIGCHLD 信号的处理程序  
    pid_t id = fork(); // 创建子进程  
    if (id == 0) {  
        std::cout << "child is running" << std::endl;  
        sleep(5); // 子进程休眠5秒钟  
        exit(10); // 子进程正常退出,返回状态10  
    }  

    // 主循环,每秒打印一次计数  
    int cnt = 10;  
    while (cnt) {  
        std::cout << "cnt: " << cnt << std::endl;  
        sleep(1); // 主进程休眠1秒,计数减少  
        cnt--;  
    }  
    wait(NULL); // 父进程等待子进程结束  

    return 0;  
}

 

前5秒子进程正常运行,5秒后退出子进程变僵尸进程;父进程会收到17号信号;

         也可以利用子进程退出给父进程发送信号的方法来进行子进程的回收,在父进程不退出的情况下;

#include <iostream>  
#include <csignal>  
#include <sys/wait.h>  
#include <unistd.h>  

void handler(int signo) {  
    std::cout << "signo: " << signo << std::endl;  
    waitpid(-1, nullptr, 0); // 阻塞等待任意一个进程结束  
}  

int main() {  
    signal(SIGCHLD, handler); // 设置 SIGCHLD 信号的处理函数  
    pid_t id = fork(); // 创建一个子进程  
    if (id == 0) {  
        std::cout << "child is running" << std::endl;  
        sleep(5); // 子进程休眠5秒  
        exit(10); // 子进程正常退出  
    }  
    while (true) sleep(1); // 父进程持续运行  
    return 0;  
}

问题:如果父进程有对应的多个子进程怎么回收?前边提到过,在信号处理时如果同时收到多个相同的信号时,pending表就只会记录一次;那么子进程回收就只会回收一次喽,剩余进程不就会变成僵尸进程吗?如何设计解决?

其实很简单,一直循环等待即可:

void handler(int signo)  
{  
   std::cout << "signo: " << signo << std::endl;  
   pid_t id = 0;  
   while((id = waitpid(-1, nullptr, 0)))  //阻塞等待
   {  
    if(id < 0) break;  
        std::cout << "回收进程:" << id << std::endl;  
   }  
}
int main()  
{  
    signal(SIGCHLD, handler);  
    for (int i = 0; i < 10; i++)  
    {  
        pid_t id = fork();  
        if (id == 0)  
        {  
            std::cout << "child is running" << std::endl;  
            sleep(5);  
            exit(10);  
        }  
    }  

    while (true) sleep(1);  
    return 0;  
}

         这样设计依然存在缺陷,比如:10个进程退出了8个,还有2个不退出,waitpid 在 while 循环中执行到第9次时依然会等待未退出的子进程,这里设置的是阻塞等待,那父进程就会一直阻塞等待子进程;还可以继续优化一下:

// 非阻塞等待
void handler(int signo)  
{  
   std::cout << "signo: " << signo << std::endl;  
   pid_t id = 0;  
   while ((id = waitpid(-1, nullptr, WNOHANG)) > 0)
   {  
    if(id < 0) break;  
        std::cout << "回收进程:" << id << std::endl;  
   }  
}

 避免产生僵尸进程的方式有很多,Linux支持手动忽略SIGCHLD,所有的子进程都不需要父进程进行等待了!!退出自动回收就不会产生僵尸进程了,同时,也不会通知父进程;

手动忽略,就可以自动回收子进程,那还要信号处理等待回收干嘛?

        等待不仅仅是回收僵尸的子进程,还要获取子进程的退出信息;获取子进程退出信息时,就可以使用信号处理时等待回收;

对SIGCHILD设置忽略,此方法对于Linux可用,但不保证在其它UNIX系统上都可用;

6. 可重入函数

 

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。

         像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱。像这样的函数称为 不可重入函数;反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

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

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

7. volatile

volatile修饰变量,每次使用被修饰的变量都需要到内存中去拿;

volatile int flag = 0;
void handler(int signo)  
{  
    std::cout << "signo: " << signo << std::endl;  
    flag = 1;  
    std::cout << "change flag to: " << flag << std::endl;  
}
int main()  
{  
    signal(2, handler);  // 设定对SIGINT信号的处理程序  
    std::cout << "getpid: " << getpid() << std::endl; // 打印当前进程ID  
    while(!flag);  // 循环等待,直到flag被设置为1  
    std::cout << "quit normal!" << std::endl;  
}

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

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

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


总结

        以上便是本文的全部内容,希望对你有所帮助或启发,感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读 - 《Large Language Models Are Zero-Shot Time Series Forecasters》

Abstract 通过将时间序列编码为数字组成的字符串&#xff0c;我们可以将时间序列预测当做文本中下一个 token预测的框架。通过开发这种方法&#xff0c;我们发现像GPT-3和LLaMA-2这样的大语言模型在下游任务上可以有零样本时间序列外推能力上持平或者超过专门设计的时间序列训…

Llama系列关键知识总结

系列文章目录 第一章&#xff1a;LoRA微调系列笔记 第二章&#xff1a;Llama系列关键知识总结 文章目录 系列文章目录Llama: Open and Efficient Foundation Language Models关键要点LLaMa模型架构&#xff1a;Llama2分组查询注意力 (GQA) Llama3关键信息 引用&#xff1a; Ll…

项目实践-贪吃蛇小游戏

目录 声明 1、前言 2、实现目标 3、技术要点 4、Win32API介绍 4.1、Win32API 4.2、控制台程序 4.3、控制台屏幕上的坐标COORD 4.4、GetStdHandle 4.5、GetConsoleCursorInfo 4.6、SetConsoleCursorInfo 4.7、SetConsoleCursorPosition 4.8、GetAsyncKeyState 5、…

Java编程题_面向对象和常用API01_B级

Java编程题_面向对象和常用API01_B级 第1题 面向对象、异常、集合、IO 题干: 请编写程序&#xff0c;完成键盘录入学生信息&#xff0c;并计算总分将学生信息与总分一同写入文本文件 需求&#xff1a;键盘录入3个学生信息(姓名,语文成绩,数学成绩) 求出每个学生的总分 ,并…

Jmeter自学【8】- 使用JMeter模拟设备通过MQTT发送数据

今天使用jmeter推送数据到MQTT&#xff0c;给大家分享一下操作流程。 一、安装JMeter 参考文档&#xff1a;Jmeter自学【1】- Jmeter安装、配置 二、安装MQTT插件 1、下载插件 我的Jmeter版本是5.6.3&#xff0c;用到的插件是&#xff1a;mqtt-xmeter-2.0.2-jar-with-depe…

Uniapp跨域请求

1.什么是跨域 是指当一个请求的URL的协议、域名或端口与当前页面的URL不同时&#xff0c;该请求被视为跨域请求。跨域是一种安全策略&#xff0c;用于限制一个域的网页如何与另一个域的资源进行交互。就比如我们进行前端向后端进行发送请求的时候&#xff0c;如果是开发前后端…

基于Resnet、LSTM、Shufflenet及CNN网络的Daily_and_Sports_Activities数据集仿真

在深度学习领域&#xff0c;不同的网络结构设计用于解决特定的问题。本文将详细分析四种主流网络结构&#xff1a;卷积神经网络&#xff08;CNN&#xff09;、残差网络&#xff08;ResNet&#xff09;、长短期记忆网络&#xff08;LSTM&#xff09;和洗牌网络&#xff08;Shuff…

算法进阶:贪心算法

贪心算法是一种简单而直观的算法思想&#xff0c;它在每一步选择中都采取在当前状态下最优的选择&#xff0c;以期望最终得到全局最优解。贪心算法通常适用于一些具有最优子结构的问题&#xff0c;即问题的最优解可以通过一系列局部最优解的选择得到。 贪心算法的基本思路是&a…

139.《python中的正则详解》

文章目录 什么是正则正则表达式语法正则demo1.匹配模式2.finditer3.正则分组4.非捕获组5.分组的引用6. 正则替换7.正则切割7.正则「或」7.枚举取反 面试题 前言: 拉开差距的不是上班的8小时,而是下班后的16小时,同志们,加油,卷起!!! 什么是正则 1.正则表达式是一种高级文本处理…

从安全角度看 SEH 和 VEH

从安全角度看 SEH 和 VEH 异常处理程序是处理程序中不可预见的错误的基本方法之一 https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/ SEH——结构化异常处理程序 就其工作方式而言&#xff0c;异常处理程序与其他处理程序相比相当基础&#xff0…

ESP-IDF学习记录(3)ESP-IDF组件管理

既然官方把这个组件管理按钮放置的这么明显&#xff0c;就一定有他的用心良苦&#xff0c;今天学习一下这个组件管理。 Componments manager 1.给当前项目安装组件 IDF Component Manager and ESP Component Registry Documentation — IDF Component Management documenta…

华为麦芒5(安卓6)termux记录 使用ddns-go,alist

下载0.119bate1 安卓5和6版本,不能换源,其他源似乎都用不了,如果root可以直接用面具模块 https://github.com/termux/termux-app/releases/download/v0.119.0-beta.1/termux-app_v0.119.0-beta.1apt-android-5-github-debug_arm64-v8a.apk 安装ssh(非必要) pkg install open…

园区网综合拓扑实验

一、实验要求 实验拓扑图如上图所示 1、按照图示的VLAN及IP地址需求&#xff0c;完成相关配置 2、要求SW1为VLAN 2/3的主根及主网关 SW2为vlan 20/30的主根及主网关 SW1和SW2互为备份 3、可以使用super vlan&#xff08;本实验未使用&#xff09; 4、上层…

JSON 系列之4:JSON_VALUE

JSON_VALUE的作用&#xff0c;简单来说&#xff0c;就是从JSON到SQL&#xff1a; SQL/JSON function JSON_VALUE selects JSON data and returns a SQL scalar or an instance of a user-defined SQL object type or SQL collection type (varray, nested table) 所以&#xff…

设置首选网络类型以及调用Android框架层的隐藏API

在Android SDK中提供的framework.jar是阉割版本的&#xff0c;比如有些类标记为hide&#xff0c;这些类不会被打包到这个jar中&#xff0c;而有些只是类中的某个方法或或属性被标记为hide&#xff0c;则这些类或属性会被打包到framework.jar&#xff0c;但是我们无法调用&#…

Mac 12.1安装tiger-vnc问题-routines:CRYPTO_internal:bad key length

背景&#xff1a;因为某些原因需要从本地mac连接远程linxu桌面查看一些内容&#xff0c;必须使用桌面查看&#xff0c;所以ssh无法满足&#xff0c;所以决定安装vnc客户端。 问题&#xff1a; 在mac上通过 brew install tiger-vnc命令安装, 但是报错如下&#xff1a; > D…

【Java-tesseract】OCR图片文本识别

文章目录 一、需求二、概述三、部署安装四、技术细节五、总结 一、需求 场景需求:是对识别常见的PNG,JPEG,TIFF,GIF图片识别&#xff0c;环境为离线内网。组件要求开源免费&#xff0c;并且可以集成Java生成接口服务。 二、概述 我不做选型对比了,我筛选测试了下Tesseract(v…

PCIe和DMA:数据传输的“双子星“

简单来说&#xff0c;PCIe是一种硬件总线标准&#xff0c;就像高速公路&#xff1b;DMA是一种数据传输机制&#xff0c;就像在高速公路上行驶的卡车。所以这两个是两种不同的概念。 理解PCIe传输 PCIe&#xff08;PCI Express&#xff09;是一种硬件接口规范&#xff0c;定义…

VS Code中怎样查看某分支的提交历史记录

VsCode中无法直接查看某分支的提交记录&#xff0c;需借助插件才行&#xff0c;常见的插件如果git history只能查看某页面的改动记录&#xff0c;无法查看某分支的整体提交记录&#xff0c;我们可以安装GIT Graph插件来解决这个问题 1.在 VSCode的插件库中搜索 GIT Graph安装&a…

第三方接口设计注意要点

实际工作中&#xff0c;我们会遇到与三方系统对接的情形&#xff0c;比如对接短信服务、支付服务、地图服务、以及一些外部业务系统的调用和回调等等&#xff0c;不论是我们调用第三方接口还是我们为其他系统提供接口服务&#xff0c;调用过程中会遇到一些大大小小的问题和吐槽…