『 Linux 』高级IO (一)

news2024/12/24 8:52:08

文章目录

    • 内容回顾及铺垫
    • 五种IO模型
    • 不同类型IO的区别
    • 非阻塞IO
      • fcntl( )
    • 多路转接 - select( )
      • select( ) 的基本使用 - SelectServer服务器


内容回顾及铺垫

在博客『 Linux 』基础IO/文件IO (万字)中介绍了对IO的认识;

IO实际上为Input/Output,输入输出;

以网络协议栈的视角来看,操作系统分为四层,分别为应用层,传输层,网络层及数据链路层;

假设在网络通信过程中调用read()/write()系统调用接口,对于应用层而言并不是真正的在进行IO操作;

当应用层在网络通信过程中通过调用read()/write()系统调用接口来进行网络通信,本质上是在进行一个拷贝操作;

  • read()

    将数据由内核缓冲区拷贝至应用层缓冲区;

  • write()

    将应用层缓冲区内数据拷贝至内核缓冲区;

因为这些函数本质只是拷贝函数,而真正对于数据是否进行发送的决策交由各层协议来决定;

而这些拷贝函数的拷贝行为是在缓冲区内资源(数据)已经就绪的前提下,若是资源未就绪则可能进行等待;

如在调用一些输入函数时,如从键盘中进行输入的函数scanf(),在调用该函数时执行流将会进行阻塞,原因为需要等待资源就绪,当用户使用键盘输入完毕后即表示资源已经就绪,届时执行流将继续执行;

再举例在进行网络通信过程中(以TCP协议为例),双方可以通过调用read()/write()send()/recv()函数进行网络通信,而TCP协议中, 双方都具有相同的缓冲区,即一个输入缓冲区和一个输出缓冲区;

而调用recv()或是read()进行对内核缓冲区内数据进行读取时若是内核缓冲区资源未就绪,则该执行流可能会阻塞一段时间;

因此本质上IO操作即为 “拷贝 + 等” ;

而要进行拷贝,必须先判断条件是否成立,此处的条件通常代指读写事件;

  • 读事件

    读事件表示内核缓冲区中的数据已经就绪,可以拷贝至上层缓冲区;

  • 写事件

    写事件表示上层缓冲区中的数据已经就绪,可以拷贝至内核缓冲区;

有一个关于IO的概念为,单位时间内单次IO吞吐量越高效率越高,实际上这也被称为高效的IO;

也可以理解为单位时间内的IO过程中,等的比重越小,IO的效率也就越高;

几乎所有提高IO效率的策略本质上就是降低IO过程中 “等” 的时间比重;


五种IO模型

IO模型主要分为五种,分别为阻塞IO,非阻塞IO,多路复用,信号驱动式IO以及异步IO;

该章节介绍IO模型以调用recvfrom()函数,即触发 “读事件” 为例;

  • 阻塞式IO

    阻塞IO是最常见的IO模型,通常情况下阻塞IO将在内核数据准备好前对应的系统调用将会一直处于等待状态;

    同时所有的套接字默认都为阻塞方式;

    当上层调用recvfrom()函数进行读取操作时,执行流将会阻塞,内核将会等待直至读事件就绪;

    当读事件就绪后执行流将继续,数据将由内核缓冲区拷贝至应用层缓冲区,再由上层进行对数据报的处理;

  • 非阻塞式IO

    非阻塞式IO同样会判断事件是否已就绪,与阻塞式IO模型不同,非阻塞式IO模型并不会阻塞执行流并等待事件就绪,该模型将会判断一次事件是否已经就绪,若是事件就绪则进行后续操作,若是事件未就绪则直接返回;

    当使用recvfrom()函数进行非阻塞IO操作时,若是事件未就绪将直接返回EWOULDBLOCK;

    因此通常在使用非阻塞式IO模型时都会采用轮询的方式,即反复调用对应的非阻塞式IO操作;

    通过轮询的方式,通常情况下,由于recvfrom()函数的非阻塞轮询操作是由上层调用的,这意味着上层可以通过轮询的策略使得在事件未就绪前能够进行其他操作;

  • 信号驱动IO

    信号驱动IO是一种利用SIGIO信号进行驱动的一种IO模型;

    通过注册信号处理函数告诉操作系统当事件就绪时发送对应的信号以进行通知,当注册好后对应的执行流可以进行其他任务的处理;

    当事件就绪后操作系统将会发送SIGIO信号给进程,对应的信号处理函数中将会调用recvfrom()等相关操作将数据由内核缓冲区拷贝至用户缓冲区;

    最终将数据交由上层进行数据报处理;

    信号驱动IO犹如收外卖,当外卖员将外卖送到了你的门前将会敲门,按门铃或是以打电话的方式,这些方式都是一种信号的递交方式;

    当你接收到信号后将要放下手中的活去开门拿外卖,或是付钱等操作;

    当然也可以利用多线程来完成这些操作,如让家里的其他家人代你去签收外卖一样;

  • IO多路转接(多路复用)

    多路转接(多路复用)类似于阻塞IO模型,也可以说该模型是阻塞模型的一种优化版本;

    传统的阻塞IO模型只能对一个IO的位置进行观察与等待;

    而通常情况下IO是通过文件描述符进行的,这意味着传统的阻塞式IO模型只能观察一个文件描述符,而多路复用则可以观察多个文件描述符以保证效率上的提升;

  • 异步IO模型

    异步IO模型与信号驱动型IO模型类似但不同,在使用信号驱动型IO模型进行IO操作时,当应用程序接收到对应的信号后,将要阻塞当前的执行流从而去处理IO操作中的拷贝操作或其他操作;

    而异步IO模型只需调用aio_read()函数即可通知内核当读事件就绪时直接让内核进行完整的IO操作而不必预先通知上层,当IO操作处理完毕后再使用指定的信号通知上层;


不同类型IO的区别

IO的本质是 " 等 + 拷贝 ";

  • 阻塞IO与非阻塞IO

    实际上无论是阻塞IO还是非阻塞IO其两者单凭IO效率而言两者的效率是相当的;

    在上文中所提到的五种模型中,单凭IO而言都是等待事件就绪再进行拷贝操作;

    而常常提到的非阻塞IO效率高的原因是在使用非阻塞IO模型时(可能是轮询也可能是信号驱动),在进行等待的过程中进程不会阻塞在原地一直等待至事件就绪,在非阻塞等待事件就绪的这个时间段进程可以去完成一些其他的任务,在总体的效率上提升效率;

  • 同步IO与异步IO

    上文中介绍的物种IO模型,除了异步IO模型以外都属于同步IO模型;

    其本质上的区别为是否需要参与IO过程;

    上述中的模型中除了异步IO外,其他所有的IO模型执行流都将在事件就绪后去进行数据的拷贝操作;

    而异步IO并不需要参与该过程,当调用对应的aio_read()函数后IO将全权交由内核进行处理,包括等待与拷贝的过程;

    当整个IO过程完成后内核将发送特定的信号或是采用回调函数的方式告诉上层IO已经完成;

    换句话来说就是异步IO只发起IO,不参与IO过程;


非阻塞IO

  • 如何进行非阻塞IO

在一些系统调用接口中,如read()/write()recv()/send()等函数进行IO操作时,默认采用的是阻塞IO的方式;

当然recv()/send()系统调用接口可根据flag参数来设置使用阻塞还是非阻塞的IO方式;

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

这两个函数中的flag参数选项有许多,但用来设置非阻塞IO的选项为MSG_DONTWAIT;

但这种方式在实际的使用过程中并不方便,即使当需要设置一个文件描述符为非阻塞式IO时都要使用对应的函数并设置的选项,且使用这种方式进行非阻塞IO时只是在此操作中进行了一次非阻塞IO;

相比之下使用fcntl()系统调用接口将会在IO中使IO操作更加灵活;


fcntl( )

NAME
       fcntl - manipulate file descriptor

SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>

       int fcntl(int fd, int cmd, ... /* arg */ );

DESCRIPTION
       fcntl() performs one of the operations described below on the open file descriptor fd.  The operation is determined by cmd.

通常情况下,每一个文件描述符的IO操作都是属于阻塞式IO的;

fcntl()系统调用接口可以设置一个文件描述符的IO属性为非阻塞模式;

当调用该函数将一个文件描述符的IO属性设置为非阻塞模式时,至手动解除非阻塞模式或是该文件描述符的生命周期结束前,所有关于该文件描述符的IO操作都将视为非阻塞IO操作(如read()/write(),recv()/send()等);

这种方式比每次调用send()/recv()函数时都传递MSG_DONTWAIT参数更加方便和灵活;

假设存在一个程序,这个程序将调用read()系统调用接口来获取0号文件描述符(键盘文件)中的数据并进行打印;

int main()
{
    char buff[1024];

    while (true)
    {
        printf("Please Enter # ");
        fflush(stdout);
        ssize_t n = read(0, buff, sizeof(buff) - 1);
        if (n > 0)
        {
            buff[n - 1] = 0;
            cout << "Echo: " << buff << endl;
        }
        else if (0 == n)
        {
            cout << "Read Done" << endl;
            break;
        }
        else
        {
            cout << "Read Error" << endl;
            break;
        }
    }

    return 0;
}

以这段代码为例,定义了一个char类型的数组作为字符串来接收由0号文件描述符中所接收到的数据,并进行打印;

当程序运行时,进程阻塞并等待键盘文件中的资源就绪,即等待读事件就绪;

当读事件就绪后read()系统调用函数将数据读出而后进行打印;

在上文中提到,任何文件描述符的默认IO属性都是为阻塞式IO,而通过fcntl()系统调用接口可以设置一个文件描述符的IO属性;

void SetNonBlock(int fd)
{
    int flags = fcntl(fd, F_GETFL); // 获取标记位
    if (flags < 0)
    { // 获取标记位失败
        perror("fcntl");
        return;
    }
    fcntl(fd, F_SETFL, flags | O_NONBLOCK); // 利用或来增加属性
}

int main()
{
    char buff[1024];
    SetNonBlock(0); // 调用
    while (true)
    {
 		// ...
    }

    return 0;
}

在原有的代码中增加了一个SetNonBlock()函数,这个函数是利用fcntl()系统调用接口实现的;

首先先调用fcnt()系统调用接口传递F_GETFL获取对应文件描述符中的标记位fd;

    int flags = fcntl(fd, F_GETFL); // 获取标记位

而后判断标记位是否获取成功,如果获取失败则返回;

随后调用fcnt()系统调用接口传递F_SETFL参数并添加O_NONBLOCK来将标记位以属性的方式设置进对应的文件描述符中;

    fcntl(fd, F_SETFL, flags | O_NONBLOCK); // 利用或来增加属性

当进程运行过后将进入非阻塞的轮询状态,不断调用read()系统调用接口,由于未读到任何有效数据(读事件未就绪),将一直走判断中的else部分;

键盘的输入与进程中的非阻塞轮询状态并不冲突,当键盘输入并按下回车时表示读事件已经就绪,对应的键盘所输入的信息将被打印出来;

而通常情况下read()系统调用接口默认是阻塞式IO方式,也验证了可以通过使用fcntl()系统调用接口来设置一个文件描述符的IO属性为非阻塞IO;

同时为了验证该处所对应的"Read Error"表示资源未就绪,可以使用strerror()函数来打印出对应的错误码及错误码信息;

 if (n > 0)
        {
           // ...
        }
        else if (0 == n)
        {
            // ...
        }
        else
        {
            cout << "Read Error , n = " << n << " errno code: " << errno << "  " << strerror(errno) << endl;
        }

运行结果为:

表示资源未就绪;

通常情况下可以通过errno以及strerror(errno)等方式来判断出错原因;

当然在程序当中最好根据错误码来识别是否出错,因此可以将代码完善一下:

    while (true)
    {
        printf("Please Enter # ");
        fflush(stdout);
        ssize_t n = read(0, buff, sizeof(buff) - 1);

        if (n > 0)
        {
            buff[n - 1] = 0;
            cout << "Echo: " << buff << endl;
        }
        else if (0 == n)
        {
            cout << "Read Done" << endl;

            break;
        }
        else
        {
            if (errno == EWOULDBLOCK)
            {
                cout << "Resource temporarily unavailable" << endl;
                // TODO 可完成其他任务
            }
            else
            {
                cout << "Read Error , n = " << n << " errno code: " << errno << "  " << strerror(errno) << endl;
            }
        }
        sleep(1);
    }

这是典型的非阻塞轮询IO模型,当判断事件未就绪时,执行流可以根据需求完成其他任务(TODO部分)随后再进行下一次轮询从而提高整体效率;


多路转接 - select( )

select() 函数是用来进行多路转接的一个接口;

IO可以分为 “等 + 拷贝” 两个部分,而select()函数真正做到的是其中的 “等” 的部分,其可以设置一个进程在IO过程中通过该接口阻塞等待多个文件描述符;

#include <sys/select.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

	   void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

该函数的返回值(n表返回值)通常有三种情况:

  • n > 0

    返回值大于0则表示有n个文件描述符就绪;

  • n == 0

    表示超时,没有错误也没有文件描述符就绪;

  • n < 0

    表示出错;

select()函数参数为如下:

  • int nfds

    该参数用于标示可能需要检查的文件描述符个数;

    通常需要传入 当前进程最大文件描述符+1 ,即maxfd + 1;

  • struct timeval *timeout

    该参数类型是一个结构体类型,其定义如下:

    The timeout argument for select() is a structure of the following type:
    
               struct timeval {
                   time_t      tv_sec;         /* seconds */
                   suseconds_t tv_usec;        /* microseconds */
               };
    

    该类型为系统提供的一个时间结构体;

    其中time_t tv_sec;表示时间戳,单位为秒,suseconds_t tv_usec;表示微秒;

    该变量本质是为select()设置等待方式,也是一个超时事件,假设该参数传入[5,0],即struct timeval timeout = [5,0],则表示当5s过后将会返回(timeout)一次;

    若是设置为[0,0]则表示立马返回(典型的非阻塞式IO);

    需要澄清一下的是该参数所设置的等待方式是只用作于一次调用的;

    若是该参数设置为NULL则表示阻塞等待;

    同时当使用select()并设置等待方式后,该参数就会成为一个输入输出型参数;

    假设使用select()并设置等待方式为[5,0],但若是在2s后有文件描述符的事件就绪,对应的select()将立即返回,同时其timeout参数将会变为[3,0];

除了上两个参数以外,select()系统调用接口还有三个类型相同的参数,分别为readfds,writefds以及exceptfds;

这三个参数的类型都为fd_set,而该类型实际上即为一个位图类型,是有操作系统内核提供,与sigset类似;

这三个参数分别代表使用select()系统调用接口所关心的事件类型,即:

  • 读事件
  • 写事件
  • 异常事件

三种事件,其中用户所传入位图中的各个位标识着select()需要关心哪些文件描述符需要关心哪些事件;

如在读事件对应的参数传入0001,即表示关心0号文件描述符的读事件;

同时这三个fd_set类型的参数同样是输入输出型参数,当用户因调用select()传入对应的fd_set类型时,其中位图中的位标识对应文件描述符需要关心的对应事件是否就绪,而当该函数因某些文件描述符中的某些事件就绪后而返回,对应的事件类型中的位图将会被修改为事件已经就绪的文件描述符;

这表明了在使用select()函数时需要大量的进行位图操作,而这里的位图结构是由操作系统内核提供,为了保证内核安全性,系统不会直接让用户来修改对应的位图结构,因此为用户提供了一系列的位图操作;

即在上文中提到的:

	   void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);
  • void FD_CLR(int fd, fd_set *set)

    表示将一个文件描述符从一个位图集合上去除;

  • int FD_ISSET(int fd, fd_set *set)

    判断一个文件描述符是否在对应的位图集合上被设置;

  • void FD_SET(int fd, fd_set *set)

    在一个位图集合上设置对应的文件描述符;

  • void FD_ZERO(fd_set *set)

    清空一个位图集合;


select( ) 的基本使用 - SelectServer服务器

为了验证select()的功能及其基础的使用,这里实现了一个SelectServer服务器;

在之前的博客中对Socket直接进行了封装,这里直接套用原有的代码不进行赘述(参考博客『 Linux 』协议的定制中 " 套接字接口的封装 " 部分,同样的这里还涉及到之前实现的日志系统的demo);

对于main()函数而言,只需要实例化一个服务器对象,并且调用服务器对象中的初始化方法Init()与服务器对象中的启动方法即Start();

/*	Main.cc	*/
int main()
{
    std::unique_ptr<SelectServer> svr(new SelectServer()); // 防拷贝智能指针
    svr->Init();
    svr->Start();
    return 0;
}

其中SelectServer即为服务器类型;

同样的,服务器需要提供初始化与启动的方法API;

/*	SelectServer.hpp	*/

static const uint16_t defaultport = 8050;

class SelectServer
{
public:
    SelectServer(uint16_t port = defaultport) : _port(port) {}
    bool Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
        return true;
    }

    void Start()
    {
		for (;;){
				// ...
        }
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    NetSocket _listensock;
    uint16_t _port;
};

在这个类中提供了初始化的方法和启动的方法,其中初始化的方法包括监听套接字的创建初始化,绑定以及设置监听;

通常情况下,启动服务器时就可以调用accept()来获取新的连接;

而在使用select()时这里不能直接使用accept()来获取新连接,因为本质上accept()就是检测并获取_listensock监听套接字上的读事件,只能在这上面进行二选一;

若是使用select()时需要一个位图结构,这个位图结构即为内核提供的fd_set类型;

/*	SelectServer.hpp	*/

class SelectServer
{
public:
    void Start()
    {
        int listensock = _listensock.GetFd();
        fd_set rfds;               // 读文件描述符集

        for (;;)
        {
            // 不能直接accept 本质上accept就表示检测并获取listensock上的事件
            // 新连接的到来等价于读事件的就绪

            FD_ZERO(&rfds);            // 清空描述符集
            FD_SET(listensock, &rfds); // 将文件描述符写入至读文件描述符集中
            struct timeval timeout = {5, 0};

            int n = select(listensock + 1, &rfds, nullptr, nullptr, &timeout);

            switch (n)
            {
            case 0:
                printf("Time out, timeout: %ld . %ld\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                printf("Select Error\n");
                break;
            default:
                printf("%d fd Even Ready, timeout: %ld . %ld\n", n, timeout.tv_sec, timeout.tv_usec);
                sleep(2);
                break;
            }
        }
    }
};

这段代码为SelectServer服务器中的Start()启动方法,使用select()进行事件的查看,首先定义了一个fd_set rfds文件描述符集(位图);

  • 随后循环进行以下操作:

    以此调用FD_ZERO()方法,将文件描述符集进行一次清空;

    随后调用FD_SET()方法传入_listensock.GetFd()+1,设置文件描述符中最大值+1(根据文件描述符的规律为 “顺序向上,有空补空” ,因此SelectServer服务器中的监听套接字描述符是最大的);

    创建timeval时间结构体并设置timeout等待方式(超时事件),此处设置为[5,0],表示5s后返回一次(timeout被设置时为输入输出型参数,每次都在自减,因此需要在循环中循环重新进行设置);

    最后调用select()将文件描述符集传入对应位置;

                int n = select(listensock + 1, &rfds, nullptr, nullptr, &timeout);
    

    最后根据返回值n判断其他操作;

对于返回值n的判断使用了switch()case语句的方式,当n > 0时表示n个文件描述符已经就绪,当n == 0时表示超时事件就绪(timeout一次),当n < 0时表示select()出错;

运行程序,并使用telnet工具配合环回地址测试;

从结果可以看出,当程序运行时,在还没有使用telnet工具进行连接时,由于select()没有检测到读事件,在5stimeout一次,对应的timeout的时间戳已经被减到了[0.0];

在第二次调用时等待了两秒作用使用telnet工具进行本地连接,select()立即返回,此时也可以观察到timeout的时间戳发生了变化;

第三次以及后续的timeout都返回了[4 . 99...]类似的值,本质是连接一直是存在的,而情况读文件描述符集与将文件描述符集通过select()以及该函数的调用与返回是有时间上的开销的,即使速度很快;

若是此处将timeout设置为[ 0 , 0 ]则说明使用非阻塞的方式进行,这里配合循环显然是一个典型的轮询机制,当然如果使用select()依旧使用轮询的话将会加大消耗,通常不建议这么使用;

同样的若是timeout设置为nullptr则表示使用阻塞式;

当程序运行时却没有连接(事件未就绪)将出于阻塞状态,直至有连接;

通常情况下当使用seletc()后返回时事件就绪后应该立即对就绪时间进行处理,若上层未处理select()将一直通知(原因在上文中有介绍);

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

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

相关文章

Spark-Streaming集成Kafka

Spark Streaming集成Kafka是生产上最多的方式&#xff0c;其中集成Kafka 0.10是较为简单的&#xff0c;即&#xff1a;Kafka分区和Spark分区之间是1:1的对应关系&#xff0c;以及对偏移量和元数据的访问。与高版本的Kafka Consumer API 集成时做了一些调整&#xff0c;下面我们…

「下载」智慧城市包括哪些方面:大数据公共服务平台、城市运行指挥中心、城市综合治理平台、城市体检综合运营平台解决方案

在当今信息化高速发展的时代&#xff0c;智慧城市已成为全球城市发展的新趋势。系列全面而创新的智慧城市解决方案&#xff0c;旨在助力城市实现智慧化转型&#xff0c;提升城市管理效率&#xff0c;增强市民生活质量。 智慧城市最新解决方案&#xff0c;标准规范顶层设计指南、…

ChatGPT生成接口文档实践案例(二)

不难发现&#xff0c;两个方案都出色地完成了接口文档的生成&#xff0c;但笔者更喜欢Response 2的表达&#xff0c;因为其描述更加全面。 还可以让ChatGPT生成符合OpenAPI 3.0规范的接口文档&#xff0c;以便于项目相关成员阅读&#xff0c;如图5-13所示。 为什么要生成OpenAP…

【解决】Linux更新系统内核后Nvidia-smi has failed...

问题概述 由于服务器(操作系统为 RedHat 9)宕机&#xff0c;重启后&#xff0c;系统内核自动更新了&#xff0c;然后输入 nvidia-smi 发现报了下面的异常&#xff1a; NVIDIA-SMI has failed because it couldnt communicate with the NVIDIA driver. Make sure that the late…

Docker Compose 安装 Harbor

我使用的系统是rocky Linux 9 1. 准备环境 确保你的系统已经安装了以下工具&#xff1a; DockerDocker ComposeOpenSSL&#xff08;用于生成证书&#xff09;#如果不需要通过https连接的可以不设置 1.1 安装 Docker 如果尚未安装 Docker&#xff0c;可以参考以下命令安装&…

PCIe_Host驱动分析_设备枚举

往期内容 本文章相关专栏往期内容&#xff0c;PCI/PCIe子系统专栏&#xff1a; 嵌入式系统的内存访问和总线通信机制解析、PCI/PCIe引入 深入解析非桥PCI设备的访问和配置方法 PCI桥设备的访问方法、软件角度讲解PCIe设备的硬件结构 深入解析PCIe设备事务层与配置过程 PCIe的三…

【CVE-2024-53375】TP-Link Archer系列路由器认证操作系统命令注入(内附远离和代码利用)

CVE-2024-53375 TP-Link Archer系列路由器认证操作系统命令注入 受影响的设备 使用 HomeShield 功能的 TP-Link 设备容易受到此漏洞的影响。这包括 TP-Link Archer 系列的多款路由器。 经过测试 Archer AXE75(EU)_V1_1.2.2 Build 20240827(发布日期 2024 年 11 月 4 日)…

SpringBoot 自动装配原理及源码解析

目录 一、引言 二、什么是 Spring Boot 的自动装配 三、自动装配的核心注解解析 3.1 SpringBootApplication 注解 &#xff08;1&#xff09;SpringBootConfiguration&#xff1a; &#xff08;2&#xff09;EnableAutoConfiguration&#xff1a; &#xff08;3&#xf…

2025系统架构师(一考就过):案例题之一:嵌入式架构、大数据架构、ISA

一、嵌入式系统架构 软件脆弱性是软件中存在的弱点(或缺陷)&#xff0c;利用它可以危害系统安全策略&#xff0c;导致信息丢失、系统价值和可用性降低。嵌入式系统软件架构通常采用分层架构&#xff0c;它可以将问题分解为一系列相对独立的子问题&#xff0c;局部化在每一层中…

单片机上电后程序不运行怎么排查问题?

1.电源检查。使用电压表测量单片机的电源电压是否正常&#xff0c;确保电压在规定的范围内&#xff0c;如常见的5V。 2.复位检查。检查复位引脚的电压是否正常&#xff0c;在单片机接通电源时&#xff0c;复位引脚通常会有一个高电平&#xff0c;按下复位按钮时&#xff0c;复位…

初学stm32 --- 外部中断

目录 STM32 IO 口中断基础知识 相关库函数&#xff1a; 使用 IO 口外部中断的一般步骤 STM32 IO 口中断基础知识 STM32 的每个 IO 都可以作为外部中断的中断输入口。STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断设有状态位&#xff0c;每个中断/事件都有独立…

c++------------------函数

函数定义 语法格式 函数定义包括函数头和函数体。函数头包含返回类型、函数名和参数列表。函数体是用花括号{}括起来的代码块&#xff0c;用于实现函数的功能。例如&#xff0c;定义一个计算两个整数之和的函数&#xff1a; int add(int a, int b) {return a b; }这里int是返回…

【java基础系列】实现一个简单的猜数字小游戏

主要是用的java中的键盘录入和随机数两个api&#xff0c;实现这种人机交互的小游戏&#xff0c;可以用来锻炼基础算法思维 实现效果 实现代码 package com.gaofeng.day10;import java.util.Random; import java.util.Scanner;/*** author gaofeng* date 2024-12-22 - 9:21*/ …

helm的介绍和安装

1 helm概述 1.1 资源对象难以管理的问题 helm是k8s资源清单的管理工具&#xff0c;它就像Linux下的包管理器&#xff0c;比如centos的yum&#xff0c;ubuntu的apt helm&#xff1a;命令行工具&#xff0c;主要用于k8s的chart的创建&#xff0c;打包&#xff0c;发布和管理。…

AI,cursor快速上手思维导图

https://cursor101.com/zh/tutorial/learn-cursor-tab

ESP32S3 使用LVGL驱动LCD屏(ST7789主控)

ESP32S3 使用LVGL驱动LCD屏&#xff08;ST7789主控&#xff09; 目录 1 分析原理图 2 驱动、点亮LCD(ST7789) 2.1 在工程中添加目录、文件 2.2 添加esp_lvgl_port组件 2.3 对工程进行必要的配置 2.4 编写必要代码 3 烧录、验证 1 分析原理图 要使用SOC驱动LCD屏&#…

【hackmyvm】Zday靶机wp

HMVrbash绕过no_root_squash静态编译fogproject 1. 基本信息^toc 这里写目录标题 1. 基本信息^toc2. 信息收集2.1. 端口扫描2.2. 目录扫描 3. fog project Rce3.1. ssh绕过限制 4. NFS no_root_squash5. bash运行不了怎么办 靶机链接 https://hackmyvm.eu/machines/machine.ph…

neo4j console 报错

项目场景&#xff1a; neo4j 开启失败 问题描述 在终端打开 neo4j 失败打开cmd, 输入: neo4j console 报错 原因分析&#xff1a; 1 可能是没有配置环境变量2 当前脚本的执行策略有问题 解决方案&#xff1a; 解决没有配置环境变量 添加环境变量 在path路径中将变量添加进去…

范德蒙矩阵(Vandermonde 矩阵)简介:意义、用途及编程应用

参考&#xff1a; Introduction to Applied Linear Algebra – Vectors, Matrices, and Least Squares Stephen Boyd and Lieven Vandenberghe 书的网站: https://web.stanford.edu/~boyd/vmls/ Vandermonde 矩阵简介&#xff1a;意义、用途及编程应用 在数学和计算科学中&a…

编译原理复习---正则表达式+有穷自动机

适用于电子科技大学编译原理期末考试复习。 1. 正则表达式 正则表达式&#xff08;Regular Expression&#xff0c;简称regex或regexp&#xff09;是一种用于描述、匹配和操作文本模式的强大工具。它由一系列字符和特殊符号组成&#xff0c;这些字符和符号定义了一种搜索模式…