[Linux] 进程信号概念 | 信号产生

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:青果大战linux

总有光环在陨落,总有新星在闪烁

为什么我的课设这么难啊,久久叔叔吧,悲,模电要挂了


信号的概念

信号和信号量没有任何关系,他们就是老婆和老婆饼的关系

信号是进程之间事件异步通知的一种方式,属于软中断。
  • 同步(Synchronous)
    • 定义:同步操作是一种按照顺序依次执行的方式。在同步模式下,一个任务必须等待前一个任务完成后才能开始。可以把它想象成一个餐厅,顾客(程序)点完菜(发起任务)后,顾客什么都不干,等菜上来了,开始吃饭,这就是同步
  • 异步(Asynchronous)
    • 定义:任务的发起和完成不需要严格按照顺序。当一个异步任务被发起后,程序不会等待这个任务完成,而是可以继续执行其他任务。当异步任务完成时,会通过某种方式(如回调函数、事件通知等)通知程序。可以把它想象成一个餐厅,顾客(程序)点完菜(发起任务)后,可以做其他事情,比如聊天、看手机,等菜做好了(任务完成),服务员会通知顾客,这就是异步
    • 示例:在 JavaScript 中,使用setTimeout()函数就是一种异步操作。例如,setTimeout(() => console.log("Hello"), 1000);会在 1 秒后打印 “Hello”,但是在这 1 秒内,程序可以继续执行其他代码,而不是等待这个打印操作。
  • 因为信号也是由进程发送的,所以当一个进程正常运行的时候,系统收到了比如杀死这个进程的信号,那么就会有一个进程A作为信号去终止该进程B。但是B进程是不会等信号来的,而是一直做自己的事情。

可以通过指令kill -l来查询linux所支持的常见信号:

这里的信号如一号信号SIGHUP都属于宏,他们的值就是他们的编号,SIGHUP的值就是1. 

  • [1, 31]:这些信号称为非实时信号,当进程收到这些信号后,可以自己选择合适的时候处理
  • [34, 64]:这些信号称为实时信号,当进程收到这些信号后,必须立马处理
  • 实时操作系统:对外部事件响应有严格时间要求,必须在规定时间内作出响应。在任务调度上,采用优先级抢占式和时间片轮转(同优先级)调度,确保关键任务优先执行。用于工业控制、航空航天、医疗设备等对时间敏感的领域。
  • 非实时操作系统:没有严格时间限制,注重通用功能。在任务调度上,有优先级调度(非严格抢占)和公平共享调度,平衡资源分配。用于个人桌面和部分服务器领域,对响应时间要求不高。

事实上,大多数计算机都是非实时的,因此我们今天只学习非实时信号。

进程是如何认识信号的

进程识别信号,由程序员内置的特性,信号的处理方法,在信号产生前就设置好了

就像你在第一次过马路之前,就先被别人告诉了红灯停,绿灯行的信号处理方法

信号会被立刻处理吗

处理信号,不一定是立即处理的,而是选取一个合适的时候

因为当前做的事情的优先级可能比处理信号这件事更高,

处理信号的方法

  1. 默认方法

  2. 忽略该信号(忽略本身也是一种处理方法!!)

  3. 自定义处理方法

 signal

 signal函数是在 Unix、Linux 等操作系统中用于设置信号处理方式的函数。

参数解释

  1. signum:要设置处理方式的信号编号。

  2. handler:是一个函数指针,指向当接收到signum信号时要执行的函数。这个函数应该有一个int类型的参数(用于接收信号编号),并且返回值为void

  3. 如果将handler参数设置为SIG_DFL,则表示当接收到指定信号时,采用系统默认的处理方式。如果将handler参数设置为SIG_IGN,则表示当接收到指定信号时,进程将忽略该信号。也可以编写一个自定义的函数,然后将函数指针传递给signal函数作为handler参数。

由于信号不一定是被立即处理,所以在信号接受和信号处理之间,还有一个信号保存(或者叫信号记录)的操作,防止进程忘记处理。

信号记录

这里我们要注意,信号的编号是1到31,那么请问如何记录信号呢?位图出场了

因此发送信号的本质就是OS把task_struct中signalbitmap的位图的某个比特位从0置1

当然OS是有这个权力的,毕竟OS是进程的管理者,但是也只有它可以,因为他是唯一管理者

计算机中,无论是硬件还是软件的何种方式发送信号,归根结底到最后都是OS修改位图


信号产生

1.键盘产生

ctrl+c:结束前台进程,后台不行(对前后台不懂的可以去这篇博客前台进程与后台进程)

#include<bits/stdc++.h>
#include<unistd.h>
using namespace std;
int main(){
    while(true){
        sleep(1);
    cout<<"Hello"<<endl;
    }
    return 0;
}

显然我们这里输入了ctrl+c,于是进程被结束了。

ctrl+c本质就是向前台进程发送了2号信号(SIGINT)。他的默认处理方式是终止该进程

#include<bits/stdc++.h>
#include<unistd.h>
#include<signal.h>
using namespace std;
void Handler(int sign_num){
cout<<"Get asignal ,it is "<<sign_num<<endl;
}
int main(){
    signal(2,Handler);
    while(true){
        cout<<"hello"<<endl;
        sleep(1);
    }
    return 0;
}

这里我们把二号命令的处理方式修改为了我们自定义的方法,这就是自定义处理,当然也证明了ctrl+c确实是二号命令 

如果你想结束进程可以ctrl+\,他是三号命令

通过man 7 signal可以查看更详细的信号信息

2号和3号的描述是“被键盘中断了”。这里的Core和Term都表示终止进程

我们这里验证一下三号

#include<bits/stdc++.h>
#include<unistd.h>
#include<signal.h>
using namespace std;
void Handler(int sign_num){
cout<<"Get asignal ,it is "<<sign_num<<endl;
}
int main(){
    signal(2,Handler);
     signal(3,Handler);
    while(true){
        cout<<"hello"<<endl;
        sleep(1);
    }
    return 0;
}

可以看到ctrl+\也结束不了进程了。

signal函数是放在while循环之前的,为什么呢

signal函数对一个信号处理方式的修改只需要设置一次足以,在这之后进程会记录该信号的被修改的处理方式,因此在while循环中,当我们再去ctrl+c时,就会触发被记录的新的处理方式

如果没有产生2号三号信号呢

那么handler函数永远不会被调用

我们的前31号信号大多数都是终止信号,那么要是我们把所有信号都捕捉,按照自定义的方式处理,那我们的死循环代码是不是就无法被结束了

我们在另一个窗口输入kill -n pid,貌似真的杀不了它了

好了逗你的,其实我们的19号命令(SIGSTOP)和9号命令(SIGKILL)是无法被自定义捕捉的,这就是为防止用户把所有能终止的信号都自定义了,然后没法结束进程了

硬件中断

键盘产生信号本质是OS获取并且识别了键盘上的ctrl+c组合键

那么请问,OS怎么知道键盘上有数据了呢,难道是死循环不停的检测键盘的状态?

那么鼠标呢,显示器么,网卡,磁盘呢?难道这些外设都是死循环检测,那会忙死的

ok这里就要来点硬件电路知识了

  • 硬件中断是一种硬件机制,用于通知 CPU(中央处理器)有一个需要立即处理的事件发生。这些事件通常来自外部设备,如键盘、鼠标、磁盘驱动器、网络接口卡等。当外部设备需要 CPU 的注意时,它会发送一个中断信号给 CPU。例如,当你按下键盘上的一个键时,键盘控制器会向 CPU 发送一个中断信号,告诉 CPU 有按键事件发生,CPU 会暂停当前正在执行的任务,cpu会把信号传递给OS,表示键盘资源准备好了,于是OS就会去键盘读取信息,这样OS就不用死循环检测键盘了,其他外设也是同理

可能有细心的同学想到了,之前学了冯诺依曼体系结构,说键盘等外设是不能直接和OS交互的

那么这个硬件中断的信号,是不是违背了冯诺伊曼,

是的,对于该信号,键盘和cpu直接交互了,这个设置你可以理解一种特殊处理

于是硬件就可以和OS并行执行了,OS先给一个硬件发送某种工作信号(比如读写磁盘时要先让磁盘进行寻址工作),然后硬件进行工作,与此同时OS并没等带硬件把工作做完,而是继续忙他自己的事情,比如管理以下文件,给别的软件下达指令,当硬件把事情做完了,就会通过中断告诉OS资源已经就绪,这个时候OS再回来检查结果就好了

至于硬件中断是怎么实现的,先别管了

OS依靠中断管理硬件,同理也可以靠中断管理软件,这种靠中断管理软件的操作就是信号

所以信号本质就是对硬件中断操作的模拟

现在我们再看ctrl+c

键盘按下,向cpu发送硬件中断,cpu去告知OS键盘资源就绪,OS去读取键盘获取了ctrl+c的组合键,将他解释为二号命令,然后把二号命令写入前台进程的signalbitmap位图中,前台进程会等到一个合适的实际去处理该信号


2.指令产生

我们之前的kill -9 [进程pid]就是靠指令产生信号发送给目标进程

kill -n [进程pid]把n号命令发送给目标进程


3.函数调用产生信号

kill

  • pid:收到该信号的进程的pid

  • sig:发送哪一个信号

  • 返回0:发送信号成功

  • 返回-1:发送信号失败

是这样的,kill不但是一个指令,而且是一个系统调用

我们当然也可以自己写一个kill指令

#include<iostream>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<signal.h>
using namespace std;
void Usage(string s){
cout<<s<<"-number"<<"  pid"<<endl;
}
int main(int argc,char*argv[]){
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    pid_t id=(pid_t)stoi(argv[2]);
    int i=(int)(argv[1][1]-'0');
    kill(id,i);
}

 raise

raise函数用于向调用该函数的进程发送一个信号。 

  1. rig参数:它代表信号编号。

  2. 如果信号发送成功,函数返回 0。

  3. 如果发送信号失败,函数返回一个非零值。


 abort

用于异常终止调用该函数的进程。它会发送SIGABRT信号

当然了,raise和abort底层都是调用了kill系统接口


4.软件产生

由于软件条件(不具备该条件、具备该条件、条件出错等等)而产生信号:

管道

我们学习管道的时候了解到,如果管道的读端已经关闭了,但是写端还没有关闭,那么OS就会直接终止进程,这个终止本质就是发送了13号命令

读端关闭,可以被认为是管道的读写条件没有准备齐全,于是被终止


alarm

设置一个定时器。当定时器超时后,会向调用进程发送一个14号信号(SIGALRM)。默认是终止进程

 

  1. 参数seconds:用于指定定时器的时长,单位是秒
  2. 返回值alarm函数返回上一个定时器剩余的秒数(如果之前设置了定时器)。如果之前没有设置定时器,或者之前设置的定时器已经响了,返回 0。

  3. 当设置的上一个闹钟还没响,就再次使用alarm函数,会用本次设置的闹钟覆盖掉上一个还没响的闹钟

  4. 参数设置为0,表示取消上一个闹钟

 这里并不是设置了1,2,3,4秒各一个闹钟,而是每次设置都更新,最后只设置了一个四秒的闹钟。

#include<unistd.h>
#include<iostream>
int main(){
alarm(3);
while(true){
    std::cout<<"闹钟没响"<<std::endl;
    sleep(1);
}
}

alarm可以认为是设置了一个定时器,每个进程都可以设置定时器,定时器可以有很多个,这些定时器会被OS管理起来

struct Timer{
    pid_t id;//哪个进程设置的定时器
    struct Timer* Next;
    int end;//定时器什么时候响(时间戳)
    //..........
};

我们可以按照时间戳来建一个小堆,这样每次只要看堆顶元素有没有超时即可

实际OS是依靠链表加哈希的方法,但是为了方便大家理解,就当作小堆即可 

alarm所带来的定时器是会被OS先描述再组织的软件数据结构,当这些软件数据结构的信息准备好了(即闹钟时间到了),OS就会向目标进程发送信号,因此闹钟本质属于软件条件是否满足,而决定是否发送信号

如果通过sleep、pause卡住进程一段时间,而闹钟响的时间是在被卡出期间的,那么当进程继续运行时,就会检测到到闹钟时间已经过了但是还没有向进程发出14号命令,于是就会发出14号命令

#include<unistd.h>
#include<iostream>
#include<sys/types.h>
#include<signal.h>
void handler(int n){
    std::cout<<"闹钟响了"<<std::endl;
}
int main(){
alarm(2);
    signal(SIGALRM,handler);
    sleep(3); 
    int a=alarm(0);
    std::cout<<a<<std::endl;
}

 alarm是一个一次性的闹钟,当时间到了,她会去执行对应的方法,执行完后就会被取消

如果你想一直执行一个闹钟,那么可以把alarm函数放到对14号信号的自定义捕捉中

我们基于此可以设计一个定时处理任务的程序

pause函数会阻塞进程,当接受到除了9号和19号信号之外的信号时,会取消阻塞。 

#include<unistd.h>
#include<iostream>
#include<sys/types.h>
#include<signal.h>
#include<functional>
#include<vector>
using func_t =std::function<void()>;
std::vector<func_t>v;
void handler(int n){
    std::cout<<"闹钟响了,开始写作业"<<std::endl;
    for(auto &i:v)
    i();
    alarm(1);
}

int main(){
alarm(2);
    signal(SIGALRM,handler);
    v.push_back([](){std::cout<<"我是卑微的高数"<<std::endl;});
    v.push_back([](){std::cout<<"我是卑微的模电"<<std::endl;});
    v.push_back([](){std::cout<<"我是卑微的复变函数"<<std::endl;});
   int a=0;
   alarm(1);
   while(true)
   a++;
}

 ........

突然就笑不出来了(悲,期末周去死啊,我还啥都不会呢

但是,如果我们把代码稍微改改呢

#include<unistd.h>
#include<iostream>
#include<sys/types.h>
#include<signal.h>
#include<functional>
#include<vector>
#include<stdio.h>
using func_t =std::function<void()>;
std::vector<func_t>v;
void handler(int n){
    std::cout<<"闹钟响了,OS开始work"<<std::endl;
    for(auto &i:v)
    i();
    //alarm(1);
}

int main(){
    v.push_back([](){std::cout<<"我要刷新内核缓冲区了"<<std::endl;});
    v.push_back([](){std::cout<<"我要检测时间片是否到了,如果到了就切换进程"<<std::endl;});
    v.push_back([](){std::cout<<"我要定期清理内存中的垃圾了"<<std::endl;});
   int a=0;
   alarm(1);
     signal(SIGALRM,handler);
   while(true)
 {  
    pause();
   std::cout<<"行啊"<<std::endl;
 }
}

这时的你,是不是突然发现“原来OS就是这么工作的啊!”

OS就是一个死循环, 他会接受外部的一个固定事件源--时钟中断(集成在cpu内部的),每隔很短的时间他就会想cpu触发硬件中断,OS就是一个中断处理器


5异常产生

我们知道程序除零或者野指针就会崩溃,那么这是为什么呢

因为他们导致进程接受了终止信号。野指针错误对应的是11号信号,除零是8号信号

那要是我们把十一号信号捕捉了呢?

#include<unistd.h>
#include<iostream>
#include<sys/types.h>
#include<signal.h>
#include<functional>
#include<vector>
#include<stdio.h>
void handler(int num){
    std::cout<<"我捕捉了"<<num<<"信号"<<std::endl;
}
int main(){
    signal(11,handler);
    int* a=nullptr;
    *a=100;
    while(1);
    return 0;
}

 结果是handler函数被疯狂调用,

可是按我们的想法,发现野指针,然后想进城发送11号命令,然后执行我们的自定义捕捉,这不就完了吗,怎么会一直捕捉个不停???

OS怎么知道内部出异常了

对于除零问题

cpu中有一个状态寄存器(EFlags),他可以记录cpu的操作有没有出现错误,他上面有一个比特位是溢出标记位,如果该比特位为1,表示计算结果有问题。当CPU出现计算错误时会通知OS,OS当然要知道这件事,因为OS要管理好硬件。接着OS知道后就会杀掉该进程以维护cpu安全

而我们刚才把OS用来杀死进程的信号进行了自定义捕捉,于是进程没有退出,接着继续在while循环中运行,可是状态寄存器中的溢出标记位没有回复为0,于是进程继续在CPU上跑,CPU发现这家伙状态寄存器有个比特位为1,说明有问题,继续告知OS,OS继续发命令,命令继续被我们自定义捕捉,周而复始,即便你的进程暂时被换出,EFLAGS寄存器的值也会作为进程的上下文数据保存在task_struct中,不会置零,下次进程换入还是要报错。

对于野指针问题

CPU中的CR3寄存器会保存页表

MMU这个单元会在页表中,根据虚拟地址查找对应的物理地址,但是对于NULL我们是没有权限进行转化寻找物理地址的,于是MMU这个硬件会报错,MMU中也有类似于状态寄存器的东西,OS当然要直到这件事,剩下的就和上面一样了。


Core VS Term

Term就是终止,没有别的多余操作

Core:

核心转储。除了退出之外,还会在当前目录形成一个文件core.pid,OS会把进程的部分信息保存下来,方便后序调试debug

但是这个文件一般会被云服务器关闭

通过该指令看出,这个pid.core文件大小被设置为0,所以你看不到这个文件了

ulimit -c 1024

这个指令可以设置pid.core文件的大小

我们再写个有除零或者野指针的代码

这时就会发现出现了core.pid,但是如果你的linux内核比较新,那这个文件名就是core 

为什么云服务器要关了它

我们打开它。对于云服务器,假如你的项目因为除零野指针的问题崩溃了,那么他的debug信息就会被写进core.pid文件中,这个进程也会被终止,但是对于这种放在云服务器上的项目,如果他进程被终止了,我们会选择立即重启(自动),因为要保证24h服务啊,不然就差评满天飞了

那么就会每次重启就挂掉,每次都会生成debug文件,那当你发现时你的磁盘都被打满了,那这就又会影响别的模块的服务了

如果你的linux较新,那么他的debug文件就不是core.pid而是core,因为这样即是程序被重启很多次也会把信息打在一个文件里,每次写入问价都是先刷新再写入,这样磁盘就不会被打满了

使用core.pid进行debug

编译链接记得加入-g选项,对生成的exe进行gdb调试 。

在gdb中输入core-file [core.pid],就有详细的报错信息了,被11信号终止,属于内存错误,在第

22行等等信息 

core_domp

现在我们终于可以回答这个第八位是什么了

第八位表示是否发生了core-dump,他表示子进程是否发生了core_dump,我们直接写一个野指针错误,野指针11号信号,属于core类型

#include<unistd.h>
#include<iostream>
#include<sys/types.h>
#include<signal.h>
#include<functional>
#include<vector>
#include<stdio.h>
#include<wait.h>
int main(){
   pid_t id=fork();
   if(id==0)
    {int* a=nullptr;
    *a=100;
    }
    else{
        int st=0;
        waitpid(id,&st,0);
        std::cout<<"core_dump:"<<((st>>7)&1)<<std::endl;
    }
    return 0;
}

 也确实生成了新的core文件

子进程是否出现core_dump取决两个条件:

  1. 生成core文件的功能是否被打开

  2. 该进程是否被core命令终止

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

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

相关文章

流程引擎Activiti性能优化方案

流程引擎Activiti性能优化方案 Activiti工作流引擎架构概述 Activiti工作流引擎架构大致分为6层。从上到下依次为工作流引擎层、部署层、业务接口层、命令拦截层、命令层和行为层。 基于关系型数据库层面优化 MySQL建表语句优化 Activiti在MySQL中创建默认字符集为utf8&…

51c视觉~合集36

我自己的原文哦~ https://blog.51cto.com/whaosoft/12275223 #无监督盲超分算法MLMC 即插即用的解决方案 本文介绍了一种新的无监督盲超分辨率算法MLMC&#xff0c;该算法结合了元学习和马尔可夫链蒙特卡罗核估计&#xff0c;无需监督预训练或参数先验&#xff0c;即可实现…

Firecrawl教程①:自动化抓取与数据转化,赋能AI应用

Firecrawl教程①:自动化抓取与数据转化,赋能AI应用 前言一、功能特点1. 支持 LLM 可处理的数据格式2. 全面抓取网站3. 强大的操作支持4. 灵活的定制选项5. 支持多种编程语言 SDK二、如何开始使用 Firecrawl第一步:获取 API 密钥第二步:官网在线工具使用第三步:安装 Firecr…

关于目标检测YOLO 各版本区别v1-v11/X/R/P

概述 YOLO&#xff08;You Only Look Once&#xff0c;你只看一次&#xff09;是一系列开创性的实时目标检测模型&#xff0c;它们彻底改变了计算机视觉领域。由Joseph Redmon开发&#xff0c;后续版本由不同研究人员迭代&#xff0c;YOLO模型以其在图像中检测对象的高速度和准…

SpringBoot3整合FastJSON2如何配置configureMessageConverters

在 Spring Boot 3 中整合 FastJSON 2 主要涉及到以下几个步骤&#xff0c;包括添加依赖、配置 FastJSON 作为 JSON 处理器等。下面是详细的步骤&#xff1a; 1. 添加依赖 首先&#xff0c;你需要在你的 pom.xml 文件中添加 FastJSON 2 的依赖。以下是 Maven 依赖的示例&#…

java全栈day19--Web后端实战(java操作数据库3)

一、MyBatis 1.1介绍 前提引入&#xff1a; controller(控制层)作用&#xff1a;接受请求&#xff0c;响应数据 service(业务层)作用&#xff1a;负责具体的逻辑处理 dao(持久层)作用&#xff1a;数据访问层 一般的访问流程&#xff1a;浏览器发起请求过来&#xff0c;先…

社区版 IDEA 开发webapp 配置tomcat

1.安装tomcat 2.构建webapp项目结构 3.配置tomcat 安装smart tomcat插件 完成后settings会多一个选项tomcat server&#xff0c;然后我们把本地的tomcat配置过去。 4.为项目配置tomcat 配置项目路径&#xff0c;端口号。Context path 配置/ 表示直接用localhost就能访问 5.添加…

重新定义页签!Choerodon UI Tabs让管理更高效

01 引言 Tabs 组件通过提供平级区域&#xff0c;将大块内容进行有效的收纳和展现&#xff0c;从而保持界面整洁。但在企业应用的快速发展中&#xff0c;这样传统的页签组件已无法满足我们对界面布局和个性化展示的追求。Choerodon UI Tabs 组件通过支持多级分组、个性化配置、…

Qt编译MySQL数据库驱动

目录 Qt编译MySQL数据库驱动 测试程序 Qt编译MySQL数据库驱动 &#xff08;1&#xff09;先找到MySQL安装路径以及Qt安装路径 C:\Program Files\MySQL\MySQL Server 8.0 D:\qt\5.12.12 &#xff08;2&#xff09;在D:\qt\5.12.12\Src\qtbase\src\plugins\sqldrivers\mysql下…

vulnhub靶场【DriftingBlues】之9 final

前言 靶机&#xff1a;DriftingBlues-6&#xff0c;IP地址192.168.1.66 攻击&#xff1a;kali&#xff0c;IP地址192.168.1.16 都采用虚拟机&#xff0c;网卡为桥接模式 主机发现 使用arp-scan -l或netdiscover -r 192.168.1.1/24 信息收集 使用nmap扫描端口 网站探测 访…

智慧公交指挥中枢,数据可视化 BI 驾驶舱

随着智慧城市的蓬勃发展&#xff0c;公共交通作为城市运营的核心枢纽&#xff0c;正朝着智能化和数据驱动的方向演进。通过整合 CAN 总线技术(Controller Area Network&#xff0c;控制器局域网总线)、车载智能终端、大数据分析及处理等尖端技术&#xff0c;构建的公交“大脑”…

页面无滚动条,里面div各自有滚动条

一、双滚动条左右布局 实现效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Doc…

美畅物联丨分布式锁实战:Spring Boot项目中的Redis应用

在分布式系统里&#xff0c;多个节点或许会同时对共享资源进行访问与操作。为防止出现数据不一致、资源竞争等状况&#xff0c;就需要一种机制来对这些并发访问加以协调&#xff0c;于是分布式锁就出现了。它如同一把全局的钥匙&#xff0c;在同一时刻仅有一个节点能够获取该钥…

[计算机网络]ARP协议的故事:小明找小红的奇妙旅程

1.ARP小故事 在一个繁忙的网络世界中&#xff0c;每个设备都有自己的身份标识——MAC地址&#xff0c;就像每个人的身份证号码一样。在这个故事里&#xff0c;我们的主角小明&#xff08;主机&#xff09;需要找到小红&#xff08;目标主机&#xff09;的MAC地址&#xff0c;才…

从RNN到Transformer:生成式AI自回归模型的全面剖析

个人主页&#xff1a;chian-ocean 文章专栏 生成式AI中的自回归模型详解 在生成式AI的飞速发展中&#xff0c;自回归模型作为核心技术之一&#xff0c;成为文本生成、语音合成、图像生成等领域的重要支柱。本文将全面探讨自回归模型的原理、架构、实际应用&#xff0c;并结合…

「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台

本篇将带你实现一个虚拟音乐控制台。用户可以通过界面控制音乐的播放、暂停、切换歌曲&#xff0c;并查看当前播放的歌曲信息。页面还支持调整音量和动态显示播放进度&#xff0c;是音乐播放器界面开发的基础功能示例。 关键词 UI互动应用音乐控制播放控制动态展示状态管理按钮…

用QT制作的倒计时软件

一、pro代码 RC_ICONS countdown.ico 二、mainwindow.cpp代码 #include "mainwindow.h" #include "ui_mainwindow.h"#include <QDateTime> #include <QMessageBox> #include <QSettings>MainWindow::MainWindow(QWidget *parent): QM…

Unbuntu下怎么生成SSL自签证书?

环境&#xff1a; WSL2 Unbuntu 22.04 问题描述&#xff1a; Unbuntu下怎么生成SSL自签证书&#xff1f; 解决方案&#xff1a; 生成自签名SSL证书可以使用OpenSSL工具&#xff0c;这是一个广泛使用的命令行工具&#xff0c;用于创建和管理SSL/TLS证书。以下是生成自签名…

springboot446数字化农家乐管理平台的设计与实现(论文+源码)_kaic

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#x…

laya游戏引擎中打包之后图片模糊

如下图正常运行没问题&#xff0c;打包之后却模糊 纹理类型中的默认类型都是精灵纹理&#xff0c;改为默认值即可。注意&#xff1a;要点击“应用”才可有效。精灵纹理类型会对图片进行渲染处理&#xff0c;而默认值 平面类型不会处理图片。