異步IO框架io_uring實(shí)現(xiàn)TCP服務(wù)器的詳細(xì)過(guò)程
一、io_uring介紹
io_uring是 Linux 于 2019 年加入到內(nèi)核的一種新型異步 I/O 模型,io_uring 主要為了解決 原生AIO(Native AIO) 存在的一些不足之處。下面介紹一下原生 AIO 的不足之處:
系統(tǒng)調(diào)用開(kāi)銷(xiāo)大:提交 I/O 操作和獲取 I/O 操作的結(jié)果都需要通過(guò)系統(tǒng)調(diào)用完成,而觸發(fā)系統(tǒng)調(diào)用時(shí),需求進(jìn)行上下文切換。在高 IOPS(Input/Output Per Second)的情況下,進(jìn)行上下文切換也會(huì)消耗大量的CPU時(shí)間。
僅支持 Direct I/O(直接I/O):在使用原生 AIO 的時(shí)候,只能指定
O_DIRECT標(biāo)識(shí)位(直接 I/O),不能借助文件系統(tǒng)的頁(yè)緩存(page cache)來(lái)緩存當(dāng)前的 I/O 請(qǐng)求。對(duì)數(shù)據(jù)有大小對(duì)齊限制:所有寫(xiě)操作的數(shù)據(jù)大小必須是文件系統(tǒng)塊大?。ㄒ话銥?KB)的倍數(shù),而且要與內(nèi)存頁(yè)大小對(duì)齊。
數(shù)據(jù)拷貝開(kāi)銷(xiāo)大:每個(gè) I/O 提交需要拷貝 64+8 字節(jié),每個(gè) I/O 完成結(jié)果需要拷貝 32 字節(jié),總共 104 字節(jié)的拷貝。這個(gè)拷貝開(kāi)銷(xiāo)是否可以承受,和單次 I/O 大小有關(guān):如果需要發(fā)送的 I/O 本身就很大,相較之下,這點(diǎn)消耗可以忽略。而在大量小 I/O 的場(chǎng)景下,這樣的拷貝影響比較大。
對(duì)應(yīng)io_uring就有他的優(yōu)勢(shì):
- 減少系統(tǒng)調(diào)用:io_uring通過(guò)內(nèi)核態(tài)和用戶態(tài)共享內(nèi)存的方式進(jìn)行通信。如下圖:
 

用戶態(tài)和內(nèi)核態(tài)之間通過(guò)共享內(nèi)存維護(hù)了三部分:提交隊(duì)列、完成隊(duì)列、提交隊(duì)列表項(xiàng)數(shù)組。
提交隊(duì)列是一整塊連續(xù)的內(nèi)存空間存儲(chǔ)的環(huán)形隊(duì)列,用于存放將要執(zhí)行I/O操作的數(shù)據(jù)。
完成隊(duì)列也是一整塊連續(xù)的內(nèi)存空間存儲(chǔ)的環(huán)形隊(duì)列,其中存放了I/O操作完成返回的結(jié)果。
提交隊(duì)列表項(xiàng)數(shù)組是以數(shù)組形式將要執(zhí)行的I/O操作組織在一起,提交隊(duì)列完成隊(duì)列分別通過(guò)指針指向?qū)?yīng)表項(xiàng)(內(nèi)部通過(guò)偏移量實(shí)現(xiàn))完成對(duì)應(yīng)操作。如下圖所示:

提交隊(duì)列具體實(shí)現(xiàn)為:io_uring_sq結(jié)構(gòu)體。
struct io_uring_sq {
    unsigned *khead;
    unsigned *ktail;
    unsigned *kflags;
    unsigned *kdropped;
    unsigned *array;
    struct io_uring_sqe *sqes;
?
    unsigned sqe_head;
    unsigned sqe_tail;
?
    size_t ring_sz;
    void *ring_ptr;
?
    unsigned ring_mask;
    unsigned ring_entries;
?
    unsigned pad[2];
};提交隊(duì)列通過(guò)struct io_uring_sqe *sqes提交隊(duì)列表項(xiàng)數(shù)組,使用khead、ktail指向?qū)?yīng)隊(duì)頭和對(duì)尾完成應(yīng)用層提交的IO任務(wù)的處理。同理完成隊(duì)列也是如此,不過(guò)是由內(nèi)核態(tài)將對(duì)應(yīng)IO事件執(zhí)行完成后將結(jié)果寫(xiě)入到對(duì)應(yīng)表項(xiàng)。應(yīng)用層通過(guò)完成隊(duì)列表項(xiàng)即可獲取到最終結(jié)果。
整體流程為:
請(qǐng)求時(shí):1、應(yīng)用創(chuàng)建SQE,更新SQ tail 2、內(nèi)核消費(fèi)SQE,更新SQ head。內(nèi)核開(kāi)始處理任務(wù),處理完成后:1、內(nèi)核為完成的一個(gè)或多個(gè)請(qǐng)求創(chuàng)建CQE,更新CQ tail 2、應(yīng)用層消費(fèi)CQE,更新CQ head。
二、liburing安裝
上文介紹的io_uring均為內(nèi)核層支持,在內(nèi)核層提供了三個(gè)API:
io_uring_setup(2)
io_uring_register(2)
io_uring_enter(2)
為方便使用直接安裝liburing即可在應(yīng)用層使用io_uring。liburing為作者Axboe封裝好的用戶態(tài)庫(kù)。
實(shí)驗(yàn)環(huán)境:vmware 17安裝ubuntu22.04 、內(nèi)核版本:6.8.0-60-generic
選用源碼安裝方式,安裝liburing。
1、獲取源碼:git clone https://github.com/axboe/liburing.git
2、編譯:
cd liburing-master ./configure make -j sudo make install
三、代碼實(shí)現(xiàn)
#include <stdio.h>
#include <liburing.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
?
#define BUFFER_LENGTH   1024
#define ENTRIES_LENGTH  1024
?
#define EVENT_ACCEPT    0
#define EVENT_READ      1
#define EVENT_WRITE     2
?
// io事件信息
struct req_info{
    int fd;
    int event;
};
?
// 初始化tcp fd
int init_server(unsigned int port){
    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(struct sockaddr_in));
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    if(-1 == bind(socket_fd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr))){
        perror("bind error!\n");
    }
    listen(socket_fd, 10);
    return socket_fd;
}
?
// 設(shè)置accept事件
int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr* addr, socklen_t *addrlen, int flags){
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
?
    struct req_info accept_info = {
        .fd = sockfd,
        .event = EVENT_ACCEPT
    };
?
    memcpy(&sqe->user_data, &accept_info, sizeof(struct req_info));
    io_uring_prep_accept(sqe, sockfd, addr, addrlen, flags);
?
    return 0;
}
?
// 設(shè)置recv事件
int set_event_recv(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags){
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
?
    struct req_info recv_info = {
        .fd = sockfd,
        .event = EVENT_READ
    };
?
    memcpy(&sqe->user_data, &recv_info, sizeof(struct req_info));
    io_uring_prep_recv(sqe, sockfd, buf, len, flags);
?
    return 0;
}
?
// 設(shè)置send事件
int set_event_send(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags){
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
?
    struct req_info send_info = {
        .fd = sockfd,
        .event = EVENT_WRITE
    };
?
    memcpy(&sqe->user_data, &send_info, sizeof(struct req_info));
    io_uring_prep_send(sqe, sockfd, buf, len, flags);
?
    return 0;
}
?
int main(int argc, void *argv[]){
    unsigned int port = 9999;
    int sockfd = init_server(port);
?
    struct io_uring_params params;
    memset(¶ms, 0, sizeof(struct io_uring_params));
    // 初始化io_uring 其中包含了sq和cq
    struct io_uring ring;
?
    io_uring_queue_init_params(ENTRIES_LENGTH, &ring, ¶ms);
?
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);
?
    while(1){
        char buffer[BUFFER_LENGTH] = {0};
?
        // 事件提交
        io_uring_submit(&ring);
?
        // 等待事件完成,其他開(kāi)發(fā)場(chǎng)景下非必要情況無(wú)需等待
        struct io_uring_cqe *cqe;
        io_uring_wait_cqe(&ring, &cqe);
?
        // 獲取完成事件
        struct io_uring_cqe *cqe_list[128];
        int nready = io_uring_peek_batch_cqe(&ring, cqe_list, 128);
        // echo邏輯處理
        for(int i = 0; i < nready; ++i){
            struct io_uring_cqe *entries = cqe_list[i];
            struct req_info result;
            memcpy(&result, &entries->user_data, sizeof(struct req_info));
            if(result.event == EVENT_ACCEPT){
                printf("accept client\n");
                set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);
?
                int connfd = entries->res;
?
                set_event_recv(&ring, connfd, buffer, BUFFER_LENGTH, 0);
            }else if(result.event == EVENT_READ){
                int connfd = entries->res;
                if(connfd == 0){
                    printf("close fd:%d\n", result.fd);
                    close(result.fd);
                }else if(connfd > 0){
                    printf("recv: len:%d, data:%s\n", connfd, buffer);
                    set_event_send(&ring, result.fd, buffer, connfd, 0);
                }else{
                    printf("error recv!\n");
                    close(result.fd);
                }
            }else if(result.event == EVENT_WRITE){
                int ret = entries->res;
                printf("set_event_send ret: %d, %s\n", ret, buffer);
                set_event_recv(&ring, result.fd, buffer, BUFFER_LENGTH, 0);
            }
        }
?
        // 清除完成隊(duì)列中完成表項(xiàng),以免事件重復(fù)處理
        io_uring_cq_advance(&ring, nready);
    }
?
    return 0;
}更多內(nèi)容可參考:0voice · GitHub
到此這篇關(guān)于異步IO框架io_uring實(shí)現(xiàn)TCP服務(wù)器的文章就介紹到這了,更多相關(guān)io_uring TCP服務(wù)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
 生產(chǎn)服務(wù)器突然本機(jī)無(wú)法訪問(wèn)本機(jī)IP的端口的問(wèn)題及解決方法
生產(chǎn)服務(wù)器突然無(wú)法訪問(wèn)自己本機(jī)IP地址的端口,通過(guò)localhost或者127.0.0.1都可以正常訪問(wèn),本文給大家分享生產(chǎn)服務(wù)器突然本機(jī)無(wú)法訪問(wèn)本機(jī)IP的端口的問(wèn)題及解決方法,感興趣的朋友一起看看吧2023-11-11
 使用Npcap庫(kù)開(kāi)發(fā)簡(jiǎn)單的掃描功能
nmap(Network?Mapper)是一款開(kāi)源免費(fèi)的針對(duì)大型網(wǎng)絡(luò)的端口掃描工具,nmap可以檢測(cè)目標(biāo)主機(jī)是否在線、主機(jī)端口開(kāi)放情況、檢測(cè)主機(jī)運(yùn)行的服務(wù)類(lèi)型及版本信息、檢測(cè)操作系統(tǒng)與設(shè)備類(lèi)型等信息,本文主要介紹nmap工具安裝和基本使用方法,2024-08-08
 Win2003 Server DHCP服務(wù)器安裝圖解教程
為了節(jié)省IP地址資源,IP地址采用了DHCP自動(dòng)分配方式2012-10-10
 cwRsync提示password file must be owned by root when running as
今天在配置服務(wù)器的時(shí)候,用了rsync4.10版本,客戶端是2003服務(wù)器端是2008 r2 同步的時(shí)候提示password file must be owned by root when running as root問(wèn)題,以前用老版本的時(shí)候沒(méi)見(jiàn)過(guò),還好看了下面的文章解決了,特分享下2015-08-08
 rsync?server服務(wù)端配置文件?rsyncd.conf參數(shù)詳解
前兩篇文章我們已經(jīng)探討了rsync的原理及基本使用,今天我們來(lái)介紹一下rsync?server端的配置文件,如果你還沒(méi)有學(xué)習(xí)rsync的原理及安裝使用,那么就可以參考下面的文章2024-06-06
 基于HTTP協(xié)議實(shí)現(xiàn)的小型web服務(wù)器的方法
這篇文章主要介紹了基于HTTP協(xié)議實(shí)現(xiàn)的小型web服務(wù)器的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2007-08-08
 ssh更改默認(rèn)端口號(hào)及實(shí)現(xiàn)免密碼遠(yuǎn)程登錄
這篇文章主要介紹了ssh更改默認(rèn)端口號(hào)及實(shí)現(xiàn)免密碼遠(yuǎn)程登錄的相關(guān)資料,需要的朋友可以參考下2017-10-10
 如何恢復(fù)ubuntu20.04默認(rèn)桌面管理器
這篇文章主要介紹了如何恢復(fù)ubuntu20.04默認(rèn)桌面管理器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09
 ubuntu20.04安裝unity-tweak-tools啟動(dòng)時(shí)遇到錯(cuò)誤的解決
在Ubuntu系統(tǒng)中,安裝Unity Tweak Tool時(shí)可能會(huì)遇到schemacom.canonical.Unity.ApplicationsLens未安裝的錯(cuò)誤,解決這個(gè)問(wèn)題的辦法是安裝缺失的依賴包,執(zhí)行命令`sudo apt-get install unity-lens-applications` 和 `sudo apt-get install unity-lens-files`2024-09-09

