Linux——进程信号

目录

一、信号的理解

二、信号的种类

2.1 标准信号 (1-31)

2.2 实时信号 (通常是34及以上)

三、信号的产生

3.1 用户通过终端产生信号

3.1.1 signal 函数

 3.1.2 demo 测试

3.1.3 demo 现象

3.2 通过系统函数产生信号

3.2.1 demo 测试

3.3 由软件条件产生信号

3.3.1 alarm 函数

3.3.2 demo 测试

3.3.3 demo 现象 

3.4 硬件异常产生信号

3.4.1 demo 测试 

3.4.2 demo 现象 

3.5 进程间通信产生信号

3.5.1 SIGCHLD 信号

3.5.1 demo 测试

3.5.2 demo 现象

四、信号的保存

4.1 信号的产生和递达

4.2 信号的状态

4.3 信号的存储结构

4.4 信号的处理

4.5 sigset_t 类型的信号集

4.5.1 sigprocmask:更改或检查进程的信号屏蔽字。

4.5.2 sigpending:检查当前进程的未决信号集。

4.5.3 sigemptyset:将信号集初始化为空集,即清除信号集中所有的信号,使其不包含任何信号。

4.5.4 sigfillset:将信号集初始化为满集,即包含所有可能的信号。

4.5.5 sigaddset:将指定的信号添加到信号集中。 

 4.5.6 sigdelset:将指定的信号从信号集中删除。

函数原型

4.5.7 sigismember:检查指定的信号是否在信号集中。

4.6 测试 demo

4.7 demo 现象 

五、捕捉信号

5.1 内核态与用户态

用户态(User Mode)

特点

安全性

性能

内核态(Kernel Mode)

特点

安全性

性能

5.2 OS的存储

5.3 信号捕捉的过程


一、信号的理解

想象你在家里等待一个快递包裹的到来:

  1. 快递员到达通知

    • 当快递员到达你家楼下,他给你打电话或者发短信,告诉你快递到了。这就像一个信号,通知你有一个事件(快递到达)需要处理。
    • 在Linux中,这类似于信号的产生和发送。例如,当你按下Ctrl+C时,系统会产生一个SIGINT信号,发送给当前正在运行的前台进程。
  2. 你正在忙碌

    • 如果你正在忙着做其他事情,比如在看电影,你会告诉快递员放进快递驿站,这段时间你知道有快递到了,但还没有去取。
    • 在Linux中,这相当于信号被产生但被屏蔽了,暂时不会立即处理。信号被保留在一个“待处理”的状态,直到你准备好处理它。
  3. 处理快递

    • 当你处理完手头的事情,你决定去取快递。你有几种选择:
      1. 默认动作:直接去拿快递,打开包裹,使用里面的物品。
      2. 自定义动作:你可以决定先不打开包裹,而是把它存放在一个特定的地方,稍后再处理。
      3. 忽略:你可以选择忽略这个快递,不去取它。
    • 在Linux中,进程可以对信号有不同的处理方式:执行默认操作、自定义处理函数、或者忽略信号。例如,SIGINT的默认操作是终止进程,但你可以自定义一个处理函数来执行其他动作。

二、信号的种类

可以在终端使用 kill -l 查看信号的种类:

2.1 标准信号 (1-31)

标准信号是由POSIX标准定义的,所有Unix和Linux系统都支持这些信号。每个信号都有一个固定的编号和对应的宏定义名称。

信号在Linux系统中是一种用于进程间通信和事件通知的机制。信号的产生可以由多种途径触发,具体包括以下几种方式:

  1. SIGHUP (1):挂起信号,通常在终端断开时发送给会话控制的进程。
  2. SIGINT (2):中断信号,通常由用户按下Ctrl+C产生,用于终止前台进程。
  3. SIGQUIT (3):退出信号,通常由用户按下Ctrl+\产生,用于终止进程并产生核心转储(core dump)。
  4. SIGILL (4):非法指令信号,进程执行非法、无效的机器指令时产生。
  5. SIGABRT (6):进程异常终止信号,通常由abort()函数产生。
  6. SIGFPE (8):浮点异常信号,如除以零等算术错误。
  7. SIGKILL (9):杀死信号,无法捕捉或忽略,用于立即终止进程。
  8. SIGSEGV (11):段错误信号,进程非法访问内存时产生。
  9. SIGPIPE (13):管道破裂信号,进程写入无读端的管道时产生。
  10. SIGALRM (14):闹钟信号,由alarm()函数设定的计时器到期时产生。
  11. SIGTERM (15):终止信号,默认用于请求进程终止。

这些信号编号在1到31之间,涵盖了大多数常见的进程控制和错误处理机制。

2.2 实时信号 (通常是34及以上)

实时信号的编号范围通常从34开始,根据具体的Linux实现可能有所不同。这些信号是POSIX实时扩展的一部分,提供了更高的优先级和实时性特性。

这里不做过多说明。

三、信号的产生

3.1 用户通过终端产生信号

用户可以通过在终端按特定的键来产生信号。例如:

  • SIGINT (信号编号2):当用户按下Ctrl+C时,系统会产生一个SIGINT信号并发送给当前运行的前台进程。这个信号的默认处理动作是终止进程。
  • SIGQUIT (信号编号3):当用户按下Ctrl+\时,系统会产生一个SIGQUIT信号,默认处理动作是终止进程并产生一个核心转储(core dump)​

3.1.1 signal 函数

在Linux和其他类Unix操作系统中,signal是一个函数,用于设置进程对特定信号的处理方式。信号(signal)是进程间通信的一种机制,用于通知进程某个事件的发生。

signal函数用于指定某个信号的处理函数。当进程接收到该信号时,操作系统会调用指定的处理函数。通过这个函数,程序可以定义自定义的行为来响应信号,而不仅仅是执行默认的处理动作(比如终止进程)。

其函数原型如下:

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
  • sig: 指定的信号编号(例如 SIGINT 表示中断信号)。
  • func: 指向信号处理函数的指针。处理函数接受一个整数参数,该参数是信号的编号。

 3.1.2 demo 测试

以下是一个demo,可以用来测试用户通过终端产生信号 。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigint(int sig) 
{printf("Caught signal %d (SIGINT). Exiting...\n", sig);_exit(0);
}int main() 
{// 设置对 SIGINT 信号的处理函数signal(SIGINT, handle_sigint);// 无限循环,等待信号到来while (1) {printf("Running... Press Ctrl+C to stop.\n");sleep(1);}return 0;
}
/*signal(SIGINT, handle_sigint): 设置对 SIGINT 信号的处理函数为 handle_sigint。当进程接收到 SIGINT 信号时,将调用 handle_sigint 函数。
handle_sigint 函数:自定义的信号处理函数,打印接收到的信号编号。*/

demo 解释:

  1. signal(SIGINT, handle_sigint): 设置对 SIGINT 信号的处理函数为 handle_sigint。当进程接收到 SIGINT 信号时,将调用 handle_sigint 函数。
  2. handle_sigint 函数:自定义的信号处理函数,打印接收到的信号编号。

3.1.3 demo 现象


可见,CTRL + c 就是信号 SIGINT ,按下 CTRL + c 后,系统进入handle函数,执行了 exit

如果对 demo 进行修改,取消掉 exit 后,再按下 CTRL + c 后,程序就不会终止了。

产生这种现象的原因是 signal 函数改变了对 CTRL + c 这种信号的处理方式,把退出的处理方式修改成了 handle_sigint 函数中的方式。

3.2 通过系统函数产生信号

程序可以通过调用系统提供的函数来产生信号,例如:

  • kill()函数:可以用于向指定进程发送信号。调用形式如kill(pid, signo),其中pid是目标进程的进程ID,signo是要发送的信号编号。
  • raise()函数:用于向当前进程自身发送信号,相当于kill(getpid(), signo)
  • abort()函数:使当前进程接收到SIGABRT信号并异常终止​​。

3.2.1 demo 测试

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigusr1(int sig) 
{printf("Caught signal %d (SIGUSR1)\n", sig);_exit(0);
}int main() 
{// 设置对 SIGUSR1 信号的处理函数signal(SIGUSR1, handle_sigusr1);pid_t pid = fork();if (pid == 0) {// 子进程sleep(2);kill(getppid(), SIGUSR1); // 向父进程发送 SIGUSR1 信号_exit(0);} else {// 父进程while (1) {printf("Waiting for SIGUSR1 from child process...\n");sleep(1);}}return 0;
}

3.2.2 demo 现象

通过上述 demo ,子进程在休息2s后会对父进程发送信号然后退出,父进程进入一个无限循环,等待 SIGUSR1 信号。当信号到达时,信号处理函数 handle_sigusr1 将被调用。 

当 handle_sigusr1 被调用时,会打印出 Caught signal %d (SIGUSR1)\n ,然后执行 _exit ,退出程序。

3.3 由软件条件产生信号

一些信号是由特定的软件条件触发的,例如:

  • SIGPIPE (信号编号13):当进程尝试向一个没有读端的管道或套接字写入数据时,系统会产生SIGPIPE信号。
  • SIGALRM (信号编号14):通过alarm(seconds)函数设定一个闹钟,在指定的秒数后系统会向当前进程发送SIGALRM信号​​。

3.3.1 alarm 函数

函数原型

#include <unistd.h>unsigned int alarm(unsigned int seconds);
  • seconds:指定计时器的秒数。设定计时器在 seconds 秒之后发送 SIGALRM 信号。如果 seconds 为 0,表示取消任何现有的计时器。
  • 返回先前设定的闹钟时间还剩余的秒数。如果没有设定过闹钟,则返回 0。

3.3.2 demo 测试

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigalrm(int sig) 
{printf("Caught signal %d (SIGALRM). Time's up!\n", sig);
}int main() 
{// 设置对 SIGALRM 信号的处理函数signal(SIGALRM, handle_sigalrm);alarm(5);  // 设定闹钟在 5 秒后触发 SIGALRM 信号// 无限循环,等待信号到来while (1) {printf("Sleeping... Waiting for alarm...\n");sleep(1);}return 0;
}

3.3.3 demo 现象 

首先使用 alarm 函数设定了一个5s后的闹钟,程序会在5s后接收到 SIGALRM 信号,同时使用signal 函数重新设计了 SIGALRM 信号的处理方式,所以执行程序后会看到以下现象:

在Unix和类Unix系统中,alarm函数只支持设置一个定时器。如果在一个进程中设置了两个alarm调用,后面的调用会覆盖前面的调用。

具体来说,当你第二次调用alarm时,它会取消前一个定时器并重新设定一个新的定时器。因此,第一个定时器所关联的SIGALRM信号将不会被发送,只有最后一次调用alarm设置的定时器到期时,才会发送SIGALRM信号。

3.4 硬件异常产生信号

当进程执行非法操作(如除以0或者页表对应失败(数组越界、野指针...))时,硬件会产生异常,内核将这些异常转换为信号并发送给进程,例如:

  • SIGSEGV (信号编号11):当进程访问非法内存地址时,系统会产生SIGSEGV信号,通常导致进程异常终止。
     
  • SIGFPE (信号编号8):当进程执行非法的算术操作(如除以零)时,系统会产生SIGFPE信号​​

3.4.1 demo 测试 

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigfpe(int sig) 
{printf("Caught signal %d (SIGFPE). Division by zero!\n", sig);_exit(1);
}int main() 
{// 设置对 SIGFPE 信号的处理函数signal(SIGFPE, handle_sigfpe);int x = 1;int y = 0;int z = x / y;  // 这将导致 SIGFPE 信号printf("Result: %d\n", z);return 0;
}

3.4.2 demo 现象 

3.5 进程间通信产生信号

父进程和子进程之间可以通过信号进行通信。例如,当子进程终止时,会向父进程发送SIGCHLD信号。父进程可以捕捉并处理该信号,以便执行相应的清理工作,避免产生僵尸进程​​。

3.5.1 SIGCHLD 信号

SIGCHLD 是一个特定的信号,用于通知父进程其子进程的状态变化。通常,当一个子进程终止或停止时,系统会向父进程发送 SIGCHLD 信号。父进程可以通过捕捉和处理 SIGCHLD 信号来得知其子进程的终止或停止状态,并进行相应的处理,如清理资源或重新启动子进程。

以下是一个子进程对应一个父进程时,子进程退出向父进程发出 SIGCHLD 信号。

3.5.1 demo 测试

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>// 自定义的信号处理函数
void handle_sigchld(int sig) 
{pid_t pid;int status;while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {printf("Child %d terminated\n", pid);}
}int main() 
{// 设置对 SIGCHLD 信号的处理函数signal(SIGCHLD, handle_sigchld);if (fork() == 0) {// 子进程printf("Child process: %d\n", getpid());sleep(2);_exit(0);} else {// 父进程while (1) {printf("Parent process doing some work...\n");sleep(1);}}return 0;
}

3.5.2 demo 现象

这里对SIGCHLD信号进行处理,在处理方式中设置了 waitpid 的方法,同时,其中设置了WNOHANG 的方式,防止子进程一部分退出另一部分不退出造成的进程堵塞,这样也会导致父进程无法进行自己的操作。

同时,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用 sigaction 将SIGCHLD 的处理动作置为 SIG_IGN ,这样 fork 出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略:

signal(SIGCHLD, SIG_IGN);

四、信号的保存

4.1 信号的产生和递达

  • 信号产生:当某个事件发生时(例如用户按下Ctrl+C、调用kill函数、硬件异常等),内核会为目标进程产生一个信号。
  • 信号递达:信号递达指的是信号被实际传送到目标进程并触发相应的处理动作。递达的时机可以是立即的,也可以是当进程解除信号阻塞时。

4.2 信号的状态

信号在一个进程的生命周期中可以有三种状态:

  • 未决(Pending):信号已经产生,但由于某种原因(例如信号被阻塞)尚未递达。
  • 递达(Delivered):信号已经传递到进程,并触发了相应的处理动作。
  • 阻塞(Blocked):进程设置了信号屏蔽字,暂时阻止某些信号的递达。

4.3 信号的存储结构

每个进程都有两个重要的数据结构用于信号的管理:

  • 未决信号集(Pending Signals Set):记录当前进程所有未决的信号。通常用一个位图来表示,每个比特位对应一个信号,置1表示该信号未决。
  • 信号屏蔽字(Signal Mask):记录当前进程哪些信号被阻塞。也用一个位图来表示,每个比特位对应一个信号,置1表示该信号被阻塞。

4.4 信号的处理

当一个信号递达时,内核会根据以下步骤处理信号:

  1. 检查信号屏蔽字:如果信号被阻塞(在信号屏蔽字中置位),信号不会立即递达,而是保持未决状态。
  2. 更新未决信号集:将信号添加到未决信号集中。
  3. 检查信号处理方式
    • 默认处理:执行默认的处理动作,例如终止进程。
    • 忽略信号:信号被丢弃,不做任何处理。
    • 自定义处理函数:调用用户定义的信号处理函数。

4.5 sigset_t 类型的信号集

#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);

4.5.1 sigprocmask:更改或检查进程的信号屏蔽字。

函数原型

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how:指定如何修改信号屏蔽字(如 SIG_BLOCK 阻塞信号)。
  • set:指向要设置的信号集。
  • oldset:如果不为 NULL,保存先前的信号屏蔽字。

4.5.2 sigpending:检查当前进程的未决信号集。

函数原型

int sigpending(sigset_t *set);
  • set:指向保存未决信号集的信号集。

4.5.3 sigemptyset:将信号集初始化为空集,即清除信号集中所有的信号,使其不包含任何信号。

函数原型

int sigemptyset(sigset_t *set);
  • set:指向要初始化的信号集。
  • 成功时返回 0;出错时返回 -1。

4.5.4 sigfillset:将信号集初始化为满集,即包含所有可能的信号。

函数原型

int sigfillset(sigset_t *set);
  • set:指向要初始化的信号集
  • 成功时返回 0;出错时返回 -1。

4.5.5 sigaddset:将指定的信号添加到信号集中。 

函数原型

int sigaddset(sigset_t *set, int signo);
  • set:指向要修改的信号集。
  • signo:要添加到信号集中的信号编号。
  • 成功时返回 0;出错时返回 -1。

 4.5.6 sigdelset:将指定的信号从信号集中删除。

函数原型
int sigdelset(sigset_t *set, int signo);
  • set:指向要修改的信号集。
  • signo:要从信号集中删除的信号编号。
  • 成功时返回 0;出错时返回 -1。

4.5.7 sigismember:检查指定的信号是否在信号集中。

函数原型

int sigismember(const sigset_t *set, int signo);
  • set:指向要检查的信号集。
  • signo:要检查的信号编号。
  • 如果信号在信号集中,返回 1;如果不在信号集中,返回 0;出错时返回 -1。

4.6 测试 demo

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>void PrintPending(sigset_t &pending)//打印“位图”
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){std::cout << 1;}else{std::cout << 0;}}std::cout << "\n";
}void handler(int signo)
{std::cout << signo << " 号信号被递达!!!" << std::endl;
}int main()
{// 0. 捕捉2号信号signal(2, handler); // 自定义捕捉signal(2, SIG_IGN); // 忽略一个信号signal(2, SIG_DFL); // 信号的默认处理动作// 1. 屏蔽2号信号sigset_t block_set, old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set, SIGINT);// 1.1 设置进入进程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!int cnt = 10;while (true){// 2. 获取当前进程的pending信号集sigset_t pending;sigpending(&pending);// 3. 打印pending信号集PrintPending(pending);cnt--;// 4. 解除对2号信号的屏蔽if (cnt == 0){std::cout << "解除对2号信号的屏蔽!!!" << std::endl;sigprocmask(SIG_SETMASK, &old_set, &block_set);}sleep(1);}
}

4.7 demo 现象 


五、捕捉信号

通过前面的学习,已经了解了我们可以自定义信号处理的方式,当对某信号进行自定义处理时,系统就要去找自定义的 handler 处理方法,但是,系统拥有最高的权限,它的这种身份被称作内核态,普通用户则被成为用户态。系统会以内核态的方式直接去执行自定义的 handler 函数吗?很显然是不行的。

这样如果某个用户钻了漏子,借用系统内核态的身份完成一些用户态不可以完成的事情,就会惹到麻烦。操作系统在这时就存在着身份的转换。

5.1 内核态与用户态

下面以32位机器为例:

4G的内存中,0-3G是供用户使用的,3-4G是操作系统的所有代码和数据。当用户想访问[3-4]G的地址时,只能使用系统调用!

用户态(User Mode)

特点
  • 受限访问:在用户态下,进程只能访问受限的内存区域和受限的硬件资源,不能直接执行可能影响系统稳定性的指令。
  • 应用程序运行:大多数应用程序(如文本编辑器、浏览器等)都在用户态运行。
  • 系统调用:当用户态进程需要执行特权操作(如文件读写、内存管理等)时,它必须通过系统调用请求内核的帮助。
安全性
  • 用户态运行的代码无法直接访问硬件和系统关键资源,防止应用程序错误或恶意代码直接影响系统的稳定性。
性能
  • 用户态进程的执行速度较慢,因为它们不能直接访问硬件,需要通过系统调用进行间接访问。

内核态(Kernel Mode)

特点
  • 完全访问权限:在内核态下,代码可以访问所有的内存区域和所有的硬件资源,可以执行任何CPU指令。
  • 操作系统内核运行:操作系统的内核及其核心服务(如设备驱动程序、文件系统、网络栈等)都在内核态运行。
  • 系统调用处理:内核态负责处理来自用户态的系统调用请求,并执行相应的操作。
安全性
  • 内核态运行的代码有最高权限,因此必须确保内核代码的正确性和安全性,避免系统崩溃或安全漏洞。
性能
  • 内核态进程的执行速度较快,因为它们可以直接访问硬件和系统资源。

5.2 OS的存储

操作系统也是一个软件,它是第一个加载到内存的软件,它的页表只会维护一份,所以当从用户级换到内核级时,无论在哪个进程,相应的系统调用会访问内核地址空间,映射到同一个内核级页表,进而每个进程进入的OS内部都是相同的!

系统调用访问内核地址空间:无论哪个进程发起系统调用,都会进入相同的内核地址空间,访问相同的内核数据结构和代码。

映射到相同的内核级页表:每个进程在进入内核态时,使用的都是相同的内核级页表。这确保了内核环境的一致性和简化了内存管理。

统一的OS内部环境:由于共享相同的内核地址空间和内核级页表,每个进程进入内核态时,看到的OS内部环境是相同的。

5.3 信号捕捉的过程

  1. 信号的产生(进入内核态)

    • 当某个事件(如用户按下Ctrl+C或硬件异常)触发信号时,内核会生成该信号并将其标记为待处理状态。此时,进程会从用户态切换到内核态。
    • 如果信号是由系统调用(如killraise)产生的,同样会引发进程进入内核态。
  2. 信号的检查与处理准备(进入用户态)

    • 内核检查当前进程的信号屏蔽字和信号处理设置,确定该信号是否需要处理。
    • 如果信号未被阻塞,内核会准备将信号处理函数(用户自定义的或默认的)加入进程的执行上下文中。这将导致进程从内核态返回到用户态。
  3. 信号处理函数的执行(进入内核态)

    • 当信号处理函数被调用时,进程再次从用户态切换到内核态,以便内核进行必要的处理(例如,保存当前的进程上下文)。
    • 内核将控制权交给信号处理函数,此时进程切换回用户态,执行用户定义的信号处理函数。
  4. 信号处理函数的完成(进入内核态并返回用户态)

    • 当信号处理函数执行完毕后,进程再次进入内核态,以便内核恢复先前保存的进程上下文。
    • 最终,内核将控制权返回给进程的正常执行流,进程回到用户态,继续执行未完成的工作。
用户态 (User Mode)|  | (事件触发,如 Ctrl+C)V
内核态 (Kernel Mode)|  | (信号生成,标记待处理)V
内核态 (Kernel Mode)|  | (准备信号处理)V
用户态 (User Mode)|  | (执行信号处理函数)V
内核态 (Kernel Mode)|  | (信号处理函数执行完毕)V
用户态 (User Mode)

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

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

相关文章

uni-app App端实现文字语音播报(Ba-TTS)

前言 最近在遇到消息提示语音播放出来&#xff0c;查了一圈文档发现并没有自带api 后面想起支付宝收钱播报&#xff0c;不受限与系统环境和版本环境&#xff08;后面查阅他是音频实现的&#xff09; 如果是由安卓端需要语音播放功能-直接使用Ba-TTs救急&#xff08;需要付费2…

MTK下载AP

只升级选Firemare Upgrade &#xff0c;点下载后&#xff0c;关机下插入USB

CSP俄罗斯方块(简单易懂)

开始将题目理解成了&#xff0c;开始的列应该是从输入图案的最左端开始计算&#xff0c;将前面所有的空列都删掉&#xff0c;代码如下&#xff1a; #include<bits/stdc.h> using namespace std; const int N 1e410; const int M 1e510; int a[20][20]; int b[5][5];int…

Java的类和对象

Java的类和对象 前言一、面向过程和面向对象初步认识C语言Java 二、类和类的实例化基本语法示例注意事项 类的实例化 三、类的成员字段/属性/成员变量注意事项默认值规则字段就地初始化 方法static 关键字修饰属性代码内存解析 修饰方法注意事项静态方法和实例无关, 而是和类相…

牛客NC295 连续子链表最大和【simple 动态规划 Java/Go/PHP/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/650b68dfa69d492d92645aecd7da9b21 思路 动态规划动态规划算法通过迭代遍历输入数组&#xff0c;维护一个额外的数组 dp 来记录截止到每个位置的最大连续子数组和&#xff0c;并利用一个变量 max_num 实时更新全…

买房送户口!多城加入“抢人大战”

业内人士认为&#xff0c;近期&#xff0c;多地推出的购房落户政策已区别于此前的人才落户政策&#xff0c;更聚焦于住房消费&#xff0c;降低了落户门槛&#xff0c;体现了各地对导入人口的重视&#xff0c;有利于人才流动&#xff0c;推动新型城镇化建设。 千万人口城市“后…

GpuMall智算云:xinntao/Real-ESRGAN/Real-ESRGAN-v0.2.5.0

介绍 Real-ESRGAN旨在开发用于一般图像/视频恢复的实用算法。用纯合成数据训练现实世界盲人超级分辨率。我们将强大的ESRGAN扩展到一个实用的恢复应用程序&#xff08;即Real-ESRGAN&#xff09;&#xff0c;该应用程序使用纯合成数据进行训练。GpuMall智算云 | 省钱、好用、弹…

Qt下使用QImage和OpenCV实现图像的拼接与融合

文章目录 前言一、使用QImage进行水平拼接二、使用OpenCV进行水平拼接三、使用OpenCV进行图像融合四、示例完整代码总结 前言 本文主要讲述了在Qt下使用QImage和OpenCV实现图像的拼接与融合&#xff0c;并结合相应的示例进行讲解&#xff0c;以便大家学习&#xff0c;如有错误…

光速入门python的OpenCV

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理python的OpenCV模块的关键知识点 争取用最短的时间入门OpenCV 并且做到笔记功能直接复制使用 OpenCV简介 不浪费时间的介绍: 就是类似于ps操作图片。 至于为什么不直接用ps&#xff0c;因为只有程序能…

JAVA -- > 初识JAVA

初始JAVA 第一个JAVA程序详解 public class Main {public static void main(String[] args) {System.out.println("Hello world");} }1.public class Main: 类型,作为被public修饰的类,必须与文件名一致 2.public static 是JAVA中main函数准写法,记住该格式即可 …

碌时刻必备!微信自动回复让你告别消息堆积

在忙碌的时候&#xff0c;我们往往会面临消息堆积如山的情况。无法及时回复消息不仅容易造成交流障碍&#xff0c;还可能错过重要的机会。 但是现在&#xff0c;有一个神奇的工具——个微管理系统&#xff0c;可以帮助我们轻松应对这个问题 &#xff0c;实现微信自动回复。 首…

【DZ模板】价值288克米设计APP手机版DZ模板 数据本地化+完美使用

模版介绍 【DZ模板】价值288克米设计APP手机版DZ模板 数据本地化完美使用 腾讯官方出品discuz论坛DIY的后台设置&#xff0c;功能齐全&#xff0c;论坛功能不亚于葫芦侠&#xff0c;自定义马甲&#xff0c;自定义认证&#xff0c;自定义广告&#xff0c;完全可以打造出自己想…

【手把手带你搓组件库】从零开始实现Element Plus

从零开始实现Element Plus 前言亮点项目搭建1、创建项目初始化monorepo创建 .gitignore目录结构安装基础依赖配置文件创建各个分包入口utilscomponentscoreplaytheme 2、创建VitePress文档3、部署到Github Actions生成 GH_TOKENGitHub Page 演示 4、总结 前言 在本文中&#xf…

《计算机网络微课堂》3-11 虚拟局域网 VLAN

本节课我们介绍虚拟局域网 VLAN 的基本概念。 ‍ 3.11.1 虚拟局域网 VLAN 概述 在之前课程中我们已经介绍过了以太网交换机自学习和转发帧的流程&#xff0c;‍‍以及为避免网络环路而产生的生成树协议。 以太网交换机工作在数据链路层&#xff0c;‍‍也包括物理层&#xf…

ftp是什么,ftp能做什么,ftp有什么用 -----ftp介绍

大家好&#xff0c;我是风屿&#xff0c;今天开始我会给大家介绍一些关于网络方面的配置以及介绍等等&#xff0c;今天是ftp FTP中文名字叫做文件传输协议&#xff0c;英文名字叫做File Transfer Protocol&#xff08;简称为ftp&#xff09; FTP 是因特网网络上历史最悠久的网…

Textual for Mac:轻量级IRC客户端

在寻找一款高效、轻量级的IRC客户端时&#xff0c;Textual for Mac无疑是你的不二之选。它集成了众多现代技术&#xff0c;如本机IPv6、最新的IRCv3规范&#xff0c;以及客户端证书身份验证&#xff0c;让你的聊天体验更加顺畅和安全。 Textual for Mac v7.2.2免激活版下载 Tex…

4、PHP的xml注入漏洞(xxe)

青少年ctf&#xff1a;PHP的XXE 1、打开网页是一个PHP版本页面 2、CTRLf搜索xml&#xff0c;发现2.8.0版本&#xff0c;含有xml漏洞 3、bp抓包 4、使用代码出发bug GET /simplexml_load_string.php HTTP/1.1 补充&#xff1a; <?xml version"1.0" encoding&quo…

【C++】深入解析C++智能指针:从auto_ptr到unique_ptr与shared_ptr

文章目录 前言&#xff1a;1. 智能指针的使用及原理2. C 98 标准库中的 auto_ptr:3. C 11 中的智能指针循环引用&#xff1a;shared_ptr 定制删除器 4. 内存泄漏总结&#xff1a; 前言&#xff1a; 随着C语言的发展&#xff0c;智能指针作为现代C编程中管理动态分配内存的一种…

Win32 API

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏 : C语言 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 一.Win32 API 1.Win32 API介绍 Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外&#xff0c;它同时也是…

python中的线程并行

文章目录 1. 单线程2. 线程池ThreadPoolExecutor 1. 单线程 现在有1154张图片需要顺时针旋转后保存到本地&#xff0c;一般使用循环1154次处理&#xff0c;具体代码如下所示&#xff0c;img_paths中存储1154个图片路径&#xff0c;该代码段耗时约用97ms。 t1time.time() for …