当前位置:首页 > 科技  > 软件

聊聊 Libuv 最近引入的 io_uring

来源: 责编: 时间:2023-10-20 10:02:31 212观看
导读io_uring 是 Linux 下高性能的异步 IO 框架,网上很多相关资料,我之前也初步分析了一下它的实现,有兴趣的可以查看 https://zhuanlan.zhihu.com/p/387620810。Libuv 中最近加入了对 io_uring 的支持,那么为什么要把它引入

cyR28资讯网——每日最新资讯28at.com

io_uring 是 Linux 下高性能的异步 IO 框架,网上很多相关资料,我之前也初步分析了一下它的实现,有兴趣的可以查看 https://zhuanlan.zhihu.com/p/387620810。cyR28资讯网——每日最新资讯28at.com

Libuv 中最近加入了对 io_uring 的支持,那么为什么要把它引入 Libuv 呢?因为 epoll 不支持普通文件的 Poll 能力,所以在 Libuv 中,异步文件 IO 操作需要通过线程池来实现,具体来说就是当用户发起一个异步文件 IO 操作时,Libuv 会把这个操作放到线程池中,当子线程处理这个任务时,会执行一个阻塞式的系统调用,这个系统调用会引起线程阻塞,从而导致这个线程被消耗掉了,当 IO 操作完成后,子线程就会被唤醒,子线程再通过主线程去执行用户的回调。在 Libuv 早期的实现中,如果执行比较慢的任务过多就会把线程池中的线程消耗完,从而导致执行比较快的 IO 操作需要等待很长时间,一个例子就是 DNS 解析会阻塞文件 IO 任务。而 io_uring 可以支持普通文件 IO(当然能力不仅于此),不再需要借助线程池的能力,目前 Libuv 中部分异步文件 IO 操作已经替换成 io_uring(需要通过环境变量开启),下面来看看它的实现。cyR28资讯网——每日最新资讯28at.com

原生 io_uring 的使用比较复杂,通常需要借助 liburing 库,但是 Libuv 中可能为了减少对第三方库的依赖,实现上使用原生的方式。cyR28资讯网——每日最新资讯28at.com

io_uring 初始化cyR28资讯网——每日最新资讯28at.com

在 Libuv 初始化时会进行 io_uring 的初始化。cyR28资讯网——每日最新资讯28at.com

uv__iou_init(loop->backend_fd, &lfields->iou, 64, UV__IORING_SETUP_SQPOLL);

lfields->iou 为 io_uring 核心结构体,UVIORING_SETUP_SQPOLL 设置内核创建线程轮询是否有任务需要处理(用户层设置),接着看看 uviou_init。cyR28资讯网——每日最新资讯28at.com

static void uv__iou_init(int epollfd,struct uv__iou* iou,uint32_t entries,uint32_t flags) {struct uv__io_uring_params params;struct epoll_event e;size_t cqlen;size_t sqlen;size_t maxlen;size_t sqelen;uint32_t i;char* sq;char* sqe;int ringfd;memset(¶ms, 0, sizeof(params));params.flags = flags;// UV__IORING_SETUP_SQPOLL 模式下,设置多久没有任务提交则内核线程进入 sleep 状态if (flags & UV__IORING_SETUP_SQPOLL)params.sq_thread_idle = 10;  /* milliseconds /// 调用系统调用初始化 io_uringringfd = uv__io_uring_setup(entries, ¶ms);// 映射到内核发送 / 完成队列的内存,用户层和内核可以共同操作这个队列sq = mmap(0,maxlen,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_POPULATE,ringfd,0);  /sqe = mmap(0,sqelen,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_POPULATE,ringfd,0x10000000ull);  /* IORING_OFF_SQES */memset(&e, 0, sizeof(e));e.events = POLLIN;e.data.fd = ringfd;// 注册等待可读事件,io_uring 中有任务完成后就会通过 epollepoll_ctl(epollfd, EPOLL_CTL_ADD, ringfd, &e);// 初始化 io_uring 结构体iou->sqhead = (uint32_t*) (sq + params.sq_off.head);iou->sqtail = (uint32_t*) (sq + params.sq_off.tail);iou->sqmask = (uint32_t) (sq + params.sq_off.ring_mask);iou->sqarray = (uint32_t*) (sq + params.sq_off.array);iou->sqflags = (uint32_t*) (sq + params.sq_off.flags);iou->cqhead = (uint32_t*) (sq + params.cq_off.head);iou->cqtail = (uint32_t*) (sq + params.cq_off.tail);iou->cqmask = (uint32_t) (sq + params.cq_off.ring_mask);iou->sq = sq;iou->cqe = sq + params.cq_off.cqes;iou->sqe = sqe;iou->sqlen = sqlen;iou->cqlen = cqlen;iou->maxlen = maxlen;iou->sqelen = sqelen;iou->ringfd = ringfd;iou->in_flight = 0;iou->flags = 0;}

uv__iou_init 完成了 io_uring 的初始化,并且把 io_uring 对应的 fd 注册到 epoll,当 io_uring 有任务完成时,就可以通过 epoll 感知到。接着就可以使用 io_uring 了。cyR28资讯网——每日最新资讯28at.com

提交异步任务

下面看一个异步文件 IO 的操作。cyR28资讯网——每日最新资讯28at.com

int uv_fs_open(uv_loop_t* loop,uv_fs_t* req,const char* path,int flags,int mode,uv_fs_cb cb) {INIT(OPEN);PATH;req->flags = flags;req->mode = mode;if (cb != NULL)if (uv__iou_fs_open(loop, req))return 0;POST;}

uv_fs_open 可以以异步的方式打开一个文件,之前时通过线程池实现的,加入 io_uring 后,就会多了一层拦截,来看看 uv__iou_fs_open。cyR28资讯网——每日最新资讯28at.com

cyR28资讯网——每日最新资讯28at.com

int uv__iou_fs_open(uv_loop_t* loop, uv_fs_t* req) {struct uv__io_uring_sqe* sqe;struct uv__iou* iou;// 获取 io_uring 结构体iou = &uv__get_internal_fields(loop)->iou;// 获取一个任务节点,任务节点会和 req 互相关联,回调时会用到sqe = uv__iou_get_sqe(iou, loop, req);// 设置操作上下文sqe->addr = (uintptr_t) req->path;sqe->fd = AT_FDCWD;sqe->len = req->mode;// 设置操作类型sqe->opcode = UV__IORING_OP_OPENAT;sqe->open_flags = req->flags | O_CLOEXEC;// 提交任务uv__iou_submit(iou);return 1;}

uviou_fs_open 中有两个核心逻辑 uviou_get_sqe 和 uviou_submit,首先来看 uviou_get_sqe。cyR28资讯网——每日最新资讯28at.com

cyR28资讯网——每日最新资讯28at.com

static struct uv__io_uring_sqe* uv__iou_get_sqe(struct uv__iou* iou,uv_loop_t* loop,uv_fs_t* req) {struct uv__io_uring_sqe* sqe;uint32_t head;uint32_t tail;uint32_t mask;uint32_t slot;if (iou->ringfd == -1)return NULL;head = atomic_load_explicit((_Atomic uint32_t*) iou->sqhead,memory_order_acquire);tail = *iou->sqtail;mask = iou->sqmask;slot = tail & mask;sqe = iou->sqe;// 从请求队列中获取一个节点sqe = &sqe[slot];memset(sqe, 0, sizeof(*sqe));// 任务节点关联到 req,回调时需要使用sqe->user_data = (uintptr_t) req;req->work_req.loop = loop;req->work_req.work = NULL;req->work_req.done = NULL;uv__queue_init(&req->work_req.wq);uv__req_register(loop, req);iou->in_flight++;return sqe;}

uviou_get_sqe 主要是从任务队列中获取一个空闲节点并关联上请求上下文结构体,uviou_get_sqe 的调用方需要设置操作上下文,比如操作类型,操作的 fd 等。通过 uviou_get_sqe 获取任务节点并设置了操作上下文后,这个任务就会自动被操作系统感知。因为 Libuv 是使用了 UVIORING_SETUP_SQPOLL 模式,所以还需要判断这时候内核轮训线程是否处于睡眠状态,这就是 uv__iou_submit 的逻辑。cyR28资讯网——每日最新资讯28at.com

static void uv__iou_submit(struct uv__iou* iou) {uint32_t flags;atomic_store_explicit((_Atomic uint32_t*) iou->sqtail,*iou->sqtail + 1,memory_order_release);flags = atomic_load_explicit((_Atomic uint32_t*) iou->sqflags,memory_order_acquire);// 判断内核线程是否处于睡眠状态if (flags & UV__IORING_SQ_NEED_WAKEUP)// 唤醒内核线程,说明有任务需要处理if (uv__io_uring_enter(iou->ringfd, 0, 0, UV__IORING_ENTER_SQ_WAKEUP))if (errno != EOWNERDEAD)  /* Kernel bug. Harmless, ignore. /perror("libuv: io_uring_enter(wakeup)");  /

这样就完成了任务的提交。cyR28资讯网——每日最新资讯28at.com

任务完成

任务完成后,io_uring 对应的 fd 就会变成可读,从而 epoll 就会感知到,来看看 epoll 的处理。下面是 epoll 处理就绪 fd 时的一段逻辑。cyR28资讯网——每日最新资讯28at.com

cyR28资讯网——每日最新资讯28at.com

if(fd == iou->ringfd) {uv__poll_io_uring(loop, iou);have_iou_events = 1;continue;}

如果是 io_uring 的 fd 可读,则执行 uv__poll_io_uring。cyR28资讯网——每日最新资讯28at.com

cyR28资讯网——每日最新资讯28at.com

static void uv__poll_io_uring(uv_loop_t* loop, struct uv__iou* iou) {struct uv__io_uring_cqe* cqe;struct uv__io_uring_cqe* e;uv_fs_t* req;uint32_t head;uint32_t tail;uint32_t mask;uint32_t i;uint32_t flags;int nevents;int rc;// 完成队列头/尾节点head = iou->cqhead;tail = atomic_load_explicit((_Atomic uint32_t) iou->cqtail,memory_order_acquire);mask = iou->cqmask;cqe = iou->cqe;nevents = 0;// 遍历完成队列for (i = head; i != tail; i++) {e = &cqe[i & mask];// 拿到操作关联的请求结构体req = (uv_fs_t*) (uintptr_t) e->user_data;uv__req_unregister(loop, req);iou->in_flight--;// 操作返回值,表示操作是否成功req->result = e->res;// 执行回调req->cb(req);}

uv__poll_io_uring 的逻辑很简单,就是遍历完成队列,然后拿到对应的请求上下文结构体,最后执行它的回调。cyR28资讯网——每日最新资讯28at.com

现代软件中大多数使用的 IO 模型是 epoll,随着 io_uring 的发展和成熟,io_uring 将会出现在更多的软件中,之前我也体验了一下 io_uring,有兴趣的可以体验下 https://github.com/theanarkh/nodejs_io_uring。cyR28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-14326-0.html聊聊 Libuv 最近引入的 io_uring

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Kubernetes中的优雅关闭和零停机时间部署

下一篇: 业务痛点如此不同,为什么解决办法却那么一致?

标签:
  • 热门焦点
  • 小米官宣:2023年上半年出货量中国第一!

    小米官宣:2023年上半年出货量中国第一!

    今日早间,小米电视官方微博带来消息,称2023年小米电视上半年出货量达到了中国第一,同时还表示小米电视的巨屏风暴即将开始。“公布一个好消息2023年#小米电视上半年出货量中国
  • vivo TWS Air开箱体验:真轻 臻好听

    vivo TWS Air开箱体验:真轻 臻好听

    在vivo S15系列新机的发布会上,vivo的最新款真无线蓝牙耳机vivo TWS Air也一同发布,本次就这款耳机新品给大家带来一个简单的分享。外包装盒上,vivo TWS Air保持了vivo自家产
  • 从 Pulsar Client 的原理到它的监控面板

    从 Pulsar Client 的原理到它的监控面板

    背景前段时间业务团队偶尔会碰到一些 Pulsar 使用的问题,比如消息阻塞不消费了、生产者消息发送缓慢等各种问题。虽然我们有个监控页面可以根据 topic 维度查看他的发送状态,
  • 不容错过的MSBuild技巧,必备用法详解和实践指南

    不容错过的MSBuild技巧,必备用法详解和实践指南

    一、MSBuild简介MSBuild是一种基于XML的构建引擎,用于在.NET Framework和.NET Core应用程序中自动化构建过程。它是Visual Studio的构建引擎,可在命令行或其他构建工具中使用
  • 分享六款相见恨晚的PPT模版网站, 祝你做出精美的PPT!

    分享六款相见恨晚的PPT模版网站, 祝你做出精美的PPT!

    1、OfficePLUSOfficePLUS网站旨在为全球Office用户提供丰富的高品质原创PPT模板、实用文档、数据图表及个性化定制服务。优点:OfficePLUS是微软官方网站,囊括PPT模板、Word模
  • 共享单车的故事讲到哪了?

    共享单车的故事讲到哪了?

    来源丨海克财经与共享充电宝相差不多,共享单车已很久没有被国内热点新闻关照到了。除了一再涨价和用户直呼用不起了。近日多家媒体再发报道称,成都、天津、郑州等地多个共享单
  • “又被陈思诚骗了”

    “又被陈思诚骗了”

    作者|张思齐 出品|众面(ID:ZhongMian_ZM)如今的国产悬疑电影,成了陈思诚的天下。最近大爆电影《消失的她》票房突破30亿断层夺魁暑期档,陈思诚再度风头无两。你可以说陈思诚的
  • 国行版三星Galaxy Z Fold5/Z Flip5发布 售价7499元起

    国行版三星Galaxy Z Fold5/Z Flip5发布 售价7499元起

    2023年8月3日,三星电子举行Galaxy新品中国发布会,正式在国内推出了新一代折叠屏智能手机三星Galaxy Z Fold5与Galaxy Z Flip5,以及三星Galaxy Tab S9
  •  三星推出Galaxy Tab S9系列平板电脑以及Galaxy Watch6系列智能手表

    三星推出Galaxy Tab S9系列平板电脑以及Galaxy Watch6系列智能手表

    2023年7月26日,三星电子正式发布了Galaxy Z Flip5与Galaxy Z Fold5。除此之外,Galaxy Tab S9系列平板电脑以及三星Galaxy Watch6系列智能手表也同期
Top