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

【Linux】进程 信号的产生

🌻个人主页:路飞雪吖~

       🌠专栏:Linux


目录

一、掌握Linux信号的基本概念

 🌠前台进程 VS 后台进程

🌠 小贴士:

🪄⼀个系统函数 --- signal()

 🪄查看信号 --- man 7 signal

二、软硬件上理解 : OS如何进行信号处理

✨硬件上理解

🌠 信号 VS 硬件中断

✨软件:如何理解信号处理?

三、✨信号产生的方式

🚩1. 键盘产生

🚩2. 系统指令产生  发送信号  ---- 底层使用的是系统调用​编辑

🚩3. 系统调用 发送信号

🌠  发送信号的其他函数:

🪄 raise --- 函数可以给当前进程发送指定的信号(⾃⼰给⾃⼰发信号) 

🪄 abort --- 自己给自己发了一个特定的终止自己的信号 --- 6号信号[SIGABRT]

🚩4. 由软件条件 产生信号

 🌠OS内定时器

四、✨ alarm() 设置重复闹钟 --- 简单快速理解系统闹钟

🚩5. 异常 (野指针、 除 0)

 🪄模拟 野指针

 🪄 模拟 除 0

🌠OS怎么知道我们的进程内部出错了?为什么会陷入死循环?

☄️ 除 0 :

☄️ 野指针

🌠core VS Term 

✨core 的使用 --- 调试

🌠 如果是子进程异常了呢? core 会不会出现?

🌠小结:

 五、信号处理

1. 默认处理动作

2. 忽略:本身就是一种信号捕捉的方法,动作就是忽略

 3. 自定义 捕捉一个信号


• 信号是内置的,进程认识信号,是程序员内置的特性;

• 信号的处理方法,在信号产生之前,就已经准备好了;

• 何时处理信号?先处理优先级很高的,可能并不是立即处理,在合适的时候处理;

• 怎么处理信号:a. 默认行为,b. 忽略信号,c. 自定义动作

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

一、掌握Linux信号的基本概念

// MakefileBIN=sig
CC=g++
SRC=$(shell ls *.cc)
OBJ=$(SRC:.cc=.o)$(BIN):$(OBJ)$(CC) -o $@ $^ -std=c++11%.o:%.cc$(CC) -c $< -std=c++11.PHONY:clean
clean:rm -f $(BIN)
// Signal.cc#include <iostream>
#include <unistd.h>int main()
{while(true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}

 🌠前台进程 VS 后台进程

• 前台进程【./sig】:在命令行所输入的所有东西都不能执行。

  > Ctrl + c 终止前台进程:给的是shell,shell在前台 --> 信号发给进程。

• 后台进程【./sig &】:bash进程依旧可以进行命令行解释。

  > kill -9 [pid] 终止后台进程

  > fg [作业号] 

🌠 小贴士:

<1>

 【1 -- 31】:普通信号;

 【34 -- 64】:实时信号。

<2> 

ctrl + c --> 信号发给进程,被OS接受并解释成为2号【SIGINT】信号,发送给目标进程 -- 对2号信号的默认处理动作是 终止自己!。

🪄⼀个系统函数 --- signal()

#include <iostream>
#include <unistd.h>
#include <signal.h>void Handler(int signo)
{// 当对应的信号被触发,内核会将对应的信号编号,传递给自定义方法std::cout << "Get a signal, signal number is : " << signo << std::endl;
}int main()
{signal(SIGINT, Handler);// 默认终止 --改成了--> 执行自定义方法:Handler while(true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}

#include <iostream>
#include <unistd.h>
#include <signal.h>void Handler(int signo)
{// 当对应的信号被触发,内核会将对应的信号编号,传递给自定义方法std::cout << "Get a signal, signal number is : " << signo << std::endl;
}int main()
{signal(SIGQUIT, Handler);// 默认终止 --改成了--> 执行自定义方法:Handler while(true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}

ctrl + \ :3号信号,默认也是终止进程的。

 🪄查看信号 --- man 7 signal

#include <iostream>
#include <unistd.h>
#include <signal.h>void Handler(int signo)
{// 当对应的信号被触发,内核会将对应的信号编号,传递给自定义方法std::cout << "Get a signal, signal number is : " << signo << std::endl;
}int main()
{// signal 为什么不放在循环里面? 不需要,只需要设置一次就可以了// signal:如果没有产生2或者3号信号呢? handler不被调用!// signal(SIGINT, Handler);// 默认终止 --改成了--> 执行自定义方法:Handler// signal(SIGQUIT, Handler);// 默认终止 --改成了--> 执行自定义方法:Handlerfor (int signo = 1; signo < 32; signo++)// 捕捉 1--31 号的信号,使得很多方法杀不掉进程,但是有一些信号捕捉不到{signal(signo, Handler);std::cout << "自定义捕捉信号:" << signo << std::endl;}while (true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}

二、软硬件上理解 : OS如何进行信号处理

✨硬件上理解

OS怎么知道键盘上面有数据?

当按下键盘 首先是被驱动程序识别到 按下的按键,OS如何知道键盘被按下了?--- 硬件中断 ,键盘一旦按下,在硬件上 键盘 和 CPU 是连接的,在控制信号,输入设备是直接和中央处理器连接的,输入设备首先会给中央处理器发送一个中断信号【硬件电路】,CPU中央处理器收到这个硬件电路之后,立马就会告诉OS,当前外设有一个外设准备好了,接下来让OS主动的去把外设的数据拷贝到内存里,此时OS再也不用主动轮询检测任何输入/输出设备了,只需要等待发生中断信号 --- 硬件和OS可以并行执行了!

 

🌠 信号 VS 硬件中断

• 信号是纯软件,模拟中断的行为;

• 硬件中断,纯硬件。

✨软件:如何理解信号处理?

• 键盘上的组合键 是先被OS系统识别到【OS是键盘真正的管理者,当系统在运行的时候,OS一直在检测键盘上有没有信息】再发给进程,当进程不能立即处理这个信号时,进程就会记录下这个信号【信号是从 1-31 连续的数字,进程是否收到1-31这个数字的信号 --- 在 task_struct 里通过位图记录 0000 0000 0000 0000 0000 0000 0000 0000,比特位的位置:信号的编号,比特位的内容:是否为0/1,是否收到对应的信号】发送信号的本质是什么?【写入信号】OS修改目标进程的PCB中的信号位图【0 -> 1】,操作系统有没有权利修改进程PCB的位图?有,OS是进程的管理者,所以 无论以什么方式发送信号,最终 都是转换到OS,让OS写入信号,因为OS是进程的唯一管理者。   

信号产生有很多种,但是信号发送只有OS;

• 判断进程是否收到信号:进程里面有信号处理的表结构 sighandler_t arr[32] 函数指针数组,根据PCB里面去查位图,根据位图就可以检测出那个比特位为1,拿到比特位为1,这个数字就可以作为该数组的下标【下标-1】,直接去调用函数指针上的方法。

三、✨信号产生的方式

🚩1. 键盘产生

当在可执行程序在前台进程产生之后,按住 ctrl+c ,键盘被按下,计算机的CPU识别到键盘又被按下的动作,唤醒OS,让OS去读取键盘山的 ctrl+c ,读到后将 ctrl + c 解释成 2号 信号【if(案件 == ctrl + c)】,OS会把 ctrl + c 转化成一段代码【向目标前台进程PCB写入2号信号,即把比特位 0 --> 1,OS信号发送完成】,发送完成之后,该进程在后续合适的时候调度运行,发现自己收到一个信号,当前进程默认就要执行自己对 2号 信号的处理动作。

🚩2. 系统指令产生  发送信号  ---- 底层使用的是系统调用

🚩3. 系统调用 发送信号

// MakefileBIN=mykill
CC=g++
SRC=$(shell ls *.cc)
OBJ=$(SRC:.cc=.o)$(BIN):$(OBJ)$(CC) -o $@ $^ -std=c++11%.o:%.cc$(CC) -c $< -std=c++11.PHONY:clean
clean:rm -f $(BIN)
// Signal.cc#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
#include <signal.h>// 设置自己的killvoid Usage(std::string proc)
{std::cout << "Usage: " << proc << " signumber processid " << std::endl;
}// ./mykill  1信号    12345进程号
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);
}

信号发送 本质都是操作系统OS发的!!!

🌠  发送信号的其他函数:

🪄 raise --- 函数可以给当前进程发送指定的信号(⾃⼰给⾃⼰发信号) 

#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{int cnt = 5;while(true){std::cout << "hahaha alive" << std::endl;cnt--;if(cnt <= 0)raise(9);sleep(1);}
}

🪄 abort --- 自己给自己发了一个特定的终止自己的信号 --- 6号信号[SIGABRT]

int main()
{int cnt = 5;while(true){std::cout << "hahaha alive" << std::endl;cnt--;if(cnt <= 0)// abort();sleep(1);}
}

🚩4. 由软件条件 产生信号

 SIGPIPE 是⼀种由软件条件产生的信号。

🪄 alarm 函数 --- 调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 (14号)SIGALRM 信号,该信号的默认处理动作是终⽌当前进程。

int main()
{// 统计我的服务器 1s 可以将计数器累加多少!alarm(1);// 我自己,会在 1s 之后收到一个SIGALARM信号int number = 0;while(true){printf("count: %d\n", number);}}

int number = 0;
void die(int signumber)
{printf("get a sig : %d, count: %d\n", signumber, number);exit(0);
}int main()
{// 统计我的服务器 1s 可以将计数器累加多少!alarm(1);// 我自己,会在 1s 之后收到一个SIGALARM信号signal(SIGALRM, die);while(true){number++;}}

这两个的写法 计数器累加的次数为什么会差别这么大呢?

【printf("count: %d\n", number);】里面的 IO 影响了计算的速度!,【while(true) number++;】纯CPU计算。

 🌠OS内定时器

• 在操作系统内,要对 进程管理、文件管理、多线程、内存管理 进行管理,同时也要做定时管理,OS在开机之后,是会维护时间的,所以设置的闹钟,在OS底层就是设置了一个定时器。 

• 许多进程都可以设置闹钟,OS里可以同时存在很多被设置的闹钟,所以OS就要管理定时器【先描述,再组织】[struct timer{}] ,OS在每次检测超时的时候,会把定时器所对应的节点,按顺序进行升序排序,变成有序的列表,当有超时的时候,只需要从前往后遍历,遇到第一个没有超时的,之前的全部是超时的,遍历的同时,把超时的【struct timer】执行对应的 【func_t f;函数,给目标进程发送SIGALRM信号】在操作系统内给相应的进程发信号。

• 用堆理解定时器的排序:维护成一个最小堆,用超时时间作为键值,所以要想知道定时器有没有超时,只需要查堆顶就可以了,若堆顶没有超时,则所有的节点都没有超时;若堆顶超时,就把堆顶pop出来【堆再重新构建】,去执行相应的操作,重复操作,直到不再超时。

• 所以当我们在调用alarm函数时,就相当于在OS内给我们获取当前时间【currenttime】和 当前的超时时间【seconds】,然后在内核中设置一个节点,放在堆里面,OS就会在超时之后,直接向目标进程发送SIGALRM信号,并且把该节点释放掉。

• 闹钟的返回值 是什么?闹钟剩余时间

int main()
{alarm(10);sleep(4);int n = alarm(0);// 0:取消闹钟std::cout << "n: " << n << std::endl;
}

当软件条件就绪时【超时、闹钟、定时器】,OS就可以向目标进程发送信号。

四、✨ alarm() 设置重复闹钟 --- 简单快速理解系统闹钟

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string>
#include <signal.h>
#include <functional>
#include <vector>// 闹钟
using func_t = std::function<void()>;int gcount = 0;
std::vector<func_t> gfuncs;// 把信号 更换 成为 硬件中断
void handler(int signo)
{for(auto &f : gfuncs){f();}std::cout << "gcount : " << gcount << std::endl;alarm(1);// 上面的闹钟响了之后,继续执行
}int main()
{gfuncs.push_back([](){ std::cout << "我是一个内核刷新操作" << std::endl;});gfuncs.push_back([](){ std::cout << "我是一个检测进程时间片的操作,如果时间片到了,我会切换进程" << std::endl;});gfuncs.push_back([](){ std::cout << "我是一个内存管理操作,定期清理操作系统内部的内存碎片" << std::endl;});alarm(1);// 一次性的闹钟,超时alarm会自动被取消signal(SIGALRM, handler);while(true)  // gcount++;{pause();std::cout << "我醒来了..." << std::endl;gcount++;}
}

• 操作系统其实就是一个死循环,当OS启动之后,会接收外部固定的事件源【时钟中断,集成在CPU内部】,每隔很短的时间,向OS触发硬件中断,让OS去执行中断方法。

• OS的调度、切换、内存管理、系统调用,全都是依靠中断来完成的。所以OS不应该叫OS,应该叫一个中断程序。

🚩5. 异常 (野指针、 除 0)

 🪄模拟 野指针

void handler(int signo)
{std::cout << "get a signo: " << signo << std::endl;// 我捕捉了 11 号信号,没执行默认动作,也没有退出进程
}int main()
{signal(11, handler);int *p = nullptr;*p = 100;while(true);
}

 🪄 模拟 除 0

void handler(int signo)
{std::cout << "get a signo: " << signo << std::endl;// 我捕捉了 8 号信号,没执行默认动作,也没有退出进程
}int main()
{signal(8, handler);int a = 10;a /= 0;while(true);
}

C/C++中,常见的异常,进程崩溃了,原因是 OS给目标进程发送对应错误的信号,进而导致该进程退出。

🌠OS怎么知道我们的进程内部出错了?为什么会陷入死循环?
☄️ 除 0 :

状态寄存器里会有各种描述状态计算的结果,其中有一个叫做 溢出标记位,正常情况下计算完毕,溢出标记位为0,说明整个计算结果没有溢出,结果可靠,此时把结果写回内存里,就完成对应的复制。若对应的溢出标记位为1,就证明CPU内部计算结果出错了。

当出现溢出,即CPU内部结果出错了,OS就会知道【CPU[硬件]的中断有 内部中断 和 外部中断,CPU内部一旦出现溢出的错误,CPU内部硬件就会触发内部中断,就会告诉OS,OS是硬件的管理者,OS在调度、切换,就是在合理的使用CPU资源】,是哪个进程引起的硬件错误,OS就会通过信号杀掉进程!即当计算出现硬件错误,OS要识别到,就会给目标进程发送对应的信号,杀掉进程!

为什么会陷入死循环【不退出进程】?

这个进程一直没有退出,还要被调度,进程就会被切换、执行等等,【a /= 0】CPU里面的状态寄存器的 Eflags的溢出标记位一直都是1,CPU里面的数据一直都是这个进程的上下文内容不会更改,所以就会一直被捕捉,被调度,就会出现死循环,并且一直在触发8号信号。

☄️ 野指针

CR3 寄存器:保存页表的起始地址,负责虚拟地址和物理地址转化。

MMU【硬件】被集成在CPU内部:完成虚拟地址和物理地址转化。

ELP:读取可执行程序的起始地址。

当传递一个 0 地址,在MMU转化出来 0,我们没有权利访问最低的地址0,MMU这个硬件经过 转化或权限 方面上, 发现根本不能转化出来这个地址,使得MMU【硬件】报错,即CPU内部出错【与上面的类似】,OS就会知道,OS就会向目标进程去识别,就会杀掉这个进程。也要类似 状态寄存器的东西,转化错误,所以就会死循环。

OS怎么知道我们的进程内部出错了?程序内部的错误,其实都会表现在硬件错误上,OS就会知道是哪个地方出错,进而给对应的目标进程发送信号。

🌠core VS Term 

Term :正常终止进程【不需要进行debug】。

Core:也是终止进程,但会多做一些处理。核心转储,在当前目录下形成文件【pid.core】,OS在进程崩溃的时候,将进程在内存中的部分信息保存起来【保存在磁盘上,持久化起来】,方便后续调试!这个文件【pid.core】一般都会被云服务器关掉。

把core功能关闭的原因:在云服务器上,若野指针、除 0 出现错误等等,一般都会在后端立即重启这个服务。若不关闭,当某个错误,一直重复 重启后挂掉,磁盘上就会出现大量的core文件,甚至磁盘爆满,就会影响系统 或 某些应用服务直接就挂掉,即 会因为系统问题,导致磁盘爆满

✨core 的使用 --- 调试

🌠 如果是子进程异常了呢? core 会不会出现?

子进程会不会出现core取决于:退出信号是否终止动作 && 服务器是否开启 core 功能。

🌠小结:

• 键盘、系统指令、系统调用、软件条件、异常 的信号产生,最终都要有OS来执行,因为OS是进程的管理者,统一由OS来向目标进程发信号;

• 信号的处理方式:不是立即处理,而是在合适的时候处理;

• 信号如果不是被立即处理,信号就会暂时被进程记录下来,保存在进程对应的PCB中的信号位图;

• 一个进程在没有收到信号的时候,知道自己应该对合法信号作何处理:默认、忽略、自定义;

• OS向目标进程发送信号:实则是OS向目标进程写信号。

 五、信号处理

1. 默认处理动作

void handler(int signo)
{std::cout << "get a signal: " << signo << std::endl;exit(1);
}int main()
{signal(2, SIG_DFL);// default:默认while(true){pause();}
}

2. 忽略:本身就是一种信号捕捉的方法,动作就是忽略

void handler(int signo)
{std::cout << "get a signal: " << signo << std::endl;exit(1);
}int main()
{signal(2, SIG_IGN);// 忽略(不做处理),本身就是一种信号捕捉的方法,动作就是忽略while(true){pause();}
}

 3. 自定义 捕捉一个信号

void handler(int signo)
{std::cout << "get a signal: " << signo << std::endl;exit(1);
}int main()
{// 信号捕捉:// 1. 默认// 2. 忽略// 3. 自定义::signal(2, handler);// 自定义while(true){pause();}
}

如若对你有帮助,记得关注、收藏、点赞哦~ 您的支持是我最大的动力🌹🌹🌹🌹!!!

若有误,望各位,在评论区留言或者私信我 指点迷津!!!谢谢 ヾ(≧▽≦*)o  \( •̀ ω •́ )/

相关文章:

【Linux】进程 信号的产生

&#x1f33b;个人主页&#xff1a;路飞雪吖~ &#x1f320;专栏&#xff1a;Linux 目录 一、掌握Linux信号的基本概念 &#x1f320;前台进程 VS 后台进程 &#x1f320; 小贴士&#xff1a; &#x1fa84;⼀个系统函数 --- signal() &#x1fa84;查看信号 --- man 7 sign…...

实时操作系统革命:实时Linux驱动的智能时代底层重构

一、智能时代对实时性的终极挑战 在万物互联的智能时代&#xff0c;人类对机器响应速度的期待已突破物理极限。当工业机器人以亚毫米级精度执行微米级加工任务&#xff0c;当自动驾驶系统在130公里时速下需在10毫秒内完成决策切换&#xff0c;当医疗机器人需在5毫秒内响应神经…...

NGINX HTTP/3 实验指南安装、配置与调优

一、HTTP/3 简介 基于 QUIC&#xff1a;在 UDP 之上实现的多路复用传输&#xff0c;内置拥塞控制与前向纠错&#xff0c;无需三次握手即可恢复连接。零 RTT 重连&#xff1a;借助 TLS 1.3&#xff0c;实现连接恢复时的 0-RTT 数据发送&#xff08;视底层库支持&#xff09;。多…...

机器学习中的维度、过拟合、降维

1. 维度灾难 当我们谈论机器学习模型在处理数据时遇到的困难&#xff0c;一个常常被提及的词便是“维度灾难”&#xff08;Curse of Dimensionality&#xff09;。这不是科幻小说里的情节&#xff0c;而是数学和计算世界里真实存在的困境。它指的正是&#xff1a;当数据集的特…...

关于git的使用

下载git 可以去git的官网下载https://git-scm.com/downloads 也可以去找第三方的资源下载&#xff0c;下载后是一个exe应用程序&#xff0c;直接点开一直下一步就可以安装了 右键任意位置显示这两个就代表成功&#xff0c;第一个是git官方的图形化界面&#xff0c;第二个是用…...

预约按摩小程序源码介绍

基于ThinkPHP、FastAdmin和UniApp开发的预约按摩小程序源码&#xff0c;ThinkPHP作为后端框架&#xff0c;以其高效稳定著称&#xff0c;能妥善处理数据逻辑与业务规则。FastAdmin作为后台管理框架&#xff0c;极大简化了后台管理系统的搭建与维护。UniApp则让小程序具备跨平台…...

Elasticsearch创建快照仓库报错处理

创建快照仓库报错&#xff1a; 根据报错提示的信息&#xff0c;问题可能出在 Elasticsearch 的配置中。当你尝试创建一个文件系统&#xff08;fs&#xff09;类型的快照仓库时&#xff0c;虽然已经指定了 location 参数&#xff0c;但 Elasticsearch 仍然报错&#xff0c;这通…...

LINUX安装运行jeelowcode前端项目

参考 JeeLowCode低代码社区,JeeLowCode低代码开发平台,JeeLowCode低代码开发框架,快速启动&#xff08;VUE&#xff09; 安装node 18 LINUX安装node/nodejs_linux安装node 安装到哪-CSDN博客 安装PNPM LINUX安装PNPM-CSDN博客 下载 git clone https://gitcode.com/jeelo…...

使用DDR4控制器实现多通道数据读写(十三)

一、概述 在上一章节中使用仿真简单验证了interconnect的功能&#xff0c;使用四个axi4的主端口同时发起读写命令&#xff0c;经过interconnect后&#xff0c;将这些读写指令依次发给ddr4控制器。Ddr4控制器响应后再依次将响应发送到各个通道。从而实现多通道读写ddr4控制器的功…...

如何描述BUG

一、如何描述BUG 1.1 版本信息 1.1.1 必填项 精确到构建版本号&#xff08;如v2.3.1_build20240615&#xff09; 获取方式&#xff1a; 代码版本控制系统&#xff08;Git提交哈希值&#xff09;应用内版本信息&#xff08;App的About页面&#xff09;持续集成工具生成的版本…...

Python进阶:如何通过组合模式实现高性能Vector类?

&#x1f50d; 设计动机&#xff1a;为何抛弃继承选择组合&#xff1f; 在实现多维向量类Vector时&#xff0c;我们刻意采用组合模式而非继承&#xff0c;核心优势在于&#xff1a; 高扩展性&#xff1a;通过array数组存储分量&#xff0c;天然支持高维向量低耦合&#xff1a…...

谷歌Veo vs Sora:AI视频生成技术的巅峰对决

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 ——从架构到实践&#xff0c;解析音画同步、物理模拟与长视频生成的破局之战 一、技术架构&#xff1a;双雄对垒&#xff0c;殊途同归&#xff1f; 谷歌…...

基于Spring boot+vue的中医养生系统的设计与实现(源码+论文+部署+安装+调试+售后)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统背景 在健康中国战略持续推进与全民健康意识显著提升的时代背景下&#xff0c;中医养生作为中…...

31.第二阶段x64游戏实战-封包-线程发包

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;30.第二阶段x64游戏实战-认识网络数据包发送流程 代码跳转 ws2_32.send跳转sen…...

Unity数字人开发笔记

开源工程地址&#xff1a;https://github.com/zhangliwei7758/unity-AI-Chat-Toolkit 先致敬zhangliwei7758&#xff0c;开放这个源码 一、建立工程 建立Unity工程&#xff08;UnityAiChat&#xff09;拖入Unity-AI-Chat-Toolkit.unitypackage打开chatSample工程&#xff0c;可…...

嵌入式开发--STM32G431无法正常运行程序,BOOT0与CAN冲突

故障现象 今天开发STM32G431时遇到一个问题&#xff0c;板子打样回来后&#xff0c;焊接完成&#xff0c;可以烧程序&#xff0c;可以读FLASH&#xff0c;却死活不能运行&#xff0c;也不能进仿真调试。 故障定位 经过排查&#xff0c;发现将隔离芯片π121M31拆除&#xff0…...

每天掌握一个Linux命令 - sqlite3

Linux 命令工具 sqlite3 使用指南 一、工具概述 sqlite3 是 SQLite 数据库的命令行工具&#xff0c;用于在 Linux 系统中直接操作 SQLite 数据库&#xff08;轻量级、无服务器、嵌入式关系型数据库&#xff09;。 核心特点&#xff1a; 无需安装数据库服务&#xff0c;直接通…...

程序环境与预处理

一、程序的翻译环境和执行环境 翻译环境&#xff1a;将源代码转化为可执行的机器指令 执行环境&#xff1a;执行代码 1、翻译环境 流程&#xff1a; 二、运行环境 程序执行过程&#xff1a; 三、预编译阶段 1、预定义符号 __FILE__ //进行编译的原文件名 __LINE__ //文…...

RT Thread Nano V4.1.1 rtconfig.h 注释 Configuration Wizard 格式

rtcomfig.h 以下是对 [rtconfig.h](file://c:\Users\admin\Downloads\rtthread-nano-master\rt-thread\bsp\stm32f407-msh\RT-Thread\rtconfig.h) 文件中每一个配置项的详细注释说明: 基本配置(Basic Configuration) [RT_THREAD_PRIORITY_MAX](file://c:\Users\admin\Downl…...

《Java 单例模式:从类加载机制到高并发设计的深度技术剖析》

【作者简介】“琢磨先生”--资深系统架构师、985高校计算机硕士&#xff0c;长期从事大中型软件开发和技术研究&#xff0c;每天分享Java硬核知识和主流工程技术&#xff0c;欢迎点赞收藏&#xff01; 一、单例模式的核心概念与设计目标 在软件开发中&#xff0c;我们经常会遇…...

JSONP跨域原理全解析

JSONP&#xff08;JSON with Padding&#xff09;是一种绕过浏览器同源策略限制、实现跨域数据请求的“hack”式方案。其核心原理和流程如下&#xff1a; 同源策略限制 浏览器为了安全&#xff0c;只允许页面从与当前页面相同协议、域名、端口的服务器加载数据。而 <script&…...

【MySQL】第11节|MySQL 8.0 主从复制原理分析与实战(一)

一、MySQL主从复制基础 1. 核心概念 定义&#xff1a; MySQL主从复制是将主库&#xff08;Source/Master&#xff09;的数据变更同步到一个或多个从库&#xff08;Replica/Slave&#xff09;的机制&#xff0c;默认采用异步复制&#xff0c;支持全库、指定库或表的同步。 角…...

全志F1c200开发笔记——移植根文件系统

1.下载buildroot Index of /downloads/ 使用2018.02.11版本 直链下载 https://buildroot.org/downloads/buildroot-2018.02.11.tar.gz 2.配置 进入buildroot压缩包目录下&#xff0c;使用命令解压并进入工作目录 tar -xf buildroot-2018.02.11.tar.gz cd buildroot-2018.…...

[yolov11改进系列]基于yolov11引入自注意力与卷积混合模块ACmix提高FPS+检测效率python源码+训练源码

[ACmix的框架原理] 1.1 ACMix的基本原理 ACmix是一种混合模型&#xff0c;结合了自注意力机制和卷积运算的优势。它的核心思想是&#xff0c;传统卷积操作和自注意力模块的大部分计算都可以通过1x1的卷积来实现。ACmix首先使用1x1卷积对输入特征图进行投影&#xff0c;生成一组…...

Java NIO编程:构建高性能网络应用

1.Java NIO 核心概念与架构 1. 传统 BIO 与 NIO 的对比 特性 BIO (Blocking I/O) NIO (Non-blocking I/O) I/O 模型 阻塞 非阻塞 / 异步 线程模式 每个连接一个线程 单线程管理多个连接 数据处理单位 字节流 / 字符流 缓冲区 (Buffer) 核心组件 Socket, ServerSoc…...

如何实现高性能超低延迟的RTSP或RTMP播放器

随着直播行业的快速发展&#xff0c;RTSP和RTMP协议成为了广泛使用的流媒体传输协议&#xff0c;尤其是在实时视频直播领域&#xff0c;如何构建一个高性能超低延迟的直播播放器&#xff0c;已经成为了决定直播平台成功与否的关键因素之一。作为音视频直播SDK技术老兵&#xff…...

每天掌握一个Linux命令 - sar

Linux 系统监控工具 sar 使用指南 一、工具概述 sar&#xff08;System Activity Reporter&#xff09; 是 Linux 下功能强大的系统活动报告工具&#xff0c;属于 sysstat 软件包的核心组件。它通过采集系统资源&#xff08;CPU、内存、磁盘、网络、进程等&#xff09;的使用…...

RabbitMQ 集群与高可用方案设计(三)

五、高可用方案设计与实现 &#xff08;一&#xff09;负载均衡与代理 1. HAProxy 配置 HAProxy 是一款广泛应用的开源负载均衡器和代理服务器&#xff0c;它能够实现对 RabbitMQ 集群节点的负载均衡和健康检查&#xff0c;有效提高系统的可用性和性能。以下是使用 HAProxy …...

Linux的读写屏障

在 Linux 中&#xff0c;读写屏障&#xff08;Read-Write Barriers&#xff0c;简称 RWB&#xff09;是对内存访问顺序的一种控制机制&#xff0c;用来保证在多核处理器环境下&#xff0c;内存访问的正确顺序&#xff0c;避免因乱序执行导致的数据一致性问题。它是操作系统内核…...

Vue中的 VueComponent

VueComponent 组件的本质 Vue 组件是一个可复用的 Vue 实例。每个组件本质上就是通过 Vue.extend() 创建的构造函数&#xff0c;或者在 Vue 3 中是由函数式 API&#xff08;Composition API&#xff09;创建的。 // Vue 2 const MyComponent Vue.extend({template: <div…...