LinuxKernel全球下载站点与镜像站点统计

Linux Kernel全球下载站点与镜像站点统计

Linux Kernel全球下载与镜像站点统计,了解官方最新下载数据与全球分布情况。

关键词:Linux Kernel 下载站点统计, Linux Kernel 镜像站点分布, 全球 Linux Kernel 下载源, Kernel.org 官方下载地址, Linux 内核镜像服务器列表, Linux 内核全球下载统计, Linux Kernel 镜像站点排名, 官方 Linux 内核下载网站, Linux 内核下载源代码站点, Linux 内核全球镜像节点分析

🌐 官方主站点

Kernel.org (官方主站)

  • 站点地址: https://www.kernel.org/
  • 适用对象: 全球所有用户,官方权威源
  • 特点:
  • 官方发布版本
  • 最新稳定版本
  • 完整的历史版本归档
  • 数字签名验证
  • 多种下载格式支持

官方FTP站点

  • FTP地址: https://ftp.kernel.org/
  • 适用对象: 需要FTP协议下载的用户
  • 特点: FTP协议访问,适合企业环境

🌍 全球镜像站点

北美地区镜像

1. 美国 – Kernel.org 主镜像

  • 站点地址: https://www.kernel.org/pub/
  • 适用对象: 北美地区用户,全球用户备选
  • 特点: 官方主镜像,速度稳定

2. 美国 – 加州大学洛杉矶分校 (UCLA)

3. 美国 – 俄勒冈州立大学 (OSU)

4. 美国 – 麻省理工学院 (MIT)

5. 加拿大 – 多伦多大学

欧洲地区镜像

6. 德国 – Kernel.org 欧洲镜像

7. 英国 – 萨里大学

8. 法国 – 法国国家数字科学与技术研究所

9. 荷兰 – NLUUG

  • 站点地址: https://ftp.nluug.nl/pub/os/Linux/distr/kernel.org/
  • 适用对象: 荷兰及北欧用户
  • 特点: 荷兰Linux用户组镜像

10. 瑞典 – 斯德哥尔摩大学

  • 站点地址: https://ftp.acc.umu.se/mirror/kernel.org/
  • 适用对象: 北欧地区用户
  • 特点: 北欧教育网镜像

11. 意大利 – 比萨大学

  • 站点地址: https://mirror.dome.cloud/kernel.org/
  • 适用对象: 意大利及南欧用户
  • 特点: 意大利教育网镜像

12. 俄罗斯 – Yandex

  • 站点地址: https://mirror.yandex.ru/kernel.org/
  • 适用对象: 俄罗斯及东欧用户
  • 特点: 俄罗斯最大互联网公司镜像

亚洲地区镜像

13. 中国 – 清华大学

  • 站点地址: https://mirrors.tuna.tsinghua.edu.cn/kernel/
  • 适用对象: 中国大陆用户
  • 特点: 中国教育网顶级镜像,速度极快

14. 中国 – 中科大

  • 站点地址: https://mirrors.ustc.edu.cn/kernel.org/
  • 适用对象: 中国用户,特别是教育网用户
  • 特点: 中国科学技术大学镜像

15. 中国 – 上海交通大学

  • 站点地址: https://mirror.sjtu.edu.cn/kernel.org/
  • 适用对象: 华东地区用户
  • 特点: 上海交通大学镜像

16. 中国 – 华为云

  • 站点地址: https://mirrors.huaweicloud.com/kernel/
  • 适用对象: 中国用户,企业用户
  • 特点: 华为云CDN加速

17. 中国 – 阿里云

  • 站点地址: https://mirrors.aliyun.com/kernel/
  • 适用对象: 中国用户
  • 特点: 阿里云全球CDN

18. 日本 – JAIST

19. 韩国 – KAIST

20. 新加坡 – 国立大学

21. 印度 – 印度理工学院

  • 站点地址: https://mirror.cse.iitk.ac.in/kernel.org/
  • 适用对象: 印度及南亚用户
  • 特点: 印度教育网镜像

大洋洲地区镜像

22. 澳大利亚 – 澳大利亚国立大学

  • 站点地址: https://mirror.aarnet.edu.au/pub/kernel.org/
  • 适用对象: 澳大利亚及大洋洲用户
  • 特点: 澳大利亚教育网镜像

南美地区镜像

23. 巴西 – 圣保罗大学

  • 站点地址: https://sft.if.usp.br/kernel.org/
  • 适用对象: 巴西及南美用户
  • 特点: 巴西教育网镜像

📊 镜像站点性能对比

按地区推荐优先级

中国大陆用户

  1. 清华大学镜像 – 最推荐
  2. 中科大镜像 – 教育网用户首选
  3. 阿里云镜像 – 商业用户推荐
  4. 华为云镜像 – 企业用户备选

北美用户

  1. Kernel.org主站 – 官方推荐
  2. UCLA镜像 – 西海岸用户
  3. MIT镜像 – 东海岸用户

欧洲用户

  1. Kernel.org欧洲镜像 – 官方欧洲镜像
  2. 英国镜像 – 西欧用户
  3. 德国镜像 – 中欧用户
  4. 荷兰镜像 – 北欧用户

亚洲用户

  1. 日本镜像 – 东亚用户
  2. 韩国镜像 – 韩国用户
  3. 新加坡镜像 – 东南亚用户

🚀 下载方式与建议

直接下载

# 从官方站点下载
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.x.x.tar.xz

# 从镜像站点下载(以清华为例)
wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v6.x/linux-6.x.x.tar.xz

使用Git获取

# 克隆官方Git仓库
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

# 从镜像克隆(以清华为例)
git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux.git

镜像同步测试

# 测试不同镜像的下载速度
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/kernel/HEADER.html
time wget -O /dev/null https://mirrors.edge.kernel.org/pub/linux/kernel/HEADER.html

🔧 镜像站点维护信息

官方镜像列表

  • 查看所有镜像: https://www.kernel.org/mirrors.html
  • 镜像状态检查: https://mirrors.edge.kernel.org/status.html
  • 镜像同步时间: 各镜像同步频率不同

镜像站点要求

  1. 带宽要求: 至少100Mbps
  2. 存储要求: 至少5TB可用空间
  3. 同步频率: 每日同步
  4. 协议支持: HTTP/HTTPS/FTP/Rsync

⚠️ 安全验证

校验文件完整性

# 验证SHA256校验和
sha256sum linux-6.x.x.tar.xz

# 验证PGP签名
wget https://www.kernel.org/signature.asc
gpg --verify signature.asc linux-6.x.x.tar.sign

官方密钥

  • 获取密钥: https://www.kernel.org/signature.asc
  • 密钥指纹: 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E

📋 镜像站点适用对象总结

镜像站点地理位置适用对象特点
Kernel.org美国全球用户官方权威,最可靠
清华大学中国中国大陆速度最快,教育网
中科大中国中国教育网同步及时
阿里云全球CDN中国企业商业稳定
UCLA美国西部北美西海岸教育网高速
MIT美国东部北美东海岸东海岸优化
欧洲镜像欧洲欧洲用户官方欧洲镜像
日本镜像日本东亚地区亚洲优化

🔄 镜像同步状态检查

实时状态监控

  • 官方状态页: https://mirrors.edge.kernel.org/status.html
  • 镜像健康检查: 定期自动检查
  • 故障转移: 自动切换到可用镜像

镜像选择建议

  1. 优先选择地理距离近的镜像
  2. 教育网用户优先选择教育网镜像
  3. 企业用户可选择商业CDN镜像
  4. 始终验证文件完整性和数字签名

这些镜像站点为全球Linux用户提供了快速、可靠的内核下载服务,用户可以根据地理位置和网络环境选择最适合的镜像站点。

https://www.calcguide.tech/2025/08/28/%e5%85%a8%e7%90%83linux-kernel%e4%b8%8b%e8%bd%bd%e7%ab%99%e7%82%b9%e4%b8%8e%e9%95%9c%e5%83%8f%e7%ab%99%e7%82%b9%e7%bb%9f%e8%ae%a1

发表在 linux文章 | 留下评论

How to Build an AI Agent with Dify

Here’s the English version of the beginner-friendly, highly practical guide to building an Agent using Dify — designed for non-technical users, with a clear, visual, and step-by-step approach.


🤖 How to Build an AI Agent with Dify (For Absolute Beginners)

A visual, no-code guide to creating smart agents that think, decide, and act — even if you’re not a developer.


🎯 What Is an AI Agent?

An AI Agent is more than a chatbot. It can:

  • Understand your goal
  • Break it into steps
  • Use tools (like search, APIs)
  • Make decisions
  • Take action
  • Return a complete result

Example: You say, “Will it rain in Shanghai tomorrow? Remind me to bring an umbrella if so.”
The agent figures out what to do, checks the weather, and gives you a smart reply.


✅ Why Use Dify?

Dify is one of the best platforms for beginners to build AI agents because:

BenefitWhy It Helps Beginners
Visual Workflow BuilderDrag-and-drop nodes — no coding needed
Built-in LLM SupportUse GPT, Qwen, etc. out of the box
Custom ToolsConnect to APIs, databases, web services
Full in Chinese & EnglishEasy for global users
Open-source & Self-hostableFlexible and secure

Dify turns complex agent logic into simple visual blocks.


🚀 Step-by-Step: Build a “Weather Reminder Agent”

We’ll create an agent that:

  1. Understands if you want weather info
  2. Checks the weather
  3. Decides whether to remind you
  4. Replies naturally

No code. Just drag, click, and test.


🧱 Step 1: Create a Workflow App

  1. Go to Dify.ai → Log in
  2. Click “Create Application”
  3. Choose “Workflow” mode

🔧 This is where you build your agent’s “brain”.


🧩 Step 2: Design the Workflow (5 Simple Nodes)

Here’s the flow:

[User Input]
     ↓
🟢 Node 1: Intent Detection (LLM) — What does the user want?
     ↓
🟡 Node 2: Condition — Should we check weather?
     ↓ Yes                 ↓ No
🔵 Node 3: Tool Call       🔵 Node 4: Simple Reply
     ↓
🟢 Node 5: Final Response (with reminder logic)
     ↓
[Output to User]

Let’s configure each node.


🔧 Step 3: Configure Each Node

🟢 Node 1: Intent Detection (LLM Node)

Purpose: Extract whether the user wants weather info and which city.

Settings:

  • Type: LLM
  • Model: GPT-3.5 / Qwen / etc.
  • Prompt (copy-paste this):
You are a task analyzer. Analyze the user input and decide if weather check is needed.

User input: {{input}}

Return JSON format:
{
  "need_check": true or false,
  "city": "city name, e.g. Beijing"
}

✅ Enable Structured Output → Format: JSON
📌 Save output as variable: intent


🟡 Node 2: Condition Branch

Purpose: Decide which path to take.

Rule:

intent.need_check == true
  • If true → go to weather tool
  • If false → go to simple reply

🔵 Node 3: Tool Call — Get Weather

🛠️ First: Create a Custom Tool

Go to: Application Settings → Tools → Create Tool

FieldValue
Nameget_weather
DescriptionGet weather for a city
ParametersUse this JSON Schema
{
  "type": "object",
  "properties": {
    "city": {
      "type": "string",
      "description": "City name, e.g. Shanghai"
    }
  },
  "required": ["city"]
}

📌 After saving, Dify gives you a Webhook URL — you’ll use this.


🌐 Build the Weather Backend (Beginner-Friendly)

You need a small service to return real weather data.

Option 1: Use a Free Weather API

Example with OpenWeatherMap:

  • Sign up (free tier)
  • Build a simple FastAPI/Flask app that calls their API

Option 2: Use a Ready-Made Template

We’ve prepared a simple FastAPI weather tool:

from fastapi import FastAPI
import requests

app = FastAPI()

@app.post("/weather")
def get_weather(data: dict):
    city = data.get("city")
    api_key = "YOUR_OPENWEATHER_KEY"
    url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}"
    response = requests.get(url).json()
    return {
        "temp": f"{response['main']['temp'] - 273.15:.1f}°C",
        "condition": response['weather'][0]['description']
    }

Deploy it on:

  • Vercel / Render / Railway (free)
  • Or use Alibaba Cloud Function Compute

Then set the webhook URL in Dify.


🔧 Back in Dify: Call the Tool

  • Type: Tool
  • Tool: get_weather
  • Parameters: {"city": "{{intent.city}}"}
  • Save result as: weather_info

🟢 Node 5: Generate Final Reply (LLM Node)

Prompt:

You are a helpful assistant. Based on the weather info, decide if a reminder is needed.

Weather info:
{{weather_info}}

Reply in natural language. If it's raining, remind the user to bring an umbrella.

This is your agent’s final answer.


🔵 Node 4: Simple Reply (for non-weather queries)

Prompt:

The user didn’t ask about weather. Just reply politely:
{{input}}

▶️ Step 4: Test It!

Input:

Will it rain in Hangzhou tomorrow? If yes, remind me.

Expected Output:

It will rain in Hangzhou tomorrow. Don’t forget your umbrella!

🎉 Success! Your first AI Agent is live.


📈 Level Up: Make Your Agent Smarter

FeatureHow to Add
Remember past chatsEnable session context in Dify
Plan a tripAdd a “task planner” LLM node to break goals into steps
Book hotelsAdd a booking API as a tool
Multi-step loopsUse parallel or retry nodes (Pro feature)

🧰 Starter Kit for Beginners

🎁 1. Ready-to-Use Weather Webhook (Test Only)

We provide a demo endpoint (for testing):

POST https://demo-agent-tools.example.com/weather
Body: {"city": "Beijing"}
→ Returns: {"temp": "22°C", "condition": "Sunny"}

🔒 For real use, deploy your own for security.


🧩 2. Exportable Workflow Template (JSON)

{
  "nodes": [
    {
      "id": "intent",
      "type": "llm",
      "config": {
        "prompt": "You are a task analyzer...\nReturn JSON..."
      },
      "output_var": "intent"
    },
    {
      "id": "condition",
      "type": "condition",
      "expression": "intent.need_check == true"
    },
    {
      "id": "tool_weather",
      "type": "tool",
      "tool": "get_weather",
      "params": {"city": "{{intent.city}}"},
      "output_var": "weather_info"
    },
    {
      "id": "final_reply",
      "type": "llm",
      "config": {
        "prompt": "Based on {{weather_info}}, generate a reply..."
      }
    }
  ]
}

You can import this structure into Dify (if supported).


📘 3. Learning Resources

ResourceLink
Dify Official Docshttps://docs.dify.ai
YouTube: “Build AI Agents with Dify”Search on YouTube
Dify Community (Discord/WeChat)Join for help and templates

🧭 Learning Path for Beginners

WeekGoal
Week 1Build a Q&A bot with Dify
Week 2Add one tool (e.g. weather, search)
Week 3Create a decision-making agent
Week 4Build a real-world agent (e.g. travel planner, daily report generator)

🎉 Summary: How Beginners Can Succeed

TipExplanation
🧱 Think in BlocksEach node is a step: Understand → Decide → Act → Reply
🤖 LLM = BrainUse it for understanding and reasoning
🔌 Tools = HandsThey do the real work (APIs, search, etc.)
🖼️ Visual = CodeNo coding needed — just drag and connect
🔄 Test Early, Iterate FastAdd one feature at a time

❓ FAQ

Q: I’m not a developer. Can I really do this?
A: Yes! If you can use a mouse and understand logic, you can build agents.

Q: Do I need to code the tools?
A: Not always. Use free APIs (like weather, translation). Only complex tools need coding.

Q: Can it remember past conversations?
A: Yes! Enable session context in Dify settings.

Q: Can I connect to Slack, WeChat, or DingTalk?
A: Yes! Dify supports API integration and webhooks.


📎 Next Steps

Want me to:

  • Generate a full exportable workflow file?
  • Provide a Docker-ready weather tool?
  • Help you build a custom agent (e.g. sales assistant, customer support)?

Just ask! I’ll guide you step by step. 🚀


🎯 Start now: Log in to Dify → Create a Workflow → Drag an LLM Node → Try it!
Your first AI agent is just minutes away.

如何基于 Dify 平台开发 Agent智能代理 – LinuxGuide 如何基于 Dify 平台开发 Agent智能代理 如何基于 Dify 平台开发 Agent智能代理,如何基于Dify平台,开发,如何基于 Dify 平台开发 Agent,Dify 平台 Agent 开发教程,Dify 如何创建智能代理,Agent 智能代理开发指南,Dify 平台低代码开发 Agent,小白也能学的 Agent 开发,Dify 平台智能代理教程,如何快速开发智能代理,Dify Agent 开发入门指南,智能代理开发步骤详解LinuxGuide

发表在 AI导航 | 留下评论

如何基于 Dify 平台开发 Agent智能代理

以下是为小白用户量身定制的、基于 Dify 平台开发 Agent(智能代理)超直观、低门槛、高可行性实现方案


🎯 目标:让“零代码/低代码”小白也能轻松做出一个能“思考 + 执行”的 Agent

✅ 比如:你问它“帮我查明天上海天气,如果下雨就提醒我带伞”,它能自己分析、调用工具、做判断、给出结果。


🧩 一、一句话理解:什么是 Agent?

Agent = 会自己动脑子 + 能干活的小助手

它不像普通聊天机器人只是“回答问题”,而是:

  • 理解你的目标
  • 自己拆解任务步骤
  • 调用工具(如搜索、API)
  • 做出决策
  • 返回最终结果

🛠️ 二、为什么选 Dify?对小白友好吗?

优点对小白的意义
可视化拖拽工作流不用写代码也能搭逻辑
内置大模型能力自动理解语言、生成回复
支持自定义工具能连接外部功能(如天气、数据库)
中文界面 + 国内可用上手无障碍

结论:Dify 是目前最适合小白做 Agent 的国产平台!


🚀 三、小白也能做的 Agent 示例:天气提醒助手

我们来做一个完整的例子,全程可视化操作,无需写一行代码


🌐 第一步:准备环境

  1. 打开 Dify 官网(或私有部署版本)
  2. 注册账号 → 登录
  3. 点击「创建应用」→ 选择「工作流(Workflow)」

💡 提示:一定要选“工作流”模式,这是实现 Agent 的关键!


🧱 第二步:搭建 Agent 的“大脑”——可视化工作流

我们将用 5 个积木块(节点) 搭出一个完整的 Agent:

[用户输入]
     ↓
🟢 节点1:意图识别(LLM)—— 它想干啥?
     ↓
🟡 节点2:条件判断 —— 要不要查天气?
     ↓ 是                  ↓ 否
🔵 节点3:调用天气工具     🔵 节点4:直接回复
     ↓
🟢 节点5:生成最终回答(带提醒)
     ↓
[输出给用户]

🧩 第三步:逐个配置节点(手把手教学)

🟢 节点1:意图识别(LLM 节点)

作用:看懂用户说的是不是要查天气

配置方法

  • 类型:LLM 节点
  • 模型:选 GPT-3.5 或 国产模型(如通义千问)
  • 提示词(复制粘贴即可):
你是任务分析助手,请分析用户输入,判断是否需要查询天气。

用户输入:{{input}}

请输出 JSON 格式:
{
  "need_check": true/false,
  "city": "城市名,例如北京"
}

✅ 勾选“结构化输出” → 输出格式选 JSON

📌 输出变量名设为:intent


🟡 节点2:条件判断

作用:决定走哪条路

  • 如果 intent.need_check == true → 查天气
  • 否则 → 直接回复

配置方法

  • 类型:条件分支
  • 添加规则:
  intent.need_check == true
  • 设置两个分支:“是” 和 “否”

🔵 分支1:需要查天气 → 节点3(工具调用)

✅ 先创建一个“天气查询”工具

路径:应用设置 → 工具 → 创建自定义工具

填写内容

字段
名称get_weather
描述获取指定城市的天气信息
参数JSON Schema(复制下面)
{
  "type": "object",
  "properties": {
    "city": {
      "type": "string",
      "description": "城市名称,比如北京、上海"
    }
  },
  "required": ["city"]
}

📌 保存后,Dify 会生成一个 Webhook URL(后面要用)


🛠️ 搭建工具后端(小白友好版)

你需要一个简单的服务来返回天气数据。推荐使用 FastAPI + 免费天气 API

👉 推荐使用这个现成模板(GitHub):
https://github.com/zhayujie/chatgpt-on-wechat/tree/master/agents/tools/weather_tool

或者你也可以用我为你准备的 一键部署模板(见文末附录)

⚠️ 小白注意:你可以把这部分交给技术人员,或者用阿里云函数计算快速部署。


🔧 回到 Dify:配置工具调用节点

  • 类型:工具调用
  • 工具:选择 get_weather
  • 参数:{"city": "{{intent.city}}"}

输出保存为变量:weather_info


🟢 节点5:生成最终回答(LLM 节点)

提示词

你是贴心助手。根据天气情况决定是否提醒带伞。

天气信息:
{{weather_info}}

请用自然语言告诉用户天气情况,并在下雨时提醒带伞。

输出就是最终答案!


🔵 分支2:不需要查天气 → 节点4(直接回复)

提示词

用户没有问天气相关问题,请用友好语气回复:
{{input}}

✅ 第四步:测试运行!

输入:

明天杭州下雨吗?如果下雨记得提醒我。

预期输出:

明天杭州有雨,记得带伞哦~

🎉 成功!你的第一个 Agent 上线了!


📈 五、进阶扩展:让 Agent 更聪明

功能实现方式
查多个城市在意图识别中提取多个地点
多轮对话记忆开启会话变量,保存历史
自动规划旅行加入“任务分解”LLM 节点,拆成查天气、订酒店等
连接数据库创建数据库查询工具(SQL 工具)

🧰 六、给小白的“工具包”(降低门槛)

🎁 1. 已打包的天气工具(免开发)

我为你准备了一个 免费可用的 Webhook 接口(测试用):

POST https://your-agent-tools.example.com/weather
{
  "city": "北京"
}
→ 返回:{"temp": "20℃", "condition": "小雨"}

(实际项目建议自己部署,安全性更高)


🧩 2. 可导出的工作流模板(JSON)

{
  "nodes": [
    {
      "id": "intent",
      "type": "llm",
      "prompt": "你是任务分析助手...\n输出JSON..."
    },
    {
      "id": "decision",
      "type": "condition",
      "expression": "intent.need_check == true"
    },
    {
      "id": "tool_weather",
      "type": "tool",
      "tool": "get_weather",
      "params": {"city": "{{intent.city}}"}
    },
    {
      "id": "final_reply",
      "type": "llm",
      "prompt": "根据天气信息{{weather_info}}生成回复..."
    }
  ]
}

在 Dify 中可导入此结构(需平台支持)


📘 3. 学习资源推荐

资源说明
Dify 官方文档工具、工作流详解
B站视频:《Dify 零基础做智能助手》搜索关键词即可
微信群/社区加入 Dify 中文社区获取帮助

🧭 七、小白开发 Agent 的最佳路径(推荐路线图)

阶段目标时间
第1天学会用 Dify 搭一个问答机器人1小时
第2天添加一个工具(如天气)2小时
第3天做出带判断逻辑的 Agent3小时
第1周做出旅行规划、日报生成等实用 Agent自由发挥

🎉 总结:小白也能做 Agent 的秘诀

秘诀说明
🧱 用“积木思维”搭流程每个节点就是一个功能模块
🤖 让 LLM 当“大脑”负责理解、判断、生成
🔌 工具是“手脚”干活靠工具(API、搜索等)
📊 可视化即代码不用写代码,拖拽就能实现
🧪 多测试 + 小步迭代每次只加一个功能

📎 附录:常见问题 FAQ

Q:我没有技术背景,能做吗?
A:完全可以!只要你会用鼠标拖拽,就能完成 80% 的工作。

Q:工具必须自己开发吗?
A:简单场景可以用现成 API(如天气、翻译),复杂才需开发。

Q:能做多轮对话吗?
A:可以!开启“会话上下文”即可记住之前聊的内容。

Q:能连接企业微信/钉钉吗?
A:可以!Dify 支持 API 接入,可嵌入各种系统。


如果你想要:

  • 我帮你生成完整的 Workflow JSON 文件
  • 提供可运行的天气工具部署包(Docker)
  • 定制你的专属 Agent(如客服、销售助手)

欢迎继续提问!我可以一步步带你做出来 💪


🎯 现在就开始吧:登录 Dify → 创建 Workflow → 拖一个 LLM 节点 → 试试看!

如何基于 Dify 开发 Agent 智能代理-CSDN博客

发表在 未分类, AI导航 | 留下评论

query_module系统调用及示例

query_module 函数详解

1. 函数介绍

query_module 是 Linux 系统中用于查询内核模块信息的系统调用。可以把内核模块想象成”系统功能的插件”——就像你可以为浏览器安装插件来扩展功能一样,Linux 内核也可以通过加载不同的模块来扩展功能。

query_module 就像一个”模块信息查询器”,它允许你查看系统中已加载的内核模块的详细信息,包括模块名称、引用计数、依赖关系等。

2. 函数原型

#include <linux/module.h>

int query_module(const char *name, int which, void *buf, 
                 size_t bufsize, size_t *ret);

3. 功能

query_module 函数用于查询内核模块的各种信息。它可以获取模块列表、模块引用计数、模块符号信息、模块依赖关系等。

4. 参数

  • name: 要查询的模块名称(NULL 表示查询所有模块)
  • which: 查询类型(指定要获取的信息类型)
  • buf: 指向缓冲区的指针,用于存储返回的信息
  • bufsize: 缓冲区大小
  • ret: 指向返回值的指针

5. 查询类型(which 参数)

类型说明
QM_MODULES1获取模块名称列表
QM_REFS2获取模块引用计数
QM_SYMBOLS3获取模块符号信息
QM_INFO4获取模块详细信息
QM_MODINFO5获取模块信息(新版本)

6. module_info 结构体

struct module_info {
    unsigned long   addr;          /* 模块地址 */
    unsigned long   size;          /* 模块大小 */
    unsigned long   flags;         /* 模块标志 */
    long            refcnt;        /* 引用计数 */
    unsigned char   usecount;      /* 使用计数 */
};

7. 返回值

  • 成功: 返回 0
  • 失败: 返回 -1,并设置相应的 errno 错误码

8. 常见错误码

  • EPERM: 权限不足(通常需要 root 权限)
  • EINVAL: 参数无效
  • ENOENT: 指定的模块不存在
  • ENOMEM: 内存不足
  • EFAULT: 参数指针无效
  • ENOSYS: 系统不支持此调用

9. 相似函数或关联函数

  • lsmod: 命令行工具列出模块
  • insmod: 加载内核模块
  • rmmod: 卸载内核模块
  • modprobe: 智能加载内核模块
  • /proc/modules: 模块信息文件
  • /sys/module/: 模块 sysfs 接口
  • kmod: 内核模块管理工具

10. 示例代码

示例1:基础用法 – 查询模块列表

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/module.h>
#include <errno.h>
#include <string.h>

// 注意:query_module 在现代 Linux 系统中可能不可用
// 这里提供概念性示例和现代替代方案

int main() {
    printf("=== query_module 基础示例 ===\n\n");
    
    printf("注意: query_module 是已废弃的系统调用\n");
    printf("在现代 Linux 系统中通常不可用\n\n");
    
    printf("功能说明:\n");
    printf("query_module 用于查询内核模块信息:\n");
    printf("1. QM_MODULES: 获取模块名称列表\n");
    printf("2. QM_REFS: 获取模块引用计数\n");
    printf("3. QM_SYMBOLS: 获取模块符号信息\n");
    printf("4. QM_INFO: 获取模块详细信息\n");
    printf("5. QM_MODINFO: 获取模块信息(新版本)\n");
    
    printf("\n=== 现代替代方案 ===\n");
    printf("1. 使用 /proc/modules 文件\n");
    printf("2. 使用 lsmod 命令\n");
    printf("3. 使用 modinfo 命令\n");
    printf("4. 使用 /sys/module/ 接口\n");
    
    return 0;
}

示例2:现代替代方案 – 模块信息查询

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>

// 通过 /proc/modules 获取模块信息
int get_modules_via_proc() {
    printf("=== 通过 /proc/modules 获取模块信息 ===\n");
    
    FILE *fp = fopen("/proc/modules", "r");
    if (!fp) {
        perror("打开 /proc/modules 失败");
        return -1;
    }
    
    printf("%-25s %-10s %-8s %-15s %s\n", 
           "模块名称", "大小", "使用数", "被谁使用", "状态");
    printf("%-25s %-10s %-8s %-15s %s\n", 
           "--------", "----", "----", "------", "----");
    
    char line[512];
    int count = 0;
    
    while (fgets(line, sizeof(line), fp) && count < 20) {
        char module_name[64];
        unsigned long size;
        int use_count;
        char used_by[256];
        char status[32];
        char address[32];
        
        // 解析 /proc/modules 格式
        int parsed = sscanf(line, "%63s %lu %d %255s %31s %31s",
                           module_name, &size, &use_count, used_by, status, address);
        
        if (parsed >= 4) {
            printf("%-25s %-10lu %-8d %-15s %s\n", 
                   module_name, size, use_count, used_by, 
                   parsed >= 5 ? status : "-");
            count++;
        }
    }
    
    fclose(fp);
    
    if (count == 0) {
        printf("没有模块信息\n");
    } else {
        printf("\n显示了前 %d 个模块\n", count);
    }
    
    return 0;
}

// 通过 /sys/module/ 获取模块详细信息
int get_module_details_via_sysfs() {
    printf("\n=== 通过 /sys/module/ 获取模块详细信息 ===\n");
    
    // 检查 /sys/module/ 目录
    if (access("/sys/module", F_OK) != 0) {
        printf("系统不支持 /sys/module/ 接口\n");
        return -1;
    }
    
    // 列出几个模块的详细信息
    const char *modules[] = {"usbcore", "tcp", "ext4", "loop", NULL};
    
    for (int i = 0; modules[i]; i++) {
        char module_path[256];
        snprintf(module_path, sizeof(module_path), "/sys/module/%s", modules[i]);
        
        if (access(module_path, F_OK) == 0) {
            printf("\n模块: %s\n", modules[i]);
            
            // 检查模块参数
            char param_path[256];
            snprintf(param_path, sizeof(param_path), "%s/parameters", module_path);
            if (access(param_path, F_OK) == 0) {
                printf("  ✓ 支持参数配置\n");
            }
            
            // 检查模块引用
            char refcnt_path[256];
            snprintf(refcnt_path, sizeof(refcnt_path), "%s/refcnt", module_path);
            FILE *refcnt_fp = fopen(refcnt_path, "r");
            if (refcnt_fp) {
                char refcnt[32];
                if (fgets(refcnt, sizeof(refcnt), refcnt_fp)) {
                    printf("  引用计数: %s", refcnt);
                }
                fclose(refcnt_fp);
            }
            
            // 检查模块状态
            char initstate_path[256];
            snprintf(initstate_path, sizeof(initstate_path), "%s/initstate", module_path);
            FILE *initstate_fp = fopen(initstate_path, "r");
            if (initstate_fp) {
                char initstate[32];
                if (fgets(initstate, sizeof(initstate), initstate_fp)) {
                    printf("  初始化状态: %s", initstate);
                }
                fclose(initstate_fp);
            }
        }
    }
    
    return 0;
}

// 模拟 query_module 功能
void simulate_query_module() {
    printf("\n=== 模拟 query_module 功能 ===\n");
    
    printf("QM_MODULES (获取模块列表):\n");
    printf("  模块1: usbcore\n");
    printf("  模块2: tcp\n");
    printf("  模块3: ext4\n");
    printf("  模块4: loop\n");
    printf("  模块5: sd_mod\n");
    
    printf("\nQM_INFO (获取模块详细信息):\n");
    printf("  模块名称: usbcore\n");
    printf("  地址: 0xffffffffc0000000\n");
    printf("  大小: 123456 字节\n");
    printf("  引用计数: 5\n");
    printf("  状态: Live\n");
    
    printf("\nQM_REFS (获取引用计数):\n");
    printf("  usbcore: 5\n");
    printf("  tcp: 3\n");
    printf("  ext4: 2\n");
    printf("  loop: 1\n");
    
    printf("\nQM_SYMBOLS (获取符号信息):\n");
    printf("  usb_register: 0xffffffffc0001000\n");
    printf("  usb_deregister: 0xffffffffc0002000\n");
    printf("  usb_submit_urb: 0xffffffffc0003000\n");
}

int main() {
    printf("=== 内核模块查询系统 ===\n\n");
    
    // 显示系统信息
    printf("系统信息:\n");
    printf("  用户 ID: %d\n", getuid());
    printf("  进程 ID: %d\n", getpid());
    
    // 检查权限
    if (getuid() != 0) {
        printf("  注意: 某些模块信息需要 root 权限\n");
    }
    printf("\n");
    
    // 通过 /proc/modules 获取信息
    get_modules_via_proc();
    
    // 通过 /sys/module/ 获取详细信息
    get_module_details_via_sysfs();
    
    // 模拟 query_module 功能
    simulate_query_module();
    
    printf("\n=== 现代模块管理工具 ===\n");
    printf("常用的模块管理命令:\n");
    printf("1. lsmod              # 列出已加载模块\n");
    printf("2. modinfo module     # 显示模块信息\n");
    printf("3. insmod module.ko   # 加载模块\n");
    printf("4. rmmod module       # 卸载模块\n");
    printf("5. modprobe module    # 智能加载模块\n");
    printf("6. depmod             # 生成模块依赖\n");
    printf("\n");
    
    printf("模块信息文件:\n");
    printf("1. /proc/modules      # 已加载模块列表\n");
    printf("2. /proc/devices      # 设备号信息\n");
    printf("3. /sys/module/       # 模块 sysfs 接口\n");
    printf("4. /lib/modules/       # 模块文件目录\n");
    
    return 0;
}

示例3:完整的模块管理工具

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>

// 配置结构体
struct module_config {
    int list_modules;      // 列出模块
    int show_details;       // 显示详细信息
    int verbose;            // 详细输出
    int show_refs;          // 显示引用计数
    int show_symbols;       // 显示符号信息
    char *module_name;       // 指定模块名称
    char *search_pattern;    // 搜索模式
};

// 模块信息结构体
struct module_info {
    char name[256];
    unsigned long size;
    int ref_count;
    char used_by[512];
    char status[32];
    unsigned long address;
};

// 通过 /proc/modules 解析模块信息
int parse_proc_modules(struct module_info *modules, int max_modules) {
    FILE *fp = fopen("/proc/modules", "r");
    if (!fp) {
        return -1;
    }
    
    int count = 0;
    char line[1024];
    
    while (fgets(line, sizeof(line), fp) && count < max_modules) {
        struct module_info *mod = &modules[count];
        char extra[512];
        
        // 解析 /proc/modules 格式
        int parsed = sscanf(line, "%255s %lu %d %511s %31s %31s",
                           mod->name, &mod->size, &mod->ref_count, 
                           mod->used_by, mod->status, extra);
        
        if (parsed >= 4) {
            count++;
        }
    }
    
    fclose(fp);
    return count;
}

// 显示模块列表
void show_module_list(struct module_info *modules, int count, 
                     const struct module_config *config) {
    printf("=== 内核模块列表 ===\n");
    printf("%-25s %-12s %-8s %-20s %s\n", 
           "模块名称", "大小", "引用数", "被谁使用", "状态");
    printf("%-25s %-12s %-8s %-20s %s\n", 
           "--------", "----", "----", "------", "----");
    
    int displayed = 0;
    for (int i = 0; i < count && displayed < 50; i++) {
        struct module_info *mod = &modules[i];
        
        // 如果指定了搜索模式,进行过滤
        if (config->search_pattern) {
            if (strstr(mod->name, config->search_pattern) == NULL) {
                continue;
            }
        }
        
        // 如果指定了模块名称,精确匹配
        if (config->module_name) {
            if (strcmp(mod->name, config->module_name) != 0) {
                continue;
            }
        }
        
        printf("%-25s %-12lu %-8d %-20s %s\n", 
               mod->name, mod->size, mod->ref_count, 
               mod->used_by, mod->status);
        displayed++;
    }
    
    if (displayed == 0) {
        printf("没有找到匹配的模块\n");
    } else if (displayed < count) {
        printf("\n显示了 %d 个匹配模块 (总共 %d 个)\n", displayed, count);
    }
}

// 显示模块详细信息
void show_module_details(struct module_info *modules, int count, 
                        const struct module_config *config) {
    printf("\n=== 模块详细信息 ===\n");
    
    int found = 0;
    for (int i = 0; i < count; i++) {
        struct module_info *mod = &modules[i];
        
        // 检查是否匹配
        if (config->module_name) {
            if (strcmp(mod->name, config->module_name) != 0) {
                continue;
            }
        } else if (config->search_pattern) {
            if (strstr(mod->name, config->search_pattern) == NULL) {
                continue;
            }
        }
        
        printf("\n模块: %s\n", mod->name);
        printf("  大小: %lu 字节 (%.2f KB)\n", mod->size, (double)mod->size / 1024);
        printf("  引用计数: %d\n", mod->ref_count);
        printf("  被谁使用: %s\n", mod->used_by);
        printf("  状态: %s\n", mod->status);
        
        // 显示 sysfs 信息
        char sysfs_path[256];
        snprintf(sysfs_path, sizeof(sysfs_path), "/sys/module/%s", mod->name);
        
        if (access(sysfs_path, F_OK) == 0) {
            printf("  Sysfs 路径: %s\n", sysfs_path);
            
            // 检查参数
            char param_path[256];
            snprintf(param_path, sizeof(param_path), "%s/parameters", sysfs_path);
            if (access(param_path, F_OK) == 0) {
                printf("  ✓ 支持运行时参数配置\n");
            }
            
            // 检查引用计数
            char refcnt_path[256];
            snprintf(refcnt_path, sizeof(refcnt_path), "%s/refcnt", sysfs_path);
            FILE *refcnt_fp = fopen(refcnt_path, "r");
            if (refcnt_fp) {
                char refcnt[32];
                if (fgets(refcnt, sizeof(refcnt), refcnt_fp)) {
                    printf("  当前引用计数: %s", refcnt);
                }
                fclose(refcnt_fp);
            }
        }
        
        found++;
        if (config->module_name) {
            break;  // 只显示指定模块
        }
        
        if (found >= 5) {
            printf("\n只显示前 5 个匹配模块的详细信息\n");
            break;
        }
    }
    
    if (found == 0) {
        if (config->module_name) {
            printf("未找到模块: %s\n", config->module_name);
        } else if (config->search_pattern) {
            printf("未找到匹配模式 '%s' 的模块\n", config->search_pattern);
        } else {
            printf("没有模块信息\n");
        }
    }
}

// 显示系统模块统计
void show_module_statistics(struct module_info *modules, int count) {
    printf("\n=== 模块统计信息 ===\n");
    printf("总模块数: %d\n", count);
    
    if (count > 0) {
        // 统计引用计数
        int total_refs = 0;
        int max_refs = 0;
        int zero_refs = 0;
        unsigned long total_size = 0;
        
        for (int i = 0; i < count; i++) {
            total_refs += modules[i].ref_count;
            total_size += modules[i].size;
            
            if (modules[i].ref_count > max_refs) {
                max_refs = modules[i].ref_count;
            }
            
            if (modules[i].ref_count == 0) {
                zero_refs++;
            }
        }
        
        printf("总大小: %.2f MB\n", (double)total_size / (1024 * 1024));
        printf("平均引用计数: %.2f\n", (double)total_refs / count);
        printf("最大引用计数: %d\n", max_refs);
        printf("零引用模块数: %d\n", zero_refs);
        
        // 显示最常见的模块
        printf("\n最常用的模块:\n");
        int shown = 0;
        for (int i = 0; i < count && shown < 5; i++) {
            if (modules[i].ref_count > 0) {
                printf("  %s (%d 引用)\n", modules[i].name, modules[i].ref_count);
                shown++;
            }
        }
    }
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -l, --list              列出所有模块\n");
    printf("  -d, --details           显示模块详细信息\n");
    printf("  -r, --refs              显示引用计数\n");
    printf("  -n, --name=MODULE       指定模块名称\n");
    printf("  -s, --search=PATTERN    搜索模块名称\n");
    printf("  -v, --verbose           详细输出\n");
    printf("  -S, --statistics         显示统计信息\n");
    printf("  -h, --help              显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s -l                          # 列出所有模块\n", program_name);
    printf("  %s -d -n usbcore               # 显示 usbcore 模块详细信息\n", program_name);
    printf("  %s -l -s usb                   # 列出包含 usb 的模块\n", program_name);
    printf("  %s -S                          # 显示模块统计信息\n", program_name);
    printf("  %s -d -s network                # 显示网络相关模块详情\n", program_name);
}

int main(int argc, char *argv[]) {
    struct module_config config = {
        .list_modules = 0,
        .show_details = 0,
        .verbose = 0,
        .show_refs = 0,
        .show_symbols = 0,
        .module_name = NULL,
        .search_pattern = NULL
    };
    
    printf("=== 内核模块查询工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"list",        no_argument,       0, 'l'},
        {"details",     no_argument,       0, 'd'},
        {"refs",        no_argument,       0, 'r'},
        {"symbols",     no_argument,       0, 'y'},
        {"name",        required_argument, 0, 'n'},
        {"search",      required_argument, 0, 's'},
        {"verbose",     no_argument,       0, 'v'},
        {"statistics",  no_argument,       0, 'S'},
        {"help",        no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "ldryn:s:vSh", long_options, NULL)) != -1) {
        switch (opt) {
            case 'l':
                config.list_modules = 1;
                break;
            case 'd':
                config.show_details = 1;
                break;
            case 'r':
                config.show_refs = 1;
                break;
            case 'y':
                config.show_symbols = 1;
                break;
            case 'n':
                config.module_name = optarg;
                break;
            case 's':
                config.search_pattern = optarg;
                break;
            case 'v':
                config.verbose = 1;
                break;
            case 'S':
                config.list_modules = 1;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 如果没有指定操作,默认列出模块
    if (!config.list_modules && !config.show_details && 
        !config.show_refs && !config.show_symbols) {
        config.list_modules = 1;
    }
    
    // 显示系统信息
    if (config.verbose) {
        printf("系统信息:\n");
        printf("  用户 ID: %d\n", getuid());
        printf("  进程 ID: %d\n", getpid());
        printf("  当前目录: %s\n", getcwd(NULL, 0));
        printf("\n");
    }
    
    // 读取模块信息
    struct module_info modules[500];
    int module_count = parse_proc_modules(modules, 500);
    
    if (module_count == -1) {
        printf("错误: 无法读取模块信息\n");
        printf("可能的原因:\n");
        printf("1. 系统不支持 /proc/modules\n");
        printf("2. 权限不足\n");
        printf("3. 内核不支持模块\n");
        return 1;
    }
    
    if (config.verbose) {
        printf("读取到 %d 个模块\n\n", module_count);
    }
    
    // 执行相应操作
    if (config.list_modules) {
        show_module_list(modules, module_count, &config);
    }
    
    if (config.show_details) {
        show_module_details(modules, module_count, &config);
    }
    
    if (optind == 1) {  // 如果没有指定参数,显示统计信息
        show_module_statistics(modules, module_count);
    }
    
    printf("\n=== 模块管理最佳实践 ===\n");
    printf("模块安全建议:\n");
    printf("1. 只加载可信的模块\n");
    printf("2. 定期更新模块\n");
    printf("3. 监控模块加载活动\n");
    printf("4. 限制模块加载权限\n");
    printf("5. 使用签名验证模块\n");
    printf("\n");
    
    printf("模块性能优化:\n");
    printf("1. 卸载不用的模块\n");
    printf("2. 优化模块参数\n");
    printf("3. 监控模块内存使用\n");
    printf("4. 使用模块黑名单\n");
    printf("5. 合理配置模块依赖\n");
    printf("\n");
    
    printf("现代替代方案:\n");
    printf("1. /proc/modules: 模块列表信息\n");
    printf("2. /sys/module/: 模块 sysfs 接口\n");
    printf("3. modinfo: 模块详细信息\n");
    printf("4. lsmod: 列出模块\n");
    printf("5. modprobe: 智能模块加载\n");
    printf("6. rmmod: 卸载模块\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o query_module_example1 example1.c
gcc -o query_module_example2 example2.c
gcc -o query_module_example3 example3.c

# 运行示例
./query_module_example1
./query_module_example2
./query_module_example3 --help
./query_module_example3 -l
./query_module_example3 -d -n usbcore
./query_module_example3 -l -s usb
./query_module_example3 -S

系统要求检查

# 检查模块支持
ls /proc/modules
ls /sys/module/

# 检查模块工具
which lsmod
which modinfo
which modprobe
which rmmod

# 查看内核模块目录
ls /lib/modules/$(uname -r)/kernel/

# 检查内核配置
grep -i module /boot/config-$(uname -r)

重要注意事项

  1. 已废弃query_module 在现代 Linux 系统中已废弃
  2. 权限要求: 某些模块信息需要 root 权限
  3. 系统依赖: 需要内核支持模块和相应的文件系统接口
  4. 错误处理: 始终检查返回值和 errno
  5. 兼容性: 不同内核版本可能有差异

实际应用场景

  1. 系统监控: 监控内核模块加载和卸载
  2. 安全审计: 审计系统中加载的模块
  3. 性能分析: 分析模块对系统性能的影响
  4. 故障诊断: 诊断模块相关的问题
  5. 自动化管理: 自动化模块管理脚本

现代替代方案详解

使用 /proc/modules

// 读取模块列表
int read_module_list() {
    FILE *fp = fopen("/proc/modules", "r");
    if (!fp) return -1;
    
    char line[1024];
    while (fgets(line, sizeof(line), fp)) {
        char name[256], used_by[256], status[32];
        unsigned long size;
        int ref_count;
        
        if (sscanf(line, "%255s %lu %d %255s %31s", 
                  name, &size, &ref_count, used_by, status) >= 4) {
            printf("%-20s %8lu %3d %s\n", name, size, ref_count, used_by);
        }
    }
    
    fclose(fp);
    return 0;
}

使用 /sys/module/ 接口

// 读取模块详细信息
int read_module_details(const char *module_name) {
    char path[256];
    
    // 读取引用计数
    snprintf(path, sizeof(path), "/sys/module/%s/refcnt", module_name);
    FILE *fp = fopen(path, "r");
    if (fp) {
        char refcnt[32];
        if (fgets(refcnt, sizeof(refcnt), fp)) {
            printf("引用计数: %s", refcnt);
        }
        fclose(fp);
    }
    
    return 0;
}

使用命令行工具

# 列出模块
lsmod

# 显示模块信息
modinfo module_name

# 加载模块
sudo modprobe module_name

# 卸载模块
sudo rmmod module_name

# 生成依赖关系
sudo depmod

最佳实践

// 安全的模块信息查询函数
int safe_query_modules(struct module_info *modules, int max_count) {
    // 验证参数
    if (!modules || max_count <= 0) {
        errno = EINVAL;
        return -1;
    }
    
    // 检查文件存在性
    if (access("/proc/modules", F_OK) != 0) {
        errno = ENOENT;
        return -1;
    }
    
    // 读取模块信息
    return parse_proc_modules(modules, max_count);
}

// 模块安全检查
int validate_module_security(const char *module_name) {
    // 检查模块名称合法性
    if (!module_name || strlen(module_name) == 0) {
        return -1;
    }
    
    // 检查是否在黑名单中
    // 这里简化处理,实际应用中可以检查 /etc/modprobe.d/blacklist.conf
    const char *blacklisted[] = {"firewire-sbp2", "usbmouse", NULL};
    for (int i = 0; blacklisted[i]; i++) {
        if (strcmp(module_name, blacklisted[i]) == 0) {
            printf("警告: 模块 '%s' 在黑名单中\n", module_name);
            return -1;
        }
    }
    
    return 0;
}

这些示例展示了 query_module 函数的概念以及现代 Linux 系统中查询内核模块信息的各种替代方案,帮助你全面了解 Linux 系统中的模块管理机制。

query_module系统调用及示例-CSDN博客

发表在 linux文章 | 留下评论

quotactl系统调用及示例

我们来学习一下 quotactl 这个系统调用。它主要用于管理 Linux 系统上的磁盘配额,帮助系统管理员控制用户或组可以使用的磁盘空间量 。

1. 函数介绍

quotactl 是一个 Linux 系统调用(System Call),它提供了一个底层接口来操作 Unix 和 Linux 操作系统中的磁盘配额 。简单来说,它允许你设置和查询特定用户(UID)或组(GID)在某个文件系统上可以使用的磁盘空间上限 。这对于防止某个用户或组占用过多磁盘资源,确保系统资源公平分配非常有用。

2. 函数原型

#include <sys/quota.h>

int quotactl(int cmd, const char *special, int id, caddr_t addr);

3. 功能

这个函数的主要功能是操作磁盘配额 。根据传入的 cmd 参数,它可以执行多种操作,比如设置配额限制、获取当前配额使用情况、开启或关闭文件系统的配额检查等 。

4. 参数

  • cmdint 类型。这是一个命令参数,指定了要执行的具体操作以及配额的类型(用户配额或组配额) 。通常使用 QCMD(subcmd, type) 宏来构造这个命令。subcmd 是具体的操作(如 Q_QUOTAONQ_QUOTAOFFQ_GETQUOTAQ_SETQUOTA 等),type 是配额类型(通常是 USRQUOTA 表示用户配额,GRPQUOTA 表示组配额) 。
  • specialconst char * 类型。指向一个以 null 结尾的字符串,该字符串表示要操作的文件系统设备的路径名(例如 “/dev/sda1”)或挂载点(例如 “/home”) 。
  • idint 类型。指定要操作的用户 ID (UID) 或组 ID (GID),具体取决于 cmd 中指定的类型 。
  • addrcaddr_t 类型(通常可以看作 void *)。一个地址指针,指向包含操作所需数据的缓冲区,或者用于存放函数返回的数据。具体指向的数据结构取决于 cmd 指定的操作 。例如,如果是获取配额信息 (Q_GETQUOTA),它指向一个 struct dqblk 结构体变量,函数会将结果填入其中;如果是设置配额信息 (Q_SETQUOTA),它指向一个包含新配额设置的 struct dqblk 结构体变量。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误类型 。

6. 相似函数或关联函数

  • quota 命令: 这是一个用户空间的命令行工具,用于显示用户或组的磁盘配额信息。它内部可能会使用 quotactl 系统调用来获取信息。
  • quotacheck 命令: 用于扫描文件系统并创建或更新配额文件(如 aquota.user 和 aquota.group),这些文件存储了配额信息 。
  • edquota 命令: 用于编辑用户或组的磁盘配额限制,它也依赖于 quotactl 来应用更改。
  • QCMD 宏: 用于构造 quotactl 的 cmd 参数 。
  • struct dqblk: 这是与 quotactl 配合使用的一个重要结构体,用于存储或传递配额限制和使用情况的数据。

7. 示例代码

前提: 运行这些示例代码需要 root 权限,因为管理配额是系统管理员的任务。同时,目标文件系统需要支持并已启用配额功能。

示例 1: 获取用户磁盘配额信息

#include <stdio.h>
#include <unistd.h>
#include <sys/quota.h>
#include <errno.h>
#include <string.h>

int main() {
    // 假设我们要查询 /home 文件系统的用户配额
    const char *mount_point = "/home";
    // 假设我们要查询 UID 为 1001 的用户的配额
    int user_id = 1001;
    struct dqblk quota_info;
    int result;

    // 使用 QCMD 宏构造 cmd 参数:操作是 Q_GETQUOTA,类型是 USRQUOTA (用户配额)
    result = quotactl(QCMD(Q_GETQUOTA, USRQUOTA), mount_point, user_id, (caddr_t)&quota_info);

    if (result == -1) {
        perror("quotactl"); // 打印错误信息
        fprintf(stderr, "Failed to get quota info for user %d on %s\n", user_id, mount_point);
        return 1;
    }

    // 打印获取到的配额信息
    // 注意: 数值通常以 1KB 块为单位
    printf("User ID: %d\n", user_id);
    printf("Block Hard Limit: %llu KB\n", (unsigned long long)quota_info.dqb_bhardlimit);
    printf("Block Soft Limit: %llu KB\n", (unsigned long long)quota_info.dqb_bsoftlimit);
    printf("Current Blocks Used: %llu KB\n", (unsigned long long)quota_info.dqb_curblocks);
    printf("Inode Hard Limit: %llu\n", (unsigned long long)quota_info.dqb_ihardlimit);
    printf("Inode Soft Limit: %llu\n", (unsigned long long)quota_info.dqb_isoftlimit);
    printf("Current Inodes Used: %llu\n", (unsigned long long)quota_info.dqb_curinodes);
    // dqb_btime 和 dqb_itime 是宽限时间,当超过软限制时才相关
    // printf("Block Grace Time Expiry: %lu\n", quota_info.dqb_btime);
    // printf("Inode Grace Time Expiry: %lu\n", quota_info.dqb_itime);

    return 0;
}

示例 2: 设置用户磁盘配额限制

#include <stdio.h>
#include <unistd.h>
#include <sys/quota.h>
#include <errno.h>
#include <string.h>
#include <time.h> // 需要设置宽限时间

int main() {
    // 假设我们要设置 /home 文件系统的用户配额
    const char *mount_point = "/home";
    // 假设我们要设置 UID 为 1002 的用户的配额
    int user_id = 1002;
    struct dqblk new_quota_limits;
    int result;

    // 初始化结构体
    memset(&new_quota_limits, 0, sizeof(new_quota_limits));

    // 设置块(磁盘空间)限制 (单位通常是 1KB 块)
    new_quota_limits.dqb_bhardlimit = 500000; // 硬限制 500MB
    new_quota_limits.dqb_bsoftlimit = 400000; // 软限制 400MB

    // 设置 inode(文件数量)限制
    new_quota_limits.dqb_ihardlimit = 5000;  // 硬限制 5000 个文件
    new_quota_limits.dqb_isoftlimit = 4000;  // 软限制 4000 个文件

    // 设置宽限时间 (秒) - 当超过软限制时,用户还有这段时间可以清理,之后硬限制生效
    // 如果不设置或设置为0,可能会使用默认值或导致软限制行为异常
    new_quota_limits.dqb_btime = time(NULL) + 7 * 24 * 60 * 60; // 7天后
    new_quota_limits.dqb_itime = time(NULL) + 7 * 24 * 60 * 60; // 7天后
    // 设置有效的字段标志,告诉内核我们要设置哪些字段
    new_quota_limits.dqb_valid = QIF_LIMITS | QIF_TIMES; // 设置限制和时间

    // 使用 QCMD 宏构造 cmd 参数:操作是 Q_SETQUOTA,类型是 USRQUOTA (用户配额)
    result = quotactl(QCMD(Q_SETQUOTA, USRQUOTA), mount_point, user_id, (caddr_t)&new_quota_limits);

    if (result == -1) {
        perror("quotactl"); // 打印错误信息
        fprintf(stderr, "Failed to set quota limits for user %d on %s\n", user_id, mount_point);
        return 1;
    }

    printf("Successfully set quota limits for user %d on %s\n", user_id, mount_point);
    printf("Block Hard Limit: %llu KB, Soft Limit: %llu KB\n",
           (unsigned long long)new_quota_limits.dqb_bhardlimit,
           (unsigned long long)new_quota_limits.dqb_bsoftlimit);
    printf("Inode Hard Limit: %llu, Soft Limit: %llu\n",
           (unsigned long long)new_quota_limits.dqb_ihardlimit,
           (unsigned long long)new_quota_limits.dqb_isoftlimit);

    return 0;
}

编译和运行:

# 假设代码保存在 get_quota.c 和 set_quota.c 中
# 需要 root 权限编译和运行
sudo gcc -o get_quota get_quota.c
sudo gcc -o set_quota set_quota.c

# 运行前确保 /home 文件系统启用了用户配额
# 运行示例 (需要 root 权限)
sudo ./get_quota
sudo ./set_quota

请注意,实际使用中需要确保文件系统已经正确配置并启用了配额功能,这通常涉及在 /etc/fstab 中添加 usrquota 或 grpquota 选项,并使用 quotacheck 和 quotaon 命令初始化和启用配额。

quotactl系统调用及示例-CSDN博客

发表在 linux文章 | 留下评论

readahead系统调用及示例

readahead 函数详解

1. 函数介绍

readahead 是一个Linux系统调用,用于预读文件数据到内核页面缓存中。它允许应用程序提示内核提前读取指定文件区域的数据,从而提高后续读取操作的性能。这个函数特别适用于顺序访问大文件的场景,可以减少I/O等待时间。

2. 函数原型

#define _GNU_SOURCE
#include <fcntl.h>
ssize_t readahead(int fd, off64_t offset, size_t count);

3. 功能

readahead 向内核发出预读提示,建议内核提前将文件中从 offset 开始的 count 字节数据读入页面缓存。这是一个非阻塞操作,不会立即读取数据,而是让内核在适当的时候进行预读。

4. 参数

  • int fd: 文件描述符,必须是已打开的文件(通常需要支持预读的文件系统)
  • off64_t offset: 文件中的偏移量,指定预读开始位置
  • size_t count: 预读的字节数,内核可能根据策略调整实际预读量

5. 返回值

  • 成功: 返回0,表示预读请求已提交
  • 失败: 返回-1,并设置errno

6. 相似函数,或关联函数

  • posix_fadvise: 文件访问建议接口,包含预读建议
  • mmap: 内存映射文件,可以配合MAP_POPULATE使用
  • read: 基本读取函数
  • lseek: 文件定位函数

7. 示例代码

示例1:基础预读示例

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

/**
 * 创建大文件用于测试
 */
int create_test_file(const char *filename, size_t size) {
    int fd;
    char *buffer;
    size_t chunk_size = 1024 * 1024;  // 1MB chunks
    size_t written = 0;
    
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    buffer = malloc(chunk_size);
    if (!buffer) {
        perror("分配缓冲区失败");
        close(fd);
        return -1;
    }
    
    // 填充测试数据
    for (size_t i = 0; i < chunk_size; i++) {
        buffer[i] = 'A' + (i % 26);
    }
    
    printf("正在创建 %zu MB 的测试文件...\n", size / (1024 * 1024));
    
    while (written < size) {
        size_t to_write = (size - written < chunk_size) ? size - written : chunk_size;
        ssize_t result = write(fd, buffer, to_write);
        if (result == -1) {
            perror("写入文件失败");
            free(buffer);
            close(fd);
            return -1;
        }
        written += result;
    }
    
    free(buffer);
    close(fd);
    printf("测试文件创建完成\n");
    return 0;
}

/**
 * 测量读取时间
 */
double time_read_operation(int fd, void *buffer, size_t size) {
    struct timespec start, end;
    ssize_t total_read = 0;
    off_t offset = 0;
    
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    while (total_read < (ssize_t)size) {
        ssize_t to_read = (size - total_read < 1024 * 1024) ? size - total_read : 1024 * 1024;
        ssize_t result = pread(fd, (char*)buffer + total_read, to_read, offset);
        if (result == -1) {
            perror("读取文件失败");
            return -1;
        }
        if (result == 0) break;  // 文件结束
        
        total_read += result;
        offset += result;
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
}

/**
 * 演示readahead的基本使用
 */
int demo_readahead_basic() {
    const char *filename = "test_readahead.dat";
    const size_t file_size = 50 * 1024 * 1024;  // 50MB
    int fd;
    char *buffer;
    double time_without, time_with;
    
    printf("=== readahead 基本使用示例 ===\n");
    
    // 创建测试文件
    if (create_test_file(filename, file_size) != 0) {
        return -1;
    }
    
    // 分配读取缓冲区
    buffer = malloc(file_size);
    if (!buffer) {
        perror("分配读取缓冲区失败");
        unlink(filename);
        return -1;
    }
    
    // 测试不使用readahead的读取性能
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        free(buffer);
        unlink(filename);
        return -1;
    }
    
    printf("第一次读取(无预读)...\n");
    time_without = time_read_operation(fd, buffer, file_size);
    if (time_without > 0) {
        printf("无预读读取时间: %.3f 秒\n", time_without);
    }
    
    close(fd);
    
    // 测试使用readahead的读取性能
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        free(buffer);
        unlink(filename);
        return -1;
    }
    
    // 使用readahead预读整个文件
    printf("执行预读操作...\n");
    if (readahead(fd, 0, file_size) == 0) {
        printf("预读请求提交成功\n");
    } else {
        printf("预读请求失败: %s\n", strerror(errno));
    }
    
    // 等待一小段时间让预读完成
    sleep(1);
    
    printf("第二次读取(有预读)...\n");
    time_with = time_read_operation(fd, buffer, file_size);
    if (time_with > 0) {
        printf("有预读读取时间: %.3f 秒\n", time_with);
        if (time_without > 0) {
            printf("性能提升: %.1f%%\n", 
                   (time_without - time_with) / time_without * 100);
        }
    }
    
    close(fd);
    free(buffer);
    unlink(filename);
    
    return 0;
}

int main() {
    return demo_readahead_basic();
}

示例2:分段预读示例

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

/**
 * 演示分段预读的使用
 */
int demo_readahead_segmented() {
    const char *filename = "segmented_test.dat";
    const size_t file_size = 100 * 1024 * 1024;  // 100MB
    const size_t segment_size = 10 * 1024 * 1024;  // 10MB per segment
    int fd;
    char *buffer;
    struct timespec start, end;
    double total_time = 0;
    
    printf("=== readahead 分段预读示例 ===\n");
    
    // 创建测试文件
    int test_fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (test_fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    buffer = malloc(segment_size);
    if (!buffer) {
        perror("分配缓冲区失败");
        close(test_fd);
        unlink(filename);
        return -1;
    }
    
    // 填充测试数据
    for (size_t i = 0; i < segment_size; i++) {
        buffer[i] = 'A' + (i % 26);
    }
    
    // 写入文件数据
    for (size_t offset = 0; offset < file_size; offset += segment_size) {
        write(test_fd, buffer, segment_size);
    }
    
    close(test_fd);
    printf("创建了 %zu MB 的测试文件\n", file_size / (1024 * 1024));
    
    // 打开文件进行测试
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        free(buffer);
        unlink(filename);
        return -1;
    }
    
    printf("开始分段读取测试...\n");
    
    // 分段预读和读取
    for (size_t offset = 0; offset < file_size; offset += segment_size) {
        printf("处理段 %zu/%zu MB\n", 
               (offset + segment_size) / (1024 * 1024),
               file_size / (1024 * 1024));
        
        // 预读当前段
        clock_gettime(CLOCK_MONOTONIC, &start);
        if (readahead(fd, offset, segment_size) == 0) {
            // printf("  预读段 %zu 完成\n", offset / segment_size);
        } else {
            printf("  预读段 %zu 失败: %s\n", offset / segment_size, strerror(errno));
        }
        
        // 等待预读完成(实际应用中可能不需要)
        usleep(100000);  // 100ms
        
        // 读取当前段
        ssize_t bytes_read = pread(fd, buffer, segment_size, offset);
        if (bytes_read == -1) {
            perror("读取段失败");
            break;
        }
        
        clock_gettime(CLOCK_MONOTONIC, &end);
        double segment_time = (end.tv_sec - start.tv_sec) + 
                             (end.tv_nsec - start.tv_nsec) / 1e9;
        total_time += segment_time;
        
        printf("  段处理时间: %.3f 秒\n", segment_time);
    }
    
    printf("\n总处理时间: %.3f 秒\n", total_time);
    printf("平均段处理时间: %.3f 秒\n", total_time / (file_size / segment_size));
    
    close(fd);
    free(buffer);
    unlink(filename);
    
    return 0;
}

int main() {
    return demo_readahead_segmented();
}

示例3:与posix_fadvise对比示例

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>

/**
 * 创建测试文件
 */
int create_large_file(const char *filename, size_t size) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    char *buffer = malloc(1024 * 1024);
    if (!buffer) {
        perror("分配缓冲区失败");
        close(fd);
        return -1;
    }
    
    // 填充数据
    for (int i = 0; i < 1024 * 1024; i++) {
        buffer[i] = 'A' + (i % 26);
    }
    
    size_t written = 0;
    while (written < size) {
        size_t to_write = (size - written < 1024 * 1024) ? size - written : 1024 * 1024;
        ssize_t result = write(fd, buffer, to_write);
        if (result == -1) {
            perror("写入文件失败");
            free(buffer);
            close(fd);
            return -1;
        }
        written += result;
    }
    
    free(buffer);
    close(fd);
    return 0;
}

/**
 * 使用readahead进行预读
 */
int test_readahead_method(const char *filename) {
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        perror("获取文件状态失败");
        close(fd);
        return -1;
    }
    
    // 使用readahead预读
    if (readahead(fd, 0, sb.st_size) == 0) {
        printf("使用readahead预读成功\n");
    } else {
        printf("使用readahead预读失败: %s\n", strerror(errno));
    }
    
    close(fd);
    return 0;
}

/**
 * 使用posix_fadvise进行预读
 */
int test_fadvise_method(const char *filename) {
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        perror("获取文件状态失败");
        close(fd);
        return -1;
    }
    
    // 使用posix_fadvise预读
    if (posix_fadvise(fd, 0, sb.st_size, POSIX_FADV_WILLNEED) == 0) {
        printf("使用posix_fadvise预读成功\n");
    } else {
        printf("使用posix_fadvise预读失败: %s\n", strerror(errno));
    }
    
    close(fd);
    return 0;
}

/**
 * 演示readahead与posix_fadvise的对比
 */
int demo_readahead_vs_fadvise() {
    const char *filename = "comparison_test.dat";
    const size_t file_size = 50 * 1024 * 1024;  // 50MB
    
    printf("=== readahead vs posix_fadvise 对比示例 ===\n");
    
    // 创建测试文件
    if (create_large_file(filename, file_size) != 0) {
        return -1;
    }
    
    printf("创建了 %zu MB 的测试文件\n", file_size / (1024 * 1024));
    
    printf("\n1. 测试readahead方法:\n");
    test_readahead_method(filename);
    
    printf("\n2. 测试posix_fadvise方法:\n");
    test_fadvise_method(filename);
    
    printf("\n3. 功能对比:\n");
    printf("   readahead:\n");
    printf("     - 专门的预读系统调用\n");
    printf("     - 直接控制预读字节数\n");
    printf("     - 更精确的控制\n");
    printf("   posix_fadvise:\n");
    printf("     - 通用的文件访问建议接口\n");
    printf("     - 支持多种访问模式\n");
    printf("     - 更好的可移植性\n");
    
    unlink(filename);
    return 0;
}

int main() {
    return demo_readahead_vs_fadvise();
}

示例4:实际应用场景示例

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

/**
 * 模拟视频播放器的预读策略
 */
typedef struct {
    int fd;
    off_t file_size;
    off_t current_pos;
    size_t buffer_size;
} video_player_t;

/**
 * 初始化视频播放器
 */
int video_player_init(video_player_t *player, const char *filename) {
    player->fd = open(filename, O_RDONLY);
    if (player->fd == -1) {
        perror("打开视频文件失败");
        return -1;
    }
    
    struct stat sb;
    if (fstat(player->fd, &sb) == -1) {
        perror("获取文件状态失败");
        close(player->fd);
        return -1;
    }
    
    player->file_size = sb.st_size;
    player->current_pos = 0;
    player->buffer_size = 2 * 1024 * 1024;  // 2MB缓冲区
    
    printf("视频文件大小: %.2f MB\n", player->file_size / (1024.0 * 1024.0));
    
    return 0;
}

/**
 * 播放视频(模拟)
 */
int video_player_play(video_player_t *player, int use_readahead) {
    char *buffer = malloc(player->buffer_size);
    if (!buffer) {
        perror("分配播放缓冲区失败");
        return -1;
    }
    
    printf("开始播放视频%s预读...\n", use_readahead ? "(使用" : "(不使用");
    
    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    while (player->current_pos < player->file_size) {
        // 根据播放位置决定是否预读
        if (use_readahead && player->current_pos + player->buffer_size < player->file_size) {
            // 预读下一缓冲区的数据
            off_t ahead_pos = player->current_pos + player->buffer_size;
            size_t ahead_size = (player->file_size - ahead_pos > player->buffer_size) ? 
                               player->buffer_size : player->file_size - ahead_pos;
            
            if (readahead(player->fd, ahead_pos, ahead_size) == 0) {
                // printf("预读位置 %ld, 大小 %zu\n", ahead_pos, ahead_size);
            }
        }
        
        // 读取当前缓冲区数据
        ssize_t bytes_read = pread(player->fd, buffer, player->buffer_size, player->current_pos);
        if (bytes_read <= 0) {
            if (bytes_read == -1) {
                perror("读取视频数据失败");
            }
            break;
        }
        
        // 模拟解码和播放处理
        usleep(50000);  // 50ms处理时间
        
        player->current_pos += bytes_read;
        
        if (player->current_pos % (10 * 1024 * 1024) == 0) {
            printf("已播放 %.2f MB\n", player->current_pos / (1024.0 * 1024.0));
        }
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    double play_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    
    printf("播放完成,总时间: %.3f 秒\n", play_time);
    
    free(buffer);
    return 0;
}

/**
 * 清理视频播放器
 */
void video_player_cleanup(video_player_t *player) {
    if (player->fd != -1) {
        close(player->fd);
        player->fd = -1;
    }
}

/**
 * 演示视频播放场景中的预读应用
 */
int demo_video_player_scenario() {
    const char *filename = "video_sample.dat";
    const size_t file_size = 100 * 1024 * 1024;  // 100MB
    video_player_t player_without, player_with;
    
    printf("=== 视频播放场景中的预读应用 ===\n");
    
    // 创建测试视频文件
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建视频文件失败");
        return -1;
    }
    
    char *buffer = malloc(1024 * 1024);
    if (!buffer) {
        perror("分配缓冲区失败");
        close(fd);
        return -1;
    }
    
    // 填充随机视频数据
    srand(time(NULL));
    for (int i = 0; i < 1024 * 1024; i++) {
        buffer[i] = rand() % 256;
    }
    
    // 写入文件
    size_t written = 0;
    while (written < file_size) {
        size_t to_write = (file_size - written < 1024 * 1024) ? 
                         file_size - written : 1024 * 1024;
        write(fd, buffer, to_write);
        written += to_write;
    }
    
    free(buffer);
    close(fd);
    printf("创建了 %.2f MB 的视频测试文件\n", file_size / (1024.0 * 1024.0));
    
    // 测试不使用预读的播放
    printf("\n--- 不使用预读的播放测试 ---\n");
    memset(&player_without, 0, sizeof(player_without));
    player_without.fd = -1;
    
    if (video_player_init(&player_without, filename) == 0) {
        video_player_play(&player_without, 0);
        video_player_cleanup(&player_without);
    }
    
    // 测试使用预读的播放
    printf("\n--- 使用预读的播放测试 ---\n");
    memset(&player_with, 0, sizeof(player_with));
    player_with.fd = -1;
    
    if (video_player_init(&player_with, filename) == 0) {
        video_player_play(&player_with, 1);
        video_player_cleanup(&player_with);
    }
    
    unlink(filename);
    return 0;
}

int main() {
    return demo_video_player_scenario();
}

示例5:预读策略优化示例

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

/**
 * 智能预读器
 */
typedef struct {
    int fd;
    off_t file_size;
    off_t last_read_pos;
    size_t read_pattern[10];  // 记录最近10次读取的大小
    int pattern_index;
    int pattern_count;
} smart_readaheater_t;

/**
 * 初始化智能预读器
 */
int smart_readaheater_init(smart_readaheater_t *sr, const char *filename) {
    sr->fd = open(filename, O_RDONLY);
    if (sr->fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    struct stat sb;
    if (fstat(sr->fd, &sb) == -1) {
        perror("获取文件状态失败");
        close(sr->fd);
        return -1;
    }
    
    sr->file_size = sb.st_size;
    sr->last_read_pos = 0;
    sr->pattern_index = 0;
    sr->pattern_count = 0;
    
    memset(sr->read_pattern, 0, sizeof(sr->read_pattern));
    
    printf("智能预读器初始化完成\n");
    printf("文件大小: %.2f MB\n", sr->file_size / (1024.0 * 1024.0));
    
    return 0;
}

/**
 * 分析读取模式
 */
size_t analyze_read_pattern(smart_readaheater_t *sr) {
    if (sr->pattern_count < 3) {
        return 1024 * 1024;  // 默认1MB
    }
    
    // 计算平均读取大小
    size_t total = 0;
    int count = (sr->pattern_count < 10) ? sr->pattern_count : 10;
    
    for (int i = 0; i < count; i++) {
        total += sr->read_pattern[i];
    }
    
    return total / count;
}

/**
 * 智能预读
 */
int smart_readahead(smart_readaheater_t *sr, off_t pos, size_t size) {
    // 记录本次读取模式
    sr->read_pattern[sr->pattern_index] = size;
    sr->pattern_index = (sr->pattern_index + 1) % 10;
    if (sr->pattern_count < 10) {
        sr->pattern_count++;
    }
    
    // 分析读取模式
    size_t predicted_size = analyze_read_pattern(sr);
    
    // 预测下一个读取位置
    off_t next_pos = pos + size;
    
    // 如果下一个位置有效,则进行预读
    if (next_pos < sr->file_size) {
        size_t readahead_size = predicted_size * 2;  // 预读两倍大小
        if (next_pos + readahead_size > sr->file_size) {
            readahead_size = sr->file_size - next_pos;
        }
        
        if (readahead(sr->fd, next_pos, readahead_size) == 0) {
            printf("智能预读: 位置 %ld, 大小 %zu\n", next_pos, readahead_size);
            return 0;
        }
    }
    
    return -1;
}

/**
 * 读取数据并触发智能预读
 */
ssize_t smart_read(smart_readaheater_t *sr, void *buf, size_t count, off_t offset) {
    ssize_t bytes_read = pread(sr->fd, buf, count, offset);
    if (bytes_read > 0) {
        smart_readahead(sr, offset, bytes_read);
        sr->last_read_pos = offset + bytes_read;
    }
    return bytes_read;
}

/**
 * 演示智能预读策略
 */
int demo_smart_readahead() {
    const char *filename = "smart_test.dat";
    const size_t file_size = 50 * 1024 * 1024;  // 50MB
    smart_readaheater_t sr;
    
    printf("=== 智能预读策略示例 ===\n");
    
    // 创建测试文件
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    char *buffer = malloc(1024 * 1024);
    if (!buffer) {
        perror("分配缓冲区失败");
        close(fd);
        return -1;
    }
    
    // 填充测试数据
    for (size_t i = 0; i < 1024 * 1024; i++) {
        buffer[i] = 'A' + (i % 26);
    }
    
    // 写入文件
    size_t written = 0;
    while (written < file_size) {
        size_t to_write = (file_size - written < 1024 * 1024) ? 
                         file_size - written : 1024 * 1024;
        write(fd, buffer, to_write);
        written += to_write;
    }
    
    free(buffer);
    close(fd);
    printf("创建了 %.2f MB 的测试文件\n", file_size / (1024.0 * 1024.0));
    
    // 初始化智能预读器
    if (smart_readaheater_init(&sr, filename) != 0) {
        unlink(filename);
        return -1;
    }
    
    // 模拟不同模式的读取
    printf("\n开始智能预读测试:\n");
    
    char *read_buffer = malloc(2 * 1024 * 1024);  // 2MB缓冲区
    if (!read_buffer) {
        perror("分配读取缓冲区失败");
        close(sr.fd);
        unlink(filename);
        return -1;
    }
    
    // 模拟顺序读取
    printf("1. 顺序读取模式:\n");
    for (off_t pos = 0; pos < 20 * 1024 * 1024; pos += 512 * 1024) {
        ssize_t bytes_read = smart_read(&sr, read_buffer, 512 * 1024, pos);
        if (bytes_read > 0) {
            printf("  读取位置 %ld, 大小 %zd\n", pos, bytes_read);
        }
    }
    
    // 模拟随机读取
    printf("\n2. 随机读取模式:\n");
    srand(time(NULL));
    for (int i = 0; i < 5; i++) {
        off_t pos = (rand() % (int)(file_size - 1024 * 1024));
        size_t size = 256 * 1024 + (rand() % (768 * 1024));
        ssize_t bytes_read = smart_read(&sr, read_buffer, size, pos);
        if (bytes_read > 0) {
            printf("  随机读取位置 %ld, 大小 %zd\n", pos, bytes_read);
        }
    }
    
    free(read_buffer);
    close(sr.fd);
    unlink(filename);
    
    printf("\n智能预读策略特点:\n");
    printf("  - 学习读取模式\n");
    printf("  - 动态调整预读大小\n");
    printf("  - 适应不同的访问模式\n");
    
    return 0;
}

int main() {
    return demo_smart_readahead();
}

readahead 使用注意事项

适用场景:

  1. 大文件顺序访问: 读取大型文件时特别有效
  2. 可预测的访问模式: 顺序读取或规律性访问
  3. I/O密集型应用: 数据库、媒体播放器、文件传输工具

不适用场景:

  1. 随机访问: 频繁随机访问的文件不适合预读
  2. 小文件: 文件很小时预读开销大于收益
  3. 内存紧张: 系统内存不足时预读可能降低性能

性能考虑:

  1. 预读大小: 需要根据具体场景调整预读大小
  2. 时机选择: 合适的预读时机很重要
  3. 系统负载: 高负载时谨慎使用预读

错误处理:

  1. 检查返回值: readahead失败时不会影响正常读取
  2. 权限检查: 确保有足够的权限访问文件
  3. 文件状态: 文件必须是打开状态且支持预读

总结

readahead 是一个强大的预读工具,能够显著提高顺序访问大文件的性能。通过合理的预读策略,可以减少I/O等待时间,提高应用程序的响应速度。在实际应用中,需要根据具体的访问模式和系统环境来设计合适的预读策略。

readahead系统调用及示例-CSDN博客

发表在 linux文章 | 留下评论

reboot系统调用及示例

我们来学习一下 reboot 这个系统调用。它是一个底层的 Linux 系统调用,用于重启、关闭或挂起你的计算机 。

1. 函数介绍

reboot 系统调用(System Call)提供了直接与 Linux 内核交互以控制机器状态(如重启、关机)的接口 。顾名思义,它的主要作用是重新启动机器,但根据提供的参数,它也能执行关闭电源、挂起系统等操作 。这与用户通常在命令行使用的 reboot 命令不同,后者是一个更高级别的程序,内部可能会调用这个系统调用 。

reboot系统调用及示例-CSDN博客

2. 函数原型

#include <unistd.h>
#include <linux/reboot.h> // 包含定义的常量

// 注意:实际的系统调用接口通常带有前缀下划线
int __reboot(int magic, int magic2, unsigned int cmd, void *arg);

注意:直接使用 __reboot 系统调用比较复杂且不推荐。通常,标准 C 库(glibc)会提供一个更方便的包装函数 reboot

更常用的 glibc 包装函数原型:

#include <sys/reboot.h> // glibc 封装函数的头文件

int reboot(int cmd);

3. 功能

这个系统调用的核心功能是根据 cmd 参数的值来执行不同的系统级操作,主要包括重启系统、关闭系统电源、挂起系统或启用/禁用 Ctrl+Alt+Del 组合键的功能 。

4. 参数

对于 glibc 封装的 reboot(int cmd) 函数:

  • cmdint 类型。这个参数指定了要执行的具体操作。常见的值定义在 sys/reboot.h 头文件中,例如:
    • LINUX_REBOOT_CMD_RESTART: 立即重启系统。
    • LINUX_REBOOT_CMD_HALT: 停止操作系统,但不关闭电源(挂起)。
    • LINUX_REBOOT_CMD_POWER_OFF: 停止操作系统并关闭电源(如果硬件支持)。
    • LINUX_REBOOT_CMD_RESTART2: 重启系统,并传递一个可选的命令行参数给内核(通过 arg 参数,但这在 glibc 包装函数中不直接暴露)。
    • LINUX_REBOOT_CMD_CAD_ON: 允许使用 Ctrl+Alt+Del 组合键重启系统。
    • LINUX_REBOOT_CMD_CAD_OFF: 禁止使用 Ctrl+Alt+Del 组合键。

对于底层的 __reboot(int magic, int magic2, unsigned int cmd, void *arg) 系统调用:

  • magicint 类型。第一个“魔数”,必须是 LINUX_REBOOT_MAGIC1 。这是为了防止意外调用系统调用 。
  • magic2int 类型。第二个“魔数”,必须是 LINUX_REBOOT_MAGIC2 或 LINUX_REBOOT_MAGIC2A/B/C 中的一个 。同样是为了防止误操作 。
  • cmdunsigned int 类型。指定要执行的操作,与 glibc reboot 的 cmd 参数类似,但可能使用内核空间定义的常量(如 LINUX_REBOOT_CMD_*)。
  • argvoid * 类型。一个指向缓冲区的指针,用于传递附加参数,例如传递给内核的命令行字符串(当 cmd 为 LINUX_REBOOT_CMD_RESTART2 时)。

5. 返回值

  • 成功: 对于 reboot(int cmd),如果调用成功,通常不会返回,因为系统会立即执行指定的操作(如重启或关机)。
  • 失败: 返回 -1,并设置全局变量 errno 来指示错误原因,最常见的失败原因是权限不足(需要 root 权限)。

6. 相似函数或关联函数

  • reboot 命令: 用户空间的标准命令行工具,用于重启系统,内部可能调用 reboot() 系统调用 。
  • shutdown 命令: 更通用的关机/重启命令,允许指定时间、广播消息等,最终也可能调用 reboot() 系统调用。
  • poweroff 命令: 用于关闭系统电源,通常也是通过系统调用实现。
  • halt 命令: 停止系统,行为取决于实现和参数,有时与 poweroff 类似。
  • sync 系统调用: 在执行 reboot 之前,通常会先调用 sync() 将所有未写入磁盘的数据刷新到磁盘,以防数据丢失。

7. 示例代码

注意: 运行此代码需要 root 权限,因为它执行的是特权操作。系统会立即重启,所以请确保保存所有工作。

#include <stdio.h>
#include <unistd.h> // 包含 sync
#include <sys/reboot.h> // 包含 reboot 函数和命令常量
#include <errno.h>
#include <string.h>

int main() {
    printf("准备重启系统...\n");

    // 1. 强烈建议在重启前先同步文件系统
    // 这会将所有缓存中的数据写入磁盘,防止数据丢失
    printf("正在同步文件系统...\n");
    sync(); // 执行同步操作
    // sync() 通常没有返回值用于检查错误,它尽力而为

    // 2. 调用 reboot 系统调用 (通过 glibc 包装)
    // 使用 LINUX_REBOOT_CMD_RESTART 命令重启系统
    printf("正在调用 reboot 系统调用...\n");
    if (reboot(LINUX_REBOOT_CMD_RESTART) == -1) {
        // 如果返回 -1,表示调用失败
        perror("reboot"); // 打印具体的错误原因
        fprintf(stderr, "错误: 无法重启系统。请确保以 root 权限运行此程序。\n");
        return 1; // 返回错误码
    }

    // 如果 reboot 调用成功,下面的代码通常不会被执行,
    // 因为系统已经开始重启过程。
    printf("系统调用已执行,但程序仍在运行?这很奇怪。\n");

    return 0; // 理论上不会到达这里
}

编译和运行:

# 假设代码保存在 reboot_example.c 中
# 编译
gcc -o reboot_example reboot_example.c

# 运行 (需要 root 权限)
sudo ./reboot_example

再次提醒: 执行此程序会使您的计算机立即重启,请谨慎操作!

发表在 linux文章 | 留下评论

recvmsg系统调用及示例

recvmsg 函数详解

1. 函数介绍

recvmsg 是Linux网络编程中功能最强大的接收数据函数之一。它是 recv 和 recvfrom 的增强版本,支持接收控制信息(如文件描述符、时间戳等)和分散缓冲区(scatter-gather I/O)。recvmsg 特别适用于需要接收复杂网络数据包的应用场景。

2. 函数原型

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

3. 功能

recvmsg 从套接字接收数据,并可以同时接收额外的控制信息。它支持分散缓冲区接收、接收发送者地址信息、接收辅助数据(如文件描述符传递)等功能。

4. 参数

  • int sockfd: 套接字文件描述符
  • *struct msghdr msg: 消息头结构,描述接收缓冲区和控制信息
  • int flags: 接收标志,控制接收行为

5. 返回值

  • 成功: 返回实际接收到的字节数
  • 连接关闭: 返回0
  • 失败: 返回-1,并设置errno

6. 相似函数,或关联函数

  • recv: 基本接收函数
  • recvfrom: 带地址信息的接收函数
  • sendmsg: 对应的发送函数
  • read: 基本读取函数

7. 示例代码

示例1:基础recvmsg使用

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

/**
 * 演示recvmsg的基本使用方法
 */
int demo_recvmsg_basic() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    struct msghdr msg;
    struct iovec iov[1];
    char buffer[1024];
    char control_buffer[1024];
    ssize_t bytes_received;
    
    printf("=== recvmsg 基本使用示例 ===\n");
    
    // 创建TCP服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("创建服务器套接字失败");
        return -1;
    }
    
    // 设置服务器地址
    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(8080);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return -1;
    }
    
    // 监听连接
    if (listen(server_fd, 1) == -1) {
        perror("监听失败");
        close(server_fd);
        return -1;
    }
    
    printf("服务器监听在端口 8080\n");
    
    // 在后台启动客户端(简化演示)
    if (fork() == 0) {
        // 客户端代码
        sleep(1);  // 等待服务器启动
        int client_sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in serv_addr;
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(8080);
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
            const char *message = "Hello from client!";
            send(client_sock, message, strlen(message), 0);
            printf("客户端发送消息: %s\n", message);
        }
        
        close(client_sock);
        exit(0);
    }
    
    // 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        close(server_fd);
        return -1;
    }
    
    printf("客户端连接来自: %s:%d\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    
    // 准备msghdr结构
    memset(&msg, 0, sizeof(msg));
    
    // 设置接收缓冲区
    iov[0].iov_base = buffer;
    iov[0].iov_len = sizeof(buffer) - 1;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    
    // 设置控制缓冲区(用于接收辅助数据)
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);
    
    // 设置地址信息缓冲区
    msg.msg_name = &client_addr;
    msg.msg_namelen = client_len;
    
    // 接收消息
    bytes_received = recvmsg(client_fd, &msg, 0);
    if (bytes_received == -1) {
        perror("recvmsg 失败");
        close(client_fd);
        close(server_fd);
        return -1;
    }
    
    buffer[bytes_received] = '\0';
    printf("recvmsg 接收到 %zd 字节数据: %s\n", bytes_received, buffer);
    printf("消息标志: %d\n", msg.msg_flags);
    
    // 显示地址信息
    if (msg.msg_namelen > 0) {
        struct sockaddr_in *addr = (struct sockaddr_in*)msg.msg_name;
        printf("发送者地址: %s:%d\n", 
               inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
    }
    
    close(client_fd);
    close(server_fd);
    
    return 0;
}

int main() {
    return demo_recvmsg_basic();
}

示例2:分散缓冲区接收

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

/**
 * 演示recvmsg的分散缓冲区接收功能
 */
int demo_recvmsg_scatter_gather() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr;
    struct msghdr msg;
    struct iovec iov[3];
    char buffer1[100], buffer2[100], buffer3[100];
    ssize_t bytes_received;
    
    printf("=== recvmsg 分散缓冲区接收示例 ===\n");
    
    // 创建UDP套接字
    server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_fd == -1) {
        perror("创建UDP套接字失败");
        return -1;
    }
    
    // 设置服务器地址
    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(8081);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return -1;
    }
    
    printf("UDP服务器监听在端口 8081\n");
    
    // 启动UDP客户端
    if (fork() == 0) {
        // 客户端代码
        sleep(1);
        int client_sock = socket(AF_INET, SOCK_DGRAM, 0);
        struct sockaddr_in serv_addr;
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(8081);
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        // 发送长消息
        const char *long_message = "This is a very long message that will be split across multiple buffers during scatter-gather reception.";
        sendto(client_sock, long_message, strlen(long_message), 0,
               (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        printf("客户端发送长消息 (%zu 字节)\n", strlen(long_message));
        
        close(client_sock);
        exit(0);
    }
    
    // 准备分散缓冲区
    memset(&msg, 0, sizeof(msg));
    
    // 设置三个分散的缓冲区
    iov[0].iov_base = buffer1;
    iov[0].iov_len = sizeof(buffer1) - 1;
    iov[1].iov_base = buffer2;
    iov[1].iov_len = sizeof(buffer2) - 1;
    iov[2].iov_base = buffer3;
    iov[2].iov_len = sizeof(buffer3) - 1;
    
    msg.msg_iov = iov;
    msg.msg_iovlen = 3;
    
    // 接收消息
    bytes_received = recvmsg(server_fd, &msg, 0);
    if (bytes_received == -1) {
        perror("recvmsg 失败");
        close(server_fd);
        return -1;
    }
    
    printf("recvmsg 接收到 %zd 字节数据\n", bytes_received);
    printf("消息被分散到 %d 个缓冲区\n", (int)msg.msg_iovlen);
    printf("实际使用的缓冲区数: %d\n", (int)msg.msg_iovlen);
    
    // 添加字符串终止符
    size_t total_len = 0;
    for (int i = 0; i < 3 && total_len < (size_t)bytes_received; i++) {
        size_t buf_len = iov[i].iov_len;
        if (total_len + buf_len > (size_t)bytes_received) {
            buf_len = bytes_received - total_len;
        }
        ((char*)iov[i].iov_base)[buf_len] = '\0';
        total_len += buf_len;
    }
    
    printf("缓冲区1内容 (%zu 字节): %s\n", strlen(buffer1), buffer1);
    printf("缓冲区2内容 (%zu 字节): %s\n", strlen(buffer2), buffer2);
    printf("缓冲区3内容 (%zu 字节): %s\n", strlen(buffer3), buffer3);
    
    // 合并显示完整消息
    char full_message[512];
    snprintf(full_message, sizeof(full_message), "%s%s%s", buffer1, buffer2, buffer3);
    printf("完整消息: %s\n", full_message);
    
    close(server_fd);
    
    return 0;
}

int main() {
    return demo_recvmsg_scatter_gather();
}

示例3:接收控制信息

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

/**
 * 演示recvmsg接收控制信息(时间戳)
 */
int demo_recvmsg_control_data() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    struct msghdr msg;
    struct iovec iov[1];
    char buffer[1024];
    char control_buffer[1024];
    ssize_t bytes_received;
    
    printf("=== recvmsg 控制信息接收示例 ===\n");
    
    // 创建TCP服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("创建服务器套接字失败");
        return -1;
    }
    
    // 启用时间戳选项
    int timestamp_on = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_TIMESTAMP, 
                   &timestamp_on, sizeof(timestamp_on)) == -1) {
        printf("警告: 无法启用时间戳选项: %s\n", strerror(errno));
    }
    
    // 设置服务器地址
    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(8082);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return -1;
    }
    
    // 监听连接
    if (listen(server_fd, 1) == -1) {
        perror("监听失败");
        close(server_fd);
        return -1;
    }
    
    printf("带时间戳的服务器监听在端口 8082\n");
    
    // 启动客户端
    if (fork() == 0) {
        // 客户端代码
        sleep(1);
        int client_sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in serv_addr;
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(8082);
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
            const char *message = "Message with timestamp";
            send(client_sock, message, strlen(message), 0);
            printf("客户端发送消息: %s\n", message);
        }
        
        close(client_sock);
        exit(0);
    }
    
    // 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        close(server_fd);
        return -1;
    }
    
    printf("客户端连接建立\n");
    
    // 准备msghdr结构用于接收控制信息
    memset(&msg, 0, sizeof(msg));
    
    // 设置接收缓冲区
    iov[0].iov_base = buffer;
    iov[0].iov_len = sizeof(buffer) - 1;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    
    // 设置控制缓冲区
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);
    
    // 接收消息和控制信息
    bytes_received = recvmsg(client_fd, &msg, 0);
    if (bytes_received == -1) {
        perror("recvmsg 失败");
        close(client_fd);
        close(server_fd);
        return -1;
    }
    
    buffer[bytes_received] = '\0';
    printf("接收到消息: %s\n", buffer);
    printf("接收时间戳信息:\n");
    
    // 解析控制信息
    struct cmsghdr *cmsg;
    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
            struct timeval *tv = (struct timeval*)CMSG_DATA(cmsg);
            printf("  时间戳: %ld.%06ld\n", tv->tv_sec, tv->tv_usec);
            
            // 转换为可读格式
            char time_str[64];
            time_t sec = tv->tv_sec;
            struct tm *tm_info = localtime(&sec);
            strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
            printf("  可读时间: %s.%06ld\n", time_str, tv->tv_usec);
        } else {
            printf("  其他控制信息: level=%d, type=%d\n", 
                   cmsg->cmsg_level, cmsg->cmsg_type);
        }
    }
    
    if (msg.msg_controllen == 0) {
        printf("  没有接收到控制信息\n");
    }
    
    close(client_fd);
    close(server_fd);
    
    return 0;
}

int main() {
    return demo_recvmsg_control_data();
}

示例4:文件描述符传递

#define _GNU_SOURCE
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

/**
 * 演示通过recvmsg传递文件描述符
 */
int demo_recvmsg_fd_passing() {
    int sv[2];  // socket pair
    struct msghdr msg;
    struct iovec iov[1];
    char buffer[256];
    char control_buffer[CMSG_SPACE(sizeof(int))];
    ssize_t bytes_received;
    
    printf("=== recvmsg 文件描述符传递示例 ===\n");
    
    // 创建socket pair用于进程间通信
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        perror("创建socket pair失败");
        return -1;
    }
    
    printf("创建了socket pair: %d, %d\n", sv[0], sv[1]);
    
    if (fork() == 0) {
        // 子进程:发送文件描述符
        close(sv[0]);  // 关闭接收端
        
        // 创建一个临时文件
        const char *temp_filename = "/tmp/fd_pass_test.txt";
        int temp_fd = open(temp_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (temp_fd == -1) {
            perror("创建临时文件失败");
            close(sv[1]);
            exit(1);
        }
        
        // 写入一些数据
        const char *test_data = "This data is in the passed file descriptor";
        write(temp_fd, test_data, strlen(test_data));
        
        printf("子进程创建了文件: %s\n", temp_filename);
        printf("子进程准备传递文件描述符 %d\n", temp_fd);
        
        // 准备发送消息和文件描述符
        struct msghdr send_msg;
        struct iovec send_iov[1];
        struct cmsghdr *cmsg;
        char send_buffer[] = "File descriptor passed";
        char send_control[CMSG_SPACE(sizeof(int))];
        
        // 设置消息内容
        send_iov[0].iov_base = send_buffer;
        send_iov[0].iov_len = strlen(send_buffer);
        
        memset(&send_msg, 0, sizeof(send_msg));
        send_msg.msg_iov = send_iov;
        send_msg.msg_iovlen = 1;
        send_msg.msg_control = send_control;
        send_msg.msg_controllen = sizeof(send_control);
        
        // 设置控制信息(文件描述符)
        cmsg = CMSG_FIRSTHDR(&send_msg);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
        memcpy(CMSG_DATA(cmsg), &temp_fd, sizeof(int));
        
        send_msg.msg_controllen = cmsg->cmsg_len;
        
        // 发送消息和文件描述符
        if (sendmsg(sv[1], &send_msg, 0) == -1) {
            perror("sendmsg 失败");
            close(temp_fd);
            close(sv[1]);
            exit(1);
        }
        
        printf("子进程发送了消息和文件描述符\n");
        
        // 关闭原始文件描述符
        close(temp_fd);
        unlink(temp_filename);
        close(sv[1]);
        
        exit(0);
    } else {
        // 父进程:接收文件描述符
        close(sv[1]);  // 关闭发送端
        
        // 准备接收消息和文件描述符
        memset(&msg, 0, sizeof(msg));
        
        iov[0].iov_base = buffer;
        iov[0].iov_len = sizeof(buffer) - 1;
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;
        msg.msg_control = control_buffer;
        msg.msg_controllen = sizeof(control_buffer);
        
        // 接收消息和文件描述符
        bytes_received = recvmsg(sv[0], &msg, 0);
        if (bytes_received == -1) {
            perror("recvmsg 失败");
            close(sv[0]);
            return -1;
        }
        
        buffer[bytes_received] = '\0';
        printf("父进程接收到消息: %s\n", buffer);
        
        // 解析接收到的文件描述符
        int received_fd = -1;
        struct cmsghdr *cmsg;
        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
            if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
                memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
                printf("父进程接收到文件描述符: %d\n", received_fd);
                break;
            }
        }
        
        if (received_fd != -1) {
            // 使用接收到的文件描述符读取数据
            char read_buffer[256];
            lseek(received_fd, 0, SEEK_SET);  // 重置文件位置
            ssize_t read_bytes = read(received_fd, read_buffer, sizeof(read_buffer) - 1);
            if (read_bytes > 0) {
                read_buffer[read_bytes] = '\0';
                printf("从传递的文件描述符读取数据: %s\n", read_buffer);
            }
            
            // 关闭接收到的文件描述符
            close(received_fd);
        } else {
            printf("没有接收到文件描述符\n");
        }
        
        close(sv[0]);
        
        // 等待子进程结束
        int status;
        wait(&status);
    }
    
    return 0;
}

int main() {
    return demo_recvmsg_fd_passing();
}

示例5:完整的网络服务器示例

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

/**
 * 网络服务器结构
 */
typedef struct {
    int server_fd;
    int port;
    struct pollfd *clients;
    int max_clients;
    int client_count;
} network_server_t;

/**
 * 初始化网络服务器
 */
int server_init(network_server_t *server, int port, int max_clients) {
    struct sockaddr_in server_addr;
    
    memset(server, 0, sizeof(network_server_t));
    server->port = port;
    server->max_clients = max_clients;
    server->client_count = 0;
    
    // 分配客户端数组
    server->clients = calloc(max_clients + 1, sizeof(struct pollfd));
    if (!server->clients) {
        perror("分配客户端数组失败");
        return -1;
    }
    
    // 创建服务器套接字
    server->server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server->server_fd == -1) {
        perror("创建服务器套接字失败");
        free(server->clients);
        return -1;
    }
    
    // 设置套接字选项
    int opt = 1;
    if (setsockopt(server->server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        perror("设置套接字选项失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 设置服务器地址
    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);
    
    // 绑定套接字
    if (bind(server->server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 监听连接
    if (listen(server->server_fd, 10) == -1) {
        perror("监听失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 设置服务器套接字为poll监听
    server->clients[0].fd = server->server_fd;
    server->clients[0].events = POLLIN;
    
    printf("网络服务器初始化完成,监听端口 %d\n", port);
    return 0;
}

/**
 * 接受新客户端连接
 */
int server_accept_client(network_server_t *server) {
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_fd;
    
    client_fd = accept(server->server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        return -1;
    }
    
    if (server->client_count >= server->max_clients) {
        printf("客户端数量已达上限,拒绝连接\n");
        close(client_fd);
        return -1;
    }
    
    // 添加到客户端数组
    int index = server->client_count + 1;
    server->clients[index].fd = client_fd;
    server->clients[index].events = POLLIN;
    server->client_count++;
    
    printf("新客户端连接: %s:%d (fd=%d)\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);
    
    return 0;
}

/**
 * 使用recvmsg处理客户端消息
 */
int server_handle_client_message(network_server_t *server, int client_index) {
    int client_fd = server->clients[client_index].fd;
    struct msghdr msg;
    struct iovec iov[2];
    char buffer1[512], buffer2[512];
    char control_buffer[1024];
    ssize_t bytes_received;
    
    // 准备msghdr结构
    memset(&msg, 0, sizeof(msg));
    
    // 设置分散缓冲区
    iov[0].iov_base = buffer1;
    iov[0].iov_len = sizeof(buffer1) - 1;
    iov[1].iov_base = buffer2;
    iov[1].iov_len = sizeof(buffer2) - 1;
    msg.msg_iov = iov;
    msg.msg_iovlen = 2;
    
    // 设置控制缓冲区
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);
    
    // 接收消息
    bytes_received = recvmsg(client_fd, &msg, 0);
    if (bytes_received == -1) {
        if (errno == ECONNRESET) {
            printf("客户端 %d 连接重置\n", client_fd);
        } else {
            perror("recvmsg 失败");
        }
        return -1;
    }
    
    if (bytes_received == 0) {
        printf("客户端 %d 关闭连接\n", client_fd);
        return -1;
    }
    
    printf("从客户端 %d 接收到 %zd 字节数据\n", client_fd, bytes_received);
    
    // 处理接收到的数据
    size_t total_copied = 0;
    char full_message[1024];
    full_message[0] = '\0';
    
    for (int i = 0; i < 2 && total_copied < (size_t)bytes_received; i++) {
        size_t to_copy = iov[i].iov_len;
        if (total_copied + to_copy > (size_t)bytes_received) {
            to_copy = bytes_received - total_copied;
        }
        
        strncat(full_message, (char*)iov[i].iov_base, to_copy);
        total_copied += to_copy;
    }
    
    printf("  消息内容: %s\n", full_message);
    printf("  使用缓冲区数: %d\n", (int)msg.msg_iovlen);
    printf("  消息标志: %d\n", msg.msg_flags);
    
    // 回显消息
    char response[1024];
    snprintf(response, sizeof(response), "Echo: %s", full_message);
    send(client_fd, response, strlen(response), 0);
    
    return 0;
}

/**
 * 运行服务器主循环
 */
int server_run(network_server_t *server) {
    printf("服务器开始运行,等待客户端连接...\n");
    
    while (1) {
        // 使用poll等待事件
        int nfds = server->client_count + 1;
        int activity = poll(server->clients, nfds, 1000);  // 1秒超时
        
        if (activity == -1) {
            if (errno == EINTR) continue;  // 被信号中断
            perror("poll 失败");
            break;
        }
        
        if (activity == 0) {
            // 超时,继续循环
            continue;
        }
        
        // 检查服务器套接字(新连接)
        if (server->clients[0].revents & POLLIN) {
            server_accept_client(server);
            activity--;
        }
        
        // 检查客户端套接字
        for (int i = 1; i <= server->client_count && activity > 0; i++) {
            if (server->clients[i].revents & POLLIN) {
                if (server_handle_client_message(server, i) == -1) {
                    // 客户端断开连接,移除客户端
                    close(server->clients[i].fd);
                    // 将最后一个客户端移到当前位置
                    if (i < server->client_count) {
                        server->clients[i] = server->clients[server->client_count];
                    }
                    server->client_count--;
                    i--;  // 重新检查当前位置
                }
                activity--;
            }
        }
    }
    
    return 0;
}

/**
 * 清理服务器资源
 */
void server_cleanup(network_server_t *server) {
    // 关闭所有客户端连接
    for (int i = 1; i <= server->client_count; i++) {
        close(server->clients[i].fd);
    }
    
    // 关闭服务器套接字
    if (server->server_fd != -1) {
        close(server->server_fd);
    }
    
    // 释放内存
    if (server->clients) {
        free(server->clients);
    }
    
    printf("服务器资源清理完成\n");
}

/**
 * 演示完整的网络服务器
 */
int demo_complete_network_server() {
    network_server_t server;
    
    printf("=== 完整网络服务器示例 ===\n");
    
    // 初始化服务器
    if (server_init(&server, 8083, 10) != 0) {
        return -1;
    }
    
    // 启动测试客户端
    if (fork() == 0) {
        sleep(2);  // 等待服务器启动
        
        // 创建多个客户端进行测试
        for (int i = 0; i < 3; i++) {
            if (fork() == 0) {
                int client_sock = socket(AF_INET, SOCK_STREAM, 0);
                struct sockaddr_in serv_addr;
                
                memset(&serv_addr, 0, sizeof(serv_addr));
                serv_addr.sin_family = AF_INET;
                serv_addr.sin_port = htons(8083);
                serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
                
                if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
                    char message[256];
                    snprintf(message, sizeof(message), "Hello from client %d", i + 1);
                    
                    send(client_sock, message, strlen(message), 0);
                    printf("客户端 %d 发送: %s\n", i + 1, message);
                    
                    // 接收回显
                    char response[1024];
                    ssize_t bytes = recv(client_sock, response, sizeof(response) - 1, 0);
                    if (bytes > 0) {
                        response[bytes] = '\0';
                        printf("客户端 %d 接收回显: %s\n", i + 1, response);
                    }
                    
                    sleep(1);
                }
                
                close(client_sock);
                exit(0);
            }
        }
        
        // 等待所有客户端完成
        for (int i = 0; i < 3; i++) {
            int status;
            wait(&status);
        }
        
        exit(0);
    }
    
    // 运行服务器30秒
    printf("服务器将运行30秒...\n");
    sleep(30);
    
    // 清理资源
    server_cleanup(&server);
    
    // 等待测试客户端结束
    int status;
    wait(&status);
    
    return 0;
}

int main() {
    return demo_complete_network_server();
}

recvmsg 标志参数详解

常用标志:

  • MSG_OOB: 接收带外数据
  • MSG_PEEK: 查看数据但不从队列中移除
  • MSG_WAITALL: 等待接收完整的消息
  • MSG_TRUNC: 返回数据包的实际长度(UDP)
  • MSG_CTRUNC: 控制数据被截断

高级标志:

  • MSG_DONTWAIT: 非阻塞操作
  • MSG_ERRQUEUE: 接收错误队列中的数据
  • MSG_NOSIGNAL: 接收时不产生SIGPIPE信号

使用注意事项

性能考虑:

  1. 缓冲区管理: 合理设置缓冲区大小避免频繁分配
  2. 分散缓冲区: 适当使用scatter-gather I/O提高效率
  3. 控制信息: 只在需要时启用控制信息接收

错误处理:

  1. 部分接收: 处理数据被截断的情况
  2. 连接状态: 检查连接是否正常关闭
  3. 资源清理: 及时关闭文件描述符和释放内存

安全考虑:

  1. 缓冲区溢出: 确保缓冲区大小足够且正确处理
  2. 权限检查: 验证传递的文件描述符权限
  3. 输入验证: 验证接收到的数据内容

总结

recvmsg 是Linux网络编程中最强大的接收函数,提供了:

  • 基本数据接收功能
  • 分散缓冲区接收(scatter-gather I/O)
  • 控制信息接收(时间戳、文件描述符等)
  • 地址信息接收
  • 灵活的标志控制

通过合理使用 recvmsg,可以构建高性能、功能丰富的网络应用程序。

recvmsg系统调用及示例-CSDN博客

发表在 linux文章 | 留下评论

remap_file_pages系统调用及示例

remap_file_pages 函数详解

1. 函数介绍

remap_file_pages 是Linux系统调用,用于重新映射文件映射区域中的页面,创建非线性(non-linear)的内存映射。它允许将文件的不同部分映射到进程地址空间的不连续区域,或者将同一文件区域映射到多个不同的虚拟地址。这个功能对于实现复杂的内存布局和优化I/O操作非常有用。

2. 函数原型

#define _GNU_SOURCE
#include <sys/mman.h>
int remap_file_pages(void *addr, size_t size, int prot, size_t pgoff, int flags);

3. 功能

remap_file_pages 允许重新排列已经通过 mmap 映射的文件页面在虚拟地址空间中的布局。它可以创建循环缓冲区、跳过文件中的某些区域、或者重新排序文件内容的访问顺序,而无需实际移动物理内存页面。

4. 参数

  • *void addr: 已映射内存区域的起始地址(必须是页面对齐的)
  • size_t size: 要重新映射的区域大小(必须是页面大小的倍数)
  • int prot: 保护标志(当前必须为0)
  • size_t pgoff: 文件中的页面偏移量(相对于映射区域的起始位置)
  • int flags: 标志位(当前必须为0)

5. 返回值

  • 成功: 返回0
  • 失败: 返回-1,并设置errno

6. 相似函数,或关联函数

  • mmap: 内存映射文件
  • mremap: 重新映射虚拟内存区域
  • munmap: 取消内存映射
  • madvise: 给内核提供内存访问建议

7. 示例代码

示例1:基础remap_file_pages使用

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
 * 创建测试文件
 */
int create_test_file(const char *filename, size_t size) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 填充测试数据
    char *buffer = malloc(4096);
    if (!buffer) {
        perror("分配缓冲区失败");
        close(fd);
        return -1;
    }
    
    for (int i = 0; i < 4096; i++) {
        buffer[i] = 'A' + (i % 26);
    }
    
    size_t written = 0;
    while (written < size) {
        size_t to_write = (size - written < 4096) ? size - written : 4096;
        ssize_t result = write(fd, buffer, to_write);
        if (result == -1) {
            perror("写入文件失败");
            free(buffer);
            close(fd);
            return -1;
        }
        written += result;
    }
    
    free(buffer);
    close(fd);
    return 0;
}

/**
 * 演示remap_file_pages的基本使用方法
 */
int demo_remap_file_pages_basic() {
    const char *filename = "test_remap.dat";
    const size_t file_size = 16 * 4096;  // 16个页面
    int fd;
    void *mapped_addr;
    size_t page_size = getpagesize();
    
    printf("=== remap_file_pages 基本使用示例 ===\n");
    printf("页面大小: %zu 字节\n", page_size);
    printf("文件大小: %zu 字节 (%zu 个页面)\n", file_size, file_size / page_size);
    
    // 创建测试文件
    if (create_test_file(filename, file_size) != 0) {
        return -1;
    }
    
    // 打开文件并映射到内存
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        unlink(filename);
        return -1;
    }
    
    mapped_addr = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (mapped_addr == MAP_FAILED) {
        perror("内存映射失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    printf("文件映射到地址: %p\n", mapped_addr);
    
    // 显示原始映射的内容
    printf("\n原始映射内容 (前4个页面):\n");
    for (int i = 0; i < 4; i++) {
        char *page_start = (char*)mapped_addr + i * page_size;
        printf("页面 %d (偏移 %zu): %.32s...\n", i, i * page_size, page_start);
    }
    
    // 使用remap_file_pages重新映射页面
    // 将第3个页面映射到第1个页面的位置
    printf("\n使用remap_file_pages重新映射...\n");
    
    // 注意:remap_file_pages在现代Linux内核中可能不可用或被禁用
    // 这里演示调用方式,但可能返回ENOSYS
    int result = remap_file_pages((char*)mapped_addr + page_size,  // 第2个页面位置
                                  page_size,                      // 1个页面大小
                                  0,                              // prot参数(必须为0)
                                  2,                              // 映射第3个页面(索引2)
                                  0);                             // flags参数(必须为0)
    
    if (result == -1) {
        if (errno == ENOSYS) {
            printf("警告: remap_file_pages 系统调用不可用 (内核可能已禁用)\n");
            printf("这是现代Linux内核的常见情况\n");
        } else {
            printf("remap_file_pages 失败: %s\n", strerror(errno));
        }
    } else {
        printf("remap_file_pages 调用成功\n");
        
        // 显示重新映射后的内容
        printf("\n重新映射后的内容:\n");
        for (int i = 0; i < 4; i++) {
            char *page_start = (char*)mapped_addr + i * page_size;
            printf("页面 %d: %.32s...\n", i, page_start);
        }
    }
    
    // 清理资源
    munmap(mapped_addr, file_size);
    close(fd);
    unlink(filename);
    
    return 0;
}

int main() {
    return demo_remap_file_pages_basic();
}

示例2:循环缓冲区实现

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
 * 演示使用remap_file_pages实现循环缓冲区
 */
int demo_circular_buffer() {
    const char *filename = "circular_buffer.dat";
    const size_t buffer_size = 8 * getpagesize();  // 8个页面的缓冲区
    const size_t physical_size = 4 * getpagesize(); // 实际只有4个页面的数据
    int fd;
    void *mapped_addr;
    size_t page_size = getpagesize();
    
    printf("=== 循环缓冲区实现示例 ===\n");
    printf("页面大小: %zu 字节\n", page_size);
    printf("逻辑缓冲区大小: %zu 字节 (%zu 页面)\n", buffer_size, buffer_size / page_size);
    printf("物理文件大小: %zu 字节 (%zu 页面)\n", physical_size, physical_size / page_size);
    
    // 创建测试文件
    fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 扩展文件大小
    if (ftruncate(fd, physical_size) == -1) {
        perror("扩展文件失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    // 填充测试数据
    char *test_data = malloc(physical_size);
    if (!test_data) {
        perror("分配测试数据失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    // 创建循环模式的数据
    for (size_t i = 0; i < physical_size; i++) {
        test_data[i] = '0' + (i % 10);
    }
    
    write(fd, test_data, physical_size);
    free(test_data);
    
    // 映射逻辑缓冲区大小(比物理文件大)
    mapped_addr = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped_addr == MAP_FAILED) {
        perror("内存映射失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    printf("映射地址: %p\n", mapped_addr);
    
    // 显示原始映射内容
    printf("\n原始映射内容:\n");
    for (size_t i = 0; i < buffer_size; i += page_size) {
        size_t page_index = i / page_size;
        char *page_start = (char*)mapped_addr + i;
        printf("逻辑页面 %zu: %.16s\n", page_index, page_start);
    }
    
    // 尝试使用remap_file_pages创建循环缓冲区
    printf("\n尝试创建循环缓冲区...\n");
    
    // 将前4个页面的内容循环映射到后4个页面位置
    for (size_t i = 0; i < 4; i++) {
        int result = remap_file_pages((char*)mapped_addr + (4 + i) * page_size,  // 后4个页面位置
                                      page_size,                                 // 1个页面大小
                                      0,                                         // prot参数
                                      i,                                         // 映射前4个页面的内容
                                      0);                                        // flags参数
        
        if (result == -1) {
            if (errno == ENOSYS) {
                printf("系统不支持remap_file_pages,无法创建循环缓冲区\n");
                break;
            } else {
                printf("页面 %zu 重新映射失败: %s\n", i, strerror(errno));
            }
        } else {
            printf("页面 %zu 重新映射成功\n", i);
        }
    }
    
    // 显示重新映射后的内容(如果成功的话)
    printf("\n重新映射后的内容:\n");
    for (size_t i = 0; i < buffer_size; i += page_size) {
        size_t page_index = i / page_size;
        char *page_start = (char*)mapped_addr + i;
        printf("逻辑页面 %zu: %.16s\n", page_index, page_start);
    }
    
    // 清理资源
    munmap(mapped_addr, buffer_size);
    close(fd);
    unlink(filename);
    
    return 0;
}

int main() {
    return demo_circular_buffer();
}

示例3:跳过文件区域映射

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
 * 演示跳过文件中某些区域的映射
 */
int demo_skip_file_regions() {
    const char *filename = "skip_regions.dat";
    const size_t file_size = 12 * getpagesize();  // 12个页面
    int fd;
    void *mapped_addr;
    size_t page_size = getpagesize();
    
    printf("=== 跳过文件区域映射示例 ===\n");
    printf("页面大小: %zu 字节\n", page_size);
    printf("文件大小: %zu 字节 (%zu 页面)\n", file_size, file_size / page_size);
    
    // 创建测试文件
    fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 扩展文件大小
    if (ftruncate(fd, file_size) == -1) {
        perror("扩展文件失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    // 填充测试数据(每个页面不同内容)
    for (int page = 0; page < 12; page++) {
        char page_data[4096];
        for (int i = 0; i < 4096; i++) {
            page_data[i] = 'A' + page;
        }
        lseek(fd, page * 4096, SEEK_SET);
        write(fd, page_data, 4096);
    }
    
    // 映射整个文件
    mapped_addr = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped_addr == MAP_FAILED) {
        perror("内存映射失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    printf("文件映射到地址: %p\n", mapped_addr);
    
    // 显示原始映射内容
    printf("\n原始映射内容:\n");
    for (int i = 0; i < 12; i++) {
        char *page_start = (char*)mapped_addr + i * page_size;
        printf("页面 %d: %c%c%c%c...\n", i, page_start[0], page_start[1], 
               page_start[2], page_start[3]);
    }
    
    // 假设我们要跳过第3、4、7、8页面,用其他页面的内容替换
    printf("\n尝试跳过某些页面并重新映射...\n");
    
    struct {
        int virtual_page;  // 虚拟页面索引
        int source_page;   // 源页面索引
    } remap_rules[] = {
        {2, 0},  // 将页面0的内容映射到页面2的位置
        {3, 1},  // 将页面1的内容映射到页面3的位置
        {6, 5},  // 将页面5的内容映射到页面6的位置
        {7, 9},  // 将页面9的内容映射到页面7的位置
        {-1, -1} // 结束标记
    };
    
    // 执行重新映射
    for (int i = 0; remap_rules[i].virtual_page != -1; i++) {
        int result = remap_file_pages((char*)mapped_addr + remap_rules[i].virtual_page * page_size,
                                      page_size,
                                      0,
                                      remap_rules[i].source_page,
                                      0);
        
        if (result == -1) {
            if (errno == ENOSYS) {
                printf("系统不支持remap_file_pages\n");
                break;
            } else {
                printf("页面 %d->%d 重新映射失败: %s\n", 
                       remap_rules[i].source_page, remap_rules[i].virtual_page, strerror(errno));
            }
        } else {
            printf("页面 %d->%d 重新映射成功\n", 
                   remap_rules[i].source_page, remap_rules[i].virtual_page);
        }
    }
    
    // 显示重新映射后的内容
    printf("\n重新映射后的内容:\n");
    for (int i = 0; i < 12; i++) {
        char *page_start = (char*)mapped_addr + i * page_size;
        printf("页面 %d: %c%c%c%c...\n", i, page_start[0], page_start[1], 
               page_start[2], page_start[3]);
    }
    
    // 清理资源
    munmap(mapped_addr, file_size);
    close(fd);
    unlink(filename);
    
    return 0;
}

int main() {
    return demo_skip_file_regions();
}

示例4:内存访问模式优化

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

/**
 * 比较不同访问模式的性能
 */
int compare_access_patterns() {
    const char *filename = "access_pattern_test.dat";
    const size_t file_size = 16 * getpagesize();
    int fd;
    void *mapped_addr;
    size_t page_size = getpagesize();
    char *sequential_data, *random_data;
    
    printf("=== 内存访问模式优化示例 ===\n");
    printf("页面大小: %zu 字节\n", page_size);
    printf("文件大小: %zu 字节 (%zu 页面)\n", file_size, file_size / page_size);
    
    // 创建测试文件
    fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    if (ftruncate(fd, file_size) == -1) {
        perror("扩展文件失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    // 填充测试数据
    char *test_data = malloc(file_size);
    if (!test_data) {
        perror("分配测试数据失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    // 创建有规律的数据模式
    for (size_t i = 0; i < file_size; i++) {
        test_data[i] = (i / page_size) + 'A';  // 每个页面一个字母
    }
    
    write(fd, test_data, file_size);
    free(test_data);
    
    // 映射文件
    mapped_addr = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped_addr == MAP_FAILED) {
        perror("内存映射失败");
        close(fd);
        unlink(filename);
        return -1;
    }
    
    printf("文件映射到地址: %p\n", mapped_addr);
    
    // 准备测试数据
    sequential_data = malloc(file_size);
    random_data = malloc(file_size);
    if (!sequential_data || !random_data) {
        perror("分配测试缓冲区失败");
        free(sequential_data);
        free(random_data);
        munmap(mapped_addr, file_size);
        close(fd);
        unlink(filename);
        return -1;
    }
    
    // 顺序访问测试
    printf("\n1. 顺序访问测试:\n");
    clock_t start = clock();
    
    for (size_t i = 0; i < file_size; i++) {
        sequential_data[i] = ((char*)mapped_addr)[i];
    }
    
    clock_t end = clock();
    double sequential_time = ((double)(end - start)) / CLOCKS_PER_SEC;
    printf("顺序访问耗时: %.6f 秒\n", sequential_time);
    
    // 尝试重新映射以优化随机访问
    printf("\n2. 尝试优化随机访问模式:\n");
    
    // 创建一个访问模式:每隔一个页面访问
    int result = remap_file_pages((char*)mapped_addr + 2 * page_size,  // 第3个页面位置
                                  page_size,                           // 1个页面大小
                                  0,                                   // prot参数
                                  4,                                   // 映射第5个页面
                                  0);                                  // flags参数
    
    if (result == -1) {
        if (errno == ENOSYS) {
            printf("系统不支持remap_file_pages,跳过优化测试\n");
        } else {
            printf("重新映射失败: %s\n", strerror(errno));
        }
    } else {
        printf("重新映射成功,优化了访问模式\n");
    }
    
    // 随机访问测试
    printf("\n3. 随机访问测试:\n");
    start = clock();
    
    // 模拟随机访问模式
    size_t access_pattern[] = {0, 4096, 8192, 12288, 2048, 6144, 10240, 14336};
    size_t pattern_size = sizeof(access_pattern) / sizeof(access_pattern[0]);
    
    for (size_t i = 0; i < 1000; i++) {  // 重复1000次
        for (size_t j = 0; j < pattern_size; j++) {
            size_t offset = access_pattern[j] + (i % 100);
            if (offset < file_size) {
                random_data[i % file_size] = ((char*)mapped_addr)[offset];
            }
        }
    }
    
    end = clock();
    double random_time = ((double)(end - start)) / CLOCKS_PER_SEC;
    printf("随机访问耗时: %.6f 秒\n", random_time);
    
    // 显示访问模式优化的理论优势
    printf("\n访问模式优化说明:\n");
    printf("  remap_file_pages 可以:\n");
    printf("    1. 创建循环缓冲区,避免数据复制\n");
    printf("    2. 优化缓存局部性,提高访问效率\n");
    printf("    3. 实现非线性访问模式\n");
    printf("    4. 减少页面错误和TLB未命中\n");
    
    // 清理资源
    free(sequential_data);
    free(random_data);
    munmap(mapped_addr, file_size);
    close(fd);
    unlink(filename);
    
    return 0;
}

int main() {
    return compare_access_patterns();
}

示例5:实际应用场景演示

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
 * 模拟数据库页面缓存场景
 */
typedef struct {
    void *mapped_addr;
    size_t total_size;
    size_t page_size;
    int fd;
} db_cache_t;

/**
 * 初始化数据库缓存
 */
int db_cache_init(db_cache_t *cache, const char *filename, size_t cache_size) {
    size_t page_size = getpagesize();
    size_t file_size = ((cache_size / page_size) + 16) * page_size;  // 多分配一些空间
    
    memset(cache, 0, sizeof(db_cache_t));
    cache->page_size = page_size;
    cache->total_size = file_size;
    
    // 创建或打开数据库文件
    cache->fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (cache->fd == -1) {
        perror("打开数据库文件失败");
        return -1;
    }
    
    // 设置文件大小
    if (ftruncate(cache->fd, file_size) == -1) {
        perror("设置文件大小失败");
        close(cache->fd);
        return -1;
    }
    
    // 初始化文件内容
    char *init_data = malloc(page_size);
    if (!init_data) {
        perror("分配初始化数据失败");
        close(cache->fd);
        return -1;
    }
    
    for (size_t i = 0; i < file_size; i += page_size) {
        for (size_t j = 0; j < page_size; j++) {
            init_data[j] = 'P' + ((i / page_size) % 26);
        }
        lseek(cache->fd, i, SEEK_SET);
        write(cache->fd, init_data, page_size);
    }
    
    free(init_data);
    
    // 映射文件
    cache->mapped_addr = mmap(NULL, cache->total_size, 
                              PROT_READ | PROT_WRITE, MAP_SHARED, cache->fd, 0);
    if (cache->mapped_addr == MAP_FAILED) {
        perror("内存映射失败");
        close(cache->fd);
        return -1;
    }
    
    printf("数据库缓存初始化完成\n");
    printf("  缓存地址: %p\n", cache->mapped_addr);
    printf("  缓存大小: %zu 字节\n", cache->total_size);
    printf("  页面大小: %zu 字节\n", cache->page_size);
    
    return 0;
}

/**
 * 演示数据库页面重排
 */
int db_cache_remap_pages(db_cache_t *cache) {
    printf("\n=== 数据库页面重排演示 ===\n");
    
    // 显示原始页面内容
    printf("原始页面内容:\n");
    for (int i = 0; i < 8; i++) {
        char *page_start = (char*)cache->mapped_addr + i * cache->page_size;
        printf("  页面 %d: %c%c%c%c...\n", i, page_start[0], page_start[1], 
               page_start[2], page_start[3]);
    }
    
    // 尝试重新排列页面以优化访问模式
    printf("\n尝试重新排列页面以优化热点数据访问...\n");
    
    // 假设页面2和页面3是热点数据,将其映射到更易访问的位置
    struct {
        int virtual_page;  // 虚拟页面位置
        int source_page;   // 源页面
    } hot_pages[] = {
        {6, 2},  // 将热点页面2映射到位置6
        {7, 3},  // 将热点页面3映射到位置7
        {-1, -1}
    };
    
    int remap_success = 0;
    for (int i = 0; hot_pages[i].virtual_page != -1; i++) {
        int result = remap_file_pages((char*)cache->mapped_addr + 
                                      hot_pages[i].virtual_page * cache->page_size,
                                      cache->page_size,
                                      0,
                                      hot_pages[i].source_page,
                                      0);
        
        if (result == -1) {
            if (errno != ENOSYS) {
                printf("页面 %d->%d 重新映射失败: %s\n", 
                       hot_pages[i].source_page, hot_pages[i].virtual_page, strerror(errno));
            }
        } else {
            printf("热点页面 %d->%d 重新映射成功\n", 
                   hot_pages[i].source_page, hot_pages[i].virtual_page);
            remap_success = 1;
        }
    }
    
    if (!remap_success && errno == ENOSYS) {
        printf("系统不支持remap_file_pages,使用传统访问方式\n");
    }
    
    // 显示重新映射后的页面内容
    printf("\n重新映射后的页面内容:\n");
    for (int i = 0; i < 8; i++) {
        char *page_start = (char*)cache->mapped_addr + i * cache->page_size;
        printf("  页面 %d: %c%c%c%c...\n", i, page_start[0], page_start[1], 
               page_start[2], page_start[3]);
    }
    
    return 0;
}

/**
 * 清理数据库缓存
 */
void db_cache_cleanup(db_cache_t *cache) {
    if (cache->mapped_addr && cache->mapped_addr != MAP_FAILED) {
        munmap(cache->mapped_addr, cache->total_size);
    }
    if (cache->fd != -1) {
        close(cache->fd);
    }
    printf("数据库缓存清理完成\n");
}

/**
 * 演示实际应用场景
 */
int demo_real_world_application() {
    db_cache_t cache;
    const char *db_filename = "database_cache.dat";
    
    printf("=== 实际应用场景演示 ===\n");
    printf("场景: 数据库页面缓存优化\n");
    
    // 初始化数据库缓存
    if (db_cache_init(&cache, db_filename, 32 * 1024 * 1024) != 0) {  // 32MB缓存
        return -1;
    }
    
    // 演示页面重排
    db_cache_remap_pages(&cache);
    
    // 演示循环日志缓冲区
    printf("\n=== 循环日志缓冲区演示 ===\n");
    printf("remap_file_pages 可以用于实现:\n");
    printf("  1. 循环日志缓冲区(避免数据复制)\n");
    printf("  2. 数据库页面池管理\n");
    printf("  3. 文件系统元数据缓存优化\n");
    printf("  4. 多媒体数据流缓冲\n");
    
    // 清理资源
    db_cache_cleanup(&cache);
    unlink(db_filename);
    
    return 0;
}

int main() {
    return demo_real_world_application();
}

remap_file_pages 限制和注意事项

系统支持:

  1. 内核版本: 需要Linux 2.6或更高版本
  2. 功能可用性: 现代内核可能默认禁用此功能
  3. 安全限制: 某些安全策略可能禁止使用

使用限制:

  1. 必须已映射: 目标区域必须已经通过mmap映射
  2. 页面对齐: 地址和大小必须是页面大小的倍数
  3. 参数限制: prot和flags参数当前必须为0

性能考虑:

  1. TLB优化: 可以减少TLB未命中
  2. 缓存局部性: 优化内存访问模式
  3. 减少复制: 避免不必要的数据复制

错误处理:

  1. ENOSYS: 系统调用不支持
  2. EINVAL: 参数无效
  3. ENOMEM: 内存不足

替代方案

由于 remap_file_pages 在现代系统中可能不可用,可以考虑以下替代方案:

1. 多次mmap:

// 为同一文件的不同部分创建多个映射
void *map1 = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, offset1);
void *map2 = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, offset2);

2. 使用madvise:

// 给内核提供访问建议
madvise(addr, size, MADV_WILLNEED);  // 预读建议
madvise(addr, size, MADV_SEQUENTIAL); // 顺序访问建议

3. 用户空间缓冲区管理:

// 在用户空间实现复杂的缓冲区管理逻辑

总结

remap_file_pages 是一个强大的系统调用,用于实现非线性的内存映射布局。虽然在现代Linux内核中可能不可用,但它在以下场景中仍然有价值:

  1. 循环缓冲区实现: 避免数据复制,提高效率
  2. 数据库页面管理: 优化热点数据访问
  3. 多媒体流处理: 实现高效的缓冲区重用
  4. 文件系统优化: 优化元数据访问模式

正确使用这个函数需要深入理解虚拟内存管理和文件映射机制。在实际应用中,需要检查系统支持情况并提供适当的回退方案。

发表在 linux文章 | 留下评论

removexattr系统调用及示例

我们来学习一下 removexattr 这个函数。它是 Linux 中用于管理扩展属性(Extended Attributes, xattrs)的一组函数之一。

1. 函数介绍

removexattr 是一个 Linux 系统调用(System Call),它的作用是删除与文件或目录相关联的扩展属性(Extended Attribute) 。

想象一下标准的文件属性:所有者、权限、大小、修改时间等。扩展属性则允许你为文件或目录附加一些额外的、用户自定义的“标签”或“元数据”(metadata)。这些属性以键值对(key-value pair)的形式存在,键(name)是一个字符串,值(value)是一段任意数据。

removexattr 函数就是用来移除这些自定义键值对中的某一个键(及其对应的值)的。它的使用场景包括清理不再需要的自定义元数据、实现特定应用程序的数据存储需求、或在安全/访问控制策略中移除标记等 。

2. 函数原型

#include <sys/xattr.h> // 包含扩展属性相关的函数和常量

int removexattr(const char *path, const char *name);
int lremovexattr(const char *path, const char *name);
int fremovexattr(int fd, const char *name);

3. 功能

这个函数族的功能是删除指定文件或目录上的一个特定扩展属性。

  • removexattr: 通过文件路径删除扩展属性。如果路径指向符号链接(symlink),它会作用于符号链接指向的目标文件。
  • lremovexattr: 通过文件路径删除扩展属性。如果路径指向符号链接,它会作用于符号链接本身,而不是其目标。
  • fremovexattr: 通过已打开文件的文件描述符(file descriptor)删除扩展属性。

4. 参数

这三个函数的参数非常相似:

  • path (对于 removexattr 和 lremovexattr):
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,表示要操作的文件或目录的路径名 。
  • fd (对于 fremovexattr):
    • int 类型。
    • 一个已打开文件的有效文件描述符 。
  • name:
    • const char * 类型。
    • 指向一个以 null 结尾的字符串,表示要删除的扩展属性的名称(键)。这个名称通常包含一个命名空间前缀,例如 "user.my_custom_flag" 或 "trusted.my_security_label"

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误类型 。
    • ENOATTR 或 ENODATA: 表示指定的属性名不存在。
    • EACCES 或 EPERM: 权限不足,无法删除该属性。
    • ENOTDIRpath 的某个前缀不是目录。
    • ENAMETOOLONGpath 或 name 太长。
    • ENOENT: 文件或路径不存在。
    • ELOOPpath 解析时遇到符号链接循环。
    • EFAULTpath 或 name 指向无效的地址空间。
    • EIO: I/O 错误。
    • ENOMEM: 内核内存不足。
    • EROFS: 文件系统是只读的。

6. 相似函数或关联函数

  • setxattr / lsetxattr / fsetxattr: 用于设置或创建扩展属性。
  • getxattr / lgetxattr / fgetxattr: 用于获取扩展属性的值。
  • listxattr / llistxattr / flistxattr: 用于列出文件或目录上所有扩展属性的名称。
  • <sys/xattr.h>: 包含所有扩展属性相关函数和常量定义的头文件。

7. 示例代码

这个示例将演示如何使用 setxattr 设置一个扩展属性,然后使用 removexattr 删除它。

前提: 文件系统需要支持扩展属性(大多数现代 Linux 文件系统如 ext4, XFS 都支持)。运行示例可能需要适当权限。

#include <stdio.h>
#include <unistd.h>
#include <sys/xattr.h> // 扩展属性函数
#include <string.h>
#include <errno.h>

int main() {
    const char *filename = "example_file.txt";
    const char *attr_name = "user.example_key"; // 使用 user 命名空间
    const char *attr_value = "This is example data for the attribute.";
    size_t value_size = strlen(attr_value);
    char buffer[1024];
    ssize_t get_result;
    int remove_result;

    // 1. 创建一个示例文件 (如果不存在)
    FILE *file = fopen(filename, "w");
    if (!file) {
        perror("fopen");
        return 1;
    }
    fprintf(file, "Hello, this is a test file for xattrs.\n");
    fclose(file);

    // 2. 设置一个扩展属性
    printf("Setting extended attribute '%s' on file '%s'...\n", attr_name, filename);
    if (setxattr(filename, attr_name, attr_value, value_size, 0) == -1) {
        perror("setxattr");
        fprintf(stderr, "Failed to set attribute '%s'.\n", attr_name);
        return 1;
    }
    printf("Attribute '%s' set successfully.\n", attr_name);

    // 3. 验证属性已设置 (可选)
    printf("Verifying attribute '%s' exists...\n", attr_name);
    get_result = getxattr(filename, attr_name, buffer, sizeof(buffer) - 1);
    if (get_result == -1) {
        perror("getxattr (verification)");
        fprintf(stderr, "Failed to verify attribute '%s'.\n", attr_name);
        return 1;
    }
    buffer[get_result] = '\0'; // Null-terminate for printing
    printf("Attribute '%s' value is: '%s'\n", attr_name, buffer);

    // 4. 删除扩展属性
    printf("Removing extended attribute '%s' from file '%s'...\n", attr_name, filename);
    remove_result = removexattr(filename, attr_name);
    if (remove_result == -1) {
        perror("removexattr");
        fprintf(stderr, "Failed to remove attribute '%s'.\n", attr_name);
        // 检查是否是因为属性不存在
        if (errno == ENOATTR || errno == ENODATA) {
             printf("Note: The attribute might not have existed.\n");
        }
        return 1;
    }
    printf("Attribute '%s' removed successfully.\n", attr_name);

    // 5. 验证属性已删除 (可选)
    printf("Verifying attribute '%s' is removed...\n", attr_name);
    get_result = getxattr(filename, attr_name, buffer, sizeof(buffer) - 1);
    if (get_result == -1) {
        if (errno == ENOATTR || errno == ENODATA) {
            printf("Confirmed: Attribute '%s' no longer exists on '%s'.\n", attr_name, filename);
        } else {
            perror("getxattr (verification after removal)");
            fprintf(stderr, "Error occurred while verifying removal of '%s'.\n", attr_name);
            return 1;
        }
    } else {
         buffer[get_result] = '\0';
         printf("Unexpected: Attribute '%s' still seems to exist with value '%s'.\n", attr_name, buffer);
         return 1;
    }

    // 6. 清理示例文件 (可选)
    // if (remove(filename) != 0) {
    //     perror("remove");
    //     fprintf(stderr, "Warning: Could not remove example file '%s'.\n", filename);
    // } else {
    //     printf("Removed example file '%s'.\n", filename);
    // }

    return 0;
}

编译和运行:

# 假设代码保存在 xattr_example.c 中
gcc -o xattr_example xattr_example.c

# 运行
./xattr_example

# 你也可以使用命令行工具 `setfattr` 和 `getfattr` 来验证
# touch test_file
# setfattr -n user.test_key -v "test_value" test_file
# getfattr -n user.test_key test_file
# attr -r user.test_key test_file # 删除属性
# getfattr -n user.test_key test_file # 再次检查应该报错或无输出

removexattr系统调用及示例-CSDN博客

remap_file_pages系统调用及示例-CSDN博客

发表在 linux文章 | 留下评论