信号的产生、处理
一、信号的概念
信号是linux系统提供的一种,向指定进程发送特定事件的方式。收到信号的进程,要对信号做识别和处理。信号的产生是异步的,进程在工作过程中随时可能收到信号。
信号的种类分为以下这么多种(用指令kill -l查看)
其中1-31号信号是普通信号,32-64号信号是实时信号(暂且不关注)。
二、信号的产生
1、通过kill命令,向指定的进程发送指定信号
通过代码验证:利用 man 2 signal查看接受信号的系统调用:
其中handler是一个函数指针,返回值为void,参数为int,int表示的是接收的是几号信号;该系统调用是将信号进行自定义处理,也就是捕捉信号,捕捉之后的信号一般都不会进行自己的默认处理(有例外)。下面用代码验证kill命令向进程发送的信号:
#include<iostream>
#include<signal.h>
using namespace std;void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;exit(1);//异常退出}int main()
{sighandler_t n=signal(4,handler);while(1){cout<<"I am working! pid:"<<getpid()<<endl;sleep(1);}return 0;
}
可以看到接受4号信号后,实现了自定义处理。
2、键盘产生信号
Ctrl + c 产生2号信号;Ctrl + \ 产生3号信号;Ctrl + z 产生20号信号
只需修改sighandler_t n=signal(2,handler);中的2为3 、20即可自定义处理不同的信号。
3、系统调用产生信号
①kill
kill(pid,signal) pid表示给指定的进程发送。signal表示发送几号信号。
#include<iostream>
#include<signal.h>
using namespace std;void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;//exit(1);//表示异常退出}int main()
{sighandler_t n=signal(2,handler);int cnt=10;while(cnt--){cout<<"I am working! pid:"<<getpid()<<endl;sleep(1);}kill(getpid(),2);//等价于 raise(2)//abort();//发送6号信号return 0;
}
②raise
raise(int signal) 表示向本进程发送signal号信号,代码如上
③abort
abort发送6号信号。
注意:
①为了防止有程序对所有信号都自定义捕捉而恶意不退出,有9号信号不允许自定义捕捉。
②如何理解信号的发送?
真正发送信号的是OS
信号在进程的task_struct结构体中其实是一个32位int位图的形式,这就是为什么普通信号有31个,且从1开始而不是从0开始。发送信号的过程其实是OS在修改指定进程pcb中的信号的指定位图。因为只有OS有这个权限去修改进程的内核结构对象,所以只有OS能发送信号。
4、软件条件
①在管道学习中,已知当管道读端关闭之后,写端如果还在写,则会发送13号信号SIGPIPE让写端进程退出。这是一种软件条件。(写端写入操作的条件不满足)
②闹钟:设定几秒后收到一个14号SIGALRM信号
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include<unistd.h>
using namespace std;void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;exit(1);//表示异常退出}int main()
{sighandler_t n=signal(14,handler);unsigned int ret=alarm(5);while(1){cout<<"I am working! pid:"<<getpid()<<endl;sleep(1);}/*int cnt=5;while(cnt--){cout<<"I am working! pid:"<<getpid()<<endl;sleep(1);}*///kill(getpid(),9);//raise(2);//abort();//发送6号信号return 0;
}
OS在对一些超时的程序,会设定一个闹钟,超出这个时间之后就会向指定进程发送14信号终止。
时间:通过时间戳(整数)来比较。
操作系统要对闹钟做管理:先描述再组织
闹钟结构体struct alarm
{
time_t expired;//未来的超时时间=seconds+Now();
pid_t pid;
}
对于闹钟结构体,采用最大堆最小堆的组织方式,高效组织。这样以未来的超时时间作为基准进行堆排序,只要最近的超时的闹钟未超时,后面的闹钟就都不会超时。
闹钟的返回值表示的是上一个闹钟的剩余时间。
alarm(0):表示的含义是取消闹钟,返回上一个闹钟的剩余时间。
5、异常
①除0异常
当系统有除0异常时,OS会发送8号信号SIGFPE使进程退出,当运行以下代码时,系统会一直打印:
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include<unistd.h>
using namespace std;void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;//exit(1);//表示异常退出}
int main()
{sighandler_t n=signal(8,handler);int a=4;int b=a/0;return 0;
}
②野指针异常
当系统有野指针异常时,OS会发送11号信号SIGSEGV使进程退出,当运行以下代码时,系统会一直打印
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include<unistd.h>
using namespace std;void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;//exit(1);//表示异常退出}int main()
{sighandler_t n=signal(11,handler);int * p=nullptr;*p=100;return 0;
}
③产生异常的原理
a、对于除0操作
计算都要通过CPU计算,CPU内部有一个寄存器eflag,内部有一个溢出标记位,当有除0操作时,CPU是将其转换为加法,而除0操作会一直加,该硬件就会出错,然后将错误传递给OS,OS接受到这种错误之后就会处理,处理方式就是向目标进程发送信号。
当我们自定义处理信号之后,为什么会一直打印?寄存器只有一套,但是寄存器里面的数据是属于每一个进程的;当我们自定义信号处理后进程不退出后,随着进程的切换调度,寄存器先是被其他进程使用,而当本错误进程又切换到运行队列中时,寄存器会恢复上下文,此时发现又出错,又交给OS,然后就会再次发送一次信号,所以就又会打印。
所以推荐接受信号后退出进程,否则就会一直卡在错误代码(这里是除0的代码)。退出进程后,寄存器的错误数据就会清空,进程自己的异常自己承受,不用让OS承担。
b、对于野指针操作
在CPU内部有一个硬件MMU,MMU将页表的虚拟地址转换为物理地址;CR3寄存器存储的是页表的地址,MMU+CR3得到物理地址;当给定指针p指向空的时候,又对其进行赋值,MMU+CR3转换的时候,接收到的是一个错误的虚拟地址,会将错误的虚拟地址放进CR2;此时OS发现了CR2内有错误的地址,就向进程发信号了。
三、信号的处理(基础)
1、默认处理与忽略
使用 man signal 7指令查看所有信号的默认处理方式
默认处理方式为终止进程的有Core Term两种,这两种的区别:
Term:异常终止;
Core:异常终止,但会形成一个debug文件。
对于云服务器来说,默认形成debug文件的功能是关闭的;
ulimit -a查看被关闭的功能;ulimit -c 10240打开core file功能,当系统错误时会形成debug文件(core文件),这里面存储的是进程退出前运行的信息,是进程退出的时候的镜像数据。
centos系统的core dump形成的文件,文件名是以进程id为结尾的,这可能会导致无数的core文件,因为每次运行的进程id可能不同;而ubuntu系统对该bug做了修正,每次都会覆盖。
为什么云服务器要默认关闭core file(核心转储)功能?当云服务器挂了之后,有一些监控程序会让该服务器自动重启,而如果一个程序每次重启就报错,就会形成大量core 文件,会将磁盘占满。
core文件如何使用:当程序出错退出时,在makefile 文件中如果带有一个 -g选项,那么后续gdb的时候输入core-file core 就可以定位哪一行出错。core是协助我们进行debug的文件,进行事后调试。
代码验证core dump标志位
core dump标志位为1说明形成了core文件。
代码:
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include <sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
using namespace std;void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;//exit(1);//表示异常退出}
int Sum(int start,int end)
{int sum=0;for(int i=start;i<=end;i++){sum /=0;sum+=i;}return sum;
}int main()
{pid_t id=fork();if(id==0){//child processint total=Sum(0,100);exit(0);}//father processint status;pid_t rid=waitpid(id,&status,0);if(rid==id){//等待成功printf("exit code: %d,exit sig: %d,core dump:%d\n",(status>>8)&0xff,(status&0x7f),(status>>7)&0x1);}return 0;}/*int main()
{sighandler_t n=signal(11,handler);int * p=nullptr;*p=100;return 0;
}*//*int main()
{sighandler_t n=signal(8,handler);int a=4;int b=a/0;return 0;
}*//*int main()
{sighandler_t n=signal(14,handler);unsigned int ret=alarm(5);while(1){cout<<"I am working! pid:"<<getpid()<<endl;sleep(1);}/*int cnt=5;while(cnt--){cout<<"I am working! pid:"<<getpid()<<endl;sleep(1);}//kill(getpid(),9);//raise(2);//abort();//发送6号信号return 0;
}*/
结果:
2、自定义
利用signal系统调用,实现自定义处理,具体代码在前面。
四、信号的保存
1、一些新概念
实际执行信号的处理动作称为信号递达。
信号从产生到递达之间的状态,叫做信号未决。(临时在PCB中保存而未被处理)
有的信号会被进程阻塞。阻塞一个信号,那么对应的信号一旦产生,将永不递达,一直未决,直到主动接触阻塞。阻塞描述的是信号要不要被递达的特点。
注:①一个信号是否处于阻塞状态,和它是否未决并没有关系。②阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
2、信号保存的深入理解
(1)pending表(未决信号集)
一张位图(32位整数位图,但只用了31位),其中比特位的位置代表信号编号,比特位的内容代表信号是否收到(0 ->未收到,1->收到)。
(2)handler表
是一个函数指针数组。普通信号是1-31号数字编号,这些编号对应的handler数组的下标,OS为每一个进程都维护了这样一个handler表,例如收到2号信号就那种2号信号去索引信号处理方法。
在系统调用signal(2,handler)中,我们能够实现对2号信号的自定义捕捉的原理,其实是拿着2号信号的编号,将自己写的自定义函数指针填进2号信号对应的handler数组中的处理方法,而原默认处理方法就被覆盖了。
(3)block表
一张位图,和pending类型完全一样,其中比特位的位置代表信号编号,比特位的内容代表信号是否阻塞(0 ->未阻塞,1->阻塞)
两张位图+函数指针数组,实现了让进程识别信号。
五、信号集操作
sigset_t : linux系给用户提供的一个用户级的数据类型,但禁止用户手动设置该位图的值,而是提供了一系列信号机操作函数。
1、sigprocmask:对block位图进行修改的系统调用。
其中how:
其中old_set表示返回修改之前的位图。
返回值0成功,返回值-1失败;
2、sigpending:获取当前进程的pending位图
只用来获取,改变是通过产生信号的那5种方式。其中set是输出型参数。
3、代码验证
屏蔽2号信号;向2号信号不断发送,再获取pending信号集,就能看到由0变1的变化。再解除对2号信号的屏蔽,看到由1到0的变化。
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include <sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
using namespace std;void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;//exit(1);//表示异常退出//验证pending位图清0的操作是在handler处理前还是处理后cout<<"-------------------"<<endl;sigset_t pending_bit;sigpending(&pending_bit);PrintPending(pending_bit);cout<<"-------------------"<<endl;}
void PrintPending(sigset_t& pending_bit)
{for(int i=31;i>=1;i--){if(sigismember(&pending_bit,i)){cout<<1;}else cout<<0;}cout<<endl;
}int main()
{sighandler_t n=signal(2,handler);sigset_t new_bit;sigset_t old_bit;sigemptyset(&new_bit);sigemptyset(&old_bit);sigaddset(&new_bit,2);//屏蔽2号信号sigprocmask(SIG_BLOCK,&new_bit,&old_bit);int cnt=20;while(1){sigset_t pending_bit;sigpending(&pending_bit);cout<<"pid:"<<getpid()<<"pending:";PrintPending(pending_bit);cnt--;if(cnt==0){//解除对2号信号的屏蔽cout<<"解除对2号信号的屏蔽: ";sigprocmask(SIG_SETMASK,&old_bit,&new_bit);}sleep(1);}return 0;
}
解除屏蔽,一般会立即处理当前被解除屏蔽且被pending的信号。
pending位图对应的信号在被递达之前清0。
六、信号的处理(底层理解)
三种处理方式:
signal(2,SIG_IGN);//忽略处理signal(2,SIG_DFL);//默认处理signal(2,handler);//自定义捕捉
1、信号处理的时机和信号捕捉
信号可能不会被立即处理,而是在合适的时候处理;合适的时候指的是进程从内核态返回到用户态的时候。
当OS从内核态准备切换到用户态之前,先处理当前进程中可以递达的信号,查看信号的handler指针数组;如果是SIG_DFL,大部分就直接退出进程了;如果是SIG_IGN,那么就直接忽略;而如果是自定义捕捉,就要回到用户态执行信号处理函数(而不是回到主控制流程)。
OS不能在内核态直接执行信号处理函数,因为该函数内可能会对OS内核做修改,这会影响到OS的安全。所以该信号处理函数只能在用户态下执行。由于信号处理函数和main函数是两个执行流,不能切换,所以OS要继续回到内核态,然后再返回到用户态往下执行。
信号捕捉的过程:要经历4次状态的切换。
2、内核态和用户态
(1)深入理解地址空间——内核空间
对于每个进程,其【3,4】GB的地址空间是内核空间,其实就是OS;除了用户级页表之外,还有一个内核级页表,因为OS在电脑启动时就被加载到内存中了,是第一个软件,内核级页表指向的就是内存中的OS。这就是为什么,无论进程如何切换,我们都能找到OS。
我们进程访问OS,就是在本身的虚拟地址空间中访问的,和访问库函数(在共享区)和本身写的函数(在正文代码区)一样;区别是,因为OS要保证自己的安全,用户在地址空间内访问OS时,只能通过系统调用。
而内核级页表只需维护一份就够了,也就是说所有的进程的内核空间都是一样的,内核级页表在系统中只存在一份。
(2)键盘输入数据的过程
当键盘上有按键被按,则会通过硬件中断的方式触发CPU中断;每种设备(包括磁盘、网卡等)在事前已经被分配了中断号;当CPU触发中断时,其实是该设备对应的中断号被写入CPU寄存器中;OS在启动时已经构建了一张函数指针表,CPU则会根据中断号索引到函数指针表中的函数,键盘的中断号对应的函数就是将键盘中的数据读到内存中。
这套机制类似信号,其实是先有硬件中断,然后信号是根据这种机制用软件模仿出来的。
(3)如何理解OS正常运行?
OS是如何运行的:
操作系统的本质是一个死循环+时钟中断不断调度系统任务
如何理解系统调用:
系统提供了一张函数指针数组,只要找到特定数组下标的方法,就能执行系统调用了。具体的如何实现系统调用,如下图:
(4)内核态和用户态
也就是说,CPU保证了什么时候可以访问内核空间的地址。
注意:OS由用户态进入内核态并不一定是进行了系统调用,系统在调度时,当某个进程的时间片到时,会从调度队列中剥离下来从而从用户态进入内核态,该过程是可能发生在代码的任意位置的,所以在代码执行的任一位置都可能发生;而该进程再次被调度时则是从内核态回到用户态的过程,该过程会检测信号。
3、信号在处理时默认屏蔽本信号
另一种处理信号的方式:sigaction
其中与该函数同名的结构体结构:
代码如下:
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include <sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
using namespace std;//sigaction的使用 signal mask的用处
//当前如果正在处理2号信号,默认2号信号会被屏蔽
//对2号信号处理完成的时候,会自动解除对2号信号的屏蔽 避免递归处理void PrintPending(sigset_t& pending_bit)
{for(int i=31;i>=1;i--){if(sigismember(&pending_bit,i)){cout<<1;}else cout<<0;}cout<<endl;
}
void handler(int signal)
{cout<<"receive signal:"<<signal<<endl;//sleep(100);while(1){sigset_t pending;sigpending(&pending);PrintPending(pending);sleep(1);}exit(1);
}int main()
{struct sigaction act,oact;sigemptyset(&act.sa_mask);//设置处理2号信号时对2号屏蔽的同时,对其他信号也进行屏蔽sigaddset(&act.sa_mask,3);//有很多信号无法被屏蔽 act.sa_handler=handler;sigaction(2,&act,&oact);while(1){cout<<"I am a process,pid:"<<getpid()<<endl;sleep(1);}return 0;
}
代码结果:
从结果中可以看出,在sa_mask中加入3时,处理2号信号的过程中3号信号也被屏蔽了。
当前如果正在处理2号信号,默认2号信号会被屏蔽;对2号信号处理完成的时候,会自动解除对2号信号的屏蔽,这是为了避免递归处理。
七、可重入函数与volatile
1、可重入函数
在执行头插时,有两行代码;在执行第一行代码时发生信号捕捉(OS从用户态进入内核态的方式不止系统调用一种);而信号捕捉内部也有头插,这样最后得到的结果中,信号捕捉内部头插的节点则会丢失;这种函数称为不可重入函数。
STL的绝大多数函数,都是不可重入函数。函数是否可重入是函数的特点,对于不可重入函数,在多线程时就需要注意;可重入函数一般只会使用内部的局部变量而不会使用全局变量。
2、volatile
代码:
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include <sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
using namespace std;int gflag=0;
//volatile int gflag=0;void changedata(int signo)
{cout<<"receive signo:"<<signo<<endl;gflag=1;}int main()
{signal(2,changedata);while(!gflag);//while不要其他代码cout<<"process exit normal"<<endl;return 0;
}
正常编译运行后结果:
但是,编译器知道在main函数执行流中没有对 gflag做修改
CPU有两种运算:算数运算和逻辑运算
对于while(!gflag)语句,其实是CPU不断对gflag做检测,而gflag是在内存中的数据,那么CPU进行判断的话就需要不断的将gflag加载到CPU中
但编译器是可以优化编译的, -O查看所有优化级别
将优化级别改为-O1,就会出现下列问题,即发送2号信号,修改gflag之后进程仍然无法退出。
这是因为优化之后,编译器发现main函数执行流没有对gflag做修改,那么直接将gflag的值放到寄存器中;修改gflag的值只是修改内存中的gflag,而对寄存器中的值没有影响。这是寄存器隐藏了内存中的真实值
这是编译器的优化导致的问题。如何保持内存的可见性?利用volatile关键字。改完之后运行结果:
八、SIGCHILD信号
子进程退出时,会给父进程发送sigchild信号。sigchild的默认处理是ignore。
验证sigchild的存在:
#include<iostream>
#include<signal.h>
#include<cstdlib>
#include <sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
using namespace std;//子进程退出时会给父进程发sigchild信号void DoOtherthing()
{cout<<"I am doing other thing"<<endl;
}
void notice(int signo)
{cout<<"get signal: "<<signo<<" pid: "<<getpid()<<endl;pid_t rid=waitpid(-1,nullptr,0);//waitpid的第一个参数为-1表示等待任意一个子进程if(rid>0){cout<<"wait child success,pid:"<<rid<<endl;}
}int main()
{signal(SIGCHLD,notice);pid_t id=fork();if(id==0){//child processcout<<"I am child process,pid:"<<getpid()<<endl;sleep(5);exit(1);}//父进程while(1){DoOtherthing();sleep(1);}return 0;
}
代码结果:
但以上在信号捕捉中进程等待的问题是:
如果有多个子进程,这些子进程同时退出,那么以上代码只会等待一次,只会回收一个子进程;所以要将代码改成while循环的形式不断回收;
如果有多个子进程,有些进程退出,有些进程永远不退出。此时waitpid就会阻塞,但由于现在是在信号捕捉的逻辑中,那么就会一直停在这而不会去执行main函数中的主逻辑。此时应该选择非阻塞方式等待。代码改成下面的形式:
#include <iostream>
#include <signal.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;// 子进程退出时会给父进程发sigchild信号void DoOtherthing()
{cout << "I am doing other thing" << endl;
}
void notice(int signo)
{cout << "get signal: " << signo << " pid: " << getpid() << endl;while (1){pid_t rid = waitpid(-1, nullptr, WNOHANG); // waitpid的第一个参数为-1表示等待任意一个子进程if (rid > 0){cout << "wait child success,pid:" << rid << endl;}else if(rid<0){cout << "wait child success done" << endl;break; // rid<0说明等待失败,父进程已经没有子进程等待了}else{cout << "wait child success done" << endl;break; // 非阻塞等待方式}}
}int main()
{signal(SIGCHLD, notice);for (int i = 0; i < 10; i++){pid_t id = fork();if (id == 0){// child processcout << "I am child process,pid:" << getpid() << endl;sleep(1);exit(1);}}// 父进程while (1){DoOtherthing();sleep(1);}return 0;
}
由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。
等待的目的:①获取子进程的退出信息②使子进程不再是僵尸进程
如果不关心子进程的退出信息,则可以直接设置对SIGCHILD信号的处理动作为SIG_IGN,这是最简单的方式。系统默认的IGN和用户设置的SIG_IGN是不一样的
相关文章:

信号的产生、处理
一、信号的概念 信号是linux系统提供的一种,向指定进程发送特定事件的方式。收到信号的进程,要对信号做识别和处理。信号的产生是异步的,进程在工作过程中随时可能收到信号。 信号的种类分为以下这么多种(用指令kill -l查看&…...
在Linux中,zabbix如何监控脑裂?
在Linux中,zabbix监控脑裂主要涉及对高可用(HA)系统中可能发生的节点间通信中断或不一致状态的监控。脑裂问题通常发生在具有冗余节点的高可用系统中,如集群、HA系统或分布式数据库系统,当节点之间失去通信时ÿ…...

C++基础概念复习
前言 本篇文章作基础复习用,主要是在C学习中遇到的概念总结,后续会继续补充。如有不足,请前辈指出,万分感谢。 1、什么是封装,有何优点,在C中如何体现封装这一特性? 封装是面向对象编程&…...

Earth靶场
打开靶机后使用 arp-scan -l 查询靶机 ip 我们使用 nmap 进行 dns 解析 把这两条解析添加到hosts文件中去,这样我们才可以访问页面 这样网站就可以正常打开 扫描ip时候我们发现443是打开的,扫描第二个dns解析的443端口能扫描出来一个 txt 文件 dirsear…...
JavaScript 日期格式
在 JavaScript 中,日期格式可以通过 Date 对象进行操作和格式化。下面是一些常见的 JavaScript 日期格式及其示例: 1. ISO 8601 格式 ISO 8601 是一种标准的日期和时间表示方法,格式为 YYYY-MM-DDTHH:mm:ss.sssZ,例如: let date = new Date(); console.log(date.toISOS…...

django vue3实现大文件分段续传(断点续传)
前端环境准备及目录结构: npm create vue 并取名为big-file-upload-fontend 通过 npm i 安装以下内容"dependencies": {"axios": "^1.7.9","element-plus": "^2.9.1","js-sha256": "^0.11.0&quo…...

xiaoya小雅超集使用夸克网盘缓存教程
距离上一次小白写到关于小雅的教程已经过去了一周的时间,这段时间里,有很多小伙伴都想知道怎么用夸克网盘作为小雅的缓存。 今天这不就来了吗? 这段时间确实是比较忙,毕竟快过年了嘛,辛辛苦苦一整年,至少…...
计算机基础知识复习1.4
数据库事务 #开启一个事务 start transaction #执行SQL语句 SQL1 SQL2 .. #提交事务 commit 类加载器 启动类加载器:负责加载Java的核心库 用C编写,是JVM的一部分,启动类加载器无法被Java程序直接引用 扩展类加载器:是Java语…...

SpringMVC(三)请求
目录 一、RequestMapping注解 1.RequestMapping的属性 实例 1.在这里创建文件,命名为Test: 2.复现-返回一个页面: 创建test界面(随便写点什么): Test文件中编写: 编辑 运行: 3.不返回…...

Node.js应用程序遇到了内存溢出的问题
vue 项目 跑起来,一直报错,内存溢出 在 文件node_modules 里 .bin > vue-cli-service.cmd 在依赖包这个文件第一行加上这个 node --max-old-space-size102400 "%~dp0\..\vue\cli-service\bin\vue-cli-service.js" %* node --max-old-s…...

如何构建云原生时空大数据平台?
在现代企业中,随着对技术的依赖日益加深,空间数据的重要性愈发显著。它通过结合地理成分(如纬度、经度、地址、邮编等)与业务数据,成为解决复杂问题的重要工具。地理空间数据可从多种来源获取,例如卫星影像…...

二极管钳位电路分享
二极管钳位(I/O的过压/浪涌保护等) 如果我们的电路环境接收外部输入信号容易受到噪声影响,那我们必须采取过压和浪涌保护措施,其中一个方式就是二极管钳位保护。 像上图,从INPUT输入的电压被钳位在-Vf与VCCVf之间&…...

腾讯云智能结构化 OCR:驱动多行业数字化转型的核心引擎
在当今数字化时代的汹涌浪潮中,数据已跃升为企业发展的关键要素,其高效、精准的处理成为企业在激烈市场竞争中脱颖而出的核心竞争力。腾讯云智能结构化 OCR 技术凭借其前沿的科技架构与卓越的功能特性,宛如一颗璀璨的明星,在交通、…...
19.3、Unix Linux安全分析与防护
目录 UNIX/Linux系统的安全分析与防护UNIX/Linux系统安全增强技术UNIX/Linux安全模块应用参考国产操作系统安全分析与防护 UNIX/Linux系统的安全分析与防护 unix和linux操作系统分成三层,分别是硬件层,系统内核层以及应用层。Windows系统也是分成三层&a…...

JVM对象内存结构
1对象内存结构说明 注意: 如果对象为数组对象,在对象头后面有4字节存储数组长度; 1.1对象头 对象头分为Mark Word和Class Pointer两部分; Mark Word:对象基础信息 32位操作系统中占4字节,64位操作系统中占8…...
联邦学习和大模型相结合: 数据隐私,提升训练效率,架构优化
联邦学习和大模型相结合: 数据隐私,提升训练效率,架构优化 数据隐私保护方面 增强隐私保护机制:大模型通常需要大量的数据进行训练,而联邦学习可以在不共享原始数据的情况下进行模型训练。结合两者,可以设计出更强大的隐私保护机制,如利用联邦学习的加密技术和差分隐私…...

命令别名和命令历史
1.1命名别名 别名是命令的快捷方式。对于需要经常执行,并需要很长时间输入的长命令创建快捷方式很有用。 语法: alias 别名 ’ 原命令 [ 选项 ]…… [ 参数 ]……’ 1.2命令历史...
打造三甲医院人工智能矩阵新引擎(二):医学影像大模型篇--“火眼金睛”TransUNet
一、引言 1.1 研究背景与意义 在现代医疗领域,医学影像作为疾病诊断与治疗的关键依据,发挥着不可替代的作用。从传统的X射线、CT(计算机断层扫描)到MRI(磁共振成像)等先进技术,医学影像能够直观呈现人体内部结构,为医生提供丰富的诊断信息,涵盖疾病识别、病灶定位、…...
Scade pragma: separate_io
概述 在 Scade 语言中,支持对用户自定义算子使用 separate_io pragma 进行修饰。其形式如: function #pragma kcg separate_io #end N(x: int8) returns (y,z: int8) let y x;z x; tel在上例中,算子N 就被 pragma #pragma kcg separate_i…...

IWOA-GRU和GRU时间序列预测(改进的鲸鱼算法优化门控循环单元)
时序预测 | MATLAB实现IWOA-GRU和GRU时间序列预测(改进的鲸鱼算法优化门控循环单元) 目录 时序预测 | MATLAB实现IWOA-GRU和GRU时间序列预测(改进的鲸鱼算法优化门控循环单元)预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现IWOA-GRU和GRU时间序列预测…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...
flow_controllers
关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...