当前位置: 首页 > news >正文

Linux_信号

信号的概念 && 知识补充

信号是进程之间事件异步通知的一种方式,是一种软中断。

标准信号:编号为1-31之间都是标准信号,这些都是预定义信号,用于通知进程发生的各种事件。
实时信号:编号从32开始起均是实时信号,与标准信号相对应。  

当OS收到相关信号时,要通知相应的进程,但是当前进程可能正在做着别的事情,没空处理这个信号。此时就要记录下来该信号,那么如何记录呢?普通信号的编号是从 1 - 31,并没有0号信号,我们将0号位置作为位图来记录信号。所以发送信号的本质就是OS修改目标进程的PCB中的信号位图:0 ->1。

发送信号的只能是OS,因为task_struct的管理者是OS。 

信号的处理方式:

  1. 忽略信号:进程可以选择忽略某些信号,即不对该信号进行任何处理。
  2. 捕捉信号(自定义处理):进程可以注册一个信号处理函数来捕捉特定的信号,并在接收到该信号时执行相应的处理逻辑。
  3. 默认处理:如果进程没有注册信号处理函数且没有选择忽略信号,则系统会按照默认的处理方式来处理该信号。通常情况下,默认处理方式会导致进程终止或停止。

前台进程与后台进程

信号的产生

键盘产生信号

在信号概念模块 我们使用键盘 ctrl + c 组合键,触发的就是2号信号(SIGINT)。

signal函数

  • signum:要处理的信号类型,它是一个整数,对应于某个具体的信号。
  • handler:函数指针类型,用来接收自定义的函数。执行调用的函数就是执行自定义的操作。
// 修改2号信号的默认处理动作
void Handler(int signo)
{std::cout << "Get a signal, signal number is : " << signo << std::endl;
}
int main()
{// 如果没有产生2号信号,则Handler将不被调用!signal(SIGINT, Handler);while(1){std::cout << "hello world" << std::endl;sleep(1);}return 0;    
}

 在所有的普通信号里面,除了9号信号外,所有信号都可以被signal函数捕捉!

调用系统指令发送信号

指令底层使用的也是系统调用(下面的kill函数) 

使用函数产生信号 

kill函数(系统调用)

// 使用kill函数模拟实现一个kill命令
#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>int main(int argc, char *argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " signum pid" << std::endl;return 1;}int signum = std::stoi(argv[1]);pid_t id = std::stoi(argv[2]);int n = ::kill(id, signum);if(n < 0){perror("kill");exit(2);}exit(0);
}

运行结果: 

 raise函数(系统调用)

raise 函数给当前进程发送指定的信号(自己给自己发信号)。

  • 参数
    • sig:要发送的信号编号。如果,该参数为SIGKILL(信号编号为9),则该进程会自杀。
  • 返回值
    • 如果成功,返回0。
    • 如果失败,返回-1并设置errno以指示错误。
// 3秒后该进程自杀
int main(int argc, char *argv[])
{int cnt = 3;while(1){std::cout << "alive" << std::endl;cnt--;if(cnt <= 0)    raise(9);sleep(1);}return 0;
}

实际上是调用kill函数:kill(getpid(), signo)

abort函数(系统调用)

abort();

abort是用来终止进程的,实际上就是自己给自己发送6号信号。实际上也是调用kill函数:kill(getpid(), 6)

软件条件产生信号

alarm函数

调用 alarm 函数可以设定一个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发SIGALRM 信号,该信号的默认处理动作终止当前进

函数的返回值是0或者是以前设定的闹钟时间还余下的秒数

// 统计我的服务器1S可以将计数器累加多少
int number = 0;
void die(int signumber)
{printf("get a sig : %d, count : %d\n", signumber, number);exit(0);
}
int main(int argc, char *argv[])
{   alarm(1); // 我自己,会在1S之后收到一个SIGALRM信号(14号信号)signal(SIGALRM, die);while (true)    number++;return 0;
}

闹钟的功能

  • 当调用alarm(seconds)时,内核会启动一个定时器,当定时器到期时,内核会向调用进程发送一个SIGALRM信号。当操作系统中多处要用到alarm的时候,OS就会借助最小堆进行判断,谁的定时器到期,就向谁发送SIGALRM信号。
  • 如果在定时器到期之前再次调用alarm,之前的定时器会被取消,新的定时器开始计时。
  • 如果进程在定时器到期之前终止,定时器也会被取消。

如果之前已经设置了一个定时器,alarm会返回之前定时器剩余的时间(以秒为单位)。如果没有之前设置的定时器,返回0。

int n = alarm(0); // 0:取消闹钟

 Demo代码

// 设置重复闹钟 
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <unistd.h>
#include <signal.h>using func_t = std::function<void()>;int gcount = 0;
std::vector<func_t> gfuncs;void hanlder(int signo)
{for(auto &f : gfuncs) { f(); }std::cout << "gcount : " << gcount << std::endl;alarm(1);
}int main()
{gfuncs.push_back([](){ std::cout << "我是一个内核刷新操作" << std::endl; });gfuncs.push_back([](){ std::cout << "我是一个检测进程时间片的操作,如果时间片到了,我会切换进程" << std::endl; });gfuncs.push_back([](){ std::cout << "我是一个内存管理操作,定期清理操作系统内部的内存碎片" << std::endl; });alarm(1); // 一次性的闹钟,超时alarm会自动被取消signal(SIGALRM, hanlder);while (true){pause();std::cout << "我醒来了..." << std::endl;gcount++;}
}

异常产生信号

// 空指针 / 除0 操作引发异常产生信号
int main()
{int *p = nullptr;*p = 100;       // 对空指针解引用// int a = 10;// a /= 0;      // 除0while (true){std::cout << "hello bit, pid: " << getpid() << std::endl;sleep(1);}
}

运行结果: 

 Core VS Term

term(termination)

  • 定义:term信号的动作是直接终止进程。当进程接收到term信号时,它会立即停止运行,并且不会生成core dump文件。
  • 用途:通常用于正常终止进程,或者在进程已经处于异常状态时强制终止它,以避免进一步的资源占用或系统不稳定。

core

  • 定义:core信号的动作同样是终止进程,但与term不同的是,core在终止进程的同时会生成core dump文件。这个文件包含了进程在内存中的核心数据(主要是与调试有关的数据),如变量值、函数调用栈等。
  • core dump文件:当进程因为接收到core信号而异常退出时,它会将内存中的核心数据转储到磁盘上,形成core dump文件。这个文件对我们程序员来说非常有用,因为它可以帮助定位程序为什么退出以及是在哪一行退出的。通过分析core dump文件,程序员可以找出导致程序崩溃的原因,并修复相应的bug。
  • 用途:主要用于调试和错误分析。程序员可以利用core dump文件来重现程序崩溃时的场景,以便更好地理解和修复问题。此外,core dump文件还可以用于检查内存泄漏等问题。

总结

  • 进程终止方式:term信号直接终止进程,不生成core dump文件;而core信号在终止进程的同时生成core dump文件。
  • 调试和错误分析:term信号不提供额外的调试信息;而core信号通过生成的core dump文件为程序员提供了丰富的调试信息,有助于定位和解决程序中的问题。

 信号捕捉的三种方式

  • 默认捕捉
::signal(2, handler); // 自定义捕捉
  • 忽略捕捉
::signal(2, SIG_IGN);   // ignore: 忽略:本身就是一种信号捕捉的方法,动作是忽略
  • 自定义捕捉
::signal(2, SIG_DFL);   // default: 默认。

信号的保存

补充概念

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

 信号在内核中的表示:

相关函数接口

sigset_t: sigset_t称为信号集(一个位图)。信号集用于表示每个信号的状态,即该信号是有效的(未被阻塞)还是无效的(被阻塞)。通过使用sigset_t类型的变量,可以进行信号集的操作,例如添加信号、删除信号、检查信号是否存在等操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”是阻塞而不是忽略。

信号集操作函数

#include <signal.h>
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置位,表示该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调用 sigemptyset 或 sigfillset 做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用 sigaddset 和 sigdelset 在该信号集中添加或删除某种有效信号。

上面四个函数都是成功返回0,出错返回-1。而sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。


sigprocmask函数

sigprocmask函数可以 读取或更改 进程的信号屏蔽字(阻塞信号集,block表)

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
// 返回值:若成功则为0,若出错则为-1

how:指定对信号屏蔽集的操作方式,有以下几种方式:

  • SIG_BLOCK:将set所指向的信号集中包含的信号添加到当前的信号屏蔽集中,即信号屏蔽集和set信号集进行逻辑或操作。
  • SIG_UNBLOCK:将set所指向的信号集中包含的信号从当前的信号屏蔽集中删除,即信号屏蔽集和set信号集的补集进行逻辑与操作。
  • SIG_SETMASK:将set的值设定为新的进程信号屏蔽集,即set直接对信号屏蔽集进行了赋值操作。

set:指向一个sigset_t类型的指针,表示需要修改的信号集合。如果只想读取当前的屏蔽值而不进行修改,可以将其置为NULL。

oldset:指向一个sigset_t类型的指针,用于存储修改前的内核阻塞信号集。如果不关心旧的信号屏蔽集,可以传递NULL。

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。


 sigpending函数

 sigpending函数用于 读取 当前进程的未决信号集(pending表),通过set参数传出。

#include <signal.h>
int sigpending(sigset_t *set);
// 调⽤成功则返回0,出错则返回-1

 ps. signal函数修改handler表。

Demo代码

结合上面介绍的相关函数做一个Demo:我们把2号信号block对应的位图置为1,即将2号信号屏蔽掉;此时我们给当前进程发送2号信号,因为2号信号已经被屏蔽了,所以2号信号永远不会递达;之后我们不断的获取当前进程的pending表,我们就能肉眼看见2号信号被pending的效果:验证

1. 对2号信号的屏蔽 

// 1.屏蔽2号信号
int main()
{sigset_t block_set,old_set;sigemptyset(&block_set);sigemptyset(&old_set);// 1.1 添加SIGINT信号(编号为2)sigaddset(&block_set,SIGINT);// 1.2 设置进入进程的Block表中// 真正的修改当前进行的内核的block表,完成了对2号信号的屏蔽!sigprocmask(SIG_BLOCK, &block_set, &old_set); while(true) sleep(1);
}

2. 打印pending表,并给该进程发送2号信号

void PrintPending(sigset_t &pending)
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo))//如果存在就返回1std::cout << 1;else    std::cout << 0;}std::cout << "\n";
}int main()
{// 1.屏蔽2号信号sigset_t block_set,old_set;sigemptyset(&block_set);sigemptyset(&old_set);// 1.1 添加SIGINT信号(编号为2)sigaddset(&block_set,SIGINT);// 1.2 设置进入进程的Block表中// 真正的修改当前进行的内核的block表,完成了对2号信号的屏蔽!sigprocmask(SIG_BLOCK, &block_set, &old_set); while(true) {//2.获取当前进程的pending信号集sigset_t pending;sigpending(&pending);//3.打印pending信号集PrintfPending(pending);sleep(1);}
}
 

我们将2号信号屏蔽后,打印pending表,首先打印出来的是31个0;当我们按下ctrl + c组合键后,向该进程发送2号信号,该进程没有被终止,因为信号产生之后,pending表2号位置由 0 变 1 ,又因为2号信号被屏蔽,即永远不会递达,所以我们可以看到panding表发生变化,但进程不会被终止;如果不解除屏蔽,pending表2号位置永远都是1!

3. 解除屏蔽,打印pending表

void PrintPending(sigset_t &pending)
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo))//如果存在就返回1std::cout << 1;else    std::cout << 0;}std::cout << "\n";
}
int cnt = 0;
int main()
{// 解除屏蔽后,2号信号正常递达,在该进程中就会直接退出// 我们还想看到后续的现象,所以使用signal忽略掉此信号::signal(2, SIG_IGN);// 1.屏蔽2号信号sigset_t block_set,old_set;sigemptyset(&block_set);sigemptyset(&old_set);// 1.1 添加SIGINT信号(编号为2)sigaddset(&block_set,SIGINT);// 1.2 设置进入进程的Block表中// 真正的修改当前进行的内核的block表,完成了对2号信号的屏蔽!sigprocmask(SIG_BLOCK, &block_set, &old_set); while(true) {//2.获取当前进程的pending信号集sigset_t pending;sigpending(&pending);//3.打印pending信号集PrintfPending(pending);sleep(1);cnt++;// 4. 解除屏蔽if(cnt == 5){std::cout << "解除对2号信号的屏蔽" << std::endl;sigprocmask(SIG_SETMASK, &oblock, nullptr);}}
}

通过运行结果我们可以发现,一开始panding表全为0,当我们按下ctrl + c组合键后,变为1。几秒后,解除屏蔽又变为0。

信号的处理

当产生一个信号,进程需要将改进好保存起来,在合适的时候在执行该信号。那么这个合适的时候是什么时候呢?其实是在进程在从 内核态 切换回 用户态 的时候,检测当前进程的pending表&& block表,决定是否处理handler表处理信号!

用户态内核态

操作系统是怎样运行的

硬件中断

  • 中断向量表就是操作系统的一部分,启动就加载到内存中了
  • 通过外部硬件中断,操作系统就不需要对外设进行任何周期性的检测或者轮询
  • 由外部设备触发的,中断系统运行流程,叫做硬件中断

时钟中断

进程可以在操作系统的指挥下,被调度,被执行,那么操作系统自己被谁指挥,被谁推动执行呢?答案是时钟中断!在每一个系统中都有一个叫做 “时钟源” 的外设,在极短的时间内一直向CPU发送中断请求,进而CPU获取中断号,查中断向量表,OS就不断的进行中断服务!只不过在现代计算机中, “时钟源” 都被集成在CPU内部(主频),因为在外部,时间上太慢了,空间上一直占用中断控制器。

基于上面的理解,操作系统是什么?操作系统就是基于中断向量表,进行工作的!

操作系统自己不做任何事情,需要什么功能,就向中断向量表里面添加方法即可。操作系统的本质:就是一个死循环!

什么是时间片?时间片就是一个int count;如果时间片没有减为0,就什么也不做;如果减为0,就进行进程切换!

软中断

软中断则是由软件(即系统中的某个进程或程序)触发的,常用于系统调用、异常处理以及中断服务的下半部处理。

为了让操作系统支持进行系统调用,CPU设计了对应的汇编指令(int 或者 syscall),可以让CPU内部触发中断逻辑,即软中断。

系统调用的过程,其实就是先int 0x80、syscall陷入内核,本质就是触发软中断,CPU就会自动执行系统调用的处理方法,而这个方法会根据系统调用号(数组下标),自动查表,执行对应的方法

  • CPU内部的软中断,比如int 0x80或者syscall,CPU内部没有错误,我们叫做 陷阱。
  • CPU内部的软中断,比如除零/野指针等,CPU内部出现错误, 我们叫做 异常。

信号捕捉的操作

sigaction函数

 参数说明

  • signum:指定要设置或获取处理程序的信号编号。可以指定SIGKILL和SIGSTOP以外的所有信号。
  • act:指向sigaction结构体的指针,用于指定新的信号处理方式。如果此参数非空,则根据此参数修改信号的处理动作。
  • oldact:如果非空,则通过此参数传出该信号原来的处理动作。(如果你想恢复以前的方式,此参数就是保存之前的操作方式)

 Demo样例代码

//act指向的sigaction结构体(我们只考虑第一个和第三个参数)
//struct sigaction {  
//    void (*sa_handler)(int);    // 指向信号处理函数的指针,接收信号编号作为参数  
//    void (*sa_sigaction)(int, siginfo_t *, void *);  // 另一个信号处理函数指针,支持更丰富的信号信息
//    sigset_t sa_mask;           // 设置在处理该信号时暂时屏蔽的信号集  
//    int sa_flags;               // 指定信号处理的其他相关操作,一般为0  
//    void (*sa_restorer)(void);  // 已废弃,不用关心  
//};void handler(int signo)
{std::cout << "get a sig: " << signo << std::endl;exit(1);
}
int main()
{// 定义struct sigaction结构体对象struct sigaction act, oact;// 获得结构体中的方法act.sa_handler = handler;// 调用sigaction函数::sigaction(2, &act, &oact);while (true)pause();return 0;
}


注意:如果在处理信号期间,又来了一个该信号需要处理。这时OS会自动把对应信号的block位设置为1(阻塞住该信号),等信号处理完成,OS又会自动把对应信号的block位由1置为0。

// 打印Block表
void PirintBLock()
{sigset_t set, oset;// 将信号集set、oset全部位置为0sigemptyset(&set);sigemptyset(&oset);sigprocmask(SIG_BLOCK, &set, &oset);std::cout << "block: ";for (int signo = 31; signo > 0; signo--){if (sigismember(&oset, signo))    std::cout << 1;else    std::cout << 0;}std::cout << std::endl;
}
void handler(int signo)
{static int cnt = 0;cnt++;while (true){std::cout << "get a sig: " << signo << ", cnt: " << cnt << std::endl;PirintBLock();sleep(1);}exit(1);
}
int main()
{struct sigaction act, oact;act.sa_handler = handler;::sigaction(2, &act, &oact);while (true){PirintBLock();pause();}
}

其他问题

volatile关键字

#include <stdio.h>
#include <signal.h>
#include <unistd.h>int flag = 0;
void change(int signo) // 信号捕捉的执行流
{flag = 1;printf("change flag 0->1, getpid: %d\n", getpid());
}int main()
{printf("I am main process, pid is : %d\n", getpid());signal(2, change);while(!flag); // 主执行流--- flag我们没有做任何修改!printf("我是正常退出的!\n");
}

上面代码中共有两个执行流,但它们同属一个进程。

当我们使用 signal 捕捉了2号信号,进而执行了change自定义方法后,全局变量flag就被更改为1,再回到main函数,其里面的while循环条件不成立,就会停止执行,因为CPU在执行while循环的时候,是实时的从内存中取flag来进行比较的,所以程序正常退出。

我们再次运行,并在编译时进行优化,优化会让CPU保存 之前在内存中取的flag的值,即,取flag在内存中最开始的值,这就会导致虽然flag的值发生了变化,但是CPU一直取得都是flag最开始的值,就导致while循环无法结束。

如果将上面代码中的int flag = 0;改为

volatile int flag = 0;    // 易变关键字

再运行。

我们发现可以正常退出了。volatile关键字表示保持flag变量的内存可见性即确保变量每次访问时都直接从内存中读取。(ps. 所有的关键字都是给编译器看的)

SIGCHLD信号(17号信号)

子进程退出时,不是静悄悄的退出的,会给父进程发送 SIGCHLD信号。

通过man 7 signal可以查到SIGCHLD信号的默认处理动作是Ign(忽略)

 Signal      Standard   Action   Comment
SIGCHLD      P1990      Ign     Child stopped or terminated

父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心⼦进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

// 验证子进程退出,给父进程发送SIGCHLD,父进程通过该信号回收子进程
void handler(int signo)
{std::cout << "get a sig: " << signo << " I am : " << getpid() << std::endl;// 等待进程的pid为-1时,表示回收最近退出的子进程pid_t rid = ::waitpid(-1, nullptr, 0);if(rid > 0){std::cout << "子进程退出了,回收成功,child id: " << rid << std::endl;}
}
int main()
{// 捕捉并自定义动作::signal(SIGCHLD, handler);if(fork() == 0){sleep(3);std::cout << "子进程退出" << std::endl;// 子进程exit(0);}while(1)    sleep(1);return 0;
}


signal(SIGCHLD, SIG_IGN); 

也可以通过上面一行代码来实现父进程对子进程的回收。这行代码的作用是设置信号处理函数,以便当子进程结束时(即发送SIGCHLD信号给父进程时),父进程忽略这个信号。通常,当子进程结束时,父进程需要处理这个信号以回收子进程的资源,但在这里,通过将其设置为SIG_IGN,父进程选择忽略这个信号,这意味着子进程的资源将由操作系统自动回收(这通常被称为“僵尸进程”的避免,尽管在这种情况下,由于子进程正常退出并设置了退出码,它实际上不会成为僵尸进程,因为操作系统会注意到并清理它)。

注意:如果你不关心子进程的退出信息就可以使用这种方法,否则还是要进行等待。

相关文章:

Linux_信号

信号的概念 && 知识补充 信号是进程之间事件异步通知的一种方式&#xff0c;是一种软中断。 标准信号&#xff1a;编号为1-31之间都是标准信号&#xff0c;这些都是预定义信号&#xff0c;用于通知进程发生的各种事件。实时信号&#xff1a;编号从32开始起均是实时信号…...

LeetCode100之搜索二维矩阵(46)--Java

1.问题描述 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回…...

学员答疑:安卓分屏窗口的TouchableRegion设置流程追踪

背景&#xff1a; vip学员在群里问到了一个分屏触摸区域设置的问题&#xff0c;开始以为就是和普通Activity设置区域没啥差别,都是在InputMonitor中进行的设置&#xff0c;但是仔细研究下来其实并不是哈。本文就带大家来手把手分析一下分屏情况下的触摸区域是怎么设置的。 d…...

[cg] UE5 调试技巧

UE 中 rhi命令的提交是在render 线程&#xff0c;而graphics api 真正的执行是在rhi 线程&#xff0c; 今天想看下rhi的底层调用&#xff0c;但由于是通过task执行的&#xff0c;无法获取到render thread传入的地方&#xff0c;调试起来不太方便。 可通过开启下面的命令来调试 …...

Python Wi-Fi密码测试工具

Python Wi-Fi测试工具 相关资源文件已经打包成EXE文件&#xff0c;可双击直接运行程序&#xff0c;且文章末尾已附上相关源码&#xff0c;以供大家学习交流&#xff0c;博主主页还有更多Python相关程序案例&#xff0c;秉着开源精神的想法&#xff0c;望大家喜欢&#xff0c;点…...

Linux 创建用户

Linux 创建用户 创建用户 sudo useradd -m -s /bin/bash test - -m&#xff1a;自动创建家目录 /home/test - -s /bin/bash&#xff1a;指定默认的 shell 为 bash修改密码 # 修改密码 sudo passwd test删除用户 userdel -r zengshun - -r&#xff1a;把用户的主目录一起删…...

自建RustDesk服务器

RustDesk服务端 下面的截图是我本地的一个服务器做为演示用&#xff0c;你自行的搭建服务需要该服务器有固定的ip地址 1、通过宝塔面板快速安装 2、点击【安装】后会有一个配置信息&#xff0c;默认即可 3、点击【确认】后会自动安装等待安装完成 4、安装完成后点击【打开…...

Spring Boot Web技术栈(官网文档解读)

摘要 Spring Boot框架既支持传统的Servlet技术栈&#xff0c;也支持新兴的响应式&#xff08;Reactive&#xff09;技术栈。本篇文章将详细讲述Spring Boot 对两种技术栈的详细支持和使用。 Servlet 概述 基于Java Servlet API构建&#xff0c;它依赖于传统的阻塞I/O模型&…...

【llama_factory】qwen2_vl训练与批量推理

训练llama factory配置文件 文件&#xff1a;examples/train_lora/qwen2vl_lora_sft.yaml ### model model_name_or_path: qwen2_vl/model_72b trust_remote_code: true### method stage: sft do_train: true finetuning_type: lora lora_target: all### dataset dataset: ca…...

wpa_cli命令使用记录

wpa_cli可以用于查询当前状态、更改配置、触发事件和请求交互式用户输入。具体来说&#xff0c;它可以显示当前的认证状态、选择的安全模式、dot11和dot1x MIB等&#xff0c;并可以配置一些变量&#xff0c;如EAPOL状态机参数。此外&#xff0c;wpa_cli还可以触发重新关联和IEE…...

【Uniapp-Vue3】页面生命周期onLoad和onReady

一、onLoad函数 onLoad在页面载入时触发&#xff0c;多用于页面跳转时进行参数传递。 我们在跳转的时候传递参数name和age: 接受参数&#xff1a; import {onLoad} from "dcloudio/uni-app"; onLoad((e)>{...}) 二、onReady函数 页面生命周期函数中的onReady其…...

《C++11》并发库:简介与应用

在C11之前&#xff0c;C并没有提供原生的并发支持。开发者通常需要依赖于操作系统的API&#xff08;如Windows的CreateThread或POSIX的pthread_create&#xff09;或者第三方库&#xff08;如Boost.Thread&#xff09;来创建和管理线程。这些方式存在以下几个问题&#xff1a; …...

LeetCode - #183 Swift 实现查询未下订单的客户

网罗开发 &#xff08;小红书、快手、视频号同名&#xff09; 大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等…...

HTML拖拽功能(纯html5+JS实现)

1、HTML拖拽--单元行拖动 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><…...

mysql 等保处理,设置wait_timeout引发的问题

&#x1f468;‍⚕ 主页&#xff1a; gis分享者 &#x1f468;‍⚕ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕ 收录于专栏&#xff1a;运维工程师 文章目录 前言问题处理 前言 系统部署完成后&#xff0c;客户需要做二级等保&…...

7.STM32F407ZGT6-RTC

参考&#xff1a; 1.正点原子 前言&#xff1a; RTC实时时钟是很基本的外设&#xff0c;用来记录绝对时间。做个总结&#xff0c;达到&#xff1a; 1.学习RTC的原理和概念。 2.通过STM32CubeMX快速配置RTC。 27.1 RTC 时钟简介 STM32F407 的实时时钟&#xff08;RTC&#xf…...

重写(补充)

大家好&#xff0c;今天我们把剩下一点重写内容说完&#xff0c;来看。 [重写的设计规则] 对于已经投入使用的类,尽量不要进行修政 &#xff0c;最好的方式是:重新定义一个新的类,来重复利用其中共性的内容 我们不该在原来的类上进行修改&#xff0c;因为原来的类,可能还有用…...

30分钟内搭建一个全能轻量级springboot 3.4 + 脚手架 <3>5分钟集成好druid并使用druid自带监控工具监控sql请求

快速导航 快速导航 <1> 5分钟快速创建一个springboot web项目 <2> 5分钟集成好最新版本的开源swagger ui&#xff0c;并使用ui操作调用接口 <3> 5分钟集成好druid并使用druid自带监控工具监控sql请求 <4> 5分钟集成好mybatisplus并使用mybatisplus g…...

【C#深度学习之路】如何使用C#实现Yolo8/11 Segment 全尺寸模型的训练和推理

【C#深度学习之路】如何使用C#实现Yolo8/11 Segment 全尺寸模型的训练和推理 项目背景项目实现推理过程训练过程 项目展望写在最后项目下载链接 本文为原创文章&#xff0c;若需要转载&#xff0c;请注明出处。 原文地址&#xff1a;https://blog.csdn.net/qq_30270773/article…...

Oracle 分区索引简介

目录 一. 什么是分区索引二. 分区索引的种类2.1 局部分区索引&#xff08;Local Partitioned Index&#xff09;2.2 全局分区索引&#xff08;Global Partitioned Index&#xff09; 三. 分区索引的创建四. 分区索引查看4.1 USER_IND_COLUMNS 表4.2 USER_INDEXES 表 五. 分区索…...

【科技赋能未来】NDT2025第三届新能源数字科技大会全面启动!

随着我国碳达峰目标、碳中和目标的提出&#xff0c;以及经济社会的发展进步&#xff0c;以风电、光伏发电为代表的新能源行业迎来巨大发展机遇&#xff0c;成为未来绿色经济发展的主要趋势和方向。 此外&#xff0c;数字化技术的不断发展和创新&#xff0c;其在新能源领域的应…...

Broker收到消息之后如何存储

1.前言 此文章是在儒猿课程中的学习笔记&#xff0c;感兴趣的想看原来的课程可以去咨询儒猿课堂《从0开始带你成为RocketMQ高手》&#xff0c;我本人觉得这个作者还是不错&#xff0c;都是从场景来进行分析&#xff0c;感觉还是挺适合我这种小白的。这块主要都是我自己的学习笔…...

Mysql--实战篇--SQL优化(查询优化器,常用的SQL优化方法,执行计划EXPLAIN,Mysql性能调优,慢日志开启和分析等)

一、查询优化 1、查询优化器 (Query Optimizer) MySQL查询优化器&#xff08;Query Optimizer&#xff09;是MySQL数据库管理系统中的一个关键组件&#xff0c;负责分析和选择最有效的执行计划来执行SQL查询。查询优化器的目标是尽可能减少查询的执行时间和资源消耗&#xff…...

BERT与CNN结合实现糖尿病相关医学问题多分类模型

完整源码项目包获取→点击文章末尾名片&#xff01; 使用HuggingFace开发的Transformers库&#xff0c;使用BERT模型实现中文文本分类&#xff08;二分类或多分类&#xff09; 首先直接利用transformer.models.bert.BertForSequenceClassification()实现文本分类 然后手动实现B…...

rabbitmqp安装延迟队列

在RabbitMQ中&#xff0c;延迟队列是一种特殊的队列类型。当消息被发送到此类队列后&#xff0c;不会立即投递给消费者&#xff0c;而是会等待预设的一段时间&#xff0c;待延迟期满后才进行投递。这种队列在多种场景下都极具价值&#xff0c;比如可用于处理需要在特定时间触发…...

深入探讨DICOM医学影像中的MPPS服务及其具体实现

深入探讨DICOM医学影像中的MPPS服务及其具体实现 1. 引言 在医疗影像的管理和传输过程中&#xff0c;DICOM&#xff08;数字影像和通信医学&#xff09;标准发挥着至关重要的作用。除了DICOM影像的存储和传输&#xff08;如影像存储SCP和影像传输SCP&#xff09;&#xff0c;…...

集合帖:区间问题

一、AcWing 803&#xff1a;区间合并 &#xff08;1&#xff09;题目来源&#xff1a;https://www.acwing.com/problem/content/805/ &#xff08;2&#xff09;算法代码&#xff1a;https://blog.csdn.net/hnjzsyjyj/article/details/145067059 #include <bits/stdc.h>…...

C#,入门教程(27)——应用程序(Application)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(26)——数据的基本概念与使用方法https://blog.csdn.net/beijinghorn/article/details/124952589 一、什么是应用程序 Application&#xff1f; 应用程序是编程的结果。一般把代码经过编译&#xff08;等&#xff09;过程&#…...

迅翼SwiftWing | ROS 固定翼开源仿真平台正式发布!

经过前期内测调试&#xff0c;ROS固定翼开源仿真平台今日正式上线&#xff01;现平台除适配PX4ROS环境外&#xff0c;也已实现APROS环境下的单机飞行控制仿真适配。欢迎大家通过文末链接查看项目地址以及具体使用手册。 1 平台简介 ROS固定翼仿真平台旨在实现固定翼无人机决策…...

CSS 样式 box-sizing: border-box; 详细解读

box-sizing是 CSS 中的一个属性&#xff0c;用于控制元素的盒模型计算方式。border-box值表示元素的宽度和高度将包括内边距&#xff08;padding&#xff09;和边框&#xff08;border&#xff09;&#xff0c;而不仅仅是内容的宽度和高度。这意味着当你为元素设置宽度和高度时…...