linux中对信号的认识
信号的概念与相关知识认识
信号是向目标进程发送消息通知的的一种机制。 信号可以以异步的方式发送给进程,也就是说,进程无需主动等待,而是在任何时间都可以接收到信号。
信号的种类
用kill-l命令查看系统定义的信号列表:
前台进程和后台进程的认识
我们首先要知道,打开xshell软件时,默认就已经执行了shell进程,而这就是前台进程,而且前台进程只有一个。当我们运行一个我们写的死循环进程时:
#include <iostream>
#include <unistd.h>
using namespace std;int main()
{while(1){cout << "haha" << endl;sleep(1);}return 0;
}
其实此时该进程就自动变成了前台进程,而操作系统会自动的将shell切换成后台进程。而且此时我们呢无论输入什么指令都不会被执行,因为我们所输入的指令默认都会被前台进程接收。但是我们可以通过kill -9 进程pid和Ctrl c来终止此前台进程,终止以后,操作系统还会自动的将shell设置为前台进程。记住Ctrl c并不能终止前台进程shell。
如何生成后台进程
如果我们想要将进程执行成后台进程的话可以在运行进程的后面加上&符号:
而对于这两个数字对应的分别就是:后台进程编号和该后台进程pid。
而且我们可以同时可以执行多个后台进程。
查看后台进程
通过jobs指令查看后台进程:
后台进程与前台进程的转换
通过fg(front ground)+后台进程编号将后台进程变成前台进程:
通过Ctrl z指令和bg(back ground)+暂停进程编号将前台进程变成后台进程:
我们可以通过Ctrl z将前台进程暂停,但是我们的前台必须要有进程执行,否则系统就会挂掉了,此时操作系统会默认将shell设置为前台进程,因此原先的前台进程就被暂停在了后台中。
操作系统是怎么知道外设有数据的
操作系统通常通过中断机制来确定外设是否有数据可用。中断是计算机系统中的一种重要机制,它是指在计算机执行某个任务时,由于发生了某种特殊事件或接收信号,硬件或软件会中断当前任务的执行,转而去处理需要优先处理的事件或任务。中断可以打破程序的顺序执行,提供了一种异步(随时)处理的方式。而且中断处理程序执行结束后,处理器会恢复之前保存的上下文,继续原来的任务执行。
我们知道CPU包括运算器和控制器的,对于控制器其实就是其他设备的控制的(因为是要控制信息,所以也会和外设相交互)。CPU在硬件上是和内存关联,而自身会提供一个个从0开始编号的针脚。针脚是和主板直接相连,而主板上插入着很多的硬件电路(相当于USB口)。而这些硬件电路就和我们的外设直接关联着。
当我们的外设在使用的时候,此时对应的针脚就会接收外设传来的高电频,从而点亮针脚(只有能进行IO的外设才能向CPU发消息的)。而我们的每个针脚都有对应的编号(中断号),所以此时的编号就会被写到寄存器里,就可以被操作系统读取。所以我们的外设有数据的时候本质上就是通过发送中断号的方式来判断。
而我们中断号的作用其实就是调用对应硬件的方法的。在操作系统内部提供了一张中断表,该表是一个函数指针数组,数组下标对应的就是中断号(0号是正常运行),其中存放的就是特定硬件的读取方法。
信号的产生
键盘产生
信号的产生其实就是通过键盘产生。当我们运行一个死循环程序时,按住Ctrl c(其实就是发送2号信号)组合键时就可以中断进程。我们一般在shell上输入的指令都会放到进程的缓冲区中进行读取,而当我们输入Ctrl c组合键,并没有将数据输入给进程,而是直接转换成某种动作,向进程发送信号的方式。而进程接收信号并处理,从而终止进程。
我们操作系统当中,每一个进程都会维护一张处理信号的方法表:其实就是函数指针数组,该数组中的每一个下标对应的数据内容都是信号的处理方法。
认识signal函数:
该函数的返回值是一个参数为int的函数指针 。而参数一是信号编号,参数二的类型和函数返回值类型一样,是一个函数指针。其实该函数的功能就是重置(自定义)进程接收信号后的反应机制,也就是重置信号处理的函数指针数组里的方法。但是一些信号,如9号信号不能被自定义捕捉修改。
void handler(int signo)
{cout << "进程收到了" << signo << "信号" << endl;exit(1);
}
int main()
{signal(2, handler); // 自定义捕捉2号信号while (1){cout << "死循环" << endl;sleep(1);}return 0;
}
信号的本质
信号实质是操作系统(进程的管理者)向目标进程发送的,进程也需要对信号进行管理。而底层其实就是信息的输入与输出,操作系统向对应的进程发送信号编号,然后进程PCB中存在中管理信号的结构体,其结构体内部会存在一张位图,而每一位都对应着各个编号的信号,如果比特位内容为1就表明收到该位置对应的信号。
所以一个进程含有信号的函数指针数组和信号位图。
系统调用产生
通过kill函数对指定进程发送信号
该函数就是我们shell命令行中的kill指令,所以我们的实现使用就可以通过命令行参数:
//kill模拟实现
//kill -9 pid
int main(int argc,char* argv[])
{if(argc!=3){cout<<"输入方式错误"<<endl;cout<<"please input "<<argv[0]<<" + "<<"signum + process_id"<<endl;return -1;}int signum = stoi(argv[1]+1);//去掉'-'字符int process_id = stoi(argv[2]);kill(process_id,signum);return 0;
}
通过raise函数对当前进程发送信号
void handler(int signo)
{cout<<"向当前进程发送信号:"<<signo<<endl;
}
int main()
{signal(2,handler);//重置当前进程的信号处理表中的2号方法while(1){raise(2);sleep(1);}return 0;
}
通过abort函数对当前进程发生终止信号
void handler(int signo)
{cout<<"向当前进程发送信号:"<<signo<<endl;
}
int main()
{signal(6,handler);//重置当前进程的信号处理表中的6号方法abort();while(1){cout<<"运行中..."<<endl;}return 0;
}
代码编译异常产生信号
除0异常
int main()
{int a=10;int ret=a/0;//0不能作为除数return 0;
}
我们知道CPU包含运算器和控制器。所以CPU会存在很多的寄存器会临时存储调度进程中需要进行运算的数据。而CPU中还存在着一种状态寄存器(status),状态寄存器中有很多的标志位,很多标志位是用来衡量运算的结果状态的。而溢出标记为也是其中。当我们的操作系统调度进程时,将改进程放到运行队列中,如果CPU的运算器发现除数为0时,状态寄存器中的溢出标志位就被置为1。而此时寄存器中的标志位出问题时,CPU中的内部模块或针脚就会向操作系统通知,而操作系统就会根据针脚为下标的函数指针数组表的内容执行对应的中断方法。操作系统接下来就会将溢出标志位解释为kill+目标进程+信号的方式向目标进程发信号。
这对应的恰好就是信号表中的8号信号。
更改8号信号处理方式:
void handler(int signo)
{cout<<"进程错误信号:"<<signo<<endl;sleep(1);
}
int main()
{signal(8,handler);//自定义8号信号处理方法int a=10;a/=0;//0不能作为除数return 0;
}
现象与解释:
我们发现运行可执行程序以后就开始间隔一秒的死循环输出错误信息,而并没有退出。
对此现象我们要知道,寄存器硬件是属于CPU的,而寄存器里的内容是属于当前调度的进程的,寄存器≠寄存器里的内容。
当正常情况下:我们除0错误时,状态寄存器的对应标志位会置为1,所以此时就会向操作系统发送错误信号,最终终止进程,而其他进程被CPU调度运行时,对应的寄存器数据就会被覆盖改写,所以寄存器也就供当前进程使用了。
而对于以上情况时:我们进行了自定义的信号处理方式,所以我们的状态寄存器发现了除0错误以后就会向操作系统发送信号,操作系统就会解释为8号信号发送给进程,而进程会通过调用handler函数来处理8号错误信号。而该进程并没有退出,且CPU一直在调度该进程,但是异常是一直存在的,所以CPU就会一直向操作系统发送信号 。
CPU发现异常会通过操作系统向进程发送信号,而进程也会对信号进行处理,但是进程没有退出,CPU中标记进程执行位置的寄存器并不会往下调度。
软件条件产生信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM(14号)信号, 该信号的默认处理动作是终止当前进程。
闹钟的设置使用
int n=0;
void handler(int signo)
{n = alarm(0);//参数等于0表示取消之前设置的闹钟,返回值n还是之前闹钟的剩余时间//参数大于0表示重设闹钟cout<<"alarm result : "<<n<<endl;exit(1);
}
int main()
{cout<<"mypid = "<<getpid()<<endl;signal(14,handler);alarm(20);//返回值是闹钟剩余的时间,一般都是0while(1){}return 0;
}
信号的保存
我们操作系统向进程传输信号的时候,进程一般可能并不会立即处理会再合适的时候处理。
信号常见概念
- 实际执行信号的处理动作称为信号递达(Delivery)
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
- 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
信号递达
信号递达分为信号的忽略(信号接收以后取消响应)、信号的默认(无论之前信号应答方式如何,一旦默认就恢复最初的处理方式)、信号的自定义捕捉(我们自己实现handler方法)。而这些递达方式均可以通过signal函数进行实现。
void handler(int signo)
{cout<<"信号自定义捕捉处理 "<<signo<<endl;//exit(1);
}
int main()
{cout<<"mypid = "<<getpid()<<endl;signal(2,handler);sleep(10);cout<<"默认处理"<<endl;//signal(2,SIG_IGN);//忽略信号处理signal(2,SIG_DFL);//默认信号处理while(1){sleep(1);}return 0;
}
信号未决
信号未决顾名思义就是进程收到信号但还未对信号做出决策。也就是我们的操作系统向进程信号后,将信号标识到进程PCB中的信号位图中。
信号阻塞
信号被阻塞就是指当前进程不对所接收的信号做任何处理,但是依旧记录着该信号。也就是说明该信号在未处理时一直处于未决阶段,不递达。
而递达中的信号忽略并不是阻塞,因为忽略其实是递达的一种,也就是已经对信号做了处理,而阻塞是还没做处理。
信号在内核中的表示
内核中会存在数据结构为其进程维护这三张表,分别是block表(该位图表标识着该位置处信号是否阻塞和屏蔽),pending表(和block表的结构一样,其实就是未决表,标识该进程是否收到该信号),handler表(这是一张函数指针数组表,其中的数组小标对应着信号的编号,其中的内容就是对应信号的处理方法)。
对信号集的操作
我们的handler表可以通过我们实现的函数进行传参,从而对该表的内容进行修改。对于阻塞位图表与未决位图表,我们也有对应的函数进行修改。
既然要设置信号集,而信号集的结构又是位图结构,所以我们肯定是要用到该结构的对象,然后通过系统调用函数将该信号集对象设置到进程的内核中。
sigset_t类型
sigset_t是一种数据类型,其实底层也就是我们的位图结构,0对应的就是无该位置对应的信号,1对应的就是有。而且提供了相关函数将该类型的数据进行处理。
int sigemptyset(sigset_t *set);//初始化set所指向的信号集,表示该信号集不包含任何有效信号
int sigfillset(sigset_t *set);//初始化,表示该信号集包含所有系统支持的有效信号
int sigaddset (sigset_t *set, int signo);//添加信号signo
int sigdelset(sigset_t *set, int signo);//删除信号signo
int sigismember(const sigset_t *set, int signo);//判断是否存在信号signo
在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的
状态。
更改屏蔽字(阻塞信号集)sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
假设当前信号屏蔽字为mask:
how | 说明 |
SIG_BLOCK | set包含了我们希望阻塞的附加信号,相当 mask=mask|set |
SIG_UNBLOCK | set包含了我希望解除阻塞的信号,相当于 mask=mask&(~set) |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值,相当于 mask=set |
对于参数二和三,如果oset是非空指针,则读取进程的当前信号屏蔽字,通过oset参数传出。
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字
使用该函数:
//sigprocmask信号屏蔽void handler(int signo)
{cout<<"当前收到"<<signo<<"号信号"<<endl;
}
int main()
{cout<<"mypid = "<<getpid()<<endl;sigset_t set,oset;//创建临时位图block表signal(2,handler);//自定义二号信号的方法//清空临时信号集sigemptyset(&set);sigemptyset(&oset);//设置信号集到临时变量sigaddset(&set,2);sigprocmask(SIG_SETMASK,&set,&oset);//将信号集设置到该进程中while(1){}return 0;
}
当我们发送二号信号时,该进程是屏蔽此信号的,也就是并没有做出任何的反应。
而且我们的9号信号是不会被屏蔽的。
获取未决信号集sigpending
其实对于pending表,我们之前已经了解可以通过键盘上的组合键等方式来设置pending表,所以我们现在想知道的就是pending表中的内容。
sigpending(sigset_t *set)
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1
函数使用:
//sigpending获取未决信号集void handler(int signo)
{cout<<"当前收到"<<signo<<"号信号"<<endl;
}
void print(const sigset_t& tmp)
{for(int i=1;i<=32;i++){if(sigismember(&tmp,i))cout<<1;elsecout<<0;}cout<<endl;
}
int main()
{signal(2,handler);//自定义//屏蔽2号信号cout<<"mypid = "<<getpid()<<endl;sigset_t set,tmp,oset;sigemptyset(&set);sigemptyset(&tmp);sigemptyset(&oset);sigaddset(&set,2);sigprocmask(SIG_BLOCK,&set,&oset);//将信号集添加到该进程中int count=0;while(1){sigpending(&tmp);print(tmp);//取消屏蔽,信号处理if(count==3){cout<<"解除2号信号的屏蔽状态"<<endl;sigprocmask(SIG_UNBLOCK,&set,&oset);//解除屏蔽后,进程立马就递达了}sleep(1);count++;}return 0;
}
对于非屏蔽的信号,也就是在pending集中处于未决状态,当我们发送某一个信号时,该pending表的对应信号位图数据瞬间由1置为0(在执行handler方法之前)。
信号的处理
认识进程内核态和用户态
我们对于信号的处理需要先了解进程状态,存在内核态和用户态。我们进程默认都是用户态的,也是一种受控的状态,访问的资源都是有限的。内核态是一种操作系统的工作状态,能够访问大部分系统资源,如系统调用函数。
我们的进程地址空间的0到3GB都属于用户空间,3到4GB属于内核空间。而对应的就分别是用户态和内核态。当我们进程在启动的时候,内存中的操作系统会首先被映射到内核空间的,而这之中虚拟和物理地址之间通过内核级页表进行转换。所以当我们在调用系统函数的时候就可以在地址空间从正文代码中去访问内核空间里的数据。但是此时是需要将进程从用户态切换到内核态。
我们CPU是实现对进程的调度的,而且CPU中存在很多的寄存器。对于进程的用户态还是内核态,CPU会通过一个CS寄存器中两个比特位数据进行判定,01表示内核,11表示用户。所以对应状态切换就是改变CS寄存器中的数据。同时进行页表(也同样保存在寄存器中)的切换。
信号捕捉处理
我们知道信号的处理是要在内核中进行的,因为我们内核中有关于信号的三张表,block、pending、handler。所以我们信号处理实际就是在进程从内核态返回到用户态的时候。
进程在执行接收的信号时,首先回去切换到内核态去遍历三张表,判断是否接受信号,是否阻塞,是否自定义了handler方法。如果没有自定义handler方法就是直接在内核中进行执行。但是如果自定义了handler方法的话就需要对信号方法进行捕捉处理。此时进程时时需要返回到用户态去遍历handler中的代码的(如果不转换成用户态而是保持内核态去访问handler方法的话,可能会造成非法资源的访问,有危险),当执行完handler方法之后依旧是要切换回来的, 而切换回来是因为通过了特殊的系统调用执行了sigreturn返回到内核。此时信号处理完毕最终进程返回到用户态。
而且对于进程在运行期间是会存在很多次的进程切换的。就例如代码执行死循环,然后通过Ctrl c命令直接终止进程的过程。此时进程运行,被CPU调度,但是每个进程都有时间片,所以在CPU执行的过程中,时钟中断会不断进行检测,如果该进程时间片到了,操作系统就会终止此进程,然后将该进程从CPU中剥离(此时进程上下文等数据都会保存到进程PCB中,好在下次调度是直接将数据拷贝到寄存器中),在运行队列重新排队。此时CPU会执行Ctrl c命令,切换到内核态执行该命令,终止进程。
sigaction读取和修改与指定信号相关联的处理动作
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);struct sigaction
{ void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};
我们主要了解一下第二个参数sigaction类型,其中该类型是一个结构体,其中主要认识一下sa_handler字段和sa_mask字段。
对于sigaction结构体sa_handler的使用就相当于是signal函数,对信号自定义捕捉:
void handler(int signo)
{cout<<" recieve a signal: "<<signo<<endl;
}
int main()
{struct sigaction act,oact;act.sa_handler=handler;sigaction(2,&act,&oact);while(1);return 0;
}
对于sigaction结构体的sa_mask的使用表明需要额外屏蔽的信号:
当某个信号的处理函数被调用时(还没调用完成),内核会自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时则会自动恢复原来的信号屏蔽字。这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞,直到当前该信号handler方法处理完为止。
验证:
void print(const sigset_t& tmp)
{for(int i=31;i>=1;i--){if(sigismember(&tmp,i))cout<<1;elsecout<<0;}cout<<endl;
}
void handler(int signo)
{cout<<" recieve a signal: "<<signo<<endl; while(1)//执行不退出{sigset_t pending;sigpending(&pending);print(pending);sleep(1);}}int main()
{cout<<"pid = "<<getpid()<<endl;struct sigaction act,oact;act.sa_handler=handler;sigaction(2,&act,&oact);sleep(5);return 0;
}
分析:当我们 第一次按住Ctrl c时,会执行2号信号的自定义方法,此时在执行之前就会将pending集中的2号由1置为0,开始执行对应的方法。而接下来再次接收该信号时就会发生阻塞,不会执行2号信号的方法,而且会将pending表中的2号位置置为1.
如果在调用信号处理函数时,除了当前信号被自动屏蔽(正在执行当前信号且没退出时)之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
以上代码加上:同时屏蔽三四号信号
int main()
{cout<<"pid = "<<getpid()<<endl;struct sigaction act,oact;act.sa_handler=handler;sigemptyset(&act.sa_mask);//初始化sigaddset(&act.sa_mask,3);//同时屏蔽三四号信号sigaddset(&act.sa_mask,4);sigaction(2,&act,&oact);sleep(10);return 0;
}
当我们进程sleep(10)的过程中接受了信号的话,那么就会执行信号的处理方法,处理完以后就会停止继续sleep,而是执行sleep之后剩余代码。
认识SIGCHLD信号
我们知道可以通过wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,从而在此实现回收子进程的工作。
void handler(int sig)
{pid_t id;while( (id = waitpid(-1, NULL, WNOHANG)) > 0)//循环等待,回收所有子进程,防止多个进程同时退出,形成阻塞,导致不会调用handler方法。WNOHANG等待方式防止子进程一直不退出而导致父进程无法执行后续代码。{printf("wait child success: %d\n", id);}printf("child is quit! %d\n", getpid());
}
而且对于linux平台中还有一种方式:父进程不用进行等待回收子进程的资源数据,当子进程退出时,操作系统会自动的回收子进程的资源。
signal(SIGCHLD, SIG_IGN);//忽略
相关文章:

linux中对信号的认识
信号的概念与相关知识认识 信号是向目标进程发送消息通知的的一种机制。 信号可以以异步的方式发送给进程,也就是说,进程无需主动等待,而是在任何时间都可以接收到信号。 信号的种类 用kill-l命令查看系统定义的信号列表: 前台…...

【万题详解】P1048 [NOIP2005 普及组] 采药
题目描述 链接——题目在这里!!! 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草…...

Golang基于Redis bitmap实现布隆过滤器(完结版)
Golang基于Redis bitmap实现布隆过滤器(完结版) 为了防止黑客恶意刷接口(请求压根不存在的数据),目前通常有以下几种做法: 限制IP(限流)Redis缓存不存在的key布隆过滤器挡在Redis前 …...

Java基础-内部类
内部类 引言内部类的共性成员内部类静态内部类非静态内部类 局部内部类匿名内部类内部类的使用场景和好处 引言 Java不仅可以定义变量和方法,还可以定义类. 内部类允许你把一些逻辑相关的类组织在一起,并可以控制内部中类的可见性. 这么看来,内部类就像是代码一种隐藏机制:将类…...

设计模式-行为型模式-职责链模式
在软件系统运行时,对象并不是孤立存在的,它们可以通过相互通信协作完成某些功能,一个对象在运行时也将影响到其他对象的运行。行为型模式(Behavioral Pattern)关注系统中对象之间的交互,研究系统在运行时对…...

代码随想录算法训练营第四十天|LeetCode343 整数拆分、LeetCode96 不同的二叉搜索树
343.整数拆分 思路:确定dp数组以及下标的含义 dp[i]代表 i可以被拆分后的最大乘积。确定递推公式,假如拆成连个数,dp[i] j*(i-j),拆成两个数以上,dp[i]j*dp[i-j],j的范围为1到i-1.dp[i]找到所有情况的最大值。初始化…...

接口自动化测试用例如何设计
说到自动化测试,或者说接口自动化测试,多数人的第一反应是该用什么工具,比如:Python Requests、Java HttpClient、Apifox、MeterSphere、自研的自动化平台等。大家似乎更关注的是哪个工具更优秀,甚至出现“ 做平台的 &…...

弱电综合布线:连接现代生活的纽带
在当今信息化快速发展的时代,弱电网络布线作为信息传输的重要基础设施,其作用日益凸显。它不仅保障了数据的高效流通,还确保了通信的稳定性。从商业大厦到教育机构,从政府机关到医院急救中心,再到我们居住的社区&#…...

Java零基础 - 数组的定义和声明
哈喽,各位小伙伴们,你们好呀,我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。 我是一名后…...

CSS补充(下),弹性布局(上)
高级选择器 1.兄弟选择器 2.同时满足 div.bg{background-color: red;}p.bg{background-color: green;}spam.bg{background-color: blue;}注:选择器中间没有空格,有明确标识的选择器写在后面 3.各种伪类的应用 3.1作为第几个子元素 选择器:nth-child…...

图数据库 之 Neo4j - 应用场景4 - 反洗钱(9)
原理 Neo4j图数据库可以用于构建和分析数据之间的关系。它使用节点和关系来表示数据,并提供实时查询能力。通过使用Neo4j,可以将大量的交易数据导入图数据库,并通过查询和分析图结构来发现洗钱行为中的模式和关联。 案例分析 假设有一家转账服务公司,有以下交易数据,每个…...

uboot分区介绍
RK平台的U-Boot支持两种分区表 RK paramter格式(旧)和 标准GPT格式(新),当机器上同时存在 两种分区表时,优先使用GPT分区表。无论是 GPT 还是 RK parameter,烧写用的分区表文件都叫parameter.t…...

快速收集诊断信息,敏捷诊断工具obdiag应用实践——《OceanBase诊断系列》之三
1. 前言 作为OceanBase的敏捷诊断工具,obdiag具有以下特点: 部署便捷:提供rpm包和OBD上部署的模式,都能够一键部署安装。用户可以选择将其部署到集群中任意一台能连接到各个节点的设备上,而不仅限于OBServer节点。即…...

C++错误总结(1)
1.定义函数类型时,如果没有返回值,用void void swap(int &x, int &y){ int tem x; x y; y tem; } 2.输入时,不加换行符 cin >> a >> b >> c >> endl ;(红色标记的是错误的部分) 3.【逆序出入…...

std::shared_from_this注意事项:exception bad_weak_ptr
1.不可以在构造函数中调用shared_from_this() 因为它的实现是: _LIBCPP_INLINE_VISIBILITYshared_ptr<_Tp> shared_from_this(){return shared_ptr<_Tp>(__weak_this_);}也就是它依赖的__weak_this_此时还未创建完成。 2.一定要public继承 class MyTy…...

【工具】Raycast – Mac提效工具
引入 以前看到同事们锁屏的时候,不知按了什么键,直接调出这个框,然后输入lock屏幕就锁了。 跟我习惯的按Mac开机键不大一样。个人觉得还是蛮炫酷的~ 调研 但是由于之前比较繁忙,这件事其实都忘的差不多了࿰…...

蓝桥杯集训·每日一题2024 (二分,双指针)
前言: 开学了,平时学习的压力也逐渐大起来了,不过还算可以接受,等到后面阶段考的时候就不一样了,我目前为了转专业退选了很多课,这些课我都需要花时间来刷绩点,不然保研就没有竞争力了。我自己会…...

在Linux(Ubuntu)中使用终端编译 vscode安装
文章目录 📚在Linux(Ubuntu)中使用终端编译🐇.cpp程序编译🐇.py程序编译🐇查看Python、C编程环境 📚vscode安装 📚在Linux(Ubuntu)中使用终端编译 虚拟机安装…...

官网正在被哪些产品蚕食,定制网站又被哪些建站产品挤占。
2023-12-09 16:22贝格前端工场 官网建设是一个被大多数人看衰的市场,本文来理性分析下,谁在蚕食这个市场,谁又在挤占这个产品生存空间,欢迎大家评论,探讨。 网站正在被以下产品形式取代: 1. 移动应用&…...

BUUCTF---[MRCTF2020]你传你呢1
1.题目描述 2.打开题目链接 3.上传shell.jpg文件,显示连接成功,但是用蚁剑连接却连接不上。shell文件内容为 <script languagephp>eval($_REQUEST[cmd]);</script>4.用bp抓包,修改属性 5.需要上传一个.htaccess的文件来把jpg后缀…...

vite+vue3门户网站菜单栏动态路由控制
门户网站用户端需要分板块展示,板块内容由管理端配置,包括板块名称,访问路径,路由组件,展示顺序,是否展示。如下图所示: 用户访问门户网站时,展示菜单跳转通过板块配置,动…...

【C语言】linux内核packet_setsockopt
一、中文注释 // 发送数据包函数。它尝试通过特定的网络设备队列直接传输一个skb(socket缓冲区)。 static int packet_direct_xmit(struct sk_buff *skb) {return dev_direct_xmit(skb, packet_pick_tx_queue(skb)); // 调用dev_direct_xmit函数&#x…...

LeetCode的使用方法
LeetCode的使用方法 一、LeetCode是什么?1.LeetCode简介2.LeetCode官网 二、LeetCode的使用方法1.注册账号2.力扣社区力扣编辑器 2.1 讨论发起讨论参与讨论关注讨论 2.2 文章撰写文章关注文章 3.力扣面试官版测评面试招聘竞赛 4.力扣学习LeetBook 书架我的阅读猜您喜…...

Vue事件处理:.passive修饰符与应用场景
.passive修饰符 passive这个修饰符会执行默认方法。你们可能会问,明明默认执行为什么会设置这样一个修饰符。这就要说一下这个修饰符的本意了。 浏览器只有等内核线程执行到事件监听器对应的JavaScript代码时,才能知道内部是否会调用preventDefa…...

智慧城市中的数字孪生:构建城市管理的未来框架
目录 一、引言 二、数字孪生技术概述 三、数字孪生技术在智慧城市中的应用 1、实时监测与预警 2、模拟与优化 3、智能化决策 4、协同与共享 四、数字孪生技术构建城市管理的未来框架的价值 1、提高管理效率 2、优化资源配置 3、提升公共服务水平 4、增强应对突发事…...

强引用、软引用、弱引用、幻象引用 —— Java的四种引用类型解析
强引用、软引用、弱引用、幻象引用 —— Java的四种引用类型解析 在Java中,对象的生命周期并不总是由我们直接控制。除了我们常见的强引用外,Java还提供了软引用、弱引用和幻象引用这三种引用类型,它们对对象生命周期的影响各不相同。理解这…...

基于禁忌搜索算法(TS)的TSP(Python实现)
本篇文章是博主在最化优学习、人工智能等领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在最优化算…...

Linux shell 网络掩码地址转CIDR
例子: ./1.sh 255.255.255.0 ./1.sh 255.255.255.128 ./1.sh 255.255.0.0 源实现: #!/bin/bashnetmask_to_cidr() {local IFSlocal -a octetslocal i0local cidr0IFS. read -r -a octets <<< "$1"for octet in "${octets[]}…...

C#,煎饼排序问题(Pancake Sorting Problem)算法与源代码
1 煎饼排序问题 给定一个未排序的数组,任务是对给定数组进行排序。您只能在阵列上执行以下操作。 翻转(arr,i):将数组从0反转为i 示例: 输入:arr[]{23、10、20、11、12、6、7} 输出:…...

13.西瓜书——半监督学习
1.概述 (1) 纯半监督学习 (Pure Semi-Supervised Learning) 纯半监督学习是一种典型的半监督学习方法,它的主要特点是同时利用有标签数据和无标签数据进行模型训练。目标是通过整合这两种类型的数据来提高模型的泛化性能。在这个过程中&#…...