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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

访问列表元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中&#xff0c;如果想将列表的内容输出也比较简单&#xff0c;可以直接使用print()函数即可。例如&#xff0c;创建一个名称为untitle的列表…

JAVA开发 基础Jaccard来计算两个字符串之间的重复率

计算两个字符串之间的重复率 Jaccard实现代码基于最长公共子序列来计算两个字符串之间的重复率 Jaccard Jaccard方法&#xff0c;也称为Jaccard相似度或Jaccard相似系数&#xff0c;是一种用于衡量两个集合相似程度的指标。其逻辑基于集合之间的交集与并集的关系来衡量它们的相…

Linux bc命令(bc指令)(基本计算器)(任意精度计算语言:支持浮点数运算、变量赋值和自定义函数等)

文章目录 bc命令文档英文中文 Linux bc 命令详解bc 命令的基本用法启动 bc 环境进行基本计算退出 bc bc 中的数学功能执行高级数学计算平方根和指数函数对数函数 处理精度问题 变量和数组变量赋值和使用数组的使用 创建和使用自定义函数 bc 命令的高级用法在脚本中使用 bc基本脚…

注解大全更新中~

Slf4j 是什么&#xff1a;Slf4j 是 Lombok 提供的一种注解&#xff0c;用于在类中自动生成一个名为 log 的日志对象。通过使用 Slf4j 注解&#xff0c;可以方便地在代码中使用日志功能&#xff0c;而无需手动创建和初始化日志对象。 怎么用&#xff1a; 1.导入依赖的包lombok…

uniapp移动端骨架屏流程

1.使用微信开发者工具来生成骨架屏&#xff1b;在分窗模式下选择页面信息&#xff0c;下拉选择生成骨架屏&#xff1b;他会基于当前页面生成骨架屏的样式 点击确定&#xff1b; 会自动生成这两个文件&#xff1b;一个是html结构文件&#xff0c;一个是css样式文件。 然后把这两…

R语言使用 ggscidca包优雅的绘制支持向量机决策曲线

DCA(Decision Curve Analysis)临床决策曲线是一种用于评价诊断模型诊断准确性的方法&#xff0c;在2006年由AndrewVickers博士创建&#xff0c;我们通常判断一个疾病喜欢使用ROC曲线的AUC值来判定模型的准确性&#xff0c;但ROC曲线通常是通过特异度和敏感度来评价&#xff0c;…

一文读懂RDMA: Remote Direct Memory Access(远程直接内存访问)

目录 ​编辑 引言 一、RDMA的基本原理 二、RDMA的主要特点 三、RDMA的编程接口 四、RDMA的代码演示 服务器端代码&#xff1a; 客户端代码&#xff1a; 五、总结 引言 RDMA&#xff0c;全称Remote Direct Memory Access&#xff0c;即远程直接内存访问&#xff0c;是…

Kafka-ACK机制(ack应答原理、冥等性、事务)

Kafka-ACK机制 Kafka中的ACK&#xff08;Acknowledgement&#xff09;机制是用于保证消息可靠传递的关键组件之一。在生产者发送消息到Kafka集群时&#xff0c;ACK机制决定了何时认为消息已经成功发送。这个机制非常重要&#xff0c;因为它影响了生产者对消息发送的信心以及消费…

fork 与 vfork 的区别

关键区别一&#xff1a; vfork 直接使用父进程存储空间&#xff0c;不拷贝。 关键区别二&#xff1a; vfork保证子进程先运行,当子进程调用exit退出后&#xff0c;父进程才执行。 我们可以定义一个cnt&#xff0c;在子进程中让它变成3&#xff0c; 如果使用fork&#xff0c;那…

uniapp中使用 iconfont字体

下载 iconfont 字体文件 打开 iconfont.css 文件&#xff0c;修改一下 把文件 复制到 static/iconfont/… 目录下 在App.vue中引入iconfont 5. 使用iconfont 使用 iconfont 有两种方式&#xff0c; 一种是 class 方式&#xff0c; 一种是使用 unicode 的方式 5.1 使用 class 的…

firewalld

一、Firewalld概述 Firewalld 支持网络区域所定义的网络链接以及接口安全等级的动态防火墙管理工具 支持IPV4、IPV6防火墙设置以及以太网桥 支持服务或应用程序直接添加防火墙规则接口 拥有两种配置模式 运行时配置 永久配置 二、Firewalld和iptables的关系 netfilter 位于L…

4. Java多线程面试题汇总

Java全栈面试题汇总目录-CSDN博客 1. 为什么要使用并发编程 充分利用多核CPU的计算能力&#xff1a;通过并发编程的形式可以将多核CPU的计算能力发挥到极致&#xff0c;性能得到提升方便进行业务拆分&#xff0c;提升系统并发能力和性能&#xff1a;在特殊的业务场景下&#…

微服务架构-数据共享设计模式

微服务架构-数据共享设计模式 每个微服务拥有自己的数据库&#xff0c;可以独立地进行数据库架构设计、部署和维护。这种是属于常规的方式&#xff0c;不受其他微服务的影响&#xff0c;具有高度的自治性。 然而&#xff0c;在将单体应用拆分成微服务时&#xff0c;可能会遇到…

Stable Diffusion简单食用方法

1.下载 1.1打开B站 哔哩哔哩 (゜-゜)つロ 干杯~-bilibili哔哩哔哩&#xff08;bilibili.com)是国内知名的视频弹幕网站&#xff0c;这里有及时的动漫新番&#xff0c;活跃的ACG氛围&#xff0c;有创意的Up主。大家可以在这里找到许多欢乐。https://www.bilibili.com/ 1.2搜索…

纯血鸿蒙APP实战开发——边缓存边播放案例

介绍 OhosVideoCache是一个支持边播放边缓存的库&#xff0c;只需要将音视频的url传递给OhosVideoCache处理之后再设置给播放器&#xff0c; OhosVideoCache就可以一边下载音视频数据并保存在本地&#xff0c;一边读取本地缓存返回给播放器&#xff0c;使用者无需进行其他操作…

低耦合双写一致性方案-使用canal+MQ

需求&#xff1a;继上一篇使用xxljob实现数据的全量同步到es后&#xff0c;当数据库中新增、删除、修改数据时&#xff0c;应该对es中的对应索引库实现增量同步。 本文介绍了2种双写一致性方案&#xff0c;对其中使用MQ的方案进行了实现。 1. 方案设计 1.1 数据一致性问题分析…

vue 点击复制文本到剪贴板

一、首先在vue文件的template中定义复制按钮 <div size"small" v-if"item.prop jadeCode" class"cell-container"><span>{{ scope.row.jadeCode }}</span> <button click"handleCopy(scope.row.jadeCode)" clas…

js——数据操作——实现阶梯价格排序——基础积累

最近在写网络报价的时候&#xff0c;遇到一个需求&#xff0c;就是要根据采购数量&#xff0c;找到符合数量的阶梯区间&#xff0c;并找到最便宜的采购价格。 比如下面&#xff1a; let originViewList [{id:1,incrementalQuantity:10,priceList:[{minQuantity:1,price:20},…

el-upload上传图片,视频可获取视频时长。

对element-ui组件的upload组件再一次封装&#xff0c;简单记录。下面是效果图。 注意点&#xff1a;该组件现在仅支持单图和单个视频上传。 <template><div :style"myStyle"><divclass"uploads":style"{width: upWith px,height: up…

零门槛微调大模型:基于 Ludwig 低代码框架使用 LoRA 技术微调实践

一、Ludwig 介绍 自然语言处理 (NLP) 和人工智能 (AI) 的飞速发展催生了许多强大的模型&#xff0c;它们能够理解和生成如同人类般的文本&#xff0c;为聊天机器人、文档摘要等应用领域带来了革命性的改变。然而&#xff0c;释放这些模型的全部潜力需要针对特定用例进行微调。…