当前位置: 首页 > news >正文

Linux进程信号处理(26)

文章目录

  • 前言
  • 一、信号的处理时机
    • 处理情况
    • “合适”的时机
  • 二、用户态与内核态
    • 概念
    • 重谈进程地址空间
    • 信号的处理过程
  • 三、信号的捕捉
    • 内核如何实现信号的捕捉?
    • sigaction
  • 四、信号部分小结
  • 五、可重入函数
  • 六、volatile
  • 七、SIGCHLD 信号
  • 总结


前言

  这篇就是我们关于信号的最后一篇啦!
  马上就要到线程喽,进一步说,我们的Linux系统编程就要结束喽!

从信号产生到信号保存,中间经历了很多,当操作系统准备对信号进行处理时,还需要判断时机是否 “合适”,在绝大多数情况下,只有在 “合适” 的时机才能处理信号


一、信号的处理时机

处理情况

普通情况

  所谓的普通情况就是指 信号没有被阻塞,直接产生,记录未决信息后,再进行处理
  在这种情况下,信号是不会被立即递达的,也就无法立即处理,需要等待合适的时机

特殊情况

  当信号被 阻塞 后,信号 产生 时,记录未决信息,此时信号被阻塞了,也不会进行处理
  当阻塞解除后,信号会被立即递达,此时信号会被立即处理

  特殊情况 很好理解,就好比往气球里吹气,当气球炸了,空气会被立即释放,因为空气是被气球 阻塞 的,当气球炸了之后(阻塞解除),空气立马往外跑,这不就是 立即递达、立即处理 吗?

普通情况 就有点难搞了,它需要等待 “合适” 的时机,才能被 递达,继而被 处理

“合适”的时机

  信号的产生是 异步 的

  也就是说,信号可能随时产生,当信号产生时,进程可能在处理更重要的事,此时贸然处理信号显然不够明智

比如进程正在执行一个重要的 IO,突然一个终止信号发出,IO 立即终止,对进程、磁盘都不好

  因此信号在 产生 后,需要等进程将 更重要 的事忙完后(合适的时机),才进行 处理
  合适的时机:进程从 内核态 返回 用户态 时,会在操作系统的指导下,对信号进行检测及处理
  至于处理动作,分为:默认动作、忽略、用户自定义
  搞清楚 “合适” 的时机 后,接下来我们需要来学习 用户态 和 内核态 相关知识

二、用户态与内核态

  对于 用户态、内核态 的理解及引出的 进程地址空间 和 信号处理过程 相关知识是本文的重难点

概念

先来看看什么是 用户态 和 内核态

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

自己写的代码被执行很好理解,操作系统的代码是什么?
  操作系统也是由大量代码构成的
  在对进程进行调度、执行系统调用、异常、中断、陷阱等,都需要借助操作系统之手
  此时执行的就是操作系统的代码

也就是说,用户态 与 内核态 是两种不同的状态,必然存在相互转换的情况

注意: 当你访问用户空间时你必须处于用户态,当你访问内核空间时你必须处于内核态。

用户态 切换为 内核态

  • 当进程时间片到了之后,进行进程切换动作
  • 调用系统调用接口,比如 open、close、read、write 等
  • 产生异常、中断、陷阱等

内核态 切换为 用户态

  • 进程切换完毕后,运行相应的进程
  • 系统调用结束后
  • 异常、中断、陷阱等处理完毕

  信号的处理时机就是 内核态 切换为 用户态,也就是 当把更重要的事做完后,进程才会在操作系统的指导下,对信号进行检测、处理

重谈进程地址空间

  • 进程地址空间 是虚拟的,依靠 页表 + MMU机制真实的地址空间 建立映射关系
  • 每个进程都有自己的 进程地址空间,不同 进程地址空间 中地址可能冲突,但实际上地址是独立的
  • 进程地址空间 可以让进程以统一的视角看待自己的代码和数据

在这里插入图片描述
  感兴趣的可以回看我这篇文章《Linux进程地址空间》

  不难发现,在 进程地址空间 中,存在 1 GB 的 内核空间,每个进程都有,而这 1 GB 的空间中存储的就是 操作系统 相关代码 和 数据,并且这块区域采用 内核级页表 与 真实地址空间 进行映射

我们不禁思考,为什么要这样子区分 用户态 和 内核态?

  • 内核空间中存储的可是操作系统的代码和数据,权限非常高,绝不允许随便一个进程对其造成影响
  • 区域的合理划分也是为了更好的进行管理

在这里插入图片描述
  所谓的 执行操作系统的代码及系统调用,就是在使用这 1 GB 的内核空间

进程间具有独立性,比如存在用户空间中的代码和数据是不同的,难道多个进程需要存储多份操作系统的代码和数据吗?

  • 当然不用,内核空间比较特殊,所有进程最终映射的都是同一块区域,也就是说,进程只是将 操作系统代码和数据 映射入自己的 进程地址空间 而已
  • 而 内核级页表 不同于 用户级页表,专注于对 操作系统代码和数据 进行映射,是很特殊的

  当我们执行诸如 open 这类的 系统调用 时,会跑到 内核空间 中调用对应的函数
  而 跑到内核空间 就是 用户态 切换为 内核态 了(用户空间切换至内核空间)

这个 跑到 是如何实现的呢?

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

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

通过一个 寄存器,表征当前所处的 状态,修改其中的 值,就可以表示不同的 状态,这是很聪明的做法

在这里插入图片描述
重谈 进程地址空间 后,得到以下结论

  1. 所有进程的用户空间 [0, 3] GB 是不一样的,并且每个进程都要有自己的 用户级页表 进行不同的映射
  2. 所有进程的内核空间 [3, 4] GB 是一样的,每个进程都可以看到同一张内核级页表,从而进行统一的映射,看到同一个 操作系统
  3. 操作系统运行 的本质其实就是在该进程的 内核空间内运行的(最终映射的都是同一块区域)
  4. 系统调用 的本质其实就是在调用库中对应的方法后,通过内核空间中的地址进行跳转调用

那么进程又是如何被调度的呢?

  1. 操作系统的本质
  • 操作系统也是软件,并且是一个死循环式等待指令的软件
  • 存在一个硬件:操作系统时钟硬件,每隔一段时间向操作系统发送时钟中断
  1. 进程被调度,就意味着它的时间片到了,操作系统会通过时钟中断,检测到是哪一个进程的时间片到了,然后通过系统调用函数 schedule() 保存进程的上下文数据,然后选择合适的进程去运行

信号的处理过程

  当在 内核态 完成某种任务后,需要切回 用户态,此时就可以对信号进行 检测 并 处理 了

情况1:信号被阻塞,信号产生 / 未产生

  信号都被阻塞了,也就不需要处理信号,此时不用管,直接切回 用户态 就行了

  下面的情况都是基于 信号未被阻塞 且 信号已产生 的前提

情况2:当前信号的执行动作为 默认

  大多数信号的默认执行动作都是 终止 进程,此时只需要把对应的进程干掉,然后切回 用户态 就行了

在这里插入图片描述

情况3:当前信号的执行动作为 忽略

  当信号执行动作为 忽略 时,不做出任何动作,直接返回 用户态

在这里插入图片描述

情况4:当前信号的执行动作为 用户自定义

  这种情况就比较麻烦了,用户自定义的动作位于 用户态 中,也就是说,需要先切回 用户态,把动作完成了,重新坠入 内核态,最后才能带着进程的上下文相关数据,返回 用户态

在 内核态 中,也可以直接执行 自定义动作,为什么还要切回 用户态 执行自定义动作?

  • 因为在 内核态 可以访问操作系统的代码和数据,自定义动作 可能干出危害操作系统的事
  • 在 用户态 中可以减少影响,并且可以做到溯源

为什么不在执行完 自定义动作 直接后返回进程?

  • 因为 自定义动作 和 待返回的进程 属于不同的堆栈,是无法返回的
  • 并且进程的上下文数据还在内核态中,所以需要先坠入内核态,才能正确返回用户态

在这里插入图片描述
  注意: 用户自定义的动作,需要先切换至 用户态 中执行,执行结束后,还需要坠入 内核态
  通过一张图快速记录信号的 处理 过程

在这里插入图片描述

三、信号的捕捉

  接下来谈谈 信号 是如何被 捕捉 的

内核如何实现信号的捕捉?

  如果信号的执行动作为 用户自定义动作,当信号 递达 时调用 用户自定义动作,这一动作称为 信号捕捉

  用户自定义动作 是位于 用户空间 中的

  当 内核态 中任务完成,准备返回 用户态 时,检测到信号 递达,并且此时为 用户自定义动作,需要先切入 用户态 ,完成 用户自定义动作 的执行;因为 用户自定义动作 和 待返回的函数 属于不同的 堆栈 空间,它们之间也不存在 调用与被调用 的关系,是两个 独立的执行流,需要先坠入 内核态 (通过 sigreturn() 坠入),再返回 用户态 (通过 sys_sigreturn() 返回)

上述过程可以总结为下图:

在这里插入图片描述

sigaction

  sigaction 也可以 用户自定义动作,比 signal 功能更丰富

在这里插入图片描述

#include <signal.h>int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);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 结构体信息

这个函数的主要看点是 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);	//实时信号相关,不用管
};

  其中部分字段不需要管,因为那些是与 实时信号 相关的,我们这里不讨论

  重点可以看看 sa_mask 字段

  sa_mask:当信号在执行 用户自定义动作 时,可以将部分信号进行屏蔽,直到 用户自定义动作 执行完成

  也就是说,我们可以提前设置一批 待阻塞 的 屏蔽信号集,当执行 signum 中的 用户自定义动作 时,这些 屏蔽信号集 中的 信号 将会被 屏蔽(避免干扰 用户自定义动作 的执行),直到 用户自定义动作 执行完成

可以简单用一下 sigaction 函数

#include <iostream>
#include <cassert>
#include <cstring>
#include <signal.h>
#include <unistd.h>using namespace std;static void DisplayPending(const sigset_t& pending)
{// 打印 pending 表cout << "当前进程的 pending 表为: ";int i = 1;while (i < 32){if (sigismember(&pending, i))cout << "1";elsecout << "0";i++;}cout << endl;
}static void handler(int signo)
{cout << signo << " 号信号确实递达了" << endl;// 最终不退出进程int n = 10;while (n--){// 获取进程的 未决信号集sigset_t pending;sigemptyset(&pending);int ret = sigpending(&pending);assert(ret == 0);(void)ret; // 欺骗编译器,避免 release 模式中出错DisplayPending(pending);sleep(1);}
}int main()
{cout << "当前进程: " << getpid() << endl;//使用 sigaction 函数struct sigaction act, oldact;//初始化结构体memset(&act, 0, sizeof(act));memset(&oldact, 0, sizeof(oldact));//初始化 自定义动作act.sa_handler = handler;//初始化 屏蔽信号集sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);//给 2号 信号注册自定义动作sigaction(2, &act, &oldact);// 死循环while (true);return 0;
}

在这里插入图片描述
  当 2 号信号的循环结束(10 秒),3、4、5 信号的 阻塞 状态解除,立即被 递达,进程就被干掉了

  注意: 屏蔽信号集 sa_mask 中已屏蔽的信号,在 用户自定义动作 执行完成后,会自动解除 阻塞 状态

四、信号部分小结

  截至目前,信号 处理的所有过程已经全部学习完毕了

  信号产生阶段:有四种产生方式,包括 键盘键入、系统调用、软件条件、硬件异常

  信号保存阶段:内核中存在三张表,blcok 表、pending 表以及 handler 表,信号在产生之后,存储在 pending 表中

  信号处理阶段:信号在 内核态 切换回 用户态 时,才会被处理

在这里插入图片描述

五、可重入函数

  可以被重复进入的函数称为 可重入函数

  比如单链表头插的场景中,节点 node1 还未完成插入时,node2 也进行了头插,最终导致 节点 node2 丢失,造成 内存泄漏

在这里插入图片描述
  导致 内存泄漏 的罪魁祸首:对于 node1 和 node2 来说,操作的 单链表 是同一个,同时进行并发访问(重入)会出现问题的,因为此时的 单链表 是临界资源

  我们学过的函数中,90% 都是 不可重入的

  函数是否可重入是一个特性,而非缺点,需要正确看待

不可重入的条件:

  • 调用了内存管理相关函数
  • 调用了标准 I/O 库函数,因为其中很多实现都以不可重入的方式使用数据结构

本质上还是因为 main函数 和 sighandler函数 使用不同的堆栈空间,它们之间不存在调用与被调用的关系,是两个独立的控制流程

六、volatile

  volatile 关键字可以避免 编译器 的优化,保证内存的 可见性

  比如我们现在来个例子,借助全局变量 flag 设计一个死循环的场景,在此之前将 2 号信号进行自定义动作捕捉,具体动作为:将 flag 改为 1,可以终止 main 函数中的循环体

#include <stdio.h>
#include <signal.h>int flag = 0;   // 一开始为假void handler(int signo)
{printf("%d号信号已经成功发出了\n", signo);flag = 1;
}int main()
{signal(2, handler);while(!flag);   // 故意不写 while 的代码块 { }printf("进程已退出\n");return 0;
}

在这里插入图片描述
  初步结果符合预期,2 号信号发出后,循环结束,程序正常退出

  这段代码能符合我们预期般的正确运行是因为 当前编译器默认的优化级别很低,没有出现意外情况

通过指令查询 gcc 优化级别的相关信息

man gcc
: /o1

在这里插入图片描述
  其中数字越大,优化级别越高,理论上编译出来的程序性能会更好

  事实真的如此吗?

  让我们重新编译上面的程序,并指定优化级别为 o1

编译成功后,再次运行程序

在这里插入图片描述
  此时得到了不一样的结果:2 号信号发出后,对于 flag 变量的修改似乎失效了

  将优化级别设为更高是一样的结果,如果设为 O0 则会符合预期般的运行,说明我们当前的编译器默认的优化级别是 O0

查看编译器的版本

gcc --version

在这里插入图片描述
  不同编译器版本的优化策略可能存在差异,以上是我的 gcc 编译环境

那么我们这段代码哪个地方被优化了呢?

  • 答案是 while 循环判断

首先要明白:

  • 对于程序中的数据,需要先被 load 到 CPU 中的 寄存器 中
  • 判断语句所需要的数据(比如 flag),在进行判断时,是从 寄存器 中拿取并判断
  • 根据判断的结果,判断代码的下一步该如何执行(通过 PC 指针指向具体的代码执行语句)

所以程序在优化级别为 O0 或更低时,是这样执行的:
在这里插入图片描述

  面对这种情况,我们就可以使用volatile关键字对flag变量进行修饰,告知编译器,对flag变量的任何操作都必须真实的在内存中进行,即保持了内存的可见性。

七、SIGCHLD 信号

  在 进程控制 学习时期,我们明白了一个事实:父进程必须等待子进程退出并回收,并为其 “收尸”,避免变成 “僵尸进程” 占用系统资源、造成内存泄漏

那么 父进程是如何知道子进程退出了呢?

  • 在之前的场景中,父进程要么就是设置为 阻塞式 专心等待,要么就是 设置为 WNOHANG 非阻塞式等待,这两种方法都需要 父进程 主动去检测 子进程 的状态

如今学习了 进程信号 相关知识后,可以思考一下:子进程真的是安安静静的退出的吗?

  • 答案当然不是,子进程在退出后,会给父进程发送 SIGCHLD 信号

  可以通过 SIGCHLD 信号 通知 父进程,子进程 要退出了,这样可以解放 父进程,不必再去 主动检测 ,而是 子进程 要退出的时候才通知其来 “收尸”

SIGCHLD 信号比较特殊,默认动作 SIG_DEF 是 什么都不做

通过自定义捕捉,打印相关信息

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void handler(int signo)
{printf("进程 %d 捕捉到了 %d 号信号\n", getpid(), signo);
}int main()
{signal(SIGCHLD, handler);pid_t id = fork();if(id == 0){int n = 5;while(n)printf("子进程剩余生存时间: %d秒 [pid: %d  ppid: %d]\n", n--, getpid(), getppid());// 子进程退出exit(-1);}waitpid(id, NULL, 0);return 0;
}

在这里插入图片描述
  因此可以证明 SIGCHLD 是被子进程真实发出的,当然,我们可以自定义捕捉动作为 回收子进程,让父进程不再主动检测子进程的状态,可以自己忙自己的事

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>pid_t id;   // 将子进程的id设为全局变量,方便对比void handler(int signo)
{printf("进程 %d 捕捉到了 %d 号信号\n", getpid(), signo);// 这里的 -1 表示父进程等待时,只要是已经退出了的子进程,都可以进行回收pid_t ret = waitpid(-1, NULL, 0);if(ret > 0)printf("父进程: %d 已经成功回收了 %d 号进程,之前的子进程是 %d\n", getpid(), ret, id);
}int main()
{signal(SIGCHLD, handler);id = fork();if(id == 0){int n = 5;while(n){printf("子进程剩余生存时间: %d秒 [pid: %d  ppid: %d]\n", n--, getpid(), getppid());sleep(1);}// 子进程退出exit(-1);}// 父进程很忙的话,可以去做自己的事while(1){// TODOprintf("父进程正在忙...\n");sleep(1);}return 0;
}

  父进程和子进程各忙各的,子进程退出后会发信号通知父进程,并且能做到正确回收

在这里插入图片描述
那么这种方法就一定对吗?

  • 答案是不一定,在只有一个子进程的场景中,这个代码没问题,但如果是涉及多个子进程回收时,这个代码就有问题了

  根本原因:SIGCHLD 也是一个信号啊,它可能也会在 block 表和 pending 表中被置为 1,当多个子进程同时向父进程发出信号时,父进程只能先回收最快发出信号的子进程,并将随后发出信号的子进程 SIGCHLD 信号保存在 blcok 表中,除此之外,其他的子进程信号就丢失了,父进程处理完这两个信号后,就认为没有信号需要处理了,这就造成了内存泄漏

  解决方案:自定义捕捉函数中,采取 while 循环式回收,有很多进程都需要回收没问题,排好队一个个来就好了,这样就可以确保多个子进程同时发出 SIGCHLD 信号时,可以做到一一回收

  细节:多个子进程运行时,可能有的退了,有的没退,这会导致退了的子进程发出信号后,触发自定义捕捉函数中的循环等待机制,回收完已经退出了的子进程后,会阻塞式的等待还没有退出的子进程,如果子进程一直不退,就会一直被阻塞,所以我们需要把进程回收设为 WNOHANG 非阻塞式等待

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void handler(int signo)
{printf("进程 %d 捕捉到了 %d 号信号\n", getpid(), signo);// 这里的 -1 表示父进程等待时,只要是已经退出了的子进程,都可以进行回收while (1){pid_t ret = waitpid(-1, NULL, WNOHANG);if (ret > 0)printf("父进程: %d 已经成功回收了 %d 号进程\n", getpid(), ret);elsebreak;}printf("子进程回收成功\n");
}int main()
{signal(SIGCHLD, handler);// 创建10个子进程int n = 10;while (n--){pid_t id = fork();if (id == 0){int n = 5;while (n){printf("子进程剩余生存时间: %d秒 [pid: %d  ppid: %d]\n", n--, getpid(), getppid());sleep(1);}// 子进程退出exit(-1);}}// 父进程很忙的话,可以去做自己的事while (1){// TODOprintf("父进程正在忙...\n");sleep(1);}return 0;
}

在这里插入图片描述
  我们可以注意到程序分几个批次把所有子进程都回收了


总结

  结束了,开始进入线程!

相关文章:

Linux进程信号处理(26)

文章目录 前言一、信号的处理时机处理情况“合适”的时机 二、用户态与内核态概念重谈进程地址空间信号的处理过程 三、信号的捕捉内核如何实现信号的捕捉&#xff1f;sigaction 四、信号部分小结五、可重入函数六、volatile七、SIGCHLD 信号总结 前言 这篇就是我们关于信号的最…...

黑马Java跟学.最新AI+若依框架项目开发(一)

黑马Java跟学.最新AI若依框架项目开发.一 前瞻为什么学习若依&#xff1f;AI局限性若依是什么?创新项目开发新方案课程安排前置知识 一、若依搭建若依版本官方非官方 RuoYi-Vue运行后端项目初始化项目Git下载Maven构建 MySQL相关导入sql配置信息 Redis相关启动配置信息 项目运…...

【自学30天掌握AI开发】第1天 - 人工智能与大语言模型基础

自学30天掌握AI开发 - 第1天 &#x1f4c6; 日期和主题 日期&#xff1a;第1天 主题&#xff1a;人工智能与大语言模型基础 &#x1f3af; 学习目标 了解人工智能的发展历史和基本概念掌握大语言模型的基本原理和工作机制区分不同类型的AI模型及其特点理解AI在当前社会中的…...

(十六)Java String类全面解析

一、String类概述 1.1 String的本质 在Java中&#xff0c;String类可能是使用最频繁的类之一&#xff0c;但它也是最容易被误解的类之一。从本质上讲&#xff0c;String代表的是一个不可变的Unicode字符序列。这种不可变性(immutability)是String类设计的核心特性。 java S…...

Android架构之自定义native进程

在Android五层架构中&#xff0c;native层基本上全是c的世界&#xff0c;这些c进程基本上靠android世界的第一个进程init进程创建&#xff0c;init通过rc配置文件&#xff0c;创建了众多的c子进程&#xff0c;也是这众多的c进程&#xff0c;构建了整个android世界的native层。 …...

#跟着若城学鸿蒙# HarmonyOS NEXT学习之AlphabetIndexer组件详解

一、组件介绍 AlphabetIndexer&#xff08;字母索引条&#xff09;是HarmonyOS NEXT中一个非常实用的UI组件&#xff0c;它主要用于在列表视图中提供快速的字母导航功能。当应用中有大量按字母顺序排列的数据&#xff08;如联系人列表、城市列表等&#xff09;时&#xff0c;A…...

React百日学习计划——Deepseek版

阶段一&#xff1a;基础巩固&#xff08;1-20天&#xff09; 目标&#xff1a;掌握HTML/CSS/JavaScript核心语法和开发环境搭建。 每日学习内容&#xff1a; HTML/CSS&#xff08;1-10天&#xff09; 标签语义化、盒模型、Flex布局、Grid布局、响应式设计&#xff08;媒体查询…...

Room持久化库:从零到一的全面解析与实战

简介 在Android开发中,Room作为官方推荐的数据库持久化库,提供了对SQLite的抽象层,使得数据库操作更加安全、高效且易于维护。 Room通过注解处理器和编译时验证,显著降低了数据库操作的复杂度,同时支持响应式编程模式,使开发者能够轻松实现数据变化的实时监听。对于企业…...

Linux云计算训练营笔记day07(MySQL数据库)

数据库 DataBase 保存数据的仓库 数据库管理系统 DBMS 这是一个可以独立运行&#xff0c;用于维护磁盘上的数据的一套软件 特点: 维护性高&#xff0c;灵活度高&#xff0c;效率高&#xff0c;可扩展性强 常见的DBMS Mysql Mariadb Oracle DB2 SQLServer MySQL是一个关系型…...

C语言之旅5---分支与循环【2】

&#x1f4ab;只有认知的突破&#x1f4ab;才来带来真正的成长&#x1f4ab;编程技术的学习&#x1f4ab;没有捷径&#x1f4ab;一起加油&#x1f4ab; &#x1f341;感谢各位的观看&#x1f341;欢迎大家留言&#x1f341;咱们一起加油&#x1f341;努力成为更好的自己&#x…...

K230 ISP:一种新的白平衡标定方法

第一次遇见需要利用光谱响应曲线进行白平衡标定的方法。很好奇是如何利用光谱响应曲线进行白平衡标定的。 参考资料参考&#xff1a;K230 ISP图像调优指南 K230 介绍 嘉楠科技 Kendryte 系列 AIoT 芯片中的最新一代 AIoT SoC K230 芯片采用全新的多核异构单元加速计算架构&a…...

【Web应用】Vue 项目前端项目文件夹和文件介绍

文章目录 ⭐前言⭐一、文件夹介绍&#x1f31f;1、.idea&#x1f31f;2、bin&#x1f31f;3、build&#x1f31f;4、node_modules&#x1f31f;5、public&#x1f31f;6、src ⭐二、文件介绍&#x1f31f;1、.editorconfig&#x1f31f;2、.env.development、.env.production、…...

Leetcode 3544. Subtree Inversion Sum

Leetcode 3544. Subtree Inversion Sum 1. 解题思路2. 代码实现 题目链接&#xff1a;3544. Subtree Inversion Sum 1. 解题思路 这一题我的思路上就是一个动态规划的思路&#xff0c;因为原则上我们只需要遍历一下所有的状态即可&#xff0c;但是这样显然时间复杂度过高&am…...

分别在windows和linux上使用curl,有啥区别?

作为开发者常用的网络工具&#xff0c;curl 在 Windows 和 Linux 上的使用看似相似&#xff0c;但实际存在不少细节差异。以下从 命令语法、环境特性、功能支持 和 开发体验 四个角度展开对比&#xff0c;帮助读者避免跨平台开发时的常见“坑”。 一、命令语法差异&#xff1a;…...

微服务八股(自用)

微服务 SpringCloud 注册中心&#xff1a;Eureka 负载均衡&#xff1a;Ribbon 远程调用&#xff1a;Feign 服务熔断&#xff1a;Hystrix 网关&#xff1a;Gateway/Zuul Alibaba 配置中心&#xff1a;Nacos 负载均衡&#xff1a;Ribbon 服务调用&#xff1a;Feign 服务…...

TCP首部格式及三次握手四次挥手

TCP协议详解&#xff1a;首部格式与连接管理 一、TCP首部格式 TCP首部最小20字节&#xff0c;最大60字节&#xff0c;包含以下字段&#xff1a; | 源端口号(16bit) | 目的端口号(16bit) | | 序列号(32bit) | | 确认号(32bit) | | 数据偏移(4bit)| 保留(6bit) |U|A|P|R|S|…...

Python查询ES错误ApiError(406, ‘Content-Type ...is not supported

现象 使用python查询es数据时出现下面错误 Traceback (most recent call last):File "getUsers.py", line 26, in <module>response es.search(index"lizz_users", bodyquery)File "/usr/local/lib/python3.6/site-packages/elasticsearch/_…...

下周,Coinbase将被纳入标普500指数

Coinbase加入标普500指数紧随比特币突破10万美元大关之后。加密资产正在日益成为美国金融体系的一部分。大型机构已获得监管批准创建现货比特币交易所交易基金&#xff0c;进一步推动了加密货币的主流化进程。 加密货币行业迎来里程碑时刻&#xff0c;Coinbase即将加入标普500…...

物理:由基本粒子组成的个体能否提炼和重组?

个体差异源于基本粒子组合的复杂性与随机性,这一假设若成立,确实可能为生物医学带来革命性突破——但需要突破技术、理论与系统层级的多重壁垒。以下从科学逻辑与技术路径展开分析: 一、随机组合中的共性与稳定结构 1. 自然界的自组织规律 涌现性(Emergence):尽管粒子组…...

Python Day 24 学习

讲义Day16内容的精进 NumPy数组 Q. 什么是NumPy数组&#xff1f; NumPy数组是Python中由NumPy库提供的一种多维数组对象&#xff0c;它称为N-dimensional array,简称ndarray。它是用于数值计算的核心数据结构&#xff0c;能够高效地存储和操作大量的同类型数据。 Q. NumPy数…...

ppy/osu构建

下载 .NET (Linux、macOS 和 Windows) | .NET dotnet还行 构建&#xff1a;f5 运行&#xff1a;dotnet run --project osu.Desktop -c Debug...

前端学习(2)—— CSS详解与使用

目录 一&#xff0c;CSS基础 1.1 语法规范 1.2 引入方式 1.3 选择器 1.3.1 基础选择器 1.3.2 复合选择器 1.3.3 选择器小结 二&#xff0c;CSS使用 2.1 字体设置 2.2 文本属性 2.3 背景属性 2.2 圆角矩形 三&#xff0c;关于浏览器 3.1 Chrome 调试工具 -- 查看 …...

邀请函|PostgreSQL培训认证报名正式开启

掌握PostgreSQL 轻松驾驭主流国产数据库 PostgreSQL培训认证 6月开课 报名火热进行中&#xff5e; 美创中国PostgreSQL培训认证合作机构 中国PostgreSQL培训认证由中国开源软件联盟PostgreSQL分会联合中国电子工业标准化技术协会共同打造&#xff0c;是国内权威的PG技术等级…...

力扣HOT100之二叉树:543. 二叉树的直径

这道题本来想到可以用递归做&#xff0c;但是还是没想明白&#xff0c;最后还是去看灵神题解了&#xff0c;感觉这道题最大的收获就是巩固了我对lambda表达式的掌握。 按照灵神的思路&#xff0c;直径可以理解为从一个叶子出发向上&#xff0c;在某个节点处拐弯&#xff0c;然后…...

深入理解 NumPy:Python 科学计算的基石

在数据科学、人工智能和科学计算的世界里&#xff0c;NumPy 是一块绕不过去的基石。它是 Python 语言中用于高性能科学计算的基础包&#xff0c;几乎所有的数据分析与机器学习框架&#xff08;如 Pandas、TensorFlow、Scikit-learn&#xff09;都离不开它的支持。 一、什么是 …...

基于STM32、HAL库的ADAU1701JSTZ-RL音频接口芯片驱动程序设计

一、简介: ADAU1701JSTZ-RL 是一款高性能音频编解码器 (Codec),专为便携式和低功耗应用设计。它集成了 ADC、DAC、麦克风前置放大器、耳机放大器和数字信号处理功能,支持 I2S/PCM 音频接口和 I2C 控制接口,非常适合与 STM32 微控制器配合使用。 二、硬件接口: 典型的 ST…...

SpringBoot--springboot简述及快速入门

spring Boot是spring提供的一个子项目&#xff0c;用于快速构建spring应用程序 传统方式&#xff1a; 在众多子项目中&#xff0c;spring framework项目为核心子项目&#xff0c;提供了核心的功能&#xff0c;其他的子项目都需要依赖于spring framework&#xff0c;在我们实际…...

智慧校园场景下iVX 研发基座应用实践与行业适配研究

一、智慧校园多系统协同实践 在智慧校园建设中&#xff0c;iVX 研发基座通过模块化协作开发模式实现跨系统集成与数据治理。以校园门户与子系统整合为例&#xff0c;基座通过统一身份认证体系实现单点登录&#xff08;SSO&#xff09;&#xff0c;用户中心基于 ABAC 模型动态控…...

故障诊断模型评估——混淆矩阵,如何使样本量一致(上)

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 基于FFT CNN - BiGRU-Attention 时域、频域特征注意力融合的轴承故障识别模型-CSDN博客 基于FFT CNN - Transformer 时域、频域特征融合的轴承故障识别模型-CSDN博客 P…...

Redis Cluster 集群搭建和集成使用的详细步骤示例

以下是Redis集群搭建和集成使用的详细步骤示例&#xff1a; 搭建Redis集群 环境准备 下载Redis&#xff1a;从Redis官方网站下载最新稳定版本的Redis源代码&#xff0c;解压到指定目录&#xff0c;如/opt/redis。安装依赖&#xff1a;确保系统安装了必要的依赖&#xff0c;如…...

【技巧】使用UV创建python项目的开发环境

回到目录 【技巧】使用UV创建python项目的开发环境 0. 为什么用UV 下载速度快、虚拟环境、多版本python支持、清晰的依赖关系 1. 安装基础软件 1.1. 安装python 下载地址&#xff1a;https://www.python.org/downloads/windows/ 1.2. 安装UV > pip install uv -i ht…...

竞业禁止协议中AI技能限制的深度剖析

首席数据官高鹏律师团队 在当今科技飞速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;领域成为了商业竞争的关键战场。随着AI技术在各行业的广泛渗透&#xff0c;竞业禁止协议中涉及AI技能的限制条款愈发受到关注&#xff0c;其背后蕴含着复杂而关键的法律与商业…...

Mirror的多人连接管理及房间系统

以下是一个基于Mirror的多人连接管理及房间系统的服务端实现方案&#xff0c;包含部署说明&#xff1a; 一、服务端架构设计 网络管理扩展 using Mirror; using UnityEngine;public class RoomNetworkManager : NetworkManager {// 房间字典&#xff08;房间ID -> 房间对象…...

基于Session实现短信登录全流程详解

前言 在当今的Web应用中&#xff0c;短信验证码登录已成为最常用的身份验证方式之一。本文将详细介绍基于Session实现短信登录的全套流程&#xff0c;包括技术选型、流程设计、具体实现以及安全防护措施。通过本文&#xff0c;您将掌握从发送验证码到完成登录的完整实现方案。…...

关于 javax.validation.constraints的详细说明

以下是关于 javax.validation.constraints&#xff08;现为 ​Jakarta Bean Validation&#xff09;的详细说明&#xff0c;涵盖核心注解、使用场景、代码示例及最佳实践&#xff1a; 一、javax.validation.constraints 是什么&#xff1f; ​作用​&#xff1a;提供一组标准注…...

linux系统如何将采集的串口数据存储到txt

步骤&#xff1a; 确认串口设备&#xff1a;通常为/dev/ttyS0&#xff08;COM1&#xff09;或/dev/ttyUSB0&#xff08;USB转串口&#xff09;。设置波特率等参数&#xff1a;使用stty命令&#xff0c;例如&#xff1a; bash stty -F /dev/ttyUSB0 9600 cs8 -icanon -ixon 实时…...

(顺序表、单链表、双链表)==>一篇解决!(Java版)

文章目录 一、线性表二、顺序表三、单链表四、双链表 一、线性表 线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。 线性表的特征&#xff1a;数据元素之间具有一种“一对一”的逻辑关系。 线性表的分类&#xff1a; 线…...

大模型常用位置编码方式

深度学习中常见的位置编码方式及其Python实现&#xff1a; 一、固定位置编码&#xff08;Sinusoidal Positional Encoding&#xff09; 原理 通过不同频率的正弦和余弦函数生成位置编码&#xff0c;使模型能够捕捉绝对位置和相对位置信息。公式为&#xff1a; 公式标准数学表达…...

【fastadmin开发实战】在前端页面中使用bootstraptable以及表格中实现文件上传

先看效果&#xff1a; 1、前端页面中引入了表格 2、表格中实现文件上传 3、增加截止时间页面 难点在哪呢&#xff1f; 1、这是前端页面&#xff0c;并不支持直接使用btn-dialog的类属性实现弹窗&#xff1b; 2、前端页面一般绑定了layout模板&#xff0c;如何实现某个页面不…...

IO、存储、硬盘、文件系统相关常识

目录 1. IO&#xff08;输入输出&#xff09;基础概念 1.1 IO的定义 1.2 流 1.3 IO流 2.存储 2.1 存储技术 2.2 存储介质的分类&#xff08;机械硬盘、固态硬盘、光盘、磁带&#xff09; 2.2.1 机械硬盘 2.2.2 固态硬盘 2.2.3 光盘 2.2.4 磁盘 2.3 存储管理 2.4 存…...

amd架构主机构建arm架构kkfileview

修改本机使用镜像仓库地址 vim /etc/docker/daemon.json { “experimental”: true, “registry-mirrors”: [ “https://docker.m.daocloud.io”, “https://docker.1panel.live”, “http://mirrors.ustc.edu.cn/”, “http://mirror.azure.cn/”, “https://docker.hpcloud.…...

日志链路ID配置,traceId多线程不打印什么鬼?

logback.xml 关键配置 [traceId:%X{traceId}] <!-- 彩色日志格式模板 --><property name"log.pattern.color"value"%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level){FATALred, ERRORred, WARNyellow, INFOgreen, DEBUGcyan, TRA…...

InfluxDB-数据看板实现流程:从数据采集到可视化展示

数据看板的实现涉及到多个步骤和技术组件&#xff0c;以下是基于提供的知识库内容&#xff0c;详细解释数据看板&#xff08;特别是30日活跃用户数趋势&#xff09;的实现过程&#xff1a; 1. 数据来源 所有用户行为数据通过网关进行数据埋点&#xff0c;并通过消息队列&…...

Git基本操作命令

文章目录 Git基本操作命令创建仓库命令提交与修改提交日志版本回退分支切换删除文件.gitignore文件远程操作 Git分支管理创建分支查看分支合并分支删除分支保存当前文件未提交更改并切换分支 Git提交历史恢复和回退 Git标签标签推送删除标签附注标签查看标签信息删除标签 Git基…...

JavaScript实践(三)JavaScript序列化与反序列化深度解析

JavaScript中的序列化与反序列化是数据存储、网络传输和跨系统交互的核心技术之一。本文将从底层原理、核心方法、复杂场景处理、安全风险及工程实践等多个维度&#xff0c;系统性地解析这一技术体系&#xff0c;并附完整的代码实现示例。 一、序列化与反序列化的核心价值 序列…...

大模型—— FastGPT 知识库无缝集成到 n8n 工作流 (基于 MCP 协议)

大模型—— FastGPT 知识库无缝集成到 n8n 工作流 (基于 MCP 协议) 背景:n8n 与 RAG 知识库集成的挑战 n8n 作为一款强大的开源自动化工作流工具,正获得越来越多用户的青睐。它由前《加勒比海盗》视觉设计师 Jan Oberhauser 于 2019 年创立,旨在提供比 Zapier 等工具更灵活…...

安卓刷机模式详解:Fastboot、Fastbootd、9008与MTK深刷

安卓刷机模式详解&#xff1a;Fastboot、Fastbootd、9008与MTK深刷 一、刷机模式对比 1. Fastboot模式 简介&#xff1a;传统安卓底层刷机模式&#xff0c;通过USB连接电脑操作优点&#xff1a;支持大多数安卓设备&#xff0c;操作相对简单缺点&#xff1a;需要设备进入特定…...

深入浅出MySQL 8.0:新特性与最佳实践

MySQL作为开源关系型数据库的佼佼者&#xff0c;近年来持续更新迭代&#xff0c;尤其是在8.0版本中引入了一系列令人兴奋的新特性。本文将介绍一些MySQL 8.0的关键新功能&#xff0c;并提供最佳实践&#xff0c;旨在帮助开发人员和DBA更好地利用这一强大的数据库管理系统。 一…...

【登录认证】JWT令牌

一、概述 JWT全称:**JSON Web Token **(https://jwt.io/)定义了一种简洁的、自包含的格式&#xff0c;用于通信双方以json数据格式安全的传输信息。组成: ①第一部分:Header(头)&#xff0c;记录令牌类型、签名算法等。例如: (“alg”:" HS256"," type":“…...

coco数据集mAP评估

0 coco数据集划分说明 1 用yolo自带的评估 from ultralytics import YOLOmodel YOLO("../spatial-perception/checkpoints/yolo11n.pt")metrics model.val(data"./coco.yaml", save_jsonTrue) ## save_json为True,可以把预测结果存成json文件&#xff…...