linux学习笔记 linux下I/O接口的演变之路

Posted 坐望云起

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux学习笔记 linux下I/O接口的演变之路相关的知识,希望对你有一定的参考价值。

一、概述

        Linux(实际上是 Unix)的一个基本概念是 Unix/Linux 中的一切都是文件的规则。每个进程都有一个指向文件、套接字、设备和其他操作系统对象的文件描述符表。

        与许多 IO 源一起工作的典型系统有一个初始化阶段,然后进入某种待机模式——等待任何客户端发送请求并响应它。

1、用户和内核模式

        在任何现代操作系统中,CPU 实际上都在两种截然不同的模式下花费时间:

        内核模式

        在内核模式下,执行代码可以完全且不受限制地访问底层硬件。它可以执行任何 CPU 指令并引用任何内存地址。内核模式通常保留给操作系统的最低级别、最受信任的功能。内核模式下的崩溃是灾难性的;他们将停止整个 PC。

        用户模式

        ​​在用户模式下,执行代码无法直接访问硬件或引用内存。在用户模式下运行的代码必须委托给系统 API 来访问硬件或内存。由于这种隔离提供的保护,用户模式下的崩溃总是可以恢复的。在您的计算机上运行的大部分代码都将在用户模式下执行。

2、进程切换

        进程切换是操作系统调度程序从一个正在运行的程序更改为另一个。这需要保存当前执行程序的所有状态,包括它的寄存器状态、相关的内核状态以及它的所有虚拟内存配置。

        一个进程切换将通过以下更改:

        保存处理器上下文,包括程序计数器和其他寄存器
        更新过程控制块 (PCB) 信息
        将PCB的进程移动到相应的队列中,如就绪队列、事件块队列等队列
        选择另一个进程执行并更新其 PCB
        更新内存中的数据结构
        恢复 PCB 上下文

3、阻塞进程

        阻塞进程通常在等待一个事件,例如释放信号量或消息到达其消息队列。在多任务系统中,此类进程被期望通过系统调用通知调度程序它要等待,以便可以将它们从活动调度队列中删除,直到事件发生。在等待期间继续运行的进程(即,在紧密循环中不断轮询事件)被称为忙等待,这是不可取的,因为它浪费了可用于其他进程的时钟周期。当进程进入阻塞状态时,不会占用CPU资源。

4、缓冲 I/O

        缓冲的输出流会将写入结果累积到一个中间缓冲区中,仅当累积了足够的数据(或请求了 flush())时才将其发送到 OS 文件系统。这减少了文件系统调用的数量。由于在大多数平台上文件系统调用可能很昂贵(与短 memcpy 相比),因此在执行大量小型写入时,缓冲输出是一种净赢。当您已经有大缓冲区要发送时,无缓冲输出通常会更好——复制到中间缓冲区不会进一步减少操作系统调用的数量,并且会引入额外的工作。数据在传输过程中需要进行数据复制操作,操作操作带来的CPU内存开销非常高。

5、文件描述符 (FD)

        在 Unix 和相关计算机操作系统中,文件描述符(FD,不太常见的 fildes)是用于访问文件或其他输入/输出资源(例如管道或网络套接字)的抽象指示符(句柄)。文件描述符构成 POSIX 应用程序编程接口的一部分。它是一个非负的索引值,很多底层程序经常基于它。

        当发生读取操作时,数据会经历两个阶段:

        1、等待数据准备好

        2、将数据从内核复制到进程

        因为这两个阶段,Linux系统产生了以下5种网络模型:

        阻塞 I/O

        非阻塞 I/O

        I/O 多路复用

        信号驱动I/O

        异步 I/O

        其中I/O 多路复用貌似是最常用的方式。

二、使用 fork()

        Fork()创建一个与其父进程同步运行的新子进程,Fork 比多线程效率低。在使用fork的服务器中,一个进程对应一个客户端,即使有性能空间,内存也会耗尽,响应性能明显下降。最终导致著名的 C10K 问题。

#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  while (1) 
    // Check new connection
    struct sockaddr_in client_addr;
    socklen_t len_client = sizeof(client_addr);

    int conn_fd;
    if ((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr,
                          &len_client)) < 0) 
      fprintf(stderr, "Error: accept\\n");
      return EXIT_FAILURE;
    

    printf("Accept socket %d (%s : %hu)\\n", conn_fd,
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    pid_t pid = fork();

    if (pid < 0) 
      fprintf(stderr, "Error: fork\\n");
      return EXIT_FAILURE;
    

    if (pid == 0) 
      // child
      char buf[BUF_SIZE];
      close(listen_fd);
      while (1) 
        ssize_t n = read(conn_fd, buf, BUF_SIZE);

        if (n < 0) 
          fprintf(stderr, "Error: read from socket %d\\n", conn_fd);
          close(conn_fd);
          exit(-1);
         else if (n == 0)   // connection closed by client
          printf("Close socket %d\\n", conn_fd);
          close(conn_fd);
          exit(0);
         else 
          printf("Read %zu bytes from socket %d\\n", n, conn_fd);
          write_n(conn_fd, buf, n);
        
      
     else 
      // parent
      close(conn_fd);
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

三、I/O多路复用

1、select 

        计算复杂度为 O(n),因为必须线性搜索描述符(select函数仅仅知道有几个I/O事件发生了,但并不知道具体是哪几个socket连接有I/O事件,还需要轮询去查找,时间复杂度为O(n),处理的请求数越多,所消耗的时间越长。)。特点是可以管理的描述符数量有上限。

        select() 只能监视小于 FD_SETSIZE (1024) 的文件描述符数量——对于许多现代应用程序来说,这是一个不合理的下限——并且这个限制不会改变。所有现代应用程序都应该使用 poll(2) 或 epoll(7 ),不受此限制。

        参考代码

#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE];
  fd_set fds;
  FD_ZERO(&fds);
  int clients[N_CLIENT];
  for (int i = 0; i < N_CLIENT; i++) 
    clients[i] = DISABLE;
  
  memset(&fds, 0, sizeof(fds));

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  fprintf(stderr, "listen on port %d\\n", port);

  FD_SET(listen_fd, &fds);

  int max_fd = listen_fd;  // max fd
  int max_i = 0;           // max client into clients[] array

  while (1) 
    FD_ZERO(&fds);
    FD_SET(listen_fd, &fds);
    for (int i = 0; i < N_CLIENT; i++) 
      if (clients[i] != DISABLE) 
        FD_SET(clients[i], &fds);
      
    

    int res_select = select(max_fd + 1, &fds, NULL, NULL, NULL);

    if (res_select < 0) 
      fprintf(stderr, "Error: select");
      return EXIT_FAILURE;
    

    // Check new connection
    if (FD_ISSET(listen_fd, &fds)) 
      struct sockaddr_in client_addr;
      socklen_t len_client = sizeof(client_addr);

      int connfd;
      if ((connfd = accept(listen_fd, (struct sockaddr *)&client_addr,
                           &len_client)) < 0) 
        fprintf(stderr, "Error: accept\\n");
        return EXIT_FAILURE;
      

      printf("Accept socket %d (%s : %hu)\\n", connfd,
             inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

      // Save client socket into clients array
      int i;
      for (i = 0; i < N_CLIENT; i++) 
        if (clients[i] == DISABLE) 
          clients[i] = connfd;
          break;
        
      

      // No enough space in clients array
      if (i == N_CLIENT) 
        fprintf(stderr, "Error: too many clients\\n");
        close(connfd);
      

      if (i > max_i) 
        max_i = i;
      
      if (connfd > max_fd) 
        max_fd = connfd;
      
    

    // Check all clients to read data
    for (int i = 0; i <= max_i; i++) 
      int sock_fd;
      if ((sock_fd = clients[i]) == DISABLE) 
        continue;
      

      // If the client is readable or errors occur
      ssize_t n = read(sock_fd, buf, BUF_SIZE);
      if (n < 0) 
        fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
        close(sock_fd);
        clients[i] = DISABLE;
       else if (n == 0)   // connection closed by client
        printf("Close socket %d\\n", sock_fd);
        close(sock_fd);
        clients[i] = DISABLE;
       else 
        printf("Read %zu bytes from socket %d\\n", n, sock_fd);
        write_n(sock_fd, buf, n);
        write_n(1, buf, n);
      
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

2、poll

        它和select的功能几乎一样,同样的计算量只需要O(n)。但是,它可以被认为是向上兼容的,因为对要管理的描述符没有限制。

        参考代码

#include <arpa/inet.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE];
  struct pollfd clients[N_CLIENT];

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  clients[0].fd = listen_fd;
  clients[0].events = POLLIN;

  for (int i = 1; i < N_CLIENT; i++) 
    clients[i].fd = DISABLE;
  
  int max_i = 0;  // max index into clients[] array

  while (1) 
    int n_ready = poll(clients, max_i + 1, INF_TIME);

    // Time out
    if (n_ready == 0) 
      continue;
    

    // Error poll
    if (n_ready < 0) 
      fprintf(stderr, "Error: poll %d\\n", errno);
      return errno;
    

    // Check new connection
    if (clients[0].revents & POLLIN) 
      struct sockaddr_in client_addr;
      socklen_t len_client = sizeof(client_addr);

      int connfd;
      if ((connfd = accept(listen_fd, (struct sockaddr *)&client_addr,
                           &len_client)) < 0) 
        fprintf(stderr, "Error: accept\\n");
        return EXIT_FAILURE;
      

      printf("Accept socket %d (%s : %hu)\\n", connfd,
             inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

      // Save client socket into clients array
      int i;
      for (i = 0; i < N_CLIENT; i++) 
        if (clients[i].fd == DISABLE) 
          clients[i].fd = connfd;
          break;
        
      

      // No enough space in clients array
      if (i == N_CLIENT) 
        fprintf(stderr, "Error: too many clients\\n");
        close(connfd);
      

      clients[i].events = POLLIN;

      if (i > max_i) 
        max_i = i;
      
    

    // Check all clients to read data
    for (int i = 1; i <= max_i; i++) 
      int sock_fd;
      if ((sock_fd = clients[i].fd) == DISABLE) 
        continue;
      

      // If the client is readable or errors occur
      if (clients[i].revents & (POLLIN | POLLERR)) 
        ssize_t n = read(sock_fd, buf, BUF_SIZE);

        if (n < 0) 
          fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
          close(sock_fd);
          clients[i].fd = DISABLE;
         else if (n == 0)   // connection closed by client
          printf("Close socket %d\\n", sock_fd);
          close(sock_fd);
          clients[i].fd = DISABLE;
         else 
          printf("Read %zu bytes from socket %d\\n", n, sock_fd);
          write_n(sock_fd, buf, n);
          write_n(1, buf, n);
        
      
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

3、epoll

        select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。

        由于描述符的状态是在内核空间监控的,所以直接返回修改后的描述符,所以计算量为O(1),可以高速处理。

        系统调用帮助我们在内核中创建和管理上下文。我们将任务分为 3 个步骤:

#include <sys/epoll.h>

# 调用epoll_create()创建一个ep对象,即红黑树的根节点,返回一个文件句柄
int epoll_create(int size);

# 调用epoll_ctl()向这个ep对象(红黑树)中添加、删除、修改感兴趣的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

# 调用epoll_wait()等待,当有事件发生时网卡驱动会调用fd上注册的函数并将该fd添加到rdlist中,解除阻塞
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

        参考代码

#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE];

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  // Create epoll
  int epfd = epoll_create1(0);
  if (epfd < 0) 
    fprintf(stderr, "Error: epoll create\\n");
    close(listen_fd);
    return EXIT_FAILURE;
  

  struct epoll_event listen_ev;
  memset(&listen_ev, 0, sizeof(listen_ev));
  listen_ev.events = EPOLLIN;
  listen_ev.data.fd = listen_fd;
  if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &listen_ev) < 0) 
    fprintf(stderr, "Error: epoll ctl add listen\\n");
    close(listen_fd);
    return EXIT_FAILURE;
  

  struct epoll_event evs[N_CLIENT];
  while (1) 
    // Wait epoll listener
    int n_fds = epoll_wait(epfd, evs, N_CLIENT, -1);

    // Error epoll
    if (n_fds < 0) 
      fprintf(stderr, "Error: epoll wait\\n");
      close(listen_fd);
      return EXIT_FAILURE;
    

    for (int i = 0; i < n_fds; i++) 
      if (evs[i].data.fd == listen_fd)   // Add epoll listener
        struct sockaddr_in client_addr;
        socklen_t len_client = sizeof(client_addr);

        int conn_fd;
        if ((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr,
                              &len_client)) < 0) 
          fprintf(stderr, "Error: accept\\n");
          return EXIT_FAILURE;
        

        printf("Accept socket %d (%s : %hu)\\n", conn_fd,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        struct epoll_event conn_ev;
        memset(&conn_ev, 0, sizeof(listen_ev));
        conn_ev.events = EPOLLIN;
        conn_ev.data.fd = conn_fd;
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &conn_ev) < 0) 
          fprintf(stderr, "Error: epoll ctl add listen\\n");
          close(listen_fd);
          return EXIT_FAILURE;
        
       else if (evs[i].events & EPOLLIN)   // Read data from client
        int sock_fd = evs[i].data.fd;
        ssize_t n = read(sock_fd, buf, BUF_SIZE);

        if (n < 0) 
          fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
          close(sock_fd);
         else if (n == 0)   // connection closed by client
          printf("Close socket %d\\n", sock_fd);
          struct epoll_event sock_ev;
          memset(&sock_ev, 0, sizeof(listen_ev));
          sock_ev.events = EPOLLIN;
          sock_ev.data.fd = sock_fd;
          if (epoll_ctl(epfd, EPOLL_CTL_DEL, sock_fd, &sock_ev) < 0) 
            fprintf(stderr, "Error: epoll ctl dell\\n");
            close(listen_fd);
            return EXIT_FAILURE;
          
          close(sock_fd);
         else 
          printf("Read %zu bytes from socket %d\\n", n, sock_fd);
          write_n(sock_fd, buf, n);
          write_n(1, buf, n);
        
      
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

四、io_uring

        io_uring是由 Facebook 的 Jens Axboe 创建的用于 Linux 的新异步 I/O API。它旨在提供一个不受当前select、poll、epoll或aio系列系统调用限制的 API。

        从linux kernel 5.1 开始提供一个新的异步 I/O API。处理是通过操纵两个环形缓冲区,提交队列(SQ)和完成队列(CQ )来执行的。例如,要执行从套接字接收消息,通过将操作码提交队列条目 (SQE) 注入 SQ 来异步执行处理。这些进程的完成通知也可以通过从CQ接收完成队列条目 (CQE) 来实现。

        io_uring 的用户态 API

io_uring_setup(2)

io_uring_enter(2)

io_uring_register(2)

        一个名为 liburing 的库为 io_uring 提供了一个简单的接口。

#include <arpa/inet.h>
#include <errno.h>
#include <liburing.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define N_ENTRY 2048
#define GID 1

int listen_fd;

enum 
  ACCEPT,
  READ,
  WRITE,
;

typedef struct UserData 
  __u32 fd;
  __u16 type;
 UserData;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE] = 0;

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr, client_addr;
  socklen_t client_len = sizeof(client_addr);
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  // Initialize io_uring
  struct io_uring ring;
  struct io_uring_sqe *sqe;
  struct io_uring_cqe *cqe;
  int init_ret = io_uring_queue_init(N_ENTRY, &ring, 0);
  if (init_ret < 0) 
    fprintf(stderr, "Error: init io_uring queue %d\\n", init_ret);
    close(listen_fd);
    return EXIT_FAILURE;
  

  // Setup first accept
  sqe = io_uring_get_sqe(&ring);
  io_uring_prep_accept(sqe, listen_fd, (struct sockaddr *)&client_addr,
                       &client_len, 0);
  io_uring_sqe_set_flags(sqe, 0);
  UserData conn_info = 
      .fd = listen_fd,
      .type = ACCEPT,
  ;
  memcpy(&sqe->user_data, &conn_info, sizeof(conn_info));

  while (1) 
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    struct UserData conn_info;
    memcpy(&conn_info, &cqe->user_data, sizeof(conn_info));
    int type = conn_info.type;

    if (cqe->res == -ENOBUFS) 
      fprintf(stderr, "Error: no buffer %d\\n", cqe->res);
      close(listen_fd);
      return EXIT_FAILURE;
     else if (type == ACCEPT) 
      int conn_fd = cqe->res;
      printf("Accept socket %d \\n", conn_fd);
      if (conn_fd >= 0)   // no error
        // Read from client
        sqe = io_uring_get_sqe(&ring);
        io_uring_prep_recv(sqe, conn_fd, buf, BUF_SIZE, 0);

        UserData read_info = 
            .fd = conn_fd,
            .type = READ,
        ;
        memcpy(&sqe->user_data, &read_info, sizeof(read_info));
      

      // Add new client
      sqe = io_uring_get_sqe(&ring);
      io_uring_prep_accept(sqe, listen_fd, (struct sockaddr *)&client_addr,
                           &client_len, 0);
      io_uring_sqe_set_flags(sqe, 0);
      UserData conn_info = 
          .fd = listen_fd,
          .type = ACCEPT,
      ;
      memcpy(&sqe->user_data, &conn_info, sizeof(conn_info));
     else if (type == READ) 
      int n_byte = cqe->res;
      if (cqe->res <= 0)   // connection closed by client
        printf("Close socket %d\\n", conn_info.fd);
        close(conn_info.fd);
       else 
        // Add Write
        printf("Read %d bytes from socket %d\\n", n_byte, conn_info.fd);
        sqe = io_uring_get_sqe(&ring);
        io_uring_prep_send(sqe, conn_info.fd, buf, n_byte, 0);
        write_n(1, buf, n_byte);  // output stdout
        io_uring_sqe_set_flags(sqe, 0);
        UserData write_info = 
            .fd = conn_info.fd,
            .type = WRITE,
        ;
        memcpy(&sqe->user_data, &write_info, sizeof(write_info));
      
     else if (type == WRITE) 
      // Add read
      sqe = io_uring_get_sqe(&ring);
      io_uring_prep_recv(sqe, conn_info.fd, buf, BUF_SIZE, 0);

      UserData read_info = 
          .fd = conn_info.fd,
          .type = READ,
      ;
      memcpy(&sqe->user_data, &read_info, sizeof(read_info));
    

    io_uring_cqe_seen(&ring, cqe);
  

  close(listen_fd);
  return EXIT_SUCCESS;

以上是关于linux学习笔记 linux下I/O接口的演变之路的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统学习笔记:文件I/O

linux开发笔记

《Linux内核设计与实现》学习笔记——I/O调度算法

I/O模型演变

I/O模型演变

I/O模型演变