linux之进程信号(初识信号,信号的产生)
目录
- 引入
- 一、初识信号(信号预备知识)
- 1.生活中的信号
- 2.Linux中的信号
- 3.信号+进程得出的初步结论
- 二、信号的产生
- 1.通过终端输入产生信号
- 拓展: 硬件中断
- 2.调用系统函数向进程发信号
- 3.硬件异常产生信号
- 4.软件条件产生信号
- 拓展: 核心转储技术
- 总结一下:
引入
一、初识信号(信号预备知识)
1.生活中的信号
- 当你在打着永杰无间的时候,你的外卖到了,外卖员给你打电话去取,而你此时正在打游戏,你心里想着这把打完了去取,过了一会,你打完了这把游戏,你想起来你还有外卖没取,你就马上去取了
- 信号弹
- 我们根据红绿灯的颜色过马路
- 上课铃声响了,我们取上课
-
你怎么认识这些信号?
有人教过我,我就记住了 -> 1.识别信号 2.知道信号的处理方法 -
即使我们现在没有信号产生,我们也知道信号产生之后,我该干什么?
-
信号产生了之后,我们可能不会立即处理这个信号,我们可能有更重要的事情要做,但是我们必须要把信号产生这个信息保存下来,在合适的时候去处理
2.Linux中的信号
概念:Linux信号通常由操作系统或其他进程发送给目标进程,可以用于多种目的,例如中断进程、终止进程或请求进程执行某个特定操作。本质是一种通信机制。
用kill -l命令可以察看系统定义的信号列表
可以看到全是大写的,因为linux是用c语言写的,所以就是c语言中的宏,所以我们也可以使用数字也可以使用以上宏都是一个意思
一共有62个信号,因为32、33不存在,其中,1-31是普通信号,也是本文重点讲解的, 34-64属于实时信号,优先级比较高,立即处理,本文不做讲解。
3.信号+进程得出的初步结论
所以进程信号?
-
进程必须识别并能够处理信号 ,信号即使没有产生。也要具备处理信号的能力,怎么做到呢?信号的处理能力,是操作系统给进程内置的功能的一部分
-
进程即使没有收到信号,也能知道哪些信号该如何处理
-
当进程真的收到了一个具体的信号的时候,进程可能并不会立即处理这个信号,会在合适的时候区里这个信号 处理动作:1.默认动作 2.忽略 3.自定义动作
-
一个进程必须当信号产生,到信号开始处理,就会有一定的时间窗口,这段窗口,进程需要保存哪些信号已经发送了的能力
所以信号要经历: 信号产生 -> 信号保存 -> 信号处理 三个阶段
ctrl + c 为啥能杀死我们的前台进程呢?
- linux中,一次登录中,一般会配上一个bash,每一个登录,只允许一个进程是前台进程,可以允许多个进程是后台进程。
- 当我们用键盘输入ctrl + c 快捷键时候,会被解释成2号信号然后发给前台进程
- 启动进程的时候 加上 & 表示以后台进程的方式启动
- 所以,当我们以后台进程状态运行时,我们用键盘输入ctrl + c 的时候进程收不到信号,也就不会终止进程,我们只能使用kill命令杀死该进程
二、信号的产生
通过以上对linux信号的简单了解后,我们再来看一下信号是如何产生的,以下是信号产生的几种方式
1.通过终端输入产生信号
比如ctrl + c 就是2号信号SIGINT,我们如何来验证呢?
介绍一个函数
SIGNAL(2) Linux Programmer's Manual SIGNAL(2)NAMEsignal - ANSI C signal handlingSYNOPSIS#include <signal.h>typedef void (*sighandler_t)(int); //函数指针sighandler_t signal(int signum, sighandler_t handler); //表示当我们收到signum信号后,处理动作为handler方法//,涉及信号处理,此处先简单讲解
实验一:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
//num:收到了哪个信号
void handler(int num)
{cout << num << " handler ..." << endl;//exit(1);
}
int main()
{signal(SIGINT, handler);//只需设置一次,以后都有效while(1){cout << "i am a process, mypid: " << getpid() << endl;sleep(1);}return 0;
}
可以看到输入的ctrl + c被转换成2号信号
其实还有一些快捷键也可以表示为信号,比如ctrl + \ 表示3号信号即SIGQUIT
实验二:
code:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
void handler(int num)
{cout << "signal: " << num << " handler ..." << endl;//exit(1);
}
int main()
{signal(SIGINT, handler);signal(3, handler);while(1){cout << "i am a process, mypid: " << getpid() << endl;sleep(1);}return 0;
}
我们可以发现,2号和3号信号的处理动作都是同一个函数,这也就是为handler函数要有一个参数为int,就是为了标识是哪个信号正在处理。
但是有些信号不会使用signal定义的处理动作,比如19号信号SIGSTOP
实验三:
code:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
void handler(int num)
{cout << "signal: " << num << " handler ..." << endl;//exit(1);
}
int main()
{signal(SIGINT, handler);//signal(3, handler);signal(19, handler);while(1){cout << "i am a process, mypid: " << getpid() << endl;sleep(1);}return 0;
}
可以看到ctrl + z 并没有调用自定义的signal方法,仍然使进程停止了,这是为什么呢?想知后续请继续往下看!
实验四:
对所有信号都实验signal捕捉
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
void handler(int num)
{cout << "signal: " << num << " handler ..." << endl;//exit(1);
}
int main()
{//signal(SIGINT, handler);//signal(3, handler);//signal(19, handler);for(int i = 1; i <= 31; ++i){signal(i, handler);}while(1){cout << "i am a process, mypid: " << getpid() << endl;sleep(1);}return 0;
}
可以看到1-8都被捕捉了,而9号信号没有。我们继续重启进程,继续向后发信号。
可以看到10-18都被捕捉了,19不会,也就是之前做的实验三。
继续重启进程,发信号,可以发现都被捕捉了(这里不做演示了)。
所以只有9号和19号信号不会被signal捕捉,这是为什么呢?
可以发现这俩一个是杀死进程,一个是停止进程,都跟进程的执行有关,你觉得操作系统为啥不让所有信号被捕捉呢?很简单,操作系统又不是傻子,万一有个病毒软件什么的,它把所有信号都捕捉了,那它不就永远都杀不掉了嘛。
拓展: 硬件中断
看了上面的,你有什么疑问嘛?
键盘数据是如何传入给内核的?,ctrl + c 又是如何变成信号交给进程呢?
接下来就要谈谈硬件了
首先,进程是由操作系统管理的,所以键盘被按下,肯定是操作系统先知道?那么操作系统又是如何知道键盘上有数据了????
- 操作系统会在启动的时候加载进内存
- linux下一切皆文件,每个文件都有自己的缓冲区,即struct file结构体
- os怎么知道键盘上有数据?os会对外设进行轮询监测嘛?os系统没这么蠢,系统中这么多设备,操作系统这样的化效率太低了
- cpu和键盘通过主板连接在一起,cpu上有很多针脚,和各种设备间接或直接连接着,当键盘中有数据产生的时候,键盘会给cpu发送硬件中断,将cpu特定的针脚发送高低电频,即充放电,然后cpu感受到了。
- cpu中的寄存器如何存储数据呢?也就是上述充放电的过程
- os内核中有一个中断向量表,就和文件描述符表向上,中断向量表中存储各个设备的读写方法
- 当键盘中有数据产生,给cpu发送硬件中断,cpu让操作系统执行中断相量表中的方法去读取键盘中的数据
- 当键盘中的数据是一般数据的时候,会被读入缓冲区中,当是快捷键比如ctrl + c 这样的,会被解释成信号由操作系统发送给进程
那么以上所讲硬件中断和我们讲的信号有什么联系嘛?
我们学习的信号,就是利用软件的方式,对进程模拟硬件中断
再谈缓冲区
- 平常我们输入命令的时候,我们可以看到我们自己的输入,其实是操作系统将键盘输入缓冲区中的数据拷贝到显示器输出缓冲区中去了,我们就可以看到我们输入的内容了,而linux中输入密码的时候我们看不到密码,也就是os没有把此时的输入缓冲区中的内容拷贝到输出缓冲区中去
- 当我们以后台进程的方式启动进程时,我们即使隔一段时间才输入一个完整的命令,也可以正常执行,因为我们我们看到的很长时间才输入完整的命令实在显示器缓冲区中,而输入缓冲区中的数据时连续完整的,所以可以正常执行
2.调用系统函数向进程发信号
可以看到,kill系统调用的作用是发送一个信号给指定进程,就不就是和我们命令行中的kill命令一样的嘛,其实我们命令行中的kill命令底层就是调用的kill系统调用。
那么我们直接来做一个kill命令
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <cstdio>
#include <signal.h>
using namespace std;
static void Usage(const string& cmd)
{cout << "\n\r " << cmd << " signo process_pid " << endl << endl;
}
int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int sig = stoi(argv[1]);pid_t id = stoi(argv[2]);int ret = kill(id, sig);if(ret == -1){perror("kill");exit(2);}return 0;
}
raise作用:发送一个信号给调用者,实际上就是kill的封装,就是kill(getpid(), sig).
abort作用: 给调用者发送6号信号(SIGABRT,实际也是对kill的封装,但是他有一些特性,直接看实验
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <cstdio>
#include <signal.h>
using namespace std;
// static void Usage(const string& cmd)
// {
// cout << "\n\r " << cmd << " signo process " << endl << endl;
// }
void handler(int signum)
{cout << "get a sig : " << signum << endl;
}
int main(int argc, char* argv[])
{// if(argc != 3)// {// Usage(argv[0]);// exit(1);// }// int sig = stoi(argv[1]);// pid_t id = stoi(argv[2]);// int ret = kill(id, sig);// if(ret == -1)// {// perror("kill");// exit(2);// }signal(6, handler);for(int i = 0; i < 3; ++i){cout << "i am a process pid: " << getpid() << endl;sleep(1);}abort();return 0;
}
可以看到,即使signal捕捉了6号信号,进程也执行了handler方法,但进程最终还是终止了,所以abort内部采用一定的手段使进程强制停止了。
3.硬件异常产生信号
1.除0错误
code:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <cstdio>
#include <signal.h>
using namespace std;void handler(int signum)
{cout << "get a sig : " << signum << endl;
}
int main()
{// signal(SIGFPE, handler);cout << "zero before" << endl;sleep(1);int a = 10;cout << 10 / 0 << endl;;cout << "zero after" << endl;return 0;
}
实验现象:
放开注释后,自定义捕捉SIGFPE信号
实验现象:
可以看到我们捕捉了8号信号(SIGFPE),所以发生除0错误后,会收到8号信号,但是我们一直死循环重复捕捉,这是为什么呢?
- 我们从硬件角度来分析,cpu内有一套寄存器,保存着进程的上下文数据,其中一些特殊的寄存器比如eip/pc记录着进程执行的代码情况。
- 最重要的是有一个状态寄存器,他就是一个位图结构,用来记录进程的运行状态。每一个比特位代表不同的状态。
- 当发生除0错误的时候,状态寄存器中溢出标记位由0变1,发生异常。
- 而操作系统是硬件的管理者,它发现cpu中状态寄存器发生异常,就会给进程发送特定的信号,而进程本身无法处理这个异常,即使捕捉了信号, 操作系统就会不断的发信号。
2.野指针异常
code:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <cstdio>
#include <signal.h>
using namespace std;void handler(int signum)
{cout << "get a sig : " << signum << endl;
}int main()
{//signal(SIGSEGV, handler);cout << "before" << endl;sleep(1);int *p = nullptr;*p = 30;cout << "after" << endl;return 0;
}
实验现象:
放开注释,实验现象:
可以看到,发生野指针的时候,也会不断收到信号。
我们再从底层的角度解释一下:
- 发生野指针的本质是访问无效的空间,由进程地址空间可以知道,我们语言上访问的空间其实是虚拟地址,实际上我们要通过页表进行虚拟地址到物理地址的转化,而这一过程os系统不会参与,会有页表和mmu(memory manager unit)即内存管理单元(cpu中硬件),当转化失败的时候mmu会报错。
- os系统是硬件的管理者,它识别到mmu的报错,而进程无法修正错误,即使捕捉了信号,就不断给进程发信号
总结一下:
当我们出现异常的时候,操作系统为啥选择给进程发信号,然后去执行特定的代码,而不是直接干掉进程呢?
其实向我们发信号是为了让进程知道进程出现了什么异常,而且让进程有一定的缓存时间,去处理日志信息等,如果操作系统直接把进程干掉了,就可能处理不了这些情况了。
补充:
上述过程一个进程导致cpu中的寄存器发生了异常,也就是硬件发生了异常,为啥其他进程还能正常运行呢?
- cpu中的寄存器只有一套,存储的是进程的上下文信息,当发生进程切换的时候,进程会带走它的上下文数据,然后其他进程将它之前访问的上下文数据放进寄存器,上一个进程带来的寄存器异常已经被带走了,而新来的进程上下文数据并没有异常,所以不会影响其他进程的运行。
- 所以信号的存在不是让我们解决问题,操作系统都解决不了,你让进程自己解决码?
- 信号的存在是为了让我们知道程序异常的原因,并且给我们一定的缓冲时间,用来打印日志信息等等。
4.软件条件产生信号
比如我们写管道的时候,当读端关闭的时候,写端还有继续写入,此时操作系统就会向写端进程发送SIGPIPE信号,然后终止进程。
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
返回值代表上次设定闹钟的剩余时间,比如你定一个闹钟30分钟后响,20分钟后你就行了,此时你查看时间闹钟还有10分钟才响。
实验:如果我们自定义处理闹钟的动作,进行一些日志打印等操作,然后再在捕捉函数内定一个闹钟,不就做到了不断做日志打印的业务了嘛?
code:
#include <signal.h>
using namespace std;
void work()
{cout << "print a log..." << endl;
}
void handler(int signum)
{cout << "get a sig : " << signum << endl;work();alarm(5);
}
int main()
{signal(SIGALRM, handler);alarm(5);while(1){cout << "i am a process pid : " << getpid() << endl;sleep(1);}return 0;
}
实验现象:
返回值验证:可以看到上面实验现象每次闹钟剩余时间都是0,因为我们没有利用其他手段让闹钟提前苏醒,我们使用kill命令来验证
实验现象:
补充:
- 操作系统内部有很多进程,每个进程都可以设置闹钟,那么操作系统内部就有很多闹钟了,操作系统就要管理它们,管理的本质是先描述再组织,用特定的结构体来描述闹钟,比如 struct alarm.
- struct alarm中需要存储设置这个alarm的进程,以及什么时候响,一般用当前时间戳+设计多久响来表示
- 那么操作系统用什么组织alarm呢?很明显可以用我们常见的数据结构小堆来管理,堆顶存最早响的闹钟,os只需要查看堆顶的时间,如果超时了,给struct alarm中存的进程pid发送信号,然后将堆顶的数据删除,继续查看堆顶,重复以上动作,直到堆顶没有超时。
拓展: 核心转储技术
之前讲进程退出状态的时候,正常退出时次第8为为退出码,低8位为0,
收到信号退出的时候,次第0位无意义,低7位表示退出信号,其中第8个比特位之前没有讲,其实是core dump(核心转储)标识。
什么是核心转储?
将进程异常退出时的状态保存下来形成一个core.pid文件,放在当前路径下(磁盘)可以配合gdb进行时候调试,一会做实验。
一般在项目上线后是关闭的,为什么?
一般服务即使出现异常挂掉了,会有一定的手段自动重启,如果一些程序员写的代码太水,一上线就挂掉,不断自动重启,不断形成core dump文件,把磁盘写满,挂的就是操作系统了,那问题就严重了。
并不是所以的信号都会使进程挂掉后形成核心转储,通过man 7 signal可以查看
其中term表示只终止进程,core表示终止进程并形成core dump核心转储文件。
ign表示忽略动作,stop表示停止,这俩现在不详细讲。
实验验证:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <signal.h>
using namespace std;int main()
{pid_t id = fork();if(id == 0){int cnt = 500;while(cnt){cout << "i am child process, pid: " << getpid() << "cnt: " << cnt-- << endl;sleep(1);}exit(0);}int status = 0;pid_t rid = waitpid(id, &status, 0);cout << " child quit info, rid: " << rid << " exit code: " << ((status >> 8) & 0xff) << " exit signal: " << (status & 0x7f) << " core dump: " << (status >> 7 & 1) << endl;return 0;
}
默认情况下core dump是关闭的,ulimit -a可以查看
此时实验现象:
可以看到此时无论发送term信号还是core信号都不会形成core dump。
当我们把核心转储打开
开始重做实验:
可以看到当我们使用core信号时,形成了核心转储文件,并且很大。
那么我们如何将他和gdb配合使用呢?
code:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <signal.h>
using namespace std;int main()
{int a = 10;a /= 0;cout << "a: " << a << endl;return 0;
}
当我们直接运行的时候,可以看到报错多了一段core dumped,并形成了core.pid文件
直接开始gdb
可以看到直接定位到了异常的位置
总结一下:
-
上面所说的所有信号产生,最终都要有OS来进行执行,为什么?
OS是进程的管理者 -
信号的处理是否是立即处理的?
普通在合适的时候进行处理,但是对于实时信号来说,必须立马处理。 -
信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
可以发现普通信号一共有31个,一个int有32个比特位,不觉得很巧嘛?每一个比特位代表着一个信号,0和1表示着是否收到该信号,其实信号就是用类似这样的结构保存的,也就是位图,但是可以发现只能存0和1那就只能表示是否收到该信号,而不能知道收到该信号的数量的,实际上内核也是这样的。
而实时信号需要立即处理,所以发几个信号就要处理几个,所以就不能用位图结构来存储了,而是用一个队列(先到先处理)。 -
一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢?
必须知道。 -
如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?
与其说os向进程发信号,不如说向进程的pcb发信号,信号在pcb中用位图方式存储,那么os向进程发信号就是修改进程pcb中的位图,将对应的信号比特位由0置1.
相关文章:

linux之进程信号(初识信号,信号的产生)
目录 引入一、初识信号(信号预备知识)1.生活中的信号2.Linux中的信号3.信号进程得出的初步结论 二、信号的产生1.通过终端输入产生信号拓展: 硬件中断2.调用系统函数向进程发信号3.硬件异常产生信号4.软件条件产生信号拓展: 核心转储技术总结一下: 引入 一、初识信…...

基于nginx实现正向代理(linux版本)
介绍 在企业开发环境中,局域网内的设备通常需要通过正向代理服务器访问互联网。正向代理服务器充当中介,帮助客户端请求外部资源并返回结果。局域网内也就是俗称的内网,局域网外的互联网就是外网,在一些特殊场景内,例…...

【蓝牙】win11 笔记本电脑连接 hc-06
文章目录 前言步骤 前言 使用电脑通过蓝牙添加串口 步骤 设置 -> 蓝牙和其他设备 点击 显示更多设备 更多蓝牙设置 COM 端口 -> 添加 有可能出现卡顿,等待一会 传出 -> 浏览 点击添加 hc-06,如果没有则点击 再次搜索 确定 添加成…...

小程序组件 —— 31 事件系统 - 事件绑定和事件对象
小程序中绑定事件和网页开发中绑定事件几乎一致,只不过在小程序不能通过 on 的方式绑定事件,也没有 click 等事件,小程序中绑定事件使用 bind 方法,click 事件也需要使用 tap 事件来进行代替,绑定事件的方式有两种&…...

力扣cf补题-1【算法学习day.94】
前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向(例如想要掌握基础用法,该刷哪些题?建议灵神的题单和代码随想录)和记录自己的学习过程,我的解析也不会做的非常详细,只会提供思路和一些关…...

系统学习算法:专题四 前缀和
题目一: 算法原理: 这道题是一维前缀和的模板题,通过这道题我们可以了解什么是前缀和 题意很简单,就是先输入数组个数和查询次数,然后将数组的值放进数组,每次查询给2个数,第一个是起点&#x…...

java 迪米特法则,原理、思想、工作流程、实现细节、稳定性、优缺点、应用场景等
迪米特法则(Law of Demeter,LoD),也被称为“最少知识原则”,是一种指导面向对象设计的原则,旨在减少对象之间的耦合度。以下是对迪米特法则的详细解析。 1. 定义 迪米特法则指出:一个对象应该…...

vue项目引入阿里云svg资源图标
1:生成svg图标 登录阿里云官网 1.1 创建项目组 1.2 从阿里云网站上面获取喜欢的图标加入到已有的项目组 1.3 如果团队有自己的设计师,也可以让设计师上传自己的svg图标到阿里云指定的项目组; 使用的时候,把 资源包下载到本地项…...

存储过程和触发器
目录 1、存储过程 1.1 存储过程的概述 1.2 存储过程的类型 1. 系统存储过程 2. 本地存储过程 3. 临时存储过程 4. 扩展存储过程 1.3 T-SQL创建存储过程 1.4 T-SQL执行存储过程 1.5 T-SQL查看存储过程 1.6 T-SQL修改存储过程 1.7 T-SQL删除存储过程 2、触发器 2.1 …...

《拉依达的嵌入式\驱动面试宝典》—计算机网络篇(二)
《拉依达的嵌入式\驱动面试宝典》—计算机网络篇(二) 你好,我是拉依达。 感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动 相关内容搜索的推荐首位,感谢大家支持。 《…...

【深度学习实战】kaggle 自动驾驶的假场景分类
本次分享我在kaggle中参与竞赛的历程,这个版本是我的第一版,使用的是vgg。欢迎大家进行建议和交流。 概述 判断自动驾驶场景是真是假,训练神经网络或使用任何算法来分类驾驶场景的图像是真实的还是虚假的。 图像采用 RGB 格式并以 JPEG 格式…...

Spring Boot 和微服务:快速入门指南
💖 欢迎来到我的博客! 非常高兴能在这里与您相遇。在这里,您不仅能获得有趣的技术分享,还能感受到轻松愉快的氛围。无论您是编程新手,还是资深开发者,都能在这里找到属于您的知识宝藏,学习和成长…...

qt QPainter setViewport setWindow viewport window
使用qt版本5.15.2 引入viewport和window目的是用于实现QPainter画出来的内容随着窗体伸缩与不伸缩两种情况,以及让QPainter在widget上指定的区域(viewport)进行绘制/渲染(分别对应下方demo1,demo2,demo3)。 setViewpo…...

网络安全面试题汇总(个人经验)
1.谈一下SQL主从备份原理? 答:主将数据变更写入自己的二进制log,从主动去主那里去拉二进制log并写入自己的二进制log,从而自己数据库依据二进制log内容做相应变更。主写从读 2.linux系统中的计划任务crontab配置文件中的五个星星分别代表什么ÿ…...

【网络云SRE运维开发】2025第3周-每日【2025/01/14】小测-【第13章ospf路由协议】理论和实操
文章目录 选择题(10道)理论题(5道)实操题(5道) 【网络云SRE运维开发】2025第3周-每日【2025/01/14】小测-【第12章ospf路由协议】理论和实操 选择题(10道) 在OSPF协议中,…...

FreeType 介绍及 C# 示例
FreeType 是一个开源的字体渲染引擎,用于将字体文件(如 TrueType、OpenType、Type 1 等)转换为位图或矢量图形。它广泛应用于操作系统、图形库、游戏引擎等领域,支持高质量的字体渲染和复杂的文本布局。 FreeType 的核心功能 字体…...

金融项目实战 04|JMeter实现自动化脚本接口测试及持续集成
目录 一、⾃动化测试理论 二、自动化脚本 1、添加断言 1️⃣注册、登录 2️⃣认证、充值、开户、投资 2、可重复执行:清除测试数据脚本按指定顺序执行 1️⃣如何可以做到可重复执⾏? 2️⃣清除测试数据:连接数据库setup线程组 ①明确…...

Linux网络知识——路由表
路由表 1 定义与作用 Linux路由表是一个内核数据结构,用于描述Linux主机与其他网络设备之间的路径,以及如何将数据包从源地址路由到目标地址。路由表的主要作用是指导数据包在网络中的传输路径,确保数据包能够准确、高效地到达目标地址。 …...

浅谈云计算14 | 云存储技术
云存储技术 一、云计算网络存储技术基础1.1 网络存储的基本概念1.2云存储系统结构模型1.1.1 存储层1.1.2 基础管理层1.1.3 应用接口层1.1.4 访问层 1.2 网络存储技术分类 二、云计算网络存储技术特点2.1 超大规模与高可扩展性2.1.1 存储规模优势2.1.2 动态扩展机制 2.2 高可用性…...

AI 编程工具—Cursor进阶使用 阅读开源项目
AI 编程工具—Cursor进阶使用 阅读开源项目 首先我们打开一个最近很火的项目browser-use ,直接从github 上克隆即可 索引整个代码库 这里我们使用@Codebase 这个选项会索引这个代码库,然后我们再选上这个项目的README.md 文件开始提问 @Codebase @README.md 这个项目是用…...

使用 WPF 和 C# 将纹理应用于三角形
此示例展示了如何将纹理应用于三角形,以使场景比覆盖纯色的场景更逼真。以下是为三角形添加纹理的基本步骤。 创建一个MeshGeometry3D对象。像往常一样定义三角形的点和法线。通过向网格的TextureCoordinates集合添加值来设置三角形的纹理坐标。创建一个使用想要显示的纹理的 …...

Elasticsearch搜索引擎(二)
RestClient 基础 前言一、RestAPI1. 初始化 *RestClient*2. 创建索引库3. 删除索引库4. 判断索引库是否存在 二、RestClient操作文档1.新增文档2.查询文档3. 删除文档4. 修改文档5. 批量导入文档 前言 ES官方提供了各种不同语言的客户端用来操作ES,这些客户端的本质…...

unity学习17:unity里的旋转学习,欧拉角,四元数等
目录 1 三维空间里的旋转与欧拉角,四元数 1.1 欧拉角比较符合直观 1.2 四元数 1.3 下面是欧拉角和四元数的一些参考文章 2 关于旋转的这些知识点 2.1 使用euler欧拉角旋转 2.2 使用quaternion四元数,w,x,y,z 2.3 使用quaternion四元数,类 Vector3.zero 这种…...

走出实验室的人形机器人,将复刻ChatGPT之路?
1月7日,在2025年CES电子展现场,黄仁勋不仅展示了他全新的皮衣和采用Blackwell架构的RTX 50系列显卡,更进一步展现了他对于机器人技术领域,特别是人形机器人和通用机器人技术的笃信。黄仁勋认为机器人即将迎来ChatGPT般的突破&…...

如何使用wireshark 解密TLS-SSL报文
目录 前言 原理 操作 前言 现在网站都是https 或者 很多站点都支持 http2。这些站点为了保证数据的安全都通过TLS/SSL 加密过,用wireshark 并不能很好的去解析报文,我们就需要用wireshark去解密这些报文。我主要讲解下mac 在 chrome 怎么配置的&…...

电脑有两张网卡,如何实现同时访问外网和内网?
要是想让一台电脑用两张网卡,既能访问外网又能访问内网,那可以通过设置网络路由还有网卡的 IP 地址来达成。 检查一下网卡的连接 得保证电脑的两张网卡分别连到外网和内网的网络设备上,像路由器或者交换机啥的。 给网卡配上不一样的 IP 地…...

定义:除了Vue内置指令以外的其他 v-开头的指令(需要程序员自行扩展定义)作用:自己定义的指令, 可以封装一些 dom 操作, 扩展
1.自定义指令(directives) 1.用法 定义:除了Vue内置指令以外的其他 v-开头的指令(需要程序员自行扩展定义)作用:自己定义的指令, 可以封装一些 dom 操作, 扩展额外功能 语法: ① 局部注册 ●inserted:被绑…...

SpringBoot错误码国际化
先看测试效果: 文件结构 1.中文和英文的错误消息配置 package com.ldj.mybatisflex.common;import lombok.Getter;/*** User: ldj* Date: 2025/1/12* Time: 17:50* Description: 异常消息枚举*/ Getter public enum ExceptionEnum {//# code命名规则:模…...

LeetCode 3066.超过阈值的最少操作数 II:模拟 - 原地建堆O(1)空间 / 优先队列O(n)空间
【LetMeFly】3066.超过阈值的最少操作数 II:模拟 - 原地建堆O(1)空间 / 优先队列O(n)空间 力扣题目链接:https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii/ 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 一次…...

深度学习中的模块复用原则(定义一次还是多次)
文章目录 1. 模块复用的核心原则(1)模块是否有**可学习参数**(2)模块是否有**内部状态**(3)模块的功能需求是否一致 2. 必须单独定义的模块(1)nn.Linear(全连接层&#x…...