poll为什么使用poll_list链表结构而不是数组 - 深入内核源码分析

一:引言

在Linux内核中,poll机制是一个非常重要的I/O多路复用机制。它允许进程监视多个文件描述符,等待其中任何一个进入就绪状态。poll的内部实现使用了poll_list链表结构而不是数组,这个设计选择背后有其深层的技术考量。本文将从内核源码层面深入分析这个设计决策的原因。

二:poll的基本工作原理

poll系统调用的基本接口如下:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

fds 是一个 struct pollfd 数组,每个元素包含一个文件描述符及其事件掩码。

nfds 是数组的大小。

timeout 是等待的时间(以毫秒为单位),如果为-1则无限期等待。

struct pollfd 结构体定义如下:

struct pollfd {int   fd;         // 文件描述符short events;     // 请求的事件short revents;    // 返回的事件
};

在Linux内核中,poll 的实现主要位于 fs/select.c 文件中。我们可以通过以下步骤来追踪 poll 的执行流程:

1、用户空间到内核空间的转换:

用户空间的 poll 调用通过系统调用进入内核,内核调用 sys_poll 函数。

2、文件描述符集合的构建:

内核需要将用户传递的 pollfd 数组转换为内核可以处理的数据结构。

3、轮询文件描述符:

内核遍历文件描述符集合,检查每个文件描述符是否满足指定的事件条件。

4、结果返回:

将结果返回给用户空间。

其中第2点,应用层我们传递的是pollfd数组,要转换为内核的数据结构,内核对应的数据结构为:(V5.15版本)

// include/linux/poll.h
struct poll_list {struct poll_list *next;   // 指向下一个poll_list节点int len;                  // 本节点包含的pollfd数量  struct pollfd entries[]; // 弹性数组成员
};// 文件系统层面的poll实现接口
struct file_operations {...__poll_t (*poll) (struct file *file, struct poll_table_struct *wait);...
};

poll_list 是一个动态分配的链表节点,每个节点包含一个 poll_table_entry 数组,用于存储文件描述符及其相关的等待队列

三:为什么选择链表而不是数组

1、内存分配的灵活性

使用链表结构的第一个重要原因是内存分配的灵活性。让我们看看内核是如何为poll_list分配内存的:

#define POLLFD_PER_PAGE  ((PAGE_SIZE-sizeof(struct poll_list)) / sizeof(struct pollfd))
//这个宏定义计算了在一个页面大小内可以容纳多少个 struct pollfd 结构体。PAGE_SIZE 是系统页面的大小,
//sizeof(struct poll_list) 是 poll_list 结构体的大小,sizeof(struct pollfd) 是 pollfd 结构体的大小。
//这个计算结果表示在一个页面内除了存储 poll_list 结构体本身外,还能存储多少个 pollfd 结构体。static struct poll_list *alloc_poll_list(int size)
{struct poll_list *p;int entries = POLLFD_PER_PAGE;if (size < entries) {entries = size;}p = kmalloc(struct_size(p, entries, sizeof(struct pollfd)), GFP_KERNEL);if (!p)return NULL;p->next = NULL;p->len = entries;return p;
}

从上面的代码可以看出:

poll_list使用页对齐的内存分配,每个节点尽量利用一个完整的页面

通过链表结构,可以根据实际需要的pollfd数量动态分配多个节点

避免了一次性分配大块连续内存的问题

如果使用数组结构,则存在以下问题:

// 假设使用数组方式
struct poll_array {int len;struct pollfd entries[]; // 需要一次性分配足够大的连续内存
};// 问题演示
struct poll_array *alloc_poll_array(int size) 
{// 1. 需要一次性分配大块连续内存// 2. 可能导致内存碎片// 3. 在内存压力大时更容易分配失败return kmalloc(sizeof(*p) + size * sizeof(struct pollfd), GFP_KERNEL);
}

2、支持大量文件描述符

链表结构的第二个重要优势是能够支持大量文件描述符。内核中的实现:

// fs/select.c
static int do_poll(struct poll_list *list, struct poll_wqueues *wait,struct timespec64 *end_time)
{poll_table* pt = &wait->pt;int error = 0;for (;;) {struct poll_list *walk;for (walk = list; walk != NULL; walk = walk->next) {struct pollfd * pfd = walk->entries;int len = walk->len;for (int i = 0; i < len; i++) {// 处理每个文件描述符error = do_pollfd(&pfd[i], pt, ...);}}if (!pt->qproc)break; // 所有fd都已就绪或出错if (signal_pending(current))break;if (end_time && time_after64(ktime_get(), *end_time))break; // 超时schedule(); // 让出CPU}return error;
}

通过链表结构:

可以支持远超过单页内存大小的文件描述符数量

每个节点的大小限制在一个页面内,便于内存管理

遍历性能损失可以忽略不计,因为poll本身就是一个相对耗时的操作

如果使用数组:

// 使用数组的问题
struct poll_array {int len; struct pollfd entries[MAX_POLL_FDS];
};// 1. 需要预定义最大FD数量
// 2. 过大的数组导致栈内存浪费
// 3. 难以支持动态扩容

3、高效的插入和删除操作

链表的优势:

常数时间复杂度:链表在插入和删除节点时具有常数时间复杂度 O(1),而数组需要移动大量元素,时间复杂度为 O(n)。

减少数据移动:链表不需要移动其他元素,只需要修改指针即可完成插入和删除操作。

数组的限制:

移动元素:数组在插入或删除元素时需要移动大量元素,特别是在数组中间位置进行操作时,性能下降明显。

复杂度高:数组的操作复杂度较高,不适合频繁的插入和删除操作

总结

  1. 动态扩展性:链表可以根据实际需要动态分配和释放内存,避免了固定大小数组带来的内存浪费或不足问题。

  2. 高效的插入和删除操作:链表在插入和删除节点时具有常数时间复杂度,而数组需要移动大量元素,导致性能下降。

  3. 优化内存管理:页对齐的内存分配可以简化内存管理操作,提高内存管理的效率。

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

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

相关文章

使用 Azure AKS 保护 Kubernetes 部署的综合指南

企业不断寻求增强其软件开发和部署流程的方法。DevOps 一直是这一转型的基石,弥合了开发与运营之间的差距。然而,随着安全威胁日益复杂,将安全性集成到 DevOps 流水线(通常称为 DevSecOps)已变得势在必行。本指南深入探讨了如何使用 Azure Kubernetes 服务 (AKS) 来利用 D…

2025年常见渗透测试面试题-webshell免杀思路(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 webshell免杀思路 PHP免杀原理 webshell免杀测试&#xff1a; webshell免杀绕过方法&#xff1a; 编…

访问不到服务器上启动的llamafactory-cli webui

采用SSH端口转发有效&#xff0c;在Windows上面进行访问 在服务器上启动 llamafactory-cli webui 后&#xff0c;访问方式需根据服务器类型和网络环境选择以下方案&#xff1a; 一、本地服务器&#xff08;物理机/虚拟机&#xff09; 1. 直接访问 若服务器与操作设备处于同一…

基于 LSTM 的多特征序列预测-SHAP可视化!

往期精彩内容&#xff1a; 单步预测-风速预测模型代码全家桶-CSDN博客 半天入门&#xff01;锂电池剩余寿命预测&#xff08;Python&#xff09;-CSDN博客 超强预测模型&#xff1a;二次分解-组合预测-CSDN博客 VMD CEEMDAN 二次分解&#xff0c;BiLSTM-Attention预测模型…

C++ 编程指南35 - 为保持ABI稳定,应避免模板接口

一&#xff1a;概述 模板在 C 中是编译期展开的&#xff0c;不同模板参数会生成不同的代码&#xff0c;这使得模板类/函数天然不具备 ABI 稳定性。为了保持ABI稳定&#xff0c;接口不要直接用模板&#xff0c;先用普通类打个底&#xff0c;模板只是“外壳”&#xff0c;这样 AB…

【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(二)

自动引用计数 前言ARC规则所有权修饰符**__strong修饰符**__weak修饰符__unsafe_unretained修饰符__autoreleasing修饰符 规则属性数组 前言 上一篇我们主要学习了一些引用计数方法的内部实现&#xff0c;现在我们学习ARC规则。 ARC规则 所有权修饰符 OC中&#xff0c;为了处…

可信空间数据要素解决方案

可信空间数据要素解决方案 一、引言 随着数字经济的蓬勃发展&#xff0c;数据已成为重要的生产要素。可信空间数据要素解决方案旨在构建一个安全、可靠、高效的数据流通与应用环境&#xff0c;促进数据要素的合理配置和价值释放&#xff0c;推动各行业的数字化转型和创新发展…

mysql删除表后重建表报错Tablespace exists

版本 mysql:8.0.23 复现步骤 1、删除表 DROP TABLE IF EXISTS xxx_demo; 2、新建表 CREATE TABLE xxx_demo (id bigint NOT NULL AUTO_INCREMENT COMMENT 主键id,creator varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT COMMENT 创建者,c…

【Leetcode-Hot100】缺失的第一个正数

题目 解答 有一处需要注意&#xff0c;我使用注释部分进行交换值&#xff0c;报错&#xff1a;超出时间限制。有人知道是为什么吗&#xff1f;难道是先给nums[i]赋值后&#xff0c;从而改变了后一项的索引&#xff1f; class Solution(object):def firstMissingPositive(sel…

从单模态到多模态:五大模型架构演进与技术介绍

前言 1. ResNet — 残差神经网络背景核心问题与解决方案原理模型架构ResNet 系列变体技术创新与影响 2. ViT — Vision Transformer背景核心思想发展历程Transformer的起源&#xff1a;ViT的出现&#xff1a;ViT的进一步发展&#xff1a; 模型架构技术创新与影响 3. Swin Trans…

JavaScript事件循环

目录 JavaScript 执行机制与事件循环 一、同步与异步代码 1. 同步代码&#xff08;Synchronous Code&#xff09; 2. 异步代码&#xff08;Asynchronous Code&#xff09; 二、事件循环&#xff08;Event Loop&#xff09; 1. 核心组成 2. 事件循环基本流程 3. 运行机制…

Java Collection(7)——Iterable接口

1.Iterator接口 1.1 Iterator接口和其他集合类的关系 Java集合类中&#xff0c;Iterable接口属于顶层接口&#xff0c;除Map接口外&#xff0c;其他都实现了Iterable接口&#xff0c;这意味着它们都可以重写和使用Iterable接口中的方法 1.2 Iterable接口简介 在JDK1.7以前&a…

若依微服务版启动小程序后端

目录标题 本地启动&#xff0c;dev对应 nacos里的 xxx-xxx-dev配置文件 本地启动&#xff0c;dev对应 nacos里的 xxx-xxx-dev配置文件

STM32基础教程——DMA+ADC多通道

目录 前言 ​编辑 技术实现 连线图 代码实现 技术要点 实验结果 问题记录 前言 DMA(Direct Memory Access)直接存储器存取&#xff0c;用来提供在外设和存储器 之间或者存储器和存储器之间的高速数据传输。无需CPU干预&#xff0c;数据可以通过DMA快速地移动&#xff0…

23黑马产品经理Day01

今天过了一遍23黑马产品经理的基础视频 问题思考维度 抓住核心用户 为什么需要抓住核心用户&#xff1f; 主要原因&#xff1a;用户越来越细分&#xff0c;保持市场竞争力&#xff0c;产品开发推广更聚焦 做产品为什么要了解用户&#xff1a;了解用户的付费点&#xff0c;…

C/C++ 通用代码模板

✅ C 语言代码模板&#xff08;main.c&#xff09; 适用于基础项目、算法竞赛或刷题&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <math.h>// 宏定义区 #define MAX_N 1000 #defi…

【数据结构_7】栈和队列(上)

一、概念 栈和队列&#xff0c;也是基于顺序表和链表实现的 栈是一种特殊的线性表&#xff0c;其只允许在固定的一段进行插入和删除元素操作。 遵循后进先出的原则 此处所见到的栈&#xff0c;本质上就是一个顺序表/链表&#xff0c;但是&#xff0c;实在顺序表/链表的基础…

git UserInterfaceState.xcuserstate 文件频繁更新

1> 退出 Xcdoe&#xff0c;打开终端&#xff08;Terminal&#xff09;&#xff0c;进入到你的项目目录下。 2> 在终端键入 git rm --cached <YourProjectName>.xcodeproj/project.xcworkspace/xcuserdata/<YourUsername>.xcuserdatad/UserInterfaceState.x…

【Ai】MCP实战:手写 client 和 server [Python版本]

什么是mcp MCP 是一个开放协议&#xff0c;它为应用程序向 LLM 提供上下文的方式进行了标准化。你可以将 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 为设备连接各种外设和配件提供了标准化的方式一样&#xff0c;MCP 为 AI 模型连接各种数据源和工具提供了标准化的接口…

ESP8266/32作为AVR编程器(ISP programmer)的使用介绍

ESP8266作为AVR编程器( ISP programmer)的使用介绍 &#x1f33f;ESP8266自带库例程&#xff1a;https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266AVRISP&#x1f4cd;支持ESP8266/32的ESP_AVRISP其它开源工程&#xff08;个人没有再去验证&#xff09;&…