进程间的信号

news2025/8/6 4:11:29

目录

一.信号入门

1.1概念

1.2信号发送与记录

1.3信号的处理方式

 二.产生信号的方式

2.1通过终端按键产生

 2.2通过系统函数向进程发信号

2.3由软件条件产生信号

 2.4由硬件异常产生信号

三.阻塞信号

3.1信号相关概念

3.2信号在内核的表示

3.3sigset_t:

3.4信号集操作函数

3.5 sigprocmask

 3.6sigpending

 四.捕抓信号

4.1内核实现信号的捕抓

 4.2sigaction

五.可重入函数

六.SIGCHLD信号


一.信号入门

1.1概念

Shell 下启动一个前台进程,  用户按下Ctrl - C , 这个键盘输入产生一个硬件中断,被 OS 获取,解释成信号,发送给目标前台进程,  前台进程收到信号后,进程将会退出。
例如:

注意:程序运行时后面加上&,就是让它在后台运行,后台运行时无法通过ctrl+c终止,可用fg+编号提到前台,ctrl+z暂停后,可用bg+编号提到后台。可用jobs命令查看。

补充:这个键盘输入产生的硬中断,被OS获取后解释成了2号信号,可以使用signal函数对2号信号进行捕捉。

参数解释:第一个参数表示的信号参数,第二个参数表示的是处理方法,该处理方法的参数是int,返回值是void。

例如:

#include<iostream>
using namespace std;
#include<unistd.h>
#include<signal.h>
void handler(int s)
{
  cout<<"获取一个信号"<<endl;
}
int main()
{
  signal(2, handler);
  while(true)
  {
    cout<<"hello world"<<endl;
    sleep(2);
  }
  return 0;
}

结果:

 当进程收到2号信号后,执行的就是我们自定义处理的handle方法,而不会退出。但9号命令不会执行我们自定义的方法。

注意:

1.Ctrl+C产生的信号只能发送给前台进程。在一个命令后面加个&就可以将其放到后台运行,这样Shell就不必等待进程结束就可以接收新的命令,启动新的进程。
2.Shell可以同时运行一个前台进程和任意多个后台进程,但是只有前台进程才能接到像Ctrl+C这种控制键产生的信号。
3.前台进程在运行过程中,用户随时可能按下Ctrl+C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步的。
4.信号是进程之间事件异步通知的一种方式,属于软中断。

1.2信号发送与记录

可以使用 kill -l 查看信号

 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

其中1~31号信号是普通信号,34~64号信号是实时信号,普通信号和实时信号各自都有31个,每个信号都有一个编号和一个宏定义名称。

那么信号的记录方式是怎样的呢?又是怎样产生的呢?

当一个进程接收到某种信号后,该信号是被记录在该进程的进程控制块当中的。我们都知道进程控制块本质上就是一个结构体变量,而对于信号来说我们主要就是记录某种信号是否产生,因此,我们可以用一个32位的位图来记录信号是否产生。信号的产生就是操作系统直接去修改目标进程的task_struct中的信号位图。

1.3信号的处理方式

  1. 执行该信号的默认处理动作。
  2. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。
  3. 忽略该信号。

我们可以通过man手册查看各个信号默认的处理动作。

 二.产生信号的方式

2.1通过终端按键产生

例如在键盘上的ctrl+c/ctrl +/,OS会将其识别为对应的信号,其实Ctrl+c是向进程发送2号信号SIGINT,Ctrl+/是发送3号信号,看这两个信号的默认处理动作,可以看到这两个信号的Action是不一样的,2号信号是Term,而3号信号是Core。但是Core在终止进程的时候会进行一个动作,那就是核心转储。

先解释下Core Dump,一个程序异常终止时,可以把该进程用户空间的数据保存在磁盘上,磁盘文件名叫Core,这叫做Core Dump。事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许 产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB)。默认是不允许产生core文件的,

因为 core 文件中可能包含用户密码等敏感信息 , 不安全。在开发调试阶段可以用 ulimit 命令改变这个限制 , 允许 产生core 文件。 首先用 ulimit 命令改变 Shell 进程的 Resource Limit, 允许 core 文件最大为 1024K: $ ulimit - c 1024。
例如:可以使用ulimit -a 命令查看当前的限制。

 我们可以通过ulimit -c size命令来设置core文件的大小。

 core文件的大小设置好后,就相当于将核心转储功能打开了。此时如果我们再使用Ctrl+\对进程进行终止,就会发现终止进程后会显示core dumped

 举例验证:

 并且会在当前路径下生成一个core文件,该文件以一串数字为后缀,而这一串数字实际上就是发生这一次核心转储的进程的PID。

说明一下: ulimit命令改变的是Shell进程的Resource Limit,但test进程的PCB是由Shell进程复制而来的,所以也具有和Shell进程相同的Resource Limit值。

我们可以运用核心转储进行调试,用那个生成的core文件。gdb +文件名,core-file core文件查看。

举例:

int main()
{
  int k=10;
  for(int i=5;i>=0;i--)
  {
    k/=0;
    cout<<i<<endl;
    sleep(1);

  }
  return 0;
}

 core dump标志:

pid_t waitpid(pid_t pid, int *status, int options);

这个status参数是一个输出型参数,用于获取子进程的退出状态。status是一个整型变量,但status不能简单的当作整型来看待,status的不同比特位所代表的信息不同,具体细节如下(只关注status低16位比特位): 在这里插入图片描述

 若进程是正常终止的,那么status的次低8位就表示进程的退出状态,即退出码。若进程是被信号所杀,那么status的低7位表示终止信号,而第8位比特位是core dump标志,即进程终止时是否进行了核心转储。

举例验证:

int main()
{
  int k=99;
  int a=0;
  pid_t id= fork();
  if(id==0)
  {
    k/=a;
  }
  int status = 0;
	waitpid(-1, &status, 0);
  cout<<"退出信号是:"<<(status&0x7f)<<" "<<"core:"<<((status>>7)&1)<<endl;
  return 0;
}

 2.2通过系统函数向进程发信号

当我们要使用kill命令向一个进程发送信号时,我们可以以kill -信号名 进程ID或者kill -信号编号 进程ID的形式进行发送的形式进行发送.

实际上kill命令是通过调用kill函数实现的,kill函数可以给指定的进程发送指定的信号,kill函数的函数原型如下

int kill(pid_t pid, int sig);

 如果信号发送成功,则返回0,否则返回-1。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

void Usage(char* proc)
{
	printf("Usage: %s pid signo\n", proc);
}
int main(int argc, char* argv[])
{
	if (argc != 3){
		Usage(argv[0]);
		return 1;
	}
	pid_t pid = atoi(argv[1]);
	int signo = atoi(argv[2]);
	kill(pid, signo);
	return 0;
}

raise函数:raise函数可以给当前进程发送指定信号,即自己给自己发送信号,raise函数的函数原型如下

int raise(int sig);

例如:

void handler(int signo)
{
	printf("get a signal:%d\n", signo);
}
int main()
{
	signal(8, handler);
	while (1){
		sleep(1);
		raise(8);
	}
	return 0;
}

结果: 

abort 函数可以给当前进程发送SIGABRT信号,使得当前进程异常终止.

void abort(void);

例如:

void handler(int signo)
{
	printf("get a signal:%d\n", signo);
}
int main()
{
	signal(6, handler);
	while (1){
		sleep(1);
    abort();
	}
	return 0;
}

 结果:

 说明一下: abort函数的作用是异常终止进程,exit函数的作用是正常终止进程,而abort本质是通过向当前进程发送SIGABRT信号而终止进程的,因此使用exit函数终止进程可能会失败,但使用abort函数终止进程总是成功的。

2.3由软件条件产生信号

SIGPIPE信号:SIGPIPE信号是一种由软件条件产生的信号,当进程在使用管道进行通信时,读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么此时写端进程就会收到SIGPIPE信号进而终止。

SIGALRM信号:其实alarm函数设定一个闹钟,也就是告诉操作系统在若干时间后发送SIGALRM信号给当前进程,alarm函数的函数原型如下:

unsigned int alarm(unsigned int seconds);

该信号默认的执行动作也是终止进程。

alarm函数的返回值:

  • 若调用alarm函数前,进程已经设置了闹钟,则返回上一个闹钟时间的剩余时间,并且本次闹钟的设置会覆盖上一次闹钟的设置。
  • 如果调用alarm函数前,进程没有设置闹钟,则返回值为0。

可以用下alarm函数:

int a=0;
void handler(int sin)
{
  printf("获取一个信号:%d\n",sin);
  cout<<a<<endl;
  exit(-1);
}
int main()
{
  alarm(1);
  signal(SIGALRM, handler);
  while(1)
  {
    a++;
  }
  return 0;
}

结果:

 2.4由硬件异常产生信号

例如一些除零错误,越界,野指针等。因为硬件异常,而导致OS向目标进程发送信号,进而导致进程终止。

除零错误问题:在进行运算的时候,cpu内部有一些寄存器,当发生错误时,某些寄存器会保存错误信息,OS识别后,发现状态寄存器被置位了,此时操作系统会识别到当前是哪个进程导致的错误,并将所识别到的硬件错误包装成信号发送给目标进程。本质就是操作系统去直接找到这个进程的task_struct,并向该进程的位图中写入8信号,写入8号信号后这个进程就会在合适的时候被终止。

野指针问题:首先要知道我们语言层所使用的地址是虚拟的地址,要经过页表的映射后才能与物理地址相关联。而地址转化的工作是由(MMU(memory maneger unit 内存管理单元,是硬件)+页表(软件)),MU既然是硬件单元,那么它当然也有相应的状态信息,当地址转换过程中出现错误时,也会记录错误信息,当被OS识别后,也会相对应的目标进程发送信号,该进程收到信号后会在合适时候被终止。

三.阻塞信号

3.1信号相关概念

实际执行信号的处理动作称为信号递达 (Delivery)
信号从产生到递达之间的状态 , 称为信号未决 (Pending)
进程可以选择阻塞 (Block ) 某个信号。
被阻塞的信号产生时将保持在未决状态 , 直到进程解除对此信号的阻塞 , 才执行递达的动作 .
注意 , 阻塞和忽略是不同的 , 只要信号被阻塞就不会递达 , 而忽略是在递达之后可选的一种处理动作。

3.2信号在内核的表示

在内核中的表示:

解释:

每一个信号都有两个标志位,分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。当信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。中。当信号被阻塞时,它不能被递达,只能处于未决状态,无论它的处理方式是什么。

补充:

1.在block位图中,比特位的位置代表某一个信号,比特位的内容代表该信号是否被阻塞。
2.在pending位图中,比特位的位置代表某一个信号,比特位的内容代表是否收到该信号。
3.handler表本质上是一个函数指针数组,数组的下标代表某一个信号,数组的内容代表该信号递达时的处理动作,处理动作包括默认、忽略以及自定义。
4.block、pending和handler这三张表的每一个位置是一一对应的。

3.3sigset_t:

从上面可知,每个信号的阻塞与未决状态都是一个比特位,0或1。所以,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集。这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。

3.4信号集操作函数

sigset_t类型表示了信号的阻塞与未决状态,至于该类型的具体实现不必关心,我们可以使用以下函数来操作sigset_t。

int sigemptyset(sigset_t *set);
初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。

 int sigfillset(sigset_t *set);初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。

int sigaddset(sigset_t *set, int signum);在set所指向的信号集中添加某种有效信号。

 int sigdelset(sigset_t *set, int signum);在set所指向的信号集中删除某种有效信号。

sigemptyset、sigfillset、sigaddset和sigdelset函数都是成功返回0,出错返回-1。 

int sigismember(const sigset_t *set, int signum);  判断在set所指向的信号集中是否包含某种信号,若包含则返回1,不包含则返回0,调用失败返回-1。

3.5 sigprocmask

sigprocmask函数可以用于读取或更改进程的信号屏蔽字(阻塞信号集).

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

 参数说明:

参数说明:

  • 如果oldset是非空指针,则读取进程当前的信号屏蔽字通过oldset参数传出。
  • 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
  • 如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字。

调用成功返回0,失败返回-1.

假设当前的信号屏蔽字为mask,下表说明了how参数的可选值及其含义:

 3.6sigpending

sigpending函数可以用于读取进程的未决信号集,该函数的函数原型如下:

int sigpending(sigset_t *set);

解释:sigpending函数读取当前进程的未决信号集,并通过set参数传出。该函数调用成功返回0,出错返回-1。 

代码演示:

void printPending(sigset_t *pending)
{
	int i = 1;
	for (i = 1; i <= 31; i++){
		if (sigismember(pending, i)){
			printf("1 ");
		}
		else{
			printf("0 ");
		}
	}
	printf("\n");
}
void handler(int sin)
{
    printf("获取一个信号%d\n",sin);
}
int main()
{
	sigset_t set, oset;
	sigemptyset(&set);
	sigemptyset(&oset);

	sigaddset(&set, 2); 
	sigprocmask(SIG_SETMASK, &set, &oset); //阻塞2号信号

	sigset_t pending;
	sigemptyset(&pending);
    int k=10;
   	signal(2, handler);
	while (1){
		sigpending(&pending); //获取pending
		printPending(&pending); //打印pending位图(1表示未决)
        k--;
		sleep(1);
        if(k==0)
        {
            printf("2号信号阻塞解除了\n");
            sigprocmask(SIG_UNBLOCK, &set, NULL);
        }
	}
	return 0;
}

结果:

 四.捕抓信号

4.1内核实现信号的捕抓

先来看一下内核空间与用户空间:

当进程创建好后,进程地址空间分为内核空间与用户空间,

用户所写的代码和数据位于用户空间,通过用户级页表与物理内存之间建立映射关系。内核空间存储的是操作系统代码和数据,通过内核级页表与物理内存之间建立映射关系,而内核级页表只有一份,所有的进程共享(前提是有权力去访问).

内核态:一般用来执行操作系统的代码,权限是很高的状态。

进程如果处于用户态,则只能访问用户级页表,若处于内核态,则都可以访问。(CPU内部有对应的状态寄存器来标识处于哪种状态)

从用户态切换为内核态通常有如下几种情况:

  1. 需要进行系统调用时。
  2. 当前进程的时间片到了,导致进程切换。
  3. 产生异常、中断、陷阱等。

与之相对应,从内核态切换为用户态有如下几种情况:

  1. 系统调用返回时。
  2. 进程切换完毕。
  3. 异常、中断、陷阱等处理完毕。

用户态切换为内核态我们称之为陷入内核,比如系统调用等。

当从内核态切换回用户态时,操作系统会进行信号的检测与处理。会去查看pending位图,如果发现有未决信号,并且该信号没有被阻塞,那么此时就需要该信号进行处理。

重点:此时对该信号处理又分位两种情况。1.执行默认的处理方法,或者忽略。2.该处理方法是用户自定义的。

1.如果待处理信号的处理动作是默认或者忽略,则执行该信号的处理动作后清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,从主控制流程中上次被中断的地方继续向下执行。

2.如果待处理信号是自定义捕捉的,那么处理该信号时就要先返回用户态执行对应的自定义处理动作(若以内核态去执行用户写的代码,权限高,可能会出问题),执行完后再通过特殊的系统调用sigreturn再次陷入内核并清除对应的pending标志位,如果没有新的信号要递达,就直接返回用户态,继续执行主控制流程的代码。


 

 4.2sigaction

可以使用sigaction函数对信号进行捕捉,sigaction函数的函数原型如下:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

 参数解释:

  • signum代表指定信号的编号。
  • 若act指针非空,则根据act修改该信号的处理动作。
  • 若oldact指针非空,则通过oldact传出该信号原来的处理动作。

act和oldact都是结构体指针变量,该结构体的定义如下:

struct sigaction {
	void(*sa_handler)(int);
	void(*sa_sigaction)(int, siginfo_t *, void *);
	sigset_t   sa_mask;
	int        sa_flags;
	void(*sa_restorer)(void);
};

 该结构体的变量解释:

sa_handler:是注册信号的函数处理方法,SIG_IGN传给sigaction函数,表示忽略信号。赋值为常数SIG_DFL传给sigaction函数,表示执行系统默认动作。sa_handler赋值为一个函数指针,表示用自定义函数捕捉信号

sa_sigaction:表示的是实时信号

sa_flags:设置为0

sa_mask:当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

举例:

int main()
{

  struct sigaction act, oact;
  act.sa_handler = handler;
  act.sa_flags = 0;
 sigemptyset(&act.sa_mask);
 sigaction(2, &act, &oact);

 while(1)
 {
   
 cout<<"main running"<<endl;
 sleep(1);
 }
  return 0;
}

结果:

五.可重入函数

主函数中调用insert函数向一个链表中插入结点node1,某信号处理函数中也调用了insert函数向该链表中插入结点node2,如图:

 函数insert有两条语句,当执行完第一条指令时,被中断切换为内核态,处理完后回到用户态之前,检查到有信号待处理,于是会去调用该信号处理函数,而该信号处理函数中也调用了Insert函数,如图:

第一步:在这里插入图片描述

 调用信号处理函数后:

 main函数中的insert函数执行完后:

 而此时node2结点便丢失了,会造成内存泄露。

如果一个函数符合以下条件之一则是不可重入的:

  1. 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  2. 调用了标志I/O库函数,因为标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

六.SIGCHLD信号

为了避免出现僵尸进程,父进程需要使用wait或waitpid函数等待子进程结束,父进程可以阻塞等待或则使用非阻塞等待的去查询是否有子进程结束等待清理,即轮询的方式。采用第一种方式,父进程阻塞就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。

其实,子进程在终止时会给父进程发生SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理动作,这样父进程就只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait或waitpid函数清理子进程即可。

注意:如果在同一时刻有多个子进程同时退出,那么在handler函数当中实际上只清理了一个子进程,因此在使用waitpid函数清理子进程时需要使用while不断进行清理。

例如:

void FreeChild(int signo)
{
  cout<<"获取一个信号:"<<signo<<endl;
    assert(signo == SIGCHLD);
    while (true)
    {
        pid_t id = waitpid(-1, nullptr, 0);
        if (id > 0)
        {
            cout << "父进程等待成功,chld id:" << id << endl;
        }
        else
        {
            cout<<"所有子进程已退出"<<endl;
            break;
        }
    }
}
int main()
{
    signal(SIGCHLD, FreeChild);
    for (int i = 0; i < 10; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            int cnt = 5;
            while (cnt)
            {
                cout << "我是子进程,pid:" << getpid() << "当前cnt:" << cnt-- << endl;
                sleep(1);
            }
            cout << "子进程退出,进入僵尸状态" << endl;
            exit(0);
        }
    }
 
    while (1)
    {
        cout << "我是父进程,pid:" << getpid() << endl;
        sleep(1);
    }
    return 0;
}

结果:

 

如果子进程退出的时间间隔很大,可以使用非阻塞等待。 waitpid(-1,nullptr,WNOHANG):此时是父进程非阻塞等待,那么当父进程等待先结束的子进程后,此时父进程进入FreeChld中的循环中,父进程是非阻塞等待会通过id==0(等待成功,但子进程没有退出 )这个条件跳出循环去做父进程的事情。

例如:

void FreeChld(int signo)
{
    assert(signo == SIGCHLD);
    while (true)
    {
 
        pid_t id = waitpid(-1, nullptr, WNOHANG);
        if (id > 0)
        {
            cout << "父进程等待成功,chld id:" << id << endl;
        }
        else if (id == 0) //等待成功,但子进程没有退出 
        {
            cout << "还有子进程没有退出,父进程要忙自己的事了" << endl;
            break;
        }
        else
        {        // waitpid 已经没有子进程了调用失败,id<0
            cout << "所有子进程已退出" << endl;
            break;
        }
    }
}

事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用signal或sigaction函数将SIGCHLD信号的处理动作设置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用signal或sigaction函数自定义的忽略通常是没有区别的,但这是一个特列。此方法对于Linux可用,但不保证在其他UNIX系统上都可用。

signal(SIGCHLD, SIG_IGN);

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

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

相关文章

工具及方法 - 使用Total Commander来查找重名文件

我只是一个Total Commander的轻度使用者&#xff0c;主要使用的是打开多个窗口&#xff0c;可以方便的以标签形式切换。 还有&#xff0c;这个软件是免费的&#xff0c;只是免费版打开时多一步&#xff0c;要输入个数字验证。 今天在使用一个SDK时&#xff0c;要包含进很多头文…

论文阅读笔记《Locality Preserving Matching》

核心思想 该文提出一种基于局部保持的特征匹配方法&#xff08;LPM&#xff09;。其核心思想是对于一个正确匹配点&#xff0c;其邻域范围内的其他匹配点与对应目标点之间的变换关系&#xff0c;应该和正确的匹配点保持一致&#xff0c;而错误匹配点&#xff0c;则应该有较大的…

第一章《初学者问题大集合》第6节:IntelliJ IDEA的下载与安装

当完成了Java开发环境之后&#xff0c;各位读者就可以开始编写第一个Java程序了。可是应该在哪里写程序呢&#xff1f;早期的开发者们都是用纯文本编辑工具编写Java程序&#xff0c;并且在命令行窗口中编译和运行Java程序。时至今日&#xff0c;我们早已远离了那个程序开发的“…

CentOs程序环境准备

1. MySQL的安装启动 选择指定操作系统指定版本的mysql进行下载 MySQL :: Download MySQL Community Serverhttps://dev.mysql.com/downloads/mysql/5.7.html#downloads 选择复制下载链接 回到终端&#xff0c;执行此命令下载 wget https://dev.mysql.com/get/Downloads/MyS…

举个栗子~Tableau 技巧(244):用和弦图(Chord diagram)呈现数据关系

关于和弦图 和弦图&#xff08;Chord diagram&#xff09;常用来表示数据之间的相互关系。数据点沿着圆圈分布&#xff0c;通过点和点之间相互连接的弧线来呈现相互之间的关系。和弦图从视觉上来说比较美观&#xff0c;数据呈现又很直观&#xff0c;所以深受数据粉喜爱。 之前…

【Linux初阶】Linux调试器-gdb使用 | gdb的 l/b/info/d/r/n/s/bt/finish/p/(un)display/q

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;gdb使用相关背景知识&#xff0c;gdb的使用&#xff08;打断点、查断点、消断点、调试运行、查看对应变量&…

【python拼图游戏】图片自选,来挑战一下自己的极限吧~

嗨害大家好鸭&#xff01;我是小熊猫❤ 拼图的画面多以自然风光、建筑物以及一些为人所熟识的图案的为题材。 城堡和山峦是两类传统的主题&#xff0c; 不过任何图画和影像都可以用做拼图的素材。 有一些公司还提供将私人摄影作品制成拼图的服务。 今天我小熊猫就给带来py…

FFmpeg5.1 解码rtsp 并用OpenCV 播放

RTSP 连接过程如下图 看下实际过程中FFmpeg 的日志情况&#xff1a; [tcp 0000014CC3256D40] No default whitelist set [tcp 0000014CC3256D40] Original list of addresses: [tcp 0000014CC3256D40] Address ::1 port 8554 [tcp 0000014CC3256D40] Address 127.0.0.1 po…

使用BP神经网络、RBF神经网络以及PSO优化的RBF神经网络对数据进行预测(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

Keysight是德科技E5061B网络分析仪-安泰测试

E5061B ENA系列网络分析仪从5Hz 至3GHz提供了广泛的频率范围。它不仅支持一般的射频应用(例如滤波器或放大器测量等)&#xff0c;还支持低频应用(例如直流至直流转换器环路增益测量)。因此&#xff0c;它是所有实验台上进行网络分析的最重要工具。 拥有E5061B&#xff0c;您就…

Selenium4 新特性

一、Selenium4 简介 Selenium是一个综合性项目,包含一系列的工具和库,支持Web浏览器的各种自动化操作: 软件测试爬虫领域RPA领域优点: 开源:https://github.com/SeleniumHQ兼容性: Chrome、FireFox、Edeg、IE、Opera、Safari支持多种编程语言:Java、Python、C#、Ruby、…

Charles抓包web、手机、小程序配置

一、下载地址 二、web抓包 Charles Web抓包&#xff0c;启动Charles会自动与浏览器设置成代理&#xff0c;不需要进行过多的设置。接下来就是通过浏览器发送网络请求&#xff0c;Charles就会直接抓取到这些信息和响应信息。 1、抓取HTTPS协议 Charles配置 点击顶部菜单栏【He…

双功能螯合剂p-NCS-Bz-DFO,1222468-90-7,p-SCN-Bn-Deferoxamine特点分析

●外观以及性质&#xff1a; p-SCN-Bn-Deferoxamine属于双功能螯合剂&#xff0c;西安凯新生物科技有限公司是各种修饰性PEG供应商&#xff0c;提供各种品质优良PEG衍生物&#xff0c;分子量从1000-40000不等&#xff0c;纯度≥95%&#xff0c;发货速度快。 ●中文名&#xff…

Web学习笔记-React

笔记内容转载自AcWing的Web应用课讲义&#xff0c;课程链接&#xff1a;AcWing Web应用课。 CONTENTS1. React配置环境2. ES6语法补充3. Components1. React配置环境 React官网&#xff1a;React。 React是一个声明式&#xff0c;高效且灵活的用于构建用户界面的JavaScript库…

凡事预则立,不预则缺货!2022年底了核芯物联蓝牙AOA定位基站GA25缺货!GA10缺货!GA60也缺货!

凡事预则立&#xff0c;不预则缺货&#xff01;2022年底了核芯物联蓝牙AOA定位基站GA25缺货&#xff01;GA10缺货&#xff01;GA60也缺货&#xff01; 核芯物联岳毅恒 ​ 深圳核芯物联科技有限公司 战略合作拓展总监 凡事预则立&#xff0c;不预则缺货&#xff01;202222年底…

brython | 初探鼠标事件-1:点击事件

各位前端大佬勿笑话&#xff0c;运维小弟献丑了。 前段时间看见大佬使用【文言文】写了一个连连看&#xff0c;于是我也想用【brython】也实现一个连连看。 于是乎&#xff0c;你们就看到这篇文章和这个项目了。 如果大佬们对brython不是特别了解的话&#xff0c;建议先查看之…

JVM分析工具

JVM在运行过程中&#xff0c;我们可以利用各种工具对JVM进行分析&#xff0c;这是性能调优的利器。可以通过各种工具了解在系统运行过程中&#xff0c;在JVM底层到底发生了什么。JVM分析工具一般分为两种&#xff0c;一种是命令行工具&#xff0c;一种是图形界面工具。 JVM分析…

卷积操作的不同类型

文章目录1. 一维卷积2. 二维卷积3. 三维卷积4. 扩张卷积5. 空洞卷积6. 分组卷积一般而言&#xff0c;一维卷积用于文本数据&#xff1b;二维卷积用于图像数据&#xff0c;对宽度和高度都进行卷积&#xff1b;三维卷积用于视频及3D图像处理领域&#xff0c;对立方体的三个面进行…

CS5269TypeC转HDMI2.0+VGA带PD100W快充拓展坞方案芯片完整设计资料(原理图+PCB文件)

CS5269TypeC转HDMI2.0VGA带PD100W快充方案芯片原理图&#xff1a; 用于US Type-C或DP1.4连接到HDMI2.0转换器。CS5269集成了DP1.4HDMI2.0的输出端和VGA输出接口。另外&#xff0c;两个CC控制器包括CC通信&#xff0c;以实现DP Alt模式和功率传输功能&#xff0c;一个用于上游类…

SD-WAN NFV uCPE VNF

NFV&#xff08;network function virtulization) NFV&#xff08;network function virtulization&#xff0c;网络功能虚拟化&#xff09;是指一种操作框架&#xff0c;目的是为了将部署在COTS硬件上的、在虚拟基础架构中的VNF软件设备编排和自动化起来&#xff0c;然后进行…