Linux之进程信号

news2024/11/25 7:25:48

一、什么是进程信号

进程信号是一种事件通知机制,属于软件中断

信号的作用:发生某事件时,打断进程当前操作,转而去处理这个事件

例子:

假设你正在学习,没有突发事件你不会停止学习。(此时你就是一个进程)此时你的母上大人做好了午饭,跑来叫你吃饭(吃饭信号)。然后你就去吃饭了,不学习了,吃完饭又回来学习(处理完信号回来继续作业)

注意:

信号和信号量不是同一个概念。

信号量是进程间通信(IPC)的方式之一,而信号是一种事件通知机制。

二、常见的信号

kill -l   #该命令可以查看常见的信号
man 7 signal   #查看信号的相关描述

1-31号信号属于非可靠信号 34-64号信号属于可靠信号

我们主要学习1到31号非可靠信号。

输入man 7 signal可以看到

三、信号的产生

1、硬件产生信号

通过按键的终端软件产生信号:

之前已经知道按下组合键Ctrl+C可以结束一个进程。

Ctrl + C 的本质就是给进程发送了 2 信号,进程接收到 2 号信号后的默认处理动作是结束进程。

那如何理解组合键变成信号呢?其实键盘的工作方式是通过中断方式进行的。键盘是槽位的,每个槽位都会对应一个编号。因为有键盘驱动,操作系统是能够识别这些编号的。只要按下了一些键,操作系统立马就能够识别到。那么当你按下组合键,操作系统也是可以识别到的。操作系统既然都识别到了你按下了组合键,那么操作系统给特定的进程发送信号,也就是轻而易举的事情了。

注意:

Ctrl + C 产生的信号只能发给前台进程。一个命令后面加个 & 可以放到后台运行,这样 shell 不必等待进程结束就可以接受新的命令,启动新的进程。 shell 可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl + C 这种组合键产生的信号。 前台进程在运行过程中用户随时可能按下 Ctrl + C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous) 的。

使用 signal 函数后,当进程接收到 signum 信号时,进程会调用 handler 函数(handler 是回调函数,handler 是 函数指针类型,该函数的返回值是 void,参数是 int)并将 signum 传递给 handler 函数,其实 signal 函数相当于可以自定义捕捉某个信号。signal 函数的返回值是对于 signum 信号的旧的处理方法。

代码块:

#include <iostream>
#include <signal.h>
#include <unistd.h>
​
using namespace std;
​
void catchSignal(int signal)
{
    cout << "捕捉到了一个信号: " << signal << endl;
}
​
int main()
{
    // signal(2, catchSignal);  // 这种写法也可以
    // catchSignal是自定义捕捉
    signal(SIGINT, catchSignal);  // 特定信号的处理动作一般只有一个
    while(true)
    {
        cout << "我是一个进程,我的pid是: " << getpid() << endl;
        sleep(2);
    }
​
    return 0;
}

注:signal 函数仅仅是修改进程对特定信号的后续处理动作,并不是直接调用对应的处理动作。而是当进程接收到特定信号时,才会去调用对应的处理动作。如果后续没有产生 SIGINT 信号,catchSignal 函数就不会被调用,signal 函数往往放在最前面,先注册特定信号的处理方法。 现在就无法通过 Ctrl + C(2 号信号)终止该进程了,那么我们可以通过 Ctrl + \ (3 号信号)终止该进程。如果你也将 3 号信号也自定义捕捉了,那么可以发生 8 号信号(浮点数异常)来终止进程。

2、调用系统函数向进程发信号

通过系统调用实现 mykill 命令

2.1 kill函数

系统调用 kill 函数可以想指定的进程发送指定的信号。

#include <sys/types.h>
#include <signal.h>
​
int kill(pid_t pid, int sig);
  • pid参数是要发送信号的目标进程ID。可以使用进程ID(PID)或进程组ID(PGID)。

  • sig参数是要发送的信号编号。可以使用预定义的信号宏(如SIGKILLSIGTERM)或用户自定义的信号编号kill()函数返回0表示成功,-1表示失败。

代码展示:

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <cstring>
#include <stdlib.h>
​
using namespace std;
​
static void Usage(string proc)
{
    cout << "\tUsage: \n\t";
    cout << proc << " 信号编号 目标进程\n"
             <<endl;
}
​
// 通过系统调用向进程发送信号(设计mykill命令)
// ./mykill -2 pid
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
​
    int signal = atoi(argv[1] + 1);
    int id = atoi(argv[2]);
​
    kill(id, signal);
​
    return 0;
}

2.2 raise函数

raise()函数:可以用来向当前进程自身发送信号。

raise(sig)等价于kill(getpid(), sig)

  • sig参数是要发送的信号编号,同样可以使用预定义的信号宏(如SIGKILLSIGTERM)或用户自定义的信号编号。

#include <signal.h>
int raise(int sig);

代码:

using namespace std;
​
int main()
{
    while (true)
    {
        std::cout << "我是一个进程,我正在运行 ..., pid: " << getpid() << std::endl;
        sleep(1);
        raise(8);
    }
}

3、通过软件条件产生信号

3.1 终止写端进程SIGPIPE(13号信号)

学习管道的时候,我们说过:当管道读端关闭,写端一直在写,操作系统会自动终止对应的写端进程。操作系统是通过发送 13 号信号(SIGPIPE)来终止写端进程的!

    1. 创建匿名管道

    1. 让父进程进行读取,子进程进行写入

    1. 父子进程通行一段时间(该步骤可以省略)

    1. 让父进程先关闭读端,子进程只有一直写入就行

    1. 父进程通过 waitpid 等待子进程拿到子进程的退出信息

父进程的读端已经关闭,子进程的写端再进行写入也没有任何的意义,那么操作系统就向子进程发送 13 号信号(SIGPIPE)。像管道的读端关闭写端还在写的这样情况,其实就是不符合软件条件(管道通信的条件,管道也是一种软件),那么操作系统就会向不符合软件条件的进程发送特定的信号终止进程。

3.2 alarm函数与SIGALRM信号

alarm 函数可以设定一个闹钟,也就是告诉操作系统在 seconds 秒后给当前进程发送 14 号信号(SIGALRM),该信号的默认处理动作是终止当前进程。

alarm函数:

  • alarm函数是一个POSIX标准的函数,用于设置一个定时器,当定时器到达指定时间后,会发送一个SIGALRM信号。

  • 函数原型:unsigned int alarm(unsigned int seconds)

  • 参数seconds表示定时器的时间间隔,单位为秒。

  • 返回值:若之前已设置了定时器,则返回之前的剩余时间;若之前没有设置定时器,则返回0。

这个函数的返回值是 0 或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为 30 分钟之后响,20 分钟后被人吵醒了,还想多睡一会儿。于是重新设定闹钟为 15 分钟之后响,以前设定的闹钟时间还余下的时间就是 10 分钟。如果 seconds 值为 0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

代码:

#include <iostream>
#include <unistd.h>
​
using namespace std;
​
int main()
{
    alarm(1);
    int count = 0;
    // 验证1s内,count++会进行多少次
    // cout + 网络 = IO
    while(true)
    {
        cout << "count: " << count++ << endl;
    }
​
    return 0;
}

通过上图可以看到,count 一定被加加了 7w+ 次,这次数是比较少的,其实是由 cout 和网络传输数据慢导致的。如果想单纯看看计算的算力,可以通过下面的程序。

4、硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

4.1 SIGFPE(8号信号)

代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>
​
using namespace std;
​
void handler(int signum)
{
    sleep(1);
    cout << "收到了一个信号: " << signum << endl;
}
​
int main()
{
    signal(SIGFPE, handler);
    int a = 100;
    a /= 0;
    while(1)  sleep(1);
    
    return 0;
}
​

将程序运行起来,就会发现程序在死循环打印语句。那为什么会这样呢?如何理解除零呢?进行计算的是 CPU 这个硬件,CPU 内部是有寄存器的,其中有一个寄存器是状态寄存器。该寄存器不进行数值保存,它只用来保存 CPU 本次计算的状态,其结构也是位图,有着对应的状态标记位(溢出标记位)。当状态寄存器的溢出标记位为 0,操作系统就将计算结果写回到内存中;而当溢出标记位为 1时,操作系统就会意识到有除零错误(溢出问题),操作系统会找到当前哪个进程在运行,向该进程发送 SIGFPE 信号,进程会在合适的时候处理该信号。

4.2 SIGSEGV(11号信号)
#include <iostream>
#include <unistd.h>
#include <signal.h>
​
using namespace std;
​
void handler(int signum)
{
    sleep(1);
    cout << "收到了一个信号: " << signum << endl;
}
​
int main()
{
    // SIGSEGV 段错误(11号信号)
    signal(SIGSEGV, handler);
    sleep(2);
     cout << "野指针问题 ... here" << endl;
    int *p = nullptr;
    *p = 100; // 2, 野指针问题
    while(1)  sleep(1);
    
    return 0;
}

当野指针或越界访问时,使用的地址都是非法地址,那么 MMU 进行转化的时候,就一定会报错。只有 MMU 报错,操作系统就能识别当前进程出现了硬件异常,将该硬件异常转化成对应的信号发送给进程。

四、核心转储

是否有一个疑问,31个信号的默认处理方式都是结束进程,并且还可以自定义处理方式,那么为什么要这么多信号呢?一个信号不就行了吗?

  • 重要的不是产生信号的结果而是产生信号的原因

  • 所有出现异常的进程,必然是收到了某一个信号。

首先解释什么是核心转储(Core Dump)。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是 core,这种行为就叫做核心转储(Core Dump)。

以信号2和3为例,他两的默认处理方式一个是Term,一个是Core。

  • Term和Core的结果都是结束进程。

那么这两个方式的区别在哪里呢?

  • Term方式仅仅是结束进程,结束了以后就什么都不干了。

  • 但是Core不仅结束进程,而且还会保存一些信息。

在数据越界非常严重的时候,该进程会接收到SIGSEGV信号,来结束进程。

11号信号的默认处理方式是Core。

在云服务器上,默认情况下是看不到Core退出的现象的,这是因为云服务器关闭了core file选项:

core file size(红色框)的大小是0,意味着这个选项是关闭的。

  • 从这里还可以看到别的关于这个云服务器的信息,比如能够打开的最多文件个数,管道个数,以及栈的大小等等信息。

为了能够看到Core方式的明显现象,我们需要将core file选项打开:

此时该选项就打开了,表示的意思就是核心转储文件的大小是1024个数据块。

  • 再运行数据越界的程序时,同样会收到SIGSEGV信号停止。

  • 但是在当前目录下会多出一个文件,如上图中的绿色框。

core.1739:被叫做核心转储文件,其中后缀1739是接收到该信号进程的pid值。

对于一个奔溃的程序,我们最关心的是它为什么崩溃,在哪里崩溃?

  • 当进程出现异常的时候,将进程在对应的时刻,在内存中的有效数据转储到磁盘中-------核心转储

  • 核心转储的文件我们可以拿着它进行调试,快速定位到出现异常而崩溃的位置。

  • 使用gdb调试我们的可执行程序。

  • 调试开始后,输入core-file core.pid值,表明调试核心转储文件。

  • 此时gdb就会直接定位到产生异常的位置。

这就是核心转储的重要意义,它相比Term方式,能够让我们快速定位出现异常的位置。

五、阻塞信号与信号处理

1、信号其他的常见的相关概念

实际执行信号的处理动作称为信号递达(Delivery),信号处理动作有默认、忽略、自定义捕捉。 信号从产生到递达之间的状态,称为信号未决(Pending),也就是进程收到了一个信号但该信号还未被处理,信号被保存在位图(Pending 位图)中。 进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。 注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

2、信号在内核中的表示

为了表示信号递达、未决和阻塞三个概念,那么操作系统就要用一定的结构去表示它们。操作系统就使用了三张表来表示这三个概念,如下图所示:

block是位图结构,1表示该信号被阻塞,0表示该信号未被阻塞,如果某个信号对应的比特位为1,就block了,则该信号无法被递达,只有解除block,才能递达该信号。其中 pending 表就是保存信号的位图结构(unsigned int),1 表示收到了信号,0 表示没有收到信号;handler 表是函数指针数组,数组的下标就是信号编号,数组中存的是信号的处理动作;

信号处理的过程:操作系统给目标进程就是修改 pending 位图,这样信号就完成发送了。进程在合适的时候处理信号,遍历 pending 位图看哪些比特位为 1。当发现比特位为 1 时,就去看对应的 block 位图上的比特位是否为 1。如果是 1,则说明该信号被阻塞着,进程不会去处理该信号,也不会将 pending 位图的比特位从 1 改成 0;而如果是 0,则说明该信号没有被阻塞,进程可以处理该信号,处理完成后还需要将 pending 位图上的比特位从 1 改成 0,表示该信号已经处理完成。

3、sigset_t类型

编程语言都会给我们提高 .h 或者 .hpp 和语言本身的定义类型;操作系统也会给我们提供 .h 和操作系统自定义的类型,像 pid_t 和 key_t 等。如果要访问硬件,那么语言类的头文件也会包含对应的系统调用接口,将系统调用封装起来给我们使用。

sigset_t 也是操作系统自定义的类型,该类型是位图结构,用以表示上图的 pending 表和 block 表。用户不能直接通过位操作来修改位图(unsigned int),需要使用操作系统提供的方法来修改位图。

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

4、信号集操作函数

sigset_t 类型对于每种信号用一个比特位表示有效或无效状态,至于这个类型内部如何存储这些比特位则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作 sigset_ t 变量,而不应该对它的内部数据做任何解释,比如用 printf 直接打印 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);
  • 函数 sigemptyset 初始化 set 所指向的信号集,使其中所有信号的对应比特位清零,表示该信号集不包含任何有效信号。

  • 函数 sigfillset 初始化 set 所指向的信号集,使其中所有信号的对应比特位置 1,表示该信号集的有效信号包括系统支持的所有信号。

  • sigaddset 函数将 signo 信号对应的比特位置为 1sigdelset 函数将 signo 信号对应的比特位置为 0。

  • sigismember 函数可以判断 signo 信号是否在信号集中,如果 signo 信号在信号集中,返回 1;如果不在,返回 0;出错则返回 -1。

  • 注意:在使用 sigset_ t 类型的变量之前,一定要调用sigemptyset 或 sigfillset 函数做初始化,使信号集处于确定的状态。初始化 sigset_t 变量之后就可以在调用 sigaddset 和 sigdelset 在该信号集中添加或删除某种有效信号。

5、sigpending与sigprocmask

5.1 sigpending

sigpending 函数通过输出型参数 set 获取当前进程的未决信号集,调用成功返回 0,出错则返回 -1。

5.2 sigprocmask

sigprocmask 函数可以帮助我们读取或更改进程的信号屏蔽字(阻塞信号集),调用成功返回 0,出错则返回 -1。

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

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cassert>
​
using namespace std;
​
static void showPending(sigset_t& pending)
{
    for(int signal = 31; signal >= 1; --signal)
    {
        if(sigismember(&pending, signal))
            cout << '1';
        else
            cout << '0';
    }
    cout << endl << "----------------" << endl;
}
​
int main()
{
    // 定义并初始化信号集
    sigset_t set, oldset;
    sigemptyset(&set);
    sigemptyset(&oldset);
​
    // 将2号信号添加到信号屏蔽集中
    sigaddset(&set, 2);
    // 将信号屏蔽集设置到当前进程的PCB中
    // 默认情况下,进程不会对任何信号进行block
    int n = sigprocmask(SIG_BLOCK, &set, &oldset);
    assert(n == 0); // assert本质是一个宏
    (void)n;
    cout << "block 2 号信号成功......" << endl;
​
    // 重复打印当前进程的pending信号集
    sigset_t pending;
    sigemptyset(&pending);
    while(true)
    {
        // 获取当前进程的pending信号集
        sigpending(&pending);
        // 打印pending信号集
        showPending(pending);
        sleep(2);
    }
​
    return 0;
}
​

6、信号处理

处理信号,就是进程收到信号,当进程对该信号不阻塞时,会在handle函数指针数组中找到对应的递达方法,来处理当前信号。

注意:当进程收到某信号,并不是立马进行处理的,而是等到合适的时机才进行处理。

处理信号有三种方法:

1.使用默认方法

2.忽略此信号

3.自定义捕捉

由于是由默认方法和忽略信号,就是在handle数组对应信号数组中填入SIG_DEL和SIG_IGN。很好理解,下面来说明一下自定义捕捉信号。

六、捕捉信号

1、内核如何实现信号的捕捉

在上面提及到,信号产生之后,进程可能无法立即处理,进程需要在合适的时候去处理信号。那这个合适的时候是什么呢?带着这个问题,我们来探究一下信号处理的整个流程!

信号相关的数据字段是在进程的 PCB 内部,PCB 内部属于内核范畴,普通用户无法对信号进行检测和处理。那么要对信号进行处理,就需要在内核状态。当执行系统调用或被系统调度时,进程所处的状态就是内核态;不执行操作系统的代码时,进程所处的状态就是用户态。现在我们已经知道需要在内核态下进行信号处理,那究竟具体是什么时候呢?结论:在内核态中,从内核态返回用户态的时候,进行信号的检测和处理!如何进入内核态呢?进行系统调用或产生异常等。汇编指令int 80(80 是中断编号)可以进程进入内核态,也就是将代码的执行权限从普通用户转交给操作系统,让操作系统去执行!注:汇编指令int 80内置在系统调用函数中。

为什么要从用户态到内核态?操作系统是软硬件资源的管理者。

用户需要要访问某些软硬件资源,只要访问硬件,就必须通过操作系统来进行访问,那么就必须从用户态转变到内核态! 。

什么行为会引起从用户态到内核态的转变?执行系统调用、进程调度、处理异常等!

为什么要从内核态到用户态呢?用户的代码还没有执行完、用户的进程还没有调度完等!

Linux信号知识看这文章:(389条消息) 【Linux】进程信号_阿亮joy.的博客-CSDN博客

2、信号的捕获函数sigaction

  • sa_mask,之前有说到过当在处理一个信号的自定义函数时,这个信号会被系统阻塞,直到处理完。如果还想阻塞其它的信号,可以设置sa_mask。

  • sigaction多用于实时信号。

#include <iostream>
#include <signal.h>
#include <unistd.h>
​
​
using namespace std;
​
​
void Count(int cnt)//延时函数
{
    while(cnt)
    {
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
}
​
​
void sigcb(int signo){
    cout<<"信号已递达,编号是:"<<signo<<endl;
    Count(10);//延时10秒
}
​
​
int main(){
    struct sigaction act,oldact;
    act.sa_handler =sigcb;//自定义处理方式
    act.sa_flags=0;
    sigaction(SIGINT,&act,&oldact);
    while(1) sleep(1);
}

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

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

相关文章

ubuntu安装MobaXterm和WPS

文章目录 ubuntu安装MobaXtermi386 架构wine操作步骤 ubuntu安装WPS操作步骤WPS版本知识补充 ubuntu安装MobaXterm i386 架构 sudo dpkg --add-architecture i386 是一个Linux系统中的命令&#xff0c;用于添加一个新的架构&#xff08;architecture&#xff09;支持到当前系统…

Ant Design4中Form.List和shouldUpdate一起使用的坑

背景 在antd3.x版本中&#xff0c;如果要实现一组表单增加删除的功能&#xff0c;需要Array.map()加上state状态来控制&#xff0c;代码比较复杂&#xff0c;而且非常不优雅。 其次在antd3.x中&#xff0c;表单中任何一个表单项的内容更新都会触发页面重新渲染&#xff0c;这在…

自定义MVC引用XML配置文件实现

目录 前言 自定义MVC实现 1. 导入XML配置文件 2. 导入XML解析建模 3. 优化中央控制器 3.1 修改DisPathServlet中init初始化方法 3.2 修改ActionServlet逻辑处理流程 3.3 通过反射机制实例化子控制器类 3.4 中央控制器将请求委托给子控制器处理 3.5 根据请求结果码跳…

【运维工程师学习】低级格式化磁盘

【运维工程师学习】低级格式化磁盘 1、查看需要格式化的磁盘2、下载低级格式化工具3、打开低级格式化工具&#xff0c;选择目标磁盘&#xff0c;并操作 1、查看需要格式化的磁盘 找不到目标磁盘的话需要到磁盘管理去找&#xff0c;这边我要低级格式话的磁盘是磁盘0 2、下载低级…

BPMNJS 在HTML中的引入与使用

BPMNJS 在HTML中的引入与使用 在网上看到的大多是基于vue使用BPMN的示例或者教程&#xff0c;竟然没有在HTML使用的示例&#xff0c;有也是很简单的介绍核心库的引入和使用&#xff0c;并没有涉及到扩展库。于是简单看了下&#xff0c;真的是一波三折&#xff0c;坎坎坷坷。不过…

vue使用mapbox地图

1、下载依赖 npm install --save mapbox-gl mapbox/mapbox-gl-language 2、引入mapBox&#xff0c;将引入的内容封装为js文件 在api/map文件夹下新建mapbox.js文件 代码&#xff1a; import mapboxgl from mapbox-gl import mapbox-gl/dist/mapbox-gl.css import MapboxLang…

vue表单验证的时候提示 async-validator:‘‘xxx is not a string“

对vue不是很熟悉&#xff0c;在做vue开发的时候&#xff0c;遇到一个很奇怪的问题&#xff0c;输入框涉及到number类型的时候会提示 is not a string 这块的代码是这样的&#xff1a; v-decorator"[ fraction, { rules: [{ required: true, type: number, message: 请输入…

SQL Server中如何将累积数值拆解

概要 本文通过一个计算汽车每日里程数的例子&#xff0c;展现如何通过汽车每日的总里程数&#xff0c;来计算汽车每日的里程数。 代码及实现 每辆汽车中都有一个里程数表&#xff0c;记录汽车从出场到当前行驶的里程数&#xff0c;下表是一样汽车的里程数表&#xff0c;该表…

Elastic Stack之Logstash Beats

文章目录 Logstash简介&运行流程使用&#xff08;日志采集&#xff09;常用操作查看线程 BeatFilebeat Logstash 简介&运行流程 教程 一文快速上手Logstash 使用&#xff08;日志采集&#xff09; 配置logstash使用elasticsearch作为logstash后端 在logstash\bin目录…

Spring容器获取Bean的9种方式 | 京东云技术团队

1 前言 随着SpringBoot的普及&#xff0c;Spring的使用也越来越广&#xff0c;在某些场景下&#xff0c;我们无法通过注解或配置的形式直接获取到某个Bean。比如&#xff0c;在某一些工具类、设计模式实现中需要使用到Spring容器管理的Bean&#xff0c;此时就需要直接获取到对…

postgres数据库基础操作-ok

文章目录 1. 链接数据库2. 库操作2.1 创建库2.2 查看数据库2.3 切换数据库2.4 修改库名2.5 删除数据库 3. 表操作3.1 创建表3.2 查看table list3.3 删除表 4. 数据操作4.1 插入数据4.2 查询数据4.3 删除数据 5. 用户&权限5.1 创建用户5.2 查看用户5.3 删除用户5.4 修改用户…

spring cloud +java企业工程管理系统源码之提高工程项目管理软件的效率

高效的工程项目管理软件不仅能够提高效率还应可以帮你节省成本提升利润 在工程行业中&#xff0c;管理不畅以及不良的项目执行&#xff0c;往往会导致项目延期、成本上升、回款拖后&#xff0c;最终导致项目整体盈利下降。企企管理云业财一体化的项目管理系统&#xff0c;确保…

5.设计模式之思维导图整理

1.七大原则 2.分类 3.23大设计模式 //展开 ![ 在这里插入图片描述](https://img-blog.csdnimg.cn/070e9ba070a54a22ab4a05653ae1cf27.png)

钡铼技术多功能RTUS475:稳定可靠的油田数据采集解决方案

标题&#xff1a;S475在油田数据采集中的应用 摘要&#xff1a;本文介绍了钡铼技术多功能RTUS475在油田数据采集中的应用。该设备基于高性能微处理器MCU和嵌入式实时操作系统&#xff0c;支持Modbus Slave和Modbus Master功能&#xff0c;并能通过无线网络实现短信报警和数据传…

华为OD机试真题 Python 实现【木板】【2023 B卷 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、Python算法源码五、效果展示1、输入2、输出3、说明 一、题目描述 小明有n块木板&#xff0c;第块木板的长度为a_i。 小明买了一块长度为m的木料&#xff0c;这块木料可以切割成任意块&#xff0c;拼接到已有的木板上&#xff0…

虚机Centos忘记密码如何重置 -- 小黑日常超细教学

有时候虚机太多&#xff0c;会忘记有一些虚机的密码&#xff0c;当启动机器的时候那我们可以尝试重置虚机密码然后登录。 日常的小技能记述&#xff01;&#xff01; 目录 一、 演示虚机为centos7系列 二、进入开机前的页面&#xff0c;选中第一个&#xff0c;按“e”键&…

idea中如何过滤某些文件不提交

文章目录 前言设置.gitignore文件解决方案 设置新的忽略文件具体步骤如下 常用过滤文件 前言 在开发过程中&#xff0c;经常会遇到一些文件是我们不想提交的内容。那么应该如何过滤掉&#xff1f;不去提交到我们的git仓库&#xff1f; 比如&#xff0c;我们常用的一些配置文件…

MBD开发 STM32 UASRT

目录 轮询 ptintf 中断方式 DMA方式 轮询 串口要加入这两个文件 bug在于接到10个后会一直发送 ptintf function buffPtr convert(buff)if coder.target(Sfun)%固定句式%Executing in MATLAB, Buff is nullbuffPtr uint32(0); elsecoder.cinclude(getBuffPtr.h);%加入头…

Apikit 自学日记:智能 Mock 规则

功能入口&#xff1a;API管理应用 / 公共资源菜单 / 智能 Mock 设置 二级菜单在编写API文档返回结果时&#xff0c;若参数字段和类型匹配智能Mock规则&#xff0c;系统则会自动填入对应的Mock值。该功能提供无感的快速mock值配置&#xff0c;减轻mock规则配置的工作负担。 智能…

Dubbo学习记录

Dubbo学习记录 一、Dubbo架构二、Provider启动Dubbo1.实现类的Service注解2.Dubbo的配置信息3.引入web.xml&#xff0c;加载Spring核心配置文件&#xff0c;才可以扫描到Dubbo的配置信息 二、Consumer启动Dubbo1.Autowired改为Reference2.qos介绍&#xff1a;Dubbo远程监控和控…