【Linux】进程 信号的产生

news2025/5/30 13:32:12

🌻个人主页:路飞雪吖~

       🌠专栏: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信号的基本概念

// Makefile

BIN=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);// 默认终止 --改成了--> 执行自定义方法:Handler

    for (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. 系统调用 发送信号

// Makefile

BIN=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>


// 设置自己的kill

void 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  \( •̀ ω •́ )/

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

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

相关文章

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

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;这通…

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

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

谷歌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…

程序环境与预处理

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

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

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

C语言数据结构-单向链表

头文件&#xff1a;link.h #ifndef __LINK_H__ #define __LINK_H__ #include <stdio.h> #include <stdlib.h> typedef int DataType; /*节点数据类型*/ typedef struct node { DataType data; //数据域 struct node *pNext; //指…

小样本分类新突破:QPT技术详解

问题导向式提示调优(QPT) 这篇论文主要讲了一个针对小样本(数据量少)文本分类问题的新方法,叫问题导向式提示调优(QPT)。 核心思路是让预训练语言模型(比如BERT的升级版RoBERTa)在少量标注数据下,通过设计特定的“提问式模板”和“标签词扩展技术”来提升分类效果。…

Excel常用公式全解析(1):从基础计算到高级应用

Excel常用公式全解析&#xff1a;从基础计算到高级应用 目录 Excel常用公式全解析&#xff1a;从基础计算到高级应用[toc](目录)一、基础计算类&#xff1a;数据运算的基石1. 求和公式&#xff08;SUM&#xff09;2. 平均值公式&#xff08;AVERAGE&#xff09;3. 最值与计数公…

与 PyCharm 官方沟通解决开发环境问题记录(进展:官方已推出2个新的修复版本)

​​​​​​主题&#xff1a;有关 PyCharm 中终端和环境激活问题的反馈&#xff1a;PY-81233 前言 目前进展&#xff1a; 官方已有2个修复版本推出测试。 更新方法&#xff1a; 使用JetBrains Toolbox App&#xff0c;如下图所示&#xff0c;从“其他版本”进入查看更新。…