【Linux篇章】Linux 进程信号2:解锁系统高效运作的 “隐藏指令”,开启性能飞跃新征程(精讲捕捉信号及OS运行机制)
本篇文章将以一个小白视角,通俗易懂带你了解信号在产生,保存之后如何进行捕捉;以及在信号这个话题中;OS扮演的角色及背后是如何进行操作的;如何理解用户态内核态;还有一些可以引出的其他知识点;如:可重入函数,volatile关键字;SIGCHLD信号等一些相关知识点的介绍;欢迎大家阅读!!
欢迎大家支持:
羑悻的小杀马特.-CSDN博客
欢迎拜访:羑悻的小杀马特.-CSDN博客本篇主题:秒懂百科之Linux进程信号2(捕捉信号及OS如何运转等)
制作日期:2025.05.12
隶属专栏:linux之旅
目录
一·信号捕捉流程:
二·操作系统是如何运行的:
2.1硬件中断:
2.2OS的时钟中断与死循环:
2.3软中断:
三·何为用户态与内核态:
四·可重入函数:
五·volatile关键字:
六·SIGCHLD信号:
七·本篇小结:
一·信号捕捉流程:
在前一篇;我们讲了信号产生与保存;下面具体讲一下它是如何被捕捉的:
那么信号是如何被执行的呢?
还是先看一张图:
这里就是在执行的时候进行了四次用户态与内核态的交互来完成的来完成了信号的发送与执行;下面我们以一个具体的例子来讲述一下(这里我们以上一篇讲过的硬件异常引起的3号信号为例子)
对于这里执行完自定义处理方式是如何返回内核态:
这里在执行这个自定义捕捉方法的时候需要展开栈;在展开之前先保存这个回内核的入口;然后在展开进行执行;执行完后销毁这块栈;然后就从返回地址处开始走;就回到内核了。
解释一下:首先遇到了除0;cpu内计算出现错误(硬件异常引起软中断);产生中断号给os【用户态到内核态】;然后os去执行对应的任务:给当前进程发送3号信号以及进行相关信息收集;然后 os就会检测pending表看是否有信号没处理如果是默认或者忽略就直接执行如果是自定义类型就跑到用户态执行自定义的代码【内核到用户态】;然后执行完就返回内核态【用户态到内核态】;然后再看看是否还有;当没有了就返回用户去执行下面逻辑代码【内核态到用户态】。
为了简化一下我们可以根据图这么来理解这个过程:
这四个交点就是上面我们所说的那几个转换状态;然后把上面的例子带进去即可。
二·操作系统是如何运行的:
对于操作系统是如何运行;可以分为下面几个部分来理解:
2.1硬件中断:
首先先要明白:
中断向量表就是操作系统的⼀部分,启动就加载到内存中了。
通过外部硬件中断,操作系统就不需要对外设进⾏任何周期性的检测或者轮询。
由外部设备触发的,中断系统运⾏流程,叫做硬件中断。
下面我们举个例子理解下:
当外设(如键盘)输入时候;中断控制器的对应位置的中断号就变成0;然后就会通过cpu针脚被cpu识别到;然后本身有注册的中断向量表;根据中断号执行对应比如键盘的任务等。
中断向量表IDT有很多对应方法:
set_trap_gate(0,÷_error);// 设置除操作出错的中断向量值。以下雷同。
set_trap_gate(1,&debug);
set_trap_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&reserved);
set_trap_gate(16,&coprocessor_error);
os就可以拿着对应中断号去匹配执行它;说白了;它就是个函数指针数组而已;此时;OS再也不关注,外部设备是否准备好,而是它准备好,会叫我!!
下面我们以键盘输入然后写入文件然后再访问打印在屏幕上过程结合上面的硬件中断配合冯.诺依曼体系理解下:
2.2OS的时钟中断与死循环:
可以理解成在外设或者cpu内部有个时钟;每一定时间就会响然后告诉os去检查是否存在中断号然后去执行对应任务。
下面看张图:
时钟可以在外面但是大部分都集中在cpu内;也就是它会按照频率去响:
下面我们以进程调度为例子来说明时钟中断是怎么进行的:
os会在时钟的一定频率催促下去检查是否存在中断号去执行;这里我们可以这么认为->只要cpu内的时钟一响;os就拿到对应中断号去执行相关进程调度工作看是否可以执行:
这里时间片可以粗略理解为每个进程被分配的执行任务的时间;当到0后就会跑去等待队列,换其他进程来执行;等待下一次被分配时间片再去执行任务(不考虑优先级进程)。
这里counter就可以理解成时间片。
这里可以理解成调用进程完成任务;如果当前进程时间片还没有到时就继续走它的任务;到时间了就换另一个进程来替他;把原先的放入等待队列休息
所以说对于os只能喂它指定的中断号它才会动否则死死的呆在那里。
os:
void main(void)
{
for (;;)
pause();
}
pause只有接收到相关中断号才回干别的事情;但是干完别的事情又会回到这个for循环。
操作系统不就可以躺平了吗?对,操作系统自己不做任何事情,需要什么功能,就向中断向量表里面添加方法即可.操作系统的本质:就是一个死循环!
操作系统依赖于中断号和中断向量表。
这里对于任务0比较特殊;当os处于悠闲即pause状态;它就会时刻执行0任务;看是否还有其他中断号促使它去执行 。
2.3软中断:
其实就是用软件来模拟硬件中断。
这里软中断也是通过中断向量表处理的一部分;下面请看图:
这里就是cpu内部会通过某些能发生软中断的方法产生执行软中断对应的中断号以及处理软中断的系统调用号等。
也就是处理软中断的方法中有是一个函数指针数组;所有的系统调用都会被os转化成一个系统调用号然后去调用指定的那些函数。
从此往后,对于软中断而言;每一个系统调用,都有一个唯一的下标;叫做系统调用号。
/ 系统调⽤函数指针表。⽤于系统调⽤中断处理程序(int 0x80),作为跳转表。
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid, sys_setregid
};
下面我们细谈一下:
为了让操作系统支持进行系统调用,CPU也设计了对应的汇编指令(int或者syscall),可以让CPU内部触发中断逻辑(也就是说这俩会直接产生对应去处理软中断的中断号)。
int0x80:x86
syscall:x64
本质就是用汇编代码搞成的相关指令集,可以让CPU内部触发中断逻辑(软中断号)。
当出现能引起软中断的事件时;在它俩作用下就会陷入内核;也就是系统去执行对应的软中断处理方法(比如之前讲的硬件中断:除0;野指针;缺页中断都是软中断;它们对应的方法都会被注册在上面的那张表中)
下面我们以fork函数为例子来探究下软中断是如何进行的:
先普及个知识:我们调用的fork函数常说是系统调用;其实它是被glibc封装的;也就是说它不是真正的系统调用(它内部会调用c+汇编写的一个函数;也就是真正的系统调用来完成;因为os只认识系统调用号)。
我们调用类似fork等系统接口;他就会自动给os一个系统调用号;然后os拿着它去执行对应任务。
下面看张图清晰了解下:
这里syscall会陷入内核给os对应中断号告诉os将要去执行软中断处理 ;对于我们用户看到的fork函数或者其他系统调用函数有参数最后都会变成汇编然后被os知道的;因此我们只要明白这个os是如何处理这个软中断的就行;完成后操作系统通过寄存器或者⽤⼾传⼊的缓冲区地址。
对于软中断;得出结论:OS不提供任何系统调用接口! ! OS只提供系统调用号!! ! ;OS只知道对应的系统调用号和对应的调用表。
因此我们可以总结一下:
比如调用的fork open等函数其实都是libc库封装的系统调用(汇编+c):
给用户端呈现的是那些;然后其实里面套了层c+汇.编:拿到用户输入的位置权限等;搞成汇编或者c打包然后把调用号给os;让os知道;os就会调用指定任务+打的包然后进行执行。
软件中断可以分为所有的系统调用函数,除0,野指针,缺页中断等等都输入软中断。
缺⻚中断?内存碎⽚处理?除零野指针错误?这些问题,全部都会被转换成为CPU内部的软中断,然后⾛中断处理例程,完成所有处理。有的是进⾏申请内存,填充⻚表,进⾏映射的。有的是⽤来处理内存碎⽚的,有的是⽤来给⽬标进⾏发送信号,杀掉进程等等。
比如;它们通过系统调用号执行对应任务:
void trap_init(void)
{
int i;
set_trap_gate(0,÷_error);// 设置除操作出错的中断向量值。以下雷同。
set_trap_gate(1,&debug);
set_trap_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&reserved);
set_trap_gate(16,&coprocessor_error);
}
因此,对于软中断;我们再做最后的总结:
操作系统就是躺在中断处理例程上的代码块!
CPU内部的软中断,⽐如int0x80或者syscall(系统调用),我们叫做陷阱(故意陷入内核)。
CPU内部的软中断,⽐如除零/野指针等,我们叫做异常。
三·何为用户态与内核态:
首先看张图:
用户态:以用户身份,只能访问自己的[0,3GB]
内核态:以内核的身份,运行你通过系统调用的方式,访问OS[3,4GB]
操作系统也是软件也是有一定内存的。
这里如果有多个进程;那么就有多个不同虚拟地址空间;然后用户级页表也是多个;但是内核级页表只有一个。
我们不难发现os在操作系统只有一份:然后被各个经常映射到自己的虚拟地址空间--->因此每个进程都共亨同一个os;这意味着:操作系统无论怎么切换进程,都能找到同一个操作系统!换句话说操作系统系统调用方法的执行,是在进程的地址空间中执行的!
那么此时可能有一个问题:
用户和内核,都在同一个[0,4GB]的地址空间上了;如果用户,随便拿一个虚拟地址[3,4GB],用户不就可以随便访问内核中的代码和数据了吗?
当然os也不是傻子;因此又出了一套机制:
os只允许使用系统调用;且只能以os身份去访问这块物理内存。
也就是用户态如果出来先系统调用函数就会走int0x80 sysca11 那—套然后陷入内核进行调用号进行相关系统调用。
对于如何判断是内核态还是用户态;这里粗略理解下:
cpu内部cs段寄存器有标志位:
3:用户;0:内核;当进程从用户区跑到内核区的时候;它会进行检查;符合那么就放行否则拦截。
下面我们距离说明一下:
用户拿着自己0-3g的虚拟地址访间用户态无关;﹔比如我们此时调用os的fork进行访间内核区执行系统调用﹔首先这个fork先会把对应的权限改成0﹔然后陷入了内核区;此时cs检测到允许访间;然后fork又把调用号告诉os;让它执行对应系统调用任务,搞完后返回用户态;此时cs标记位再次恢复3;每一次执行系统调用就这样来---->确保了操作系统只允许自己的os调用陷入内核去访间自己。
四·可重入函数:
首先先来个图:
原本是把两个节点头插;但是由于设置了自定义信号捕捉导致如果突然收到信号会立刻去处理它(此时不能保证刚才是否头插完成)然后就会如图所示导致两次并没有成功完成头插--->因此这个insert函数就是不可重入函数。
因此这里又划分了可重入与不可重入函数。而stl里面其实大多数都是不可重入函数。
一般像全局的变量之类的一般函数都是不可重入的(因为做了修改;比如再次重入会有判断;这样就会误判)﹔而局部的话;再次重入某些量还是不变的;因此即可重入。
判断不可重入函数的条件:
调⽤了malloc或free,因为malloc也是⽤全局链表来管理堆的。
调⽤了标准I/O库函数。标准I/O库的很多实现都以不可重⼊的⽅式使⽤全局数据结构。
五·volatile关键字:
保持内存的可⻅性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进⾏操作。
该关键字在C当中我们已经有所涉猎,今天我们站在信号的⻆度重新理解⼀下;
首先我们先看个例子:
写一个如下的代码:
#include <stdio.h>
#include <signal.h>
volatile 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;
}
效果:
下面来解释一下:
这里f1ag确实改了但是修改的是内存里的f1ag;当执行完二号信号﹔我们回到main函数的时候;它所取的f1ag是在寄存器中而不去内存了。
如果编译器优化:这里编译器发现f1ag的值当一直在执行死循环发现它不变就会直接放到寄存器中(一般可以放不变的量;到时侯不用去内存看了)﹔因此就对f1ag变成了内存不可见,因此即便我们修改了内存的f1ag的值;它仍旧是从寄存器取;故还是拿到0。
如图:
加上volatile关键字后:
六·SIGCHLD信号:
简单理解成子进程退出时候给给父进程发送的信号。
默认处理方式是忽略;也就是不回收这个发信号的子进程。
因此我们可以和父进程回收子进程结合起来;使用自定义捕捉来;如果收到这个信号就进行捕捉;然后进行回收。
下面我们来演示下:
有十个进程;我们使用阻塞状态来回收;看一下效果:
测试1代码:
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <sys/wait.h>void chld(int sig){while (true){pid_t n = waitpid(-1, nullptr,0); // waitpid默认是阻塞的!}}
int main()
{// 父进程signal(SIGCHLD, chld); // 父进程for (int i = 0; i < 10; i++){pid_t id = fork(); if (id == 0){sleep(3);exit(3);}}while (true){std::cout << "I am fater" << std::endl;sleep(1);}return 0;
}
发现会阻塞:
首先父进程会走3s打印三次;然后就收到信号父进程就会跑到自定义捕捉函数里进行循环( sigch1d只接受一次,屏蔽了)·然后当回收完十次还是处于阻塞状态不是我们想要的。
因此下面我们采用非阻塞(WNOKANG):
这里考虑了可能很多孩子同时退出故写成循环形式来确保都回收了(结合执行信号时候b1ock的标记)。
使用检测脚本来时刻看着进程情况:
while :; do ps ajx|head -1 && ps ajx lgrep a.out; s1eep 1; done
这里如果还有子进程没有退出就返回0﹔如果子进程全部退出就返回-1,而后面我们创建10个子进程但是只回收6个。
发现确实退出了6个:
测试2代码:
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <sys/wait.h>void chld(int sig){while (true){//这里考虑了可能很多孩子同时退出故写成循环形式来确保都回收了pid_t n = waitpid(-1, nullptr, WNOHANG); if (n == 0){break;}else if (n < 0){std::cout << "waitpid error " << std::endl;break;}else{ std::cout<<"回收孩子的pid: "<<n<<std::endl;}}
}
int main()
{// 父进程signal(SIGCHLD, chld); // 父进程for (int i = 0; i < 10; i++){pid_t id = fork(); if (id == 0){sleep(3);if(i < 6){std::cout << "I am child, exit" << std::endl;exit(3);}else pause();}}while (true){std::cout << "I am fater" << std::endl;sleep(1);}return 0;
}
因此上面的办法是可行的;但是过于麻烦;下面对应SIGCHLD这个信号的忽略函数就支持了这个功能:SIG_IGN。
只是这里稍作修改:
这两种方法区别。如果自定义可以查看孩子pid及孩子的信息,但是这种忽略作为处理函数只能防止僵尸进程出现﹔无法查看pid等孩子的信息,因此根据需要选择。
有个疑问:这里默认不就是忽略吗;为什么还要SIG_IGN?
解释下:
SIGCHLD默认处理方式(SIG_DFL)是忽略不管它;也就是让僵尸进程产生了;但是如果我们传递SIG_IGN是走它忽略的处理方式并不是真正的忽略。即如果是SIG_IGN;它内置处理就会回收僵尸进程;如果是SIG_DFL它内置就啥也不做。
七·本篇小结:
本篇博主同样是根据自己学习然后根据整理的笔记;进行了一次博文疏导;也起到了一定查漏补缺作用;重温了信号捕捉流程;OS背后是如何运行的一些原理;以及根据信号章节透露出的一些其他小知识点;信号章节分为两篇(只不过这里目前只研究的是普通信号;对于实时信号还未涉及)也就到此结束;希望对大家学习普通信号这一块有所帮助!!!
相关文章:

【Linux篇章】Linux 进程信号2:解锁系统高效运作的 “隐藏指令”,开启性能飞跃新征程(精讲捕捉信号及OS运行机制)
本篇文章将以一个小白视角,通俗易懂带你了解信号在产生,保存之后如何进行捕捉;以及在信号这个话题中;OS扮演的角色及背后是如何进行操作的;如何理解用户态内核态;还有一些可以引出的其他知识点;…...

多功能秒达开源工具箱源码|完全开源的中文工具箱
源码介绍 完全开源的中文工具箱永远的自由软件轻量级运行全平台支持(包括ARMv8)类似GPT的智能支持高效UI高度集成提供Docker映像和便携式版本支持桌面版开源插件库 下载地址 百度网盘下载 提取码:p9ck ▌本文由 6v6-博客网 整理分享 ▶ 更多…...
如何在腾讯云 OpenCloudOS 上安装 Docker 和 Docker Compose
从你提供的 /etc/os-release 文件内容来看,你的服务器运行的是 OpenCloudOS 9.2。这是一个基于 CentOS 和 RHEL 的开源操作系统,因此它属于 CentOS/RHEL 系列。 关键信息总结 操作系统名称:OpenCloudOS版本:9.2ID:op…...
清理skywalking历史索引
import requests from datetime import datetime, timedelta import os import re# 配置参数 ES_HOST os.getenv("ES_HOST", "http://192.168.0.250:9200") # ES地址 ES_USER os.getenv("ES_USER", "") # 用户…...

用nz-tabel写一个合并表格
用nz-tabel写一个合并表格 <nz-table #basicTable [nzData]"tableSearchStatus.dataList" nzBordered><thead><tr><th>班级</th><th>姓名</th><th>年龄</th><th>电话</th></tr></thead&…...
matlab计算转子系统的固有频率、振型、不平衡响应
可以计算转子系统的固有频率、振型、不平衡响应 MatrixRiccati/code/Dichotomy_1 (2).m , 2210 MatrixRiccati/code/Dichotomy_1.m , 2210 MatrixRiccati/code/RiccatiSY_1.m , 2756 MatrixRiccati/code/Trans1x (2).m , 451 MatrixRiccati/code/Trans1x.m , 451 MatrixRiccat…...

leetcode hot100刷题日记——29.合并两个有序链表
解答: 方法一:递归 递归的边界条件是啥呢? 递归别想那么多具体步骤,考虑大步骤,小的递归自己会去做的 class Solution { public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {//递归比较大小//先考虑…...
【机器人】具身导航 VLN 最新论文汇总 | Vision-and-Language Navigation
本文汇总了具身导航的论文,供大家参考学习,涵盖2025、2024、2023等 覆盖的会议和期刊:CVPR、IROS、ICRA、RSS、arXiv等等 论文和方法会持续更新的~ 一、🏠 中文标题版 2025 😆 [2025] WMNav:…...
Windows 安装 WSL2 并运行 Ubuntu 22.04 指南
Windows 安装 WSL2 并运行 Ubuntu 22.04 指南 本文为 Windows 10 和 Windows 11 用户提供从零开始搭建 WSL2 环境的详细指南,涵盖安装 Ubuntu 22.04、自定义存储位置、性能优化以及常见问题排查。无论你是开发者、运维工程师还是 Linux 爱好者,本文将助你…...
AI情感陪伴在医疗领域的核心应用潜力
一、精准情绪监测与干预 多模态情感识别系统 通过整合语音语调分析(降调与语速异常检测抑郁倾向)、微表情捕捉(面部肌肉运动追踪焦虑状态)、生理指标监测(心率变异幅度反映应激水平)等技术,构建…...

【计算机网络】第1章:概述—分组延时、丢失和吞吐量
目录 一、分组延时、丢失 1. 节点处理延时: 2. 排队延时: 3. 传输延时: 4. 传播延时: 5. 节点延时 6. 排队延时 7. 分组丢失 二、吞吐量 三、总结 (一)分组延时 1. 处理延时(Processing Delay) …...
Python Day38
Task: 1.Dataset类的__getitem__和__len__方法(本质是python的特殊方法) 2.Dataloader类 3.minist手写数据集的了解 1. Dataset 类的 __getitem__ 和 __len__ 方法 在 PyTorch (或类似深度学习框架) 中,Dataset 是一个抽象基类&a…...

DeepSeek R1 模型小版本升级,DeepSeek-R1-0528都更新了哪些新特性?
DeepSeek-R1‑0528 技术剖析:思维链再进化,推理性能飙升 目录 版本概览深度思考能力再升级基准测试成绩功能与体验更新API 变动与示例模型开源与下载结语 版本概览 DeepSeek 团队今日发布 DeepSeek‑R1‑0528 —— 基于 DeepSeek V3 Base(2…...
线路板厂家遇到的PCB元件放置的常见问题有哪些?
印刷电路板现在无处不在。尽管大多数人认为这是理所当然的,但工程师和设计师们充分意识到这些电路开发和生产背后的巨大努力。传统的PCB生产涉及复杂的机械和高昂的前期成本,因此必须将制造外包给专业工厂。 说到交货时间,你可能需要几周的时…...
【C/C++】无限长有序数组中查找特定元素
在无限长有序数组中查找特定元素,由于数组长度未知,需先定位搜索范围,再进行二分查找。以下是C实现: #include <iostream> #include <vector> #include <climits> using namespace std;// 假设数组访问函数&am…...

SQL正则表达式总结
这里写目录标题 一、元字符二、正则表达函数1、 regexp_like(x,pattern[,match_option])2、 regexp_instr(x,pattern[,start[,occurrence[,return_option[, match_option]]]]) 3、 REGEXP_SUBSTR(x,pattern[,start[,occurrence[, match_option]]]) 4、 REGEXP_REPLACE(x,patter…...

力扣经典算法篇-13-接雨水(较难,动态规划,加法转减法优化,双指针法)
1、题干 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3…...

STM32 -- USB虚拟串口通信
本篇操作: 通过CubeMX Keil,配置STM32作为USB设备端,与电脑上位机进行通信(CDC);通用带USB功能的 STM32 芯片 (如F1、F4等,系统时钟配置不同,代码通用)。 目录 一、 S…...

uni-app开发特殊社交APP
uni-app开发特殊社交APP 目录 1.展示APP功能 2.展示项目结构 3.关于我的GitHub 引言 博主最近自己在GitHub上面上传了一个关于社交软件的项目(该项目早已开发完毕), 这个社交软件比较特殊, 被称之为blind-date, blind-date 是基于 uni-…...

Linux中Shell脚本的常用命令
一、设置主机名称 1、通过修改系统文件来修改主机名称 [rootsakura1 桌面]# vim /etc/hostname sakura /etc/hostname:Linux 系统中存储主机名的配置文件。修改完文件后,在当前的shell中是不生效的,需要关闭当前shell后重新开启才能看到效…...

RabbitMQ项目实战
先参考文章:(必看) 06-MQ基础_mq服务-CSDN博客 07-MQ高级(幂等性)-CSDN博客 https://cloud.iocoder.cn/message-queue/rabbitmq/#_2-0-%E5%BC%95%E5%85%A5%E4%BE%9D%E8%B5%96%E4%B8%8E%E9%85%8D%E7%BD%AE 1、Rabbi…...

安卓开发用到的设计模式(3)行为型模式
安卓开发用到的设计模式(3)行为型模式 文章目录 安卓开发用到的设计模式(3)行为型模式1. 命令模式(Command Pattern)2. 策略模式(Strategy Pattern)3. 观察者模式(Observ…...
生成模型:从数据学习到创造的 AI 新范式
一、生成模型:定义与核心逻辑 生成模型是一类通过学习数据潜在分布来创造新样本的机器学习模型。其核心目标是构建数据的概率分布模型 P(X),使生成的样本 X^ 与真实数据 X 具有相似的统计特征。 1.1 与判别模型的本质区别 维度生成模型判别模型核心目…...

尚硅谷redis7 90-92 redis集群分片之集群扩容
90 redis集群分片之集群扩容 三主三从不够用了,进行扩容变为4主4从 问题:1.新建两个redis实例,怎么加入原有集群?2.原有的槽位分3段,又加进来一个槽位怎么算? 新建6387、6388两个服务实例配置文件新建后启…...
RabbitMQ性能调优:关键技术、技巧与最佳实践
RabbitMQ作为一款高可靠、高扩展性的消息中间件,其性能表现直接影响到分布式系统的吞吐量和响应延迟。本文基于RabbitMQ官方文档和最佳实践,结合核心性能优化方向,详细探讨RabbitMQ性能调优的关键技术、技巧和策略。 通过以下优化策略&#…...
系统架构中的组织驱动:康威定律在系统设计中的应用
康威定律(Conway’s Law) 是由计算机科学家 Melvin Conway 在1967年提出的理论,其核心观点是:“系统的架构设计会不可避免地反映其开发组织的沟通结构。换句话说,软件系统的结构会与构建它的团队的组织结构高度相似。 …...
TypeScript 中高级类型 keyof 与 typeof的场景剖析。
文章目录 前言一、typeof:从值到类型的映射1. 核心概念2. 类型推导示例3. 常见用途 二、keyof:从类型到键的映射1. 核心概念2. 常见用途 三、typeof keyof:强强联合的实战场景1. 场景一:对象属性的安全访问2. 场景二:…...
Android LiveData 详解
一、LiveData 核心概念与特性 1.1 定义与基本功能 LiveData 是 Android Jetpack 架构组件中的一个可观察数据持有者类,其核心功能是实现数据与 UI 的响应式绑定。与传统观察者模式不同,LiveData 具有生命周期感知能力,能够自动根据观察者…...
为什么共现矩阵是高维稀疏的
为什么共现矩阵是高维稀疏的? 共现矩阵(Co-occurrence Matrix)的高维稀疏性是其固有特性,主要由以下原因导致: 1. 高维性的根本原因 词汇表大小决定维度: 共现矩阵的维度为 ( V \times V ),其…...

离散化算法的二分法应用
我们思考一个问题:其实这里的二分法回归本源也是基于下标映射的原理,只是实现是借助二分的形式。 在排序好的数组中对目标数值进行二分搜索,在 O(logn) 的时间复杂度内找到该数值是整体数据中的第几个。 具体的我们可以如下操作: …...