2.10 高性能异步IO机制:io_uring

news2025/1/16 21:40:31

一、io_uring的引入

为了方便说明io_uring的作用,先举一个通俗点的例子

1、通过异步提高读写的效率

假设有一批数量很大的货,需要分批次运到厂里处理。这个时候就有两种方式:
1)同步方式:运送一批到厂里,等厂里反馈OK了,再回来送下一批;
2)异步方式:送到厂里之后,不用等厂里反馈,直接回来接送下一批货。

哪一种方式比较好呢?当然如果货的数量不多,同步方式比较可靠,效率差别也不大。但如果面对数量很大,比如读取一个很大的数据包,那异步方式效率就会显著提高。

2、减少系统开销

另外,正常送货到工厂,需要开门进入,卸完货之后再出来。这样频繁的进出消耗很大。因此,可以在工厂门口开辟一块共享仓库,货车卸货进仓库,工厂从仓库里取货。

对应的,IO操作需要进行系统调用。而在调用系统调用时,会从用户态切换到内核态,进行上下文切换。在高 IOPS(Input/Output Per Second)的情况下,进行上下文切换会消耗大量的CPU时间。
在这里插入图片描述
io_uring为了减少系统调用带来的上下文切换,采用了用户态和内核态共享内存的方式。用户态对共享内存进行读写操作是不需要使用系统调用的,所以不会发生上下文切换的情况。
在这里插入图片描述

二、io_uring

1、内核接口

linux内核(5.10版本之后)为io_uring提供了三个接口
1)io_uring_setup:该函数用于初始化和配置 io_uring环境。该函数将返回一个文件描述符,称为 io_uring文件描述符,用于后续的 I/O 操作。

int io_uring_setup(unsigned entries, struct io_uring_params *p);
  • entries:指定io_uring 的入口数目,即同时处理的 I/O 事件数目。
  • p:指向 struct io_uring_params 结构的指针,用于传递其他配置参数。

2)io_uring_enter:该函数用于提交 I/O 事件并等待其完成。该函数将返回已完成的 I/O 事件数量。

int io_uring_enter(int fd, unsigned to_submit, unsigned min_complete, unsigned flags, sigset_t *sig);
  • fd:io_uring文件描述符。
  • to_submit:要提交的 I/O 事件数量。
  • min_complete:指定在返回之前至少完成的 I/O 事件数量。
  • flags:用于指定操作行为的标志。
  • sig:用于传递信号集,以便在等待期间阻塞特定的信号。

3)io_uring_register:该函数用于将文件描述符或内存区域与io_uring关联起来。该函数将返回注册的文件描述符或内存区域的索引,以便后续的 I/O 操作可以使用。

int io_uring_register(int fd, unsigned int opcode, const void *arg, unsigned int nr_args);
  • fd:io_uring文件描述符。
  • opcode:指定注册操作的类型,如文件描述符的注册或内存区域的注册。
  • arg:指向相关数据结构的指针,用于传递需要注册的文件描述符或内存区域的信息。
  • nr_args:指定相关参数的数量。

2、提交队列SQ和完成队列CQ

1)提交队列(Submission Queues,SQ):环形队列,存放即将执行I/O操作的数据;
2)完成队列(Completion Queue, CQ):环形队列,存放I/O操作完成返回后的结果;

对于提交队列,内核会将io_sq_ring结构映射到应用程序的内存空间,这样应用程序可以直接向io_sq_ring结构的环形队列提交I/O操作,内核可以通过io_sq_ring结构的环形队列读取I/O操作。这样不用通过系统调用,避免了上下文切换。

同样对于完成队列也是如此。

2.1 io_sq_ringio_cq_ring

io_sq_ring结构用于向内核发起 I/O 操作请求,即将需要执行的 I/O 操作请求提交到队列中。io_sq_ring 中的array指向一个环形队列的SQE数组,该数组用于存储应用程序提交的I/O请求信息。每个SQE对应一个io_uring_sqe结构体,代表一个具体的 I/O 操作请求,其中包含了执行该操作所需的各种参数和信息。
在这里插入图片描述
同样对于io_cq_ring结构也是如此。需要特别注意的是结构体中res 的含义会有所不同:
对于读取操作(如 read()),res 表示成功读取的字节数。
对于写入操作(如 write()),res 表示成功写入的字节数。
对于 accept 操作(如 accept()),res 表示接受到的新连接的文件描述符(即,已连接套接字)。
在这里插入图片描述

3、io_uring的流程

io_uring的操作流程如下:
1)应用程序向 提交队列io_sq_ring提交I/O操作
2)SQ内核线程从 提交队列 中读取 I/O 操作。
3)SQ内核线程发起 I/O 请求。
4)I/O 请求完成后,SQ内核线程会将 I/O 请求的结果写入到 io_uring 的 完成队列io_cq_ring中。
5)应用程序可以从 完成队列io_cq_ring 中读取到 I/O 操作的结果。
在这里插入图片描述

三、liburing库

liburing是已经封装好io_uring的库

1、安装liburing

1)下载源码

sudo apt-get install git
git clone git://git.kernel.dk/liburing

2)进入liburing

cd liburing/

3)配置

./configure

4)编译和安装

make && sudo make install

5)配置pkg-config文件,以便其他程序能够使用已安装的liburing库

echo "/usr/local/lib/pkgconfig" | sudo tee /etc/ld.so.conf.d/usr-local-lib.conf >/dev/null
sudo ldconfig -v

6)编译应用程序

gcc -o uring uring.c -luring -static

2、接口

2.1 io_uring_queue_init_params()

执行io_uring_setup()系统调用来初始化io_uring队列。

int io_uring_queue_init_params(unsigned entries, struct io_uring *ring, const struct io_uring_params *p);

1)entries:指定 I/O uring 的入口数目,即同时处理的 I/O 事件数目。
2)ring:指向 struct io_uring 结构的指针,用于接收初始化后的 I/O uring 环境。
3)p:指向 struct io_uring_params 结构的指针,包含了自定义的初始化参数。

2.2 io_uring_get_sqe()

用于从 I/O uring 环境的 submission queue (SQ) 中获取一个 Submission Queue Entry (SQE)。通过获取 SQE,你可以将 I/O 操作请求添加到 I/O uring 中,并提交给内核进行处理。

struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring);

1)ring:指向 struct io_uring 结构的指针,表示要操作的 I/O uring 环境。
该函数返回一个指向 struct io_uring_sqe 结构的指针,该结构表示一个 Submission Queue Entry (SQE)。SQE 包含了要执行的 I/O 操作的详细信息

2.3 io_uring_prep_accept()

用于准备执行 accept 操作的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_accept 函数,可以设置要接受连接的套接字描述符、新连接的文件描述符和连接地址等参数。

void io_uring_prep_accept(struct io_uring_sqe *sqe, int fd, struct sockaddr *addr, socklen_t *addrlen, int flags);

1)sqe:一个指向 struct io_uring_sqe 结构的指针,表示要准备的 SQE。
2)fd:要接受连接的套接字描述符。
3)addr:一个指向 struct sockaddr 结构的指针,用于接收连接的远程地址信息。
4)addrlen:一个指向 socklen_t 类型的指针,表示 addr 缓冲区的长度。
5)flags:一个整数,用于设置一些额外的标志。

2.4 io_uring_prep_recv()

用于准备执行接收数据的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_recv 函数,你可以设置要接收数据的套接字描述符以及数据缓冲区等参数。

void io_uring_prep_accept(struct io_uring_sqe *sqe, int fd, struct sockaddr *addr, socklen_t *addrlen, int flags);

1)sqe:一个指向 struct io_uring_sqe 结构的指针,表示要准备的 SQE。
2)fd:要接受连接的套接字描述符。
3)addr:一个指向 struct sockaddr 结构的指针,用于接收连接的远程地址信息。
4)addrlen:一个指向 socklen_t 类型的指针,表示 addr 缓冲区的长度。
5)flags:一个整数,用于设置一些额外的标志。

2.5 io_uring_prep_send()

用于准备执行发送数据的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_send 函数,你可以设置要发送数据的套接字描述符以及数据缓冲区等参数。

void io_uring_prep_send(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned int len, int flags);

1)sqe:一个指向 struct io_uring_sqe 结构的指针,表示要准备的 SQE。
2)fd:要发送数据的套接字描述符。
3)buf:一个指向数据缓冲区的指针,包含要发送的数据。
4)len:一个无符号整数,表示数据缓冲区中要发送的数据长度。
5)flags:一个整数,用于设置一些额外的标志。

2.6 io_uring_submit()

用于提交 SQE (Submission Queue Entry) 到 I/O uring 环境中进行处理。

int io_uring_submit(struct io_uring *ring);

1)ring:一个指向 struct io_uring 结构的指针,表示要提交的 I/O uring 环境。
2)io_uring_submit 函数将等待队列中的 SQEs 进行提交,并触发 I/O uring 环境的处理。

函数返回已提交的 SQE 数量,或者在出现错误时返回负数

2.7 io_uring_wait_cqe()

用于等待完成队列项(Completion Queue Entry,CQE)的到来。

int io_uring_wait_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr);

1)ring:指向 struct io_uring 的指针,表示I/O uring环境。
2)cqe_ptr:指向CQE指针的指针,用于存储返回的已完成CQE。

io_uring_wait_cqe() 函数会阻塞当前线程,直到有已完成的CQE可用。一旦有CQE可用,函数将填充 cqe_ptr 指向的指针,并返回0。

2.8 io_uring_peek_batch_cqe()

用于批量获取已完成的CQE(Completion Queue Entry)而无需等待.

int io_uring_peek_batch_cqe(struct io_uring *ring, struct io_uring_cqe **cqes, unsigned int count);

1)ring:指向 struct io_uring 的指针,表示I/O uring环境。
2)cqes:一个指向指针数组的指针,用于存储返回的已完成CQE。
3)count:要获取的CQE数量。

io_uring_peek_batch_cqe() 函数会尝试从I/O uring环境中立即获取指定数量的已完成CQE,并将它们存储到 cqes 指向的指针数组中。如果成功获取了指定数量的CQE,函数将返回成功接收到并处理完毕的I/O事件数量,否则将返回负值。

2.9 io_uring_cq_advance()

用于标记完成队列(Completion Queue,CQ)上已经处理的CQE数量。当一个或多个CQE被处理后,需要调用io_uring_cq_advance()函数来更新下一次读取CQE时应该从哪个位置开始。

void io_uring_cq_advance(struct io_uring *ring, unsigned int steps);

1)ring:指向 struct io_uring 的指针,表示 I/O uring 环境。
2)steps:要向前推进的步数,即要消耗的 CQE 数量。

io_uring_cq_advance() 函数将指定数量的已完成的 CQE 标记为已消耗,从而使 I/O uring 可以继续接收新的 CQE。

2.10 io_uring_cq_advance()

用于推进完成队列(Completion Queue)中的消费指针,即更新消费指针的位置。以告知 I/O uring 已经成功处理了一定数量的完成事件,并且可以释放这些事件所占用的资源。

void io_uring_cq_advance(struct io_uring *ring, unsigned int count);

1)ring 是指向 struct io_uring 的指针,代表 I/O uring 环境。
2)count 是要推进的完成事件数量。

三、liburing的TCP服务器实现

基于liburing,实现多个TCP服务器的收发
在这里插入图片描述


#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

#include <liburing.h>


#define ENTRIES_LENGTH		1024

enum {
 	EVENT_ACCEPT = 0,
	EVENT_READ,
	EVENT_WRITE   
};

typedef struct _conninfo{
    int connfd;
    int event;
}conninfo;

//设置accept事件所需的信息,并将其与相应的 SQE 关联起来
void set_accept_event(struct io_uring *ring, int sockfd, struct sockaddr *addr, 
                        socklen_t *addrlen, int flags){
    //获取一个SQE
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    //准备执行accept操作的SQE
    io_uring_prep_accept(sqe, sockfd, addr, addrlen, flags);

    //创建了一个结构体变量 info_accept,其中存储了与连接相关的信息
    conninfo info_accept = {
        .connfd = sockfd,
        .event = EVENT_ACCEPT
    };

    memcpy(&sqe->user_data, &info_accept, sizeof(info_accept));

}

//设置recv事件所需的信息,并将其与相应的 SQE 关联起来
void set_recv_event(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags){
    
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    io_uring_prep_recv(sqe, sockfd, buf, len, flags);

    conninfo info_recv = {
        .connfd = sockfd,
        .event = EVENT_READ
    };

    memcpy(&sqe->user_data, &info_recv, sizeof(info_recv));
}

//设置send事件所需的信息,并将其与相应的 SQE 关联起来
void set_send_event(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags){
    
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    io_uring_prep_send(sqe, sockfd, buf, len, flags);

    conninfo info_send = {
        .connfd = sockfd,
        .event = EVENT_WRITE
    };

    memcpy(&sqe->user_data, &info_send, sizeof(info_send));
}


int main(){

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(struct sockaddr_in));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);

    if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))){
        printf("bind failed:%s\n",strerror(errno));
        return -1;
    }

    listen(sockfd, 10);

    //linuring


    struct io_uring_params params;
    memset(&params, 0 ,sizeof(struct io_uring_params));

    struct io_uring ring;
    //初始化io_uring队列
    io_uring_queue_init_params(ENTRIES_LENGTH, &ring, &params);

    //获取一个SQE
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);

    struct sockaddr_in clientaddr;
    socklen_t clilen = sizeof(struct sockaddr);

    //设置accept事件所需的信息,并将其与相应的 SQE 关联起来
    set_accept_event(&ring, sockfd, (struct sockaddr *)&clientaddr, &clilen, 0);

    char buffer[1024] = {0};

    while (1){

        //提交 SQE
        io_uring_submit(&ring);

        struct io_uring_cqe *cqe;
        //等待完成队列项cqe
        io_uring_wait_cqe(&ring, &cqe);

        struct io_uring_cqe *cqes[10];
        //批量获取已完成IO操作的CQE
        int cqecount = io_uring_peek_batch_cqe(&ring, cqes, 10);

        int i = 0;
        for (i = 0; i < cqecount; i++){

            cqe = cqes[i];

            conninfo ci;
            memcpy(&ci, &cqe->user_data, sizeof(ci));

            if (ci.event == EVENT_ACCEPT){ //此时已经完成accept

                if (cqe->res < 0) continue;

                int connfd = cqe->res;  

                //在已接受连接的connfd上设置recv事件处理器,以便处理已建立连接的数据接收操作。
                set_recv_event(&ring, connfd, buffer, 1024, 0); //此处的connfd表示已建立连接的fd,clientfd

                //重新设置接受连接的事件,以便处理新连接的接受操作。
                set_accept_event(&ring, ci.connfd, (struct sockaddr *)&clientaddr, &clilen, 0); //此处的connfd表示新连接的fd,listenfd
                
            }else if (ci.event == EVENT_READ){//此时已经完成read

                if (cqe->res < 0) continue;
                if (cqe->res == 0){
                    close(ci.connfd);
                }else{

                    printf("recv --> %s, %d\n", buffer, cqe->res);
                    
                    //重新设置写的事件,以便回发
                    set_send_event(&ring, ci.connfd, buffer, cqe->res, 0);

                }
            } else if (ci.event == EVENT_WRITE){//此时已经完成write
                
                //重新设置读的事件
                set_recv_event(&ring, ci.connfd, buffer, 1024, 0);
            }
        }
    
        io_uring_cq_advance(&ring, cqecount);
    }

    getchar();

}

在这里插入图片描述

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

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

相关文章

TypeScript ~ TS 掌握编译文件配置项 ④

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; TypeScript ~ TS &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &…

初识EasyX图形库

EasyX图形库 1. EasyX是什么&#xff1f;2. 入手EasyX3. EasyX函数介绍创建和关闭绘图窗口操作initgraphclosegraph 设置绘图背景setbkcolorcleardevice 画图形circlefillcirclerectanglefillrectangle 图形颜色及样式设置setfillcolorsetlinecolorsetbkcolorsetbkmodesetlines…

计算物理专题:有限差分法解决本征值问题

计算物理专题&#xff1a;有限差分法解决本征值问题 定态薛定谔方程差分形式 一维定态薛定谔方程 谐振子 解法代码 import numpy as np def householder(symmetric_matrix):M symmetric_matrixassert np.allclose(M,M.T),"matrix is not symmetric"N len(M)for …

chatgpt赋能python:用Python分析电影评分数据

用Python分析电影评分数据 Python是一种流行的数据分析和可视化工具&#xff0c;它可以让我们更深入地了解电影的评分数据。在本文中&#xff0c;我们将使用Python来分析一些电影评分数据&#xff0c;并试图找出一些有趣的模式和趋势。 数据来源 我们将使用公共数据集IMDb电…

第4章 网络层

1‌、下列关于路由算法描述错误的是&#xff08; &#xff09; A. 链路状态算法是一种全局路由算法&#xff0c;每个路由器需要维护全局状态信息B. OSPF 是一种域内路由协议&#xff0c;核心是基于 Dijkstra 最低费用路径算法C. RIP 是一种域内路由算法&#xff0c;核心是基…

采用SqlSugar的DBFirst相关功能创建数据库表对应的实体类

.NET Core官方教程中推荐使用的EF Core数据库ORM框架虽然能用&#xff0c;但是用起来并不是太方便&#xff08;或者是不习惯&#xff0c;之前用的最多的还是linq&#xff09;。之前下载的开源博客项目中使用的SqlSugar&#xff0c;后者是由果糖大数据科技团队维护和更新 &#…

基于WebAssembly构建Web端音视频通话引擎

Web技术在发展&#xff0c;音视频通话需求在演进&#xff0c;怎么去实现新的Web技术点在实际应用中的值&#xff0c;以及给我们带来更大的收益是需要我们去探索和实践的。LiveVideoStackCon 2022北京站邀请到田建华为我们从实践中来介绍WebAssembly、WebCodecs、WebTransport等…

【裸机开发】IRQ 中断服务函数(一) —— 汇编初始化

IRQ 和前面的Reset 函数不大一样&#xff0c;当一个IRQ中断产生时&#xff0c;我们也不知道这个IRQ中断来自哪个外设&#xff0c;因此&#xff0c;需要先获取到中断ID&#xff0c;随后才会跳转到真正的中断服务函数执行处理逻辑。 整个 IRQ 中断处理可以看做是包含了两个部分&…

MySQL 自增主键一定是连续的吗?

众所周知&#xff0c;自增主键可以让聚集索引尽量地保持递增顺序插入&#xff0c;避免了随机查询&#xff0c;从而提高了查询效率 但实际上&#xff0c;MySQL 的自增主键并不能保证一定是连续递增的。 下面举个例子来看下&#xff0c;如下所示创建一张表&#xff1a; 自增值保…

ORCA优化器浅析——GP数据库调用优化器流程

首先我们需要看CGPOptimizer类(src/include/gpopt/CGPOptimizer.h)为Greenplum数据库提供ORCA优化器export出来的函数的封装。Greenplum数据库主流程调用extern "C"中提供的函数&#xff0c;比如初始化ORCA优化器的函数InitGPOPT&#xff0c;优化查询树的函数GPOPTOp…

springboot+jsp农产品商城宣传网站设计与实现oo6e3

在该在线助农系统设计与实现中&#xff0c;idea能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其功能有比较灵活的数据应用&#xff0c;只需利用小部分代码…

【Leetcode60天带刷】day30回溯算法——332.重新安排行程 , 51. N皇后 ,37. 解数独

​ 题目&#xff1a; 332. 重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#xff0c;…

【从零开始学习JAVA | 第十四篇】继承

目录 前言&#xff1a; 引入&#xff1a; 继承&#xff1a; 小拓展&#xff1a; 优点&#xff1a; 成员方法的继承问题&#xff1a; 总结&#xff1a; 前言&#xff1a; 继承是面向对象三大特性之一&#xff0c;它是在封装之后我们讲解的一个重要的性质&#xff0c;继承…

在github上创建个人主页的方法【2023更新版】

01-进入github的网站&#xff0c;链接 https://github.com/ &#xff0c;然后注册&#xff0c;登陆&#xff0c;注意登陆时设置的用户名(username)就是将来你个人主页的三级域名&#xff0c;所以这里一定要慎重填写username。如下图所示&#xff1a; 02-注册完成后进入个人主…

2024考研408-计算机组成原理第四章-指令系统

文章目录 前言一、指令系统现代计算机的结构1.1、指令格式1.1.1、指令的定义1.1.2、指令格式1.1.3、指令—按照地址码数量分类①零地址指令②一地址指令&#xff08;1个操作数、2个操作数情况&#xff09;③二地址指令④三地址指令⑤四地址指令 1.1.4、指令-按照指令长度分类1.…

【计算机组成原理】Yy-z02模型机的硬布线控制器设计

目录 一、Yy-z02模型机的系统结构 二、Yy-z02模型机的数据通路 三、Yy-z02模型机的指令执行 四、Yy-z02模型机的硬布线控制器 一、Yy-z02模型机的系统结构 指令系统的实现 <--- 构造它的硬件系统 硬件系统构造过程&#xff1a; 分析指令格式和各指令的功能确定部件连…

金蝶软件遭遇.locked勒索病毒攻击:如何保护与解救您的数据?

引言&#xff1a; 近期&#xff0c;部分运行金蝶云星空软件的服务器遭受了一场勒索病毒的网络安全攻击&#xff0c;其重要数据遭到了.locked勒索病毒的加密。作为一个知名的企业级ERP软件及财务软件&#xff0c;金蝶软件的数据安全事关客户和企业的利益。91数据恢复在本文将深…

【王道·操作系统】第四章 文件管理【未完】

一、初识文件管理 文件&#xff1a;一组有意义的信息/数据集合文件属性&#xff1a; 文件名&#xff1a;创建文件的用户决定&#xff0c;主要是为了方便用户找到文件&#xff0c;同一目录下不允许有重名文件标识符&#xff1a;一个系统内的各文件标识符唯一&#xff0c;对用户来…

老大给了个新需求:如何将汉字转换成拼音字母?1行Python代码搞定!

大家好&#xff0c;这里是程序员晚枫&#xff0c;小红薯也叫这个名。 之前的视频给大家分享了&#xff1a;中文编程&#xff0c;一行代码实现。 今天给大家分享一下&#xff0c;如何通过1行Python代码&#xff0c;实现汉语转拼音 1、先上代码 实现汉语转拼音效果的第三方库…

逻辑回归(Logistics Regression)

1.逻辑回归&#xff08;Logistics Regression&#xff09; 逻辑回归用于解决二分类问题 1.1 Sigmoid函数 sigmoid函数在神经网络中如何起作用&#xff1f;详见本人笔记&#xff1a;机器学习和AI底层逻辑 复杂非线性分类->多个线段->每个线段是叠加而来的->sigmoid函…