Linux信号的概念信号的产生

news2024/12/27 13:04:28

前言

我们前面已经对进程已做了介绍!知道进程具有独立性,但在运行起来后可能会"放飞自我",即不受控制的执行,这就会导致系统崩溃等问题,非常不利于管理。因此OS需要一种机制来协调和控制进程的运行,而进程信号就是这样的重要机制!

目录

一、信号的基本概念

1、生活中信号的例子

2、什么是信号?

3、31个普通信号

4、自定义信号处理行为

二、信号的产生

1、 kill指令

2、键盘产生信号

3、函数的接口调用

• kill 

• raise

• abort

4、软件条件

• 管道

• alarm

测试IO效率

5、异常

• 除 0 异常

• 野指针异常

三、其他问题

1、如何理解信号的发送?

• 信号的存储

• 信号的发送

2、核心转储

• Core和Trem的区别

• 核心转储的概念

• 核心转储的作用

• 云服务器默认关闭core dump的原因

• 解决历史遗留问题


一、信号的基本概念

1、生活中信号的例子

信号在我们的生活中是随处可见的!比如:

• 红绿灯 --> 红灯停,绿灯行

• 闹钟 --> 起床,该学习了

• 坤叫 --> 天要亮了

• 你考了30分你妈黑着脸 --> 你要被"奖励"了

•  .......

当我们在生活中,看到这些信号时,我们会下意识的想到相关的动作,这是因为我们认识这些并能够处理信号! 其实,OS中的进程也是一样的!当进程收到一个信号后,至少要做到两点:1、认识信号 2、能处理信号 

2、什么是信号?

信号是进程之间事件异步通知的一种方式!

这样直接说太过于官方了!不好理解!下面用直观的方式解释一下:

信号是Linux提供的一种向指定进程发送特定事件的方式!信号的产生是异步的,也就是收到信号的进程不知道具体什么时候就收到了,即信号和接收信号的进程是两个不相关的执行流!

OK,举个栗子:

比如说,你今天买了快递,但是你不知道他什么时候给你通知说到了!快递的发送是独立于你的生活的,当快递到了,快递员会通知你拿!这个栗子中,你就是进程,通知拿快递就是信号,你收到信号拿快递就是处理信号!你和通知你拿快递的消息是独立的,你也不知道啥时候会通知你,即异步的!

3、31个普通信号

Linux中有62个信号,我们可以用 kill -l 来查看:

其中, 1-31 是普通信号(收到信号后,选择合适的时间处理); 34-64 是实时信号(必须马上处理);

其中实时信号我们不介绍,他是用于一些实时的操作系统的,例如车载系统;而我们的一般开发用的操作系统都是分时操作系统,这也是普通信号的主要用武之地!

上面介绍了当进程收到信号后,需要对该信号做处理,处理的方式有三种:

1、忽略此信号

2、执行该信号的默认处理函数

3、执行自定义的处理函数,这种方式也称为信号的捕捉

忽略就是不管,当没看到!自定义后面解释!这里先介绍一下信号的默认处理动作:

可以用 man 7 signal 查看信号的默认动作:

其中TermIgnCoreStopCont都是信号处理的默认行为

  • Term:默认操作是终止进程
  • Ign:默认操作是忽略信号
  • Core:默认操作是终止进程并转储核心
  • Stop:默认操作是暂停进程
  • Cont:默认操作是,如果该进程当前已暂停,则继续该进程

以上5种,其中TermCore 都是终止进程,区别是Core会额外的进行 核心转储,这个后面介绍!我们再往下面翻,就会看到每个进程的描述:

4、自定义信号处理行为

默认行为了介绍了,忽略不管!那自定义行为如何处理呢?自定义信号的行为,是你当前的需求和系统默认的该信号的行为不符,此时你需要自定义该信号的行为!这个就和我们写的那个仿函数控制sort以及优先级队列的行为一样!

可以使用, signal 系统调用函数,来实现!

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

注意 typedef void (*sighandler_t)(int); 这是一个函数指针类型, 参数是一个int 类型的变量,其实就是收到的信号!

signal的第一个参数就是收到的信号,第二个就是该信号的行为函数,我们要想自定义该信号的行为,只需要自主实现handker函数即可!signal 的返回值也是sighandler_t类型,返回的是原先处理该信号函数的函数指针!

OK,我们用一个2号信号来演示:

2号信号SIGINT的默认行为是终止进程,和ctrl + c一样! 我们可以先来验证一下:

#include <iostream>
#include <unistd.h>

int main()  
{
    while (true)
    {
        std::cout << "pid: " << getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

OK,下面我们不想让他按默认的终止进程来,我们想就让他打印一下他收到的信号是多少

#include <iostream>
#include <unistd.h>
#include <signal.h>

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
}


int main()  
{
    signal(SIGINT, handler);//自定义2号信号的行为
    while (true)
    {
        std::cout << "pid: " << getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

我们发现此时无论是给该进程发送2号信号,还是直接ctrl + c 都是不能准直进程的!而且上面你ctrl + c显示的是收到的是2号信号!

这里你可能会想,普通信号就31个,那我全部捕捉了,都让他不终止那信号岂不是没用了!

哈哈是的,这个问题设计OS的人也想到了,人家避免上述的情况发生,于是9号和19号信号设计成了不可自定义捕捉

小tips : 其实上面的所有信号都是宏!所以既可以使用数字,也可以使用对应的大写字符串!

二、信号的产生

OK,简单的介绍了信号的概念和三种处理方式后,我们来看看Linux中信号产生的5种方式!

1、 kill指令

可以在命令行,向指定的进程发送指定的信号!例如,kill -9 

2、键盘产生信号

例如,ctrl + c(2) ctrl + \(3)

int main()  
{
    while (true)
    {
        std::cout << "pid: " << getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

3、函数的接口调用

• kill 

作用:给指定进程发送指定的信号

参数

• pid : 收到信号进程的pid

• sig : 发送哪一个信号

返回值

成功,返回0; 失败,返回-1

OK,举个例子:让父进程休眠3秒,通过kill系统调用给子进程发送3号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdio>

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
    exit(1);
}

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        //child 
        signal(3, handler);
        while (true)
        {
            std::cout << "i am process" <<std::endl;
            sleep(1);
        }
    }

    //father 
    sleep(3);
    kill(id, 3);

    return 0;
}

果然在,3秒后收到了3号信号!这里你可能会想kill 指令和 kill系统调用是啥关系呢其实kill指令的底层一定是调用了kill系统调用!

• raise

作用:给当前进程发指定的信号

  参数

• sig : 要发送的信号

返回值

成功,返回0; 失败,返回非0

我们让我们的进程先跑5秒,然后给自己发送9号信号,让OS杀掉当前进程!

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdio>

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
    exit(1);
}

int main()
{
    signal(SIGKILL, handler);//捕捉一下
    int cnt = 5;
    while (cnt--)
    {
        std::cout << "hello world -> "  << cnt << std::endl;
        sleep(1);
    }

    raise(9);
    return 0;
}

小tips:kill(getpid(), xxx) == raise(xxx);

• abort

作用:给自己发送6号信号(SIGABRT)

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdio>
#include <cstdlib>

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
    exit(1);
}

int main()
{
    signal(SIGKILL, handler);
    int cnt = 5;
    while (cnt--)
    {
        std::cout << "hello world -> "  << cnt << std::endl;
        sleep(1);
    }

    abort();
    return 0;
}

OK,这里6号信号的作用好像也是终止进程!是的,但是他会形成core文件,即核心转储!

4、软件条件

这个看字面可能很抽象!但是,其实我们以前就提到过!例如管道!

• 管道

前面介绍管道的时候,介绍过管道的一个特性:当管道的读端关闭,写端还在写,此时OS会想写写进程发送13号信号!这是我们以前介绍的!这里不再验证了!

• alarm

作用:设定一个闹钟,一定时间后,给当前进程发送14( SIGALRM)号信号

参数

• seconds : 表示设定多少秒的闹钟

返回值

• 如果提前醒了,上一个闹钟还有剩余的时间,返回上一次闹钟的剩余秒数

• 否则,返回0

OK,我们先来演示一下,正常的闹钟:

int main()
{
    alarm(5);
    int cnt = 1;
    while(true)
    {
        std::cout << "second = " << cnt << std::endl;
        cnt++;
        sleep(1);
    }

    return 0;
}

我们可以自定义捕捉看看是不是收到了14号信号:

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
    exit(1);
}

int main()
{
    signal(14, handler);//捕捉一下14号信号
    alarm(5);//设置一个5秒的闹钟
    int cnt = 1;
    while(true)
    {
        std::cout << "second = " << cnt << std::endl;
        cnt++;
        sleep(1);
    }

    return 0;
}

下面我们来一个提前结束的:alarm(0)是取消闹钟

int main()
{
    alarm(5);
    sleep(1);
    int n = alarm(0);// alarm(0) 提前取消闹钟,返回值是上一次闹钟的剩余时间
    std::cout << "n = " << n << std::endl;
    return 0;
}

这里本来设了5秒的闹钟,1秒后取消了;就剩下了4秒!

注意:alarm设的闹钟只有有效一次!如果想要多次有效就需要,每次重置(自举)

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdio>
#include <cstdlib>

void handler(int sig)
{
    alarm(1);
    std::cout << "get sig: " << sig << std::endl;
}

int main()
{
    signal(14, handler);
    alarm(1);
    while (true)
    {
        std::cout << "hello world" << std::endl;
        sleep(1);
    }
    return 0;
}

理解闹钟

我们一个进程可以定一个,也可以定好多的闹钟,同理OS中的其他进程也可以订个十个八个的,而OS中的进程很多,所以需要对闹钟进行做管理,如何做管理?先描述,在组织

闹钟的描述可以用一个结构体来描述,例如这样的:

struct _alarm
{
    time_t expired; // 未来要超时的时间 = seconds + 当前的时间
    pid_t pid;      // 哪一个进程设置的
    func_t fun;     // 默认行为
    //....
};

组织的话,因为闹钟很多所以,可以用小根堆组织,到之后只需要判断当前时间和超时时间即可!

测试IO效率

这个以前从来没有测试过,我们以前只是说,I/O本是和外设打交道,外设的效率很慢,所以I/O的效率慢!理论是这样!但是没有一个量化的认识!我们这里借着闹钟演示一下:

先来看看一秒,有I\O可以执行多少次:

int cnt = 1;

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
    std::cout << "cnt: " << cnt << std::endl;
    exit(1);
}

int main()
{
    signal(14, handler);
    alarm(1);
    while (true)
    {
        std::cout << "cnt: " << cnt << std::endl;
        cnt++;
    }

    return 0;
}

我们发现是4万多次!再来看看没有I\O的:

int cnt = 1;

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
    std::cout << "cnt: " << cnt << std::endl;
    exit(1);
}

int main()
{
    signal(14, handler);
    alarm(1);
    while (true)
    {
        cnt++;
    }

    return 0;
}

接近高了1W多倍!所以I/O是很费时间的!而我今天用的是云服务器,还要跨网络通信代价更大!

5、异常

最后一种产生的信号的方式是:异常,更准确说是硬件异常!例如,我们平时的代码遇到的:除0、野指针

• 除 0 异常

先来一段除0的代码看效果:

int main()
{
    int n = 10;
    std::cout << n / 0 << std::endl;
    return 0;
}

这里报的错误是浮点数异常,一般这种错误就是除0导致的!这里进程终止了,说明他收到了信号!这里浮点数异常是收到了 8 号信号 SIGFPE

我们可以用前面的自定义捕捉,证明一下收到的就是8号信号:

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
}

int main()
{
    signal(8, handler);
    int n = 10;
    std::cout << n / 0 << std::endl;
    return 0;
}

确实验证了8号信号!但是我们看到这里一直死循环的打印!我只发生了一次除0错误啊!OK,我下面来解释一下原因:

我们所有的代码最后编译成二进制后,加载到内存都是由CPU来执行的(CPU执行又分为算术运算和逻辑运算)!所以上面的除0错误也是由CPU抛出的(CPU是硬件,也就是硬件异常)!现在的问题是CPU执行时他怎么知道当前的代码是正常还是异常呢?其实在CPU内部不只有正常运算的寄存器。还有一个状态寄存器eflag,在他内部有溢出标记位当执行时发生异常时将溢出标记位设为1即异常的状态,OS是软硬件资源的管理者,当OS检测到异常时就要解决这种硬件问题保证系统的安全,如何处理?向指定的进程发送对应的信号让其终止!如果是正常的情况发送信号当前进程就结束了,但是我们这里给把对应的信号给自定义捕捉了,也就是打印一句话而不是进程退出。当OS给该进程发送信号后该进程就要被切换下来,前面介绍了进程切换时需要保存硬件的上下文数据!保存在哪里?当前进程的PCB里面!而当前进程把8号给自定义了有没有退出,接下来还会被调度当再次调度时,他就会把上一次异常的数据又给CPU,CPU的溢出标记为又是1,OS又发送信号!这样就进入了死循环,也就是我们上面看到的一直打印!

• 野指针异常

野指针的异常包括越界、栈溢出等!

还是先来个例子看现象:

int main()
{
    int* ptr = nullptr;
    *ptr = 100;
    return 0;
}

这里是段错误!其实他也是收到了信号,收到了11(SGISEGV) 号信号并终止了进程!我们也可以自己捕捉看看:

void handler(int sig)
{
    std::cout << "get sig: " << sig << std::endl;
}

int main()
{
    signal(11,handler);
    int* ptr = nullptr;
    *ptr = 100;
    return 0;
}

这里看到了确实收到了11号信号!这里的一直循环的打印和上面的原理类似:

前面介绍过CPU在执行时全部使用的是虚拟地址,当他执行时根据页表转换成物理地址找到其数据和代码!如果CPU拿着虚拟地址去页表中依次查找的话有些慢。其实在CPU内部右两个专门负责虚拟地址转物理地址的寄存器:MMUCR3,其中CR3存的是当前页表的起始地址,MMU是当前的虚拟地址,所以MMU+CR3 = 当前虚拟地址对应的物理地址。那CPU又如何知道我指针越界访问了呢?CPU内部还有一个和上面的状态寄存器作用相似的一个页故障线性地址寄存器CR2专门存储的是当前虚拟地址是否非法!如果当前的地址转换过程中发现非法,直接将该虚拟地址放到CR2,当OS检测到之后就会发送11号信号!这里一直循环打印的原因和上面的一样!

OK,介绍玩这里我们就可以回答下面三个问题了: 

1、程序为什么会奔溃?

         • 原因是当前程序中有代码进行了非法访问或非法操作,导致OS向该进程发送了信号!

2、程序奔溃后默认是退出,可以不退出吗?

        • 可以!自定义捕捉异常。

3、为什么推荐异常收到信号后,终止进程?

        • 为了释放进程的上下文数据,包括溢出标记为等其他异常数据!

三、其他问题

1、如何理解信号的发送?

我们前面都在说发送信号。但是如何理解这个发送信号呢?OK,要谈清楚发送信号,我们呢就得先介绍一下信号的存储!

• 信号的存储

我们分时OS用的是普通信号,有31个[1,31],没有0。你说巧不巧我们每个进程的PCB内部都会有一个字段(uint32_t),专门描述信号的!也就是一个整型,采用的是位图的思想!

• 信号的发送

当进程收到对应的信号后,OS就会把对应进程的PCB中的信号字段对应的位置1!所以发送信号的本质就是,将指定进程PCB中的描述信号位图中的特定位置修改为1;而PCB属于内核数据结构,只有OS有权利修改,所以,真正发送信号的是操作系统!!

2、核心转储

• Core和Trem的区别

前面就介绍了,Core和Trem最终都是异常终止进程!但是它两你有啥区别呢?

• Trem : 单纯的异常终止!

• Core : 在异常终止时,会额外生成一个Core文件,一般用于时候debug!

  这个Core文件的内容就是,进程在收到信号异常终止时内存中核心的代码和数据!

OK ,先来一个验证一下:这里的11号信号就是Core

我们这里也没有形成Core文件啊!难道是环境的问题?这回还真是!我们当前的环境是云服务器!一般云服务器时把Core关掉的!

可以用ulimit -a 查看

我们可以用 ulimit -c size 打开

我们再来试一下:

这里面是乱码,就不再看了!如果你不想要了,你就把他关闭了!使用: ulimit -c 0

• 核心转储的概念

一些信号终止进程时,需要对该进程的核心数据进行在当前进程的目录下生成一个core.pid的文件,这个过程称作核心转储(Core Dump)

常见的信号有:3号 SIGQUIT4号 SIGILL5号 SIGTRAP6号 SIGABRT7号 SIGBUS8号 SIGFPE11号 SIGSEGV24号 SIGXCPU25号 SIGXFSZ31号 SIGSYS 都是可以产生核心转储文件的。

• 核心转储的作用

核心转储的主要作用是:调试!核心转储可以直接使用core-file corefile到出错的地方!也就是时候调试!

• 云服务器默认关闭core dump的原因

一般在云服务器上是部署项目的,一旦一个程序异常终止,就得立刻重启!当然这个工作是相关的监控脚本做的!如果默认打开了core dump,如果项目和上面的一样有问题,就会挂掉,但是自动脚本就会立即重启,这样就会生成大量的core文件,如果时间稍微一长可能磁盘就爆了!导致服务器宕机了!所以一般的服务器都是将core dump关掉的!

• 解决历史遗留问题

我们在进程控制中说过:子进程退出父进程要等待!其中status是获取子进程退出的信息的!他的低7位是退出信号,次低8位是退出码!但是我们当时留了一个问题就是,第8位,我们当时是说后面介绍!这里就可以介绍了:

其实第8位就是刚刚介绍的,是否核心转储,如果是核心转储,第8位就是1,否则就是0!

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int* ptr = nullptr;
        *ptr = 100;
    }

    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        printf("exit code: %d, exit sig: %d, core dump: %d\n", (status>>8)&0xff, (status&0x7f), (status >> 7)&1);
    }
    return 0;
}

注意:这里只有进程收到Core信号才会让core dump位,置1,Trem是不会让core dump位,置1的!

OK, 好兄弟本期分享就到这里,我是cp我们下期再见!

结束语:要使劲加油啊!

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

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

相关文章

【C++】拓扑排序(BFS)

目录 拓扑排序介绍 有向无环图 如何解决这类问题 课程表 算法思路 代码实现 课程表2 算法思路 代码实现 火星词典 代码实现 拓扑排序介绍 有向无环图 入度&#xff1a;指向活动节点的箭头个数&#xff1b; 出度&#xff1a;从活动节点出去指向别的节点的箭头个数。…

交互式实时距离测量-单目测距-社交距离检测

使用说明 使用鼠标点击两个目标框要删除在距离计算过程中绘制的点&#xff0c;你可以使用鼠标右键点击。这会清除所有已绘制的点 使用 Ultralytics YOLOv8 进行距离计算 距离计算是在指定空间内测量两个物体之间间隙的基本概念。在 Ultralytics YOLOv8 的情况下&#xff0c;通…

React学习-初始化react项目

目标: reactv18&#xff1a;->1.核心的22中api2路由3.数据状态管理&#xff1a;redux项目&#xff1a; 1.b端业务闭环:登录方案、权限设计、用户管理方案、业务功能、系统架构设计、路由设计流程闭环&#xff1a;开发环境、生产环境、测试环境、代码规范、分支管理规范、项…

SpringBoot整合knife4j配置使用直接拷贝即可(快速入门超详细版)

1. SpringBoor整合Knife4j添加maven 1.1 第一种maven <!--添加Knife4j依赖--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.5.0</ver…

Unity新输入系统 之 PlayerInput(真正的最后封装部分)

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​ 首先你应该了解新输入系统的基本单位和输入配置文件 Unity新输入系统 之 InputAction&#xff08;输入配置文件最基本的…

6 款最佳付费和免费 iPhone 解锁应用和软件

iPhone解锁应用程序是一种可以不受任何限制地移除 iOS 设备上不同类型锁的工具。iPhone 可能受锁屏密码、Apple ID 密码、屏幕使用时间密码、iCloud 激活锁、MDM 等保护。如果您忘记了密码&#xff0c;您将无法使用设备或无法完全访问您的 iPhone。幸运的是&#xff0c;有软件可…

跨平台控制神器Escrcpy,您的智能生活助手

Escrcpy 是一款基于 Scrcpy 开发的图形化安卓手机投屏控制软件&#xff0c;它允许用户将 Android 手机屏幕实时镜像到电脑上&#xff0c;并使用电脑的鼠标和键盘直接操作手机&#xff0c;实现了无线且高效的操控。这款软件是免费开源的&#xff0c;支持跨平台使用&#xff0c;包…

2024 年可免费下载的 6 款最佳 iOS 解锁软件

众所周知&#xff0c;如果所有者或其他人多次输入错误密码&#xff0c;iOS 会锁定并禁用 iPhone 或 iPad。Apple 推出了使用 iTunes/Finder、iCloud 或其他 iOS 设备解锁已禁用设备的方法。但是&#xff0c;每种方法都需要一些先决条件&#xff0c;例如 Apple 密码。在这种情况…

Unity使用代码生成ScriptableObject数据并赋值之后,重启数据就没有啦!

2024年8月14日早&#xff0c;因数据持续化存储&#xff0c;重启电脑后数据会丢失&#xff0c;而我找不到原因被领导质疑了&#xff0c;故写一片博客记录这个错误。 省流 使用在编辑器的play模式中为ScriptableObject赋值之后&#xff0c;需要使用 #if UNITY_EDITORUnityEdit…

GLCIC:全局和局部一致的图像补全

GLCIC&#xff1a;全局和局部一致的图像补全 前言相关介绍GLCIC 的工作原理核心思想主要组件训练目标 优点缺点总结 实验环境项目地址LinuxWindows 项目结构具体用法准备数据集进行训练进行测试 参考文献 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改…

四十一、大数据技术之Kafka3.x(4)

&#x1f33b;&#x1f33b; 目录 一、Kafka 消费者1.1 Kafka 消费方式1.2 Kafka 消费者工作流程1.2.1 消费者总体工作流程1.2.2 消费者组原理1.2.3 消费者重要参数 1.3 消费者 API1.3.1 独立消费者案例&#xff08;订阅主题&#xff09;1.3.2 独立消费者案例&#xff08;订阅分…

基于SpringBoot+Vue框架的租车管理系统

文章目录 一、项目介绍二、项目类型三、技术栈介绍1.客户端技术栈2.服务端技术栈 四、项目创新点五、项目功能介绍1.客户端功能2.服务端功能 六、项目的主要截图页面如下展示1.客户端展示2.服务端展示 七、项目源码 一、项目介绍 ​大家好&#xff0c;我是执手天涯&#xff0c;…

找出字符串中第一个匹配项的下标 | LeetCode-28 | KMP算法 | next数组 | Java详细注释

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f579;️KMP算法练习题 LeetCode链接&#xff1a;28. 找出字符串中第一个匹配项的下标 文章目录 1.题目描述&#x1f347;2.题解&#x1f349;2.1 暴力解法&a…

【树的遍历】

题目 代码 #include<bits/stdc.h> using namespace std;const int N 40;int in[N], pos[N]; //中序、后序 int idx[N]; //中序的值->索引 unordered_map<int, int> l, r; //根节点的左、右树根节点 int n; int build(int il, int ir, int pl, int pr) {int ro…

【2】MySQL相关概念

一.数据库相关概念 二.MySQL数据库

软件接口测试有多重要?专业软件测试公司接口测试流程分享

在当今软件开发的各个阶段&#xff0c;软件接口测试无疑是一个极其重要的环节。接口测试主要针对软件系统与外部环境之间的交互部分&#xff0c;包括API、Web服务、中间件等。在现代软件架构中&#xff0c;接口的稳定性和一致性直接关系到系统的整体性能和用户体验。因此&#…

掌握电容器:详解其工作原理、分类、应用及测试技巧

电容器是一种不可或缺的基础元件。它们广泛应用于各种电路设计中&#xff0c;从简单的滤波电路到复杂的电源管理系统&#xff0c;无处不在。在此&#xff0c;道合顺将一一阐述其基本原理、分类、应用以及测试好坏方法&#xff0c;帮助读者们更清楚了解这一重要组件。 1、电容器…

如何利用YOLOv8训练自己的数据集 3种加载模型场景讲解

文章目录 前言1、环境搭建2、YOLOv8训练3、官网训练文档3.1、官网示例3.2、三种加载模型场景3.2.1、从YAML文件构建新模型3.2.2、从预训练权重构建模型3.2.3、从YAML文件构建新模型&#xff0c;并将预训练权重转移到新模型 4、总结5、目标检测系列文章 前言 本文主要介绍一下如…

linux 文件编程

标准IO和文件IO是计算机编程中用于处理输入/输出&#xff08;Input/Output&#xff0c;简称IO&#xff09;操作的两种不同方式&#xff0c;它们各自具有不同的特点和使用场景。 一、定义与特点 标准IO&#xff1a; 定义&#xff1a;标准IO通常指的是C语言提供的标准库中的IO…

第八季完美童模全球总冠军·韩嘉潞 破浪扬帆写就传奇

梦想的舞台上&#xff0c;星光璀璨&#xff0c;每一步都闪耀着坚持与努力的光芒。在这个盛夏&#xff0c;我们共同见证了一个关于勇气、才华与梦想的辉煌篇章——星光女孩韩嘉潞&#xff0c;在第八季完美童模的璀璨舞台上&#xff0c;以非凡的魅力与不懈的努力&#xff0c;勇夺…