【Linux】深入理解进程信号机制:信号的产生、捕获与阻塞

52bc67966cad45eda96494d9b411954d.png

🎬 个人主页:谁在夜里看海.

📖 个人专栏:《C++系列》《Linux系列》《算法系列》

⛰️ 时间不语,却回答了所有问题


目录

📚前言

📚一、信号的本质

📖1.异步通信

📖2.信号列表

📖3.信号发送的本质

📚二、信号的产生

📖1.终端按键

🔖core dump

📖2.系统调用

🔖kill()

🔖raise()

🔖abort()

🔖alarm()

📚三、信号的捕获

📖1.signal信号处理

🔖函数原型

🔖示例

📖2.sigaction信号处理

🔖函数原型

🔖示例 

📚四、信号的阻塞与未决

📖1.阻塞的本质

🔖未决信号

🔖阻塞信号

⚠️阻塞≠忽略

📖2.阻塞的应用

🔖函数原型

🔖示例

📚五、可重入函数

📖1.定义

🔖特性

🔖示例

📖2.volatile关键字

🔖-O1优化 


📚前言

上一章我们谈论了进程间的通信机制,由于不同进程之间有时需要相互协作,所以引入了进程间通信的概念,使得不同的进程可以共享同一份资源,完成数据的传输。进程间通信时是用户层面的通信,为什么这么说呢,因为在这个过程中,用户空间的进程通过特定的通信机制来传递数据或信息,而这些操作并不涉及操作系统的内核执行逻辑,通信的内容也是由用户自定义的。

然而系统层面也需要通信,比如一个进程执行异常时,操作系统必须对异常进程进行处理,那么有哪些处理方式呢?

✅①:直接“杀死”该进程,将进程从CPU运行队列中移除,但是存在隐患,比如文件还没关闭,资源还未释放,有内存泄漏的风险;

✅②:告知进程退出,让进程主动退出,这样可以保证资源被释放。

系统告知进程退出,不就是系统与进程间的通信嘛,这一类通信不需要用户层的参与,在操作系统底层完成,并且通信的内容也不由用户定义,而是由系统定义,这种通信方式就叫做信号

📚一、信号的本质

📖1.异步通信

在用户层的通信中,比如管道通信,我们可以明确通信何时发生,数据何时进行交换吗?答案是可以,管道通信是借助一个管道文件完成的,进程需要对管道文件进行打开和关闭操作,而这些操作在执行流中是确定的,也就是说,用户规定了进程在何时发送数据,何时接收数据,我们称这种通信为同步通信

然而在系统层的通信中,我们可以明确通信何时发生吗,就比如程序执行异常时,需要发送信号告知程序终止,我们能确定程序何时异常吗,并不能确定(如果能确定的话,我们就完全可以避免异常的发生了,就不会引发异常了),我们称这种通信为异步通信

📖2.信号列表

❓为什么操作系统需要与进程进行通信呢?

✅通常,进程和操作系统之间的通信发生在一些特殊情况下,而不是无缘无故的通信。这些特殊情况往往是有限的,比如异常、外部中断、资源不足等。针对这些情况,操作系统有相应的处理方法,例如遇到异常时,操作系统会终止进程;当外部中断发生时,操作系统会中断进程的执行并进行相应处理。因此,操作系统只需将所有可能出现的特殊情况及其对应的处理办法保存在一个集合中,当进程遇到某种情况时,操作系统就发送相应的信号进行处理,这个集合被称为信号列表。

通过kill -l命令可以查看系统定义的信号列表:

我们可以看到,每个信号都有一个编号以及宏定义名称,这些宏定义可以signal.h中找到。其中编号32号以下的信号是标准信号,它们用于处理进程中的常见情况,如进程终止、异常、退出等;32号以上的信号是是实时信号,通常由用户定义并用于更精细的进程控制。

📖3.信号发送的本质

在操作系统中,每一个信号都有一个对应的编号,这些编号的范围是1~64,所以在信号传输的过程中,我们并不需要传输具体的信号宏名称,只需要一个位图即可实现,比如传输SIGINT信号时,只需要将位图中第二位置为1,即可完成信号的传输。因此,每一个进程都会维护一个用于表示信号集的位图,每一位对应一个信号的状态。

理解了信号的本质,我们来谈谈信号的操作,即信号是如何产生的,进程对信号又是如何处理的

📚二、信号的产生

📖1.终端按键

其实,我们早就接触过信号了,平常我们写代码时,偶尔会遇到程序陷入死循环或卡住的情况,这个时候我们按住ctrl+c就可以退出进程,结束死循环,其实,按下ctrl+c的过程就是向进程发送一个SIGINT信号,用于通知进程中断当前操作,这就是第一种信号产生的方式:终端按键产生。

不止ctrl+c可以产生信号,其他按键也可以产生相应的信号,例如ctrl+\,用于发送SIGQUIT信号,该信号可以强制终止进程,这与SIGINT类似,但不同的是,SIGQUIT可以生成核心转储文件(core dump)以便于调试:

🔖core dump

Core dump(核心转储)是操作系统在进程崩溃时将该进程的内存内容保存到一个文件中的机制,能够为开发者提供有关崩溃时程序状态的详细信息。

⚠️触发core dump时,系统会向磁盘写入一个核心转储文件,用于保存程序崩溃的完整快照,但是并不是所有程序崩溃的场景都需要一个快照,因为这样会导致磁盘空间被大量占用,当磁盘空间被大量占用时,可能会引发比程序崩溃更严重的问题,所以默认情况下,禁用core dump文件

然而可以通过ulimit命令接触限制:

① 使用 ulimit -c 命令查看当前系统的核心转储限制。如果输出为 0,则表示禁用了核心转储。

② 通过 ulimit -c unlimited 命令可以取消核心转储大小限制,允许生成核心转储文件。

📖2.系统调用

🔖kill()

通常情况下,我们是通过系统调用 kill 来实现信号的发送的,例如:

kill -SIGINT (进程pid)
# 或者
kill -2 (进程pid)

这种方式等价与按键ctrl+c,都是向指定进程发送SIGINT信号。

除了在终端输入kill指令,我们还可以在函数内部调用kill()函数:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = getpid();  // 获取当前进程IDprintf("Sending SIGINT signal to process %d\n", pid);// 向当前进程发送 SIGINT 信号if (kill(pid, SIGINT) == -1) {perror("Error sending signal");}// 除了向当前进行发送,还可以向其他进程发送 SIGINT 信号,只要知道它的pidreturn 0;
}

❓为什么是kill?

kill是“杀死”的意思,为什么发送信号的指令要取名为“杀死”呢?

✅这大概是因为,在信号被设计之初,其作用主要就是“杀死”一个进程,所以当时直接将信号发送取名为kill,到了后面,信号的种类越来越多,但是kill这个指令名称被保留了下来,沿用至今。但是不要因为指令叫做kill就认为它只能“杀死”进程,实际上它用于发送多种不同类型的信号。

除了kill()之外,其他一些与信号相关的系统调用和库函数也能触发信号的发送或控制信号的行为。 

🔖raise()

raise()函数用于向当前进程发送信号(自己给自己发信号):

#include <signal.h>
int raise(int sig);

sig表示要发送的信号。

🔖abort()

abort()函数也可以向当前进程发送信号,但与raise不同的是,它只能发送SIGABRT信号,因此函数内部不包含任何参数:

#include <stdio.h>
#include <stdlib.h>int main() {printf("Before abort\n");abort();  // 终止进程并生成核心转储printf("This line will never be executed.\n");return 0;
}
🔖alarm()

alarm()函数的作用是在指定时间之后发送一个SIGALRM信号:

#include <unistd.h>unsigned int alarm(unsigned int seconds);

参数

        seconds:设置定时器的时间,单位为秒,当 alarm() 被调用时,系统会在 seconds 秒后向当前进程发送 SIGALRM 信号。

返回值

        如果成功设置定时器,它返回剩余的时间。如果定时器已经存在,返回值是该定时器剩余的时间。

示例

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>int main()
{alarm(5);while(1){cout<<"I am a process..."<<endl;sleep(1);}return 0;}

📚三、信号的捕获

进程对于信号的处理有以下三种方式:

忽略信号:进程可以选择忽略某个信号,即在接收该信号时,不执行任何操作;

处理信号:进程可以指定一个信号处理函数来处理指定信号;

默认处理:如果没有指定默认的信号处理函数,操作系统会采用该信号的默认行为。

📖1.signal信号处理

为了捕捉并处理信号,我们通常调用signal()函数,通过该函数,我们可以自定义信号处理行为。

🔖函数原型
#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

signum是我们要捕获的信号编号;

对于handler参数,我们有两种选项:

采用关键字:SIG_IGN,表示当前信号被忽略;SIG_DFL,表示采用默认处理方式;

传入自定义信号处理函数,函数内部带有一个int型参数,表示信号编号

🔖示例
#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>void sigcb(int sig)
{if(sig == 2)cout<<"catch a sig: "<<sig<<endl;
}int main()
{signal(2,sigcb); // 调用自定义的信号处理函数cout<<"This is a process,pid is: "<<getpid()<<endl;while(1);return 0;
}

此时通过ctrl+c发生SIGINT信号时程序执行自定义行为:

此时我们将signal的第二个参数修改成SIG_IGN,表示忽略当前信号:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>int main()
{signal(2,SIG_IGN);cout<<"This is a process,pid is: "<<getpid()<<endl;while(1);return 0;
}

此时输入ctrl+c,SIGINT信号被忽略,不执行任何行为:

📖2.sigaction信号处理

sigaction函数不仅可以指定信号处理程序,还可以设置额外的选项来控制信号处理的细节。

🔖函数原型
#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数:

① signum: 指定要操作的信号编号;

② act:指向一个struct sigaction 类型的结构体,用于定义信号处理行为,可设置为NULL;

③ oldact:保存之前的信号处理行为(结构体),可设置为NULL。

struct sigaction:

该结构体用于定义信号处理行为:

struct sigaction {void     (*sa_handler)(int);     // 信号处理函数,或以下宏之一:// SIG_DFL:使用默认行为// SIG_IGN:忽略信号void     (*sa_sigaction)(int, siginfo_t *, void *); // 备用信号处理函数sigset_t sa_mask;                // 在处理此信号时需要阻塞的其他信号集合int      sa_flags;               // 信号处理的标志void     (*sa_restorer)(void);   // 已废弃
};

结构体成员:

① sa_handler:指定信号处理函数(简单处理);

② sa_sigaction:用于处理更复杂信号信息(需要设置 SA_SIGINFO 标志);

③ sa_mask:在处理信号时需要阻塞的其他信号;

④ sa_flags:控制信号处理行为的选项。常用值:

        SA_RESTART:自动重启被信号中断的系统调用

        SA_SIGINFO:启用 sa_sigaction,传递更多信号信息

        SA_NOCLDWAIT:阻止僵尸进程的生成(用于 SIGCHLD

        SA_NODEFER:在处理信号时不自动阻塞该信号

🔖示例 

设置对SIGINT信号的处理行为:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>void sigcb(int sig)
{cout<<"catch a sig: "<<sig<<endl;
}int main()
{struct sigaction sa;sa.sa_handler = sigcb;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(2,&sa,nullptr);while(1);return 0;
}

 

📚四、信号的阻塞与未决

然而,对于进程来说,并不是所有信号都需要立即响应的,因为在信号到达时,进程需要立即停止当前执行流,去执行信号处理操作。例如,一个进程的部分执行内容是对一个大文件进行拷贝写入,如果在写入的途中中断了,就会导致文件不完整甚至损坏,所以进程希望写入操作执行完毕后再响应信号,此时就需要信号阻塞操作。

📖1.阻塞的本质

我们前面提到,信号发送的本质是将目标进程的信号集(位图)中相应位置的标志位置为1,实际上,进程要维护两个这样信号集,一个为阻塞信号集,一个为未决信号集

🔖未决信号

当系统向进程发送信号时,修改的就是未决信号集中的指定标志位,在未决信号是指已经由系统发送但进程因为某些原因还未处理的信号,一个信号要想被进程处理的第一个条件就是在未决信号集(Pending)中标志位为1;

🔖阻塞信号

阻塞信号(Block)就是进程希望延迟执行的信号(例如在文件写入完毕之后再执行SIGINT信号,此时就需要先将SIGINT信号阻塞,写入完毕后再取消阻塞),未决信号想要被进程执行还需要判断是否阻塞,即在阻塞信号集中的标志位是否为1,为1则阻塞:

所以一个信号想要被执行需要满足:

未决位为1,表示信号已产生;

阻塞位为0,表示信号不被阻塞

③ 其他条件,例如进程空闲,没有在执行其他信号...

⚠️阻塞≠忽略

信号被阻塞与信号被忽略是两个不同的概念:

信号被阻塞表示信号不会被进程接收,如果产生则处于未决状态(Pending);

信号被忽略是进程对信号的一种处理方式,表示进程接收信号时忽略该信号,不做处理;

📖2.阻塞的应用

🔖函数原型

sigprocmask() 是一个用于设置或获取当前进程阻塞信号集的系统调用。它可以阻塞、解除阻塞或替换进程的阻塞信号集。

#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数:

① how:指定对信号集的操作方式,可以取以下值:

        SIG_BLOCK:将 set 中的信号添加到当前阻塞信号集中;

        SIG_UNBLOCK:从当前阻塞信号集中移除 set 中的信号;

        SIG_SETMASK:将当前阻塞信号集替换set

② set:指向包含要阻塞或解除阻塞的信号集的指针。

③ oldset:存储之前的阻塞信号集,为NULL表示不作存储。

返回值:

成功返回 0;失败返回 -1,并设置errno。

除了上述函数,我们还需要一系列的信号集操作函数:

#include <signal.h>
// sigset_t set; // 调用下面函数前,需要先定义一个函数集变量
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

下面对这些函数进行一一介绍:

① sigemptyset

初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号

② sigfillset

初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号

③ sigaddset

向set指向的信号集中添加信号signo,即将signo对应的标志位置为1。

④ sigdelset

将set指向的信号集中的signo信号删除,即将signo对应的标志位置为0。

⑤ sigismember

判断set指向的信号集中是否存在信号signo,也可以理解成返回signo对应的标志位。

🔖示例

设置阻塞信号2与40,观察发生信号时的状态:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>void printsigset(sigset_t *set)
{for(int i=0;i<64;++i){if(sigismember(set,i))putchar('1');elseputchar('0');}cout<<endl;
}int main()
{cout<<"This is a process,pid is: "<<getpid()<<endl;sigset_t m,p;sigemptyset(&m);sigaddset(&m,2);sigaddset(&m,40);sigprocmask(SIG_BLOCK,&m,NULL);while(1){sigpending(&p);printsigset(&p);sleep(3);}return 0;
}

此时阻塞了信号2与40,并通过打印未决信号集,观察情况:

发现发送信号2和40后,未决信号集的第2位和第40位置为1,表示信号2和40为未决信号。

📚五、可重入函数

信号处理函数执行过程中可能被任意中断,因此信号处理函数需要使用可重入函数,以避免数据竞争、资源冲突和未定义行为。

📖1.定义

可重入函数是指函数在执行过程中可以被中断,并且在中断期间又可以安全地被调用,且两次调用互不干扰。

🔖特性

① 无全局变量的依赖: 函数内部不依赖于全局或静态变量,或者对这些变量的操作是线程安全的(原子性操作)

② 无静态数据的修改: 函数不能修改其内部的静态变量,因为多次调用可能导致状态不一致

③ 无不可重入函数调用: 函数只能调用其他可重入函数

④ 使用局部资源: 函数仅使用局部变量或参数,不会影响其他调用的状态。 

🔖示例

情况一:两个线程并发地执行以下代码,假设a是全局变量,初始为0,那么输出结果会是什么?

   void foo(){a=a+1;printf("%d ",a);}

✅答案:1 1 、1 2 、2 1 、2 2 都有可能

分析:

a=a+1并不是原子性操作,怎么理解呢?

原子性操作只有未执行执行完毕两种情况,而a=a+1的操作流程是:

①将a从全局数据区提取到寄存器中;②在寄存器中完成+1操作;③将a返回到全局数据区

线程之间是共享虚拟地址空间的(除了栈区,线程之间各自拥有一块栈区空间),所以它们在访问全局变量a的时候会产生竞争访问,它们可能同时将a提取到各自的寄存器中,也有可能进程1先提取,修改完毕返回后进程2才提取,此时进程2提取的就是已经经历过一次+1的a:

情况二:那如果此时a是局部变量呢?

✅答案:只会是 1 1。

因为线程之间各自拥有一块栈空间,a是局部变量的话,那么在两个线程之间各有一份,即线程会在各自的栈空间中提取a到寄存器并返回+1后的结果,不存在竞争访问问题,执行结果只会是1 1。

情况一的foo函数是不可重入函数,因为存在竞争访问;情况二的foo函数是可重入函数,因为没有使用全局变量(只存在于栈空间),不存在竞争访问。

📖2.volatile关键字

看下面代码:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>int flag = 0;
void handler(int sig)
{if(sig == 2){cout<<"change flag 0 to 1"<<endl;flag = 1;}
}int main()
{signal(2,handler);while(!flag);exit(0);
}

在标准情况下键入CTRL-C ,2号信号被捕捉,执行自定义动作,修改flag=1,while条件不满足,退出循 环,进程退出:

# makefile
test:volatile.cppg++ -o $@ $^ #标准情况.PHONY:clean
clean:rm -f test

但是在优化情况下,键入CTRL-C ,2号信号被捕捉,执行自定义动作,修改flag=1,但是while条件依旧满足,进程继续运行!

test:volatile.cppg++ -o $@ $^ -O1 #优化情况.PHONY:clean
clean:rm -f test

这是为什么呢?

🔖-O1优化 

-O1是编译器的一个优化级别,在当前级别下,编译器会对代码进行性能优化, 原本存储在全局数据区的变量flag被直接存储到了寄存器,signal函数调用中,对flag值的修改是在另一个寄存器中进行的,而while循环中对flag的判断还是在原先的寄存器中提取,导致signal函数中对flag的修改没有作用到while循环中,键入CTRL-C最终不会退出进程。

为了解决编译器优化带来的这一潜在问题,我们可以使用volatile关键字修饰flag的定义,表示flag不被优化,这样就保证了signal中对flag的修改可以同步到while循环中:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>volatile int flag = 0; // volatile修饰
void handler(int sig)
{if(sig == 2){cout<<"change flag 0 to 1"<<endl;flag = 1;}
}int main()
{signal(2,handler);while(!flag);exit(0);
}

在优化情况下,键入CTRL-C ,2号信号被捕捉,执行自定义动作,修改flag=1,while条件不满足,退出循 环,进程退出:

test:volatile.cppg++ -o $@ $^ -O1 #优化情况.PHONY:clean
clean:rm -f test


以上就是【文件操作的艺术——从基础到精通】的全部内容,欢迎指正~ 

码文不易,还请多多关注支持,这是我持续创作的最大动力!  

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

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

相关文章

【西门子PLC.博途】——面向对象编程及输入输出映射FC块

当我们做面向对象编程的时候&#xff0c;需要用到输入输出的映射。这样建立的变量就能够被复用&#xff0c;从而最大化利用了我们建立的udt对象。 下面就来讲讲映射是什么。 从本质上来说&#xff0c;映射就是拿实际物理对象对应程序虚拟对象&#xff0c;假设程序对象是I0.0&…

MySQL索引的理解

MySQL与磁盘的交互 根据冯诺依曼结构体系&#xff0c;我们知道我们任何上层的应用想要去访问磁盘就必须要通过内存来访问&#xff0c;MySQL作为一款储存数据的服务&#xff0c;肯定是很多时间要用来访问磁盘。而大量访问磁盘一定会影响运行效率的在innoDB的存储引擎下为了减少…

分布式全文检索引擎ElasticSearch-数据的写入存储底层原理

一、数据写入的核心流程 当向 ES 索引写入数据时&#xff0c;整体流程如下&#xff1a; 1、客户端发送写入请求 客户端向 ES 集群的任意节点&#xff08;称为协调节点&#xff0c;Coordinating Node&#xff09;发送一个写入请求&#xff0c;比如 index&#xff08;插入或更…

Maven 生命周期

文章目录 Maven 生命周期- Clean 生命周期- Build 生命周期- Site 生命周期 Maven 生命周期 Maven 有以下三个标准的生命周期&#xff1a; Clean 生命周期&#xff1a; clean&#xff1a;删除目标目录中的编译输出文件。这通常是在构建之前执行的&#xff0c;以确保项目从一个…

Android Studio AI助手---Gemini

从金丝雀频道下载最新版 Android Studio&#xff0c;以利用所有这些新功能&#xff0c;并继续阅读以了解新增内容。 Gemini 现在可以编写、重构和记录 Android 代码 Gemini 不仅仅是提供指导。它可以编辑您的代码&#xff0c;帮助您快速从原型转向实现&#xff0c;实现常见的…

#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍04-盲SQL注入(Blind SQL Injection)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

Scala 的迭代器

迭代器定义&#xff1a;迭代器不是一种集合&#xff0c;它是一种用于访问集合的方法。 迭代器需要通过集合对应的迭代器调用迭代器的方法来访问。 支持函数式编程风格&#xff0c;便于链式操作。 创建一个迭代器&#xff0c;相关代码如下&#xff1a; object Test {def mai…

底层理论基础(单片机)

计算机基础 IO逻辑 计算机系统中的高低电平逻辑1和0&#xff0c;数据在计算机中的存储、传输、运算都是以二进制形式进行的。 数据的传输通过总线真正传递的是电信号&#xff0c;高低电平&#xff08;0、1&#xff09;。运算在电路中进行&#xff0c;集成电路中运算。 计算机的…

B站bilibili视频转文字字幕下载方法

本文将讲述介绍一种使用本地工具如何快速的下载B站的字幕为本地文本文件的方法。 通常获取B站字幕需要在浏览器中安装第三方插件&#xff0c;通过插件获取字幕。随着大模型&#xff0c;生成式AI&#xff0c;ChatGPT的应用&#xff0c;B站也提供了AI小助手对视频的内容进行总结…

ROS+PX4+Gazebo仿真环境配置全流程解析

上一期文章介绍了我们即将发布的仿真平台&#xff0c;并提到后续需要在Ubuntu系统上进行PX4软件在环仿真。本期文章将为大家详细介绍如何配置Ubuntu环境以及安装ROS和PX4仿真环境。具体配置包括&#xff1a;Ubuntu 20.04 ROS Noetic PX4 Python3。 需要注意的是&#xff0c…

基础入门-APP应用微信小程序原生态开发H5+Vue技术WEB封装打包反编译抓包点

知识点&#xff1a; 1、基础入门-APP应用-开发架构安全问题 2、基础入门-小程序应用-开发架构安全问题 通用&#xff1a; 1、反编译-得到源码-源码提取资产&#xff08;泄漏的配置信息&#xff09;-安全测试 2、抓包-资产-安全测试 一、演示案例-移动App-开发架构-原生&H…

Elasticsearch:使用 Open Crawler 和 semantic text 进行语义搜索

作者&#xff1a;来自 Elastic Jeff Vestal 了解如何使用开放爬虫与 semantic text 字段结合来轻松抓取网站并使其可进行语义搜索。 Elastic Open Crawler 演练 我们在这里要做什么&#xff1f; Elastic Open Crawler 是 Elastic 托管爬虫的后继者。 Semantic text 是 Elasti…

Qt之自定义标题栏拓展(十)

Qt开发 系列文章 - user-defined-titlebars&#xff08;十&#xff09; 目录 前言 一、方式一 1.效果演示 2.创建标题栏类 3.可视化UI设计 4.定义相关函数 5.使用标题栏类 二、方式二 1.效果演示 2.创建标题栏类 3.定义相关函数 4.使用标题栏类 总结 前言 Qt自带…

vue3前端组件库的搭建与发布(一)

前言&#xff1a; 最近在做公司项目中&#xff0c;有这么一件事情&#xff0c;很是头疼&#xff0c;就是同一套代码&#xff0c;不同项目&#xff0c;要改相同bug&#xff0c;改好多遍&#xff0c;改的都想吐&#xff0c;于是就想做一个组件库&#xff0c;这样更新一下就全都可…

学技术学英文:代码中的锁:悲观锁和乐观锁

本文导读&#xff1a; 1. 举例说明加锁的场景&#xff1a; 多线程并发情况下有资源竞争的时候&#xff0c;如果不加锁&#xff0c;会出现数据错误&#xff0c;举例说明&#xff1a; 业务需求&#xff1a;账户余额>取款金额&#xff0c;才能取钱。 时间线 两人共有账户 …

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下…

CTFHUB-web(SSRF)

内网访问 点击进入环境&#xff0c;输入 http://127.0.0.1/flag.php 伪协议读取文件 /?urlfile:///var/www/html/flag.php 右击查看页面源代码 端口扫描 1.根据题目提示我们知道端口号在8000-9000之间,使用bp抓包并进行爆破 POST请求 点击环境&#xff0c;访问flag.php 查看页…

游戏引擎学习第43天

仓库 https://gitee.com/mrxiao_com/2d_game 介绍运动方程 今天我们将更进一步&#xff0c;探索运动方程&#xff0c;了解真实世界中的物理&#xff0c;并调整它们&#xff0c;以创建一种让玩家感觉愉悦的控制体验。这并不是在做一个完美的物理模拟&#xff0c;而是找到最有趣…

YashanDB共享集群产品能力观测:细节足见功底

本文基于前泽塔数科研发总监-王若楠2024年11月在“2024年国产数据库创新生态大会”-“根”技术专场的演讲整理形成&#xff0c;主要对崖山共享集群YAC的架构、功能、高可用性、性能四大方面进行全面测试&#xff0c;并分享了测试环境和测试结论。 年初&#xff0c;基于某些商业…

VMware ubuntu16.04怎么设置静态IP联网

1.将VMware桥接到当前电脑使用的网络上面&#xff1b; 2.点击网络符号&#xff0c;编辑连接&#xff1b; 3.双击有线连接1&#xff1b; 4.选择IPv4设置&#xff0c;将地址&#xff0c;子网掩码&#xff0c;网关&#xff0c;DNS服务器设置好&#xff0c;保存&#xff1b; 5.在终…