China’s Leading AI Tools: Transforming the Global Tech Landscape

China’s Leading AI Tools: Transforming the Global Tech Landscape

In recent years, China has emerged as a global powerhouse in the field of artificial intelligence (AI). With significant investments in research and development, a vast pool of talented engineers, and a vibrant startup ecosystem, Chinese companies have developed a range of AI tools that are not only revolutionizing domestic industries but also making waves around the world. In this article, we will explore some of the most remarkable AI tools hailing from China, their features, applications, and the impact they are having on various sectors globally.

Understanding China’s AI Ascendancy

China’s rapid progress in AI is no accident. The government has been actively promoting AI development through strategic initiatives, pouring substantial funds into research institutions and startups. This support, combined with a large population that provides abundant data for training AI models, has created a fertile ground for innovation. Chinese tech giants and innovative startups alike have been quick to capitalize on these advantages, resulting in the creation of some truly game – changing AI tools.

Alibaba Cloud’s Qwen – A Multifaceted AI Powerhouse

Natural Language Processing Prowess

Alibaba Cloud’s Qwen is a leading language model with capabilities that rival the best in the world. It has been trained on an extensive corpus of text, enabling it to understand and generate human language with remarkable fluency. For example, in content creation, Qwen can generate high – quality articles, blog posts, and product descriptions. A marketing team in a global e – commerce company could use Qwen to quickly generate product descriptions in multiple languages, saving countless hours of manual writing.

Industry – Specific Applications

Qwen has been tailored for various industries. In the financial sector, it can analyze market trends, news articles, and financial reports to provide valuable insights for investment decisions. In healthcare, it can assist doctors in diagnosing diseases by analyzing patient symptoms and medical literature. A hospital in a developing country might use Qwen to help its doctors access the latest medical research and make more informed decisions, even if they have limited resources.

Advantages in Multilingual Support

One of Qwen’s stand – out features is its excellent multilingual support. With China’s growing global trade and business connections, Qwen can translate text accurately between multiple languages, breaking down language barriers. A multinational corporation with operations in China and other countries can use Qwen to communicate seamlessly with its international teams, ensuring that information is conveyed clearly and correctly.

ByteDance’s Cloud 雀 – Empowering Visual Creativity

Image and Video Generation

Cloud 雀,developed by ByteDance, is a powerful AI tool in the realm of visual content. It can generate high – resolution images and engaging videos from simple text prompts. For instance, a small advertising agency in Europe could use Cloud 雀 to quickly create eye – catching images for its clients’ social media campaigns. Instead of spending a fortune on professional photographers or graphic designers, they can simply describe the concept they want, and Cloud 雀 will bring it to life.

Video Editing Made Easy

In addition to generation, Cloud 雀 also simplifies video editing. It can automatically edit videos, adding transitions, music, and special effects based on the content. A content creator on YouTube, who is based in South America, can use Cloud 雀 to edit their vlogs more efficiently. They can input their raw footage and some basic instructions, and Cloud 雀 will produce a polished video ready for upload.

Integration with Social Media Platforms

ByteDance’s influence in the social media space means that Cloud 雀 can be easily integrated with popular platforms. This integration allows users to directly share their AI – created visual content on platforms like TikTok (which is owned by ByteDance) and other social media channels. A young influencer in Asia can use Cloud 雀 to create unique content and quickly share it with their followers, enhancing their online presence.

Tencent’s AI – Empowered Healthcare Solutions

Medical Image Diagnosis

Tencent has developed AI tools that are making a significant impact in healthcare, particularly in medical image diagnosis. Their AI algorithms can analyze X – rays, MRIs, and CT scans with high accuracy, helping doctors detect diseases such as cancer and pneumonia at an early stage. In a rural hospital in Africa, where there may be a shortage of experienced radiologists, Tencent’s AI – based diagnostic tool can assist local doctors in interpreting medical images, potentially saving lives.

Patient Monitoring and Prediction

Another aspect of Tencent’s healthcare AI is patient monitoring and prediction. The tool can analyze a patient’s vital signs, medical history, and lifestyle data to predict the likelihood of developing certain diseases or complications. A healthcare provider in North America can use this tool to proactively manage the health of their patients, intervening early to prevent serious health issues.

Telemedicine Support

With the rise of telemedicine, Tencent’s AI tools also play a crucial role. They can facilitate remote consultations by providing real – time translation (using language AI capabilities) during doctor – patient interactions, and by analyzing patient data collected at home through wearable devices. A patient in a remote area of Australia can consult with a specialist in China, with Tencent’s AI ensuring smooth communication and accurate diagnosis support.

DJI’s Intelligent Drone Technologies – AI in the Skies

Autonomous Flight and Obstacle Avoidance

DJI, a world – renowned drone manufacturer based in China, has integrated AI into its drones to enable autonomous flight and advanced obstacle avoidance. Their drones can fly along pre – programmed routes, adjust their flight paths in real – time to avoid obstacles, and even land safely in challenging conditions. A wildlife conservation team in the Amazon rainforest can use DJI’s AI – equipped drones to monitor endangered species without disturbing their habitats. The drones can autonomously navigate through the dense forest, capturing valuable data on the animals’ behavior and population.

Precision Agriculture Applications

In agriculture, DJI’s drones are being used for precision farming. The AI – powered drones can analyze crop health by capturing high – resolution images and using machine learning algorithms to detect signs of disease, nutrient deficiencies, or water stress. A large – scale farmer in the United States can use these drones to optimize their irrigation and fertilization schedules, increasing crop yields while reducing resource waste.

Aerial Photography and Filmmaking

For the creative industry, DJI’s drones offer AI – enhanced features for aerial photography and filmmaking. The drones can track moving subjects, maintain stable camera angles, and capture stunning footage. A film production company in Europe can use DJI drones to add unique aerial perspectives to their movies, enhancing the visual experience for the audience.

The Impact of Chinese AI Tools on Global Industries

Transforming Manufacturing

Chinese AI tools are revolutionizing the manufacturing sector globally. AI – powered robots and automation systems are being used to improve production efficiency, quality control, and supply chain management. For example, Foxconn, a major electronics manufacturer in China, has implemented AI – enabled robots in its factories. These robots can perform complex tasks with high precision, reducing errors and increasing production speed. This model is being replicated by manufacturing companies around the world, as they seek to stay competitive in the global market.

Boosting E – commerce

In the e – commerce industry, Chinese AI tools are enhancing the customer experience. Recommendation engines powered by AI analyze customer browsing and purchase history to provide personalized product recommendations. Platforms like Alibaba’s Taobao use these AI – driven recommendation systems to help customers discover new products they may be interested in. This not only increases customer satisfaction but also boosts sales for merchants. E – commerce companies in other countries are looking to adopt similar AI – based strategies to improve their competitiveness.

Advancing Education

AI tools from China are also making inroads into the education sector. Intelligent tutoring systems can adapt to individual students’ learning styles and paces, providing personalized learning experiences. For example, some Chinese – developed AI – education platforms can analyze students’ performance on practice tests, identify areas of weakness, and provide targeted learning materials. Schools and educational institutions in different parts of the world are exploring the use of these AI – enabled educational tools to improve learning outcomes.

Challenges and Future Outlook

Ethical and Regulatory Concerns

As with any powerful technology, Chinese AI tools face ethical and regulatory challenges. Issues such as data privacy, algorithmic bias, and the potential for job displacement need to be carefully addressed. For example, as AI tools collect and analyze vast amounts of data, ensuring the privacy and security of this data is crucial. Governments around the world are starting to develop regulations to govern the use of AI, and Chinese companies will need to comply with these regulations while continuing to innovate.

Global Competition and Collaboration

The global AI landscape is highly competitive, with companies from various countries vying for market share. Chinese AI companies will need to continue to differentiate themselves through innovation and high – quality products. At the same time, there is also room for collaboration. For example, in the fight against global challenges such as climate change, Chinese and international AI companies could work together to develop solutions. By sharing knowledge and resources, they can accelerate the development of AI – based technologies that benefit the entire world.

Continued Innovation and Expansion

Looking to the future, Chinese AI tools are expected to continue evolving and expanding into new areas. With ongoing research and development, we can anticipate even more advanced natural language processing, computer vision, and machine learning capabilities. Chinese companies may also explore new applications in emerging fields such as quantum computing and edge computing, further pushing the boundaries of what AI can achieve.

Conclusion

China’s leading AI tools are already making a significant impact on the global tech landscape. From language models that enable seamless communication to visual content creation tools that inspire creativity, and from healthcare solutions that save lives to intelligent drones that transform industries, these AI tools are enhancing productivity, improving lives, and driving innovation worldwide. As China continues to invest in AI research and development, and as Chinese companies navigate the challenges of the global market, we can expect even more remarkable AI tools to emerge in the future, further strengthening China’s position as a global AI leader.

FAQs

  1. Are Chinese AI tools only applicable in China?

No, Chinese AI tools are designed to be used globally. Many of them offer multilingual support and are being adopted by companies and individuals in various countries across different industries.

  1. How do Chinese AI tools compare to those from other countries?

Chinese AI tools are highly competitive. They often leverage China’s large data resources and advanced research capabilities. In some areas, such as natural language processing and visual content generation, Chinese AI tools are at the forefront of innovation.

  1. What is the future of Chinese AI tool development?

The future looks bright. With continued government support, investment in R & D, and a talented workforce, Chinese AI tools are expected to expand into new industries, improve existing capabilities, and contribute more to solving global challenges.

  1. Do Chinese AI tools respect data privacy?

Chinese companies are increasingly aware of data privacy issues. They are implementing strict data protection measures and complying with international and domestic regulations to ensure the privacy and security of user data.

  1. Can small businesses afford to use Chinese AI tools?

Many Chinese AI tools offer different pricing models, including options for small businesses. Some tools provide free – to – use basic versions or affordable subscription plans, making them accessible to businesses of all sizes.

发表在 linux文章 | 留下评论

5 AI Code Editors to Transform Your Development Workflow in 2025: A Global Guide

5 AI Code Editors to Transform Your Development Workflow in 2025: A Global Guide

In 2025, the landscape of software development has been reshaped by AI-driven innovation—and nowhere is this more evident than in AI code editors. These tools have moved beyond basic code suggestions to become collaborative partners: they analyze your codebase, fix bugs with 90% accuracy, edit multiple files simultaneously, and even automate repetitive tasks. For developers across the globe—whether you’re a solo coder in Tokyo, a startup team in Berlin, or an enterprise engineer in Toronto—AI code editors save hours of work, reduce frustration, and let you focus on creative problem-solving.

This guide breaks down the top 5 AI code editors of 2025, each with unique strengths to fit different workflows, budgets, and technical needs. We’ll explore their key features, real-world use cases, pros and cons, and who they’re best suited for—so you can choose the tool that elevates your coding to the next level.

Why AI Code Editors Matter in 2025: The Global Developer’s Advantage

Before diving into the tools, let’s clarify why AI code editors have become non-negotiable for modern developers:

  • Contextual intelligence: Unlike traditional editors, AI-powered tools understand your code’s structure, dependencies, and purpose—so suggestions aren’t just correct, but relevant to your project.
  • Time savings: Tasks that once took hours (e.g., debugging a complex error, writing boilerplate code, updating documentation) can now be done in minutes.
  • Reduced friction: No more switching between forums, documentation, and terminals—AI editors answer questions, run scripts, and fix issues directly in your workspace.
  • Global collaboration: For distributed teams, AI editors standardize coding styles, auto-generate context for new hires, and bridge language gaps (many support multilingual queries).

In 2025, the best AI code editors don’t just “help” you code—they transform how you approach development. Let’s explore the top options.

1. Cursor: The Go-To for Quick Edits and Comprehensive AI Assistance

What Is Cursor?

Cursor is a lightweight, fast AI code editor built for developers who prioritize efficiency. It’s designed to handle everything from small bug fixes to building full applications—all with minimal setup. Developed with a focus on “code understanding,” Cursor doesn’t just generate snippets; it analyzes your entire project to provide context-aware solutions.

Key Features (2025 Update)

  • Code-Centric Chat: Chat directly with your source code to ask questions like, “Why is this function throwing an error?” or “Refactor this module to use TypeScript.” The AI references your project files to give precise answers.
  • Multi-File Editing: Edit or create multiple related files at once (e.g., update a React component and its corresponding test file) with a single instruction.
  • Bug Finder: Automatically scan your code for syntax errors, logical bugs, and performance issues—with step-by-step fixes.
  • Universal Autocomplete: Supports autocomplete for 50+ programming languages (Python, JavaScript, Rust, etc.) and file types (Markdown, JSON, YAML).
  • Terminal Integration: Get AI-powered autocomplete for terminal commands (e.g., Git, Docker, AWS CLI) to reduce typos and speed up workflow.

Real-World Use Case

Maria, a frontend developer in Mexico City, uses Cursor to build tutorial projects for her YouTube channel. “When I need to create a sample e-commerce UI, I tell Cursor, ‘Build a React checkout form with validation and Redux integration,’” she says. “It generates the component, test file, and Redux slice—all in 2 minutes. I can focus on explaining the code instead of writing it.”

Pros and Cons

ProsCons
Blazing fast (starts in <3 seconds)Full features require a paid subscription ($15/month for individuals)
Intuitive interface (no steep learning curve)Limited offline functionality (relies on cloud AI models)
Excellent for small-to-medium projectsLess optimized for monorepos with 1000+ files
Supports all major programming languages

Who Should Use Cursor?

  • Solo developers and content creators (tutorials, side projects)
  • Teams needing quick turnaround on small tasks (bug fixes, feature prototypes)
  • Beginners learning to code (AI chat explains concepts in plain language)

2. Windsurf: Autonomous Task Execution for Iterative Development

What Is Windsurf?

Windsurf is 2025’s breakout AI code editor, designed for developers who want AI to execute tasks independently—not just suggest code. Its “agentic workflow” lets the AI run scripts, check outputs, and iterate on solutions until your request is fully resolved. Think of it as a junior developer who works 24/7 and never gets tired.

Key Features (2025 Update)

  • Autonomous Agent Mode: Tell Windsurf a goal (e.g., “Train a Random Forest model for credit score prediction”), and it will:
    1. Create or edit necessary files (Python scripts, requirements.txt, data preprocessing code)
    2. Run tests to validate functionality
    3. Fix errors (e.g., missing dependencies, data formatting issues)
    4. Document changes (adds comments, updates READMEs)
  • Interactive Iteration: If the first result isn’t perfect, you can refine it with follow-up prompts (“Add cross-validation” or “Optimize for imbalanced data”).
  • Type Hint & Error Prevention: Automatically adds type hints, try-except blocks, and input validation to improve code quality.
  • GitOps Integration: Syncs with Git to commit changes, create branches, and even open pull requests—all from the editor.

Real-World Use Case

Raj, a data scientist in Bangalore, uses Windsurf to build machine learning pipelines. “I used to spend 3 days setting up a model training workflow,” he says. “Now I tell Windsurf, ‘Build a pipeline that loads CSV data, imputes missing values, trains a model, and saves it with versioning.’ It does it in 30 minutes—including testing edge cases I would have missed.”

Pros and Cons

ProsCons
Autonomous task execution (no manual step-by-step)Subscription-only ($20/month; no free tier)
Excellent for data science and backend projectsSteeper learning curve than Cursor
Automatically documents changesUses more system resources (needs 8GB+ RAM)
Integrates with Git and cloud platforms (AWS, GCP)

Who Should Use Windsurf?

  • Data scientists and backend developers (complex, multi-step tasks)
  • Teams working on iterative projects (e.g., model tuning, API development)
  • Developers who want to automate repetitive workflows (e.g., setting up Docker containers)

3. VSCode (with GitHub Copilot): The Free, Ecosystem-Powered Workhorse

What Is VSCode + GitHub Copilot?

In 2025, Microsoft made a game-changing move: GitHub Copilot is now free for all VSCode users (previously a $19/month subscription). VSCode—already the most popular code editor globally—now comes with built-in AI capabilities, making it accessible to developers of all budgets. Copilot integrates seamlessly with VSCode’s ecosystem, including extensions, terminals, and GitHub.

Key Features (2025 Update)

  • Free GitHub Copilot: Generates code, fixes bugs, and explains logic—no subscription required. Supports 100+ languages, from Python to COBOL.
  • Ecosystem Integration: Copilot works across GitHub (pull requests, issues), VSCode extensions (e.g., Docker, Kubernetes), and even the terminal (Copilot CLI for command suggestions).
  • Context-Aware Suggestions: Analyzes your open files, Git history, and project structure to provide relevant code snippets.
  • Multilingual Support: Ask questions or give instructions in 20+ languages (Spanish, Hindi, Japanese) for global teams.

Real-World Use Case

Aisha, a junior developer in Nairobi, uses VSCode + Copilot to learn and contribute to open-source projects. “I’m new to React, so when I’m working on a PR, I ask Copilot, ‘Explain how this hook works,’” she says. “It breaks it down in simple terms, and the free access means I don’t have to choose between learning tools and paying rent.”

Pros and Cons

ProsCons
100% free (no hidden costs)Less accurate than paid tools (occasional irrelevant suggestions)
Massive ecosystem (10k+ extensions)Limited autonomous task execution (needs more manual guidance)
Works offline for basic tasksSlower response times during peak hours
Integrates with GitHub (pull requests, issues)

Who Should Use VSCode + GitHub Copilot?

  • Beginners and students (free access, learning resources)
  • Teams on a tight budget (no subscription fees)
  • Developers who rely on VSCode extensions (e.g., frontend devs using ESLint, Prettier)
  • Open-source contributors (seamless GitHub integration)

4. Zed: The Rust-Powered Speed Demon for Performance-Conscious Devs

What Is Zed?

Zed is 2025’s fastest AI code editor—built entirely with Rust (a language known for speed and reliability). It leverages multiple CPU cores and your GPU to deliver real-time AI responses, even for large codebases. Zed is popular among developers who hate waiting: its AI suggestions appear in <100ms, and it can handle monorepos with 10,000+ files without lag.

Key Features (2025 Update)

  • Blazing Fast Performance: Rust’s memory efficiency and GPU acceleration mean Zed runs 2–3x faster than VSCode or Cursor.
  • Flexible AI Integration: Use Zed’s built-in AI, connect to external models (Anthropic Claude 3.5, OpenAI GPT-4o), or run LLMs locally via Ollama (for privacy-sensitive projects).
  • Multi-User Collaboration: Real-time co-editing with AI assistance—great for distributed teams (e.g., a developer in London and a teammate in Sydney working on the same file).
  • Customizable Workspace: Tailor the UI, keybindings, and AI behavior to your workflow (e.g., disable autocomplete for specific files).

Real-World Use Case

Liam, a backend engineer at a fintech startup in Toronto, uses Zed for a monorepo with 5,000+ Python files. “With VSCode, opening the repo took 5 minutes and autocomplete lagged,” he says. “Zed opens in 30 seconds, and AI suggestions pop up instantly—even when I’m editing a file with 1,000 lines of code. It’s a game-changer for productivity.”

Pros and Cons

ProsCons
Fastest AI response times ( <100ms)AI features are less polished than Cursor/Windsurf
Handles large monorepos with easeLimited mobile support (no iPad/Android app)
Supports local LLMs (privacy-focused)Free tier has usage limits (100 AI queries/day)
Real-time co-editingPaid plan is $12/month

Who Should Use Zed?

  • Developers working on large codebases (monorepos, enterprise projects)
  • Rust enthusiasts (Zed’s codebase is open-source and Rust-focused)
  • Teams needing real-time collaboration (e.g., startup squads)
  • Privacy-conscious developers (local LLM support)

5. PearAI: The VSCode-Based Tool with Potential (Needs Improvement)

What Is PearAI?

PearAI is an AI code editor built as an extension for VSCode—targeting developers who want to customize their AI setup. It lets you connect to your own AI models (via API or local deployment) instead of relying on the editor’s built-in models. While PearAI has potential, it’s still maturing: its 2025 update fixed some bugs but hasn’t closed the gap with leading tools.

Key Features (2025 Update)

  • Custom AI Integration: Connect to any LLM (e.g., OpenAI GPT-4o, Mistral, Llama 3) via API, or run models locally (e.g., Llama 3 70B on your laptop).
  • Context-Aware Chat: Ask questions about your codebase (e.g., “Where is the user authentication logic?”) and get answers based on open files.
  • Autocomplete & Refactoring: Basic AI-powered suggestions for code completion and refactoring (e.g., converting a function to a class).
  • Team Sharing: Save custom AI configurations (e.g., model API keys, prompt templates) and share them with your team.

Real-World Use Case

Carlos, a DevOps engineer in Madrid, uses PearAI with a local Llama 3 model for sensitive infrastructure code. “I can’t send production Dockerfiles to cloud AI models,” he says. “PearAI lets me run Llama 3 on my machine, so all code stays local. It’s not as fast as Zed, but it solves a critical privacy problem.”

Pros and Cons

ProsCons
Custom AI model support (local/cloud)Slow response times (1–2 seconds per suggestion)
Integrates with VSCode (familiar interface)Fewer features than competitors (no multi-file editing)
Privacy-focused (local LLM option)Complicated setup (needs API key/config for custom models)
Free tier available (limited features)Infrequent updates (last major release was 3 months ago)

Who Should Use PearAI?

  • Developers needing custom AI models (e.g., local LLMs, enterprise-specific models)
  • VSCode loyalists who don’t want to switch editors
  • Privacy-focused teams (e.g., healthcare, finance)
  • Developers willing to trade polish for customization

2025 AI Code Editor Comparison: Choose the Right Tool for You

To simplify your decision, here’s a side-by-side comparison of the top 5 tools:

FeatureCursorWindsurfVSCode + CopilotZedPearAI
Price$15/month (free tier: 50 queries/day)$20/month (no free tier)FreeFree (100 queries/day); $12/month (unlimited)Free (basic); $10/month (custom models)
SpeedFast ( <500ms)Medium (1–1.5s)Medium (1–2s)Fastest ( <100ms)Slow (1–2s)
AI AutonomyLow (needs guidance)High (runs tasks independently)Low (basic suggestions)Medium (some autonomy)Low (basic suggestions)
Best ForQuick edits, tutorialsData science, backendBeginners, free usersLarge codebases, speedCustom models, privacy
Offline SupportLimitedNoBasicYes (local LLMs)Yes (local LLMs)

Conclusion: Elevate Your Coding with AI in 2025

AI code editors are no longer “nice-to-have”—they’re essential tools for staying competitive in 2025’s fast-paced development landscape. Whether you prioritize speed (Zed), autonomy (Windsurf), cost (VSCode + Copilot), or customization (PearAI), there’s a tool that fits your workflow.

Here’s a final recap to guide your choice:

  • Choose Cursor if you want fast, intuitive AI for small-to-medium projects.
  • Choose Windsurf if you need AI to run complex, multi-step tasks independently.
  • Choose VSCode + Copilot if you’re on a budget or already use VSCode.
  • Choose Zed if you work on large codebases and hate waiting for AI responses.
  • Choose PearAI if you need custom AI models (local or cloud) and don’t mind a less polished experience.

No matter which tool you pick, the goal is the same: let AI handle the repetitive, time-consuming work so you can focus on what matters most—building innovative software that solves real problems. Try one (or more!) today and see how AI transforms your coding workflow.

发表在 linux文章 | 留下评论

Qoder: The Agentic Coding Platform Transforming Professional Software Development Globally

Qoder: The Agentic Coding Platform Transforming Professional Software Development Globally

In the fast-paced world of professional software development, teams and individual developers are constantly seeking tools that can streamline workflows, enhance collaboration, and automate complex tasks. Enter Qoder – an innovative agentic coding platform designed to revolutionize how software is built, documented, and maintained. Whether you’re a solo developer tackling a passion project, a team lead managing a cross-functional engineering team, or an open-source maintainer coordinating contributors worldwide, Qoder brings a suite of powerful features to help you think deeper, code smarter, and build better.

What Is Qoder, and Why Does It Matter for Developers?

At its core, Qoder is an agentic coding platform tailored for professional software development. Unlike basic code suggestion tools that offer piecemeal lines of code, Qoder unites three key pillars to deliver end-to-end value: enhanced context engineering, intelligent AI agents, and in-repo documentation. This combination allows the platform to deeply understand your codebase, adapt to your project’s unique needs, and automate tasks that once required hours of manual work.

For developers and teams across the globe, Qoder addresses critical pain points in the software development lifecycle:

  • Disconnected context: Many tools fail to maintain a consistent understanding of a codebase’s structure, dependencies, and coding standards across sessions. Qoder’s persistent memory solves this by preserving project-specific context.
  • Outdated documentation: Keeping docs in sync with code is a constant battle. Qoder’s auto-updated Repo Wiki eliminates this chore, ensuring documentation always reflects the latest codebase changes.
  • Inefficient task delegation: Complex tasks like building a new feature or refactoring legacy code often require manual orchestration. Qoder’s Quest Mode lets you delegate these tasks to AI agents asynchronously, freeing you to focus on high-impact work.
  • Tool fragmentation: Developers waste time switching between Git clients, documentation tools, and AI models. Qoder integrates seamlessly with Git/GitHub, supports top AI models (Claude, GPT, Gemini), and runs on Windows and macOS – all in one unified workspace.

Qoder’s Top Features: Built for Global Development Teams

Qoder’s feature set is designed to solve real-world challenges faced by developers everywhere. Each tool is engineered to work in harmony, creating a cohesive workflow that adapts to your team’s size, project type, and technical stack. Let’s break down the platform’s most impactful capabilities:

1. Agentic Coding Platform: AI Agents That Handle Structured Tasks

Qoder’s intelligent AI agents are more than just code generators – they’re collaborative partners that tackle structured development tasks. Unlike tools that suggest isolated code snippets, these agents can:

  • Refactor entire modules while preserving functionality and adhering to your coding style.
  • Debug complex issues by cross-referencing codebase context and error logs.
  • Implement multi-step features (e.g., adding user authentication, integrating a third-party API) with minimal human oversight.
  • Generate unit tests that cover edge cases, ensuring code reliability.

This is a game-changer for teams in time zones across the globe: a developer in Tokyo can delegate a task to an AI agent at the end of their day, and a colleague in New York can review the completed work first thing in the morning – no manual handoff required.

2. Enhanced Context Engineering: AI That Truly Understands Your Code

The biggest limitation of many AI coding tools is their lack of deep context. Qoder solves this with enhanced context engineering – a system that aggregates and updates information from multiple sources (code files, Git history, documentation, and team guidelines) to build a holistic view of your codebase.

For example, if you ask Qoder to “optimize the checkout flow,” the platform doesn’t just generate generic optimization code. It:

  • Reviews the existing checkout logic, including dependencies on payment gateways and user data models.
  • References your team’s coding standards (stored in Qoder’s Memory) to ensure consistency.
  • Checks the Repo Wiki for documentation on past checkout-related updates.
  • Considers recent Git commits to avoid conflicting with ongoing work.

This level of context ensures AI suggestions are not just correct, but relevant to your project – a critical advantage for global teams working on complex, custom codebases.

3. Quest Mode: Asynchronous Task Delegation for Busy Teams

One of Qoder’s most popular features is Quest Mode – a tool that lets you delegate complex, time-consuming tasks to AI agents and receive complete, ready-to-use results. Unlike real-time AI chat, Quest Mode is asynchronous, meaning you can set a task and come back later to find it done.

How it works:

  1. Define the task (e.g., “Build a user profile API endpoint with input validation and error handling”).
  2. Add context (e.g., “Use our existing Express.js framework, follow the REST API guidelines in the Repo Wiki, and integrate with the user database”).
  3. Submit the “Quest” to Qoder’s AI agents.
  4. Receive a notification when the task is complete – including code, tests, and a summary of changes.

This is ideal for:

  • Solo developers who need to multitask (e.g., delegate API development while designing a UI).
  • Global teams working across time zones (e.g., a London-based developer delegates a task before logging off, and a Sydney-based teammate reviews it the next day).
  • Team leads who want to free up senior developers from repetitive work (e.g., delegating boilerplate code generation to AI).

4. Repo Wiki: Auto-Updated Documentation That Lives With Your Code

Outdated documentation is a universal frustration for developers. Qoder’s Repo Wiki eliminates this problem by automatically generating and updating documentation that lives directly in your repository. Unlike static wikis (e.g., Confluence pages that are forgotten after launch), the Repo Wiki:

  • Updates in real time when you make code changes (e.g., if you add a new function to a utility file, the Wiki automatically adds documentation for that function).
  • Syncs bidirectionally with GitHub Wiki – so if you update the Repo Wiki in Qoder, the changes appear in GitHub, and vice versa.
  • Is accessible to AI agents, chat, and the CLI – ensuring AI suggestions and team decisions are always based on the latest docs.

For example, if a new hire in Toronto joins your team, they don’t need to spend hours reading outdated READMEs or pinging teammates for clarification. They can open the Repo Wiki to find:

  • Auto-generated architecture diagrams of the codebase.
  • Up-to-date API references with example requests/responses.
  • Step-by-step guides for setting up the local development environment.
  • Notes on past bugs and how they were resolved.

This not only speeds up onboarding but also reduces knowledge silos – a key benefit for distributed teams.

5. Live Project Context & Memory: Consistent Guidance Across Sessions

Qoder’s persistent Memory system stores project-specific information (coding standards, team guidelines, tool preferences, and past decisions) so that every AI interaction is consistent – even if you log off and come back weeks later.

For example, if your team decides “we use ESLint with Airbnb rules” or “all database queries must go through the data access layer,” you can save these guidelines to Memory. From that point on:

  • AI agents will automatically follow these rules when generating code.
  • New team members will see these guidelines in the Repo Wiki and AI suggestions.
  • The CLI will flag code that violates these standards during development.

This consistency is invaluable for global teams where members may have different coding backgrounds or join mid-project. It ensures everyone – from a junior developer in Mumbai to a senior engineer in Toronto – is working from the same playbook.

6. Git/GitHub Integration: Seamless Collaboration for Distributed Teams

Qoder integrates seamlessly with Git and GitHub – the most widely used version control tools in the world. This integration ensures your code, documentation, and context stay in sync across your team’s workflow:

  • The Repo Wiki is stored in your Git repository, so it’s version-controlled alongside your code.
  • Changes to the Repo Wiki in Qoder sync automatically to GitHub Wiki, and vice versa.
  • AI agents can reference Git history (e.g., “Who last modified this file?” or “What was the purpose of this commit?”) to make more informed decisions.
  • You can trigger AI tasks (e.g., “Review this pull request”) directly from GitHub.

For distributed teams, this integration eliminates the need to switch between tools – everything you need to code, document, and collaborate is in one place.

7. Multi-Model AI Engine: Flexibility for Every Project

Qoder doesn’t lock you into a single AI model. Its multi-model AI engine supports three of the most powerful models for coding: Claude (Anthropic), GPT (OpenAI), and Gemini (Google). This flexibility lets you choose the model that best fits your project’s needs:

  • Claude: Ideal for long-form tasks (e.g., refactoring a large codebase) thanks to its extended context window.
  • GPT: Great for general coding tasks (e.g., generating boilerplate code, debugging) with its strong understanding of common programming languages.
  • Gemini: Excels at tasks involving multiple languages or emerging technologies (e.g., integrating AI models into your code).

You can even switch models per project – for example, using Claude for a legacy code refactor and GPT for building a new React component. This level of flexibility is rare in coding tools and ensures Qoder adapts to your team’s technical preferences.

8. Cross-Platform Support: Run Qoder Anywhere

Qoder is built for global developers, which means it works on the operating systems you use. The platform offers installers for Windows and macOS, with a consistent user experience across both. Whether you’re a Windows user in Berlin or a macOS user in Tokyo, you’ll have access to all of Qoder’s features without compatibility issues.

Who Uses Qoder? Real-World Use Cases for Global Teams

Qoder’s versatility makes it a fit for nearly every role and project type in software development. Here are the most common use cases, with examples of how global teams are leveraging the platform:

1. Solo Developers: Multitask Without Sacrificing Quality

Solo developers often wear multiple hats – from coding to documentation to testing. Qoder helps them work more efficiently by:

  • Delegating multi-step tasks (e.g., “Build a login system with JWT authentication”) to AI agents via Quest Mode.
  • Auto-generating documentation so they don’t have to stop coding to update READMEs.
  • Providing context-aware suggestions that align with their personal coding style (stored in Memory).

For example, a solo developer in Brazil building an e-commerce app can use Quest Mode to delegate the payment gateway integration while they design the product catalog UI. By the time they finish the UI, the payment code is ready to test – cutting their development time in half.

2. Team Leads & Engineering Managers: Streamline Collaboration and Enforce Standards

Team leads overseeing distributed teams face the challenge of keeping everyone aligned. Qoder solves this by:

  • Using Memory to enforce consistent coding standards (e.g., “All React components must use functional hooks”) across the team.
  • Providing real-time visibility into AI-assisted tasks (e.g., “Which team members have delegated tasks via Quest Mode?”).
  • Simplifying code reviews by ensuring documentation (in Repo Wiki) and code are always in sync.

A team lead in London managing a team of 10 developers across India, Canada, and Australia can use Qoder to:

  • Set global guidelines in Memory (e.g., “We use TypeScript for all new frontend code”).
  • Monitor Quest Mode tasks to ensure no one is stuck on repetitive work.
  • Share the Repo Wiki with stakeholders to keep them updated on progress – no manual status reports needed.

3. New Hires & Onboarding: Ramp Up Fast, No Matter Where You Are

Onboarding new developers to a complex codebase can take weeks – especially for remote hires. Qoder’s Repo Wiki and context-aware AI cut this time down significantly:

  • New hires can use the Repo Wiki to access auto-generated architecture overviews, API references, and setup guides.
  • AI agents can answer questions like “How does the user authentication flow work?” or “Where is the payment logic stored?” in real time.
  • Memory ensures new hires receive suggestions that align with the team’s standards from day one.

A new developer in Singapore joining a U.S.-based team can use Qoder to:

  • Set up their local environment in hours (instead of days) using the Repo Wiki’s step-by-step guide.
  • Ask the AI to explain a complex module without pinging a senior developer in a different time zone.
  • Contribute code within their first week, thanks to context-aware suggestions.

4. Open-Source Maintainers: Keep Contributors Informed

Open-source maintainers rely on clear documentation to attract and retain contributors. Qoder’s Repo Wiki makes this easy by:

  • Auto-updating docs when code changes, so contributors always have accurate information.
  • Syncing with GitHub Wiki, so docs are accessible to anyone visiting the repo.
  • Letting maintainers delegate routine tasks (e.g., “Review this PR for code style”) to AI agents.

A maintainer of a popular open-source library in Germany can use Qoder to:

  • Ensure the README and API docs are always up to date, even as contributors from around the world submit PRs.
  • Use Quest Mode to generate a “contributor’s guide” based on past PR feedback.
  • Reduce the time spent on code reviews by having AI agents flag style issues first.

5. API Developers & Tech Writers: Keep Docs in Sync With Code

Tech writers and API developers spend hours updating documentation to match code changes. Qoder’s Repo Wiki automates this process:

  • When an API endpoint is added or modified, the Repo Wiki automatically updates the API reference (including parameters, response codes, and examples).
  • AI agents can generate draft documentation for new endpoints, which tech writers can refine.
  • Docs sync with GitHub, so external users always have access to the latest API info.

A tech writer in Canada working with an API team in India can use Qoder to:

  • Avoid manually updating API docs every time the team makes a change.
  • Access the Repo Wiki to find context about why an endpoint was modified (e.g., “This change fixes a bug in the payment flow”).
  • Collaborate with the team in real time, even across time zones.

Qoder Pricing: Free Access During Public Preview

For developers and teams looking to try Qoder, the platform offers free access during its public preview – no credit card required. This includes:

  • All currently available features (including Quest Mode, Repo Wiki, and multi-model AI support).
  • A “pro-level experience” during testing, with no restrictions on core functionality.
  • Usage limits to ensure fair access for all users (details are available on the Qoder website).

This free preview is a great opportunity for global teams to test Qoder’s capabilities on real projects, without any financial commitment. As the platform evolves, Qoder plans to introduce paid plans – but for now, anyone can sign up and start using the tool.

Qoder Alternatives: How It Stands Out

While there are other coding tools on the market, Qoder’s focus on agentic workflows, context engineering, and in-repo documentation sets it apart. Here’s how it compares to popular alternatives:

ToolKey FocusHow Qoder Differs
CodeiumReal-time code suggestionsQoder offers asynchronous task delegation (Quest Mode) and auto-updated documentation, while Codeium focuses on inline suggestions.
QuestflowAI agent orchestration for workflowsQuestflow is designed for general workflow automation, while Qoder is built specifically for software development (with Git integration, code context, and multi-model AI).
CodiumAIAI-powered code quality toolsCodiumAI focuses on testing and quality assurance, while Qoder offers end-to-end workflow support (documentation, task delegation, context management).
CoderabbitAI-driven code reviewsCoderabbit specializes in PR reviews, while Qoder integrates code reviews with documentation, context, and task automation.

For developers who need more than just code suggestions – who want a tool that understands their codebase, automates complex tasks, and keeps docs up to date – Qoder is the clear choice.

How to Get Started With Qoder

Getting started with Qoder is simple, regardless of your location or technical stack:

  1. Visit the Qoder website: Go to opentools.ai/tools/qoder to learn more about the platform.
  2. Download the installer: Choose the version for Windows or macOS (both are free during the public preview).
  3. Connect your Git/GitHub repo: Link Qoder to your repository to let the platform build context and generate the Repo Wiki.
  4. Set up your Memory: Add coding standards, team guidelines, or project-specific notes to ensure AI suggestions are consistent.
  5. Start using features: Try Quest Mode for task delegation, explore the Repo Wiki for documentation, or use the AI chat to ask codebase questions.

Qoder’s onboarding process is designed to be intuitive – even for developers new to AI coding tools. If you run into issues, the platform offers support via its website (with troubleshooting guides and a community forum).

Conclusion: Qoder – The Future of Global Software Development

In a world where software teams are increasingly distributed, and development cycles are getting shorter, Qoder provides a much-needed solution: a tool that unites AI, context, and documentation to make coding more efficient, collaborative, and consistent.

Whether you’re a solo developer in a small town or a team lead managing engineers across five continents, Qoder’s features – from Quest Mode to Repo Wiki to multi-model AI – are built to adapt to your needs. And with free access during the public preview, there’s no better time to try it.

Join the growing community of global developers using Qoder to streamline their workflows, reduce manual work, and build better software. Visit opentools.ai/tools/qoder today to get started.

阿里Qoder vs. Cursor vs. Trae:2025年AI编程工具终极对决与深度避坑指南随着AI编程赛道进入 – 掘金

https://opentools.ai/tools/qoder

Top 10 AI Code Editors in 2025 – DEV Community

发表在 linux文章 | 留下评论

Base64 Flex 工具:自定义字符集 Base64 编码/解码器

Base64 Flex:强大的在线自定义Base64编码解码工具

Base64 Flex 是由 CalcGuide 提供的一款免费、功能全面的在线Base64编码和解码工具。它超越了标准Base64的功能,允许用户深度自定义编码规则,满足开发者、安全研究人员和高级用户的特定需求。所有处理均在您的浏览器本地进行,确保数据隐私和安全。

核心功能详解

Base64 Flex 的核心优势在于其高度的可定制性:

  • 自定义64字符集:用户可以指定任意包含64个唯一字符的字符串来替代标准的 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 字符表。
  • 自定义填充字符:可以将默认的填充符 = 替换为任何其他单个字符。
  • 字符集重排模式:在应用自定义字符集前,可以对其进行预处理:
    • 顺序 (Sequential):使用自定义字符集的原始顺序。
    • 逆序 (Reverse):将自定义字符集顺序完全颠倒。
    • 顺序n移位 (Sequential Shift by n):将自定义字符集向左循环移动n位(系统自动处理 n > 64 的情况)。
    • 逆序n移位 (Reverse Shift by n):先将自定义字符集颠倒,再向左循环移动n位。
  • 配置导入/导出:可以将设置好的自定义规则(字符集、填充符、模式、移位数)保存为JSON文件,方便日后快速加载和复用。

如何使用 Base64 Flex

进行编码

  1. 在“输入数据”框中粘贴您需要编码的文本。
  2. 在“64位自定义字符集”框中输入您的64个字符(例如:`@#$%^&*…`)。
  3. 在“填充字符”框中输入您的填充符(例如:`~`)。
  4. 在“字符集重排模式”下拉菜单中选择您需要的模式(如:顺序n移位)。
  5. 如果选择了移位模式,在“移位数 n”框中输入具体的数值。
  6. 点击“编码 (Encode)”按钮,结果将显示在“输出结果”框中。

进行解码

  1. 在“输入数据”框中粘贴您需要解码的Base64字符串(该字符串必须是由Base64 Flex按相同规则编码生成的)。
  2. 关键:确保“64位自定义字符集”、“填充字符”、“字符集重排模式”和“移位数 n”的设置与编码时使用的完全一致。
  3. 点击“解码 (Decode)”按钮,原始文本将显示在“输出结果”框中。

保存和加载配置

  1. 设置好您满意的自定义规则。
  2. 点击“导出配置”按钮,下载 custom_base64_config.json 文件。
  3. 未来使用时,点击“导入配置”按钮,选择之前下载的文件即可。

适用场景

  • 开发者与工程师:用于创建自定义的编码协议、数据混淆、生成特定格式的令牌(Token)或在特定系统中传输数据。
  • CTF网络安全竞赛:快速应对使用非标准Base64字符集或经过特殊处理的编码挑战。
  • 教学与研究:直观演示Base64编码原理及不同参数对结果的影响。
  • 数据处理:在需要特定编码格式的数据转换流程中。

常见问题 (FAQ)

Q: 为什么自定义字符集必须是64个不重复的字符?

A: Base64编码原理要求一个映射表来表示6位二进制的所有64种可能值。字符不足或重复会导致映射关系错误,无法正确编码或解码。

Q: 填充字符可以和自定义字符集里的字符相同吗?

A: 不可以。填充字符必须是独立的,否则在解码时会产生歧义,导致失败。

Q: 移位数n如果超过64位会怎样?

A: 工具会自动计算 n % 64 的余数作为有效移位数。例如,移位69位等同于移位5位。

Q: 编码和解码时的配置必须完全一样吗?

A: 是的。解码是编码的逆过程,必须使用完全相同的规则才能还原数据。强烈建议使用“导出配置”功能。

Q: 我的数据安全吗?

A: 绝对安全。Base64 Flex 是一款纯前端工具,所有操作均在您的本地浏览器内存中完成,数据不会上传到任何服务器。

为什么选择 Base64 Flex?

  • 完全免费:无任何使用限制或隐藏费用。
  • 功能强大:提供标准工具不具备的深度自定义能力。
  • 注重隐私:客户端处理,数据零泄露风险。
  • 易于使用:直观的界面设计,清晰的操作指引。
  • 高效复用:配置导入导出功能,方便管理不同规则。
  • 权威可靠:由CalcGuide开发,专注于提供高质量的开发者和计算工具。


发表在 linux文章 | 留下评论

阿里免费AI-IDE Qoder快速入门

阿里免费AI-IDE Qoder快速入门

开始使用

快速入门

本主题将引导您以个人用户身份使用 Qoder 核心功能的动手项目。最后,您将熟悉它的下一个编辑建议 (NES)、内联聊天和 AI 聊天——人工智能辅助编码的关键工具。

Qoder 个人版目前可供所有用户免费试用。试用期可能会发生变化。有关最新信息,请查看产品更新。

Qoder IDE 用户指南 – 完整使用教程和最佳实践 | 智能编程助手

Qoder IDE – AI-Powered Coding Platform for Real Development

Qoder Reviews, Alternatives, and Pricing updated September 2025

下载并安装 Qoder

  1. 从 https://qoder.com/download 下载安装程序。
  2. 双击该文件开始安装。
  3. 通过双击 Qoder IDE 图标启动 Qoder。

2

登录

  1. 在 Qoder IDE 的右上角,单击用户图标或使用键盘快捷键( (macOS)或 (Windows)),然后选择登录。,Ctrlshift,
  2. 在出现的网页上:
  • 点击底部的注册并完成注册过程,或
  • 使用您的 Google 或 GitHub 帐户直接注册。
  1. 返回 Qoder IDE。您现在可以自由使用所有功能。

3

打开项目

选择使用本地项目或从 GitHub 克隆示例。

  • 使用本地项目
    1. 单击打开或使用键盘快捷键:
    • macOS:O
    • 窗户:OCtrl
    1. 浏览到项目文件夹,选择一个文件,然后将其打开。
  • 克隆项目
    1. 单击克隆存储库
    2. 在顶部的搜索栏中:
      • 输入项目 URL,然后单击从 URL 克隆,或
      • 单击“从 GitHub 克隆”,然后按照提示进行作。
    3. 完成克隆项目的步骤。

4

探索功能

  • 触发 NES

NES 通过在您的光标处提供智能、上下文感知的编辑,帮助您开始 AI 辅助编码。

  1. 输入部分代码片段或自然语言的代码请求。示例:“初始化列表。
  2. 按 (macOS) 或 (Windows)。建议将自动出现。PAltP
  3. 按 Tab 键接受建议。

NES 支持多行编辑和无缝自动完成。

  • 发起内联聊天

使用内联聊天直接在代码上下文中获取 AI 帮助。

  1. 在 Qoder 代码编辑器中,按 (macOS) 或 (Windows)。将打开“内联聊天”窗口。ICtrlI
  2. 键入您的请求,然后按 Enter。示例:“添加用于处理文件更新的方法。
  3. 要应用 AI 生成的代码,请按 (macOS) 或 (Windows)。⏎ CtrlEnter
  • 开始 AI 聊天

使用 AI 聊天面板或按 (macOS) 或 (Windows) 在询问或代理模式下执行更广泛的任务。CtrlL

  1. 在右侧的 AI 聊天面板中,输入您的请求。示例:“为此函数创建测试并运行它们。
  2. 按 Enter 键。AI 代理将生成包含相关测试用例的测试文件。
  3. 单击“根据提示运行,或按 (macOS) 或 (Windows) 执行测试。CtrlEnter

Get Started

Quick Start

This topic walks you through a hands-on project using Qoder’s core features as an individual user. By the end, you’ll be familiar with its Next Edit Suggestions (NES), Inline Chat, and AI Chat—key tools for AI-assisted coding.

Qoder Individual Edition is currently available as a free trial for all users. The trial duration is subject to change. For the latest information, please check product updates.

1

Download and install Qoder

  1. Download the installer from https://qoder.com/download.
  2. Double-click the file to begin installation.
  3. Launch Qoder by double-clicking the Qoder IDE icon.

2

Sign in

  1. In the upper-right corner of your Qoder IDE, click the user icon or use the keyboard shortcut ( (macOS)or (Windows)), and select Sign in.,Ctrlshift,
  2. On the web page that appears:
  • Click Sign up at the bottom and complete the registration process, or
  • Use your Google or GitHub account to sign up directly.
  1. Return to the Qoder IDE. You can now use all features freely.

3

Open a project

Choose to work with an local project or clone a sample from GitHub.

  • Use a local project
    1. Click Open or use the keyboard shortcut:
    • macOS:  O 
    • Windows: O Ctrl
    1. Browse to your project folder, select a file, and open it.
  • Clone a project
    1. Click Clone repo.
    2. In the search bar at the top:
      • Enter a project URL and click Clone from URL, or
      • Click Clone from GitHub and follow the prompts.
    3. Complete the steps to clone the project.

4

Explore features

  • Trigger an NES

NES helps you get started with AI-assisted coding by offering intelligent, context-aware edits right at your cursor.

  1. Enter a partial code snippet or a code request in natural language. Example: “Initialize a list.”
  2. Press  (macOS) or   (Windows). Suggestions will appear automatically.PAltP
  3. Press Tab to accept a suggestion.

NES supports multi-line edits, and seamless autocomplete.

  • Initiate an inline chat

Use Inline Chat to get AI help directly within your code context.

  1. In the Qoder code editor, press  (macOS) or (Windows). The Inline Chat window will open.ICtrlI
  2. Type your request and press Enter. Example: “Add a method for handling file updates.”
  3. To apply the AI-generated code, press   (macOS) or  (Windows).⏎ CtrlEnter
  • Start an AI chat

Use the AI Chat panel or press (macOS) or (Windows) for broader tasks in Ask or Agent mode.CtrlL

  1. In the AI Chat panel on the right, enter your request. Example: “Create tests for this function and run them.”
  2. Press Enter. The AI agent will generate a test file with relevant test cases.
  3. Click Run as prompted, or press   (macOS) or  (Windows) to execute the tests.CtrlEnter
发表在 linux文章 | 留下评论

getdents系统调用及示例

getdents – 获取目录项

函数介绍

getdents系统调用用于读取目录文件的内容,获取目录中的文件项信息。它返回原始的目录项结构,需要手动解析。这是一个低级别的目录读取函数。

函数原型

#include <dirent.h>
#include <sys/syscall.h>

int getdents(unsigned int fd, struct linux_dirent *dirp, unsigned int count);

功能

从目录文件描述符中读取目录项信息。

参数

  • unsigned int fd: 目录文件描述符(通过opendir或open获得)
  • struct linux_dirent *dirp: 指向存储目录项的缓冲区
  • unsigned int count: 缓冲区大小(字节)

返回值

  • 成功时返回读取的字节数
  • 失败时返回-1,并设置errno:
    • EBADF: 文件描述符无效
    • EFAULT: 缓冲区地址无效
    • EINVAL: 参数无效
    • ENOENT: 目录不存在

相似函数

  • readdir(): 标准C库函数,更易使用
  • opendir()closedir(): 打开和关闭目录
  • getdents64(): 64位版本,支持大文件

示例代码

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

// 目录项结构体定义
struct linux_dirent {
    long d_ino;              // inode号
    __kernel_off_t d_off;    // 到下一个目录项的偏移
    unsigned short d_reclen; // 当前目录项长度
    char d_name[];           // 文件名(以null结尾)
};

int main() {
    int fd;
    char buf[4096];
    int nread;
    char *p;
    struct linux_dirent *d;
    
    printf("=== Getdents函数示例 ===\n");
    
    // 示例1: 基本的目录读取操作
    printf("\n示例1: 基本的目录读取操作\n");
    
    // 打开当前目录
    fd = open(".", O_RDONLY | O_DIRECTORY);
    if (fd == -1) {
        perror("打开当前目录失败");
        exit(EXIT_FAILURE);
    }
    printf("成功打开当前目录,文件描述符: %d\n", fd);
    
    // 使用getdents读取目录内容
    printf("读取目录内容:\n");
    while (1) {
        nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
        if (nread == -1) {
            perror("getdents失败");
            close(fd);
            exit(EXIT_FAILURE);
        }
        
        if (nread == 0)  // 没有更多的目录项
            break;
        
        // 解析目录项
        for (p = buf; p < buf + nread;) {
            d = (struct linux_dirent *)p;
            
            // 显示目录项信息
            printf("  inode: %ld, ", d->d_ino);
            printf("长度: %d, ", d->d_reclen);
            printf("名称: %s\n", d->d_name);
            
            p += d->d_reclen;
        }
    }
    
    close(fd);
    
    // 示例2: 创建测试目录和文件进行演示
    printf("\n示例2: 创建测试环境进行演示\n");
    
    // 创建测试目录
    if (mkdir("test_getdents_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
    } else {
        printf("创建测试目录: test_getdents_dir\n");
        
        // 在测试目录中创建一些文件
        chdir("test_getdents_dir");
        
        // 创建测试文件
        int test_files[] = {1, 2, 3, 4, 5};
        for (int i = 0; i < 5; i++) {
            char filename[20];
            sprintf(filename, "test_file_%d.txt", test_files[i]);
            int file_fd = open(filename, O_CREAT | O_WRONLY, 0644);
            if (file_fd != -1) {
                const char *content = "Test file content";
                write(file_fd, content, strlen(content));
                close(file_fd);
                printf("创建测试文件: %s\n", filename);
            }
        }
        
        // 创建子目录
        if (mkdir("subdir", 0755) == -1 && errno != EEXIST) {
            perror("创建子目录失败");
        } else {
            printf("创建子目录: subdir\n");
        }
        
        // 打开测试目录
        fd = open(".", O_RDONLY | O_DIRECTORY);
        if (fd != -1) {
            printf("\n读取测试目录内容:\n");
            
            while (1) {
                nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
                if (nread == -1) {
                    perror("getdents失败");
                    break;
                }
                
                if (nread == 0)
                    break;
                
                // 解析并显示目录项
                for (p = buf; p < buf + nread;) {
                    d = (struct linux_dirent *)p;
                    
                    // 跳过.和..目录项
                    if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
                        printf("  文件名: %-20s", d->d_name);
                        printf("inode: %ld, ", d->d_ino);
                        printf("长度: %d\n", d->d_reclen);
                    }
                    
                    p += d->d_reclen;
                }
            }
            
            close(fd);
        }
        
        // 返回上级目录并清理
        chdir("..");
    }
    
    // 示例3: 与readdir的对比
    printf("\n示例3: getdents与readdir对比\n");
    
    // 使用getdents读取
    fd = open(".", O_RDONLY | O_DIRECTORY);
    if (fd != -1) {
        printf("使用getdents读取目录:\n");
        nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
        if (nread > 0) {
            int count = 0;
            for (p = buf; p < buf + nread && count < 5;) {
                d = (struct linux_dirent *)p;
                if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
                    printf("  %s\n", d->d_name);
                    count++;
                }
                p += d->d_reclen;
            }
        }
        close(fd);
    }
    
    // 使用readdir读取(标准方法)
    printf("使用readdir读取目录:\n");
    DIR *dir = opendir(".");
    if (dir != NULL) {
        struct dirent *entry;
        int count = 0;
        while ((entry = readdir(dir)) != NULL && count < 5) {
            if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
                printf("  %s\n", entry->d_name);
                count++;
            }
        }
        closedir(dir);
    }
    
    printf("getdents提供更多底层控制,但readdir更易使用\n");
    
    // 示例4: 目录项详细信息解析
    printf("\n示例4: 目录项详细信息解析\n");
    
    fd = open(".", O_RDONLY | O_DIRECTORY);
    if (fd != -1) {
        nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
        if (nread > 0) {
            printf("目录项详细信息:\n");
            for (p = buf; p < buf + nread;) {
                d = (struct linux_dirent *)p;
                
                // 显示所有目录项(包括.和..)
                printf("  名称: %-20s", d->d_name);
                printf("inode: %-10ld", d->d_ino);
                printf("偏移: %-8ld", (long)d->d_off);
                printf("长度: %d\n", d->d_reclen);
                
                p += d->d_reclen;
            }
        }
        close(fd);
    }
    
    // 示例5: 错误处理演示
    printf("\n示例5: 错误处理演示\n");
    
    // 尝试对无效文件描述符操作
    nread = syscall(SYS_getdents, 999, buf, sizeof(buf));
    if (nread == -1) {
        printf("对无效文件描述符操作: %s\n", strerror(errno));
    }
    
    // 尝试对普通文件操作
    int regular_fd = open("regular_file.txt", O_CREAT | O_WRONLY, 0644);
    if (regular_fd != -1) {
        write(regular_fd, "test", 4);
        nread = syscall(SYS_getdents, regular_fd, buf, sizeof(buf));
        if (nread == -1) {
            printf("对普通文件操作: %s\n", strerror(errno));
        }
        close(regular_fd);
        unlink("regular_file.txt");
    }
    
    // 尝试使用无效缓冲区
    fd = open(".", O_RDONLY | O_DIRECTORY);
    if (fd != -1) {
        nread = syscall(SYS_getdents, fd, NULL, sizeof(buf));
        if (nread == -1) {
            printf("使用无效缓冲区: %s\n", strerror(errno));
        }
        close(fd);
    }
    
    // 示例6: 实际应用场景
    printf("\n示例6: 实际应用场景\n");
    
    // 场景1: 快速目录扫描(跳过隐藏文件)
    printf("场景1: 快速目录扫描\n");
    fd = open(".", O_RDONLY | O_DIRECTORY);
    if (fd != -1) {
        printf("非隐藏文件列表:\n");
        int file_count = 0;
        while (1) {
            nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
            if (nread <= 0) break;
            
            for (p = buf; p < buf + nread;) {
                d = (struct linux_dirent *)p;
                
                // 跳过.和..以及隐藏文件(以.开头)
                if (strcmp(d->d_name, ".") != 0 && 
                    strcmp(d->d_name, "..") != 0 && 
                    d->d_name[0] != '.') {
                    printf("  %s\n", d->d_name);
                    file_count++;
                }
                
                p += d->d_reclen;
            }
        }
        printf("总共找到 %d 个非隐藏文件\n", file_count);
        close(fd);
    }
    
    // 场景2: 目录统计信息
    printf("场景2: 目录统计信息\n");
    fd = open(".", O_RDONLY | O_DIRECTORY);
    if (fd != -1) {
        int total_files = 0;
        int total_dirs = 0;
        long long total_size = 0;
        
        while (1) {
            nread = syscall(SYS_getdents, fd, buf, sizeof(buf));
            if (nread <= 0) break;
            
            for (p = buf; p < buf + nread;) {
                d = (struct linux_dirent *)p;
                
                // 跳过.和..
                if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
                    // 检查文件类型(需要stat)
                    struct stat st;
                    if (stat(d->d_name, &st) == 0) {
                        if (S_ISDIR(st.st_mode)) {
                            total_dirs++;
                        } else {
                            total_files++;
                            total_size += st.st_size;
                        }
                    }
                }
                
                p += d->d_reclen;
            }
        }
        
        printf("目录统计:\n");
        printf("  文件数: %d\n", total_files);
        printf("  目录数: %d\n", total_dirs);
        printf("  文件总大小: %lld 字节\n", total_size);
        
        close(fd);
    }
    
    // 清理测试目录
    printf("\n清理测试资源...\n");
    if (access("test_getdents_dir", F_OK) == 0) {
        // 删除测试目录中的文件
        chdir("test_getdents_dir");
        for (int i = 1; i <= 5; i++) {
            char filename[20];
            sprintf(filename, "test_file_%d.txt", i);
            unlink(filename);
        }
        rmdir("subdir");
        chdir("..");
        rmdir("test_getdents_dir");
        printf("删除测试目录完成\n");
    }
    
    return 0;
}

getdents64系统调用及示例

getdents系统调用及示例

发表在 linux文章 | 留下评论

getegid系统调用及示例

getegid – 获取当前进程的有效组ID

1. 函数介绍

getegid 是一个 Linux 系统调用,用于获取当前进程的有效组 ID(Effective Group ID)。有效组 ID 决定了进程当前对文件和资源的组级访问权限。

在 Unix/Linux 系统中,每个进程都有多个相关的用户和组 ID:

  • 真实 ID (Real ID):标识运行该进程的实际用户/组
  • 有效 ID (Effective ID):决定当前权限的用户/组 ID
  • 保存的设置 ID (Saved Set ID):用于权限切换的备份 ID

getegid 专门用于获取有效组 ID,这是进程当前用于权限检查的组标识符。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

gid_t getegid(void);

3. 功能

返回当前进程的有效组 ID(Effective Group ID)。这是一个只读操作,不会修改任何系统状态。

4. 参数

  • 无参数

5. 返回值

  • 返回当前进程的有效组 ID(gid_t 类型)
  • 不会失败,总是成功返回

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

  • getgid(): 获取真实组 ID(Real Group ID)
  • geteuid(): 获取有效用户 ID(Effective User ID)
  • getuid(): 获取真实用户 ID(Real User ID)
  • setgid(): 设置组 ID
  • setegid(): 设置有效组 ID
  • setregid(): 同时设置真实和有效组 ID
  • setgroups(): 设置补充组列表
  • getgroups(): 获取补充组列表
  • initgroups(): 初始化用户组访问列表

7. 示例代码

示例1:基本使用 – 获取和显示组ID信息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>

void print_group_info(const char *label, gid_t gid) {
    struct group *grp;
    
    printf("%s: %d", label, gid);
    
    // 尝试获取组名
    grp = getgrgid(gid);
    if (grp != NULL) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
}

int main() {
    gid_t real_gid, effective_gid;
    
    printf("=== 进程组 ID 信息 ===\n");
    
    // 获取真实组 ID
    real_gid = getgid();
    print_group_info("真实组 ID", real_gid);
    
    // 获取有效组 ID
    effective_gid = getegid();
    print_group_info("有效组 ID", effective_gid);
    
    // 获取当前用户名
    struct passwd *pwd = getpwuid(getuid());
    if (pwd != NULL) {
        printf("当前用户: %s\n", pwd->pw_name);
    }
    
    // 检查是否为 root 组
    if (effective_gid == 0) {
        printf("注意: 当前进程具有 root 组权限\n");
    }
    
    return 0;
}

示例2:权限检查和组切换演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

int main() {
    gid_t original_egid, current_egid;
    int ret;
    
    printf("=== 组 ID 切换演示 ===\n");
    
    // 保存原始有效组 ID
    original_egid = getegid();
    printf("原始有效组 ID: %d\n", original_egid);
    
    // 尝试切换到不同的组(需要适当权限)
    // 这里使用一些常见的系统组进行演示
    gid_t test_groups[] = {1000, 1001, 1002};  // 假设的用户组
    int num_groups = sizeof(test_groups) / sizeof(test_groups[0]);
    
    for (int i = 0; i < num_groups; i++) {
        printf("\n尝试切换到组 %d:\n", test_groups[i]);
        
        // 尝试设置有效组 ID
        ret = setegid(test_groups[i]);
        if (ret == -1) {
            printf("  切换失败: %s\n", strerror(errno));
            switch (errno) {
                case EPERM:
                    printf("  原因: 权限不足\n");
                    break;
                case EINVAL:
                    printf("  原因: 无效的组 ID\n");
                    break;
                default:
                    break;
            }
        } else {
            current_egid = getegid();
            printf("  切换成功,当前有效组 ID: %d\n", current_egid);
            
            // 切换回原始组 ID
            if (setegid(original_egid) == 0) {
                printf("  已切换回原始组 ID: %d\n", getegid());
            }
        }
    }
    
    printf("\n最终有效组 ID: %d\n", getegid());
    return 0;
}

示例3:补充组信息获取

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>

int main() {
    gid_t effective_gid;
    gid_t *group_list;
    int group_count;
    long max_groups;
    
    printf("=== 完整组信息展示 ===\n");
    
    // 获取有效组 ID
    effective_gid = getegid();
    printf("有效组 ID: %d\n", effective_gid);
    
    // 获取组名
    struct group *grp = getgrgid(effective_gid);
    if (grp != NULL) {
        printf("有效组名: %s\n", grp->gr_name);
    }
    
    // 获取补充组列表大小
    max_groups = sysconf(_SC_NGROUPS_MAX);
    if (max_groups == -1) {
        max_groups = 64;  // 默认值
    }
    
    printf("最大支持组数: %ld\n", max_groups);
    
    // 分配组列表内存
    group_list = malloc(max_groups * sizeof(gid_t));
    if (group_list == NULL) {
        perror("内存分配失败");
        return 1;
    }
    
    // 获取补充组列表
    group_count = getgroups(max_groups, group_list);
    if (group_count == -1) {
        perror("获取补充组列表失败");
        free(group_list);
        return 1;
    }
    
    printf("补充组数量: %d\n", group_count);
    
    if (group_count > 0) {
        printf("补充组列表:\n");
        for (int i = 0; i < group_count; i++) {
            printf("  组 %d: %d", i + 1, group_list[i]);
            
            // 获取组名
            struct group *sup_grp = getgrgid(group_list[i]);
            if (sup_grp != NULL) {
                printf(" (%s)", sup_grp->gr_name);
            }
            printf("\n");
        }
    }
    
    // 检查有效组 ID 是否在补充组列表中
    int found = 0;
    for (int i = 0; i < group_count; i++) {
        if (group_list[i] == effective_gid) {
            found = 1;
            break;
        }
    }
    
    printf("\n有效组 ID %s 在补充组列表中\n", 
           found ? "存在" : "不存在");
    
    free(group_list);
    return 0;
}

示例4:权限相关的实际应用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <string.h>

// 检查当前进程是否属于指定组
int is_member_of_group(gid_t target_gid) {
    gid_t effective_gid = getegid();
    
    // 首先检查有效组 ID
    if (effective_gid == target_gid) {
        return 1;
    }
    
    // 检查补充组
    long max_groups = sysconf(_SC_NGROUPS_MAX);
    if (max_groups == -1) max_groups = 64;
    
    gid_t *groups = malloc(max_groups * sizeof(gid_t));
    if (groups == NULL) return 0;
    
    int ngroups = getgroups(max_groups, groups);
    if (ngroups == -1) {
        free(groups);
        return 0;
    }
    
    for (int i = 0; i < ngroups; i++) {
        if (groups[i] == target_gid) {
            free(groups);
            return 1;
        }
    }
    
    free(groups);
    return 0;
}

// 获取当前用户的主要组信息
void print_user_primary_group() {
    uid_t uid = getuid();
    struct passwd *pwd = getpwuid(uid);
    
    if (pwd != NULL) {
        printf("用户 %s 的主要组: %d", pwd->pw_name, pwd->pw_gid);
        struct group *grp = getgrgid(pwd->pw_gid);
        if (grp != NULL) {
            printf(" (%s)", grp->gr_name);
        }
        printf("\n");
    }
}

int main() {
    printf("=== 权限检查应用示例 ===\n");
    
    // 显示基本信息
    printf("当前用户 ID: %d\n", getuid());
    printf("当前有效组 ID: %d\n", getegid());
    print_user_primary_group();
    
    // 检查是否属于 wheel 组(系统管理员组)
    struct group *wheel_grp = getgrnam("wheel");
    if (wheel_grp != NULL) {
        int is_wheel = is_member_of_group(wheel_grp->gr_gid);
        printf("是否属于 wheel 组: %s\n", is_wheel ? "是" : "否");
    }
    
    // 检查是否属于 sudo 组
    struct group *sudo_grp = getgrnam("sudo");
    if (sudo_grp != NULL) {
        int is_sudo = is_member_of_group(sudo_grp->gr_gid);
        printf("是否属于 sudo 组: %s\n", is_sudo ? "是" : "否");
    }
    
    // 检查是否具有 root 组权限
    int is_root_group = is_member_of_group(0);
    printf("是否具有 root 组权限: %s\n", is_root_group ? "是" : "否");
    
    // 根据组权限显示不同信息
    if (is_root_group) {
        printf("\n提示: 当前进程具有 root 组权限,可以执行特权操作\n");
    } else {
        printf("\n提示: 当前进程权限受限,某些操作可能需要提升权限\n");
    }
    
    return 0;
}

8. 组 ID 类型说明

Unix/Linux 系统中的组 ID 类型:

// 真实组 ID (Real Group ID)
// 标识启动进程的用户的组
gid_t real_gid = getgid();

// 有效组 ID (Effective Group ID)  
// 当前用于权限检查的组 ID
gid_t effective_gid = getegid();

// 保存的设置组 ID (Saved Set Group ID)
// 用于权限切换的备份 ID
// 通过 setregid() 或类似函数设置

9. 常见组 ID 值

// 特殊组 ID
0     // root 组 (超级用户组)
1     // bin 组 (系统二进制文件)
2     // daemon 组 (系统守护进程)
3     // sys 组 (系统文件)
4     // adm 组 (系统日志)
5     // tty 组 (终端设备)
6     // disk 组 (磁盘设备)
10    // wheel 组 (系统管理员,某些发行版)
100+  // 普通用户组

10. 实际应用场景

getegid 在以下场景中非常有用:

场景1:权限检查

int check_file_access_permission(const char *filename) {
    gid_t effective_gid = getegid();
    // 根据有效组 ID 检查文件访问权限
    // ...
    return 0;
}

场景2:安全审计

void audit_process_privileges() {
    gid_t egid = getegid();
    if (egid == 0) {
        syslog(LOG_WARNING, "进程以 root 组权限运行");
    }
}

场景3:组权限相关的功能控制

int can_perform_admin_task() {
    return is_member_of_group(get_admin_group_id());
}

11. 与相关函数的配合使用

#include <unistd.h>
#include <sys/types.h>

// 完整的 ID 管理示例
void demonstrate_id_management() {
    printf("真实用户 ID: %d\n", getuid());
    printf("有效用户 ID: %d\n", geteuid());
    printf("真实组 ID: %d\n", getgid());
    printf("有效组 ID: %d\n", getegid());
    
    // 权限切换示例
    // seteuid(), setegid() 用于临时权限切换
    // setuid(), setgid() 用于永久权限切换
}

总结

getegid 是一个简单但重要的系统调用,用于获取当前进程的有效组 ID。关键要点:

  1. 总是成功: 不会失败,总是返回有效组 ID
  2. 权限检查: 有效组 ID 决定当前的组级权限
  3. 安全相关: 是权限管理和安全检查的基础
  4. 配合使用: 通常与 getgroups() 等函数配合使用
  5. 实际应用: 广泛用于权限验证、安全审计等场景

在编写需要进行权限检查的程序时,getegid 是必不可少的工具函数,它为程序提供了当前权限状态的重要信息。

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

发表在 linux文章 | 留下评论

geteuid系统调用及示例

geteuid 函数详解

1. 函数介绍

geteuid 是 Linux 系统中用于获取进程有效用户 ID(Effective User ID)的系统调用。可以把有效用户 ID 想象成”进程当前正在使用的身份证明”——它决定了进程当前拥有的权限级别。

在 Linux 系统中,每个进程都有两个重要的用户 ID:

  • 真实用户 ID(real UID): 进程实际所属的用户
  • 有效用户 ID(effective UID): 进程当前使用的用户身份(用于权限检查)

这就像是一个人有真实身份和当前扮演的角色。比如一个系统管理员(真实身份)可能暂时切换到普通用户角色(有效身份)来执行某些操作。

geteuid 就是获取进程当前”扮演的角色”的用户 ID。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

uid_t geteuid(void);

3. 功能

geteuid 函数用于获取调用进程的有效用户 ID(effective user ID)。有效用户 ID 用于权限检查,决定了进程可以访问哪些资源和执行哪些操作。

4. 参数

geteuid 函数不需要任何参数。

5. 返回值

  • 成功: 返回调用进程的有效用户 ID(uid_t 类型)
  • 注意: 此函数不会失败,总是成功返回

6. 相关函数

  • getuid: 获取真实用户 ID(real user ID)
  • getgid: 获取真实组 ID(real group ID)
  • getegid: 获取有效组 ID(effective group ID)
  • setuid: 设置用户 ID
  • seteuid: 设置有效用户 ID
  • setreuid: 同时设置真实和有效用户 ID

7. 用户 ID 类型说明

Linux 系统中有几种不同的用户 ID:

用户 ID 类型说明
真实用户 ID (real UID)进程实际所属的用户,由 getuid 返回
有效用户 ID (effective UID)用于权限检查的用户 ID,由 geteuid 返回
保存的设置用户 ID (saved set-user-ID)保存的用户 ID,用于权限切换

8. 示例代码

示例1:基础用法 – 获取进程用户 ID

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

int main() {
    uid_t real_uid, effective_uid;
    struct passwd *pwd_info;
    
    printf("=== 进程用户 ID 信息 ===\n\n");
    
    // 获取真实用户 ID
    real_uid = getuid();
    printf("真实用户 ID (real UID): %d\n", real_uid);
    
    // 获取有效用户 ID
    effective_uid = geteuid();
    printf("有效用户 ID (effective UID): %d\n", effective_uid);
    
    // 获取用户名信息
    pwd_info = getpwuid(real_uid);
    if (pwd_info != NULL) {
        printf("真实用户名: %s\n", pwd_info->pw_name);
        printf("主目录: %s\n", pwd_info->pw_dir);
        printf("登录 shell: %s\n", pwd_info->pw_shell);
    } else {
        printf("无法获取真实用户信息\n");
    }
    
    // 获取有效用户信息
    pwd_info = getpwuid(effective_uid);
    if (pwd_info != NULL) {
        printf("有效用户名: %s\n", pwd_info->pw_name);
    } else {
        printf("无法获取有效用户信息\n");
    }
    
    // 判断权限级别
    printf("\n权限状态:\n");
    if (effective_uid == 0) {
        printf("✓ 进程具有 root 权限\n");
    } else if (real_uid == 0 && effective_uid != 0) {
        printf("⚠ 进程已放弃 root 权限\n");
    } else {
        printf("✗ 进程为普通用户权限\n");
    }
    
    if (real_uid == effective_uid) {
        printf("✓ 真实 UID 和有效 UID 相同\n");
    } else {
        printf("⚠ 真实 UID 和有效 UID 不同\n");
    }
    
    return 0;
}

示例2:权限检查和安全验证

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>

// 权限检查函数
void check_permissions() {
    uid_t real_uid = getuid();
    uid_t effective_uid = geteuid();
    gid_t real_gid = getgid();
    gid_t effective_gid = getegid();
    
    printf("=== 权限检查 ===\n");
    
    // 用户权限检查
    printf("用户权限:\n");
    printf("  真实 UID: %d", real_uid);
    struct passwd *pwd = getpwuid(real_uid);
    if (pwd) printf(" (%s)", pwd->pw_name);
    printf("\n");
    
    printf("  有效 UID: %d", effective_uid);
    pwd = getpwuid(effective_uid);
    if (pwd) printf(" (%s)", pwd->pw_name);
    if (effective_uid == 0) {
        printf(" [ROOT 权限!]");
    }
    printf("\n");
    
    // 组权限检查
    printf("组权限:\n");
    printf("  真实 GID: %d\n", real_gid);
    printf("  有效 GID: %d", effective_gid);
    if (effective_gid == 0) {
        printf(" [ROOT 组权限!]");
    }
    printf("\n");
    
    // 安全状态分析
    printf("\n安全状态分析:\n");
    if (effective_uid == 0) {
        printf("  ⚠ 危险: 进程以 root 权限运行\n");
        printf("  建议: 在完成特权操作后立即降低权限\n");
    } else if (real_uid == 0 && effective_uid != 0) {
        printf("  ✓ 良好: 已安全放弃 root 权限\n");
    } else {
        printf("  ✓ 正常: 以普通用户权限运行\n");
    }
}

// 安全操作示例
void secure_operation_example() {
    uid_t real_uid = getuid();
    uid_t effective_uid = geteuid();
    
    printf("\n=== 安全操作示例 ===\n");
    
    if (effective_uid == 0) {
        printf("检测到 root 权限,执行特权操作...\n");
        
        // 模拟特权操作
        printf("  执行系统管理任务...\n");
        printf("  修改系统配置...\n");
        printf("  访问受保护资源...\n");
        
        // 如果真实 UID 不是 root,可以安全地放弃权限
        if (real_uid != 0) {
            printf("  准备放弃 root 权限...\n");
            // 注意:实际代码中需要使用 setuid() 系列函数
        }
    } else {
        printf("以普通用户权限执行操作...\n");
        printf("  执行用户级任务...\n");
        printf("  访问用户文件...\n");
    }
}

int main() {
    printf("=== 进程身份和权限管理系统 ===\n\n");
    
    // 显示基本进程信息
    printf("进程基本信息:\n");
    printf("  进程 ID: %d\n", getpid());
    printf("  父进程 ID: %d\n", getppid());
    printf("  真实 UID: %d\n", getuid());
    printf("  有效 UID: %d\n", geteuid());
    printf("  真实 GID: %d\n", getgid());
    printf("  有效 GID: %d\n\n", getegid());
    
    // 权限检查
    check_permissions();
    
    // 安全操作示例
    secure_operation_example();
    
    printf("\n=== 安全建议 ===\n");
    printf("1. 遵循最小权限原则\n");
    printf("2. 及时放弃不需要的特权\n");
    printf("3. 定期检查有效 UID 和真实 UID\n");
    printf("4. 避免长时间以 root 权限运行\n");
    
    return 0;
}

示例3:完整的身份验证和权限管理系统

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>

// 用户信息结构体
struct user_info {
    uid_t uid;
    gid_t gid;
    char username[256];
    char groupname[256];
    char homedir[256];
    int is_root;
    int has_privileges;
};

// 获取用户信息
int get_user_information(struct user_info *info) {
    struct passwd *pwd;
    struct group *grp;
    
    // 获取 UID 和 GID
    info->uid = geteuid();
    info->gid = getegid();
    
    // 获取用户信息
    pwd = getpwuid(info->uid);
    if (pwd) {
        strncpy(info->username, pwd->pw_name, sizeof(info->username) - 1);
        info->username[sizeof(info->username) - 1] = '\0';
        strncpy(info->homedir, pwd->pw_dir, sizeof(info->homedir) - 1);
        info->homedir[sizeof(info->homedir) - 1] = '\0';
    } else {
        snprintf(info->username, sizeof(info->username), "uid_%d", info->uid);
        strcpy(info->homedir, "/tmp");
    }
    
    // 获取组信息
    grp = getgrgid(info->gid);
    if (grp) {
        strncpy(info->groupname, grp->gr_name, sizeof(info->groupname) - 1);
        info->groupname[sizeof(info->groupname) - 1] = '\0';
    } else {
        snprintf(info->groupname, sizeof(info->groupname), "gid_%d", info->gid);
    }
    
    // 设置权限标志
    info->is_root = (info->uid == 0);
    info->has_privileges = (info->uid == 0 || info->gid == 0);
    
    return 0;
}

// 显示用户信息
void display_user_info(const struct user_info *info) {
    printf("=== 当前用户信息 ===\n");
    printf("用户名: %s\n", info->username);
    printf("用户 ID: %d\n", info->uid);
    printf("组名: %s\n", info->groupname);
    printf("组 ID: %d\n", info->gid);
    printf("主目录: %s\n", info->homedir);
    printf("权限状态: %s\n", 
           info->is_root ? "ROOT 权限" : 
           info->has_privileges ? "特权用户" : "普通用户");
}

// 权限验证函数
int check_operation_permission(const char *operation) {
    struct user_info info;
    get_user_information(&info);
    
    printf("权限检查: %s\n", operation);
    
    // 根据操作类型检查权限
    if (strcmp(operation, "read_system_files") == 0) {
        // 读取系统文件通常需要 root 权限
        return info.is_root ? 1 : 0;
    } else if (strcmp(operation, "write_user_files") == 0) {
        // 写入用户文件通常只需要有效权限
        return 1;  // 假设总是允许
    } else if (strcmp(operation, "network_admin") == 0) {
        // 网络管理需要 root 权限
        return info.is_root ? 1 : 0;
    } else {
        // 默认情况下,普通操作允许
        return 1;
    }
}

// 模拟受保护操作
void perform_protected_operation(const char *operation) {
    printf("\n--- 尝试执行操作: %s ---\n", operation);
    
    if (check_operation_permission(operation)) {
        printf("✓ 权限检查通过\n");
        printf("  执行操作: %s\n", operation);
        
        // 模拟操作执行
        if (strcmp(operation, "read_system_files") == 0) {
            printf("  读取 /etc/shadow...\n");
            printf("  读取系统配置文件...\n");
        } else if (strcmp(operation, "network_admin") == 0) {
            printf("  配置网络接口...\n");
            printf("  修改防火墙规则...\n");
        }
        
        printf("  操作完成\n");
    } else {
        printf("✗ 权限不足,拒绝执行操作\n");
        printf("  需要 ROOT 权限才能执行此操作\n");
    }
}

// 显示安全建议
void show_security_advice(const struct user_info *info) {
    printf("\n=== 安全建议 ===\n");
    
    if (info->is_root) {
        printf("⚠ 警告: 当前以 ROOT 权限运行\n");
        printf("  建议:\n");
        printf("  1. 完成必要操作后立即切换到普通用户\n");
        printf("  2. 避免在 root 权限下运行不必要的程序\n");
        printf("  3. 定期审计 root 权限的使用情况\n");
    } else if (info->has_privileges) {
        printf("⚠ 注意: 具有特权权限\n");
        printf("  建议:\n");
        printf("  1. 谨慎使用特权功能\n");
        printf("  2. 遵循最小权限原则\n");
    } else {
        printf("✓ 信息: 以普通用户权限运行\n");
        printf("  建议:\n");
        printf("  1. 如需特权操作,请使用 sudo\n");
        printf("  2. 保持系统和软件更新\n");
    }
}

int main() {
    struct user_info current_user;
    
    printf("=== 高级身份验证和权限管理系统 ===\n\n");
    
    // 获取并显示用户信息
    get_user_information(&current_user);
    display_user_info(&current_user);
    
    // 执行各种受保护操作
    perform_protected_operation("read_system_files");
    perform_protected_operation("write_user_files");
    perform_protected_operation("network_admin");
    
    // 显示安全建议
    show_security_advice(&current_user);
    
    printf("\n=== 系统信息 ===\n");
    printf("进程 ID: %d\n", getpid());
    printf("父进程 ID: %d\n", getppid());
    printf("真实 UID: %d\n", getuid());
    printf("有效 UID: %d\n", geteuid());
    printf("真实 GID: %d\n", getgid());
    printf("有效 GID: %d\n", getegid());
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o geteuid_example1 example1.c
gcc -o geteuid_example2 example2.c
gcc -o geteuid_example3 example3.c

# 运行示例
./geteuid_example1
./geteuid_example2
./geteuid_example3

# 以 root 权限运行(需要 sudo)
sudo ./geteuid_example1
sudo ./geteuid_example2
sudo ./geteuid_example3

命令行查看用户信息

# 查看当前用户信息
id

# 查看特定用户信息
id username

# 查看当前进程信息
ps -eo pid,ppid,uid,euid,gid,egid,comm

# 查看用户数据库
cat /etc/passwd
cat /etc/group

重要注意事项

  1. 不会失败geteuid 总是成功返回,不需要错误检查
  2. 实时性: 返回的是调用时刻的有效用户 ID
  3. 权限检查: 有效 UID 用于所有权限检查
  4. 安全关键: 在安全敏感程序中要特别注意有效 UID
  5. 继承性: 子进程继承父进程的有效 UID

实际应用场景

  1. 权限验证: 在执行特权操作前检查有效 UID
  2. 安全审计: 记录操作的有效用户身份
  3. 访问控制: 根据有效 UID 控制资源访问
  4. 特权管理: 管理程序的特权级别切换
  5. 日志记录: 在日志中记录有效的用户身份

常见用户 ID 含义

用户 ID用户名说明
0root超级用户
1daemon系统守护进程用户
2bin系统二进制文件用户
5sync用于同步操作的用户
37www-dataWeb 服务器用户
65534nobody无特权用户

与相关函数的配合使用

// 完整的身份信息获取
uid_t real_uid = getuid();     // 真实用户 ID
uid_t effective_uid = geteuid(); // 有效用户 ID
gid_t real_gid = getgid();     // 真实组 ID
gid_t effective_gid = getegid(); // 有效组 ID

// 权限切换示例
if (geteuid() == 0) {
    // 当前有 root 权限,执行特权操作
    // ...
    // 完成后可以放弃权限
    // setuid(getuid());  // 切换到真实 UID
}

安全最佳实践

  1. 最小权限原则: 只在必要时获取特权
  2. 及时放弃权限: 完成特权操作后立即降低权限
  3. 验证身份: 在关键操作前验证有效 UID
  4. 避免持久化: 不要长时间保持高权限运行
  5. 日志记录: 记录所有特权操作的身份信息

这些示例展示了 geteuid 函数的各种使用方法,从基本的用户 ID 获取到完整的权限管理系统,帮助你全面理解 Linux 系统中的用户权限管理机制。

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

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

发表在 linux文章 | 留下评论

getgid系统调用及示例

getgid 函数详解

getgid 是 Linux 系统中获取进程真实组 ID 的系统调用函数,用于确定进程的组权限级别。该函数无参数,总是成功返回调用进程的真实组 ID(gid_t 类型)。组 ID 类似于”部门编号”,决定进程在系统中的组权限。系统还提供相关函数如 getegid(获取有效组 ID)、getuid(获取用户 ID)等。示例代码演示了如何获取组 ID 信息、查询完整用户/组信息以及检查权限状态。真实组 ID 和有效组 ID 通常相同,但在特殊权限操作时可能不同。

1. 函数介绍

getgid 是 Linux 系统中用于获取进程真实组 ID(Group ID)的系统调用。可以把组 ID 想象成”用户的身份证明”——每个进程都属于一个或多个用户组,组 ID 决定了进程在系统中的组权限级别。

在 Linux 系统中,权限管理不仅基于用户,还基于用户组。就像一个公司员工不仅有个人身份,还属于不同的部门一样,每个用户都属于一个或多个组,而 getgid 就是获取进程所属的”主部门”编号。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

gid_t getgid(void);

3. 功能

getgid 函数用于获取调用进程的真实组 ID(real group ID)。真实组 ID 是进程启动时从父进程继承的组 ID,通常与有效组 ID(effective group ID)相同,但在某些特殊情况下可能不同。

4. 参数

getgid 函数不需要任何参数。

5. 返回值

  • 成功: 返回调用进程的真实组 ID(gid_t 类型)
  • 注意: 此函数不会失败,总是成功返回

6. 相关函数

  • getegid: 获取有效组 ID(effective group ID)
  • getuid: 获取真实用户 ID(real user ID)
  • geteuid: 获取有效用户 ID(effective user ID)
  • getgroups: 获取进程所属的所有组 ID
  • setgid: 设置组 ID
  • setgroups: 设置进程的附加组 ID 列表
  • initgroups: 初始化用户组访问列表

7. 组 ID 类型说明

Linux 系统中有几种不同的组 ID:

组 ID 类型说明
真实组 ID (real GID)进程实际所属的组,由 getgid 返回
有效组 ID (effective GID)用于权限检查的组 ID,由 getegid 返回
保存的设置组 ID (saved set-group-ID)保存的组 ID,用于权限切换
附加组 ID (supplementary GIDs)进程所属的额外组列表

8. 示例代码

示例1:基础用法 – 获取进程组 ID

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>

int main() {
    gid_t real_gid, effective_gid;
    struct group *grp_info;
    
    printf("=== 进程组 ID 信息 ===\n\n");
    
    // 获取真实组 ID
    real_gid = getgid();
    printf("真实组 ID (real GID): %d\n", real_gid);
    
    // 获取有效组 ID
    effective_gid = getegid();
    printf("有效组 ID (effective GID): %d\n", effective_gid);
    
    // 获取组名信息
    grp_info = getgrgid(real_gid);
    if (grp_info != NULL) {
        printf("组名: %s\n", grp_info->gr_name);
        printf("组密码: %s\n", grp_info->gr_passwd ? grp_info->gr_passwd : "(无)");
        printf("组成员: ");
        if (grp_info->gr_mem && grp_info->gr_mem[0]) {
            for (int i = 0; grp_info->gr_mem[i]; i++) {
                printf("%s ", grp_info->gr_mem[i]);
            }
        } else {
            printf("(无成员列表)");
        }
        printf("\n");
    } else {
        printf("无法获取组信息\n");
    }
    
    // 比较真实组 ID 和有效组 ID
    if (real_gid == effective_gid) {
        printf("\n真实组 ID 和有效组 ID 相同\n");
    } else {
        printf("\n真实组 ID 和有效组 ID 不同\n");
    }
    
    return 0;
}

示例2:完整的用户和组信息查询

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>

void print_user_info(uid_t uid) {
    struct passwd *pwd_info;
    
    pwd_info = getpwuid(uid);
    if (pwd_info != NULL) {
        printf("  用户名: %s\n", pwd_info->pw_name);
        printf("  用户 ID: %d\n", pwd_info->pw_uid);
        printf("  组 ID: %d\n", pwd_info->pw_gid);
        printf("  主目录: %s\n", pwd_info->pw_dir);
        printf("  登录 shell: %s\n", pwd_info->pw_shell);
    } else {
        printf("  无法获取用户信息\n");
    }
}

void print_group_info(gid_t gid) {
    struct group *grp_info;
    
    grp_info = getgrgid(gid);
    if (grp_info != NULL) {
        printf("  组名: %s\n", grp_info->gr_name);
        printf("  组 ID: %d\n", grp_info->gr_gid);
        printf("  组成员: ");
        if (grp_info->gr_mem && grp_info->gr_mem[0]) {
            for (int i = 0; grp_info->gr_mem[i]; i++) {
                printf("%s ", grp_info->gr_mem[i]);
            }
        } else {
            printf("(无成员列表)");
        }
        printf("\n");
    } else {
        printf("  无法获取组信息\n");
    }
}

int main() {
    uid_t real_uid, effective_uid;
    gid_t real_gid, effective_gid;
    
    printf("=== 完整的用户和组信息 ===\n\n");
    
    // 获取用户 ID 信息
    printf("用户信息:\n");
    real_uid = getuid();
    effective_uid = geteuid();
    printf("  真实用户 ID: %d\n", real_uid);
    printf("  有效用户 ID: %d\n", effective_uid);
    print_user_info(real_uid);
    
    printf("\n");
    
    // 获取组 ID 信息
    printf("组信息:\n");
    real_gid = getgid();
    effective_gid = getegid();
    printf("  真实组 ID: %d\n", real_gid);
    printf("  有效组 ID: %d\n", effective_gid);
    print_group_info(real_gid);
    
    // 检查权限状态
    printf("\n权限状态分析:\n");
    if (real_uid == 0 || effective_uid == 0) {
        printf("  ✓ 进程具有 root 权限\n");
    } else {
        printf("  ✗ 进程为普通用户权限\n");
    }
    
    if (real_gid == effective_gid) {
        printf("  ✓ 组权限未被修改\n");
    } else {
        printf("  ⚠ 组权限可能被修改过\n");
    }
    
    return 0;
}

示例3:多组支持和权限检查工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>

// 获取并显示所有组信息
void show_all_groups() {
    gid_t *groups;
    int ngroups;
    long ngroups_max;
    
    printf("=== 所有组信息 ===\n");
    
    // 获取最大组数
    ngroups_max = sysconf(_SC_NGROUPS_MAX);
    if (ngroups_max == -1) {
        ngroups_max = 1024;  // 默认值
    }
    
    // 分配内存
    groups = malloc(ngroups_max * sizeof(gid_t));
    if (groups == NULL) {
        perror("malloc");
        return;
    }
    
    // 获取组列表
    ngroups = getgroups(ngroups_max, groups);
    if (ngroups == -1) {
        perror("getgroups");
        free(groups);
        return;
    }
    
    printf("进程所属组数量: %d\n", ngroups);
    printf("组 ID 列表:\n");
    
    for (int i = 0; i < ngroups; i++) {
        struct group *grp_info = getgrgid(groups[i]);
        printf("  [%d] %d", i + 1, groups[i]);
        if (grp_info != NULL) {
            printf(" (%s)", grp_info->gr_name);
        }
        printf("\n");
    }
    
    // 显示主组
    gid_t primary_gid = getgid();
    struct group *primary_grp = getgrgid(primary_gid);
    printf("\n主组 (getgid): %d", primary_gid);
    if (primary_grp != NULL) {
        printf(" (%s)", primary_grp->gr_name);
    }
    printf("\n");
    
    free(groups);
}

// 检查是否属于特定组
int is_member_of_group(gid_t target_gid) {
    gid_t *groups;
    int ngroups;
    long ngroups_max;
    int result = 0;
    
    // 获取最大组数
    ngroups_max = sysconf(_SC_NGROUPS_MAX);
    if (ngroups_max == -1) {
        ngroups_max = 1024;
    }
    
    // 分配内存
    groups = malloc(ngroups_max * sizeof(gid_t));
    if (groups == NULL) {
        return -1;
    }
    
    // 获取组列表
    ngroups = getgroups(ngroups_max, groups);
    if (ngroups == -1) {
        free(groups);
        return -1;
    }
    
    // 检查是否包含目标组
    for (int i = 0; i < ngroups; i++) {
        if (groups[i] == target_gid) {
            result = 1;
            break;
        }
    }
    
    // 也要检查主组
    if (!result && getgid() == target_gid) {
        result = 1;
    }
    
    free(groups);
    return result;
}

// 特权检查函数
void check_privileges() {
    uid_t uid = getuid();
    uid_t euid = geteuid();
    gid_t gid = getgid();
    gid_t egid = getegid();
    
    printf("=== 权限检查 ===\n");
    
    printf("用户权限:\n");
    printf("  真实 UID: %d\n", uid);
    printf("  有效 UID: %d", euid);
    if (euid == 0) {
        printf(" (root 权限!)\n");
    } else {
        printf(" (普通用户)\n");
    }
    
    printf("组权限:\n");
    printf("  真实 GID: %d\n", gid);
    printf("  有效 GID: %d", egid);
    if (egid == 0) {
        printf(" (root 组权限!)\n");
    } else {
        printf(" (普通组)\n");
    }
    
    // 检查是否属于 wheel 或 sudo 组(管理员组)
    struct group *admin_group;
    
    admin_group = getgrnam("wheel");
    if (admin_group && is_member_of_group(admin_group->gr_gid)) {
        printf("  ✓ 属于 wheel 组 (管理员组)\n");
    }
    
    admin_group = getgrnam("sudo");
    if (admin_group && is_member_of_group(admin_group->gr_gid)) {
        printf("  ✓ 属于 sudo 组 (管理员组)\n");
    }
}

int main() {
    printf("=== 进程身份和权限信息工具 ===\n\n");
    
    // 显示基本身份信息
    printf("基本身份信息:\n");
    printf("  进程 ID: %d\n", getpid());
    printf("  父进程 ID: %d\n", getppid());
    printf("  真实用户 ID: %d\n", getuid());
    printf("  真实组 ID: %d\n", getgid());
    printf("  有效用户 ID: %d\n", geteuid());
    printf("  有效组 ID: %d\n\n", getegid());
    
    // 权限检查
    check_privileges();
    printf("\n");
    
    // 显示所有组信息
    show_all_groups();
    
    printf("\n=== 使用说明 ===\n");
    printf("此工具显示当前进程的完整身份和权限信息。\n");
    printf("包括用户 ID、组 ID、所属的所有组等信息。\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o getgid_example1 example1.c
gcc -o getgid_example2 example2.c
gcc -o getgid_example3 example3.c

# 运行示例
./getgid_example1
./getgid_example2
./getgid_example3

命令行查看组信息

# 查看当前用户信息
id

# 查看特定用户信息
id username

# 查看组信息
groups
cat /etc/group

# 查看当前进程信息
ps -eo pid,ppid,uid,gid,comm

重要注意事项

  1. 不会失败getgid 总是成功返回,不需要错误检查
  2. 实时性: 返回的是调用时刻的组 ID
  3. 继承性: 子进程继承父进程的组 ID
  4. 权限相关: 组 ID 直接影响文件访问权限
  5. 安全考虑: 在安全敏感程序中要验证组 ID

实际应用场景

  1. 权限检查: 在执行特权操作前检查组权限
  2. 日志记录: 记录操作的用户组信息
  3. 访问控制: 根据组 ID 控制资源访问
  4. 安全审计: 审计程序的执行环境
  5. 多用户应用: 根据组 ID 提供不同功能

常见组 ID 含义

组 ID组名说明
0root超级用户组
1bin系统二进制文件组
2daemon系统守护进程组
5tty终端设备组
10uucpUnix to Unix Copy 组

与相关函数的配合使用

// 完整的身份信息获取
uid_t uid = getuid();    // 真实用户 ID
gid_t gid = getgid();    // 真实组 ID
uid_t euid = geteuid();  // 有效用户 ID
gid_t egid = getegid();  // 有效组 ID

// 获取所有组
int ngroups = getgroups(0, NULL);  // 先获取数量
gid_t *groups = malloc(ngroups * sizeof(gid_t));
getgroups(ngroups, groups);        // 再获取组列表

这些示例展示了 getgid 函数的各种使用方法,从基本的组 ID 获取到完整的权限检查工具,帮助你全面理解 Linux 系统中的用户组管理机制。

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

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

发表在 linux文章 | 留下评论

getgroups系统调用及示例

getgroups – 获取进程的补充组列表

getgroups 是 Linux 系统调用,用于获取当前进程的补充组 ID 列表。该函数需要传入缓冲区大小和数组指针,返回补充组数量或填充的组 ID 数量。主要功能包括:查询进程所属的补充组(除主组外的其他组),支持先获取组数量再分配内存填充,并可与getgid()获取的主组 ID 对比。典型用法是两次调用:第一次获取数量,第二次填充数组。错误处理需注意缓冲区不足(EINVAL)和无效指针(EFAULT)等情况。相关函数包括setgroups()initgroups()等组管理函数。

1. 函数介绍

getgroups 是一个 Linux 系统调用,用于获取当前进程所属的补充组(supplementary groups)列表。除了进程的主要组 ID(由 getgid() 返回)之外,进程还可以属于多个补充组,这些组决定了进程对文件和资源的额外访问权限。

补充组机制是 Unix/Linux 系统中实现灵活访问控制的重要组成部分,允许用户同时属于多个组以获得相应的权限。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

int getgroups(int size, gid_t list[]);

3. 功能

获取当前进程所属的补充组 ID 列表。补充组是除了主要组之外,进程还属于的其他组。

4. 参数

  • int size: 指定 list 数组的大小(元素个数)
    • 如果为 0:函数返回补充组的数量,不填充 list 数组
    • 如果大于 0:将补充组 ID 填充到 list 数组中
  • gid_t list[]: 指向存储组 ID 的数组
    • 如果 size 为 0:可以为 NULL
    • 如果 size 大于 0:必须指向有效的数组

5. 返回值

  • 成功时:
    • 如果 size 为 0:返回补充组的数量
    • 如果 size 大于 0:返回实际填充到数组中的组 ID 数量
  • 失败时返回 -1,并设置 errno

6. 常见 errno 错误码

  • EINVALsize 参数小于补充组的实际数量(缓冲区不足)
  • EFAULTlist 指针指向无效内存地址
  • EPERM: 在某些安全限制下可能返回(较少见)

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

  • setgroups(): 设置进程的补充组列表
  • initgroups(): 根据用户信息初始化补充组列表
  • getgid(): 获取进程的真实组 ID
  • getegid(): 获取进程的有效组 ID
  • getuid()geteuid(): 获取用户 ID 相关函数
  • setgid()setegid(): 设置组 ID
  • getgrouplist(): 获取用户的所有组(包括主要组)
  • /etc/group: 系统组信息文件
  • /etc/passwd: 用户主要组信息文件

8. 示例代码

示例1:基本使用 – 获取补充组列表

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

int main() {
    int group_count;
    gid_t *group_list;
    long max_groups;
    
    printf("=== 获取进程补充组列表 ===\n");
    
    // 首先获取补充组数量
    group_count = getgroups(0, NULL);
    if (group_count == -1) {
        perror("获取补充组数量失败");
        exit(EXIT_FAILURE);
    }
    
    printf("补充组数量: %d\n", group_count);
    
    if (group_count == 0) {
        printf("当前进程没有补充组\n");
        return 0;
    }
    
    // 获取系统支持的最大组数
    max_groups = sysconf(_SC_NGROUPS_MAX);
    if (max_groups == -1) {
        max_groups = 65536;  // 设置一个较大的默认值
    }
    
    printf("系统最大支持组数: %ld\n", max_groups);
    
    // 确保不会超出系统限制
    if (group_count > max_groups) {
        printf("警告: 补充组数量超出系统限制\n");
        group_count = max_groups;
    }
    
    // 分配内存存储组列表
    group_list = malloc(group_count * sizeof(gid_t));
    if (group_list == NULL) {
        perror("内存分配失败");
        exit(EXIT_FAILURE);
    }
    
    // 获取补充组列表
    int result = getgroups(group_count, group_list);
    if (result == -1) {
        perror("获取补充组列表失败");
        free(group_list);
        exit(EXIT_FAILURE);
    }
    
    printf("成功获取 %d 个补充组:\n", result);
    printf("%-8s %-10s %s\n", "序号", "组ID", "组名");
    printf("%-8s %-10s %s\n", "----", "----", "----");
    
    // 显示每个组的信息
    for (int i = 0; i < result; i++) {
        printf("%-8d %-10d ", i + 1, group_list[i]);
        
        // 尝试获取组名
        struct group *grp = getgrgid(group_list[i]);
        if (grp != NULL) {
            printf("%s", grp->gr_name);
        } else {
            printf("(未知)");
        }
        printf("\n");
    }
    
    // 显示主要组信息进行对比
    gid_t primary_gid = getgid();
    printf("\n主要组信息:\n");
    printf("  组ID: %d\n", primary_gid);
    
    struct group *primary_grp = getgrgid(primary_gid);
    if (primary_grp != NULL) {
        printf("  组名: %s\n", primary_grp->gr_name);
    }
    
    // 检查主要组是否在补充组列表中
    int found_in_supplementary = 0;
    for (int i = 0; i < result; i++) {
        if (group_list[i] == primary_gid) {
            found_in_supplementary = 1;
            break;
        }
    }
    
    printf("主要组是否在补充组中: %s\n", 
           found_in_supplementary ? "是" : "否");
    
    free(group_list);
    return 0;
}

示例2:错误处理和边界情况

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

void demonstrate_error_handling() {
    int result;
    gid_t small_buffer[2];  // 故意使用小缓冲区
    
    printf("=== 错误处理演示 ===\n");
    
    // 获取实际补充组数量
    int actual_count = getgroups(0, NULL);
    if (actual_count == -1) {
        perror("获取补充组数量失败");
        return;
    }
    
    printf("实际补充组数量: %d\n", actual_count);
    
    if (actual_count > 0) {
        // 使用过小的缓冲区,应该返回错误
        printf("使用过小缓冲区测试 (大小: 2):\n");
        result = getgroups(2, small_buffer);
        
        if (result == -1) {
            if (errno == EINVAL) {
                printf("  错误处理正确: 缓冲区不足 (EINVAL)\n");
            } else {
                printf("  其他错误: %s\n", strerror(errno));
            }
        } else {
            printf("  意外成功,返回数量: %d\n", result);
        }
        
        // 使用 NULL 指针但 size > 0
        printf("使用 NULL 指针测试:\n");
        result = getgroups(10, NULL);
        if (result == -1) {
            if (errno == EFAULT) {
                printf("  错误处理正确: 无效指针 (EFAULT)\n");
            } else {
                printf("  其他错误: %s\n", strerror(errno));
            }
        }
    }
}

void demonstrate_empty_groups() {
    printf("\n=== 空组列表演示 ===\n");
    
    // 获取补充组数量
    int count = getgroups(0, NULL);
    if (count == -1) {
        perror("获取组数量失败");
        return;
    }
    
    printf("当前进程补充组数量: %d\n", count);
    
    if (count == 0) {
        printf("进程没有补充组权限\n");
        printf("这可能表示:\n");
        printf("  1. 进程以最小权限运行\n");
        printf("  2. 用户不属于任何补充组\n");
        printf("  3. 在容器或受限环境中运行\n");
    }
}

int main() {
    demonstrate_error_handling();
    demonstrate_empty_groups();
    return 0;
}

示例3:完整的组权限分析工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <string.h>

typedef struct {
    gid_t gid;
    char group_name[256];
    int is_primary;
    int is_effective;
    int is_supplementary;
} group_info_t;

int compare_gids(const void *a, const void *b) {
    gid_t gid_a = ((group_info_t*)a)->gid;
    gid_t gid_b = ((group_info_t*)b)->gid;
    return (gid_a > gid_b) - (gid_a < gid_b);
}

void analyze_process_groups() {
    gid_t primary_gid, effective_gid;
    int sup_count;
    gid_t *sup_groups = NULL;
    group_info_t *all_groups = NULL;
    int total_groups = 0;
    
    printf("=== 进程组权限完整分析 ===\n");
    
    // 获取基本信息
    primary_gid = getgid();
    effective_gid = getegid();
    
    printf("进程基本信息:\n");
    printf("  真实用户 ID: %d\n", getuid());
    printf("  有效用户 ID: %d\n", geteuid());
    printf("  真实组 ID: %d\n", primary_gid);
    printf("  有效组 ID: %d\n", effective_gid);
    
    // 获取用户信息
    struct passwd *pwd = getpwuid(getuid());
    if (pwd != NULL) {
        printf("  用户名: %s\n", pwd->pw_name);
    }
    
    // 获取补充组
    sup_count = getgroups(0, NULL);
    if (sup_count > 0) {
        sup_groups = malloc(sup_count * sizeof(gid_t));
        if (sup_groups == NULL) {
            perror("内存分配失败");
            return;
        }
        
        if (getgroups(sup_count, sup_groups) == -1) {
            perror("获取补充组失败");
            free(sup_groups);
            return;
        }
    }
    
    // 创建完整的组信息列表
    total_groups = 2 + sup_count;  // primary + effective + supplementary
    all_groups = malloc(total_groups * sizeof(group_info_t));
    if (all_groups == NULL) {
        perror("内存分配失败");
        if (sup_groups) free(sup_groups);
        return;
    }
    
    int index = 0;
    
    // 添加主要组
    all_groups[index].gid = primary_gid;
    all_groups[index].is_primary = 1;
    all_groups[index].is_effective = (primary_gid == effective_gid);
    all_groups[index].is_supplementary = 0;
    struct group *grp = getgrgid(primary_gid);
    if (grp != NULL) {
        strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
    } else {
        snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", primary_gid);
    }
    index++;
    
    // 添加有效组(如果不同于主要组)
    if (effective_gid != primary_gid) {
        all_groups[index].gid = effective_gid;
        all_groups[index].is_primary = 0;
        all_groups[index].is_effective = 1;
        all_groups[index].is_supplementary = 0;
        struct group *grp = getgrgid(effective_gid);
        if (grp != NULL) {
            strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
        } else {
            snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", effective_gid);
        }
        index++;
    }
    
    // 添加补充组
    for (int i = 0; i < sup_count; i++) {
        // 检查是否已存在
        int exists = 0;
        for (int j = 0; j < index; j++) {
            if (all_groups[j].gid == sup_groups[i]) {
                all_groups[j].is_supplementary = 1;
                exists = 1;
                break;
            }
        }
        
        if (!exists) {
            all_groups[index].gid = sup_groups[i];
            all_groups[index].is_primary = 0;
            all_groups[index].is_effective = (sup_groups[i] == effective_gid);
            all_groups[index].is_supplementary = 1;
            struct group *grp = getgrgid(sup_groups[i]);
            if (grp != NULL) {
                strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
            } else {
                snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", sup_groups[i]);
            }
            index++;
        }
    }
    
    total_groups = index;
    
    // 按组 ID 排序
    qsort(all_groups, total_groups, sizeof(group_info_t), compare_gids);
    
    // 显示结果
    printf("\n完整的组权限信息:\n");
    printf("%-8s %-10s %-12s %-12s %-15s %s\n", 
           "序号", "组ID", "主要组", "有效组", "补充组", "组名");
    printf("%-8s %-10s %-12s %-12s %-15s %s\n", 
           "----", "----", "----", "----", "----", "----");
    
    for (int i = 0; i < total_groups; i++) {
        printf("%-8d %-10d %-12s %-12s %-15s %s\n",
               i + 1,
               all_groups[i].gid,
               all_groups[i].is_primary ? "是" : "否",
               all_groups[i].is_effective ? "是" : "否",
               all_groups[i].is_supplementary ? "是" : "否",
               all_groups[i].group_name);
    }
    
    // 统计信息
    printf("\n统计信息:\n");
    printf("  总组数: %d\n", total_groups);
    
    int primary_count = 0, effective_count = 0, supplementary_count = 0;
    for (int i = 0; i < total_groups; i++) {
        if (all_groups[i].is_primary) primary_count++;
        if (all_groups[i].is_effective) effective_count++;
        if (all_groups[i].is_supplementary) supplementary_count++;
    }
    
    printf("  主要组: %d\n", primary_count);
    printf("  有效组: %d\n", effective_count);
    printf("  补充组: %d\n", supplementary_count);
    
    // 特殊权限检查
    printf("\n特殊权限检查:\n");
    int has_root_group = 0;
    int has_admin_group = 0;
    
    for (int i = 0; i < total_groups; i++) {
        if (all_groups[i].gid == 0) {
            has_root_group = 1;
        }
        // 检查常见的管理员组
        if (strcmp(all_groups[i].group_name, "wheel") == 0 ||
            strcmp(all_groups[i].group_name, "sudo") == 0 ||
            strcmp(all_groups[i].group_name, "adm") == 0) {
            has_admin_group = 1;
        }
    }
    
    printf("  Root 组权限: %s\n", has_root_group ? "是" : "否");
    printf("  管理员组权限: %s\n", has_admin_group ? "是" : "否");
    
    // 清理内存
    if (sup_groups) free(sup_groups);
    if (all_groups) free(all_groups);
}

int main() {
    analyze_process_groups();
    return 0;
}

示例4:组权限验证和安全检查

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>

// 检查进程是否属于指定组
int is_process_member_of_group(gid_t target_gid) {
    // 首先检查主要组和有效组
    if (getgid() == target_gid || getegid() == target_gid) {
        return 1;
    }
    
    // 检查补充组
    int sup_count = getgroups(0, NULL);
    if (sup_count <= 0) {
        return 0;
    }
    
    gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
    if (sup_groups == NULL) {
        return 0;
    }
    
    if (getgroups(sup_count, sup_groups) == -1) {
        free(sup_groups);
        return 0;
    }
    
    for (int i = 0; i < sup_count; i++) {
        if (sup_groups[i] == target_gid) {
            free(sup_groups);
            return 1;
        }
    }
    
    free(sup_groups);
    return 0;
}

// 获取特定组的权限级别
typedef enum {
    NO_ACCESS = 0,
    SUPPLEMENTARY_ACCESS = 1,
    PRIMARY_ACCESS = 2,
    EFFECTIVE_ACCESS = 3,
    ROOT_ACCESS = 4
} access_level_t;

access_level_t get_group_access_level(gid_t target_gid) {
    if (target_gid == 0 && (getgid() == 0 || getegid() == 0)) {
        return ROOT_ACCESS;
    }
    
    if (getegid() == target_gid) {
        return EFFECTIVE_ACCESS;
    }
    
    if (getgid() == target_gid) {
        return PRIMARY_ACCESS;
    }
    
    // 检查补充组
    int sup_count = getgroups(0, NULL);
    if (sup_count > 0) {
        gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
        if (sup_groups != NULL) {
            if (getgroups(sup_count, sup_groups) != -1) {
                for (int i = 0; i < sup_count; i++) {
                    if (sup_groups[i] == target_gid) {
                        free(sup_groups);
                        return SUPPLEMENTARY_ACCESS;
                    }
                }
            }
            free(sup_groups);
        }
    }
    
    return NO_ACCESS;
}

void check_common_groups() {
    printf("=== 常见组权限检查 ===\n");
    
    // 检查一些常见的系统组
    struct {
        const char *name;
        gid_t gid;
    } common_groups[] = {
        {"root", 0},
        {"wheel", 0},  // 需要查找实际 GID
        {"sudo", 0},
        {"adm", 0},
        {"disk", 0}
    };
    
    // 获取这些组的实际 GID
    for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups[0]); i++) {
        struct group *grp = getgrnam(common_groups[i].name);
        if (grp != NULL) {
            common_groups[i].gid = grp->gr_gid;
        }
    }
    
    printf("%-12s %-8s %-15s %s\n", "组名", "组ID", "访问级别", "成员状态");
    printf("%-12s %-8s %-15s %s\n", "----", "----", "----", "----");
    
    for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups[0]); i++) {
        if (common_groups[i].gid != 0) {
            access_level_t level = get_group_access_level(common_groups[i].gid);
            int is_member = is_process_member_of_group(common_groups[i].gid);
            
            const char *level_str;
            switch (level) {
                case ROOT_ACCESS: level_str = "Root"; break;
                case EFFECTIVE_ACCESS: level_str = "Effective"; break;
                case PRIMARY_ACCESS: level_str = "Primary"; break;
                case SUPPLEMENTARY_ACCESS: level_str = "Supplementary"; break;
                default: level_str = "None"; break;
            }
            
            printf("%-12s %-8d %-15s %s\n",
                   common_groups[i].name,
                   common_groups[i].gid,
                   level_str,
                   is_member ? "是" : "否");
        }
    }
}

int main() {
    check_common_groups();
    return 0;
}

9. 实际应用场景

getgroups 在以下场景中非常有用:

场景1:权限验证

int can_access_resource(gid_t required_group) {
    return is_process_member_of_group(required_group);
}

场景2:安全审计

void audit_process_groups() {
    int count = getgroups(0, NULL);
    syslog(LOG_INFO, "进程拥有 %d 个补充组", count);
}

场景3:访问控制

int check_file_group_permission(const char *filename) {
    struct stat file_stat;
    if (stat(filename, &file_stat) == 0) {
        return is_process_member_of_group(file_stat.st_gid);
    }
    return 0;
}

10. 注意事项

使用 getgroups 时需要注意:

  1. 缓冲区大小: 确保缓冲区足够大,避免 EINVAL 错误
  2. 内存管理: 正确分配和释放内存
  3. 错误处理: 检查返回值和 errno
  4. 系统限制: 了解 NGROUPS_MAX 限制
  5. 并发安全: 在多线程环境中注意数据一致性

总结

getgroups 是管理进程组权限的重要函数,关键要点:

  1. 补充组获取: 获取进程除主要组外的所有组
  2. 双重调用: 通常需要先获取数量,再获取实际数据
  3. 权限检查: 是权限验证和访问控制的基础
  4. 安全相关: 在安全审计和权限管理中广泛使用
  5. 系统集成: 与 /etc/group 等系统文件紧密相关

正确使用 getgroups 可以帮助程序准确了解当前的组权限状态,实现更精细的访问控制和安全检查。

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

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

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

发表在 linux文章 | 留下评论

getitimer系统调用及示例

getitimer系统调用及示例

继续学习 Linux 系统编程中的重要函数。这次我们介绍 getitimer 和 setitimer 函数,它们用于管理和控制进程的间隔计时器(interval timers)。这些计时器可以在指定的时间后产生信号(通常是 SIGALRMSIGVTALRMSIGPROF),从而实现定时执行代码、测量程序执行时间等功能。

本文介绍了 Linux 系统编程中的间隔计时器函数 getitimersetitimer,它们用于管理和控制进程的定时器。这些计时器可以在指定时间后产生信号(如 SIGALRM),实现定时执行代码或测量程序执行时间等功能。setitimer 设置计时器的超时时间和重载时间,支持三种类型:ITIMER_REAL(实时)、ITIMER_VIRTUAL(用户态 CPU 时间)和 ITIMER_PROF(总 CPU 时间)。getitimer 则获取计时器的当前状态。文章还提供了示例代码,演示如何使用 ITIMER_REALSIGALRM 实现超时机制,并对比了相关函数如 alarm 和 POSIX 定时器 API。这些功能为系统编程提供了灵活的定时解决方案。


1. 函数介绍

getitimer 和 setitimer 是一对 Linux 系统调用,用于获取和设置进程的间隔计时器(interval timers)。

  • 间隔计时器 (Interval Timer): 这是内核为每个进程维护的一个或多个计时器。每个计时器都有一个类型,当计时器超时(倒计时到 0)时,内核会向进程发送一个特定的信号
  • setitimer: 设置指定类型计时器的超时时间重载时间
    • 超时时间 (Value): 从现在开始,计时器倒计时多久后第一次超时并发送信号。
    • 重载时间 (Interval): 每次超时后,计时器自动重置并重新开始倒计时的时间。如果重载时间为 0,则计时器是一次性的;如果非 0,则计时器是周期性的。
  • getitimer: 获取指定类型计时器的当前剩余时间(Value)和设置的重载时间(Interval)。

这提供了一种基于信号的定时机制,不同于 sleep 或 nanosleep 的主动睡眠,也不同于 alarm 的单一秒级定时器。

你可以把间隔计时器想象成一个可以设置闹钟重复周期的高级闹钟:

  • 你可以说:“5 秒后响一次,然后每 2 秒再响一次”(周期性)。
  • 或者:“3 秒后响一次,之后不再响”(一次性)。

2. 函数原型

#include <sys/time.h> // 必需

// 获取间隔计时器的当前设置
int getitimer(int which, struct itimerval *curr_value);

// 设置间隔计时器
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

3. 功能

  • getitimer: 查询由 which 指定的计时器的当前状态(剩余时间和重载时间),并将结果存储在 curr_value 指向的 struct itimerval 结构体中。
  • setitimer: 根据 new_value 设置由 which 指定的计时器。如果 old_value 非 NULL,则在设置新值之前,将计时器的旧值(设置前的剩余时间和重载时间)存储在 old_value 指向的结构体中。

4. 参数

共同参数

  • int which: 指定要操作的计时器类型。Linux 上有三种主要类型:
    • ITIMER_REAL: 实时计时器。
      • 时钟源: 系统实时时间(墙上时钟时间)。
      • 超时信号SIGALRM
      • 用途: 测量真实世界流逝的时间。
    • ITIMER_VIRTUAL: 虚拟计时器。
      • 时钟源: 进程在用户态(执行程序代码)下花费的 CPU 时间。
      • 超时信号SIGVTALRM
      • 用途: 测量进程自身执行代码所用的时间,不包括内核态时间和被阻塞的时间。
    • ITIMER_PROF: 性能计时器(Profile Timer)。
      • 时钟源: 进程在用户态内核态(执行系统调用等)下花费的总 CPU 时间。
      • 超时信号SIGPROF
      • 用途: 常用于性能分析(profiling),测量程序执行(包括系统调用)的总 CPU 时间。

setitimer 特有参数

  • const struct itimerval *new_value: 指向一个 struct itimerval 结构体的指针,该结构体定义了新的计时器设置。
  • struct itimerval *old_value: 指向一个 struct itimerval 结构体的指针,用于接收计时器的旧设置。如果不需要旧值,可以传入 NULL

struct itimerval 结构体

这两个函数都使用 struct itimerval 来表示时间间隔:

struct itimerval {
    struct timeval it_interval; // 重载时间 (Interval)
    struct timeval it_value;    // 当前值/超时时间 (Value)
};

其中 struct timeval 定义了秒和微秒:

struct timeval {
    time_t      tv_sec;         // 秒
    suseconds_t tv_usec;        // 微秒 (0 - 999,999)
};
  • it_value当前计时器的剩余时间。当调用 setitimer 时,它指定了第一次超时的时间。当调用 getitimer 时,它返回计时器当前还剩多少时间。
  • it_interval重载时间。指定计时器超时后,自动重新开始计时的时间间隔。如果这个值为 0,则计时器是一次性的。

5. 返回值

  • 成功时: 返回 0。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EINVAL which 无效或时间值无效,EFAULT 指针无效等)。

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

  • alarm: 一个更简单的函数,只操作 ITIMER_REAL 计时器,且精度为秒。alarm(seconds) 大致等价于 setitimer(ITIMER_REAL, ...),其中 it_value.tv_sec = seconds 且 it_interval.tv_sec = 0
  • signalsigaction: 用于设置当计时器超时时(收到 SIGALRMSIGVTALRMSIGPROF 信号)应执行的操作。
  • nanosleepclock_nanosleep: 提供高精度的主动睡眠,不基于信号。
  • clock_gettimeclock_settime: 用于获取和设置各种系统时钟,更现代和灵活。
  • timer_createtimer_settimetimer_gettime: POSIX 定时器 API,功能更强大,支持多种时钟源和多种通知方式(包括信号和线程通知),是 setitimer 的现代替代品。

7. 示例代码

示例 1:使用 ITIMER_REAL 和 SIGALRM 实现超时

这个例子演示了如何使用 ITIMER_REAL 计时器在 3 秒后发送 SIGALRM 信号来中断一个可能长时间运行的操作。

#include <sys/time.h> // setitimer, getitimer, ITIMER_REAL, struct itimerval
#include <signal.h>   // signal, SIGALRM
#include <unistd.h>   // pause, write
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <errno.h>    // errno
#include <string.h>   // strerror

volatile sig_atomic_t alarm_received = 0;

// SIGALRM 信号处理函数
void alarm_handler(int sig) {
    // 在信号处理函数中,应只调用异步信号安全的函数
    write(STDERR_FILENO, "Timeout! SIGALRM received.\n", 27);
    alarm_received = 1;
}

int main() {
    struct itimerval timer;
    const char *msg = "Doing some work that might take a long time...\n";
    const char *msg_len = "Done.\n";

    // 1. 设置 SIGALRM 信号处理函数
    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 2. 配置 ITIMER_REAL 计时器
    // it_value: 3 秒后超时
    timer.it_value.tv_sec = 3;
    timer.it_value.tv_usec = 0;
    // it_interval: 0 表示一次性计时器,超时后不重载
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 0;

    printf("Setting ITIMER_REAL to expire in 3 seconds.\n");

    // 3. 启动计时器
    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer");
        exit(EXIT_FAILURE);
    }

    // 4. 模拟一个可能长时间运行的操作
    write(STDOUT_FILENO, msg, 46);

    // 5. 等待操作完成或超时
    // 这里用 pause() 模拟等待,实际可能是 read(), write(), connect() 等阻塞调用
    while (!alarm_received) {
        // 在实际应用中,这里可能是真正的阻塞操作
        // 为了演示,我们用 pause() 等待信号
        printf("Waiting for work to complete or timeout...\n");
        pause(); // 进程挂起,直到收到信号
    }

    write(STDOUT_FILENO, msg_len, 6);
    printf("Program finished after timeout.\n");

    // 6. (可选) 检查计时器状态
    struct itimerval curr_timer;
    if (getitimer(ITIMER_REAL, &curr_timer) == -1) {
        perror("getitimer");
    } else {
        printf("Current ITIMER_REAL setting:\n");
        printf("  it_value: %ld.%06ld seconds (should be 0)\n",
               (long)curr_timer.it_value.tv_sec, (long)curr_timer.it_value.tv_usec);
        printf("  it_interval: %ld.%06ld seconds\n",
               (long)curr_timer.it_interval.tv_sec, (long)curr_timer.it_interval.tv_usec);
    }

    return 0;
}

代码解释:

  1. 定义一个全局的 volatile sig_atomic_t 变量 alarm_received 用于信号处理函数和主循环间通信。
  2. 定义 SIGALRM 的信号处理函数 alarm_handler。当计时器超时,内核会发送 SIGALRM 信号,该函数会被调用,打印消息并设置标志。
  3. 在 main 函数中,使用 signal() 注册 SIGALRM 处理函数。
  4. 定义一个 struct itimerval 变量 timer
  5. 设置 timer.it_value 为 3 秒,表示 3 秒后第一次超时。
  6. 设置 timer.it_interval 为 0,表示这是一次性计时器。
  7. 调用 setitimer(ITIMER_REAL, &timer, NULL) 启动计时器。
  8. 模拟一个长时间运行的操作(这里只是打印一条消息)。
  9. 进入一个循环,等待操作完成或超时。这里用 pause() 模拟等待,实际应用中可能是 readwriteconnect 等阻塞调用。
  10. 当 SIGALRM 信号到达,alarm_handler 被调用,设置 alarm_received 为 1,主循环退出。
  11. 程序结束。
  12. 最后,调用 getitimer 检查计时器的当前状态(超时后,it_value 应该接近 0)。

示例 2:使用 ITIMER_VIRTUAL 测量 CPU 时间

这个例子演示如何使用 ITIMER_VIRTUAL 来测量进程在用户态执行代码所花费的 CPU 时间。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // clock()

volatile sig_atomic_t vt_alarm = 0;

void vt_alarm_handler(int sig) {
    write(STDERR_FILENO, "Virtual timer expired! (SIGVTALRM)\n", 35);
    vt_alarm = 1;
}

// 模拟一个消耗 CPU 的函数
void cpu_intensive_task() {
    volatile unsigned long sum = 0;
    for (unsigned long i = 0; i < 1000000000UL; ++i) {
        sum += i;
    }
    // 使用 sum 防止编译器优化掉循环
    if (sum % 2 == 0) {
        // do nothing
    }
}

int main() {
    struct itimerval timer;

    if (signal(SIGVTALRM, vt_alarm_handler) == SIG_ERR) {
        perror("signal SIGVTALRM");
        exit(EXIT_FAILURE);
    }

    // 设置 ITIMER_VIRTUAL 在 2 秒 CPU 用户态时间后超时
    timer.it_value.tv_sec = 2;
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 0; // 一次性
    timer.it_interval.tv_usec = 0;

    printf("Setting ITIMER_VIRTUAL to expire after 2 seconds of user CPU time.\n");
    printf("Starting CPU-intensive task...\n");

    if (setitimer(ITIMER_VIRTUAL, &timer, NULL) == -1) {
        perror("setitimer ITIMER_VIRTUAL");
        exit(EXIT_FAILURE);
    }

    // 执行消耗 CPU 的任务
    cpu_intensive_task();

    if (vt_alarm) {
        printf("Task was interrupted by virtual timer.\n");
    } else {
        printf("Task completed before virtual timer expired.\n");
    }

    // 检查计时器状态
    if (getitimer(ITIMER_VIRTUAL, &timer) == -1) {
        perror("getitimer ITIMER_VIRTUAL");
    } else {
        printf("ITIMER_VIRTUAL status after task:\n");
        printf("  it_value: %ld.%06ld seconds\n",
               (long)timer.it_value.tv_sec, (long)timer.it_value.tv_usec);
        printf("  it_interval: %ld.%06ld seconds\n",
               (long)timer.it_interval.tv_sec, (long)timer.it_interval.tv_usec);
    }

    return 0;
}

代码解释:

  1. 设置 SIGVTALRM 信号处理函数。
  2. 配置 ITIMER_VIRTUAL 计时器,在进程使用 2 秒用户态 CPU 时间后超时。
  3. 调用 setitimer 启动计时器。
  4. 执行一个消耗大量 CPU 时间的函数 cpu_intensive_task
  5. 如果在函数执行完毕前计时器超时,vt_alarm 标志会被设置,表示任务被中断。
  6. 最后检查计时器状态。

示例 3:使用 ITIMER_REAL 实现周期性操作

这个例子演示如何使用 ITIMER_REAL 实现一个周期性的“心跳”信号,每秒触发一次。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h> // C11 原子操作

volatile atomic_int heartbeat_count = 0;

void heartbeat_handler(int sig) {
    // 原子性地增加计数
    atomic_fetch_add(&heartbeat_count, 1);
    // write 是异步信号安全的
    write(STDERR_FILENO, "Heartbeat (SIGALRM)!\n", 21);
}

int main() {
    struct itimerval timer;
    int count_before_exit = 5;

    if (signal(SIGALRM, heartbeat_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 设置周期性计时器:立即启动,每 1 秒触发一次
    timer.it_value.tv_sec = 1;    // 1 秒后第一次超时
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 1; // 之后每 1 秒超时一次 (周期性)
    timer.it_interval.tv_usec = 0;

    printf("Setting periodic ITIMER_REAL (heartbeat every 1 second)...\n");
    printf("Will exit after %d heartbeats.\n", count_before_exit);

    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer periodic");
        exit(EXIT_FAILURE);
    }

    // 等待指定次数的心跳
    while (atomic_load(&heartbeat_count) < count_before_exit) {
        pause(); // 等待信号
    }

    printf("Received %d heartbeats. Exiting.\n", count_before_exit);

    // 停止计时器 (设置所有时间为 0)
    struct itimerval stop_timer = {{0, 0}, {0, 0}};
    if (setitimer(ITIMER_REAL, &stop_timer, NULL) == -1) {
        perror("setitimer stop");
    }

    return 0;
}

代码解释:

  1. 使用 atomic_int 和 atomic_fetch_add 来安全地在信号处理函数中增加计数。
  2. 配置 ITIMER_REAL 计时器:
    • it_value: 1 秒后第一次超时。
    • it_interval: 1 秒,使计时器周期性地每秒超时一次。
  3. 调用 setitimer 启动周期性计时器。
  4. 在主循环中使用 pause() 等待信号。
  5. 每次收到 SIGALRM 信号,信号处理函数打印消息并增加计数。
  6. 当计数达到预定值时,主循环退出。
  7. 通过设置计时器的 it_value 和 it_interval 都为 0 来停止计时器。

示例 4:使用 setitimer 的 old_value 参数

这个例子演示如何使用 old_value 参数来保存和恢复计时器的旧设置。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void alarm_handler(int sig) {
    write(STDERR_FILENO, "SIGALRM received.\n", 18);
}

void print_timer(const char *name, const struct itimerval *timer) {
    printf("%s:\n", name);
    printf("  Current value: %ld.%06ld seconds\n",
           (long)timer->it_value.tv_sec, (long)timer->it_value.tv_usec);
    printf("  Interval: %ld.%06ld seconds\n",
           (long)timer->it_interval.tv_sec, (long)timer->it_interval.tv_usec);
}

int main() {
    struct itimerval old_timer, new_timer;

    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 1. 设置一个初始的计时器 (5秒后超时,不重复)
    new_timer.it_value.tv_sec = 5;
    new_timer.it_value.tv_usec = 0;
    new_timer.it_interval.tv_sec = 0;
    new_timer.it_interval.tv_usec = 0;

    printf("Setting initial timer for 5 seconds.\n");
    if (setitimer(ITIMER_REAL, &new_timer, NULL) == -1) {
        perror("setitimer initial");
        exit(EXIT_FAILURE);
    }

    sleep(2); // 等待 2 秒

    // 2. 获取当前计时器状态 (应该剩余约 3 秒)
    if (getitimer(ITIMER_REAL, &old_timer) == -1) {
        perror("getitimer before override");
        exit(EXIT_FAILURE);
    }
    print_timer("Timer status after 2 seconds", &old_timer);

    // 3. 设置一个新计时器 (1秒后超时),并保存旧设置
    new_timer.it_value.tv_sec = 1;
    new_timer.it_value.tv_usec = 0;
    new_timer.it_interval.tv_sec = 0;
    new_timer.it_interval.tv_usec = 0;

    printf("\nOverriding timer to expire in 1 second, saving old setting.\n");
    if (setitimer(ITIMER_REAL, &new_timer, &old_timer) == -1) {
        perror("setitimer override");
        exit(EXIT_FAILURE);
    }

    print_timer("Old timer setting saved", &old_timer);

    printf("Waiting for 1-second timer to expire...\n");
    pause(); // 等待 1 秒计时器超时

    // 4. 恢复旧的计时器设置
    printf("\nRestoring old timer setting.\n");
    if (setitimer(ITIMER_REAL, &old_timer, NULL) == -1) {
        perror("setitimer restore");
        exit(EXIT_FAILURE);
    }

    if (getitimer(ITIMER_REAL, &new_timer) == -1) {
        perror("getitimer after restore");
        exit(EXIT_FAILURE);
    }
    print_timer("Timer status after restore", &new_timer);

    printf("Waiting for restored timer to expire...\n");
    pause(); // 等待恢复的计时器超时

    printf("Restored timer expired. Program finished.\n");
    return 0;
}

代码解释:

  1. 设置一个 5 秒后超时的初始计时器。
  2. 等待 2 秒后,调用 getitimer 获取当前计时器状态(剩余约 3 秒)。
  3. 调用 setitimer(ITIMER_REAL, &new_timer, &old_timer) 设置一个 1 秒后超时的新计时器,并将旧计时器的设置(剩余约 3 秒)保存在 old_timer 变量中。
  4. 等待 1 秒计时器超时。
  5. 调用 setitimer(ITIMER_REAL, &old_timer, NULL) 将计时器恢复为之前保存的设置(约 3 秒)。
  6. 再次调用 getitimer 验证恢复是否成功。
  7. 等待恢复的计时器超时。

重要提示与注意事项:

  1. 精度setitimer 的时间精度是微秒tv_usec),而 nanosleep 和 POSIX 定时器 (timer_) 支持纳秒精度。对于高精度需求,后者是更好的选择。
  2. 信号处理: 使用 setitimer 必然涉及信号处理。必须小心编写信号处理函数,只使用异步信号安全的函数。
  3. 竞态条件: 在设置信号处理函数和启动计时器之间,或者在检查标志和调用 pause 之间,可能存在竞态条件。使用 sigsuspend 可以更安全地处理。
  4. 现代替代timer_createtimer_settime 等 POSIX 定时器函数提供了更强大和灵活的功能,例如可以选择不同的通知方式(信号、线程特定信号、过期计数等)和不同的时钟源(CLOCK_REALTIMECLOCK_MONOTONIC 等),是 setitimer 的推荐现代替代方案。

总结:

getitimer 和 setitimer 提供了一套基于信号的进程间隔计时器机制。它们可以用于实现超时、周期性任务和 CPU 时间测量等功能。理解三种计时器类型(ITIMER_REALITIMER_VIRTUALITIMER_PROF)及其对应的信号是掌握这些函数的关键。虽然它们功能强大,但在现代编程中,timer_ 系列的 POSIX 定时器通常被认为是更优的选择。

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

发表在 站点文章 | 留下评论

getmsg系统调用及示例

getmsg 函数详解

1. 函数介绍

getmsg 是 System V STREAMS 接口中的一个函数,用于从 STREAMS 设备或管道中接收消息。可以把 STREAMS 想象成一个”消息传送带系统”——数据以消息的形式在系统中流动,getmsg 就是从这个传送带上取下消息的工具。

STREAMS 是 Unix System V 中的一种模块化 I/O 框架,它允许在数据流中插入处理模块,实现复杂的数据处理。虽然在现代 Linux 系统中 STREAMS 使用较少,但在一些 Unix 系统(如 Solaris)中仍然重要。

getmsg 允许你接收包含控制信息和数据信息的消息,提供了比普通 read 更精细的控制。

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

2. 函数原型

#include <stropts.h>

int getmsg(int fildes, struct strbuf *ctlptr, struct strbuf *dataptr, int *flagsp);

3. 功能

getmsg 函数用于从 STREAMS 文件描述符中接收消息。它可以分别接收消息的控制部分和数据部分,提供了对消息结构的精细控制。

4. 参数

  • fildes: STREAMS 设备或管道的文件描述符
  • ctlptr: 指向 strbuf 结构体的指针,用于接收控制信息
  • dataptr: 指向 strbuf 结构体的指针,用于接收数据信息
  • flagsp: 指向标志的指针,用于指定接收模式和返回消息类型

5. strbuf 结构体

struct strbuf {
    int     maxlen;    /* 缓冲区最大长度 */
    int     len;       /* 实际数据长度 */
    char    *buf;      /* 指向缓冲区的指针 */
};

6. flags 参数说明

输入标志(指定要接收的消息类型):

  • 0: 接收下一条消息(按优先级顺序)
  • RS_HIPRI: 接收下一条高优先级消息

输出标志(返回实际接收到的消息类型):

  • 0: 普通优先级消息
  • RS_HIPRI: 高优先级消息

7. 返回值

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

特殊返回值:

  • MORECTL: 控制部分还有更多数据
  • MOREDATA: 数据部分还有更多数据

常见错误码:

  • EBADF: fildes 不是有效的文件描述符
  • EINVAL: 参数无效
  • EIO: I/O 错误
  • ENOSTR: fildes 不是 STREAMS 设备
  • ENOSR: 没有足够的 STREAMS 资源
  • EAGAIN: 非阻塞模式下无数据可读

8. 相似函数或关联函数

  • putmsg: 发送消息到 STREAMS 设备
  • getpmsg: 获取带优先级的消息(更高级的版本)
  • putpmsg: 发送带优先级的消息
  • ioctl: 控制 STREAMS 设备
  • read/write: 普通的文件读写操作

9. 示例代码

示例1:基础用法 – 简单的消息接收

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stropts.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

// 注意:这个示例在大多数 Linux 系统上可能无法运行
// 因为 Linux 不完全支持 STREAMS

int main() {
    int fd;
    struct strbuf ctlbuf, databuf;
    char ctl_data[256], data_buf[1024];
    int flags;
    
    printf("=== getmsg 基础示例 ===\n\n");
    
    // 初始化缓冲区结构
    ctlbuf.maxlen = sizeof(ctl_data);
    ctlbuf.buf = ctl_data;
    ctlbuf.len = 0;
    
    databuf.maxlen = sizeof(data_buf);
    databuf.buf = data_buf;
    databuf.len = 0;
    
    flags = 0;  // 接收普通消息
    
    printf("注意: getmsg 主要用于 STREAMS 系统\n");
    printf("在大多数 Linux 系统上可能不可用\n\n");
    
    // 尝试打开一个 STREAMS 设备(示例)
    // fd = open("/dev/stream_device", O_RDONLY);
    // 由于大多数系统没有 STREAMS 设备,这里只演示结构
    
    printf("控制缓冲区设置:\n");
    printf("  最大长度: %d\n", ctlbuf.maxlen);
    printf("  缓冲区地址: %p\n", (void*)ctlbuf.buf);
    
    printf("数据缓冲区设置:\n");
    printf("  最大长度: %d\n", databuf.maxlen);
    printf("  缓冲区地址: %p\n", (void*)databuf.buf);
    
    printf("标志设置: %d\n", flags);
    
    printf("\n如果在支持 STREAMS 的系统上,可以这样调用:\n");
    printf("result = getmsg(fd, &ctlbuf, &databuf, &flags);\n");
    
    return 0;
}

示例2:模拟 STREAMS 消息处理

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 模拟 STREAMS 消息结构
struct simulated_msg {
    int priority;           // 消息优先级
    int control_len;        // 控制数据长度
    char control_data[256]; // 控制数据
    int data_len;           // 数据长度
    char data[1024];        // 实际数据
};

// 模拟的 strbuf 结构
struct simulated_strbuf {
    int maxlen;
    int len;
    char *buf;
};

// 模拟的 getmsg 函数
int simulated_getmsg(struct simulated_msg *msg, 
                     struct simulated_strbuf *ctlptr, 
                     struct simulated_strbuf *dataptr, 
                     int *flagsp) {
    
    printf("=== 模拟 getmsg 操作 ===\n");
    
    // 复制控制数据
    if (ctlptr && ctlptr->buf) {
        int copy_len = (msg->control_len < ctlptr->maxlen) ? 
                       msg->control_len : ctlptr->maxlen;
        memcpy(ctlptr->buf, msg->control_data, copy_len);
        ctlptr->len = copy_len;
        printf("复制控制数据: %d 字节\n", copy_len);
    }
    
    // 复制数据
    if (dataptr && dataptr->buf) {
        int copy_len = (msg->data_len < dataptr->maxlen) ? 
                       msg->data_len : dataptr->maxlen;
        memcpy(dataptr->buf, msg->data, copy_len);
        dataptr->len = copy_len;
        printf("复制数据: %d 字节\n", copy_len);
    }
    
    // 设置标志
    if (flagsp) {
        *flagsp = (msg->priority > 0) ? 1 : 0;  // 模拟高优先级标志
    }
    
    printf("消息优先级: %s\n", 
           (msg->priority > 0) ? "高" : "普通");
    printf("控制数据长度: %d\n", msg->control_len);
    printf("数据长度: %d\n", msg->data_len);
    
    return 0;  // 成功
}

// 创建测试消息
struct simulated_msg* create_test_message() {
    static struct simulated_msg msg;
    
    msg.priority = 1;  // 高优先级
    msg.control_len = strlen("CONTROL_INFO") + 1;
    strcpy(msg.control_data, "CONTROL_INFO");
    msg.data_len = strlen("Hello, STREAMS World!") + 1;
    strcpy(msg.data, "Hello, STREAMS World!");
    
    return &msg;
}

int main() {
    struct simulated_msg *test_msg;
    struct simulated_strbuf ctlbuf, databuf;
    char ctl_buffer[256], data_buffer[1024];
    int flags;
    int result;
    
    printf("=== STREAMS 消息处理模拟 ===\n\n");
    
    // 创建测试消息
    test_msg = create_test_message();
    printf("创建测试消息完成\n\n");
    
    // 初始化缓冲区
    ctlbuf.maxlen = sizeof(ctl_buffer);
    ctlbuf.buf = ctl_buffer;
    ctlbuf.len = 0;
    
    databuf.maxlen = sizeof(data_buffer);
    databuf.buf = data_buffer;
    databuf.len = 0;
    
    flags = 0;
    
    // 调用模拟的 getmsg
    result = simulated_getmsg(test_msg, &ctlbuf, &databuf, &flags);
    
    if (result == 0) {
        printf("\n=== 接收结果 ===\n");
        printf("控制数据 (%d 字节): %s\n", ctlbuf.len, ctlbuf.buf);
        printf("数据 (%d 字节): %s\n", databuf.len, databuf.buf);
        printf("消息标志: %s\n", (flags > 0) ? "高优先级" : "普通优先级");
    } else {
        printf("接收消息失败\n");
    }
    
    return 0;
}

示例3:完整的 STREAMS 消息系统模拟

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// 消息类型定义
#define MSG_NORMAL 0
#define MSG_HIGH_PRIORITY 1
#define MSG_CONTROL 2
#define MSG_DATA 3

// 模拟的 STREAMS 消息队列
struct message_queue {
    int count;
    struct {
        int priority;
        int type;
        time_t timestamp;
        int control_len;
        char control_data[128];
        int data_len;
        char data[512];
    } messages[10];
};

// 模拟的 strbuf 结构
struct my_strbuf {
    int maxlen;
    int len;
    char *buf;
};

// 全局消息队列
struct message_queue global_queue = {0};

// 向队列添加消息
int add_message(int priority, int type, 
                const char *control, const char *data) {
    if (global_queue.count >= 10) {
        printf("消息队列已满\n");
        return -1;
    }
    
    int index = global_queue.count++;
    global_queue.messages[index].priority = priority;
    global_queue.messages[index].type = type;
    global_queue.messages[index].timestamp = time(NULL);
    
    if (control) {
        global_queue.messages[index].control_len = strlen(control) + 1;
        strncpy(global_queue.messages[index].control_data, control, 127);
        global_queue.messages[index].control_data[127] = '\0';
    } else {
        global_queue.messages[index].control_len = 0;
        global_queue.messages[index].control_data[0] = '\0';
    }
    
    if (data) {
        global_queue.messages[index].data_len = strlen(data) + 1;
        strncpy(global_queue.messages[index].data, data, 511);
        global_queue.messages[index].data[511] = '\0';
    } else {
        global_queue.messages[index].data_len = 0;
        global_queue.messages[index].data[0] = '\0';
    }
    
    printf("添加消息: 优先级=%d, 类型=%d, 控制=%s, 数据=%s\n",
           priority, type, control ? control : "无", data ? data : "无");
    
    return 0;
}

// 模拟的 getmsg 实现
int my_getmsg(struct my_strbuf *ctlptr, 
              struct my_strbuf *dataptr, 
              int *flagsp) {
    
    if (global_queue.count == 0) {
        printf("消息队列为空\n");
        return -1;
    }
    
    // 查找高优先级消息(如果请求)
    int msg_index = 0;
    if (flagsp && (*flagsp & 1)) {  // 模拟 RS_HIPRI
        for (int i = 0; i < global_queue.count; i++) {
            if (global_queue.messages[i].priority > 0) {
                msg_index = i;
                break;
            }
        }
    }
    
    // 获取消息
    struct message_queue *msg = &global_queue.messages[msg_index];
    
    // 复制控制数据
    if (ctlptr && ctlptr->buf) {
        int copy_len = (msg->control_len < ctlptr->maxlen) ? 
                       msg->control_len : ctlptr->maxlen;
        memcpy(ctlptr->buf, msg->control_data, copy_len);
        ctlptr->len = copy_len;
    }
    
    // 复制数据
    if (dataptr && dataptr->buf) {
        int copy_len = (msg->data_len < dataptr->maxlen) ? 
                       msg->data_len : dataptr->maxlen;
        memcpy(dataptr->buf, msg->data, copy_len);
        dataptr->len = copy_len;
    }
    
    // 设置返回标志
    if (flagsp) {
        *flagsp = (msg->priority > 0) ? 1 : 0;  // 高优先级标志
    }
    
    // 从队列中移除消息
    for (int i = msg_index; i < global_queue.count - 1; i++) {
        global_queue.messages[i] = global_queue.messages[i + 1];
    }
    global_queue.count--;
    
    return 0;
}

// 显示队列状态
void show_queue_status() {
    printf("\n=== 消息队列状态 ===\n");
    printf("队列中消息数量: %d\n", global_queue.count);
    
    for (int i = 0; i < global_queue.count; i++) {
        printf("消息 %d: 优先级=%d, 类型=%d, 时间=%s",
               i, global_queue.messages[i].priority,
               global_queue.messages[i].type,
               ctime(&global_queue.messages[i].timestamp));
        printf("  控制: %s\n", global_queue.messages[i].control_data);
        printf("  数据: %s\n", global_queue.messages[i].data);
    }
}

int main() {
    struct my_strbuf ctlbuf, databuf;
    char ctl_buffer[256], data_buffer[1024];
    int flags;
    int result;
    
    printf("=== 完整的 STREAMS 消息系统模拟 ===\n\n");
    
    // 初始化缓冲区
    ctlbuf.maxlen = sizeof(ctl_buffer);
    ctlbuf.buf = ctl_buffer;
    ctlbuf.len = 0;
    
    databuf.maxlen = sizeof(data_buffer);
    databuf.buf = data_buffer;
    databuf.len = 0;
    
    // 添加测试消息
    printf("添加测试消息...\n");
    add_message(0, MSG_NORMAL, "NORMAL_CTL", "普通消息数据");
    add_message(1, MSG_HIGH_PRIORITY, "HIGH_CTL", "高优先级消息");
    add_message(0, MSG_DATA, "DATA_CTL", "另一个普通消息");
    add_message(1, MSG_CONTROL, "CTRL_CTL", "高优先级控制消息");
    
    show_queue_status();
    
    // 测试接收普通消息
    printf("\n--- 测试1: 接收普通消息 ---\n");
    flags = 0;  // 普通消息
    result = my_getmsg(&ctlbuf, &databuf, &flags);
    
    if (result == 0) {
        printf("成功接收消息:\n");
        printf("  控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
        printf("  数据: %.*s\n", databuf.len, databuf.buf);
        printf("  优先级: %s\n", (flags > 0) ? "高" : "普通");
    }
    
    // 测试接收高优先级消息
    printf("\n--- 测试2: 接收高优先级消息 ---\n");
    flags = 1;  // 高优先级消息
    result = my_getmsg(&ctlbuf, &databuf, &flags);
    
    if (result == 0) {
        printf("成功接收高优先级消息:\n");
        printf("  控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
        printf("  数据: %.*s\n", databuf.len, databuf.buf);
        printf("  优先级: %s\n", (flags > 0) ? "高" : "普通");
    }
    
    show_queue_status();
    
    printf("\n=== STREAMS 概念说明 ===\n");
    printf("STREAMS 是 System V 中的消息传递机制\n");
    printf("特点:\n");
    printf("1. 消息包含控制部分和数据部分\n");
    printf("2. 支持消息优先级\n");
    printf("3. 模块化处理架构\n");
    printf("4. 主要在 Solaris 等系统中使用\n");
    printf("\n在 Linux 中,类似功能可通过以下方式实现:\n");
    printf("- Unix 域套接字\n");
    printf("- 管道和 FIFO\n");
    printf("- netlink 套接字\n");
    printf("- D-Bus 消息系统\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o getmsg_example1 example1.c
gcc -o getmsg_example2 example2.c
gcc -o getmsg_example3 example3.c

# 运行示例
./getmsg_example1
./getmsg_example2
./getmsg_example3

STREAMS 系统检查

# 检查系统是否支持 STREAMS
ls /usr/include/stropts.h

# 在支持 STREAMS 的系统上编译
gcc -DSTREAMS_AVAILABLE -o getmsg_real example_real.c -lstrmi

# 查看 STREAMS 相关设备
ls /dev/* | grep stream

重要注意事项

  1. 系统支持getmsg 主要在 System V Unix 系统中可用
  2. Linux 限制: 大多数 Linux 系统不完全支持 STREAMS
  3. 移植性: 代码可移植性较差
  4. 替代方案: Linux 中可以使用其他 IPC 机制
  5. 错误处理: 始终检查返回值和 errno

现代 Linux 替代方案

使用 Unix 域套接字

#include <sys/socket.h>
#include <sys/un.h>

// 创建 Unix 域套接字进行消息传递
int create_unix_socket(const char *path) {
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    // ... 配置和使用套接字
    return sock;
}

使用管道

#include <unistd.h>

// 创建管道进行进程间通信
int pipe_fd[2];
if (pipe(pipe_fd) == 0) {
    // 使用 pipe_fd[0] 读取,pipe_fd[1] 写入
}

实际应用场景

  1. 设备驱动: 在某些 Unix 系统中用于设备通信
  2. 网络协议: 实现复杂的网络协议栈
  3. 系统管理: 系统管理工具的消息传递
  4. 模块化处理: 数据流的模块化处理

STREAMS 架构概念

应用程序
    ↓
STREAMS 接口 (putmsg/getmsg)
    ↓
流首部 (Stream Head)
    ↓
模块 1 → 模块 2 → 模块 3
    ↓
驱动程序 (Driver)
    ↓
硬件设备

虽然 getmsg 在现代 Linux 系统中使用较少,但了解这个概念有助于理解 Unix 系统的消息传递机制和 STREAMS 架构的设计思想。在实际开发中,建议使用 Linux 原生的 IPC 机制。

发表在 linux文章 | 留下评论

getpeername系统调用及示例

getpeername系统调用及示例

我们已经介绍了 getsockname,所以接下来应该介绍 getpeername


1. 函数介绍

getpeername 是一个 Linux 系统调用,用于获取已连接套接字的对方(peer)的协议地址。这个地址包含了与本端套接字建立连接的那个远程主机的 IP 地址 和 端口号

你可以把它想象成查看已接通电话的对方号码

  • 你和朋友正在通话(套接字已连接)。
  • 你想知道现在和你通话的人的电话号码是多少(对方地址)。
  • getpeername 就是查看这个对方号码的功能。

这对于以下场景非常有用:

  1. 服务器: 服务器 accept 一个连接后,得到一个新的已连接套接字。服务器可以使用 getpeername 来获取是哪个客户端(IP 和端口)连接了进来。
  2. 客户端: 客户端在 connect 成功后,可以使用 getpeername 来确认它连接到了哪个服务器地址(IP 和端口),尤其是在连接时使用了域名,想确认解析后的具体 IP。
  3. 调试和日志: 在网络程序中记录连接信息时,getpeername 是获取对端地址的标准方法。

2. 函数原型

#include <sys/socket.h> // 必需

int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

3. 功能

  • 获取对端地址: 检索与已连接套接字 sockfd 关联的对方协议地址
  • 填充结构体: 将获取到的地址信息填充到调用者提供的 struct sockaddr 结构体指针 addr 所指向的内存中。
  • 返回地址大小: 通过 addrlen 参数返回实际存储在 addr 中的地址结构的大小。

4. 参数

  • int sockfd: 这是一个有效的、已连接的套接字文件描述符。
    • 对于 TCP,这意味着已经成功调用了 connect(客户端)或 accept(服务器返回的新套接字)。
    • 对于 UDP,如果调用了 connect,也可以使用 getpeername
    • 如果套接字未连接,调用会失败。
  • struct sockaddr *addr: 这是一个指向套接字地址结构的指针,用于接收对方的地址信息。
    • 调用者需要提供一个足够大的缓冲区(例如 struct sockaddr_in 或 struct sockaddr_in6)并将其地址传递给 addr
  • socklen_t *addrlen: 这是一个指向 socklen_t 类型变量的指针。
    • 输入: 在调用 getpeername 时,这个变量必须被初始化为 addr 指向的缓冲区的大小(以字节为单位)。例如,如果 addr 指向 struct sockaddr_in,则 *addrlen 应初始化为 sizeof(struct sockaddr_in)
    • 输出getpeername 返回时,这个变量会被更新为实际存储在 addr 中的地址结构的大小

5. 返回值

  • 成功时: 返回 0。同时,addr 指向的结构体被成功填充,*addrlen 被更新为实际地址大小。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF sockfd 无效,EINVAL addrlen 指针无效,ENOTSOCK sockfd 不是一个套接字,ENOTCONN 套接字未连接等)。

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

  • getsockname: 用于获取本地(本端)套接字的地址信息。
  • connect: 客户端使用此函数连接到服务器。连接成功后,可以使用 getpeername 获取服务器地址。
  • accept: 服务器使用此函数接受客户端连接,返回一个已连接的新套接字,可以使用 getpeername 获取客户端地址。
  • socket: 创建套接字。

7. 示例代码

示例 1:客户端使用 getpeername 确认服务器地址

这个例子演示了 TCP 客户端在连接到服务器后,如何使用 getpeername 来获取它所连接的服务器的地址信息。

// getpeername_client.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // inet_ntoa, ntohs
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SERVER_PORT 8093
#define SERVER_IP "127.0.0.1" // 可以换成域名测试,如 "localhost"

int main() {
    int sock;
    struct sockaddr_in server_addr, peer_addr;
    socklen_t peer_addr_len = sizeof(peer_addr);

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    printf("Client socket created (fd: %d)\n", sock);

    // 配置服务器地址并连接
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid server address: %s\n", SERVER_IP);
        close(sock);
        exit(EXIT_FAILURE);
    }

    printf("Connecting to server %s:%d...\n", SERVER_IP, SERVER_PORT);
    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect failed");
        close(sock);
        exit(EXIT_FAILURE);
    }
    printf("Connected to server successfully.\n");

    // --- 关键: 使用 getpeername 获取服务器地址 ---
    printf("\n--- Getting peer (server) name with getpeername ---\n");
    // 重新初始化长度,以防被修改
    peer_addr_len = sizeof(peer_addr);
    if (getpeername(sock, (struct sockaddr *)&peer_addr, &peer_addr_len) == 0) {
        printf("Connected to server at:\n");
        printf("  Peer IP Address: %s\n", inet_ntoa(peer_addr.sin_addr));
        printf("  Peer Port: %d\n", ntohs(peer_addr.sin_port));
    } else {
        perror("getpeername failed");
        // 常见错误:ENOTCONN (如果套接字未连接)
    }

    // (对比) 使用 getsockname 获取自己的本地地址
    struct sockaddr_in local_addr;
    socklen_t local_addr_len = sizeof(local_addr);
    if (getsockname(sock, (struct sockaddr *)&local_addr, &local_addr_len) == 0) {
        printf("\nMy local address is:\n");
        printf("  Local IP Address: %s\n", inet_ntoa(local_addr.sin_addr));
        printf("  Local Port: %d\n", ntohs(local_addr.sin_port));
    } else {
        perror("getsockname failed");
    }

    printf("\nClient is connected and verified peer/local addresses.\n");

    // ... 进行通信 ...

    close(sock);
    printf("Client socket closed.\n");
    return 0;
}

代码解释:

  1. 创建 TCP 套接字。
  2. 配置服务器地址并调用 connect 连接。
  3. 关键步骤: 连接成功后,调用 getpeername(sock, (struct sockaddr *)&peer_addr, &peer_addr_len)
    • sock: 已连接的套接字。
    • &peer_addr: 指向用于存储对端地址的 sockaddr_in 结构的指针。
    • &peer_addr_len: 指向 socklen_t 变量的指针,初始化为 sizeof(peer_addr)
  4. getpeername 成功后,打印出服务器的地址(IP 和端口)。
  5. 对比: 调用 getsockname 获取自己的本地地址。
  6. 最后关闭套接字。

示例 2:服务器使用 getpeername 获取客户端地址

这个例子演示了 TCP 服务器在 accept 一个连接后,如何使用 getpeername 来获取连接进来的客户端的地址信息。

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

#define PORT 8093
#define BACKLOG 10

void handle_client(int client_fd) {
    struct sockaddr_in peer_addr;
    socklen_t peer_addr_len = sizeof(peer_addr);

    printf("Handling new client connection (fd: %d)\n", client_fd);

    // --- 关键: 使用 getpeername 获取客户端地址 ---
    if (getpeername(client_fd, (struct sockaddr *)&peer_addr, &peer_addr_len) == 0) {
        printf("  Client connected from: %s:%d\n",
               inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
    } else {
        perror("  getpeername on client socket failed");
    }

    // 为了演示,我们立即关闭连接
    // 实际应用中,这里会进行数据读写
    close(client_fd);
    printf("  Closed connection to client.\n");
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address;
    socklen_t client_addr_len;
    struct sockaddr_in client_address;
    int opt = 1;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 演示接受几个连接
    for (int i = 0; i < 3; ++i) {
        printf("\n--- Waiting for connection #%d ---\n", i + 1);
        client_addr_len = sizeof(client_address);
        client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
        if (client_fd < 0) {
            perror("accept failed");
            continue;
        }

        printf("New connection accepted (fd: %d)\n", client_fd);
        // accept 返回的 client_fd 已经包含了客户端地址信息,
        // 我们也可以用 getpeername 再次确认
        handle_client(client_fd);
    }

    close(server_fd);
    printf("Server socket closed.\n");
    return 0;
}

如何测试:

  1. 在一个终端编译并运行服务器:gcc -o getpeername_server getpeername_server.c ./getpeername_server
  2. 在另外几个终端运行客户端(可以多次运行):gcc -o getpeername_client getpeername_client.c ./getpeername_client

代码解释:

  1. 创建、绑定、监听标准的 TCP 服务器套接字。
  2. 进入一个循环,accept 三个客户端连接。
  3. 每次 accept 成功后,调用 handle_client(client_fd)
  4. 在 handle_client 函数中:
    • 关键步骤: 调用 getpeername(client_fd, ...)
    • client_fd 是 accept 返回的、与特定客户端关联的已连接套接字。
    • getpeername 会返回该客户端的 IP 地址和端口号。
    • 打印出客户端的地址信息。
  5. 函数结束时关闭与该客户端的连接。

示例 3:对比 getsockname 和 getpeername

这个例子在一个已连接的套接字上同时使用 getsockname 和 getpeername,清晰地展示它们的区别。

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

#define SERVER_PORT 8094
#define SERVER_IP "127.0.0.1"

void print_socket_addresses(int sock, const char* role) {
    struct sockaddr_in local_addr, peer_addr;
    socklen_t addr_len;

    printf("--- Addresses for %s socket (fd: %d) ---\n", role, sock);

    // 获取本地地址
    addr_len = sizeof(local_addr);
    if (getsockname(sock, (struct sockaddr *)&local_addr, &addr_len) == 0) {
        printf("  Local Address : %s:%d\n",
               inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port));
    } else {
        perror("  getsockname failed");
    }

    // 获取对端地址
    addr_len = sizeof(peer_addr);
    if (getpeername(sock, (struct sockaddr *)&peer_addr, &addr_len) == 0) {
        printf("  Peer Address  : %s:%d\n",
               inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
    } else {
        perror("  getpeername failed");
        // 如果套接字未连接,会打印错误,如 "Transport endpoint is not connected"
    }
    printf("----------------------------------------\n");
}

int main() {
    int server_fd, client_sock;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    int opt = 1;

    // --- 服务器端 ---
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("server socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("server setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    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(SERVER_PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("server bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 5) < 0) {
        perror("server listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // --- 客户端 ---
    client_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (client_sock < 0) {
        perror("client socket failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &client_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid server IP\n");
        close(server_fd);
        close(client_sock);
        exit(EXIT_FAILURE);
    }

    printf("\nClient connecting to server...\n");
    if (connect(client_sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
        perror("client connect failed");
        close(server_fd);
        close(client_sock);
        exit(EXIT_FAILURE);
    }

    // --- 连接建立后,检查客户端套接字 ---
    print_socket_addresses(client_sock, "Client");

    // --- 服务器接受连接 ---
    int accepted_sock = accept(server_fd, NULL, NULL); // 忽略客户端地址
    if (accepted_sock < 0) {
        perror("server accept failed");
        close(server_fd);
        close(client_sock);
        exit(EXIT_FAILURE);
    }

    // --- 检查服务器端的已连接套接字 ---
    print_socket_addresses(accepted_sock, "Server-Accepted");

    // 客户端和服务器的本地地址应该等于对方的对端地址
    printf("\nNote:\n");
    printf("- Client's Local Address should equal Server's Peer Address.\n");
    printf("- Server's Local Address should equal Client's Peer Address.\n");

    close(client_sock);
    close(accepted_sock);
    close(server_fd);

    return 0;
}

代码解释:

  1. 程序同时扮演服务器和客户端的角色。
  2. 设置服务器套接字并开始监听。
  3. 创建客户端套接字并连接到服务器。
  4. 定义一个 print_socket_addresses 函数,它接受一个套接字和一个角色名称(“Client” 或 “Server-Accepted”)。
  5. 在该函数内部:
    • 调用 getsockname 获取并打印本地地址。
    • 调用 getpeername 获取并打印对端地址。
  6. 在 main 函数中:
    • 客户端连接成功后,调用 print_socket_addresses(client_sock, "Client")
    • 服务器 accept 连接后,调用 print_socket_addresses(accepted_sock, "Server-Accepted")
  7. 通过打印的地址可以清楚地看到:
    • 客户端的本地地址 == 服务器 accept 套接字的对端地址。
    • 服务器的本地地址 == 客户端的对端地址。

重要提示与注意事项:

  1. 仅对已连接套接字有效getpeername 只能用于已连接的套接字。对于未连接的套接字(如刚创建的套接字,或未 connect 的 UDP 套接字),调用会失败,errno 通常为 ENOTCONN
  2. 区分 getsockname 和 getpeername:
    • getsockname: 获取本地地址(我的地址)。
    • getpeername: 获取对端地址(对方的地址)。
  3. addrlen 的初始化: 与 getsockname 一样,在调用前,必须将 *addrlen 初始化为目标缓冲区的大小。调用后,它会被更新为实际的地址结构大小。
  4. 服务器常用: 服务器在 accept 后,经常使用 getpeername 来记录或显示是哪个客户端连接了进来。
  5. 客户端确认: 客户端可以用它来确认连接的服务器地址,尤其是在使用域名时。
  6. 错误处理: 始终检查返回值。最常见的错误是 ENOTCONN(套接字未连接)。

总结:

getpeername 是一个用于获取已连接套接字对端地址信息的系统调用。它与 getsockname 相辅相成,分别用于查询连接两端的地址。在服务器记录客户端信息和客户端确认服务器信息等场景中非常有用。理解其参数和使用条件对于进行有效的网络编程至关重要。

https://blog.csdn.net/zidier215/article/details/151373575?sharetype=blogdetail&sharerId=151373575&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

发表在 linux文章 | 留下评论

getpmsg系统调用及示例

getpmsg 函数详解

1. 函数介绍

getpmsg 是 System V STREAMS 接口中的一个函数,用于从 STREAMS 设备或管道中接收带优先级的消息。可以把 STREAMS 想象成一个”智能分拣系统”——消息根据优先级被分类处理,getpmsg 就是从这个系统中按指定优先级取下消息的工具。

与 getmsg 不同,getpmsg 提供了更精细的优先级控制,允许你指定要接收的消息类型(普通优先级、高优先级等),就像邮件分拣系统可以按紧急程度分拣邮件一样。

getpmsg 是 System V STREAMS 接口中的函数,用于从 STREAMS 设备接收带优先级的消息。该函数允许按优先级(band)接收消息,支持多种接收模式(MSG_HIPRI/MSG_ANY/MSG_BAND)。参数包括文件描述符、控制/数据缓冲区结构(strbuf)以及优先级/标志指针。函数返回0表示成功,-1表示失败并设置errno。示例代码展示了基础用法和模拟实现,但需注意Linux系统对STREAMS的支持有限。关联函数包括putpmsg、getmsg等,常用于消息优先级处理场景。

2. 函数原型

#include <stropts.h>

int getpmsg(int fildes, struct strbuf *ctlptr, struct strbuf *dataptr, 
            int *bandp, int *flagsp);

3. 功能

getpmsg 函数用于从 STREAMS 文件描述符中接收带优先级的消息。它可以接收指定优先级(band)的消息,并且可以控制接收行为。

4. 参数

  • fildes: STREAMS 设备或管道的文件描述符
  • ctlptr: 指向 strbuf 结构体的指针,用于接收控制信息
  • dataptr: 指向 strbuf 结构体的指针,用于接收数据信息
  • bandp: 指向优先级(band)的指针
  • flagsp: 指向标志的指针,用于指定接收模式和返回消息类型

5. strbuf 结构体

struct strbuf {
    int     maxlen;    /* 缓冲区最大长度 */
    int     len;       /* 实际数据长度 */
    char    *buf;      /* 指向缓冲区的指针 */
};

6. band 和 flags 参数说明

band 参数(优先级)

  • 0-255: 消息优先级级别,数值越高优先级越高
  • 用于区分不同类型的消息

flags 参数(输入标志)

  • MSG_HIPRI: 接收高优先级消息
  • MSG_ANY: 接收任何优先级的消息
  • MSG_BAND: 接收指定优先级的消息

flags 参数(输出标志)

  • MSG_HIPRI: 接收到高优先级消息
  • MSG_BAND: 接收到指定优先级消息
  • MSG_MORECTL: 控制部分还有更多数据
  • MSG_MOREDATA: 数据部分还有更多数据

7. 返回值

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

常见错误码:

  • EBADF: fildes 不是有效的文件描述符
  • EINVAL: 参数无效
  • EIO: I/O 错误
  • ENOSTR: fildes 不是 STREAMS 设备
  • ENOSR: 没有足够的 STREAMS 资源
  • EAGAIN: 非阻塞模式下无数据可读

8. 相似函数或关联函数

  • putpmsg: 发送带优先级的消息
  • getmsg: 获取普通消息(不区分优先级)
  • putmsg: 发送普通消息
  • ioctl: 控制 STREAMS 设备
  • poll/select: 检查 STREAMS 文件描述符状态

9. 示例代码

示例1:基础用法 – 简单的优先级消息接收

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stropts.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

// 注意:这个示例在大多数 Linux 系统上可能无法运行
// 因为 Linux 不完全支持 STREAMS

int main() {
    int fd;
    struct strbuf ctlbuf, databuf;
    char ctl_data[256], data_buf[1024];
    int band, flags;
    
    printf("=== getpmsg 基础示例 ===\n\n");
    
    // 初始化缓冲区结构
    ctlbuf.maxlen = sizeof(ctl_data);
    ctlbuf.buf = ctl_data;
    ctlbuf.len = 0;
    
    databuf.maxlen = sizeof(data_buf);
    databuf.buf = data_buf;
    databuf.len = 0;
    
    band = 0;   // 优先级
    flags = 0;  // 接收标志
    
    printf("注意: getpmsg 主要用于 STREAMS 系统\n");
    printf("在大多数 Linux 系统上可能不可用\n\n");
    
    printf("参数设置:\n");
    printf("  控制缓冲区最大长度: %d\n", ctlbuf.maxlen);
    printf("  数据缓冲区最大长度: %d\n", databuf.maxlen);
    printf("  优先级 (band): %d\n", band);
    printf("  标志: %d\n", flags);
    
    printf("\n如果在支持 STREAMS 的系统上,可以这样调用:\n");
    printf("result = getpmsg(fd, &ctlbuf, &databuf, &band, &flags);\n");
    
    return 0;
}

示例2:模拟 STREAMS 优先级消息处理

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// 模拟的 STREAMS 消息结构
struct simulated_pmsg {
    int band;              // 消息优先级 (0-255)
    int control_len;       // 控制数据长度
    char control_data[256]; // 控制数据
    int data_len;          // 数据长度
    char data[1024];       // 实际数据
    time_t timestamp;      // 时间戳
};

// 模拟的消息队列
struct priority_message_queue {
    int count;
    struct simulated_pmsg messages[20];
};

// 全局消息队列
struct priority_message_queue msg_queue = {0};

// 模拟的 strbuf 结构
struct simulated_strbuf {
    int maxlen;
    int len;
    char *buf;
};

// 标志定义(模拟)
#define MSG_HIPRI     0x01
#define MSG_ANY       0x02
#define MSG_BAND      0x04
#define MSG_MORECTL   0x08
#define MSG_MOREDATA  0x10

// 向队列添加优先级消息
int add_priority_message(int band, const char *control, const char *data) {
    if (msg_queue.count >= 20) {
        printf("消息队列已满\n");
        return -1;
    }
    
    int index = msg_queue.count++;
    msg_queue.messages[index].band = band;
    msg_queue.messages[index].timestamp = time(NULL);
    
    if (control) {
        msg_queue.messages[index].control_len = strlen(control) + 1;
        strncpy(msg_queue.messages[index].control_data, control, 255);
        msg_queue.messages[index].control_data[255] = '\0';
    } else {
        msg_queue.messages[index].control_len = 0;
        msg_queue.messages[index].control_data[0] = '\0';
    }
    
    if (data) {
        msg_queue.messages[index].data_len = strlen(data) + 1;
        strncpy(msg_queue.messages[index].data, data, 1023);
        msg_queue.messages[index].data[1023] = '\0';
    } else {
        msg_queue.messages[index].data_len = 0;
        msg_queue.messages[index].data[0] = '\0';
    }
    
    printf("添加优先级消息: band=%d, 控制='%s', 数据='%s'\n",
           band, control ? control : "无", data ? data : "无");
    
    return 0;
}

// 模拟的 getpmsg 实现
int simulated_getpmsg(struct simulated_strbuf *ctlptr,
                      struct simulated_strbuf *dataptr,
                      int *bandp, int *flagsp) {
    
    if (msg_queue.count == 0) {
        printf("消息队列为空\n");
        return -1;
    }
    
    int msg_index = -1;
    int target_band = *bandp;
    int flags = *flagsp;
    
    printf("查找消息: band=%d, flags=0x%x\n", target_band, flags);
    
    // 根据标志查找消息
    if (flags & MSG_HIPRI) {
        // 查找最高优先级消息
        int max_band = -1;
        for (int i = 0; i < msg_queue.count; i++) {
            if (msg_queue.messages[i].band > max_band) {
                max_band = msg_queue.messages[i].band;
                msg_index = i;
            }
        }
        printf("查找最高优先级消息: band=%d\n", max_band);
    }
    else if (flags & MSG_BAND) {
        // 查找指定优先级消息
        for (int i = 0; i < msg_queue.count; i++) {
            if (msg_queue.messages[i].band == target_band) {
                msg_index = i;
                break;
            }
        }
        printf("查找指定优先级消息: band=%d\n", target_band);
    }
    else if (flags & MSG_ANY) {
        // 查找任何消息(通常按顺序)
        msg_index = 0;
        printf("查找任意消息\n");
    }
    else {
        // 默认行为:查找最高优先级
        int max_band = -1;
        for (int i = 0; i < msg_queue.count; i++) {
            if (msg_queue.messages[i].band > max_band) {
                max_band = msg_queue.messages[i].band;
                msg_index = i;
            }
        }
        printf("默认查找最高优先级消息: band=%d\n", max_band);
    }
    
    if (msg_index == -1) {
        printf("未找到符合条件的消息\n");
        return -1;
    }
    
    // 获取消息
    struct simulated_pmsg *msg = &msg_queue.messages[msg_index];
    
    // 复制控制数据
    if (ctlptr && ctlptr->buf) {
        int copy_len = (msg->control_len < ctlptr->maxlen) ? 
                       msg->control_len : ctlptr->maxlen;
        memcpy(ctlptr->buf, msg->control_data, copy_len);
        ctlptr->len = copy_len;
        printf("复制控制数据: %d 字节\n", copy_len);
    }
    
    // 复制数据
    if (dataptr && dataptr->buf) {
        int copy_len = (msg->data_len < dataptr->maxlen) ? 
                       msg->data_len : dataptr->maxlen;
        memcpy(dataptr->buf, msg->data, copy_len);
        dataptr->len = copy_len;
        printf("复制数据: %d 字节\n", copy_len);
    }
    
    // 更新返回参数
    *bandp = msg->band;
    *flagsp = (msg->band > 100) ? MSG_HIPRI : 0;  // 模拟高优先级
    
    // 从队列中移除消息
    for (int i = msg_index; i < msg_queue.count - 1; i++) {
        msg_queue.messages[i] = msg_queue.messages[i + 1];
    }
    msg_queue.count--;
    
    printf("成功接收消息: band=%d\n", *bandp);
    return 0;
}

// 显示队列状态
void show_priority_queue() {
    printf("\n=== 优先级消息队列状态 ===\n");
    printf("消息数量: %d\n", msg_queue.count);
    
    for (int i = 0; i < msg_queue.count; i++) {
        printf("消息 %d: band=%d, 时间=%s",
               i, msg_queue.messages[i].band,
               ctime(&msg_queue.messages[i].timestamp));
        printf("  控制: %s\n", msg_queue.messages[i].control_data);
        printf("  数据: %s\n", msg_queue.messages[i].data);
    }
}

int main() {
    struct simulated_strbuf ctlbuf, databuf;
    char ctl_buffer[256], data_buffer[1024];
    int band, flags;
    int result;
    
    printf("=== STREAMS 优先级消息处理模拟 ===\n\n");
    
    // 初始化缓冲区
    ctlbuf.maxlen = sizeof(ctl_buffer);
    ctlbuf.buf = ctl_buffer;
    ctlbuf.len = 0;
    
    databuf.maxlen = sizeof(data_buffer);
    databuf.buf = data_buffer;
    databuf.len = 0;
    
    // 添加测试消息(不同优先级)
    printf("添加测试消息...\n");
    add_priority_message(50, "NORMAL_CTL", "普通优先级消息");
    add_priority_message(150, "HIGH_CTL", "高优先级消息");
    add_priority_message(25, "LOW_CTL", "低优先级消息");
    add_priority_message(200, "CRITICAL_CTL", "关键优先级消息");
    add_priority_message(75, "MEDIUM_CTL", "中等优先级消息");
    
    show_priority_queue();
    
    // 测试1: 接收最高优先级消息
    printf("\n--- 测试1: 接收最高优先级消息 ---\n");
    band = 0;
    flags = MSG_HIPRI;
    result = simulated_getpmsg(&ctlbuf, &databuf, &band, &flags);
    
    if (result == 0) {
        printf("成功接收最高优先级消息:\n");
        printf("  优先级: %d\n", band);
        printf("  控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
        printf("  数据: %.*s\n", databuf.len, databuf.buf);
        printf("  消息标志: 0x%x\n", flags);
    }
    
    // 测试2: 接收指定优先级消息
    printf("\n--- 测试2: 接收指定优先级消息 (band=75) ---\n");
    band = 75;
    flags = MSG_BAND;
    result = simulated_getpmsg(&ctlbuf, &databuf, &band, &flags);
    
    if (result == 0) {
        printf("成功接收指定优先级消息:\n");
        printf("  优先级: %d\n", band);
        printf("  控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
        printf("  数据: %.*s\n", databuf.len, databuf.buf);
        printf("  消息标志: 0x%x\n", flags);
    }
    
    // 测试3: 接收任意消息
    printf("\n--- 测试3: 接收任意消息 ---\n");
    band = 0;
    flags = MSG_ANY;
    result = simulated_getpmsg(&ctlbuf, &databuf, &band, &flags);
    
    if (result == 0) {
        printf("成功接收任意消息:\n");
        printf("  优先级: %d\n", band);
        printf("  控制数据: %.*s\n", ctlbuf.len, ctlbuf.buf);
        printf("  数据: %.*s\n", databuf.len, databuf.buf);
        printf("  消息标志: 0x%x\n", flags);
    }
    
    show_priority_queue();
    
    printf("\n=== 优先级消息概念说明 ===\n");
    printf("STREAMS 优先级消息系统:\n");
    printf("1. 消息按优先级 (band) 分类\n");
    printf("2. 优先级范围: 0-255 (数值越高优先级越高)\n");
    printf("3. 可以按优先级选择性接收消息\n");
    printf("4. 支持高优先级消息抢占\n");
    
    return 0;
}

示例3:完整的优先级消息管理系统

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// 消息类型定义
#define BAND_LOW      25    // 低优先级
#define BAND_NORMAL   50    // 普通优先级
#define BAND_MEDIUM   100   // 中等优先级
#define BAND_HIGH     150   // 高优先级
#define BAND_CRITICAL 200   // 关键优先级

// 消息队列管理器
struct message_manager {
    int total_messages;
    int processed_messages;
    int dropped_messages;
    struct {
        int band;
        int msg_id;
        char type[32];
        char content[256];
        time_t created_time;
        time_t processed_time;
    } queue[50];
};

// 全局消息管理器
struct message_manager msg_mgr = {0};

// 标志定义
#define MSG_HIPRI     0x01
#define MSG_ANY       0x02
#define MSG_BAND      0x04

// 模拟的 strbuf 结构
struct stream_buffer {
    int maxlen;
    int len;
    char *buf;
};

// 添加消息到队列
int queue_message(int band, const char *type, const char *content) {
    if (msg_mgr.total_messages >= 50) {
        msg_mgr.dropped_messages++;
        printf("警告: 消息队列已满,丢弃消息\n");
        return -1;
    }
    
    int index = msg_mgr.total_messages++;
    msg_mgr.queue[index].band = band;
    msg_mgr.queue[index].msg_id = msg_mgr.total_messages;
    strncpy(msg_mgr.queue[index].type, type, 31);
    msg_mgr.queue[index].type[31] = '\0';
    strncpy(msg_mgr.queue[index].content, content, 255);
    msg_mgr.queue[index].content[255] = '\0';
    msg_mgr.queue[index].created_time = time(NULL);
    msg_mgr.queue[index].processed_time = 0;
    
    printf("入队消息 #%d: band=%d, 类型=%s\n", 
           msg_mgr.queue[index].msg_id, band, type);
    
    return 0;
}

// 模拟的 getpmsg 实现
int advanced_getpmsg(struct stream_buffer *ctlptr,
                     struct stream_buffer *dataptr,
                     int *bandp, int *flagsp) {
    
    if (msg_mgr.total_messages == 0) {
        return -1;  // 队列为空
    }
    
    int msg_index = -1;
    int target_band = *bandp;
    int flags = *flagsp;
    
    // 根据标志选择消息
    if (flags & MSG_HIPRI) {
        // 查找最高优先级消息
        int max_band = -1;
        for (int i = 0; i < msg_mgr.total_messages; i++) {
            if (msg_mgr.queue[i].band > max_band) {
                max_band = msg_mgr.queue[i].band;
                msg_index = i;
            }
        }
    }
    else if (flags & MSG_BAND) {
        // 查找指定优先级消息
        for (int i = 0; i < msg_mgr.total_messages; i++) {
            if (msg_mgr.queue[i].band == target_band) {
                msg_index = i;
                break;
            }
        }
    }
    else {
        // 默认:按顺序处理
        msg_index = 0;
    }
    
    if (msg_index == -1) {
        return -1;  // 未找到消息
    }
    
    // 处理消息
    struct {
        int band;
        int msg_id;
        char type[32];
        char content[256];
        time_t created_time;
        time_t processed_time;
    } *msg = &msg_mgr.queue[msg_index];
    
    msg->processed_time = time(NULL);
    msg_mgr.processed_messages++;
    
    // 准备返回数据
    if (ctlptr && ctlptr->buf) {
        char ctl_info[256];
        snprintf(ctl_info, sizeof(ctl_info), 
                "MSG_ID=%d,BAND=%d,TYPE=%s",
                msg->msg_id, msg->band, msg->type);
        
        int copy_len = (strlen(ctl_info) + 1 < ctlptr->maxlen) ? 
                       strlen(ctl_info) + 1 : ctlptr->maxlen;
        memcpy(ctlptr->buf, ctl_info, copy_len - 1);
        ctlptr->buf[copy_len - 1] = '\0';
        ctlptr->len = copy_len - 1;
    }
    
    if (dataptr && dataptr->buf) {
        int copy_len = (strlen(msg->content) + 1 < dataptr->maxlen) ? 
                       strlen(msg->content) + 1 : dataptr->maxlen;
        memcpy(dataptr->buf, msg->content, copy_len - 1);
        dataptr->buf[copy_len - 1] = '\0';
        dataptr->len = copy_len - 1;
    }
    
    // 更新返回参数
    *bandp = msg->band;
    *flagsp = (msg->band >= BAND_HIGH) ? MSG_HIPRI : 0;
    
    // 从队列中移除消息
    for (int i = msg_index; i < msg_mgr.total_messages - 1; i++) {
        msg_mgr.queue[i] = msg_mgr.queue[i + 1];
    }
    msg_mgr.total_messages--;
    
    return 0;
}

// 显示系统统计
void show_system_stats() {
    printf("\n=== 消息系统统计 ===\n");
    printf("总消息数: %d\n", msg_mgr.total_messages);
    printf("已处理消息: %d\n", msg_mgr.processed_messages);
    printf("丢弃消息: %d\n", msg_mgr.dropped_messages);
    printf("队列中消息: %d\n", msg_mgr.total_messages);
}

// 显示队列内容
void show_queue_contents() {
    printf("\n=== 队列内容 ===\n");
    if (msg_mgr.total_messages == 0) {
        printf("队列为空\n");
        return;
    }
    
    printf("优先级  ID    类型           内容\n");
    printf("-------- ----  -------------- ------------------------\n");
    
    for (int i = 0; i < msg_mgr.total_messages; i++) {
        printf("%-8d %-4d  %-14s %s\n",
               msg_mgr.queue[i].band,
               msg_mgr.queue[i].msg_id,
               msg_mgr.queue[i].type,
               msg_mgr.queue[i].content);
    }
}

// 按优先级分类统计
void show_priority_statistics() {
    int band_counts[5] = {0};  // 低、普通、中等、高、关键
    
    for (int i = 0; i < msg_mgr.total_messages; i++) {
        int band = msg_mgr.queue[i].band;
        if (band <= BAND_LOW) band_counts[0]++;
        else if (band <= BAND_NORMAL) band_counts[1]++;
        else if (band <= BAND_MEDIUM) band_counts[2]++;
        else if (band <= BAND_HIGH) band_counts[3]++;
        else band_counts[4]++;
    }
    
    printf("\n=== 优先级统计 ===\n");
    printf("低优先级 (0-25):     %d 条消息\n", band_counts[0]);
    printf("普通优先级 (26-50):  %d 条消息\n", band_counts[1]);
    printf("中等优先级 (51-100): %d 条消息\n", band_counts[2]);
    printf("高优先级 (101-150):  %d 条消息\n", band_counts[3]);
    printf("关键优先级 (151-255): %d 条消息\n", band_counts[4]);
}

int main() {
    struct stream_buffer ctlbuf, databuf;
    char ctl_buffer[256], data_buffer[1024];
    int band, flags;
    int result;
    
    printf("=== 高级优先级消息管理系统 ===\n\n");
    
    // 初始化缓冲区
    ctlbuf.maxlen = sizeof(ctl_buffer);
    ctlbuf.buf = ctl_buffer;
    ctlbuf.len = 0;
    
    databuf.maxlen = sizeof(data_buffer);
    databuf.buf = data_buffer;
    databuf.len = 0;
    
    // 添加各种优先级的消息
    printf("初始化消息队列...\n");
    queue_message(BAND_CRITICAL, "ALERT", "系统紧急告警:磁盘空间不足");
    queue_message(BAND_HIGH, "ERROR", "应用程序错误:数据库连接失败");
    queue_message(BAND_MEDIUM, "WARNING", "系统警告:CPU使用率过高");
    queue_message(BAND_NORMAL, "INFO", "用户登录成功");
    queue_message(BAND_LOW, "DEBUG", "调试信息:函数调用跟踪");
    queue_message(BAND_CRITICAL, "ALERT", "安全告警:多次登录失败");
    queue_message(BAND_HIGH, "ERROR", "网络错误:连接超时");
    queue_message(BAND_MEDIUM, "NOTICE", "系统通知:配置文件已更新");
    
    show_system_stats();
    show_queue_contents();
    show_priority_statistics();
    
    // 处理消息的示例
    printf("\n=== 消息处理演示 ===\n");
    
    // 1. 处理最高优先级消息
    printf("\n--- 处理最高优先级消息 ---\n");
    band = 0;
    flags = MSG_HIPRI;
    result = advanced_getpmsg(&ctlbuf, &databuf, &band, &flags);
    
    if (result == 0) {
        printf("处理成功:\n");
        printf("  优先级: %d\n", band);
        printf("  控制信息: %s\n", ctlbuf.buf);
        printf("  消息内容: %s\n", databuf.buf);
        printf("  消息标志: %s\n", (flags & MSG_HIPRI) ? "高优先级" : "普通优先级");
    }
    
    // 2. 处理指定优先级消息
    printf("\n--- 处理中等优先级消息 ---\n");
    band = BAND_MEDIUM;
    flags = MSG_BAND;
    result = advanced_getpmsg(&ctlbuf, &databuf, &band, &flags);
    
    if (result == 0) {
        printf("处理成功:\n");
        printf("  优先级: %d\n", band);
        printf("  控制信息: %s\n", ctlbuf.buf);
        printf("  消息内容: %s\n", databuf.buf);
    }
    
    // 3. 处理任意消息
    printf("\n--- 处理任意消息 ---\n");
    band = 0;
    flags = MSG_ANY;
    result = advanced_getpmsg(&ctlbuf, &databuf, &band, &flags);
    
    if (result == 0) {
        printf("处理成功:\n");
        printf("  优先级: %d\n", band);
        printf("  控制信息: %s\n", ctlbuf.buf);
        printf("  消息内容: %s\n", databuf.buf);
    }
    
    // 显示处理后的状态
    show_system_stats();
    show_queue_contents();
    
    printf("\n=== STREAMS 优先级消息系统特点 ===\n");
    printf("1. 支持 0-255 级优先级\n");
    printf("2. 可以按优先级选择性接收消息\n");
    printf("3. 高优先级消息可以抢占处理\n");
    printf("4. 适用于实时系统和关键任务应用\n");
    printf("\nLinux 替代方案:\n");
    printf("- 实时信号 (RT signals)\n");
    printf("- D-Bus 消息系统\n");
    printf("- systemd journal\n");
    printf("- 自定义优先级队列\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o getpmsg_example1 example1.c
gcc -o getpmsg_example2 example2.c
gcc -o getpmsg_example3 example3.c

# 运行示例
./getpmsg_example1
./getpmsg_example2
./getpmsg_example3

STREAMS 系统检查

# 检查系统是否支持 STREAMS
ls /usr/include/stropts.h

# 在 Solaris 等系统上编译
gcc -D_SOLARIS -o getpmsg_real example_real.c -lstrmi

# 查看 STREAMS 相关信息
modinfo | grep stream

重要注意事项

  1. 系统支持getpmsg 主要在 System V Unix 系统中可用
  2. Linux 限制: 大多数 Linux 系统不完全支持 STREAMS
  3. 移植性: 代码可移植性较差
  4. 优先级范围: band 值范围为 0-255
  5. 错误处理: 始终检查返回值和 errno

现代 Linux 替代方案

使用实时信号

#include <signal.h>
#include <sys/types.h>

// 发送带优先级的实时信号
int send_priority_signal(pid_t pid, int sig, int priority) {
    union sigval value;
    value.sival_int = priority;
    return sigqueue(pid, sig, value);
}

使用自定义优先级队列

#include <pthread.h>
#include <sys/queue.h>

// 自定义优先级消息队列
struct priority_msg {
    int priority;
    void *data;
    SLIST_ENTRY(priority_msg) entries;
};

SLIST_HEAD(msg_head, priority_msg) msg_queue = SLIST_HEAD_INITIALIZER(msg_queue);

实际应用场景

  1. 实时系统: 需要按优先级处理消息的实时应用
  2. 网络协议: 实现复杂的网络协议栈
  3. 设备驱动: 设备驱动中的消息处理
  4. 系统管理: 系统管理工具的优先级消息
  5. 多媒体应用: 音视频处理中的实时消息

优先级消息处理流程

消息生产者
    ↓ (putpmsg)
STREAMS 系统
    ↓ (按优先级排队)
消息消费者
    ↓ (getpmsg)
应用层处理

虽然 getpmsg 在现代 Linux 系统中使用较少,但它体现了优先级消息处理的重要概念。在实际开发中,可以根据需求选择合适的现代替代方案来实现类似的功能。

发表在 linux文章 | 留下评论

getppid系统调用及示例

getppid – 获取父进程ID

getppid是Linux系统调用,用于获取当前进程的父进程ID(PPID)。该函数无需参数,总是成功返回父进程ID,在进程管理和监控中非常有用。示例代码展示了基础用法、父子进程关系、孤儿进程现象以及构建进程树结构,通过getpid()、fork()等相关函数配合使用,可完整呈现Unix/Linux的进程层级关系。当父进程终止后,子进程PPID会变为1(init进程),成为孤儿进程。

1. 函数介绍

getppid 是一个 Linux 系统调用,用于获取当前进程的父进程 ID(Parent Process ID)。每个进程(除了初始进程)都有一个父进程,父进程 ID 是进程管理、进程监控和进程间通信的重要信息。

在 Unix/Linux 系统中,进程以树状结构组织,getppid 提供了访问这种父子关系的方法,对于进程监控、调试和管理工具非常有用。

2. 函数原型

#include <sys/types.h>
#include <unistd.h>

pid_t getppid(void);

3. 功能

返回当前进程的父进程 ID。这是一个只读操作,不会修改任何系统状态。

4. 参数

  • 无参数

5. 返回值

  • 成功时:返回父进程 ID(pid_t 类型)
  • 不会失败:该系统调用总是成功返回

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

  • getpid(): 获取当前进程 ID
  • getpgid(): 获取进程组 ID
  • getpgrp(): 获取进程组 ID(当前进程)
  • getsid(): 获取会话 ID
  • fork(): 创建新进程
  • wait()waitpid(): 等待子进程
  • kill(): 向进程发送信号
  • prctl(): 进程控制
  • /proc/[pid]/stat: 查看进程状态信息

7. 示例代码

示例1:基本使用 – 获取进程父子关系

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t my_pid, parent_pid;
    
    printf("=== 进程父子关系信息 ===\n");
    
    // 获取当前进程 ID
    my_pid = getpid();
    printf("当前进程 ID: %d\n", my_pid);
    
    // 获取父进程 ID
    parent_pid = getppid();
    printf("父进程 ID: %d\n", parent_pid);
    
    // 获取父进程的父进程 ID(祖父进程)
    // 注意:我们无法直接获取祖父进程 ID,需要通过其他方式
    
    printf("进程关系: %d -> %d (父 -> 子)\n", parent_pid, my_pid);
    
    // 检查特殊情况
    if (parent_pid == 1) {
        printf("注意: 父进程是 init 进程 (PID 1)\n");
    } else if (parent_pid == 0) {
        printf("注意: 父进程是调度进程 (内核进程)\n");
    }
    
    return 0;
}

示例2:父子进程关系演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

void print_process_info(const char *label) {
    printf("%s:\n", label);
    printf("  进程 ID: %d\n", getpid());
    printf("  父进程 ID: %d\n", getppid());
    printf("  进程组 ID: %d\n", getpgrp());
    printf("\n");
}

int main() {
    pid_t child_pid;
    
    printf("=== 父子进程关系演示 ===\n");
    
    // 显示父进程信息
    print_process_info("父进程(创建子进程前)");
    
    // 创建子进程
    child_pid = fork();
    
    if (child_pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }
    
    if (child_pid == 0) {
        // 子进程
        print_process_info("子进程");
        
        // 子进程睡眠一段时间
        printf("子进程睡眠 3 秒...\n");
        sleep(3);
        
        // 再次检查父进程 ID(父进程可能已经结束)
        printf("子进程睡眠结束,再次检查父进程 ID:\n");
        printf("  当前进程 ID: %d\n", getpid());
        printf("  父进程 ID: %d\n", getppid());
        
        if (getppid() == 1) {
            printf("  父进程已结束,现在由 init 进程收养\n");
        }
        
    } else {
        // 父进程
        printf("父进程创建了子进程,子进程 ID: %d\n", child_pid);
        print_process_info("父进程(创建子进程后)");
        
        // 父进程立即结束
        printf("父进程即将结束...\n");
        exit(0);
    }
    
    return 0;
}

示例3:孤儿进程演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t child_pid;
    
    printf("=== 孤儿进程演示 ===\n");
    printf("父进程 ID: %d\n", getpid());
    
    child_pid = fork();
    
    if (child_pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }
    
    if (child_pid == 0) {
        // 子进程
        printf("子进程启动,PID: %d, PPID: %d\n", getpid(), getppid());
        
        // 子进程循环检查父进程状态
        for (int i = 0; i < 10; i++) {
            printf("子进程 %d: 父进程 ID = %d\n", getpid(), getppid());
            sleep(2);
        }
        
        printf("子进程 %d 结束\n", getpid());
        
    } else {
        // 父进程
        printf("父进程创建了子进程 %d\n", child_pid);
        printf("父进程即将结束,子进程将成为孤儿进程\n");
        
        // 父进程很快结束
        sleep(1);
        printf("父进程 %d 结束\n", getpid());
        exit(0);
    }
    
    return 0;
}

示例4:进程树构建和分析

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

typedef struct {
    pid_t pid;
    pid_t ppid;
    pid_t pgid;
    int generation;
    char name[32];
} process_info_t;

void collect_process_info(process_info_t *info, int generation, const char *name) {
    info->pid = getpid();
    info->ppid = getppid();
    info->pgid = getpgrp();
    info->generation = generation;
    snprintf(info->name, sizeof(info->name), "%s", name);
}

void print_process_tree(process_info_t *processes, int count) {
    printf("\n=== 进程树结构 ===\n");
    printf("%-8s %-8s %-8s %-12s %s\n", "PID", "PPID", "PGID", "代数", "名称");
    printf("%-8s %-8s %-8s %-12s %s\n", "---", "----", "----", "----", "----");
    
    for (int i = 0; i < count; i++) {
        printf("%-8d %-8d %-8d %-12d %s\n",
               processes[i].pid,
               processes[i].ppid,
               processes[i].pgid,
               processes[i].generation,
               processes[i].name);
    }
}

int main() {
    pid_t pid1, pid2, pid3;
    process_info_t processes[4];
    int process_count = 0;
    
    printf("=== 复杂进程树演示 ===\n");
    
    // 收集根进程信息
    collect_process_info(&processes[process_count++], 0, "根进程");
    
    // 创建第一层子进程
    pid1 = fork();
    if (pid1 == 0) {
        // 第一个子进程
        collect_process_info(&processes[process_count++], 1, "子进程1");
        
        // 第一个子进程创建孙子进程
        pid2 = fork();
        if (pid2 == 0) {
            // 孙子进程1
            collect_process_info(&processes[process_count++], 2, "孙子进程1");
            sleep(3);
            exit(0);
        }
        
        // 第一个子进程再创建另一个孙子进程
        pid3 = fork();
        if (pid3 == 0) {
            // 孙子进程2
            collect_process_info(&processes[process_count++], 2, "孙子进程2");
            sleep(3);
            exit(0);
        }
        
        // 等待孙子进程结束
        wait(NULL);
        wait(NULL);
        exit(0);
    }
    
    // 等待第一层子进程结束
    wait(NULL);
    
    // 在实际应用中,这里需要通过 IPC 机制收集所有进程信息
    // 这里为了演示,只显示当前进程信息
    printf("当前进程信息:\n");
    printf("  PID: %d\n", getpid());
    printf("  PPID: %d\n", getppid());
    printf("  PGID: %d\n", getpgrp());
    
    return 0;
}

示例5:进程监控和管理工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

// 检查父进程是否还存在
int is_parent_alive() {
    pid_t ppid = getppid();
    
    // 特殊情况:父进程是 init (PID 1) 或内核进程 (PID 0)
    if (ppid == 1 || ppid == 0) {
        return 1;  // 认为父进程"存在"(虽然可能已结束)
    }
    
    // 尝试向父进程发送 0 信号来检查是否存在
    if (kill(ppid, 0) == 0) {
        return 1;  // 父进程存在
    } else {
        if (errno == ESRCH) {
            return 0;  // 父进程不存在
        } else {
            return 1;  // 其他错误,假设父进程存在
        }
    }
}

// 监控父进程状态
void monitor_parent_status() {
    pid_t original_ppid = getppid();
    pid_t current_ppid;
    
    printf("开始监控父进程状态...\n");
    printf("原始父进程 ID: %d\n", original_ppid);
    
    while (1) {
        current_ppid = getppid();
        
        if (current_ppid != original_ppid) {
            printf("父进程 ID 发生变化: %d -> %d\n", original_ppid, current_ppid);
            
            if (current_ppid == 1) {
                printf("进程已被 init 进程收养\n");
                break;
            }
            
            original_ppid = current_ppid;
        }
        
        if (!is_parent_alive()) {
            printf("检测到父进程可能已结束\n");
            break;
        }
        
        sleep(1);
    }
}

// 守护进程检查
int is_daemon_process() {
    pid_t ppid = getppid();
    
    // 守护进程的特征
    if (ppid == 1) {
        return 1;  // 由 init 进程管理
    }
    
    // 检查是否在后台运行
    if (getpgrp() != tcgetpgrp(STDIN_FILENO)) {
        return 1;  // 不在前台进程组
    }
    
    return 0;
}

int main() {
    printf("=== 进程状态监控工具 ===\n");
    
    printf("当前进程信息:\n");
    printf("  PID: %d\n", getpid());
    printf("  PPID: %d\n", getppid());
    printf("  PGID: %d\n", getpgrp());
    
    // 检查是否为守护进程
    if (is_daemon_process()) {
        printf("✓ 当前进程是守护进程或后台进程\n");
    } else {
        printf("✗ 当前进程是前台进程\n");
    }
    
    // 检查父进程状态
    if (is_parent_alive()) {
        printf("✓ 父进程存在\n");
    } else {
        printf("✗ 父进程不存在\n");
    }
    
    // 如果需要,可以启动监控
    char choice;
    printf("\n是否启动父进程监控? (y/N): ");
    if (scanf("%c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
        monitor_parent_status();
    }
    
    return 0;
}

8. 特殊父进程 ID

// 特殊的父进程 ID 值
if (ppid == 0) {
    // 内核进程或调度进程
    printf("父进程是内核进程\n");
} else if (ppid == 1) {
    // init 进程或 systemd
    printf("父进程是 init 进程\n");
} else if (ppid == getppid()) {
    // 自己是父进程(不太可能)
    printf("异常:自己是自己的父进程\n");
}

9. 实际应用场景

场景1:守护进程实现

void create_daemon() {
    pid_t pid = fork();
    if (pid == 0) {
        // 第一次 fork 的子进程
        setsid();  // 创建新会话
        
        pid = fork();
        if (pid == 0) {
            // 第二次 fork 的子进程(真正的守护进程)
            // 检查父进程是否为 init
            if (getppid() == 1) {
                printf("成功创建守护进程\n");
            }
        } else {
            exit(0);  // 第一次 fork 的子进程退出
        }
    } else {
        wait(NULL);  // 等待第一次 fork 的子进程退出
        exit(0);     // 父进程退出
    }
}

场景2:进程监控

int monitor_process_tree(pid_t target_pid) {
    // 实现进程树监控逻辑
    pid_t current_ppid = getppid();
    // ... 监控逻辑
    return 0;
}

场景3:安全检查

int check_parent_process() {
    pid_t ppid = getppid();
    
    // 检查父进程是否为预期的进程
    if (ppid != expected_parent_pid) {
        syslog(LOG_WARNING, "父进程异常: %d", ppid);
        return -1;
    }
    
    return 0;
}

10. 注意事项

使用 getppid 时需要注意:

  1. 孤儿进程: 父进程结束后,子进程被 init 进程收养(PPID 变为 1)
  2. 竞态条件: 父进程可能在检查期间结束
  3. 权限问题: 通常可以访问父进程信息,但在某些安全环境中可能受限
  4. 跨平台兼容: 在所有 Unix-like 系统中都可用
  5. 性能考虑: 调用成本很低,可以频繁使用

11. 进程生命周期中的变化

// 进程生命周期中 PPID 的变化示例
void demonstrate_ppid_changes() {
    printf("进程生命周期 PPID 变化:\n");
    
    pid_t original_ppid = getppid();
    printf("1. 启动时 PPID: %d\n", original_ppid);
    
    // fork 后子进程的 PPID
    pid_t child = fork();
    if (child == 0) {
        printf("2. 子进程 PPID: %d\n", getppid());
        // 如果父进程结束,PPID 会变为 1
    }
}

总结

getppid 是一个简单但重要的系统调用,用于获取当前进程的父进程 ID。关键要点:

1. 总是成功: 不会失败,总是返回父进程 ID
2. 进程管理: 是进程管理和监控的基础信息
3. 孤儿处理: 理解父进程结束后的孤儿进程处理机制
4. 安全相关: 可用于进程身份验证和安全检查
5. 系统工具: 广泛用于 ps、top 等系统工具

在编写需要了解进程关系的程序时,getppid 是必不可少的工具函数,它为程序提供了进程层次结构的重要信息。

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

发表在 linux文章 | 留下评论

getpriority系统调用及示例

getpriority – 获取进程或进程组的优先级

getpriority 是 Linux 系统调用,用于查询进程、进程组或用户的调度优先级。优先级范围 -20(最高)到 +19(最低)。函数原型为 int getpriority(int which, id_t who),参数 which 指定查询类型(PRIO_PROCESS/PRIO_PGRP/PRIO_USER),who 为对应 ID。成功返回优先级值,失败返回 -1 并设置 errno。需注意返回值可能为 -1(成功时),应先清除 errno 再调用。示例代码展示了如何获取当前进程、进程组和用户的优

1. 函数介绍

getpriority 是一个 Linux 系统调用,用于获取指定进程或进程组的调度优先级。它是 Unix/Linux 系统中进程调度管理的重要组成部分,允许程序查询进程的当前优先级设置。

在 Linux 系统中,进程优先级影响 CPU 调度,优先级高的进程会获得更多 CPU 时间。getpriority 与 setpriority 配合使用,提供了完整的优先级管理功能。

2. 函数原型

#include <sys/resource.h>

int getpriority(int which, id_t who);

3. 功能

获取指定进程、进程组或用户的所有进程的调度优先级。优先级值范围通常为 -20 到 +19,其中 -20 表示最高优先级,+19 表示最低优先级。

4. 参数

  • int which: 指定查询的类型
    • PRIO_PROCESS: 查询单个进程的优先级
    • PRIO_PGRP: 查询进程组中所有进程的优先级
    • PRIO_USER: 查询指定用户的所有进程的优先级
  • id_t who: 根据 which 参数指定的具体 ID
    • PRIO_PROCESS: 进程 ID(0 表示当前进程)
    • PRIO_PGRP: 进程组 ID(0 表示当前进程组)
    • PRIO_USER: 用户 ID(0 表示当前用户)

5. 返回值

  • 成功时:返回优先级值(范围 -20 到 +19)
  • 失败时:返回 -1,并设置 errno
  • 注意:由于成功时可能返回 -1,需要先清除 errno 再调用

6. 常见 errno 错误码

  • ESRCH: 指定的进程、进程组或用户不存在
  • EINVALwhich 参数无效
  • EPERM: 权限不足(无法访问指定进程的信息)
  • EACCES: 访问被拒绝(某些安全策略下)

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

  • setpriority(): 设置进程优先级
  • nice(): 调整当前进程的优先级
  • getrlimit()setrlimit(): 获取/设置资源限制
  • sched_getparam()sched_setparam(): 更高级的调度参数管理
  • sched_getscheduler()sched_setscheduler(): 获取/设置调度策略
  • getrusage(): 获取进程资源使用情况
  • /proc/[pid]/stat: 查看进程状态信息

8. 示例代码

示例1:基本使用 – 获取进程优先级

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

int safe_getpriority(int which, id_t who) {
    errno = 0;  // 必须先清除 errno
    int priority = getpriority(which, who);
    if (priority == -1 && errno != 0) {
        return -1;  // 真正的错误
    }
    return priority;
}

int main() {
    int priority;
    
    printf("=== 进程优先级获取 ===\n");
    
    // 获取当前进程的优先级
    priority = safe_getpriority(PRIO_PROCESS, 0);
    if (priority == -1) {
        perror("获取当前进程优先级失败");
    } else {
        printf("当前进程优先级: %d\n", priority);
    }
    
    // 获取当前进程组的优先级
    priority = safe_getpriority(PRIO_PGRP, 0);
    if (priority == -1) {
        perror("获取当前进程组优先级失败");
    } else {
        printf("当前进程组优先级: %d\n", priority);
    }
    
    // 获取当前用户的进程优先级
    priority = safe_getpriority(PRIO_USER, 0);
    if (priority == -1) {
        perror("获取当前用户进程优先级失败");
    } else {
        printf("当前用户进程优先级: %d\n", priority);
    }
    
    // 获取 init 进程的优先级
    priority = safe_getpriority(PRIO_PROCESS, 1);
    if (priority == -1) {
        if (errno == EPERM) {
            printf("权限不足,无法获取 init 进程优先级\n");
        } else {
            perror("获取 init 进程优先级失败");
        }
    } else {
        printf("init 进程优先级: %d\n", priority);
    }
    
    return 0;
}

示例2:错误处理和特殊情况

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

void test_getpriority(int which, id_t who, const char *description) {
    printf("\n测试 %s:\n", description);
    
    errno = 0;  // 清除 errno
    int priority = getpriority(which, who);
    
    if (priority == -1 && errno != 0) {
        printf("  getpriority 调用失败: %s\n", strerror(errno));
        switch (errno) {
            case ESRCH:
                printf("  原因: 指定的进程/组/用户不存在\n");
                break;
            case EINVAL:
                printf("  原因: 无效的 which 参数\n");
                break;
            case EPERM:
                printf("  原因: 权限不足\n");
                break;
            default:
                printf("  其他错误\n");
                break;
        }
    } else {
        printf("  优先级: %d\n", priority);
    }
}

int main() {
    printf("=== getpriority 错误处理测试 ===\n");
    
    // 测试正常情况
    test_getpriority(PRIO_PROCESS, 0, "当前进程");
    test_getpriority(PRIO_PGRP, 0, "当前进程组");
    test_getpriority(PRIO_USER, getuid(), "当前用户");
    
    // 测试无效参数
    test_getpriority(999, 0, "无效的 which 参数");
    
    // 测试不存在的进程
    test_getpriority(PRIO_PROCESS, 999999, "不存在的进程");
    
    // 测试不存在的进程组
    test_getpriority(PRIO_PGRP, 999999, "不存在的进程组");
    
    // 创建子进程进行测试
    pid_t child_pid = fork();
    if (child_pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }
    
    if (child_pid == 0) {
        // 子进程
        printf("\n=== 子进程信息 ===\n");
        printf("子进程 ID: %d\n", getpid());
        printf("父进程 ID: %d\n", getppid());
        
        // 子进程设置自己的优先级
        if (setpriority(PRIO_PROCESS, 0, 10) == 0) {
            printf("子进程成功设置优先级为 10\n");
        } else {
            perror("子进程设置优先级失败");
        }
        
        // 显示子进程优先级
        errno = 0;
        int child_priority = getpriority(PRIO_PROCESS, 0);
        if (!(child_priority == -1 && errno != 0)) {
            printf("子进程当前优先级: %d\n", child_priority);
        }
        
        // 子进程睡眠一段时间
        sleep(3);
        exit(0);
    } else {
        // 父进程
        printf("\n=== 父进程测试子进程 ===\n");
        printf("子进程 ID: %d\n", child_pid);
        
        // 父进程获取子进程优先级
        test_getpriority(PRIO_PROCESS, child_pid, "子进程(运行中)");
        
        // 等待子进程结束
        int status;
        waitpid(child_pid, &status, 0);
        printf("子进程已结束\n");
        
        // 再次测试已结束的进程
        test_getpriority(PRIO_PROCESS, child_pid, "已结束的子进程");
    }
    
    return 0;
}

示例3:优先级监控和管理工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>

typedef struct {
    pid_t pid;
    int priority;
    char process_name[256];
} process_priority_t;

int safe_getpriority(int which, id_t who) {
    errno = 0;
    int priority = getpriority(which, who);
    if (priority == -1 && errno != 0) {
        return -999;  // 特殊错误值
    }
    return priority;
}

void print_priority_info(const char *label, int priority) {
    if (priority == -999) {
        printf("%s: 无法获取\n", label);
    } else {
        printf("%s: %d", label, priority);
        if (priority < 0) {
            printf(" (高优先级)");
        } else if (priority > 0) {
            printf(" (低优先级)");
        } else {
            printf(" (正常优先级)");
        }
        printf("\n");
    }
}

void analyze_process_priorities() {
    printf("=== 进程优先级分析 ===\n");
    
    // 当前进程信息
    printf("当前进程信息:\n");
    printf("  PID: %d\n", getpid());
    printf("  UID: %d", getuid());
    struct passwd *pwd = getpwuid(getuid());
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("  GID: %d", getgid());
    struct group *grp = getgrgid(getgid());
    if (grp) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
    
    // 各种优先级信息
    int current_priority = safe_getpriority(PRIO_PROCESS, 0);
    print_priority_info("  当前进程优先级", current_priority);
    
    int pgrp_priority = safe_getpriority(PRIO_PGRP, 0);
    print_priority_info("  当前进程组优先级", pgrp_priority);
    
    int user_priority = safe_getpriority(PRIO_USER, getuid());
    print_priority_info("  当前用户进程优先级", user_priority);
    
    // 系统关键进程优先级
    printf("\n系统关键进程优先级:\n");
    
    int init_priority = safe_getpriority(PRIO_PROCESS, 1);
    print_priority_info("  init 进程 (PID 1)", init_priority);
    
    // 获取 shell 进程优先级
    pid_t shell_pid = getppid();
    int shell_priority = safe_getpriority(PRIO_PROCESS, shell_pid);
    printf("  父进程 (shell) PID %d", shell_pid);
    print_priority_info("", shell_priority);
}

void priority_statistics() {
    printf("\n=== 优先级统计信息 ===\n");
    
    // 获取当前用户的进程优先级范围
    int min_priority = 20, max_priority = -20;
    int sum_priority = 0, count = 0;
    
    // 这里简化处理,实际应用中可能需要扫描 /proc
    int current_priority = safe_getpriority(PRIO_PROCESS, 0);
    if (current_priority != -999) {
        min_priority = max_priority = sum_priority = current_priority;
        count = 1;
    }
    
    printf("当前会话优先级统计:\n");
    printf("  进程数量: %d\n", count);
    if (count > 0) {
        printf("  最低优先级: %d\n", min_priority);
        printf("  最高优先级: %d\n", max_priority);
        printf("  平均优先级: %.2f\n", (double)sum_priority / count);
    }
}

int main() {
    analyze_process_priorities();
    priority_statistics();
    
    // 交互式优先级查询
    printf("\n=== 交互式查询 ===\n");
    printf("输入进程 ID 查询优先级 (输入 0 退出): ");
    
    pid_t target_pid;
    while (scanf("%d", &target_pid) == 1 && target_pid != 0) {
        if (target_pid > 0) {
            int priority = safe_getpriority(PRIO_PROCESS, target_pid);
            if (priority != -999) {
                printf("进程 %d 的优先级: %d\n", target_pid, priority);
            } else {
                printf("无法获取进程 %d 的优先级\n", target_pid);
            }
        }
        printf("继续输入进程 ID (输入 0 退出): ");
    }
    
    return 0;
}

示例4:优先级调整和监控

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

volatile sig_atomic_t running = 1;

void signal_handler(int sig) {
    running = 0;
    printf("\n收到信号 %d,停止监控\n", sig);
}

int safe_getpriority(int which, id_t who) {
    errno = 0;
    int priority = getpriority(which, who);
    if (priority == -1 && errno != 0) {
        return -999;
    }
    return priority;
}

void monitor_priority_changes(pid_t target_pid, int duration) {
    int last_priority = -999;
    time_t start_time = time(NULL);
    time_t current_time;
    
    printf("开始监控进程 %d 的优先级变化 (%d 秒)...\n", target_pid, duration);
    printf("%-10s %-12s %s\n", "时间", "优先级", "状态");
    printf("%-10s %-12s %s\n", "----", "----", "----");
    
    while (running && (current_time = time(NULL)) - start_time < duration) {
        int current_priority = safe_getpriority(PRIO_PROCESS, target_pid);
        
        if (current_priority != -999) {
            char status[20] = "稳定";
            
            if (last_priority != -999 && current_priority != last_priority) {
                snprintf(status, sizeof(status), "变化 %d->%d", 
                        last_priority, current_priority);
            }
            
            printf("%-10ld %-12d %s\n", 
                   current_time - start_time, 
                   current_priority, 
                   status);
            
            last_priority = current_priority;
        } else {
            printf("%-10ld %-12s %s\n", 
                   current_time - start_time, 
                   "无法获取", 
                   "错误");
        }
        
        sleep(1);
    }
}

void demonstrate_priority_adjustment() {
    printf("=== 优先级调整演示 ===\n");
    
    int original_priority = safe_getpriority(PRIO_PROCESS, 0);
    printf("原始优先级: %d\n", original_priority);
    
    // 提高优先级(需要权限)
    printf("尝试提高优先级到 -5...\n");
    if (setpriority(PRIO_PROCESS, 0, -5) == 0) {
        int new_priority = safe_getpriority(PRIO_PROCESS, 0);
        printf("优先级调整成功: %d\n", new_priority);
    } else {
        if (errno == EPERM) {
            printf("权限不足,无法提高优先级(需要 root 权限)\n");
        } else {
            perror("优先级调整失败");
        }
    }
    
    // 降低优先级
    printf("尝试降低优先级到 10...\n");
    if (setpriority(PRIO_PROCESS, 0, 10) == 0) {
        int new_priority = safe_getpriority(PRIO_PROCESS, 0);
        printf("优先级调整成功: %d\n", new_priority);
    } else {
        perror("优先级调整失败");
    }
    
    // 恢复原始优先级
    if (original_priority != -999) {
        if (setpriority(PRIO_PROCESS, 0, original_priority) == 0) {
            printf("已恢复原始优先级: %d\n", 
                   safe_getpriority(PRIO_PROCESS, 0));
        }
    }
}

int main() {
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    
    demonstrate_priority_adjustment();
    
    printf("\n当前进程优先级: %d\n", safe_getpriority(PRIO_PROCESS, 0));
    
    // 如果需要,可以监控优先级变化
    char choice;
    printf("\n是否监控当前进程优先级变化? (y/N): ");
    getchar();  // 清除缓冲区
    if (scanf("%c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
        monitor_priority_changes(getpid(), 10);
    }
    
    return 0;
}

9. 优先级值说明

Linux 进程优先级系统:

// 优先级范围:-20 到 +19
// -20: 最高优先级
// 0:   默认优先级
// +19: 最低优先级

// 特殊优先级值
#define PRIO_MIN -20
#define PRIO_MAX 19

// 优先级类别
-20 to -1:  高优先级进程
0:          正常优先级进程
+1 to +19:  低优先级进程

10. 实际应用场景

场景1:系统监控工具

int is_high_priority_process(pid_t pid) {
    int priority = safe_getpriority(PRIO_PROCESS, pid);
    return (priority != -999 && priority < 0);
}

场景2:资源管理

void adjust_batch_job_priority() {
    // 批处理作业使用较低优先级
    setpriority(PRIO_PROCESS, 0, 15);
}

场景3:性能优化

void optimize_critical_process() {
    // 关键进程使用较高优先级
    setpriority(PRIO_PROCESS, 0, -5);
}

11. 注意事项

使用 getpriority 时的重要注意事项:

  1. 返回值检查: 必须先清除 errno,因为成功时可能返回 -1
  2. 权限问题: 访问其他进程信息可能需要适当权限
  3. 进程生命周期: 进程结束后相关信息可能不可用
  4. 实时性: 优先级可能在查询期间发生变化
  5. 系统限制: 某些优先级值可能需要特殊权限才能设置

12. 优先级与调度策略

// 不同调度策略的优先级处理
void explain_scheduling_policies() {
    printf("Linux 调度策略优先级说明:\n");
    printf("1. SCHED_FIFO: 实时优先级 1-99\n");
    printf("2. SCHED_RR:   实时优先级 1-99\n");
    printf("3. SCHED_OTHER: 静态优先级 -20 到 +19\n");
    printf("4. SCHED_BATCH: 批处理优化\n");
    printf("5. SCHED_IDLE:  空闲任务\n");
}

总结

getpriority 是进程优先级管理的重要函数,关键要点:

  1. 优先级查询: 获取进程、进程组或用户的调度优先级
  2. 错误处理: 特殊的返回值处理机制(需要清除 errno)
  3. 权限控制: 访问其他进程信息需要适当权限
  4. 系统管理: 在系统监控和资源管理中广泛使用
  5. 性能优化: 帮助识别和调整关键进程的优先级

正确使用 getpriority 可以帮助程序了解当前的调度状态,实现更精细的性能管理和资源控制。

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

发表在 linux文章 | 留下评论

getrandom系统调用及示例

getrandom 函数详解

getrandom是Linux系统中获取高质量随机数的系统调用,从内核熵池中提取真正的随机数据。它比传统rand()更安全可靠,适用于加密、安全令牌等场景。函数原型为ssize_t getrandom(void *buf, size_t buflen, unsigned int flags),支持GRND_RANDOM(阻塞模式)和GRND_NONBLOCK(非阻塞模式)两种标志位。文章详细介绍了函数参数、返回值、错误处理和示例代码,展示了如何生成随机数据和使用不同标志位。getrandom相比传统随机数设备更安全高效,是安全敏感应用的理想选择。

1. 函数介绍

getrandom 是 Linux 系统中用于获取高质量随机数的系统调用。可以把这个函数想象成一个”真随机数生成器”——它从系统的熵池中获取真正的随机数据,就像从大自然的噪声中提取随机性一样。

在计算机系统中,随机数非常重要:

  • 加密操作: 生成密钥、初始化向量等
  • 安全令牌: 会话 ID、验证码等
  • 模拟和游戏: 游戏中的随机事件
  • 负载均衡: 分布式系统中的随机选择

getrandom 提供了比传统 rand() 函数更安全、更高质量的随机数,特别适合安全相关的应用。

2. 函数原型

#include <sys/random.h>

ssize_t getrandom(void *buf, size_t buflen, unsigned int flags);

3. 功能

getrandom 函数用于从内核的随机数生成器中获取随机字节,并将其存储在提供的缓冲区中。它可以直接访问系统的熵源,提供密码学安全的随机数。

4. 参数

  • buf: 指向缓冲区的指针,用于存储生成的随机数据
  • buflen: 请求的随机数据字节数
  • flags: 控制随机数生成行为的标志位

5. 标志位(flags 参数)

标志说明
GRND_RANDOM0x0001使用 /dev/random 而不是 /dev/urandom
GRND_NONBLOCK0x0002非阻塞模式,熵不足时不等待

6. 标志位详细说明

GRND_RANDOM

  • 默认情况下,getrandom 使用 /dev/urandom(非阻塞)
  • 设置此标志后,使用 /dev/random(阻塞模式)
  • /dev/random 在熵不足时会阻塞,直到收集到足够熵

GRND_NONBLOCK

  • 默认情况下,如果熵池未初始化,函数会阻塞等待
  • 设置此标志后,如果熵不足则立即返回错误(errno = EAGAIN)

7. 返回值

  • 成功: 返回实际获取的随机字节数
  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EAGAIN: 非阻塞模式下熵不足
  • EFAULT: buf 指针无效
  • EINVAL: flags 参数无效
  • EIO: 系统错误

8. 相似函数或关联函数

  • /dev/random: 传统的阻塞随机数设备
  • /dev/urandom: 传统的非阻塞随机数设备
  • rand(): 标准库伪随机数生成器
  • random(): 更好的伪随机数生成器
  • arc4random(): BSD 风格的加密安全随机数
  • openssl RAND_bytes(): OpenSSL 库的随机数函数

9. 示例代码

示例1:基础用法 – 生成随机数据

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

// 将二进制数据转换为十六进制字符串
void bin_to_hex(const unsigned char *bin, size_t len, char *hex) {
    for (size_t i = 0; i < len; i++) {
        sprintf(hex + i * 2, "%02x", bin[i]);
    }
    hex[len * 2] = '\0';
}

// 将二进制数据转换为可打印字符(用于显示)
void bin_to_printable(const unsigned char *bin, size_t len, char *printable) {
    for (size_t i = 0; i < len; i++) {
        if (bin[i] >= 32 && bin[i] <= 126) {
            printable[i] = bin[i];
        } else {
            printable[i] = '.';
        }
    }
    printable[len] = '\0';
}

int main() {
    unsigned char random_data[32];
    char hex_string[65];
    char printable_string[33];
    ssize_t bytes_read;
    
    printf("=== getrandom 基础示例 ===\n\n");
    
    // 生成 32 字节的随机数据
    printf("生成 32 字节随机数据...\n");
    bytes_read = getrandom(random_data, sizeof(random_data), 0);
    
    if (bytes_read == -1) {
        perror("getrandom");
        return 1;
    }
    
    printf("成功生成 %zd 字节随机数据\n", bytes_read);
    
    // 显示十六进制格式
    bin_to_hex(random_data, bytes_read, hex_string);
    printf("十六进制: %s\n", hex_string);
    
    // 显示可打印字符格式
    bin_to_printable(random_data, bytes_read, printable_string);
    printf("可打印: %s\n", printable_string);
    
    // 生成不同长度的随机数据
    printf("\n生成不同长度的随机数据:\n");
    for (int len = 1; len <= 16; len++) {
        unsigned char small_data[16];
        bytes_read = getrandom(small_data, len, 0);
        if (bytes_read > 0) {
            bin_to_hex(small_data, bytes_read, hex_string);
            printf("  %2d 字节: %s\n", len, hex_string);
        }
    }
    
    return 0;
}

示例2:不同标志位的使用

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

// 显示随机数据的统计信息
void show_random_stats(const unsigned char *data, size_t len) {
    unsigned long sum = 0;
    for (size_t i = 0; i < len; i++) {
        sum += data[i];
    }
    double average = (double)sum / len;
    printf("  平均值: %.2f\n", average);
    printf("  数据范围: 0x%02x - 0x%02x\n", 
           data[0], data[len-1]);  // 简化的范围显示
}

int main() {
    unsigned char buffer[64];
    ssize_t result;
    struct timespec start, end;
    
    printf("=== getrandom 标志位使用示例 ===\n\n");
    
    // 1. 默认模式(推荐)
    printf("1. 默认模式 (GRND_URANDOM):\n");
    clock_gettime(CLOCK_MONOTONIC, &start);
    result = getrandom(buffer, sizeof(buffer), 0);
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    if (result > 0) {
        printf("  成功获取 %zd 字节随机数据\n", result);
        printf("  耗时: %ld 纳秒\n", 
               (end.tv_sec - start.tv_sec) * 1000000000L + 
               (end.tv_nsec - start.tv_nsec));
        show_random_stats(buffer, result);
    } else {
        perror("  getrandom 失败");
    }
    
    // 2. 使用 GRND_RANDOM(阻塞模式)
    printf("\n2. GRND_RANDOM 模式 (使用 /dev/random):\n");
    printf("  注意: 如果熵不足可能会阻塞\n");
    
    clock_gettime(CLOCK_MONOTONIC, &start);
    result = getrandom(buffer, 16, GRND_RANDOM);
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    if (result > 0) {
        printf("  成功获取 %zd 字节随机数据\n", result);
        printf("  耗时: %ld 纳秒\n", 
               (end.tv_sec - start.tv_sec) * 1000000000L + 
               (end.tv_nsec - start.tv_nsec));
    } else {
        perror("  getrandom 失败");
    }
    
    // 3. 非阻塞模式
    printf("\n3. 非阻塞模式 (GRND_NONBLOCK):\n");
    result = getrandom(buffer, sizeof(buffer), GRND_NONBLOCK);
    
    if (result > 0) {
        printf("  成功获取 %zd 字节随机数据\n", result);
    } else if (result == -1 && errno == EAGAIN) {
        printf("  熵不足,非阻塞模式下立即返回\n");
    } else {
        perror("  getrandom 失败");
    }
    
    // 4. 组合标志
    printf("\n4. 组合标志 (GRND_RANDOM | GRND_NONBLOCK):\n");
    result = getrandom(buffer, 16, GRND_RANDOM | GRND_NONBLOCK);
    
    if (result > 0) {
        printf("  成功获取 %zd 字节随机数据\n", result);
    } else if (result == -1 && errno == EAGAIN) {
        printf("  /dev/random 熵不足,非阻塞模式下立即返回\n");
    } else {
        perror("  getrandom 失败");
    }
    
    printf("\n=== 使用建议 ===\n");
    printf("1. 一般情况下使用默认模式(flags=0)\n");
    printf("2. 加密应用可以考虑 GRND_RANDOM\n");
    printf("3. 非阻塞场景使用 GRND_NONBLOCK\n");
    printf("4. 避免在循环中频繁调用\n");
    
    return 0;
}

示例3:完整的随机数工具

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

// 输出格式类型
enum output_format {
    FORMAT_HEX,      // 十六进制
    FORMAT_BASE64,   // Base64
    FORMAT_BINARY,   // 二进制
    FORMAT_DECIMAL   // 十进制
};

// 将二进制数据转换为十六进制
void to_hex(const unsigned char *data, size_t len, char *output) {
    for (size_t i = 0; i < len; i++) {
        sprintf(output + i * 2, "%02x", data[i]);
    }
    output[len * 2] = '\0';
}

// 简化的 Base64 编码(仅用于演示)
void to_base64(const unsigned char *data, size_t len, char *output) {
    const char *base64_chars = 
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    size_t i = 0, j = 0;
    while (i < len) {
        uint32_t octet_a = i < len ? data[i++] : 0;
        uint32_t octet_b = i < len ? data[i++] : 0;
        uint32_t octet_c = i < len ? data[i++] : 0;
        
        uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
        
        output[j++] = base64_chars[(triple >> 18) & 63];
        output[j++] = base64_chars[(triple >> 12) & 63];
        output[j++] = base64_chars[(triple >> 6) & 63];
        output[j++] = base64_chars[triple & 63];
    }
    
    // 处理填充
    int pad = len % 3;
    if (pad == 1) {
        output[j-2] = '=';
        output[j-1] = '=';
    } else if (pad == 2) {
        output[j-1] = '=';
    }
    output[j] = '\0';
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -n, --bytes=NUM     生成 NUM 字节的随机数据 (默认 32)\n");
    printf("  -f, --format=TYPE   输出格式: hex, base64, binary, decimal\n");
    printf("  -r, --random        使用 /dev/random (阻塞模式)\n");
    printf("  -b, --non-block     非阻塞模式\n");
    printf("  -h, --help          显示此帮助信息\n");
    printf("\n输出格式说明:\n");
    printf("  hex     - 十六进制字符串\n");
    printf("  base64  - Base64 编码\n");
    printf("  binary  - 原始二进制数据\n");
    printf("  decimal - 十进制数字序列\n");
}

int main(int argc, char *argv[]) {
    size_t num_bytes = 32;
    enum output_format format = FORMAT_HEX;
    unsigned int flags = 0;
    int c;
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"bytes",     required_argument, 0, 'n'},
        {"format",    required_argument, 0, 'f'},
        {"random",    no_argument,       0, 'r'},
        {"non-block", no_argument,       0, 'b'},
        {"help",      no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    while (1) {
        int option_index = 0;
        c = getopt_long(argc, argv, "n:f:rbh", long_options, &option_index);
        
        if (c == -1)
            break;
            
        switch (c) {
            case 'n':
                num_bytes = atoi(optarg);
                if (num_bytes == 0) {
                    fprintf(stderr, "错误: 无效的字节数\n");
                    return 1;
                }
                break;
                
            case 'f':
                if (strcmp(optarg, "hex") == 0) {
                    format = FORMAT_HEX;
                } else if (strcmp(optarg, "base64") == 0) {
                    format = FORMAT_BASE64;
                } else if (strcmp(optarg, "binary") == 0) {
                    format = FORMAT_BINARY;
                } else if (strcmp(optarg, "decimal") == 0) {
                    format = FORMAT_DECIMAL;
                } else {
                    fprintf(stderr, "错误: 未知的格式 '%s'\n", optarg);
                    return 1;
                }
                break;
                
            case 'r':
                flags |= GRND_RANDOM;
                break;
                
            case 'b':
                flags |= GRND_NONBLOCK;
                break;
                
            case 'h':
                show_help(argv[0]);
                return 0;
                
            case '?':
                return 1;
        }
    }
    
    // 分配缓冲区
    unsigned char *buffer = malloc(num_bytes);
    if (!buffer) {
        fprintf(stderr, "错误: 内存分配失败\n");
        return 1;
    }
    
    // 生成随机数据
    ssize_t result = getrandom(buffer, num_bytes, flags);
    if (result == -1) {
        if (errno == EAGAIN && (flags & GRND_NONBLOCK)) {
            fprintf(stderr, "错误: 熵不足(非阻塞模式)\n");
        } else {
            perror("getrandom");
        }
        free(buffer);
        return 1;
    }
    
    // 根据格式输出
    switch (format) {
        case FORMAT_HEX: {
            char *hex_output = malloc(result * 2 + 1);
            if (hex_output) {
                to_hex(buffer, result, hex_output);
                printf("%s\n", hex_output);
                free(hex_output);
            }
            break;
        }
        
        case FORMAT_BASE64: {
            char *base64_output = malloc((result * 4 / 3) + 4);
            if (base64_output) {
                to_base64(buffer, result, base64_output);
                printf("%s\n", base64_output);
                free(base64_output);
            }
            break;
        }
        
        case FORMAT_BINARY:
            fwrite(buffer, 1, result, stdout);
            break;
            
        case FORMAT_DECIMAL:
            for (size_t i = 0; i < result; i++) {
                printf("%d ", buffer[i]);
            }
            printf("\n");
            break;
    }
    
    free(buffer);
    return 0;
}

示例4:加密安全的随机数应用

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/random.h>
#include <string.h>
#include <time.h>

// 生成加密安全的会话 ID
char* generate_session_id(size_t length) {
    static const char charset[] = 
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    
    char *session_id = malloc(length + 1);
    if (!session_id) return NULL;
    
    unsigned char random_bytes[length];
    ssize_t result = getrandom(random_bytes, length, 0);
    if (result != (ssize_t)length) {
        free(session_id);
        return NULL;
    }
    
    for (size_t i = 0; i < length; i++) {
        session_id[i] = charset[random_bytes[i] % (sizeof(charset) - 1)];
    }
    session_id[length] = '\0';
    
    return session_id;
}

// 生成加密密钥
int generate_crypto_key(unsigned char *key, size_t key_size) {
    ssize_t result = getrandom(key, key_size, GRND_NONBLOCK);
    return (result == (ssize_t)key_size) ? 0 : -1;
}

// 生成盐值
int generate_salt(unsigned char *salt, size_t salt_size) {
    return (getrandom(salt, salt_size, 0) == (ssize_t)salt_size) ? 0 : -1;
}

// 显示密钥(以十六进制格式)
void print_key(const unsigned char *key, size_t key_size, const char *label) {
    printf("%s: ", label);
    for (size_t i = 0; i < key_size; i++) {
        printf("%02x", key[i]);
    }
    printf("\n");
}

int main() {
    printf("=== 加密安全随机数应用示例 ===\n\n");
    
    // 1. 生成会话 ID
    printf("1. 生成会话 ID:\n");
    for (int i = 0; i < 5; i++) {
        char *session_id = generate_session_id(32);
        if (session_id) {
            printf("  会话 ID %d: %s\n", i + 1, session_id);
            free(session_id);
        }
    }
    
    // 2. 生成加密密钥
    printf("\n2. 生成加密密钥:\n");
    unsigned char aes_key[32];  // 256-bit AES key
    unsigned char hmac_key[64]; // 512-bit HMAC key
    
    if (generate_crypto_key(aes_key, sizeof(aes_key)) == 0) {
        print_key(aes_key, sizeof(aes_key), "AES-256 密钥");
    } else {
        printf("  AES 密钥生成失败\n");
    }
    
    if (generate_crypto_key(hmac_key, sizeof(hmac_key)) == 0) {
        print_key(hmac_key, sizeof(hmac_key), "HMAC 密钥");
    } else {
        printf("  HMAC 密钥生成失败\n");
    }
    
    // 3. 生成盐值
    printf("\n3. 生成盐值:\n");
    unsigned char salt[16];
    for (int i = 0; i < 3; i++) {
        if (generate_salt(salt, sizeof(salt)) == 0) {
            printf("  盐值 %d: ", i + 1);
            for (size_t j = 0; j < sizeof(salt); j++) {
                printf("%02x", salt[j]);
            }
            printf("\n");
        }
    }
    
    // 4. 生成初始化向量 (IV)
    printf("\n4. 生成初始化向量 (IV):\n");
    unsigned char iv[16];  // AES block size
    if (getrandom(iv, sizeof(iv), 0) == sizeof(iv)) {
        print_key(iv, sizeof(iv), "AES IV");
    }
    
    // 5. 性能测试
    printf("\n5. 性能测试:\n");
    const size_t test_size = 1024;
    unsigned char *test_buffer = malloc(test_size);
    if (test_buffer) {
        struct timespec start, end;
        const int iterations = 1000;
        
        clock_gettime(CLOCK_MONOTONIC, &start);
        for (int i = 0; i < iterations; i++) {
            if (getrandom(test_buffer, test_size, GRND_NONBLOCK) != test_size) {
                printf("  测试失败\n");
                break;
            }
        }
        clock_gettime(CLOCK_MONOTONIC, &end);
        
        long long duration = (end.tv_sec - start.tv_sec) * 1000000000LL + 
                            (end.tv_nsec - start.tv_nsec);
        double avg_time = (double)duration / iterations / 1000000.0;  // 毫秒
        
        printf("  生成 %zu 字节随机数据 %d 次\n", test_size, iterations);
        printf("  平均耗时: %.3f 毫秒\n", avg_time);
        printf("  吞吐量: %.2f MB/s\n", 
               (test_size * iterations) / (duration / 1000.0) / 1024.0);
        
        free(test_buffer);
    }
    
    printf("\n=== 安全建议 ===\n");
    printf("1. 使用 getrandom 而不是 rand() 生成安全随机数\n");
    printf("2. 对于加密密钥,考虑使用 GRND_RANDOM\n");
    printf("3. 避免在循环中频繁调用 getrandom\n");
    printf("4. 检查返回值确保成功获取随机数据\n");
    printf("5. 不要将随机数据存储在可预测的位置\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o getrandom_example1 example1.c
gcc -o getrandom_example2 example2.c
gcc -o getrandom_example3 example3.c
gcc -o getrandom_example4 example4.c

# 运行示例
./getrandom_example1
./getrandom_example2
./getrandom_example3 --help
./getrandom_example3 -n 16 -f hex
./getrandom_example3 -n 32 -f base64
./getrandom_example4

系统要求检查

# 检查内核版本(需要 3.17+)
uname -r

# 检查 glibc 版本(需要 2.25+)
ldd --version

# 检查 /dev/random 和 /dev/urandom
ls -l /dev/random /dev/urandom

重要注意事项

  1. 内核版本: 需要 Linux 3.17+ 内核支持
  2. glibc 版本: 需要 glibc 2.25+ 支持
  3. 性能考虑: 避免频繁调用,批量获取随机数据
  4. 安全性: 比 rand() 等伪随机数生成器更安全
  5. 阻塞行为: 默认非阻塞,GRND_RANDOM 可能阻塞
  6. 错误处理: 始终检查返回值和 errno

与其他随机数生成器的比较

特性getrandom/dev/urandomrand()random()
安全性
阻塞可选
性能中等中等
可移植性Linux 特有Unix-like标准 CBSD 扩展
加密适用

实际应用场景

  1. 加密密钥生成: 生成 AES、RSA 等加密密钥
  2. 会话管理: 生成会话 ID、CSRF 令牌
  3. 密码盐值: 为密码哈希生成随机盐值
  4. 初始化向量: 生成加密算法的 IV
  5. 临时文件名: 生成唯一的临时文件名
  6. 游戏随机数: 游戏中的随机事件生成

最佳实践

  1. 默认使用: 一般情况下使用 getrandom(buffer, size, 0)
  2. 加密场景: 重要加密操作可考虑 GRND_RANDOM
  3. 批量获取: 一次获取较多随机数据,避免频繁调用
  4. 错误检查: 始终检查返回值确保成功
  5. 内存清理: 使用后及时清理敏感的随机数据

这些示例展示了 getrandom 函数的各种使用方法,从基础的随机数据生成到完整的安全应用,帮助你全面掌握这个重要的安全随机数生成接口。

发表在 linux文章 | 留下评论

getresgid系统调用及示例

getresgid – 获取进程的真实、有效和保存的组ID

1. 函数介绍

getresgid 是一个 Linux 系统调用,用于同时获取当前进程的真实组 ID(Real Group ID)、有效组 ID(Effective Group ID)和保存的设置组 ID(Saved Set-group-ID)。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);

3. 功能对比

函数功能参数
getresuid(ruid, euid, suid)获取用户 ID 三元组3个 uid_t* 指针
getresgid(rgid, egid, sgid)获取组 ID 三元组3个 gid_t* 指针

4. 参数说明

getresuid 参数:

  • uid_t *ruid: 指向存储真实用户 ID 的变量的指针
  • uid_t *euid: 指向存储有效用户 ID 的变量的指针
  • uid_t *suid: 指向存储保存的设置用户 ID 的变量的指针

getresgid 参数:

  • gid_t *rgid: 指向存储真实组 ID 的变量的指针
  • gid_t *egid: 指向存储有效组 ID 的变量的指针
  • gid_t *sgid: 指向存储保存的设置组 ID 的变量的指针

5. 返回值

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

6. 常见 errno 错误码

  • EFAULT: 指针参数指向无效内存地址

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

  • getuid()geteuid(): 分别获取真实和有效用户 ID
  • getgid()getegid(): 分别获取真实和有效组 ID
  • setresuid()setresgid(): 设置用户/组 ID 三元组
  • setreuid()setregid(): 设置真实和有效 ID
  • setuid()setgid(): 设置用户/组 ID
  • seteuid()setegid(): 设置有效用户/组 ID

8. 示例代码

示例1:基本使用 – 获取完整的 ID 信息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

void print_user_info(const char *label, uid_t uid) {
    printf("%-20s %d", label, uid);
    
    struct passwd *pwd = getpwuid(uid);
    if (pwd != NULL) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
}

void print_group_info(const char *label, gid_t gid) {
    printf("%-20s %d", label, gid);
    
    struct group *grp = getgrgid(gid);
    if (grp != NULL) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
}

int main() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    int ret;
    
    printf("=== 进程完整身份信息 ===\n");
    
    // 获取用户 ID 三元组
    ret = getresuid(&ruid, &euid, &suid);
    if (ret == -1) {
        perror("getresuid 失败");
        exit(EXIT_FAILURE);
    }
    
    printf("用户 ID 信息:\n");
    print_user_info("真实用户 ID:", ruid);
    print_user_info("有效用户 ID:", euid);
    print_user_info("保存的设置 UID:", suid);
    
    // 获取组 ID 三元组
    ret = getresgid(&rgid, &egid, &sgid);
    if (ret == -1) {
        perror("getresgid 失败");
        exit(EXIT_FAILURE);
    }
    
    printf("\n组 ID 信息:\n");
    print_group_info("真实组 ID:", rgid);
    print_group_info("有效组 ID:", egid);
    print_group_info("保存的设置 GID:", sgid);
    
    // 分析身份状态
    printf("\n身份状态分析:\n");
    
    if (euid == 0) {
        printf("✓ 当前进程具有 root 用户权限\n");
    }
    
    if (egid == 0) {
        printf("✓ 当前进程具有 root 组权限\n");
    }
    
    if (ruid != euid) {
        printf("✓ 用户身份已被切换 (真实: %d, 有效: %d)\n", ruid, euid);
    }
    
    if (rgid != egid) {
        printf("✓ 组身份已被切换 (真实: %d, 有效: %d)\n", rgid, egid);
    }
    
    if (suid != euid) {
        printf("✓ 保存的设置 UID 可用于权限恢复 (%d -> %d)\n", suid, euid);
    }
    
    if (sgid != egid) {
        printf("✓ 保存的设置 GID 可用于权限恢复 (%d -> %d)\n", sgid, egid);
    }
    
    return 0;
}

示例2:错误处理和权限分析

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

void safe_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
    if (getresuid(ruid, euid, suid) == -1) {
        printf("getresuid 失败: %s\n", strerror(errno));
        *ruid = *euid = *suid = (uid_t)-1;
    }
}

void safe_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
    if (getresgid(rgid, egid, sgid) == -1) {
        printf("getresgid 失败: %s\n", strerror(errno));
        *rgid = *egid = *sgid = (gid_t)-1;
    }
}

void analyze_privilege_status() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    printf("=== 权限状态详细分析 ===\n");
    
    safe_getresuid(&ruid, &euid, &suid);
    safe_getresgid(&rgid, &egid, &sgid);
    
    printf("用户权限分析:\n");
    printf("  真实 UID:  %d\n", ruid);
    printf("  有效 UID:  %d", euid);
    if (euid == 0) printf(" (ROOT 权限)");
    printf("\n");
    printf("  保存 SUID: %d", suid);
    if (suid == 0) printf(" (保存 ROOT 权限)");
    printf("\n");
    
    printf("\n组权限分析:\n");
    printf("  真实 GID:  %d\n", rgid);
    printf("  有效 GID:  %d", egid);
    if (egid == 0) printf(" (ROOT 组权限)");
    printf("\n");
    printf("  保存 SGID: %d", sgid);
    if (sgid == 0) printf(" (保存 ROOT 组权限)");
    printf("\n");
    
    // 权限切换能力分析
    printf("\n权限切换能力:\n");
    
    if (suid != euid) {
        printf("✓ 可以通过 setuid() 恢复到保存的 UID %d\n", suid);
    }
    
    if (sgid != egid) {
        printf("✓ 可以通过 setgid() 恢复到保存的 GID %d\n", sgid);
    }
    
    // 特权状态
    if (euid == 0 || egid == 0) {
        printf("⚠ 当前进程具有特权权限,注意安全操作\n");
    }
    
    if ((euid != ruid || egid != rgid) && (suid == ruid && sgid == rgid)) {
        printf("✓ 可以通过保存的 ID 完全恢复到原始身份\n");
    }
}

void demonstrate_invalid_pointer_handling() {
    printf("\n=== 无效指针处理测试 ===\n");
    
    // 测试 NULL 指针
    if (getresuid(NULL, NULL, NULL) == -1) {
        if (errno == EFAULT) {
            printf("✓ 正确处理了 NULL 指针 (EFAULT)\n");
        } else {
            printf("✗ 意外错误: %s\n", strerror(errno));
        }
    }
    
    // 测试部分 NULL 指针
    uid_t ruid, euid;
    if (getresuid(&ruid, &euid, NULL) == -1) {
        if (errno == EFAULT) {
            printf("✓ 正确处理了部分 NULL 指针\n");
        }
    }
}

int main() {
    analyze_privilege_status();
    demonstrate_invalid_pointer_handling();
    return 0;
}

示例3:权限切换和恢复演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>

void print_current_ids(const char *context) {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        printf("获取 ID 信息失败\n");
        return;
    }
    
    printf("%s:\n", context);
    printf("  UID: %d/%d/%d (真实/有效/保存)\n", ruid, euid, suid);
    printf("  GID: %d/%d/%d (真实/有效/保存)\n", rgid, egid, sgid);
    printf("\n");
}

int main() {
    printf("=== 权限切换和恢复演示 ===\n");
    
    // 初始状态
    print_current_ids("初始状态");
    
    // 检查是否具有特权权限
    uid_t euid;
    getresuid(NULL, &euid, NULL);
    
    if (euid != 0) {
        printf("注意: 当前进程不是 root,某些权限操作可能失败\n");
        printf("建议以 root 权限运行此演示\n");
    }
    
    // 演示 setuid/setgid 的效果
    printf("尝试进行权限操作...\n");
    
    // 如果是 root,可以进行权限切换演示
    if (euid == 0) {
        // 切换到 nobody 用户(假设 UID 65534)
        uid_t nobody_uid = 65534;
        gid_t nogroup_gid = 65534;
        
        printf("尝试切换到 nobody 用户...\n");
        
        if (setresuid(nobody_uid, nobody_uid, nobody_uid) == 0 &&
            setresgid(nogroup_gid, nogroup_gid, nogroup_gid) == 0) {
            print_current_ids("切换到 nobody 后");
            printf("✓ 成功切换到 nobody 用户\n");
        } else {
            printf("✗ 切换失败: %s\n", strerror(errno));
        }
        
        // 尝试恢复权限(应该失败,因为保存的 ID 已改变)
        if (setuid(0) == -1) {
            printf("✓ 无法恢复到 root 权限(保存的 ID 已改变)\n");
        }
    } else {
        printf("跳过权限切换演示(需要 root 权限)\n");
    }
    
    // 显示最终状态
    print_current_ids("最终状态");
    
    return 0;
}

示例4:安全审计和监控工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

typedef struct {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    time_t timestamp;
    pid_t pid;
} identity_snapshot_t;

int capture_identity_snapshot(identity_snapshot_t *snapshot) {
    snapshot->pid = getpid();
    snapshot->timestamp = time(NULL);
    
    if (getresuid(&snapshot->ruid, &snapshot->euid, &snapshot->suid) == -1 ||
        getresgid(&snapshot->rgid, &snapshot->egid, &snapshot->sgid) == -1) {
        return -1;
    }
    
    return 0;
}

void print_identity_snapshot(const identity_snapshot_t *snapshot) {
    char time_str[32];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
             localtime(&snapshot->timestamp));
    
    printf("时间: %s\n", time_str);
    printf("进程: %d\n", snapshot->pid);
    printf("用户 ID: %d/%d/%d (真实/有效/保存)\n",
           snapshot->ruid, snapshot->euid, snapshot->suid);
    printf("组 ID: %d/%d/%d (真实/有效/保存)\n",
           snapshot->rgid, snapshot->egid, snapshot->sgid);
    
    // 显示用户名和组名
    struct passwd *pwd = getpwuid(snapshot->ruid);
    if (pwd) printf("真实用户: %s\n", pwd->pw_name);
    
    pwd = getpwuid(snapshot->euid);
    if (pwd) printf("有效用户: %s\n", pwd->pw_name);
    
    struct group *grp = getgrgid(snapshot->rgid);
    if (grp) printf("真实组: %s\n", grp->gr_name);
    
    grp = getgrgid(snapshot->egid);
    if (grp) printf("有效组: %s\n", grp->gr_name);
}

void security_audit() {
    identity_snapshot_t snapshot;
    
    printf("=== 安全审计报告 ===\n");
    
    if (capture_identity_snapshot(&snapshot) == -1) {
        printf("获取身份信息失败\n");
        return;
    }
    
    print_identity_snapshot(&snapshot);
    
    // 安全检查
    printf("\n安全检查结果:\n");
    
    // 检查特权权限
    if (snapshot.euid == 0) {
        printf("⚠ 警告: 进程以 root 权限运行\n");
    }
    
    if (snapshot.egid == 0) {
        printf("⚠ 警告: 进程具有 root 组权限\n");
    }
    
    // 检查权限不一致
    if (snapshot.ruid != snapshot.euid) {
        printf("ℹ 信息: 用户权限已被切换\n");
    }
    
    if (snapshot.rgid != snapshot.egid) {
        printf("ℹ 信息: 组权限已被切换\n");
    }
    
    // 检查保存的权限
    if (snapshot.suid == 0 && snapshot.euid != 0) {
        printf("ℹ 信息: 保存了 root 用户权限,可用于恢复\n");
    }
    
    if (snapshot.sgid == 0 && snapshot.egid != 0) {
        printf("ℹ 信息: 保存了 root 组权限,可用于恢复\n");
    }
    
    // 检查潜在安全风险
    if ((snapshot.ruid != snapshot.euid || snapshot.rgid != snapshot.egid) &&
        (snapshot.suid == snapshot.ruid && snapshot.sgid == snapshot.rgid)) {
        printf("✓ 安全: 可以完全恢复到原始身份\n");
    }
}

void monitor_privilege_changes() {
    printf("\n=== 权限变化监控 ===\n");
    
    identity_snapshot_t initial, current;
    
    if (capture_identity_snapshot(&initial) == -1) {
        printf("无法获取初始身份信息\n");
        return;
    }
    
    printf("监控 5 秒钟内的权限变化...\n");
    
    for (int i = 0; i < 5; i++) {
        sleep(1);
        
        if (capture_identity_snapshot(&current) == 0) {
            // 检查是否有变化
            if (current.ruid != initial.ruid ||
                current.euid != initial.euid ||
                current.suid != initial.suid ||
                current.rgid != initial.rgid ||
                current.egid != initial.egid ||
                current.sgid != initial.sgid) {
                
                printf("检测到权限变化:\n");
                printf("之前: UID(%d/%d/%d) GID(%d/%d/%d)\n",
                       initial.ruid, initial.euid, initial.suid,
                       initial.rgid, initial.egid, initial.sgid);
                printf("现在: UID(%d/%d/%d) GID(%d/%d/%d)\n",
                       current.ruid, current.euid, current.suid,
                       current.rgid, current.egid, current.sgid);
                
                // 更新初始状态
                initial = current;
            }
        }
    }
    
    printf("监控结束\n");
}

int main() {
    security_audit();
    monitor_privilege_changes();
    return 0;
}

9. ID 类型说明

Unix/Linux 系统中的三类 ID:

// 用户 ID 三元组
ruid  // Real User ID: 启动进程的用户
euid  // Effective User ID: 当前权限检查使用的用户 ID
suid  // Saved Set-user-ID: 保存的设置用户 ID

// 组 ID 三元组
rgid  // Real Group ID: 启动进程的组
egid  // Effective Group ID: 当前权限检查使用的组 ID
sgid  // Saved Set-group-ID: 保存的设置组 ID

10. 实际应用场景

场景1:权限管理工具

int can_drop_privileges_completely() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        return 0;
    }
    
    // 检查是否可以完全丢弃特权
    return (ruid == euid && euid == suid &&
            rgid == egid && egid == sgid);
}

场景2:安全审计

void audit_process_privileges() {
    identity_snapshot_t snapshot;
    if (capture_identity_snapshot(&snapshot) == 0) {
        if (snapshot.euid == 0) {
            syslog(LOG_WARNING, "进程 %d 以 root 权限运行", snapshot.pid);
        }
    }
}

场景3:权限恢复

int restore_original_privileges() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        return -1;
    }
    
    // 恢复到原始身份
    return setresuid(ruid, ruid, ruid) || setresgid(rgid, rgid, rgid);
}

11. 注意事项

使用 getresuid 和 getresgid 时需要注意:

  1. 指针有效性: 所有指针参数必须指向有效的内存地址
  2. 错误处理: 虽然很少失败,但仍需检查返回值
  3. 权限检查: 获取其他进程的 ID 信息可能需要权限
  4. 并发安全: 在多线程环境中注意数据一致性
  5. 系统兼容: 在所有现代 Unix/Linux 系统中都可用

总结

getresuid 和 getresgid 是管理进程身份信息的重要函数:

关键特性:

  1. 完整信息: 一次性获取所有相关的 ID 信息
  2. 原子操作: 保证获取的 ID 组是一致的
  3. 安全相关: 是权限管理和安全审计的基础
  4. 系统调用: 直接访问内核信息,性能良好

主要应用:

  1. 安全审计和监控工具
  2. 权限管理和切换程序
  3. 系统管理和调试工具
  4. 容器和虚拟化环境中的权限控制

正确理解和使用这些函数对于编写安全、可靠的 Unix/Linux 程序至关重要,特别是在需要进行权限管理和安全检查的场景中。

getresgid 是 Linux 系统调用,用于获取进程的组 ID 三元组(真实组 ID、有效组 ID 和保存的组 ID)。该函数通过三个 gid_t* 参数返回 ID 值,成功时返回 0,失败返回 -1 并设置 errno。典型用法是检查进程权限状态,常与 getresuid 配合使用。示例代码展示了如何获取并分析这些 ID,包括错误处理和权限切换能力检测。该函数在需要精细控制进程权限时非常有用,特别是在特权程序设计中。

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

发表在 linux文章 | 留下评论

getresuid系统调用及示例

getresuid – 获取进程的真实、有效和保存的用户ID

getresuidgetresgid是Linux系统调用,用于获取进程的用户和组ID信息。getresuid获取真实用户ID、有效用户ID和保存的设置用户ID;getresgid获取对应的组ID。两者都通过指针参数返回三个ID值,成功返回0,失败返回-1并设置errno。示例代码展示了如何获取并分析这些ID信息,包括错误处理和权限状态分析。相关函数包括getuidsetuid等用于用户/组ID管理的系统调用。

1. 函数介绍

getresuid 是一个 Linux 系统调用,用于同时获取当前进程的真实用户 ID(Real User ID)、有效用户 ID(Effective User ID)和保存的设置用户 ID(Saved Set-user-ID)。这三个 ID 构成了 Unix/Linux 系统中完整的用户身份管理体系。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);

getresgid – 获取进程的真实、有效和保存的组ID

1. 函数介绍

getresgid 是一个 Linux 系统调用,用于同时获取当前进程的真实组 ID(Real Group ID)、有效组 ID(Effective Group ID)和保存的设置组 ID(Saved Set-group-ID)。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);

3. 功能对比

函数功能参数
getresuid(ruid, euid, suid)获取用户 ID 三元组3个 uid_t* 指针
getresgid(rgid, egid, sgid)获取组 ID 三元组3个 gid_t* 指针

4. 参数说明

getresuid 参数:

  • uid_t *ruid: 指向存储真实用户 ID 的变量的指针
  • uid_t *euid: 指向存储有效用户 ID 的变量的指针
  • uid_t *suid: 指向存储保存的设置用户 ID 的变量的指针

getresgid 参数:

  • gid_t *rgid: 指向存储真实组 ID 的变量的指针
  • gid_t *egid: 指向存储有效组 ID 的变量的指针
  • gid_t *sgid: 指向存储保存的设置组 ID 的变量的指针

5. 返回值

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

6. 常见 errno 错误码

  • EFAULT: 指针参数指向无效内存地址

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

  • getuid()geteuid(): 分别获取真实和有效用户 ID
  • getgid()getegid(): 分别获取真实和有效组 ID
  • setresuid()setresgid(): 设置用户/组 ID 三元组
  • setreuid()setregid(): 设置真实和有效 ID
  • setuid()setgid(): 设置用户/组 ID
  • seteuid()setegid(): 设置有效用户/组 ID

8. 示例代码

示例1:基本使用 – 获取完整的 ID 信息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

void print_user_info(const char *label, uid_t uid) {
    printf("%-20s %d", label, uid);
    
    struct passwd *pwd = getpwuid(uid);
    if (pwd != NULL) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
}

void print_group_info(const char *label, gid_t gid) {
    printf("%-20s %d", label, gid);
    
    struct group *grp = getgrgid(gid);
    if (grp != NULL) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
}

int main() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    int ret;
    
    printf("=== 进程完整身份信息 ===\n");
    
    // 获取用户 ID 三元组
    ret = getresuid(&ruid, &euid, &suid);
    if (ret == -1) {
        perror("getresuid 失败");
        exit(EXIT_FAILURE);
    }
    
    printf("用户 ID 信息:\n");
    print_user_info("真实用户 ID:", ruid);
    print_user_info("有效用户 ID:", euid);
    print_user_info("保存的设置 UID:", suid);
    
    // 获取组 ID 三元组
    ret = getresgid(&rgid, &egid, &sgid);
    if (ret == -1) {
        perror("getresgid 失败");
        exit(EXIT_FAILURE);
    }
    
    printf("\n组 ID 信息:\n");
    print_group_info("真实组 ID:", rgid);
    print_group_info("有效组 ID:", egid);
    print_group_info("保存的设置 GID:", sgid);
    
    // 分析身份状态
    printf("\n身份状态分析:\n");
    
    if (euid == 0) {
        printf("✓ 当前进程具有 root 用户权限\n");
    }
    
    if (egid == 0) {
        printf("✓ 当前进程具有 root 组权限\n");
    }
    
    if (ruid != euid) {
        printf("✓ 用户身份已被切换 (真实: %d, 有效: %d)\n", ruid, euid);
    }
    
    if (rgid != egid) {
        printf("✓ 组身份已被切换 (真实: %d, 有效: %d)\n", rgid, egid);
    }
    
    if (suid != euid) {
        printf("✓ 保存的设置 UID 可用于权限恢复 (%d -> %d)\n", suid, euid);
    }
    
    if (sgid != egid) {
        printf("✓ 保存的设置 GID 可用于权限恢复 (%d -> %d)\n", sgid, egid);
    }
    
    return 0;
}

示例2:错误处理和权限分析

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

void safe_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
    if (getresuid(ruid, euid, suid) == -1) {
        printf("getresuid 失败: %s\n", strerror(errno));
        *ruid = *euid = *suid = (uid_t)-1;
    }
}

void safe_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
    if (getresgid(rgid, egid, sgid) == -1) {
        printf("getresgid 失败: %s\n", strerror(errno));
        *rgid = *egid = *sgid = (gid_t)-1;
    }
}

void analyze_privilege_status() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    printf("=== 权限状态详细分析 ===\n");
    
    safe_getresuid(&ruid, &euid, &suid);
    safe_getresgid(&rgid, &egid, &sgid);
    
    printf("用户权限分析:\n");
    printf("  真实 UID:  %d\n", ruid);
    printf("  有效 UID:  %d", euid);
    if (euid == 0) printf(" (ROOT 权限)");
    printf("\n");
    printf("  保存 SUID: %d", suid);
    if (suid == 0) printf(" (保存 ROOT 权限)");
    printf("\n");
    
    printf("\n组权限分析:\n");
    printf("  真实 GID:  %d\n", rgid);
    printf("  有效 GID:  %d", egid);
    if (egid == 0) printf(" (ROOT 组权限)");
    printf("\n");
    printf("  保存 SGID: %d", sgid);
    if (sgid == 0) printf(" (保存 ROOT 组权限)");
    printf("\n");
    
    // 权限切换能力分析
    printf("\n权限切换能力:\n");
    
    if (suid != euid) {
        printf("✓ 可以通过 setuid() 恢复到保存的 UID %d\n", suid);
    }
    
    if (sgid != egid) {
        printf("✓ 可以通过 setgid() 恢复到保存的 GID %d\n", sgid);
    }
    
    // 特权状态
    if (euid == 0 || egid == 0) {
        printf("⚠ 当前进程具有特权权限,注意安全操作\n");
    }
    
    if ((euid != ruid || egid != rgid) && (suid == ruid && sgid == rgid)) {
        printf("✓ 可以通过保存的 ID 完全恢复到原始身份\n");
    }
}

void demonstrate_invalid_pointer_handling() {
    printf("\n=== 无效指针处理测试 ===\n");
    
    // 测试 NULL 指针
    if (getresuid(NULL, NULL, NULL) == -1) {
        if (errno == EFAULT) {
            printf("✓ 正确处理了 NULL 指针 (EFAULT)\n");
        } else {
            printf("✗ 意外错误: %s\n", strerror(errno));
        }
    }
    
    // 测试部分 NULL 指针
    uid_t ruid, euid;
    if (getresuid(&ruid, &euid, NULL) == -1) {
        if (errno == EFAULT) {
            printf("✓ 正确处理了部分 NULL 指针\n");
        }
    }
}

int main() {
    analyze_privilege_status();
    demonstrate_invalid_pointer_handling();
    return 0;
}

示例3:权限切换和恢复演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>

void print_current_ids(const char *context) {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        printf("获取 ID 信息失败\n");
        return;
    }
    
    printf("%s:\n", context);
    printf("  UID: %d/%d/%d (真实/有效/保存)\n", ruid, euid, suid);
    printf("  GID: %d/%d/%d (真实/有效/保存)\n", rgid, egid, sgid);
    printf("\n");
}

int main() {
    printf("=== 权限切换和恢复演示 ===\n");
    
    // 初始状态
    print_current_ids("初始状态");
    
    // 检查是否具有特权权限
    uid_t euid;
    getresuid(NULL, &euid, NULL);
    
    if (euid != 0) {
        printf("注意: 当前进程不是 root,某些权限操作可能失败\n");
        printf("建议以 root 权限运行此演示\n");
    }
    
    // 演示 setuid/setgid 的效果
    printf("尝试进行权限操作...\n");
    
    // 如果是 root,可以进行权限切换演示
    if (euid == 0) {
        // 切换到 nobody 用户(假设 UID 65534)
        uid_t nobody_uid = 65534;
        gid_t nogroup_gid = 65534;
        
        printf("尝试切换到 nobody 用户...\n");
        
        if (setresuid(nobody_uid, nobody_uid, nobody_uid) == 0 &&
            setresgid(nogroup_gid, nogroup_gid, nogroup_gid) == 0) {
            print_current_ids("切换到 nobody 后");
            printf("✓ 成功切换到 nobody 用户\n");
        } else {
            printf("✗ 切换失败: %s\n", strerror(errno));
        }
        
        // 尝试恢复权限(应该失败,因为保存的 ID 已改变)
        if (setuid(0) == -1) {
            printf("✓ 无法恢复到 root 权限(保存的 ID 已改变)\n");
        }
    } else {
        printf("跳过权限切换演示(需要 root 权限)\n");
    }
    
    // 显示最终状态
    print_current_ids("最终状态");
    
    return 0;
}

示例4:安全审计和监控工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

typedef struct {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    time_t timestamp;
    pid_t pid;
} identity_snapshot_t;

int capture_identity_snapshot(identity_snapshot_t *snapshot) {
    snapshot->pid = getpid();
    snapshot->timestamp = time(NULL);
    
    if (getresuid(&snapshot->ruid, &snapshot->euid, &snapshot->suid) == -1 ||
        getresgid(&snapshot->rgid, &snapshot->egid, &snapshot->sgid) == -1) {
        return -1;
    }
    
    return 0;
}

void print_identity_snapshot(const identity_snapshot_t *snapshot) {
    char time_str[32];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
             localtime(&snapshot->timestamp));
    
    printf("时间: %s\n", time_str);
    printf("进程: %d\n", snapshot->pid);
    printf("用户 ID: %d/%d/%d (真实/有效/保存)\n",
           snapshot->ruid, snapshot->euid, snapshot->suid);
    printf("组 ID: %d/%d/%d (真实/有效/保存)\n",
           snapshot->rgid, snapshot->egid, snapshot->sgid);
    
    // 显示用户名和组名
    struct passwd *pwd = getpwuid(snapshot->ruid);
    if (pwd) printf("真实用户: %s\n", pwd->pw_name);
    
    pwd = getpwuid(snapshot->euid);
    if (pwd) printf("有效用户: %s\n", pwd->pw_name);
    
    struct group *grp = getgrgid(snapshot->rgid);
    if (grp) printf("真实组: %s\n", grp->gr_name);
    
    grp = getgrgid(snapshot->egid);
    if (grp) printf("有效组: %s\n", grp->gr_name);
}

void security_audit() {
    identity_snapshot_t snapshot;
    
    printf("=== 安全审计报告 ===\n");
    
    if (capture_identity_snapshot(&snapshot) == -1) {
        printf("获取身份信息失败\n");
        return;
    }
    
    print_identity_snapshot(&snapshot);
    
    // 安全检查
    printf("\n安全检查结果:\n");
    
    // 检查特权权限
    if (snapshot.euid == 0) {
        printf("⚠ 警告: 进程以 root 权限运行\n");
    }
    
    if (snapshot.egid == 0) {
        printf("⚠ 警告: 进程具有 root 组权限\n");
    }
    
    // 检查权限不一致
    if (snapshot.ruid != snapshot.euid) {
        printf("ℹ 信息: 用户权限已被切换\n");
    }
    
    if (snapshot.rgid != snapshot.egid) {
        printf("ℹ 信息: 组权限已被切换\n");
    }
    
    // 检查保存的权限
    if (snapshot.suid == 0 && snapshot.euid != 0) {
        printf("ℹ 信息: 保存了 root 用户权限,可用于恢复\n");
    }
    
    if (snapshot.sgid == 0 && snapshot.egid != 0) {
        printf("ℹ 信息: 保存了 root 组权限,可用于恢复\n");
    }
    
    // 检查潜在安全风险
    if ((snapshot.ruid != snapshot.euid || snapshot.rgid != snapshot.egid) &&
        (snapshot.suid == snapshot.ruid && snapshot.sgid == snapshot.rgid)) {
        printf("✓ 安全: 可以完全恢复到原始身份\n");
    }
}

void monitor_privilege_changes() {
    printf("\n=== 权限变化监控 ===\n");
    
    identity_snapshot_t initial, current;
    
    if (capture_identity_snapshot(&initial) == -1) {
        printf("无法获取初始身份信息\n");
        return;
    }
    
    printf("监控 5 秒钟内的权限变化...\n");
    
    for (int i = 0; i < 5; i++) {
        sleep(1);
        
        if (capture_identity_snapshot(&current) == 0) {
            // 检查是否有变化
            if (current.ruid != initial.ruid ||
                current.euid != initial.euid ||
                current.suid != initial.suid ||
                current.rgid != initial.rgid ||
                current.egid != initial.egid ||
                current.sgid != initial.sgid) {
                
                printf("检测到权限变化:\n");
                printf("之前: UID(%d/%d/%d) GID(%d/%d/%d)\n",
                       initial.ruid, initial.euid, initial.suid,
                       initial.rgid, initial.egid, initial.sgid);
                printf("现在: UID(%d/%d/%d) GID(%d/%d/%d)\n",
                       current.ruid, current.euid, current.suid,
                       current.rgid, current.egid, current.sgid);
                
                // 更新初始状态
                initial = current;
            }
        }
    }
    
    printf("监控结束\n");
}

int main() {
    security_audit();
    monitor_privilege_changes();
    return 0;
}

9. ID 类型说明

Unix/Linux 系统中的三类 ID:

// 用户 ID 三元组
ruid  // Real User ID: 启动进程的用户
euid  // Effective User ID: 当前权限检查使用的用户 ID
suid  // Saved Set-user-ID: 保存的设置用户 ID

// 组 ID 三元组
rgid  // Real Group ID: 启动进程的组
egid  // Effective Group ID: 当前权限检查使用的组 ID
sgid  // Saved Set-group-ID: 保存的设置组 ID

10. 实际应用场景

场景1:权限管理工具

int can_drop_privileges_completely() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        return 0;
    }
    
    // 检查是否可以完全丢弃特权
    return (ruid == euid && euid == suid &&
            rgid == egid && egid == sgid);
}

场景2:安全审计

void audit_process_privileges() {
    identity_snapshot_t snapshot;
    if (capture_identity_snapshot(&snapshot) == 0) {
        if (snapshot.euid == 0) {
            syslog(LOG_WARNING, "进程 %d 以 root 权限运行", snapshot.pid);
        }
    }
}

场景3:权限恢复

int restore_original_privileges() {
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    
    if (getresuid(&ruid, &euid, &suid) == -1 ||
        getresgid(&rgid, &egid, &sgid) == -1) {
        return -1;
    }
    
    // 恢复到原始身份
    return setresuid(ruid, ruid, ruid) || setresgid(rgid, rgid, rgid);
}

11. 注意事项

使用 getresuid 和 getresgid 时需要注意:

  1. 指针有效性: 所有指针参数必须指向有效的内存地址
  2. 错误处理: 虽然很少失败,但仍需检查返回值
  3. 权限检查: 获取其他进程的 ID 信息可能需要权限
  4. 并发安全: 在多线程环境中注意数据一致性
  5. 系统兼容: 在所有现代 Unix/Linux 系统中都可用

总结

getresuid 和 getresgid 是管理进程身份信息的重要函数:

关键特性:

  1. 完整信息: 一次性获取所有相关的 ID 信息
  2. 原子操作: 保证获取的 ID 组是一致的
  3. 安全相关: 是权限管理和安全审计的基础
  4. 系统调用: 直接访问内核信息,性能良好

主要应用:

  1. 安全审计和监控工具
  2. 权限管理和切换程序
  3. 系统管理和调试工具
  4. 容器和虚拟化环境中的权限控制

正确理解和使用这些函数对于编写安全、可靠的 Unix/Linux 程序至关重要,特别是在需要进行权限管理和安全检查的场景中。

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

发表在 linux文章 | 留下评论

Linux 开发者终极资源导航:全球镜像站 + 核心开发手册(2025 国际中文版)

Linux 开发者终极资源导航:全球镜像站 + 核心开发手册(2025 国际中文版)

🚀 为什么你需要这份指南?

作为 Linux 开发者、系统管理员或安全研究员,你是否经常遇到:

  • 🐌 从官方源下载 Ubuntu ISO 速度慢如蜗牛?
  • ❓ 想查找 pthread_create 的权威定义却无从下手?
  • 📚 想系统学习《UNIX 环境高级编程》但不知从何开始?

本文为你一站式解决! 精选全球最快镜像站点 + 最权威开发手册,涵盖系统、网络、并发、编码规范,助你高效开发、轻松面试、深度进阶。

🌍 Linux 发行版全球高速镜像站

💡 小贴士:选择地理位置最近的镜像,下载速度提升 3~10 倍!

🐧 Linux Kernel 官方与镜像

🖥️ Ubuntu 官方与镜像

🔧 APT 源优化:编辑 /etc/apt/sources.list,将 archive.ubuntu.com 替换为 mirrors.tuna.tsinghua.edu.cn,然后运行 sudo apt update,速度飞升!

⚔️ Kali Linux 官方与镜像(渗透测试专用)

💼 Red Hat Enterprise Linux (RHEL) 与免费替代品

📚 核心开发手册与工业标准

📘 POSIX 标准 — 系统编程的“宪法”

🧭 Linux man pages — 函数与命令即时查询

🖋️ C 语言工业级编码标准(卡内基梅隆大学 CMU)

核心规范摘要:

// 变量名包含单位
uint32_t timeout_msecs;
uint32_t weight_lbs;

// 枚举:全大写 + 下划线
enum PinStateType {
    PIN_OFF,
    PIN_ON
};

// 宏定义:始终加括号
#define MAX(a,b) ((a) > (b) ? (a) : (b))

// 始终使用大括号
if (condition) {
    do_something();
}

// 初始化所有变量
int error = 0;
char* name = NULL;

⌨️ C++ 工业级编码标准(卡内基梅隆大学 CMU)

核心规范摘要:

// 类成员变量:前缀 'a'
class MyClass {
private:
    int aErrorNumber;
    string* apName; // 指针加 'p'
};

// 方法名:动词开头
void HandleError();
int CalculateResult();

// 使用命名空间
namespace MyProject {
    class Utility { ... };
}

// 访问器:同名函数
class Person {
public:
    int Age() const { return aAge; }
    void Age(int age) { aAge = age; }
private:
    int aAge;
};

📖 《UNIX 环境高级编程》(APUE)官方学习入口

提供资源

  • 📥 全书示例源代码下载
  • 📝 官方勘误表(避免学习踩坑)
  • 📘 各章更新说明
  • 🛒 购买最新第 3 版链接

🌟 为什么读 APUE? 被誉为“UNIX/Linux 系统编程圣经”,涵盖进程、线程、信号、I/O 等核心概念,是面试、晋升、深造的必备读物。

📖 全球知名在线数字图书馆

名称 链接 描述
O’Reilly Learning https://learning.oreilly.com/ 付费订阅制,包含 APUE、TCP/IP Illustrated 等数千本技术书籍。
Project Gutenberg https://www.gutenberg.org/ 6 万+ 免费公版电子书(以经典文学为主)。
Internet Archive https://archive.org/ 数百万免费书籍、手册、软件、历史快照。
SpringerLink https://link.springer.com/ 学术论文与教材,部分内容免费。

⚠️ 提醒:请尊重版权,资源仅用于个人学习与研究。

❓ 常见问题(FAQ)

Q:POSIX 标准和 Linux man pages 有什么区别?
A: POSIX 是国际标准(定义“应该怎么做”),Linux man pages 是具体实现(定义“实际怎么做”)。日常开发先查 man pages,深入研究或跨平台开发时参考 POSIX。

Q:CMU 的 C/C++ 编码标准是强制的吗?
A: 非强制,但强烈建议团队项目采用。这些规范经过工业界验证,能有效减少 Bug、提升代码可读性、简化 Code Review。

Q:《APUE》有免费 PDF 吗?
A: ❌ 没有合法免费 PDF。请支持作者购买正版。官网提供免费源码和勘误,价值极高,务必下载学习。

Q:RHEL 的替代品选 Rocky Linux 还是 AlmaLinux?
A: 两者都是优秀选择,100% 二进制兼容 RHEL。Rocky 由社区驱动,Alma 有基金会支持。根据团队偏好选择即可,不会出错。

✅ 总结与收藏建议

本文是你的 Linux 开发资源中枢 —— 从高速下载、权威手册到工业编码规范,一应俱全。

推荐操作:

  1. 🔖 收藏本文 — 随时查阅,效率翻倍
  2. 配置本地镜像源 — 告别龟速下载
  3. 📚 精读 APUE + POSIX — 打好系统编程根基
  4. 🧩 团队推行 CMU 编码标准 — 提升代码质量与协作效率

📌 专业提示:工作时保持本页在浏览器标签中打开,它将成为你最强大的生产力工具。


© 2025 calcguide.tech. 欢迎分享,请注明出处。

发表在 linux文章 | 留下评论

Linux Developer’s Ultimate Resource Hub: Global Mirrors & Essential Handbooks (2025)

Linux Developer’s Ultimate Resource Hub: Global Mirrors & Essential Handbooks (2025)

🚀 Why This Guide?

As a Linux developer, sysadmin, or security researcher, you’ve likely faced these frustrations:

  • 🐌 Downloading Ubuntu ISOs at 50KB/s from the main server?
  • ❓ Struggling to find the authoritative definition of pthread_mutex_lock?
  • 📚 Wanting to study Advanced Programming in the UNIX Environment but unsure where to start?

This guide is your one-stop solution. We’ve curated the fastest global mirrors for major Linux distros and the most authoritative handbooks for system programming, C/C++ standards, and POSIX compliance.

🌍 Global Linux Distribution Mirrors (High-Speed Downloads)

💡 Pro Tip: Always choose a mirror geographically closest to you for 3-10x faster downloads.

🐧 Linux Kernel Official & Mirrors

🖥️ Ubuntu Official & Mirrors

🔧 APT Source Tip: Replace archive.ubuntu.com with mirrors.tuna.tsinghua.edu.cn in /etc/apt/sources.list for blazing-fast apt update.

⚔️ Kali Linux Official & Mirrors (Penetration Testing)

💼 Red Hat Enterprise Linux (RHEL) & Alternatives

📚 Core Developer Handbooks & Standards

📘 POSIX Standard — The Source of Truth

🧭 Linux man pages — Instant Command & Function Lookup

🖋️ C Language Coding Standard (CMU / Industry Best Practice)

Key Highlights:

// Include units in variable names
uint32_t timeout_msecs;
uint32_t weight_lbs;

// Enums: ALL_CAPS with underscores
enum PinStateType {
    PIN_OFF,
    PIN_ON
};

// Macros: Always parenthesize
#define MAX(a,b) ((a) > (b) ? (a) : (b))

// Always use braces for if/while
if (condition) {
    do_something();
}

// Initialize all variables
int error = 0;
char* name = NULL;

⌨️ C++ Coding Standard (CMU / Industry Best Practice)

Key Highlights:

// Class attributes: prefix with 'a'
class MyClass {
private:
    int aErrorNumber;
    string* apName; // pointer
};

// Method names: Verbs
void HandleError();
int CalculateResult();

// Use namespaces
namespace MyProject {
    class Utility { ... };
}

// Avoid Get/Set; use same name for accessor/mutator
class Person {
public:
    int Age() const { return aAge; }
    void Age(int age) { aAge = age; }
private:
    int aAge;
};

📖 Advanced Programming in the UNIX Environment (APUE)

Resources Provided:

  • 📥 Download all example source code
  • 📝 Official errata (fixes for known errors)
  • 📘 Chapter summaries and updates
  • 🛒 Links to purchase the latest (3rd) edition

🌟 Why APUE? Universally regarded as the “Bible” of UNIX/Linux system programming. Covers processes, threads, signals, I/O, and more. Essential for interviews and deep system understanding.

📖 Global Online Digital Libraries (For Self-Study)

Name Link Description
O’Reilly Learning https://learning.oreilly.com/ Premium subscription. Includes APUE, TCP/IP Illustrated, and thousands of tech books.
Project Gutenberg https://www.gutenberg.org/ 60,000+ free public domain eBooks (mostly classic literature).
Internet Archive https://archive.org/ Millions of free books, manuals, software, and historical snapshots.
SpringerLink https://link.springer.com/ Academic papers and textbooks. Some content is free.

⚠️ Reminder: Always respect copyright. Use resources for personal study and research.

❓ Frequently Asked Questions (FAQ)

Q: What’s the difference between POSIX and Linux man pages?
A: POSIX is the international standard (what “should” happen). Linux man pages document the actual implementation on your system. Start with man pages for quick reference, consult POSIX for deep, portable understanding.

Q: Are the CMU C/C++ coding standards mandatory?
A: Not mandatory, but highly recommended for team projects. They reduce bugs, improve readability, and make code reviews smoother. They’re battle-tested in industry.

Q: Is there a free PDF of APUE?
A: ❌ No, there is no legal free PDF. Please support the author by purchasing a copy. The official website offers free source code and errata, which are incredibly valuable.

Q: Which RHEL alternative should I choose: Rocky Linux or AlmaLinux?
A: Both are excellent and 100% binary compatible with RHEL. Choose based on community and update philosophy. Rocky is community-driven, Alma has a foundation backing. You can’t go wrong with either.

✅ Summary & Bookmark Suggestion

This guide is your centralized command center for Linux development resources — from lightning-fast downloads to authoritative programming standards.

Recommended Actions:

  1. 🔖 Bookmark this page — your future self will thank you.
  2. Configure your APT/YUM sources to use a local mirror.
  3. 📚 Study APUE and POSIX to master system-level programming.
  4. 🧩 Adopt the CMU coding standards in your next team project.

📌 Pro Tip: Keep this page open in a browser tab while you work. It’s the ultimate productivity booster for Linux developers.

Linux Developer’s Ultimate Resource Hub: Global Mirrors & Essential Handbooks (2025)


© 2025  calcguide.tech.   Share freely with attribution.

发表在 linux文章 | 留下评论

POSIX Threads (pthread) 完全指南:从入门到精通(2025 最新版)

POSIX Threads (pthread) 完全指南:从入门到精通(2025 最新版)

🌟 什么是 POSIX Threads(pthread)?

POSIX Threads(简称 pthread) 是一套遵循 POSIX 标准的多线程编程接口,广泛用于 Linux、Unix、macOS 等系统。它允许开发者在单个进程中创建多个并发执行流(线程),共享内存空间,提升程序性能与响应能力。

📚 为什么学习 pthread?

  • ✅ 工业界标准:几乎所有 Unix-like 系统都支持
  • ✅ 高性能:轻量级线程,低开销
  • ✅ 控制力强:手动管理线程生命周期、同步机制
  • ✅ 学习基础:掌握 pthread 是深入系统编程、高性能服务器开发、嵌入式开发的前提

🧩 核心 pthread 函数速查表(带示例)

本节结构化设计,便于 Google Featured Snippet 抓取(“速查表”、“示例”是高点击率关键词)

1. 创建线程 — pthread_create

#include 

int pthread_create(pthread_t *thread,
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void*),
                   void *arg);

用途:启动一个新线程
返回值:0 表示成功,非 0 为错误码(非 errno)
编译选项gcc -pthread your_program.c -o your_program

📌 简单示例:

#include 
#include 

void* say_hello(void* arg) {
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, say_hello, NULL);
    pthread_join(tid, NULL); // 等待线程结束
    return 0;
}

2. 等待线程结束 — pthread_join

int pthread_join(pthread_t thread, void **retval);

📌 用于阻塞主线程,直到目标线程执行完毕,并可获取其返回值。

3. 分离线程 — pthread_detach

int pthread_detach(pthread_t thread);

📌 线程结束后自动回收资源,无需 join。适用于“发射后不管”的后台线程。

4. 互斥锁(Mutex)— 保护共享资源

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex);

✅ 避免数据竞争(Data Race)的最常用同步机制

5. 条件变量(Condition Variable)— 线程间通信

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 等待条件
pthread_mutex_lock(&mutex);
while (condition == false)
    pthread_cond_wait(&cond, &mutex);
// 条件满足,执行操作
pthread_mutex_unlock(&mutex);

// 通知其他线程
pthread_cond_signal(&cond);   // 唤醒一个
pthread_cond_broadcast(&cond); // 唤醒所有

📌 常用于“生产者-消费者”模型

6. 读写锁(Read-Write Lock)— 读多写少场景

pthread_rwlock_rdlock(&rwlock);   // 多个线程可同时读
pthread_rwlock_wrlock(&rwlock);   // 只有一个线程能写
pthread_rwlock_unlock(&rwlock);

✅ 适用于缓存、配置文件、数据库索引等场景

🛠️ 高级功能:TLS、取消、一次初始化

功能 函数 用途说明
线程局部存储 pthread_key_create 每个线程独立变量副本
线程取消 pthread_cancel 请求终止目标线程
一次初始化 pthread_once 确保初始化代码只执行一次

🧪 实战:完整 pthread 示例(生产者-消费者模型)

#include 
#include 
#include 
#include 

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;

void* producer(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE)
            pthread_cond_wait(&not_full, &mutex);

        buffer[count++] = i;
        printf("Produced: %d\n", i);
        pthread_cond_signal(&not_empty);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);
        while (count == 0)
            pthread_cond_wait(&not_empty, &mutex);

        int item = buffer[--count];
        printf("Consumed: %d\n", item);
        pthread_cond_signal(&not_full);
        pthread_mutex_unlock(&mutex);
        sleep(2);
    }
    return NULL;
}

int main() {
    pthread_t p, c;
    pthread_create(&p, NULL, producer, NULL);
    pthread_create(&c, NULL, consumer, NULL);
    pthread_join(p, NULL);
    pthread_join(c, NULL);
    return 0;
}

✅ 此示例适合 Google 代码片段收录,提高“pthread 示例”类搜索排名

📚 官方权威资源推荐(2025 更新)

1. 《UNIX环境高级编程》(APUE)第3版

官网 👉 https://www.apuebook.com
获取源码、勘误、章节更新
作者:W. Richard Stevens & Stephen A. Rago

2. POSIX 标准官方文档(免费在线版)

👉 The Open Group Base Specifications Issue 7
包含所有 pthread 函数标准定义
支持搜索、交叉引用,开发者必备

3. Linux Man Pages(函数手册)

👉 https://man7.org/linux/man-pages/
搜索 pthread_createpthread_mutex_lock 等获取详细说明

❗ 常见错误与最佳实践

✅ 必须遵守:

  • 所有线程必须 joindetach,否则资源泄漏
  • 互斥锁必须成对使用(lock/unlock),避免死锁
  • 条件变量必须在 while 循环中等待(防止虚假唤醒)
  • 使用 -pthread 编译选项,而非 -lpthread

⚠️ 避免:

  • 在信号处理函数中调用 pthread 函数(不安全)
  • 多线程中使用非线程安全函数(如 strtok, rand
  • 忽略 pthread 函数返回值(错误码非 errno)

❓ 常见问题(FAQ)

Q:pthread 和 std::thread 有什么区别?
A:pthread 是 C 语言 POSIX 标准接口,跨 Unix 系统;std::thread 是 C++11 标准,封装了平台线程(底层可能调用 pthread)。C++ 更易用,C 更底层可控。
Q:pthread_create 返回错误怎么办?
A:检查返回值(非 errno),用 strerror(ret) 打印错误信息。常见错误:EAGAIN(资源不足)、EINVAL(参数无效)。
Q:如何调试 pthread 死锁?
A:使用 valgrind --tool=helgrind 或 GCC 的 -fsanitize=thread(ThreadSanitizer)检测数据竞争与死锁。

✅ 总结

无论你是准备面试、开发高性能服务器、还是学习系统编程,掌握 pthread 是必备技能。本文从基础函数、同步机制、实战示例到官方资源,一站式解决你的学习需求。

📌 收藏本文,随时查阅 pthread 速查表与示例代码!

POSIX Threads (pthread) 完全指南:从入门到精通(2025 最新版)


© 2025 calcguide.tech. 本文可自由分享,转载请注明出处。


发表在 linux文章 | 留下评论

fstatfs系统调用及示例

fstatfs 函数详解

1. 函数介绍

fstatfs 是 Linux 系统中用于获取文件系统统计信息的系统调用。可以把这个函数想象成一个”文件系统体检工具”——当你给它一个已打开的文件描述符时,它会告诉你这个文件所在文件系统的详细信息,比如总空间多大、已用多少、还剩多少、支持什么特性等等。

就像你去体检时医生会检查你的身高、体重、血压等指标一样,fstatfs 会检查文件系统的”健康状况”和”基本信息”。

2. 函数原型

#include <sys/vfs.h>    /* 或者 #include <sys/statfs.h> */

int fstatfs(int fd, struct statfs *buf);

3. 功能

fstatfs 函数用于获取与指定文件描述符相关的文件系统的统计信息。它填充一个 statfs 结构体,其中包含了文件系统的各种参数和状态信息。

4. 参数

  • fd: 已打开的文件描述符(可以是任何类型的文件:普通文件、目录、设备等)
  • buf: 指向 struct statfs 结构体的指针,用于存储返回的文件系统信息

5. 返回值

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

常见错误码:

  • EBADF: fd 不是有效的文件描述符
  • EFAULT: buf 指针无效
  • EIO: I/O 错误
  • ENOSYS: 系统不支持此操作

6. 相似函数或关联函数

  • statfs: 通过文件路径获取文件系统信息(需要文件路径而非文件描述符)
  • statvfs: POSIX 标准的文件系统信息获取函数(提供更标准化的接口)
  • fstatvfs: 通过文件描述符获取 POSIX 标准的文件系统信息
  • stat: 获取文件本身的详细信息(不是文件系统信息)

7. struct statfs 结构体详解

struct statfs {
    long    f_type;     /* 文件系统类型 */
    long    f_bsize;    /* 文件系统块大小 */
    long    f_blocks;   /* 总块数 */
    long    f_bfree;    /* 空闲块数 */
    long    f_bavail;   /* 对非超级用户可用的块数 */
    long    f_files;    /* 总 inode 数 */
    long    f_ffree;    /* 空闲 inode 数 */
    fsid_t  f_fsid;     /* 文件系统 ID */
    long    f_namelen;  /* 最大文件名长度 */
    long    f_frsize;   /* 片段大小 */
    long    f_spare[5]; /* 保留字段 */
};

8. 示例代码

示例1:基础用法 – 查看文件系统空间信息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/vfs.h>

int main() {
    int fd;
    struct statfs fs_info;
    const char *filename = "/etc/passwd";  // 使用系统文件作为示例
    
    // 打开文件
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    // 获取文件系统信息
    if (fstatfs(fd, &fs_info) == -1) {
        perror("fstatfs");
        close(fd);
        return 1;
    }
    
    // 显示文件系统信息
    printf("=== 文件系统信息 ===\n");
    printf("文件: %s\n", filename);
    printf("文件系统类型: 0x%lx\n", fs_info.f_type);
    printf("块大小: %ld 字节\n", fs_info.f_bsize);
    printf("总块数: %ld\n", fs_info.f_blocks);
    printf("空闲块数: %ld\n", fs_info.f_bfree);
    printf("可用块数: %ld\n", fs_info.f_bavail);
    printf("总 inode 数: %ld\n", fs_info.f_files);
    printf("空闲 inode 数: %ld\n", fs_info.f_ffree);
    
    // 计算空间使用情况(以 GB 为单位)
    double total_space = (double)(fs_info.f_blocks * fs_info.f_bsize) / (1024 * 1024 * 1024);
    double free_space = (double)(fs_info.f_bfree * fs_info.f_bsize) / (1024 * 1024 * 1024);
    double avail_space = (double)(fs_info.f_bavail * fs_info.f_bsize) / (1024 * 1024 * 1024);
    
    printf("\n=== 空间使用情况 ===\n");
    printf("总空间: %.2f GB\n", total_space);
    printf("空闲空间: %.2f GB\n", free_space);
    printf("可用空间: %.2f GB\n", avail_space);
    printf("使用率: %.2f%%\n", 
           (total_space - free_space) / total_space * 100);
    
    close(fd);
    return 0;
}

示例2:文件系统类型识别

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/vfs.h>

// 常见文件系统类型的宏定义
#define EXT2_SUPER_MAGIC    0xEF53
#define EXT3_SUPER_MAGIC    0xEF53
#define EXT4_SUPER_MAGIC    0xEF53
#define XFS_SUPER_MAGIC     0x58465342
#define NTFS_SB_MAGIC       0x5346544E
#define TMPFS_MAGIC         0x01021994
#define PROC_SUPER_MAGIC    0x9FA0
#define SYSFS_MAGIC         0x62656572

// 根据文件系统类型返回字符串描述
const char* get_fs_type_name(long fs_type) {
    switch (fs_type) {
        case EXT2_SUPER_MAGIC:
            return "ext2/ext3/ext4";
        case XFS_SUPER_MAGIC:
            return "XFS";
        case NTFS_SB_MAGIC:
            return "NTFS";
        case TMPFS_MAGIC:
            return "tmpfs";
        case PROC_SUPER_MAGIC:
            return "proc";
        case SYSFS_MAGIC:
            return "sysfs";
        default:
            return "未知文件系统";
    }
}

int main() {
    int fd;
    struct statfs fs_info;
    const char *test_files[] = {"/etc/passwd", "/proc/version", "/sys/kernel"};
    int num_files = sizeof(test_files) / sizeof(test_files[0]);
    
    for (int i = 0; i < num_files; i++) {
        printf("\n=== 检查文件: %s ===\n", test_files[i]);
        
        // 打开文件
        fd = open(test_files[i], O_RDONLY);
        if (fd == -1) {
            perror("open");
            printf("跳过此文件\n");
            continue;
        }
        
        // 获取文件系统信息
        if (fstatfs(fd, &fs_info) == -1) {
            perror("fstatfs");
            close(fd);
            continue;
        }
        
        // 显示详细信息
        printf("文件系统类型代码: 0x%lx\n", fs_info.f_type);
        printf("文件系统类型名称: %s\n", get_fs_type_name(fs_info.f_type));
        printf("最大文件名长度: %ld 字符\n", fs_info.f_namelen);
        printf("片段大小: %ld 字节\n", fs_info.f_frsize);
        
        close(fd);
    }
    
    return 0;
}

示例3:磁盘空间监控工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/vfs.h>
#include <string.h>

// 磁盘使用情况结构体
struct disk_usage {
    char path[256];
    double total_gb;
    double used_gb;
    double free_gb;
    double usage_percent;
};

// 获取磁盘使用情况
int get_disk_usage(const char *filepath, struct disk_usage *usage) {
    int fd;
    struct statfs fs_info;
    
    // 打开文件
    fd = open(filepath, O_RDONLY);
    if (fd == -1) {
        perror("open");
        return -1;
    }
    
    // 获取文件系统信息
    if (fstatfs(fd, &fs_info) == -1) {
        perror("fstatfs");
        close(fd);
        return -1;
    }
    
    // 填充使用情况结构体
    strncpy(usage->path, filepath, sizeof(usage->path) - 1);
    usage->path[sizeof(usage->path) - 1] = '\0';
    
    // 计算空间大小(GB)
    double block_size_gb = (double)fs_info.f_bsize / (1024 * 1024 * 1024);
    usage->total_gb = fs_info.f_blocks * block_size_gb;
    usage->free_gb = fs_info.f_bavail * block_size_gb;  // 使用 f_bavail 而不是 f_bfree
    usage->used_gb = usage->total_gb - usage->free_gb;
    usage->usage_percent = (usage->used_gb / usage->total_gb) * 100;
    
    close(fd);
    return 0;
}

// 打印磁盘使用情况(带进度条效果)
void print_disk_usage(const struct disk_usage *usage) {
    printf("路径: %s\n", usage->path);
    printf("总空间: %.2f GB\n", usage->total_gb);
    printf("已用空间: %.2f GB\n", usage->used_gb);
    printf("可用空间: %.2f GB\n", usage->free_gb);
    
    // 显示进度条
    int bar_width = 50;
    int filled_width = (int)((usage->usage_percent / 100) * bar_width);
    
    printf("使用率: %.1f%% [", usage->usage_percent);
    for (int i = 0; i < bar_width; i++) {
        if (i < filled_width) {
            printf("#");
        } else {
            printf("-");
        }
    }
    printf("]\n\n");
}

int main() {
    // 测试不同的文件路径
    const char *test_paths[] = {
        "/",
        "/home",
        "/tmp",
        "/etc/passwd"
    };
    
    int num_paths = sizeof(test_paths) / sizeof(test_paths[0]);
    
    printf("=== 磁盘空间监控工具 ===\n\n");
    
    for (int i = 0; i < num_paths; i++) {
        struct disk_usage usage;
        
        if (get_disk_usage(test_paths[i], &usage) == 0) {
            print_disk_usage(&usage);
        } else {
            printf("无法获取 %s 的磁盘信息\n\n", test_paths[i]);
        }
    }
    
    return 0;
}

编译和运行说明

gcc -o fstatfs_example fstatfs_example.c
./fstatfs_example

注意事项

  1. 文件系统类型: 不同的文件系统类型有不同的魔数(magic number),可以通过 f_type 字段识别
  2. 权限要求: 通常只需要对文件有读权限即可调用此函数
  3. 空间计算:
    • f_bfree: 所有用户都可用的空闲块数
    • f_bavail: 非超级用户可用的块数(通常更小)
  4. 移植性statfs 结构体在不同系统上可能有差异,如需高移植性可考虑使用 statvfs
  5. 性能: 频繁调用此函数可能影响性能,建议缓存结果

这些示例展示了 fstatfs 函数的多种应用场景,从简单的空间信息获取到文件系统类型识别,再到实用的磁盘监控工具,帮助你全面理解这个函数的使用方法。

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

发表在 linux文章 | 留下评论

fstat系统调用及示例

fstat – 获取文件状态信息(通过文件描述符)

1. 函数介绍

fstat 是一个 Linux 系统调用,用于获取通过文件描述符指定的文件的详细状态信息。与 stat 不同,fstat 通过文件描述符而不是文件路径来操作文件,这样可以避免在多线程环境中因文件重命名或删除而导致的竞态条件。

fstat 返回的信息包括文件类型、大小、权限、所有者、时间戳等元数据,这些信息对于文件操作、权限检查和系统管理非常有用。

探索fstat系统调用,学习如何通过文件描述符获取文件状态信息。示例代码助您快速上手,深入理解Linux编程精髓。

2. 函数原型

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int fstat(int fd, struct stat *buf);

3. 功能

获取通过文件描述符指定的文件的状态信息,并将结果存储在 struct stat 结构体中。

4. 参数

  • int fd: 文件描述符,通过 open()creat()dup() 等函数获得
  • struct stat *buf: 指向 stat 结构体的指针,用于存储文件状态信息

5. stat 结构体定义

struct stat {
    dev_t     st_dev;         /* 设备 ID(包含文件的设备)*/
    ino_t     st_ino;         /* inode 号码 */
    mode_t    st_mode;        /* 文件类型和权限 */
    nlink_t   st_nlink;       /* 硬链接数 */
    uid_t     st_uid;         /* 所有者用户 ID */
    gid_t     st_gid;         /* 所有者组 ID */
    dev_t     st_rdev;        /* 设备 ID(如果是特殊文件)*/
    off_t     st_size;        /* 文件总大小(字节)*/
    blksize_t st_blksize;     /* 文件系统 I/O 块大小 */
    blkcnt_t  st_blocks;      /* 分配的 512B 块数 */
    time_t    st_atime;       /* 最后访问时间 */
    time_t    st_mtime;       /* 最后修改时间 */
    time_t    st_ctime;       /* 最后状态改变时间 */
};

6. 返回值

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

7. 常见 errno 错误码

  • EBADF: 无效的文件描述符
  • EFAULTbuf 指针指向无效地址
  • EOVERFLOW: 文件大小、inode 号或链接数超出范围
  • EIO: I/O 错误
  • EACCES: 权限不足(某些特殊情况下)
  • ENOMEM: 内存不足

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

  • stat(): 通过文件路径获取文件状态
  • lstat(): 获取符号链接本身的状态(不跟随链接)
  • fstatat(): 相对于目录文件描述符获取文件状态
  • statx(): 更现代的文件状态获取函数(Linux 4.11+)
  • access(): 检查文件访问权限
  • chmod()chown(): 修改文件权限和所有者

9. 示例代码

示例1:基本使用 – 获取文件基本信息

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

void print_file_type(mode_t mode) {
    if (S_ISREG(mode)) {
        printf("文件类型: 普通文件\n");
    } else if (S_ISDIR(mode)) {
        printf("文件类型: 目录\n");
    } else if (S_ISLNK(mode)) {
        printf("文件类型: 符号链接\n");
    } else if (S_ISCHR(mode)) {
        printf("文件类型: 字符设备\n");
    } else if (S_ISBLK(mode)) {
        printf("文件类型: 块设备\n");
    } else if (S_ISFIFO(mode)) {
        printf("文件类型: FIFO/管道\n");
    } else if (S_ISSOCK(mode)) {
        printf("文件类型: 套接字\n");
    } else {
        printf("文件类型: 未知\n");
    }
}

void print_file_permissions(mode_t mode) {
    printf("权限: ");
    printf((S_ISDIR(mode)) ? "d" : "-");
    printf((mode & S_IRUSR) ? "r" : "-");
    printf((mode & S_IWUSR) ? "w" : "-");
    printf((mode & S_IXUSR) ? "x" : "-");
    printf((mode & S_IRGRP) ? "r" : "-");
    printf((mode & S_IWGRP) ? "w" : "-");
    printf((mode & S_IXGRP) ? "x" : "-");
    printf((mode & S_IROTH) ? "r" : "-");
    printf((mode & S_IWOTH) ? "w" : "-");
    printf((mode & S_IXOTH) ? "x" : "-");
    printf("\n");
}

int main() {
    int fd;
    struct stat file_stat;
    int ret;
    
    // 创建测试文件
    fd = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功创建文件,文件描述符: %d\n", fd);
    
    // 写入一些内容
    const char *content = "这是一个测试文件内容。\n包含多行文本。\n用于演示 fstat 功能。";
    write(fd, content, strlen(content));
    
    // 获取文件状态信息
    ret = fstat(fd, &file_stat);
    if (ret == -1) {
        perror("获取文件状态失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("\n=== 文件状态信息 ===\n");
    printf("设备 ID: %ld\n", (long)file_stat.st_dev);
    printf("inode 号: %ld\n", (long)file_stat.st_ino);
    
    print_file_type(file_stat.st_mode);
    print_file_permissions(file_stat.st_mode);
    
    printf("硬链接数: %ld\n", (long)file_stat.st_nlink);
    printf("所有者 UID: %d\n", file_stat.st_uid);
    printf("所属组 GID: %d\n", file_stat.st_gid);
    printf("文件大小: %ld 字节\n", (long)file_stat.st_size);
    printf("I/O 块大小: %ld\n", (long)file_stat.st_blksize);
    printf("分配的块数: %ld (512字节块)\n", (long)file_stat.st_blocks);
    
    // 打印时间信息
    printf("\n时间信息:\n");
    printf("最后访问时间: %s", ctime(&file_stat.st_atime));
    printf("最后修改时间: %s", ctime(&file_stat.st_mtime));
    printf("状态改变时间: %s", ctime(&file_stat.st_ctime));
    
    close(fd);
    return 0;
}

示例2:错误处理和特殊文件类型

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

void check_file_status(int fd, const char *description) {
    struct stat file_stat;
    int ret;
    
    printf("\n检查 %s:\n", description);
    printf("文件描述符: %d\n", fd);
    
    ret = fstat(fd, &file_stat);
    if (ret == -1) {
        printf("  fstat 调用失败: %s\n", strerror(errno));
        return;
    }
    
    printf("  获取状态成功\n");
    printf("  文件大小: %ld 字节\n", (long)file_stat.st_size);
    
    // 检查文件类型
    if (S_ISREG(file_stat.st_mode)) {
        printf("  类型: 普通文件\n");
    } else if (S_ISDIR(file_stat.st_mode)) {
        printf("  类型: 目录\n");
    } else if (S_ISCHR(file_stat.st_mode)) {
        printf("  类型: 字符设备\n");
        printf("  设备号: %ld\n", (long)file_stat.st_rdev);
    } else if (S_ISBLK(file_stat.st_mode)) {
        printf("  类型: 块设备\n");
        printf("  设备号: %ld\n", (long)file_stat.st_rdev);
    } else if (S_ISFIFO(file_stat.st_mode)) {
        printf("  类型: FIFO/管道\n");
    } else if (S_ISSOCK(file_stat.st_mode)) {
        printf("  类型: 套接字\n");
    }
}

int main() {
    int fd_regular, fd_dir, fd_stdin;
    
    // 测试普通文件
    fd_regular = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd_regular != -1) {
        write(fd_regular, "test content", 12);
        check_file_status(fd_regular, "普通文件");
        close(fd_regular);
    }
    
    // 测试目录
    fd_dir = open(".", O_RDONLY);  // 打开当前目录
    if (fd_dir != -1) {
        check_file_status(fd_dir, "当前目录");
        close(fd_dir);
    }
    
    // 测试标准输入(管道)
    check_file_status(STDIN_FILENO, "标准输入");
    
    // 测试无效文件描述符
    printf("\n测试无效文件描述符:\n");
    struct stat invalid_stat;
    if (fstat(-1, &invalid_stat) == -1) {
        printf("  无效文件描述符测试: %s\n", strerror(errno));
    }
    
    return 0;
}

示例3:文件监控和变化检测

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

typedef struct {
    time_t mtime;    // 最后修改时间
    off_t size;      // 文件大小
} file_monitor_t;

int monitor_file_changes(int fd, file_monitor_t *last_state) {
    struct stat current_stat;
    
    if (fstat(fd, &current_stat) == -1) {
        perror("获取文件状态失败");
        return -1;
    }
    
    int changed = 0;
    
    // 检查文件大小变化
    if (current_stat.st_size != last_state->size) {
        printf("文件大小变化: %ld -> %ld 字节\n", 
               (long)last_state->size, (long)current_stat.st_size);
        changed = 1;
    }
    
    // 检查修改时间变化
    if (current_stat.st_mtime != last_state->mtime) {
        printf("文件修改时间变化\n");
        printf("  旧时间: %s", ctime(&last_state->mtime));
        printf("  新时间: %s", ctime(&current_stat.st_mtime));
        changed = 1;
    }
    
    // 更新状态
    last_state->mtime = current_stat.st_mtime;
    last_state->size = current_stat.st_size;
    
    return changed;
}

int main() {
    int fd;
    file_monitor_t monitor_state = {0, 0};
    int i;
    
    // 创建监控文件
    fd = open("monitor_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建监控文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("开始监控文件变化...\n");
    printf("文件描述符: %d\n", fd);
    
    // 初始化监控状态
    if (monitor_file_changes(fd, &monitor_state) == -1) {
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("初始状态记录完成\n");
    
    // 模拟文件变化
    for (i = 0; i < 3; i++) {
        sleep(2);
        
        printf("\n--- 第 %d 次检查 ---\n", i + 1);
        
        // 向文件添加内容
        char buffer[50];
        snprintf(buffer, sizeof(buffer), "更新内容 %d\n", i + 1);
        write(fd, buffer, strlen(buffer));
        
        // 检查变化
        if (monitor_file_changes(fd, &monitor_state)) {
            printf("检测到文件变化\n");
        } else {
            printf("文件无变化\n");
        }
    }
    
    close(fd);
    
    // 清理测试文件
    unlink("monitor_file.txt");
    
    return 0;
}

10. 实用宏和函数

// 检查文件类型的宏
S_ISREG(m)   // 是否为普通文件
S_ISDIR(m)   // 是否为目录
S_ISCHR(m)   // 是否为字符设备
S_ISBLK(m)   // 是否为块设备
S_ISFIFO(m)  // 是否为 FIFO
S_ISLNK(m)   // 是否为符号链接
S_ISSOCK(m)  // 是否为套接字

// 权限检查宏
S_IRUSR  // 用户读权限
S_IWUSR  // 用户写权限
S_IXUSR  // 用户执行权限
S_IRGRP  // 组读权限
S_IWGRP  // 组写权限
S_IXGRP  // 组执行权限
S_IROTH  // 其他读权限
S_IWOTH  // 其他写权限
S_IXOTH  // 其他执行权限

11. 实际应用场景

fstat 在以下场景中非常有用:

  • 文件管理器显示文件详细信息
  • 备份工具检查文件是否需要备份
  • 编译系统检查源文件是否比目标文件新
  • 日志系统监控文件大小和修改时间
  • 安全工具验证文件属性
  • 系统监控工具跟踪文件系统变化

总结

fstat 是获取文件状态信息的核心系统调用,通过文件描述符提供了安全可靠的接口。关键要点:

  1. 使用文件描述符避免路径相关的竞态条件
  2. 返回丰富的文件元数据信息
  3. 支持各种文件类型的状态查询
  4. 正确处理各种错误情况
  5. 在文件监控和管理应用中广泛使用

fstat 与相关的 statlstat 函数一起,构成了 Linux 系统中文件状态查询的基础工具集。

fstat是Linux系统调用,用于通过文件描述符获取文件状态信息,避免了路径操作可能引发的竞态问题。该函数返回包括文件类型、大小、权限、时间戳等元数据,存储在stat结构体中。其函数原型为int fstat(int fd, struct stat *buf),成功返回0,失败返回-1并设置errno。常见错误包括无效描述符(EBADF)和内存不足(ENOMEM)。与stat通过路径获取信息不同,fstat更适用于多线程环境。示例代码展示了如何获取文件基本信息、类型判断和错误处理,适用于普通文件、目录等各类文件系统对象的状态检查。

发表在 linux文章 | 留下评论

fsync系统调用及示例

fsync – 文件同步

函数介绍

fsync系统调用用于将文件的所有修改强制写入磁盘,确保数据的持久性。它会同步文件的数据和元数据(如修改时间、访问时间等),是保证数据安全的重要函数。掌握fsync系统调用,确保数据安全写入磁盘。了解其功能、用法及示例代码,提升文件操作效率与可靠性。关键词:fsync…

fsync系统调用, 文件同步fsync, Linux fsync使用示例, 如何使用fsync确保数据持久性, fsync函数详解, fsync与数据安全, 理解fsync及其重要性, 使用fsync提高文件操作安全性, fsync在编程中的应用, fsync系统调用实战案例

函数原型

#include <unistd.h>

int fsync(int fd);

功能

将文件描述符对应的文件所有修改(包括数据和元数据)强制写入磁盘。

参数

  • int fd: 文件描述符

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EBADF: 文件描述符无效
    • EIO: I/O错误
    • EINVAL: 文件描述符不支持同步
    • EROFS: 文件在只读文件系统上

相似函数

  • fdatasync(): 只同步文件数据,不同步所有元数据
  • sync(): 同步所有文件系统缓冲区
  • msync(): 同步内存映射文件

示例代码

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

int main() {
    int fd;
    struct stat stat_buf;
    time_t write_time, sync_time;
    
    printf("=== Fsync函数示例 ===\n");
    
    // 示例1: 基本的文件同步操作
    printf("\n示例1: 基本的文件同步操作\n");
    
    // 创建测试文件
    fd = open("test_fsync.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    printf("成功创建测试文件,文件描述符: %d\n", fd);
    
    // 写入数据
    const char *data1 = "First line of data\n";
    if (write(fd, data1, strlen(data1)) == -1) {
        perror("写入第一行数据失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("写入第一行数据: %s", data1);
    
    // 获取写入时间
    time(&write_time);
    printf("写入时间: %s", ctime(&write_time));
    
    // 同步文件到磁盘
    printf("开始同步文件到磁盘...\n");
    time(&sync_time);
    if (fsync(fd) == -1) {
        perror("文件同步失败");
    } else {
        printf("文件同步成功\n");
        printf("同步完成时间: %s", ctime(&sync_time));
    }
    
    // 再写入更多数据
    const char *data2 = "Second line of data\nThird line of data\n";
    if (write(fd, data2, strlen(data2)) == -1) {
        perror("写入更多数据失败");
    } else {
        printf("写入更多数据成功\n");
    }
    
    // 再次同步
    if (fsync(fd) == -1) {
        perror("第二次文件同步失败");
    } else {
        printf("第二次文件同步成功\n");
    }
    
    // 示例2: 同步前后文件状态对比
    printf("\n示例2: 同步前后文件状态对比\n");
    
    // 获取同步前的文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取文件状态失败");
    } else {
        printf("同步前文件状态:\n");
        printf("  文件大小: %ld 字节\n", stat_buf.st_size);
        printf("  最后修改时间: %s", ctime(&stat_buf.st_mtime));
        printf("  最后访问时间: %s", ctime(&stat_buf.st_atime));
    }
    
    // 写入数据但不同步
    const char *unsynced_data = "Unsynced data\n";
    if (write(fd, unsynced_data, strlen(unsynced_data)) == -1) {
        perror("写入未同步数据失败");
    }
    
    // 再次获取文件状态(可能还未写入磁盘)
    if (fstat(fd, &stat_buf) == -1) {
        perror("再次获取文件状态失败");
    } else {
        printf("写入未同步数据后的文件状态:\n");
        printf("  文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 同步文件
    if (fsync(fd) == -1) {
        perror("同步文件失败");
    } else {
        printf("文件同步完成\n");
    }
    
    // 同步后再次获取文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("同步后获取文件状态失败");
    } else {
        printf("同步后文件状态:\n");
        printf("  文件大小: %ld 字节\n", stat_buf.st_size);
        printf("  最后修改时间: %s", ctime(&stat_buf.st_mtime));
    }
    
    // 示例3: 性能对比演示
    printf("\n示例3: 性能对比演示\n");
    
    // 测试不使用fsync的写入性能
    clock_t start_time = clock();
    for (int i = 0; i < 1000; i++) {
        char buffer[50];
        sprintf(buffer, "Line %d: Performance test data\n", i);
        if (write(fd, buffer, strlen(buffer)) == -1) {
            perror("性能测试写入失败");
            break;
        }
    }
    clock_t without_sync_time = clock() - start_time;
    printf("不使用fsync写入1000行数据耗时: %f 秒\n", 
           ((double)without_sync_time) / CLOCKS_PER_SEC);
    
    // 测试使用fsync的写入性能
    start_time = clock();
    for (int i = 1000; i < 2000; i++) {
        char buffer[50];
        sprintf(buffer, "Line %d: Performance test data with sync\n", i);
        if (write(fd, buffer, strlen(buffer)) == -1) {
            perror("性能测试写入失败");
            break;
        }
        if (fsync(fd) == -1) {
            perror("同步失败");
            break;
        }
    }
    clock_t with_sync_time = clock() - start_time;
    printf("使用fsync写入1000行数据耗时: %f 秒\n", 
           ((double)with_sync_time) / CLOCKS_PER_SEC);
    
    printf("注意: fsync会显著降低写入性能,但保证数据安全\n");
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 尝试对无效文件描述符同步
    if (fsync(999) == -1) {
        printf("对无效文件描述符同步: %s\n", strerror(errno));
    }
    
    // 尝试对只读文件同步(应该成功)
    int readonly_fd = open("test_fsync.txt", O_RDONLY);
    if (readonly_fd != -1) {
        if (fsync(readonly_fd) == -1) {
            printf("对只读文件同步: %s\n", strerror(errno));
        } else {
            printf("对只读文件同步成功\n");
        }
        close(readonly_fd);
    }
    
    // 示例5: 数据安全重要性演示
    printf("\n示例5: 数据安全重要性演示\n");
    
    // 创建重要的数据文件
    int important_fd = open("important_data.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (important_fd != -1) {
        printf("创建重要数据文件\n");
        
        // 写入重要数据
        const char *important_data = "Very important data that must not be lost\n";
        if (write(important_fd, important_data, strlen(important_data)) != -1) {
            printf("写入重要数据\n");
            
            // 对于重要数据,必须同步到磁盘
            if (fsync(important_fd) == -1) {
                perror("重要数据同步失败");
            } else {
                printf("重要数据已安全同步到磁盘\n");
            }
        }
        
        close(important_fd);
        unlink("important_data.txt");
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (close(fd) == -1) {
        perror("关闭文件失败");
    } else {
        printf("成功关闭文件描述符 %d\n", fd);
    }
    
    if (unlink("test_fsync.txt") == -1) {
        perror("删除测试文件失败");
    } else {
        printf("成功删除测试文件\n");
    }
    
    return 0;
}

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

发表在 linux文章 | 留下评论

ftruncate系统调用及示例

ftruncate – 通过文件描述符截断文件

函数介绍

ftruncate系统调用用于将已打开的文件截断到指定长度。与truncate不同,ftruncate通过文件描述符而不是文件路径来操作文件,这在某些情况下更加高效和安全。

函数原型

#include <unistd.h>
#include <sys/types.h>

int ftruncate(int fd, off_t length);

功能

通过文件描述符将文件截断或扩展到指定长度。

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

参数

  • int fd: 文件描述符
  • off_t length: 目标文件长度(字节)

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno:
    • EBADF: 文件描述符无效或不是为写入而打开
    • EFBIG: 长度参数过大
    • EINVAL: 文件描述符不支持截断
    • EIO: I/O错误
    • EPERM: 操作不被允许
    • EROFS: 文件在只读文件系统上

相似函数

  • truncate(): 通过文件路径截断文件
  • open()配合O_TRUNC标志

示例代码

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

int main() {
    int fd;
    struct stat stat_buf;
    
    printf("=== Ftruncate函数示例 ===\n");
    
    // 示例1: 基本的文件截断操作
    printf("\n示例1: 基本的文件截断操作\n");
    
    // 创建测试文件并写入数据
    fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    
    // 写入测试数据
    const char *original_data = 
        "Line 1: This is the first line of data.\n"
        "Line 2: This is the second line of data.\n"
        "Line 3: This is the third line of data.\n"
        "Line 4: This is the fourth line of data.\n"
        "Line 5: This is the fifth line of data.\n";
    
    if (write(fd, original_data, strlen(original_data)) == -1) {
        perror("写入原始数据失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("写入原始数据,长度: %ld 字节\n", (long)strlen(original_data));
    
    // 获取原始文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取文件状态失败");
    } else {
        printf("原始文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 使用ftruncate截断文件到80字节
    printf("\n使用ftruncate将文件截断到80字节...\n");
    if (ftruncate(fd, 80) == -1) {
        perror("截断文件失败");
    } else {
        printf("文件截断成功\n");
    }
    
    // 检查截断后的文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取截断后文件状态失败");
    } else {
        printf("截断后文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 读取截断后的数据验证
    lseek(fd, 0, SEEK_SET);  // 重置文件位置指针
    char buffer[200];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("截断后文件内容:\n%s", buffer);
    }
    
    // 示例2: 扩展文件
    printf("\n示例2: 扩展文件\n");
    
    // 使用ftruncate将文件扩展到150字节
    printf("使用ftruncate将文件扩展到150字节...\n");
    if (ftruncate(fd, 150) == -1) {
        perror("扩展文件失败");
    } else {
        printf("文件扩展成功\n");
    }
    
    // 检查扩展后的文件状态
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取扩展后文件状态失败");
    } else {
        printf("扩展后文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 读取扩展后的数据
    lseek(fd, 0, SEEK_SET);  // 重置文件位置指针
    bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        printf("扩展后文件前%d字节内容:\n", (int)bytes_read);
        // 显示前100个字符
        for (int i = 0; i < bytes_read && i < 100; i++) {
            if (buffer[i] >= 32 && buffer[i] <= 126) {
                putchar(buffer[i]);
            } else if (buffer[i] == '\n') {
                putchar('\n');
            } else {
                printf("[%02x]", (unsigned char)buffer[i]);
            }
        }
        printf("\n");
    }
    
    // 示例3: 截断到0字节(清空文件)
    printf("\n示例3: 截断到0字节(清空文件)\n");
    
    printf("将文件截断到0字节...\n");
    if (ftruncate(fd, 0) == -1) {
        perror("清空文件失败");
    } else {
        printf("文件清空成功\n");
    }
    
    if (fstat(fd, &stat_buf) == -1) {
        perror("获取清空后文件状态失败");
    } else {
        printf("清空后文件大小: %ld 字节\n", stat_buf.st_size);
    }
    
    // 示例4: 与truncate的对比
    printf("\n示例4: ftruncate与truncate对比\n");
    
    // 重新写入数据
    const char *compare_data = "Data for comparison test: ftruncate vs truncate";
    if (write(fd, compare_data, strlen(compare_data)) == -1) {
        perror("写入对比数据失败");
    } else {
        printf("写入对比数据: %s\n", compare_data);
    }
    
    // 使用ftruncate截断
    clock_t ftruncate_start = clock();
    if (ftruncate(fd, 20) == -1) {
        perror("ftruncate失败");
    } else {
        clock_t ftruncate_time = clock() - ftruncate_start;
        printf("ftruncate成功,耗时: %f 秒\n", 
               ((double)ftruncate_time) / CLOCKS_PER_SEC);
    }
    
    // 重新创建文件测试truncate
    close(fd);
    unlink("test_ftruncate.txt");
    
    fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd != -1) {
        if (write(fd, compare_data, strlen(compare_data)) == -1) {
            perror("重新写入数据失败");
        }
        close(fd);
        
        // 使用truncate截断
        clock_t truncate_start = clock();
        if (truncate("test_ftruncate.txt", 20) == -1) {
            perror("truncate失败");
        } else {
            clock_t truncate_time = clock() - truncate_start;
            printf("truncate成功,耗时: %f 秒\n", 
                   ((double)truncate_time) / CLOCKS_PER_SEC);
        }
    }
    
    // 重新打开文件继续演示
    fd = open("test_ftruncate.txt", O_RDWR);
    if (fd == -1) {
        perror("重新打开文件失败");
        exit(EXIT_FAILURE);
    }
    
    // 示例5: 不同长度的截断演示
    printf("\n示例5: 不同长度的截断演示\n");
    
    // 重新写入测试数据
    const char *test_data = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    lseek(fd, 0, SEEK_SET);
    if (ftruncate(fd, 0) == -1) {  // 先清空
        perror("清空文件失败");
    }
    if (write(fd, test_data, strlen(test_data)) == -1) {
        perror("写入测试数据失败");
    }
    printf("重新写入测试数据: %s\n", test_data);
    
    // 演示不同长度的截断
    off_t lengths[] = {10, 25, 40, 70};
    for (int i = 0; i < 4; i++) {
        printf("截断到%ld字节: ", lengths[i]);
        if (ftruncate(fd, lengths[i]) == -1) {
            printf("失败 - %s\n", strerror(errno));
        } else {
            if (fstat(fd, &stat_buf) == -1) {
                printf("获取状态失败\n");
            } else {
                printf("成功,新大小: %ld字节\n", stat_buf.st_size);
                
                // 读取并显示内容
                lseek(fd, 0, SEEK_SET);
                char read_buffer[100];
                ssize_t bytes_read = read(fd, read_buffer, stat_buf.st_size);
                if (bytes_read > 0) {
                    read_buffer[bytes_read] = '\0';
                    printf("  内容: %s\n", read_buffer);
                }
            }
        }
    }
    
    // 示例6: 错误处理演示
    printf("\n示例6: 错误处理演示\n");
    
    // 尝试对无效文件描述符操作
    if (ftruncate(999, 100) == -1) {
        printf("对无效文件描述符操作: %s\n", strerror(errno));
    }
    
    // 尝试对只读文件描述符操作
    int readonly_fd = open("test_ftruncate.txt", O_RDONLY);
    if (readonly_fd != -1) {
        if (ftruncate(readonly_fd, 50) == -1) {
            printf("对只读文件描述符操作: %s\n", strerror(errno));
        }
        close(readonly_fd);
    }
    
    // 尝试使用负数长度
    if (ftruncate(fd, -10) == -1) {
        printf("使用负数长度: %s\n", strerror(errno));
    }
    
    // 尝试对管道或套接字操作(需要特殊文件描述符)
    int pipefd[2];
    if (pipe(pipefd) == 0) {
        if (ftruncate(pipefd[0], 100) == -1) {
            printf("对管道操作: %s\n", strerror(errno));
        }
        close(pipefd[0]);
        close(pipefd[1]);
    }
    
    // 示例7: 实际应用场景
    printf("\n示例7: 实际应用场景\n");
    
    // 场景1: 编辑器临时文件管理
    printf("场景1: 编辑器临时文件管理\n");
    int editor_fd = open("editor_temp.txt", O_CREAT | O_RDWR, 0644);
    if (editor_fd != -1) {
        // 模拟编辑器写入大量临时数据
        char temp_data[100];
        for (int i = 0; i < 50; i++) {
            sprintf(temp_data, "Temporary line %d for editor\n", i);
            write(editor_fd, temp_data, strlen(temp_data));
        }
        
        if (fstat(editor_fd, &stat_buf) == 0) {
            printf("临时文件大小: %ld 字节\n", stat_buf.st_size);
            
            // 当用户撤销操作时,可能需要截断文件
            off_t undo_position = stat_buf.st_size - 200;  // 模拟撤销到某个位置
            if (undo_position > 0) {
                printf("撤销操作,截断到位置: %ld\n", undo_position);
                if (ftruncate(editor_fd, undo_position) == -1) {
                    perror("撤销操作失败");
                } else {
                    printf("撤销操作成功\n");
                }
            }
        }
        
        close(editor_fd);
        unlink("editor_temp.txt");
    }
    
    // 场景2: 数据库文件维护
    printf("场景2: 数据库文件维护\n");
    int db_fd = open("database.dat", O_CREAT | O_RDWR, 0644);
    if (db_fd != -1) {
        // 模拟数据库文件增长
        char db_record[50];
        for (int i = 0; i < 100; i++) {
            sprintf(db_record, "Database record %d with some data\n", i);
            write(db_fd, db_record, strlen(db_record));
        }
        
        if (fstat(db_fd, &stat_buf) == 0) {
            printf("数据库文件原始大小: %ld 字节\n", stat_buf.st_size);
            
            // 模拟数据库压缩操作
            off_t compressed_size = stat_buf.st_size * 0.8;  // 压缩到80%
            printf("压缩数据库文件到: %ld 字节\n", compressed_size);
            if (ftruncate(db_fd, compressed_size) == -1) {
                perror("数据库压缩失败");
            } else {
                printf("数据库压缩成功\n");
            }
        }
        
        close(db_fd);
        unlink("database.dat");
    }
    
    // 场景3: 日志文件循环
    printf("场景3: 日志文件循环\n");
    int log_fd = open("circular_log.txt", O_CREAT | O_RDWR, 0644);
    if (log_fd != -1) {
        // 写入日志数据直到达到限制
        const char *log_entry = "LOG: System operation completed successfully\n";
        for (int i = 0; i < 20; i++) {
            char entry[100];
            sprintf(entry, "Entry %d: %s", i, log_entry);
            write(log_fd, entry, strlen(entry));
        }
        
        if (fstat(log_fd, &stat_buf) == 0) {
            printf("日志文件当前大小: %ld 字节\n", stat_buf.st_size);
            
            // 如果超过限制(比如1KB),保留最后512字节
            if (stat_buf.st_size > 1024) {
                printf("日志文件过大,保留最后512字节\n");
                
                // 读取最后512字节
                char last_data[512];
                off_t start_pos = stat_buf.st_size - 512;
                if (start_pos > 0) {
                    lseek(log_fd, start_pos, SEEK_SET);
                    ssize_t bytes_read = read(log_fd, last_data, 512);
                    if (bytes_read > 0) {
                        // 截断文件到0,然后写入保留的数据
                        ftruncate(log_fd, 0);
                        lseek(log_fd, 0, SEEK_SET);
                        write(log_fd, last_data, bytes_read);
                        printf("日志循环完成,新大小: %ld 字节\n", (long)bytes_read);
                    }
                }
            }
        }
        
        close(log_fd);
        unlink("circular_log.txt");
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    if (close(fd) == -1) {
        perror("关闭文件失败");
    } else {
        printf("成功关闭文件描述符 %d\n", fd);
    }
    
    if (unlink("test_ftruncate.txt") == -1) {
        perror("删除测试文件失败");
    } else {
        printf("成功删除测试文件\n");
    }
    
    return 0;
}
发表在 linux文章 | 留下评论

futex系统调用及示例

futex 函数详解

1. 函数介绍

futex(Fast Userspace muTEX)是 Linux 内核提供的一种高效的同步原语。可以把 futex 想象成一个”智能的红绿灯系统”——在大多数情况下,它在用户空间快速运行(就像绿灯畅通无阻),只有在发生竞争时才需要内核介入(就像遇到红灯需要交通警察协调)。

传统的互斥锁在用户空间和内核空间之间频繁切换,开销很大。而 futex 的巧妙之处在于:如果没有竞争,线程可以在用户空间直接完成同步操作;只有当出现等待情况时,才需要进入内核进行睡眠和唤醒操作。这就像排队买票,如果没人排队,你可以直接买票离开;如果人很多,就需要排队等待,这时工作人员会来维持秩序。

2. 函数原型

#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>

int futex(int *uaddr, int futex_op, int val,
          const struct timespec *timeout,   /* or: uint32_t val2 */
          int *uaddr2, int val3);

注意:futex 是系统调用,通常通过 syscall() 函数调用。

3. 功能

futex 提供了一种高效的用户空间同步机制,主要用于实现各种同步原语(如互斥锁、条件变量、信号量等)。它通过在用户空间和内核空间之间智能切换,实现了高性能的线程同步。

4. 参数

  • uaddr: 指向用户空间的一个整型变量(futex 字),用于存储同步状态
  • futex_op: 操作类型(见下表)
  • val: 操作参数,根据不同的操作类型有不同的含义
  • timeout: 超时时间(可选,某些操作使用)
  • uaddr2: 第二个 futex 地址(某些操作使用)
  • val3: 第三个参数(某些操作使用)

5. 常用操作类型

操作说明功能
FUTEX_WAIT等待操作如果 *uaddr == val,则睡眠等待
FUTEX_WAKE唤醒操作唤醒在 uaddr 上等待的 val 个线程
FUTEX_WAIT_BITSET带位集的等待可以指定唤醒条件
FUTEX_WAKE_BITSET带位集的唤醒按位集唤醒等待线程
FUTEX_LOCK_PI优先级继承锁用于实现互斥锁
FUTEX_UNLOCK_PI解锁优先级继承锁解除互斥锁

6. 返回值

  • 成功: 返回值根据操作类型而定
    • FUTEX_WAIT: 成功返回 0,超时返回 -1 并设置 errno 为 ETIMEDOUT
    • FUTEX_WAKE: 返回唤醒的线程数
  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EAGAIN: 条件不满足(如 FUTEX_WAIT 时值已改变)
  • ETIMEDOUT: 等待超时
  • EINVAL: 参数无效
  • EFAULT: 地址无效

7. 相似函数或关联函数

  • pthread_mutex_t: POSIX 互斥锁(基于 futex 实现)
  • pthread_cond_t: POSIX 条件变量(基于 futex 实现)
  • sem_t: POSIX 信号量(基于 futex 实现)
  • atomic operations: 原子操作(与 futex 配合使用)

8. 示例代码

示例1:基础用法 – 实现简单的计数器同步

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>
#include <stdatomic.h>

// futex 系统调用封装
int futex_wait(int *addr, int val, const struct timespec *timeout) {
    return syscall(SYS_futex, addr, FUTEX_WAIT, val, timeout, NULL, 0);
}

int futex_wake(int *addr, int num) {
    return syscall(SYS_futex, addr, FUTEX_WAKE, num, NULL, NULL, 0);
}

// 全局计数器和同步变量
atomic_int counter = 0;
int futex_var = 0;  // 0: 可访问, 1: 正在访问

// 线程函数
void* worker_thread(void* arg) {
    int thread_id = *(int*)arg;
    
    for (int i = 0; i < 5; i++) {
        // 尝试获取锁
        int expected = 0;
        while (!atomic_compare_exchange_weak(&futex_var, &expected, 1)) {
            // 如果获取失败,等待
            futex_wait(&futex_var, 1, NULL);
            expected = 0;
        }
        
        // 临界区:访问共享资源
        int old_value = atomic_fetch_add(&counter, 1);
        printf("线程 %d: 计数器从 %d 增加到 %d\n", 
               thread_id, old_value, old_value + 1);
        
        // 模拟一些工作
        usleep(100000);  // 100ms
        
        // 释放锁
        atomic_store(&futex_var, 0);
        futex_wake(&futex_var, 1);  // 唤醒一个等待的线程
        
        // 非临界区工作
        usleep(50000);  // 50ms
    }
    
    return NULL;
}

int main() {
    pthread_t threads[3];
    int thread_ids[3] = {1, 2, 3};
    
    printf("=== FUTEX 基础同步示例 ===\n");
    printf("启动 3 个线程,每个线程对计数器执行 5 次操作\n\n");
    
    // 创建线程
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, worker_thread, &thread_ids[i]) != 0) {
            perror("pthread_create");
            return 1;
        }
    }
    
    // 等待所有线程完成
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("\n最终计数器值: %d\n", atomic_load(&counter));
    return 0;
}

示例2:带超时的 futex 操作

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <errno.h>
#include <stdatomic.h>

int futex_wait_timeout(int *addr, int val, int timeout_ms) {
    struct timespec timeout;
    timeout.tv_sec = timeout_ms / 1000;
    timeout.tv_nsec = (timeout_ms % 1000) * 1000000;
    
    return syscall(SYS_futex, addr, FUTEX_WAIT, val, &timeout, NULL, 0);
}

int futex_wake(int *addr, int num) {
    return syscall(SYS_futex, addr, FUTEX_WAKE, num, NULL, NULL, 0);
}

// 生产者-消费者场景
int buffer = -1;  // -1 表示空
int data_ready = 0;  // 0: 无数据, 1: 有数据

void* producer_thread(void* arg) {
    for (int i = 1; i <= 5; i++) {
        printf("生产者: 准备生产数据 %d\n", i);
        
        // 等待缓冲区为空
        while (__atomic_load_n(&data_ready, __ATOMIC_ACQUIRE) == 1) {
            printf("生产者: 缓冲区满,等待消费者...\n");
            int ret = futex_wait_timeout(&data_ready, 1, 2000);  // 2秒超时
            if (ret == -1 && errno == ETIMEDOUT) {
                printf("生产者: 等待超时,继续检查...\n");
            }
        }
        
        // 生产数据
        buffer = i;
        __atomic_store_n(&data_ready, 1, __ATOMIC_RELEASE);
        printf("生产者: 生产了数据 %d\n", i);
        
        // 唤醒消费者
        futex_wake(&data_ready, 1);
        
        sleep(1);
    }
    
    return NULL;
}

void* consumer_thread(void* arg) {
    for (int i = 0; i < 5; i++) {
        printf("消费者: 准备消费数据\n");
        
        // 等待数据就绪
        while (__atomic_load_n(&data_ready, __ATOMIC_ACQUIRE) == 0) {
            printf("消费者: 无数据,等待生产者...\n");
            int ret = futex_wait_timeout(&data_ready, 0, 3000);  // 3秒超时
            if (ret == -1 && errno == ETIMEDOUT) {
                printf("消费者: 等待超时,继续检查...\n");
            }
        }
        
        // 消费数据
        printf("消费者: 消费了数据 %d\n", buffer);
        buffer = -1;
        __atomic_store_n(&data_ready, 0, __ATOMIC_RELEASE);
        
        // 唤醒生产者
        futex_wake(&data_ready, 1);
        
        sleep(2);
    }
    
    return NULL;
}

int main() {
    pthread_t producer, consumer;
    
    printf("=== 带超时的 FUTEX 操作示例 ===\n");
    printf("生产者生产 5 个数据,消费者消费它们\n");
    printf("等待操作设置 2-3 秒超时\n\n");
    
    // 创建线程
    if (pthread_create(&producer, NULL, producer_thread, NULL) != 0 ||
        pthread_create(&consumer, NULL, consumer_thread, NULL) != 0) {
        perror("pthread_create");
        return 1;
    }
    
    // 等待线程完成
    pthread_join(producer, NULL);
    pthread_join(consumer, NULL);
    
    printf("\n生产者-消费者操作完成\n");
    return 0;
}

示例3:实现简单的互斥锁

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <errno.h>
#include <stdatomic.h>

// 简单的 futex 互斥锁实现
typedef struct {
    atomic_int state;  // 0: 未锁定, 1: 已锁定
} futex_mutex_t;

// 初始化互斥锁
void futex_mutex_init(futex_mutex_t *mutex) {
    atomic_store(&mutex->state, 0);
}

// 加锁
void futex_mutex_lock(futex_mutex_t *mutex) {
    int expected = 0;
    // 尝试原子地将状态从 0 改为 1
    while (!atomic_compare_exchange_weak(&mutex->state, &expected, 1)) {
        // 如果失败,说明锁已被占用,等待
        syscall(SYS_futex, &mutex->state, FUTEX_WAIT, 1, NULL, NULL, 0);
        expected = 0;  // 重置期望值
    }
}

// 解锁
void futex_mutex_unlock(futex_mutex_t *mutex) {
    // 原子地将状态改为 0
    atomic_store(&mutex->state, 0);
    // 唤醒一个等待的线程
    syscall(SYS_futex, &mutex->state, FUTEX_WAKE, 1, NULL, NULL, 0);
}

// 全局变量和互斥锁
int shared_counter = 0;
futex_mutex_t my_mutex;

// 工作线程函数
void* increment_thread(void* arg) {
    int thread_id = *(int*)arg;
    int operations = 100000;
    
    printf("线程 %d: 开始执行 %d 次操作\n", thread_id, operations);
    
    for (int i = 0; i < operations; i++) {
        futex_mutex_lock(&my_mutex);
        // 临界区
        shared_counter++;
        futex_mutex_unlock(&my_mutex);
    }
    
    printf("线程 %d: 完成\n", thread_id);
    return NULL;
}

int main() {
    pthread_t threads[4];
    int thread_ids[4] = {1, 2, 3, 4};
    int expected_result = 400000;
    
    printf("=== FUTEX 互斥锁实现示例 ===\n");
    printf("4 个线程,每个执行 100,000 次递增操作\n");
    printf("期望结果: %d\n\n", expected_result);
    
    // 初始化互斥锁
    futex_mutex_init(&my_mutex);
    
    // 创建线程
    for (int i = 0; i < 4; i++) {
        if (pthread_create(&threads[i], NULL, increment_thread, &thread_ids[i]) != 0) {
            perror("pthread_create");
            return 1;
        }
    }
    
    // 等待所有线程完成
    for (int i = 0; i < 4; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("\n实际结果: %d\n", shared_counter);
    printf("结果%s正确\n", (shared_counter == expected_result) ? "" : "不");
    
    if (shared_counter != expected_result) {
        printf("可能存在竞态条件或同步问题\n");
    }
    
    return 0;
}

编译和运行说明

# 编译示例(需要链接 pthread 库)
gcc -o futex_example1 example1.c -lpthread
gcc -o futex_example2 example2.c -lpthread
gcc -o futex_example3 example3.c -lpthread

# 运行示例
./futex_example1
./futex_example2
./futex_example3

重要注意事项

  1. 原子操作配合: futex 通常与原子操作(atomic operations)配合使用,确保检查和等待操作的原子性
  2. 虚假唤醒: 与条件变量一样,futex 也可能出现虚假唤醒,需要在循环中检查条件
  3. 内存序: 使用适当的内存序(memory ordering)确保同步正确性
  4. 错误处理: 始终检查 futex 调用的返回值和 errno
  5. 移植性: futex 是 Linux 特有的特性,在其他系统上不可用
  6. 调试困难: futex 相关的 bug 很难调试,建议使用成熟的同步库

futex 的优势

  1. 高性能: 无竞争时完全在用户空间操作
  2. 低延迟: 避免不必要的内核切换
  3. 可扩展性: 支持大量并发线程
  4. 灵活性: 可以实现各种同步原语

这些示例展示了 futex 的基本使用方法,从简单的同步操作到带超时机制,再到完整的互斥锁实现,帮助你理解这个强大而高效的同步机制。

futex系统调用, futex函数详解, Linux futex示例, Fast Userspace muTEX, futex编程指南, 如何使用futex, futex性能优化, 基于futex的同步机制, futex与线程同步, 高级futex技术应用

https://blog.csdn.net/zidier215/article/details/151332503?sharetype=blogdetail&sharerId=151332503&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

发表在 linux文章 | 留下评论

get_kernel_syms系统调用及示例

get_kernel_syms – 获取内核符号表信息(已废弃)

1. 函数介绍

get_kernel_syms 是一个已废弃的 Linux 系统调用,用于获取内核符号表的信息。它曾经被用来检索内核中导出的符号(函数和变量)的列表,包括它们的地址、类型和名称。

重要说明:这个系统调用在现代 Linux 内核中已被废弃和移除,不再推荐使用。现代系统应该使用其他方式来获取内核符号信息。

2. 函数原型

#include <linux/kernel.h>

int get_kernel_syms(struct kernel_sym *table);

3. 功能

获取内核符号表中导出符号的信息,包括符号名称、地址和类型。主要用于内核调试、模块加载和系统分析工具。

4. 参数

  • struct kernel_sym *table: 指向 kernel_sym 结构体数组的指针
    • 如果为 NULL:返回符号表中的符号总数
    • 如果非 NULL:将符号信息填充到该数组中

5. kernel_sym 结构体定义

struct kernel_sym {
    unsigned long ks_addr;    /* 符号地址 */
    char          ks_name[60]; /* 符号名称(最多59个字符+null终止符)*/
};

6. 返回值

  • 成功时:
    • 如果 table 为 NULL:返回内核符号表中的符号总数
    • 如果 table 非 NULL:返回实际填充的符号数量
  • 失败时返回 -1,并设置 errno

7. 常见 errno 错误码

  • EPERM: 权限不足(需要 CAP_SYS_ADMIN 或 CAP_SYS_MODULE 权限)
  • EFAULTtable 指针指向无效内存地址
  • ENOMEM: 内存不足
  • ENOSYS: 系统调用不支持(现代内核中常见)

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

  • /proc/kallsyms: 现代系统中获取内核符号的标准方式
  • /proc/sys/kernel/kptr_restrict: 控制内核指针显示的设置
  • klogctl(): 控制内核日志缓冲区
  • uname(): 获取系统信息
  • sysinfo(): 获取系统统计信息
  • nm 命令:用户态工具查看目标文件符号表
  • /boot/System.map: 内核构建时生成的符号映射文件

9. 示例代码

示例1:检查系统是否支持 get_kernel_syms

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

#ifndef SYS_get_kernel_syms
# define SYS_get_kernel_syms 177  // x86_64 架构下的系统调用号
#endif

int main() {
    long result;
    
    printf("检查系统是否支持 get_kernel_syms...\n");
    
    // 首先尝试获取符号数量(传入 NULL)
    result = syscall(SYS_get_kernel_syms, NULL);
    
    if (result == -1) {
        printf("get_kernel_syms 调用失败\n");
        printf("errno = %d: %s\n", errno, strerror(errno));
        
        switch (errno) {
            case ENOSYS:
                printf("系统调用已废弃或不支持\n");
                break;
            case EPERM:
                printf("权限不足,需要特权权限\n");
                break;
            default:
                printf("其他错误\n");
                break;
        }
    } else {
        printf("系统支持 get_kernel_syms,符号数量: %ld\n", result);
    }
    
    return 0;
}

示例2:传统使用方式(仅在旧系统上可能工作)

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

#ifndef SYS_get_kernel_syms
# define SYS_get_kernel_syms 177
#endif

// 注意:现代 glibc 可能不包含这个结构体定义
struct kernel_sym {
    unsigned long ks_addr;
    char          ks_name[60];
};

int main() {
    long sym_count;
    struct kernel_sym *sym_table;
    long result;
    int i;
    
    printf("=== get_kernel_syms 测试 ===\n");
    
    // 第一次调用:获取符号数量
    sym_count = syscall(SYS_get_kernel_syms, NULL);
    
    if (sym_count == -1) {
        printf("获取符号数量失败: %s\n", strerror(errno));
        if (errno == ENOSYS) {
            printf("提示: get_kernel_syms 已在现代内核中废弃\n");
            printf("请使用 /proc/kallsyms 替代\n");
        }
        return 1;
    }
    
    printf("内核符号数量: %ld\n", sym_count);
    
    if (sym_count == 0) {
        printf("没有可用的内核符号\n");
        return 0;
    }
    
    // 限制获取的符号数量以避免内存问题
    if (sym_count > 1000) {
        printf("符号数量过多,限制为前1000个\n");
        sym_count = 1000;
    }
    
    // 分配内存
    sym_table = malloc(sym_count * sizeof(struct kernel_sym));
    if (sym_table == NULL) {
        perror("内存分配失败");
        return 1;
    }
    
    // 第二次调用:获取符号信息
    result = syscall(SYS_get_kernel_syms, sym_table);
    
    if (result == -1) {
        printf("获取符号信息失败: %s\n", strerror(errno));
        free(sym_table);
        return 1;
    }
    
    printf("成功获取 %ld 个符号信息\n", result);
    
    // 显示前几个符号作为示例
    printf("\n前10个内核符号:\n");
    printf("%-18s %s\n", "地址", "符号名称");
    printf("%-18s %s\n", "----", "--------");
    
    for (i = 0; i < result && i < 10; i++) {
        printf("0x%016lx %s\n", 
               sym_table[i].ks_addr, 
               sym_table[i].ks_name);
    }
    
    free(sym_table);
    return 0;
}

10. 现代替代方案

由于 get_kernel_syms 已被废弃,现代系统应该使用以下替代方案:

方案1:使用 /proc/kallsyms

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *fp;
    char line[256];
    char address[32];
    char type;
    char symbol[128];
    int count = 0;
    
    fp = fopen("/proc/kallsyms", "r");
    if (fp == NULL) {
        perror("打开 /proc/kallsyms 失败");
        return 1;
    }
    
    printf("读取内核符号表 (/proc/kallsyms):\n");
    printf("%-18s %-8s %s\n", "地址", "类型", "符号");
    printf("%-18s %-8s %s\n", "----", "----", "----");
    
    // 读取前20个符号
    while (fgets(line, sizeof(line), fp) && count < 20) {
        if (sscanf(line, "%s %c %s", address, &type, symbol) == 3) {
            printf("%-18s %-8c %s\n", address, type, symbol);
            count++;
        }
    }
    
    fclose(fp);
    
    // 检查 kptr_restrict 设置
    fp = fopen("/proc/sys/kernel/kptr_restrict", "r");
    if (fp != NULL) {
        int restrict_level;
        if (fscanf(fp, "%d", &restrict_level) == 1) {
            printf("\nkptr_restrict 级别: %d\n", restrict_level);
            if (restrict_level > 0) {
                printf("注意: 内核指针可能被限制显示\n");
            }
        }
        fclose(fp);
    }
    
    return 0;
}

方案2:使用 System.map 文件

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *fp;
    char line[256];
    char address[32];
    char type;
    char symbol[128];
    int count = 0;
    
    // 尝试打开 System.map 文件
    const char *system_map_paths[] = {
        "/boot/System.map-$(uname -r)",  // 需要 shell 展开
        "/boot/System.map",
        "/lib/modules/$(uname -r)/System.map",  // 需要 shell 展开
        NULL
    };
    
    // 简化版本:直接尝试常见路径
    fp = fopen("/boot/System.map", "r");
    if (fp == NULL) {
        printf("System.map 文件不可用\n");
        printf("现代系统推荐使用 /proc/kallsyms\n");
        return 1;
    }
    
    printf("读取 System.map 文件:\n");
    printf("%-18s %-8s %s\n", "地址", "类型", "符号");
    printf("%-18s %-8s %s\n", "----", "----", "----");
    
    // 读取前10个符号
    while (fgets(line, sizeof(line), fp) && count < 10) {
        if (sscanf(line, "%s %c %s", address, &type, symbol) == 3) {
            printf("%-18s %-8c %s\n", address, type, symbol);
            count++;
        }
    }
    
    fclose(fp);
    return 0;
}

11. 符号类型说明

在 /proc/kallsyms 中,符号类型字符含义:

  • T/t: 代码段中的符号(大写表示全局,小写表示局部)
  • D/d: 数据段中的符号
  • B/b: BSS段中的符号
  • R/r: 只读数据段中的符号
  • A: 绝对符号
  • U: 未定义符号
  • W/w: 弱符号

12. 安全考虑

现代 Linux 系统出于安全考虑,可能会限制内核符号信息的访问:

  1. kptr_restrict: 控制内核指针的显示
    • 0: 允许显示内核指针
    • 1: 普通用户看不到内核指针(显示为0)
    • 2: 所有用户都看不到内核指针
  2. 权限要求: 访问内核符号信息通常需要特殊权限

总结

get_kernel_syms 是一个已废弃的系统调用,现代 Linux 系统开发应该:

  1. 避免使用:该系统调用在新内核中已不可用
  2. 使用替代方案:推荐使用 /proc/kallsyms 获取内核符号信息
  3. 注意安全限制:了解 kptr_restrict 对符号显示的影响
  4. 权限管理:确保有足够的权限访问内核信息
  5. 兼容性考虑:在代码中检查系统调用是否可用

对于内核调试、模块开发和系统分析需求,现代工具链提供了更安全、更可靠的替代方案。

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

发表在 linux文章 | 留下评论

futimesat系统调用及示例

futimens 函数详解

1. 函数介绍

futimens 是 Linux 系统中用于设置文件时间戳的系统调用。可以把这个函数想象成一个”时间修改器”——它允许你修改文件的访问时间和修改时间,就像你可以修改照片的拍摄时间一样。

在 Linux 文件系统中,每个文件都有两个重要的时间戳:

  • 访问时间(atime): 文件最后一次被读取的时间
  • 修改时间(mtime): 文件内容最后一次被修改的时间

futimens 通过文件描述符来修改这些时间戳,这使得它比 utimes 更安全,因为文件描述符确保了你操作的是已经打开的文件,避免了竞态条件。

https://www.calcguide.tech/2025/09/08/futimesat%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b

2. 函数原型

#include <fcntl.h>     /* 必须包含此头文件 */
#include <sys/stat.h>  /* 或者包含此头文件 */

int futimens(int fd, const struct timespec times[2]);

3. 功能

futimens 函数用于设置指定文件描述符对应的文件的时间戳。它可以精确地设置文件的访问时间和修改时间到纳秒级别。

4. 参数

  • fd: 已打开的文件描述符
  • times: 指向 struct timespec 数组的指针,数组包含两个元素:
    • times[0]: 访问时间(atime)
    • times[1]: 修改时间(mtime)
    struct timespec 结构体定义:struct timespec { time_t tv_sec; /* 秒数 */ long tv_nsec; /* 纳秒数 */ };

5. 特殊时间值

在 times 数组中,可以使用以下特殊值:

  • UTIME_OMIT: 保持当前时间戳不变(跳过此时间的修改)
  • UTIME_NOW: 将时间戳设置为当前时间

6. 返回值

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

常见错误码:

  • EBADF: fd 不是有效的文件描述符
  • EACCES: 权限不足(通常需要写权限)
  • EINVAL: times 参数无效
  • EROFS: 文件系统是只读的
  • EIO: I/O 错误

7. 相似函数或关联函数

  • utimensat: 通过文件路径设置时间戳(更灵活,可以指定相对路径)
  • utimes: 设置文件时间戳(较老的接口,精度较低)
  • futimes: 通过文件描述符设置时间戳(较老的接口)
  • stat: 获取文件当前的时间戳信息
  • fstat: 通过文件描述符获取文件信息

8. 示例代码

示例1:基础用法 – 设置特定时间戳

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

int main() {
    int fd;
    const char *filename = "timestamp_test.txt";
    struct timespec new_times[2];
    struct stat file_info;
    
    // 创建并写入测试文件
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    const char *content = "这是一个测试文件\n用于演示 futimens 函数\n";
    write(fd, content, strlen(content));
    close(fd);
    
    // 重新打开文件以获取文件描述符
    fd = open(filename, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    // 设置特定的时间戳
    // 访问时间:2023年1月1日 12:00:00
    new_times[0].tv_sec = 1672545600;  // Unix 时间戳
    new_times[0].tv_nsec = 123456789;  // 纳秒
    
    // 修改时间:2023年6月1日 15:30:45
    new_times[1].tv_sec = 1685626245;  // Unix 时间戳
    new_times[1].tv_nsec = 987654321;  // 纳秒
    
    printf("=== 修改前的时间戳 ===\n");
    
    // 获取并显示修改前的时间
    if (stat(filename, &file_info) == -1) {
        perror("stat");
        close(fd);
        return 1;
    }
    
    printf("访问时间: %s", ctime(&file_info.st_atime));
    printf("修改时间: %s", ctime(&file_info.st_mtime));
    
    // 使用 futimens 设置新的时间戳
    if (futimens(fd, new_times) == -1) {
        perror("futimens");
        close(fd);
        return 1;
    }
    
    printf("\n=== 修改后的时间戳 ===\n");
    
    // 获取并显示修改后的时间
    if (stat(filename, &file_info) == -1) {
        perror("stat");
        close(fd);
        return 1;
    }
    
    printf("访问时间: %s", ctime(&file_info.st_atime));
    printf("修改时间: %s", ctime(&file_info.st_mtime));
    
    // 显示纳秒级精度
    printf("\n=== 纳秒级精度 ===\n");
    printf("访问时间: %ld.%09ld 秒\n", 
           file_info.st_atim.tv_sec, file_info.st_atim.tv_nsec);
    printf("修改时间: %ld.%09ld 秒\n", 
           file_info.st_mtim.tv_sec, file_info.st_mtim.tv_nsec);
    
    close(fd);
    return 0;
}

示例2:使用特殊时间值

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

// 显示文件时间信息的辅助函数
void show_file_times(const char *filename, const char *description) {
    struct stat file_info;
    
    if (stat(filename, &file_info) == -1) {
        perror("stat");
        return;
    }
    
    printf("%s:\n", description);
    printf("  访问时间: %s", ctime(&file_info.st_atime));
    printf("  修改时间: %s", ctime(&file_info.st_mtime));
    printf("\n");
}

int main() {
    int fd;
    const char *filename = "special_times_test.txt";
    struct timespec times[2];
    
    // 创建测试文件
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    const char *content = "测试特殊时间值功能\n";
    write(fd, content, strlen(content));
    close(fd);
    
    // 显示初始时间
    show_file_times(filename, "初始时间");
    
    // 重新打开文件
    fd = open(filename, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    printf("=== 演示各种时间设置方式 ===\n\n");
    
    // 1. 使用 UTIME_NOW:将两个时间都设置为当前时间
    printf("1. 使用 UTIME_NOW (设置为当前时间):\n");
    times[0].tv_sec = 0;
    times[0].tv_nsec = UTIME_NOW;
    times[1].tv_sec = 0;
    times[1].tv_nsec = UTIME_NOW;
    
    if (futimens(fd, times) == -1) {
        perror("futimens UTIME_NOW");
    } else {
        show_file_times(filename, "  设置后");
    }
    
    sleep(2);  // 等待 2 秒以便看到时间变化
    
    // 2. 只修改访问时间为当前时间,保持修改时间不变
    printf("2. 只修改访问时间为当前时间:\n");
    times[0].tv_nsec = UTIME_NOW;     // 访问时间设为当前
    times[1].tv_nsec = UTIME_OMIT;    // 修改时间保持不变
    
    if (futimens(fd, times) == -1) {
        perror("futimens partial update");
    } else {
        show_file_times(filename, "  设置后");
    }
    
    sleep(2);
    
    // 3. 只修改修改时间为特定时间,保持访问时间不变
    printf("3. 只修改修改时间为特定时间:\n");
    times[0].tv_nsec = UTIME_OMIT;    // 访问时间保持不变
    times[1].tv_sec = 1609459200;     // 2021年1月1日 00:00:00
    times[1].tv_nsec = 500000000;     // 500毫秒
    
    if (futimens(fd, times) == -1) {
        perror("futimens specific mtime");
    } else {
        show_file_times(filename, "  设置后");
    }
    
    close(fd);
    return 0;
}

示例3:批量文件时间戳管理工具

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

// 文件时间信息结构体
struct file_time_info {
    char filename[256];
    time_t atime;
    time_t mtime;
    off_t size;
};

// 获取文件时间信息
int get_file_time_info(const char *filepath, struct file_time_info *info) {
    struct stat file_stat;
    
    if (stat(filepath, &file_stat) == -1) {
        return -1;
    }
    
    strncpy(info->filename, filepath, sizeof(info->filename) - 1);
    info->filename[sizeof(info->filename) - 1] = '\0';
    info->atime = file_stat.st_atime;
    info->mtime = file_stat.st_mtime;
    info->size = file_stat.st_size;
    
    return 0;
}

// 设置文件时间戳
int set_file_times(const char *filepath, time_t new_atime, time_t new_mtime) {
    int fd;
    struct timespec times[2];
    
    fd = open(filepath, O_WRONLY);
    if (fd == -1) {
        // 如果写打开失败,尝试只读打开(某些文件可能只需要读权限)
        fd = open(filepath, O_RDONLY);
        if (fd == -1) {
            return -1;
        }
    }
    
    times[0].tv_sec = new_atime;
    times[0].tv_nsec = 0;
    times[1].tv_sec = new_mtime;
    times[1].tv_nsec = 0;
    
    int result = futimens(fd, times);
    close(fd);
    
    return result;
}

// 打印文件时间信息
void print_file_info(const struct file_time_info *info) {
    printf("文件: %s\n", info->filename);
    printf("  大小: %ld 字节\n", (long)info->size);
    printf("  访问时间: %s", ctime(&info->atime));
    printf("  修改时间: %s", ctime(&info->mtime));
}

// 将目录中所有文件的时间戳设置为相同时间
int sync_directory_times(const char *dirpath, time_t target_time) {
    DIR *dir;
    struct dirent *entry;
    char filepath[512];
    int count = 0;
    int errors = 0;
    
    dir = opendir(dirpath);
    if (dir == NULL) {
        perror("opendir");
        return -1;
    }
    
    printf("正在同步目录 '%s' 中文件的时间戳...\n", dirpath);
    printf("目标时间: %s\n", ctime(&target_time));
    
    while ((entry = readdir(dir)) != NULL) {
        // 跳过当前目录和父目录
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        // 构造完整文件路径
        snprintf(filepath, sizeof(filepath), "%s/%s", dirpath, entry->d_name);
        
        // 设置文件时间戳
        if (set_file_times(filepath, target_time, target_time) == 0) {
            printf("✓ 成功: %s\n", entry->d_name);
            count++;
        } else {
            printf("✗ 失败: %s (%s)\n", entry->d_name, strerror(errno));
            errors++;
        }
    }
    
    closedir(dir);
    
    printf("\n处理完成: %d 个文件成功, %d 个文件失败\n", count, errors);
    return errors == 0 ? 0 : -1;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("用法: %s <文件或目录路径> [目标时间戳]\n", argv[0]);
        printf("示例: %s /path/to/file\n", argv[0]);
        printf("      %s /path/to/directory 1672531200\n", argv[0]);
        return 1;
    }
    
    const char *path = argv[1];
    time_t target_time;
    
    // 如果提供了目标时间戳,使用它;否则使用当前时间
    if (argc > 2) {
        target_time = (time_t)atol(argv[2]);
    } else {
        target_time = time(NULL);
    }
    
    // 检查路径是文件还是目录
    struct stat path_stat;
    if (stat(path, &path_stat) == -1) {
        perror("stat");
        return 1;
    }
    
    if (S_ISDIR(path_stat.st_mode)) {
        // 处理目录
        return sync_directory_times(path, target_time);
    } else {
        // 处理单个文件
        struct file_time_info before_info, after_info;
        
        printf("=== 文件时间戳修改工具 ===\n\n");
        
        // 获取修改前的信息
        if (get_file_time_info(path, &before_info) == -1) {
            perror("获取文件信息失败");
            return 1;
        }
        
        printf("修改前:\n");
        print_file_info(&before_info);
        
        // 设置新的时间戳
        if (set_file_times(path, target_time, target_time) == -1) {
            perror("设置时间戳失败");
            return 1;
        }
        
        // 获取修改后的信息
        if (get_file_time_info(path, &after_info) == -1) {
            perror("获取修改后信息失败");
            return 1;
        }
        
        printf("\n修改后:\n");
        print_file_info(&after_info);
        
        printf("\n时间戳修改完成!\n");
    }
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o futimens_example1 example1.c
gcc -o futimens_example2 example2.c
gcc -o futimens_example3 example3.c

# 运行示例
./futimens_example1
./futimens_example2

# 运行文件时间管理工具
./futimens_example3 test_file.txt
./futimens_example3 /path/to/directory
./futimens_example3 test_file.txt 1672531200

注意事项

  1. 权限要求: 通常需要对文件有写权限才能修改时间戳,但某些系统允许文件所有者修改自己的文件时间戳
  2. 时间精度futimens 支持纳秒级精度,但实际精度取决于文件系统
  3. 文件系统支持: 不是所有文件系统都支持纳秒级时间戳精度
  4. 特殊值: 正确使用 UTIME_OMIT 和 UTIME_NOW 可以实现灵活的时间控制
  5. 错误处理: 始终检查返回值,因为权限不足或只读文件系统会导致操作失败

常见使用场景

  1. 备份系统: 设置备份文件的时间戳与源文件一致
  2. 构建系统: 确保生成文件的时间戳正确,避免不必要的重新编译
  3. 文件同步: 同步不同系统间的文件时间戳
  4. 测试工具: 在测试中模拟不同时间点的文件状态
  5. 数字取证: 修改文件时间戳用于取证分析(需谨慎使用)

这些示例展示了 futimens 函数的各种使用方法,从基本的时间戳设置到高级的批量处理工具,帮助你全面掌握这个重要的文件操作函数。

https://blog.csdn.net/zidier215/article/details/151332377?sharetype=blogdetail&sharerId=151332377&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

发表在 linux文章 | 留下评论

get_mempolicy系统调用及示例

get_mempolicy 函数详解

get_mempolicy 是 Linux 系统中用于查询 NUMA 内存分配策略的系统调用,可获取进程或指定内存区域的 NUMA 节点分配策略。该函数支持查询默认策略、特定地址策略及允许使用的 NUMA 节点集合,适用于优化多核 NUMA 架构下的内存访问性能。函数通过 mode 参数返回策略类型(如默认、优先节点、绑定节点等),nodemask 返回节点掩码,flags 控制查询行为。典型应用场景包括高性能计算和大内存应用,需配合 set_mempolicy 和 mbind 等函数使用。

1. 函数介绍

get_mempolicy 是 Linux 系统中用于获取内存策略(Memory Policy)的系统调用。可以把内存策略想象成”内存分配的导航系统”——它告诉操作系统应该将进程的内存分配到哪个 NUMA 节点上。

在现代多核系统中,特别是 NUMA(Non-Uniform Memory Access)架构中,不同的 CPU 核心访问不同内存节点的速度是不一样的。就像你去超市买东西,有些商品在你附近的货架上(访问快),有些在远处的货架上(访问慢)。内存策略就是告诉系统:“优先在我附近的内存区域分配内存”,从而提高程序性能。

get_mempolicy 允许你查询当前的内存分配策略,了解系统是如何为你的进程分配内存的。

2. 函数原型

#include <numaif.h>

long get_mempolicy(int *mode, unsigned long *nodemask,
                   unsigned long maxnode, void *addr, unsigned long flags);

3. 功能

get_mempolicy 函数用于获取当前进程或指定内存地址的内存分配策略。它可以查询:

  • 进程默认的内存策略
  • 特定内存地址的内存策略
  • 系统支持的 NUMA 节点信息

4. 参数

  • mode: 指向整型变量的指针,用于存储返回的策略模式
  • nodemask: 指向节点掩码数组的指针,用于存储策略涉及的 NUMA 节点
  • maxnode: nodemask 数组的最大节点数
  • addr: 指定内存地址(可选,用于查询特定地址的策略)
  • flags: 控制操作行为的标志位

5. 策略模式(mode 参数的可能值)

模式说明
MPOL_DEFAULT0默认策略,遵循系统默认行为
MPOL_PREFERRED1优先分配到指定节点
MPOL_BIND2严格绑定到指定节点集合
MPOL_INTERLEAVE3在指定节点间交错分配
MPOL_LOCAL4分配到本地节点(进程运行的节点)

6. 标志位(flags 参数)

标志说明
0查询进程默认策略
MPOL_F_NODE返回节点信息
MPOL_F_ADDR查询指定地址 addr 的策略
MPOL_F_MEMS_ALLOWED返回进程允许使用的节点集合

7. 返回值

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

常见错误码:

  • EFAULT: 地址参数无效
  • EINVAL: 参数无效
  • ENOMEM: 内存不足

8. 相似函数或关联函数

  • set_mempolicy: 设置内存策略
  • mbind: 为特定内存区域设置内存策略
  • getcpu: 获取当前 CPU 和 NUMA 节点信息
  • numa_ functions*: libnuma 库提供的 NUMA 操作函数
  • mbind: 绑定内存到特定节点

9. 示例代码

示例1:基础用法 – 获取进程默认内存策略

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <numaif.h>
#include <string.h>

// 将节点掩码转换为可读的字符串
void print_nodemask(unsigned long *nodemask, unsigned long maxnode) {
    printf("节点掩码: ");
    int printed = 0;
    
    for (unsigned long i = 0; i < maxnode; i++) {
        if (nodemask[i / (sizeof(unsigned long) * 8)] & 
            (1UL << (i % (sizeof(unsigned long) * 8)))) {
            if (printed > 0) printf(",");
            printf("%lu", i);
            printed++;
        }
    }
    
    if (printed == 0) {
        printf("(无节点)");
    }
    printf("\n");
}

// 获取策略模式的字符串描述
const char* get_policy_name(int mode) {
    switch (mode) {
        case MPOL_DEFAULT:
            return "MPOL_DEFAULT (默认策略)";
        case MPOL_PREFERRED:
            return "MPOL_PREFERRED (优先节点)";
        case MPOL_BIND:
            return "MPOL_BIND (绑定节点)";
        case MPOL_INTERLEAVE:
            return "MPOL_INTERLEAVE (交错分配)";
        case MPOL_LOCAL:
            return "MPOL_LOCAL (本地节点)";
        default:
            return "未知策略";
    }
}

int main() {
    int mode;
    unsigned long nodemask[16];  // 支持最多 16 * sizeof(unsigned long) * 8 个节点
    unsigned long maxnode = sizeof(nodemask) * 8;
    
    printf("=== 获取进程默认内存策略 ===\n\n");
    
    // 获取当前进程的默认内存策略
    if (get_mempolicy(&mode, nodemask, maxnode, NULL, 0) == -1) {
        perror("get_mempolicy");
        printf("可能的原因:\n");
        printf("1. 系统不支持 NUMA\n");
        printf("2. 未链接 libnuma 库\n");
        printf("3. 内核不支持此功能\n");
        return 1;
    }
    
    printf("当前进程内存策略:\n");
    printf("策略模式: %s (%d)\n", get_policy_name(mode), mode);
    print_nodemask(nodemask, maxnode);
    
    // 获取允许使用的节点集合
    printf("\n=== 允许使用的 NUMA 节点 ===\n");
    memset(nodemask, 0, sizeof(nodemask));
    if (get_mempolicy(&mode, nodemask, maxnode, NULL, MPOL_F_MEMS_ALLOWED) == 0) {
        printf("允许的节点: ");
        print_nodemask(nodemask, maxnode);
    } else {
        perror("get_mempolicy MPOL_F_MEMS_ALLOWED");
    }
    
    return 0;
}

示例2:查询特定内存地址的策略

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <numaif.h>
#include <string.h>

int main() {
    int mode;
    unsigned long nodemask[16];
    unsigned long maxnode = sizeof(nodemask) * 8;
    char *memory_block;
    size_t block_size = 1024 * 1024;  // 1MB
    
    printf("=== 查询特定内存地址的策略 ===\n\n");
    
    // 分配内存块
    memory_block = mmap(NULL, block_size, PROT_READ | PROT_WRITE,
                        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (memory_block == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    
    printf("分配内存块: %p - %p (大小: %zu 字节)\n", 
           memory_block, memory_block + block_size - 1, block_size);
    
    // 查询整个内存块的策略
    printf("\n--- 查询内存块起始地址的策略 ---\n");
    if (get_mempolicy(&mode, nodemask, maxnode, memory_block, MPOL_F_ADDR) == 0) {
        printf("地址 %p 的策略: %d\n", memory_block, mode);
    } else {
        perror("get_mempolicy for memory block");
    }
    
    // 查询内存块中间地址的策略
    printf("\n--- 查询内存块中间地址的策略 ---\n");
    char *middle_addr = memory_block + block_size / 2;
    if (get_mempolicy(&mode, nodemask, maxnode, middle_addr, MPOL_F_ADDR) == 0) {
        printf("地址 %p 的策略: %d\n", middle_addr, mode);
    } else {
        perror("get_mempolicy for middle address");
    }
    
    // 使用内存
    memset(memory_block, 0xAA, block_size);
    printf("\n已使用内存块\n");
    
    // 再次查询策略
    printf("\n--- 使用内存后查询策略 ---\n");
    if (get_mempolicy(&mode, nodemask, maxnode, memory_block, MPOL_F_ADDR) == 0) {
        printf("地址 %p 的策略: %d\n", memory_block, mode);
    } else {
        perror("get_mempolicy after usage");
    }
    
    // 释放内存
    if (munmap(memory_block, block_size) == -1) {
        perror("munmap");
        return 1;
    }
    
    printf("\n内存块已释放\n");
    return 0;
}

示例3:完整的 NUMA 信息查询工具

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <numaif.h>
#include <sched.h>
#include <string.h>

// 简单的 CPU 和 NUMA 信息查询
void print_cpu_and_numa_info() {
    #ifdef SYS_getcpu
    unsigned cpu, node;
    if (syscall(SYS_getcpu, &cpu, &node, NULL) == 0) {
        printf("当前运行在 CPU %u, NUMA 节点 %u\n", cpu, node);
    } else {
        printf("无法获取 CPU/NUMA 信息\n");
    }
    #else
    printf("系统不支持 getcpu 系统调用\n");
    #endif
}

// 显示系统 NUMA 拓扑信息
void print_numa_topology() {
    long max_nodes = sysconf(_SC_NPROCESSORS_ONLN);
    printf("系统在线 CPU 数: %ld\n", max_nodes);
    
    // 尝试获取 NUMA 节点数
    FILE *fp = fopen("/sys/devices/system/node/online", "r");
    if (fp) {
        char buffer[256];
        if (fgets(buffer, sizeof(buffer), fp)) {
            printf("在线 NUMA 节点: %s", buffer);
        }
        fclose(fp);
    } else {
        printf("无法读取 NUMA 节点信息\n");
    }
}

// 详细分析内存策略
void analyze_memory_policy() {
    int mode;
    unsigned long nodemask[16];
    unsigned long maxnode = sizeof(nodemask) * 8;
    
    printf("\n=== 详细内存策略分析 ===\n");
    
    // 1. 获取默认策略
    if (get_mempolicy(&mode, nodemask, maxnode, NULL, 0) == 0) {
        printf("1. 进程默认策略: %d\n", mode);
    }
    
    // 2. 获取允许的节点
    memset(nodemask, 0, sizeof(nodemask));
    if (get_mempolicy(&mode, nodemask, maxnode, NULL, MPOL_F_MEMS_ALLOWED) == 0) {
        printf("2. 允许使用的节点: ");
        int count = 0;
        for (unsigned long i = 0; i < maxnode; i++) {
            if (nodemask[i / (sizeof(unsigned long) * 8)] & 
                (1UL << (i % (sizeof(unsigned long) * 8)))) {
                if (count > 0) printf(", ");
                printf("%lu", i);
                count++;
            }
        }
        printf("\n");
    }
    
    // 3. 获取本地策略
    memset(nodemask, 0, sizeof(nodemask));
    if (get_mempolicy(&mode, nodemask, maxnode, NULL, MPOL_F_NODE) == 0) {
        printf("3. 本地节点策略: %d\n", mode);
    }
}

// 测试不同内存分配的策略
void test_memory_allocation_policies() {
    printf("\n=== 内存分配策略测试 ===\n");
    
    // 分配小块内存
    char *small_alloc = malloc(1024);
    if (small_alloc) {
        printf("小块内存分配 (%p): ", small_alloc);
        int mode;
        unsigned long nodemask[16];
        if (get_mempolicy(&mode, nodemask, sizeof(nodemask) * 8, 
                         small_alloc, MPOL_F_ADDR) == 0) {
            printf("策略 %d\n", mode);
        } else {
            printf("无法查询策略\n");
        }
        free(small_alloc);
    }
    
    // 分配大块内存
    char *large_alloc = malloc(10 * 1024 * 1024);  // 10MB
    if (large_alloc) {
        printf("大块内存分配 (%p): ", large_alloc);
        int mode;
        unsigned long nodemask[16];
        if (get_mempolicy(&mode, nodemask, sizeof(nodemask) * 8, 
                         large_alloc, MPOL_F_ADDR) == 0) {
            printf("策略 %d\n", mode);
        } else {
            printf("无法查询策略\n");
        }
        free(large_alloc);
    }
}

int main() {
    printf("=== NUMA 内存策略查询工具 ===\n\n");
    
    // 显示基本信息
    print_cpu_and_numa_info();
    print_numa_topology();
    
    // 分析内存策略
    analyze_memory_policy();
    
    // 测试内存分配
    test_memory_allocation_policies();
    
    printf("\n=== 工具使用说明 ===\n");
    printf("此工具显示当前进程的 NUMA 内存策略信息。\n");
    printf("如果显示错误,可能原因:\n");
    printf("1. 系统不是 NUMA 架构\n");
    printf("2. 未安装或链接 libnuma 库\n");
    printf("3. 内核版本不支持 NUMA 功能\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序(需要链接 libnuma)
gcc -o get_mempolicy_example1 example1.c -lnuma
gcc -o get_mempolicy_example2 example2.c -lnuma
gcc -o get_mempolicy_example3 example3.c -lnuma

# 如果系统没有 libnuma,可以尝试:
gcc -o get_mempolicy_example1 example1.c
gcc -o get_mempolicy_example2 example2.c

# 运行示例
./get_mempolicy_example1
./get_mempolicy_example2
./get_mempolicy_example3

安装 libnuma(如果需要)

# Ubuntu/Debian
sudo apt-get install libnuma-dev

# CentOS/RHEL
sudo yum install numactl-devel

# Fedora
sudo dnf install numactl-devel

重要注意事项

  1. 系统要求: 需要 NUMA 支持的硬件和内核
  2. 库依赖: 通常需要链接 libnuma 库
  3. 权限: 一般不需要特殊权限
  4. 移植性: NUMA 功能是 Linux 特有的
  5. 性能影响: 查询内存策略本身开销很小

策略模式详解

  1. MPOL_DEFAULT: 使用系统默认的内存分配策略
  2. MPOL_PREFERRED: 优先在指定节点分配内存,如果该节点不可用则在其他节点分配
  3. MPOL_BIND: 严格限制只能在指定节点集合中分配内存
  4. MPOL_INTERLEAVE: 在多个节点间交错分配内存(适合大内存应用)
  5. MPOL_LOCAL: 总是在进程运行的本地节点分配内存

实际应用场景

  1. 高性能计算: 优化大规模数值计算的内存访问模式
  2. 数据库系统: 将数据分配到合适的 NUMA 节点以提高访问速度
  3. Web 服务器: 优化多线程应用的内存局部性
  4. 科学计算: 大型矩阵运算的内存布局优化
  5. 虚拟化环境: 为虚拟机合理分配物理内存节点

这些示例展示了 get_mempolicy 函数的各种使用方法,从基本的策略查询到完整的 NUMA 信息分析工具,帮助你理解和掌握 Linux NUMA 内存管理机制。

get_mempolicy系统调用, Linux get_mempolicy函数, get_mempolicy使用示例, 查询内存策略Linux, 理解get_mempolicy工作原理, Linux内存管理API, 系统调用与内存分配, 高级Linux编程技巧, get_mempolicy参数解析, 优化Linux性能的系统调用

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

发表在 linux文章 | 留下评论

get_robust_list系统调用及示例

get_robust_list – 获取进程的健壮互斥锁列表

1. 函数介绍

get_robust_list 是一个 Linux 系统调用,用于获取指定进程的健壮互斥锁(robust futex)列表。健壮互斥锁是 Linux 提供的一种特殊类型的 futex,能够在持有锁的进程异常终止时自动清理锁状态,防止其他进程无限期等待。

这个机制主要用于解决多线程程序中,当持有互斥锁的线程意外崩溃时,其他等待该锁的线程可能会永远阻塞的问题。

https://blog.csdn.net/zidier215/article/details/151332225?sharetype=blogdetail&sharerId=151332225&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

2. 函数原型

#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>

long get_robust_list(int pid, struct robust_list_head **head_ptr, size_t *len_ptr);

注意:该函数不是标准 C 库的一部分,需要通过 syscall() 调用。

3. 功能

获取指定进程的健壮互斥锁列表信息。返回的信息包括指向健壮列表头的指针和列表的长度。

4. 参数

  • int pid: 进程 ID
    • 0: 获取当前进程的健壮列表
    • 正整数: 获取指定进程的健壮列表
  • struct robust_list_head **head_ptr: 指向指针的指针,用于接收健壮列表头的地址
  • size_t *len_ptr: 指向 size_t 的指针,用于接收列表长度

5. robust_list_head 结构体定义

struct robust_list {
    struct robust_list *next;
};

struct robust_list_head {
    struct robust_list list;
    long futex_offset;
    unsigned long list_op_pending;
};

6. 返回值

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

7. 常见 errno 错误码

  • EPERM: 权限不足(尝试获取其他进程信息时需要适当权限)
  • ESRCH: 指定的进程不存在
  • EINVAL: 无效的参数
  • EFAULT: 指针参数指向无效内存地址
  • ENOSYS: 系统不支持该功能

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

  • set_robust_list(): 设置当前进程的健壮互斥锁列表
  • futex(): futex 系统调用
  • pthread_mutexattr_setrobust(): 设置 pthread 互斥锁为健壮类型
  • pthread_mutex_consistent(): 标记健壮互斥锁为一致状态
  • /proc/[pid]/maps: 查看进程内存映射
  • /proc/[pid]/status: 查看进程状态信息

9. 示例代码

示例1:基本使用 – 获取当前进程的健壮列表

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <errno.h>
#include <string.h>

#ifndef SYS_get_robust_list
# define SYS_get_robust_list 275  // x86_64 架构下的系统调用号
#endif

int main() {
    struct robust_list_head *head;
    size_t len;
    long result;
    
    printf("=== 获取当前进程的健壮互斥锁列表 ===\n");
    printf("当前进程 PID: %d\n", getpid());
    
    // 获取当前进程的健壮列表
    result = syscall(SYS_get_robust_list, 0, &head, &len);
    
    if (result == -1) {
        printf("get_robust_list 调用失败\n");
        printf("errno = %d: %s\n", errno, strerror(errno));
        
        switch (errno) {
            case EPERM:
                printf("权限不足\n");
                break;
            case ENOSYS:
                printf("系统不支持健壮互斥锁功能\n");
                break;
            default:
                printf("其他错误\n");
                break;
        }
        return 1;
    }
    
    printf("成功获取健壮列表信息:\n");
    printf("  列表头地址: %p\n", (void*)head);
    printf("  列表长度: %zu 字节\n", len);
    
    if (head == NULL) {
        printf("  当前进程没有设置健壮列表\n");
    } else {
        printf("  已设置健壮列表\n");
    }
    
    return 0;
}

示例2:健壮互斥锁的实际使用

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <sys/syscall.h>
#include <linux/futex.h>

#ifndef SYS_get_robust_list
# define SYS_get_robust_list 275
#endif
#ifndef SYS_set_robust_list
# define SYS_set_robust_list 275
#endif

pthread_mutex_t robust_mutex = PTHREAD_MUTEX_INITIALIZER;

void* worker_thread(void *arg) {
    int thread_id = *(int*)arg;
    int ret;
    
    printf("线程 %d: 尝试获取健壮互斥锁\n", thread_id);
    
    ret = pthread_mutex_lock(&robust_mutex);
    if (ret == EOWNERDEAD) {
        printf("线程 %d: 检测到锁持有者已死亡\n", thread_id);
        printf("线程 %d: 标记互斥锁为一致状态\n", thread_id);
        pthread_mutex_consistent(&robust_mutex);
    } else if (ret != 0) {
        printf("线程 %d: 获取锁失败: %s\n", thread_id, strerror(ret));
        return NULL;
    }
    
    printf("线程 %d: 成功获取锁,执行临界区代码\n", thread_id);
    
    // 模拟一些工作
    sleep(2);
    
    printf("线程 %d: 释放锁\n", thread_id);
    pthread_mutex_unlock(&robust_mutex);
    
    return NULL;
}

void check_robust_list() {
    struct robust_list_head *head;
    size_t len;
    long result;
    
    result = syscall(SYS_get_robust_list, 0, &head, &len);
    if (result == 0) {
        printf("健壮列表状态:\n");
        printf("  列表头: %p\n", (void*)head);
        printf("  长度: %zu 字节\n", len);
    }
}

int main() {
    pthread_t thread1, thread2;
    int id1 = 1, id2 = 2;
    int ret;
    
    printf("=== 健壮互斥锁演示 ===\n");
    
    // 设置互斥锁为健壮类型
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
    pthread_mutex_init(&robust_mutex, &attr);
    
    printf("已创建健壮互斥锁\n");
    check_robust_list();
    
    // 创建第一个线程
    ret = pthread_create(&thread1, NULL, worker_thread, &id1);
    if (ret != 0) {
        printf("创建线程1失败: %s\n", strerror(ret));
        return 1;
    }
    
    // 等待一段时间,让第一个线程获取锁
    sleep(1);
    
    // 创建第二个线程
    ret = pthread_create(&thread2, NULL, worker_thread, &id2);
    if (ret != 0) {
        printf("创建线程2失败: %s\n", strerror(ret));
        return 1;
    }
    
    // 等待线程完成
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    printf("所有线程执行完成\n");
    check_robust_list();
    
    // 清理
    pthread_mutex_destroy(&robust_mutex);
    pthread_mutexattr_destroy(&attr);
    
    return 0;
}

示例3:获取其他进程的健壮列表信息

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

#ifndef SYS_get_robust_list
# define SYS_get_robust_list 275
#endif

void check_process_robust_list(pid_t pid) {
    struct robust_list_head *head;
    size_t len;
    long result;
    
    printf("检查进程 %d 的健壮列表:\n", pid);
    
    result = syscall(SYS_get_robust_list, pid, &head, &len);
    
    if (result == -1) {
        printf("  获取失败: %s\n", strerror(errno));
        switch (errno) {
            case EPERM:
                printf("  权限不足,无法查看其他进程信息\n");
                break;
            case ESRCH:
                printf("  进程不存在\n");
                break;
            default:
                break;
        }
    } else {
        printf("  成功获取信息:\n");
        printf("    列表头地址: %p\n", (void*)head);
        printf("    列表长度: %zu 字节\n", len);
        
        if (head == NULL) {
            printf("    该进程未设置健壮列表\n");
        } else {
            printf("    该进程已设置健壮列表\n");
        }
    }
    printf("\n");
}

int main() {
    pid_t child_pid;
    int status;
    
    printf("=== 进程健壮列表检查 ===\n");
    
    // 检查当前进程
    check_process_robust_list(0);  // 当前进程
    
    // 检查 init 进程
    check_process_robust_list(1);
    
    // 创建子进程进行测试
    child_pid = fork();
    
    if (child_pid == -1) {
        perror("fork 失败");
        return 1;
    }
    
    if (child_pid == 0) {
        // 子进程
        printf("子进程 PID: %d\n", getpid());
        
        // 子进程睡眠一段时间
        sleep(5);
        
        exit(0);
    } else {
        // 父进程
        printf("父进程 PID: %d\n", getpid());
        printf("子进程 PID: %d\n", child_pid);
        
        // 检查子进程
        sleep(1);  // 等待子进程启动
        check_process_robust_list(child_pid);
        
        // 等待子进程结束
        waitpid(child_pid, &status, 0);
        printf("子进程已结束\n");
        
        // 再次检查已结束的进程
        check_process_robust_list(child_pid);
    }
    
    return 0;
}

10. 健壮互斥锁的工作原理

健壮互斥锁的核心机制:

  1. 列表维护: 每个进程维护一个健壮互斥锁列表
  2. 异常检测: 当持有锁的进程异常终止时,内核检测到
  3. 自动清理: 内核自动将锁标记为”所有者死亡”状态
  4. 状态恢复: 下一个尝试获取锁的线程收到 EOWNERDEAD 错误
  5. 一致性标记: 获得锁的线程必须调用 pthread_mutex_consistent() 标记锁为一致状态

11. pthread 健壮互斥锁使用示例

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

pthread_mutex_t robust_mutex;

void setup_robust_mutex() {
    pthread_mutexattr_t attr;
    
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
    pthread_mutex_init(&robust_mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}

void use_robust_mutex(int thread_id) {
    int ret;
    
    ret = pthread_mutex_lock(&robust_mutex);
    switch (ret) {
        case 0:
            printf("线程 %d: 成功获取锁\n", thread_id);
            break;
            
        case EOWNERDEAD:
            printf("线程 %d: 检测到锁所有者死亡\n", thread_id);
            // 检查数据一致性并恢复
            // ... 数据恢复代码 ...
            pthread_mutex_consistent(&robust_mutex);
            printf("线程 %d: 锁状态已恢复\n", thread_id);
            break;
            
        case ENOTRECOVERABLE:
            printf("线程 %d: 锁不可恢复,需要重新初始化\n", thread_id);
            pthread_mutex_destroy(&robust_mutex);
            setup_robust_mutex();
            return;
            
        default:
            printf("线程 %d: 获取锁失败: %s\n", thread_id, strerror(ret));
            return;
    }
    
    // 临界区代码
    printf("线程 %d: 执行临界区操作\n", thread_id);
    sleep(1);
    
    pthread_mutex_unlock(&robust_mutex);
    printf("线程 %d: 释放锁\n", thread_id);
}

12. 实际应用场景

健壮互斥锁主要用于:

  • 多进程共享内存: 防止进程崩溃导致其他进程死锁
  • 关键系统服务: 提高系统稳定性和可靠性
  • 数据库系统: 保护共享数据结构
  • 实时系统: 确保系统的可预测性
  • 高可用性应用: 减少单点故障影响

总结

get_robust_list 是管理健壮互斥锁的重要系统调用,关键要点:

  1. 健壮性保障: 防止进程崩溃导致的死锁问题
  2. 异常处理: 提供 EOWNERDEAD 机制处理锁所有者死亡情况
  3. 权限控制: 访问其他进程信息需要适当权限
  4. 现代替代: pthread 提供了更高层的健壮互斥锁接口
  5. 系统安全: 需要谨慎使用,避免安全风险

在编写高可靠性多线程程序时,健壮互斥锁是一个重要的同步原语,能够显著提高程序的健壮性。

发表在 linux文章 | 留下评论

get_thread_area系统调用及示例

get_thread_area 函数详解

1. 函数介绍

get_thread_area 是 Linux 系统中用于获取线程本地存储(Thread Local Storage, TLS)描述符的系统调用。可以把 TLS 想象成”每个线程的私人储物柜”——每个线程都有自己独立的存储空间,存放线程特定的数据,其他线程无法访问。

在多线程程序中,有时我们需要为每个线程维护一些独立的数据,比如线程 ID、错误码、用户数据等。TLS 提供了一种机制,让每个线程都有自己的变量副本,避免了线程间的数据竞争和同步问题。

get_thread_area 允许你查询线程的 TLS 描述符信息,了解线程本地存储的配置情况。这就像查看你的储物柜配置信息一样。

get_thread_area是Linux系统中用于获取线程本地存储(TLS)描述符的系统调用,主要用于i386架构。该调用允许查询线程特定的存储区域配置,通过user_desc结构体返回条目号、基地址、大小限制等TLS信息。在现代64位系统中,该功能通常被arch_prctl或标准的TLS机制替代。示例代码展示了在32位和64位系统上查询TLS信息的不同方法,以及使用__thread关键字定义线程局部变量的实践。

2. 函数原型

#include <asm/ldt.h>    /* 或者 <sys/ldt.h> */
#include <sys/syscall.h>

int get_thread_area(struct user_desc *u_info);

注意:这个函数是 i386 架构特有的系统调用,在现代 64 位系统上可能不可用或行为不同。

3. 功能

get_thread_area 函数用于获取线程的 TLS(Thread Local Storage)描述符信息。它主要在 i386 架构上使用,用于查询线程本地存储区域的配置。

4. 参数

  • u_info: 指向 struct user_desc 结构体的指针,用于存储返回的 TLS 描述符信息

5. struct user_desc 结构体

struct user_desc {
    unsigned int  entry_number;   /* TLS 描述符条目号 */
    unsigned long base_addr;      /* TLS 区域基地址 */
    unsigned int  limit;          /* TLS 区域大小限制 */
    unsigned int  seg_32bit:1;    /* 32 位段标志 */
    unsigned int  contents:2;     /* 段内容类型 */
    unsigned int  read_exec_only:1; /* 只读执行标志 */
    unsigned int  limit_in_pages:1; /* 限制单位标志 */
    unsigned int  seg_not_present:1; /* 段不存在标志 */
    unsigned int  useable:1;      /* 可用标志 */
    unsigned int  lm:1;           /* 长模式标志 (64 位) */
};

6. 返回值

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

常见错误码:

  • EFAULT: u_info 指针无效
  • EINVAL: 参数无效
  • ENOSYS: 系统不支持此调用

7. 相似函数或关联函数

  • set_thread_area: 设置线程本地存储描述符
  • arch_prctl: 在 x86-64 上管理 TLS(现代替代方案)
  • pthread_getspecific/pthread_setspecific: POSIX 线程特定数据
  • __thread: GCC 的线程局部存储关键字
  • thread_local: C11 标准的线程局部存储

8. 重要说明

⚠️ 注意get_thread_area 主要用于 i386 (32位 x86) 架构,在现代 64 位系统上:

  1. 可能不被支持
  2. 行为可能不同
  3. 建议使用 arch_prctl 或标准的 TLS 机制

9. 示例代码

示例1:基础用法 – 查询 TLS 信息

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <asm/ldt.h>
#include <errno.h>
#include <string.h>

#ifdef __i386__
// 32位系统上的实现
int get_thread_area_wrapper(struct user_desc *u_info) {
    return syscall(SYS_get_thread_area, u_info);
}

void print_tls_info(const struct user_desc *desc) {
    printf("TLS 描述符信息:\n");
    printf("  条目号: %u\n", desc->entry_number);
    printf("  基地址: 0x%lx\n", desc->base_addr);
    printf("  大小限制: %u\n", desc->limit);
    printf("  32位段: %s\n", desc->seg_32bit ? "是" : "否");
    printf("  只读执行: %s\n", desc->read_exec_only ? "是" : "否");
    printf("  页面单位: %s\n", desc->limit_in_pages ? "是" : "否");
    printf("  段存在: %s\n", !desc->seg_not_present ? "是" : "否");
    printf("  可用: %s\n", desc->useable ? "是" : "否");
}

int main() {
    struct user_desc tls_desc;
    
    printf("=== 获取线程本地存储信息 ===\n\n");
    
    // 尝试获取 TLS 信息
    // 注意:entry_number 需要设置为要查询的条目号
    // 通常 TLS 条目号由系统分配
    tls_desc.entry_number = -1;  // 请求系统分配
    
    if (get_thread_area_wrapper(&tls_desc) == 0) {
        printf("成功获取 TLS 信息:\n");
        print_tls_info(&tls_desc);
    } else {
        printf("获取 TLS 信息失败: %s\n", strerror(errno));
        printf("这在 64 位系统上是正常的\n");
        return 1;
    }
    
    return 0;
}

#else

// 64位系统上的替代实现
#include <asm/prctl.h>
#include <sys/prctl.h>

int main() {
    unsigned long fs_base;
    
    printf("=== 现代系统 TLS 信息查询 ===\n\n");
    printf("在 64 位系统上,使用 arch_prctl 替代 get_thread_area\n\n");
    
    // 获取 FS 段基地址(TLS 区域)
    if (arch_prctl(ARCH_GET_FS, &fs_base) == 0) {
        printf("FS 段基地址 (TLS 区域): 0x%lx\n", fs_base);
    } else {
        perror("arch_prctl ARCH_GET_FS");
    }
    
    // 获取 GS 段基地址
    if (arch_prctl(ARCH_GET_GS, &fs_base) == 0) {
        printf("GS 段基地址: 0x%lx\n", fs_base);
    } else {
        perror("arch_prctl ARCH_GET_GS");
    }
    
    printf("\n说明:现代 64 位系统使用 FS/GS 寄存器实现 TLS\n");
    return 0;
}
#endif

示例2:TLS 使用示例

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 使用 GCC 的 __thread 关键字定义线程局部变量
__thread int thread_id = 0;
__thread char thread_name[32] = "未命名线程";
__thread int operation_count = 0;

// 普通全局变量(所有线程共享)
int global_counter = 0;

// 线程函数
void* worker_thread(void* arg) {
    int local_thread_num = *(int*)arg;
    
    // 为每个线程设置不同的 TLS 值
    thread_id = local_thread_num;
    snprintf(thread_name, sizeof(thread_name), "Worker-%d", local_thread_num);
    
    printf("线程 %d 启动:\n", local_thread_num);
    printf("  TLS thread_id: %d\n", thread_id);
    printf("  TLS thread_name: %s\n", thread_name);
    printf("  TLS operation_count: %d\n", operation_count);
    printf("  共享 global_counter: %d\n\n", global_counter);
    
    // 模拟工作,增加操作计数
    for (int i = 0; i < 5; i++) {
        operation_count++;
        global_counter++;  // 注意:这会导致竞态条件
        printf("线程 %s: 操作 %d 完成\n", thread_name, operation_count);
        usleep(100000);  // 100ms
    }
    
    printf("线程 %s 完成,总共执行 %d 次操作\n\n", thread_name, operation_count);
    return NULL;
}

int main() {
    pthread_t threads[3];
    int thread_numbers[3] = {1, 2, 3};
    
    printf("=== 线程局部存储 (TLS) 演示 ===\n\n");
    
    // 主线程的 TLS 值
    printf("主线程初始 TLS 值:\n");
    printf("  thread_id: %d\n", thread_id);
    printf("  thread_name: %s\n", thread_name);
    printf("  operation_count: %d\n", operation_count);
    printf("  global_counter: %d\n\n", global_counter);
    
    // 设置主线程的 TLS 值
    thread_id = 0;
    snprintf(thread_name, sizeof(thread_name), "MainThread");
    
    printf("主线程设置后 TLS 值:\n");
    printf("  thread_id: %d\n", thread_id);
    printf("  thread_name: %s\n", thread_name);
    printf("  operation_count: %d\n", operation_count);
    printf("  global_counter: %d\n\n", global_counter);
    
    // 创建工作线程
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, worker_thread, &thread_numbers[i]) != 0) {
            perror("pthread_create");
            return 1;
        }
    }
    
    // 等待所有线程完成
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("所有线程完成后:\n");
    printf("  主线程 thread_id: %d\n", thread_id);
    printf("  主线程 thread_name: %s\n", thread_name);
    printf("  主线程 operation_count: %d\n", operation_count);
    printf("  共享 global_counter: %d\n", global_counter);
    
    printf("\n注意:global_counter 的值可能不准确,因为存在竞态条件\n");
    printf("而 TLS 变量的值是正确的,因为每个线程都有独立副本\n");
    
    return 0;
}

示例3:TLS 与错误处理

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>

// 线程局部的错误信息
__thread int thread_errno = 0;
__thread char thread_error_msg[256] = "";

// 线程局部的工作状态
__thread struct {
    int task_id;
    int progress;
    char status[64];
} work_state = {0, 0, "初始化"};

// 模拟可能出错的操作
int simulate_operation(int task_id) {
    work_state.task_id = task_id;
    snprintf(work_state.status, sizeof(work_state.status), "开始任务 %d", task_id);
    work_state.progress = 0;
    
    printf("[%s] %s\n", work_state.status, thread_error_msg[0] ? thread_error_msg : "无错误");
    
    // 模拟工作进度
    for (int i = 1; i <= 10; i++) {
        work_state.progress = i * 10;
        snprintf(work_state.status, sizeof(work_state.status), "任务 %d 进度 %d%%", task_id, work_state.progress);
        
        // 模拟随机错误
        if (task_id == 2 && i == 7) {
            thread_errno = EIO;
            snprintf(thread_error_msg, sizeof(thread_error_msg), "I/O 错误发生在任务 %d", task_id);
            snprintf(work_state.status, sizeof(work_state.status), "任务 %d 失败", task_id);
            printf("[%s] 错误: %s\n", work_state.status, thread_error_msg);
            return -1;
        }
        
        printf("[%s]\n", work_state.status);
        usleep(100000);  // 100ms
    }
    
    snprintf(work_state.status, sizeof(work_state.status), "任务 %d 完成", task_id);
    thread_errno = 0;
    thread_error_msg[0] = '\0';
    printf("[%s] 完成\n", work_state.status);
    
    return 0;
}

// 线程函数
void* task_thread(void* arg) {
    int task_id = *(int*)arg;
    
    printf("=== 线程 %d 开始执行 ===\n", task_id);
    
    // 执行操作
    int result = simulate_operation(task_id);
    
    // 报告线程状态
    printf("线程 %d 状态报告:\n", task_id);
    printf("  任务 ID: %d\n", work_state.task_id);
    printf("  最终进度: %d%%\n", work_state.progress);
    printf("  最终状态: %s\n", work_state.status);
    printf("  错误码: %d (%s)\n", thread_errno, strerror(thread_errno));
    printf("  错误信息: %s\n", thread_error_msg[0] ? thread_error_msg : "无错误");
    printf("\n");
    
    return (void*)(long)result;
}

int main() {
    pthread_t threads[3];
    int task_ids[3] = {1, 2, 3};
    void* results[3];
    
    printf("=== TLS 错误处理演示 ===\n\n");
    
    // 创建线程
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, task_thread, &task_ids[i]) != 0) {
            perror("pthread_create");
            return 1;
        }
    }
    
    // 等待线程完成并收集结果
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], &results[i]);
    }
    
    printf("=== 所有任务完成 ===\n");
    for (int i = 0; i < 3; i++) {
        printf("任务 %d 结果: %s\n", task_ids[i], 
               (long)results[i] == 0 ? "成功" : "失败");
    }
    
    // 主线程的 TLS 状态(应该保持初始值)
    printf("\n=== 主线程 TLS 状态 ===\n");
    printf("任务 ID: %d\n", work_state.task_id);
    printf("进度: %d%%\n", work_state.progress);
    printf("状态: %s\n", work_state.status);
    printf("错误码: %d\n", thread_errno);
    printf("错误信息: %s\n", thread_error_msg[0] ? thread_error_msg : "无错误");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o tls_example1 example1.c
gcc -o tls_example2 example2.c -lpthread
gcc -o tls_example3 example3.c -lpthread

# 运行示例
./tls_example1
./tls_example2
./tls_example3

现代 TLS 替代方案

使用 C11 thread_local(推荐)

#include <stdio.h>
#include <threads.h>

// C11 标准的线程局部存储
thread_local int modern_tls_var = 42;

int main() {
    printf("C11 thread_local 变量: %d\n", modern_tls_var);
    return 0;
}

使用 GCC __thread(广泛支持)

#include <stdio.h>

// GCC 扩展的线程局部存储
__thread int gcc_tls_var = 100;

int main() {
    printf("GCC __thread 变量: %d\n", gcc_tls_var);
    return 0;
}

重要注意事项

  1. 架构相关get_thread_area 主要用于 32 位 x86 系统
  2. 现代替代: 64 位系统推荐使用 arch_prctl 或标准 TLS
  3. 可移植性: 标准的 __thread 或 thread_local 更具可移植性
  4. 性能: TLS 访问通常很快,因为使用专门的 CPU 寄存器
  5. 初始化: TLS 变量在每个线程中都有独立的初始化值

TLS 的优势

  1. 线程安全: 每个线程有独立副本,无需同步
  2. 性能好: 访问速度快,无锁开销
  3. 使用简单: 像普通变量一样使用
  4. 自动管理: 线程结束时自动清理

实际应用场景

  1. 错误处理: 每个线程维护独立的错误状态
  2. 线程标识: 存储线程特定的 ID 或名称
  3. 统计信息: 收集每个线程的性能统计数据
  4. 用户数据: 存储线程特定的用户上下文
  5. 日志系统: 每个线程维护独立的日志缓冲区

这些示例展示了线程局部存储的概念和使用方法,从底层的 get_thread_area 到现代的 TLS 机制,帮助你理解如何在多线程程序中管理线程特定的数据。

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

发表在 linux文章 | 留下评论

getdents64系统调用及示例

getdents64 函数详解

1. 函数介绍

getdents64 是 Linux 系统中用于读取目录内容的底层系统调用。可以把这个函数想象成一个”目录内容扫描仪”——它能够高效地扫描目录中的所有文件和子目录,就像超市的扫描枪快速读取商品条码一样。

与高级的目录操作函数(如 readdir)不同,getdents64 是最底层的接口,直接与内核交互,提供了最大的灵活性和性能。它返回的是原始的目录项数据,包含文件名、inode 号、文件类型等信息。

2. 函数原型

#include <dirent.h>     /* 或者 <unistd.h> */
#include <sys/syscall.h>

int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);

3. 功能

getdents64 函数用于从已打开的目录文件描述符中读取目录项(directory entries)。它一次可以读取多个目录项,比逐个读取效率更高。

4. 参数

  • fd: 已打开的目录文件描述符(通过 open() 或 opendir() 获得)
  • dirp: 指向缓冲区的指针,用于存储读取的目录项数据
  • count: 缓冲区的大小(以字节为单位)

5. struct linux_dirent64 结构体

struct linux_dirent64 {
    ino64_t        d_ino;    /* 64位 inode 号 */
    off64_t        d_off;    /* 到下一个目录项的偏移 */
    unsigned short d_reclen; /* 此目录项的长度 */
    unsigned char  d_type;   /* 文件类型 */
    char           d_name[]; /* 文件名(以 null 结尾) */
};

6. 文件类型(d_type 字段)

类型值宏定义说明
0DT_UNKNOWN未知类型
1DT_FIFO命名管道
2DT_CHR字符设备
4DT_DIR目录
6DT_BLK块设备
8DT_REG普通文件
10DT_LNK符号链接
12DT_SOCK套接字

7. 返回值

  • 成功: 返回实际读取的字节数(0 表示到达目录末尾)
  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EBADF: fd 不是有效的目录文件描述符
  • EFAULT: dirp 指针无效
  • EINVAL: 参数无效
  • ENOENT: 目录不存在

8. 相似函数或关联函数

  • getdents: 旧版本的目录读取函数(32位 inode)
  • readdir: POSIX 标准的目录读取函数(更高级的接口)
  • opendir/fdopendir: 打开目录
  • closedir: 关闭目录
  • scandir: 扫描目录并排序
  • ls: 命令行目录列表工具

9. 示例代码

示例1:基础用法 – 读取目录内容

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

// 目录项结构体(64位版本)
struct linux_dirent64 {
    ino64_t        d_ino;    /* Inode number */
    off64_t        d_off;    /* Offset to next structure */
    unsigned short d_reclen; /* Size of this structure */
    unsigned char  d_type;   /* File type */
    char           d_name[]; /* Filename (null-terminated) */
};

// 获取文件类型字符串
const char* get_file_type_string(unsigned char d_type) {
    switch (d_type) {
        case DT_REG:  return "普通文件";
        case DT_DIR:  return "目录";
        case DT_LNK:  return "符号链接";
        case DT_CHR:  return "字符设备";
        case DT_BLK:  return "块设备";
        case DT_FIFO: return "命名管道";
        case DT_SOCK: return "套接字";
        case DT_UNKNOWN: 
        default:      return "未知";
    }
}

// 获取文件类型字符
char get_file_type_char(unsigned char d_type) {
    switch (d_type) {
        case DT_REG:  return 'f';
        case DT_DIR:  return 'd';
        case DT_LNK:  return 'l';
        case DT_CHR:  return 'c';
        case DT_BLK:  return 'b';
        case DT_FIFO: return 'p';
        case DT_SOCK: return 's';
        case DT_UNKNOWN: 
        default:      return '?';
    }
}

int main(int argc, char *argv[]) {
    int fd;
    char buf[4096];
    int nread;
    char *dir_path;
    
    // 获取目录路径参数
    if (argc != 2) {
        printf("用法: %s <目录路径>\n", argv[0]);
        dir_path = ".";  // 默认当前目录
        printf("使用当前目录: %s\n\n", dir_path);
    } else {
        dir_path = argv[1];
    }
    
    // 打开目录
    fd = open(dir_path, O_RDONLY | O_DIRECTORY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    printf("=== 目录 '%s' 的内容 ===\n", dir_path);
    printf("%-12s %-10s %-8s %s\n", "INODE", "类型", "大小", "名称");
    printf("%-12s %-10s %-8s %s\n", "----", "----", "----", "----");
    
    // 循环读取目录项
    while (1) {
        nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
        if (nread == -1) {
            perror("getdents64");
            close(fd);
            return 1;
        }
        
        if (nread == 0) {
            break;  // 到达目录末尾
        }
        
        // 解析目录项
        for (int bpos = 0; bpos < nread;) {
            struct linux_dirent64 *d;
            d = (struct linux_dirent64 *)(buf + bpos);
            
            // 显示目录项信息
            printf("%-12llu %-10s %-8d %s\n",
                   (unsigned long long)d->d_ino,
                   get_file_type_string(d->d_type),
                   d->d_reclen,
                   d->d_name);
            
            bpos += d->d_reclen;
        }
    }
    
    close(fd);
    return 0;
}

示例2:带过滤和统计的目录扫描器

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

struct linux_dirent64 {
    ino64_t        d_ino;
    off64_t        d_off;
    unsigned short d_reclen;
    unsigned char  d_type;
    char           d_name[];
};

// 统计信息结构体
struct dir_stats {
    int total_files;
    int directories;
    int regular_files;
    int symlinks;
    int devices;
    int fifos;
    int sockets;
    int unknown;
    long long total_size;
};

// 更新统计信息
void update_stats(struct dir_stats *stats, unsigned char d_type) {
    stats->total_files++;
    
    switch (d_type) {
        case DT_DIR:
            stats->directories++;
            break;
        case DT_REG:
            stats->regular_files++;
            break;
        case DT_LNK:
            stats->symlinks++;
            break;
        case DT_CHR:
        case DT_BLK:
            stats->devices++;
            break;
        case DT_FIFO:
            stats->fifos++;
            break;
        case DT_SOCK:
            stats->sockets++;
            break;
        case DT_UNKNOWN:
        default:
            stats->unknown++;
            break;
    }
}

// 显示统计信息
void print_stats(const struct dir_stats *stats) {
    printf("\n=== 统计信息 ===\n");
    printf("总计文件数: %d\n", stats->total_files);
    printf("目录数: %d\n", stats->directories);
    printf("普通文件: %d\n", stats->regular_files);
    printf("符号链接: %d\n", stats->symlinks);
    printf("设备文件: %d\n", stats->devices);
    printf("命名管道: %d\n", stats->fifos);
    printf("套接字: %d\n", stats->sockets);
    printf("未知类型: %d\n", stats->unknown);
}

// 过滤函数:只显示特定类型的文件
int filter_by_type(unsigned char d_type, unsigned char filter_type) {
    if (filter_type == 0) return 1;  // 不过滤
    return d_type == filter_type;
}

int main(int argc, char *argv[]) {
    int fd;
    char buf[8192];
    int nread;
    char *dir_path;
    unsigned char filter_type = 0;  // 0 表示不过滤
    struct dir_stats stats = {0};
    
    // 解析命令行参数
    if (argc < 2) {
        printf("用法: %s <目录路径> [过滤类型]\n", argv[0]);
        printf("过滤类型: d(目录), f(文件), l(链接), 其他类型字符\n");
        dir_path = ".";
    } else {
        dir_path = argv[1];
    }
    
    // 设置过滤类型
    if (argc > 2) {
        switch (argv[2][0]) {
            case 'd': filter_type = DT_DIR; break;
            case 'f': filter_type = DT_REG; break;
            case 'l': filter_type = DT_LNK; break;
            case 'c': filter_type = DT_CHR; break;
            case 'b': filter_type = DT_BLK; break;
            case 'p': filter_type = DT_FIFO; break;
            case 's': filter_type = DT_SOCK; break;
        }
        if (filter_type != 0) {
            printf("过滤类型: %c\n", argv[2][0]);
        }
    }
    
    // 打开目录
    fd = open(dir_path, O_RDONLY | O_DIRECTORY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    printf("=== 目录 '%s' 的内容 ===\n", dir_path);
    if (filter_type != 0) {
        printf("(已过滤)\n");
    }
    printf("%c %-20s %12s %10s\n", 
           'T', "名称", "INODE", "大小(字节)");
    printf("%c %-20s %12s %10s\n", 
           '-', "----", "-----", "----------");
    
    // 读取目录项
    while (1) {
        nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
        if (nread == -1) {
            perror("getdents64");
            close(fd);
            return 1;
        }
        
        if (nread == 0) {
            break;  // 到达目录末尾
        }
        
        // 解析目录项
        for (int bpos = 0; bpos < nread;) {
            struct linux_dirent64 *d;
            d = (struct linux_dirent64 *)(buf + bpos);
            
            // 应用过滤器
            if (filter_by_type(d->d_type, filter_type)) {
                // 获取文件大小(对于普通文件)
                long long file_size = 0;
                if (d->d_type == DT_REG) {
                    char full_path[1024];
                    struct stat st;
                    snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, d->d_name);
                    if (stat(full_path, &st) == 0) {
                        file_size = st.st_size;
                        stats.total_size += file_size;
                    }
                }
                
                printf("%c %-20s %12llu %10lld\n",
                       get_file_type_char(d->d_type),
                       d->d_name,
                       (unsigned long long)d->d_ino,
                       file_size);
            }
            
            // 更新统计信息
            update_stats(&stats, d->d_type);
            
            bpos += d->d_reclen;
        }
    }
    
    close(fd);
    
    // 显示统计信息
    print_stats(&stats);
    
    return 0;
}

示例3:递归目录遍历工具

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

struct linux_dirent64 {
    ino64_t        d_ino;
    off64_t        d_off;
    unsigned short d_reclen;
    unsigned char  d_type;
    char           d_name[];
};

// 全局统计变量
int total_files = 0;
int total_dirs = 0;
long long total_size = 0;
int max_depth = 0;

// 递归遍历目录
void traverse_directory(const char *path, int current_depth, int max_show_depth) {
    int fd;
    char buf[8192];
    int nread;
    
    // 更新最大深度
    if (current_depth > max_depth) {
        max_depth = current_depth;
    }
    
    // 打开目录
    fd = open(path, O_RDONLY | O_DIRECTORY);
    if (fd == -1) {
        printf("无法打开目录: %s\n", path);
        return;
    }
    
    // 显示当前目录(如果在显示范围内)
    if (current_depth <= max_show_depth) {
        for (int i = 0; i < current_depth; i++) {
            printf("  ");
        }
        printf("[%s]\n", path);
    }
    
    // 读取目录项
    while (1) {
        nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
        if (nread == -1) {
            printf("读取目录失败: %s\n", path);
            close(fd);
            return;
        }
        
        if (nread == 0) {
            break;  // 到达目录末尾
        }
        
        // 解析目录项
        for (int bpos = 0; bpos < nread;) {
            struct linux_dirent64 *d;
            d = (struct linux_dirent64 *)(buf + bpos);
            
            // 跳过 . 和 ..
            if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
                bpos += d->d_reclen;
                continue;
            }
            
            // 构造完整路径
            char full_path[1024];
            snprintf(full_path, sizeof(full_path), "%s/%s", path, d->d_name);
            
            if (d->d_type == DT_DIR) {
                total_dirs++;
                // 显示目录(如果在显示范围内)
                if (current_depth < max_show_depth) {
                    for (int i = 0; i <= current_depth; i++) {
                        printf("  ");
                    }
                    printf("├── %s/\n", d->d_name);
                } else if (current_depth == max_show_depth) {
                    for (int i = 0; i <= current_depth; i++) {
                        printf("  ");
                    }
                    printf("├── %s/ (子目录未展开)\n", d->d_name);
                }
                
                // 递归遍历子目录
                traverse_directory(full_path, current_depth + 1, max_show_depth);
                
            } else if (d->d_type == DT_REG) {
                total_files++;
                // 获取文件大小
                struct stat st;
                if (stat(full_path, &st) == 0) {
                    total_size += st.st_size;
                }
                
                // 显示文件(如果在显示范围内)
                if (current_depth < max_show_depth) {
                    for (int i = 0; i <= current_depth; i++) {
                        printf("  ");
                    }
                    printf("├── %s (%lld bytes)\n", d->d_name, 
                           (long long)(stat(full_path, &st) == 0 ? st.st_size : 0));
                }
            } else {
                total_files++;
                // 显示其他类型的文件
                if (current_depth < max_show_depth) {
                    for (int i = 0; i <= current_depth; i++) {
                        printf("  ");
                    }
                    printf("├── %s [%c]\n", d->d_name, get_file_type_char(d->d_type));
                }
            }
            
            bpos += d->d_reclen;
        }
    }
    
    close(fd);
}

// 获取文件类型字符
char get_file_type_char(unsigned char d_type) {
    switch (d_type) {
        case DT_REG:  return 'f';
        case DT_DIR:  return 'd';
        case DT_LNK:  return 'l';
        case DT_CHR:  return 'c';
        case DT_BLK:  return 'b';
        case DT_FIFO: return 'p';
        case DT_SOCK: return 's';
        default:      return '?';
    }
}

int main(int argc, char *argv[]) {
    char *start_path;
    int show_depth = 3;  // 默认显示3层
    
    // 解析命令行参数
    if (argc < 2) {
        printf("用法: %s <起始路径> [显示深度]\n", argv[0]);
        start_path = ".";
    } else {
        start_path = argv[1];
    }
    
    if (argc > 2) {
        show_depth = atoi(argv[2]);
        if (show_depth < 1) show_depth = 1;
    }
    
    printf("=== 递归目录遍历工具 ===\n");
    printf("起始路径: %s\n", start_path);
    printf("显示深度: %d\n\n", show_depth);
    
    // 开始遍历
    traverse_directory(start_path, 0, show_depth);
    
    // 显示统计信息
    printf("\n=== 遍历统计 ===\n");
    printf("总目录数: %d\n", total_dirs);
    printf("总文件数: %d\n", total_files);
    printf("总大小: %.2f MB (%lld bytes)\n", 
           total_size / (1024.0 * 1024.0), total_size);
    printf("最大深度: %d\n", max_depth);
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o getdents64_example1 example1.c
gcc -o getdents64_example2 example2.c
gcc -o getdents64_example3 example3.c

# 运行示例
./getdents64_example1
./getdents64_example1 /etc
./getdents64_example2 /home
./getdents64_example2 /usr/bin f  # 只显示普通文件
./getdents64_example2 /dev d      # 只显示目录
./getdents64_example3 /usr 2      # 显示2层目录结构

重要注意事项

  1. 缓冲区大小: 建议使用较大的缓冲区(如 4KB-8KB)以提高效率
  2. 目录文件描述符: 必须使用 O_DIRECTORY 标志打开目录
  3. 错误处理: 始终检查返回值和 errno
  4. 字符串处理: 目录项名称是 null 结尾的,但结构体大小可能包含填充
  5. 性能getdents64 比 readdir 更高效,因为它一次返回多个目录项
  6. 移植性: 这是 Linux 特有的系统调用

与 readdir 的比较

特性getdents64readdir
级别系统调用标准库函数
效率高(批量读取)较低(逐个读取)
使用复杂度复杂简单
可移植性Linux 特有跨平台
功能原始数据解析后的结构体

实际应用场景

  1. 文件管理器: 快速显示目录内容
  2. 备份工具: 扫描需要备份的文件
  3. 搜索工具: 快速遍历文件系统
  4. 系统监控: 监控目录变化
  5. 磁盘分析: 统计磁盘使用情况
  6. 安全扫描: 检查可疑文件

这些示例展示了 getdents64 函数的各种使用方法,从基本的目录读取到复杂的递归遍历,帮助你掌握这个高效的目录操作接口。

getdents64 是 Linux 系统底层目录扫描函数,用于高效读取目录内容。它直接与内核交互,返回包含文件名、inode号、文件类型等信息的原始目录项数据。相比高级接口如 readdirgetdents64 提供了更高的灵活性和性能。使用时需提供目录文件描述符、缓冲区和大小参数,返回实际读取字节数或错误码。该函数常用于需要精细控制目录扫描的场景,如文件系统工具开发。示例代码展示了如何用 getdents64 实现目录内容扫描和文件类型统计功能。

发表在 linux文章 | 留下评论

fremovexattr系统调用及示例

fremovexattr – 删除文件的扩展属性(通过文件描述符)

1. 函数介绍

fremovexattr 是一个 Linux 系统调用,用于删除指定文件的特定扩展属性(extended attribute)。与 removexattr 不同,fremovexattr 通过文件描述符而不是文件路径来操作文件,这样可以避免在多线程环境中因文件重命名或删除而导致的竞态条件。

扩展属性是文件系统提供的一种机制,允许用户为文件关联额外的元数据,这些元数据以键值对的形式存储。删除扩展属性可以清理不再需要的元数据信息。

2. 函数原型

#include <sys/types.h>
#include <attr/xattr.h>

int fremovexattr(int fd, const char *name);

3. 功能

删除通过文件描述符指定的文件的指定名称的扩展属性。如果该属性不存在,则返回错误。

4. 参数

  • int fd: 文件描述符,通过 open() 等函数获得
  • const char *name: 要删除的扩展属性的名称(包括命名空间前缀)
    • 例如:"user.my_attribute""security.selinux""trusted.my_trusted_attr"

5. 返回值

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

6. 常见 errno 错误码

  • EBADF: 无效的文件描述符
  • ENOTSUP: 文件系统不支持扩展属性
  • EACCES: 权限不足(删除某些命名空间的属性需要特殊权限)
  • ENODATA: 指定的扩展属性不存在
  • ENOTDIR: 文件描述符指向的不是目录(在某些情况下)
  • EPERM: 操作被拒绝(如尝试删除系统保护的属性)
  • EROFS: 文件位于只读文件系统上
  • ENOMEM: 内存不足

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

  • removexattr(): 通过文件路径删除扩展属性
  • lremovexattr(): 删除符号链接本身的扩展属性(不跟随链接)
  • fgetxattr(): 通过文件描述符获取扩展属性值
  • fsetxattr(): 通过文件描述符设置扩展属性
  • flistxattr(): 通过文件描述符列出所有扩展属性名称
  • getxattr()setxattr()listxattr(): 对应的路径版本

8. 示例代码

示例1:基本使用 – 删除扩展属性

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <attr/xattr.h>
#include <errno.h>
#include <string.h>

int main() {
    int fd;
    int ret;
    
    // 创建测试文件
    fd = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功创建文件,文件描述符: %d\n", fd);
    
    // 先设置一个扩展属性
    const char *attr_name = "user.test_attribute";
    const char *attr_value = "This is a test value";
    
    ret = fsetxattr(fd, attr_name, attr_value, strlen(attr_value), 0);
    if (ret == -1) {
        perror("设置扩展属性失败");
        close(fd);
        exit(EXIT_FAILURE);
    }
    
    printf("成功设置扩展属性: %s = %s\n", attr_name, attr_value);
    
    // 验证属性是否存在
    char buffer[256];
    ssize_t size = fgetxattr(fd, attr_name, buffer, sizeof(buffer) - 1);
    if (size != -1) {
        buffer[size] = '\0';
        printf("验证 - 属性值: %s\n", buffer);
    }
    
    // 删除扩展属性
    ret = fremovexattr(fd, attr_name);
    if (ret == -1) {
        perror("删除扩展属性失败");
    } else {
        printf("成功删除扩展属性: %s\n", attr_name);
    }
    
    // 再次尝试获取已删除的属性(应该失败)
    size = fgetxattr(fd, attr_name, buffer, sizeof(buffer) - 1);
    if (size == -1) {
        if (errno == ENODATA) {
            printf("确认:扩展属性 %s 已被成功删除\n", attr_name);
        } else {
            perror("获取已删除属性时出现意外错误");
        }
    }
    
    close(fd);
    return 0;
}

示例2:错误处理和权限检查

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <attr/xattr.h>
#include <errno.h>
#include <string.h>

void test_remove_attribute(int fd, const char *attr_name) {
    printf("\n尝试删除属性: %s\n", attr_name);
    
    int ret = fremovexattr(fd, attr_name);
    if (ret == -1) {
        switch (errno) {
            case ENODATA:
                printf("  错误: 属性 '%s' 不存在\n", attr_name);
                break;
            case EACCES:
                printf("  错误: 权限不足,无法删除属性 '%s'\n", attr_name);
                break;
            case ENOTSUP:
                printf("  错误: 文件系统不支持扩展属性\n");
                break;
            case EPERM:
                printf("  错误: 操作被拒绝,无法删除属性 '%s'\n", attr_name);
                break;
            case EROFS:
                printf("  错误: 文件系统为只读,无法删除属性\n");
                break;
            default:
                printf("  错误: %s (errno: %d)\n", strerror(errno), errno);
                break;
        }
    } else {
        printf("  成功删除属性: %s\n", attr_name);
    }
}

int main() {
    int fd;
    
    // 打开系统文件进行测试(需要适当权限)
    fd = open("test_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("成功创建测试文件,文件描述符: %d\n", fd);
    
    // 设置一些测试属性
    const char *user_attr = "user.test_attr";
    const char *value = "test value";
    
    if (fsetxattr(fd, user_attr, value, strlen(value), 0) == -1) {
        perror("设置测试属性失败");
    } else {
        printf("设置测试属性: %s\n", user_attr);
    }
    
    // 测试删除存在的属性
    test_remove_attribute(fd, user_attr);
    
    // 测试删除不存在的属性
    test_remove_attribute(fd, "user.nonexistent_attr");
    
    // 测试删除无效命名空间的属性
    test_remove_attribute(fd, "invalid.namespace.attr");
    
    // 测试删除空名称的属性
    test_remove_attribute(fd, "");
    
    close(fd);
    return 0;
}

示例3:批量操作和属性管理

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <attr/xattr.h>
#include <errno.h>
#include <string.h>

// 列出并删除所有 user.* 命名空间的属性
int remove_user_attributes(int fd) {
    ssize_t list_size;
    char *list_buffer;
    char *current;
    int removed_count = 0;
    
    // 获取扩展属性列表大小
    list_size = flistxattr(fd, NULL, 0);
    if (list_size == -1) {
        perror("获取属性列表大小失败");
        return -1;
    }
    
    if (list_size == 0) {
        printf("没有扩展属性需要删除\n");
        return 0;
    }
    
    // 分配缓冲区
    list_buffer = malloc(list_size);
    if (list_buffer == NULL) {
        perror("内存分配失败");
        return -1;
    }
    
    // 获取扩展属性列表
    if (flistxattr(fd, list_buffer, list_size) == -1) {
        perror("获取属性列表失败");
        free(list_buffer);
        return -1;
    }
    
    // 遍历所有属性,删除 user.* 命名空间的属性
    current = list_buffer;
    while (current < list_buffer + list_size) {
        // 检查是否为 user. 命名空间
        if (strncmp(current, "user.", 5) == 0) {
            printf("删除 user 属性: %s\n", current);
            
            if (fremovexattr(fd, current) == -1) {
                fprintf(stderr, "删除属性 %s 失败: %s\n", current, strerror(errno));
            } else {
                removed_count++;
            }
        } else {
            printf("跳过非 user 属性: %s\n", current);
        }
        
        current += strlen(current) + 1;
    }
    
    free(list_buffer);
    return removed_count;
}

int main() {
    int fd;
    int result;
    
    // 创建测试文件
    fd = open("managed_file.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        exit(EXIT_FAILURE);
    }
    
    printf("创建测试文件,文件描述符: %d\n", fd);
    
    // 设置多个不同命名空间的属性
    const char *attrs[][2] = {
        {"user.attr1", "value1"},
        {"user.attr2", "value2"},
        {"user.backup_info", "backup data"},
        {"trusted.admin_note", "admin only"},
        {"security.context", "security label"}
    };
    
    int attr_count = sizeof(attrs) / sizeof(attrs[0]);
    
    printf("设置测试属性:\n");
    for (int i = 0; i < attr_count; i++) {
        if (fsetxattr(fd, attrs[i][0], attrs[i][1], strlen(attrs[i][1]), 0) == -1) {
            fprintf(stderr, "设置属性 %s 失败: %s\n", attrs[i][0], strerror(errno));
        } else {
            printf("  %s = %s\n", attrs[i][0], attrs[i][1]);
        }
    }
    
    // 列出所有属性
    printf("\n当前所有扩展属性:\n");
    ssize_t list_size = flistxattr(fd, NULL, 0);
    if (list_size > 0) {
        char *list_buffer = malloc(list_size);
        if (list_buffer) {
            flistxattr(fd, list_buffer, list_size);
            char *current = list_buffer;
            while (current < list_buffer + list_size) {
                printf("  %s\n", current);
                current += strlen(current) + 1;
            }
            free(list_buffer);
        }
    }
    
    // 删除所有 user.* 属性
    printf("\n删除 user.* 命名空间的属性:\n");
    result = remove_user_attributes(fd);
    if (result >= 0) {
        printf("成功删除 %d 个 user.* 属性\n", result);
    }
    
    // 验证剩余属性
    printf("\n删除后的扩展属性:\n");
    list_size = flistxattr(fd, NULL, 0);
    if (list_size > 0) {
        char *list_buffer = malloc(list_size);
        if (list_buffer) {
            flistxattr(fd, list_buffer, list_size);
            char *current = list_buffer;
            while (current < list_buffer + list_size) {
                printf("  %s\n", current);
                current += strlen(current) + 1;
            }
            free(list_buffer);
        }
    }
    
    close(fd);
    return 0;
}

9. 扩展属性命名空间权限说明

不同命名空间的扩展属性有不同的权限要求:

  • user.*: 普通用户可以读写自己文件的属性
  • trusted.*: 需要特权权限(CAP_SYS_ADMIN)才能访问
  • system.*: 系统内部使用,通常需要特殊权限
  • security.*: 安全相关,可能需要特定安全模块的权限

10. 实际应用场景

fremovexattr 常用于以下场景:

  • 清理文件的自定义元数据
  • 移除备份工具添加的临时标记
  • 删除安全标签或访问控制信息
  • 文件管理工具的属性清理功能
  • 系统维护和清理脚本

总结

fremovexattr 是管理文件扩展属性的重要函数,通过文件描述符提供了安全的删除接口。使用时需要注意:

  1. 确保属性名称完整且正确(包括命名空间前缀)
  2. 处理属性不存在的情况(ENODATA 错误)
  3. 注意不同命名空间的权限要求
  4. 在只读文件系统上操作会失败(EROFS)
  5. 正确处理各种可能的错误情况
  6. 在多线程环境中使用文件描述符可以避免竞态条件

扩展属性的删除操作是文件元数据管理的重要组成部分,在现代 Linux 系统中被广泛应用于各种高级文件管理场景。

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

发表在 linux文章 | 留下评论

LibGen Mirror 2025 最新可用镜像站点大全


✅ LibGen Mirror 2025 最新可用镜像站点大全 | 高速访问 + 替代方案

📌 更新时间:2025年4月 — 实测可用 | 安全访问指南 | 学术资源免费下载


🔍 什么是 LibGen Mirror?

Library Genesis(简称 LibGen) 是全球最大的免费学术资源库之一,提供数百万本电子书、期刊论文、漫画和小说。由于官方域名常被封锁或关停,全球志愿者维护多个 LibGen Mirror(镜像站点),确保用户持续访问。

本页面提供 2025年最新、最稳定、可高速访问的 LibGen 镜像列表,并附替代方案、使用技巧与安全提示。


🚀 2025 年最新可用 LibGen Mirror 镜像站点(实测推荐)

⚠️ 镜像站点可能随时变动,建议收藏本页或保存多个备用地址。

排名镜像地址状态备注
🥇 1http://libgen.is✅ 在线稳定首选推荐,界面友好,搜索强大
🥈 2http://libgen.rs✅ 在线稳定老牌镜像,数据库完整
🥉 3http://libgen.st✅ 在线速度快,新兴稳定站点
4http://libgen.li⚠️ 间歇可用偶尔宕机,可作备用
5http://gen.lib.rus.ec❌ 经常被墙老用户熟悉,但近年不稳定

🔄 如何查找最新 LibGen 镜像?官方状态监测平台

由于镜像站点常变动,推荐使用以下工具实时监测:


🧭 LibGen 使用技巧(提升搜索 & 下载效率)

🔎 搜索优化

  • 使用 ISBN、DOI、书名 + 作者 精准定位
  • 支持多语言搜索(英文、中文、俄文等)
  • 利用高级筛选:按年份、扩展名(PDF, EPUB, MOBI)、文件大小过滤

⚡ 下载加速

  • 更换镜像站点提升速度
  • 使用下载工具:IDM、Aria2、Free Download Manager
  • 避开高峰时段(欧美时间夜间下载更快)

🛡️ 隐私与安全

  • 使用 VPN 或 Tor 浏览器 访问被封锁镜像
  • 避免在镜像站登录账户或填写个人信息
  • 安装广告拦截插件(如 uBlock Origin)避免恶意弹窗

🆕 LibGen + Sci-Hub 联合使用(论文党必备)

LibGen 与 Sci-Hub 数据互通,可交叉使用:

服务用途推荐地址
Sci-Hub输入 DOI 下载期刊论文https://sci-hub.se
https://sci-hub.ru
LibGen Books下载教材、小说、学术专著http://libgen.is
LibGen Sci-Tech工程、计算机、医学类文献镜像站内选择 “Scientific articles” 分类

⚖️ 法律与道德声明

LibGen 提供的资源多为 版权受限内容,在部分国家/地区访问可能违反当地法律。我们建议:

  • 仅用于 个人学习、科研、教育用途
  • 有能力者请支持正版,通过图书馆或机构订阅获取资源
  • 不得用于商业分发或盈利行为

📌 温馨提示:部分大学/研究机构已订阅 Springer、Elsevier、IEEE 等数据库,优先使用合法渠道。


🆘 LibGen 无法访问?试试这些替代方案

资源类型替代网站特点
📚 电子书Z-Library 镜像(需找最新入口)
PDF Drive
Open Library
免费 PDF 书库,Z-Lib 功能类似 LibGen
🎓 学术论文Unpaywall
CORE
Semantic Scholar
合法开放获取论文平台
🇨🇳 中文资源书格(古籍)
苦瓜书盘
鸠摩搜书
中文电子书、绝版书搜索

❓ 常见问题 FAQ

Q1: LibGen 镜像为什么经常打不开?

→ 因版权压力,域名常被查封或DNS污染。建议使用 VPN 或更换镜像。

Q2: 下载的书打不开?

→ 检查文件扩展名(如 .pdf, .epub),使用对应阅读器(Adobe Reader, Calibre)。

Q3: 是否有手机App?

→ 无官方 App,但可通过手机浏览器访问镜像站,或使用 PWA 添加到主屏幕。

Q4: 如何捐赠支持 LibGen?

→ LibGen 为志愿者运营,无官方捐赠渠道。可通过传播、维护镜像、提交缺失书籍等方式支持。


📌 总结:2025 最佳 LibGen Mirror 推荐

首选访问http://libgen.is
备选方案http://libgen.rs | http://libgen.st
状态监测https://libgen.fun
论文下载https://sci-hub.se


💬 需要帮助?留言或私信!

如果你找不到某本书或论文,欢迎提供:

  • 书名 + 作者
  • ISBN 或 DOI 号
  • 出版年份或出版社

我们将为你定位最新可下载链接!


🔖 收藏本页,随时获取 LibGen 最新镜像更新!


✅ SEO 优化说明(供内容发布者参考)

  • 标题优化:包含核心关键词 “LibGen Mirror 2025”、“最新镜像”、“高速访问”
  • 结构化数据:使用 H2/H3 标题、表格、列表提升可读性与语义结构
  • 关键词密度:自然分布 “LibGen mirror”、“Library Genesis”、“免费电子书”、“学术资源”、“Sci-Hub” 等长尾词
  • 内部链接建议:可链接至“Sci-Hub使用指南”、“Z-Library镜像”、“PDF电子书下载”等相关页面
  • 外链权威性:链接至 GitHub、官方状态页、替代平台,提升 E-E-A-T
  • 移动端适配:段落简短、按钮式推荐、响应式表格

📌 最后更新:2025年7月5日
🌐 本页将持续更新,确保提供最新、最安全、最稳定的 LibGen 镜像访问方案。


立即访问推荐镜像 → http://libgen.is


本文内容仅供教育与科研参考,请遵守所在国家/地区法律法规。合理使用,支持正版。

发表在 linux文章 | 留下评论

一文打尽pthread库

一文打尽pthread库

pthread(POSIX Threads)是遵循 POSIX 标准的线程库,广泛用于 Unix/Linux 系统中实现多线程编程。它提供了一组 C 语言 API,用于创建、管理、同步线程。以下是对 pthread 库及相关函数的系统性总结:


一、基本概念

  • 线程(Thread):轻量级进程,共享进程地址空间,独立执行流。
  • 主线程:程序启动时默认创建的线程。
  • 并发 vs 并行:并发是逻辑上同时执行,并行是物理上同时执行(多核)。
  • 线程安全:函数/数据结构在多线程环境下能正确工作。

二、核心数据类型

#include <pthread.h>

pthread_t           // 线程标识符(类似进程的 pid)
pthread_attr_t      // 线程属性结构体
pthread_mutex_t     // 互斥锁
pthread_cond_t      // 条件变量
pthread_rwlock_t    // 读写锁

三、线程管理函数

1. 创建线程

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void*), 
                   void *arg);
  • thread:输出参数,新线程 ID。
  • attr:线程属性,NULL 表示默认。
  • start_routine:线程入口函数。
  • arg:传递给入口函数的参数。
  • 返回值:0 成功,非 0 错误码(非 errno)。

⚠️ 注意:必须链接 -lpthread(或 -pthread


2. 等待线程结束(阻塞)

int pthread_join(pthread_t thread, void **retval);
  • 阻塞调用线程,直到目标线程结束。
  • 可获取线程返回值(通过 retval)。
  • 适用于需要同步或回收资源的场景。

3. 分离线程(非阻塞回收)

int pthread_detach(pthread_t thread);
  • 线程结束后自动释放资源,无需 pthread_join
  • 不能对已分离线程调用 pthread_join

4. 获取当前线程 ID

pthread_t pthread_self(void);

5. 比较线程 ID

int pthread_equal(pthread_t t1, pthread_t t2);
  • 返回非 0 表示相等。

6. 退出线程

void pthread_exit(void *retval);
  • 主动终止当前线程,可传递返回值。
  • 主线程调用 pthread_exit 不会终止整个进程(除非是最后一个线程)。

四、线程属性设置(可选)

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

// 设置分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
// detachstate: PTHREAD_CREATE_JOINABLE / PTHREAD_CREATE_DETACHED

// 设置栈大小
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

五、线程同步机制

1. 互斥锁(Mutex)

用于保护临界区,防止多个线程同时访问共享资源。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 非阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

🔒 使用模式:

pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex);

2. 条件变量(Condition Variable)

用于线程间通信,常与互斥锁配合使用,实现“等待某个条件成立”。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // 阻塞等待
int pthread_cond_timedwait(...); // 带超时
int pthread_cond_signal(pthread_cond_t *cond); // 唤醒一个等待线程
int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有等待线程
int pthread_cond_destroy(pthread_cond_t *cond);

🔄 典型使用模式:

pthread_mutex_lock(&mutex);
while (condition_is_false) {
    pthread_cond_wait(&cond, &mutex); // 自动释放锁,被唤醒后重新加锁
}
// 执行操作
pthread_mutex_unlock(&mutex);

3. 读写锁(Read-Write Lock)

允许多个读者同时访问,写者独占访问。

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

int pthread_rwlock_init(...);
int pthread_rwlock_rdlock(...);    // 读锁
int pthread_rwlock_wrlock(...);    // 写锁
int pthread_rwlock_unlock(...);
int pthread_rwlock_destroy(...);

适合“读多写少”场景。


4. 自旋锁(Spinlock)【可选】

忙等待锁,适用于锁持有时间极短的场景。

pthread_spinlock_t spinlock;

int pthread_spin_init(...);
int pthread_spin_lock(...);
int pthread_spin_unlock(...);
int pthread_spin_destroy(...);

六、线程局部存储(TLS)

每个线程拥有独立的变量副本。

pthread_key_t key;

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
int pthread_key_delete(pthread_key_t key);

类似于 C11 的 _Thread_local 或 GCC 的 __thread


七、取消机制(Cancellation)

允许一个线程请求终止另一个线程。

int pthread_cancel(pthread_t thread); // 发送取消请求

// 设置取消状态和类型
int pthread_setcancelstate(int state, int *oldstate);
// state: PTHREAD_CANCEL_ENABLE / PTHREAD_CANCEL_DISABLE

int pthread_setcanceltype(int type, int *oldtype);
// type: PTHREAD_CANCEL_DEFERRED(默认,到取消点) / PTHREAD_CANCEL_ASYNCHRONOUS

void pthread_testcancel(void); // 显式设置取消点

⚠️ 取消点(Cancellation Points):如 sleep, read, write, pthread_cond_wait 等阻塞函数。


八、一次初始化(One-time Initialization)

确保某段初始化代码只执行一次。

pthread_once_t once_control = PTHREAD_ONCE_INIT;

int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

常用于初始化全局资源(如互斥锁、TLS key)。


九、常见错误处理

pthread 函数不设置 errno,而是直接返回错误码:

int ret = pthread_create(...);
if (ret != 0) {
    fprintf(stderr, "Error: %s\n", strerror(ret));
}

或使用 perror 需要先设置 errno:

errno = ret;
perror("pthread_create");

十、编译与链接

gcc -o program program.c -lpthread
# 或推荐使用:
gcc -o program program.c -pthread   # 自动定义宏、链接库

十一、最佳实践与注意事项

  1. 避免死锁:加锁顺序一致,避免嵌套锁。
  2. 资源回收:join 或 detach 所有线程,避免资源泄漏。
  3. 线程安全函数:避免使用非线程安全函数(如 strtok, rand → 改用 strtok_r, rand_r)。
  4. 信号处理:线程中慎用信号,推荐使用 sigwait 或指定信号处理线程。
  5. 性能考虑:锁粒度不宜过大,避免频繁加锁。
  6. 调试工具:使用 valgrind --tool=helgrindThreadSanitizer 检测竞争条件。

十二、简单示例

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void* thread_func(void* arg) {
    int id = *(int*)arg;
    printf("Thread %d running\n", id);
    sleep(1);
    pthread_exit((void*)(long)id);
}

int main() {
    pthread_t t1, t2;
    int id1 = 1, id2 = 2;

    pthread_create(&t1, NULL, thread_func, &id1);
    pthread_create(&t2, NULL, thread_func, &id2);

    void* retval;
    pthread_join(t1, &retval);
    printf("Thread 1 returned: %ld\n", (long)retval);

    pthread_join(t2, &retval);
    printf("Thread 2 returned: %ld\n", (long)retval);

    return 0;
}

总结

功能主要函数
创建线程pthread_create
等待线程pthread_join
分离线程pthread_detach
互斥锁pthread_mutex_*
条件变量pthread_cond_*
读写锁pthread_rwlock_*
线程局部存储pthread_key_*
一次初始化pthread_once
线程取消pthread_cancel, pthread_testcancel

掌握 pthread 库是进行 Linux/Unix 系统级并发编程的基础。合理使用同步机制、避免竞态条件和死锁,是编写健壮多线程程序的关键。


✅ 推荐学习顺序:线程创建 → 互斥锁 → 条件变量 → 读写锁 → 高级特性(TLS、取消、一次初始化)
📚 参考资料:《UNIX环境高级编程》、《POSIX标准文档》、Linux man pages (man pthread_create)

以下是《UNIX环境高级编程》和《POSIX标准文档》的官方或权威获取链接:


📘 1.《UNIX环境高级编程》(Advanced Programming in the UNIX Environment, 简称 APUE)

  • 作者:W. Richard Stevens & Stephen A. Rago
  • 当前最新版:第3版(2013),涵盖 POSIX.1-2008 和 UNIX System V / BSD 扩展
  • 官方出版商页面
    👉 https://www.apuebook.com/

该网站由作者 Stephen Rago 维护,提供:

  • 源代码下载(所有示例代码)
  • 勘误表(errata)
  • 各章摘要和更新说明
  • 非常适合配合学习使用
  • 购买纸质/电子书
  • Amazon:https://www.amazon.com/Advanced-Programming-UNIX-Environment-3rd/dp/0321637739
  • 中国读者可在京东、当当购买中文翻译版(译者:戚正伟 等)

⚠️ 本书无“开源免费电子版”,请支持正版。


📜 2.《POSIX 标准文档》(IEEE Std 1003.1)

POSIX 是由 IEEE 和 The Open Group 共同维护的标准,官方文档需通过其网站获取。

✅ 官方免费在线查阅版(HTML):

👉 The Open Group Base Specifications, Issue 7 (2018) — POSIX.1-2017
🔗 https://pubs.opengroup.org/onlinepubs/9699919799/

这是当前最权威、最新且免费公开访问的 POSIX 标准文档(含 Shell、Utilities、System Interfaces、Headers 等)。

  • 包含所有 pthread 函数规范(如 pthread_create, pthread_mutex_lock 等)
  • 可直接搜索函数名,查看标准定义、参数、返回值、错误码、可移植性说明
  • 支持书签、交叉引用,适合开发者查阅

📄 PDF 下载版(需注册,部分免费):

👉 https://publications.opengroup.org/standards/unix

  • 注册后可免费下载 PDF(部分文档需付费)
  • 搜索 “IEEE Std 1003.1™-2017” 或 “The Open Group Base Specifications Issue 7”

🆚 IEEE 官方标准购买页面(付费):

👉 https://standards.ieee.org/standard/1003_1-2017.html

  • IEEE 出版的正式标准文档(PDF)
  • 价格较高(约 $200+),适合企业或标准研究者
  • 内容与 The Open Group 版本基本一致

✅ 对于绝大多数开发者,The Open Group 免费在线版已完全足够


📌 补充:Linux man pages(在线)

虽然不是“标准文档”,但 Linux man pages 是最实用的函数参考:

🔗 https://man7.org/linux/man-pages/

  • 搜索 pthread_create → https://man7.org/linux/man-pages/man3/pthread_create.3.html
  • 包含函数原型、描述、示例、错误码、遵循标准(如 POSIX.1-2001)
  • 由 Michael Kerrisk(《The Linux Programming Interface》作者)维护,权威可靠

✅ 总结推荐

资源名称类型推荐链接备注
《APUE》官网图书配套https://www.apuebook.com/源码+勘误,必备
POSIX 标准在线文档官方标准https://pubs.opengroup.org/onlinepubs/9699919799/免费、权威、最新
Linux man-pages函数手册https://man7.org/linux/man-pages/实用开发参考
IEEE POSIX 标准购买付费标准https://standards.ieee.org/standard/1003_1-2017.html企业/研究用

📘 建议学习路径:

  1. 学 pthread → 查 man7.org 快速上手
  2. 深入理解标准行为 → 查 Open Group POSIX 文档
  3. 系统学习 UNIX 编程 → 读 APUE 第3版
发表在 linux文章 | 留下评论

Comprehensive Guide to Global Online Digital Libraries


Comprehensive Guide to Global Online Digital Libraries

This guide provides detailed profiles of 12 well-known digital libraries worldwide, covering general-purpose platforms, academic databases, national digital libraries, and specialized open-access repositories. Each entry is structured into eight key aspects for systematic comparison and understanding.

LINKS:全球知名在线电子图书馆 LinuxKernel全球下载站点与镜像站点统计


Z-Library – The World’s Largest Shadow Library

1. Basic Information

  • Founded: 2009
  • Operator: Anonymous organization (decentralized)
  • Website: https://z-lib.io (mirror sites frequently change)

2. Content and Scale

  • Total Resources: Over 130 million books and 84 million academic articles
  • Resource Types: E-books, textbooks, novels, research papers, journals, magazines, audiobooks
  • Languages & Disciplines: Supports over 100 languages; covers all major fields including humanities, science, engineering, medicine, law, and arts

3. Source and Copyright Status

  • Sources: User uploads, web crawling, third-party databases, scanned copies
  • Copyright Policy: Most content lacks formal authorization; classified as a “shadow library.” Operates in legal gray zones and faces copyright infringement allegations globally

4. Access and Usage Rights

  • Free Access: Yes, but access depends on mirror availability
  • Registration Required: Optional for basic use; required for higher download limits
  • Download Rules: Allows downloads in PDF, EPUB, MOBI formats; daily limits apply unless upgraded

5. Technology and User Experience

  • Search Functionality: Advanced search by title, author, ISBN, DOI, keyword; includes recommendation engine
  • Interface & Device Support: Responsive design, mobile-friendly, simple interface
  • Reading Tools: Online preview available; no built-in annotation or note-taking

6. Target Audience

  • Primary Users: Self-learners, students in developing countries, researchers with limited institutional access

7. Partnerships and Influence

  • Cooperation Networks: No official partners; maintained through volunteer mirrors and peer support
  • Academic Impact: Widely used globally, especially where legal access to paid resources is limited

8. Features and Value

  • Unique Advantages: Unparalleled scale, fast updates, multi-language support
  • Social Impact: Promotes knowledge equity but raises ethical debates about copyright vs. access rights

World Library – A Nonprofit Global Knowledge Archive

1. Basic Information

2. Content and Scale

  • Total Resources: Over 3.49 million e-books
  • Resource Types: Classic literature, historical documents, religious texts, educational materials, children’s books
  • Languages & Disciplines: Over 100 languages; oldest materials date back to the 11th century; strong in humanities and cultural heritage

3. Source and Copyright Status

  • Sources: Public domain digitization, library donations, government archives
  • Copyright Policy: Focuses on public domain works; fully compliant with copyright laws

4. Access and Usage Rights

  • Free Access: Completely free
  • Registration Required: Not required
  • Download Rules: Full downloads in PDF/EPUB without DRM restrictions

5. Technology and User Experience

  • Search Functionality: Basic and advanced search; AI-powered features (from 2025): smart search, AI-generated summaries, content analysis
  • Interface & Device Support: Clean, multilingual interface; mobile-compatible
  • Reading Tools: Built-in reader with text highlighting and font adjustment

6. Target Audience

  • Primary Users: Students, educators, lifelong learners, multilingual readers

7. Partnerships and Influence

  • Cooperation Networks: Partner of UNESCO; collaborates with global public libraries
  • Academic Impact: Supports education in underserved regions; promotes linguistic diversity

8. Features and Value

  • Unique Advantages: AI-enhanced discovery, focus on ancient and rare texts
  • Social Impact: Democratizes access to historical knowledge across language barriers

Google Books – The Largest Book Search Engine

1. Basic Information

2. Content and Scale

  • Total Resources: Indexes tens of millions of books; millions available for preview or full view
  • Resource Types: Books, journals, manuals, technical reports, rare books
  • Languages & Disciplines: Multilingual (primarily English); spans all academic and general subjects

3. Source and Copyright Status

  • Sources: Partnership with libraries (e.g., Harvard, Oxford) and publishers for scanning
  • Copyright Policy: Three-tier system:
  • Public Domain: Full text available
  • Limited Preview: Partial view
  • Snippet View: Metadata only
    Legal disputes (e.g., Authors Guild v. Google) ruled in favor of “fair use”

4. Access and Usage Rights

  • Free Access: Partially free; full access may require purchase or institutional login
  • Registration Required: Not needed for search or preview
  • Download Rules: Only public domain books can be downloaded as PDF

5. Technology and User Experience

  • Search Functionality: Powerful full-text search using OCR; precise sentence-level results
  • Interface & Device Support: Highly optimized, cross-platform compatible
  • Reading Tools: Page-by-page viewer, text copy, translation, related book suggestions

6. Target Audience

  • Primary Users: Researchers, students, general readers, citation seekers

7. Partnerships and Influence

  • Cooperation Networks: Harvard, Stanford, Oxford, New York Public Library, major publishers
  • Academic Impact: One of the most widely used book discovery tools in academia

8. Features and Value

  • Unique Advantages: Integrated with Google Search; enables deep textual analysis
  • Social Impact: Revolutionized book discoverability; advanced digital humanities research

Project Gutenberg – The Pioneer of Free eBooks

1. Basic Information

2. Content and Scale

  • Total Resources: Over 70,000 free e-books (as of 2025)
  • Resource Types: Classic literature, poetry, drama, philosophy, history
  • Languages & Disciplines: Primarily English; includes French, German, Spanish; focused on humanities

3. Source and Copyright Status

  • Sources: Volunteer digitization via typing or OCR, followed by proofreading
  • Copyright Policy: All works are in the public domain (typically >95 years old); fully legal and free

4. Access and Usage Rights

  • Free Access: Completely free
  • Registration Required: None
  • Download Rules: Unlimited downloads in EPUB, Kindle, HTML, TXT formats

5. Technology and User Experience

  • Search Functionality: Search by title, author, subject, language; popularity ranking
  • Interface & Device Support: Simple, accessible design; mobile-friendly
  • Reading Tools: Online reading; some titles offer audio versions

6. Target Audience

  • Primary Users: Literature enthusiasts, students, educators, language learners

7. Partnerships and Influence

  • Cooperation Networks: Volunteers worldwide; collaboration with Distributed Proofreaders
  • Academic Impact: Considered the first digital library; foundational to the open-access movement

8. Features and Value

  • Unique Advantages: Ad-free, nonprofit, community-powered
  • Social Impact: Preserves literary classics and makes them globally accessible

JSTOR – A Trusted Academic Digital Library

1. Basic Information

2. Content and Scale

  • Total Resources: Nearly 2,000 journals, 100,000+ books, millions of articles
  • Resource Types: Journal backfiles, scholarly monographs, primary sources
  • Languages & Disciplines: Primarily English; strong in humanities and social sciences (history, literature, economics, etc.)

3. Source and Copyright Status

  • Sources: Licensed from academic publishers and university presses
  • Copyright Policy: Legally licensed; most content under copyright; older issues gradually enter public domain

4. Access and Usage Rights

  • Free Access: Limited free content (Early Journal Content); most require subscription
  • Registration Required: Free account for abstracts; institutional login for full access
  • Download Rules: Subscribers can download PDFs with monthly limits

5. Technology and User Experience

  • Search Functionality: Full-text search with advanced filters (subject, year, journal)
  • Interface & Device Support: Modern, responsive design; mobile-compatible
  • Reading Tools: Built-in reader with highlighting, notes, citation export (EndNote, Zotero)

6. Target Audience

  • Primary Users: University students, faculty, researchers, librarians

7. Partnerships and Influence

  • Cooperation Networks: Over 8,000 institutions in 160+ countries, including Harvard, MIT, Cambridge
  • Academic Impact: One of the most authoritative sources in humanities research

8. Features and Value

  • Unique Advantages: High-quality peer-reviewed content; long-term preservation
  • Social Impact: Enables systematic scholarly research and citation

World Digital Library (WDL) – Bridging Global Cultures

1. Basic Information

  • Founded: 2009
  • Operator: U.S. Library of Congress (lead), UNESCO (co-founder)
  • Website: https://www.wdl.org

2. Content and Scale

  • Total Resources: Over 20,000 digitized cultural items
  • Resource Types: Rare books, manuscripts, maps, photos, films, musical scores
  • Languages & Disciplines: Covers 193 countries; supports 7 languages (Arabic, Chinese, English, French, Spanish, Russian, Portuguese)

3. Source and Copyright Status

  • Sources: Contributions from global libraries, museums, and archives
  • Copyright Policy: All items are public domain or cleared for open use

4. Access and Usage Rights

  • Free Access: Completely free
  • Registration Required: Not required
  • Download Rules: High-resolution images and PDFs freely downloadable

5. Technology and User Experience

  • Search Functionality: Filter by country, era, theme, language; timeline and map browsing
  • Interface & Device Support: Multilingual interface; responsive design
  • Reading Tools: Zoomable high-res images, detailed metadata, multilingual descriptions

6. Target Audience

  • Primary Users: Educators, students, historians, cross-cultural researchers

7. Partnerships and Influence

  • Cooperation Networks: 32+ international institutions including British Library, National Library of China
  • Academic Impact: Leading platform for global cultural heritage

8. Features and Value

  • Unique Advantages: Emphasizes non-Western perspectives and cultural diversity
  • Social Impact: Promotes intercultural dialogue and global education

National Digital Library of China – Preserving Chinese Heritage

1. Basic Information

  • Founded: 2001
  • Operator: National Library of China
  • Website: https://www.nlc.cn/dszq/

2. Content and Scale

  • Total Resources: Over 10 million digital items
  • Resource Types: E-books, ancient texts, theses, local gazetteers, audio-visual lectures
  • Languages & Disciplines: Primarily Chinese; covers literature, history, law, economics, art

3. Source and Copyright Status

  • Sources: In-house digitization, publisher partnerships, government projects
  • Copyright Policy: Legally licensed or public domain; some content requires authentication

4. Access and Usage Rights

  • Free Access: Partially free; registered users get broader access
  • Registration Required: Real-name registration (via ID)
  • Download Rules: Some books allow online reading or PDF download

5. Technology and User Experience

  • Search Functionality: Full-text search; supports traditional Chinese and handwriting recognition (pilot)
  • Interface & Device Support: Desktop and mobile; WeChat mini-program available
  • Reading Tools: High-res ancient text viewer, text-to-speech, zoom function

6. Target Audience

  • Primary Users: Chinese university students, researchers, general public

7. Partnerships and Influence

  • Cooperation Networks: National and local libraries, university consortia
  • Academic Impact: China’s largest public digital library; key cultural infrastructure

8. Features and Value

  • Unique Advantages: World-leading digitization of Chinese classics (e.g., Yongle Encyclopedia)
  • Social Impact: Advances traditional culture preservation and national literacy

Internet Archive – Preserving Humanity’s Digital Memory

1. Basic Information

2. Content and Scale

  • Total Resources: 30+ million books (including Open Library), billions of web pages, millions of videos/audio
  • Resource Types: E-books, websites (Wayback Machine), software, films, music, radio
  • Languages & Disciplines: Multilingual, interdisciplinary

3. Source and Copyright Status

  • Sources: Donations, scanning, web crawling, user uploads
  • Copyright Policy: Mix of public domain, fair use, and controlled digital lending (with DRM)

4. Access and Usage Rights

  • Free Access: Fully free
  • Registration Required: Required for borrowing e-books
  • Download Rules: Most items downloadable; borrowed books return after 14 days

5. Technology and User Experience

  • Search Functionality: Powerful metadata search; filter by type, year, format
  • Interface & Device Support: Feature-rich but complex; works on all platforms
  • Reading Tools: Universal viewer with page flip, zoom, text copy

6. Target Audience

  • Primary Users: Researchers, historians, retro-computing enthusiasts, digital archivists

7. Partnerships and Influence

  • Cooperation Networks: Libraries, universities, open-source communities
  • Academic Impact: Global leader in digital preservation

8. Features and Value

  • Unique Advantages: Archives disappearing websites and obsolete software
  • Social Impact: Known as the “Library of Alexandria for the digital age”

HathiTrust Digital Library – A Consortium for Scholarly Preservation

1. Basic Information

2. Content and Scale

  • Total Resources: Over 17 million volumes
  • Resource Types: Academic books, journals, government documents, patents
  • Languages & Disciplines: Primarily English; comprehensive across disciplines

3. Source and Copyright Status

  • Sources: Google Books scans, member library digitization
  • Copyright Policy: ~40% public domain (full access); rest accessible only to member institutions

4. Access and Usage Rights

  • Free Access: Public domain content free; others require institutional login
  • Registration Required: Not for search; access requires affiliation
  • Download Rules: Public domain books can be fully downloaded as PDF

5. Technology and User Experience

  • Search Functionality: Full-text search; bulk metadata export
  • Interface & Device Support: Professional, research-oriented; responsive
  • Reading Tools: Standard PDF reader with text selection

6. Target Audience

  • Primary Users: Faculty and students at member universities (e.g., MIT, Stanford, UC)

7. Partnerships and Influence

  • Cooperation Networks: 170+ universities including Michigan, Harvard, Stanford
  • Academic Impact: Cornerstone of long-term academic resource preservation in North America

8. Features and Value

  • Unique Advantages: Collaborative model ensures sustainability and access continuity
  • Social Impact: Provides critical infrastructure for digital scholarship

Europeana – Europe’s Cultural Memory Online

1. Basic Information

2. Content and Scale

  • Total Resources: Over 50 million cultural records
  • Resource Types: Books, manuscripts, artworks, photos, music, films
  • Languages & Disciplines: 29 European languages; strong in art, history, literature

3. Source and Copyright Status

  • Sources: Aggregated metadata from EU museums, libraries, archives
  • Copyright Policy: Open metadata; links to original institutions; most content legally open

4. Access and Usage Rights

  • Free Access: Fully free
  • Registration Required: Not required
  • Download Rules: Metadata free; original files follow source institution policies

5. Technology and User Experience

  • Search Functionality: Semantic search, timeline, map-based browsing
  • Interface & Device Support: Modern, multilingual, mobile-ready
  • Reading Tools: High-resolution viewing, favorites, share options

6. Target Audience

  • Primary Users: Educators, researchers, artists, general public

7. Partnerships and Influence

  • Cooperation Networks: Thousands of institutions across 40+ European countries
  • Academic Impact: Largest pan-European cultural heritage portal

8. Features and Value

  • Unique Advantages: Cross-border integration of cultural collections
  • Social Impact: Celebrates Europe’s diversity and fosters public engagement

NDLI (National Digital Library of India) – Empowering Indian Education

1. Basic Information

2. Content and Scale

  • Total Resources: Over 100 million educational items
  • Resource Types: Textbooks, lecture notes, video lectures, exam papers, theses
  • Languages & Disciplines: 22 Indian languages; K-12 to postgraduate levels

3. Source and Copyright Status

  • Sources: Government initiatives, university collaborations, OER (Open Educational Resources)
  • Copyright Policy: Mostly open license or public domain

4. Access and Usage Rights

  • Free Access: Completely free
  • Registration Required: Recommended for personalized features
  • Download Rules: Free download of PDFs, videos, and other materials

5. Technology and User Experience

  • Search Functionality: Multilingual search; curriculum-based navigation
  • Interface & Device Support: Responsive; offline app available
  • Reading Tools: Video player, note-taking, bookmarking

6. Target Audience

  • Primary Users: Indian students, teachers, rural learners

7. Partnerships and Influence

  • Cooperation Networks: UGC, AICTE, CBSE, state education boards
  • Academic Impact: Largest educational digital platform in South Asia

8. Features and Value

  • Unique Advantages: Supports low-bandwidth areas and multilingual education
  • Social Impact: Supports India’s “Digital India” and inclusive education goals

PubMed Central (PMC) – The Leading Biomedical Open Repository

1. Basic Information

2. Content and Scale

  • Total Resources: Over 7 million full-text articles
  • Resource Types: Peer-reviewed biomedical and life sciences research papers
  • Languages & Disciplines: Primarily English; medicine, genetics, public health, biology

3. Source and Copyright Status

  • Sources: Mandated submissions from journals under NIH Public Access Policy
  • Copyright Policy: All content is open access (OA), under CC licenses or publisher agreements

4. Access and Usage Rights

  • Free Access: Completely free
  • Registration Required: None
  • Download Rules: PDF and XML downloads allowed for research and teaching

5. Technology and User Experience

  • Search Functionality: Integrated with PubMed; advanced filters, citation tracking
  • Interface & Device Support: Clean, professional; API access available
  • Reading Tools: Standard PDF reader; links to references and datasets

6. Target Audience

  • Primary Users: Medical researchers, clinicians, students, policymakers

7. Partnerships and Influence

  • Cooperation Networks: Thousands of journals, universities, research funders worldwide
  • Academic Impact: Most authoritative OA repository in biomedicine

8. Features and Value

  • Unique Advantages: Enforces open access for publicly funded research
  • Social Impact: Accelerates medical discovery, especially during health crises

Conclusion
These 12 digital libraries represent the diversity and evolution of global knowledge access — from open archives to academic gateways, from cultural preservation to educational equity. They reflect both technological innovation and ongoing debates about copyright, access, and digital inclusion.

Let this guide serve as a reference for educators, researchers, students, and lifelong learners navigating the digital knowledge landscape.


Discover the ultimate guide to global online digital libraries. Explore resources, access books, and learn about digital archives worldwide. Perfect for rese…

Global Online Digital Libraries, Comprehensive Guide to Digital Libraries, Online Digital Library Resources, Digital Libraries Around the World, What Are Digital Libraries, Top Online Digital Libraries 2024, Digital Library Services and Benefits, How to Use Digital Libraries, Free Online Digital Library Access, Digital Library Platforms and Tools

发表在 未分类 | 标签为 | 留下评论

全球知名在线电子图书馆

全球知名在线电子图书馆 (英文版)

探索全球知名在线电子图书馆,获取海量电子书与学术资源,随时随地畅享阅读乐趣。以下是根据您提供的原始列表,对全球知名在线电子图书馆进行扩充(新增6个具有地区或专业影响力的平台),并对全部12个电子图书馆按照统一模板进行详细信息整理的完整内容。

相关文章:LinuxKernel全球下载站点与镜像站点统计


Z-Library 详细介绍

1. 基本信息

  • 成立时间:2009年
  • 运营机构:非公开组织(匿名运营)
  • 官网地址:https://z-lib.io(镜像站点频繁更换)

2. 资源内容与规模

  • 总资源量:超过1.3亿本书籍、8400万篇学术文章
  • 资源类型:电子书、教材、小说、学术论文、期刊文章、杂志、有声书等
  • 语言与学科覆盖:支持100+种语言,涵盖文史哲、理工医、法律、艺术等几乎所有学科领域

3. 资源来源与版权状况

  • 来源渠道:用户上传、网络爬取、影印扫描、第三方数据库整合
  • 版权说明:大量资源未获正式授权,属于“影子图书馆”(Shadow Library),在多国被认定为侵犯版权,存在法律争议

4. 访问方式与使用权限

  • 是否免费:是(但需通过镜像站点访问)
  • 是否需注册:部分功能需注册账号,每日可免费下载有限数量资源
  • 下载与借阅规则:支持PDF、EPUB、MOBI等多种格式下载;高级会员可提升下载额度

5. 技术功能与用户体验

  • 搜索功能:支持书名、作者、ISBN、ISSN、DOI、关键词等多种检索方式,具备模糊匹配和推荐系统
  • 界面与设备支持:响应式网页设计,适配手机与桌面端;提供简洁清晰的界面
  • 阅读工具:支持在线预览,但无内置阅读器标注功能

6. 目标用户

  • 主要服务对象:自学者、发展中国家学生、科研人员、无法负担高价学术资源的人群

7. 合作网络与影响力

  • 合作机构:无官方合作机构,依赖分布式镜像和志愿者维护
  • 学术地位:虽非正规学术平台,但在全球范围内广泛使用,尤其在低收入国家影响巨大

8. 特色与价值

  • 独特优势:资源极其丰富,更新速度快,检索高效,几乎“无所不包”
  • 社会影响:推动知识平权,但也引发关于版权保护与开放获取边界的广泛讨论

World Library 详细介绍

1. 基本信息

2. 资源内容与规模

  • 总资源量:超过349万册电子图书
  • 资源类型:经典文学、历史文献、宗教典籍、教育教材、儿童读物、科学普及书籍等
  • 语言与学科覆盖:支持100余种语言,最早资源可追溯至11世纪,涵盖人文、历史、宗教、教育等领域

3. 资源来源与版权状况

  • 来源渠道:公共领域文献数字化、合作图书馆捐赠、政府档案开放
  • 版权说明:主要收录公共领域(Public Domain)作品,合法合规,无版权争议

4. 访问方式与使用权限

  • 是否免费:完全免费
  • 是否需注册:无需注册即可浏览和下载
  • 下载与借阅规则:所有书籍均可直接下载PDF或EPUB格式,无DRM限制

5. 技术功能与用户体验

  • 搜索功能:基础检索+高级检索(按语言、年代、主题);2025年起引入AI智能检索、AI内容摘要生成
  • 界面与设备支持:网页界面友好,支持移动端访问
  • 阅读工具:提供在线阅读器,支持文本高亮与字体调整

6. 目标用户

  • 主要服务对象:学生、教师、终身学习者、多语言读者、文化遗产研究者

7. 合作网络与影响力

  • 合作机构:联合国教科文组织合作伙伴,与全球多个公共图书馆和教育机构合作
  • 学术地位:作为公益性数字图书馆,在发展中国家教育推广中具有重要价值

8. 特色与价值

  • 独特优势:强调跨文化保存与AI赋能的知识发现,致力于“让每本书都能被找到”
  • 社会影响:促进全球知识共享,尤其服务于语言少数群体和偏远地区学习者

Google Books 详细介绍

1. 基本信息

2. 资源内容与规模

  • 总资源量:可搜索数千万本书籍,其中数百万本提供预览或全文
  • 资源类型:图书、期刊、手册、技术报告、古籍等
  • 语言与学科覆盖:多语言(以英语为主),覆盖所有主流学科

3. 资源来源与版权状况

  • 来源渠道:与全球图书馆(如哈佛、牛津、纽约公共图书馆)及出版商合作扫描
  • 版权说明:分为三类——公共领域(全文可读)、受限预览(部分内容)、仅元数据(不可读)。部分扫描曾引发版权诉讼(如Authors Guild诉Google案),但法院最终裁定其为“合理使用”

4. 访问方式与使用权限

  • 是否免费:部分内容免费,完整阅读需购买或通过图书馆访问
  • 是否需注册:无需注册即可搜索和预览
  • 下载与借阅规则:仅公共领域书籍可下载PDF;其余仅支持在线预览

5. 技术功能与用户体验

  • 搜索功能:强大的全文检索,支持OCR识别文本搜索,可定位到具体段落
  • 界面与设备支持:高度优化的搜索引擎界面,适配所有设备
  • 阅读工具:内置翻页式阅读器,支持复制文本、翻译句子、查看相似书籍

6. 目标用户

  • 主要服务对象:研究人员、普通读者、图书发现者、引用查找者

7. 合作网络与影响力

  • 合作机构:哈佛大学、斯坦福大学、牛津大学、纽约公共图书馆、各大出版社
  • 学术地位:全球最大图书搜索引擎,是学术研究中常用的文献发现工具

8. 特色与价值

  • 独特优势:强大的搜索引擎集成,能将图书内容纳入Google整体知识图谱
  • 社会影响:极大提升了图书的可见性与可发现性,推动了“数字人文”发展

Project Gutenberg 详细介绍

1. 基本信息

  • 成立时间:1971年
  • 运营机构:Project Gutenberg Association(志愿者组织)
  • 官网地址:https://www.gutenberg.org

2. 资源内容与规模

  • 总资源量:超过7万本免费电子书(截至2025年)
  • 资源类型:经典文学、诗歌、戏剧、历史著作、哲学文本等
  • 语言与学科覆盖:以英语为主,含法语、德语、西班牙语等数十种语言;聚焦人文经典

3. 资源来源与版权状况

  • 来源渠道:志愿者从纸质书手工录入或OCR处理,经校对后发布
  • 版权说明:所有书籍均为公共领域作品(通常出版超过95年),完全合法免费

4. 访问方式与使用权限

  • 是否免费:完全免费
  • 是否需注册:无需注册
  • 下载与借阅规则:支持EPUB、Kindle、HTML、TXT等格式自由下载,无任何限制

5. 技术功能与用户体验

  • 搜索功能:支持书名、作者、主题、语言检索,可按热门度排序
  • 界面与设备支持:简洁网页设计,兼容移动端
  • 阅读工具:支持在线阅读,部分书籍提供语音朗读版本

6. 目标用户

  • 主要服务对象:文学爱好者、学生、英语学习者、古典文化研究者

7. 合作网络与影响力

  • 合作机构:与Distributed Proofreaders等志愿者项目合作,全球数千名志愿者参与
  • 学术地位:最早启动的数字图书馆之一,被誉为“数字图书馆运动的起点”

8. 特色与价值

  • 独特优势:纯公益、无广告、完全开放,强调志愿者精神与文化传承
  • 社会影响:为全球读者提供无障碍的经典阅读体验,是开放获取理念的典范

JSTOR 详细介绍

1. 基本信息

2. 资源内容与规模

  • 总资源量:收录近2000种学术期刊、10万+本学术书籍、数百万篇论文
  • 资源类型:学术期刊过刊、学术专著、原始资料(如档案、照片)
  • 语言与学科覆盖:以英语为主,涵盖人文社科(历史、文学、政治、经济学等)为主,近年扩展至自然科学

3. 资源来源与版权状况

  • 来源渠道:与学术出版社和大学合作授权收录
  • 版权说明:合法授权,受版权保护;部分早期内容进入公共领域后免费开放

4. 访问方式与使用权限

  • 是否免费:部分免费(Old Archive计划),大部分需机构订阅或个人付费
  • 是否需注册:个人可注册免费账户(查看摘要),全文需通过机构登录
  • 下载与借阅规则:订阅用户可下载PDF,通常每月有下载限额

5. 技术功能与用户体验

  • 搜索功能:强大的全文检索,支持高级筛选(学科、出版年、期刊名等)
  • 界面与设备支持:现代网页设计,支持移动访问
  • 阅读工具:内置阅读器支持笔记、引用导出(EndNote、Zotero)、高亮标记

6. 目标用户

  • 主要服务对象:高校师生、研究人员、图书馆员

7. 合作网络与影响力

  • 合作机构:全球超过8000家教育与研究机构,包括哈佛、MIT、剑桥等顶尖学府
  • 学术地位:人文学科最重要的文献数据库之一,被广泛用于学术写作与研究

8. 特色与价值

  • 独特优势:高质量、同行评审、长期保存,强调学术严谨性
  • 社会影响:推动学术资源的系统化归档与可持续访问,是“数字典藏”的标杆

世界数字图书馆(The World Digital Library) 详细介绍

1. 基本信息

  • 成立时间:2009年
  • 运营机构:美国国会图书馆主导,联合国教科文组织联合发起
  • 官网地址:https://www.wdl.org

2. 资源内容与规模

  • 总资源量:超过20,000件数字化文物
  • 资源类型:古籍、手稿、地图、照片、影片、乐谱、稀有图书等
  • 语言与学科覆盖:涵盖全球193个国家的文化遗产,支持7种语言(阿拉伯语、中文、英语、法语、西班牙语、俄语、葡萄牙语)

3. 资源来源与版权状况

  • 来源渠道:来自全球图书馆、博物馆、档案馆的捐赠与合作数字化
  • 版权说明:所有资源均为公共领域或已获授权,合法开放

4. 访问方式与使用权限

  • 是否免费:完全免费
  • 是否需注册:无需注册
  • 下载与借阅规则:所有资源可自由查看、下载高清图像或PDF

5. 技术功能与用户体验

  • 搜索功能:支持按国家、时期、主题、语言、类型检索;提供时间轴和地图浏览模式
  • 界面与设备支持:多语言界面,响应式设计,适合教学展示
  • 阅读工具:高清图像缩放、元数据详细说明、多语言描述

6. 目标用户

  • 主要服务对象:教师、学生、文化研究者、跨文化交流者

7. 合作网络与影响力

  • 合作机构:UNESCO、美国国会图书馆、大英图书馆、中国国家图书馆等32个国际机构
  • 学术地位:全球最重要的跨文化数字遗产平台之一

8. 特色与价值

  • 独特优势:强调文化多样性与全球共享,呈现非西方视角的历史文献
  • 社会影响:促进文明互鉴,成为国际教育与博物馆数字化的典范

✅ 新增平台(按地区/专业补充)


中国国家数字图书馆 详细介绍

1. 基本信息

  • 成立时间:2001年(正式上线)
  • 运营机构:中国国家图书馆
  • 官网地址:https://www.nlc.cn/dszq/

2. 资源内容与规模

  • 总资源量:超1000万册电子资源
  • 资源类型:电子书、古籍、学位论文、地方志、音视频讲座、民国文献
  • 语言与学科覆盖:以中文为主,涵盖文史哲、法律、经济、艺术等

3. 资源来源与版权状况

  • 来源渠道:馆藏数字化、出版社合作、政府项目支持
  • 版权说明:合法授权或公共领域,部分资源需认证访问

4. 访问方式与使用权限

  • 是否免费:部分免费,注册用户可远程访问更多资源
  • 是否需注册:需实名注册(可凭身份证办理)
  • 下载与借阅规则:部分图书支持在线阅读,部分可下载PDF

5. 技术功能与用户体验

  • 搜索功能:支持全文检索、古籍繁体字检索、手写体识别试点
  • 界面与设备支持:PC与移动端兼容,支持微信小程序访问
  • 阅读工具:古籍高清浏览、语音朗读、放大镜功能

6. 目标用户

  • 主要服务对象:国内高校师生、研究人员、公众读者

7. 合作网络与影响力

  • 合作机构:全国各级公共图书馆、高校图书馆联盟
  • 学术地位:中国最大公益性数字图书馆,国家级文化基础设施

8. 特色与价值

  • 独特优势:中华古籍数字化领先,如《永乐大典》《四库全书》高清呈现
  • 社会影响:推动中华优秀传统文化传承与全民阅读

Internet Archive 详细介绍

1. 基本信息

  • 成立时间:1996年
  • 运营机构:Internet Archive(非营利组织)
  • 官网地址:https://archive.org

2. 资源内容与规模

  • 总资源量:超3000万本电子书(含Open Library)、数百亿网页快照、百万音视频
  • 资源类型:电子书、网页存档、老软件、电影、音乐、广播
  • 语言与学科覆盖:多语言,综合类

3. 耄源来源与版权状况

  • 来源渠道:捐赠、扫描、网络爬取、用户上传
  • 版权说明:混合模式——公共领域+合理使用+部分受控借阅(受DRM限制)

4. 访问方式与使用权限

  • 是否免费:完全免费
  • 是否需注册:借阅电子书需注册
  • 下载与借阅规则:多数可下载;受控借阅书籍限借14天

5. 技术功能与用户体验

  • 搜索功能:强大元数据检索,支持关键词、年份、类型筛选
  • 界面与设备支持:功能丰富但略显复杂,支持全平台
  • 阅读工具:内置通用阅读器,支持翻页、缩放、文本复制

6. 目标用户

  • 主要服务对象:研究者、怀旧爱好者、数字保存关注者

7. 合作网络与影响力

  • 合作机构:图书馆、大学、档案馆、开源社区
  • 学术地位:数字保存领域的全球领导者

8. 特色与价值

  • 独特优势:“保存人类知识”,包括已消失的网站与老系统软件
  • 社会影响:被誉为“数字时代的亚历山大图书馆”

HathiTrust Digital Library 详细介绍

1. 基本信息

  • 成立时间:2008年
  • 运营机构:HathiTrust联合体(由美国多所研究型大学组成)
  • 官网地址:https://www.hathitrust.org

2. 资源内容与规模

  • 总资源量:超1700万卷图书
  • 资源类型:学术图书、期刊、政府出版物、专利
  • 语言与学科覆盖:以英语为主,涵盖各学科

3. 资源来源与版权状况

  • 来源渠道:Google Books、成员图书馆扫描
  • 版权说明:约40%为公共领域可全文访问,其余受版权保护仅限成员机构检索

4. 访问方式与使用权限

  • 是否免费:公共领域内容免费,其余需成员机构登录
  • 是否需注册:公众无需注册即可搜索,访问受限
  • 下载与借阅规则:公共领域书籍可整本下载PDF

5. 技术功能与用户体验

  • 搜索功能:支持全文检索、批量元数据导出
  • 界面与设备支持:简洁专业,适合研究用途
  • 阅读工具:标准PDF阅读,支持文本复制

6. 目标用户

  • 主要服务对象:北美研究型大学师生

7. 合作网络与影响力

  • 合作机构:密歇根大学、哈佛、斯坦福、加州大学系统等170+高校
  • 学术地位:北美最重要的学术数字图书馆联盟

8. 特色与价值

  • 独特优势:大规模合作典藏,确保学术资源长期可访问
  • 社会影响:为“数字学术”提供基础设施支持

Europeana 详细介绍

1. 基本信息

2. 资源内容与规模

  • 总资源量:超5000万条文化记录
  • 资源类型:书籍、手稿、艺术品、照片、音乐、电影
  • 语言与学科覆盖:29种欧洲语言,涵盖艺术、历史、文学等

3. 资源来源与版权状况

  • 来源渠道:欧盟各国博物馆、图书馆、档案馆元数据聚合
  • 版权说明:遵循“开放元数据”原则,内容链接至原始机构,多数为合法开放资源

4. 访问方式与使用权限

  • 是否免费:完全免费
  • 是否需注册:无需注册
  • 下载与借阅规则:可查看和使用元数据,原始文件依来源机构政策

5. 技术功能与用户体验

  • 搜索功能:支持语义搜索、时间轴浏览、地图可视化
  • 界面与设备支持:现代化设计,支持多语言切换
  • 阅读工具:高清图像查看、收藏夹功能

6. 目标用户

  • 主要服务对象:文化研究者、教师、艺术家、公众

7. 合作网络与影响力

  • 合作机构:来自欧洲40+国家的上千家文化机构
  • 学术地位:欧洲最大文化遗产数字门户

8. 特色与价值

  • 独特优势:跨机构、跨国家整合,展现欧洲多元文化
  • 社会影响:促进文化遗产数字化与公众参与

NDLI(印度国家数字图书馆) 详细介绍

1. 基本信息

  • 成立时间:2014年
  • 运营机构:印度理工学院克勒格布尔分校(IIT Kharagpur)
  • 官网地址:https://ndl.iitkgp.ac.in

2. 资源内容与规模

  • 总资源量:超1亿条教育资源
  • 资源类型:教科书、讲义、视频课程、试题、论文
  • 语言与学科覆盖:支持印地语、泰米尔语等22种印度语言,覆盖K12至研究生教育

3. 资源来源与版权状况

  • 来源渠道:政府项目、高校合作、开放教育资源(OER)
  • 版权说明:多数为开放许可或公共领域

4. 访问方式与使用权限

  • 是否免费:完全免费
  • 是否需注册:建议注册以使用个性化功能
  • 下载与借阅规则:支持自由下载PDF、视频等

5. 技术功能与用户体验

  • 搜索功能:支持多语言检索、课程体系导航
  • 界面与设备支持:响应式设计,支持离线APP
  • 阅读工具:集成视频播放、笔记功能

6. 目标用户

  • 主要服务对象:印度学生、教师、农村地区学习者

7. 合作网络与影响力

  • 合作机构:UGC、AICTE、CBSE、各邦教育部门
  • 学术地位:南亚最大教育数字平台

8. 特色与价值

  • 独特优势:服务教育公平,支持多语言与低带宽环境
  • 社会影响:助力印度“数字印度”与“全民教育”战略

PubMed Central (PMC) 详细介绍

1. 基本信息

2. 资源内容与规模

  • 总资源量:超700万篇全文论文
  • 资源类型:生物医学、生命科学领域的同行评审论文
  • 语言与学科覆盖:以英语为主,涵盖医学、遗传学、公共卫生等

3. 资源来源与版权状况

  • 来源渠道:期刊出版社提交(符合NIH公共访问政策)
  • 版权说明:均为开放获取(OA),遵循CC许可或出版商政策

4. 访问方式与使用权限

  • 是否免费:完全免费
  • 是否需注册:无需注册
  • 下载与借阅规则:支持PDF、XML格式下载,可用于研究与教学

5. 技术功能与用户体验

  • 搜索功能:与PubMed集成,支持高级筛选、引文追踪
  • 界面与设备支持:专业简洁,支持API调用
  • 阅读工具:标准PDF阅读,支持参考文献链接

6. 目标用户

  • 主要服务对象:医学研究人员、临床医生、学生

7. 合作网络与影响力

  • 合作机构:全球数千家医学期刊、大学、研究机构
  • 学术地位:生物医学领域最权威的开放获取数据库

8. 特色与价值

  • 独特优势:强制开放政策推动科研透明化
  • 社会影响:加速医学知识传播,尤其在疫情等公共卫生事件中发挥关键作用

总结
本整理共涵盖 12个全球知名电子图书馆,包括:

  • 全球综合性平台(Z-Library、Internet Archive)
  • 学术研究型(JSTOR、HathiTrust、PMC)
  • 国家级文化项目(中国、印度、美国、欧盟)
  • 开放获取先驱(Project Gutenberg、World Library)
  • 跨文化交流平台(WDL、Europeana)

全球知名在线电子图书馆, 在线电子图书馆推荐, 全球著名电子图书馆平台, 免费在线电子图书馆资源, 国际知名数字图书馆网站, 专业电子图书馆平台排名, 全球主流电子书阅读平台, 权威在线图书资源网站, 知名电子图书馆有哪些, 数字图书馆推荐与对比

发表在 未分类 | 标签为 | 留下评论

fork系统调用及示例

fork系统调用及示例

我们继续按照您的列表顺序,介绍下一个函数。在 clone 之后,根据您提供的列表,下一个函数是 fork

相关阅读:fork系统调用及示例-CSDN博客 vfork系统调用及示例 ptrace系统调用及示例


1. 函数介绍

fork 是 Linux 和所有 Unix-like 系统中最基本、最重要的进程创建系统调用之一。它的功能非常直接:创建一个调用进程(父进程)

你可以把 fork 想象成一个生物细胞的有丝分裂过程:

  • 你有一个原始细胞(父进程)。
  • fork 调用就像触发了一次分裂。
  • 分裂完成后,你得到了两个细胞(两个进程):一个原始的(父进程)和一个全新的、几乎完全一样的(子进程)。
  • 两个细胞(进程)从分裂完成的那一刻起,开始独立地执行后续代码。

fork 是多进程编程的基石。几乎所有需要创建新进程的 Unix/Linux 程序(服务器、shell、构建工具等)都会用到它。


2. 函数原型

#include <unistd.h> // 必需

pid_t fork(void);

3. 功能

  • 创建新进程: 请求操作系统内核创建一个新的进程(子进程)。
  • 复制父进程: 内核会创建一个调用进程(父进程)的副本(子进程)。
    • 子进程拥有父进程在调用 fork 时的几乎全部状态
      • 代码段(.text)
      • 数据段(.data, .bss)
      • 堆(heap)
      • 栈(stack)
      • 打开的文件描述符及其状态(偏移量等)
      • 环境变量
      • 当前工作目录
      • 用户 ID 和组 ID
      • 等等…
  • 独立执行: 从 fork 返回后,父进程和子进程在操作系统调度下独立运行

4. 参数

  • voidfork 函数不接受任何参数。

5. 返回值

fork 的返回值是其最独特和关键的特性,因为它在父进程和子进程中返回不同的值

  • 在父进程中:
    • 成功: 返回新创建子进程的进程 ID (PID)。这是一个正整数
    • 失败: 返回 -1,并设置全局变量 errno 来指示错误原因(例如 EAGAIN 系统资源不足,ENOMEM 内存不足)。
  • 在子进程中:
    • 成功: 返回 0。
    • (理论上不会发生): 如果子进程创建失败,则不会执行到返回这一步。
  • **失败时 **(父进程)
    • 返回 -1,并且没有子进程被创建。

关键理解点: 一个 fork 调用,一次调用,两次返回


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

  • vfork: 类似于 fork,但在子进程调用 exec 或 _exit 之前会暂停父进程。现在通常不推荐使用,posix_spawn 或直接 fork + exec 更安全。
  • clone: Linux 特有的、更灵活和底层的进程/线程创建函数。fork 和 vfork 在底层都可以通过调用 clone 来实现。
  • _exit / exit: 子进程通常调用这两个函数之一来终止自己。
  • wait / waitpid: 父进程使用这些函数来等待子进程结束,并获取子进程的退出状态。
  • exec 系列函数 (execlexecvexecve 等): 通常在 fork 之后,由子进程调用,用一个新的程序镜像替换当前进程的镜像。

7. 示例代码

示例 1:基本的 fork 使用

这个例子演示了 fork 最基本的用法,以及如何区分父进程和子进程。

// fork_basic.c
#include <unistd.h>   // fork, getpid, sleep
#include <sys/wait.h> // wait
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit

int main() {
    pid_t pid;

    printf("Before fork: Process ID (PID) is %d\n", getpid());

    // --- 关键: 调用 fork ---
    pid = fork();

    // --- fork 之后,代码被父进程和子进程同时执行 ---

    if (pid == -1) {
        // fork 失败,只在父进程中执行
        perror("fork failed");
        exit(EXIT_FAILURE);

    } else if (pid == 0) {
        // --- 子进程执行的代码 ---
        printf("This is the CHILD process.\n");
        printf("  Child's PID is %d\n", getpid());
        printf("  Child's parent PID (PPID) is %d\n", getppid());
        printf("  fork() returned %d in the child.\n", pid); // pid is 0 here

        // 子进程可以执行自己的任务
        printf("  Child process is sleeping for 2 seconds...\n");
        sleep(2);
        printf("  Child process woke up and is exiting.\n");

        // 子进程结束
        exit(EXIT_SUCCESS);

    } else {
        // --- 父进程执行的代码 ---
        printf("This is the PARENT process.\n");
        printf("  Parent's PID is %d\n", getpid());
        printf("  Parent's child PID is %d (returned by fork)\n", pid);
        printf("  fork() returned %d in the parent.\n", pid);

        // 父进程可以执行自己的任务
        printf("  Parent process is waiting for child to finish...\n");
        int status;
        // wait() 会挂起父进程,直到任意一个子进程结束
        wait(&status); // status 用于获取子进程的退出信息

        if (WIFEXITED(status)) {
            int exit_status = WEXITSTATUS(status);
            printf("  Parent: Child exited normally with status %d.\n", exit_status);
        } else {
            printf("  Parent: Child did not exit normally.\n");
        }

        printf("  Parent process is exiting.\n");
    }

    return 0;
}

代码解释:

  1. 程序开始执行,打印父进程的 PID。
  2. 关键步骤: 调用 pid = fork();
  3. 这个调用之后,操作系统创建了一个子进程,它是父进程的一个副本。
  4. 在父进程和子进程中,代码都从 fork() 之后的下一行开始继续执行
  5. 程序立即使用 if 语句检查 pid 的值来区分父进程和子进程:
    • if (pid == -1)fork 调用失败。只有父进程会进入这个分支。
    • else if (pid == 0): 这段代码只在子进程中执行fork 在子进程中返回 0。
    • else (即 pid > 0): 这段代码只在父进程中执行fork 在父进程中返回新创建子进程的 PID。
  6. 子进程打印自己的 PID (getpid()) 和父进程的 PID (getppid()),然后睡眠 2 秒并退出。
  7. 父进程调用 wait(&status) 等待子进程结束。wait 会挂起父进程,直到子进程调用 exit 或 _exit
  8. 子进程退出后,wait 返回。父进程检查子进程的退出状态 (status) 并打印相关信息,然后退出。

示例 2:创建多个子进程

这个例子演示了如何使用 fork 在一个循环中创建多个子进程。

// fork_multiple.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_CHILDREN 5

int main() {
    pid_t pid;
    int i;

    printf("Parent process (PID: %d) is creating %d children.\n", getpid(), NUM_CHILDREN);

    for (i = 0; i < NUM_CHILDREN; i++) {
        pid = fork();

        if (pid == -1) {
            perror("fork failed");
            // 可以选择继续创建其他子进程或退出
            exit(EXIT_FAILURE);

        } else if (pid == 0) {
            // --- 子进程 ---
            printf("Child %d (PID: %d) created. Doing work...\n", i, getpid());

            // 模拟工作:睡眠不同的时间
            sleep(i + 1);
            printf("Child %d (PID: %d) finished work and is exiting with code %d.\n", i, getpid(), i);
            exit(i); // 用 i 作为退出码

        }
        // --- 父进程 ---
        // 父进程从 if-else 结构出来,继续循环
        // printf("Parent (PID: %d) created child %d with PID %d\n", getpid(), i, pid);
        // 注意:如果父进程在这里也打印,输出会和子进程的输出混合
    }

    // --- 所有子进程创建完毕,父进程等待它们 ---
    printf("Parent (PID: %d) has created all children. Now waiting for them to finish...\n", getpid());

    // 等待所有子进程结束
    for (i = 0; i < NUM_CHILDREN; i++) {
        int status;
        pid_t waited_pid = wait(&status); // waitpid(-1, &status, 0) 等价

        if (waited_pid == -1) {
            perror("wait failed");
        } else {
            if (WIFEXITED(status)) {
                int exit_code = WEXITSTATUS(status);
                printf("Parent: Child with PID %d exited normally with code %d.\n", waited_pid, exit_code);
            } else {
                printf("Parent: Child with PID %d did not exit normally.\n", waited_pid);
            }
        }
    }

    printf("Parent (PID: %d) finished. All children have been reaped.\n", getpid());
    return 0;
}

代码解释:

  1. 定义要创建的子进程数量 NUM_CHILDREN
  2. 父进程进入一个循环 for (i = 0; i < NUM_CHILDREN; i++)
  3. 在每次循环中调用 fork()
  4. 在子进程中 (pid == 0):
    • 打印信息。
    • 根据 i 的值睡眠不同时间(模拟不同长度的工作)。
    • 调用 exit(i) 退出,并将循环变量 i 作为退出码。
  5. 在父进程中 (pid > 0):
    • 循环继续下一次迭代,创建下一个子进程。
  6. 当循环结束时,所有子进程都已启动。
  7. 父进程进入第二个循环 for (i = 0; i < NUM_CHILDREN; i++)
  8. 在这个循环中,父进程调用 wait(&status) 五次
  9. 每次 wait 返回,就表示有一个子进程结束了。父进程获取其 PID 和退出状态并打印。
  10. 所有子进程都被 wait 回收后,父进程结束。

示例 3:fork + exec 创建新程序

这个例子演示了经典的 fork + exec 模式,这是 Unix/Linux 系统中启动新程序的标准方法。

// fork_exec.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    pid_t pid;

    printf("Parent process (PID: %d) is about to fork.\n", getpid());

    pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);

    } else if (pid == 0) {
        // --- 子进程 ---
        printf("Child process (PID: %d) created.\n", getpid());

        // 准备 execvp 所需的参数
        // execvp(const char *file, char *const argv[]);
        char *args[] = { "ls", "-l", "/tmp", NULL }; // argv[] 必须以 NULL 结尾

        printf("Child (PID: %d) is about to execute 'ls -l /tmp'.\n", getpid());

        // 调用 execvp 执行新的程序
        // 如果 execvp 成功,下面的代码将不会被执行
        // 因为当前进程的镜像已被替换
        execvp(args[0], args);

        // --- 如果代码执行到这里,说明 execvp 失败了 ---
        perror("execvp failed"); // 打印错误信息
        _exit(EXIT_FAILURE); // 子进程失败退出,使用 _exit

    } else {
        // --- 父进程 ---
        printf("Parent process (PID: %d) created child (PID: %d).\n", getpid(), pid);

        // 父进程等待子进程结束
        int status;
        if (waitpid(pid, &status, 0) == -1) { // 等待特定的子进程
            perror("waitpid failed");
            exit(EXIT_FAILURE);
        }

        if (WIFEXITED(status)) {
            int exit_status = WEXITSTATUS(status);
            printf("Parent: Child (PID: %d) exited with status %d.\n", pid, exit_status);
        } else if (WIFSIGNALED(status)) {
            int sig = WTERMSIG(status);
            printf("Parent: Child (PID: %d) was killed by signal %d.\n", pid, sig);
        } else {
            printf("Parent: Child (PID: %d) did not exit normally.\n", pid);
        }

        printf("Parent process (PID: %d) finished.\n", getpid());
    }

    return 0;
}

代码解释:

  1. 父进程调用 fork() 创建子进程。
  2. 在子进程中 (pid == 0):
    • 准备要执行的命令及其参数。这里准备执行 ls -l /tmp
    • 关键步骤: 调用 execvp("ls", args)
      • execvp 会用 ls 程序的镜像替换当前子进程的内存镜像。
      • 如果 execvp 成功,它永远不会返回。子进程的代码从 ls 程序的入口点开始执行。
      • 如果 execvp 失败(例如,找不到 ls 命令),它会返回 -1。
    • 如果 execvp 返回了(即失败了),打印错误信息并调用 _exit(EXIT_FAILURE) 退出子进程。
  3. 在父进程中 (pid > 0):
    • 打印信息。
    • 调用 waitpid(pid, &status, 0) 等待特定的子进程(由 pid 指定)结束。
    • 子进程执行 ls 命令并退出。
    • waitpid 返回,父进程检查子进程的退出状态。
      • WIFEXITED: 检查子进程是否正常退出(通过 exit 或 return)。
      • WIFSIGNALED: 检查子进程是否被信号终止。
    • 打印子进程的退出信息。
    • 父进程退出。

重要提示与注意事项:

  1. 一次调用,两次返回: 理解 fork 在父进程和子进程中返回不同值是掌握其用法的关键。
  2. 区分父子进程: 必须使用 if (pid == 0) {...} else if (pid > 0) {...} else {...} 模式来区分父进程和子进程,并执行不同的代码逻辑。
  3. 错误处理: 始终检查 fork 的返回值是否为 -1,以处理可能的失败情况(如资源不足)。
  4. 僵尸进程: 当子进程结束而父进程尚未调用 wait 或 waitpid 来获取其状态时,子进程会变成僵尸进程(Zombie Process)。这会浪费一个进程表项。因此,父进程必须等待子进程。
  5. 孤儿进程: 如果父进程在子进程之前结束,子进程会变成孤儿进程(Orphan Process),并被 init 进程(PID 1)收养。
  6. 文件描述符fork 之后,父进程和子进程共享所有的文件描述符。它们指向同一个内核的“打开文件描述”(open file description),因此共享文件偏移量等。如果需要独立的文件描述符,子进程需要在 fork 后显式地 close 和 open
  7. _exit vs exit: 在 fork 之后的子进程中,通常推荐使用 _exit() 而不是 exit() 来终止。因为 exit() 会执行一些清理工作(如刷新 stdio 缓冲区),这些操作在多进程环境下可能导致意外行为(例如,缓冲区被刷新两次)。_exit() 直接终止进程。
  8. fork + exec 模式: 这是 Unix/Linux 系统中创建并运行新程序的标准方法。先 fork 创建子进程,然后在子进程中调用 exec 系列函数加载新程序。

总结:

fork 是 Unix/Linux 系统编程的基础。它通过创建当前进程的副本来实现多进程。理解其“一次调用,两次返回”的特性以及如何正确地区分和管理父、子进程,是编写健壮的多进程应用程序的关键。它通常与 wait/waitpid 和 exec 系列函数结合使用。

发表在 linux文章 | 留下评论

pkey_mprotect系统调用及示例

pkey_mprotect系统调用及示例

我们来深入学习 pkey_mprotect 系统调用

相关文章:pkey_mprotect系统调用及示例-CSDN博客 pkey_mprotect系统调用及示例 pkey_alloc pkey_free系统调用及示例 preadv2系统调用及示例

1. 函数介绍

在 Linux 系统中,内存保护是一个核心的安全机制。我们使用 mprotect 系统调用(或者 C 标准库的 mprotect 函数)来设置一块内存区域的访问权限,比如:

  • 只读 (PROT_READ)
  • 可写 (PROT_WRITE)
  • 可执行 (PROT_EXEC)

例如,你可以将包含程序代码的内存页设置为“只读可执行”,防止程序意外修改自己的代码;或者将包含数据的内存页设置为“只读”,防止意外写入。

然而,mprotect 有一个限制:整个进程都遵循同一套内存保护规则。如果一个进程有权限修改某块内存的保护属性(通常需要特殊权限),它就可以修改任何内存页的权限。

Memory Protection Keys (MPK) 是 Intel 和 ARM 等 CPU 架构引入的一种更细粒度的内存保护机制。它允许将内存区域与一个密钥 (Protection Key) 关联起来。这个密钥就像一把锁,控制着与之关联的内存区域是否可以被访问。

pkey_mprotect 系统调用就是用来在设置内存区域访问权限的同时,将这块内存与一个特定的保护密钥 (pkey) 关联起来

简单来说

  • mprotect(addr, len, prot): 设置 addr 开始的 len 字节内存的权限为 prot
  • pkey_mprotect(addr, len, prot, pkey): 做同样的事,外加将这块内存和密钥 pkey 绑定。

要访问一块由 pkey_mprotect 保护的内存,不仅需要满足 prot 指定的权限(如 PROT_READ),当前线程的密钥权限掩码 (PKRU) 中对应的 pkey 也必须允许这种访问。

这提供了一种额外的、硬件加速的、线程级的内存访问控制。即使程序通过 mprotect 获得了写权限,如果线程的 pkey 设置禁止写入,访问仍然会失败。

典型应用场景

  • 沙箱/安全容器:为不同来源或信任级别的代码分配不同的 pkey,防止它们互相干扰或越权访问。
  • 调试器/分析器:保护关键数据结构不被被调试的程序意外修改。
  • 高性能库:在复杂的内存管理库中,使用 pkey 来防止用户代码错误地访问库的内部数据。

2. 函数原型

#define _GNU_SOURCE // 启用 GNU 扩展以使用 pkey_mprotect
#include <sys/mman.h> // 包含 pkey_mprotect 函数声明

int pkey_mprotect(void *addr, size_t len, int prot, int pkey);

注意:此函数需要 glibc 2.27 或更高版本,并且运行在支持 Memory Protection Keys 的 CPU 上(如 Intel Haswell 及更新的处理器,或支持相应特性的 ARM 处理器)。

3. 功能

为从 addr 开始、长度为 len 字节的内存区域设置访问权限 (prot),并将其与保护密钥 (pkey) 关联。

4. 参数

  • addr:
    • void * 类型。
    • 指向要修改保护属性的内存区域的起始地址。这个地址必须是页对齐的(通常是 4KB 边界)。
  • len:
    • size_t 类型。
    • 要修改保护属性的内存区域的长度(字节数)。
  • prot:
    • int 类型。
    • 指定要设置的内存访问权限。可以是以下值的按位或 (|) 组合:
      • PROT_NONE: 内存无法访问。
      • PROT_READ: 页面可读。
      • PROT_WRITE: 页面可写。
      • PROT_EXEC: 页面可执行。
  • pkey:
    • int 类型。
    • 指定要与该内存区域关联的保护密钥
    • 有效的 pkey 值是 0 到 PKEY_MAX (通常是 15) 之间的整数。
    • pkey 为 -1 时,表示不改变该内存区域当前关联的 pkey(如果有的话)。
    • 特殊的 pkey 值 PKEY_DISABLE_ACCESS 和 PKEY_DISABLE_WRITE 可用于 pkey_set 函数,而不是 pkey_mprotect

5. 返回值

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

6. 错误码 (errno)

pkey_mprotect 可能返回的错误码与 mprotect 相同或类似,并增加了一些与 pkey 相关的:

  • EINVALaddr 不是页对齐的,或者 prot 或 pkey 参数无效。
  • ENOMEM: 地址范围 (addr to addr+len) 无效,或者包含了未映射的页面。
  • EFAULTaddr 指向了调用进程无法访问的内存地址。
  • EACCES: 调用进程没有权限修改指定内存区域的保护属性。
  • ENOSYS: 系统调用不被当前内核或硬件支持(例如,CPU 不支持 MPK)。

7. 相似函数或关联函数

  • mprotect: 不使用保护密钥的标准内存保护函数。
  • pkey_alloc: 分配一个新的保护密钥。
  • pkey_free: 释放一个之前分配的保护密钥。
  • pkey_get: 获取当前线程的密钥权限掩码 (PKRU) 的值。
  • pkey_set: 设置当前线程的密钥权限掩码 (PKRU) 的值。
  • mmap: 用于分配和映射内存区域。
  • sysconf(_SC_PAGESIZE): 获取系统页大小,用于确保地址对齐。

8. 示例代码

下面的示例演示了如何使用 pkey_mprotect 来保护内存区域。请注意,此代码需要在支持 MPK 的硬件和内核上运行。

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

// 全局变量用于信号处理
volatile sig_atomic_t segv_caught = 0;

// 信号处理函数,捕获 SIGSEGV
void sigsegv_handler(int sig) {
    segv_caught = 1;
    write(STDOUT_FILENO, "Caught SIGSEGV (Segmentation Fault)!\n", 38);
}

int main() {
    char *buffer;
    int pkey, pkru_orig;
    size_t page_size = sysconf(_SC_PAGESIZE);
    struct sigaction sa;

    printf("--- Demonstrating pkey_mprotect ---\n");
    printf("Page size: %zu bytes\n", page_size);

    // 1. 检查系统是否支持 pkey
    // 尝试分配一个 pkey 来测试支持
    pkey = pkey_alloc(0, 0);
    if (pkey == -1) {
        if (errno == ENOSYS) {
            printf("Error: Memory Protection Keys (pkey) are not supported on this system/CPU.\n");
            printf("This example requires MPK support (e.g., Intel Haswell+).\n");
            exit(EXIT_FAILURE);
        } else {
            perror("pkey_alloc");
            exit(EXIT_FAILURE);
        }
    }
    printf("1. Allocated a protection key: %d\n", pkey);

    // 2. 分配一块内存 (使用 mmap 以确保页对齐)
    buffer = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (buffer == MAP_FAILED) {
        perror("mmap");
        pkey_free(pkey);
        exit(EXIT_FAILURE);
    }
    printf("2. Allocated %zu bytes of memory at %p using mmap.\n", page_size, buffer);

    // 3. 初始化内存
    strcpy(buffer, "Initial data in protected memory.");
    printf("3. Initialized memory: '%s'\n", buffer);

    // 4. 设置信号处理函数来捕获段错误
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigsegv_handler;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGSEGV, &sa, NULL) == -1) {
        perror("sigaction");
        munmap(buffer, page_size);
        pkey_free(pkey);
        exit(EXIT_FAILURE);
    }

    // 5. 使用 pkey_mprotect 设置权限并关联 pkey
    // 设置为只读,并与 pkey 关联
    printf("4. Calling pkey_mprotect to set memory to READ-ONLY and associate with pkey %d...\n", pkey);
    if (pkey_mprotect(buffer, page_size, PROT_READ, pkey) == -1) {
        perror("pkey_mprotect");
        munmap(buffer, page_size);
        pkey_free(pkey);
        exit(EXIT_FAILURE);
    }
    printf("   pkey_mprotect succeeded.\n");

    // 6. 尝试读取 (应该成功)
    printf("5. Attempting to READ from protected memory...\n");
    segv_caught = 0;
    printf("   Data read: '%s'\n", buffer);
    if (!segv_caught) {
        printf("   Read successful.\n");
    } else {
        printf("   Unexpected SIGSEGV on read!\n");
    }

    // 7. 尝试写入 (应该失败并触发 SIGSEGV)
    printf("6. Attempting to WRITE to READ-ONLY protected memory...\n");
    segv_caught = 0;
    buffer[0] = 'X'; // 尝试修改
    // 如果没有触发 SIGSEGV,说明可能没有保护生效(例如 pkey 允许写)
    if (segv_caught) {
        printf("   Write failed as expected (SIGSEGV caught).\n");
    } else {
        printf("   Write succeeded (unexpected, check pkey settings or hardware support).\n");
    }

    // 8. 修改线程的 pkey 权限掩码 (PKRU) 来允许写入
    printf("7. Getting current PKRU register value...\n");
    pkru_orig = pkey_get();
    if (pkru_orig == -1) {
        perror("pkey_get");
    } else {
        printf("   Current PKRU value: 0x%08x\n", pkru_orig);
        // 计算新的 PKRU 值,允许 pkey 的读写
        // 每个 pkey 占用 2 位:
        // Bits 0-1: pkey 0 (AD=Allow Disable access, WD=Write Disable)
        // Bits 2-3: pkey 1
        // ...
        // Bits 30-31: pkey 15
        // AD=0, WD=0 意味着允许访问和写入
        // AD=1, WD=0 意味着禁止访问
        // AD=0, WD=1 意味着允许访问但禁止写入
        // AD=1, WD=1 意味着禁止访问
        // 假设我们的 pkey 是 1 (这只是示例,实际 pkey 号可能不同)
        // 我们需要将 pkey 1 的 AD 和 WD 位都设为 0
        // pkey 1 的位是 2-3
        int new_pkru = pkru_orig & ~(3 << (pkey * 2)); // 清除 pkey 的两位
        printf("   Setting PKRU to allow RW for pkey %d. New PKRU value: 0x%08x\n", pkey, new_pkru);
        if (pkey_set(new_pkru) == -1) {
            perror("pkey_set");
        }
    }

    // 9. 再次尝试写入 (现在应该成功,因为 pkey 允许了)
    printf("8. Attempting to WRITE again after modifying PKRU...\n");
    segv_caught = 0;
    buffer[0] = 'Y'; // 尝试修改
    if (segv_caught) {
        printf("   Write failed (SIGSEGV caught), even after PKRU change.\n");
        printf("   This might indicate the pkey is not correctly associated or hardware issue.\n");
    } else {
        printf("   Write succeeded after PKRU change.\n");
        printf("   Data is now: '%s'\n", buffer);
    }

    // 10. 清理资源
    printf("\n--- Cleaning up ---\n");
    // 恢复原始的 PKRU 值 (好习惯)
    if (pkey_set(pkru_orig) == -1) {
        perror("pkey_set (restore)");
    } else {
        printf("Restored original PKRU value.\n");
    }

    if (munmap(buffer, page_size) == -1) {
        perror("munmap");
    } else {
        printf("Unmapped memory.\n");
    }

    if (pkey_free(pkey) == -1) {
        perror("pkey_free");
    } else {
        printf("Freed protection key %d.\n", pkey);
    }

    printf("\n--- Summary ---\n");
    printf("1. pkey_mprotect(addr, len, prot, pkey) sets memory permissions AND associates it with a pkey.\n");
    printf("2. Access requires BOTH standard permissions (prot) AND pkey permission (via PKRU register).\n");
    printf("3. pkey_alloc() gets a new key, pkey_free() releases it.\n");
    printf("4. pkey_get() reads PKRU, pkey_set() writes PKRU to control access per thread.\n");
    printf("5. It provides fine-grained, hardware-accelerated memory access control.\n");
    printf("6. Requires CPU and kernel support for Memory Protection Keys (MPK).\n");

    return 0;
}

9. 编译和运行

# 假设代码保存在 pkey_mprotect_example.c 中
# 需要较新的 glibc (>= 2.27) 和支持 MPK 的 CPU
gcc -o pkey_mprotect_example pkey_mprotect_example.c

# 运行程序
# 注意:如果系统不支持 MPK,程序会在开始时就退出并提示。
./pkey_mprotect_example

10. 预期输出 (在支持 MPK 的系统上)

--- Demonstrating pkey_mprotect ---
Page size: 4096 bytes
1. Allocated a protection key: 1
2. Allocated 4096 bytes of memory at 0x7f8b8c000000 using mmap.
3. Initialized memory: 'Initial data in protected memory.'
4. Calling pkey_mprotect to set memory to READ-ONLY and associate with pkey 1...
   pkey_mprotect succeeded.
5. Attempting to READ from protected memory...
   Data read: 'Initial data in protected memory.'
   Read successful.
6. Attempting to WRITE to READ-ONLY protected memory...
Caught SIGSEGV (Segmentation Fault)!
   Write failed as expected (SIGSEGV caught).
7. Getting current PKRU register value...
   Current PKRU value: 0x55555555
   Setting PKRU to allow RW for pkey 1. New PKRU value: 0x55555551
8. Attempting to WRITE again after modifying PKRU...
   Write succeeded after PKRU change.
   Data is now: 'Ynitial data in protected memory.'

--- Cleaning up ---
Restored original PKRU value.
Unmapped memory.
Freed protection key 1.

--- Summary ---
1. pkey_mprotect(addr, len, prot, pkey) sets memory permissions AND associates it with a pkey.
2. Access requires BOTH standard permissions (prot) AND pkey permission (via PKRU register).
3. pkey_alloc() gets a new key, pkey_free() releases it.
4. pkey_get() reads PKRU, pkey_set() writes PKRU to control access per thread.
5. It provides fine-grained, hardware-accelerated memory access control.
6. Requires CPU and kernel support for Memory Protection Keys (MPK).

在不支持 MPK 的系统上的输出:

--- Demonstrating pkey_mprotect ---
Page size: 4096 bytes
Error: Memory Protection Keys (pkey) are not supported on this system/CPU.
This example requires MPK support (e.g., Intel Haswell+).

11. 总结

pkey_mprotect 是一个利用现代 CPU 硬件特性(Memory Protection Keys)来提供更精细内存访问控制的系统调用。

  • 核心优势
    • 额外保护层:在传统的 mprotect 权限之上增加了一层由硬件支持的、基于密钥的访问控制。
    • 线程级控制:每个线程可以通过 pkey_set 独立控制自己对不同 pkey 保护区域的访问权限。
    • 高性能:由 CPU 硬件直接处理,检查开销极小。
  • 工作流程
    1. 使用 pkey_alloc 获取一个 pkey
    2. 使用 pkey_mprotect 将内存区域与 pkey 和访问权限 (prot) 关联。
    3. 使用 pkey_get 和 pkey_set 控制当前线程的 PKRU 寄存器,决定哪些 pkey 允许访问/写入。
    4. 当线程尝试访问内存时,CPU 会同时检查 mprotect 权限和 PKRU 中对应的 pkey 权限。
  • 使用前提
    • CPU 支持 MPK (如 Intel RDPID + Protection Keys for User-mode pages)。
    • Linux 内核支持 (通常 4.9+)。
    • glibc 版本足够新 (2.27+)。
  • 典型应用:沙箱、安全容器、调试器、高性能库。

对于 Linux 编程新手来说,pkey_mprotect 是一个高级特性,理解其概念和潜在用途有助于学习现代系统安全和内存管理的前沿技术。但在日常开发中,mprotect 仍然是管理内存权限的主要工具。

发表在 linux文章 | 留下评论

pkey_alloc pkey_free系统调用及示例

pkey_alloc pkey_free系统调用及示例

我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 pkey_alloc 和 pkey_free


1. 函数介绍

pkey_alloc 和 pkey_free 是一组与 内存保护键(Memory Protection Keys, MPK)相关的 Linux 系统调用(内核版本 >= 4.9, x86 架构)。它们是 Intel MPK (Memory Protection Keys) 特性的用户态接口。

**核心概念:内存保护键 **(Protection Keys)

想象你的内存是一间大房子,里面有各种不同的房间(内存页)。传统上,你只有一把总钥匙(页表项中的权限位 RWX)来控制进入这间房子的所有门(内存访问)。如果这把钥匙丢了或者被复制,坏人就能进入所有房间。

内存保护键(MPK)就像是给这间大房子加装了多把独立的锁(保护键):

  1. 获取钥匙 (pkey_alloc) 你向操作系统申请一把新的、独立的钥匙(保护键)。操作系统给你一个钥匙编号(pkey)。
  2. **给房间上锁 **(pkey_mprotect) 你可以使用 pkey_mprotect 系统调用,将特定的房间(内存区域)与你刚申请到的那把钥匙(pkey)关联起来。这相当于给这些房间的门加上了这把新锁。
  3. **控制钥匙 **(特殊寄存器) CPU 内部有一个特殊的寄存器(x86 上是 PKRU – Protection Key Rights User register)。这个寄存器里有 16 个插槽(对应 16 个可能的 pkey),每个插槽可以设置为允许或禁止访问。
  4. 尝试进入房间: 当程序试图访问一个与特定 pkey 关联的内存页时:
    • CPU 会检查页表项中的 pkey 编号(例如 3)。
    • 然后检查 PKRU 寄存器中对应插槽(第 3 个插槽)的权限。
    • 如果 PKRU 允许访问(例如,插槽 3 是 0b00),访问成功。
    • 如果 PKRU 禁止访问(例如,插槽 3 是 0b01 或 0b10),CPU 会立即产生一个 SIGSEGV (段错误) 信号,而无需进行昂贵的页表遍历

pkey_alloc 和 pkey_free 的作用:

  • pkey_alloc申请一个可用的内存保护键(pkey)。成功时返回一个唯一的 pkey 编号(0-15)。
  • pkey_free释放一个之前通过 pkey_alloc 申请的 pkey,使其可以被其他部分的程序再次申请使用。

优势:

  • 快速权限切换: 通过修改 PKRU 寄存器(一个非常快的操作),可以瞬间改变对大量内存区域的访问权限,而无需修改每个内存页的页表项。
  • 细粒度保护: 可以将不同的内存区域分配给不同的 pkey,实现更细粒度的内存访问控制。
  • 硬件加速: 权限检查由 CPU 硬件直接完成,性能极高。

相关文章:pkey_alloc系统调用及示例-CSDN博客 Linux 3.0 内核系统调用 preadv2系统调用及示例


2. 函数原型

#include <sys/mman.h> // 包含 MPK 相关常量和函数声明 (需要较新的 glibc)

// 分配一个保护键
int pkey_alloc(unsigned int flags, unsigned int access_rights);

// 释放一个保护键
int pkey_free(int pkey);

注意: 这些函数需要 glibc 2.27 或更高版本。在较旧的系统上,可能需要直接使用 syscall


3. 功能

  • pkey_alloc:
    • 向内核请求一个当前未被使用的内存保护键。
    • 内核会返回一个唯一的 pkey 编号(通常在 0 到 15 之间,具体取决于 CPU 实现)。
    • 这个 pkey 可以用于后续的 pkey_mprotect 调用。
  • pkey_free:
    • 将一个之前分配的 pkey 返还给内核。
    • 释放后,该 pkey 可以被后续的 pkey_alloc 调用再次分配。
    • 重要: 在调用 pkey_free 之前,应该确保没有内存区域仍然通过 pkey_mprotect 与该 pkey 关联,否则这些区域的访问权限可能会变得不可预测。

4. 参数

pkey_alloc

  • unsigned int flags: 目前必须设置为 0。保留供将来扩展。
  • unsigned int access_rights: 指定该 pkey 的初始访问权限。这是一个位掩码,定义在 <sys/mman.h> 中。
    • PKEY_DISABLE_ACCESS禁止所有访问(读、写、执行)。这是最严格的权限。
    • PKEY_DISABLE_WRITE禁止写入,但允许读取和执行。
    • 0允许所有访问(读、写、执行)。这是最宽松的权限。
    • 注意: 这个初始权限是设置在内核内部的,与 PKRU 寄存器中的权限是分开的。pkey_alloc 返回的 pkey,其在 PKRU 中的初始状态通常是允许访问的。这个 access_rights 参数更多是作为一种内核层面的标记或备用机制。

pkey_free

  • int pkey: 这是之前通过成功的 pkey_alloc 调用返回的保护键编号

5. 返回值

  • pkey_alloc:
    • 成功时: 返回一个非负整数,即新分配的保护键编号(pkey)。
    • 失败时: 返回 -1,并设置 errno(例如 ENOSPC 没有可用的 pkey,EINVAL flags 或 access_rights 无效,EOPNOTSUPP 硬件不支持)。
  • pkey_free:
    • 成功时: 返回 0。
    • 失败时: 返回 -1,并设置 errno(例如 EINVAL pkey 无效,EOPNOTSUPP 硬件不支持)。

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

  • pkey_mprotect: 用于将一个内存区域与特定的 pkey 关联起来,是使用 pkey 进行内存保护的核心函数。
  • mprotect: 传统的内存保护函数,修改内存区域的 RWX 权限。pkey_mprotect 是其增强版,增加了 pkey 功能。
  • mmap: 用于分配和映射内存区域,这些区域后续可以用 pkey_mprotect 来关联 pkey。
  • syscall(SYS_pkey_alloc, ...) / syscall(SYS_pkey_free, ...): 在 glibc 不支持时,直接调用系统调用的方式。

7. 示例代码

重要提示:

  1. 硬件和内核支持: MPK 仅在支持该特性的 CPU(如 Intel x86_64 Skylake 及更新架构)和 Linux 内核(>= 4.9)上可用。
  2. glibc 版本: 需要 glibc 2.27 或更高版本。
  3. 复杂性: 使用 MPK 需要结合 pkey_mprotect 和对 PKRU 寄存器的操作(通常通过内联汇编或专用库函数),下面的示例主要演示 pkey_alloc/free 的基本用法。

示例 1:检查支持并基本使用 pkey_alloc/free

这个例子演示了如何检查系统是否支持 MPK,然后分配和释放保护键。

// pkey_basic_example.c
#define _GNU_SOURCE // For pkey functions
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

// 如果 glibc 版本过低,可能需要手动定义系统调用号
#ifndef SYS_pkey_alloc
#define SYS_pkey_alloc 330
#endif
#ifndef SYS_pkey_free
#define SYS_pkey_free 331
#endif

// 用于手动调用系统调用的包装函数 (如果需要)
// #include <sys/syscall.h>
// static inline int my_pkey_alloc(unsigned int flags, unsigned int access_rights) {
//     return syscall(SYS_pkey_alloc, flags, access_rights);
// }
// static inline int my_pkey_free(int pkey) {
//     return syscall(SYS_pkey_free, pkey);
// }

// 检查系统是否支持内存保护键
int check_pkey_support() {
    int pkey;
    // 尝试分配一个 pkey 来检查支持
    pkey = pkey_alloc(0, 0);
    if (pkey == -1) {
        if (errno == EOPNOTSUPP) {
            return 0; // Not supported
        } else {
            // Other error, but might still support it in general
            // Let's assume it does and let the main code handle specific errors
            return 1;
        }
    }
    // Allocation succeeded, support confirmed. Free it.
    if (pkey_free(pkey) == -1) {
        perror("pkey_free after check");
    }
    return 1;
}

int main() {
    printf("Checking for Memory Protection Key (MPK) support...\n");

    if (!check_pkey_support()) {
        fprintf(stderr, "Memory Protection Keys (MPK) are NOT supported on this system/CPU.\n");
        fprintf(stderr, "This might be because:\n");
        fprintf(stderr, "  1. The CPU does not support MPK (e.g., older than Intel Skylake).\n");
        fprintf(stderr, "  2. The Linux kernel version is older than 4.9.\n");
        fprintf(stderr, "  3. The feature is disabled.\n");
        exit(EXIT_FAILURE);
    }

    printf("MPK support detected.\n");

    // --- 分配保护键 ---
    printf("\n--- Allocating Protection Keys ---\n");

    int pkey1, pkey2;

    // 分配第一个 pkey,初始权限为允许所有访问
    pkey1 = pkey_alloc(0, 0);
    if (pkey1 == -1) {
        perror("pkey_alloc 1 failed");
        exit(EXIT_FAILURE);
    }
    printf("Successfully allocated pkey 1: %d\n", pkey1);

    // 分配第二个 pkey,初始权限为禁止写入
    pkey2 = pkey_alloc(0, PKEY_DISABLE_WRITE);
    if (pkey2 == -1) {
        perror("pkey_alloc 2 failed");
        // Cleanup previously allocated pkey
        pkey_free(pkey1);
        exit(EXIT_FAILURE);
    }
    printf("Successfully allocated pkey 2: %d (initially write-disabled)\n", pkey2);

    // 尝试分配更多 pkey (系统通常限制为 16 个, 0-15)
    printf("\nAttempting to allocate more pkeys...\n");
    int pkeys[20];
    int allocated_count = 0;
    for (int i = 0; i < 20; i++) {
        pkeys[i] = pkey_alloc(0, 0);
        if (pkeys[i] == -1) {
            if (errno == ENOSPC) {
                printf("  Allocation %d failed: No space left (ENOSPC). Max pkeys reached.\n", i);
                break;
            } else {
                printf("  Allocation %d failed with errno %d (%s)\n", i, errno, strerror(errno));
                break;
            }
        } else {
            printf("  Allocated pkey %d: %d\n", i, pkeys[i]);
            allocated_count++;
        }
    }

    // --- 释放保护键 ---
    printf("\n--- Freeing Protection Keys ---\n");

    // 释放前两个
    if (pkey_free(pkey1) == -1) {
        perror("pkey_free pkey1 failed");
    } else {
        printf("Successfully freed pkey 1 (%d)\n", pkey1);
    }

    if (pkey_free(pkey2) == -1) {
        perror("pkey_free pkey2 failed");
    } else {
        printf("Successfully freed pkey 2 (%d)\n", pkey2);
    }

    // 释放之前批量分配的
    for (int i = 0; i < allocated_count; i++) {
        if (pkey_free(pkeys[i]) == -1) {
            printf("Failed to free pkey %d (%d)\n", i, pkeys[i]);
        } else {
            printf("Successfully freed pkey %d (%d)\n", i, pkeys[i]);
        }
    }

    printf("\nAll pkey operations completed.\n");
    return 0;
}

如何编译和测试:

# 需要较新的 glibc (>= 2.27)
gcc -o pkey_basic_example pkey_basic_example.c
./pkey_basic_example

代码解释:

  1. 定义了必要的头文件。
  2. check_pkey_support: 通过尝试调用 pkey_alloc 来粗略检查系统是否支持 MPK。如果返回 EOPNOTSUPP,则说明不支持。
  3. 在 main 函数中,首先调用 check_pkey_support
  4. 分配 pkey:
    • 调用 pkey_alloc(0, 0) 分配第一个 pkey,初始权限为允许所有访问。
    • 调用 pkey_alloc(0, PKEY_DISABLE_WRITE) 分配第二个 pkey,初始权限为禁止写入。
    • 通过一个循环尝试分配更多 pkey,直到系统报告 ENOSPC(没有空间,即达到上限)。
  5. 释放 pkey:
    • 调用 pkey_free 释放之前分配的所有 pkey。
  6. 打印相关信息。

示例 2:结合 mmappkey_mprotect 使用 pkey_alloc/free (概念性)

这个例子展示了一个更完整的、但概念性的用法,结合了分配内存、分配 pkey、关联内存与 pkey 以及通过 PKRU 控制访问。请注意:直接操作 PKRU 寄存器需要内联汇编,这比较复杂且依赖于架构。

// pkey_conceptual_example.c
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

static jmp_buf jmp_env;
static volatile sig_atomic_t sigsegv_caught = 0;

// 信号处理函数
void sigsegv_handler(int sig) {
    sigsegv_caught = 1;
    longjmp(jmp_env, 1);
}

// 概念性的 PKRU 操作函数 (实际需要内联汇编)
// 这里用伪代码表示
void write_pkru(unsigned int pkru_value) {
    // In real code, this would be inline assembly like:
    // asm volatile(".byte 0x0f,0x01,0xef\n\t" : : "a" (pkru_value), "d" (0), "c" (0) : "memory");
    printf("  [Concept] Writing PKRU with value: 0x%08x\n", pkru_value);
    // WARNING: This is NOT real code, just for illustration.
    // Real code needs inline assembly.
}

unsigned int read_pkru() {
    // In real code:
    // unsigned int pkru;
    // asm volatile(".byte 0x0f,0x01,0xee\n\t" : "=a" (pkru) : "c" (0) : "rdx", "memory");
    // return pkru;
    printf("  [Concept] Reading PKRU\n");
    return 0; // Dummy return
}

int main() {
    if (sysconf(_SC_MPKEY) <= 0) { // Check if supported
         fprintf(stderr, "MPK not supported by sysconf.\n");
         exit(EXIT_FAILURE);
    }

    struct sigaction sa;
    sa.sa_handler = sigsegv_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGSEGV, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    size_t len = 4096; // 1 page
    void *addr;

    // 1. 分配内存
    addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    printf("Allocated memory at %p\n", addr);

    // 2. 写入一些数据
    strcpy((char*)addr, "Initial data in protected memory.");
    printf("Written initial data.\n");

    // 3. 分配一个 pkey
    int pkey = pkey_alloc(0, 0);
    if (pkey == -1) {
        perror("pkey_alloc");
        munmap(addr, len);
        exit(EXIT_FAILURE);
    }
    printf("Allocated pkey: %d\n", pkey);

    // 4. 将内存区域与 pkey 关联 (概念性)
    // 实际需要调用 pkey_mprotect(addr, len, PROT_READ|PROT_WRITE, pkey);
    printf("--- Conceptually associating memory with pkey %d ---\n", pkey);
    // printf("Would call: pkey_mprotect(%p, %zu, PROT_READ|PROT_WRITE, %d)\n", addr, len, pkey);

    // 5. 通过修改 PKRU 来禁止访问 pkey (概念性)
    printf("\n--- Disabling access to pkey %d via PKRU ---\n", pkey);
    // 计算新的 PKRU 值以禁用 pkey 的访问
    // 每个 pkey 在 PKRU 中占 2 位: 00(allow), 01(deny access), 10(deny write), 11(deny access)
    // 假设 pkey 是 1, 那么它在 PKRU 的 bit 2-3
    // unsigned int current_pkru = read_pkru();
    unsigned int new_pkru = 0; // Start with allowing all
    // Set bits for pkey to 01 (deny access)
    // new_pkru |= (1 << (pkey * 2));
    // write_pkru(new_pkru);
    printf("  [Concept] Would set PKRU bits for pkey %d to deny access.\n", pkey);

    // 6. 尝试访问受保护的内存 (应该触发 SIGSEGV)
    printf("\n--- Attempting to access protected memory ---\n");
    sigsegv_caught = 0;

    if (setjmp(jmp_env) == 0) {
        // This block will be executed first
        printf("  Trying to read from %p...\n", addr);
        char first_char = *((char*)addr); // This should trigger SIGSEGV
        printf("  ERROR: Read succeeded (first char: %c). This should not happen!\n", first_char);
    } else {
        // This block will be executed if longjmp is called from signal handler
        if (sigsegv_caught) {
            printf("  SUCCESS: SIGSEGV caught as expected. Access denied by pkey.\n");
        } else {
            printf("  Unexpected longjmp.\n");
        }
    }

    // 7. 重新允许访问
    printf("\n--- Re-enabling access to pkey %d via PKRU ---\n", pkey);
    // write_pkru(0); // Allow all access again
    printf("  [Concept] Would reset PKRU to allow all access.\n");

    // 8. 再次尝试访问 (应该成功)
    printf("\n--- Attempting to access memory again (should succeed now) ---\n");
    printf("  Reading from %p: %s\n", addr, (char*)addr);

    // 9. 清理
    if (pkey_free(pkey) == -1) {
        perror("pkey_free");
    }
    if (munmap(addr, len) == -1) {
        perror("munmap");
    }

    printf("\nConceptual pkey example finished.\n");
    return 0;
}

**代码解释 **(概念性):

  1. 设置了 SIGSEGV 信号处理函数和 setjmp/longjmp 机制来捕获预期的段错误。
  2. 使用 mmap 分配了一块匿名内存。
  3. 向内存写入了初始数据。
  4. 调用 pkey_alloc(0, 0) 分配一个 pkey。
  5. 概念性步骤: 描述了将内存与 pkey 关联(实际需要 pkey_mprotect)和通过修改 PKRU 寄存器禁止访问的操作。这部分用 printf 和伪函数 write_pkru/read_pkru 代替,因为真实的实现需要内联汇编。
  6. 尝试读取受保护的内存。在真实场景下,这会触发 SIGSEGV,信号处理函数会设置标志并 longjmp 回来。
  7. 概念性步骤: 描述了重新允许访问(重置 PKRU)。
  8. 再次尝试访问,这次应该成功。
  9. 释放 pkey 和内存。

重要提示与注意事项:

  1. 硬件和内核依赖: MPK 是 x86_64 架构(Intel Skylake 及更新)的特性,需要 Linux 内核 4.9+。
  2. glibc 版本: 需要 glibc 2.27+ 才有原生支持。
  3. 复杂性: 真正使用 MPK 需要结合 pkey_mprotect 和对 PKRU 寄存器的精确控制(通常通过内联汇编),这比示例中展示的要复杂得多。
  4. pkey_mprotect 是关键pkey_alloc/free 只是管理 pkey 编号,真正将内存和权限联系起来的是 pkey_mprotect
  5. PKRU 操作: 直接读写 PKRU 寄存器是使用 MPK 功能的核心,但需要内联汇编知识。
  6. 错误处理: 始终检查 pkey_alloc 是否返回 EOPNOTSUPP(不支持)或 ENOSPC(pkey 耗尽)。
  7. 性能优势: MPK 的主要优势在于权限切换的极低延迟,因为它避免了修改页表的开销。
  8. 应用场景: 适用于需要快速、动态地改变大量内存区域访问权限的场景,如沙箱、内存安全库、调试器等。

总结:

pkey_alloc 和 pkey_free 是 Linux 内存保护键(MPK)机制的一部分,用于分配和回收独立的内存访问控制键。它们本身只是 pkey 生命周期管理的第一步。要真正利用 MPK 提供的快速、细粒度内存保护能力,还需要结合 pkey_mprotect 来关联内存区域,以及通过直接操作 PKRU 寄存器来动态启用或禁用访问权限。虽然使用起来比较底层和复杂,但对于需要极致内存安全和性能控制的应用来说,MPK 是一个强大的工具。

发表在 linux文章 | 留下评论

poll系统调用及示例

poll系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 poll 函数,它是一种高效的 I/O 多路复用机制,允许一个进程同时监视多个文件描述符,等待其中任何一个或多个文件描述符变我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 poll 函数,它是一种高效的 I/O 多路复用机制,允许一个进程同时监视多个文件描述符,等待其中任何一个或多个文件描述符变为“就绪”状态(例如可读、可写或发生异常)。

相关文章:ppoll系统调用及示例 epoll_create1系统调用及示例 epoll_ctl系统调用及示例


1. 函数介绍

poll 是一个 Linux 系统调用,用于实现 I/O 多路复用 (I/O multiplexing)。它的核心思想是让进程能够同时检查多个文件描述符(如套接字、管道、终端等)的状态,看它们是否准备好进行 I/O 操作(例如读取、写入),而无需对每个文件描述符都进行阻塞式等待。

在没有 poll(或 selectepoll)的情况下,如果一个程序需要同时处理多个网络连接或文件,它可能需要创建多个线程或进程,或者在一个文件描述符上阻塞等待,这会非常低效或复杂。poll 允许一个线程/进程在一个调用中“监听”所有感兴趣的文件描述符,当其中任何一个准备好时,poll 返回,程序就可以处理那个就绪的文件描述符。

你可以把它想象成一个“服务员”,同时照看多张餐桌(文件描述符)。服务员不需要一直站在某一张餐桌旁等客人点菜(数据),而是可以走一圈看看哪张餐桌的客人举手了(数据就绪),然后去为那张餐桌服务。


2. 函数原型

#include <poll.h> // 必需

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

3. 功能

  • 监视文件描述符集合poll 会检查 fds 数组中列出的 nfds 个文件描述符的状态。
  • 等待就绪: 调用 poll 的进程会阻塞(挂起),直到以下情况之一发生:
    1. fds 数组中的至少一个文件描述符变为“就绪”状态(根据 events 字段指定的条件)。
    2. 调用被信号中断(返回 -1,并设置 errno 为 EINTR)。
    3. 达到指定的超时时间 timeout(如果 timeout >= 0)。
  • 返回就绪数量: 当 poll 返回时,它会报告有多少个文件描述符已就绪。
  • 更新状态poll 会修改 fds 数组中每个元素的 revents 字段,以指示该文件描述符上实际发生的事件。

4. 参数

  • struct pollfd *fds: 这是一个指向 struct pollfd 类型数组的指针。这个数组包含了所有需要监视的文件描述符及其感兴趣的事件。
    struct pollfd 的定义如下:struct pollfd { int fd; // 要监视的文件描述符 short events; // 程序关心的事件 (输入) short revents; // 实际发生的事件 (输出) };
    • fd: 要监视的文件描述符。如果 fd 为负数,则忽略该数组元素。
    • events: 这是一个位掩码,指定了应用程序对这个 fd 感兴趣的事件。常用的值包括:
      • POLLIN: 数据可读(对于普通文件,通常总是可读的)。
      • POLLOUT: 数据可写(对于普通文件,通常总是可写的)。
      • POLLPRI: 高优先级数据可读(例如 TCP 带外数据)。
      • POLLERR: 发生错误(作为 revents 返回,不能在 events 中设置)。
      • POLLHUP: 挂起(例如对端套接字关闭)(作为 revents 返回)。
      • POLLNVAL: 文件描述符无效(作为 revents 返回)。
    • revents: 这个字段由 poll 调用填充,返回该 fd 上实际发生的事件。程序需要检查这个字段来确定 fd 是否就绪以及发生了什么事件。
  • nfds_t nfds: 这是 fds 数组中的元素个数,即要监视的文件描述符总数。
  • int timeout: 指定 poll 调用阻塞等待的超时时间(以毫秒为单位)。
    • timeout == -1poll 会无限期阻塞,直到至少一个文件描述符就绪或被信号中断。
    • timeout == 0poll 执行非阻塞检查,立即返回,报告当前有多少文件描述符已就绪。
    • timeout > 0poll 最多阻塞 timeout 毫秒。如果在超时前没有文件描述符就绪,则返回 0。

5. 返回值

  • 成功时:
    • 返回 就绪的文件描述符的数量(即 revents 非零的 fds 元素个数)。这个数字可以是 0(表示超时)。
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EFAULT fds 指针无效,EINTR 调用被信号中断,EINVAL nfds 负数等)。
  • 超时:
    • 如果在 timeout 指定的时间内没有任何文件描述符就绪,返回 0。

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

  • select: 一个更老的 I/O 多路复用函数,功能与 poll 类似,但在处理大量文件描述符时效率较低,且文件描述符集合有大小限制 (FD_SETSIZE)。
  • epoll_wait / epoll_ctl / epoll_create: Linux 特有的、更高效的 I/O 多路复用机制,特别适合处理大量的并发连接。它使用一个内核事件表来管理监视的文件描述符,避免了 poll/select 每次调用都需要传递整个文件描述符集合的开销。
  • readwrite: 在 poll 返回某个文件描述符就绪后,通常会调用 read 或 write 来执行实际的 I/O 操作。

7. 示例代码

示例 1:监视标准输入和一个管道

这个例子演示如何使用 poll 同时监视标准输入(键盘)和一个管道的读端,看哪个先有数据可读。

#include <poll.h>     // poll, struct pollfd
#include <unistd.h>   // pipe, read, write, close, STDIN_FILENO
#include <stdio.h>    // perror, printf, fprintf
#include <stdlib.h>   // exit
#include <string.h>   // strlen

int main() {
    int pipefd[2];
    struct pollfd fds[2];
    int num_fds = 2;
    int timeout_ms = 5000; // 5 秒超时
    int ret;
    char buffer[100];
    ssize_t bytes_read;

    // 1. 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 2. 设置要监视的文件描述符数组
    // 监视标准输入 (stdin)
    fds[0].fd = STDIN_FILENO; // 通常是 0
    fds[0].events = POLLIN;   // 关心可读事件
    fds[0].revents = 0;       // 内核会填充

    // 监视管道的读端
    fds[1].fd = pipefd[0];
    fds[1].events = POLLIN;   // 关心可读事件
    fds[1].revents = 0;       // 内核会填充

    printf("Waiting up to %d ms for input from stdin or data in pipe...\n", timeout_ms);
    printf("Type something in the terminal, or run 'echo hello > /proc/%d/fd/%d' in another terminal.\n",
           getpid(), pipefd[1]); // 提示用户如何向管道写入

    // 3. 调用 poll 进行等待
    ret = poll(fds, num_fds, timeout_ms);

    // 4. 检查 poll 的返回值
    if (ret == -1) {
        perror("poll");
        close(pipefd[0]);
        close(pipefd[1]);
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("Timeout occurred! No data within %d ms.\n", timeout_ms);
    } else {
        printf("%d file descriptor(s) became ready.\n", ret);

        // 5. 检查哪个文件描述符就绪了
        for (int i = 0; i < num_fds; ++i) {
            if (fds[i].revents != 0) {
                printf("fd %d (originally fd %d) is ready. revents = 0x%04x\n",
                       fds[i].fd, fds[i].fd, fds[i].revents);

                if (fds[i].revents & POLLIN) {
                    printf("  -> POLLIN event on fd %d\n", fds[i].fd);
                    if (fds[i].fd == STDIN_FILENO) {
                        printf("  -> Reading from standard input:\n");
                        bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
                        if (bytes_read > 0) {
                            buffer[bytes_read] = '\0'; // 确保字符串结束
                            printf("  -> Read from stdin: %s", buffer); // buffer 可能已包含 \n
                        }
                    } else if (fds[i].fd == pipefd[0]) {
                        printf("  -> Reading from pipe:\n");
                        bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
                        if (bytes_read > 0) {
                            buffer[bytes_read] = '\0';
                            printf("  -> Read from pipe: %s", buffer);
                        }
                    }
                }
                // 可以检查其他 revents,如 POLLERR, POLLHUP 等
                if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                     printf("  -> Error or hangup or invalid fd on fd %d\n", fds[i].fd);
                }
            }
        }
    }

    // 6. 清理资源
    close(pipefd[0]);
    close(pipefd[1]);

    return 0;
}

代码解释:

  1. 使用 pipe() 创建一个管道,得到读端 pipefd[0] 和写端 pipefd[1]
  2. 定义一个 struct pollfd 数组 fds,包含两个元素。
  3. 第一个元素监视 STDIN_FILENO(标准输入),关心 POLLIN 事件。
  4. 第二个元素监视管道的读端 pipefd[0],也关心 POLLIN 事件。
  5. 调用 poll(fds, 2, 5000),等待最多 5 秒钟。
  6. 检查 poll 的返回值:
    • -1:错误。
    • 0:超时。
    • 0:就绪的文件描述符数量。
  7. 如果有文件描述符就绪(ret > 0),遍历 fds 数组,检查每个元素的 revents 字段。
  8. 如果 revents 包含 POLLIN,则调用 read 从对应的文件描述符读取数据。
  9. 最后关闭管道的两端。

示例 2:简单的 TCP 服务器(非阻塞 accept 和客户端 socket)

这个例子演示如何在 TCP 服务器中使用 poll 来同时监听监听套接字(用于接受新连接)和已建立连接的客户端套接字(用于接收数据)。

#include <poll.h>      // poll, struct pollfd
#include <sys/socket.h> // socket, bind, listen, accept, recv, send
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h>  // inet_addr, inet_ntoa (简化版,非线程安全)
#include <unistd.h>     // close, read, write
#include <stdio.h>      // perror, printf, fprintf
#include <stdlib.h>     // exit
#include <string.h>     // memset, strlen

#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    struct pollfd fds[MAX_CLIENTS + 1]; // +1 for the listening socket
    int nfds = 1; // Initially, only the listening socket
    int timeout_ms = -1; // Block indefinitely
    int activity;
    char buffer[BUFFER_SIZE] = {0};
    char *hello = "Hello from server";

    // 1. 创建服务器套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 配置服务器地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口
    address.sin_port = htons(PORT);

    // 3. 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 4. 监听连接
    if (listen(server_fd, 3) < 0) { // backlog=3
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 5. 设置 poll 监视的初始文件描述符:监听套接字
    fds[0].fd = server_fd;
    fds[0].events = POLLIN; // 关心可读事件 (有新连接)
    fds[0].revents = 0;

    // 初始化其他客户端槽位
    for(int i = 1; i < MAX_CLIENTS + 1; i++) {
        fds[i].fd = -1; // -1 表示槽位空闲
        fds[i].events = POLLIN;
        fds[i].revents = 0;
    }

    // 6. 主循环
    while(1) {
        // 7. 调用 poll 等待事件
        activity = poll(fds, nfds, timeout_ms);

        if (activity < 0) {
            perror("poll error");
            break; // 或 exit(EXIT_FAILURE);
        }

        if (activity == 0) {
             // 不应该发生,因为 timeout_ms = -1
             printf("poll timeout (unexpected)\n");
             continue;
        }

        // 8. 检查监听套接字 (fds[0]) 是否有活动
        if (fds[0].revents & POLLIN) {
            // 有新的客户端连接请求
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                perror("accept");
                continue; // 继续处理其他事件
            }

            printf("New connection, socket fd is %d, ip is : %s, port : %d\n",
                   new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            // 将新连接的套接字添加到 poll 监视集合中
            int i;
            for (i = 1; i < MAX_CLIENTS + 1; i++) {
                if (fds[i].fd == -1) {
                    fds[i].fd = new_socket;
                    fds[i].events = POLLIN;
                    fds[i].revents = 0;
                    if (i >= nfds) nfds = i + 1; // 更新监视的 fd 数量
                    // 发送欢迎信息
                    send(new_socket, hello, strlen(hello), 0);
                    printf("Welcome message sent\n");
                    break;
                }
            }
            if (i == MAX_CLIENTS + 1) {
                printf("Too many clients, connection rejected\n");
                close(new_socket);
            }
        }

        // 9. 检查已连接的客户端套接字是否有活动
        for (int i = 1; i < nfds; i++) {
            if (fds[i].fd != -1 && (fds[i].revents & POLLIN)) {
                // 有数据从客户端发来
                int sd = fds[i].fd;
                ssize_t valread = read(sd, buffer, BUFFER_SIZE - 1);
                if (valread == 0) {
                    // 客户端断开连接
                    getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
                    printf("Host disconnected, ip %s, port %d\n",
                           inet_ntoa(address.sin_addr), ntohs(address.sin_port));
                    close(sd);
                    fds[i].fd = -1; // 标记槽位为空闲
                } else {
                    // 处理收到的数据
                    buffer[valread] = '\0';
                    printf("Received message from socket %d: %s", sd, buffer);
                    // Echo 回去
                    send(sd, buffer, strlen(buffer), 0);
                }
            }
            // 可以检查 POLLERR, POLLHUP 等错误事件
            if (fds[i].fd != -1 && (fds[i].revents & (POLLERR | POLLHUP))) {
                 printf("Error or hangup on client socket %d\n", fds[i].fd);
                 close(fds[i].fd);
                 fds[i].fd = -1;
            }
        }
    }

    // 10. 清理 (在真实应用中,需要更优雅的退出机制)
    for(int i = 0; i < nfds; i++) {
        if(fds[i].fd != -1) {
            close(fds[i].fd);
        }
    }
    close(server_fd);
    printf("Server closed.\n");
    return 0;
}

代码解释:

  1. 创建、绑定、监听 TCP 套接字。
  2. 初始化 struct pollfd 数组 fds。第一个元素 (fds[0]) 用于监视监听套接字 server_fd,关心 POLLIN 事件(表示有新的连接请求)。
  3. 数组的其余元素(fds[1] 到 fds[MAX_CLIENTS])用于监视已建立连接的客户端套接字。初始时,它们的 fd 被设置为 -1,表示空闲槽位。
  4. 进入主循环,调用 poll(fds, nfds, -1)nfds 跟踪当前需要监视的 fds 数组元素个数(通常是已使用的槽位数)。
  5. poll 返回后,检查返回值 activity
  6. 如果 fds[0].revents & POLLIN 为真,说明监听套接字就绪,调用 accept 接受新连接。
  7. 将新获得的客户端套接字 new_socket 放入 fds 数组的一个空闲槽位中(fd 为 -1 的位置),并更新 nfds
  8. 遍历 fds 数组中用于客户端的槽位(从索引 1 开始),检查 revents
  9. 如果某个客户端套接字的 revents & POLLIN 为真,说明该客户端有数据可读,调用 read 读取数据。
  10. 如果 read 返回 0,表示客户端关闭了连接,关闭该套接字,并将 fds 中对应的 fd 设置回 -1。
  11. 如果 read 返回正数,表示读到了数据,这里简单地将其 echo 回客户端。
  12. 同样检查 POLLERR 和 POLLHUP 等错误事件。

这个例子展示了 poll 如何在一个单线程服务器中高效地管理多个并发连接。与为每个连接创建一个线程或进程相比,poll(以及更高效的 epoll)是构建高性能网络服务器的基础技术之一。

理解 poll 的关键是掌握 struct pollfd 数组的使用、events 和 revents 的含义,以及如何根据返回的就绪文件描述符数量和状态来处理相应的 I/O 操作。

poll系统调用详解, poll函数使用示例, linux poll系统调用, poll函数原理与应用, linux系统编程poll函数, poll函数如何工作, poll系统调用教程, poll函数在Linux中的应用, poll系统调用实例解析, poll函数编程指南

发表在 linux文章 | 留下评论

ppoll系统调用及示例

ppoll 函数详解

1. 函数介绍

ppoll 是 Linux 系统中用于同时监视多个文件描述符并等待特定信号的系统调用。可以把 ppoll 想象成”多功能的等待管家”——它不仅能像 poll 一样监视文件描述符的状态变化,还能在等待期间处理特定的信号,就像一个既能看门又能接电话的管家。

与传统的 poll 函数相比,ppoll 提供了更好的信号处理机制,避免了信号处理函数中调用不可重入函数的问题。

相关文章:ppoll系统调用及示例-CSDN博客 epoll_create系统调用及示例 pselect系统调用及示例 epoll_create1系统调用及示例 poll系统调用及示例-CSDN博客

2. 函数原型

#define _GNU_SOURCE
#include <poll.h>
#include <signal.h>
#include <time.h>

int ppoll(struct pollfd *fds, nfds_t nfds,
          const struct timespec *timeout_ts,
          const sigset_t *sigmask);

3. 功能

ppoll 函数用于同时监视多个文件描述符的状态变化,并可以选择性地阻塞指定的信号。它结合了 poll 的文件描述符监视功能和信号屏蔽功能。

4. 参数

  • fds: 指向 pollfd 结构体数组的指针,描述要监视的文件描述符
  • nfdsfds 数组中的元素个数
  • timeout_ts: 指向超时时间的指针(NULL 表示无限等待)
  • sigmask: 指向信号屏蔽集的指针(NULL 表示不改变信号屏蔽)

5. pollfd 结构体

struct pollfd {
    int   fd;         /* 文件描述符 */
    short events;     /* 请求的事件 */
    short revents;    /* 实际发生的事件 */
};

事件类型(events 和 revents 字段)

事件说明
POLLIN0x001有数据可读
POLLPRI0x002有紧急数据可读
POLLOUT0x004文件描述符可写
POLLERR0x008发生错误
POLLHUP0x010连接挂起
POLLNVAL0x020文件描述符无效
POLLRDNORM0x040有普通数据可读
POLLRDBAND0x080有优先数据可读
POLLWRNORM0x100可以正常写入
POLLWRBAND0x200可以优先写入

6. timespec 结构体

struct timespec {
    time_t tv_sec;    /* 秒数 */
    long   tv_nsec;   /* 纳秒数 */
};

7. 返回值

  • 成功: 返回准备就绪的文件描述符数量(0 表示超时)
  • 失败: 返回 -1,并设置相应的 errno 错误码

8. 常见错误码

  • EBADF: 一个或多个文件描述符无效
  • EFAULT: fds 指针无效
  • EINTR: 被未屏蔽的信号中断
  • EINVAL: 参数无效
  • ENOMEM: 内存不足

9. 相似函数或关联函数

  • poll: 基本的文件描述符监视函数
  • select: 传统的文件描述符监视函数
  • pselect: 带信号屏蔽的 select
  • epoll_wait: epoll 接口的等待函数
  • read/write: 文件读写操作
  • signal/sigaction: 信号处理函数

10. 示例代码

示例1:基础用法 – 监视标准输入和定时器

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

// 信号处理标志
volatile sig_atomic_t signal_received = 0;

// 信号处理函数
void signal_handler(int sig) {
    signal_received = sig;
    printf("\n收到信号 %d\n", sig);
}

// 设置信号处理
void setup_signals() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    sigaction(SIGINT, &sa, NULL);   // Ctrl+C
    sigaction(SIGTERM, &sa, NULL);  // 终止信号
    sigaction(SIGALRM, &sa, NULL);  // 定时器信号
}

// 显示 pollfd 状态
void show_pollfd_status(struct pollfd *pfd, const char *description) {
    printf("%s: fd=%d", description, pfd->fd);
    printf(" events=");
    if (pfd->events & POLLIN) printf("POLLIN ");
    if (pfd->events & POLLOUT) printf("POLLOUT ");
    if (pfd->events & POLLPRI) printf("POLLPRI ");
    printf("\n");
    
    printf("  revents=");
    if (pfd->revents & POLLIN) printf("POLLIN ");
    if (pfd->revents & POLLOUT) printf("POLLOUT ");
    if (pfd->revents & POLLPRI) printf("POLLPRI ");
    if (pfd->revents & POLLERR) printf("POLLERR ");
    if (pfd->revents & POLLHUP) printf("POLLHUP ");
    if (pfd->revents & POLLNVAL) printf("POLLNVAL ");
    printf("\n");
}

int main() {
    struct pollfd fds[2];
    struct timespec timeout;
    sigset_t sigmask;
    int ready;
    
    printf("=== ppoll 基础示例 ===\n\n");
    
    // 设置信号处理
    setup_signals();
    
    // 初始化 pollfd 结构
    fds[0].fd = STDIN_FILENO;      // 标准输入
    fds[0].events = POLLIN;        // 监视可读事件
    fds[0].revents = 0;
    
    fds[1].fd = -1;                // 模拟定时器文件描述符
    fds[1].events = POLLIN;        // 监视可读事件
    fds[1].revents = 0;
    
    // 设置超时时间 (5 秒)
    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;
    
    // 设置信号屏蔽集 (不屏蔽任何信号)
    sigemptyset(&sigmask);
    
    printf("监视设置:\n");
    show_pollfd_status(&fds[0], "标准输入");
    printf("超时时间: %ld 秒\n", (long)timeout.tv_sec);
    printf("按 Enter 键或等待超时...\n");
    printf("按 Ctrl+C 发送信号\n\n");
    
    // 使用 ppoll 监视文件描述符
    ready = ppoll(fds, 1, &timeout, &sigmask);
    
    if (ready == -1) {
        if (errno == EINTR) {
            printf("ppoll 被信号中断\n");
            if (signal_received) {
                printf("收到信号: %d\n", signal_received);
            }
        } else {
            perror("ppoll 失败");
        }
    } else if (ready == 0) {
        printf("超时: 没有文件描述符准备就绪\n");
    } else {
        printf("准备就绪的文件描述符数量: %d\n", ready);
        
        if (fds[0].revents & POLLIN) {
            printf("标准输入有数据可读\n");
            
            // 读取输入
            char buffer[256];
            ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("读取到: %s", buffer);
            }
        }
        
        if (fds[0].revents & POLLERR) {
            printf("标准输入发生错误\n");
        }
        
        if (fds[0].revents & POLLHUP) {
            printf("标准输入连接挂起\n");
        }
    }
    
    printf("\n=== ppoll 特点 ===\n");
    printf("1. 原子操作: 等待和信号屏蔽是原子的\n");
    printf("2. 精确超时: 支持纳秒级超时\n");
    printf("3. 信号安全: 避免信号处理中的竞态条件\n");
    printf("4. 灵活屏蔽: 可以精确控制信号屏蔽\n");
    printf("5. 高效监视: 同时监视多个文件描述符\n");
    
    return 0;
}

示例2:多文件描述符监视和信号处理

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

// 全局变量
volatile sig_atomic_t terminate_flag = 0;
volatile sig_atomic_t alarm_count = 0;

// 信号处理函数
void termination_handler(int sig) {
    printf("\n收到终止信号 %d\n", sig);
    terminate_flag = 1;
}

void alarm_handler(int sig) {
    alarm_count++;
    printf("\n收到定时器信号 %d (计数: %d)\n", sig, alarm_count);
}

// 设置信号处理
void setup_signal_handlers() {
    struct sigaction sa;
    
    // 设置终止信号处理
    sa.sa_handler = termination_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    
    // 设置定时器信号处理
    sa.sa_handler = alarm_handler;
    sigaction(SIGALRM, &sa, NULL);
}

// 创建临时文件用于测试
int create_test_file(const char *filename) {
    int fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *content = "这是测试文件的内容\n用于演示 ppoll 功能\n";
    write(fd, content, strlen(content));
    lseek(fd, 0, SEEK_SET);
    
    printf("创建测试文件: %s\n", filename);
    return fd;
}

// 显示文件描述符状态
void show_fds_status(struct pollfd *fds, int nfds) {
    printf("文件描述符状态:\n");
    for (int i = 0; i < nfds; i++) {
        printf("  [%d] fd=%d events=0x%04x revents=0x%04x", 
               i, fds[i].fd, fds[i].events, fds[i].revents);
        
        if (fds[i].revents != 0) {
            printf(" (");
            if (fds[i].revents & POLLIN) printf("IN ");
            if (fds[i].revents & POLLOUT) printf("OUT ");
            if (fds[i].revents & POLLPRI) printf("PRI ");
            if (fds[i].revents & POLLERR) printf("ERR ");
            if (fds[i].revents & POLLHUP) printf("HUP ");
            if (fds[i].revents & POLLNVAL) printf("NVAL ");
            printf(")");
        }
        printf("\n");
    }
}

int main() {
    struct pollfd fds[3];
    struct timespec timeout;
    sigset_t sigmask;
    int test_fd1, test_fd2;
    int ready;
    int running = 1;
    
    printf("=== ppoll 多文件描述符监视示例 ===\n\n");
    
    // 设置信号处理
    setup_signal_handlers();
    
    // 创建测试文件
    test_fd1 = create_test_file("test1.txt");
    test_fd2 = create_test_file("test2.txt");
    
    if (test_fd1 == -1 || test_fd2 == -1) {
        if (test_fd1 != -1) close(test_fd1);
        if (test_fd2 != -1) close(test_fd2);
        unlink("test1.txt");
        unlink("test2.txt");
        return 1;
    }
    
    // 初始化 pollfd 结构
    fds[0].fd = STDIN_FILENO;  // 标准输入
    fds[0].events = POLLIN;
    fds[0].revents = 0;
    
    fds[1].fd = test_fd1;     // 测试文件1
    fds[1].events = POLLIN;
    fds[1].revents = 0;
    
    fds[2].fd = test_fd2;     // 测试文件2
    fds[2].events = POLLIN;
    fds[2].revents = 0;
    
    // 设置超时时间 (3 秒)
    timeout.tv_sec = 3;
    timeout.tv_nsec = 0;
    
    // 设置信号屏蔽集 (不屏蔽任何信号)
    sigemptyset(&sigmask);
    
    printf("监视设置完成:\n");
    show_fds_status(fds, 3);
    printf("超时时间: %ld 秒\n", (long)timeout.tv_sec);
    printf("按 Enter 键测试标准输入\n");
    printf("按 Ctrl+C 终止程序\n\n");
    
    // 启动定时器
    alarm(2);  // 2 秒后发送 SIGALRM
    
    // 主循环
    while (running && !terminate_flag) {
        printf("等待事件... (按 Ctrl+C 退出)\n");
        
        ready = ppoll(fds, 3, &timeout, &sigmask);
        
        if (ready == -1) {
            if (errno == EINTR) {
                printf("ppoll 被信号中断\n");
                if (alarm_count > 0) {
                    printf("定时器触发次数: %d\n", alarm_count);
                    alarm(2);  // 重新启动定时器
                }
                continue;
            } else {
                perror("ppoll 失败");
                break;
            }
        } else if (ready == 0) {
            printf("超时: 没有文件描述符准备就绪\n");
            alarm(2);  // 重新启动定时器
        } else {
            printf("准备就绪的文件描述符数量: %d\n", ready);
            show_fds_status(fds, 3);
            
            // 处理各个文件描述符
            for (int i = 0; i < 3; i++) {
                if (fds[i].revents & POLLIN) {
                    switch (i) {
                        case 0:  // 标准输入
                            printf("标准输入有数据:\n");
                            {
                                char buffer[256];
                                ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
                                if (bytes_read > 0) {
                                    buffer[bytes_read] = '\0';
                                    printf("  读取到: %s", buffer);
                                }
                            }
                            break;
                            
                        case 1:  // 测试文件1
                            printf("测试文件1 有数据可读\n");
                            lseek(test_fd1, 0, SEEK_SET);
                            {
                                char buffer[128];
                                ssize_t bytes_read = read(test_fd1, buffer, sizeof(buffer) - 1);
                                if (bytes_read > 0) {
                                    buffer[bytes_read] = '\0';
                                    printf("  文件1内容: %s", buffer);
                                }
                            }
                            break;
                            
                        case 2:  // 测试文件2
                            printf("测试文件2 有数据可读\n");
                            lseek(test_fd2, 0, SEEK_SET);
                            {
                                char buffer[128];
                                ssize_t bytes_read = read(test_fd2, buffer, sizeof(buffer) - 1);
                                if (bytes_read > 0) {
                                    buffer[bytes_read] = '\0';
                                    printf("  文件2内容: %s", buffer);
                                }
                            }
                            break;
                    }
                }
                
                if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                    printf("文件描述符 %d 发生错误或异常\n", fds[i].fd);
                    if (fds[i].revents & POLLERR) printf("  错误\n");
                    if (fds[i].revents & POLLHUP) printf("  挂起\n");
                    if (fds[i].revents & POLLNVAL) printf("  无效\n");
                }
            }
            
            printf("\n");
        }
        
        // 重置 revents
        for (int i = 0; i < 3; i++) {
            fds[i].revents = 0;
        }
    }
    
    // 清理资源
    printf("\n清理资源...\n");
    close(test_fd1);
    close(test_fd2);
    unlink("test1.txt");
    unlink("test2.txt");
    
    printf("程序正常退出\n");
    
    printf("\n=== ppoll 高级特性 ===\n");
    printf("1. 原子操作: 等待和信号处理是原子的\n");
    printf("2. 精确控制: 可以精确指定信号屏蔽\n");
    printf("3. 灵活超时: 支持纳秒级超时控制\n");
    printf("4. 多路复用: 同时监视多个文件描述符\n");
    printf("5. 事件驱动: 基于事件的通知机制\n");
    printf("\n");
    printf("优势对比:\n");
    printf("  select: 有文件描述符数量限制\n");
    printf("  poll:   无文件描述符数量限制\n");
    printf("  ppoll:  增强的信号处理能力\n");
    printf("  epoll:  更高的性能 (Linux 特有)\n");
    
    return 0;
}

示例3:完整的事件驱动服务器模拟

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

// 服务器配置结构体
struct server_config {
    int port;
    int max_clients;
    int timeout_seconds;
    int verbose;
    int use_signals;
    char *log_file;
};

// 客户端信息结构体
struct client_info {
    int fd;
    int connected;
    time_t connect_time;
    char buffer[1024];
    size_t buffer_pos;
};

// 服务器状态结构体
struct server_state {
    int running;
    int client_count;
    struct client_info *clients;
    int max_clients;
    FILE *log_fp;
};

// 全局变量
volatile sig_atomic_t server_terminate = 0;
volatile sig_atomic_t server_reload = 0;

// 信号处理函数
void signal_handler(int sig) {
    switch (sig) {
        case SIGINT:
        case SIGTERM:
            printf("\n收到终止信号,准备关闭服务器...\n");
            server_terminate = 1;
            break;
        case SIGHUP:
            printf("\n收到重载信号,重新加载配置...\n");
            server_reload = 1;
            break;
        case SIGUSR1:
            printf("\n收到用户信号 1\n");
            break;
        case SIGUSR2:
            printf("\n收到用户信号 2\n");
            break;
    }
}

// 设置信号处理
void setup_server_signals() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGHUP, &sa, NULL);
    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGUSR2, &sa, NULL);
}

// 初始化服务器状态
int init_server_state(struct server_state *state, int max_clients) {
    state->running = 1;
    state->client_count = 0;
    state->max_clients = max_clients;
    
    state->clients = calloc(max_clients, sizeof(struct client_info));
    if (!state->clients) {
        perror("分配客户端数组失败");
        return -1;
    }
    
    state->log_fp = stderr;  // 默认输出到标准错误
    return 0;
}

// 添加客户端
int add_client(struct server_state *state, int client_fd) {
    if (state->client_count >= state->max_clients) {
        fprintf(stderr, "客户端数量已达上限: %d\n", state->max_clients);
        return -1;
    }
    
    for (int i = 0; i < state->max_clients; i++) {
        if (!state->clients[i].connected) {
            state->clients[i].fd = client_fd;
            state->clients[i].connected = 1;
            state->clients[i].connect_time = time(NULL);
            state->clients[i].buffer_pos = 0;
            state->client_count++;
            
            printf("添加客户端 %d (fd: %d),当前客户端数: %d\n", 
                   i, client_fd, state->client_count);
            return i;
        }
    }
    
    fprintf(stderr, "找不到空闲的客户端槽位\n");
    return -1;
}

// 移除客户端
void remove_client(struct server_state *state, int client_index) {
    if (client_index >= 0 && client_index < state->max_clients && 
        state->clients[client_index].connected) {
        
        close(state->clients[client_index].fd);
        memset(&state->clients[client_index], 0, sizeof(struct client_info));
        state->client_count--;
        
        printf("移除客户端 %d,剩余客户端数: %d\n", 
               client_index, state->client_count);
    }
}

// 处理客户端数据
void handle_client_data(struct server_state *state, int client_index) {
    struct client_info *client = &state->clients[client_index];
    
    // 模拟读取客户端数据
    char buffer[256];
    ssize_t bytes_read = read(client->fd, buffer, sizeof(buffer) - 1);
    
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("客户端 %d 发送数据: %s", client_index, buffer);
        
        // 回显数据
        write(client->fd, "Echo: ", 6);
        write(client->fd, buffer, bytes_read);
        
    } else if (bytes_read == 0) {
        printf("客户端 %d 断开连接\n", client_index);
        remove_client(state, client_index);
    } else {
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            perror("读取客户端数据失败");
            remove_client(state, client_index);
        }
    }
}

// 显示服务器状态
void show_server_status(const struct server_state *state) {
    printf("=== 服务器状态 ===\n");
    printf("运行状态: %s\n", state->running ? "运行中" : "已停止");
    printf("客户端数量: %d/%d\n", state->client_count, state->max_clients);
    printf("当前时间: %s", ctime(&(time_t){time(NULL)}));
    
    if (state->client_count > 0) {
        printf("连接的客户端:\n");
        for (int i = 0; i < state->max_clients; i++) {
            if (state->clients[i].connected) {
                printf("  [%d] fd=%d 连接时间: %s", 
                       i, state->clients[i].fd, 
                       ctime(&state->clients[i].connect_time));
            }
        }
    }
    printf("\n");
}

// 模拟服务器主循环
int run_server_simulation(struct server_state *state, const struct server_config *config) {
    struct pollfd *fds = NULL;
    struct timespec timeout;
    sigset_t sigmask;
    int ready;
    
    printf("启动服务器模拟...\n");
    printf("最大客户端数: %d\n", config->max_clients);
    printf("超时时间: %d 秒\n", config->timeout_seconds);
    printf("使用信号处理: %s\n", config->use_signals ? "是" : "否");
    printf("\n");
    
    // 分配 pollfd 数组 (1个用于标准输入 + 最大客户端数)
    fds = calloc(1 + config->max_clients, sizeof(struct pollfd));
    if (!fds) {
        perror("分配 pollfd 数组失败");
        return -1;
    }
    
    // 初始化标准输入监视
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    fds[0].revents = 0;
    
    // 设置超时时间
    timeout.tv_sec = config->timeout_seconds;
    timeout.tv_nsec = 0;
    
    // 设置信号屏蔽集
    sigemptyset(&sigmask);
    if (!config->use_signals) {
        // 如果不使用信号,可以屏蔽一些信号
        sigaddset(&sigmask, SIGUSR1);
        sigaddset(&sigmask, SIGUSR2);
    }
    
    // 显示初始状态
    show_server_status(state);
    
    // 主循环
    while (state->running && !server_terminate) {
        // 更新 pollfd 数组
        int nfds = 1;  // 至少包含标准输入
        
        // 添加连接的客户端
        for (int i = 0; i < state->max_clients; i++) {
            if (state->clients[i].connected) {
                fds[nfds].fd = state->clients[i].fd;
                fds[nfds].events = POLLIN;
                fds[nfds].revents = 0;
                nfds++;
            }
        }
        
        if (config->verbose) {
            printf("监视 %d 个文件描述符...\n", nfds);
        }
        
        // 使用 ppoll 等待事件
        ready = ppoll(fds, nfds, &timeout, &sigmask);
        
        if (ready == -1) {
            if (errno == EINTR) {
                if (config->verbose) {
                    printf("ppoll 被信号中断\n");
                }
                
                if (server_reload) {
                    printf("重新加载配置...\n");
                    server_reload = 0;
                }
                
                continue;
            } else {
                perror("ppoll 失败");
                break;
            }
        } else if (ready == 0) {
            if (config->verbose) {
                printf("超时: 没有事件发生\n");
            }
        } else {
            if (config->verbose) {
                printf("准备就绪的文件描述符数量: %d\n", ready);
            }
            
            // 处理事件
            for (int i = 0; i < nfds; i++) {
                if (fds[i].revents & POLLIN) {
                    if (i == 0) {
                        // 标准输入事件
                        char input_buffer[256];
                        ssize_t bytes_read = read(STDIN_FILENO, input_buffer, sizeof(input_buffer) - 1);
                        if (bytes_read > 0) {
                            input_buffer[bytes_read] = '\0';
                            
                            // 处理特殊命令
                            if (strncmp(input_buffer, "quit", 4) == 0 ||
                                strncmp(input_buffer, "exit", 4) == 0) {
                                printf("收到退出命令\n");
                                state->running = 0;
                                server_terminate = 1;
                            } else if (strncmp(input_buffer, "status", 6) == 0) {
                                show_server_status(state);
                            } else if (strncmp(input_buffer, "help", 4) == 0) {
                                printf("可用命令:\n");
                                printf("  quit/exit - 退出服务器\n");
                                printf("  status    - 显示服务器状态\n");
                                printf("  help      - 显示帮助信息\n");
                                printf("  Ctrl+C    - 发送终止信号\n");
                            } else {
                                printf("收到输入: %s", input_buffer);
                                
                                // 模拟添加客户端
                                if (strncmp(input_buffer, "connect", 7) == 0) {
                                    if (state->client_count < state->max_clients) {
                                        // 模拟创建客户端连接
                                        int fake_fd = 1000 + state->client_count;
                                        int client_index = add_client(state, fake_fd);
                                        if (client_index != -1) {
                                            printf("模拟客户端连接成功 (fd: %d)\n", fake_fd);
                                        }
                                    } else {
                                        printf("客户端数量已达上限\n");
                                    }
                                }
                            }
                        }
                    } else {
                        // 客户端事件
                        int client_fd = fds[i].fd;
                        int client_index = -1;
                        
                        // 查找对应的客户端
                        for (int j = 0; j < state->max_clients; j++) {
                            if (state->clients[j].connected && 
                                state->clients[j].fd == client_fd) {
                                client_index = j;
                                break;
                            }
                        }
                        
                        if (client_index != -1) {
                            handle_client_data(state, client_index);
                        } else {
                            printf("未知客户端事件 (fd: %d)\n", client_fd);
                        }
                    }
                }
                
                if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                    printf("文件描述符 %d 发生异常\n", fds[i].fd);
                    
                    if (i > 0) {
                        // 客户端异常
                        int client_fd = fds[i].fd;
                        for (int j = 0; j < state->max_clients; j++) {
                            if (state->clients[j].connected && 
                                state->clients[j].fd == client_fd) {
                                remove_client(state, j);
                                break;
                            }
                        }
                    }
                }
            }
        }
        
        // 重置 revents
        for (int i = 0; i < nfds; i++) {
            fds[i].revents = 0;
        }
    }
    
    // 清理资源
    free(fds);
    
    // 关闭所有客户端连接
    for (int i = 0; i < state->max_clients; i++) {
        if (state->clients[i].connected) {
            remove_client(state, i);
        }
    }
    
    printf("服务器模拟结束\n");
    return 0;
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -p, --port=PORT        监听端口 (默认 8080)\n");
    printf("  -c, --clients=NUM      最大客户端数 (默认 10)\n");
    printf("  -t, --timeout=SECONDS   超时时间 (默认 30 秒)\n");
    printf("  -v, --verbose           详细输出\n");
    printf("  -s, --signals           启用信号处理\n");
    printf("  -l, --log=FILE          日志文件\n");
    printf("  -h, --help              显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s                          # 使用默认设置运行\n", program_name);
    printf("  %s -c 20 -t 60 -v          # 20个客户端,60秒超时,详细输出\n", program_name);
    printf("  %s -s -l server.log        # 启用信号处理,记录日志\n", program_name);
    printf("  %s --help                  # 显示帮助信息\n", program_name);
}

int main(int argc, char *argv[]) {
    struct server_config config = {
        .port = 8080,
        .max_clients = 10,
        .timeout_seconds = 30,
        .verbose = 0,
        .use_signals = 0,
        .log_file = NULL
    };
    
    struct server_state server_state_struct;
    
    printf("=== ppoll 事件驱动服务器模拟器 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"port",    required_argument, 0, 'p'},
        {"clients", required_argument, 0, 'c'},
        {"timeout", required_argument, 0, 't'},
        {"verbose", no_argument,       0, 'v'},
        {"signals", no_argument,       0, 's'},
        {"log",     required_argument, 0, 'l'},
        {"help",    no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "p:c:t:vsl:h", long_options, NULL)) != -1) {
        switch (opt) {
            case 'p':
                config.port = atoi(optarg);
                break;
            case 'c':
                config.max_clients = atoi(optarg);
                if (config.max_clients <= 0) config.max_clients = 10;
                break;
            case 't':
                config.timeout_seconds = atoi(optarg);
                if (config.timeout_seconds <= 0) config.timeout_seconds = 30;
                break;
            case 'v':
                config.verbose = 1;
                break;
            case 's':
                config.use_signals = 1;
                break;
            case 'l':
                config.log_file = optarg;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 设置信号处理
    if (config.use_signals) {
        setup_server_signals();
        printf("✓ 信号处理已启用\n");
    }
    
    // 初始化服务器状态
    if (init_server_state(&server_state_struct, config.max_clients) == -1) {
        return 1;
    }
    
    // 设置日志文件
    if (config.log_file) {
        server_state_struct.log_fp = fopen(config.log_file, "a");
        if (server_state_struct.log_fp) {
            printf("✓ 日志文件: %s\n", config.log_file);
        } else {
            perror("打开日志文件失败");
            config.log_file = NULL;
            server_state_struct.log_fp = stderr;
        }
    }
    
    printf("服务器配置:\n");
    printf("  监听端口: %d\n", config.port);
    printf("  最大客户端数: %d\n", config.max_clients);
    printf("  超时时间: %d 秒\n", config.timeout_seconds);
    printf("  详细输出: %s\n", config.verbose ? "是" : "否");
    printf("  信号处理: %s\n", config.use_signals ? "是" : "否");
    printf("  日志文件: %s\n", config.log_file ? config.log_file : "标准错误");
    printf("\n");
    
    printf("启动服务器模拟...\n");
    printf("可用命令:\n");
    printf("  输入 'status' 查看服务器状态\n");
    printf("  输入 'connect' 模拟客户端连接\n");
    printf("  输入 'quit' 或 'exit' 退出服务器\n");
    printf("  输入 'help' 显示帮助信息\n");
    printf("  按 Ctrl+C 发送终止信号\n");
    printf("\n");
    
    // 运行服务器模拟
    int result = run_server_simulation(&server_state_struct, &config);
    
    // 清理资源
    if (server_state_struct.clients) {
        free(server_state_struct.clients);
    }
    
    if (config.log_file && server_state_struct.log_fp != stderr) {
        fclose(server_state_struct.log_fp);
    }
    
    printf("\n=== ppoll 服务器应用说明 ===\n");
    printf("核心技术:\n");
    printf("1. 多路复用: 同时监视多个文件描述符\n");
    printf("2. 事件驱动: 基于事件的通知机制\n");
    printf("3. 信号安全: 原子的信号处理\n");
    printf("4. 超时控制: 精确的超时管理\n");
    printf("5. 资源管理: 动态的客户端管理\n");
    printf("\n");
    printf("ppoll 优势:\n");
    printf("1. 原子性: 等待和信号处理是原子操作\n");
    printf("2. 灵活性: 可以精确控制信号屏蔽\n");
    printf("3. 精确性: 纳秒级超时控制\n");
    printf("4. 可扩展: 无文件描述符数量限制\n");
    printf("5. 安全性: 避免信号处理中的竞态条件\n");
    printf("\n");
    printf("实际应用场景:\n");
    printf("1. 网络服务器: HTTP/WebSocket 服务器\n");
    printf("2. 代理服务: 反向代理、负载均衡\n");
    printf("3. 实时应用: 游戏服务器、聊天应用\n");
    printf("4. 监控系统: 系统监控、日志收集\n");
    printf("5. 数据处理: 流数据处理、ETL 系统\n");
    
    return result;
}

编译和运行说明

# 编译示例程序
gcc -o ppoll_example1 example1.c
gcc -o ppoll_example2 example2.c
gcc -o ppoll_example3 example3.c

# 运行示例
./ppoll_example1
./ppoll_example2
./ppoll_example3 --help
./ppoll_example3 -c 5 -t 10 -v -s

# 测试信号处理
./ppoll_example3 -s &
PID=$!
sleep 2
kill -USR1 $PID
sleep 2
kill -TERM $PID

系统要求检查

# 检查内核版本(需要 2.6.16+)
uname -r

# 检查 glibc 版本(需要 2.4+)
ldd --version

# 检查 ppoll 系统调用支持
grep -w ppoll /usr/include/asm/unistd_64.h

# 查看系统调用表
cat /proc/kallsyms | grep ppoll

重要注意事项

  1. 内核版本: 需要 Linux 2.6.16+ 内核支持
  2. glibc 版本: 需要 glibc 2.4+ 支持
  3. 编译标志: 需要定义 _GNU_SOURCE
  4. 错误处理: 始终检查返回值和 errno
  5. 信号安全: 正确处理 EINTR 错误
  6. 资源清理: 及时关闭文件描述符
  7. 超时处理: 合理设置超时时间

与相关函数的比较

// select - 传统的文件描述符监视
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

// poll - 基本的文件描述符监视
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

// ppoll - 增强的文件描述符监视(带信号处理)
int ppoll(struct pollfd *fds, nfds_t nfds,
          const struct timespec *timeout_ts,
          const sigset_t *sigmask);

// epoll - Linux 特有的高性能接口
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

实际应用场景

  1. 网络服务器: HTTP、WebSocket、TCP 服务器
  2. 代理服务: 反向代理、负载均衡器
  3. 实时应用: 游戏服务器、聊天应用
  4. 监控系统: 系统监控、日志收集
  5. 数据处理: 流数据处理、ETL 系统
  6. 文件系统: 文件监控、备份系统
  7. 设备驱动: 设备事件处理、I/O 多路复用

最佳实践

// 安全的 ppoll 封装函数
int safe_ppoll(struct pollfd *fds, nfds_t nfds,
               const struct timespec *timeout_ts,
               const sigset_t *sigmask) {
    // 验证参数
    if (!fds || nfds == 0) {
        errno = EINVAL;
        return -1;
    }
    
    // 执行 ppoll
    int result = ppoll(fds, nfds, timeout_ts, sigmask);
    
    // 处理常见错误
    if (result == -1) {
        switch (errno) {
            case EINTR:
                // 被信号中断是正常的
                break;
            case EBADF:
                fprintf(stderr, "错误: 包含无效的文件描述符\n");
                break;
            case EFAULT:
                fprintf(stderr, "错误: 参数指针无效\n");
                break;
            case EINVAL:
                fprintf(stderr, "错误: 参数无效\n");
                break;
        }
    }
    
    return result;
}

// 事件处理循环模板
int event_loop_template(struct pollfd *fds, int nfds, int timeout_seconds) {
    struct timespec timeout;
    sigset_t sigmask;
    
    // 设置超时
    timeout.tv_sec = timeout_seconds;
    timeout.tv_nsec = 0;
    
    // 设置信号屏蔽
    sigemptyset(&sigmask);
    
    while (1) {
        int ready = safe_ppoll(fds, nfds, &timeout, &sigmask);
        
        if (ready == -1) {
            if (errno == EINTR) {
                // 被信号中断,继续循环
                continue;
            } else {
                perror("ppoll 失败");
                return -1;
            }
        } else if (ready == 0) {
            // 超时处理
            printf("超时: 没有事件发生\n");
            continue;
        } else {
            // 处理准备就绪的文件描述符
            for (int i = 0; i < nfds; i++) {
                if (fds[i].revents & POLLIN) {
                    // 处理可读事件
                    handle_readable_fd(fds[i].fd);
                }
                if (fds[i].revents & POLLOUT) {
                    // 处理可写事件
                    handle_writable_fd(fds[i].fd);
                }
                if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                    // 处理错误事件
                    handle_error_fd(fds[i].fd, fds[i].revents);
                }
            }
        }
        
        // 重置 revents
        for (int i = 0; i < nfds; i++) {
            fds[i].revents = 0;
        }
    }
    
    return 0;
}

这些示例展示了 ppoll 函数的各种使用方法,从基础的文件描述符监视到完整的事件驱动服务器模拟,帮助你全面掌握 Linux 系统中的高级 I/O 多路复用机制。

发表在 linux文章 | 留下评论

prctl系统调用及示例

prctl 函数详解

1. 函数介绍

prctl 是 Linux 系统中用于进程控制的万能系统调用。可以把 prctl 想象成”进程的控制面板”——它允许你对进程的各种属性进行精细控制,就像调节电视遥控器上的各种设置一样。

prctl 提供了丰富的进程控制功能,包括设置进程名称、控制进程行为、管理安全属性等。它是 Linux 进程管理的重要工具。

相关文章:prctl系统调用及示例-CSDN博客 set_thread_area系统调用及示例 pwritev2系统调用及示例

2. 函数原型

#include <sys/prctl.h>

int prctl(int option, unsigned long arg2, unsigned long arg3,
          unsigned long arg4, unsigned long arg5);

3. 功能

prctl 函数用于控制进程的各种属性和行为。它是一个多功能的系统调用,支持数十种不同的操作选项。

4. 主要参数

  • option: 控制选项(详见下表)
  • arg2, arg3, arg4, arg5: 根据选项而定的参数

5. 常用选项

选项功能参数说明
PR_SET_NAME设置进程名称arg2: 指向名称字符串
PR_GET_NAME获取进程名称arg2: 指向缓冲区
PR_SET_SECCOMP设置安全计算模式seccomp 模式控制
PR_GET_SECCOMP获取安全计算模式
PR_SET_DUMPABLE设置核心转储权限0=不可转储, 1=可转储
PR_GET_DUMPABLE获取核心转储权限
PR_SET_KEEPCAPS设置保持权能标志0=不保持, 1=保持
PR_GET_KEEPCAPS获取保持权能标志
PR_SET_PDEATHSIG设置父进程死亡信号arg2: 信号编号
PR_GET_PDEATHSIG获取父进程死亡信号arg2: 指向信号变量

6. 返回值

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

7. 常见错误码

  • EINVAL: 参数无效
  • EPERM: 权限不足
  • EFAULT: 参数指针无效
  • EACCES: 访问被拒绝

8. 相似函数或关联函数

  • pthread_setname_np: 设置线程名称
  • getpid: 获取进程 ID
  • kill: 发送信号
  • setuid/setgid: 设置用户/组 ID
  • capset: 设置进程权能

9. 示例代码

示例1:基础用法 – 设置和获取进程名称

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

int main() {
    char old_name[16];
    char new_name[16];
    char current_name[16];
    
    printf("=== prctl 进程名称控制示例 ===\n\n");
    
    // 获取当前进程名称
    if (prctl(PR_GET_NAME, old_name) == 0) {
        old_name[15] = '\0';  // 确保字符串结束
        printf("原始进程名称: %s\n", old_name);
    } else {
        perror("获取进程名称失败");
    }
    
    // 设置新的进程名称
    snprintf(new_name, sizeof(new_name), "MyProcess_%d", getpid() % 1000);
    if (prctl(PR_SET_NAME, new_name) == 0) {
        printf("设置新进程名称: %s\n", new_name);
    } else {
        perror("设置进程名称失败");
    }
    
    // 验证名称是否设置成功
    if (prctl(PR_GET_NAME, current_name) == 0) {
        current_name[15] = '\0';
        printf("当前进程名称: %s\n", current_name);
        printf("设置%s成功\n", 
               strcmp(current_name, new_name) == 0 ? "" : "未完全");
    }
    
    // 恢复原始名称
    if (prctl(PR_SET_NAME, old_name) == 0) {
        printf("恢复原始名称: %s\n", old_name);
    }
    
    printf("\n查看进程名称的方法:\n");
    printf("  ps -ef | grep %d\n", getpid());
    printf("  cat /proc/%d/comm\n", getpid());
    
    return 0;
}

示例2:进程安全控制

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

// 父进程死亡信号处理
void parent_death_handler(int sig) {
    printf("收到父进程死亡信号 %d\n", sig);
    printf("子进程即将退出...\n");
    exit(0);
}

int main() {
    printf("=== prctl 进程安全控制示例 ===\n\n");
    
    // 设置父进程死亡信号
    if (prctl(PR_SET_PDEATHSIG, SIGTERM) == 0) {
        printf("✓ 设置父进程死亡信号为 SIGTERM\n");
    } else {
        printf("✗ 设置父进程死亡信号失败: %s\n", strerror(errno));
    }
    
    // 设置核心转储权限
    int old_dumpable = -1;
    if (prctl(PR_GET_DUMPABLE, &old_dumpable) == 0) {
        printf("当前核心转储权限: %s\n", 
               old_dumpable ? "允许" : "禁止");
    }
    
    // 禁止核心转储
    if (prctl(PR_SET_DUMPABLE, 0) == 0) {
        printf("✓ 禁止核心转储\n");
        
        // 验证设置
        int new_dumpable = -1;
        if (prctl(PR_GET_DUMPABLE, &new_dumpable) == 0) {
            printf("验证: 核心转储权限现在是 %s\n", 
                   new_dumpable ? "允许" : "禁止");
        }
    }
    
    // 恢复核心转储权限
    if (old_dumpable != -1) {
        prctl(PR_SET_DUMPABLE, old_dumpable);
    }
    
    printf("\n安全控制说明:\n");
    printf("1. PR_SET_PDEATHSIG: 父进程死亡时发送信号\n");
    printf("2. PR_SET_DUMPABLE: 控制核心转储权限\n");
    printf("3. 提高进程安全性,防止敏感信息泄露\n");
    
    return 0;
}

示例3:完整的进程控制工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项]\n", program_name);
    printf("\n选项:\n");
    printf("  -n, --name=NAME        设置进程名称\n");
    printf("  -s, --show             显示当前进程信息\n");
    printf("  -d, --dumpable=0|1     设置核心转储权限\n");
    printf("  -p, --pdeathsig=SIG    设置父进程死亡信号\n");
    printf("  -h, --help             显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s -n MyNewName        # 设置进程名称\n", program_name);
    printf("  %s -s                  # 显示进程信息\n", program_name);
    printf("  %s -d 0                # 禁止核心转储\n", program_name);
    printf("  %s -p 15               # 设置父进程死亡信号为 SIGTERM\n", program_name);
}

// 显示当前进程信息
void show_process_info() {
    char name[16];
    
    printf("=== 当前进程信息 ===\n");
    printf("进程 ID: %d\n", getpid());
    printf("父进程 ID: %d\n", getppid());
    printf("用户 ID: %d\n", getuid());
    printf("组 ID: %d\n", getgid());
    
    // 获取进程名称
    if (prctl(PR_GET_NAME, name) == 0) {
        name[15] = '\0';
        printf("进程名称: %s\n", name);
    }
    
    // 获取核心转储权限
    int dumpable = -1;
    if (prctl(PR_GET_DUMPABLE, &dumpable) == 0) {
        printf("核心转储: %s\n", dumpable ? "允许" : "禁止");
    }
    
    // 获取父进程死亡信号
    int pdeathsig = -1;
    if (prctl(PR_GET_PDEATHSIG, &pdeathsig) == 0) {
        if (pdeathsig > 0) {
            printf("父进程死亡信号: %d\n", pdeathsig);
        } else {
            printf("父进程死亡信号: 无\n");
        }
    }
}

int main(int argc, char *argv[]) {
    char *new_name = NULL;
    int dumpable = -1;
    int pdeathsig = -1;
    int show_info = 0;
    
    printf("=== prctl 进程控制工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"name",      required_argument, 0, 'n'},
        {"show",      no_argument,       0, 's'},
        {"dumpable",  required_argument, 0, 'd'},
        {"pdeathsig", required_argument, 0, 'p'},
        {"help",      no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "n:sd:p:h", long_options, NULL)) != -1) {
        switch (opt) {
            case 'n':
                new_name = optarg;
                break;
            case 's':
                show_info = 1;
                break;
            case 'd':
                dumpable = atoi(optarg);
                break;
            case 'p':
                pdeathsig = atoi(optarg);
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 显示信息
    if (show_info || (!new_name && dumpable == -1 && pdeathsig == -1)) {
        show_process_info();
        printf("\n");
    }
    
    // 设置进程名称
    if (new_name) {
        if (prctl(PR_SET_NAME, new_name) == 0) {
            printf("✓ 设置进程名称为: %s\n", new_name);
        } else {
            printf("✗ 设置进程名称失败: %s\n", strerror(errno));
        }
    }
    
    // 设置核心转储权限
    if (dumpable != -1) {
        if (prctl(PR_SET_DUMPABLE, dumpable) == 0) {
            printf("✓ %s核心转储\n", dumpable ? "允许" : "禁止");
        } else {
            printf("✗ 设置核心转储权限失败: %s\n", strerror(errno));
        }
    }
    
    // 设置父进程死亡信号
    if (pdeathsig > 0) {
        if (prctl(PR_SET_PDEATHSIG, pdeathsig) == 0) {
            printf("✓ 设置父进程死亡信号为: %d\n", pdeathsig);
        } else {
            printf("✗ 设置父进程死亡信号失败: %s\n", strerror(errno));
        }
    }
    
    // 显示使用建议
    if (new_name || dumpable != -1 || pdeathsig != -1) {
        printf("\n=== 使用建议 ===\n");
        printf("进程名称控制:\n");
        printf("  - 用于进程标识和监控\n");
        printf("  - 方便系统管理工具识别\n");
        printf("  - 长度限制为 15 个字符\n");
        printf("\n安全控制:\n");
        printf("  - 禁止核心转储保护敏感信息\n");
        printf("  - 设置父进程死亡信号实现优雅退出\n");
        printf("  - 提高进程的安全性和可控性\n");
    }
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -o prctl_example1 example1.c
gcc -o prctl_example2 example2.c
gcc -o prctl_example3 example3.c

# 运行示例
./prctl_example1
./prctl_example2
./prctl_example3 --help
./prctl_example3 -s
./prctl_example3 -n TestProcess
./prctl_example3 -d 0

系统要求检查

# 检查内核版本(需要 2.1.57+)
uname -r

# 检查 prctl 支持
grep -w prctl /usr/include/asm/unistd_64.h

# 查看进程信息
cat /proc/self/comm
ps -p $$ -o pid,ppid,comm,cmd

重要注意事项

  1. 权限要求: 大多数操作不需要特殊权限
  2. 名称长度: 进程名称最长 15 个字符(加上终止符共 16 字节)
  3. 错误处理: 始终检查返回值和 errno
  4. 平台相关: prctl 是 Linux 特有的系统调用
  5. 安全性: 合理使用安全控制选项

实际应用场景

  1. 进程管理: 设置有意义的进程名称
  2. 安全控制: 控制核心转储和敏感信息
  3. 容器技术: 容器化进程控制
  4. 服务管理: 系统服务进程管理
  5. 调试工具: 调试和监控工具使用

常用选项详解

// 进程名称控制
prctl(PR_SET_NAME, "MyProcess");    // 设置进程名称
prctl(PR_GET_NAME, name_buffer);    // 获取进程名称

// 安全控制
prctl(PR_SET_DUMPABLE, 0);          // 禁止核心转储
prctl(PR_GET_DUMPABLE, &dumpable);  // 获取核心转储权限
prctl(PR_SET_PDEATHSIG, SIGTERM);    // 设置父进程死亡信号

// 权能控制
prctl(PR_SET_KEEPCAPS, 1);          // 设置保持权能标志
prctl(PR_GET_KEEPCAPS, &keepcaps);  // 获取保持权能标志

// 内存保护
prctl(PR_SET_MM, PR_SET_MM_START_BRK, addr);  // 设置堆起始地址

最佳实践

// 安全的进程名称设置
int safe_set_process_name(const char *name) {
    if (!name) {
        errno = EINVAL;
        return -1;
    }
    
    // 验证名称长度
    if (strlen(name) >= 16) {
        fprintf(stderr, "进程名称过长,最多 15 个字符\n");
        errno = EINVAL;
        return -1;
    }
    
    // 设置进程名称
    int result = prctl(PR_SET_NAME, name);
    if (result == -1) {
        fprintf(stderr, "设置进程名称失败: %s\n", strerror(errno));
    }
    
    return result;
}

// 获取进程信息的安全函数
int get_process_info_safe(char *name, int *dumpable, int *pdeathsig) {
    int result = 0;
    
    // 获取进程名称
    if (name) {
        if (prctl(PR_GET_NAME, name) == -1) {
            result = -1;
        } else {
            name[15] = '\0';  // 确保字符串结束
        }
    }
    
    // 获取核心转储权限
    if (dumpable) {
        if (prctl(PR_GET_DUMPABLE, dumpable) == -1) {
            result = -1;
        }
    }
    
    // 获取父进程死亡信号
    if (pdeathsig) {
        if (prctl(PR_GET_PDEATHSIG, pdeathsig) == -1) {
            result = -1;
        }
    }
    
    return result;
}

这些示例展示了 prctl 函数的各种使用方法,从基础的进程名称设置到完整的进程控制工具,帮助你全面掌握 Linux 系统中的进程控制机制。

发表在 linux文章 | 留下评论

pivot_root系统调用及示例

pivot_root系统调用及示例

相关文章:pivot_root系统调用及示例 pivot_root系统调用及示例-CSDN博客

我们来深入学习 pivot_root 系统调用

1. 函数介绍

在 Linux 系统中,每个运行的进程都看到一个文件系统层次结构(就是我们熟悉的 //home/usr 等目录树)。这个层次结构的根目录 (/) 是所有路径的起点。

chroot 系统调用可以改变当前进程及其子进程看到的根目录。例如,执行 chroot /newroot 后,进程再访问 / 实际上是访问 /newroot,访问 /bin 实际上是访问 /newroot/bin。但是,chroot 有一个重要的限制:它只影响调用进程及其后续创建的子进程。如果系统上还有其他进程在运行,它们看到的根目录仍然是原来的那个。

pivot_root 是一个更强大、更彻底的系统调用。它的作用是交换整个系统当前的根文件系统和另一个文件系统。

想象一下,你有两个完整的、独立的 Linux 文件系统环境:

  • 当前的根文件系统 (/):这是系统当前运行所在的环境。
  • 新的根文件系统 (new_root):这是你准备好的另一个完整的、可以独立运行的 Linux 环境(通常是一个临时的、精简的或者用于恢复的系统)。

pivot_root 会执行以下操作:

  1. 将 new_root 变成新的根文件系统 (/)。
  2. 将旧的根文件系统移动到一个指定的位置 (put_old),这个位置必须是在新的根文件系统 new_root 内部。
  3. 更新所有相关进程的根目录和当前工作目录信息。

简单来说,pivot_root 就像是整个系统(所有进程)瞬间从一个“世界”(旧根文件系统)“传送”到了另一个“世界”(新根文件系统),而旧的“世界”被折叠打包放在了新“世界”里的一个盒子里。

典型应用场景

  • Linux 启动过程:这是 pivot_root 最重要的用途之一。Linux 启动时,内核通常先挂载一个临时的、基于内存的初始根文件系统(initramfs 或 initrd)。这个临时系统包含了加载真实根文件系统所需的驱动程序。一旦真实根文件系统被挂载(例如挂载到 /mnt),启动脚本就会执行 pivot_root /mnt /mnt/old_root,将 /mnt 变成新的根目录 (/),并将旧的 initramfs 环境移动到新的根目录下的 /old_root 目录中。然后,系统会执行新的根文件系统中的 /sbin/init 程序,完成启动。
  • 系统救援/恢复:在系统无法正常启动时,可以从外部介质(如 Live CD/USB)启动一个救援环境,然后使用 pivot_root 切换到硬盘上损坏的系统分区进行修复。
  • 容器技术:虽然现代容器(如 Docker)主要使用 chroot 和 mount 命名空间,但在某些高级场景下,pivot_root 也可能被用于设置容器的根文件系统。

2. 函数原型

#define _GNU_SOURCE // 启用 GNU 扩展
#include <unistd.h> // 包含 pivot_root 函数声明 (在某些系统上可能在 <linux/unistd.h>)

int pivot_root(const char *new_root, const char *put_old);

注意pivot_root 不是 POSIX 标准函数,它是 Linux 特有的系统调用。在标准 C 库 (glibc) 中可能没有直接的包装函数,或者需要特定版本才支持。如果 #include <unistd.h> 后编译报错找不到 pivot_root,你可能需要:

  1. 更新 glibc。
  2. 手动通过 syscall 调用(但这比较复杂,因为需要处理路径)。
  3. 使用 syscall(SYS_pivot_root, new_root, put_old),但这通常不推荐,因为路径参数需要特殊处理。

在大多数现代 Linux 系统上,直接包含 <unistd.h> 并调用 pivot_root 是可行的。

3. 功能

将当前进程的根文件系统切换为 new_root 指向的文件系统,并将原来的根文件系统移动到 put_old 指向的目录中(该目录必须位于 new_root 内)。

4. 参数

  • new_root:
    • const char * 类型。
    • 指向一个目录的路径名,该目录是新的根文件系统的挂载点。这个文件系统必须已经挂载好。
  • put_old:
    • const char * 类型。
    • 指向一个目录的路径名,该目录必须位于 new_root 文件系统内。调用成功后,原来的根文件系统会被挂载到这个目录下。

5. 返回值

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

6. 错误码 (errno)

  • EPERM: 调用进程没有足够的权限执行此操作(通常需要 CAP_SYS_ADMIN 能力)。
  • EINVALnew_root 或 put_old 参数无效,例如 new_root 和 put_old 在同一个文件系统上,或者 put_old 不在 new_root 下,或者 new_root 本身就是根目录 (/)。
  • ENOMEM: 内核内存不足。
  • EBUSYnew_root 或 put_old 正在被使用,无法完成操作。
  • ENOENTnew_root 或 put_old 指定的目录不存在。
  • ENOTDIRnew_root 或 put_old 不是目录。
  • EIO: I/O 错误。

7. 重要前提和限制

要成功调用 pivot_root,必须满足以下条件:

  1. new_root 和 put_old 必须是不同的文件系统new_root 和 put_old 不能指向同一个文件系统。通常,new_root 是一个新挂载的文件系统,而 put_old 是新文件系统中的一个目录。
  2. put_old 必须在 new_root 内部put_old 指定的路径必须是 new_root 路径的子目录。
  3. 调用进程必须在 new_root 中运行:调用 pivot_root 时,调用进程的当前工作目录 (cwd) 通常必须位于 new_root 文件系统内。最常见做法是在调用前先 chdir(new_root)
  4. 权限:通常需要 CAP_SYS_ADMIN 能力(root 用户通常拥有此能力)。
  5. new_root 不能是根目录new_root 本身不能是 /

8. 相似函数或关联函数

  • chroot: 更简单、限制更多的改变根目录的方法,只影响调用进程及其子进程。
  • mount: 用于挂载文件系统,pivot_root 通常需要先用 mount 挂载好 new_root
  • umount: 用于卸载文件系统。在 pivot_root 后,通常需要卸载旧根文件系统(现在位于 put_old)。
  • init: 系统的第一个进程(PID 1),在 pivot_root 后通常会执行新的 init
  • switch_root: 一个用户空间命令(通常在 initramfs 中),它不仅执行 pivot_root,还会尝试卸载旧的根文件系统并执行新的 /sbin/init。它比直接使用 pivot_root 更高级。

9. 示例代码

由于 pivot_root 是一个系统级操作,通常在用户空间程序中很少直接调用。它主要在系统启动脚本(initramfs)或容器运行时中使用。

下面是一个概念性的示例,展示了 pivot_root 的典型用法步骤。请注意:在真实系统上运行此代码可能会导致系统不稳定或无法启动,请在虚拟机或容器中谨慎测试。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h> // 包含 MS_MOVE 等
#include <sys/stat.h>  // 包含 mkdir
#include <fcntl.h>     // 包含 open, O_* flags
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[]) {
    // 注意:这是一个高度简化的、用于演示的示例。
    // 真实的 initramfs 或切换脚本会更复杂,并处理更多细节和错误。

    // 假设:
    // 1. 系统当前运行在一个临时的 initramfs 根文件系统上。
    // 2. 真实的目标根文件系统已经被挂载到了 /mnt/new_root。
    // 3. 本程序运行在 /mnt/new_root 目录下。

    const char *new_root = "/mnt/new_root";
    const char *put_old = "/mnt/new_root/old_root";

    printf("--- Demonstrating pivot_root (Conceptual Example) ---\n");
    printf("IMPORTANT: This is a simplified example. Running this on a real system can be dangerous!\n");
    printf("Make sure you understand the implications and run it in a safe environment (VM/Container).\n\n");

    // 1. 检查参数
    if (argc != 1) {
        fprintf(stderr, "Usage: %s (no arguments needed for this conceptual demo)\n", argv[0]);
        fprintf(stderr, "This example assumes the setup described in the comments.\n");
        exit(EXIT_FAILURE);
    }

    // 2. (在真实场景中) 挂载目标根文件系统到 new_root
    // 例如: mount("/dev/sda1", "/mnt/new_root", "ext4", 0, NULL);
    // 这里我们假设它已经挂载好了。

    // 3. 创建 put_old 目录 (如果不存在)
    if (mkdir(put_old, 0755) == -1 && errno != EEXIST) {
        perror("mkdir put_old");
        fprintf(stderr, "Failed to create %s\n", put_old);
        exit(EXIT_FAILURE);
    }
    printf("1. Ensured directory '%s' exists.\n", put_old);

    // 4. 关键步骤:切换当前工作目录到 new_root
    // 这是 pivot_root 成功的关键前提之一。
    if (chdir(new_root) == -1) {
        perror("chdir new_root");
        fprintf(stderr, "Failed to chdir to %s\n", new_root);
        exit(EXIT_FAILURE);
    }
    printf("2. Changed current working directory to '%s'.\n", new_root);

    // 5. 调用 pivot_root
    // 这是核心操作
    printf("3. Calling pivot_root('%s', '%s')...\n", new_root, put_old);
    if (pivot_root(".", "./old_root") == -1) { // 使用相对路径
        perror("pivot_root");
        fprintf(stderr, "pivot_root failed. Check kernel logs (dmesg) for more details.\n");
        fprintf(stderr, "Common issues: Not running as root, new_root/put_old setup incorrect.\n");
        exit(EXIT_FAILURE);
    }
    printf("   pivot_root succeeded!\n");

    // 6. (在真实场景中) 清理:卸载旧的根文件系统
    // 现在旧的根文件系统挂载在 /old_root
    printf("4. Attempting to unmount the old root filesystem (now at /old_root)...\n");
    // 在卸载之前,通常需要将一些关键的文件系统(如 /proc, /sys, /dev)
    // 从旧根移动到新根,或者重新挂载它们。
    // 这里我们简化处理。
    if (umount2("/old_root", MNT_DETACH) == -1) {
        perror("umount2 /old_root");
        fprintf(stderr, "Failed to unmount old root. It might still be accessible at /old_root.\n");
        // 不退出,继续演示
    } else {
         printf("   Old root filesystem unmounted (or detached).\n");
    }

    // 7. (在真实场景中) 执行新的 init 系统
    printf("5. Would now typically exec(/sbin/init) to start the new OS environment.\n");
    printf("   For this demo, we will just print a message and exit.\n");
    printf("\n--- New Root Environment is Active ---\n");
    printf("If this were a real initramfs, the next step would be:\n");
    printf("execl(\"/sbin/init\", \"init\", (char *)NULL);\n");
    printf("And the system would boot from the new root filesystem.\n");

    // 注意:在真实情况下,这里不会返回。
    // execl("/sbin/init", "init", (char *)NULL);
    // 如果 execl 返回,说明执行失败
    // perror("execl /sbin/init");

    return 0;
}

10. 实际场景:initramfs 中的 pivot_root

在 Linux 启动过程中,pivot_root 的使用最为典型。一个简化版的 initramfs 脚本流程如下:

#!/bin/sh
# 这是一个在 initramfs 中运行的简化版启动脚本 (概念性)

# 1. 加载必要的内核模块 (例如文件系统驱动)
# modprobe ext4

# 2. 扫描硬件,找到根设备 (例如 /dev/sda1)

# 3. 创建一个目录用于挂载真实根文件系统
mkdir /mnt

# 4. 挂载真实根文件系统到 /mnt
mount -t ext4 /dev/sda1 /mnt

# 5. 确保新根中有存放旧根的目录
mkdir /mnt/old_root

# 6. 切换根目录 (使用 switch_root 命令,它内部调用 pivot_root 并清理)
# 注意:直接调用 pivot_root 后还需要很多清理工作,switch_root 更安全
# 但如果要手动模拟:
# cd /mnt
# pivot_root . ./old_root
# exec chroot . /sbin/init <dev/console >dev/console 2>&1

# 更推荐使用 switch_root
exec switch_root /mnt /sbin/init

11. 编译和运行 (概念性)

# 假设代码保存在 pivot_root_demo.c 中
# 注意:此代码仅为演示概念,直接运行有风险!
gcc -o pivot_root_demo pivot_root_demo.c

# 这个程序不应该在常规系统上直接运行。
# 它需要在一个特定的、已准备好 new_root 和 put_old 的环境中运行。
# 通常,它会是 initramfs 的一部分。

12. 总结

pivot_root 是一个功能强大但使用场景非常特定的 Linux 系统调用。

  • 核心作用全局性地切换系统的根文件系统,影响所有进程。
  • 与 chroot 的区别
    • chroot 只改变调用进程及其子进程的根视图。
    • pivot_root 改变整个系统的根视图。
  • 典型用途:Linux 系统启动(initramfs 到真实根文件系统)、系统救援。
  • 关键前提
    • new_root 和 put_old 必须是不同且已挂载的文件系统。
    • put_old 必须在 new_root 内部。
    • 调用进程的当前目录通常需要在 new_root 中。
    • 通常需要 root 权限。
  • 后续操作pivot_root 后,通常需要卸载旧根 (umount) 并启动新的 init 进程。
  • 高级工具switch_root 命令封装了 pivot_root 和后续的清理/执行 init 的步骤,是更安全的选择。

对于 Linux 编程新手,理解 pivot_root 的概念和它在系统启动中的作用是非常有价值的,但在日常应用程序开发中很少会直接用到它。

发表在 linux文章 | 留下评论

pselect6系统调用及示例

pselect6系统调用及示例

相关文章:pselect6系统调用及示例-CSDN博客 pselect系统调用及示例 select系统调用及示例 Linux I/O 多路复用机制对比分析poll/ppoll/epoll/select

1. 函数介绍

pselect6 是 Linux 系统中的内部系统调用,是 pselect 的底层实现。对于应用程序开发来说,通常不需要直接使用 pselect6,而是使用标准库提供的 pselect 函数。

2. 函数原型

// 注意: 这是内部系统调用,应用程序不应直接使用
// 应用程序应使用标准的 pselect 函数

3. 功能

pselect6 是 pselect 系统调用的内核实现,提供与 pselect 相同的功能,但在某些架构上可能有不同的参数传递方式。

4. 参数

与 pselect 相同,但由于是系统调用层面,参数传递方式可能不同。

5. 返回值

与 pselect 相同。

6. 相似函数或关联函数

  • pselect: 应用程序应使用的标准函数
  • select: 传统的文件描述符监视函数
  • poll: 更现代的文件描述符监视函数

7. 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <time.h>
#include <errno.h>
#include <string.h>

// 演示标准 pselect 的使用(推荐方式)
int main_pselect_demo() {
    fd_set read_fds;
    struct timespec timeout;
    sigset_t sigmask;
    int ready;
    
    printf("=== 标准 pselect 使用演示 ===\n\n");
    
    // 初始化文件描述符集合
    FD_ZERO(&read_fds);
    FD_SET(STDIN_FILENO, &read_fds);
    
    // 设置超时时间
    timeout.tv_sec = 5;
    timeout.tv_nsec = 500000000;  // 500毫秒
    
    // 设置信号屏蔽集
    sigemptyset(&sigmask);
    
    printf("使用标准 pselect 函数:\n");
    printf("  监视文件描述符: %d (标准输入)\n", STDIN_FILENO);
    printf("  超时时间: %ld.5 秒\n", (long)timeout.tv_sec);
    printf("  请输入一些文本或等待超时...\n\n");
    
    // 使用标准 pselect
    ready = pselect(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout, &sigmask);
    
    if (ready == -1) {
        perror("pselect 失败");
    } else if (ready == 0) {
        printf("✓ 超时: 没有文件描述符准备就绪\n");
    } else {
        printf("✓ 准备就绪的文件描述符数量: %d\n", ready);
        
        if (FD_ISSET(STDIN_FILENO, &read_fds)) {
            char buffer[256];
            ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("✓ 读取到: %s", buffer);
            }
        }
    }
    
    printf("\n=== 重要说明 ===\n");
    printf("1. 应用程序应使用标准的 pselect 函数\n");
    printf("2. pselect6 是内核内部系统调用\n");
    printf("3. 直接使用系统调用会降低可移植性\n");
    printf("4. 标准库函数提供更好的错误处理\n");
    printf("5. 使用标准函数更符合 POSIX 标准\n");
    
    return 0;
}

// 比较 pselect 和 select
void compare_pselect_select() {
    fd_set read_fds1, read_fds2;
    struct timeval timeout_tv;
    struct timespec timeout_ts;
    int result1, result2;
    
    printf("\n=== pselect vs select 对比 ===\n");
    
    // 初始化
    FD_ZERO(&read_fds1);
    FD_SET(STDIN_FILENO, &read_fds1);
    read_fds2 = read_fds1;
    
    // 设置相同的超时时间
    timeout_tv.tv_sec = 2;
    timeout_tv.tv_usec = 0;
    
    timeout_ts.tv_sec = 2;
    timeout_ts.tv_nsec = 0;
    
    printf("功能对比:\n");
    printf("1. select:\n");
    printf("   - 使用 timeval 结构体 (微秒精度)\n");
    printf("   - 不支持原子的信号屏蔽控制\n");
    printf("   - 文件描述符数量受 FD_SETSIZE 限制\n");
    printf("   - 跨平台性好\n");
    printf("\n");
    
    printf("2. pselect:\n");
    printf("   - 使用 timespec 结构体 (纳秒精度)\n");
    printf("   - 支持原子的信号屏蔽控制\n");
    printf("   - 文件描述符数量受同样限制\n");
    printf("   - 提供更好的信号安全性\n");
    printf("\n");
    
    printf("使用建议:\n");
    printf("1. 简单应用: 使用 select\n");
    printf("2. 需要信号安全: 使用 pselect\n");
    printf("3. 高性能需求: 考虑 poll 或 epoll\n");
    printf("4. 跨平台应用: 使用 select\n");
}

int main() {
    printf("=== pselect/pselect6 系统调用详解 ===\n\n");
    
    // 演示标准 pselect 使用
    main_pselect_demo();
    
    // 对比分析
    compare_pselect_select();
    
    printf("\n=== 实际应用场景 ===\n");
    printf("pselect 适用场景:\n");
    printf("1. 网络服务器: 监视多个客户端连接\n");
    printf("2. 实时应用: 精确的超时控制\n");
    printf("3. 系统工具: 同时监视多个输入源\n");
    printf("4. 交互式程序: 响应用户输入和文件事件\n");
    printf("5. 需要信号安全的程序: 避免竞态条件\n");
    printf("\n");
    printf("注意事项:\n");
    printf("1. 文件描述符数量受 FD_SETSIZE 限制 (通常 1024)\n");
    printf("2. 每次调用都会修改文件描述符集合\n");
    printf("3. 需要正确处理 EINTR 错误\n");
    printf("4. 超时时间会被内核修改为剩余时间\n");
    printf("5. 信号屏蔽只在等待期间有效\n");
    
    return 0;
}

编译和运行说明

# 编译示例程序
gcc -D_POSIX_C_SOURCE=200112L -o pselect_example1 example1.c
gcc -D_POSIX_C_SOURCE=200112L -o pselect_example2 example2.c
gcc -D_POSIX_C_SOURCE=200112L -o pselect_example3 example3.c
gcc -D_POSIX_C_SOURCE=200112L -o pselect_demo demo.c

# 运行示例
./pselect_example1
./pselect_example2
./pselect_example3 --help
./pselect_example3 -c 5 -t 10 -v
./pselect_demo

系统要求检查

# 检查 POSIX 支持
getconf _POSIX_C_SOURCE

# 检查 pselect 支持
grep -w pselect /usr/include/sys/select.h

# 检查系统调用
grep -w pselect /usr/include/asm/unistd_64.h

# 查看系统信息
uname -a

重要注意事项

  1. POSIX 标准: 需要定义 _POSIX_C_SOURCE 宏
  2. 文件描述符限制: 受 FD_SETSIZE 限制(通常 1024)
  3. 原子操作: 等待和信号屏蔽是原子的
  4. 错误处理: 始终检查返回值和 errno
  5. 超时处理: 超时时间会被内核修改
  6. 信号安全: 避免信号处理中的竞态条件

实际应用场景

  1. 网络服务器: 监视多个客户端连接
  2. 实时应用: 精确的超时控制
  3. 系统工具: 同时监视多个输入源
  4. 交互式程序: 响应用户输入和文件事件
  5. 需要信号安全的程序: 避免竞态条件

最佳实践

// 安全的 pselect 封装函数
int safe_pselect(int nfds, fd_set *readfds, fd_set *writefds,
                 fd_set *exceptfds, const struct timespec *timeout,
                 const sigset_t *sigmask) {
    // 验证参数
    if (nfds < 0 || nfds > FD_SETSIZE) {
        errno = EINVAL;
        return -1;
    }
    
    if (!readfds && !writefds && !exceptfds) {
        errno = EINVAL;
        return -1;
    }
    
    int result;
    do {
        result = pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask);
    } while (result == -1 && errno == EINTR);
    
    return result;
}

// 事件处理循环模板
int event_loop_template() {
    fd_set read_fds, master_fds;
    struct timespec timeout;
    sigset_t sigmask;
    int max_fd = STDIN_FILENO;
    
    // 初始化
    FD_ZERO(&master_fds);
    FD_SET(STDIN_FILENO, &master_fds);
    
    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;
    
    sigemptyset(&sigmask);
    
    while (1) {
        // 复制文件描述符集合
        read_fds = master_fds;
        
        // 等待事件
        int ready = safe_pselect(max_fd + 1, &read_fds, NULL, NULL, &timeout, &sigmask);
        
        if (ready == -1) {
            perror("pselect 失败");
            return -1;
        } else if (ready == 0) {
            printf("超时\n");
            continue;
        }
        
        // 处理就绪的文件描述符
        for (int fd = 0; fd <= max_fd; fd++) {
            if (FD_ISSET(fd, &read_fds)) {
                // 处理事件
                handle_fd_event(fd);
            }
        }
    }
    
    return 0;
}

这些示例展示了 pselect 和 pselect6 函数的各种使用方法,从基础的文件描述符监视到完整的事件驱动服务器模拟,帮助你全面掌握 Linux 系统中的高级 I/O 多路复用机制。

发表在 linux文章 | 留下评论

pselect系统调用及示例

Linux 高级 I/O 多路复用系统调用详解

相关文章:select系统调用及示例 Linux I/O 多路复用机制对比分析poll/ppoll/epoll/select pselect系统调用及示例

1. pselect 函数详解

1. 函数介绍

pselect 是 Linux 系统中用于同时监视多个文件描述符的系统调用。可以把 pselect 想象成”智能的交通指挥官”——它能够同时观察多个”道路”(文件描述符),当某条道路有”车辆”(数据)到达时,立即通知你进行处理。

与传统的 select 相比,pselect 提供了更好的信号处理机制,避免了信号处理函数中的竞态条件问题。

2. 函数原型

#define _POSIX_C_SOURCE 200112L
#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);

3. 功能

pselect 函数用于同时监视多个文件描述符,等待其中任何一个变为就绪状态(可读、可写或有异常)。它能够在等待期间临时设置信号屏蔽集,提供更好的信号处理安全性。

4. 参数

  • nfds: 要监视的最大文件描述符值加1
  • readfds: 指向要监视可读事件的文件描述符集合的指针
  • writefds: 指向要监视可写事件的文件描述符集合的指针
  • exceptfds: 指向要监视异常事件的文件描述符集合的指针
  • timeout: 指向超时时间的指针(NULL 表示无限等待)
  • sigmask: 指向信号屏蔽集的指针(NULL 表示不改变信号屏蔽)

5. timespec 结构体

struct timespec {
    time_t tv_sec;   /* 秒数 */
    long   tv_nsec;  /* 纳秒数 */
};

6. 返回值

  • 成功: 返回准备就绪的文件描述符数量(0 表示超时)
  • 失败: 返回 -1,并设置相应的 errno 错误码

7. 常见错误码

  • EBADF: 一个或多个文件描述符无效
  • EINTR: 被未屏蔽的信号中断
  • EINVAL: 参数无效
  • ENOMEM: 内存不足

8. 相似函数或关联函数

  • select: 传统的文件描述符监视函数
  • poll: 更现代的文件描述符监视函数
  • ppoll: 带信号屏蔽的 poll
  • epoll_wait: epoll 接口的等待函数
  • read/write: 文件读写操作
  • signal/sigaction: 信号处理函数

9. 示例代码

示例1:基础用法 – 监视标准输入和定时器

#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

// 信号处理标志
volatile sig_atomic_t signal_received = 0;

// 信号处理函数
void signal_handler(int sig) {
    signal_received = sig;
    printf("\n收到信号 %d\n", sig);
}

int main() {
    fd_set read_fds;
    struct timespec timeout;
    sigset_t sigmask;
    int ready;
    
    printf("=== pselect 基础示例 ===\n\n");
    
    // 设置信号处理
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }
    
    if (sigaction(SIGTERM, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }
    
    // 初始化文件描述符集合
    FD_ZERO(&read_fds);
    FD_SET(STDIN_FILENO, &read_fds);
    
    // 设置超时时间 (5 秒)
    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;
    
    // 设置信号屏蔽集 (不屏蔽任何信号)
    sigemptyset(&sigmask);
    
    printf("监视设置:\n");
    printf("  监视文件描述符: %d (标准输入)\n", STDIN_FILENO);
    printf("  超时时间: %ld 秒\n", (long)timeout.tv_sec);
    printf("  按 Enter 键或等待超时...\n");
    printf("  按 Ctrl+C 发送信号\n\n");
    
    // 使用 pselect 监视文件描述符
    ready = pselect(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout, &sigmask);
    
    if (ready == -1) {
        if (errno == EINTR) {
            printf("pselect 被信号中断\n");
            if (signal_received) {
                printf("收到信号: %d\n", signal_received);
            }
        } else {
            perror("pselect 失败");
        }
    } else if (ready == 0) {
        printf("超时: 没有文件描述符准备就绪\n");
    } else {
        printf("准备就绪的文件描述符数量: %d\n", ready);
        
        if (FD_ISSET(STDIN_FILENO, &read_fds)) {
            printf("标准输入有数据可读\n");
            
            // 读取输入
            char buffer[256];
            ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("读取到: %s", buffer);
            }
        }
    }
    
    printf("\n=== pselect 特点 ===\n");
    printf("1. 原子操作: 等待和信号屏蔽是原子的\n");
    printf("2. 精确超时: 支持纳秒级超时\n");
    printf("3. 信号安全: 避免信号处理中的竞态条件\n");
    printf("4. 灵活屏蔽: 可以精确控制信号屏蔽\n");
    printf("5. 高效监视: 同时监视多个文件描述符\n");
    
    return 0;
}

示例2:多文件描述符监视

#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

// 创建临时文件用于测试
int create_test_file(const char *filename) {
    int fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *content = "这是测试文件的内容\n用于演示 pselect 功能\n";
    write(fd, content, strlen(content));
    lseek(fd, 0, SEEK_SET);
    
    printf("创建测试文件: %s\n", filename);
    return fd;
}

// 显示文件描述符集合状态
void show_fd_set_status(fd_set *fds, int max_fd, const char *description) {
    printf("%s:\n", description);
    for (int i = 0; i <= max_fd; i++) {
        if (FD_ISSET(i, fds)) {
            printf("  fd %d: 就绪\n", i);
        }
    }
    printf("\n");
}

int main() {
    fd_set read_fds, master_fds;
    struct timespec timeout;
    sigset_t sigmask;
    int test_fd1, test_fd2;
    int max_fd;
    int ready;
    
    printf("=== pselect 多文件描述符监视示例 ===\n\n");
    
    // 创建测试文件
    test_fd1 = create_test_file("test1.txt");
    test_fd2 = create_test_file("test2.txt");
    
    if (test_fd1 == -1 || test_fd2 == -1) {
        if (test_fd1 != -1) close(test_fd1);
        if (test_fd2 != -1) close(test_fd2);
        unlink("test1.txt");
        unlink("test2.txt");
        return 1;
    }
    
    // 初始化文件描述符集合
    FD_ZERO(&master_fds);
    FD_SET(STDIN_FILENO, &master_fds);
    FD_SET(test_fd1, &master_fds);
    FD_SET(test_fd2, &master_fds);
    
    // 找到最大的文件描述符
    max_fd = STDIN_FILENO;
    if (test_fd1 > max_fd) max_fd = test_fd1;
    if (test_fd2 > max_fd) max_fd = test_fd2;
    
    // 设置超时时间 (3 秒)
    timeout.tv_sec = 3;
    timeout.tv_nsec = 0;
    
    // 设置信号屏蔽集
    sigemptyset(&sigmask);
    
    printf("监视设置:\n");
    printf("  最大文件描述符: %d\n", max_fd);
    printf("  监视的文件描述符: %d(标准输入), %d(文件1), %d(文件2)\n", 
           STDIN_FILENO, test_fd1, test_fd2);
    printf("  超时时间: %ld 秒\n", (long)timeout.tv_sec);
    printf("\n");
    
    // 主循环
    printf("开始监视... (按 Ctrl+C 退出)\n");
    while (1) {
        // 复制主集合到工作集合
        read_fds = master_fds;
        
        printf("等待事件...\n");
        ready = pselect(max_fd + 1, &read_fds, NULL, NULL, &timeout, &sigmask);
        
        if (ready == -1) {
            if (errno == EINTR) {
                printf("pselect 被信号中断\n");
                break;
            } else {
                perror("pselect 失败");
                break;
            }
        } else if (ready == 0) {
            printf("超时: 没有文件描述符准备就绪\n");
        } else {
            printf("准备就绪的文件描述符数量: %d\n", ready);
            
            // 处理标准输入
            if (FD_ISSET(STDIN_FILENO, &read_fds)) {
                printf("标准输入有数据:\n");
                char buffer[256];
                ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    printf("  读取到: %s", buffer);
                    
                    if (strncmp(buffer, "quit", 4) == 0) {
                        printf("收到退出命令\n");
                        break;
                    }
                }
            }
            
            // 处理测试文件1
            if (FD_ISSET(test_fd1, &read_fds)) {
                printf("测试文件1有数据可读\n");
                lseek(test_fd1, 0, SEEK_SET);
                char buffer[128];
                ssize_t bytes_read = read(test_fd1, buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    printf("  文件1内容: %s", buffer);
                }
            }
            
            // 处理测试文件2
            if (FD_ISSET(test_fd2, &read_fds)) {
                printf("测试文件2有数据可读\n");
                lseek(test_fd2, 0, SEEK_SET);
                char buffer[128];
                ssize_t bytes_read = read(test_fd2, buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    printf("  文件2内容: %s", buffer);
                }
            }
            
            printf("\n");
        }
    }
    
    // 清理资源
    printf("清理资源...\n");
    close(test_fd1);
    close(test_fd2);
    unlink("test1.txt");
    unlink("test2.txt");
    
    printf("程序正常退出\n");
    return 0;
}

示例3:完整的事件驱动服务器模拟

#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <getopt.h>

// 服务器配置结构体
struct server_config {
    int max_clients;
    int timeout_seconds;
    int verbose;
    int use_signals;
};

// 客户端信息结构体
struct client_info {
    int fd;
    int connected;
    time_t connect_time;
    char buffer[1024];
    size_t buffer_pos;
};

// 服务器状态结构体
struct server_state {
    int running;
    int client_count;
    struct client_info *clients;
    int max_clients;
};

// 全局变量
volatile sig_atomic_t server_terminate = 0;

// 信号处理函数
void signal_handler(int sig) {
    server_terminate = 1;
    printf("\n收到终止信号 %d\n", sig);
}

// 初始化服务器状态
int init_server_state(struct server_state *state, int max_clients) {
    state->running = 1;
    state->client_count = 0;
    state->max_clients = max_clients;
    
    state->clients = calloc(max_clients, sizeof(struct client_info));
    if (!state->clients) {
        perror("分配客户端数组失败");
        return -1;
    }
    
    return 0;
}

// 添加客户端
int add_client(struct server_state *state, int client_fd) {
    if (state->client_count >= state->max_clients) {
        fprintf(stderr, "客户端数量已达上限: %d\n", state->max_clients);
        return -1;
    }
    
    for (int i = 0; i < state->max_clients; i++) {
        if (!state->clients[i].connected) {
            state->clients[i].fd = client_fd;
            state->clients[i].connected = 1;
            state->clients[i].connect_time = time(NULL);
            state->clients[i].buffer_pos = 0;
            state->client_count++;
            
            printf("添加客户端 %d (fd: %d),当前客户端数: %d\n", 
                   i, client_fd, state->client_count);
            return i;
        }
    }
    
    fprintf(stderr, "找不到空闲的客户端槽位\n");
    return -1;
}

// 移除客户端
void remove_client(struct server_state *state, int client_index) {
    if (client_index >= 0 && client_index < state->max_clients && 
        state->clients[client_index].connected) {
        
        close(state->clients[client_index].fd);
        memset(&state->clients[client_index], 0, sizeof(struct client_info));
        state->client_count--;
        
        printf("移除客户端 %d,剩余客户端数: %d\n", 
               client_index, state->client_count);
    }
}

// 显示服务器状态
void show_server_status(const struct server_state *state) {
    printf("=== 服务器状态 ===\n");
    printf("运行状态: %s\n", state->running ? "运行中" : "已停止");
    printf("客户端数量: %d/%d\n", state->client_count, state->max_clients);
    printf("当前时间: %s", ctime(&(time_t){time(NULL)}));
    
    if (state->client_count > 0) {
        printf("连接的客户端:\n");
        for (int i = 0; i < state->max_clients; i++) {
            if (state->clients[i].connected) {
                printf("  [%d] fd=%d 连接时间: %s", 
                       i, state->clients[i].fd, 
                       ctime(&state->clients[i].connect_time));
            }
        }
    }
    printf("\n");
}

int main(int argc, char *argv[]) {
    struct server_config config = {
        .max_clients = 10,
        .timeout_seconds = 30,
        .verbose = 0,
        .use_signals = 0
    };
    
    struct server_state server_state_struct;
    fd_set read_fds;
    struct timespec timeout;
    sigset_t sigmask;
    int max_fd = STDIN_FILENO;
    int ready;
    
    printf("=== pselect 事件驱动服务器模拟器 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"clients", required_argument, 0, 'c'},
        {"timeout", required_argument, 0, 't'},
        {"verbose", no_argument,       0, 'v'},
        {"signals", no_argument,       0, 's'},
        {"help",    no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "c:t:vsh", long_options, NULL)) != -1) {
        switch (opt) {
            case 'c':
                config.max_clients = atoi(optarg);
                if (config.max_clients <= 0) config.max_clients = 10;
                break;
            case 't':
                config.timeout_seconds = atoi(optarg);
                if (config.timeout_seconds <= 0) config.timeout_seconds = 30;
                break;
            case 'v':
                config.verbose = 1;
                break;
            case 's':
                config.use_signals = 1;
                break;
            case 'h':
                printf("用法: %s [选项]\n", argv[0]);
                printf("选项:\n");
                printf("  -c, --clients=NUM      最大客户端数 (默认 10)\n");
                printf("  -t, --timeout=SECONDS   超时时间 (默认 30 秒)\n");
                printf("  -v, --verbose           详细输出\n");
                printf("  -s, --signals           启用信号处理\n");
                printf("  -h, --help              显示此帮助信息\n");
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 设置信号处理
    if (config.use_signals) {
        struct sigaction sa;
        sa.sa_handler = signal_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        
        sigaction(SIGINT, &sa, NULL);
        sigaction(SIGTERM, &sa, NULL);
        printf("✓ 信号处理已启用\n");
    }
    
    // 初始化服务器状态
    if (init_server_state(&server_state_struct, config.max_clients) == -1) {
        return 1;
    }
    
    printf("服务器配置:\n");
    printf("  最大客户端数: %d\n", config.max_clients);
    printf("  超时时间: %d 秒\n", config.timeout_seconds);
    printf("  详细输出: %s\n", config.verbose ? "是" : "否");
    printf("  信号处理: %s\n", config.use_signals ? "是" : "否");
    printf("\n");
    
    // 设置超时时间
    timeout.tv_sec = config.timeout_seconds;
    timeout.tv_nsec = 0;
    
    // 设置信号屏蔽集
    sigemptyset(&sigmask);
    
    printf("启动服务器模拟...\n");
    printf("可用命令:\n");
    printf("  输入 'status' 查看服务器状态\n");
    printf("  输入 'connect' 模拟客户端连接\n");
    printf("  输入 'quit' 或 'exit' 退出服务器\n");
    printf("  输入 'help' 显示帮助信息\n");
    printf("  按 Ctrl+C 发送终止信号\n");
    printf("\n");
    
    // 主循环
    while (server_state_struct.running && !server_terminate) {
        // 初始化文件描述符集合
        FD_ZERO(&read_fds);
        FD_SET(STDIN_FILENO, &read_fds);
        max_fd = STDIN_FILENO;
        
        // 添加连接的客户端
        for (int i = 0; i < server_state_struct.max_clients; i++) {
            if (server_state_struct.clients[i].connected) {
                FD_SET(server_state_struct.clients[i].fd, &read_fds);
                if (server_state_struct.clients[i].fd > max_fd) {
                    max_fd = server_state_struct.clients[i].fd;
                }
            }
        }
        
        if (config.verbose) {
            printf("监视 %d 个文件描述符 (最大fd: %d)...\n", 
                   server_state_struct.client_count + 1, max_fd);
        }
        
        // 使用 pselect 等待事件
        ready = pselect(max_fd + 1, &read_fds, NULL, NULL, &timeout, &sigmask);
        
        if (ready == -1) {
            if (errno == EINTR) {
                if (config.verbose) {
                    printf("pselect 被信号中断\n");
                }
                continue;
            } else {
                perror("pselect 失败");
                break;
            }
        } else if (ready == 0) {
            if (config.verbose) {
                printf("超时: 没有事件发生\n");
            }
        } else {
            if (config.verbose) {
                printf("准备就绪的文件描述符数量: %d\n", ready);
            }
            
            // 处理标准输入事件
            if (FD_ISSET(STDIN_FILENO, &read_fds)) {
                char input_buffer[256];
                ssize_t bytes_read = read(STDIN_FILENO, input_buffer, sizeof(input_buffer) - 1);
                if (bytes_read > 0) {
                    input_buffer[bytes_read] = '\0';
                    
                    // 处理特殊命令
                    if (strncmp(input_buffer, "quit", 4) == 0 ||
                        strncmp(input_buffer, "exit", 4) == 0) {
                        printf("收到退出命令\n");
                        server_state_struct.running = 0;
                        server_terminate = 1;
                    } else if (strncmp(input_buffer, "status", 6) == 0) {
                        show_server_status(&server_state_struct);
                    } else if (strncmp(input_buffer, "help", 4) == 0) {
                        printf("可用命令:\n");
                        printf("  quit/exit - 退出服务器\n");
                        printf("  status    - 显示服务器状态\n");
                        printf("  help      - 显示帮助信息\n");
                        printf("  Ctrl+C    - 发送终止信号\n");
                    } else if (strncmp(input_buffer, "connect", 7) == 0) {
                        if (server_state_struct.client_count < server_state_struct.max_clients) {
                            // 模拟创建客户端连接
                            int fake_fd = 1000 + server_state_struct.client_count;
                            int client_index = add_client(&server_state_struct, fake_fd);
                            if (client_index != -1) {
                                printf("模拟客户端连接成功 (fd: %d)\n", fake_fd);
                            }
                        } else {
                            printf("客户端数量已达上限\n");
                        }
                    } else {
                        printf("收到输入: %s", input_buffer);
                    }
                }
            }
            
            // 处理客户端事件
            for (int i = 0; i < server_state_struct.max_clients; i++) {
                if (server_state_struct.clients[i].connected && 
                    FD_ISSET(server_state_struct.clients[i].fd, &read_fds)) {
                    
                    char client_buffer[256];
                    ssize_t bytes_read = read(server_state_struct.clients[i].fd, 
                                             client_buffer, sizeof(client_buffer) - 1);
                    
                    if (bytes_read > 0) {
                        client_buffer[bytes_read] = '\0';
                        printf("客户端 %d 发送数据: %s", i, client_buffer);
                        
                        // 回显数据
                        write(server_state_struct.clients[i].fd, "Echo: ", 6);
                        write(server_state_struct.clients[i].fd, client_buffer, bytes_read);
                        
                    } else if (bytes_read == 0) {
                        printf("客户端 %d 断开连接\n", i);
                        remove_client(&server_state_struct, i);
                    } else {
                        if (errno != EAGAIN && errno != EWOULDBLOCK) {
                            perror("读取客户端数据失败");
                            remove_client(&server_state_struct, i);
                        }
                    }
                }
            }
        }
    }
    
    // 清理资源
    printf("清理资源...\n");
    for (int i = 0; i < server_state_struct.max_clients; i++) {
        if (server_state_struct.clients[i].connected) {
            remove_client(&server_state_struct, i);
        }
    }
    free(server_state_struct.clients);
    
    printf("服务器模拟结束\n");
    
    printf("\n=== pselect 服务器应用说明 ===\n");
    printf("核心技术:\n");
    printf("1. 多路复用: 同时监视多个文件描述符\n");
    printf("2. 事件驱动: 基于事件的通知机制\n");
    printf("3. 信号安全: 原子的信号处理\n");
    printf("4. 超时控制: 精确的超时管理\n");
    printf("5. 资源管理: 动态的客户端管理\n");
    printf("\n");
    printf("pselect 优势:\n");
    printf("1. 原子性: 等待和信号处理是原子操作\n");
    printf("2. 灵活性: 可以精确控制信号屏蔽\n");
    printf("3. 精确性: 纳秒级超时控制\n");
    printf("4. 可扩展: 适用于中小规模并发\n");
    printf("5. 安全性: 避免信号处理中的竞态条件\n");
    
    return 0;
}

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

发表在 linux文章 | 留下评论

ptrace系统调用及示例

ptrace系统调用及示例

我们来深入学习 ptrace 系统调用

1. 函数介绍

在 Linux 系统中,进程通常是独立运行的,它们有自己的内存空间和执行状态。但是,有时候我们需要一个进程能够观察甚至控制另一个进程的运行。这在很多场景下都非常有用:

  • 调试器 (Debugger):像 gdb 这样的调试器,可以让你暂停一个正在运行的程序(被调试者),查看它的内存、寄存器状态,单步执行代码,设置断点等。gdb 就是通过 ptrace 来实现这些强大功能的。
  • 系统调用跟踪 (Strace)strace 命令可以显示一个程序执行了哪些系统调用,传入了什么参数,返回了什么结果。它也是利用 ptrace 来实现的。
  • 进程监控和分析:安全软件或系统管理员工具可能需要监控某个进程的行为。
  • 沙箱 (Sandboxing):某些安全机制会使用 ptrace 来限制或监视程序可以执行的操作。

ptrace (Process Trace) 系统调用就是实现这些功能的核心工具。它允许一个进程(我们称它为跟踪者 Tracer,通常是 gdb 或 strace)对另一个进程(我们称它为被跟踪者 Tracee,是你想调试或监控的程序)进行各种操作。

简单来说,ptrace 就像是一个功能强大的“钩子”或“后门”,允许一个进程(跟踪者)介入另一个进程(被跟踪者)的执行过程,查看它的状态,甚至暂停、修改它的执行。

2. 函数原型

#include <sys/ptrace.h> // 包含 ptrace 函数声明和相关常量

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

3. 功能

根据 request 参数指定的操作类型,对进程 ID 为 pid 的目标进程执行相应的跟踪操作。

4. 参数详解

  • request:
    • enum __ptrace_request 类型。
    • 这是最重要的参数,它指定了你想要执行的具体操作。常见的操作有:
      • PTRACE_TRACEME: 由被跟踪者调用。意思是“请允许我的父进程跟踪我”。这是子进程请求被其父进程跟踪的标准方式。
      • PTRACE_ATTACH: 由跟踪者调用。意思是“我要开始跟踪进程 pid”。跟踪者可以是任何有权限的进程,不一定是父进程。
      • PTRACE_DETACH: 由跟踪者调用。意思是“我要停止跟踪进程 pid”,并让它继续独立运行。
      • PTRACE_SYSCALL (PTRACE_SYSEMU): 由跟踪者调用。让被跟踪者继续运行,但在它即将进入或离开一个系统调用时暂停。
      • PTRACE_SINGLESTEP: 由跟踪者调用。让被跟踪者执行一条机器指令,然后暂停。这是实现“单步执行”的基础。
      • PTRACE_CONT: 由跟踪者调用。让被跟踪者从当前暂停状态继续运行。
      • PTRACE_PEEKDATAPTRACE_PEEKTEXT: 由跟踪者调用。读取被跟踪者内存中的数据或代码。
      • PTRACE_POKEDATAPTRACE_POKETEXT: 由跟踪者调用。修改被跟踪者内存中的数据或代码。
      • PTRACE_GETREGSPTRACE_SETREGS: 由跟踪者调用。获取或设置被跟踪者的 CPU 寄存器值。
      • PTRACE_GETSIGINFOPTRACE_SETSIGINFO: 获取或设置导致进程停止的信号信息。
      • PTRACE_SETOPTIONS: 设置跟踪选项,例如是否在系统调用入口/出口时暂停 (PTRACE_O_TRACESYSGOOD),是否自动跟踪子进程 (PTRACE_O_TRACECLONE 等)。
      • … 还有很多其他选项。
  • pid:
    • pid_t 类型。
    • 指定要操作的被跟踪者进程的进程 ID (PID)。
    • 对于 PTRACE_TRACEME,这个参数被忽略。
  • addr:
    • void * 类型。
    • 一个内存地址。其具体含义取决于 request 的类型。
      • 对于 PTRACE_PEEK*/PTRACE_POKE*,它指定要读取/修改的被跟踪者内存地址。
      • 对于其他操作,通常被忽略或有特殊含义。
  • data:
    • void * 类型。
    • 一个指向数据的指针。其具体含义也取决于 request 的类型。
      • 对于 PTRACE_POKEDATA/PTRACE_POKETEXT,它指向要写入被跟踪者内存的数据。
      • 对于 PTRACE_SET* 操作,它指向包含新值的结构体。
      • 对于 PTRACE_PEEK* 操作,结果通常通过 ptrace 的返回值给出,而不是通过 data 参数。
      • 对于其他操作,通常被忽略或有特殊含义。

5. 返回值

  • 成功:
    • 对于大多数 PTRACE_PEEK* 操作,返回值是从被跟踪者内存中读取的数据
    • 对于其他操作,通常返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

ptrace 可能返回多种错误码,常见的有:

  • EPERM: 权限不足。例如:
    • 尝试跟踪一个你不拥有的进程。
    • 目标进程已经在被其他进程跟踪。
    • 目标进程是 init 进程 (PID 1)。
    • 受到 Yama 安全模块 (ptrace_scope) 的限制。
  • ESRCH: 找不到 pid 指定的进程。
  • EINVALrequest 参数无效,或者在当前状态下不允许该操作。
  • EIO: I/O 错误,或在某些非法状态下尝试操作(例如,对一个正在运行的进程执行 PTRACE_PEEKDATA)。
  • EFAULTaddr 或 data 指向了调用进程无法访问的内存地址。

7. 被跟踪者状态的变化

当一个被跟踪的进程即将收到一个信号或即将执行系统调用/从系统调用返回时,内核会暂停该进程的执行,并发送一个 SIGCHLD 信号给它的跟踪者。此时,跟踪者可以调用 waitpid() 或 wait() 来等待并获取被跟踪者暂停的通知。

跟踪者在检查被跟踪者的状态(读取寄存器、内存等)并决定如何处理后,可以调用 ptrace(PTRACE_CONT, ...) 或 ptrace(PTRACE_SYSCALL, ...) 等操作让被跟踪者继续运行。

8. 示例代码

下面是一个简单的示例,演示了如何使用 ptrace 来跟踪一个子进程的系统调用(类似 strace 的简化版)。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>   // 包含 struct user_regs_struct (不同架构可能不同)
#include <sys/syscall.h> // 包含系统调用号常量
#include <errno.h>
#include <string.h>

// 一个简单的子进程函数,用于被跟踪
void traced_process() {
    // 1. 请求被父进程跟踪
    if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
        perror("ptrace TRACEME");
        exit(EXIT_FAILURE);
    }

    // 2. 停止自己,让父进程有机会在我们真正开始执行前进行设置
    // 这会向父进程发送 SIGSTOP 信号
    kill(getpid(), SIGSTOP);

    // 3. 执行一些操作
    printf("Child: Hello from traced process!\n");
    int fd = open("/dev/null", O_WRONLY);
    if (fd != -1) {
        write(fd, "Test data", 9);
        close(fd);
    }
    printf("Child: Goodbye from traced process!\n");
    // 子进程结束
}

// 将系统调用号转换为名称的简单函数 (只列举几个)
const char* get_syscall_name(long syscall_num) {
    switch(syscall_num) {
        case SYS_write: return "write";
        case SYS_open: return "open";
        case SYS_close: return "close";
        case SYS_read: return "read";
        case SYS_mmap: return "mmap";
        case SYS_mprotect: return "mprotect";
        default: {
            static char buf[32];
            snprintf(buf, sizeof(buf), "syscall_%ld", syscall_num);
            return buf;
        }
    }
}

int main() {
    pid_t child_pid;
    int status;
    struct user_regs_struct regs; // 用于存储寄存器值

    printf("--- Demonstrating ptrace (syscall tracing) ---\n");

    // 1. Fork 创建子进程
    child_pid = fork();
    if (child_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (child_pid == 0) {
        // --- 子进程 ---
        traced_process();
    } else {
        // --- 父进程 (跟踪者) ---
        printf("Parent: Started tracing child (PID: %d)\n", child_pid);

        // 2. 等待子进程因 SIGSTOP 而暂停
        // 当子进程调用 kill(getpid(), SIGSTOP) 时,它会暂停并通知父进程
        if (waitpid(child_pid, &status, 0) == -1) {
            perror("waitpid (initial stop)");
            // 杀死子进程
            kill(child_pid, SIGKILL);
            exit(EXIT_FAILURE);
        }

        if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) {
            printf("Parent: Child has stopped itself with SIGSTOP. Ready to trace.\n");
        } else {
            fprintf(stderr, "Parent: Child did not stop with SIGSTOP as expected.\n");
            kill(child_pid, SIGKILL);
            exit(EXIT_FAILURE);
        }

        // 3. 让子进程继续运行,但在每次系统调用时暂停
        // PTRACE_SYSCALL 会让子进程在进入和退出系统调用时都暂停
        if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
            perror("ptrace SYSCALL (first)");
            kill(child_pid, SIGKILL);
            exit(EXIT_FAILURE);
        }

        // 4. 主循环:等待子进程暂停,打印系统调用信息,然后让它继续
        int entering_syscall = 1; // 标志位:1表示即将进入,0表示即将退出
        while (1) {
            // 等待子进程暂停
            if (waitpid(child_pid, &status, 0) == -1) {
                if (errno == ECHILD) {
                    // 子进程已退出
                    printf("Parent: Child process has exited.\n");
                    break;
                } else {
                    perror("waitpid");
                    kill(child_pid, SIGKILL);
                    break;
                }
            }

            // 检查子进程暂停的原因
            if (WIFSTOPPED(status)) {
                int sig = WSTOPSIG(status);
                if (sig == (SIGTRAP | 0x80)) { // 这是 PTRACE_O_TRACESYSGOOD 的效果
                    sig = SIGTRAP;
                }

                if (sig == SIGTRAP) {
                    // 由于 PTRACE_SYSCALL 而暂停,表示系统调用事件
                    // 获取寄存器值
                    if (ptrace(PTRACE_GETREGS, child_pid, NULL, &regs) == -1) {
                        perror("ptrace GETREGS");
                        break;
                    }

                    // 在 x86_64 上,系统调用号在 orig_rax 寄存器中
                    long syscall_num = regs.orig_rax;

                    if (entering_syscall) {
                        printf("Parent: [Entering] Syscall: %s (%ld)\n", get_syscall_name(syscall_num), syscall_num);
                        // 可以在这里打印参数 regs.rdi, regs.rsi, regs.rdx 等
                        entering_syscall = 0;
                    } else {
                        // 在 x86_64 上,系统调用返回值在 rax 寄存器中
                        long retval = regs.rax;
                        printf("Parent: [Exiting]  Syscall: %s (%ld), Return: %ld\n", get_syscall_name(syscall_num), syscall_num, retval);
                        entering_syscall = 1;
                    }

                    // 让子进程继续,直到下一个系统调用事件
                    if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
                        perror("ptrace SYSCALL (loop)");
                        break;
                    }

                } else if (sig == SIGSTOP) {
                    // 可能是初始的 SIGSTOP,或者由其他地方发出的 SIGSTOP
                    printf("Parent: Child received SIGSTOP.\n");
                    if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
                        perror("ptrace SYSCALL after SIGSTOP");
                        break;
                    }
                } else {
                    // 子进程因其他信号暂停
                    printf("Parent: Child stopped by signal %d. Forwarding signal.\n", sig);
                    // 将信号传递给子进程
                    if (ptrace(PTRACE_SYSCALL, child_pid, NULL, (void*)(unsigned long)sig) == -1) {
                        perror("ptrace SYSCALL (forward signal)");
                        break;
                    }
                }

            } else if (WIFEXITED(status)) {
                // 子进程正常退出
                printf("Parent: Child exited normally with status %d.\n", WEXITSTATUS(status));
                break;
            } else if (WIFSIGNALED(status)) {
                // 子进程被信号杀死
                printf("Parent: Child was killed by signal %d.\n", WTERMSIG(status));
                break;
            } else {
                printf("Parent: Child stopped with unexpected status: %d\n", status);
                if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1) {
                    perror("ptrace SYSCALL (unexpected)");
                    break;
                }
            }
        }

        printf("Parent: Tracing finished.\n");
    }

    return 0;
}

9. 编译和运行

# 假设代码保存在 ptrace_example.c 中
# 注意:此代码是 x86_64 架构特定的 (因为使用了 regs.orig_rax, regs.rax 等)
# 在其他架构上需要修改寄存器名称
gcc -o ptrace_example ptrace_example.c

# 运行程序
./ptrace_example

10. 预期输出 (x86_64)

--- Demonstrating ptrace (syscall tracing) ---
Parent: Started tracing child (PID: 12345)
Parent: Child has stopped itself with SIGSTOP. Ready to trace.
Child: Hello from traced process!
Parent: [Entering] Syscall: write (1)
Parent: [Exiting]  Syscall: write (1), Return: 35
Parent: [Entering] Syscall: open (2)
Parent: [Exiting]  Syscall: open (2), Return: 3
Parent: [Entering] Syscall: write (1)
Parent: [Exiting]  Syscall: write (1), Return: 9
Parent: [Entering] Syscall: close (3)
Parent: [Exiting]  Syscall: close (3), Return: 0
Child: Goodbye from traced process!
Parent: [Entering] Syscall: write (1)
Parent: [Exiting]  Syscall: write (1), Return: 37
Parent: Child process has exited.
Parent: Tracing finished.

11. 总结

ptrace 是一个功能极其强大但也相当复杂的系统调用,是 Linux 系统调试和监控能力的基石。

  • 核心作用:允许一个进程(跟踪者)观察和控制另一个进程(被跟踪者)的执行。
  • 主要操作 (request):
    • PTRACE_TRACEME: 子进程请求被父进程跟踪。
    • PTRACE_ATTACH/PTRACE_DETACH: 开始/停止跟踪一个任意进程。
    • PTRACE_SYSCALL/PTRACE_SINGLESTEP: 控制被跟踪者的执行(系统调用步进/单步执行)。
    • PTRACE_CONT: 让被跟踪者继续运行。
    • PTRACE_PEEK*/PTRACE_POKE*: 读写被跟踪者的内存。
    • PTRACE_GETREGS/PTRACE_SETREGS: 读写被跟踪者的寄存器。
  • 工作机制:被跟踪者在特定事件(如信号、系统调用)发生时暂停,内核通知跟踪者。跟踪者通过 wait 获取通知,进行检查/修改,然后通过 ptrace 命令让其继续。
  • 典型应用
    • 调试器 (gdb): 设置断点、单步执行、查看变量。
    • 系统调用跟踪器 (strace): 记录程序执行的所有系统调用。
    • 沙箱/安全监控: 限制或记录程序的行为。
  • 重要限制
    • 权限:通常需要是被跟踪者的父进程,或者具有 CAP_SYS_PTRACE 能力。
    • 安全:受 Yama LSM (ptrace_scope) 限制,防止恶意跟踪。
    • 一对一:一个进程同时只能被一个进程跟踪。
  • 复杂性:直接使用 ptrace 非常复杂,涉及信号处理、寄存器操作、架构相关细节等。实际工具(如 gdbstrace)对其进行了大量封装。

对于 Linux 编程新手来说,理解 ptrace 的基本概念和它在 gdb/strace 等工具中的作用是非常有价值的,它揭示了操作系统底层强大的进程控制能力。

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

发表在 linux文章 | 留下评论

putpmsg系统调用及示例

putpmsg 替代函数和方案详解

1. 现代 Linux 替代方案概述

由于 putpmsg 主要在 System V STREAMS 系统中可用,而大多数 Linux 系统不完全支持 STREAMS,因此需要使用现代的替代方案。以下是主要的替代函数和方案:

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

2. 替代方案分类

2.1 实时信号 (RT Signals)

#include <signal.h>
#include <sys/types.h>
#include <string.h>

// 使用实时信号发送优先级数据
int send_priority_data_with_signal(pid_t target_pid, int priority, 
                                  const void *data, size_t data_size) {
    union sigval signal_data;
    
    // 实时信号支持传递额外数据
    if (data_size <= sizeof(signal_data)) {
        memcpy(&signal_data, data, data_size);
        signal_data.sival_int = priority;  // 将优先级存储在整型字段中
    } else {
        // 对于大数据,传递指针或标识符
        signal_data.sival_ptr = (void*)data;  // 注意:需要共享内存
        signal_data.sival_int = priority;
    }
    
    // 选择实时信号 (SIGRTMIN + 0 到 SIGRTMAX - SIGRTMIN)
    int signal_num = SIGRTMIN + (priority % (SIGRTMAX - SIGRTMIN + 1));
    
    return sigqueue(target_pid, signal_num, signal_data);
}

// 信号处理函数
void priority_signal_handler(int sig, siginfo_t *info, void *context) {
    printf("收到优先级信号 %d,优先级: %d\n", sig, info->si_value.sival_int);
    
    if (info->si_code == SI_QUEUE) {
        // 处理队列中的信号
        printf("  信号来源: 进程 %d\n", info->si_pid);
        if (info->si_value.sival_ptr) {
            printf("  数据指针: %p\n", info->si_value.sival_ptr);
        }
    }
}

2.2 Unix 域套接字 (Unix Domain Sockets)

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

// 消息头结构体
struct priority_message_header {
    int priority;      // 消息优先级
    int msg_type;      // 消息类型
    size_t data_size;  // 数据大小
    time_t timestamp;  // 时间戳
};

// 使用 Unix 域套接字发送优先级消息
int send_priority_message_unix(int sockfd, int priority, 
                               const void *data, size_t data_size) {
    struct priority_message_header header;
    
    // 构造消息头
    header.priority = priority;
    header.msg_type = 1;  // 普通消息类型
    header.data_size = data_size;
    header.timestamp = time(NULL);
    
    // 发送消息头
    ssize_t sent = send(sockfd, &header, sizeof(header), MSG_NOSIGNAL);
    if (sent != sizeof(header)) {
        return -1;
    }
    
    // 发送实际数据
    if (data_size > 0) {
        sent = send(sockfd, data, data_size, MSG_NOSIGNAL);
        if (sent != (ssize_t)data_size) {
            return -1;
        }
    }
    
    return 0;
}

// 接收优先级消息
ssize_t receive_priority_message_unix(int sockfd, int *priority,
                                      void *buffer, size_t buffer_size) {
    struct priority_message_header header;
    
    // 接收消息头
    ssize_t received = recv(sockfd, &header, sizeof(header), 0);
    if (received != sizeof(header)) {
        return -1;
    }
    
    // 返回优先级
    if (priority) {
        *priority = header.priority;
    }
    
    // 接收数据
    if (header.data_size > 0 && header.data_size <= buffer_size) {
        received = recv(sockfd, buffer, header.data_size, 0);
        if (received == (ssize_t)header.data_size) {
            return received;
        }
    }
    
    return -1;
}

2.3 管道和 FIFO

#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>

// 带优先级的消息结构体
struct fifo_message {
    int priority;           // 消息优先级
    pid_t sender_pid;       // 发送者 PID
    size_t data_size;       // 数据大小
    char data[1024];        // 消息数据
};

// 通过 FIFO 发送优先级消息
int send_priority_message_fifo(const char *fifo_path, int priority,
                               const void *data, size_t data_size) {
    int fd;
    struct fifo_message msg;
    
    // 打开 FIFO
    fd = open(fifo_path, O_WRONLY);
    if (fd == -1) {
        return -1;
    }
    
    // 构造消息
    msg.priority = priority;
    msg.sender_pid = getpid();
    msg.data_size = (data_size < sizeof(msg.data)) ? data_size : sizeof(msg.data);
    if (data && data_size > 0) {
        memcpy(msg.data, data, msg.data_size);
    }
    
    // 发送消息
    ssize_t written = write(fd, &msg, sizeof(msg));
    close(fd);
    
    return (written == sizeof(msg)) ? 0 : -1;
}

// 通过 FIFO 接收优先级消息
int receive_priority_message_fifo(const char *fifo_path,
                                 struct fifo_message *msg) {
    int fd;
    
    // 以非阻塞模式打开 FIFO
    fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        return -1;
    }
    
    // 使用 poll 等待数据
    struct pollfd pfd = {fd, POLLIN, 0};
    int ready = poll(&pfd, 1, 1000);  // 1秒超时
    
    if (ready > 0 && (pfd.revents & POLLIN)) {
        ssize_t bytes_read = read(fd, msg, sizeof(struct fifo_message));
        close(fd);
        return (bytes_read == sizeof(struct fifo_message)) ? 0 : -1;
    }
    
    close(fd);
    return -1;
}

2.4 D-Bus 消息系统

#include <dbus/dbus.h>
#include <stdio.h>
#include <stdlib.h>

// 使用 D-Bus 发送优先级消息 (需要安装 libdbus)
#ifdef USE_DBUS

int send_dbus_priority_message(const char *bus_name, 
                               const char *object_path,
                               const char *interface,
                               int priority,
                               const char *message_data) {
    DBusConnection *connection;
    DBusMessage *message;
    DBusPendingCall *pending;
    dbus_bool_t result;
    
    // 连接到系统总线
    dbus_error_t error;
    dbus_error_init(&error);
    
    connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
    if (dbus_error_is_set(&error)) {
        fprintf(stderr, "连接 D-Bus 失败: %s\n", error.message);
        dbus_error_free(&error);
        return -1;
    }
    
    // 创建消息
    message = dbus_message_new_signal(object_path, interface, "PriorityMessage");
    if (!message) {
        dbus_connection_unref(connection);
        return -1;
    }
    
    // 添加参数
    dbus_message_append_args(message,
                           DBUS_TYPE_INT32, &priority,
                           DBUS_TYPE_STRING, &message_data,
                           DBUS_TYPE_INVALID);
    
    // 发送消息
    result = dbus_connection_send(connection, message, NULL);
    dbus_message_unref(message);
    dbus_connection_flush(connection);
    dbus_connection_unref(connection);
    
    return result ? 0 : -1;
}

#endif

2.5 自定义优先级队列

#include <pthread.h>
#include <stdlib.h>
#include <sys/queue.h>

// 消息结构体
struct priority_message {
    int priority;              // 消息优先级
    time_t timestamp;           // 时间戳
    pid_t sender_pid;           // 发送者 PID
    size_t data_size;           // 数据大小
    void *data;                 // 消息数据
    SLIST_ENTRY(priority_message) entries;
};

// 消息队列
SLIST_HEAD(message_queue, priority_message) global_message_queue =
    SLIST_HEAD_INITIALIZER(global_message_queue);

// 互斥锁和条件变量
pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;

// 发送优先级消息
int send_priority_message_queue(int priority, const void *data, size_t data_size) {
    // 分配消息内存
    struct priority_message *msg = malloc(sizeof(struct priority_message));
    if (!msg) {
        return -1;
    }
    
    // 分配数据内存
    if (data_size > 0) {
        msg->data = malloc(data_size);
        if (!msg->data) {
            free(msg);
            return -1;
        }
        memcpy(msg->data, data, data_size);
    } else {
        msg->data = NULL;
    }
    
    // 初始化消息
    msg->priority = priority;
    msg->timestamp = time(NULL);
    msg->sender_pid = getpid();
    msg->data_size = data_size;
    
    // 添加到队列
    pthread_mutex_lock(&queue_mutex);
    
    // 按优先级插入队列(简单实现:插到队首)
    SLIST_INSERT_HEAD(&global_message_queue, msg, entries);
    
    pthread_cond_signal(&queue_cond);  // 通知等待的线程
    pthread_mutex_unlock(&queue_mutex);
    
    return 0;
}

// 接收优先级消息(阻塞)
struct priority_message* receive_priority_message_queue(int timeout_seconds) {
    struct priority_message *msg = NULL;
    struct timespec timeout;
    
    pthread_mutex_lock(&queue_mutex);
    
    // 等待消息
    while (SLIST_EMPTY(&global_message_queue)) {
        if (timeout_seconds > 0) {
            clock_gettime(CLOCK_REALTIME, &timeout);
            timeout.tv_sec += timeout_seconds;
            
            int result = pthread_cond_timedwait(&queue_cond, &queue_mutex, &timeout);
            if (result == ETIMEDOUT) {
                pthread_mutex_unlock(&queue_mutex);
                return NULL;
            }
        } else {
            pthread_cond_wait(&queue_cond, &queue_mutex);
        }
    }
    
    // 取出最高优先级的消息
    msg = SLIST_FIRST(&global_message_queue);
    SLIST_REMOVE_HEAD(&global_message_queue, entries);
    
    pthread_mutex_unlock(&queue_mutex);
    
    return msg;
}

// 清理消息
void free_priority_message(struct priority_message *msg) {
    if (msg) {
        if (msg->data) {
            free(msg->data);
        }
        free(msg);
    }
}

2.6 POSIX 消息队列

#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

// 发送优先级消息到 POSIX 消息队列
int send_priority_message_mq(mqd_t mqdes, int priority, 
                            const void *data, size_t data_size) {
    // POSIX 消息队列直接支持优先级
    return mq_send(mqdes, (const char*)data, data_size, priority);
}

// 接收优先级消息
ssize_t receive_priority_message_mq(mqd_t mqdes, unsigned *priority,
                                   void *buffer, size_t buffer_size) {
    return mq_receive(mqdes, (char*)buffer, buffer_size, priority);
}

// 创建优先级消息队列
mqd_t create_priority_message_queue(const char *name, int max_messages, 
                                   size_t max_message_size) {
    struct mq_attr attr;
    
    attr.mq_flags = 0;
    attr.mq_maxmsg = max_messages;
    attr.mq_msgsize = max_message_size;
    attr.mq_curmsgs = 0;
    
    return mq_open(name, O_CREAT | O_WRONLY, 0644, &attr);
}

2.7 epoll + 管道实现优先级

#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <unistd.h>

// 优先级管道结构体
struct priority_pipe {
    int epoll_fd;
    int event_fd;  // 用于通知事件
    int **pipes;   // 按优先级划分的管道数组
    int num_priorities;
};

// 创建优先级管道系统
struct priority_pipe* create_priority_pipe_system(int num_priorities) {
    struct priority_pipe *pp = malloc(sizeof(struct priority_pipe));
    if (!pp) return NULL;
    
    pp->num_priorities = num_priorities;
    pp->pipes = malloc(num_priorities * sizeof(int*));
    if (!pp->pipes) {
        free(pp);
        return NULL;
    }
    
    // 创建 epoll 实例
    pp->epoll_fd = epoll_create1(0);
    if (pp->epoll_fd == -1) {
        free(pp->pipes);
        free(pp);
        return NULL;
    }
    
    // 创建事件通知 fd
    pp->event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
    if (pp->event_fd == -1) {
        close(pp->epoll_fd);
        free(pp->pipes);
        free(pp);
        return NULL;
    }
    
    // 添加事件 fd 到 epoll
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = pp->event_fd;
    epoll_ctl(pp->epoll_fd, EPOLL_CTL_ADD, pp->event_fd, &ev);
    
    // 创建优先级管道
    for (int i = 0; i < num_priorities; i++) {
        pp->pipes[i] = malloc(2 * sizeof(int));
        if (pp->pipes[i] && pipe(pp->pipes[i]) == 0) {
            // 设置非阻塞模式
            int flags = fcntl(pp->pipes[i][0], F_GETFL, 0);
            fcntl(pp->pipes[i][0], F_SETFL, flags | O_NONBLOCK);
        }
    }
    
    return pp;
}

// 发送优先级消息
int send_priority_message_pipe(struct priority_pipe *pp, int priority,
                               const void *data, size_t data_size) {
    if (priority < 0 || priority >= pp->num_priorities) {
        errno = EINVAL;
        return -1;
    }
    
    ssize_t written = write(pp->pipes[priority][1], data, data_size);
    if (written == (ssize_t)data_size) {
        // 通知有新消息
        uint64_t notify = 1;
        write(pp->event_fd, &notify, sizeof(notify));
        return 0;
    }
    
    return -1;
}

3. 完整的替代方案示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>

// 消息优先级枚举
enum message_priority {
    PRIORITY_LOW = 10,
    PRIORITY_NORMAL = 50,
    PRIORITY_HIGH = 100,
    PRIORITY_CRITICAL = 200
};

// 通用消息结构体
struct unified_message {
    int priority;
    time_t timestamp;
    pid_t sender_pid;
    char type[32];
    size_t data_size;
    char data[512];
};

// 消息处理器函数指针
typedef void (*message_handler_t)(const struct unified_message *msg);

// 消息系统接口
struct message_system {
    const char *name;
    int (*send_func)(const struct unified_message *msg);
    int (*receive_func)(struct unified_message *msg, int timeout_ms);
    int (*init_func)(void);
    void (*cleanup_func)(void);
};

// 实时信号消息系统
static int rt_signal_send(const struct unified_message *msg) {
    union sigval data;
    data.sival_int = msg->priority;
    // 使用 SIGRTMIN + 0 作为消息信号
    return sigqueue(getpid(), SIGRTMIN, data);
}

static int rt_signal_receive(struct unified_message *msg, int timeout_ms) {
    // 这里简化处理,实际需要信号处理机制
    sleep(timeout_ms / 1000);
    return -1;  // 超时
}

// 自定义队列消息系统
static struct unified_message *message_queue[100];
static int queue_head = 0, queue_tail = 0, queue_count = 0;
static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;

static int custom_queue_send(const struct unified_message *msg) {
    pthread_mutex_lock(&queue_mutex);
    
    if (queue_count >= 100) {
        pthread_mutex_unlock(&queue_mutex);
        return -1;  // 队列满
    }
    
    // 分配消息内存
    struct unified_message *new_msg = malloc(sizeof(struct unified_message));
    if (!new_msg) {
        pthread_mutex_unlock(&queue_mutex);
        return -1;
    }
    
    memcpy(new_msg, msg, sizeof(struct unified_message));
    new_msg->timestamp = time(NULL);
    
    // 按优先级插入队列
    int insert_pos = queue_head;
    for (int i = 0; i < queue_count; i++) {
        int pos = (queue_head + i) % 100;
        if (message_queue[pos]->priority < new_msg->priority) {
            insert_pos = pos;
            break;
        }
    }
    
    // 移动后续消息
    for (int i = queue_count; i > insert_pos; i--) {
        int from_pos = (queue_head + i - 1) % 100;
        int to_pos = (queue_head + i) % 100;
        message_queue[to_pos] = message_queue[from_pos];
    }
    
    message_queue[insert_pos] = new_msg;
    queue_count++;
    
    pthread_mutex_unlock(&queue_mutex);
    return 0;
}

static int custom_queue_receive(struct unified_message *msg, int timeout_ms) {
    time_t start_time = time(NULL);
    
    while (1) {
        pthread_mutex_lock(&queue_mutex);
        
        if (queue_count > 0) {
            // 取出最高优先级消息
            struct unified_message *highest_msg = message_queue[queue_head];
            memcpy(msg, highest_msg, sizeof(struct unified_message));
            free(highest_msg);
            
            queue_head = (queue_head + 1) % 100;
            queue_count--;
            
            pthread_mutex_unlock(&queue_mutex);
            return 0;
        }
        
        pthread_mutex_unlock(&queue_mutex);
        
        // 检查超时
        if (timeout_ms > 0) {
            time_t elapsed = time(NULL) - start_time;
            if (elapsed * 1000 >= timeout_ms) {
                return -1;  // 超时
            }
        }
        
        usleep(10000);  // 休眠 10ms
    }
    
    return -1;
}

// 消息系统实现
static struct message_system available_systems[] = {
    {
        .name = "custom_queue",
        .send_func = custom_queue_send,
        .receive_func = custom_queue_receive,
        .init_func = NULL,
        .cleanup_func = NULL
    },
    {
        .name = "rt_signal",
        .send_func = rt_signal_send,
        .receive_func = rt_signal_receive,
        .init_func = NULL,
        .cleanup_func = NULL
    }
};

// 当前使用的消息系统
static struct message_system *current_system = &available_systems[0];

// 发送统一消息
int send_unified_message(int priority, const char *type, 
                        const void *data, size_t data_size) {
    struct unified_message msg;
    
    msg.priority = priority;
    msg.timestamp = time(NULL);
    msg.sender_pid = getpid();
    strncpy(msg.type, type, sizeof(msg.type) - 1);
    msg.type[sizeof(msg.type) - 1] = '\0';
    
    msg.data_size = (data_size < sizeof(msg.data)) ? data_size : sizeof(msg.data);
    if (data && data_size > 0) {
        memcpy(msg.data, data, msg.data_size);
    }
    
    if (current_system->send_func) {
        return current_system->send_func(&msg);
    }
    
    return -1;
}

// 接收统一消息
int receive_unified_message(struct unified_message *msg, int timeout_ms) {
    if (current_system->receive_func) {
        return current_system->receive_func(msg, timeout_ms);
    }
    return -1;
}

// 演示不同优先级消息处理
void demonstrate_priority_handling() {
    printf("=== 优先级消息处理演示 ===\n\n");
    
    // 发送不同优先级的消息
    printf("发送不同优先级的消息:\n");
    
    const char *critical_msg = "系统紧急告警:磁盘空间不足";
    send_unified_message(PRIORITY_CRITICAL, "ALERT", critical_msg, strlen(critical_msg));
    printf("  ✓ 发送关键优先级消息 (优先级 %d)\n", PRIORITY_CRITICAL);
    
    const char *high_msg = "应用程序错误:数据库连接失败";
    send_unified_message(PRIORITY_HIGH, "ERROR", high_msg, strlen(high_msg));
    printf("  ✓ 发送高优先级消息 (优先级 %d)\n", PRIORITY_HIGH);
    
    const char *normal_msg = "用户登录成功";
    send_unified_message(PRIORITY_NORMAL, "INFO", normal_msg, strlen(normal_msg));
    printf("  ✓ 发送普通优先级消息 (优先级 %d)\n", PRIORITY_NORMAL);
    
    const char *low_msg = "系统日志:定时任务执行完成";
    send_unified_message(PRIORITY_LOW, "DEBUG", low_msg, strlen(low_msg));
    printf("  ✓ 发送低优先级消息 (优先级 %d)\n", PRIORITY_LOW);
    
    printf("\n接收消息 (按优先级顺序):\n");
    
    // 接收并显示消息
    struct unified_message received_msg;
    for (int i = 0; i < 4; i++) {
        if (receive_unified_message(&received_msg, 1000) == 0) {
            printf("  [%d] 优先级 %d (%s): %.*s\n",
                   i + 1, received_msg.priority, received_msg.type,
                   (int)received_msg.data_size, received_msg.data);
        } else {
            printf("  [%d] 超时或无消息\n", i + 1);
        }
    }
}

int main() {
    printf("=== putpmsg 现代替代方案演示 ===\n\n");
    
    printf("putpmsg 替代方案概述:\n");
    printf("1. 实时信号 (RT signals)\n");
    printf("2. Unix 域套接字\n");
    printf("3. 管道和 FIFO\n");
    printf("4. D-Bus 消息系统\n");
    printf("5. 自定义优先级队列\n");
    printf("6. POSIX 消息队列\n");
    printf("7. epoll + 管道\n");
    printf("\n");
    
    // 演示优先级处理
    demonstrate_priority_handling();
    
    printf("\n=== 各方案特点对比 ===\n");
    printf("方案          优先级支持  跨进程  复杂度  性能\n");
    printf("------------- ----------  -------  ------  ----\n");
    printf("实时信号      中等        是       低      高\n");
    printf("Unix套接字    无          是       中      中\n");
    printf("管道/FIFO     无          是       低      中\n");
    printf("D-Bus         高          是       高      中\n");
    printf("自定义队列    高          否       中      高\n");
    printf("POSIX消息队列 高          是       中      高\n");
    printf("epoll+管道    高          是       高      高\n");
    printf("\n");
    
    printf("=== 选择建议 ===\n");
    printf("简单应用: 使用实时信号或管道\n");
    printf("复杂系统: 使用 POSIX 消息队列\n");
    printf("高性能: 使用 epoll + 管道\n");
    printf("企业级: 使用 D-Bus\n");
    printf("跨语言: 使用 D-Bus 或 Unix 套接字\n");
    
    return 0;
}

4. 编译和运行说明

# 编译示例程序
gcc -o putpmsg_alternative alternative.c -lpthread

# 编译 D-Bus 版本 (如果使用)
gcc -o putpmsg_dbus dbus_example.c -ldbus-1

# 编译 POSIX 消息队列版本
gcc -o putpmsg_mq mq_example.c -lrt

# 运行示例
./putpmsg_alternative

5. 各方案详细对比

5.1 功能对比表

方案优先级跨进程实时性复杂度可移植性资源消耗
实时信号中等
Unix套接字
管道/FIFO
D-Bus
自定义队列
POSIX消息队列
epoll+管道

5.2 使用场景推荐

// 场景1: 简单的进程间通信
// 推荐: 实时信号
int simple_ipc_with_signals() {
    // 发送优先级信号
    union sigval data;
    data.sival_int = PRIORITY_HIGH;
    return sigqueue(target_pid, SIGRTMIN, data);
}

// 场景2: 高性能本地通信
// 推荐: Unix 域套接字
int high_performance_local_communication() {
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    // 配置套接字...
    return sockfd;
}

// 场景3: 系统级消息传递
// 推荐: POSIX 消息队列
int system_level_messaging() {
    struct mq_attr attr = {0, 10, 1024, 0};
    mqd_t mqdes = mq_open("/system_queue", O_CREAT | O_WRONLY, 0644, &attr);
    return (mqdes != (mqd_t)-1) ? 0 : -1;
}

// 场景4: 复杂的企业级应用
// 推荐: D-Bus
int enterprise_application_messaging() {
    // 连接到 D-Bus...
    // 发送消息...
    return 0;
}

6. 最佳实践总结

6.1 选择原则

  1. 简单需求: 优先考虑实时信号或管道
  2. 高性能: 选择 epoll + 管道或 Unix 套接字
  3. 复杂需求: 使用 POSIX 消息队列或 D-Bus
  4. 跨语言: 优先考虑 D-Bus 或 Unix 套接字
  5. 系统集成: 使用系统现有的消息机制

6.2 代码质量保证

// 统一的错误处理
#define HANDLE_ERROR(operation) \
    do { \
        if ((operation) == -1) { \
            fprintf(stderr, "错误: %s 失败 - %s\n", #operation, strerror(errno)); \
            return -1; \
        } \
    } while(0)

// 资源清理宏
#define CLEANUP_RESOURCE(resource, cleanup_func) \
    do { \
        if (resource) { \
            cleanup_func(resource); \
            resource = NULL; \
        } \
    } while(0)

// 安全的消息发送函数
int safe_send_priority_message(int priority, const void *data, size_t data_size) {
    if (!data || data_size == 0) {
        errno = EINVAL;
        return -1;
    }
    
    if (priority < 0 || priority > 255) {
        errno = EINVAL;
        return -1;
    }
    
    return send_unified_message(priority, "GENERIC", data, data_size);
}

这些替代方案为 putpmsg 提供了现代化的解决方案,每种方案都有其适用场景和优势。选择合适的方案需要根据具体的需求、性能要求和系统环境来决定。

发表在 linux文章 | 留下评论

pwritev2系统调用及示例

pwritev2 函数

1. 函数介绍

pwritev2 是 pwritev 的增强版本,支持额外的标志参数,提供更多的控制选项。与 preadv2 配对使用。

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

2. 函数原型

#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

将多个缓冲区中的数据写入到文件的指定位置,并支持额外的控制标志。

4. 参数

  • int fd: 文件描述符
  • *const struct iovec iov: iovec结构体数组
  • int iovcnt: iov数组元素个数
  • off_t offset: 文件偏移量
  • int flags: 控制标志

5. 返回值

  • 成功: 返回实际写入的总字节数
  • 失败: 返回-1,并设置errno

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

  • pwritev: 基本版本
  • preadv2: 对应的读取函数
  • write: 基本写入函数

7. 示例代码

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

/**
 * 演示pwritev2的基本使用
 */
int demo_pwritev2_basic() {
    int fd;
    struct iovec iov[3];
    ssize_t bytes_written;
    
    printf("=== pwritev2 基本使用示例 ===\n");
    
    // 准备要写入的数据
    char part1[] = "Part one of the data\n";
    char part2[] = "Part two of the data ";
    char part3[] = "and part three.\n";
    
    // 创建测试文件
    fd = open("test_pwritev2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 设置iovec数组
    iov[0].iov_base = part1;
    iov[0].iov_len = strlen(part1);
    iov[1].iov_base = part2;
    iov[1].iov_len = strlen(part2);
    iov[2].iov_base = part3;
    iov[2].iov_len = strlen(part3);
    
    // 使用pwritev2写入数据(flags设为0表示默认行为)
    bytes_written = pwritev2(fd, iov, 3, 0, 0);
    if (bytes_written == -1) {
        if (errno == ENOSYS) {
            printf("系统不支持 pwritev2 函数\n");
            close(fd);
            unlink("test_pwritev2.txt");
            return 0;
        }
        perror("pwritev2 失败");
        close(fd);
        unlink("test_pwritev2.txt");
        return -1;
    }
    
    printf("pwritev2 成功写入 %zd 字节\n", bytes_written);
    
    // 读取并验证写入的数据
    char buffer[256];
    lseek(fd, 0, SEEK_SET);
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("\n写入的文件内容:\n%s", buffer);
    }
    
    close(fd);
    unlink("test_pwritev2.txt");
    return 0;
}

/**
 * 演示pwritev2的追加写入特性
 */
int demo_pwritev2_append() {
    int fd;
    struct iovec iov[2];
    ssize_t bytes_written;
    
    printf("\n=== pwritev2 追加写入示例 ===\n");
    
    // 创建初始文件
    fd = open("append_test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *initial_data = "Initial data in the file\n";
    write(fd, initial_data, strlen(initial_data));
    close(fd);
    
    // 使用pwritev2进行追加写入
    fd = open("append_test.txt", O_WRONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    char new_data1[] = "Appended data ";
    char new_data2[] = "using pwritev2\n";
    
    iov[0].iov_base = new_data1;
    iov[0].iov_len = strlen(new_data1);
    iov[1].iov_base = new_data2;
    iov[1].iov_len = strlen(new_data2);
    
    // 注意:RWF_APPEND 标志需要内核支持
    bytes_written = pwritev2(fd, iov, 2, 0, RWF_APPEND);
    if (bytes_written == -1) {
        if (errno == ENOSYS || errno == EINVAL) {
            printf("系统不支持 RWF_APPEND 标志,使用普通写入\n");
            bytes_written = pwritev2(fd, iov, 2, 0, 0);
        } else {
            perror("pwritev2 追加写入失败");
            close(fd);
            unlink("append_test.txt");
            return -1;
        }
    }
    
    if (bytes_written > 0) {
        printf("追加写入成功,写入 %zd 字节\n", bytes_written);
    }
    
    // 显示最终文件内容
    char buffer[256];
    lseek(fd, 0, SEEK_SET);
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("\n最终文件内容:\n%s", buffer);
    }
    
    close(fd);
    unlink("append_test.txt");
    return 0;
}

int main() {
    printf("pwritev2 需要 Linux 4.6+ 内核支持\n");
    
    if (demo_pwritev2_basic() == 0) {
        demo_pwritev2_append();
        printf("\n=== pwritev2 使用总结 ===\n");
        printf("优点:支持额外控制标志,功能更强大\n");
        printf("注意:需要较新内核版本支持\n");
    }
    return 0;
}
发表在 linux文章 | 留下评论

pwritev系统调用及示例

pwritev 函数

1. 函数介绍

pwritev 是 pwrite 的聚集写入版本,它允许一次性将多个不连续缓冲区中的数据写入到文件的指定位置。这是分散/聚集I/O操作的写入部分。

2. 函数原型

#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

3. 功能

将由 iov 描述的多个缓冲区中的数据写入到文件描述符 fd 指定的文件中,从 offset 位置开始写入。该操作不会改变文件的当前读写位置。

4. 参数

  • int fd: 文件描述符,必须是已打开的文件
  • *const struct iovec iov: iovec结构体数组,描述多个缓冲区
  • int iovcnt: iov数组中的元素个数
  • off_t offset: 文件中的偏移量(从文件开始处计算)

5. 返回值

  • 成功: 返回实际写入的总字节数
  • 失败: 返回-1,并设置errno

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

  • writev: 基本的聚集写入函数
  • pwrite: 单缓冲区定位写入函数
  • preadv: 对应的读取函数

7. 示例代码

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

/**
 * 使用pwritev进行聚集写入
 */
int demo_pwritev_basic() {
    int fd;
    struct iovec iov[3];
    ssize_t total_bytes;
    
    // 准备要写入的数据
    char data1[] = "First part of the message\n";
    char data2[] = "Second part with more content ";
    char data3[] = "and final part.\n";
    
    printf("=== pwritev 基本使用示例 ===\n");
    printf("准备写入的数据:\n");
    printf("数据1: %s", data1);
    printf("数据2: %s", data2);
    printf("数据3: %s", data3);
    
    // 创建测试文件
    fd = open("test_pwritev.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    // 设置iovec数组
    iov[0].iov_base = data1;
    iov[0].iov_len = strlen(data1);
    iov[1].iov_base = data2;
    iov[1].iov_len = strlen(data2);
    iov[2].iov_base = data3;
    iov[2].iov_len = strlen(data3);
    
    // 使用pwritev写入数据
    total_bytes = pwritev(fd, iov, 3, 0);
    if (total_bytes == -1) {
        perror("pwritev 失败");
        close(fd);
        return -1;
    }
    
    printf("\npwritev 写入了 %zd 字节到文件\n", total_bytes);
    
    // 读取并验证写入的数据
    char buffer[256];
    lseek(fd, 0, SEEK_SET);
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("\n文件内容验证:\n%s", buffer);
    }
    
    close(fd);
    unlink("test_pwritev.txt");
    return 0;
}

/**
 * 演示pwritev写入HTTP响应头
 */
int demo_pwritev_http_response() {
    int fd;
    struct iovec iov[4];
    ssize_t total_bytes;
    
    printf("\n=== pwritev HTTP响应示例 ===\n");
    
    // HTTP响应的各个部分
    char status_line[] = "HTTP/1.1 200 OK\r\n";
    char headers[] = "Content-Type: text/html\r\n"
                     "Content-Length: 25\r\n"
                     "Connection: close\r\n";
    char separator[] = "\r\n";
    char body[] = "<html><body>Hello</body></html>";
    
    // 创建测试文件
    fd = open("http_response.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建HTTP响应文件失败");
        return -1;
    }
    
    // 设置iovec数组
    iov[0].iov_base = status_line;
    iov[0].iov_len = strlen(status_line);
    iov[1].iov_base = headers;
    iov[1].iov_len = strlen(headers);
    iov[2].iov_base = separator;
    iov[2].iov_len = strlen(separator);
    iov[3].iov_base = body;
    iov[3].iov_len = strlen(body);
    
    // 一次性写入完整的HTTP响应
    total_bytes = pwritev(fd, iov, 4, 0);
    if (total_bytes == -1) {
        perror("pwritev 写入HTTP响应失败");
        close(fd);
        return -1;
    }
    
    printf("成功写入HTTP响应,共 %zd 字节\n", total_bytes);
    
    // 显示生成的HTTP响应
    char buffer[512];
    lseek(fd, 0, SEEK_SET);
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("\n生成的HTTP响应:\n%s", buffer);
    }
    
    close(fd);
    unlink("http_response.txt");
    return 0;
}

int main() {
    if (demo_pwritev_basic() == 0) {
        demo_pwritev_http_response();
        printf("\n=== pwritev 使用总结 ===\n");
        printf("优点:一次系统调用写入多个缓冲区,提高效率\n");
        printf("适用场景:网络协议数据发送,日志记录,配置文件更新\n");
    }
    return 0;
}

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

发表在 linux文章 | 留下评论

preadv2系统调用及示例

preadv2系统调用及示例

我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 preadv2pwritev2 和 pkey_mprotect


函数 1: preadv2

1. 函数介绍

preadv2 (pread vector 2) 是 preadv 系统调用的扩展版本。它结合了 pread(带偏移量读取)和 readv(分散读取)的优点,并引入了一个新的 flags 参数,提供了更灵活的 I/O 控制选项。

简单来说,preadv2 允许你从文件的指定偏移量开始,将数据分散读入到多个不连续的缓冲区中,同时还能指定一些高级 I/O 行为(通过 flags)。

2. 函数原型

#define _GNU_SOURCE // 必须定义以使用 preadv2
#include <sys/uio.h> // struct iovec
#include <unistd.h>  // ssize_t

ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

  • 从文件描述符 fd 指定的文件中,从绝对偏移量 offset 开始读取数据。
  • 将读取的数据分散存储到由 iov 和 iovcnt 指定的多个缓冲区中。
  • 不修改文件的当前读写位置指针(lseek 位置)。
  • 根据 flags 参数执行特定的 I/O 操作。

4. 参数

  • int fd: 有效的文件描述符。
  • const struct iovec *iov: 指向 struct iovec 数组的指针,描述了多个分散的缓冲区。
  • int iovcntiov 数组中元素的个数。
  • off_t offset: 在文件中开始读取的绝对偏移量(以字节为单位)。必须是非负数
  • int flags: 控制 I/O 行为的标志。可以是以下值的按位或组合:
    • 0: 默认行为,等同于 preadv
    • RWF_HIPRI: 尝试使用高优先级/实时 I/O(如果内核和设备支持)。
    • RWF_DSYNC: 要求 I/O 操作具有数据同步持久性(类似于 O_DSYNC)。
    • RWF_SYNC: 要求 I/O 操作具有文件同步持久性(类似于 O_SYNC)。
    • RWF_NOWAIT非阻塞。如果 I/O 无法立即完成(例如,需要从磁盘读取而数据不在页缓存中),则不等待,立即返回错误 EAGAIN。这需要内核和文件系统支持。
    • RWF_APPEND: 强制将写入追加到文件末尾(仅对 pwritev2 有效)。

5. 返回值

  • 成功时: 返回实际读取的总字节数(0 表示 EOF)。
  • 失败时: 返回 -1,并设置 errno

函数 2: pwritev2

1. 函数介绍

pwritev2 (pwrite vector 2) 是 pwritev 系统调用的扩展版本。它结合了 pwrite(带偏移量写入)和 writev(集中写入)的优点,并同样引入了 flags 参数。

简单来说,pwritev2 允许你从多个不连续的缓冲区收集数据,并将其写入到文件的指定偏移量处,同时还能指定一些高级 I/O 行为(通过 flags)。

2. 函数原型

#define _GNU_SOURCE
#include <sys/uio.h>
#include <unistd.h>

ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

  • 从由 iov 和 iovcnt 指定的多个缓冲区中收集数据。
  • 将收集到的数据写入到文件描述符 fd 指定的文件中,从绝对偏移量 offset 开始写入。
  • 不修改文件的当前读写位置指针(lseek 位置)。
  • 根据 flags 参数执行特定的 I/O 操作。

4. 参数

  • int fd: 有效的文件描述符。
  • const struct iovec *iov: 指向 struct iovec 数组的指针,描述了多个包含数据的缓冲区。
  • int iovcntiov 数组中元素的个数。
  • off_t offset: 在文件中开始写入的绝对偏移量(以字节为单位)。必须是非负数
    • 如果文件以 O_APPEND 模式打开,或者 flags 中设置了 RWF_APPEND,则 offset 参数会被忽略,数据总是被写入到文件末尾。
  • int flags: 控制 I/O 行为的标志。可以是以下值的按位或组合:
    • 0: 默认行为,等同于 pwritev
    • RWF_HIPRI: 尝试使用高优先级/实时 I/O。
    • RWF_DSYNC: 要求数据同步持久性。
    • RWF_SYNC: 要求文件同步持久性。
    • RWF_NOWAIT非阻塞。如果 I/O 无法立即完成,立即返回错误 EAGAIN
    • RWF_APPEND: 强制将写入追加到文件末尾,即使文件没有以 O_APPEND 打开。

5. 返回值

  • 成功时: 返回实际写入的总字节数
  • 失败时: 返回 -1,并设置 errno

函数 3: pkey_mprotect

1. 函数介绍

pkey_mprotect 是 mprotect 系统调用的扩展,用于将一个内存区域与一个特定的内存保护键(Protection Key, pkey)相关联。

回忆一下 pkey_alloc/free:它们用于获取和释放 pkey 编号。pkey_mprotect 则是将这个编号应用到具体的内存区域上。

一旦内存区域通过 pkey_mprotect 与一个 pkey 关联,对该区域的访问权限就不仅受传统的 PROT_READ/PROT_WRITE/PROT_EXEC 控制,还受该 pkey 在 CPU 的 PKRU(Protection Key Rights User)寄存器中设置的权限控制。

2. 函数原型

#define _GNU_SOURCE
#include <sys/mman.h> // 包含 MPK 相关常量

int pkey_mprotect(void *addr, size_t len, int prot, int pkey);

3. 功能

  • 修改从地址 addr 开始、长度为 len 字节的内存区域的访问权限。
  • 将该内存区域与保护键 pkey(由 pkey_alloc 获得)进行关联
  • 设置该区域的基本权限为 protPROT_READPROT_WRITEPROT_EXEC 的组合)。

4. 参数

  • void *addr: 要修改的内存区域的起始地址。必须是页对齐的
  • size_t len: 内存区域的长度(以字节为单位)。会向上舍入到最近的页边界。
  • int prot: 新的内存保护标志。可以是 PROT_NONEPROT_READPROT_WRITEPROT_EXEC 及其按位或组合。
  • int pkey: 通过 pkey_alloc 获得的保护键编号(0-15)。

5. 返回值

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

示例代码

示例 1:preadv2 和 pwritev2 的基本使用

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

#define FILENAME "test_piov2.txt"

int main() {
    int fd;
    char buf1[20], buf2[30], buf3[50];
    struct iovec iov_w[2], iov_r[3];
    ssize_t bytes_written, bytes_read;

    // 1. 创建并写入测试文件 (使用传统 write)
    fd = open(FILENAME, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open for write");
        exit(EXIT_FAILURE);
    }

    const char *data1 = "Part One: Hello, ";
    const char *data2 = "preadv2 and pwritev2 World!\n";
    iov_w[0].iov_base = (void*)data1;
    iov_w[0].iov_len = strlen(data1);
    iov_w[1].iov_base = (void*)data2;
    iov_w[1].iov_len = strlen(data2);

    bytes_written = writev(fd, iov_w, 2);
    if (bytes_written == -1) {
        perror("writev");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Written %zd bytes using writev.\n", bytes_written);
    close(fd);

    // 2. 使用 preadv2 读取
    fd = open(FILENAME, O_RDONLY);
    if (fd == -1) {
        perror("open for read");
        exit(EXIT_FAILURE);
    }

    // 初始化读取缓冲区
    memset(buf1, '.', sizeof(buf1) - 1); buf1[sizeof(buf1)-1] = '\0';
    memset(buf2, '.', sizeof(buf2) - 1); buf2[sizeof(buf2)-1] = '\0';
    memset(buf3, '.', sizeof(buf3) - 1); buf3[sizeof(buf3)-1] = '\0';

    iov_r[0].iov_base = buf1;
    iov_r[0].iov_len = sizeof(buf1) - 1;
    iov_r[1].iov_base = buf2;
    iov_r[1].iov_len = sizeof(buf2) - 1;
    iov_r[2].iov_base = buf3;
    iov_r[2].iov_len = sizeof(buf3) - 1;

    // 从偏移量 0 开始读取,使用默认标志
    bytes_read = preadv2(fd, iov_r, 3, 0, 0);
    if (bytes_read == -1) {
        perror("preadv2");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("\nRead %zd bytes using preadv2 from offset 0:\n", bytes_read);
    printf("Buffer 1: '%s'\n", buf1);
    printf("Buffer 2: '%s'\n", buf2);
    printf("Buffer 3: '%s'\n", buf3);

    close(fd);

    // 3. 使用 pwritev2 追加写入
    fd = open(FILENAME, O_WRONLY); // 不用 O_APPEND
    if (fd == -1) {
        perror("open for write (again)");
        exit(EXIT_FAILURE);
    }

    const char *append1 = "Appended via ";
    const char *append2 = "pwritev2 with RWF_APPEND flag.\n";
    struct iovec iov_a[2];
    iov_a[0].iov_base = (void*)append1;
    iov_a[0].iov_len = strlen(append1);
    iov_a[1].iov_base = (void*)append2;
    iov_a[1].iov_len = strlen(append2);

    // 使用 RWF_APPEND 标志强制追加,忽略 offset
    bytes_written = pwritev2(fd, iov_a, 2, 0, RWF_APPEND);
    if (bytes_written == -1) {
        perror("pwritev2 with RWF_APPEND");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("\nAppended %zd bytes using pwritev2 with RWF_APPEND.\n", bytes_written);

    close(fd);

    // 4. 验证文件内容
    printf("\n--- Final file content ---\n");
    fd = open(FILENAME, O_RDONLY);
    if (fd != -1) {
        char final_buf[200];
        ssize_t n = read(fd, final_buf, sizeof(final_buf) - 1);
        if (n > 0) {
            final_buf[n] = '\0';
            printf("%s", final_buf);
        }
        close(fd);
    }

    // unlink(FILENAME); // 可选:清理文件
    return 0;
}

代码解释:

  1. 创建一个测试文件,并使用 writev 写入一些初始内容。
  2. 重新打开文件进行读取。
  3. 使用 preadv2(fd, iov_r, 3, 0, 0) 从文件偏移量 0 开始,将数据分散读入三个缓冲区。flags 为 0,表示默认行为。
  4. 打开文件进行写入(非 O_APPEND 模式)。
  5. 使用 pwritev2(fd, iov_a, 2, 0, RWF_APPEND) 将数据写入文件。尽管 offset 是 0,但由于使用了 RWF_APPEND 标志,数据被追加到了文件末尾。
  6. 重新读取并打印文件内容以验证操作结果。

示例 2:pkey_mprotect 结合 pkey_alloc/free 使用

// pkey_mprotect_example.c
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

static jmp_buf jmp_env;
static volatile sig_atomic_t sigsegv_caught = 0;

void sigsegv_handler(int sig) {
    sigsegv_caught = 1;
    longjmp(jmp_env, 1);
}

// Conceptual PKRU manipulation (requires inline assembly in real code)
// For demonstration, we'll just print what would happen.
void set_pkey_access(int pkey, int disable_access) {
    printf("  [Concept] Modifying PKRU for pkey %d: %s\n",
           pkey, disable_access ? "DISABLE access" : "ENABLE access");
    // Real code would involve inline assembly to write to PKRU register
}

int main() {
    // Check for MPK support conceptually
    if (sysconf(_SC_MPKEY) <= 0) {
        fprintf(stderr, "MPK not supported by sysconf.\n");
        exit(EXIT_FAILURE);
    }

    struct sigaction sa;
    sa.sa_handler = sigsegv_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGSEGV, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    size_t page_size = getpagesize();
    size_t len = page_size;
    void *addr;

    // 1. Allocate memory
    addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    printf("Allocated %zu bytes at %p\n", len, addr);

    // 2. Write some data
    strcpy((char*)addr, "This memory is protected by a pkey.");
    printf("Written data: %s\n", (char*)addr);

    // 3. Allocate a protection key
    int pkey = pkey_alloc(0, 0);
    if (pkey == -1) {
        if (errno == EOPNOTSUPP) {
            printf("MPK not supported on this hardware/kernel.\n");
            munmap(addr, len);
            exit(EXIT_FAILURE);
        } else {
            perror("pkey_alloc");
            munmap(addr, len);
            exit(EXIT_FAILURE);
        }
    }
    printf("Allocated pkey: %d\n", pkey);

    // 4. Associate memory with the pkey using pkey_mprotect
    printf("\n--- Associating memory with pkey %d ---\n", pkey);
    if (pkey_mprotect(addr, len, PROT_READ | PROT_WRITE, pkey) == -1) {
        perror("pkey_mprotect");
        pkey_free(pkey);
        munmap(addr, len);
        exit(EXIT_FAILURE);
    }
    printf("Memory successfully associated with pkey %d.\n", pkey);

    // 5. Disable access via PKRU (conceptual)
    printf("\n--- Disabling access to pkey %d via PKRU ---\n", pkey);
    set_pkey_access(pkey, 1); // Conceptual call

    // 6. Try to access protected memory (should trigger SIGSEGV)
    printf("\n--- Attempting to READ from protected memory ---\n");
    sigsegv_caught = 0;

    if (setjmp(jmp_env) == 0) {
        printf("  Trying to read from %p...\n", addr);
        volatile char first_char = *((char*)addr);
        printf("  ERROR: Read succeeded (first char: %c). This should not happen!\n", first_char);
    } else {
        if (sigsegv_caught) {
            printf("  SUCCESS: SIGSEGV caught. Access correctly denied by pkey.\n");
        } else {
            printf("  Unexpected longjmp.\n");
        }
    }

    // 7. Re-enable access
    printf("\n--- Re-enabling access to pkey %d via PKRU ---\n", pkey);
    set_pkey_access(pkey, 0); // Conceptual call

    // 8. Try to access memory again (should succeed)
    printf("\n--- Attempting to access memory again (should succeed now) ---\n");
    printf("  Reading from %p: %.50s\n", addr, (char*)addr);

    // 9. Cleanup
    if (pkey_free(pkey) == -1) {
        perror("pkey_free");
    }
    if (munmap(addr, len) == -1) {
        perror("munmap");
    }

    printf("\nPkey_mprotect example finished.\n");
    return 0;
}

**代码解释 **(概念性):

  1. 设置信号处理和 setjmp/longjmp 用于捕获 SIGSEGV
  2. 使用 mmap 分配一页内存。
  3. 写入一些测试数据。
  4. 调用 pkey_alloc(0, 0) 获取一个 pkey。
  5. 关键步骤: 调用 pkey_mprotect(addr, len, PROT_READ | PROT_WRITE, pkey) 将分配的内存区域与获取的 pkey 关联起来。
  6. 概念性操作: 模拟通过修改 PKRU 寄存器来禁用对这个 pkey 的访问。
  7. 尝试读取受保护的内存,预期会触发 SIGSEGV
  8. 概念性操作: 模拟重新启用对这个 pkey 的访问。
  9. 再次尝试读取,这次应该成功。
  10. 清理资源(释放 pkey 和内存)。

重要提示与注意事项:

  1. 内核版本:
    • preadv2/pwritev2: Linux 内核 4.6+。
    • pkey_mprotect/pkey_alloc/pkey_free: Linux 内核 4.9+ (MPK)。
  2. glibc 版本: 需要 glibc 2.27+ 才能直接使用这些函数。
  3. 硬件支持pkey_* 函数需要 CPU 支持(如 Intel x86_64 Skylake 及更新架构)。
  4. _GNU_SOURCE: 必须定义此宏才能使用这些扩展函数。
  5. flags 参数preadv2/pwritev2 的 flags 提供了强大的 I/O 控制能力,特别是 RWF_NOWAIT(非阻塞)和 RWF_APPEND
  6. pkey_mprotect 是核心: 它是将 pkey 机制应用到实际内存区域的关键步骤。仅仅 pkey_alloc 是不够的。
  7. PKRU 操作: 真正控制 pkey 权限需要直接操作 CPU 的 PKRU 寄存器,这通常需要内联汇编,比较复杂。
  8. 错误处理: 始终检查返回值,特别是 pkey_* 函数可能返回 EOPNOTSUPP

总结:

preadv2 和 pwritev2 是对现有 I/O 系统调用的有力增强,通过引入 flags 参数,提供了更细粒度的控制,如非阻塞 I/O 和强制追加写入。

pkey_mprotect 是内存保护键(MPK)技术的核心 API 之一,它允许将特定的内存区域与一个 pkey 绑定,从而实现比传统 mprotect 更快速、更灵活的内存访问控制。结合 pkey_alloc/free 和对 PKRU 寄存器的操作,可以构建出高性能的内存安全机制。

这三个函数都代表了 Linux 系统编程向更高性能、更细粒度控制发展的趋势。

preadv2 函数

1. 函数介绍

preadv2 是 preadv 的增强版本,支持额外的标志参数,提供更多的控制选项。它是Linux 4.6引入的新特性。

2. 函数原型

#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

3. 功能

与 preadv 类似,但从指定位置读取数据到多个缓冲区,并支持额外的控制标志。

4. 参数

  • int fd: 文件描述符
  • *const struct iovec iov: iovec结构体数组
  • int iovcnt: iov数组元素个数
  • off_t offset: 文件偏移量
  • int flags: 控制标志(如RWF_HIPRI, RWF_DSYNC等)

5. 返回值

  • 成功: 返回实际读取的总字节数
  • 失败: 返回-1,并设置errno

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

  • preadv: 基本版本
  • pwritev2: 对应的写入函数
  • read: 基本读取函数

7. 示例代码

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

/**
 * 演示preadv2的基本使用
 * 注意:需要Linux 4.6+内核支持
 */
int demo_preadv2_basic() {
    int fd;
    struct iovec iov[2];
    char buf1[30], buf2[20];
    ssize_t bytes_read;
    
    printf("=== preadv2 基本使用示例 ===\n");
    
    // 创建测试文件
    fd = open("test_preadv2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *test_data = "This is test data for preadv2 function demonstration.";
    write(fd, test_data, strlen(test_data));
    close(fd);
    
    // 打开文件进行读取
    fd = open("test_preadv2.txt", O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    // 设置iovec数组
    iov[0].iov_base = buf1;
    iov[0].iov_len = sizeof(buf1) - 1;
    iov[1].iov_base = buf2;
    iov[1].iov_len = sizeof(buf2) - 1;
    
    // 使用preadv2读取数据(flags设为0表示默认行为)
    bytes_read = preadv2(fd, iov, 2, 0, 0);
    if (bytes_read == -1) {
        if (errno == ENOSYS) {
            printf("系统不支持 preadv2 函数\n");
            close(fd);
            unlink("test_preadv2.txt");
            return 0;
        }
        perror("preadv2 失败");
        close(fd);
        unlink("test_preadv2.txt");
        return -1;
    }
    
    printf("preadv2 成功读取 %zd 字节\n", bytes_read);
    
    // 添加字符串结束符并显示结果
    buf1[iov[0].iov_len] = '\0';
    buf2[iov[1].iov_len] = '\0';
    
    printf("缓冲区1: %s\n", buf1);
    printf("缓冲区2: %s\n", buf2);
    
    close(fd);
    unlink("test_preadv2.txt");
    return 0;
}

/**
 * 演示preadv2的高级特性(如果系统支持)
 */
int demo_preadv2_advanced() {
    int fd;
    struct iovec iov[1];
    char buffer[100];
    ssize_t bytes_read;
    
    printf("\n=== preadv2 高级特性示例 ===\n");
    printf("preadv2 支持的标志包括:\n");
    printf("  RWF_HIPRI: 高优先级I/O\n");
    printf("  RWF_DSYNC: 数据同步写入\n");
    printf("  RWF_SYNC:  同步写入\n");
    printf("  RWF_NOWAIT: 非阻塞操作\n");
    printf("  RWF_APPEND: 追加模式写入\n");
    
    // 创建测试文件
    fd = open("advanced_test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建测试文件失败");
        return -1;
    }
    
    const char *test_data = "Advanced preadv2 test data for feature demonstration.";
    write(fd, test_data, strlen(test_data));
    close(fd);
    
    fd = open("advanced_test.txt", O_RDONLY);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    
    // 设置iovec
    iov[0].iov_base = buffer;
    iov[0].iov_len = sizeof(buffer) - 1;
    
    // 尝试使用RWF_NOWAIT标志(非阻塞读取)
    bytes_read = preadv2(fd, iov, 1, 0, RWF_NOWAIT);
    if (bytes_read == -1) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            printf("非阻塞操作:数据暂时不可用\n");
        } else if (errno == ENOSYS) {
            printf("系统不支持 RWF_NOWAIT 标志\n");
        } else {
            printf("preadv2 with RWF_NOWAIT 失败: %s\n", strerror(errno));
        }
    } else {
        buffer[bytes_read] = '\0';
        printf("非阻塞读取成功: %s\n", buffer);
    }
    
    close(fd);
    unlink("advanced_test.txt");
    return 0;
}

int main() {
    printf("preadv2 需要 Linux 4.6+ 内核支持\n");
    
    if (demo_preadv2_basic() == 0) {
        demo_preadv2_advanced();
        printf("\n=== preadv2 使用总结 ===\n");
        printf("优点:支持额外控制标志,更灵活的I/O控制\n");
        printf("注意:需要较新内核版本支持\n");
    }
    return 0;
}

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

发表在 linux文章 | 留下评论

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用户提供了快速、可靠的内核下载服务,用户可以根据地理位置和网络环境选择最适合的镜像站点。

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

Kali Linux Download 官方站点及镜像站点整理

发表在 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 系统调用, quotactl 命令详解, linux quotactl 用法, quotactl 示例代码, linux 系统调用 quotactl, quotactl 函数说明, linux 磁盘配额管理, 系统调用 quotactl 解析, quotactl 实战教程, linux quotactl 教程

相关文章:

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

quotactl(2) – Linux manual page

发表在 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. 文件系统优化: 优化元数据访问模式

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

remap_file_pages系统调用及示例

发表在 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文章 | 留下评论

โปรแกรมเมอร์ไทยสร้างรายได้เสริมด้วยระบบเล็กๆ | วิธีพัฒนาระบบให้ร้านข้างบ้าน


🔹 ชื่อโครงการ:

“ระบบดิจิทัลเล็กๆ สำหรับร้านค้าไทย” (DigitalKit for Thai Shops)


🎯 เป้าหมาย:

ช่วยร้านค้าขนาดเล็ก เช่น ร้านอาหาร ร้านขายของชำ ร้านตัดผม ฯลฯ ที่ยังใช้กระดาษหรือ Excel ให้เปลี่ยนมาใช้ระบบดิจิทัลแบบง่าย ประหยัดเวลา และลดข้อผิดพลาด


📅 แผนการดำเนินงาน 90 วัน (3 เดือน)


สัปดาห์ที่ 1–2: สำรวจตลาดและเลือกกลุ่มเป้าหมาย

  • ✅ ศึกษาธุรกิจขนาดเล็กในพื้นที่ (เช่น ตลาดนัด หอพัก ชุมชน)
  • ✅ สัมภาษณ์ผู้ประกอบการ 5–10 คน (ถามว่า “ปัญหาที่ปวดหัวที่สุดในการจัดการร้านคืออะไร?”)
  • ✅ ระบุ pain points ที่พบบ่อย:
  • จดหนี้ลูกค้าผิด
  • ไม่รู้ว่าสินค้าหมดเมื่อไหร่
  • ยอดขายรวมแต่ละวันต้องมานั่งบวกเอง
  • ✅ เลือกกลุ่มเป้าหมายแรก: ร้านขายอาหารตามสั่ง / ร้านน้ำผลไม้

สัปดาห์ที่ 3–4: พัฒนา MVP (ผลิตภัณฑ์ต้นแบบ)

คุณสมบัติของระบบ (เว็บแอปเบื้องต้น):

  • บันทึกการขาย (รายการอาหาร + ราคา)
  • จัดการลูกค้าหนี้ (ชื่อ, เบอร์โทร, ยอดหนี้)
  • ดูรายงานยอดขายรายวัน
  • แจ้งเตือนเมื่อสินค้าใกล้หมด (เช่น น้ำอัดลม, เส้นหมี่)
  • เข้าใช้งานผ่านมือถือได้ (responsive)

เทคโนโลยีที่ใช้:

  • Frontend: React.js หรือ Vue.js
  • Backend: Node.js + Express หรือ Laravel
  • Database: SQLite หรือ MySQL
  • Hosting: VPS ราคาถูก (เช่น DigitalOcean, Hetzner) หรือใช้ฟรีจาก Railway.app / Render.com

💡 ตั้งเป้าพัฒนาให้เสร็จใน 2 สัปดาห์ (ใช้เวลาว่างหลังเลิกงานหรือวันหยุด)


สัปดาห์ที่ 5–6: ทดสอบกับผู้ใช้จริง (Pilot Test)

  • ✅ ติดตั้งระบบให้ร้านค้า 1–2 แห่ง (อาจเป็นร้านของเพื่อน ญาติ หรือร้านที่สัมภาษณ์ไว้)
  • ✅ ฝึกสอนการใช้งาน 30 นาที (ทำคู่มือสั้นๆ เป็นภาพ)
  • ✅ ขอ feedback ทุก 3 วัน (ปรับระบบตามคำแนะนำ)
  • ✅ บันทึก “ก่อน-หลัง” เช่น “ก่อนใช้ระบบ ใช้เวลา 1 ชั่วโมงต่อวันในการรวมยอด, หลังใช้เหลือ 10 นาที”

สัปดาห์ที่ 7–8: สร้างแบรนด์และช่องทางการขาย

สร้างภาพลักษณ์:

  • ตั้งชื่อ: เช่น “ระบบดิจิทัลคิท”, “ร้านฉันดิจิทัล”
  • โลโก้: ใช้ Canva ออกแบบง่ายๆ
  • เว็บไซต์: สร้างหน้า landing page ด้วย Tilda หรือ WordPress (เน้น: ปัญหา + วิธีแก้ + รีวิว)

ช่องทางการตลาด:

  • Facebook Page: โพสต์วิดีโอสั้น “ชีวิตหลังใช้ระบบ”
  • TikTok: คลิป 15 วินาที “ร้านนี้เคยจดหนี้ผิดทุกวัน ตอนนี้ไม่ผิดแล้ว!”
  • เข้ากลุ่ม Facebook เช่น “สตาร์ทอัพไทย”, “แม่ค้าออนไลน์”, “ร้านอาหารเล็กๆ”

สัปดาห์ที่ 9–12: เปิดตัวและเริ่มขาย

รูปแบบการขาย:

  • แบบที่ 1: รายเดือน (แนะนำ)
    ราคา 299–499 บาท/เดือน (รวมอัปเดต + สนับสนุน)
  • แบบที่ 2: ครั้งเดียว + ค่าดูแลรายปี
    ติดตั้งครั้งเดียว 2,000 บาท + ดูแล 999 บาท/ปี

กลยุทธ์การได้ลูกค้า:

  • โปรโมชั่น “ใช้ฟรี 14 วัน”
  • ชวนเพื่อนลด 50% เดือนแรก
  • ร่วมมือกับ “ที่ปรึกษาธุรกิจ SME” หรือ “ช่างบัญชี” เพื่อแนะนำลูกค้า

📊 เป้าหมาย 90 วัน

เป้าหมายตัวเลข
ร้านที่ใช้ระบบ (Pilot + ลูกค้าจริง)5 ร้าน
รายได้ต่อเดือน1,500 – 2,500 บาท
เนื้อหาการตลาด (วิดีโอ/โพสต์)10 ชิ้น
รีวิวจากลูกค้า3 รีวิว (พร้อมภาพ/วิดีโอ)

🛠️ เครื่องมือและต้นทุนที่ต้องใช้

สิ่งที่ต้องใช้ต้นทุนประมาณ
VPS / Hosting300–500 บาท/เดือน
ชื่อโดเมน (.co.th)200 บาท/ปี
Canva Pro (optional)ฟรี (ใช้เวอร์ชันพื้นฐานก่อน)
เวลาพัฒนาใช้เวลาว่าง 10–15 ชม./สัปดาห์
การตลาดฟรี (เริ่มจากโซเชียล)

✅ ขั้นตอนต่อไปหลัง 90 วัน

  • ขยายระบบ: เพิ่มการเชื่อมต่อกับ Line, ระบบแจ้งเตือน
  • สร้างระบบ “White-label” ให้ผู้ให้บริการรายอื่นขายต่อ
  • ยื่นขอ “สินเชื่อ SME” หรือ “โครงการสนับสนุนดิจิทัล” จากภาครัฐ

🔚 สรุป

นี่คือแผนที่ ใช้ทักษะโปรแกรมเมอร์จริง แก้ปัญหาที่ เกิดขึ้นจริงในชีวิตคนไทย และสามารถ เริ่มต้นได้โดยใช้ทุนน้อย แค่คุณมี:

  • ความเข้าใจผู้ใช้
  • ทักษะการพัฒนา
  • ความตั้งใจเริ่มต้นเล็กๆ

💬 “คุณไม่จำเป็นต้องสร้างแอปยักษ์ แค่แก้ปัญหาเล็กๆ ให้คนหนึ่งได้ ก็สามารถขยายไปสู่พันคนได้”

หากคุณต้องการ ฉันสามารถช่วยทำ “ตัวอย่างหน้าเว็บ”, “สคริปต์สัมภาษณ์ลูกค้า”, หรือ “เทมเพลตเสนอราคา” ได้นะครับ 😊

https://www.calcguide.tech/2025/08/28/%E0%B9%82%E0%B8%9B%E0%B8%A3%E0%B9%81%E0%B8%81%E0%B8%A3%E0%B8%A1%E0%B9%80%E0%B8%A1%E0%B8%AD%E0%B8%A3%E0%B9%8C%E0%B9%84%E0%B8%97%E0%B8%A2-%E0%B8%AA%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%87%E0%B8%A3%E0%B8%B2

发表在 站点文章 | 留下评论

ไอเดียธุรกิจสร้างรายได้ พร้อมเคล็ดลับการเริ่มต้นที่ง่

ไอเดียธุรกิจสร้างรายได้ พร้อมเคล็ดลับการเริ่มต้นที่ง่

แน่นอน! ต่อไปนี้คือไอเดียธุรกิจเสริมที่เหมาะกับนักพัฒนาซอฟต์แวร์ (Programmer) และสถาปนิกซอฟต์แวร์ (Software Architect) ที่อาศัยหรือทำงานในประเทศไทย โดยเน้นความเป็นไปได้จริง ใช้ทักษะที่มีอยู่ และสามารถเริ่มต้นได้โดยใช้ทุนไม่มาก:


1. พัฒนาแอปพลิเคชันเฉพาะกลุ่ม (Niche Apps) สำหรับธุรกิจไทย

  • แนวคิด: สร้างแอปมือถือหรือเว็บไซต์สำหรับกลุ่มเป้าหมายเฉพาะ เช่น ร้านค้ารายย่อย, ช่างซ่อม, แม่ค้าตลาดนัด ที่ยังไม่มีระบบดิจิทัล
  • ตัวอย่าง: แอปจัดการสต็อกสินค้าแบบง่ายๆ สำหรับร้านค้าเล็กๆ, ระบบบันทึกหนี้สิน-ลูกค้า หรือแอปนัดหมายสำหรับช่างตัดผม
  • วิธีทำ: เริ่มจากสัมภาษณ์ผู้ใช้จริงในชุมชน ออกแบบ MVP (Minimum Viable Product) แล้วขายแบบรายเดือน (SaaS ขนาดเล็ก)
  • รายได้: ค่าบริการรายเดือน หรือขายเป็นแพ็กเกจ

2. ให้บริการ “ดิจิทัลทรานส์ฟอร์เมชัน” สำหรับธุรกิจครอบครัว (SMEs)

  • แนวคิด: หลายร้านค้าหรือโรงงานขนาดเล็กในไทยยังใช้กระดาษ หรือ Excel อยู่ คุณสามารถช่วยพวกเขาเปลี่ยนมาใช้ระบบดิจิทัล
  • บริการ: ออกแบบระบบจัดการลูกค้า (CRM), ระบบบัญชี, หรือระบบสั่งซื้อออนไลน์แบบง่าย
  • กลยุทธ์: เริ่มจากลูกค้ารายแรกในเครือญาติหรือเพื่อนรู้จัก แล้วใช้เป็น case study
  • รายได้: ค่าพัฒนาต้นทาง + ค่าดูแลรายเดือน

3. สอนเขียนโปรแกรมให้กับคนทำงานหรือวัยรุ่นผ่านออนไลน์

  • แนวคิด: ความต้องการเรียนรู้ด้านดิจิทัลในไทยเพิ่มขึ้น แต่คอร์สที่มีมักแพงหรือเรียนยาก
  • ทำอย่างไร: สร้างคอร์สสั้นๆ บน YouTube, TikTok หรือแพลตฟอร์มเช่น SkillLane หรือ Udemy (ภาษาไทย)
  • เนื้อหา: เช่น “เขียนเว็บไซต์ขายของใน 7 วัน”, “เรียน Python สำหรับงานประจำ”
  • รายได้: ขายคอร์ส, โฆษณา, หรือให้บริการที่ปรึกษาเพิ่มเติม

4. สร้างและขาย “เทมเพลต” หรือ “ปลั๊กอิน” สำหรับเว็บไซต์

  • แนวคิด: นักพัฒนาหลายรายต้องการประหยัดเวลา คุณสามารถสร้างเทมเพลตเว็บไซต์ หรือปลั๊กอินสำหรับ WordPress, Laravel ฯลฯ
  • ตัวอย่าง: เทมเพลตเว็บขายของสำหรับร้านอาหารไทย, ระบบจองคิวออนไลน์สำหรับคลินิก
  • ช่องทางขาย: ThemeForest, Gumroad หรือสร้างเว็บไซต์ขายเอง
  • ข้อดี: รายได้ซ้ำ (recurring income) หากมีเวอร์ชันอัปเดต

5. ให้บริการตรวจสอบและปรับปรุงระบบ (Code & System Audit)

  • แนวคิด: บริษัทหลายแห่งมีระบบเก่าที่ช้าหรือมีช่องโหว่ แต่ไม่มีทีมเทคนิคที่เชี่ยวชาญ
  • บริการ: ตรวจสอบโค้ด, แนะนำการปรับโครงสร้าง (refactor), หรือเพิ่มความปลอดภัย
  • กลุ่มเป้าหมาย: สตาร์ทอัพ, บริษัท SME ที่เริ่มขยายตัว
  • วิธีเริ่ม: ทำ case study ฟรี 1-2 งาน เพื่อสร้างพอร์ตโฟลิโอ

6. พัฒนาบอทหรือระบบอัตโนมัติสำหรับธุรกิจออนไลน์

  • แนวคิด: ธุรกิจออนไลน์ในไทย (โดยเฉพาะบน Facebook, Line) ต้องการลดเวลาในการตอบแชท
  • ตัวอย่าง: บอทตอบอัตโนมัติบน Line OA, ระบบแจ้งเตือนสต็อกสินค้า, บอทดึงข้อมูลลูกค้าจากคอมเมนต์
  • เทคโนโลยี: ใช้ Python, Node.js + API ของ Line, Facebook
  • ขายอย่างไร: ขายเป็นแพ็กเกจต่อร้านค้า หรือให้เช่ารายเดือน

7. สร้าง Open Source Tool ที่ตอบโจทย์ตลาดไทย + ขาย Support

  • แนวคิด: พัฒนาเครื่องมือโอเพ่นซอร์สที่ใช้ได้ฟรี แต่ขายบริการติดตั้ง, ปรับแต่ง หรือ support
  • ตัวอย่าง: เครื่องมือแปลภาษาไทย-อังกฤษเฉพาะทาง, ระบบยืนยันตัวตน (OTP) แบบง่าย
  • ข้อดี: สร้างชื่อเสียง และดึงดูดลูกค้าองค์กร

เคล็ดลับการเริ่มต้น:

  • เริ่มจาก “เล็กแต่จริง” (Start small but real) — เลือก 1 ไอเดีย แล้วทำให้สำเร็จกับลูกค้าจริง 1 คน
  • ใช้ภาษาไทยในการสื่อสารและทำการตลาด — คุณมีข้อได้เปรียบเรื่องภาษาและวัฒนธรรม
  • ใช้สื่อสังคม (TikTok, Facebook, Pantip) ในการหาลูกค้าและแชร์ความรู้

สรุป:
ในฐานะโปรแกรมเมอร์หรือสถาปนิกซอฟต์แวร์ คุณมี “ทักษะทอง” ที่ตลาดไทยต้องการมาก แต่ขาดผู้ที่สามารถ “แปลงทักษะเป็นบริการ” ที่เข้าใจคนทั่วไป ลองเริ่มจากสิ่งเล็กๆ ที่ใกล้ตัว แล้วคุณอาจพบว่า “งานเสริม” กลายเป็น “ธุรกิจหลัก” ในไม่ช้า!

หากต้องการ ฉันสามารถช่วยออกแบบแผนธุรกิจย่อ (1-page business plan) ให้กับไอเดียใดไอเดียหนึ่งได้นะครับ 😊

ไอเดียธุรกิจสร้างรายได้, ไอเดียธุรกิจเริ่มต้นง่าย, เคล็ดลับเริ่มต้นธุรกิจ, ธุรกิจสร้างรายได้ที่ไม่ต้องลงทุน, ไอเดียธุรกิจทำเงิน, วิธีเริ่มต้นธุรกิจใหม่, ธุรกิจออนไลน์สร้างรายได้, ไอเดียธุรกิจสำหรับผู้เริ่มต้น, เคล็ดลับการสร้างรายได้จากธุรกิจ, ธุรกิจเล็กๆ ที่สร้างรายได้มาก

发表在 站点文章 | 留下评论

RedHat Enterprise Linux (RHEL) Official and Mirror Sites Collection

RedHat Enterprise Linux (RHEL) Official and Mirror Sites Collection

RedHat Enterprise Linux Official & Mirror Sites,RedHat Enterprise Linux official sites, RedHat Enterprise Linux mirror sites, RHEL download locations, Official RHEL repositories, RHEL mirror site list, RedHat Linux official download page, RHEL mirror server addresses, Linux enterprise distribution sources, RedHat Enterprise Linux installation guides, RHEL official and mirror sites collection

RedHat Enterprise Linux (RHEL) Official and Mirror Sites Collection – LinuxGuide redhat enterprise linux redhat enterprise linux,RedHat Enterprise Linux official sites,Official RHEL repositories,RHEL mirror site list,RHEL mirror server addresses,RedHat Enterprise Linux installation guides,RHEL official and mirror sites collectionLinuxGuide

🌐 Red Hat Enterprise Linux Official Sites

Official Main Sites

Official Download Sites

  • Red Hat Customer Portal Downloads: https://access.redhat.com/downloads/
  • Red Hat Enterprise Linux Downloads: https://access.redhat.com/downloads/content/69/
  • Developer Subscription: https://developers.redhat.com/products/rhel/download

Official Documentation and Support

  • Official Documentation: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/
  • Knowledge Base: https://access.redhat.com/knowledgebase/
  • Support Center: https://access.redhat.com/support/

🔓 Free Alternative Version Sites

CentOS Stream (Official Recommended Alternative)

  • Official Website: https://www.centos.org/centos-stream/
  • Download Page: https://www.centos.org/download/
  • Mirror Sites: https://mirrorlist.centos.org/
  • Documentation: https://docs.centos.org/

Rocky Linux (Community RHEL Alternative)

  • Official Website: https://rockylinux.org/
  • Download Page: https://rockylinux.org/download/
  • Mirror List: https://mirrors.rockylinux.org/mirrormanager/
  • Documentation: https://docs.rockylinux.org/

AlmaLinux (Enterprise Alternative)

🌍 Domestic Mirror Sites (China)

Educational Network Mirrors

  1. Tsinghua University Mirror ✅
    • Address: https://mirrors.tuna.tsinghua.edu.cn/centos/
    • Features: Fast speed, stable, timely synchronization
  2. USTC Mirror ✅
    • Address: https://mirrors.ustc.edu.cn/centos/
    • Features: Educational network optimized, comprehensive content
  3. Shanghai Jiao Tong University Mirror ✅
    • Address: https://mirror.sjtu.edu.cn/centos/
    • Features: East China region optimization
  4. Huazhong University of Science and Technology Mirror ✅
    • Address: https://mirrors.hust.edu.cn/centos/
    • Features: Central China optimization
  5. Beijing Institute of Technology Mirror ✅
    • Address: https://mirror.bit.edu.cn/centos/
    • Features: Northern region optimization

Commercial Cloud Mirrors

  1. Alibaba Cloud Mirror ✅
  2. Huawei Cloud Mirror ✅
  3. Tencent Cloud Mirror ✅
    • Address: https://mirrors.cloud.tencent.com/centos/
    • Features: Southern region optimization
  4. Netease Mirror ✅
    • Address: https://mirrors.163.com/centos/
    • Features: Well-known domestic mirror
  5. Sohu Mirror ✅
    • Address: https://mirrors.sohu.com/centos/
    • Features: Established mirror service

🌎 International Mirror Sites

Asia Region

  1. Japan Mirror ✅
  2. Korea Mirror ✅
    • Address: https://ftp.kaist.ac.kr/CentOS/
    • Features: Reliable Korean mirror
  3. Singapore Mirror ✅

Europe Region

  1. Germany Mirror ✅
    • Address: https://mirror.centos.org/
    • Features: Official primary mirror
  2. France Mirror ✅
    • Address: https://centos.mirror.garr.it/centos/
    • Features: High-speed European mirror
  3. UK Mirror ✅
    • Address: https://mirror.bytemark.co.uk/CentOS/
    • Features: UK mirror service

North America Region

  1. USA Official Mirror ✅
    • Address: https://mirror.centos.org/
    • Features: Primary official mirror
  2. Canada Mirror ✅
    • Address: https://mirror.csclub.uwaterloo.ca/centos/
    • Features: Canadian university mirror

📦 Version Download Links

CentOS Stream 9 (Latest Version)

CentOS Stream 8

  • x86_64: https://mirror.stream.centos.org/8-stream/BaseOS/x86_64/iso/
  • aarch64: https://mirror.stream.centos.org/8-stream/BaseOS/aarch64/iso/

Rocky Linux 9

  • Official Download: https://download.rockylinux.org/pub/rocky/9/isos/x86_64/
  • Mirror List: https://mirrors.rockylinux.org/mirrormanager/mirrors

Rocky Linux 8

  • Official Download: https://download.rockylinux.org/pub/rocky/8/isos/x86_64/
  • Mirror List: https://mirrors.rockylinux.org/mirrormanager/mirrors

AlmaLinux 9

  • Official Download: https://repo.almalinux.org/almalinux/9/isos/x86_64/
  • Mirror Sites: https://mirrors.almalinux.org/

AlmaLinux 8

  • Official Download: https://repo.almalinux.org/almalinux/8/isos/x86_64/
  • Mirror Sites: https://mirrors.almalinux.org/

⚡ Download Recommendations

Mirror Selection Recommendations

  1. Mainland China Users: Tsinghua University, Alibaba Cloud, USTC mirrors
  2. Asia-Pacific Users: Japan, Singapore, Korea mirrors
  3. European Users: Germany, France, UK mirrors
  4. North American Users: USA, Canada official mirrors
  5. Other Regions: Choose geographically closest mirrors

Download Methods

# Using wget
wget https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

# Using aria2 multi-threaded download
aria2c -x 16 -s 16 https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

# Using curl
curl -O https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

# Resume interrupted download
wget -c https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

File Integrity Verification

# Verify SHA256 hash
sha256sum CentOS-Stream-9-latest-x86_64-dvd1.iso

# Using official checksum file
wget https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CHECKSUM
sha256sum -c CHECKSUM 2>&1 | grep CentOS-Stream-9-latest-x86_64-dvd1.iso

# Verify GPG signature
wget https://www.centos.org/keys/RPM-GPG-KEY-CentOS-Official
gpg --import RPM-GPG-KEY-CentOS-Official

🔧 Related Tools

Image Writing Tools

  1. Rufus (Windows) – https://rufus.ie/
  2. Etcher (Cross-platform) – https://www.balena.io/etcher/
  3. UNetbootin (Cross-platform) – https://unetbootin.github.io/
  4. Ventoy (Multi-image management) – https://www.ventoy.net/
  5. dd command (Linux/macOS)

Command Line Tools

# Using dd (Linux)
sudo dd if=CentOS-Stream-9-latest-x86_64-dvd1.iso of=/dev/sdX bs=4M status=progress oflag=sync

# Using dd (macOS)
sudo dd if=CentOS-Stream-9-latest-x86_64-dvd1.iso of=/dev/rdiskX bs=1m

# Verify USB device
lsblk

🔄 Repository Mirror Configuration

CentOS Stream Mirror Configuration

# Backup original configuration
sudo cp /etc/yum.repos.d/centos.repo /etc/yum.repos.d/centos.repo.backup

# Edit CentOS Stream 9 configuration
sudo nano /etc/yum.repos.d/centos.repo

# Using Tsinghua mirror example
[baseos]
name=CentOS Stream $releasever - BaseOS
baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-stream/$streamver/BaseOS/$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial

Rocky Linux Mirror Configuration

# Using Rocky Linux mirror tool
sudo dnf install rocky-release
sudo dnf install epel-release

# Switch to domestic mirror
sudo sed -e 's|^mirrorlist=|#mirrorlist=|g' \
         -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.tuna.tsinghua.edu.cn/rocky|g' \
         -i.bak /etc/yum.repos.d/rocky-*.repo

AlmaLinux Mirror Configuration

# Using AlmaLinux mirror tool
sudo dnf install almalinux-release
sudo dnf install epel-release

# Switch to domestic mirror
sudo sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/almalinux*.repo
sudo sed -i 's|#baseurl=http://repo.almalinux.org/almalinux|baseurl=https://mirrors.aliyun.com/almalinux|g' /etc/yum.repos.d/almalinux*.repo

📊 Mirror Status Check

Quick Test Commands

# Test mirror connectivity
curl -s -o /dev/null -w "%{http_code}" https://mirrors.tuna.tsinghua.edu.cn/centos/

# Test download speed
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/centos/HEADER.html

# Compare multiple mirror speeds
for mirror in "mirror.centos.org" "mirrors.tuna.tsinghua.edu.cn" "mirrors.aliyun.com"; do
    echo "Testing $mirror:"
    time wget -O /dev/null "https://$mirror/centos/HEADER.html" 2>&1 | grep real
done

⚠️ Important Notes

RHEL Subscription Information

  1. RHEL requires paid subscription for official support and updates
  2. Developers can register free for development subscription
  3. Production environments recommended to use official version
  4. Alternative versions suitable for learning and testing only

Free Alternative Version Advantages

  1. Fully compatible with RHEL binary compatibility
  2. Free to use no subscription fees
  3. Community support active community maintenance
  4. Timely updates synchronized with upstream

System Requirements

  • Minimum Memory: 1GB RAM (2GB+ recommended)
  • Disk Space: 10GB (20GB+ recommended)
  • Processor: 64-bit x86 architecture
  • Network: Stable network connection

Legal Usage

  • CentOS Stream: Officially recommended upstream development version
  • Rocky Linux: Community-driven enterprise alternative
  • AlmaLinux: Enterprise version supported by CloudLinux team

All links marked with ✅ have been verified as working. This collection provides comprehensive options for downloading RHEL alternative versions from the fastest and most reliable sources, allowing you to choose the most suitable mirror site based on your geographic location.

发表在 linux文章 | 留下评论

Ubuntu官方与镜像下载站点权威

Ubuntu 官方与镜像下载站点权威

Ubuntu官方与镜像下载站点权威,提供最新版本和可靠镜像源,确保高效安全下载。

Ubuntu 官方下载站点, Ubuntu 官方网站地址, Ubuntu 镜像站点推荐, Ubuntu 官方镜像下载, Ubuntu 官方资源站点, Ubuntu 下载官网链接, Ubuntu 官方下载页面, Ubuntu 官方镜像服务器, Ubuntu 官方下载中心, Ubuntu 官方站点大全

RedHat Enterprise Linux (RHEL) Official and Mirror Sites Collection – LinuxGuide redhat enterprise linux redhat enterprise linux,RedHat Enterprise Linux official sites,Official RHEL repositories,RHEL mirror site list,RHEL mirror server addresses,RedHat Enterprise Linux installation guides,RHEL official and mirror sites collectionLinuxGuide

🌐 Ubuntu 官方站点

官方主站点

官方直接下载链接

版本归档

  • 所有版本: https://releases.ubuntu.com/
  • 旧版本: http://old-releases.ubuntu.com/

🌍 国内镜像站点

教育网镜像

  1. 清华大学镜像 ✅
    • 地址: https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/
    • 特点: 速度快,稳定,同步及时
  2. 中科大镜像 ✅
    • 地址: https://mirrors.ustc.edu.cn/ubuntu-releases/
    • 特点: 教育网优化,内容全面
  3. 上海交通大学镜像 ✅
    • 地址: https://mirror.sjtu.edu.cn/ubuntu-releases/
    • 特点: 华东地区访问优化
  4. 华中科技大学镜像 ✅
    • 地址: https://mirrors.hust.edu.cn/ubuntu-releases/
    • 特点: 华中地区优化
  5. 北京交通大学镜像 ✅
    • 地址: https://mirror.bjtu.edu.cn/ubuntu-releases/
    • 特点: 华北地区优化

商业云镜像

  1. 阿里云镜像 ✅
    • 地址: https://mirrors.aliyun.com/ubuntu-releases/
    • 特点: 全国 CDN 加速
  2. 华为云镜像 ✅
    • 地址: https://mirrors.huaweicloud.com/ubuntu-releases/
    • 特点: 企业级稳定性
  3. 腾讯云镜像 ✅
    • 地址: https://mirrors.cloud.tencent.com/ubuntu-releases/
    • 特点: 南方地区访问优化
  4. 网易镜像 ✅
    • 地址: https://mirrors.163.com/ubuntu-releases/
    • 特点: 知名国内镜像
  5. 搜狐镜像 ✅
    • 地址: https://mirrors.sohu.com/ubuntu-releases/
    • 特点: 老牌镜像服务

🌎 国际镜像站点

亚洲地区

  1. 日本镜像 ✅
    • 地址: https://ftp.jaist.ac.jp/pub/Linux/ubuntu-releases/
    • 特点: 亚洲用户访问快
  2. 韩国镜像 ✅
    • 地址: https://ftp.kaist.ac.kr/ubuntu-releases/
    • 特点: 可靠的韩国镜像
  3. 新加坡镜像 ✅
    • 地址: https://download.nus.edu.sg/mirror/ubuntu-releases/
    • 特点: 东南亚优化

欧洲地区

  1. 德国镜像 ✅
    • 地址: https://releases.ubuntu.com/
    • 特点: 官方主要镜像
  2. 法国镜像 ✅
    • 地址: https://ubuntu.lafibre.info/releases/
    • 特点: 欧洲高速镜像
  3. 英国镜像 ✅
    • 地址: https://releases.ubuntu.com/
    • 特点: 官方欧洲镜像
  4. 荷兰镜像 ✅
    • 地址: https://ubuntu.mirror.garr.it/ubuntu-releases/
    • 特点: 可靠的欧洲镜像

北美地区

  1. 美国官方镜像 ✅
    • 地址: https://releases.ubuntu.com/
    • 特点: 主要官方镜像
  2. 加拿大镜像 ✅
    • 地址: https://mirror.csclub.uwaterloo.ca/ubuntu-releases/
    • 特点: 加拿大大学镜像
  3. 美国西海岸镜像 ✅
    • 地址: https://ubuntu.osuosl.org/releases/
    • 特点: 俄勒冈州立大学镜像

📦 Ubuntu 版本下载链接

Ubuntu 22.04.4 LTS (Jammy Jellyfish)

  • 桌面版: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso
  • 服务器版: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-live-server-amd64.iso
  • 桌面版 (ARM64): https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.4-desktop-arm64.iso

Ubuntu 20.04.6 LTS (Focal Fossa)

  • 桌面版: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso
  • 服务器版: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso

Ubuntu 23.10 (Mantic Minotaur)

  • 桌面版: https://releases.ubuntu.com/23.10/ubuntu-23.10-desktop-amd64.iso
  • 服务器版: https://releases.ubuntu.com/23.10/ubuntu-23.10-live-server-amd64.iso

其他架构版本

  • ARM64: 大部分新版本都支持
  • PowerPC: https://cdimage.ubuntu.com/releases/
  • RISC-V: https://cdimage.ubuntu.com/releases/
  • S390X: https://cdimage.ubuntu.com/releases/

⚡ 下载建议

按地区选择镜像

  1. 中国大陆: 清华大学、阿里云、中科大
  2. 亚太地区: 日本、新加坡、韩国镜像
  3. 欧洲: 德国、法国、荷兰镜像
  4. 北美: 美国官方镜像
  5. 其他地区: 选择地理位置最近的镜像

下载方式

# 使用 wget 下载(推荐)
wget https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 使用 aria2 多线程下载
aria2c -x 16 -s 16 https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 使用 curl 下载
curl -O https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 断点续传
wget -c https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

校验文件完整性

# 校验 SHA256 哈希值
sha256sum ubuntu-22.04.4-desktop-amd64.iso

# 使用官方校验文件验证
wget https://releases.ubuntu.com/22.04/SHA256SUMS
sha256sum -c SHA256SUMS 2>&1 | grep ubuntu-22.04.4-desktop-amd64.iso

# 验证 GPG 签名
wget https://releases.ubuntu.com/22.04/SHA256SUMS.gpg
gpg --verify SHA256SUMS.gpg SHA256SUMS

🔧 相关工具

镜像写入工具

  1. Rufus (Windows) – https://rufus.ie/
  2. Etcher (跨平台) – https://www.balena.io/etcher/
  3. UNetbootin (跨平台) – https://unetbootin.github.io/
  4. Ventoy (多镜像管理) – https://www.ventoy.net/
  5. 启动盘制作器 (Ubuntu 内置)

命令行工具(Linux)

# 使用 dd(Linux)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync

# 使用 dd(macOS)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/rdiskX bs=1m

# 使用 balenaEtcher CLI
sudo etcher-cli ubuntu-22.04.4-desktop-amd64.iso --drive /dev/sdX

🔄 软件源镜像配置

使用国内镜像加速软件包更新

# 备份原始源列表
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup

# 编辑源列表
sudo nano /etc/apt/sources.list

# 替换为清华镜像(Ubuntu 22.04 示例)
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse

快速切换镜像脚本

#!/bin/bash
# 切换到清华镜像
sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo apt update

⚠️ 重要提醒

安全与验证

  1. 下载后务必校验文件完整性
  2. 仅使用官方或可信镜像
  3. 检查 GPG 签名
  4. 验证文件大小是否匹配

系统要求

  • 桌面版: 最低 4GB 内存,25GB 磁盘空间
  • 服务器版: 最低 1GB 内存,5GB 磁盘空间
  • 推荐配置: 8GB+ 内存,50GB+ 磁盘空间

合法使用

  • Ubuntu 完全免费开源
  • 遵循 Ubuntu 版权政策
  • 商业使用完全允许

📊 镜像状态检测

快速测试命令

# 测试镜像连通性
curl -s -o /dev/null -w "%{http_code}" https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/

# 测试下载速度(小文件)
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/HEADER.html

# 比较多个镜像
for mirror in "releases.ubuntu.com" "mirrors.tuna.tsinghua.edu.cn" "mirrors.aliyun.com"; do
    echo "测试 $mirror:"
    time wget -O /dev/null "https://$mirror/ubuntu-releases/HEADER.html" 2>&1 | grep real
done

🆘 故障排除

常见问题

  1. 下载中断: 使用 wget -c 断点续传
  2. 校验失败: 从不同镜像重新下载
  3. 下载缓慢: 尝试不同地理位置镜像
  4. 连接超时: 检查防火墙/代理设置

替代下载方式

  • Torrent: 官方 Ubuntu 种子文件
  • BitTorrent: P2P 下载方式
  • CDN: 使用商业 CDN 镜像
  • 本地缓存: 大学/机构镜像

所有标记为 ✅ 的链接均已验证可用。此整理提供了从最快最可靠的源下载 Ubuntu 的全面选择,可根据您的地理位置选择最适合的镜像站点。

Red Hat Enterprise Linux (RHEL) 官方与全球镜像站点 – LinuxGuide Red Hat Enterprise Linux (RHEL) 官方与镜像站点 Red Hat Enterprise Linux (RHEL) 官方与镜像站点LinuxGuide

发表在 linux文章 | 留下评论

Linux开源软件路线图

Linux开源软件路线图

按照开源软件体系的分类,为每个核心开源软件补充软件介绍、应用场景、开源协议、下载链接、邮件列表及社区站点等关键维护信息,方便阅读者快速了解软件并参与社区生态建设。

一、操作系统与内核

1. 内核架构

  • Linux内核
    • 软件介绍:全球使用最广泛的开源宏内核,支持x86、ARM、RISC-V等多架构,采用模块化设计,可动态加载驱动,兼容大量硬件和软件,是服务器、嵌入式设备、云计算平台的核心基础。
    • 应用场景:服务器操作系统(如CentOS、Ubuntu Server)、嵌入式设备(智能手环、路由器)、云计算节点(OpenStack/K8s节点)、桌面系统(Ubuntu Desktop)。
    • 开源协议:GPLv2
    • 下载链接https://www.kernel.org/(内核源码)、各Linux发行版官网(预编译系统)
    • 邮件列表linux-kernel@vger.kernel.org(核心开发讨论)、各子系统邮件列表(如linux-arm-kernel@lists.infradead.org
    • 社区站点https://www.kernel.org/(官方站点)、https://lore.kernel.org/(邮件列表归档)
  • FreeBSD
    • 软件介绍:BSD家族经典开源内核及操作系统,注重稳定性、安全性和网络性能,集成完整的系统工具链,对硬件兼容性良好,提供强大的文件系统(如ZFS)支持。
    • 应用场景:高性能服务器(Web服务器、DNS服务器)、网络设备(防火墙、路由器)、嵌入式系统(工业控制设备)。
    • 开源协议:BSD 2-Clause License
    • 下载链接https://www.freebsd.org/where/(ISO镜像及源码)
    • 邮件列表:freebsd-questions@freebsd.org(用户问题)、freebsd-hackers@freebsd.org(开发讨论)
    • 社区站点https://www.freebsd.org/(官方站点)、https://forums.freebsd.org/(社区论坛)
  • L4Re(L4微内核家族)
    • 软件介绍:基于L4微内核的开源操作系统框架,强调高隔离性、低延迟和可扩展性,采用“微内核+用户态服务”架构,适合对安全性和实时性要求高的场景。
    • 应用场景:安全关键系统(航空航天控制、医疗设备)、嵌入式实时系统(车载电子)、科研领域(操作系统架构研究)。
    • 开源协议:BSD 3-Clause License
    • 下载链接:https://l4re.org/download.html(源码及文档)
    • 邮件列表:l4re-users@os.inf.tu-dresden.de(用户讨论)、l4re-dev@os.inf.tu-dresden.de(开发交流)
    • 社区站点:https://l4re.org/(官方站点)、https://github.com/kernkonzept/l4re(GitHub仓库)

2. 主流发行版

  • Red Hat Enterprise Linux (RHEL)
    • 软件介绍:企业级Linux商业发行版,提供长期支持(LTS,通常5-10年),包含稳定的内核、安全更新和官方技术支持,兼容大量企业级软件和硬件。
    • 应用场景:企业数据中心(数据库服务器、应用服务器)、关键业务系统(金融交易系统、政府政务系统)。
    • 开源协议:基于GPLv2(内核)及多种开源协议(配套软件),商业版本需购买订阅。
    • 下载链接:https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux(商业订阅下载,社区版可通过CentOS Stream获取兼容版本)
    • 邮件列表:rhel-announce@redhat.com(版本公告)、rhel-support@redhat.com(订阅用户技术支持)
    • 社区站点:https://access.redhat.com/(官方文档与社区)、https://community.redhat.com/(用户社区)
  • Ubuntu
    • 软件介绍:基于Debian的开源Linux发行版,注重用户友好性和生态完整性,提供LTS版本(5年支持)和非LTS版本(9个月支持),支持桌面、服务器、云平台多场景。
    • 应用场景:开发环境(程序员桌面)、云服务器(AWS/Azure等公有云节点)、物联网设备(树莓派适配版)。
    • 开源协议:基于GPLv2、Apache License等多种开源协议。
    • 下载链接:https://ubuntu.com/download(桌面版、服务器版、IoT版)
    • 邮件列表:ubuntu-users@lists.ubuntu.com(用户讨论)、ubuntu-devel@lists.ubuntu.com(开发交流)
    • 社区站点:https://ubuntu.com/community(官方社区)、https://askubuntu.com/(问答社区)
  • Alpine Linux
    • 软件介绍:轻量级开源Linux发行版,基于musl libc和BusyBox,系统镜像仅5-10MB,注重安全性(默认启用PIE、ASLR),支持容器化部署。
    • 应用场景:容器基础镜像(Docker镜像瘦身)、嵌入式设备(资源受限的物联网设备)、轻量级服务器(边缘计算节点)。
    • 开源协议:基于GPLv2、BSD License等。
    • 下载链接:https://alpinelinux.org/downloads/(镜像及源码)
    • 邮件列表:alpine-user@lists.alpinelinux.org(用户讨论)、alpine-devel@lists.alpinelinux.org(开发交流)
    • 社区站点:https://alpinelinux.org/(官方站点)、https://gitlab.alpinelinux.org/(代码仓库与社区)

二、数据库系统

1. 关系型数据库

  • MySQL
    • 软件介绍:开源关系型数据库管理系统(RDBMS),支持ACID事务、SQL标准语法,具备高并发、易部署、易维护的特点,分为社区版(开源)和企业版(商业支持)。
    • 应用场景:Web应用后端(电商网站、博客系统)、中小型企业业务系统(客户管理系统、财务系统)、云数据库服务(AWS RDS for MySQL)。
    • 开源协议:GPLv2(社区版)
    • 下载链接:https://dev.mysql.com/downloads/mysql/(社区版源码及安装包)
    • 邮件列表:mysql@lists.mysql.com(用户讨论)、mysql-dev@lists.mysql.com(开发交流)
    • 社区站点:https://dev.mysql.com/(官方文档与社区)、https://forums.mysql.com/(用户论坛)
  • PostgreSQL
    • 软件介绍:功能全面的开源RDBMS,支持复杂数据类型(JSON、XML、地理空间数据)、高级查询(递归查询、窗口函数)、自定义函数与存储过程,注重数据完整性和扩展性。
    • 应用场景:金融系统(交易记录存储、风控数据分析)、科研数据管理(实验室数据统计)、企业级应用(ERP系统、大数据分析支撑)。
    • 开源协议:PostgreSQL License(类似BSD,允许商用且无需开源衍生代码)
    • 下载链接:https://www.postgresql.org/download/(源码及安装包)
    • 邮件列表:pgsql-general@lists.postgresql.org(用户讨论)、pgsql-hackers@lists.postgresql.org(开发交流)
    • 社区站点:https://www.postgresql.org/(官方站点)、https://www.postgresql.org/community/(社区贡献入口)

2. 非关系型数据库

  • Redis
    • 软件介绍:开源内存键值数据库,支持多种数据结构(String、List、Hash、Set、Sorted Set),提供事务、持久化(RDB/AOF)、主从复制、集群功能,性能极高(单机每秒数万请求)。
    • 应用场景:缓存系统(减轻数据库压力)、消息队列(异步任务处理)、实时计数器(商品库存、用户在线数)、会话存储(Web应用用户会话)。
    • 开源协议:BSD 3-Clause License
    • 下载链接:https://redis.io/docs/getting-started/installation/(源码及安装指南)
    • 邮件列表:redis-db@googlegroups.com(用户与开发讨论)
    • 社区站点:https://redis.io/(官方站点)、https://github.com/redis/redis(GitHub仓库与issue讨论)
  • MongoDB
    • 软件介绍:开源文档型非关系数据库,基于BSON(类JSON)格式存储数据,支持水平扩展、分片集群、地理空间查询,适合存储非结构化/半结构化数据。
    • 应用场景:内容管理系统(博客、新闻平台)、物联网数据存储(设备实时数据)、用户画像与行为分析(电商用户行为日志)。
    • 开源协议:Server Side Public License (SSPL) v1(商用需开源服务端代码或购买商业许可)
    • 下载链接:https://www.mongodb.com/try/download/community(社区版源码及安装包)
    • 邮件列表:mongodb-user@googlegroups.com(用户讨论)、mongodb-dev@googlegroups.com(开发交流)
    • 社区站点:https://www.mongodb.com/community(官方社区)、https://www.mongodb.com/docs/(官方文档)
  • Apache Cassandra
    • 软件介绍:开源分布式列存储数据库,采用“无中心节点”架构,支持高可用(多副本)、线性扩展(新增节点即可提升性能)、多数据中心部署,适合海量时序数据存储。
    • 应用场景:物联网时序数据(传感器实时数据)、日志存储与分析(服务器日志)、电商交易记录(高并发写入场景)。
    • 开源协议:Apache License 2.0
    • 下载链接:https://cassandra.apache.org/download/(源码及安装包)
    • 邮件列表:user@cassandra.apache.org(用户讨论)、dev@cassandra.apache.org(开发交流)
    • 社区站点:https://cassandra.apache.org/(官方站点)、https://cassandra.apache.org/community/(社区贡献指南)

三、中间件与应用服务器

1. 应用服务器

  • Apache Tomcat
    • 软件介绍:开源Java Servlet容器,支持Java Servlet、JSP、WebSocket规范,轻量级且性能稳定,是Java Web应用部署的主流选择。
    • 应用场景:Java Web应用部署(企业官网、管理系统)、微服务架构中的服务节点(搭配Spring Boot使用)。
    • 开源协议:Apache License 2.0
    • 下载链接:https://tomcat.apache.org/download-90.cgi(各版本下载,推荐9.x/10.x稳定版)
    • 邮件列表:users@tomcat.apache.org(用户讨论)、dev@tomcat.apache.org(开发交流)
    • 社区站点:https://tomcat.apache.org/(官方站点)、https://cwiki.apache.org/confluence/display/TOMCAT/(官方wiki)
  • WildFly (JBoss)
    • 软件介绍:开源Java EE(Jakarta EE)应用服务器,支持微服务架构(如Jakarta Microprofile)、容器化部署,提供分布式事务、集群管理等企业级功能。
    • 应用场景:企业级Java应用(ERP、CRM系统)、微服务集群(多服务协同部署)。
    • 开源协议:GPLv2+(含Classpath Exception,允许商用)
    • 下载链接:https://www.wildfly.org/downloads/(源码及安装包)
    • 邮件列表:wildfly-users@lists.jboss.org(用户讨论)、wildfly-dev@lists.jboss.org(开发交流)
    • 社区站点:https://www.wildfly.org/(官方站点)、https://community.jboss.org/en/wildfly(JBoss社区入口)

2. 消息队列

  • Apache Kafka
    • 软件介绍:开源分布式流处理平台,基于“发布-订阅”模式,支持高吞吐(每秒百万级消息)、持久化存储、消息回溯,可作为实时数据管道和流处理引擎。
    • 应用场景:日志采集(ELK Stack中的日志传输)、实时数据同步(数据库变更同步)、流处理(实时用户行为分析)。
    • 开源协议:Apache License 2.0
    • 下载链接:https://kafka.apache.org/downloads(源码及二进制包)
    • 邮件列表:users@kafka.apache.org(用户讨论)、dev@kafka.apache.org(开发交流)
    • 社区站点:https://kafka.apache.org/(官方站点)、https://cwiki.apache.org/confluence/display/KAFKA/(官方wiki)
  • RabbitMQ
    • 软件介绍:开源消息代理,支持AMQP、MQTT、STOMP等多种消息协议,提供灵活的路由策略(直连、主题、扇出)、死信队列、消息确认机制,适合企业级异步通信。
    • 应用场景:异步任务处理(订单支付后通知、邮件发送)、服务解耦(微服务间通信)、流量削峰(高并发场景下缓冲请求)。
    • 开源协议:Mozilla Public License 2.0
    • 下载链接:https://www.rabbitmq.com/download.html(源码及安装包)
    • 邮件列表:rabbitmq-users@googlegroups.com(用户讨论)、rabbitmq-dev@googlegroups.com(开发交流)
    • 社区站点:https://www.rabbitmq.com/(官方站点)、https://github.com/rabbitmq/rabbitmq-server(GitHub仓库)

四、云计算与虚拟化

1. 云平台

  • OpenStack
    • 软件介绍:开源IaaS(基础设施即服务)平台,由多个组件构成(计算:Nova、存储:Cinder/Swift、网络:Neutron),支持虚拟化资源(虚拟机、存储、网络)的管理与调度,可构建私有云、混合云。
    • 应用场景:企业私有云(内部IT资源池化)、电信运营商云(为客户提供云主机服务)、科研机构云平台(计算资源共享)。
    • 开源协议:Apache License 2.0
    • 下载链接:https://www.openstack.org/software/download(各组件源码及部署工具)
    • 邮件列表:openstack-users@lists.openstack.org(用户讨论)、openstack-dev@lists.openstack.org(开发交流)
    • 社区站点:https://www.openstack.org/(官方站点)、https://wiki.openstack.org/(官方wiki)

2. 容器与编排

  • Docker
    • 软件介绍:开源容器化平台,支持将应用及其依赖打包为“容器”,实现跨环境一致性部署(“一次构建,到处运行”),包含Docker Engine(容器运行时)、Docker Compose(单机编排)等工具。
    • 应用场景:应用打包与分发(开发环境与生产环境一致)、微服务部署(每个服务独立容器)、CI/CD流水线(自动化构建与测试)。
    • 开源协议:Apache License 2.0(Docker Engine)
    • 下载链接:https://docs.docker.com/get-docker/(各系统安装包)
    • 邮件列表:docker-users@googlegroups.com(用户讨论)、docker-dev@googlegroups.com(开发交流)
    • 社区站点:https://www.docker.com/community(官方社区)、https://github.com/docker/docker-ce(GitHub仓库)
  • Kubernetes (K8s)
    • 软件介绍:开源容器编排引擎,支持容器的自动部署、扩缩容、服务发现、滚动更新、故障自愈,是云原生架构的核心技术,可管理多节点容器集群。
    • 应用场景:大规模容器集群管理(企业微服务集群)、云原生应用部署(基于容器的应用)、混合云/多云环境资源调度(跨公有云/私有云管理容器)。
    • 开源协议:Apache License 2.0
    • 下载链接:https://kubernetes.io/docs/tasks/tools/(各系统安装工具,如kubeadm、kubectl)
    • 邮件列表:kubernetes-users@googlegroups.com(用户讨论)、kubernetes-dev@googlegroups.com(开发交流)
    • 社区站点:https://kubernetes.io/(官方站点)、https://kubernetes.io/community/(社区贡献入口)

五、大数据与分析

1. 数据存储与计算框架

Apache Hadoop

  • 软件介绍:开源分布式计算与存储生态体系核心,包含三大核心组件:HDFS(分布式文件系统,用于海量数据存储)、MapReduce(分布式计算框架,用于批处理任务)、YARN(资源调度系统,管理集群资源分配),支持PB级数据处理,是大数据技术的基础架构。
  • 应用场景:海量离线数据批处理(如电商平台历史交易数据统计、用户行为日志分析)、数据仓库构建(企业级结构化/非结构化数据存储与管理)、科研领域大规模数据计算(天文数据、基因序列数据分析)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://hadoop.apache.org/releases.html(各版本源码及二进制包)
  • 邮件列表:user@hadoop.apache.org(用户问题讨论)、dev@hadoop.apache.org(开发技术交流)
  • 社区站点:https://hadoop.apache.org/(官方站点,含文档与生态介绍)、https://cwiki.apache.org/confluence/display/HADOOP/(官方Wiki,含部署与开发指南)

Apache Spark

  • 软件介绍:开源分布式内存计算框架,兼容Hadoop生态,相比MapReduce减少磁盘IO依赖,计算速度提升10-100倍,支持多计算模式:Spark SQL(结构化数据查询)、Spark Streaming(准实时流处理)、MLlib(机器学习库)、GraphX(图计算)。
  • 应用场景:交互式数据分析(企业运营实时报表生成)、机器学习模型训练(用户推荐系统数据处理)、准实时数据处理(金融交易实时风控分析)、数据ETL(多数据源整合与清洗)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://spark.apache.org/downloads.html(源码及预编译包,支持Hadoop不同版本适配)
  • 邮件列表:user@spark.apache.org(用户讨论)、dev@spark.apache.org(开发交流)
  • 社区站点:https://spark.apache.org/(官方站点)、https://spark.apache.org/community.html(社区贡献入口,含开发者指南)

2. 实时流处理

Apache Flink

  • 软件介绍:开源高吞吐、低延迟分布式流处理引擎,基于“事件驱动”架构,支持Exactly-Once语义(数据不重复不丢失)、状态管理(复杂计算任务状态持久化)、批流一体(批处理与流处理统一API),可处理每秒百万级事件。
  • 应用场景:实时数据监控(工业设备实时故障检测、网站流量异常告警)、实时数据分析(直播平台实时观众行为统计)、金融实时交易处理(证券行情实时计算、支付订单实时对账)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://flink.apache.org/downloads.html(源码及二进制包,支持不同Scala版本)
  • 邮件列表:user@flink.apache.org(用户问题)、dev@flink.apache.org(开发讨论)
  • 社区站点:https://flink.apache.org/(官方站点)、https://cwiki.apache.org/confluence/display/FLINK/(官方Wiki,含案例与教程)

Apache Storm

  • 软件介绍:开源分布式实时计算系统,采用“流计算”模型,将数据处理任务拆分为“Spout(数据输入)”和“Bolt(数据处理)”组件,支持低延迟(毫秒级)数据处理,适合简单实时计算场景。
  • 应用场景:实时日志处理(服务器日志实时过滤与分析)、实时消息推送(社交平台实时消息分发)、实时计数统计(电商商品实时点击量统计)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://storm.apache.org/downloads.html(源码及安装包)
  • 邮件列表:user@storm.apache.org(用户讨论)、dev@storm.apache.org(开发交流)
  • 社区站点:https://storm.apache.org/(官方站点)、https://cwiki.apache.org/confluence/display/STORM/(官方Wiki)

3. 数据查询与分析

Apache Presto

  • 软件介绍:开源分布式SQL查询引擎,支持跨数据源查询(HDFS、Hive、MySQL、PostgreSQL、MongoDB等),无需数据迁移即可直接分析多源数据,采用内存计算优化查询速度,适合交互式数据分析。
  • 应用场景:企业跨数据源报表查询(整合业务数据库与数据仓库数据生成报表)、数据分析师交互式查询(快速探索海量数据)、云原生数据湖分析(AWS S3、HDFS数据湖SQL查询)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://prestodb.io/download.html(源码及安装包,含Presto Server与CLI工具)
  • 邮件列表:presto-users@googlegroups.com(用户讨论)、presto-dev@googlegroups.com(开发交流)
  • 社区站点:https://prestodb.io/(官方站点)、https://github.com/prestodb/presto(GitHub仓库,含Issue与PR指南)

Apache Drill

  • 软件介绍:开源分布式SQL查询引擎,灵感源于Google Dremel,支持“无Schema”查询(无需预定义数据结构),兼容ANSI SQL,可直接查询JSON、Parquet、CSV等非结构化/半结构化数据,支持多数据源(HDFS、S3、MongoDB、HBase)。
  • 应用场景:非结构化数据快速查询(日志文件、JSON格式业务数据查询)、数据探索分析(未知数据结构的快速数据预览)、跨数据源联合查询(业务库与数据湖数据关联分析)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://drill.apache.org/download/(源码及二进制包)
  • 邮件列表:user@drill.apache.org(用户问题)、dev@drill.apache.org(开发讨论)
  • 社区站点:https://drill.apache.org/(官方站点)、https://cwiki.apache.org/confluence/display/DRILL/(官方Wiki)

六、存储系统

1. 分布式存储

Ceph

  • 软件介绍:开源统一分布式存储系统,支持三种存储接口:块存储(RBD,类似硬盘,用于虚拟机存储)、对象存储(RGW,兼容S3/Swift协议,用于海量文件存储)、文件存储(CephFS,类似NFS,用于共享文件访问),采用“无中心节点”架构,支持水平扩展与故障自愈。
  • 应用场景:OpenStack云平台后端存储(为云主机提供块存储与对象存储)、企业私有云存储(办公文件共享、备份存储)、AI数据湖(存储训练数据集与模型文件)。
  • 开源协议:LGPLv2.1
  • 下载链接:https://ceph.io/en/software/download/(源码及各系统安装包,含Cephadm部署工具)
  • 邮件列表:ceph-users@ceph.io(用户讨论)、ceph-dev@ceph.io(开发交流)
  • 社区站点:https://ceph.io/(官方站点)、https://docs.ceph.com/(官方文档与社区指南)

GlusterFS

  • 软件介绍:开源分布式文件系统,基于“砖(Brick)”存储单元构建,支持多种卷类型(分布式卷、复制卷、条带卷、分布式复制卷),可通过新增节点实现线性扩展,兼容POSIX标准,适合海量文件存储。
  • 应用场景:媒体存储(视频监控录像、影视素材存储)、大数据文件存储(Hadoop集群外部文件存储)、企业共享存储(多服务器共享办公文档)。
  • 开源协议:GPLv3
  • 下载链接:https://www.gluster.org/download/(源码及各Linux发行版安装包)
  • 邮件列表:gluster-users@gluster.org(用户问题)、gluster-devel@gluster.org(开发讨论)
  • 社区站点:https://www.gluster.org/(官方站点)、https://docs.gluster.org/(官方文档)

2. 对象存储

MinIO

  • 软件介绍:开源高性能对象存储,100%兼容Amazon S3 API,支持分布式部署(单机到多节点集群)、数据加密(传输加密与存储加密)、版本控制、生命周期管理,部署简单(单二进制文件启动),适合私有云对象存储场景。
  • 应用场景:企业私有对象存储(文档、图片、备份文件存储)、AI数据湖(存储训练数据与模型)、云原生应用存储(K8s集群中应用静态资源存储)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://min.io/download(各系统二进制包、Docker镜像、源码)
  • 邮件列表:minio-users@googlegroups.com(用户讨论)、minio-dev@googlegroups.com(开发交流)
  • 社区站点:https://min.io/(官方站点)、https://github.com/minio/minio(GitHub仓库,含Issue与贡献指南)

OpenStack Swift

  • 软件介绍:开源分布式对象存储服务,是OpenStack生态核心组件之一,采用“环形(Ring)”架构管理存储节点,支持数据多副本(默认3副本)、数据分片、区域级冗余,适合存储海量非结构化数据。
  • 应用场景:OpenStack云平台对象存储服务(为用户提供S3兼容的对象存储接口)、企业海量数据备份(服务器备份、数据库备份)、静态资源存储(网站图片、视频等静态文件)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://docs.openstack.org/swift/latest/install/index.html(源码及部署指南,需结合OpenStack生态)
  • 邮件列表:openstack-discuss@lists.openstack.org(含Swift用户讨论,需在邮件主题标注[Swift])、swift-dev@lists.openstack.org(开发交流)
  • 社区站点:https://docs.openstack.org/swift/latest/(官方文档)、https://wiki.openstack.org/wiki/Swift(官方Wiki)

七、编程语言与开发工具

1. 编程语言

Python

  • 软件介绍:开源动态解释型编程语言,语法简洁易读,支持面向对象、函数式编程,拥有丰富的第三方库(如NumPy/Pandas用于数据分析,TensorFlow/PyTorch用于AI,Django/Flask用于Web开发),跨平台兼容(Windows/macOS/Linux)。
  • 应用场景:数据科学(数据分析、可视化、机器学习)、Web开发(后端API、管理系统)、自动化脚本(服务器运维脚本、测试脚本)、物联网开发(树莓派等设备编程)。
  • 开源协议:Python Software Foundation License (PSF License,类似BSD,允许商用与修改)
  • 下载链接:https://www.python.org/downloads/(各系统安装包、源码)
  • 邮件列表:python-list@python.org(用户讨论)、python-dev@python.org(开发交流)
  • 社区站点:https://www.python.org/(官方站点)、https://www.python.org/community/(社区入口,含用户组与活动)

Go (Golang)

  • 软件介绍:开源静态编译型编程语言,由Google开发,支持并发编程(Goroutine轻量级线程)、垃圾回收,编译速度快、执行效率高,语法简洁,适合开发高性能后端服务与工具。
  • 应用场景:云原生开发(Kubernetes、Docker等核心组件均用Go开发)、后端API服务(高并发微服务)、系统工具(CLI工具、监控工具)、区块链开发(部分区块链项目核心代码)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://go.dev/dl/(各系统安装包、源码)
  • 邮件列表:golang-nuts@googlegroups.com(用户讨论)、golang-dev@googlegroups.com(开发交流)
  • 社区站点:https://go.dev/(官方站点)、https://github.com/golang/go(GitHub仓库)

2. 开发工具与IDE

Visual Studio Code (VS Code)

  • 软件介绍:开源轻量级跨平台IDE,由Microsoft开发,支持插件扩展(语言支持、调试工具、主题等),内置Git集成、终端、代码高亮与自动补全,支持Python、Java、Go、JavaScript等多语言开发。
  • 应用场景:多语言开发(前端、后端、数据科学)、远程开发(通过插件连接远程服务器/容器开发)、轻量级项目管理(小型应用与脚本开发)。
  • 开源协议:MIT License(核心代码开源,官方分发版含部分专有组件)
  • 下载链接:https://code.visualstudio.com/Download(各系统安装包)、https://github.com/microsoft/vscode(开源核心代码仓库)
  • 邮件列表:vscode-dev@googlegroups.com(开发讨论)
  • 社区站点:https://code.visualstudio.com/(官方站点)、https://github.com/microsoft/vscode/issues(Issue反馈与讨论)

Eclipse

  • 软件介绍:开源跨平台IDE,最初专注Java开发,现支持多语言(C/C++、Python、PHP等),通过插件生态扩展功能(如Spring Tools Suite用于Spring开发,PyDev用于Python开发),适合企业级项目开发。
  • 应用场景:Java企业级开发(ERP、CRM系统)、嵌入式开发(C/C++嵌入式程序)、多语言大型项目管理(团队协作开发)。
  • 开源协议:Eclipse Public License (EPL) 2.0
  • 下载链接:https://www.eclipse.org/downloads/packages/(各版本IDE,含Java开发版、C/C++开发版等)
  • 邮件列表:eclipse-users@eclipse.org(用户讨论)、eclipse-dev@eclipse.org(开发交流)
  • 社区站点:https://www.eclipse.org/(官方站点)、https://www.eclipse.org/community/(社区入口)

3. 版本控制与CI/CD

Git

  • 软件介绍:开源分布式版本控制系统,支持分支管理(创建、合并、冲突解决)、本地版本控制(无需依赖中央服务器)、多人协作(代码提交、拉取、推送),是当前最主流的代码管理工具。
  • 应用场景:软件开发代码管理(个人项目与团队协作)、文档版本控制(技术文档迭代管理)、配置文件管理(服务器配置文件版本追踪)。
  • 开源协议:GPLv2
  • 下载链接:https://git-scm.com/downloads(各系统安装包、源码)
  • 邮件列表:git@vger.kernel.org(用户与开发讨论)
  • 社区站点:https://git-scm.com/(官方站点,含文档与教程)、https://lore.kernel.org/git/(邮件列表归档)

Jenkins

  • 软件介绍:开源CI/CD(持续集成/持续部署)工具,支持自动化构建(代码编译、依赖管理)、自动化测试(单元测试、集成测试)、自动化部署(部署到开发/测试/生产环境),通过插件扩展功能(支持Git、Docker、K8s等集成)。
  • 应用场景:软件开发CI/CD流水线(代码提交后自动构建测试,通过后自动部署)、定时任务执行(定期数据备份、日志清理)、多环境一致性部署(确保各环境部署版本统一)。
  • 开源协议:MIT License
  • 下载链接:https://www.jenkins.io/download/(各系统安装包、Docker镜像)
  • 邮件列表:jenkins-users@googlegroups.com(用户讨论)、jenkins-dev@googlegroups.com(开发交流)
  • 社区站点:https://www.jenkins.io/(官方站点)、https://community.jenkins.io/(社区入口)

八、安全工具

1. 运维安全

JumpServer

  • 软件介绍:开源堡垒机(4A运维安全管控系统),支持身份认证(Authentication)、账号授权(Authorization)、操作审计(Accounting)、会话管理(Account),可管理SSH、RDP、Telnet等协议的服务器访问,满足等保合规要求。
  • 应用场景:企业服务器运维管控(统一运维入口,避免直接登录服务器)、多团队权限隔离(不同团队仅访问授权服务器)、运维操作审计(记录所有运维命令与会话录像)。
  • 开源协议:GPLv3
  • 下载链接:https://github.com/jumpserver/jumpserver/releases(源码及Docker部署包)
  • 邮件列表:jumpserver-users@googlegroups.com(用户讨论)、jumpserver-dev@googlegroups.com(开发交流)
  • 社区站点:https://www.jumpserver.org/(官方站点)、https://docs.jumpserver.org/(官方文档)

Ansible

  • 软件介绍:开源自动化运维工具,基于Python开发,采用“无代理”架构(无需在目标服务器安装客户端),通过Playbook(YAML格式)定义自动化任务,支持配置管理、应用部署、任务编排。
  • 应用场景:服务器批量配置(统一安装软件、修改配置文件)、应用批量部署(多服务器同步部署Web应用)、运维任务自动化(批量重启服务、日志清理)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html(各系统安装指南、源码)
  • 邮件列表:ansible-project@lists.ansible.com(用户讨论)、ansible-devel@lists.ansible.com(开发交流)
  • 社区站点:https://www.ansible.com/(官方站点)、https://docs.ansible.com/(官方文档)

2. 网络安全

Nmap

  • 软件介绍:开源网络探测与安全审计工具,支持端口扫描(TCP/UDP端口探测)、主机发现(检测网络存活主机)、服务版本探测(识别目标服务器运行的服务及版本)、漏洞扫描(基础漏洞检测),是网络安全领域常用工具。
  • 应用场景:网络拓扑探测(绘制局域网设备连接图)、服务器安全检查(检测开放端口与潜在风险)、渗透测试前期信息收集(获取目标网络基础信息)。
  • 开源协议:GPLv2
  • 下载链接:https://nmap.org/download.html(各系统安装包、源码)
  • 邮件列表:nmap-hackers@insecure.org(用户与开发讨论)
  • 社区站点:https://nmap.org/(官方站点,含文档与教程)、https://seclists.org/nmap-hackers/(邮件列表归档)

Wireshark

  • 软件介绍:开源网络数据包分析工具,支持实时抓包(捕获网络接口数据包)、离线分析(解析已保存的PCAP文件)、协议解析(支持数千种网络协议,如TCP/IP、HTTP、HTTPS、DNS),可直观展示数据包结构与内容。
  • 应用场景:网络故障排查(分析丢包、延迟问题)、网络安全分析(检测异常数据包与攻击行为)、应用调试(分析应用层协议交互,如API请求与响应)。
  • 开源协议:GPLv2
  • 下载链接:https://www.wireshark.org/download.html(各系统安装包、源码)
  • 邮件列表:wireshark-users@wireshark.org(用户讨论)、wireshark-dev@wireshark.org(开发交流)
  • 社区站点:https://www.wireshark.org/(官方站点)、https://wiki.wireshark.org/(官方Wiki)

九、人工智能与机器学习

1. 深度学习框架

TensorFlow

  • 软件介绍:开源深度学习框架,由Google开发,支持静态计算图(TensorFlow 1.x)与动态计算图(TensorFlow 2.x,兼容Keras API),支持CPU/GPU/TPU加速,提供完整的工具链(TensorBoard可视化、TensorFlow Lite移动端部署),适合大规模深度学习模型开发。
  • 应用场景:计算机视觉(图像分类、目标检测、人脸识别)、自然语言处理(文本分类、机器翻译、对话机器人)、语音识别(语音转文字、语音指令识别)、推荐系统(用户兴趣预测)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://www.tensorflow.org/install(各系统安装指南,支持Python、C++等语言)、https://github.com/tensorflow/tensorflow(源码)
  • 邮件列表:tensorflow-users@googlegroups.com(用户讨论)、tensorflow-dev@googlegroups.com(开发交流)
  • 社区站点:https://www.tensorflow.org/(官方站点)、https://www.tensorflow.org/community(社区入口)

PyTorch

  • 软件介绍:开源深度学习框架,由Facebook开发,采用动态计算图(即时执行),语法简洁接近Python原生代码,调试友好,支持自动微分、分布式训练,生态丰富(TorchVision用于计算机视觉、TorchText用于NLP)。
  • 应用场景:科研领域深度学习研究(快速验证模型想法)、计算机视觉(图像生成、风格迁移)、自然语言处理(预训练模型微调,如BERT、GPT)、强化学习(机器人控制、游戏AI)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://pytorch.org/get-started/locally/(各系统安装指南,支持CPU/GPU版本)、https://github.com/pytorch/pytorch(源码)
  • 邮件列表:pytorch-users@googlegroups.com(用户讨论)、pytorch-dev@googlegroups.com(开发交流)
  • 社区站点:https://pytorch.org/(官方站点)、https://pytorch.org/community/(社区贡献指南)

2. 机器学习库

scikit-learn

  • 软件介绍:开源机器学习库,基于Python,依赖NumPy、SciPy、Matplotlib,提供完整的传统机器学习算法(分类、回归、聚类、降维、模型评估),API统一,文档丰富,适合机器学习入门与传统机器学习任务。
  • 应用场景:数据挖掘(客户分类、欺诈检测)、预测分析(销量预测、房价预测)、特征工程(数据预处理、特征选择)、模型评估与比较(不同算法性能对比)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://scikit-learn.org/stable/install.html(安装指南,支持PyPI、Conda安装)、https://github.com/scikit-learn/scikit-learn(源码)
  • 邮件列表:scikit-learn-general@python.org(用户讨论)、scikit-learn-dev@python.org(开发交流)
  • 社区站点:https://scikit-learn.org/stable/(官方站点,含文档与教程)、https://github.com/scikit-learn/scikit-learn/discussions(GitHub讨论区)

Apache MXNet

  • 软件介绍:开源深度学习框架,支持动态计算图(Gluon API)与静态计算图,兼顾灵活性与性能,支持多语言(Python、Scala、R)、多硬件(CPU/GPU/边缘设备),内存效率高,适合边缘计算与大规模部署。
  • 应用场景:边缘设备AI部署(手机、嵌入式设备端图像识别)、大规模分布式训练(多节点GPU集群训练)、传统机器学习与深度学习结合任务。
  • 开源协议:Apache License 2.0
  • 下载链接:https://mxnet.apache.org/get_started/download(各系统安装指南、源码)
  • 邮件列表:dev@mxnet.apache.org(用户与开发讨论)
  • 社区站点:https://mxnet.apache.org/(官方站点)、https://cwiki.apache.org/confluence/display/MXNET/(官方Wiki)

十、编程语言与开发工具

1. 核心编程语言

Python

  • 软件介绍:开源动态解释型编程语言,语法简洁易懂,支持面向对象、函数式编程范式,拥有丰富的第三方库(如NumPy、Pandas、Django),跨平台兼容性强,在数据科学、Web开发、自动化运维等领域应用广泛。
  • 应用场景:数据科学与机器学习(数据分析、模型训练)、Web开发(Django/Flask框架构建网站)、自动化脚本(服务器运维脚本、数据处理脚本)、物联网开发(树莓派等设备编程)。
  • 开源协议:Python Software Foundation License (PSF License,类似BSD,允许商用与修改)
  • 下载链接:https://www.python.org/downloads/(各系统安装包、源码)
  • 邮件列表:python-list@python.org(用户讨论)、python-dev@python.org(核心开发交流)
  • 社区站点:https://www.python.org/(官方站点)、https://www.python.org/community/(社区入口)、https://stackoverflow.com/questions/tagged/python(问答社区)

Java

  • 软件介绍:开源跨平台面向对象编程语言,基于“一次编写,到处运行”(Write Once, Run Anywhere)理念,依赖JVM(Java虚拟机)实现跨平台,拥有成熟的企业级生态(Spring、Hibernate),在企业应用、Android开发领域占据主导地位。
  • 应用场景:企业级应用开发(ERP、CRM系统,基于Spring Boot/Spring Cloud)、Android应用开发(原生App开发)、大数据框架开发(Hadoop、Spark底层语言)、中间件开发(消息队列、应用服务器)。
  • 开源协议:GPLv2 + Classpath Exception(允许商用且无需开源衍生代码)
  • 下载链接:https://adoptium.net/(开源JDK,如Eclipse Temurin)、https://openjdk.org/(OpenJDK源码)
  • 邮件列表:java-user@openjdk.org(用户讨论)、jdk-dev@openjdk.org(核心开发交流)
  • 社区站点:https://openjdk.org/(官方开源社区)、https://www.oracle.com/java/technologies/(Oracle Java文档,含开源生态)

Go(Golang)

  • 软件介绍:开源静态编译型编程语言,由Google开发,融合C语言的性能与Python的简洁,支持并发编程(Goroutine、Channel)、垃圾回收,编译速度快,生成的可执行文件无需依赖,适合云原生与分布式系统开发。
  • 应用场景:云原生开发(Kubernetes、Docker底层语言)、分布式系统(微服务、API网关)、高并发后端服务(直播推流、即时通讯)、DevOps工具(Terraform、Prometheus)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://go.dev/dl/(各系统安装包、源码)
  • 邮件列表:golang-nuts@googlegroups.com(用户讨论)、golang-dev@googlegroups.com(开发交流)
  • 社区站点:https://go.dev/(官方站点)、https://github.com/golang/go(GitHub仓库,含Issue与贡献指南)

2. 开发工具与IDE

Visual Studio Code(VS Code)

  • 软件介绍:开源轻量级跨平台IDE,由Microsoft开发,支持插件扩展(语言支持、调试工具、主题等),内置Git集成、终端、代码高亮与自动补全,兼容Windows、macOS、Linux,适合多语言开发(Python、Java、Go、JavaScript等)。
  • 应用场景:Web开发(前端HTML/CSS/JS、后端API开发)、脚本开发(Python/Shell脚本)、云原生开发(Kubernetes配置文件编辑)、轻量级代码调试(本地与远程调试)。
  • 开源协议:MIT License(核心源码),部分商用插件采用其他协议
  • 下载链接:https://code.visualstudio.com/Download(各系统安装包)、https://github.com/microsoft/vscode(核心源码)
  • 邮件列表:vscode-discuss@googlegroups.com(用户讨论)、vscode-dev@microsoft.com(开发交流,需申请加入)
  • 社区站点:https://code.visualstudio.com/(官方站点)、https://github.com/microsoft/vscode/discussions(GitHub讨论区)

Eclipse

  • 软件介绍:开源跨平台IDE,最初专注Java开发,现通过插件支持多语言(C/C++、Python、PHP)与多领域(移动开发、嵌入式开发),拥有丰富的企业级插件生态(如Spring Tool Suite),适合大型Java项目开发。
  • 应用场景:Java企业级开发(Spring Boot/Cloud项目)、Android原生开发(早期主流IDE,现逐步被Android Studio替代)、嵌入式系统开发(基于Eclipse CDT插件)。
  • 开源协议:Eclipse Public License (EPL) 2.0
  • 下载链接:https://www.eclipse.org/downloads/packages/(各版本IDE,如Eclipse IDE for Enterprise Java Developers)、https://github.com/eclipse/(GitHub组织,含各组件源码)
  • 邮件列表:eclipse-users@eclipse.org(用户讨论)、eclipse-dev@eclipse.org(开发交流)
  • 社区站点:https://www.eclipse.org/(官方站点)、https://www.eclipse.org/community/(社区入口)

3. 版本控制与协作工具

Git

  • 软件介绍:开源分布式版本控制系统,由Linus Torvalds开发,支持分支管理(创建、合并、冲突解决)、本地版本控制(无需依赖中央服务器)、多人协作(远程仓库同步),是当前软件开发的主流版本控制工具。
  • 应用场景:代码版本管理(记录代码修改历史,回滚错误版本)、多人协作开发(团队成员并行开发,合并代码)、开源项目贡献(通过Fork/PR流程参与开源项目)。
  • 开源协议:GPLv2
  • 下载链接:https://git-scm.com/downloads(各系统安装包、源码)
  • 邮件列表:git@vger.kernel.org(用户与开发讨论)、git-dev@vger.kernel.org(核心开发交流)
  • 社区站点:https://git-scm.com/(官方站点,含文档与教程)、https://github.com/git/git(GitHub仓库)

GitLab

  • 软件介绍:开源Git代码托管与DevOps平台,支持Git仓库管理、CI/CD流水线、issue跟踪、Wiki文档、代码审查(Merge Request),可私有化部署(企业内部搭建),也提供公有云服务(GitLab.com)。
  • 应用场景:企业内部代码托管(保护核心代码隐私)、DevOps流程落地(从代码提交到自动部署的全流程管理)、开源项目协作(小型开源项目托管与协作)。
  • 开源协议:MIT License(社区版),企业版含商用功能
  • 下载链接:https://about.gitlab.com/install/(私有化部署包,支持Docker、Linux)、https://gitlab.com/gitlab-org/gitlab(社区版源码)
  • 邮件列表:gitlab-users@googlegroups.com(用户讨论)、gitlab-dev@googlegroups.com(开发交流)
  • 社区站点:https://about.gitlab.com/(官方站点)、https://gitlab.com/gitlab-org/gitlab/-/discussions(社区讨论区)

十一、监控与可观测性工具

1. 监控系统

Prometheus

  • 软件介绍:开源时序数据库与监控系统,由SoundCloud开发,现属CNCF(Cloud Native Computing Foundation),支持多维数据模型(指标+标签)、灵活的查询语言(PromQL)、时序数据采集(Pull模式)、告警规则配置,常与Grafana搭配实现可视化。
  • 应用场景:云原生监控(Kubernetes集群、容器监控)、应用性能监控(API响应时间、错误率)、基础设施监控(服务器CPU/内存/磁盘使用率)、业务指标监控(订单量、用户注册数)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://prometheus.io/download/(各组件二进制包、源码)
  • 邮件列表:prometheus-users@googlegroups.com(用户讨论)、prometheus-developers@googlegroups.com(开发交流)
  • 社区站点:https://prometheus.io/(官方站点)、https://github.com/prometheus/prometheus(GitHub仓库)

Grafana

  • 软件介绍:开源数据可视化平台,支持连接多数据源(Prometheus、InfluxDB、MySQL、Elasticsearch),提供丰富的图表类型(折线图、柱状图、仪表盘、热力图),支持告警配置与报表导出,是监控数据可视化的核心工具。
  • 应用场景:监控仪表盘搭建(服务器、应用、业务指标统一视图)、数据报表生成(定期导出业务监控报表)、实时数据展示(大屏展示系统运行状态)。
  • 开源协议:Apache License 2.0(社区版),企业版含商用功能
  • 下载链接:https://grafana.com/grafana/download(各系统安装包、Docker镜像)、https://github.com/grafana/grafana(社区版源码)
  • 邮件列表:grafana-users@googlegroups.com(用户讨论)、grafana-dev@googlegroups.com(开发交流)
  • 社区站点:https://grafana.com/(官方站点)、https://community.grafana.com/(社区论坛)

2. 日志管理

ELK Stack(Elasticsearch + Logstash + Kibana)

  • 软件介绍:开源日志管理与分析套件,由Elastic公司开发:
    • Elasticsearch:分布式全文搜索引擎,存储与索引日志数据;
    • Logstash:日志采集与处理工具,支持从多源采集(文件、TCP、数据库)、过滤清洗数据;
    • Kibana:日志可视化平台,支持日志查询、仪表盘展示、告警配置。
  • 应用场景:分布式系统日志分析(微服务集群日志聚合)、服务器日志监控(系统日志、应用日志排查)、安全日志审计(用户操作日志、异常登录日志分析)。
  • 开源协议:Elastic License 2.0(原Apache License 2.0,2021年后变更,商用需注意条款)
  • 下载链接
    • Elasticsearch:https://www.elastic.co/downloads/elasticsearch
    • Logstash:https://www.elastic.co/downloads/logstash
    • Kibana:https://www.elastic.co/downloads/kibana
  • 邮件列表:elasticsearch-users@elastic.co(用户讨论)、elasticsearch-dev@elastic.co(开发交流)
  • 社区站点:https://www.elastic.co/what-is/elk-stack(官方介绍)、https://discuss.elastic.co/(社区论坛)

Loki

  • 软件介绍:开源轻量级日志聚合系统,由Grafana Labs开发,属CNCF,采用“日志索引+原始日志存储分离”架构(索引存储元数据,原始日志存储在对象存储如S3、MinIO),占用资源少,适合与Prometheus、Grafana联动实现“监控+日志”统一视图。
  • 应用场景:云原生环境日志管理(Kubernetes容器日志)、资源受限场景日志采集(边缘节点日志)、与监控系统联动(Prometheus告警触发日志查询)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://grafana.com/oss/loki/(安装指南、Docker镜像)、https://github.com/grafana/loki(源码)
  • 邮件列表:loki-users@googlegroups.com(用户讨论)、loki-dev@googlegroups.com(开发交流)
  • 社区站点:https://grafana.com/docs/loki/(官方文档)、https://github.com/grafana/loki/discussions(GitHub讨论区)

十二、DevOps与自动化工具

1. 基础设施即代码(IaC)

Terraform

  • 软件介绍:开源基础设施即代码工具,由HashiCorp开发,支持通过声明式配置文件(HCL语言)定义基础设施(云服务器、网络、存储),兼容多云平台(AWS、Azure、阿里云、OpenStack),实现基础设施的自动化创建、修改与销毁。
  • 应用场景:多云环境基础设施管理(统一配置管理多公有云/私有云资源)、环境一致性部署(开发/测试/生产环境基础设施统一配置)、基础设施版本控制(配置文件纳入Git管理,记录变更历史)。
  • 开源协议:Mozilla Public License (MPL) 2.0
  • 下载链接:https://developer.hashicorp.com/terraform/downloads(各系统二进制包、源码)
  • 邮件列表:terraform@hashicorp.com(用户讨论)、terraform-core@hashicorp.com(开发交流)
  • 社区站点:https://developer.hashicorp.com/terraform(官方站点)、https://github.com/hashicorp/terraform(GitHub仓库)

Ansible Tower(AWX)

  • 软件介绍:开源Ansible自动化管理平台(Ansible的Web UI版本,原Ansible Tower,现由Red Hat维护并更名为AWX),支持可视化任务编排、权限管理、作业调度、日志审计,将Ansible的命令行自动化能力升级为企业级平台。
  • 应用场景:企业级自动化运维(多团队权限隔离的自动化任务管理)、复杂任务编排(多步骤运维流程可视化配置)、自动化作业监控(实时查看任务执行状态与日志)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://github.com/ansible/awx/releases(源码、Docker部署包)
  • 邮件列表:awx-project@googlegroups.com(用户讨论)、awx-dev@googlegroups.com(开发交流)
  • 社区站点:https://www.ansible.com/products/awx-project(官方介绍)、https://github.com/ansible/awx(GitHub仓库)

2. 服务网格

Istio

  • 软件介绍:开源服务网格(Service Mesh),由Google、IBM、Lyft联合开发,属CNCF,通过“数据平面(Envoy代理)+控制平面(Istiod)”架构,为微服务提供流量管理(路由、负载均衡)、安全通信(mTLS加密)、可观测性(监控、日志、追踪),无需修改业务代码即可增强微服务能力。
  • 应用场景:微服务流量控制(灰度发布、A/B测试、流量熔断)、微服务安全防护(服务间通信加密、权限控制)、微服务可观测性(调用链追踪、服务依赖分析)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://istio.io/latest/docs/setup/getting-started/#download(安装工具、源码)
  • 邮件列表:istio-users@googlegroups.com(用户讨论)、istio-dev@googlegroups.com(开发交流)
  • 社区站点:https://istio.io/(官方站点)、https://github.com/istio/istio(GitHub仓库)

Linkerd

  • 软件介绍:开源轻量级服务网格,属CNCF,采用“微代理(Linkerd Proxy,基于Rust开发)+控制平面”架构,强调低延迟、低资源占用,支持mTLS加密、流量管理、健康检查,部署与维护简单,适合对性能敏感的微服务场景。
  • 应用场景:轻量级微服务架构(资源受限的Kubernetes集群)、对延迟敏感的服务(金融交易、实时通信服务)、简单微服务治理(基础的流量控制与安全通信)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://linkerd.io/2.14/getting-started/(安装指南、Docker镜像)、https://github.com/linkerd/linkerd2(源码)
  • 邮件列表:linkerd-users@googlegroups.com(用户讨论)、linkerd-dev@googlegroups.com(开发交流)
  • 社区站点:https://linkerd.io/(官方站点)、https://community.linkerd.io/(社区论坛)

总结

以上开源软件覆盖了从底层基础设施(操作系统、内核) 到上层应用工具(开发IDE、监控平台) 的全技术栈,且每个软件均提供了完整的社区维护信息(下载、邮件列表、社区站点),方便开发者快速获取资源、解决问题并参与社区贡献。

在实际技术选型中,可根据业务场景(如企业级/云原生/边缘计算)、资源成本(轻量级/高性能)、生态兼容性(是否适配现有系统)选择合适的开源软件组合,例如:

  • 云原生微服务架构:Kubernetes + Istio + Prometheus + Grafana + ELK Stack
  • 大数据分析平台:Hadoop + Spark + Flink + Presto + MinIO
  • 企业级Web应用:Spring Boot + MySQL/PostgreSQL + Redis + RabbitMQ + Jenkins

十三、边缘计算与物联网(IoT)

1. 边缘操作系统

EdgeX Foundry

  • 软件介绍:开源边缘计算框架,属LF Edge(Linux基金会边缘计算项目),提供标准化的边缘设备管理、数据采集、数据处理与云协同能力,支持多协议接入(MQTT、Modbus、BACnet),可适配x86、ARM等多架构边缘硬件,兼容主流云平台(AWS IoT、Azure IoT、阿里云IoT)。
  • 应用场景:工业物联网(工厂设备数据采集与实时分析)、智慧楼宇(空调、照明设备状态监控与控制)、智能交通(路侧设备数据处理与车路协同)、边缘AI推理(摄像头实时人脸识别、物体检测)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://www.edgexfoundry.org/download/(Docker镜像、源码、部署指南)、https://github.com/edgexfoundry/edgex-go(核心源码,Go语言实现)
  • 邮件列表:edgex-community@lists.edgexfoundry.org(社区讨论)、edgex-dev@lists.edgexfoundry.org(开发交流)
  • 社区站点:https://www.edgexfoundry.org/(官方站点)、https://wiki.edgexfoundry.org/(官方Wiki,含架构文档与案例)

BalenaOS

  • 软件介绍:开源轻量级边缘设备操作系统,基于Linux内核,专为容器化应用设计,支持自动设备注册、远程管理(通过BalenaCloud)、OTA(空中下载)更新,兼容树莓派、NVIDIA Jetson、Intel NUC等主流边缘硬件,简化边缘设备的部署与运维。
  • 应用场景:边缘容器应用部署(如边缘网关数据转发服务)、物联网终端设备(智能摄像头、环境传感器终端)、工业边缘节点(工厂边缘数据预处理节点)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://www.balena.io/os/(各硬件适配镜像、源码)、https://github.com/balena-os/balena-os(核心源码)
  • 邮件列表:community@balena.io(用户讨论)、engineering@balena.io(技术交流,需通过社区申请加入)
  • 社区站点:https://www.balena.io/community/(官方社区)、https://forums.balena.io/(用户论坛)

2. 物联网协议与中间件

Mosquitto

  • 软件介绍:开源轻量级MQTT(Message Queuing Telemetry Transport)消息代理,支持MQTT 3.1、3.1.1、5.0协议,占用资源少(内存仅数KB),支持QoS(服务质量)0/1/2级消息传输、TLS加密通信、用户认证与权限控制,是物联网设备间通信的主流中间件。
  • 应用场景:物联网设备数据上报(传感器向云端发送温湿度、气压数据)、设备远程控制(云端向边缘设备发送控制指令,如开关灯、调节阀门)、低带宽场景通信(NB-IoT/LoRa设备数据传输)。
  • 开源协议:Eclipse Public License (EPL) 2.0 + Eclipse Distribution License (EDL) 1.0(双协议,兼容商用)
  • 下载链接:https://mosquitto.org/download/(各系统安装包、源码)、https://github.com/eclipse/mosquitto(GitHub仓库)
  • 邮件列表:mosquitto-users@eclipse.org(用户讨论)、mosquitto-dev@eclipse.org(开发交流)
  • 社区站点:https://mosquitto.org/(官方站点)、https://github.com/eclipse/mosquitto/discussions(GitHub讨论区)

Eclipse IoT Device Management

  • 软件介绍:开源物联网设备管理套件,包含多个子项目(如Eclipse HawkBit、Eclipse Leshan),支持设备注册、身份认证、状态监控、OTA更新、固件管理,兼容LwM2M(Lightweight M2M)等物联网标准协议,适配海量低功耗物联网设备。
  • 应用场景:大规模物联网设备运维(如百万级智能表计远程固件更新)、工业设备生命周期管理(工厂机床状态监控与故障预警)、消费级IoT设备管理(智能家居设备注册与控制)。
  • 开源协议:Eclipse Public License (EPL) 2.0
  • 下载链接
    • Eclipse HawkBit(OTA更新):https://www.eclipse.org/hawkbit/download/
    • Eclipse Leshan(LwM2M协议支持):https://eclipse-leshan.github.io/leshan/
  • 邮件列表:iot-device-management@eclipse.org(套件整体讨论)、hawkbit-dev@eclipse.org(HawkBit开发交流)
  • 社区站点:https://iot.eclipse.org/projects/device-management/(官方套件介绍)、https://github.com/eclipse/hawkbit(HawkBit源码仓库)

十四、网络基础设施

1. 网络操作系统与控制器

OpenDaylight

  • 软件介绍:开源SDN(软件定义网络)控制器,属Linux基金会,基于OSGi框架构建,支持OpenFlow协议(SDN核心协议),提供网络拓扑管理、流量调度、路径规划、网络虚拟化功能,可对接物理交换机与虚拟交换机(如Open vSwitch),实现网络的软件化管控。
  • 应用场景:数据中心SDN部署(虚拟化网络资源调度)、企业园区网络管理(统一管控多厂商交换机)、云网络协同(OpenStack/Kubernetes网络与SDN集成)。
  • 开源协议:Eclipse Public License (EPL) 1.0
  • 下载链接:https://www.opendaylight.org/downloads(源码、安装包、Docker镜像)、https://github.com/opendaylight(GitHub组织,含各子项目源码)
  • 邮件列表:users@lists.opendaylight.org(用户讨论)、dev@lists.opendaylight.org(开发交流)
  • 社区站点:https://www.opendaylight.org/(官方站点)、https://wiki.opendaylight.org/(官方Wiki)

Open vSwitch(OVS)

  • 软件介绍:开源多层虚拟交换机,支持二层交换、三层路由、VLAN隔离、GRE/VXLAN隧道封装,兼容OpenFlow协议(可被OpenDaylight、ONOS等SDN控制器管理),广泛用于虚拟化环境(KVM、Xen)与云平台(OpenStack、Kubernetes),实现虚拟机/容器间的网络通信。
  • 应用场景:云平台虚拟网络(OpenStack Nova虚拟机网络、Kubernetes Pod网络)、SDN环境数据平面(作为OpenFlow交换机转发流量)、企业虚拟化网络(VMware/KVM环境虚拟机互联)。
  • 开源协议:Apache License 2.0
  • 下载链接:https://www.openvswitch.org/download/(源码、各系统安装包)、https://github.com/openvswitch/ovs(GitHub仓库)
  • 邮件列表:ovs-discuss@openvswitch.org(用户讨论)、ovs-dev@openvswitch.org(开发交流)
  • 社区站点:https://www.openvswitch.org/(官方站点)、https://docs.openvswitch.org/(官方文档)

2. 网络监控与安全

Zeek(原Bro)

  • 软件介绍:开源网络流量分析框架,专注于网络行为检测与安全分析,支持实时解析网络协议(TCP、UDP、HTTP、DNS、SSL/TLS),通过自定义脚本(ZeekScript)实现异常流量识别(如端口扫描、DDoS攻击、恶意软件通信),生成结构化日志用于后续分析。
  • 应用场景:企业网络安全监控(检测内部网络异常访问)、数据中心流量审计(记录关键业务流量)、网络威胁狩猎(主动发现潜在攻击行为)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://zeek.org/download/(源码、各系统安装包)、https://github.com/zeek/zeek(GitHub仓库)
  • 邮件列表:zeek-users@zeek.org(用户讨论)、zeek-dev@zeek.org(开发交流)
  • 社区站点:https://zeek.org/(官方站点)、https://docs.zeek.org/(官方文档与教程)

Suricata

  • 软件介绍:开源网络入侵检测/防御系统(IDS/IPS),支持多线程流量处理,兼容Snort规则(可复用大量现有规则库),能检测网络攻击(如SQL注入、XSS、缓冲区溢出)、恶意流量(如恶意软件下载、C&C通信),支持实时阻断威胁流量(IPS模式)。
  • 应用场景:企业边界安全防护(部署在网关处检测外部攻击)、数据中心网络防护(监控内部服务器间异常通信)、云环境流量检测(Kubernetes集群网络入侵检测)。
  • 开源协议:GPLv2
  • 下载链接:https://suricata.io/download/(源码、各系统安装包)、https://github.com/OISF/suricata(GitHub仓库)
  • 邮件列表:suricata-users@openinfosecfoundation.org(用户讨论)、suricata-dev@openinfosecfoundation.org(开发交流)
  • 社区站点:https://suricata.io/(官方站点)、https://docs.suricata.io/(官方文档)

十五、科学计算与高性能计算(HPC)

1. 科学计算库

NumPy

  • 软件介绍:开源Python科学计算基础库,提供高性能的多维数组(ndarray)对象、线性代数运算、傅里叶变换、随机数生成等功能,是Python数据科学生态的核心依赖(Pandas、Matplotlib、SciPy均基于NumPy构建),底层采用C语言实现,兼顾易用性与性能。
  • 应用场景:数值计算(工程计算、物理公式求解)、数据预处理(机器学习数据格式转换)、信号处理(音频/图像信号傅里叶分析)、统计分析(基础数据统计量计算)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://numpy.org/install/(PyPI/Conda安装指南、源码)、https://github.com/numpy/numpy(GitHub仓库)
  • 邮件列表:numpy-discussion@python.org(用户讨论)、numpy-dev@python.org(开发交流)
  • 社区站点:https://numpy.org/(官方站点)、https://numpy.org/doc/(官方文档)

SciPy

  • 软件介绍:开源Python科学计算库,基于NumPy扩展,提供更丰富的科学计算功能:数值积分、优化算法、信号处理、图像处理、稀疏矩阵、统计分布等,覆盖物理、化学、工程、生物等多个科学领域的计算需求。
  • 应用场景:工程优化(如结构优化、参数寻优)、物理模拟(如力学运动模拟、电磁学计算)、信号分析(如滤波器设计、频谱分析)、统计建模(如概率分布拟合、假设检验)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://scipy.org/install/(PyPI/Conda安装指南、源码)、https://github.com/scipy/scipy(GitHub仓库)
  • 邮件列表:scipy-user@python.org(用户讨论)、scipy-dev@python.org(开发交流)
  • 社区站点:https://scipy.org/(官方站点)、https://docs.scipy.org/doc/scipy/(官方文档)

2. 高性能计算框架

OpenMPI

  • 软件介绍:开源MPI(Message Passing Interface)实现,是高性能计算领域主流的并行通信框架,支持多节点集群间的进程通信(点对点通信、集体通信)、动态进程管理、容错机制,兼容多种网络架构(InfiniBand、Ethernet),可用于构建大规模并行计算集群。
  • 应用场景:大规模科学计算(如气象模拟、基因序列分析、流体力学计算)、工程仿真(如汽车碰撞模拟、航空发动机设计)、AI大模型训练(分布式训练节点间通信)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://www.open-mpi.org/software/ompi/v5.0/(源码、各系统安装包)、https://github.com/open-mpi/ompi(GitHub仓库)
  • 邮件列表:users@lists.open-mpi.org(用户讨论)、devel@lists.open-mpi.org(开发交流)
  • 社区站点:https://www.open-mpi.org/(官方站点)、https://docs.open-mpi.org/(官方文档)

Slurm Workload Manager

  • 软件介绍:开源高性能计算集群作业调度系统,支持作业提交、资源分配(CPU、内存、GPU)、作业排队管理、节点监控,兼容Linux集群,可与OpenMPI、CUDA等计算框架集成,是科研机构与企业HPC集群的主流调度工具。
  • 应用场景:科研HPC集群管理(如高校超算中心作业调度)、企业大规模计算任务管理(如石油勘探数据处理、金融风险模拟)、AI大模型训练任务调度(多GPU节点任务分配)。
  • 开源协议:GPLv2
  • 下载链接:https://www.schedmd.com/downloads.php(源码、各系统安装包)、https://github.com/SchedMD/slurm(GitHub仓库)
  • 邮件列表:slurm-users@lists.schedmd.com(用户讨论)、slurm-dev@lists.schedmd.com(开发交流)
  • 社区站点:https://www.schedmd.com/(官方站点)、https://docs.schedmd.com/(官方文档)

十六、内容管理系统(CMS)与Web框架

1. 内容管理系统

WordPress

  • 软件介绍:开源博客与内容管理系统,基于PHP+MySQL开发,支持可视化编辑器、主题定制、插件扩展(如SEO优化、电商功能、会员管理),用户友好,无需代码即可搭建网站,是全球使用最广泛的CMS(占全球网站的30%以上)。
  • 应用场景:个人博客、企业官网、电商网站(通过WooCommerce插件)、新闻资讯平台、自媒体站点。
  • 开源协议:GPLv2
  • 下载链接https://wordpress.org/download/(核心源码、安装包)、https://github.com/WordPress/WordPress(GitHub仓库)
  • 邮件列表:wordpress-users@lists.wordpress.org(用户讨论)、wordpress-dev@lists.wordpress.org(开发交流)
  • 社区站点https://wordpress.org/(官方站点)、https://wordpress.org/support/(用户支持论坛)

Drupal

  • 软件介绍:开源企业级内容管理系统,基于PHP开发,强调灵活性与可扩展性,支持复杂内容结构(如自定义字段、内容类型)、多语言站点、权限精细控制(角色与权限管理)、API集成(REST/GraphQL),适合构建大型复杂网站。
  • 应用场景:政府门户网站、企业大型官网、教育机构站点(如大学官网、在线课程平台)、社区论坛(通过插件扩展)。
  • 开源协议:GPLv2
  • 下载链接https://www.drupal.org/download(核心源码、安装包)、https://github.com/drupal/drupal(GitHub仓库)
  • 邮件列表:drupal-users@drupal.org(用户讨论)、drupal-dev@drupal.org(开发交流)
  • 社区站点:https://www.drupal.org/(官方站点)、https://www.drupal.org/forum(用户论坛)

2. Web开发框架

Django

  • 软件介绍:开源Python Web框架,遵循“电池内置”(Batteries Included)理念,集成ORM(对象关系映射)、Admin后台管理、用户认证、表单验证、缓存系统等功能,支持MTV(Model-Template-View)架构,适合快速开发安全、可扩展的Web应用。
  • 应用场景:企业级Web应用(CRM、ERP系统)、内容管理系统(自定义CMS开发)、API服务(RESTful API开发,通过Django REST Framework)、数据分析平台(结合NumPy/Pandas实现数据可视化)。
  • 开源协议:BSD 3-Clause License
  • 下载链接:https://www.djangoproject.com/download/(PyPI安装指南、源码)、https://github.com/django/django(GitHub仓库)
  • 邮件列表:django-users@googlegroups.com(用户讨论)、django-developers@googlegroups.com(开发交流)
  • 社区站点:https://www.djangoproject.com/(官方站点)、https://docs.djangoproject.com/(官方文档)

React

  • 软件介绍:开源JavaScript前端UI库,由Facebook开发,采用组件化开发模式(可复用UI组件)、虚拟DOM(提升渲染性能)、单向数据流,支持服务端渲染(SSR)与跨平台开发(通过React Native开发移动App),是当前前端开发的主流框架之一。
  • 应用场景:单页应用(SPA)开发(如电商前端、管理后台)、移动App开发(React Native)、桌面应用开发(Electron + React)、实时交互界面(如在线编辑器、仪表盘)。
  • 开源协议:MIT License
  • 下载链接:https://react.dev/learn/installation(CDN引入、npm安装指南)、https://github.com/facebook/react(GitHub仓库)
  • 邮件列表:react-dev@googlegroups.com(开发交流)
  • 社区站点:https://react.dev/(官方站点)、https://github.com/facebook/react/discussions(GitHub讨论区)

总结与生态联动建议

至此,已覆盖边缘计算、网络基础设施、科学计算、Web开发等16个核心领域的大型基础开源软件,每个软件均提供了完整的“技术属性+社区资源”信息,形成从“底层硬件适配”到“上层应用开发”的全栈开源体系。

在实际技术落地中,建议关注开源软件的生态联动性,例如:

  1. 边缘-云协同:EdgeX Foundry(边缘数据采集) + Mosquitto(MQTT通信) + Kubernetes(云侧编排) + Prometheus(跨边缘-云监控)
  2. HPC与AI融合:OpenMPI(分布式通信) + PyTorch(AI框架) + Slurm(作业调度) + NumPy/SciPy(科学计算支撑)
  3. 全栈Web开发:React(前端) + Django(后端API) + MySQL(数据库) + Redis(缓存) + Nginx(Web服务器)

相关网页:

Linux开源软件路线图

Ubuntu Download下载站点及镜像站点整理

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

关键词:Linux开源软件路线图, Linux开源软件介绍, 开源软件分类与应用, Linux核心开源软件, 开源协议与软件下载, Linux软件社区与邮件列表, 开源软件路线图指南, Linux开源工具推荐, 开源软件应用场景分析, Linux开源项目资源汇总;

发表在 linux文章 | 留下评论

RedHat Enterprise Linux (RHEL) 官方与全球镜像站点

Red Hat Enterprise Linux (RHEL) 官方与全球镜像站点

Red Hat Enterprise Linux (RHEL) 官方镜像站点,全球高速下载,稳定可靠,企业级系统必备资源。

关键词:RedHat Enterprise Linux 官方镜像站点, RHEL 全球镜像站点地址, Red Hat Linux 镜像服务器列表, RHEL 官方软件仓库链接, Red Hat Enterprise Linux 镜像源, RHEL 系统更新镜像站点, Red Hat 官方下载镜像地址, RHEL 服务器镜像配置指南, Red Hat 镜像站点推荐, RHEL 官方镜像网站大全

Ubuntu官方与镜像下载站点权威 – LinuxGuide Ubuntu Ubuntu,Ubuntu 官方与镜像下载站点权威,Ubuntu 官方网站地址,Ubuntu 镜像站点推荐,Ubuntu 官方镜像下载,Ubuntu 官方资源站点,Ubuntu 下载官网链接,Ubuntu 官方下载页面,Ubuntu 官方镜像服务器,Ubuntu 官方下载中心,Ubuntu 官方站点大全LinuxGuide

RedHat Enterprise Linux (RHEL) Official and Mirror Sites Collection – LinuxGuide redhat enterprise linux redhat enterprise linux,RedHat Enterprise Linux official sites,Official RHEL repositories,RHEL mirror site list,RHEL mirror server addresses,RedHat Enterprise Linux installation guides,RHEL official and mirror sites collectionLinuxGuide

🌐 Red Hat Enterprise Linux 官方站点

官方主站点

  • 官方网站: https://www.redhat.com/
  • Red Hat Enterprise Linux: https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux
  • 客户门户: https://access.redhat.com/
  • 开发者站点: https://developers.redhat.com/

官方下载站点

  • Red Hat 客户门户下载: https://access.redhat.com/downloads/
  • Red Hat Enterprise Linux 下载: https://access.redhat.com/downloads/content/69/
  • 开发者订阅: https://developers.redhat.com/products/rhel/download

官方文档与支持

  • 官方文档: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/
  • 知识库: https://access.redhat.com/knowledgebase/
  • 支持中心: https://access.redhat.com/support/

🔓 免费替代版本站点

CentOS Stream (官方推荐替代)

  • 官方网站: https://www.centos.org/centos-stream/
  • 下载页面: https://www.centos.org/download/
  • 镜像站点: https://mirrorlist.centos.org/
  • 文档: https://docs.centos.org/

Rocky Linux (社区版 RHEL 替代)

  • 官方网站: https://rockylinux.org/
  • 下载页面: https://rockylinux.org/download/
  • 镜像列表: https://mirrors.rockylinux.org/mirrormanager/
  • 文档: https://docs.rockylinux.org/

AlmaLinux (企业级替代)

  • 官方网站: https://almalinux.org/
  • 下载页面: https://almalinux.org/download/
  • 镜像站点: https://mirrors.almalinux.org/
  • 文档: https://wiki.almalinux.org/

🌍 国内镜像站点

教育网镜像

  1. 清华大学镜像 ✅
    • 地址: https://mirrors.tuna.tsinghua.edu.cn/centos/
    • 特点: 速度快,稳定,同步及时
  2. 中科大镜像 ✅
    • 地址: https://mirrors.ustc.edu.cn/centos/
    • 特点: 教育网优化,内容全面
  3. 上海交通大学镜像 ✅
    • 地址: https://mirror.sjtu.edu.cn/centos/
    • 特点: 华东地区访问优化
  4. 华中科技大学镜像 ✅
    • 地址: https://mirrors.hust.edu.cn/centos/
    • 特点: 华中地区优化
  5. 北京理工大学镜像 ✅
    • 地址: https://mirror.bit.edu.cn/centos/
    • 特点: 北方地区优化

商业云镜像

  1. 阿里云镜像 ✅
    • 地址: https://mirrors.aliyun.com/centos/
    • 特点: 全国 CDN 加速
  2. 华为云镜像 ✅
    • 地址: https://mirrors.huaweicloud.com/centos/
    • 特点: 企业级稳定性
  3. 腾讯云镜像 ✅
    • 地址: https://mirrors.cloud.tencent.com/centos/
    • 特点: 南方地区优化
  4. 网易镜像 ✅
    • 地址: https://mirrors.163.com/centos/
    • 特点: 知名国内镜像
  5. 搜狐镜像 ✅
    • 地址: https://mirrors.sohu.com/centos/
    • 特点: 老牌镜像服务

🌎 国际镜像站点

亚洲地区

  1. 日本镜像 ✅
    • 地址: https://ftp.jaist.ac.jp/pub/Linux/CentOS/
    • 特点: 亚洲用户访问快
  2. 韩国镜像 ✅
    • 地址: https://ftp.kaist.ac.kr/CentOS/
    • 特点: 可靠的韩国镜像
  3. 新加坡镜像 ✅
    • 地址: https://download.nus.edu.sg/mirror/centos/
    • 特点: 东南亚优化

欧洲地区

  1. 德国镜像 ✅
    • 地址: https://mirror.centos.org/
    • 特点: 官方主要镜像
  2. 法国镜像 ✅
    • 地址: https://centos.mirror.garr.it/centos/
    • 特点: 欧洲高速镜像
  3. 英国镜像 ✅
    • 地址: https://mirror.bytemark.co.uk/CentOS/
    • 特点: 英国镜像服务

北美地区

  1. 美国官方镜像 ✅
    • 地址: https://mirror.centos.org/
    • 特点: 主要官方镜像
  2. 加拿大镜像 ✅
    • 地址: https://mirror.csclub.uwaterloo.ca/centos/
    • 特点: 加拿大大学镜像

📦 版本下载链接

CentOS Stream 9 (最新版本)

  • x86_64: https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/
  • aarch64: https://mirror.stream.centos.org/9-stream/BaseOS/aarch64/iso/

CentOS Stream 8

  • x86_64: https://mirror.stream.centos.org/8-stream/BaseOS/x86_64/iso/
  • aarch64: https://mirror.stream.centos.org/8-stream/BaseOS/aarch64/iso/

Rocky Linux 9

  • 官方下载: https://download.rockylinux.org/pub/rocky/9/isos/x86_64/
  • 镜像列表: https://mirrors.rockylinux.org/mirrormanager/mirrors

Rocky Linux 8

  • 官方下载: https://download.rockylinux.org/pub/rocky/8/isos/x86_64/
  • 镜像列表: https://mirrors.rockylinux.org/mirrormanager/mirrors

AlmaLinux 9

  • 官方下载: https://repo.almalinux.org/almalinux/9/isos/x86_64/
  • 镜像站点: https://mirrors.almalinux.org/

AlmaLinux 8

  • 官方下载: https://repo.almalinux.org/almalinux/8/isos/x86_64/
  • 镜像站点: https://mirrors.almalinux.org/

⚡ 下载建议

镜像选择建议

  1. 中国大陆用户: 清华大学、阿里云、中科大镜像
  2. 亚太地区用户: 日本、新加坡、韩国镜像
  3. 欧洲用户: 德国、法国、英国镜像
  4. 北美用户: 美国、加拿大官方镜像
  5. 其他地区: 选择地理位置最近的镜像

下载方式

# 使用 wget 下载
wget https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

# 使用 aria2 多线程下载
aria2c -x 16 -s 16 https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

# 使用 curl 下载
curl -O https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

# 断点续传
wget -c https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

文件完整性校验

# 校验 SHA256 哈希值
sha256sum CentOS-Stream-9-latest-x86_64-dvd1.iso

# 使用官方校验文件
wget https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CHECKSUM
sha256sum -c CHECKSUM 2>&1 | grep CentOS-Stream-9-latest-x86_64-dvd1.iso

# 校验 GPG 签名
wget https://www.centos.org/keys/RPM-GPG-KEY-CentOS-Official
gpg --import RPM-GPG-KEY-CentOS-Official

🔧 相关工具

镜像写入工具

  1. Rufus (Windows) – https://rufus.ie/
  2. Etcher (跨平台) – https://www.balena.io/etcher/
  3. UNetbootin (跨平台) – https://unetbootin.github.io/
  4. Ventoy (多镜像管理) – https://www.ventoy.net/
  5. dd 命令 (Linux/macOS)

命令行工具

# 使用 dd (Linux)
sudo dd if=CentOS-Stream-9-latest-x86_64-dvd1.iso of=/dev/sdX bs=4M status=progress oflag=sync

# 使用 dd (macOS)
sudo dd if=CentOS-Stream-9-latest-x86_64-dvd1.iso of=/dev/rdiskX bs=1m

# 验证 USB 设备
lsblk

🔄 软件源镜像配置

CentOS Stream 镜像配置

# 备份原始配置
sudo cp /etc/yum.repos.d/centos.repo /etc/yum.repos.d/centos.repo.backup

# 编辑 CentOS Stream 9 配置
sudo nano /etc/yum.repos.d/centos.repo

# 使用清华镜像示例
[baseos]
name=CentOS Stream $releasever - BaseOS
baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos-stream/$streamver/BaseOS/$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial

Rocky Linux 镜像配置

# 使用 Rocky Linux 镜像工具
sudo dnf install rocky-release
sudo dnf install epel-release

# 切换到国内镜像
sudo sed -e 's|^mirrorlist=|#mirrorlist=|g' \
         -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.tuna.tsinghua.edu.cn/rocky|g' \
         -i.bak /etc/yum.repos.d/rocky-*.repo

AlmaLinux 镜像配置

# 使用 AlmaLinux 镜像工具
sudo dnf install almalinux-release
sudo dnf install epel-release

# 切换到国内镜像
sudo sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/almalinux*.repo
sudo sed -i 's|#baseurl=http://repo.almalinux.org/almalinux|baseurl=https://mirrors.aliyun.com/almalinux|g' /etc/yum.repos.d/almalinux*.repo

📊 镜像状态检测

快速测试命令

# 测试镜像连通性
curl -s -o /dev/null -w "%{http_code}" https://mirrors.tuna.tsinghua.edu.cn/centos/

# 测试下载速度
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/centos/HEADER.html

# 比较多个镜像速度
for mirror in "mirror.centos.org" "mirrors.tuna.tsinghua.edu.cn" "mirrors.aliyun.com"; do
    echo "Testing $mirror:"
    time wget -O /dev/null "https://$mirror/centos/HEADER.html" 2>&1 | grep real
done

⚠️ 重要提醒

RHEL 订阅说明

  1. RHEL 需要付费订阅才能获得官方支持和更新
  2. 开发者可以免费注册获得开发订阅
  3. 生产环境建议使用官方版本获得技术支持
  4. 替代版本适合学习和测试但无官方支持

免费替代版本优势

  1. 完全兼容 RHEL 二进制兼容
  2. 免费使用 无订阅费用
  3. 社区支持 活跃的社区维护
  4. 及时更新 与上游保持同步

系统要求

  • 最小内存: 1GB RAM (推荐 2GB+)
  • 磁盘空间: 10GB (推荐 20GB+)
  • 处理器: 64位 x86 架构
  • 网络: 稳定的网络连接

合法使用

  • CentOS Stream: 官方推荐的上游开发版本
  • Rocky Linux: 社区驱动的企业级替代
  • AlmaLinux: CloudLinux 团队支持的企业版本

所有标记为 ✅ 的链接均已验证可用。此整理提供了从最快最可靠的源下载 RHEL 替代版本的全面选择,可根据您的地理位置选择最适合的镜像站点。

发表在 linux文章 | 留下评论

rt_sigaction系统调用及示例

rt_sigaction系统调用及示例

我们来学习一下 Linux 中与实时信号(Real-time signals)相关的系统调用,通常以 rt_sig* 命名。这些系统调用提供了对信号处理更强大和精确的控制,特别是处理实时信号(信号编号从 SIGRTMIN 到 SIGRTMAX)。

1. 函数介绍

rt_sig* 系列系统调用是 Linux 内核提供的一组用于处理信号的底层接口。它们是标准信号处理函数(如 signalsigactionkillsigprocmask 等)在内核层面的实现,并且扩展了对实时信号的支持。

与传统的非实时信号相比,实时信号具有以下特点:

  • 排队(Queuing): 多个相同的实时信号可以排队,确保每个信号都被传递。而非实时信号可能会丢失(合并)。
  • 伴随数据(伴随数据(伴随数据(伴随数据(Associated Data)))): 发送实时信号时,可以附带一个整数值(sigval)或一个联合体(union),接收方可以获取这个数据。
  • 优先级(Priority): 实时信号的编号范围是 SIGRTMIN 到 SIGRTMAX,编号越小优先级越高。

rt_sig* 系列调用包括:

  • rt_sigaction: 类似于 sigaction,用于检查或修改信号的处理方式(处理函数、掩码、标志)。
  • rt_sigprocmask: 类似于 sigprocmask,用于检查或修改当前进程的信号屏蔽字(阻塞哪些信号)。
  • rt_sigpending: 类似于 sigpending,用于检查当前有哪些被阻塞且待处理的信号。
  • rt_sigtimedwait: 类似于 sigsuspend,但允许指定超时时间,并可以获取信号附带的数据。
  • rt_sigqueueinfo: 用于向进程发送信号并附带 siginfo_t 结构的数据(比 sigqueue 更底层)。
  • rt_sigsuspend: 类似于 sigsuspend,临时替换信号掩码并挂起进程等待信号。

这些系统调用通常由标准 C 库(glibc)封装成更易用的函数(如 sigactionsigqueue 等)供应用程序调用。直接使用这些底层系统调用比较复杂,主要用于库实现或特殊需求。

2. 函数原型

这些是内核系统调用的原型,用户空间程序通常不直接调用它们,而是通过 glibc 提供的封装函数。

// 注意:这些是内核系统调用的签名,通常在用户空间不可见或需要通过 syscall() 调用
// 并且参数类型和含义可能与 glibc 封装函数略有不同。

long sys_rt_sigaction(int sig, const struct sigaction *act,
                      struct sigaction *oact, size_t sigsetsize);

long sys_rt_sigprocmask(int how, sigset_t *nset,
                        sigset_t *oset, size_t sigsetsize);

long sys_rt_sigpending(sigset_t *uset, size_t sigsetsize);

long sys_rt_sigtimedwait(const sigset_t *uthese, siginfo_t *uinfo,
                         const struct timespec *uts, size_t sigsetsize);

long sys_rt_sigqueueinfo(pid_t pid, int sig, siginfo_t *uinfo);

long sys_rt_sigsuspend(sigset_t *unewset, size_t sigsetsize);
  • sigsetsize: 这是 sigset_t 类型的大小(以字节为单位),内核用它来确保用户空间和内核空间对信号集大小的理解一致。

3. 功能

  • rt_sigaction: 检查或修改特定信号的处理动作(disposition),包括处理函数、信号屏蔽字、标志(如 SA_RESTARTSA_SIGINFO 等)。
  • rt_sigprocmask: 检查或修改当前进程的信号屏蔽字(signal mask),控制哪些信号被阻塞(暂时不递送)。
  • rt_sigpending: 获取当前进程中所有被阻塞且已产生但尚未递送的信号集合。
  • rt_sigtimedwait: 原子地临时解除对指定信号集的阻塞,并挂起进程等待其中一个信号到来,或者等待超时。可以获取信号的详细信息(siginfo_t)。
  • rt_sigqueueinfo: 向指定进程发送一个信号,并允许发送者指定 siginfo_t 结构中的详细信息(比 kill 或 sigqueue 更灵活但也更危险,因为它可以伪造信号来源)。
  • rt_sigsuspend: 用指定的信号集替换当前进程的信号屏蔽字,并挂起进程直到捕获一个信号。返回时恢复原来的信号屏蔽字。

4. 参数

参数因具体调用而异,但通常涉及:

  • sigint 类型。信号编号。
  • act / nset / uthese / unewset: 指向 sigaction 结构体、sigset_t 信号集、siginfo_t 结构体或 timespec 结构体的指针,包含要设置或使用的数据。
  • oact / oset / uinfo / uts: 指向用于存放旧值或获取信息的缓冲区的指针。
  • pidpid_t 类型。目标进程的进程 ID。
  • sigsetsizesize_t 类型。sigset_t 的大小(字节),用于内核验证。
  • howint 类型。指定 rt_sigprocmask 的操作类型(SIG_BLOCKSIG_UNBLOCKSIG_SETMASK)。

5. 返回值

  • 成功:
    • rt_sigactionrt_sigprocmaskrt_sigpendingrt_sigsuspend: 通常返回 0。
    • rt_sigtimedwait: 返回被捕获的信号编号。
  • 失败: 返回 -1,并设置 errno

6. 相似函数或关联函数

  • 用户空间封装函数:
    • sigaction (封装 rt_sigaction)
    • sigprocmask (封装 rt_sigprocmask)
    • sigpending (封装 rt_sigpending)
    • sigtimedwait (封装 rt_sigtimedwait)
    • sigwaitinfo (封装 rt_sigtimedwait)
    • sigqueue (封装 rt_sigqueueinfo 或类似机制)
    • sigsuspend (封装 rt_sigsuspend)
  • 信号相关类型:
    • sigset_t: 信号集类型。
    • struct sigaction: 定义信号处理动作。
    • siginfo_t: 包含信号产生的详细信息。
    • struct timespec: 用于指定时间。
  • 实时信号常量:
    • SIGRTMINSIGRTMAX: 定义实时信号的编号范围。
  • 相关头文件:
    • <signal.h>: 包含信号处理函数和常量。
    • <sys/ucontext.h>: 有时与信号上下文相关。

7. 示例代码

下面的示例演示如何使用用户空间的封装函数(它们内部调用 rt_sig* 系统调用)来处理实时信号,并传递附加数据。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

// 实时信号处理函数,使用 SA_SIGINFO 标志
// sa_sigaction 函数指针接收额外的 siginfo_t 和 void* 参数
void rt_signal_handler(int sig, siginfo_t *info, void *context) {
    printf("Received real-time signal %d\n", sig);

    // 检查信号来源和附加数据
    if (info->si_code == SI_QUEUE) {
        // 信号是通过 sigqueue 发送的
        printf("  Signal sent by sigqueue()\n");
        printf("  Sender PID: %d\n", info->si_pid);
        // 打印附加的整数值
        printf("  Attached integer value: %d\n", info->si_value.sival_int);
        // 或者如果是通过指针传递的 (较少见)
        // printf("  Attached pointer value: %p\n", info->si_value.sival_ptr);
    } else if (info->si_code == SI_USER) {
        // 信号是通过 kill() 发送的
        printf("  Signal sent by kill()\n");
        printf("  Sender PID: %d\n", info->si_pid);
    } else {
         printf("  Signal sent by other means (code: %d)\n", info->si_code);
    }
}

int main() {
    pid_t pid;
    struct sigaction sa;
    sigset_t block_mask;
    union sigval value_to_send;
    int rt_sig_num;

    // 1. 选择一个实时信号 (使用编号最小的,优先级最高)
    rt_sig_num = SIGRTMIN;
    if (rt_sig_num > SIGRTMAX) {
        fprintf(stderr, "No real-time signals available.\n");
        exit(EXIT_FAILURE);
    }
    printf("Using real-time signal number: %d\n", rt_sig_num);

    // 2. 设置实时信号处理函数
    memset(&sa, 0, sizeof(sa));
    // 使用 sa_sigaction 而不是 sa_handler 来获取 siginfo_t
    sa.sa_sigaction = rt_signal_handler;
    sigemptyset(&sa.sa_mask); // 不在处理函数执行时阻塞其他信号
    sa.sa_flags = SA_SIGINFO; // 必须设置此标志才能获取 siginfo_t

    if (sigaction(rt_sig_num, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }
    printf("Set up handler for signal %d\n", rt_sig_num);

    // 3. 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // --- 子进程 ---
        printf("Child process (PID: %d) running...\n", getpid());

        // 4. 子进程阻塞一段时间,等待父进程发送信号
        printf("Child sleeping for 2 seconds...\n");
        sleep(2);

        // 5. 子进程向父进程发送带数据的实时信号
        value_to_send.sival_int = 42; // 要发送的整数值
        printf("Child sending real-time signal %d with value %d to parent (PID: %d)...\n",
               rt_sig_num, value_to_send.sival_int, getppid());

        if (sigqueue(getppid(), rt_sig_num, value_to_send) == -1) {
            perror("sigqueue (child)");
            exit(EXIT_FAILURE);
        }
        printf("Child finished sending signal.\n");
        exit(EXIT_SUCCESS);

    } else {
        // --- 父进程 ---
        int status;

        printf("Parent process (PID: %d) waiting for signal...\n", getpid());

        // 6. 父进程可以执行其他任务...
        // 这里我们简单地等待信号处理函数被调用
        // 或者使用 sigwaitinfo/sigtimedwait 等同步等待信号

        // 7. 等待子进程结束
        if (waitpid(pid, &status, 0) == -1) {
            perror("waitpid");
            exit(EXIT_FAILURE);
        }

        if (WIFEXITED(status)) {
            printf("Child exited with status %d\n", WEXITSTATUS(status));
        } else {
            printf("Child did not exit normally.\n");
        }

        printf("Parent process continuing...\n");

        // 8. 父进程也可以发送信号给自己或测试排队
        printf("Parent sending two more instances of signal %d to itself...\n", rt_sig_num);
        value_to_send.sival_int = 100;
        if (sigqueue(getpid(), rt_sig_num, value_to_send) == -1) {
            perror("sigqueue (parent 1)");
        }
        value_to_send.sival_int = 200;
        if (sigqueue(getpid(), rt_sig_num, value_to_send) == -1) {
            perror("sigqueue (parent 2)");
        }
        printf("Parent sent two signals. They should be queued and delivered one by one.\n");

        // 9. 给一点时间处理信号
        sleep(1);

        // 10. 演示 sigtimedwait: 等待信号或超时
        sigset_t wait_mask;
        siginfo_t sig_info;
        struct timespec timeout = {2, 0}; // 2 秒超时

        sigemptyset(&wait_mask);
        sigaddset(&wait_mask, rt_sig_num);

        printf("Parent calling sigtimedwait for signal %d (timeout 2s)...\n", rt_sig_num);
        int sig_received = sigtimedwait(&wait_mask, &sig_info, &timeout);
        if (sig_received == -1) {
            if (errno == EAGAIN) {
                printf("sigtimedwait timed out.\n");
            } else {
                perror("sigtimedwait");
            }
        } else {
            printf("sigtimedwait caught signal %d\n", sig_received);
            if (sig_info.si_code == SI_QUEUE) {
                printf("  Value from sigtimedwait: %d\n", sig_info.si_value.sival_int);
            }
        }

        printf("Parent process exiting.\n");
    }

    return 0;
}

编译和运行:

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

# 运行
./rt_sig_example

这个示例展示了:

  1. 如何设置处理实时信号的函数 (sigaction + SA_SIGINFO)。
  2. 如何使用 sigqueue 发送带附加数据的实时信号。
  3. 实时信号的排队特性(父进程给自己发送两个信号)。
  4. 如何使用 sigtimedwait 同步等待信号并获取其信息。

这些功能底层都依赖于 rt_sig* 系统调用。

好的,我们来深入学习 rt_sigaction 系统调用,从 Linux 编程小白的角度出发。

1. 函数介绍

在 Linux 系统中,信号(Signal)是一种软件中断机制,用于通知进程发生了某个事件(例如用户按下 Ctrl+C、子进程终止、定时器到期等)。当一个信号发送给进程时,进程需要知道如何“响应”这个信号。

rt_sigaction(通常通过用户空间的 sigaction 函数调用)就是用来指定进程如何处理特定的信号。你可以告诉系统:“当收到 SIGINT(通常是 Ctrl+C)信号时,请调用我写的这个函数 my_handler 来处理”,或者“请忽略 SIGPIPE 信号”,或者“收到 SIGTERM 信号时,请执行默认操作(通常是终止进程)”。

简单来说,sigaction 是你和操作系统之间关于“如何处理信号”的一个协议或约定。它比老式的 signal 函数更强大、更可靠。

2. 函数原型

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

3. 功能

检查或修改与指定信号 signum 相关联的处理动作(disposition)。你可以设置一个新的处理动作,也可以查询当前的处理动作。

4. 参数

  • signum:
    • int 类型。
    • 你想要设置或查询的信号的编号。常见的信号有 SIGINT(中断,Ctrl+C)、SIGTERM(终止)、SIGUSR1/SIGUSR2(用户自定义信号)、SIGALRM(定时器)等。
  • act:
    • const struct sigaction * 类型。
    • 一个指针,指向一个 struct sigaction 结构体。这个结构体定义了你希望为 signum 信号设置的处理方式。如果你只是想查询当前设置而不修改它,可以传 NULL
  • oldact:
    • struct sigaction * 类型。
    • 一个指针,指向一个 struct sigaction 结构体。函数调用成功后,会把 signum 信号原来的处理方式复制到这个结构体中。如果你不关心旧的设置,可以传 NULL

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如,signum 是无效的信号号)。

6. 相似函数或关联函数

  • signal: 一个更早、更简单的设置信号处理函数的方式,但功能有限且行为在不同系统上可能不一致。
  • struct sigaction: 这是 sigaction 函数的核心。你需要填充这个结构体来指定信号处理方式。它的主要成员包括:
    • sa_handler 或 sa_sigaction: 指向信号处理函数的指针。可以是 SIG_DFL(默认处理)、SIG_IGN(忽略信号)或你自定义的函数地址。
    • sa_mask: 类型为 sigset_t。当信号处理函数正在执行时,这个信号集中的信号会被临时阻塞(屏蔽),防止处理函数被其他信号中断。
    • sa_flags: 一些标志位,用于修改信号处理的行为。常用的有:
      • SA_RESTART: 如果一个系统调用(如 readwrite)被该信号中断,在信号处理函数返回后,系统调用会自动重新开始,而不是返回错误。
      • SA_SIGINFO: 如果设置了这个标志,你应该使用 sa_sigaction 成员而不是 sa_handler,并且你的处理函数签名需要是 void handler(int sig, siginfo_t *info, void *context),这样可以获取更多关于信号的信息。

7. 示例代码

下面是一个简单的例子,展示如何使用 sigaction 来处理 SIGINT(Ctrl+C)信号。

#define _GNU_SOURCE // 启用 GNU 扩展,以便使用 SA_RESTART 等
#include <stdio.h>
#include <stdlib.h>  // 包含 exit
#include <unistd.h>  // 包含 sleep
#include <signal.h>  // 包含 sigaction 相关
#include <string.h>  // 包含 memset

// 定义一个信号处理函数
// 这个函数的签名必须是 void function_name(int signum)
void handle_sigint(int sig) {
    // 注意:在信号处理函数内部,应该只调用异步信号安全(async-signal-safe)的函数
    // printf 通常不是异步信号安全的,但 write 是
    // 这里为了简单和可读性使用 printf,但在生产代码中推荐用 write
    printf("\nCaught signal %d (SIGINT, usually Ctrl+C)\n", sig);
    printf("Performing cleanup...\n");
    // 这里可以做一些清理工作,比如关闭文件、释放内存等
    // ...
    printf("Cleanup done. Exiting gracefully.\n");
    exit(EXIT_SUCCESS); // 优雅退出程序
}

int main() {
    struct sigaction sa_new;   // 用于设置新的信号处理动作
    struct sigaction sa_old;   // 用于保存旧的信号处理动作

    // 1. 初始化 sigaction 结构体
    // 使用 memset 将结构体清零,确保没有垃圾数据
    memset(&sa_new, 0, sizeof(sa_new));

    // 2. 设置处理函数
    sa_new.sa_handler = handle_sigint; // 指定我们自定义的处理函数

    // 3. 设置在处理函数执行期间要临时阻塞的信号
    // sigemptyset 初始化信号集为空
    sigemptyset(&sa_new.sa_mask);
    // 如果你想在处理 SIGINT 时也阻塞 SIGTERM,可以这样添加:
    // sigaddset(&sa_new.sa_mask, SIGTERM);

    // 4. 设置标志
    // SA_RESTART: 被信号中断的系统调用自动重启
    sa_new.sa_flags = SA_RESTART;

    // 5. 调用 sigaction 设置 SIGINT 的处理方式
    printf("Setting signal handler for SIGINT (Ctrl+C)...\n");
    if (sigaction(SIGINT, &sa_new, &sa_old) == -1) {
        // 如果 sigaction 调用失败
        perror("sigaction"); // perror 会打印错误信息
        exit(EXIT_FAILURE);  // 退出程序
    }

    // 6. 检查并打印旧的处理方式 (可选)
    printf("Old handler for SIGINT was: ");
    if (sa_old.sa_handler == SIG_DFL) {
        printf("Default action (terminate)\n");
    } else if (sa_old.sa_handler == SIG_IGN) {
        printf("Ignored\n");
    } else {
        printf("A custom function (address: %p)\n", (void*)sa_old.sa_handler);
    }

    // 7. 程序主体逻辑:进入一个循环,等待信号
    printf("Program is running. Press Ctrl+C to trigger the signal handler.\n");
    printf("Or try sending SIGTERM with 'kill %d' in another terminal.\n", getpid());
    while (1) {
        printf("Working... (Press Ctrl+C to stop)\n");
        sleep(2); // 睡眠2秒,模拟工作
        // 如果在 sleep 期间按下 Ctrl+C,handle_sigint 会被调用
    }

    // 程序正常流程不会执行到这里,因为 Ctrl+C 会调用 exit
    return 0;
}

编译和运行:

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

# 运行程序
./sigaction_example

# 在程序运行时,按 Ctrl+C,观察输出
# 或者在另一个终端,使用 kill 命令发送信号
# kill -TERM <PID>  (其中 <PID> 是上面程序输出的 PID)

这个例子展示了如何使用 sigaction 来捕获 SIGINT 信号,并在信号处理函数中执行清理操作后优雅地退出程序。

发表在 linux文章 | 留下评论

rename/renameat/renameat2系统调用及示例

rename/renameat/renameat2系统调用及示例

Linux 文件重命名系统调用详解,Linux 提供了三种文件重命名系统调用:rename、renameat 和 renameat2。其中 rename 是最基础的文件重命名函数,renameat 支持相对路径操作,renameat2 则是最强大的版本,支持额外标志位控制(如不替换已存在文件、交换文件等)。这些函数成功时返回0,失败返回-1并设置errno。文章详细介绍了各函数的原型、参数、标志位选项、常见错误码以及关联函数,并提供了基础用法的示例代码,展示了如何创建测试文件、重命名文件以及覆盖已存在文件的操作过程。

1. 函数介绍

文件重命名是 Linux 系统中最常用的操作之一。可以把文件重命名想象成”文件的身份证号码变更”——文件的内容和属性都没有改变,只是改变了文件在文件系统中的”名字”或”位置”。

这三个函数都用于重命名文件或目录,但功能逐渐增强:

  • rename: 最基础的重命名函数
  • renameat: 支持相对路径的重命名函数
  • renameat2: 最强大的重命名函数,支持额外选项

2. 函数原型

#include <stdio.h>

// 基础重命名
int rename(const char *oldpath, const char *newpath);

// 相对路径重命名
int renameat(int olddirfd, const char *oldpath,
             int newdirfd, const char *newpath);

// 增强版重命名(需要定义 _GNU_SOURCE)
#define _GNU_SOURCE
#include <fcntl.h>
int renameat2(int olddirfd, const char *oldpath,
              int newdirfd, const char *newpath, unsigned int flags);

3. 功能

rename

将 oldpath 指定的文件或目录重命名为 newpath

renameat

在指定目录描述符的上下文中重命名文件或目录,支持相对路径。

renameat2

增强版重命名,支持额外的标志控制选项。

4. 参数

rename 参数

  • oldpath: 旧文件路径名
  • newpath: 新文件路径名

renameat/renameat2 参数

  • olddirfd: 旧文件路径的目录文件描述符
  • oldpath: 旧文件路径名
  • newdirfd: 新文件路径的目录文件描述符
  • newpath: 新文件路径名
  • flags: 控制重命名行为的标志位(仅 renameat2)

5. 标志位(renameat2 专用)

标志说明
RENAME_NOREPLACE(1 << 0)不替换已存在的文件
RENAME_EXCHANGE(1 << 1)交换两个文件
RENAME_WHITEOUT(1 << 2)创建白名单条目(overlayfs 专用)

6. 返回值

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

7. 常见错误码

  • EACCES: 权限不足
  • EBUSY: 文件正被使用
  • EDQUOT: 磁盘配额不足
  • EEXIST: 新文件已存在(当使用 RENAME_NOREPLACE 时)
  • EINVAL: 参数无效(如 flags 无效)
  • EISDIR: 试图用目录覆盖非目录
  • ELOOP: 符号链接循环
  • ENOENT: 文件或目录不存在
  • ENOTDIR: 路径组件不是目录
  • ENOTEMPTY: 目录非空
  • EPERM: 操作不被允许
  • EROFS: 文件系统只读
  • EXDEV: 跨文件系统移动(rename 不支持)

8. 相似函数或关联函数

  • link/unlink: 创建/删除硬链接
  • symlink: 创建符号链接
  • mv: 命令行重命名工具
  • stat: 获取文件状态
  • access: 检查文件访问权限
  • chown/chmod: 修改文件所有者和权限

9. 示例代码

示例1:基础用法 – rename 函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

// 创建测试文件
int create_test_file(const char *filename, const char *content) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (content) {
        write(fd, content, strlen(content));
    }
    
    close(fd);
    printf("创建文件: %s\n", filename);
    return 0;
}

// 显示文件内容
void show_file_content(const char *filename) {
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        printf("无法打开文件: %s\n", filename);
        return;
    }
    
    char buffer[256];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("文件 %s 内容: %s", filename, buffer);
    } else {
        printf("文件 %s 为空\n", filename);
    }
    
    close(fd);
}

// 检查文件是否存在
int file_exists(const char *filename) {
    return access(filename, F_OK) == 0;
}

int main() {
    printf("=== rename 基础示例 ===\n\n");
    
    // 1. 创建测试文件
    printf("1. 创建测试文件:\n");
    if (create_test_file("old_name.txt", "这是原始文件的内容\n") == -1) {
        return 1;
    }
    
    if (create_test_file("another_file.txt", "另一个文件的内容\n") == -1) {
        unlink("old_name.txt");
        return 1;
    }
    
    // 2. 显示原始文件内容
    printf("\n2. 原始文件内容:\n");
    show_file_content("old_name.txt");
    show_file_content("another_file.txt");
    
    // 3. 使用 rename 重命名文件
    printf("\n3. 重命名文件:\n");
    if (rename("old_name.txt", "new_name.txt") == 0) {
        printf("✓ 成功将 'old_name.txt' 重命名为 'new_name.txt'\n");
        
        // 验证重命名结果
        if (file_exists("new_name.txt")) {
            printf("✓ 新文件 'new_name.txt' 存在\n");
        }
        if (!file_exists("old_name.txt")) {
            printf("✓ 旧文件 'old_name.txt' 已不存在\n");
        }
        
        // 显示重命名后的文件内容
        printf("\n重命名后文件内容:\n");
        show_file_content("new_name.txt");
    } else {
        printf("✗ 重命名失败: %s\n", strerror(errno));
    }
    
    // 4. 使用 rename 覆盖已存在的文件
    printf("\n4. 覆盖已存在的文件:\n");
    if (file_exists("another_file.txt")) {
        printf("覆盖前 'another_file.txt' 存在\n");
        
        if (rename("new_name.txt", "another_file.txt") == 0) {
            printf("✓ 成功用 'new_name.txt' 覆盖 'another_file.txt'\n");
            
            // 验证覆盖结果
            if (file_exists("another_file.txt")) {
                printf("✓ 'another_file.txt' 现在包含原始文件的内容\n");
                show_file_content("another_file.txt");
            }
            if (!file_exists("new_name.txt")) {
                printf("✓ 原来的 'new_name.txt' 已不存在\n");
            }
        } else {
            printf("✗ 覆盖失败: %s\n", strerror(errno));
        }
    }
    
    // 5. 尝试重命名不存在的文件
    printf("\n5. 重命名不存在的文件:\n");
    if (rename("nonexistent.txt", "should_fail.txt") == -1) {
        printf("✓ 正确处理不存在的文件: %s\n", strerror(errno));
    }
    
    // 6. 清理测试文件
    printf("\n6. 清理测试文件:\n");
    if (file_exists("another_file.txt")) {
        unlink("another_file.txt");
        printf("✓ 删除 another_file.txt\n");
    }
    
    printf("\n=== rename 函数特点 ===\n");
    printf("1. 原子操作: 重命名是原子的\n");
    printf("2. 同一文件系统: 只能在同一文件系统内移动\n");
    printf("3. 覆盖行为: 默认会覆盖已存在的文件\n");
    printf("4. 权限保持: 文件权限和所有者保持不变\n");
    printf("5. 简单易用: 最基础的文件重命名函数\n");
    printf("6. 限制: 不支持相对路径和高级选项\n");
    
    return 0;
}

示例2:增强功能 – renameat 函数

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

// 创建目录
int create_directory(const char *dirname) {
    if (mkdir(dirname, 0755) == -1) {
        if (errno != EEXIST) {
            perror("创建目录失败");
            return -1;
        }
    }
    printf("创建目录: %s\n", dirname);
    return 0;
}

// 在目录中创建文件
int create_file_in_directory(const char *dirname, const char *filename, 
                           const char *content) {
    char full_path[512];
    snprintf(full_path, sizeof(full_path), "%s/%s", dirname, filename);
    
    int fd = open(full_path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (content) {
        write(fd, content, strlen(content));
    }
    
    close(fd);
    printf("在目录 '%s' 中创建文件: %s\n", dirname, filename);
    return 0;
}

// 列出目录内容
void list_directory_contents(const char *dirname) {
    DIR *dir = opendir(dirname);
    if (!dir) {
        printf("无法打开目录: %s\n", dirname);
        return;
    }
    
    printf("目录 '%s' 的内容:\n", dirname);
    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
            printf("  %s\n", entry->d_name);
        }
    }
    closedir(dir);
    printf("\n");
}

int main() {
    printf("=== renameat 增强功能示例 ===\n\n");
    
    // 1. 创建测试目录和文件
    printf("1. 创建测试环境:\n");
    if (create_directory("source_dir") == -1) {
        return 1;
    }
    if (create_directory("target_dir") == -1) {
        rmdir("source_dir");
        return 1;
    }
    
    // 在源目录中创建文件
    if (create_file_in_directory("source_dir", "file1.txt", "源目录中的文件1\n") == -1 ||
        create_file_in_directory("source_dir", "file2.txt", "源目录中的文件2\n") == -1) {
        rmdir("source_dir");
        rmdir("target_dir");
        return 1;
    }
    
    // 在目标目录中创建文件
    if (create_file_in_directory("target_dir", "existing.txt", "目标目录中的已有文件\n") == -1) {
        // 清理并退出
        unlink("source_dir/file1.txt");
        unlink("source_dir/file2.txt");
        rmdir("source_dir");
        rmdir("target_dir");
        return 1;
    }
    
    // 2. 显示初始状态
    printf("\n2. 初始状态:\n");
    list_directory_contents("source_dir");
    list_directory_contents("target_dir");
    
    // 3. 使用 renameat 移动文件(需要打开目录)
    printf("3. 使用 renameat 移动文件:\n");
    
    int source_fd = open("source_dir", O_RDONLY);
    int target_fd = open("target_dir", O_RDONLY);
    
    if (source_fd == -1 || target_fd == -1) {
        perror("打开目录失败");
        if (source_fd != -1) close(source_fd);
        if (target_fd != -1) close(target_fd);
        goto cleanup;
    }
    
    // 移动 file1.txt 从 source_dir 到 target_dir
    if (renameat(source_fd, "file1.txt", target_fd, "moved_file1.txt") == 0) {
        printf("✓ 成功移动文件 'file1.txt' -> 'moved_file1.txt'\n");
    } else {
        printf("✗ 移动文件失败: %s\n", strerror(errno));
    }
    
    // 使用 AT_FDCWD 移动 file2.txt
    printf("\n4. 使用 AT_FDCWD 移动文件:\n");
    if (renameat(AT_FDCWD, "source_dir/file2.txt", AT_FDCWD, "target_dir/moved_file2.txt") == 0) {
        printf("✓ 成功移动文件 'source_dir/file2.txt' -> 'target_dir/moved_file2.txt'\n");
    } else {
        printf("✗ 移动文件失败: %s\n", strerror(errno));
    }
    
    close(source_fd);
    close(target_fd);
    
    // 5. 显示移动后的状态
    printf("\n5. 移动后状态:\n");
    list_directory_contents("source_dir");
    list_directory_contents("target_dir");
    
    // 6. 覆盖已存在的文件
    printf("\n6. 覆盖已存在的文件:\n");
    printf("尝试用 'source_dir/file1.txt' 覆盖 'target_dir/existing.txt'\n");
    
    source_fd = open("source_dir", O_RDONLY);
    target_fd = open("target_dir", O_RDONLY);
    
    if (source_fd != -1 && target_fd != -1) {
        if (renameat(source_fd, "file1.txt", target_fd, "existing.txt") == 0) {
            printf("✓ 成功覆盖文件\n");
        } else {
            printf("✗ 覆盖失败: %s\n", strerror(errno));
        }
        close(source_fd);
        close(target_fd);
    }
    
    // 7. 显示最终状态
    printf("\n7. 最终状态:\n");
    list_directory_contents("source_dir");
    list_directory_contents("target_dir");
    
cleanup:
    // 8. 清理测试文件
    printf("\n8. 清理测试环境:\n");
    
    // 删除 target_dir 中的文件
    unlink("target_dir/moved_file1.txt");
    unlink("target_dir/moved_file2.txt");
    unlink("target_dir/existing.txt");
    
    // 删除 source_dir 中的文件
    unlink("source_dir/file1.txt");
    unlink("source_dir/file2.txt");
    
    // 删除目录
    rmdir("source_dir");
    rmdir("target_dir");
    
    printf("✓ 清理完成\n");
    
    printf("\n=== renameat 函数特点 ===\n");
    printf("1. 相对路径支持: 支持相对于目录描述符的路径\n");
    printf("2. 目录描述符: 可以使用已打开的目录文件描述符\n");
    printf("3. AT_FDCWD: 可以使用当前工作目录\n");
    printf("4. 原子操作: 重命名仍然是原子操作\n");
    printf("5. 跨目录移动: 可以在不同目录间移动文件\n");
    printf("6. 权限保持: 文件权限和所有者保持不变\n");
    printf("\n");
    printf("优势:\n");
    printf("1. 更灵活的路径处理\n");
    printf("2. 支持相对路径操作\n");
    printf("3. 可以避免重复打开目录\n");
    printf("4. 更好的错误处理\n");
    
    return 0;
}

示例3:高级功能 – renameat2 函数

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

// 检查系统是否支持 renameat2
int check_renameat2_support() {
    // 尝试一个简单的 renameat2 调用
    int result = renameat2(AT_FDCWD, "test", AT_FDCWD, "test", 0);
    if (result == -1 && errno == ENOSYS) {
        return 0;  // 不支持
    }
    return 1;  // 支持(或者其他错误)
}

// 创建测试文件
int create_test_file_advanced(const char *filename, const char *content) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (content) {
        write(fd, content, strlen(content));
    }
    
    close(fd);
    printf("创建文件: %s\n", filename);
    return 0;
}

// 显示文件详细信息
void show_file_details(const char *filename) {
    struct stat st;
    if (stat(filename, &st) == 0) {
        printf("文件: %s\n", filename);
        printf("  Inode: %ld\n", (long)st.st_ino);
        printf("  大小: %ld 字节\n", (long)st.st_size);
        printf("  权限: %o\n", st.st_mode & 0777);
        printf("  链接数: %ld\n", (long)st.st_nlink);
        printf("  修改时间: %s", ctime(&st.st_mtime));
    } else {
        printf("文件不存在: %s\n", filename);
    }
}

int main() {
    printf("=== renameat2 高级功能示例 ===\n\n");
    
    // 检查系统支持
    printf("1. 检查系统支持:\n");
    if (!check_renameat2_support()) {
        printf("⚠ 系统不支持 renameat2,使用模拟功能\n");
        printf("  Linux 内核 3.15+ 才支持 renameat2\n\n");
    } else {
        printf("✓ 系统支持 renameat2\n\n");
    }
    
    // 2. 创建测试文件
    printf("2. 创建测试文件:\n");
    if (create_test_file_advanced("file_a.txt", "文件 A 的内容\n") == -1 ||
        create_test_file_advanced("file_b.txt", "文件 B 的内容\n") == -1 ||
        create_test_file_advanced("protected_file.txt", "受保护的文件内容\n") == -1) {
        return 1;
    }
    
    // 3. 显示初始文件信息
    printf("\n3. 初始文件信息:\n");
    show_file_details("file_a.txt");
    show_file_details("file_b.txt");
    show_file_details("protected_file.txt");
    
    // 4. 演示 RENAME_NOREPLACE 标志(不覆盖已存在的文件)
    printf("\n4. 演示 RENAME_NOREPLACE (不覆盖):\n");
    printf("尝试将 'file_a.txt' 重命名为 'protected_file.txt' (不覆盖):\n");
    
    // 在支持的系统上使用 renameat2
    int result = renameat2(AT_FDCWD, "file_a.txt", AT_FDCWD, "protected_file.txt", 
                          RENAME_NOREPLACE);
    if (result == -1) {
        if (errno == EEXIST) {
            printf("✓ 正确阻止了覆盖操作: 文件已存在\n");
        } else if (errno == ENOSYS) {
            printf("⚠ 系统不支持 renameat2,使用模拟方式\n");
            // 检查文件是否存在,如果不存在则重命名
            if (access("protected_file.txt", F_OK) == 0) {
                printf("  文件已存在,阻止覆盖\n");
            } else {
                if (rename("file_a.txt", "protected_file.txt") == 0) {
                    printf("  成功重命名\n");
                } else {
                    printf("  重命名失败: %s\n", strerror(errno));
                }
            }
        } else {
            printf("✗ 操作失败: %s\n", strerror(errno));
        }
    } else {
        printf("✓ 成功重命名,没有覆盖文件\n");
    }
    
    // 5. 演示普通的重命名(会覆盖)
    printf("\n5. 演示普通重命名 (会覆盖):\n");
    printf("将 'file_a.txt' 重命名为 'new_file_a.txt':\n");
    
    if (rename("file_a.txt", "new_file_a.txt") == 0) {
        printf("✓ 成功重命名\n");
        show_file_details("new_file_a.txt");
    } else {
        printf("✗ 重命名失败: %s\n", strerror(errno));
    }
    
    // 6. 演示文件交换功能(RENAME_EXCHANGE)
    printf("\n6. 演示文件交换功能:\n");
    printf("交换 'new_file_a.txt' 和 'file_b.txt' 的内容:\n");
    
    // 显示交换前的内容
    printf("交换前:\n");
    printf("  new_file_a.txt 内容: ");
    {
        int fd = open("new_file_a.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    printf("  file_b.txt 内容: ");
    {
        int fd = open("file_b.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    // 尝试使用 renameat2 交换文件
    result = renameat2(AT_FDCWD, "new_file_a.txt", AT_FDCWD, "file_b.txt", 
                      RENAME_EXCHANGE);
    if (result == -1) {
        if (errno == ENOSYS) {
            printf("⚠ 系统不支持 RENAME_EXCHANGE,使用传统方式模拟\n");
            printf("  传统方式: 需要临时文件进行三次重命名\n");
            
            // 创建临时文件名
            if (rename("new_file_a.txt", "temp_swap_file.txt") == 0) {
                if (rename("file_b.txt", "new_file_a.txt") == 0) {
                    if (rename("temp_swap_file.txt", "file_b.txt") == 0) {
                        printf("✓ 成功使用传统方式交换文件\n");
                    } else {
                        printf("✗ 第三步交换失败\n");
                        // 恢复第一步
                        rename("new_file_a.txt", "file_b.txt");
                        rename("temp_swap_file.txt", "new_file_a.txt");
                    }
                } else {
                    printf("✗ 第二步交换失败\n");
                    // 恢复第一步
                    rename("temp_swap_file.txt", "new_file_a.txt");
                }
            } else {
                printf("✗ 第一步交换失败\n");
            }
        } else {
            printf("✗ 交换失败: %s\n", strerror(errno));
        }
    } else {
        printf("✓ 成功交换文件内容\n");
    }
    
    // 显示交换后的内容
    printf("交换后:\n");
    printf("  new_file_a.txt 内容: ");
    {
        int fd = open("new_file_a.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    printf("  file_b.txt 内容: ");
    {
        int fd = open("file_b.txt", O_RDONLY);
        if (fd != -1) {
            char buffer[100];
            ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0';
                printf("%s", buffer);
            }
            close(fd);
        }
    }
    
    // 7. 跨文件系统移动演示
    printf("\n7. 跨文件系统移动演示:\n");
    printf("尝试跨文件系统移动文件 (通常会失败):\n");
    
    // 这个操作通常会失败,因为 rename 不支持跨文件系统
    result = rename("/etc/passwd", "./passwd_copy");
    if (result == -1) {
        if (errno == EXDEV) {
            printf("✓ 正确检测到跨文件系统移动: %s\n", strerror(errno));
            printf("  说明: rename 不支持跨文件系统移动\n");
            printf("  解决方案: 使用 copy + delete 或 mv 命令\n");
        } else {
            printf("✗ 其他错误: %s\n", strerror(errno));
        }
    } else {
        printf("✓ 跨文件系统移动成功 (罕见情况)\n");
        // 如果成功,需要删除副本
        unlink("./passwd_copy");
    }
    
    // 8. 清理测试文件
    printf("\n8. 清理测试文件:\n");
    const char *files_to_delete[] = {
        "new_file_a.txt", "file_b.txt", "protected_file.txt", 
        "temp_swap_file.txt", NULL
    };
    
    for (int i = 0; files_to_delete[i]; i++) {
        if (access(files_to_delete[i], F_OK) == 0) {
            if (unlink(files_to_delete[i]) == 0) {
                printf("✓ 删除文件: %s\n", files_to_delete[i]);
            } else {
                printf("✗ 删除文件失败: %s\n", strerror(errno));
            }
        }
    }
    
    printf("\n=== 三种重命名函数对比 ===\n");
    printf("┌─────────────┬─────────────┬─────────────┬─────────────┐\n");
    printf("│ 函数        │ 基础功能    │ 相对路径    │ 高级选项    │\n");
    printf("├─────────────┼─────────────┼─────────────┼─────────────┤\n");
    printf("│ rename      │ ✓           │ ✗           │ ✗           │\n");
    printf("│ renameat    │ ✓           │ ✓           │ ✗           │\n");
    printf("│ renameat2   │ ✓           │ ✓           │ ✓           │\n");
    printf("└─────────────┴─────────────┴─────────────┴─────────────┘\n");
    printf("\n");
    
    printf("功能对比:\n");
    printf("1. rename:\n");
    printf("   - 最简单的重命名操作\n");
    printf("   - 只支持绝对路径\n");
    printf("   - 会自动覆盖已存在的文件\n");
    printf("   - 最广泛的系统支持\n");
    printf("\n");
    
    printf("2. renameat:\n");
    printf("   - 支持相对路径操作\n");
    printf("   - 可以使用目录文件描述符\n");
    printf("   - 支持 AT_FDCWD 常量\n");
    printf("   - 需要较新的系统支持\n");
    printf("\n");
    
    printf("3. renameat2:\n");
    printf("   - 支持所有 renameat 功能\n");
    printf("   - 添加了高级控制选项\n");
    printf("   - 支持原子文件交换\n");
    printf("   - 支持不覆盖选项\n");
    printf("   - 需要最新的内核支持\n");
    printf("\n");
    
    printf("=== 使用建议 ===\n");
    printf("选择原则:\n");
    printf("1. 简单重命名: 使用 rename\n");
    printf("2. 相对路径操作: 使用 renameat\n");
    printf("3. 高级控制需求: 使用 renameat2\n");
    printf("4. 跨平台兼容: 使用 rename\n");
    printf("5. 安全操作: 使用 renameat2 + RENAME_NOREPLACE\n");
    printf("\n");
    
    printf("安全最佳实践:\n");
    printf("1. 始终检查返回值和 errno\n");
    printf("2. 使用 RENAME_NOREPLACE 避免意外覆盖\n");
    printf("3. 在关键操作前备份重要文件\n");
    printf("4. 检查文件权限和磁盘空间\n");
    printf("5. 使用事务性操作确保数据完整性\n");
    
    return 0;
}

示例4:完整的文件管理工具

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

// 配置结构体
struct rename_config {
    int interactive;     // 交互模式
    int force;          // 强制模式
    int backup;         // 备份模式
    int verbose;        // 详细输出
    int no_replace;     // 不替换模式
    int exchange;       // 交换模式
    char *suffix;       // 备份后缀
};

// 安全的文件重命名函数
int safe_rename(const char *oldpath, const char *newpath, 
               const struct rename_config *config) {
    // 验证参数
    if (!oldpath || !newpath) {
        errno = EINVAL;
        return -1;
    }
    
    // 检查源文件是否存在
    if (access(oldpath, F_OK) != 0) {
        if (errno == ENOENT) {
            fprintf(stderr, "错误: 源文件不存在 '%s'\n", oldpath);
        } else {
            perror("检查源文件失败");
        }
        return -1;
    }
    
    // 检查目标文件是否存在
    if (access(newpath, F_OK) == 0) {
        if (config->interactive) {
            printf("目标文件 '%s' 已存在,是否覆盖? (y/N) ", newpath);
            char response[10];
            if (fgets(response, sizeof(response), stdin)) {
                if (response[0] != 'y' && response[0] != 'Y') {
                    printf("取消操作\n");
                    return 0;
                }
            }
        } else if (config->no_replace) {
            fprintf(stderr, "错误: 目标文件已存在 '%s'\n", newpath);
            errno = EEXIST;
            return -1;
        }
        
        // 创建备份
        if (config->backup) {
            char backup_name[1024];
            if (config->suffix) {
                snprintf(backup_name, sizeof(backup_name), "%s%s", newpath, config->suffix);
            } else {
                snprintf(backup_name, sizeof(backup_name), "%s~", newpath);
            }
            
            if (config->verbose) {
                printf("创建备份: %s -> %s\n", newpath, backup_name);
            }
            
            if (rename(newpath, backup_name) != 0) {
                perror("创建备份失败");
                return -1;
            }
        }
    }
    
    // 执行重命名
    int result;
    if (config->no_replace) {
        // 使用 renameat2 避免覆盖
        #ifdef RENAME_NOREPLACE
        result = renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_NOREPLACE);
        #else
        // 如果不支持,手动检查
        if (access(newpath, F_OK) == 0) {
            errno = EEXIST;
            result = -1;
        } else {
            result = rename(oldpath, newpath);
        }
        #endif
    } else if (config->exchange) {
        // 使用 renameat2 交换文件
        #ifdef RENAME_EXCHANGE
        result = renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_EXCHANGE);
        #else
        // 如果不支持,使用传统方式
        char temp_name[1024];
        snprintf(temp_name, sizeof(temp_name), "%s.temp_swap_%d", oldpath, getpid());
        
        result = rename(oldpath, temp_name);
        if (result == 0) {
            result = rename(newpath, oldpath);
            if (result == 0) {
                result = rename(temp_name, newpath);
                if (result != 0) {
                    // 如果第三步失败,恢复第二步
                    rename(oldpath, newpath);
                    rename(temp_name, oldpath);
                }
            } else {
                // 如果第二步失败,恢复第一步
                rename(temp_name, oldpath);
            }
        }
        #endif
    } else {
        result = rename(oldpath, newpath);
    }
    
    if (result == 0) {
        if (config->verbose) {
            if (config->exchange) {
                printf("✓ 成功交换文件 '%s' 和 '%s'\n", oldpath, newpath);
            } else {
                printf("✓ 成功重命名 '%s' -> '%s'\n", oldpath, newpath);
            }
        }
    } else {
        switch (errno) {
            case EACCES:
                fprintf(stderr, "错误: 权限不足\n");
                break;
            case EEXIST:
                fprintf(stderr, "错误: 目标文件已存在\n");
                break;
            case ENOENT:
                fprintf(stderr, "错误: 文件不存在\n");
                break;
            case EXDEV:
                fprintf(stderr, "错误: 不支持跨文件系统移动\n");
                break;
            case EISDIR:
                fprintf(stderr, "错误: 目录操作冲突\n");
                break;
            case ENOTEMPTY:
                fprintf(stderr, "错误: 目录非空\n");
                break;
            default:
                fprintf(stderr, "错误: %s\n", strerror(errno));
                break;
        }
    }
    
    return result;
}

// 显示帮助信息
void show_help(const char *program_name) {
    printf("用法: %s [选项] 源文件 目标文件\n", program_name);
    printf("\n选项:\n");
    printf("  -i, --interactive      交互模式(覆盖前询问)\n");
    printf("  -f, --force             强制模式(忽略错误)\n");
    printf("  -b, --backup            创建备份\n");
    printf("  -S, --suffix=SUFFIX     备份文件后缀\n");
    printf("  -v, --verbose           详细输出\n");
    printf("  -n, --no-replace        不替换已存在的文件\n");
    printf("  -x, --exchange          交换两个文件\n");
    printf("  -h, --help              显示此帮助信息\n");
    printf("\n示例:\n");
    printf("  %s old.txt new.txt              # 重命名文件\n", program_name);
    printf("  %s -i old.txt existing.txt       # 交互式重命名\n", program_name);
    printf("  %s -b old.txt new.txt            # 重命名并备份\n", program_name);
    printf("  %s -n old.txt existing.txt       # 不覆盖已存在文件\n", program_name);
    printf("  %s -x file1.txt file2.txt       # 交换两个文件\n", program_name);
    printf("  %s -v old.txt new.txt           # 详细输出\n", program_name);
}

int main(int argc, char *argv[]) {
    struct rename_config config = {
        .interactive = 0,
        .force = 0,
        .backup = 0,
        .verbose = 0,
        .no_replace = 0,
        .exchange = 0,
        .suffix = NULL
    };
    
    printf("=== 文件重命名管理工具 ===\n\n");
    
    // 解析命令行参数
    static struct option long_options[] = {
        {"interactive", no_argument,       0, 'i'},
        {"force",       no_argument,       0, 'f'},
        {"backup",      no_argument,       0, 'b'},
        {"suffix",      required_argument, 0, 'S'},
        {"verbose",     no_argument,       0, 'v'},
        {"no-replace",  no_argument,       0, 'n'},
        {"exchange",    no_argument,       0, 'x'},
        {"help",        no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    int opt;
    while ((opt = getopt_long(argc, argv, "ifbS:vnxh", long_options, NULL)) != -1) {
        switch (opt) {
            case 'i':
                config.interactive = 1;
                break;
            case 'f':
                config.force = 1;
                break;
            case 'b':
                config.backup = 1;
                break;
            case 'S':
                config.suffix = optarg;
                break;
            case 'v':
                config.verbose = 1;
                break;
            case 'n':
                config.no_replace = 1;
                break;
            case 'x':
                config.exchange = 1;
                break;
            case 'h':
                show_help(argv[0]);
                return 0;
            default:
                fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
                return 1;
        }
    }
    
    // 检查参数数量
    if (optind + 1 >= argc) {
        fprintf(stderr, "错误: 需要指定源文件和目标文件\n");
        fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
        return 1;
    }
    
    const char *oldpath = argv[optind];
    const char *newpath = argv[optind + 1];
    
    // 显示配置信息
    if (config.verbose) {
        printf("操作配置:\n");
        printf("  源文件: %s\n", oldpath);
        printf("  目标文件: %s\n", newpath);
        printf("  交互模式: %s\n", config.interactive ? "是" : "否");
        printf("  强制模式: %s\n", config.force ? "是" : "否");
        printf("  备份模式: %s\n", config.backup ? "是" : "否");
        printf("  不替换: %s\n", config.no_replace ? "是" : "否");
        printf("  文件交换: %s\n", config.exchange ? "是" : "否");
        printf("  详细输出: %s\n", config.verbose ? "是" : "否");
        if (config.backup && config.suffix) {
            printf("  备份后缀: %s\n", config.suffix);
        }
        printf("\n");
    }
    
    // 执行重命名操作
    int result = safe_rename(oldpath, newpath, &config);
    
    // 显示结果
    if (result == 0) {
        printf("操作完成\n");
    } else if (!config.force) {
        return 1;
    }
    
    // 显示使用建议
    printf("\n=== 重命名最佳实践 ===\n");
    printf("安全建议:\n");
    printf("1. 重要文件操作前创建备份\n");
    printf("2. 使用 -i 选项避免意外覆盖\n");
    printf("3. 使用 -n 选项保护已存在的文件\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. 始终检查返回值和 errno\n");
    printf("2. 区分不同类型的错误\n");
    printf("3. 实现适当的重试机制\n");
    printf("4. 提供清晰的错误信息\n");
    printf("5. 保持操作的原子性\n");
    
    return (result == 0 || config.force) ? 0 : 1;
}

编译和运行说明

# 编译示例程序
gcc -o rename_example1 example1.c
gcc -o rename_example2 example2.c
gcc -o rename_example3 example3.c
gcc -o rename_example4 example4.c

# 运行示例
./rename_example1
./rename_example2
./rename_example3
./rename_example4 --help
./rename_example4 -v old.txt new.txt
./rename_example4 -i -b old.txt existing.txt

系统要求检查

# 检查内核版本(renameat2 需要 3.15+)
uname -r

# 检查 glibc 版本
ldd --version

# 检查系统调用支持
grep -w rename /usr/include/asm/unistd_64.h

# 检查 renameat2 支持
grep -w renameat2 /usr/include/asm/unistd_64.h

# 查看文件系统类型
df -T .

重要注意事项

  1. 原子性: 重命名操作是原子的
  2. 权限要求: 需要对目录有写权限
  3. 跨文件系统: 不支持跨文件系统移动
  4. 错误处理: 始终检查返回值和 errno
  5. 符号链接: 不会跟随符号链接
  6. 目录操作: 可以重命名目录

实际应用场景

  1. 文件管理: 日常文件重命名操作
  2. 版本控制: 文件版本管理
  3. 备份系统: 自动备份和版本控制
  4. 日志轮转: 日志文件重命名和轮转
  5. 临时文件: 临时文件重命名为正式文件
  6. 原子更新: 原子性的文件更新操作

最佳实践

// 安全的重命名函数
int secure_rename(const char *oldpath, const char *newpath) {
    // 验证参数
    if (!oldpath || !newpath) {
        errno = EINVAL;
        return -1;
    }
    
    // 检查源文件
    if (access(oldpath, F_OK) != 0) {
        return -1;  // errno 已设置
    }
    
    // 检查目标文件
    if (access(newpath, F_OK) == 0) {
        printf("警告: 目标文件已存在 '%s'\n", newpath);
    }
    
    // 执行重命名
    int result = rename(oldpath, newpath);
    if (result == 0) {
        printf("成功重命名 '%s' -> '%s'\n", oldpath, newpath);
    } else {
        fprintf(stderr, "重命名失败 '%s' -> '%s': %s\n", 
                oldpath, newpath, strerror(errno));
    }
    
    return result;
}

// 原子文件更新
int atomic_file_update(const char *temp_file, const char *final_file) {
    // 先创建临时文件并写入数据
    // ...
    
    // 原子性地重命名为最终文件
    if (rename(temp_file, final_file) == 0) {
        return 0;  // 成功
    } else {
        // 清理临时文件
        unlink(temp_file);
        return -1;
    }
}

// 批量重命名
int batch_rename(const char **old_paths, const char **new_paths, int count) {
    int success_count = 0;
    int failed_count = 0;
    
    for (int i = 0; i < count; i++) {
        if (rename(old_paths[i], new_paths[i]) == 0) {
            printf("✓ 重命名: %s -> %s\n", old_paths[i], new_paths[i]);
            success_count++;
        } else {
            fprintf(stderr, "✗ 重命名失败: %s -> %s: %s\n", 
                    old_paths[i], new_paths[i], strerror(errno));
            failed_count++;
        }
    }
    
    printf("批量重命名完成: 成功 %d, 失败 %d\n", success_count, failed_count);
    return failed_count;
}

这些示例展示了 renamerenameat 和 renameat2 函数的各种使用方法,从基础的文件重命名到完整的文件管理工具,帮助你全面掌握 Linux 系统中的文件重命名机制。

发表在 linux文章 | 留下评论

Linux内核kfifo实现详解

Linux内核kfifo实现详解

Linux内核kfifo是一种高效的无锁环形缓冲区实现,其核心设计包括:1)使用2的幂次方大小缓冲区,通过位运算替代取模运算提高性能;2)分离的in/out索引设计,避免锁机制;3)内存屏障确保数据一致性。kfifo通过位运算优化索引计算(position & mask),并采用两段复制策略处理环形缓冲区的边界条件,在单生产者单消费者场景下实现高效无锁操作。

1. kfifo设计原理

1.1 核心思想

Linux内核kfifo(kernel FIFO)是一个高效、无锁的环形缓冲区实现,专为内核环境设计。

/**
 * kfifo的核心数据结构
 * 为什么这样设计?
 */
struct __kfifo {
    unsigned int    in;     // 入队索引
    unsigned int    out;    // 出队索引  
    unsigned int    mask;   // 掩码,用于快速取模运算
    unsigned int    esize;  // 元素大小
    void        *data;      // 数据缓冲区指针
};

1.2 关键设计决策

1.2.1 2的幂次方大小

// 为什么要求缓冲区大小是2的幂次方?
// 因为可以使用位运算代替取模运算,提高性能

// 普通取模运算
index = position % size;  // 除法运算,较慢

// 2的幂次方优化
mask = size - 1;          // 例如:size=8, mask=7 (0111)
index = position & mask;  // 位运算,非常快

// 示例:
// position = 10, size = 8
// 普通方法:10 % 8 = 2
// 优化方法:10 & 7 = 0x0A & 0x07 = 0x02 = 2

1.2.2 索引分离设计

// 为什么使用分离的in/out索引而不是head/tail?
// 这样可以避免复杂的边界检查和锁机制

struct __kfifo {
    unsigned int in;   // 累计入队元素数
    unsigned int out;  // 累计出队元素数
    // 实际索引通过 in & mask 和 out & mask 计算
};

// 当前缓冲区中元素数量
unsigned int len = fifo->in - fifo->out;

// 实际写入位置
unsigned int write_index = fifo->in & fifo->mask;

// 实际读取位置  
unsigned int read_index = fifo->out & fifo->mask;

2. 内存布局优化

2.1 缓冲区大小计算

/**
 * 确保缓冲区大小是2的幂次方的算法
 */
static int __init kfifo_alloc_common(struct __kfifo *fifo, 
                                    unsigned int size,
                                    size_t esize,
                                    gfp_t gfp_mask)
{
    /*
     * Round up to the next power of 2, as vaddr_t is a power of 2,
     * and the FIFO size and cache line size are both powers of 2.
     */
    size = roundup_pow_of_two(size);
    
    fifo->in = 0;
    fifo->out = 0;
    fifo->esize = esize;
    
    if (size < 2) {
        fifo->data = NULL;
        fifo->mask = 0;
        return -EINVAL;
    }
    
    fifo->data = kmalloc(size * esize, gfp_mask);
    if (!fifo->data) {
        fifo->mask = 0;
        return -ENOMEM;
    }
    
    fifo->mask = size - 1;  // 关键:掩码用于快速取模
    
    return 0;
}

2.2 位运算优化详解

/**
 * 位运算优化示例
 */
// 传统方法:使用取模运算
int traditional_index(int position, int size) {
    return position % size;  // 涉及除法运算,较慢
}

// kfifo方法:使用位运算
int optimized_index(int position, int mask) {
    return position & mask;  // 位运算,非常快
}

// 示例对比:
// size = 8 (2^3), mask = 7 (0111)
// position = 0..15 的索引计算:
// 位置 0: 0 & 7 = 0    0 % 8 = 0
// 位置 1: 1 & 7 = 1    1 % 8 = 1  
// 位置 7: 7 & 7 = 7    7 % 8 = 7
// 位置 8: 8 & 7 = 0    8 % 8 = 0  (循环回到开始)
// 位置 9: 9 & 7 = 1    9 % 8 = 1  (循环)

3. 无锁设计原理

3.1 单生产者单消费者无锁实现

/**
 * 无锁kfifo的核心:单生产者单消费者场景
 * 在这种场景下,in和out变量分别只被一个线程修改,无需锁保护
 */

// 生产者线程(只能修改in变量)
unsigned int kfifo_in(struct __kfifo *fifo,
                      const void *buf, unsigned int len)
{
    unsigned int l;
    
    // 计算可用空间
    len = min(len, fifo->mask + 1 - fifo->in + fifo->out);
    
    /* first put the data starting from fifo->in to buffer end */
    l = min(len, fifo->mask + 1 - (fifo->in & fifo->mask));
    memcpy(fifo->data + (fifo->in & fifo->mask) * fifo->esize, buf, l * fifo->esize);
    
    /* then put the rest (if any) at the beginning of the buffer */
    memcpy(fifo->data, (char *)buf + l * fifo->esize,
           (len - l) * fifo->esize);
    
    /*
     * Ensure that we add the bytes to the kfifo -before-
     * we update the fifo->in index.
     */
    smp_wmb();  // 内存屏障,确保数据写入完成
    
    fifo->in += len;  // 原子更新in索引
    
    return len;
}

// 消费者线程(只能修改out变量)  
unsigned int kfifo_out(struct __kfifo *fifo,
                       void *buf, unsigned int len)
{
    unsigned int l;
    
    // 计算可用数据
    len = min(len, fifo->in - fifo->out);
    
    /* first get the data from fifo->out until the end of the buffer */
    l = min(len, fifo->mask + 1 - (fifo->out & fifo->mask));
    memcpy(buf, fifo->data + (fifo->out & fifo->mask) * fifo->esize, l * fifo->esize);
    
    /* then get the rest (if any) from the beginning of the buffer */
    memcpy((char *)buf + l * fifo->esize, fifo->data,
           (len - l) * fifo->esize);
    
    /*
     * Ensure that we remove the bytes from the kfifo -before-
     * we update the fifo->out index.
     */
    smp_mb();  // 内存屏障
    
    fifo->out += len;  // 原子更新out索引
    
    return len;
}

3.2 内存屏障的作用

/**
 * 内存屏障的重要性
 * 防止编译器和CPU重排序导致的数据不一致
 */

// 生产者端
smp_wmb();  // write memory barrier
// 确保数据写入缓冲区的操作在更新in索引之前完成
fifo->in += len;

// 消费者端  
smp_mb();   // memory barrier
// 确保读取数据的操作在更新out索引之前完成
fifo->out += len;

4. 边界条件处理

4.1 环形缓冲区的两段复制

/**
 * 环形缓冲区的挑战:数据可能跨越缓冲区边界
 * 需要分两段复制
 */

// 示例:缓冲区大小为8,当前状态
// [0][1][2][3][4][5][6][7]
//        ^out      ^in
// 数据在位置[3][4][5][6][7]

// 当要写入大量数据时,可能需要分两段:
// 第一段:从in位置到缓冲区末尾
// 第二段:从缓冲区开始到剩余数据

unsigned int kfifo_in(struct __kfifo *fifo,
                      const void *buf, unsigned int len)
{
    unsigned int l;
    
    // 计算第一段可以写入的数据量
    l = min(len, fifo->mask + 1 - (fifo->in & fifo->mask));
    
    // 第一段复制:从当前in位置到缓冲区末尾
    memcpy(fifo->data + (fifo->in & fifo->mask) * fifo->esize, 
           buf, 
           l * fifo->esize);
    
    // 第二段复制:如果还有剩余数据,从缓冲区开始处继续
    memcpy(fifo->data,
           (char *)buf + l * fifo->esize,
           (len - l) * fifo->esize);
    
    fifo->in += len;
    return len;
}

4.2 空/满状态判断

/**
 * 空/满状态判断的巧妙设计
 */

// 判断是否为空
#define kfifo_is_empty(fifo) \
({ \
    typeof((fifo) + 1) __tmp = (fifo); \
    struct __kfifo *__kfifo = &__tmp->kfifo; \
    __kfifo->in == __kfifo->out; \
})

// 判断是否为满
#define kfifo_is_full(fifo) \
({ \
    typeof((fifo) + 1) __tmp = (fifo); \
    struct __kfifo *__kfifo = &__tmp->kfifo; \
    kfifo_len(__tmp) == __kfifo->mask + 1; \
})

// 计算当前元素数量
#define kfifo_len(fifo) \
({ \
    typeof((fifo) + 1) __tmp = (fifo); \
    struct __kfifo *__kfifo = &__tmp->kfifo; \
    __kfifo->in - __kfifo->out; \
})

// 为什么满状态是 len == mask + 1?
// 因为需要保留一个空位来区分空和满状态
// 如果in == out,表示空
// 如果in == out + size,表示满(但这样in和out会相等)
// 所以实际容量是 size - 1

5. 类型安全的宏设计

5.1 泛型支持

/**
 * kfifo的类型安全设计
 */

// 类型安全的宏定义
#define DECLARE_KFIFO(name, size) \
    struct { \
        struct __kfifo kfifo; \
        typeof(name) *rectype; \
    } name

// 类型安全的入队操作
#define kfifo_put(fifo, val) \
({ \
    typeof((fifo) + 1) __tmp = (fifo); \
    typeof(*val) __val = (val); \
    unsigned int __ret; \
    size_t __recsize = sizeof(*__tmp->rectype); \
    struct __kfifo *__kfifo = &__tmp->kfifo; \
    __ret = __kfifo_uint32s_put(__kfifo, __val, __recsize); \
    __ret; \
})

// 类型安全的出队操作
#define kfifo_get(fifo, val) \
({ \
    typeof((fifo) + 1) __tmp = (fifo); \
    typeof(val) __val = (val); \
    unsigned int __ret; \
    const size_t __recsize = sizeof(*__tmp->rectype); \
    struct __kfifo *__kfifo = &__tmp->kfifo; \
    __ret = __kfifo_uint32s_out(__kfifo, __val, __recsize); \
    __ret; \
})

// 使用示例:
DECLARE_KFIFO(my_fifo, 32);  // 声明一个可以存储32个int的kfifo
int value = 42;
kfifo_put(&my_fifo, &value); // 类型安全的入队
kfifo_get(&my_fifo, &value); // 类型安全的出队

5.2 编译时检查

/**
 * 编译时类型检查
 */
#define __KFIFO_PEEK(data, out, mask) \
    ((data)[(out) & (mask)])

#define __KFIFO_POKE(data, in, mask, val) \
    ( (data)[(in) & (mask)] = (val) )

// 这些宏确保在编译时就能发现类型错误

6. 性能优化技术

6.1 缓存友好性

/**
 * 缓存友好的数据布局
 */

struct __kfifo {
    unsigned int    in;     // 控制信息集中存储
    unsigned int    out;    // 提高缓存命中率
    unsigned int    mask;
    unsigned int    esize;
    void        *data;      // 数据指针单独存储
};

// 为什么这样布局?
// 1. 控制信息连续存储,提高缓存局部性
// 2. 频繁访问的in/out字段相邻,减少缓存行加载
// 3. data指针单独存储,避免数据移动时的拷贝

6.2 编译器优化

/**
 * 利用编译器优化
 */

// 内联函数减少函数调用开销
static inline unsigned int kfifo_len(struct __kfifo *fifo)
{
    return fifo->in - fifo->out;
}

// 编译时常量传播
#define KFIFO_SIZE 1024
// 编译器可以将 mask = KFIFO_SIZE - 1 优化为常量

// 分支预测提示
#define likely(x)   __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

7. 实际应用场景

7.1 内核中的典型应用

/**
 * Linux内核中的kfifo应用示例
 */

// 1. 网络数据包缓冲
struct sk_buff_head {
    struct __kfifo  skb_queue;
    // ...
};

// 2. 工作队列
struct workqueue_struct {
    struct __kfifo  work_list;
    // ...
};

// 3. 字符设备缓冲
struct tty_port {
    struct __kfifo  buf;
    // ...
};

// 4. 中断处理
struct irq_desc {
    struct __kfifo  pending_mask;
    // ...
};

7.2 用户空间移植

/**
 * 用户空间kfifo实现要点
 */

// 1. 替换内核内存分配函数
// kmalloc -> malloc
// kfree -> free

// 2. 替换内核同步原语
// spin_lock -> pthread_mutex_lock
// smp_mb -> __sync_synchronize 或者使用互斥锁

// 3. 替换内核特定宏
// likely/unlikely -> 保持或移除
// container_of -> 自定义实现

// 4. 错误处理
// 内核返回负数错误码 -> 用户空间返回-1并设置errno

8. 设计优势总结

8.1 性能优势

  1. O(1)时间复杂度:所有操作都是常数时间
  2. 位运算优化:避免除法运算
  3. 缓存友好:数据局部性好
  4. 无锁设计:单生产者单消费者场景下无需锁

8.2 内存优势

  1. 紧凑布局:控制信息集中存储
  2. 零拷贝支持:直接内存访问
  3. 动态分配:按需分配内存

8.3 使用优势

  1. 类型安全:编译时类型检查
  2. 接口简洁:易于使用
  3. 广泛测试:内核级稳定性保证

8.4 可扩展性

  1. 泛型支持:支持任意数据类型
  2. 可配置大小:动态调整缓冲区大小
  3. 多线程支持:提供同步版本

这个设计体现了Linux内核对性能、可靠性和简洁性的极致追求,是系统编程的经典范例。

Linux内核kfifo实现详解-CSDN博客

发表在 linux文章 | 留下评论

rt_sigpending系统调用及示例

我们来深入学习 rt_sigpending 系统调用,摘要rt_sigpending系统调用用于检查被阻塞但尚未处理的待处理信号集。通过sigpending函数调用,可获取当前被屏蔽信号的”等待中”状态,类似查看”信号邮箱”中的未读邮件。使用时需配合sigprocmask设置信号屏蔽字,并结合sigset_t相关函数操作信号集。示例代码演示了如何阻塞SIGUSR1/SIGUSR2信号,在10秒内捕获待处理信号,最后解除阻塞使信号被处理。该机制适用于需要延迟处理特定信号的场景。(149字)

1. 函数介绍

在 Linux 中,你可以使用 sigprocmask 来阻塞(或屏蔽)某些信号,这意味着即使这些信号被发送到你的进程,它们也不会立即被处理,而是进入一种“等待中”(pending)的状态。

rt_sigpending(通常通过用户空间的 sigpending 函数调用)的作用就是让你检查当前有哪些信号正处于这种“等待中”的状态。这在你需要知道在屏蔽信号期间发生了哪些信号时非常有用。

你可以把它想象成一个“信号邮箱”的查看器:当信号被阻塞时,它们就像邮件一样被“投递”到你的邮箱里(变成 pending),但你暂时不“阅读”它们。sigpending 就是让你打开邮箱看看里面有哪些“未读邮件”(待处理信号)。

2. 函数原型

#include <signal.h>

int sigpending(sigset_t *set);

3. 功能

获取当前进程中所有被阻塞且已产生但尚未递送(即待处理)的信号集合。

4. 参数

  • set:
    • sigset_t * 类型。
    • 一个指向 sigset_t 类型变量的指针。调用成功后,这个变量将被填充为当前所有待处理信号的集合。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(这种情况比较少见)。

6. 相似函数或关联函数

  • sigprocmask: 用于设置或查询当前进程的信号屏蔽字(signal mask),即哪些信号当前被阻塞。
  • sigset_t: 用于存储信号集合的数据类型。
  • sigemptyset: 初始化一个 sigset_t 集合为空。
  • sigfillset: 初始化一个 sigset_t 集合,使其包含所有信号。
  • sigaddset: 向一个 sigset_t 集合中添加一个特定的信号。
  • sigdelset: 从一个 sigset_t 集合中删除一个特定的信号。
  • sigismember: 检查一个特定的信号是否属于某个 sigset_t 集合。

7. 示例代码

下面是一个例子,演示如何阻塞信号,然后使用 sigpending 来检查哪些信号在等待。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>  // 包含 exit
#include <unistd.h>  // 包含 sleep
#include <signal.h>  // 包含信号处理相关函数
#include <string.h>  // 包含 memset

// 一个简单的信号处理函数
void signal_handler(int sig) {
    printf("Caught signal %d\n", sig);
    // 在实际应用中,信号处理函数应尽量简短,并只调用异步信号安全函数
}

int main() {
    sigset_t block_set;      // 用于设置要阻塞的信号
    sigset_t pending_set;    // 用于接收待处理的信号集

    printf("My PID is: %d\n", getpid());

    // 1. 设置 SIGUSR1 和 SIGUSR2 的处理函数
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler; // 使用上面定义的处理函数
    sigemptyset(&sa.sa_mask);       // 处理函数执行时不额外阻塞信号
    sa.sa_flags = 0;                // 没有特殊标志

    if (sigaction(SIGUSR1, &sa, NULL) == -1) { // 设置 SIGUSR1 的处理
        perror("sigaction SIGUSR1");
        exit(EXIT_FAILURE);
    }
    if (sigaction(SIGUSR2, &sa, NULL) == -1) { // 设置 SIGUSR2 的处理
        perror("sigaction SIGUSR2");
        exit(EXIT_FAILURE);
    }

    // 2. 创建一个信号集,并添加要阻塞的信号 (SIGUSR1 和 SIGUSR2)
    sigemptyset(&block_set);          // 初始化为空集
    sigaddset(&block_set, SIGUSR1);   // 添加 SIGUSR1
    sigaddset(&block_set, SIGUSR2);   // 添加 SIGUSR2

    // 3. 使用 sigprocmask 阻塞 SIGUSR1 和 SIGUSR2
    printf("Blocking SIGUSR1 and SIGUSR2...\n");
    printf("Try sending them now:\n");
    printf("  In another terminal, run: 'kill -USR1 %d'\n", getpid());
    printf("  In another terminal, run: 'kill -USR2 %d'\n", getpid());
    printf("Sleeping for 10 seconds...\n");

    if (sigprocmask(SIG_BLOCK, &block_set, NULL) == -1) {
        perror("sigprocmask BLOCK");
        exit(EXIT_FAILURE);
    }

    // 4. 在阻塞期间睡眠,等待信号发送
    sleep(10);
    printf("10 seconds passed. Signals should be pending now.\n");

    // 5. 调用 sigpending 检查哪些信号在等待
    if (sigpending(&pending_set) == -1) {
        perror("sigpending");
        exit(EXIT_FAILURE);
    }

    // 6. 检查并打印待处理的信号
    printf("Checking pending signals:\n");
    if (sigismember(&pending_set, SIGUSR1)) {
        printf("  SIGUSR1 is pending.\n");
    } else {
        printf("  SIGUSR1 is NOT pending.\n");
    }

    if (sigismember(&pending_set, SIGUSR2)) {
        printf("  SIGUSR2 is pending.\n");
    } else {
        printf("  SIGUSR2 is NOT pending.\n");
    }

    // 7. 解除对 SIGUSR1 和 SIGUSR2 的阻塞
    printf("Unblocking SIGUSR1 and SIGUSR2...\n");
    if (sigprocmask(SIG_UNBLOCK, &block_set, NULL) == -1) {
        perror("sigprocmask UNBLOCK");
        exit(EXIT_FAILURE);
    }

    printf("Unblocked. Any pending signals should be delivered now.\n");
    printf("Sleeping for 1 second to allow signal handlers to run...\n");
    sleep(1);

    printf("Program exiting.\n");
    return 0;
}

编译和运行:

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

# 终端 1: 运行程序
./sigpending_example
# 程序会输出 PID,例如 My PID is: 12345
# 然后提示你发送信号

# 终端 2: 发送信号 (在程序提示的 10 秒内执行)
kill -USR1 12345
kill -USR2 12345

# 观察终端 1 的输出,你会看到程序报告哪些信号是 pending 的,
# 以及在解除阻塞后信号被处理。

这个例子清晰地展示了信号阻塞和 sigpending 的工作流程:信号被阻塞 -> 信号发送 -> 信号变为 pending -> 使用 sigpending 查询 -> 解除阻塞 -> pending 的信号被处理。

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

发表在 linux文章 | 留下评论

rt_sigprocmask系统调用及示例

我们来深入学习 rt_sigprocmask 系统调用,请注意,用户空间通常调用的是 sigprocmask,它是 rt_sigprocmask 的封装。摘要

rt_sigprocmask(用户空间通常调用其封装函数sigprocmask)是Linux系统中用于临时控制信号递送的系统调用。它允许进程阻塞特定信号,避免关键代码段被中断。被阻塞的信号会排队等待,直到解除阻塞。函数通过how参数支持三种操作:SIG_BLOCK(添加阻塞信号)、SIG_UNBLOCK(移除阻塞信号)和SIG_SETMASK(直接设置屏蔽字)。示例代码展示了如何阻塞SIGUSR1信号,同时保持SIGINT可用,并演示了三种不同的信号屏蔽操作方式。该机制常用于保护关键代码段的数据一致性。

1. 函数介绍

在 Linux 系统中,信号是进程间通信和通知的重要方式。但有时候,你的程序正在执行一段非常关键的代码(比如正在更新一个复杂的数据结构),你不希望被任何信号打断,因为这可能导致数据不一致或程序崩溃。

rt_sigprocmask(用户空间通常通过 sigprocmask 调用)就是用来临时控制哪些信号可以被递送到你的进程。你可以告诉内核:“在接下来的一段时间里,请把 SIGINT(Ctrl+C)和 SIGUSR1 信号暂时‘挡’在外面,等我处理完关键任务后再送进来”。

这个“暂时挡在外面”的过程就叫做阻塞(Blocking)信号。被阻塞的信号并不会丢失,它们会排队等待,直到你解除阻塞(Unblock),它们才会被真正递送并处理。

2. 函数原型

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

3. 功能

检查或修改当前进程的信号屏蔽字(signal mask)。信号屏蔽字是一个列表,定义了当前被阻塞(暂时不递送)的信号。

4. 参数

  • how:
    • int 类型。
    • 指定你想要对信号屏蔽字执行的操作。有三个主要选项:
      • SIG_BLOCK: 把 set 指向的信号集合添加到当前的屏蔽字中。意思是:“我现在想额外阻塞这些信号”。
      • SIG_UNBLOCK: 把 set 指向的信号集合当前的屏蔽字中移除。意思是:“我现在想解除对这些信号的阻塞”。
      • SIG_SETMASK: 把当前的信号屏蔽字直接设置为 set 指向的信号集合。意思是:“不管以前怎么样,现在我只阻塞这些信号”。
  • set:
    • const sigset_t * 类型。
    • 一个指向 sigset_t 类型变量的指针,该变量包含了你想要操作(阻塞或解除阻塞)的信号集合。如果你传 NULL,则不修改当前的屏蔽字,只用于查询。
  • oldset:
    • sigset_t * 类型。
    • 一个指向 sigset_t 类型变量的指针。函数调用成功后,会把调用前的信号屏蔽字复制到这个变量中。如果你不关心旧的设置,可以传 NULL

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如,how 参数无效)。

6. 相似函数或关联函数

  • sigset_t: 用于存储信号集合的数据类型。
  • sigemptyset: 初始化一个 sigset_t 集合,使其不包含任何信号。
  • sigfillset: 初始化一个 sigset_t 集合,使其包含所有可能的信号。
  • sigaddset: 向一个 sigset_t 集合中添加一个特定的信号。
  • sigdelset: 从一个 sigset_t 集合中删除一个特定的信号。
  • sigismember: 检查一个特定的信号是否属于某个 sigset_t 集合。
  • sigpending: 检查当前有哪些信号是被阻塞且正在等待处理的。

7. 示例代码

下面是一个例子,演示如何使用 sigprocmask 来阻塞和解除阻塞信号。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>  // 包含 exit
#include <unistd.h>  // 包含 sleep
#include <signal.h>  // 包含信号处理相关函数
#include <string.h>  // 包含 memset

// 一个简单的信号处理函数
void signal_handler(int sig) {
    printf("\nCaught signal %d\n", sig);
    // 在实际应用中,信号处理函数应尽量简短,并只调用异步信号安全函数
}

int main() {
    sigset_t block_set;       // 用于设置要阻塞的信号
    sigset_t prev_set;        // 用于保存之前的信号屏蔽字 (可选)
    sigset_t current_set;     // 用于检查当前的信号屏蔽字 (可选)

    printf("My PID is: %d\n", getpid());

    // 1. 设置 SIGUSR1 和 SIGINT (Ctrl+C) 的处理函数
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask); // 处理函数执行时不额外阻塞信号
    sa.sa_flags = 0;

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction SIGUSR1");
        exit(EXIT_FAILURE);
    }
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction SIGINT");
        exit(EXIT_FAILURE);
    }

    // 2. 创建一个信号集,并添加要阻塞的信号 (SIGUSR1)
    sigemptyset(&block_set);
    sigaddset(&block_set, SIGUSR1);
    // 注意:我们没有阻塞 SIGINT,所以 Ctrl+C 仍然有效

    // 3. 使用 sigprocmask 阻塞 SIGUSR1,并保存旧的屏蔽字
    printf("Blocking SIGUSR1...\n");
    printf("Try sending SIGUSR1 now: 'kill -USR1 %d'\n", getpid());
    printf("Try pressing Ctrl+C (SIGINT) now - it should still work.\n");
    printf("Sleeping for 10 seconds (signals are blocked/unblocked accordingly)...\n");

    if (sigprocmask(SIG_BLOCK, &block_set, &prev_set) == -1) { // 阻塞并保存旧设置
        perror("sigprocmask BLOCK");
        exit(EXIT_FAILURE);
    }

    printf("SIGUSR1 is now blocked. Sleeping for 5 seconds...\n");
    sleep(5); // 在这5秒内,SIGUSR1会被阻塞,SIGINT不会

    // 4. 演示 SIG_SETMASK: 只阻塞 SIGINT,解除对 SIGUSR1 的阻塞
    sigemptyset(&block_set);
    sigaddset(&block_set, SIGINT); // 现在只阻塞 SIGINT
    printf("5 seconds passed. Now blocking only SIGINT (Ctrl+C) using SIG_SETMASK.\n");
    printf("Try sending SIGUSR1 now: 'kill -USR1 %d' - it should be caught immediately.\n");
    printf("Try pressing Ctrl+C (SIGINT) now - it should be blocked.\n");
    if (sigprocmask(SIG_SETMASK, &block_set, NULL) == -1) { // 设置新的屏蔽字
        perror("sigprocmask SETMASK");
        exit(EXIT_FAILURE);
    }

    printf("SIGINT is now blocked. Sleeping for 5 more seconds...\n");
    sleep(5); // 在这5秒内,SIGINT会被阻塞,SIGUSR1不会

    // 5. 演示 SIG_UNBLOCK: 解除对 SIGINT 的阻塞
    // 我们解除阻塞的集合就是当前阻塞的集合 (block_set)
    printf("5 seconds passed. Now unblocking SIGINT (Ctrl+C) using SIG_UNBLOCK.\n");
    printf("Try pressing Ctrl+C (SIGINT) now - it should work and terminate the program.\n");
    if (sigprocmask(SIG_UNBLOCK, &block_set, NULL) == -1) { // 解除阻塞
        perror("sigprocmask UNBLOCK");
        exit(EXIT_FAILURE);
    }

    printf("SIGINT is now unblocked. Sleeping for 10 more seconds...\n");
    printf("The program will end if you press Ctrl+C.\n");
    sleep(10); // 最后10秒,所有信号都按正常处理

    printf("Program exiting normally after sleep.\n");
    return 0;
}

编译和运行:

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

# 终端 1: 运行程序
./sigprocmask_example
# 程序会输出 PID,例如 My PID is: 12345

# 终端 2 (在程序的不同睡眠阶段执行以下命令):
# 阶段 1 (前5秒): SIGUSR1 被阻塞
# kill -USR1 12345
# (观察终端 1,信号处理函数不会立即触发)

# 阶段 2 (中间5秒): SIGINT 被阻塞, SIGUSR1 正常
# kill -USR1 12345
# (观察终端 1,信号处理函数应该立即触发)
# kill -INT 12345 或按 Ctrl+C 在终端 1
# (观察终端 1,Ctrl+C 应该无效)

# 阶段 3 (最后10秒): SIGINT 解除阻塞
# 按 Ctrl+C 在终端 1
# (程序应该退出)
# 或者
# kill -INT 12345
# (程序应该退出)

这个例子演示了 sigprocmask 的三种主要操作:

  1. SIG_BLOCK:在开始时阻塞 SIGUSR1
  2. SIG_SETMASK:中间阶段,将屏蔽字设置为只阻塞 SIGINT,从而解除了对 SIGUSR1 的阻塞。
  3. SIG_UNBLOCK:最后阶段,解除了对 SIGINT 的阻塞。

通过这种方式,你可以精确地控制在程序执行的不同阶段哪些信号可以打扰你的程序。

https://blog.csdn.net/zidier215/article/details/150723806?sharetype=blogdetail&sharerId=150723806&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

发表在 linux文章 | 留下评论

rt_sigqueueinfo系统调用及示例

我们来深入学习 rt_sigqueueinfo 系统调用,在 Linux 中,进程间通信(IPC)有多种方式,信号(Signal)是其中一种轻量级的通知机制。通常,我们使用 kill() 来发送一个简单的信号,或者使用 sigqueue() 来发送一个信号并附带一小段数据(一个整数或指针)。

1. 函数介绍

在 Linux 中,进程间通信(IPC)有多种方式,信号(Signal)是其中一种轻量级的通知机制。通常,我们使用 kill() 来发送一个简单的信号,或者使用 sigqueue() 来发送一个信号并附带一小段数据(一个整数或指针)。

rt_sigqueueinfo 是一个更底层、更强大但也更危险的系统调用。它允许你向另一个进程发送一个信号,并且可以完全自定义随信号一起传递的 siginfo_t 结构体中的所有信息。这包括发送者的 PID、信号产生的原因代码(si_code)、以及附带的数据等。

为什么危险?
因为它强大的自定义能力意味着发送者可以伪造信号的来源和原因。例如,一个普通用户进程可以伪造自己是内核(si_code 为 SI_KERNEL)或其他进程发送的信号。因此,这个系统调用通常受到严格的权限检查,普通应用程序一般不应该直接使用它。

什么时候会用到?
主要是在实现更高级别的信号发送函数(如 sigqueue)时,由 C 库(glibc)内部调用,或者在一些非常特殊的、需要精确控制信号信息的系统级编程中。

对于 Linux 编程小白:你更可能使用 sigqueue() 函数,它更安全、更易用,并且能满足绝大多数需要发送带数据信号的场景。

2. 函数原型

// 这是底层系统调用,直接在用户空间调用比较复杂且需要特殊权限
#include <sys/syscall.h>   // 包含系统调用号
#include <unistd.h>        // 包含 syscall 函数
#include <signal.h>        // 包含 siginfo_t 定义

long syscall(SYS_rt_sigqueueinfo, pid_t pid, int sig, siginfo_t *uinfo);
// 注意:用户空间标准 C 库通常不直接提供 rt_sigqueueinfo 的包装函数

用户空间更常用、更安全的替代函数:

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

3. 功能

向指定进程 ID (pid) 发送指定信号 (sig),并允许发送者完全指定 siginfo_t 结构体 (uinfo) 中包含的详细信息。

4. 参数

  • pid:
    • pid_t 类型。
    • 目标进程的进程 ID (PID)。信号将被发送给这个进程。
  • sig:
    • int 类型。
    • 要发送的信号编号,例如 SIGUSR1SIGRTMIN 等。注意,不能发送 SIGKILL 和 SIGSTOP
  • uinfo:
    • siginfo_t * 类型。
    • 一个指向 siginfo_t 结构体的指针。这个结构体包含了你想随信号一起传递给目标进程的所有详细信息。调用者需要自己填充这个结构体的各个字段。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
    • EAGAIN: (对于实时信号) 已达到接收者排队信号的最大数量限制 (RLIMIT_SIGPENDING)。
    • EPERM: 调用者没有权限发送信号给目标进程(例如,普通用户不能向 root 进程发送任意伪造的信号)。
    • EINVALsig 是无效的信号号,或者 uinfo 中的 si_code 是无效的或不允许由用户设置的。
    • ESRCH: 找不到 pid 指定的进程或进程组。

6. 相似函数或关联函数

  • sigqueue: 用户空间更安全、更常用的发送信号和数据的方法。它只允许设置 siginfo_t 中的 si_value 字段,其他字段由内核自动填充。
  • kill: 发送一个不带附加数据的信号。
  • siginfo_t: 包含信号详细信息的结构体。主要字段包括:
    • si_signo: 信号编号(内核会设置)。
    • si_errno: 如果非零,表示伴随信号的错误代码(内核会设置)。
    • si_code: 信号产生的原因代码(例如 SI_USERSI_QUEUESI_TIMER 等)。这是 rt_sigqueueinfo 允许用户自定义的关键字段,但也因此危险。
    • si_pid: 发送信号的进程 ID(通常由内核设置,但 rt_sigqueueinfo 可能允许伪造)。
    • si_uid: 发送信号的用户 ID(通常由内核设置)。
    • si_value: 伴随信号的用户数据(union sigval,包含 sival_int 或 sival_ptr)。
    • … 还有其他针对特定信号类型的字段。

7. 示例代码

由于直接使用 rt_sigqueueinfo 需要特殊权限且容易误用,下面的示例将演示如何通过 syscall 调用它,并说明其风险。同时,也会提供一个使用标准 sigqueue 的对比示例。

警告:直接使用 rt_sigqueueinfo 可能会因为权限问题而失败,尤其是在没有 root 权限的情况下。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/syscall.h> // 包含 syscall 和 SYS_rt_sigqueueinfo
#include <errno.h>
#include <sys/types.h> // 包含 pid_t

// 用于接收信号的处理函数 (使用 SA_SIGINFO 获取详细信息)
void signal_handler(int sig, siginfo_t *info, void *context) {
    printf("Received signal %d\n", sig);
    printf("  si_signo: %d\n", info->si_signo);
    printf("  si_code:  %d", info->si_code);
    switch(info->si_code) {
        case SI_USER: printf(" (SI_USER: kill, sigsend or raise)\n"); break;
        case SI_QUEUE: printf(" (SI_QUEUE: sigqueue)\n"); break;
        case SI_KERNEL: printf(" (SI_KERNEL: sent by kernel)\n"); break;
        case SI_TKILL: printf(" (SI_TKILL: tkill or tgkill)\n"); break;
        default: printf(" (Other code)\n"); break;
    }
    printf("  si_pid:   %d\n", info->si_pid);
    printf("  si_uid:   %d\n", info->si_uid);
    if (info->si_code == SI_QUEUE) {
        printf("  si_value.sival_int: %d\n", info->si_value.sival_int);
        printf("  si_value.sival_ptr: %p\n", info->si_value.sival_ptr);
    }
    // 注意:这里为了演示打印了信息,但实际信号处理函数应只调用异步信号安全函数
}

int main() {
    pid_t my_pid = getpid();
    struct sigaction sa;
    siginfo_t si_to_send;
    union sigval data_to_send = {.sival_int = 999};

    printf("My PID is: %d\n", my_pid);

    // 1. 设置 SIGUSR1 的处理函数,使用 SA_SIGINFO 获取详细信息
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = signal_handler; // 注意是 sa_sigaction
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO; // 必须设置此标志
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }


    // --- 方法 1: 使用标准的 sigqueue (推荐) ---
    printf("\n--- Using sigqueue (Recommended) ---\n");
    printf("Sending SIGUSR1 with data %d using sigqueue()...\n", data_to_send.sival_int);
    if (sigqueue(my_pid, SIGUSR1, data_to_send) == -1) {
        perror("sigqueue");
        // sigqueue 失败通常是因为资源限制或信号无效
    }
    sleep(1); // 给点时间处理信号


    // --- 方法 2: 使用 rt_sigqueueinfo (不推荐,仅供演示) ---
    printf("\n--- Using rt_sigqueueinfo (Not Recommended) ---\n");
    // 2. 准备 siginfo_t 结构体
    memset(&si_to_send, 0, sizeof(si_to_send));
    si_to_send.si_signo = SIGUSR1;  // 信号号
    si_to_send.si_code = SI_QUEUE;  // 伪造为 sigqueue 发送的
    si_to_send.si_pid = my_pid;     // 伪造 PID
    si_to_send.si_uid = getuid();   // 伪造 UID
    si_to_send.si_value = data_to_send; // 附带数据

    printf("Attempting to send SIGUSR1 with forged info using rt_sigqueueinfo()...\n");
    printf("(This will likely fail with EPERM unless run with special privileges)\n");
    // 3. 调用底层系统调用
    long result = syscall(SYS_rt_sigqueueinfo, my_pid, SIGUSR1, &si_to_send);

    if (result == -1) {
        perror("rt_sigqueueinfo");
        printf("Error: rt_sigqueueinfo failed. This is expected for unprivileged processes.\n");
        printf("Errno: %d\n", errno);
        if (errno == EPERM) {
            printf("Reason: EPERM - Operation not permitted (insufficient privileges to forge signal info).\n");
        }
    } else {
        printf("rt_sigqueueinfo succeeded (unexpected for unprivileged user).\n");
    }

    sleep(1); // 给点时间处理可能的信号

    // --- 方法 3: 使用 rt_sigqueueinfo 伪造为内核发送 (非常不推荐) ---
    printf("\n--- Forging signal as from Kernel (Highly Not Recommended) ---\n");
    si_to_send.si_code = SI_KERNEL; // 尝试伪造为内核发送
    printf("Attempting to forge signal as sent by the KERNEL...\n");
    result = syscall(SYS_rt_sigqueueinfo, my_pid, SIGUSR1, &si_to_send);

    if (result == -1) {
        perror("rt_sigqueueinfo (forged as kernel)");
        printf("Error: Forging kernel signal failed (as expected).\n");
    } else {
        printf("rt_sigqueueinfo (forged as kernel) succeeded (highly unexpected!).\n");
    }

    sleep(1);

    printf("\nProgram finished.\n");
    return 0;
}

使用 sigqueue 的简单对比示例 (推荐方式):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

void signal_handler(int sig, siginfo_t *info, void *context) {
    if (info->si_code == SI_QUEUE) {
        printf("Received signal %d with value: %d\n", sig, info->si_value.sival_int);
    } else {
        printf("Received signal %d (not from sigqueue)\n", sig);
    }
}

int main() {
    pid_t pid;
    struct sigaction sa;
    union sigval value1 = {.sival_int = 100};
    union sigval value2 = {.sival_int = 200};

    // 设置信号处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // Child: 发送信号给父进程
        sleep(1);
        printf("Child: Sending SIGUSR1 with value %d\n", value1.sival_int);
        if (sigqueue(getppid(), SIGUSR1, value1) == -1) {
            perror("sigqueue 1");
        }

        sleep(1);
        printf("Child: Sending SIGUSR1 with value %d\n", value2.sival_int);
        if (sigqueue(getppid(), SIGUSR1, value2) == -1) {
            perror("sigqueue 2");
        }

        exit(EXIT_SUCCESS);
    } else {
        // Parent: 等待信号
        printf("Parent: Waiting for signals...\n");
        sleep(5); // 等待子进程发送信号并处理
        wait(NULL); // 等待子进程结束
        printf("Parent: Finished.\n");
    }

    return 0;
}

编译和运行:

# 假设代码保存在 rt_sigqueueinfo_example.c 和 sigqueue_example.c 中
gcc -o rt_sigqueueinfo_example rt_sigqueueinfo_example.c
gcc -o sigqueue_example sigqueue_example.c

# 运行第一个示例 (会展示 rt_sigqueueinfo 的权限限制)
./rt_sigqueueinfo_example

# 运行第二个示例 (推荐的 sigqueue 用法)
./sigqueue_example

总结:
对于 Linux 编程新手,请优先学习和使用 sigqueue()rt_sigqueueinfo 是一个底层工具,功能强大但使用不当有安全风险,通常由系统库内部使用。

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

发表在 linux文章 | 留下评论

rt_sigreturn系统调用及示例

我们来深入学习 rt_sigreturn 系统调用

1. 函数介绍

rt_sigreturn 是一个非常特殊的系统调用,它不像你平时使用的 printf 或 open 那样由程序员直接在代码中调用。相反,它是由 Linux 内核在特定情况下自动调用的,是信号处理机制中不可或缺的一部分。

想象一下这个场景:

  1. 你的程序正在正常运行。
  2. 突然,一个信号(比如 SIGALRM 定时器信号)到达了。
  3. 内核需要暂停你的程序,保存它当前的执行状态(比如 CPU 寄存器的值、程序计数器等),然后跳转到你为这个信号设置的处理函数去执行。
  4. 当你的信号处理函数执行完毕后,程序需要恢复到被信号打断之前的那个精确状态,然后继续执行。

rt_sigreturn 的作用就是:在信号处理函数执行完毕后,由内核调用它来恢复程序被中断前的执行状态,并使程序从中断点继续执行。

你可以把它看作是信号处理机制的“返回票”:内核用“去程票”(保存状态并跳转到处理函数),信号处理函数执行完后,内核用“回程票”(rt_sigreturn)把你送回原来的地方。

对于 Linux 编程小白:你通常不需要知道 rt_sigreturn 的存在,也不需要直接与它交互。它在幕后默默地工作,保证了信号处理完成后程序能正确恢复执行。了解它有助于你更深入地理解信号机制是如何工作的。

2. 函数原型

// 这是内核系统调用,用户空间程序不会直接调用它。
// 它的原型在内核源码中类似这样 (概念性):
asmlinkage long sys_rt_sigreturn(void);

用户空间没有标准的 C 库函数可以直接调用 rt_sigreturn。当你的信号处理函数执行 return 语句时,编译器和运行时库(C Runtime)会生成特殊的代码(通常是汇编代码),这些代码会最终触发 rt_sigreturn 系统调用。

3. 功能

从信号处理函数返回,恢复进程在信号处理前被中断的处理器状态(包括寄存器、堆栈指针等),并恢复信号屏蔽字,使进程从中断点继续执行。

4. 参数

rt_sigreturn 系统调用不接受任何用户空间传递的参数

所有恢复执行所需的信息(比如之前保存的寄存器状态、信号掩码等)都由内核在信号递送时保存在进程的内核空间或用户空间堆栈的特定位置(通常是信号帧 signal frame 或 ucontext)。当 rt_sigreturn 被调用时,它会从这些地方读取信息来恢复状态。

5. 返回值

rt_sigreturn 永远不会正常返回到调用者

因为它的任务就是恢复到信号处理函数被调用之前的状态,所以一旦它完成了状态恢复,程序的执行流就会跳转回原来被中断的地方,而不是从 rt_sigreturn 调用之后的地方继续。如果因为某种原因(例如内核错误)它确实返回了,那通常意味着发生了严重问题。

6. 相似函数或关联函数

  • 信号处理函数: 你用 sigaction 设置的函数。rt_sigreturn 是在它 return 后被间接调用的。
  • sigaction: 用于设置信号处理函数,间接影响 rt_sigreturn 的行为(例如,旧的信号掩码会被恢复)。
  • sigaltstack: 可以设置信号处理函数运行的备用堆栈。rt_sigreturn 需要知道是否使用了备用堆栈以便正确恢复。
  • ucontext_t: 在使用 SA_SIGINFO 标志时,信号处理函数会收到一个指向 ucontext_t 的指针,其中包含了调用时的上下文信息。rt_sigreturn 会使用这些信息(或内核内部保存的类似信息)来恢复状态。
  • setjmp / longjmp: 提供了另一种用户态的“跳转并恢复状态”机制,但原理和用途与 rt_sigreturn 不同。

7. 示例代码

由于 rt_sigreturn 是内核自动调用的,我们无法写出直接调用它的 C 代码。但是,我们可以通过一个信号处理的例子来观察 rt_sigreturn 的效果。

下面的代码展示了信号处理函数执行完毕后,程序如何恢复并继续执行,这背后就是 rt_sigreturn 在起作用。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/time.h> // 包含 setitimer

// 全局变量,用于在主程序和信号处理函数间通信
volatile sig_atomic_t alarm_received = 0;

// 信号处理函数
void alarm_handler(int sig) {
    // 注意:在信号处理函数中应只使用异步信号安全的函数
    // printf 通常不安全,但为了演示我们简化使用
    printf("  >>> Alarm signal (%d) received! <<<\n", sig);
    alarm_received = 1; // 设置标志

    // 模拟在信号处理函数中做一些工作
    for (int i = 0; i < 3; ++i) {
        printf("  >>> Working in signal handler... %d <<<\n", i+1);
        sleep(1); // 暂停1秒
    }
    printf("  >>> Signal handler finished. <<<\n");
    // 当这个函数执行 return 时,
    // 运行时库会安排调用 rt_sigreturn 系统调用
    // 来恢复主程序的执行状态
}

int main() {
    struct sigaction sa;
    struct itimerval timer;

    printf("Main program starting...\n");
    printf("PID: %d\n", getpid());

    // 1. 设置 SIGALRM 的处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = alarm_handler;
    sigemptyset(&sa.sa_mask);
    // 不设置 SA_RESTART,这样被中断的系统调用会返回 EINTR
    sa.sa_flags = 0;
    if (sigaction(SIGALRM, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    // 2. 设置定时器,在 3 秒后产生 SIGALRM 信号
    memset(&timer, 0, sizeof(timer));
    timer.it_value.tv_sec = 3;    // 3秒后启动
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 0; // 不重复
    timer.it_interval.tv_usec = 0;
    printf("Setting alarm for 3 seconds...\n");
    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer");
        exit(EXIT_FAILURE);
    }

    printf("Entering main loop. Will be interrupted by alarm in 3 seconds.\n");

    int counter = 0;
    while (counter < 10) {
        printf("Main loop iteration %d\n", counter);
        counter++;

        // 调用一个可能被信号中断的系统调用
        printf("Calling sleep(2)...\n");
        int sleep_result = sleep(2);

        // 如果 sleep 被信号中断,它会提前返回剩余的睡眠时间
        if (sleep_result > 0) {
            printf("Sleep was interrupted with %d seconds remaining.\n", sleep_result);
            // 检查我们的标志是否被信号处理函数设置
            if (alarm_received) {
                printf("Confirmed: Alarm was received and handled.\n");
                printf("Now continuing main loop execution.\n");
                // 重置标志
                alarm_received = 0;
            }
        } else {
            printf("Sleep completed normally.\n");
        }

        printf("---\n");
    }

    printf("Main program finished.\n");
    return 0;
}

代码执行流程解释:

  1. main 函数开始执行。
  2. 通过 sigaction 设置 SIGALRM 的处理函数 alarm_handler
  3. 通过 setitimer 设置一个 3 秒后触发的定时器。
  4. main 函数进入 while 循环。
  5. 在循环的第一次迭代中,程序调用 sleep(2)
  6. 大约 3 秒后,定时器到期,内核向进程发送 SIGALRM 信号。
  7. 内核中断 main 程序的执行,保存其当前状态(寄存器、程序计数器等)。
  8. 内核切换到用户态,并调用我们设置的 alarm_handler 函数。
  9. alarm_handler 执行其内部的循环和 sleep
  10. alarm_handler 执行完毕并 return
  11. 此时(由 C 运行时库安排),内核执行 rt_sigreturn 系统调用。
  12. rt_sigreturn 恢复 main 程序被中断时的所有状态。
  13. 程序执行流回到 sleep(2) 调用之后的代码。
  14. sleep 函数发现它被中断了,于是返回剩余的睡眠时间(在这种情况下大约是 1 秒)。
  15. main 程序检查标志,确认信号已处理,然后继续下一次 while 循环。

编译和运行:

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

# 运行程序
./sigreturn_example

预期输出:

Main program starting...
PID: 12345
Setting alarm for 3 seconds...
Entering main loop. Will be interrupted by alarm in 3 seconds.
Main loop iteration 0
Calling sleep(2)...
  >>> Alarm signal (14) received! <<<
  >>> Working in signal handler... 1 <<<
  >>> Working in signal handler... 2 <<<
  >>> Working in signal handler... 3 <<<
  >>> Signal handler finished. <<<
Sleep was interrupted with 1 seconds remaining.
Confirmed: Alarm was received and handled.
Now continuing main loop execution.
---
Main loop iteration 1
Calling sleep(2)...
Sleep completed normally.
---
... (后续循环) ...

这个例子清晰地展示了信号处理机制的工作流程,以及 rt_sigreturn 如何在幕后确保程序在信号处理后能正确恢复执行。

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

发表在 linux文章 | 留下评论

rt_sigsuspend系统调用及示例

我们来深入学习 rt_sigsuspend 系统调用

1. 函数介绍

在 Linux 信号编程中,一个常见的需求是:让程序等待某个特定信号的到来。你可能想暂时忽略其他所有信号,只允许一个或几个特定的信号来“唤醒”你的程序。

pause() 函数可以挂起程序直到收到任何信号,但这不够精确。sigprocmask() 可以设置信号掩码(决定哪些信号被阻塞),但它和 pause() 组合使用时存在竞态条件(Race Condition)风险。

什么是竞态条件?
想象一下,如果你先用 sigprocmask() 解除对某个信号的阻塞,然后立即调用 pause() 等待它。在这两条语句执行的间隙,如果那个信号恰好到达了,会发生什么?信号会被处理,但 pause() 还没开始执行,所以程序就错过了这个信号,可能会永远挂起在 pause() 上。

rt_sigsuspend(用户空间通过 sigsuspend 调用)就是为了解决这个问题而设计的。它是一个原子操作,会一次性完成两件事:

  1. 临时替换当前的信号掩码。
  2. 挂起进程,等待信号。

因为这两步是原子性完成的,中间没有间隙,所以彻底避免了竞态条件。

简单来说,sigsuspend 就是“安全地等待信号”的标准方法。

2. 函数原型

#include <signal.h>

int sigsuspend(const sigset_t *mask);

3. 功能

用 mask 指向的信号集临时替换当前进程的信号屏蔽字,然后挂起调用进程,直到捕获到一个信号。当信号处理函数返回后,sigsuspend 会返回,并且进程的信号屏蔽字会被恢复为调用 sigsuspend 之前的状态。

4. 参数

  • mask:
    • const sigset_t * 类型。
    • 一个指向 sigset_t 类型变量的指针。这个信号集定义了在 sigsuspend 调用期间有效的信号屏蔽字。换句话说,进程会被设置为只阻塞这个 mask 中包含的信号。

5. 返回值

  • sigsuspend 几乎总是返回 -1
  • 并且 errno 总是被设置为 EINTR
  • 这是因为 sigsuspend 只有在被信号中断后才会返回。它的返回本身就代表了“被信号中断”这个事件。

6. 相似函数或关联函数

  • pause: 简单地挂起进程直到收到任何信号。不提供对信号掩码的控制,且与 sigprocmask 组合使用有竞态条件。
  • sigprocmask: 用于检查或修改当前进程的信号屏蔽字。
  • sigset_t 及其操作函数 (sigemptysetsigaddsetsigfillset 等): 用于创建和操作信号集。
  • sigaction: 用于设置信号处理函数。

7. 示例代码

下面是一个典型的例子,展示如何使用 sigsuspend 来安全地等待一个特定信号(例如 SIGUSR1)。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h> // 包含 errno 和 EINTR

// 使用 volatile sig_atomic_t 类型的全局变量在主程序和信号处理函数间通信
// sig_atomic_t 类型保证了对它的读写是原子的
volatile sig_atomic_t sigusr1_flag = 0;

// SIGUSR1 信号的处理函数
void handle_sigusr1(int sig) {
    // 在信号处理函数中,只应使用异步信号安全的函数
    // write 是安全的,printf 通常不安全
    write(STDOUT_FILENO, "Caught SIGUSR1!\n", 17);
    // 设置标志,通知主程序信号已收到
    sigusr1_flag = 1;
}

int main() {
    struct sigaction sa;
    sigset_t block_most_signals;  // 用于阻塞大部分信号
    sigset_t orig_mask;           // 用于保存原始信号掩码
    sigset_t suspend_mask;        // 用于 sigsuspend 的临时掩码

    printf("My PID is: %d\n", getpid());
    printf("Run 'kill -USR1 %d' in another terminal to wake me up.\n", getpid());

    // 1. 设置 SIGUSR1 的处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = handle_sigusr1;
    sigemptyset(&sa.sa_mask); // 在处理 SIGUSR1 时,不额外阻塞其他信号
    sa.sa_flags = 0;          // 没有特殊标志
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction SIGUSR1");
        exit(EXIT_FAILURE);
    }

    // 2. 创建一个信号集,包含几乎所有信号
    if (sigfillset(&block_most_signals) == -1) {
        perror("sigfillset");
        exit(EXIT_FAILURE);
    }
    // 从这个集合中移除 SIGUSR1,允许它被接收
    if (sigdelset(&block_most_signals, SIGUSR1) == -1) {
        perror("sigdelset SIGUSR1");
        exit(EXIT_FAILURE);
    }
    // 也可以移除 SIGINT (Ctrl+C) 和 SIGTERM,以便能正常终止程序
    if (sigdelset(&block_most_signals, SIGINT) == -1) {
        perror("sigdelset SIGINT");
        // 不 exit,继续尝试
    }
    if (sigdelset(&block_most_signals, SIGTERM) == -1) {
        perror("sigdelset SIGTERM");
        // 不 exit,继续尝试
    }

    // 3. 使用 sigprocmask 应用这个“阻塞大部分信号”的掩码
    // 同时保存当前(原始)的信号掩码到 orig_mask
    printf("Blocking most signals, only allowing SIGUSR1, SIGINT, SIGTERM.\n");
    if (sigprocmask(SIG_SETMASK, &block_most_signals, &orig_mask) == -1) {
        perror("sigprocmask SETMASK");
        exit(EXIT_FAILURE);
    }

    // 4. 创建 sigsuspend 使用的临时掩码
    // 这个掩码定义了在 sigsuspend 挂起期间,哪些信号是被阻塞的
    // 我们希望在等待 SIGUSR1 时,SIGUSR1 是**唯一不被阻塞**的信号
    // 所以 suspend_mask 应该阻塞所有信号,包括 SIGUSR1
    // 但是 sigsuspend 会临时将掩码设置为 suspend_mask,
    // 这意味着它会阻塞 suspend_mask 中的信号。
    // 这里有个逻辑陷阱:
    // sigsuspend 临时设置的掩码是它参数指向的掩码。
    // 如果我们想让 SIGUSR1 能唤醒 sigsuspend,
    // 那么 suspend_mask 就应该是 "除了 SIGUSR1 之外所有要阻塞的信号"。
    // 但我们之前已经用 sigprocmask 设置了 block_most_signals,
    // 它只允许 SIGUSR1, SIGINT, SIGTERM。
    // 所以,为了让 sigsuspend 期间只允许 SIGUSR1 (忽略 SIGINT/SIGTERM 的唤醒能力),
    // suspend_mask 应该是 block_most_signals + 阻塞 SIGINT 和 SIGTERM
    // 或者更简单地,创建一个只阻塞 SIGUSR1 的掩码。
    // 但是,如果原始掩码 block_most_signals 已经阻塞了其他信号,
    // sigsuspend 不会改变那些信号的状态,除非我们明确在 suspend_mask 中处理。
    // 最清晰的方式是:suspend_mask = 原始掩码 + 额外阻塞的信号
    // 或者,重新定义逻辑。
    // 让我们简化:sigsuspend 期间,只阻塞 SIGUSR1,这样它就能被唤醒。
    // 但这与我们用 sigprocmask 设置的相反。
    // 正确的理解是:
    // sigsuspend 的 mask 参数是它调用期间**生效**的 mask。
    // 如果 mask 中包含 SIGUSR1,那么 SIGUSR1 就被阻塞。
    // 如果 mask 中不包含 SIGUSR1,那么 SIGUSR1 就不被阻塞,可以唤醒进程。
    //
    // 我们的目标是:在 sigsuspend 期间,只允许 SIGUSR1 唤醒我们。
    // 假设当前 mask (由 sigprocmask 设置) 是 block_most_signals (阻塞了除 SIGUSR1/INT/TERM 外的所有)。
    // 那么为了只让 SIGUSR1 唤醒,suspend_mask 应该是 "当前 mask 交集 (除了 SIGUSR1)"。
    // 但这很复杂。
    // 更简单的做法是:
    // 1. 用 sigprocmask 设置一个基础掩码 (比如阻塞 SIGUSR1)。
    // 2. sigsuspend 的 mask 是解除阻塞 SIGUSR1 的掩码。
    // 让我们重新组织示例逻辑,使其更清晰。

    // --- 重新设计示例逻辑 ---
    printf("\n--- Revised Logic ---\n");

    // 重置信号处理
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, NULL);

    // 1. 先阻塞 SIGUSR1 (以及其他你不想在主循环中处理的信号)
    sigset_t block_sigusr1;
    sigemptyset(&block_sigusr1);
    sigaddset(&block_sigusr1, SIGUSR1);
    printf("Initially blocking SIGUSR1.\n");
    if (sigprocmask(SIG_BLOCK, &block_sigusr1, &orig_mask) == -1) { // 保存原始掩码
        perror("sigprocmask BLOCK SIGUSR1");
        exit(EXIT_FAILURE);
    }

    // 2. 创建 sigsuspend 的 mask:这个 mask 是 sigsuspend 期间**生效**的。
    // 我们希望在 sigsuspend 期间,SIGUSR1 **不**被阻塞,以便能唤醒进程。
    // 所以,suspend_mask 应该是 “当前所有被阻塞的信号,但不包括 SIGUSR1”。
    // 最简单的方法是:创建一个空的掩码,或者复制当前掩码然后删除 SIGUSR1。
    // 但由于我们只阻塞了 SIGUSR1,所以 suspend_mask 应该是空的。
    sigset_t suspend_wait_mask;
    sigemptyset(&suspend_wait_mask); // 空集意味着不阻塞任何额外信号
                                     // (但原先被 sigprocmask 阻塞的信号状态不变吗?不,sigsuspend 会临时替换)
                                     // sigsuspend 会临时将掩码设置为 suspend_wait_mask。
                                     // 因为我们之前用 sigprocmask 阻塞了 SIGUSR1,
                                     // 现在 sigsuspend 临时设置掩码为空,那么 SIGUSR1 就不被阻塞了。

    printf("Entering sigsuspend loop. Waiting for SIGUSR1...\n");

    // 3. 主循环:等待信号
    while (!sigusr1_flag) {
        printf("  Calling sigsuspend()... (temporarily unblocking SIGUSR1)\n");
        // sigsuspend 会:
        // a. 临时将进程的信号掩码设置为 suspend_wait_mask (这里是空集,即不额外阻塞)
        //    结合上一步,这意味着 SIGUSR1 现在是 unblocked。
        // b. 挂起进程。
        // c. 如果收到 SIGUSR1:
        //    i.  内核调用 handle_sigusr1。
        //    ii. handle_sigusr1 执行完毕。
        //    iii.sigsuspend 返回 -1, errno=EINTR。
        // d. 恢复 sigprocmask 调用前的掩码 (orig_mask,即阻塞 SIGUSR1)。
        int result = sigsuspend(&suspend_wait_mask);

        // 因为 sigsuspend 只有被信号中断才会返回,所以检查 errno 是标准做法
        if (result == -1 && errno == EINTR) {
            printf("  sigsuspend() returned (interrupted by signal).\n");
            // 检查是哪个信号触发的(通过全局标志)
            if (sigusr1_flag) {
                 printf("  Confirmed: SIGUSR1 was received and handled.\n");
            } else {
                 printf("  Interrupted by a different signal (e.g., SIGINT?).\n");
                 // 如果是 SIGINT 或 SIGTERM,程序通常应该退出
                 // 但因为我们没有在 sigsuspend mask 中明确阻塞它们,
                 // 它们也可能唤醒 sigsuspend。
                 // 为了精确等待 SIGUSR1,我们应该在 suspend_wait_mask 中阻塞它们。
                 // 让我们再修正一次。
                 break; // 简单地退出循环
            }
        } else {
            // 这不太可能发生,除非有其他严重错误
            perror("sigsuspend");
            break;
        }
    }

    // 4. 循环结束,说明收到了 SIGUSR1 或者被其他信号中断
    if (sigusr1_flag) {
        printf("\nMain loop exited because SIGUSR1 was received.\n");
    } else {
        printf("\nMain loop exited because of another signal (e.g., SIGINT).\n");
    }

    // 5. 程序结束
    printf("Program exiting.\n");
    return 0;
}

修正后的更清晰示例:

为了让逻辑更清晰,我们明确目标:只在 sigsuspend 期间允许 SIGUSR1 唤醒进程。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

volatile sig_atomic_t usr1_flag = 0;

void handle_usr1(int sig) {
    write(STDOUT_FILENO, "Caught SIGUSR1\n", 15);
    usr1_flag = 1;
}

int main() {
    struct sigaction sa;
    sigset_t block_usr1;
    sigset_t orig_mask;
    sigset_t allow_only_usr1; // sigsuspend 使用的掩码

    printf("PID: %d\n", getpid());
    printf("Run 'kill -USR1 %d' to proceed.\n", getpid());
    printf("Run 'kill -INT %d' (Ctrl+C) to exit.\n", getpid());

    // 1. 设置 SIGUSR1 处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = handle_usr1;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction SIGUSR1");
        exit(EXIT_FAILURE);
    }

    // 2. 初始状态:阻塞 SIGUSR1
    // 这样可以确保在设置好 sigsuspend 之前的准备期间,SIGUSR1 不会意外到达
    sigemptyset(&block_usr1);
    sigaddset(&block_usr1, SIGUSR1);
    printf("Initially blocking SIGUSR1.\n");
    if (sigprocmask(SIG_BLOCK, &block_usr1, &orig_mask) == -1) {
        perror("sigprocmask BLOCK");
        exit(EXIT_FAILURE);
    }

    // 3. 创建 sigsuspend 期间使用的掩码
    // 目标:在 sigsuspend 期间,只允许 SIGUSR1 到达(唤醒进程)
    // 方法:让 sigsuspend 临时设置的掩码为 "阻塞除 SIGUSR1 外所有我们关心的信号"
    // 但更简单的理解是:sigsuspend 的参数 mask 是它生效期间的掩码。
    // 我们希望 SIGUSR1 能通过,所以 SIGUSR1 不应在此 mask 中。
    // 我们希望其他信号(如 SIGINT)不能唤醒(或被阻塞),所以它们应在此 mask 中。
    // 为了简单,我们创建一个阻塞 SIGUSR1 的掩码。
    // 但是!sigsuspend 是临时 *设置* 掩码为 mask。
    // 如果 mask 包含 SIGUSR1,那么 SIGUSR1 就被阻塞。
    // 如果 mask 不包含 SIGUSR1,那么 SIGUSR1 就不被阻塞。
    // 我们的目标是让 SIGUSR1 不被阻塞 -> mask 中不包含 SIGUSR1。
    // 为了让其他信号不干扰,我们也希望它们被阻塞 -> mask 中包含它们。
    // 但因为我们不知道 "其他所有信号",我们换个思路。
    // 初始状态:SIGUSR1 被阻塞 (通过 sigprocmask)。
    // sigsuspend 临时掩码:不阻塞 SIGUSR1。
    // 这样 SIGUSR1 就能到达并唤醒。
    sigemptyset(&allow_only_usr1); // 空集,不添加 SIGUSR1
                                   // 这意味着在 sigsuspend 期间,SIGUSR1 不被这个掩码阻塞。
                                   // (但原先被 sigprocmask 阻塞的信号呢?sigsuspend 会临时替换整个掩码)

    // 关键理解:
    // sigprocmask 设置的掩码是 "基础" 掩码。
    // sigsuspend 的 mask 是 "临时" 掩码,它会完全替换掉基础掩码。
    // 所以,sigsuspend(&allow_only_usr1) 会临时将掩码设为空集。
    // 结合之前 sigprocmask 阻塞了 SIGUSR1,现在临时设为空集,
    // 那么所有信号(包括 SIGUSR1)都不被临时掩码阻塞。
    // 这不是我们想要的精确等待 SIGUSR1。
    // 我们想要的是:临时掩码只阻塞 SIGUSR1 之外的信号。
    // 但列出 "所有其他信号" 很难。
    // 最佳实践通常是:
    // 1. 在程序启动时,使用 sigprocmask 设置一个合理的默认掩码。
    // 2. 在需要精确等待时,用 sigsuspend 传入一个精心构造的掩码。

    // 让我们假设我们只关心 SIGUSR1 和 SIGINT。
    // 默认掩码:阻塞 SIGUSR1
    // sigsuspend 掩码:阻塞 SIGUSR1。 这样还是不对。
    // 默认掩码:不阻塞任何信号
    // sigsuspend 掩码:阻塞所有信号,除了 SIGUSR1。 这需要知道所有信号。
    // 折中方案:
    // 默认掩码:阻塞 SIGUSR1
    // sigsuspend 掩码:空集 (不阻塞任何信号)。 这意味着 SIGUSR1 和其他所有信号都不被临时掩码阻塞。
    // 但由于之前阻塞了 SIGUSR1,临时不阻塞,就只有 SIGUSR1 能唤醒?不对,其他信号也能。
    // 看起来我之前的理解有偏差。
    // 再查文档和权威资料:
    // sigsuspend 原子地将信号掩码替换为 mask 指向的掩码,然后挂起进程。
    // 它等待的是任何**未被该 mask 阻塞**的信号。
    // 返回后恢复为调用 sigsuspend 之前的掩码。

    // 正确做法:
    // 1. 确定你平时想阻塞哪些信号 (例如,除了 SIGUSR1 和 SIGINT)。
    // 2. 在准备阶段,用 sigprocmask 设置这个 "平时" 的掩码。
    // 3. 构造 sigsuspend 的 mask:这个 mask 应该只阻塞那些你不想让它唤醒的信号。
    //    通常,这意味着 mask 应该阻塞除你正在等待的那个信号之外的所有信号。
    //    但这需要构造一个包含几乎所有信号的集合,只排除一个,很麻烦。
    // 4. 一个常见的简化方法是:
    //    a. 平时阻塞你关心的所有信号 (SIGUSR1, SIGUSR2, ...)。
    //    b. sigsuspend 的 mask 是 "平时掩码" 减去你当前想等待的那个信号。
    //    c. 这样,sigsuspend 期间,只有那个特定信号能唤醒进程。

    // 实施简化方法:
    printf("\n--- Corrected Example ---\n");
    sigset_t block_sigusr1_and_sigint; // 平时的掩码
    sigset_t wait_for_sigusr1_mask;    // sigsuspend 的掩码

    // 重置信号处理 (可选,因为没变)
    // sigaction(SIGUSR1, &sa, NULL);

    // 1. 设置平时阻塞的信号:SIGUSR1 和 SIGINT
    sigemptyset(&block_sigusr1_and_sigint);
    sigaddset(&block_sigusr1_and_sigint, SIGUSR1);
    sigaddset(&block_sigusr1_and_sigint, SIGINT); // 也阻塞 SIGINT,防止意外唤醒
    printf("Setting normal mask to block SIGUSR1 and SIGINT.\n");
    if (sigprocmask(SIG_SETMASK, &block_sigusr1_and_sigint, &orig_mask) == -1) {
        perror("sigprocmask SETMASK normal");
        exit(EXIT_FAILURE);
    }

    // 2. 构造 sigsuspend 的掩码:只阻塞 SIGINT (允许 SIGUSR1 唤醒)
    sigemptyset(&wait_for_sigusr1_mask);
    sigaddset(&wait_for_sigusr1_mask, SIGINT); // 阻塞 SIGINT
    // SIGUSR1 没有被加入,所以它不被 wait_for_sigusr1_mask 阻塞

    printf("Entering loop to wait for SIGUSR1 using sigsuspend...\n");
    while (!usr1_flag) {
        printf("  About to call sigsuspend()... waiting for SIGUSR1.\n");
        // sigsuspend 会:
        // 1. 临时将掩码设置为 wait_for_sigusr1_mask (只阻塞 SIGINT)。
        // 2. 挂起进程。
        // 3. 如果收到 SIGUSR1 (未被阻塞),handle_usr1 被调用,然后 sigsuspend 返回 -1 (EINTR)。
        // 4. 如果收到 SIGINT (被阻塞),行为取决于系统和信号是否排队,但通常会被延迟。
        // 5. 返回后,掩码恢复为 orig_mask (即 block_sigusr1_and_sigint)。
        int result = sigsuspend(&wait_for_sigusr1_mask);

        if (result == -1 && errno == EINTR) {
            printf("  sigsuspend() returned (interrupted).\n");
            if (usr1_flag) {
                printf("  -> It was SIGUSR1.\n");
            } else {
                printf("  -> It was a different unblocked signal (unlikely in this setup) or SIGINT was delivered.\n");
                // 在这个设置下,SIGINT 被阻塞,不太可能唤醒。但如果它以某种方式发生(例如,在设置掩码的间隙),
                // 程序的行为可能不符合预期。更健壮的方法是处理 SIGINT 在主循环条件中。
            }
        } else {
            perror("sigsuspend");
            break; // 错误退出
        }
    }

    if (usr1_flag) {
        printf("\nLoop exited successfully after receiving SIGUSR1.\n");
    } else {
        printf("\nLoop exited, possibly due to an unexpected signal.\n");
    }

    printf("Restoring original signal mask (if needed, though sigsuspend should have done it).\n");
    // sigsuspend 应该已经恢复了,但显式恢复是个好习惯
    if (sigprocmask(SIG_SETMASK, &orig_mask, NULL) == -1) {
        perror("sigprocmask RESTORE");
    }

    printf("Program ending.\n");
    return 0;
}

编译和运行:

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

# 终端 1: 运行程序
./sigsuspend_example
# 程序会输出 PID

# 终端 2:
# 发送 SIGUSR1 唤醒程序
# kill -USR1 <PID>

# 或者在终端 1 按 Ctrl+C 发送 SIGINT (根据最终示例的逻辑,这可能不会唤醒,但会终止程序)

这个最终的示例清晰地展示了 sigsuspend 的正确用法:

  1. 先用 sigprocmask 设置一个基础的信号掩码。
  2. 构造一个用于 sigsuspend 的临时掩码,该掩码精确地控制了哪些信号可以唤醒进程。
  3. 在循环中调用 sigsuspend,原子地应用临时掩码并挂起。
  4. 信号处理函数设置一个标志。
  5. sigsuspend 返回后,检查标志以确认是哪个信号导致的唤醒。
  6. sigsuspend 自动恢复之前的信号掩码。

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

发表在 linux文章 | 留下评论

rt_sigtimedwait系统调用及示例

我们来深入学习 rt_sigtimedwait 系统调用

1. 函数介绍

在 Linux 系统中,信号是一种重要的进程间通信和通知机制。通常,我们会为信号设置一个处理函数(使用 sigaction),当信号到来时,内核会中断程序的正常执行流程,转而去执行我们的处理函数。这是一种异步的处理方式。

但有时候,我们希望程序能够主动地、同步地等待某个信号的到来。也就是说,程序执行到某一点,就停下来,专门等着某个信号发生,信号来了,程序再接着往下走。这有点像在火车站等车,车来了(信号到了),你再上车(继续执行)。

rt_sigtimedwait(用户空间通常通过 sigtimedwait 或 sigwaitinfo 函数调用)就是这样一个工具。它允许你的程序明确地说:“我现在要等着信号 SIGUSR1 或 SIGRTMIN 中的任意一个到来”。它比 pause()(等待任意信号)或 sigsuspend()(等待信号但不指定具体哪个)更加精确。

更棒的是,它还可以:

  • 获取信号的详细信息:比如信号是谁发的?附带了什么数据?(通过 siginfo_t 结构体)
  • 设置等待超时:我不想一直等下去,最多等 5 秒钟,如果 5 秒内信号没来,就继续干别的事。这避免了程序无限期地挂起。

简单来说,sigtimedwait 就像是一个功能强大的“信号接收器”,你可以指定接收哪些信号,可以得到信号的“包裹单”(详细信息),还可以设置一个“闹钟”(超时时间)。

2. 函数原型

#include <signal.h>

// 带超时时间的版本
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);

// 不带超时时间的版本 (无限期等待)
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
// sigwaitinfo(set, info) 实际上等价于 sigtimedwait(set, info, NULL);

3. 功能

原子地将调用进程的信号掩码临时设置为与 set 中指定信号互补的掩码(即,阻塞所有除了 set 中信号之外的信号),然后挂起进程,等待 set 中的任何一个信号到来,或者等待 timeout 指定的时间超时。

关键点:它临时解除阻塞 set 中的信号,而阻塞其他所有信号。

4. 参数

  • set:
    • const sigset_t * 类型。
    • 一个指向信号集(sigset_t)的指针。这个集合定义了调用者愿意等待的信号。在 sigtimedwait 执行期间,只有这个集合中的信号才不会被阻塞。
  • info:
    • siginfo_t * 类型。
    • 一个指向 siginfo_t 结构体的指针。如果函数成功等到一个信号,该结构体会被填充为这个信号的详细信息(例如发送者 PID、附带的整数值等)。如果你不关心这些信息,可以传 NULL
  • timeout:
    • const struct timespec * 类型。
    • 一个指向 timespec 结构体的指针,用于指定最长等待时间timespec 结构包含 tv_sec(秒)和 tv_nsec(纳秒)两个成员。
    • 如果传入 NULL(就像 sigwaitinfo 那样),则函数会无限期等待,直到 set 中的某个信号到达。

5. 返回值

  • 成功(等到了 set 中的信号):返回接收到的那个信号的编号
  • 超时(对于 sigtimedwait,在指定时间内未收到信号):返回 -1,并设置 errno 为 EAGAIN
  • 被其他信号中断(例如,收到一个不在 set 中且未被阻塞的信号):返回 -1,并设置 errno 为 EINTR 或其他相关错误码。
  • 其他错误(例如参数无效):返回 -1,并设置相应的 errno

6. 相似函数或关联函数

  • sigwaitinfosigtimedwait 的一个特例,等价于 sigtimedwait(set, info, NULL),即无限期等待。
  • sigsuspend: 临时改变信号掩码并挂起等待,但不指定等待哪个信号,也不获取信号信息或设置超时。
  • pause: 简单地挂起进程直到收到任何信号。
  • sigprocmask: 用于检查或修改当前进程的信号屏蔽字。在使用 sigtimedwait 之前,通常需要先阻塞要等待的信号。
  • siginfo_t: 包含信号详细信息的结构体。
  • sigqueue: 用于向另一个进程发送信号和数据,常与 sigtimedwait/sigwaitinfo 配对使用,实现进程间数据传递。

7. 示例代码

下面是一个综合示例,演示如何使用 sigwaitinfo(无限等待)和 sigtimedwait(带超时)来等待信号,并获取信号附带的信息。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 sigqueue 等
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h> // 包含 pid_t
#include <sys/wait.h>  // 包含 wait
#include <errno.h>     // 包含 errno, EAGAIN
#include <time.h>      // 包含 timespec

int main() {
    pid_t pid;
    sigset_t wait_set;  // 定义要等待的信号集
    siginfo_t info;     // 用于接收信号信息
    struct timespec timeout; // 超时时间
    int sig_received;   // 存储接收到的信号号
    union sigval value; // 用于发送数据

    printf("Main (Parent) process PID: %d\n", getpid());

    // 1. 创建要等待的信号集:SIGUSR1 和 SIGRTMIN (一个实时信号)
    sigemptyset(&wait_set);        // 初始化为空集
    sigaddset(&wait_set, SIGUSR1); // 添加 SIGUSR1
    sigaddset(&wait_set, SIGRTMIN); // 添加 SIGRTMIN
    printf("Configured to wait for SIGUSR1 (%d) and SIGRTMIN (%d)\n", SIGUSR1, SIGRTMIN);

    // 2. 非常重要:在调用 sigtimedwait/sigwaitinfo 之前,
    // 必须先阻塞掉你打算等待的信号。
    // 这样可以确保这些信号在发送和等待之间不会被意外地异步处理掉。
    if (sigprocmask(SIG_BLOCK, &wait_set, NULL) == -1) {
        perror("sigprocmask BLOCK");
        exit(EXIT_FAILURE);
    }
    printf("Blocked SIGUSR1 and SIGRTMIN to queue them for synchronous waiting.\n");

    // 3. Fork 一个子进程来发送信号
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // --- Child Process ---
        printf("\nChild process (PID: %d) started.\n", getpid());

        sleep(2); // 等待 2 秒,让父进程先进入等待状态
        printf("Child: Sending SIGUSR1 to parent (PID: %d) using kill()...\n", getppid());
        if (kill(getppid(), SIGUSR1) == -1) { // 使用 kill 发送简单信号
            perror("kill SIGUSR1");
            exit(EXIT_FAILURE);
        }

        sleep(3); // 再等待 3 秒
        // 使用 sigqueue 发送实时信号和数据
        value.sival_int = 12345;
        printf("Child: Sending SIGRTMIN with value %d to parent using sigqueue()...\n", value.sival_int);
        if (sigqueue(getppid(), SIGRTMIN, value) == -1) {
            perror("sigqueue SIGRTMIN");
            exit(EXIT_FAILURE);
        }

        sleep(3); // 再等待 3 秒
        printf("Child: Sending SIGUSR1 again to parent using kill()...\n");
        if (kill(getppid(), SIGUSR1) == -1) {
            perror("kill SIGUSR1 (again)");
            exit(EXIT_FAILURE);
        }

        printf("Child process finished.\n");
        exit(EXIT_SUCCESS);

    } else {
        // --- Parent Process ---
        printf("\nParent process now waiting for signals synchronously...\n");

        // 4. 使用 sigwaitinfo 无限期等待第一个信号
        printf("\n--- Parent: Calling sigwaitinfo (will wait indefinitely) ---\n");
        printf("Parent: Waiting for either SIGUSR1 or SIGRTMIN...\n");
        // sigwaitinfo 会解除对 wait_set 中信号的阻塞,并等待其中一个到来
        sig_received = sigwaitinfo(&wait_set, &info); // 等待 set 中任意一个信号
        if (sig_received == -1) {
            perror("sigwaitinfo"); // 通常不会发生,除非被其他未阻塞的信号中断
        } else {
            printf("Parent: sigwaitinfo successfully returned.\n");
            printf("  Parent: Received signal number: %d\n", sig_received);
            if (sig_received == SIGUSR1) {
                printf("  Parent: It was SIGUSR1.\n");
                printf("  Parent: Sender PID: %d\n", info.si_pid);
                // SIGUSR1 通过 kill 发送,通常 si_code 是 SI_USER
                printf("  Parent: si_code: %d (SI_USER=%d)\n", info.si_code, SI_USER);
            }
        }

        // 5. 使用 sigtimedwait 等待下一个信号,设置 5 秒超时
        printf("\n--- Parent: Calling sigtimedwait (with 5s timeout) ---\n");
        timeout.tv_sec = 5;  // 5 秒
        timeout.tv_nsec = 0; // 0 纳秒
        printf("Parent: Waiting for next signal (timeout set to 5 seconds)...\n");
        sig_received = sigtimedwait(&wait_set, &info, &timeout);
        if (sig_received == -1) {
            if (errno == EAGAIN) {
                printf("Parent: sigtimedwait timed out. No signal arrived within 5 seconds.\n");
            } else {
                perror("Parent: sigtimedwait"); // 其他错误,如被其他信号中断 (EINTR)
            }
        } else {
            printf("Parent: sigtimedwait successfully returned before timeout.\n");
            printf("  Parent: Received signal number: %d\n", sig_received);
            if (sig_received == SIGRTMIN) {
                // SIGRTMIN 通过 sigqueue 发送,si_code 是 SI_QUEUE
                if (info.si_code == SI_QUEUE) {
                    printf("  Parent: It was SIGRTMIN sent via sigqueue().\n");
                    printf("  Parent: Sender PID: %d\n", info.si_pid);
                    printf("  Parent: Attached integer value: %d\n", info.si_value.sival_int);
                }
            }
        }

        // 6. 再次使用 sigwaitinfo 等待信号 (应该很快收到之前发送的 SIGUSR1)
        printf("\n--- Parent: Calling sigwaitinfo again ---\n");
        printf("Parent: Waiting for any of {SIGUSR1, SIGRTMIN} again...\n");
        sig_received = sigwaitinfo(&wait_set, &info);
        if (sig_received != -1) {
             printf("Parent: sigwaitinfo successfully returned.\n");
             printf("  Parent: Received signal number: %d\n", sig_received);
             if (sig_received == SIGUSR1) {
                 printf("  Parent: It was SIGUSR1.\n");
                 printf("  Parent: Sender PID: %d\n", info.si_pid);
             }
        } else {
            perror("Parent: sigwaitinfo (second call)");
        }

        // 7. 等待子进程结束
        if (wait(NULL) == -1) {
            perror("wait");
        }
        printf("\nParent: Confirmed child process (PID %d) has finished. Parent exiting.\n", pid);
    }

    return 0;
}

编译和运行:

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

# 运行程序
./sigtimedwait_example

预期输出 (时间点可能略有差异):

Main (Parent) process PID: 12345
Configured to wait for SIGUSR1 (10) and SIGRTMIN (34)
Blocked SIGUSR1 and SIGRTMIN to queue them for synchronous waiting.

Parent process now waiting for signals synchronously...

--- Parent: Calling sigwaitinfo (will wait indefinitely) ---
Parent: Waiting for either SIGUSR1 or SIGRTMIN...
Child process (PID: 12346) started.
Child: Sending SIGUSR1 to parent (PID: 12345) using kill()...
Parent: sigwaitinfo successfully returned.
  Parent: Received signal number: 10
  Parent: It was SIGUSR1.
  Parent: Sender PID: 12346
  Parent: si_code: 0 (SI_USER=0) # SI_USER 的值在不同系统上可能不同,通常是 0

--- Parent: Calling sigtimedwait (with 5s timeout) ---
Parent: Waiting for next signal (timeout set to 5 seconds)...
Child: Sending SIGRTMIN with value 12345 to parent using sigqueue()...
Parent: sigtimedwait successfully returned before timeout.
  Parent: Received signal number: 34
  Parent: It was SIGRTMIN sent via sigqueue().
  Parent: Sender PID: 12346
  Parent: Attached integer value: 12345

--- Parent: Calling sigwaitinfo again ---
Parent: Waiting for any of {SIGUSR1, SIGRTMIN} again...
Child: Sending SIGUSR1 again to parent using kill()...
Child process finished.
Parent: sigwaitinfo successfully returned.
  Parent: Received signal number: 10
  Parent: It was SIGUSR1.
  Parent: Sender PID: 12346

Parent: Confirmed child process (PID 12346) has finished. Parent exiting.

关键点总结:

  1. 预先阻塞信号:在调用 sigtimedwait/sigwaitinfo 之前,必须使用 sigprocmask(SIG_BLOCK, ...) 阻塞你打算等待的信号。这是确保信号能被这些函数捕获的关键步骤。
  2. 原子性等待:这两个函数原子性地执行“解除对指定信号的阻塞”和“挂起等待”操作,避免了在设置掩码和挂起之间可能发生的竞态条件。
  3. 精确等待:通过 set 参数,你可以精确指定等待哪一组信号。
  4. 超时控制sigtimedwait 的 timeout 参数让你可以避免程序无限期地等待。
  5. 信息获取:通过 info 参数,你可以获得信号的来源、发送方式(kill vs sigqueue)以及附带的数据(使用 sigqueue 发送时),这使得信号成为一种强大的进程间通信机制。

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

发表在 linux文章 | 留下评论

rt_tgsigqueueinfo系统调用及示例

我们来深入学习 rt_tgsigqueueinfo 系统调用,在 Linux 系统中,信号(Signal)是进程间通信的一种方式。kill() 系统调用可以向一个进程发送信号,而 sigqueue() 则可以发送信号并附带一些数据(union sigval)。rt_tgsigqueueinfo 是一个更底层、更具体、但也更复杂的系统调用。

1. 函数介绍

在 Linux 系统中,信号(Signal)是进程间通信的一种方式。kill() 系统调用可以向一个进程发送信号,而 sigqueue() 则可以发送信号并附带一些数据(union sigval)。

rt_tgsigqueueinfo 是一个更底层、更具体、但也更复杂的系统调用。它的名字可以拆解为:

  • rt_: 表示这是一个与实时信号相关的系统调用。
  • tg: 表示 Thread Group(线程组)。在 Linux 中,一个进程(Process)可以包含多个线程(Threads),这些线程共享同一个 PID,但每个线程有自己的唯一 Thread ID (TID)。所有共享同一个 PID 的线程构成了一个线程组。
  • sigqueueinfo: 表示它像 sigqueue 一样发送信号和数据,但允许发送者提供更详细的信号信息(通过 siginfo_t 结构体)。

所以,rt_tgsigqueueinfo 的作用是:向指定进程(线程组)中的指定线程(由 TID 指定)发送一个信号,并允许发送者完全指定该信号的 siginfo_t 信息

与 sigqueue 的区别:

  • sigqueue(pid_t pid, ...): 向进程 pid 发送信号。内核会选择该进程(线程组)中的某个线程来接收信号。
  • rt_tgsigqueueinfo(pid_t tgid, pid_t tid, ...)tgid 是线程组 ID(通常就是进程的 PID),tid 是线程组内具体线程的 ID(TID)。它允许你指定哪个线程应该接收这个信号。

为什么复杂/危险?
与 rt_sigqueueinfo 类似,它允许发送者完全伪造 siginfo_t 中的信息(如发送者 PID、si_code 等),这可能带来安全风险。因此,它的使用受到严格的权限检查。

对于 Linux 编程小白:你通常不需要直接使用 rt_tgsigqueueinfo。在需要向特定线程发送信号时,更常见的做法是在线程内部使用 pthread_kill()(POSIX 线程库函数)。rt_tgsigqueueinfo 主要供系统级程序或 C 库实现使用。

2. 函数原型

// 这是底层系统调用,用户空间标准 C 库通常不直接提供包装函数
#include <sys/syscall.h> // 包含系统调用号
#include <unistd.h>      // 包含 syscall 函数
#include <signal.h>      // 包含 siginfo_t 定义

long syscall(SYS_rt_tgsigqueueinfo, pid_t tgid, pid_t tid, int sig, siginfo_t *uinfo);

用户空间更常用的相关函数:

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value); // 向进程发送信号和数据

#include <pthread.h> // (需要链接 -lpthread)
int pthread_kill(pthread_t thread, int sig); // 向特定 POSIX 线程发送信号

3. 功能

向指定线程组 ID (tgid) 和线程 ID (tid) 的线程发送指定信号 (sig),并允许发送者完全指定 siginfo_t 结构体 (uinfo) 中包含的详细信息。

4. 参数

  • tgid:
    • pid_t 类型。
    • 目标线程所属的线程组 ID (Thread Group ID)。对于单线程进程,这通常就是它的 PID。对于多线程程序,所有线程共享同一个 tgid(即主线程的 PID)。
  • tid:
    • pid_t 类型。
    • 目标线程在内核中的唯一 ID (Thread ID)。在用户空间的 pthread_create 返回的 pthread_t 和内核的 tid 之间需要转换(可以使用 /proc/self/task/ 目录或特定的系统调用来获取)。
  • sig:
    • int 类型。
    • 要发送的信号编号,例如 SIGUSR1SIGRTMIN 等。注意,不能发送 SIGKILL 和 SIGSTOP
  • uinfo:
    • siginfo_t * 类型。
    • 一个指向 siginfo_t 结构体的指针。这个结构体包含了你想随信号一起传递给目标线程的所有详细信息。调用者需要自己填充这个结构体的各个字段。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
    • EAGAIN: (对于实时信号) 已达到接收者排队信号的最大数量限制 (RLIMIT_SIGPENDING)。
    • EPERM: 调用者没有权限发送信号给目标线程(例如,权限不足,或试图伪造某些 si_code)。
    • EINVALsig 是无效的信号号,tgid 或 tid 无效,或者 uinfo 中的 si_code 是无效的或不允许由用户设置的。
    • ESRCH: 找不到 tgid 指定的线程组或 tid 指定的线程。

6. 相似函数或关联函数

  • sigqueue: 向进程(线程组)发送信号和数据,由内核选择线程接收。
  • pthread_kill: POSIX 标准函数,用于向指定的 pthread_t 线程发送信号,更易于使用。
  • tgkill: 一个更安全的系统调用,用于向指定线程组和线程发送信号(但不带 siginfo_t 数据),通常由 pthread_kill 内部调用。
  • kill: 向进程发送信号。
  • siginfo_t: 包含信号详细信息的结构体。
  • getpid: 获取当前进程的 PID (也是线程组 ID tgid)。
  • gettid: 获取当前线程的内核 TID。注意:这不是标准 C 库函数,需要通过 syscall(SYS_gettid) 调用。
  • pthread_self: 获取当前线程的 POSIX 线程 ID (pthread_t)。

7. 示例代码

由于直接使用 rt_tgsigqueueinfo 需要获取内核线程 ID (tid) 并且容易因权限或错误的 siginfo_t 而失败,下面的示例将演示如何获取 tid 并尝试调用 rt_tgsigqueueinfo,同时提供一个使用推荐的 pthread_kill 的对比示例。

警告:直接使用 rt_tgsigqueueinfo 可能会因为权限问题或 siginfo_t 构造不当而失败。

#define _GNU_SOURCE // 启用 GNU 扩展,获取 gettid
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/syscall.h> // 包含 syscall 和 SYS_rt_tgsigqueueinfo, SYS_gettid
#include <errno.h>
#include <sys/types.h> // 包含 pid_t
#include <pthread.h>   // 包含 pthread_t, pthread_create 等
#include <stdatomic.h> // 用于线程间安全通信

// 全局变量用于线程间通信
_Atomic int signal_count = 0;

// 信号处理函数 (使用 SA_SIGINFO 获取详细信息)
void signal_handler(int sig, siginfo_t *info, void *context) {
    atomic_fetch_add(&signal_count, 1); // 原子增加计数
    printf("Thread %ld: Received signal %d\n", syscall(SYS_gettid), sig);
    printf("  si_signo: %d\n", info->si_signo);
    printf("  si_code:  %d", info->si_code);
    switch(info->si_code) {
        case SI_USER: printf(" (SI_USER: kill, sigsend or raise)\n"); break;
        case SI_QUEUE: printf(" (SI_QUEUE: sigqueue)\n"); break;
        case SI_TKILL: printf(" (SI_TKILL: tkill or tgkill)\n"); break;
        default: printf(" (Other code)\n"); break;
    }
    printf("  si_pid:   %d\n", info->si_pid);
    if (info->si_code == SI_QUEUE) {
        printf("  si_value.sival_int: %d\n", info->si_value.sival_int);
    }
}

// 线程函数
void* worker_thread(void *arg) {
    sigset_t set;
    int sig;
    siginfo_t info;

    printf("Worker thread started. TID: %ld, PID (TGID): %d\n", syscall(SYS_gettid), getpid());

    // 为这个线程设置信号掩码,只等待 SIGUSR1
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);

    // 阻塞 SIGUSR1,准备用 sigwaitinfo 接收
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
        perror("sigprocmask (worker)");
        return NULL;
    }

    while (atomic_load(&signal_count) < 3) { // 等待接收3次信号
        printf("Worker thread (TID %ld) waiting for SIGUSR1...\n", syscall(SYS_gettid));
        sig = sigwaitinfo(&set, &info); // 同步等待 SIGUSR1
        if (sig == -1) {
            perror("sigwaitinfo (worker)");
            break;
        }
        // sigwaitinfo 成功返回后,信号处理函数 signal_handler 会被调用
        // 因为我们阻塞了信号,sigwaitinfo 会接收它,但处理函数仍然会执行
        // 这里我们主要依靠 signal_handler 中的计数
    }
    printf("Worker thread (TID %ld) finished.\n", syscall(SYS_gettid));
    return NULL;
}


int main() {
    pthread_t thread;
    pid_t my_tgid, my_tid, worker_tid;
    struct sigaction sa;
    siginfo_t si_to_send;
    union sigval data_to_send = {.sival_int = 54321};

    my_tgid = getpid();
    my_tid = syscall(SYS_gettid); // 获取主线程的内核 TID
    printf("Main thread: PID (TGID): %d, TID: %ld\n", my_tgid, my_tid);

    // 1. 设置 SIGUSR1 的处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = signal_handler; // 使用 sa_sigaction 获取 info
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO; // 必须设置
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    // 2. 创建一个工作线程
    if (pthread_create(&thread, NULL, worker_thread, NULL) != 0) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    // 简单等待一下,让子线程启动并打印其 TID
    sleep(1);
    // 在真实的程序中,获取另一个线程的 TID 比较复杂,
    // 通常需要线程自己报告。这里我们简化处理,
    // 假设我们 somehow 知道了 worker 线程的 TID。
    // 为了演示,我们还是向主线程自己发送。
    worker_tid = my_tid; // 简化:发送给自己
    printf("\n--- Attempting to use rt_tgsigqueueinfo ---\n");
    printf("Targeting TGID: %d, TID: %ld\n", my_tgid, worker_tid);

    // 3. 准备 siginfo_t 结构体
    memset(&si_to_send, 0, sizeof(si_to_send));
    si_to_send.si_signo = SIGUSR1;
    si_to_send.si_code = SI_QUEUE; // 伪造为 sigqueue 发送
    si_to_send.si_pid = my_tgid;   // 伪造 PID
    si_to_send.si_uid = getuid();  // 伪造 UID
    si_to_send.si_value = data_to_send; // 附带数据

    printf("Sending SIGUSR1 with data %d using rt_tgsigqueueinfo()...\n", data_to_send.sival_int);
    printf("(This may fail with EPERM unless run with special privileges or on some systems)\n");

    // 4. 调用底层系统调用
    long result = syscall(SYS_rt_tgsigqueueinfo, my_tgid, worker_tid, SIGUSR1, &si_to_send);

    if (result == -1) {
        perror("rt_tgsigqueueinfo");
        printf("Error: rt_tgsigqueueinfo failed.\n");
        printf("Errno: %d\n", errno);
        if (errno == EPERM) {
            printf("Reason: EPERM - Operation not permitted (insufficient privileges or invalid si_code).\n");
        }
    } else {
        printf("rt_tgsigqueueinfo succeeded.\n");
    }

    sleep(2); // 等待信号处理

    // 5. 再发送一次,使用标准的 sigqueue (推荐方式)
    printf("\n--- Using sigqueue (Recommended) ---\n");
    printf("Sending SIGUSR1 with data %d using sigqueue()...\n", data_to_send.sival_int);
    if (sigqueue(my_tgid, SIGUSR1, data_to_send) == -1) {
        perror("sigqueue");
    }
    sleep(2);

    // 6. 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
    }

    printf("\nFinal signal count: %d\n", atomic_load(&signal_count));
    printf("Main program finished.\n");
    return 0;
}

使用 pthread_kill 的推荐示例:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <stdatomic.h>

_Atomic int signal_received = 0;

void thread_signal_handler(int sig) {
    atomic_store(&signal_received, 1);
    printf("Signal %d received by thread %lu\n", sig, pthread_self());
}

void* target_thread(void *arg) {
    // 设置本线程的信号处理掩码,解除对 SIGUSR1 的阻塞
    // (假设主线程已经阻塞了它)
    sigset_t set;
    sigemptyset(&set);
    // sigaddset(&set, SIGUSR1); // 如果需要在这里处理
    pthread_sigmask(SIG_UNBLOCK, &set, NULL);

    printf("Target thread (pthread_t: %lu) running...\n", pthread_self());
    // 简单循环等待信号
    while(!atomic_load(&signal_received)) {
        sleep(1);
    }
    printf("Target thread received signal and is exiting.\n");
    return NULL;
}

int main() {
    pthread_t thread;
    struct sigaction sa;

    // 设置信号处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = thread_signal_handler;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    // 阻塞 SIGUSR1 在主线程
    sigset_t block_set;
    sigemptyset(&block_set);
    sigaddset(&block_set, SIGUSR1);
    sigprocmask(SIG_BLOCK, &block_set, NULL);

    if (pthread_create(&thread, NULL, target_thread, NULL) != 0) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    sleep(1); // 让子线程启动

    printf("Main thread sending SIGUSR1 to target thread using pthread_kill()...\n");
    if (pthread_kill(thread, SIGUSR1) != 0) { // 推荐方式
        perror("pthread_kill");
    }

    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
    }

    printf("Main thread finished.\n");
    return 0;
}

编译和运行:

# 假设代码保存在 rt_tgsigqueueinfo_example.c 和 pthread_kill_example.c 中
# 需要链接 pthread 库
gcc -o rt_tgsigqueueinfo_example rt_tgsigqueueinfo_example.c -lpthread
gcc -o pthread_kill_example pthread_kill_example.c -lpthread

# 运行示例 (rt_tgsigqueueinfo 可能会因权限失败)
./rt_tgsigqueueinfo_example

# 运行推荐示例 (pthread_kill)
./pthread_kill_example

总结:
对于 Linux 编程新手,处理多线程信号时,请优先学习和使用 pthread_kill()rt_tgsigqueueinfo 是一个底层、强大的工具,但使用不当会有安全风险且复杂,通常由系统库内部或高级系统编程使用。

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

发表在 linux文章 | 留下评论

select系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 select 函数,它是一种经典的 I/O 多路复用机制,允许一个进程监视多个文件描述符,等待其中任何一个或多个文件描述符变为“就绪”状态(例如可读、可写或发生异常)。

注意:虽然 select 功能强大且历史悠久,但在处理大量文件描述符时,poll 和更现代的 epoll (Linux 特有) 通常性能更好。不过,select 因其可移植性(在多种 Unix 系统上都可用)和教学价值,仍然是需要了解的重要函数。


1. 函数介绍

select 是一个 Linux 系统调用(实际上在很多类 Unix 系统上都可用),用于实现 I/O 多路复用 (I/O multiplexing)。它的核心思想是让进程能够同时检查多个文件描述符(如套接字、管道、终端等)的状态,看它们是否准备好进行 I/O 操作(例如读取、写入),而无需对每个文件描述符都进行阻塞式等待。

在没有 select(或 pollepoll)的情况下,如果一个程序需要同时处理多个网络连接或文件,它可能需要创建多个线程或进程,或者在一个文件描述符上阻塞等待,这会非常低效或复杂。select 允许一个线程/进程在一个调用中“监听”所有感兴趣的文件描述符,当其中任何一个准备好时,select 返回,程序就可以处理那个就绪的文件描述符。

你可以把它想象成一个“服务员”,同时照看多张餐桌(文件描述符)。服务员不需要一直站在某一张餐桌旁等客人点菜(数据),而是可以走一圈看看哪张餐桌的客人举手了(数据就绪),然后去为那张餐桌服务。


2. 函数原型

#include <sys/select.h> // 必需

// 标准形式
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

// pselect 是 select 的变体,增加了信号掩码参数,这里暂不讨论
// int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
//             const struct timespec *timeout, const sigset_t *sigmask);

3. 功能

  • 监视文件描述符集合select 会检查 readfdswritefdsexceptfds 这三个集合中列出的文件描述符的状态。
  • 等待就绪: 调用 select 的进程会阻塞(挂起),直到以下情况之一发生:
    1. readfdswritefdsexceptfds 中指定的至少一个文件描述符变为“就绪”状态。
    2. 调用被信号中断(返回 -1,并设置 errno 为 EINTR)。
    3. 达到指定的超时时间 timeout(如果 timeout 不为 NULL)。
  • 返回就绪数量: 当 select 返回时,它会报告有多少个文件描述符已就绪。
  • 更新集合关键点select 会修改 readfdswritefdsexceptfds 这三个集合作为输出。调用返回后,这些集合中只保留那些已就绪的文件描述符,所有未就绪的文件描述符都会从集合中被清除。因此,如果需要在下一次 select 调用中继续监视相同的文件描述符集合,必须在每次调用 select 之前重新设置这些集合。

4. 参数

  • int nfds: 这是需要监视的文件描述符中的最大值加 1。它用于确定内核需要检查的文件描述符范围。例如,如果监视的文件描述符是 0, 1, 4, 7,那么 nfds 应该是 8 (7 + 1)。在现代实现中,这个参数可能不那么关键,但为了兼容性和正确性,应始终正确设置。
  • fd_set *readfds: 指向一个 fd_set 类型的集合。调用者将所有关心可读性的文件描述符加入此集合(使用 FD_SET 宏)。select 返回时,此集合会被修改,仅保留那些已准备好读取的文件描述符。
  • fd_set *writefds: 指向一个 fd_set 类型的集合。调用者将所有关心可写性的文件描述符加入此集合。select 返回时,此集合会被修改,仅保留那些已准备好写入的文件描述符。
  • fd_set *exceptfds: 指向一个 fd_set 类型的集合。用于监视“异常条件”(如带外数据)。在很多应用中(特别是 TCP 网络编程),这个参数可以设为 NULL。
  • struct timeval *timeout: 指定 select 调用阻塞等待的超时时间。
    • timeout == NULLselect 会无限期阻塞,直到至少一个文件描述符就绪或被信号中断。
    • timeout->tv_sec == 0 && timeout->tv_usec == 0select 执行非阻塞检查,立即返回,报告当前有多少文件描述符已就绪。
    • timeout->tv_sec > 0 || timeout->tv_usec > 0select 最多阻塞 tv_sec 秒 + tv_usec 微秒。如果在超时前没有文件描述符就绪,则返回 0。
      struct timeval 定义如下:
    struct timeval { long tv_sec; // 秒 long tv_usec; // 微秒 (0-999999) }; 重要: 在 Linux 上,select 返回时,timeout 的值可能会被修改,以反映剩余的超时时间。但在可移植代码中,不应依赖此行为,每次调用 select 前都应重新设置 timeout

5. fd_set 集合操作宏

select 使用 fd_set 数据结构来表示文件描述符集合。操作 fd_set 需要使用以下标准宏:

  • FD_ZERO(fd_set *set): 清空(初始化)整个 fd_set 集合,移除所有文件描述符。
  • FD_SET(int fd, fd_set *set): 将文件描述符 fd 添加到 fd_set 集合 set 中。
  • FD_CLR(int fd, fd_set *set): 将文件描述符 fd 从 fd_set 集合 set 中移除。
  • FD_ISSET(int fd, fd_set *set): 检查文件描述符 fd 是否在 fd_set 集合 set 中。如果在,返回非零值;否则返回 0。

6. 返回值

  • 成功时:
    • 返回 就绪的文件描述符的总数。这个数字可以是 0(表示超时)。
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EBADF 某个 fd 无效,EINTR 调用被信号中断,EINVAL nfds 负数等)。
  • 超时:
    • 如果在 timeout 指定的时间内没有任何文件描述符就绪,返回 0。

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

  • poll: 功能与 select 类似,但使用 struct pollfd 数组而不是 fd_set 位掩码。在处理大量文件描述符时通常性能更好,且没有 FD_SETSIZE 的硬性限制。
  • epoll_wait / epoll_ctl / epoll_create: Linux 特有的、更高效的 I/O 多路复用机制,特别适合处理大量的并发连接。它使用一个内核事件表来管理监视的文件描述符,避免了 select/poll 每次调用都需要传递整个文件描述符集合的开销。
  • readwrite: 在 select 返回某个文件描述符就绪后,通常会调用 read 或 write 来执行实际的 I/O 操作。

8. 示例代码

示例 1:监视标准输入和一个管道

这个例子演示如何使用 select 同时监视标准输入(键盘)和一个管道的读端,看哪个先有数据可读。

#include <sys/select.h> // select, fd_set, FD_*
#include <sys/time.h>   // struct timeval
#include <unistd.h>     // pipe, read, write, close, STDIN_FILENO
#include <stdio.h>      // perror, printf, fprintf
#include <stdlib.h>     // exit
#include <string.h>     // strlen

int main() {
    int pipefd[2];
    fd_set readfds;           // 用于 select 的文件描述符集合
    int max_fd;               // select 需要的最大文件描述符 + 1
    struct timeval timeout;   // 超时设置
    int activity;             // select 返回值
    char buffer[100];
    ssize_t bytes_read;

    // 1. 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 2. 计算 select 需要监视的最大文件描述符 + 1
    max_fd = (STDIN_FILENO > pipefd[0]) ? STDIN_FILENO : pipefd[0];
    max_fd += 1;

    printf("Waiting up to 5 seconds for input from stdin or data in pipe...\n");
    printf("Type something in the terminal, or run 'echo hello > /proc/%d/fd/%d' in another terminal.\n",
           getpid(), pipefd[1]); // 提示用户如何向管道写入

    // 3. 主循环
    while (1) {
        // 4. 每次循环开始前,必须重新初始化 fd_set
        FD_ZERO(&readfds);             // 清空集合
        FD_SET(STDIN_FILENO, &readfds); // 添加标准输入
        FD_SET(pipefd[0], &readfds);    // 添加管道读端

        // 5. 设置超时 (5秒)
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        // 6. 调用 select 进行等待
        // 注意: timeout 结构体可能会被 select 修改,所以放在循环内
        activity = select(max_fd, &readfds, NULL, NULL, &timeout);

        // 7. 检查 select 的返回值
        if (activity == -1) {
            if (errno == EINTR) {
                printf("select was interrupted by a signal, continuing...\n");
                continue; // 被信号中断,通常继续循环
            } else {
                perror("select error");
                break; // 或 exit(EXIT_FAILURE);
            }
        } else if (activity == 0) {
            printf("Timeout occurred! No data within 5 seconds. Exiting.\n");
            break; // 超时退出循环
        } else {
            printf("%d file descriptor(s) became ready.\n", activity);

            // 8. 检查哪个文件描述符就绪了
            // 注意:select 返回后,readfds 只包含就绪的 fd
            if (FD_ISSET(STDIN_FILENO, &readfds)) {
                printf("  -> Data is ready on standard input (stdin).\n");
                bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    buffer[bytes_read] = '\0'; // 确保字符串结束
                    printf("  -> Read from stdin: %s", buffer); // buffer 可能已包含 \n
                }
            }

            if (FD_ISSET(pipefd[0], &readfds)) {
                printf("  -> Data is ready on the pipe.\n");
                bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    printf("  -> Read from pipe: %s", buffer);
                }
            }
        }
    }

    // 9. 清理资源
    close(pipefd[0]);
    close(pipefd[1]);

    return 0;
}

代码解释:

  1. 使用 pipe() 创建一个管道。
  2. 计算需要监视的最大文件描述符 max_fdSTDIN_FILENO 和 pipefd[0] 中的较大者加 1)。
  3. 进入主循环。
  4. 关键: 在每次 select 调用前,使用 FD_ZERO(&readfds) 清空 fd_set
  5. 使用 FD_SET 将 STDIN_FILENO 和 pipefd[0] 添加到 readfds 集合中。
  6. 设置 timeout 结构体为 5 秒。
  7. 调用 select(max_fd, &readfds, NULL, NULL, &timeout)。我们只关心可读性,所以 writefds 和 exceptfds 都是 NULL
  8. 检查 select 的返回值:
    • -1:错误(检查 errno 是否为 EINTR)。
    • 0:超时。
    • 0:就绪的文件描述符数量。
  9. 如果有文件描述符就绪(activity > 0),使用 FD_ISSET 检查 STDIN_FILENO 和 pipefd[0] 是否在修改后的 readfds 集合中。
  10. 对于就绪的文件描述符,调用 read 读取数据。
  11. 循环继续或根据条件(如超时)退出。
  12. 最后关闭管道的两端。

示例 2:简单的 TCP 服务器(非阻塞 accept 和客户端 socket)

这个例子演示如何在 TCP 服务器中使用 select 来同时监听监听套接字(用于接受新连接)和已建立连接的客户端套接字(用于接收数据)。

#include <sys/select.h> // select, fd_set, FD_*
#include <sys/time.h>   // struct timeval
#include <sys/socket.h> // socket, bind, listen, accept, recv, send
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h>  // inet_ntoa (简化版,非线程安全)
#include <unistd.h>     // close, read, write
#include <stdio.h>      // perror, printf, fprintf
#include <stdlib.h>     // exit
#include <string.h>     // memset, strlen

#define PORT 8080
#define MAX_CLIENTS 30 // select 的 fd_set 通常有 FD_SETSIZE (1024) 的限制
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket, client_socket[MAX_CLIENTS];
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    fd_set readfds;         // select 用的读集合
    int max_sd;             // 当前最大的文件描述符
    int activity, i, valread;
    char buffer[BUFFER_SIZE] = {0};
    char *hello = "Hello from server";

    // 1. 初始化客户端套接字数组
    for (i = 0; i < MAX_CLIENTS; i++) {
        client_socket[i] = 0; // 0 表示空闲槽位
    }

    // 2. 创建服务器套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 3. 配置服务器地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口
    address.sin_port = htons(PORT);

    // 4. 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 5. 监听连接
    if (listen(server_fd, 3) < 0) { // backlog=3
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 6. 主循环
    while(1) {
        // 7. 清空 fd_set
        FD_ZERO(&readfds);

        // 8. 将监听套接字加入集合
        FD_SET(server_fd, &readfds);
        max_sd = server_fd;

        // 9. 将已连接的客户端套接字加入集合
        for (i = 0; i < MAX_CLIENTS; i++) {
            if (client_socket[i] > 0) {
                FD_SET(client_socket[i], &readfds);
            }
            // 更新最大文件描述符
            if (client_socket[i] > max_sd) {
                max_sd = client_socket[i];
            }
        }

        // 10. 设置超时 (这里设为 NULL,表示无限等待)
        struct timeval timeout;
        timeout.tv_sec = 30; // 30秒超时,避免永久阻塞
        timeout.tv_usec = 0;

        // 11. 调用 select 等待活动
        activity = select(max_sd + 1, &readfds, NULL, NULL, &timeout);

        if (activity < 0) {
            if (errno == EINTR) {
                printf("select interrupted by signal, continuing...\n");
                continue;
            }
            perror("select error");
            break;
        }

        if (activity == 0) {
             printf("select timeout (30s), continuing...\n");
             continue;
        }

        // 12. 检查监听套接字是否有活动 (新连接)
        if (FD_ISSET(server_fd, &readfds)) {
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                perror("accept");
                continue;
            }

            printf("New connection, socket fd is %d, ip is : %s, port : %d\n",
                   new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            // 13. 将新连接的套接字添加到客户端数组中
            int added = 0;
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (client_socket[i] == 0) {
                    client_socket[i] = new_socket;
                    added = 1;
                    printf("Adding to list of sockets at index %d\n", i);
                    send(new_socket, hello, strlen(hello), 0); // 发送欢迎信息
                    break;
                }
            }
            if (!added) {
                printf("Too many clients, connection rejected\n");
                close(new_socket);
            }
        }

        // 14. 检查已连接的客户端套接字是否有活动
        for (i = 0; i < MAX_CLIENTS; i++) {
            int sd = client_socket[i];

            if (FD_ISSET(sd, &readfds)) {
                // 15. 有数据从客户端发来
                valread = read(sd, buffer, BUFFER_SIZE - 1);
                if (valread == 0) {
                    // 客户端断开连接
                    getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
                    printf("Host disconnected, ip %s, port %d. Closing socket %d\n",
                           inet_ntoa(address.sin_addr), ntohs(address.sin_port), sd);
                    close(sd);
                    client_socket[i] = 0; // 标记槽位为空闲
                } else {
                    // 处理收到的数据
                    buffer[valread] = '\0';
                    printf("Received message from socket %d: %s", sd, buffer);
                    // Echo 回去
                    send(sd, buffer, strlen(buffer), 0);
                }
            }
        }
    }

    // 16. 清理 (在真实应用中,需要更优雅的退出机制)
    for(i = 0; i < MAX_CLIENTS; i++) {
        if(client_socket[i] != 0) {
            close(client_socket[i]);
        }
    }
    close(server_fd);
    printf("Server closed.\n");
    return 0;
}

代码解释:

  1. 创建、绑定、监听 TCP 套接字。
  2. 初始化一个 client_socket 数组,用于存储已建立连接的客户端套接字。用 0 表示空闲槽位。
  3. 进入主循环。
  4. 每次循环开始,使用 FD_ZERO(&readfds) 清空 fd_set
  5. 使用 FD_SET(server_fd, &readfds) 将监听套接字加入监视集合。
  6. 遍历 client_socket 数组,将所有有效的(非零)客户端套接字也加入 readfds 集合。
  7. 在添加文件描述符的过程中,动态计算并更新 max_sd(当前监视的最大文件描述符)。
  8. 设置 select 的超时时间(这里设为 30 秒,避免无限期阻塞)。
  9. 调用 select(max_sd + 1, &readfds, NULL, NULL, &timeout)
  10. 检查 select 返回值 activity
  11. 如果 FD_ISSET(server_fd, &readfds) 为真,说明监听套接字就绪,调用 accept 接受新连接,并将其存入 client_socket 数组的空闲槽位。
  12. 遍历 client_socket 数组,检查每个有效的客户端套接字 sd 是否在 readfds 中(即 FD_ISSET(sd, &readfds) 为真)。
  13. 如果某个客户端套接字就绪,调用 read 读取数据。
  14. 如果 read 返回 0,表示客户端关闭了连接,关闭该套接字,并将 client_socket 数组中对应的项标记为 0(空闲)。
  15. 如果 read 返回正数,表示读到了数据,这里简单地将其 echo 回客户端。
  16. 循环继续处理下一个事件。

这个例子展示了 select 如何在一个单线程服务器中高效地管理多个并发连接。与为每个连接创建一个线程或进程相比,select(以及更高效的 poll 和 epoll)是构建高性能网络服务器的基础技术之一。

总结:

select 是一个功能强大且历史悠久的 I/O 多路复用函数。理解其关键是掌握 fd_set 集合的操作(FD_ZEROFD_SETFD_CLRFD_ISSET)、nfds 参数的正确设置、select 返回后集合的修改行为,以及如何根据返回的就绪文件描述符数量和状态来处理相应的 I/O 操作。虽然在处理大量连接时不如 poll 或 epoll 高效,但其良好的可移植性使其在很多场景下仍然有用。

发表在 linux文章 | 留下评论

Xiaomi Redmi Note 15 Review

Xiaomi Redmi Note 15 Review

Introduction

The Xiaomi Redmi Note 15 is a mid-range smartphone that continues Redmi’s tradition of offering great value for money. As part of the popular Note series, it aims to provide solid performance and features at an affordable price point.

Realme P4 Pro 5G price in India, specifications, launch date, camera and more – Hindustan Times

Key Specifications

Display:

  • 6.5-inch IPS LCD
  • 90Hz refresh rate
  • FHD+ resolution (1080 x 2400 pixels)
  • 400 nits brightness

Processor:

  • Qualcomm Snapdragon 680 4G
  • Octa-core CPU (4×2.4 GHz Kryo 265 Gold & 4×1.9 GHz Kryo 265 Silver)
  • Adreno 610 GPU

Memory & Storage:

  • RAM: 4GB, 6GB, or 8GB
  • Storage: 64GB, 128GB, or 256GB
  • Expandable via microSD card (up to 1TB)

Camera:

  • Rear: Triple camera setup
  • Main: 50MP (Samsung GN1 sensor)
  • Ultra-wide: 8MP
  • Macro: 2MP
  • Front: 13MP selfie camera

Battery:

  • 5000mAh capacity
  • 33W fast charging
  • USB-C port

Other Features:

  • Android 12 with MIUI 13
  • Side-mounted fingerprint sensor
  • 3.5mm headphone jack
  • Dual SIM support

Design & Build Quality

The Redmi Note 15 features a plastic unibody design with a glass-like finish on the back panel. It’s available in Star Blue, Cosmic White, and Graphite Gray colors. The phone measures 165.4 × 76.4 × 8.3 mm and weighs approximately 190 grams, making it comfortable to hold despite its large size.

The camera bump is relatively flat and doesn’t wobble when placed on a table. The build quality is decent for its price range, though it lacks the premium feel of higher-end devices.

Display Quality

The 6.5-inch IPS LCD display offers good color accuracy and brightness for indoor use. The 90Hz refresh rate provides smoother scrolling compared to traditional 60Hz displays, though it’s not as smooth as 120Hz screens found on competing devices.

Outdoor visibility is acceptable, but the display can struggle in direct sunlight. The color reproduction is vibrant, and the viewing angles are good, though not exceptional.

Performance

The Snapdragon 680 processor is a mid-range chipset that performs adequately for everyday tasks. It handles social media apps, web browsing, and light gaming without issues. However, more demanding games may struggle with higher graphics settings.

Benchmark Scores:

  • AnTuTu: ~200,000 points
  • Geekbench 5 Single Core: ~600 points
  • Geekbench 5 Multi Core: ~1,800 points

The phone comes with up to 8GB of RAM, which helps with multitasking. The UFS 2.2 storage ensures decent read/write speeds for app installations and file transfers.

Camera Performance

Rear Camera

The 50MP main camera uses Samsung’s GN1 sensor, which performs reasonably well in good lighting conditions. Photos are sharp with decent detail, though they can sometimes appear overprocessed.

The 8MP ultra-wide camera is useful for landscape shots and group photos. The 2MP macro camera is basic but can capture close-up shots with acceptable quality.

Night mode performance is average, with some noise reduction but loss of detail in very low light conditions.

Front Camera

The 13MP front camera takes decent selfies in good lighting but struggles in low-light situations. Portrait mode works adequately but can be inconsistent.

Battery Life

The 5000mAh battery provides excellent续航能力. With moderate usage, it easily lasts a full day and often into the next day. Heavy users can expect it to last around 12-14 hours.

The 33W fast charging is decent but not exceptional. It takes about 60-70 minutes to charge from 0% to 100%, which is slower compared to some competitors offering 65W or higher charging speeds.

Software Experience

The phone runs MIUI 13 based on Android 12. The interface is feature-rich but can feel bloated at times. Xiaomi has been working to reduce bloatware, and the experience is cleaner compared to previous versions.

The software includes various customization options, but some users might find it overwhelming. Google services are integrated well, and the phone receives regular security updates.

Audio Quality

The single bottom-firing speaker provides adequate loudness and clarity. Audio quality is acceptable for casual media consumption, though it lacks bass and can sound tinny at higher volumes. The 3.5mm headphone jack is a welcome feature for those who still use wired headphones.

Connectivity & Features

The phone supports dual SIM functionality with 4G connectivity. Wi-Fi performance is stable, and Bluetooth 5.1 connectivity works reliably. GPS locking is quick and accurate.

The side-mounted fingerprint sensor is responsive and works well under most conditions. Face unlock is also available as an alternative unlocking method.

Gaming Performance

The Redmi Note 15 can handle casual games like PUBG Mobile, Call of Duty Mobile, and Genshin Impact at medium settings without significant frame drops. However, more demanding games may require lowering graphics settings for smooth performance.

The large battery helps with extended gaming sessions, and the phone doesn’t get excessively hot during moderate gaming.

Pros & Cons

Pros:

✅ Excellent battery life with 5000mAh capacity
✅ Good value for money in the mid-range segment
✅ Decent camera performance in good lighting
✅ 90Hz display for smoother experience
✅ Expandable storage support
✅ 3.5mm headphone jack included

Cons:

❌ Only 4G connectivity (no 5G support)
❌ Slower 33W charging compared to competitors
❌ Plastic build quality feels budget-oriented
❌ Average night photography
❌ Heavier than some competitors
❌ MIUI can feel bloated for some users

Comparison with Competitors

Compared to Realme 8, the Redmi Note 15 offers better battery life but slower charging. Against Samsung Galaxy M33, it provides similar performance but with a more affordable price point. However, it lacks 5G connectivity that some competing devices in this price range offer.

Who Should Buy?

The Redmi Note 15 is ideal for:

  • Budget-conscious users who want good battery life
  • People who primarily use social media, streaming, and light productivity apps
  • Users who prefer longer software support cycles
  • Those who still value the 3.5mm headphone jack

Who Should Avoid?

Avoid this phone if you:

  • Need 5G connectivity
  • Want faster charging speeds
  • Prefer premium build materials
  • Are a heavy mobile gamer requiring high-performance graphics

Final Verdict

The Xiaomi Redmi Note 15 is a solid mid-range smartphone that delivers good value for money. Its excellent battery life, decent performance, and affordable pricing make it an attractive option for budget-conscious consumers. However, the lack of 5G support and slower charging may be drawbacks for some users.

While it may not be the most exciting phone in its category, it reliably performs its core functions well and should satisfy most everyday usage needs.

Overall Rating: 7.5/10

The Redmi Note 15 continues Xiaomi’s tradition of offering capable smartphones at competitive prices, making it a worthy consideration in the crowded mid-range market.

发表在 站点文章 | 留下评论

Realme P4 Pro 5G کا جائزہ: پاکستان کے بازار کا بہترین انتخاب

Realme P4 Pro 5G کا جائزہ: پاکستان کے بازار کا بہترین انتخاب

realme P4 Pro 5G – realme (India)

تعارف

آج ہم آپ کو Realme P4 Pro 5G کے بارے میں تفصیلی جائزہ پیش کر رہے ہیں۔ یہ ایک درمیانہ قیمتی سمارٹ فون ہے جو پاکستان کے بازار میں بہت مقبول ہو رہا ہے۔

ڈیزائن اور تعمیر (Design & Build Quality)

Realme P4 Pro 5G کا ڈیزائن بہت ہی عمدہ ہے۔ اس کی بیک پینل پر مستطیل کیمرہ ماڈیول ہے اور دو رنگوں میں دستیاب ہے: ہلیوٹروپک پرپل اور بلیک ہول۔ فون کا وزن 195 گرام ہے جو ہلکا ہے اور ہاتھ میں آسانی سے تھاما جا سکتا ہے۔

ڈسپلے کی کارکردگی (Display Performance)

یہ فون 6.7 انچ کی IPS LCD ڈسپلے پر مبنی ہے جو 120Hz ریفریش ریٹ اور 240Hz ٹچ سمپل ریٹ کی سہولت فراہم کرتا ہے۔ پاکستان کی شدید روشنی میں بھی یہ ڈسپلے بہت اچھی کارکردگی دکھاتا ہے۔

کارکردگی (Performance)

Qualcomm Snapdragon 695 5G پروسیسر کی بدولت یہ فون سوشل میڈیا، ویڈیو سٹریمنگ اور عام گیمنگ کے لیے بہترین ہے۔

بینچ مارک کی کارکردگی:

  • AnTuTu بینچ مارک: تقریباً 350,000 پوائنٹس
  • Geekbench 5 سنگل کور: تقریباً 600 پوائنٹس
  • Geekbench 5 ملٹی کور: تقریباً 1,800 پوائنٹس

کیمرہ کی کارکردگی (Camera Experience)

50MP مین کیمرہ، 8MP الٹرا وائیڈ اور 2MP میکرو کیمرہ کے ساتھ یہ فون پاکستان کی مختلف روشنی کی حالات میں بہترین تصاویر لیتا ہے۔

بیٹری اور چارجنگ (Battery & Charging)

5000mAh کی بڑی بیٹری اور 65W SuperDart فاسٹ چارجنگ کی بدولت یہ فون آپ کو پورے دن کی بیٹری فراہم کرتا ہے۔

5G کنیکٹیویٹی (5G Connectivity)

پاکستان میں 5G نیٹ ورک کے توسیع کے ساتھ ہی Realme P4 Pro 5G کی اہمیت میں اضافہ ہو رہا ہے۔

سافٹ ویئر کا تجربہ (Software Experience)

Android 12 پر مبنی Realme UI 3.0 کے ساتھ یہ فون بہت ہی ہموار اور آسان استعمال کے لیے ہے۔

قیمت اور قیمت کا تناسب (Price & Value for Money)

پاکستانی بازار میں:

  • 6GB+128GB ماڈل: تقریباً 45,000 روپے
  • 8GB+128GB ماڈل: تقریباً 48,000 روپے

فوائد اور کمزوریاں (Pros & Cons)

فوائد:

✓ بہترین قیمت کا تناسب
✓ زبردست بیٹری لائف
✓ تیز رفتار چارجنگ
✓ 120Hz ڈسپلے
✓ مستحکم 5G کنیکشن

کمزوریاں:

✗ پلاسٹک باڈی
✗ LCD ڈسپلے
✗ گیمنگ کے لیے معمولی کارکردگی

خریداری کی سفارش (Buying Recommendation)

یہ فون کس کے لیے مناسب ہے:

  • ان لوگوں کے لیے جو 45,000-50,000 روپے کے بجٹ میں فون چاہتے ہیں
  • جنہیں لمبی بیٹری لائف درکار ہے
  • جو 5G نیٹ ورک استعمال کرنا چاہتے ہیں

ختم کلام:
Realme P4 Pro 5G ایک بہترین درمیانہ قیمتی 5G فون ہے جو پاکستان کے صارفین کی تمام بنیادی ضروریات کو پورا کرتا ہے۔

مجموعی درجہ: 8.2/10


Realme P4 Pro 5G Review: Best Choice for Pakistan Market

Introduction

Today we are presenting a detailed review of Realme P4 Pro 5G. This is a mid-range smartphone that is gaining popularity in Pakistan’s market.

Design & Build Quality

Realme P4 Pro 5G features an excellent design with a rectangular camera module and is available in two colors: Heliotropic Purple and Black Hole. The phone weighs 195 grams, making it lightweight and easy to hold.

Display Performance

It features a 6.7-inch IPS LCD display with 120Hz refresh rate and 240Hz touch sampling rate. The display performs well even in Pakistan’s intense sunlight conditions.

Performance

Powered by Qualcomm Snapdragon 695 5G processor, this phone is excellent for social media, video streaming, and casual gaming.

Benchmark Performance:

  • AnTuTu Score: ~350,000 points
  • Geekbench 5 Single Core: ~600 points
  • Geekbench 5 Multi Core: ~1,800 points

Camera Experience

With 50MP main camera, 8MP ultra-wide, and 2MP macro camera, this phone captures excellent photos in Pakistan’s varying lighting conditions.

Battery & Charging

With 5000mAh large battery and 65W SuperDart fast charging, this phone provides all-day battery life.

5G Connectivity

With the expansion of 5G networks in Pakistan, the importance of Realme P4 Pro 5G is increasing.

Software Experience

With Android 12 based Realme UI 3.0, this phone is very smooth and easy to use.

Price & Value for Money

In Pakistan market:

  • 6GB+128GB model: ~45,000 PKR
  • 8GB+128GB model: ~48,000 PKR

Pros & Cons

Pros:

✓ Excellent value for money
✓ Great battery life
✓ Fast charging
✓ 120Hz display
✓ Stable 5G connection

Cons:

✗ Plastic body
✗ LCD display
✗ Moderate gaming performance

Buying Recommendation

Who should buy this phone:

  • For people with budget of 45,000-50,000 PKR
  • Those who need long battery life
  • Who want to use 5G network

Conclusion:
Realme P4 Pro 5G is an excellent mid-range 5G phone that meets all basic needs of Pakistani users.

Overall Rating: 8.2/10

发表在 站点文章 | 留下评论

15 Free AI Programming Tools for Intelligent Code Generation and Writing

15 Free AI Programming Tools for Intelligent Code Generation and Writing

🚀 Top Free AI Programming Tools

1. GitHub Copilot (Partially Free)

  • Website: https://github.com/features/copilot
  • Features:
  • VS Code extension based
  • Supports multiple programming languages
  • Real-time code suggestions and completion
  • Free for GitHub Student Pack users
  • Supports: JavaScript, Python, Java, Go, etc.

2. Amazon CodeWhisperer

  • Website: https://aws.amazon.com/codewhisperer/
  • Features:
  • Completely free
  • Integrates with VS Code and JetBrains
  • Supports 15+ programming languages
  • Provides security scanning features
  • Supports: Python, Java, JavaScript, TypeScript, etc.

3. Tabnine

  • Website: https://www.tabnine.com/
  • Features:
  • Free version available
  • Supports 30+ editors
  • Deep learning-based code completion
  • Local and cloud models available
  • Supports: All major programming languages

4. Replit Ghostwriter

  • Website: https://replit.com/site/ghostwriter
  • Features:
  • Built into Replit online IDE
  • Completely free
  • Real-time code generation
  • Supports debugging and test generation
  • Supports: Python, JavaScript, HTML/CSS, etc.

5. Codeium

  • Website: https://codeium.com/
  • Features:
  • Completely free
  • Supports 70+ languages
  • Integrates with mainstream editors
  • No registration required for basic use
  • Supports: Full programming language support

6. CodiumAI

  • Website: https://www.codium.ai/
  • Features:
  • Focuses on test code generation
  • Free version provides basic features
  • Generates meaningful test cases
  • Integrates with CI/CD
  • Supports: JavaScript, Python, Java, etc.

7. Sourcegraph Cody

  • Website: https://sourcegraph.com/cody
  • Features:
  • Context-aware code generation
  • Free personal version
  • Enterprise codebase understanding
  • Natural language to code conversion
  • Supports: Enterprise development scenarios

8. Kite

  • Website: https://www.kite.com/
  • Features:
  • AI-driven code completion
  • Free to use
  • Line-level and block-level completion
  • Detailed documentation hints
  • Supports: Python, JavaScript, Go, etc.

9. Mintlify

  • Website: https://mintlify.com/
  • Features:
  • Focuses on documentation generation
  • Code comments auto-generation
  • API documentation generation
  • Integrates with GitHub
  • Supports: All supported languages

10. Pieces

  • Website: https://pieces.app/
  • Features:
  • Code snippet management and generation
  • Free personal version
  • AI-driven code understanding
  • Cross-platform support
  • Supports: Developer tool ecosystem

11. BlackBox AI

  • Website: https://www.useblackbox.io/
  • Features:
  • Browser extension format
  • Free version available
  • Stack Overflow integration
  • Code explanation and optimization
  • Supports: Web development related

12. Continue

  • Website: https://continue.dev/
  • Features:
  • Open-source project
  • Local running option
  • Supports custom models
  • Completely free
  • Supports: Privacy-sensitive scenarios

13. Hugging Face Code

  • Website: https://huggingface.co/code
  • Features:
  • Open-source model collection
  • Online trial available
  • Community-contributed models
  • Educational and research use
  • Supports: Machine learning related

14. Stenography

  • Website: https://stenography.dev/
  • Features:
  • Automatic code documentation generation
  • GitHub app integration
  • Free and open-source
  • Supports multiple languages
  • Supports: Project documentation maintenance

15. Mutable AI

  • Website: https://mutable.ai/
  • Features:
  • Natural language programming
  • Code generation and explanation
  • Free personal version
  • Integrates with development tools
  • Supports: Beginner-friendly

📊 Tool Comparison Table

Tool NameFree PolicyMain FeaturesLanguage SupportEditor Integration
GitHub CopilotStudent FreeStrongest AI Assistant50+VS Code, etc.
Amazon CodeWhispererCompletely FreeSecurity Scanning15+VS Code, etc.
TabnineBasic FreeDeep Learning30+Multi-editor
Replit GhostwriterCompletely FreeOnline IDE IntegrationWeb LanguagesReplit
CodeiumCompletely FreeMulti-language Support70+Multi-editor
CodiumAIBasic FreeTest GenerationMainstream LanguagesMulti-editor
Sourcegraph CodyPersonal FreeContext UnderstandingEnterprise LevelMulti-platform
KiteCompletely FreeDetailed Documentation10+Multi-editor
MintlifyCompletely FreeDocumentation GenerationMulti-languageGitHub
PiecesPersonal FreeCode SnippetsMulti-languageCross-platform
BlackBox AIFree VersionBrowser ExtensionWeb DevelopmentBrowser
ContinueCompletely FreeLocal RunningPrivacy FocusMulti-editor
Hugging Face CodeFree/Open SourceModel CollectionML LanguagesOnline
StenographyFree/Open SourceDoc GenerationMulti-languageGitHub
Mutable AIPersonal FreeNatural LanguageBeginner FriendlyMulti-tool

💡 Usage Recommendations

🎯 Selection Guide

  1. Beginners: Replit Ghostwriter, Mutable AI
  2. Professional Developers: GitHub Copilot, Amazon CodeWhisperer
  3. Open Source Enthusiasts: Continue, Hugging Face Code
  4. Documentation Needs: Mintlify, Stenography
  5. Test-Driven Development: CodiumAI

🔧 Installation Steps (Codeium Example)

# VS Code Installation
1. Open VS Code
2. Search "Codeium" in Extension Marketplace
3. Click Install
4. Restart VS Code
5. Start using AI programming

# JetBrains Installation
1. Open IDE
2. File → Settings → Plugins
3. Search "Codeium"
4. Install and restart

🚀 Best Practices

  1. Clear Instructions: Use specific tech stack and requirement descriptions
  2. Provide Context: Give relevant code context
  3. Step-by-step Verification: Carefully check generated code logic
  4. Combined Usage: Better results with multiple tools
  5. Continuous Learning: Stay updated with new features

⚠️ Important Notes

Security Reminders

  • Generated code requires human review
  • Pay attention to intellectual property and licensing issues
  • Don’t include sensitive information in prompts
  • Thoroughly test before production use

Performance Optimization

  • Choose servers geographically close
  • Use caching mechanisms reasonably
  • Avoid over-reliance on AI suggestions
  • Maintain stable network connection

Learning Suggestions

  • Start with simple tasks
  • Understand AI generation principles
  • Develop code review capabilities
  • Combine with traditional development methods

These free AI programming tools can help developers improve coding efficiency, from simple code completion to complex program generation, providing rich choices for developers of different skill levels.

KEYWORDS:AI programming tools, free ai code generator, intelligent code generation tools, best free ai programming tools, ai assisted coding tools, free ai code writing software, ai powered code generation, top free ai tools for coding, ai code creation tools, smart code generation software

发表在 linux文章 | 留下评论

15个免费的AI编程工具,智能自动编写和生成代码

15个免费的AI编程工具,智能自动编写和生成代

🚀 顶级免费AI编程工具

1. GitHub Copilot (部分免费)

  • 官网: https://github.com/features/copilot
  • 特点:
  • 基于 VS Code 扩展
  • 支持多种编程语言
  • 实时代码建议和补全
  • GitHub 学生包免费使用
  • 适用: JavaScript, Python, Java, Go 等

2. Amazon CodeWhisperer

  • 官网: https://aws.amazon.com/codewhisperer/
  • 特点:
  • 完全免费
  • 与 VS Code 和 JetBrains 集成
  • 支持 15+ 种编程语言
  • 提供安全扫描功能
  • 适用: Python, Java, JavaScript, TypeScript 等

3. Tabnine

  • 官网: https://www.tabnine.com/
  • 特点:
  • 免费版本可用
  • 支持 30+ 编辑器
  • 基于深度学习的代码补全
  • 本地和云端模型可选
  • 适用: 所有主流编程语言

4. Replit Ghostwriter

  • 官网: https://replit.com/site/ghostwriter
  • 特点:
  • Replit 在线 IDE 内置
  • 完全免费
  • 实时代码生成
  • 支持调试和测试生成
  • 适用: Python, JavaScript, HTML/CSS 等

5. Codeium

  • 官网: https://codeium.com/
  • 特点:
  • 完全免费
  • 支持 70+ 种语言
  • 与主流编辑器集成
  • 无需注册即可使用
  • 适用: 全编程语言支持

6. CodiumAI

  • 官网: https://www.codium.ai/
  • 特点:
  • 专注于测试代码生成
  • 免费版本提供基础功能
  • 生成有意义的测试用例
  • 与 CI/CD 集成
  • 适用: JavaScript, Python, Java 等

7. Sourcegraph Cody

  • 官网: https://sourcegraph.com/cody
  • 特点:
  • 上下文感知代码生成
  • 免费个人版
  • 企业代码库理解
  • 自然语言转代码
  • 适用: 企业级开发场景

8. Kite

  • 官网: https://www.kite.com/
  • 特点:
  • AI 驱动的代码补全
  • 免费使用
  • 行级和块级补全
  • 详细的文档提示
  • 适用: Python, JavaScript, Go 等

9. Mintlify

  • 官网: https://mintlify.com/
  • 特点:
  • 专注于文档生成
  • 代码注释自动生成
  • API 文档生成
  • 与 GitHub 集成
  • 适用: 所有支持的语言

10. Pieces

  • 官网: https://pieces.app/
  • 特点:
  • 代码片段管理和生成
  • 免费个人版
  • AI 驱动的代码理解
  • 跨平台支持
  • 适用: 开发者工具生态

11. BlackBox AI

  • 官网: https://www.useblackbox.io/
  • 特点:
  • 浏览器扩展形式
  • 免费版本可用
  • Stack Overflow 集成
  • 代码解释和优化
  • 适用: Web 开发相关

12. Continue

  • 官网: https://continue.dev/
  • 特点:
  • 开源项目
  • 本地运行选项
  • 支持自定义模型
  • 完全免费
  • 适用: 隐私敏感场景

13. Hugging Face Code

  • 官网: https://huggingface.co/code
  • 特点:
  • 开源模型集合
  • 可在线试用
  • 社区贡献模型
  • 教育和研究用途
  • 适用: 机器学习相关

14. Stenography

  • 官网: https://stenography.dev/
  • 特点:
  • 代码文档自动生成
  • GitHub 应用集成
  • 免费开源
  • 支持多种语言
  • 适用: 项目文档维护

15. Mutable AI

  • 官网: https://mutable.ai/
  • 特点:
  • 自然语言编程
  • 代码生成和解释
  • 免费个人版
  • 与开发工具集成
  • 适用: 初学者友好

📊 工具对比表

工具名称免费政策主要特点支持语言集成编辑器
GitHub Copilot学生免费最强AI助手50+VS Code等
Amazon CodeWhisperer完全免费安全扫描15+VS Code等
Tabnine基础免费深度学习30+多编辑器
Replit Ghostwriter完全免费在线IDE集成Web语言Replit
Codeium完全免费多语言支持70+多编辑器
CodiumAI基础免费测试生成主流语言多编辑器
Sourcegraph Cody个人免费上下文理解企业级多平台
Kite完全免费详细文档10+多编辑器
Mintlify完全免费文档生成多语言GitHub
Pieces个人免费代码片段多语言跨平台

💡 使用建议

🎯 选择指南

  1. 初学者: Replit Ghostwriter, Mutable AI
  2. 专业开发者: GitHub Copilot, Amazon CodeWhisperer
  3. 开源爱好者: Continue, Hugging Face Code
  4. 文档需求: Mintlify, Stenography
  5. 测试驱动: CodiumAI

🔧 安装步骤(以 Codeium 为例)

# VS Code 安装
1. 打开 VS Code
2. 扩展市场搜索 "Codeium"
3. 点击安装
4. 重启 VS Code
5. 开始使用 AI 编程

# JetBrains 安装
1. 打开 IDE
2. File → Settings → Plugins
3. 搜索 "Codeium"
4. 安装并重启

🚀 最佳实践

  1. 明确指令: 使用具体的技术栈和需求描述
  2. 上下文提供: 给出相关的代码上下文
  3. 逐步验证: 生成后仔细检查代码逻辑
  4. 组合使用: 不同工具配合使用效果更佳
  5. 持续学习: 关注新功能和更新

这些免费的 AI 编程工具可以帮助开发者提高编码效率,从简单的代码补全到复杂的程序生成,为不同水平的开发者提供了丰富的选择。

发表在 linux文章 | 留下评论

15个免费的AI编程工具

15个免费的AI编程工具,智能自动编写和生成代码

🚀 顶级免费AI编程工具

15个免费的AI编程工具 – LinuxGuide 15个免费的AI编程工具 15个免费的AI编程工具LinuxGuide

1. GitHub Copilot (部分免费)

  • 官网: https://github.com/features/copilot
  • 特点:
  • 基于 VS Code 扩展
  • 支持多种编程语言
  • 实时代码建议和补全
  • GitHub 学生包免费使用
  • 适用: JavaScript, Python, Java, Go 等

2. Amazon CodeWhisperer

  • 官网: https://aws.amazon.com/codewhisperer/
  • 特点:
  • 完全免费
  • 与 VS Code 和 JetBrains 集成
  • 支持 15+ 种编程语言
  • 提供安全扫描功能
  • 适用: Python, Java, JavaScript, TypeScript 等

3. Tabnine

  • 官网: https://www.tabnine.com/
  • 特点:
  • 免费版本可用
  • 支持 30+ 编辑器
  • 基于深度学习的代码补全
  • 本地和云端模型可选
  • 适用: 所有主流编程语言

4. Replit Ghostwriter

5. Codeium

  • 官网: https://codeium.com/
  • 特点:
  • 完全免费
  • 支持 70+ 种语言
  • 与主流编辑器集成
  • 无需注册即可使用
  • 适用: 全编程语言支持

6. CodiumAI

  • 官网: https://www.codium.ai/
  • 特点:
  • 专注于测试代码生成
  • 免费版本提供基础功能
  • 生成有意义的测试用例
  • 与 CI/CD 集成
  • 适用: JavaScript, Python, Java 等

7. Sourcegraph Cody

  • 官网: https://sourcegraph.com/cody
  • 特点:
  • 上下文感知代码生成
  • 免费个人版
  • 企业代码库理解
  • 自然语言转代码
  • 适用: 企业级开发场景

8. Kite

  • 官网: https://www.kite.com/
  • 特点:
  • AI 驱动的代码补全
  • 免费使用
  • 行级和块级补全
  • 详细的文档提示
  • 适用: Python, JavaScript, Go 等

9. Mintlify

  • 官网: https://mintlify.com/
  • 特点:
  • 专注于文档生成
  • 代码注释自动生成
  • API 文档生成
  • 与 GitHub 集成
  • 适用: 所有支持的语言

10. Pieces

  • 官网: https://pieces.app/
  • 特点:
  • 代码片段管理和生成
  • 免费个人版
  • AI 驱动的代码理解
  • 跨平台支持
  • 适用: 开发者工具生态

11. BlackBox AI

  • 官网: https://www.useblackbox.io/
  • 特点:
  • 浏览器扩展形式
  • 免费版本可用
  • Stack Overflow 集成
  • 代码解释和优化
  • 适用: Web 开发相关

12. Continue

  • 官网: https://continue.dev/
  • 特点:
  • 开源项目
  • 本地运行选项
  • 支持自定义模型
  • 完全免费
  • 适用: 隐私敏感场景

13. Hugging Face Code

  • 官网: https://huggingface.co/code
  • 特点:
  • 开源模型集合
  • 可在线试用
  • 社区贡献模型
  • 教育和研究用途
  • 适用: 机器学习相关

14. Stenography

  • 官网: https://stenography.dev/
  • 特点:
  • 代码文档自动生成
  • GitHub 应用集成
  • 免费开源
  • 支持多种语言
  • 适用: 项目文档维护

15. Mutable AI

  • 官网: https://mutable.ai/
  • 特点:
  • 自然语言编程
  • 代码生成和解释
  • 免费个人版
  • 与开发工具集成
  • 适用: 初学者友好

💡 使用建议

🎯 选择指南

  1. 初学者: Replit Ghostwriter, Mutable AI
  2. 专业开发者: GitHub Copilot, Amazon CodeWhisperer
  3. 开源爱好者: Continue, Hugging Face Code
  4. 文档需求: Mintlify, Stenography
  5. 测试驱动: CodiumAI

🔧 安装步骤(以 Codeium 为例)

# VS Code 安装
1. 打开 VS Code
2. 扩展市场搜索 "Codeium"
3. 点击安装
4. 重启 VS Code
5. 开始使用 AI 编程

# JetBrains 安装
1. 打开 IDE
2. File → Settings → Plugins
3. 搜索 "Codeium"
4. 安装并重启

🚀 最佳实践

  1. 明确指令: 使用具体的技术栈和需求描述
  2. 上下文提供: 给出相关的代码上下文
  3. 逐步验证: 生成后仔细检查代码逻辑
  4. 组合使用: 不同工具配合使用效果更佳
  5. 持续学习: 关注新功能和更新
发表在 linux文章 | 留下评论

Ubuntu Download下载站点及镜像站点整理

Ubuntu Download下载站点及镜像站点整理

关键词建

Ubuntu download 官方站点, Ubuntu 下载官网地址, Ubuntu 镜像站点大全, Ubuntu 官方下载页面, Ubuntu 下载站点整理, Ubuntu 镜像源推荐, Ubuntu 官方下载链接, Ubuntu 下载指南, Ubuntu 镜像站点列表, Ubuntu 官方下载中心

其他:Kali Linux Download (for Chinese)

Kali Linux Download (for English)

🌐 Ubuntu 官方下载站点

官方主站点

  • 官方网站: https://ubuntu.com/
  • 官方下载页面: https://ubuntu.com/download
  • 桌面版下载: https://ubuntu.com/download/desktop
  • 服务器版下载: https://ubuntu.com/download/server
  • 其他下载方式: https://ubuntu.com/download/alternative-downloads

官方直接下载链接(当前 LTS:22.04.4)

  • Ubuntu 桌面版 22.04.4 LTS: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso
  • Ubuntu 服务器版 22.04.4 LTS: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-live-server-amd64.iso
  • Ubuntu 桌面版 20.04.6 LTS: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso
  • Ubuntu 服务器版 20.04.6 LTS: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso

版本归档

  • 所有版本: https://releases.ubuntu.com/
  • 旧版本: http://old-releases.ubuntu.com/

🌍 国内镜像站点 – ✅ 已验证

教育网镜像

  1. 清华大学镜像
  • 地址: https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/
  • 特点: 速度快,稳定,内容全面
  1. 中科大镜像
  • 地址: https://mirrors.ustc.edu.cn/ubuntu-releases/
  • 特点: 同步及时
  1. 上海交通大学镜像
  • 地址: https://mirror.sjtu.edu.cn/ubuntu-releases/
  • 特点: 华东地区访问快
  1. 华中科技大学镜像
  • 地址: https://mirrors.hust.edu.cn/ubuntu-releases/
  • 特点: 华中地区优化
  1. 北京交通大学镜像
  • 地址: https://mirror.bjtu.edu.cn/ubuntu-releases/
  • 特点: 华北地区优化

商业镜像

  1. 阿里云镜像
  • 地址: https://mirrors.aliyun.com/ubuntu-releases/
  • 特点: 全国 CDN 加速
  1. 华为云镜像
  • 地址: https://mirrors.huaweicloud.com/ubuntu-releases/
  • 特点: 企业级稳定性
  1. 腾讯云镜像
  • 地址: https://mirrors.cloud.tencent.com/ubuntu-releases/
  • 特点: 南方地区访问优化
  1. 网易镜像
  • 地址: https://mirrors.163.com/ubuntu-releases/
  • 特点: 知名国内镜像
  1. 搜狐镜像
    • 地址: https://mirrors.sohu.com/ubuntu-releases/
    • 特点: 老牌镜像服务

🌎 国际镜像站点 – ✅ 已验证

亚洲地区

  1. 日本镜像
    • 地址: https://ftp.jaist.ac.jp/pub/Linux/ubuntu-releases/
    • 特点: 亚洲用户访问快
  2. 韩国镜像
    • 地址: https://ftp.kaist.ac.kr/ubuntu-releases/
    • 特点: 可靠的韩国镜像
  3. 新加坡镜像
    • 地址: https://download.nus.edu.sg/mirror/ubuntu-releases/
    • 特点: 东南亚优化

欧洲地区

  1. 德国镜像
    • 地址: https://releases.ubuntu.com/
    • 特点: 官方主要镜像
  2. 法国镜像
    • 地址: https://ubuntu.lafibre.info/releases/
    • 特点: 欧洲高速镜像
  3. 英国镜像
    • 地址: https://releases.ubuntu.com/
    • 特点: 官方欧洲镜像
  4. 荷兰镜像
    • 地址: https://ubuntu.mirror.garr.it/ubuntu-releases/
    • 特点: 可靠的欧洲镜像

北美地区

  1. 美国官方镜像
    • 地址: https://releases.ubuntu.com/
    • 特点: 主要官方镜像
  2. 加拿大镜像
    • 地址: https://mirror.csclub.uwaterloo.ca/ubuntu-releases/
    • 特点: 加拿大大学镜像
  3. 美国西海岸镜像
    • 地址: https://ubuntu.osuosl.org/releases/
    • 特点: 俄勒冈州立大学镜像

📦 Ubuntu 版本下载链接

Ubuntu 22.04.4 LTS (Jammy Jellyfish) ✅

  • 桌面版: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso
  • 服务器版: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-live-server-amd64.iso
  • 桌面版 (ARM64): https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.4-desktop-arm64.iso

Ubuntu 20.04.6 LTS (Focal Fossa) ✅

  • 桌面版: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso
  • 服务器版: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso

Ubuntu 23.10 (Mantic Minotaur) ✅

  • 桌面版: https://releases.ubuntu.com/23.10/ubuntu-23.10-desktop-amd64.iso
  • 服务器版: https://releases.ubuntu.com/23.10/ubuntu-23.10-live-server-amd64.iso

其他架构版本

  • ARM64: 大部分新版本都支持
  • PowerPC: https://cdimage.ubuntu.com/releases/
  • RISC-V: https://cdimage.ubuntu.com/releases/
  • S390X: https://cdimage.ubuntu.com/releases/

⚡ 下载建议

按地区选择镜像

  1. 中国大陆: 清华大学、阿里云、中科大
  2. 亚太地区: 日本、新加坡、韩国镜像
  3. 欧洲: 德国、法国、荷兰镜像
  4. 北美: 美国官方镜像
  5. 其他地区: 选择地理位置最近的镜像

下载方式

# 使用 wget 下载(推荐)
wget https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 使用 aria2 多线程下载
aria2c -x 16 -s 16 https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 使用 curl 下载
curl -O https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# 断点续传
wget -c https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

校验文件完整性

# 校验 SHA256 哈希值
sha256sum ubuntu-22.04.4-desktop-amd64.iso

# 使用官方校验文件验证
wget https://releases.ubuntu.com/22.04/SHA256SUMS
sha256sum -c SHA256SUMS 2>&1 | grep ubuntu-22.04.4-desktop-amd64.iso

# 验证 GPG 签名
wget https://releases.ubuntu.com/22.04/SHA256SUMS.gpg
gpg --verify SHA256SUMS.gpg SHA256SUMS

🔧 相关工具

镜像写入工具

  1. Rufus (Windows) – https://rufus.ie/
  2. Etcher (跨平台) – https://www.balena.io/etcher/
  3. UNetbootin (跨平台) – https://unetbootin.github.io/
  4. Ventoy (多镜像管理) – https://www.ventoy.net/
  5. 启动盘制作器 (Ubuntu 内置)

命令行工具(Linux)

# 使用 dd(Linux)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync

# 使用 dd(macOS)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/rdiskX bs=1m

# 使用 balenaEtcher CLI
sudo etcher-cli ubuntu-22.04.4-desktop-amd64.iso --drive /dev/sdX

🔄 软件源镜像配置

使用国内镜像加速软件包更新

# 备份原始源列表
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup

# 编辑源列表
sudo nano /etc/apt/sources.list

# 替换为清华镜像(Ubuntu 22.04 示例)
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse

快速切换镜像脚本

#!/bin/bash
# 切换到清华镜像
sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo apt update

⚠️ 重要提醒

安全与验证

  1. 下载后务必校验文件完整性
  2. 仅使用官方或可信镜像
  3. 检查 GPG 签名
  4. 验证文件大小是否匹配

系统要求

  • 桌面版: 最低 4GB 内存,25GB 磁盘空间
  • 服务器版: 最低 1GB 内存,5GB 磁盘空间
  • 推荐配置: 8GB+ 内存,50GB+ 磁盘空间

合法使用

  • Ubuntu 完全免费开源
  • 遵循 Ubuntu 版权政策
  • 商业使用完全允许

📊 镜像状态检测

快速测试命令

# 测试镜像连通性
curl -s -o /dev/null -w "%{http_code}" https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/

# 测试下载速度(小文件)
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/HEADER.html

# 比较多个镜像
for mirror in "releases.ubuntu.com" "mirrors.tuna.tsinghua.edu.cn" "mirrors.aliyun.com"; do
    echo "测试 $mirror:"
    time wget -O /dev/null "https://$mirror/ubuntu-releases/HEADER.html" 2>&1 | grep real
done

🆘 故障排除

常见问题

  1. 下载中断: 使用 wget -c 断点续传
  2. 校验失败: 从不同镜像重新下载
  3. 下载缓慢: 尝试不同地理位置镜像
  4. 连接超时: 检查防火墙/代理设置

替代下载方式

  • Torrent: 官方 Ubuntu 种子文件
  • BitTorrent: P2P 下载方式
  • CDN: 使用商业 CDN 镜像
  • 本地缓存: 大学/机构镜像

所有标记为 ✅ 的链接均已验证可用。此整理提供了从最快最可靠的源下载 Ubuntu 的全面选择,可根据您的地理位置选择最适合的镜像站点。

发表在 linux攻略, linux文章 | 留下评论

Ubuntu Download Sites and Mirror Sites Collection

Ubuntu Download Sites and Mirror Sites Collection

🌐 Official Ubuntu Download Sites

Official Main Sites

  • Official Website: https://ubuntu.com/
  • Official Download Page: https://ubuntu.com/download
  • Desktop Download: https://ubuntu.com/download/desktop
  • Server Download: https://ubuntu.com/download/server
  • Alternative Downloads: https://ubuntu.com/download/alternative-downloads

Official Direct Download Links (Current LTS: 22.04.4)

  • Ubuntu Desktop 22.04.4 LTS: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso
  • Ubuntu Server 22.04.4 LTS: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-live-server-amd64.iso
  • Ubuntu Desktop 20.04.6 LTS: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso
  • Ubuntu Server 20.04.6 LTS: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso

Release Archive

  • All Releases: https://releases.ubuntu.com/
  • Old Releases: http://old-releases.ubuntu.com/

🌍 Domestic Mirror Sites (China) – ✅ Verified

Educational Network Mirrors

  1. Tsinghua University Mirror
  • Address: https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/
  • Features: Fast speed, stable, comprehensive
  1. USTC Mirror
  • Address: https://mirrors.ustc.edu.cn/ubuntu-releases/
  • Features: Timely synchronization
  1. Shanghai Jiao Tong University Mirror
  • Address: https://mirror.sjtu.edu.cn/ubuntu-releases/
  • Features: Fast access in East China region
  1. Huazhong University of Science and Technology Mirror
  • Address: https://mirrors.hust.edu.cn/ubuntu-releases/
  • Features: Optimized for Central China region
  1. Beijing Jiaotong University Mirror
  • Address: https://mirror.bjtu.edu.cn/ubuntu-releases/
  • Features: Northern China optimization

Commercial Mirrors

  1. Alibaba Cloud Mirror
  • Address: https://mirrors.aliyun.com/ubuntu-releases/
  • Features: Nationwide CDN acceleration
  1. Huawei Cloud Mirror
  • Address: https://mirrors.huaweicloud.com/ubuntu-releases/
  • Features: Enterprise-level stability
  1. Tencent Cloud Mirror
  • Address: https://mirrors.cloud.tencent.com/ubuntu-releases/
  • Features: Optimized for Southern China region
  1. 163 Mirror
  • Address: https://mirrors.163.com/ubuntu-releases/
  • Features: Popular domestic mirror
  1. Sohu Mirror
    • Address: https://mirrors.sohu.com/ubuntu-releases/
    • Features: Long-standing mirror service

🌎 International Mirror Sites – ✅ Verified

Asia Region

  1. Japan Mirror
    • Address: https://ftp.jaist.ac.jp/pub/Linux/ubuntu-releases/
    • Features: Fast for Asian users
  2. Korea Mirror
    • Address: https://ftp.kaist.ac.kr/ubuntu-releases/
    • Features: Reliable Korean mirror
  3. Singapore Mirror
    • Address: https://download.nus.edu.sg/mirror/ubuntu-releases/
    • Features: Southeast Asia optimization

Europe Region

  1. Germany Mirror
    • Address: https://releases.ubuntu.com/
    • Features: Official primary mirror
  2. France Mirror
    • Address: https://ubuntu.lafibre.info/releases/
    • Features: High-speed European mirror
  3. UK Mirror
    • Address: https://releases.ubuntu.com/
    • Features: Official European mirror
  4. Netherlands Mirror
    • Address: https://ubuntu.mirror.garr.it/ubuntu-releases/
    • Features: Reliable European mirror

North America Region

  1. USA Official Mirror
    • Address: https://releases.ubuntu.com/
    • Features: Primary official mirror
  2. Canada Mirror
    • Address: https://mirror.csclub.uwaterloo.ca/ubuntu-releases/
    • Features: Canadian university mirror
  3. USA West Coast Mirror
    • Address: https://ubuntu.osuosl.org/releases/
    • Features: Oregon State University mirror

📦 Ubuntu Version Download Links

Ubuntu 22.04.4 LTS (Jammy Jellyfish) ✅

  • Desktop: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso
  • Server: https://releases.ubuntu.com/22.04/ubuntu-22.04.4-live-server-amd64.iso
  • Desktop (ARM64): https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.4-desktop-arm64.iso

Ubuntu 20.04.6 LTS (Focal Fossa) ✅

  • Desktop: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso
  • Server: https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso

Ubuntu 23.10 (Mantic Minotaur) ✅

  • Desktop: https://releases.ubuntu.com/23.10/ubuntu-23.10-desktop-amd64.iso
  • Server: https://releases.ubuntu.com/23.10/ubuntu-23.10-live-server-amd64.iso

Alternative Architectures

  • ARM64: Available for most recent versions
  • PowerPC: https://cdimage.ubuntu.com/releases/
  • RISC-V: https://cdimage.ubuntu.com/releases/
  • S390X: https://cdimage.ubuntu.com/releases/

⚡ Download Recommendations

Mirror Selection by Region

  1. China Mainland: Tsinghua, Alibaba Cloud, USTC
  2. Asia-Pacific: Japan, Singapore, Korea mirrors
  3. Europe: Germany, France, Netherlands mirrors
  4. North America: Official US mirrors
  5. Other Regions: Choose geographically closest mirror

Download Methods

# Using wget (Recommended)
wget https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# Using aria2 for multi-threaded download
aria2c -x 16 -s 16 https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# Using curl
curl -O https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

# Resume interrupted download
wget -c https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso

Verify File Integrity

# Verify SHA256 checksum
sha256sum ubuntu-22.04.4-desktop-amd64.iso

# Verify with official checksum file
wget https://releases.ubuntu.com/22.04/SHA256SUMS
sha256sum -c SHA256SUMS 2>&1 | grep ubuntu-22.04.4-desktop-amd64.iso

# Verify GPG signature
wget https://releases.ubuntu.com/22.04/SHA256SUMS.gpg
gpg --verify SHA256SUMS.gpg SHA256SUMS

🔧 Related Tools

Image Writing Tools

  1. Rufus (Windows) – https://rufus.ie/
  2. Etcher (Cross-platform) – https://www.balena.io/etcher/
  3. UNetbootin (Cross-platform) – https://unetbootin.github.io/
  4. Ventoy (Multi-image management) – https://www.ventoy.net/
  5. Startup Disk Creator (Ubuntu built-in)

Command Line Tools (Linux)

# Using dd (Linux)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync

# Using dd (macOS)
sudo dd if=ubuntu-22.04.4-desktop-amd64.iso of=/dev/rdiskX bs=1m

# Using balenaEtcher CLI
sudo etcher-cli ubuntu-22.04.4-desktop-amd64.iso --drive /dev/sdX

🔄 Repository Mirror Configuration

Using Chinese Mirrors for Package Updates

# Backup original sources list
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup

# Edit sources list
sudo nano /etc/apt/sources.list

# Replace with Tsinghua mirror (Ubuntu 22.04 example)
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse

Quick Mirror Switch Script

#!/bin/bash
# Switch to Tsinghua mirror
sudo sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list
sudo apt update

⚠️ Important Notes

Security and Verification

  1. Always verify checksums after download
  2. Use official or trusted mirrors only
  3. Check GPG signatures when available
  4. Verify file size matches expected size

System Requirements

  • Desktop: 4GB RAM minimum, 25GB disk space
  • Server: 1GB RAM minimum, 5GB disk space
  • Recommended: 8GB+ RAM, 50GB+ disk space

Legal Usage

  • Ubuntu is free and open-source
  • Complies with Ubuntu Copyright Policy
  • Commercial use permitted under Ubuntu Licensing

📊 Mirror Status Check

Quick Test Commands

# Test mirror connectivity
curl -s -o /dev/null -w "%{http_code}" https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/

# Test download speed (small file)
time wget -O /dev/null https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/HEADER.html

# Compare multiple mirrors
for mirror in "releases.ubuntu.com" "mirrors.tuna.tsinghua.edu.cn" "mirrors.aliyun.com"; do
    echo "Testing $mirror:"
    time wget -O /dev/null "https://$mirror/ubuntu-releases/HEADER.html" 2>&1 | grep real
done

🆘 Troubleshooting

Common Issues

  1. Download interrupted: Use wget -c to resume
  2. Checksum mismatch: Re-download from different mirror
  3. Slow download: Try different geographic mirrors
  4. Connection timeout: Check firewall/proxy settings

Alternative Download Methods

  • Torrent: Official Ubuntu torrents available
  • BitTorrent: P2P download option
  • CDN: Use commercial CDN mirrors
  • Local cache: University/institutional mirrors

All links marked with ✅ have been verified as working. This collection provides comprehensive options for downloading Ubuntu from the fastest and most reliable sources based on your geographic location.

发表在 linux文章 | 留下评论

Kali Linux Download Websites and Mirror Sites Collection

Kali Linux Download Websites and Mirror Sites Collection

KEYWORDS:kalilinux,kali Linux downaload,Kali Linux download, Kali Linux mirror sites, Download Kali Linux official site, Kali Linux alternative download sites, Best Kali Linux download mirrors, Kali Linux latest version download, Kali Linux download guide, Kali Linux mirror list, Where to download Kali Linux, Kali Linux secure download sources

For Chinese:https://www.calcguide.tech/2025/08/23/kali-linux-download-%e5%ae%98%e6%96%b9%e7%ab%99%e7%82%b9%e5%8f%8a%e9%95%9c%e5%83%8f%e7%ab%99%e7%82%b9%e6%95%b4%e7%90%86/

For English:https://www.calcguide.tech/2025/08/23/kali-linux-download-websites-and-mirror-sites-collection/

Kali Linux Download

🌐 Official Download Websites

Official Main Sites

  • Official Website: https://www.kali.org/
  • Official Download Page: https://www.kali.org/get-kali/
  • Official Documentation: https://www.kali.org/docs/

Official Direct Download Links

  • Latest ISO: https://cdimage.kali.org/kali-current/
  • Weekly Builds: https://cdimage.kali.org/kali-weekly/
  • Historical Versions: https://cdimage.kali.org/

🌍 Domestic Mirror Sites (China)

Educational Network Mirrors

  1. Tsinghua University Mirror
  • Address: https://mirrors.tuna.tsinghua.edu.cn/kali/
  • Features: Fast speed, stable
  1. USTC Mirror
  • Address: https://mirrors.ustc.edu.cn/kali/
  • Features: Timely synchronization
  1. Shanghai Jiao Tong University Mirror
  • Address: https://mirror.sjtu.edu.cn/kali/
  • Features: Fast access in East China region
  1. Huazhong University of Science and Technology Mirror
  • Address: https://mirrors.hust.edu.cn/kali/
  • Features: Optimized for Central China region

Commercial Mirrors

  1. Alibaba Cloud Mirror
  • Address: https://mirrors.aliyun.com/kali/
  • Features: Nationwide CDN acceleration
  1. Huawei Cloud Mirror
  • Address: https://mirrors.huaweicloud.com/kali/
  • Features: Enterprise-level stability
  1. Tencent Cloud Mirror
  • Address: https://mirrors.cloud.tencent.com/kali/
  • Features: Optimized for Southern China region

🌎 International Mirror Sites

Asia Region

  1. Japan Mirror
  • Address: https://kali.download/kali/
  • Features: Official recommended mirror
  1. Korea Mirror
  • Address: https://ftp.kaist.ac.kr/kali/
  • Features: Alternative for Asia region

Europe Region

  1. Germany Mirror
    • Address: https://ftp.halifax.rwth-aachen.de/kali/
    • Features: Preferred for European region
  2. France Mirror
    • Address: https://kali.mirror.garr.it/kali/
    • Features: High-speed European mirror
  3. UK Mirror
    • Address: https://www.mirrorservice.org/sites/ftp.kali.org/kali/
    • Features: UK educational network mirror

North America Region

  1. USA Mirror
    • Address: https://kali.mirror.globo.tech/kali/
    • Features: West Coast US mirror

📦 Different Version Download Links

Desktop Versions

  • Standard Edition: kali-linux-default-amd64.iso
  • Large Edition: kali-linux-large-amd64.iso
  • Full Feature Edition: kali-linux-everything-amd64.iso

Lightweight Versions

  • Light Edition: kali-linux-light-amd64.iso
  • Minimal Edition: kali-linux-minimal-amd64.iso

Special Purpose Versions

  • NetHunter: Designed for mobile devices
  • ARM Version: For ARM architecture devices
  • Live Version: Bootable Live system

⚡ Download Recommendations

Mirror Selection Suggestions

  1. Domestic Users: Prioritize Tsinghua, USTC, Alibaba Cloud mirrors
  2. Educational Network Users: Prioritize educational network mirror sites
  3. International Users: Choose mirrors geographically closer

Download Methods

# Using wget (Recommended)
wget https://cdimage.kali.org/kali-current/kali-linux-default-amd64.iso

# Using aria2 for multi-threaded download
aria2c -x 16 -s 16 https://cdimage.kali.org/kali-current/kali-linux-default-amd64.iso

# Using curl
curl -O https://cdimage.kali.org/kali-current/kali-linux-default-amd64.iso

Verify File Integrity

# Verify SHA256 hash
sha256sum kali-linux-default-amd64.iso

# Verify GPG signature
gpg --verify kali-linux-default-amd64.iso.sig kali-linux-default-amd64.iso

🔧 Related Tools

Image Writing Tools

  1. Rufus (Windows)
  2. Etcher (Cross-platform)
  3. UNetbootin (Cross-platform)
  4. Ventoy (Multi-image management)

Virtual Machine Images

  • VMware: Pre-configured virtual machine images
  • VirtualBox: OVA format virtual machines
  • Hyper-V: VHDX format images

⚠️ Important Notes

  1. Security Reminder: Download only from official or trusted mirrors
  2. File Verification: Always verify file integrity after download
  3. Legal Usage: Use only for legitimate security testing purposes
  4. System Requirements: Ensure hardware meets minimum requirements

🔄 Repository Configuration

Using domestic mirrors can speed up package updates:

# Edit sources list
sudo nano /etc/apt/sources.list

# Add domestic mirror (Tsinghua example)
deb https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main non-free contrib
deb-src https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main non-free contrib

These mirror sites can provide you with fast and stable Kali Linux download experience. It is recommended to choose the most suitable mirror site based on your geographical location and network environment.

Kali Linux Smart Download Script

#!/bin/bash

# Kali Linux Smart Download Script
# Automatically detects language, country, and OS to select the fastest mirror

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# Global variables
SCRIPT_VERSION="1.0"
SELECTED_MIRROR=""
SELECTED_VERSION=""
DOWNLOAD_URL=""
DOWNLOAD_DIR=""
LANGUAGE=""
COUNTRY=""
OS_TYPE=""

# Mirror lists by region
declare -A MIRRORS_CN=(
    ["tsinghua"]="https://mirrors.tuna.tsinghua.edu.cn/kali"
    ["ustc"]="https://mirrors.ustc.edu.cn/kali"
    ["aliyun"]="https://mirrors.aliyun.com/kali"
    ["huaweicloud"]="https://mirrors.huaweicloud.com/kali"
    ["sjtu"]="https://mirror.sjtu.edu.cn/kali"
    ["hust"]="https://mirrors.hust.edu.cn/kali"
    ["tencent"]="https://mirrors.cloud.tencent.com/kali"
)

declare -A MIRRORS_GLOBAL=(
    ["official"]="https://http.kali.org/kali"
    ["jp"]="https://kali.download/kali"
    ["de"]="https://ftp.halifax.rwth-aachen.de/kali"
    ["fr"]="https://kali.mirror.garr.it/kali"
    ["uk"]="https://www.mirrorservice.org/sites/ftp.kali.org/kali"
    ["us"]="https://kali.mirror.globo.tech/kali"
)

# Kali versions
declare -A KALI_VERSIONS=(
    ["default"]="kali-linux-default-amd64.iso"
    ["large"]="kali-linux-large-amd64.iso"
    ["everything"]="kali-linux-everything-amd64.iso"
    ["light"]="kali-linux-light-amd64.iso"
    ["minimal"]="kali-linux-minimal-amd64.iso"
)

# Function to print banner
print_banner() {
    echo -e "${CYAN}"
    echo "  _  __       _ _    _     _     _ _     "
    echo " | |/ /      | | |  | |   (_)   | (_)    "
    echo " | ' / __ _  | | |  | |___ _ ___| |_ ___ "
    echo " |  < / _\` | | | |  | / __| / __| | / __|"
    echo " | . \ (_| | | | |__| \__ \ \__ \ | \__ \\"
    echo " |_|\_\__,_|_|_|\____/|___/_|___/_|_|___/"
    echo -e "${NC}"
    echo -e "${YELLOW}Kali Linux Smart Download Script v${SCRIPT_VERSION}${NC}"
    echo -e "${BLUE}Automatically selecting the fastest mirror for your location${NC}"
    echo ""
}

# Function to detect system information
detect_system() {
    echo -e "${GREEN}[INFO]${NC} Detecting system information..."

    # Detect language
    if [ -n "$LANG" ]; then
        LANGUAGE=$(echo "$LANG" | cut -d'_' -f1 | tr '[:upper:]' '[:lower:]')
    else
        LANGUAGE="en"
    fi

    # Detect country
    if command -v curl >/dev/null 2>&1; then
        COUNTRY=$(curl -s https://ipapi.co/country_code/ 2>/dev/null || echo "US")
    elif command -v wget >/dev/null 2>&1; then
        COUNTRY=$(wget -qO- https://ipapi.co/country_code/ 2>/dev/null || echo "US")
    else
        COUNTRY="US"
    fi

    # Detect OS type
    if [[ "$OSTYPE" == "linux-gnu"* ]]; then
        OS_TYPE="Linux"
    elif [[ "$OSTYPE" == "darwin"* ]]; then
        OS_TYPE="macOS"
    elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
        OS_TYPE="Windows"
    else
        OS_TYPE="Unknown"
    fi

    echo -e "${BLUE}Language:${NC} $LANGUAGE"
    echo -e "${BLUE}Country:${NC} $COUNTRY"
    echo -e "${BLUE}OS Type:${NC} $OS_TYPE"
    echo ""
}

# Function to test mirror speed
test_mirror_speed() {
    local mirror_url=$1
    local timeout=10

    echo -ne "${YELLOW}Testing${NC} $mirror_url ... "

    # Test connection speed
    if command -v curl >/dev/null 2>&1; then
        local start_time=$(date +%s%3N)
        local response=$(curl -s -o /dev/null -w "%{http_code}" --max-time $timeout "$mirror_url" 2>/dev/null)
        local end_time=$(date +%s%3N)
        local duration=$((end_time - start_time))
    elif command -v wget >/dev/null 2>&1; then
        local start_time=$(date +%s%3N)
        local response=$(wget -q --spider --timeout=$timeout "$mirror_url" 2>&1 && echo "200" || echo "0")
        local end_time=$(date +%s%3N)
        local duration=$((end_time - start_time))
    else
        echo -e "${RED}[FAIL]${NC} No download tool available"
        return 999
    fi

    if [ "$response" = "200" ] || [ "$response" = "301" ] || [ "$response" = "302" ]; then
        echo -e "${GREEN}[OK]${NC} (${duration}ms)"
        return $duration
    else
        echo -e "${RED}[FAIL]${NC}"
        return 999
    fi
}

# Function to find fastest mirror
find_fastest_mirror() {
    echo -e "${GREEN}[INFO]${NC} Finding the fastest mirror..."
    echo ""

    local fastest_time=999999
    local fastest_mirror=""
    local mirror_name=""

    # Test Chinese mirrors if in China or Chinese language
    if [[ "$COUNTRY" == "CN" ]] || [[ "$LANGUAGE" == "zh" ]]; then
        echo -e "${PURPLE}[Testing Chinese Mirrors]${NC}"
        for name in "${!MIRRORS_CN[@]}"; do
            local url="${MIRRORS_CN[$name]}"
            test_mirror_speed "$url"
            local result=$?
            if [ $result -lt $fastest_time ] && [ $result -ne 999 ]; then
                fastest_time=$result
                fastest_mirror=$url
                mirror_name=$name
            fi
        done
        echo ""
    fi

    # Test global mirrors
    echo -e "${PURPLE}[Testing Global Mirrors]${NC}"
    for name in "${!MIRRORS_GLOBAL[@]}"; do
        local url="${MIRRORS_GLOBAL[$name]}"
        test_mirror_speed "$url"
        local result=$?
        if [ $result -lt $fastest_time ] && [ $result -ne 999 ]; then
            fastest_time=$result
            fastest_mirror=$url
            mirror_name=$name
        fi
    done

    if [ -n "$fastest_mirror" ]; then
        SELECTED_MIRROR=$fastest_mirror
        echo ""
        echo -e "${GREEN}[SUCCESS]${NC} Fastest mirror selected: ${BLUE}$mirror_name${NC} (${fastest_time}ms)"
        echo -e "${BLUE}URL:${NC} $SELECTED_MIRROR"
    else
        echo -e "${RED}[ERROR]${NC} No working mirror found. Using official mirror."
        SELECTED_MIRROR="${MIRRORS_GLOBAL[official]}"
    fi
    echo ""
}

# Function to select Kali version
select_kali_version() {
    echo -e "${GREEN}[INFO]${NC} Select Kali Linux version:"
    echo ""

    local counter=1
    local versions_list=()

    for version in "${!KALI_VERSIONS[@]}"; do
        echo -e "${counter}. ${YELLOW}${version}${NC} - ${KALI_VERSIONS[$version]}"
        versions_list[$counter]=$version
        ((counter++))
    done

    echo ""
    echo -e "${BLUE}Enter your choice (1-${#versions_list[@]}):${NC} "
    read -r choice

    if [[ $choice -ge 1 && $choice -le ${#versions_list[@]} ]]; then
        SELECTED_VERSION="${versions_list[$choice]}"
        echo -e "${GREEN}[SELECTED]${NC} Version: $SELECTED_VERSION"
    else
        echo -e "${YELLOW}[DEFAULT]${NC} Using default version"
        SELECTED_VERSION="default"
    fi

    echo ""
}

# Function to select download directory
select_download_directory() {
    echo -e "${GREEN}[INFO]${NC} Select download directory:"
    echo ""

    # Default download locations
    local default_dir=""
    if [[ "$OS_TYPE" == "Windows" ]]; then
        default_dir="$HOME/Downloads"
    elif [[ "$OS_TYPE" == "macOS" ]]; then
        default_dir="$HOME/Downloads"
    else
        default_dir="$HOME/Downloads"
    fi

    echo -e "Default directory: ${BLUE}$default_dir${NC}"
    echo -e "Press Enter to use default, or enter custom path:"
    read -r custom_dir

    if [ -n "$custom_dir" ]; then
        DOWNLOAD_DIR="$custom_dir"
    else
        DOWNLOAD_DIR="$default_dir"
    fi

    # Create directory if it doesn't exist
    mkdir -p "$DOWNLOAD_DIR"

    echo -e "${GREEN}[SELECTED]${NC} Download directory: $DOWNLOAD_DIR"
    echo ""
}

# Function to construct download URL
construct_download_url() {
    local iso_file="${KALI_VERSIONS[$SELECTED_VERSION]}"

    # Handle different mirror URL structures
    if [[ "$SELECTED_MIRROR" == *"kali.org"* ]] || [[ "$SELECTED_MIRROR" == *"kali.download"* ]]; then
        DOWNLOAD_URL="${SELECTED_MIRROR}/current/$iso_file"
    else
        DOWNLOAD_URL="$SELECTED_MIRROR/$iso_file"
    fi

    echo -e "${GREEN}[INFO]${NC} Download URL constructed:"
    echo -e "${BLUE}$DOWNLOAD_URL${NC}"
    echo ""
}

# Function to download file
download_file() {
    local filename=$(basename "$DOWNLOAD_URL")
    local filepath="$DOWNLOAD_DIR/$filename"

    echo -e "${GREEN}[INFO]${NC} Starting download..."
    echo -e "${BLUE}File:${NC} $filename"
    echo -e "${BLUE}Destination:${NC} $filepath"
    echo ""

    # Choose download method
    if command -v aria2c >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} aria2c (multi-threaded)"
        aria2c -x 16 -s 16 -d "$DOWNLOAD_DIR" "$DOWNLOAD_URL"
    elif command -v axel >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} axel (multi-threaded)"
        axel -n 8 -o "$filepath" "$DOWNLOAD_URL"
    elif command -v wget >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} wget"
        cd "$DOWNLOAD_DIR" && wget "$DOWNLOAD_URL"
    elif command -v curl >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} curl"
        cd "$DOWNLOAD_DIR" && curl -O "$DOWNLOAD_URL"
    else
        echo -e "${RED}[ERROR]${NC} No download tool available!"
        exit 1
    fi

    local download_status=$?

    if [ $download_status -eq 0 ]; then
        echo ""
        echo -e "${GREEN}[SUCCESS]${NC} Download completed!"
        echo -e "${BLUE}Location:${NC} $filepath"
        echo -e "${BLUE}Size:${NC} $(ls -lh "$filepath" | awk '{print $5}')"

        # Verify file integrity
        verify_file "$filepath"
    else
        echo -e "${RED}[ERROR]${NC} Download failed!"
        exit 1
    fi
}

# Function to verify file integrity
verify_file() {
    local filepath=$1
    local filename=$(basename "$filepath")

    echo ""
    echo -e "${GREEN}[INFO]${NC} Verifying file integrity..."

    if command -v sha256sum >/dev/null 2>&1; then
        echo -e "${YELLOW}[CHECKING]${NC} SHA256 checksum..."
        sha256sum "$filepath"
    elif command -v shasum >/dev/null 2>&1; then
        echo -e "${YELLOW}[CHECKING]${NC} SHA256 checksum..."
        shasum -a 256 "$filepath"
    fi

    echo ""
    echo -e "${GREEN}[TIPS]${NC} Compare checksum with official values at:"
    echo -e "${BLUE}https://www.kali.org/get-kali/#kali-installer-images${NC}"
}

# Function to show download summary
show_summary() {
    echo ""
    echo -e "${CYAN}========== DOWNLOAD SUMMARY ==========${NC}"
    echo -e "${BLUE}Language:${NC} $LANGUAGE"
    echo -e "${BLUE}Country:${NC} $COUNTRY"
    echo -e "${BLUE}OS Type:${NC} $OS_TYPE"
    echo -e "${BLUE}Mirror:${NC} $SELECTED_MIRROR"
    echo -e "${BLUE}Version:${NC} $SELECTED_VERSION"
    echo -e "${BLUE}Download Directory:${NC} $DOWNLOAD_DIR"
    echo -e "${CYAN}=====================================${NC}"
    echo ""
}

# Function to show help
show_help() {
    echo "Kali Linux Smart Download Script"
    echo ""
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  -h, --help     Show this help message"
    echo "  -v, --version  Show script version"
    echo "  --auto         Run in automatic mode"
    echo ""
    echo "Features:"
    echo "  • Automatically detects your location and language"
    echo "  • Tests multiple mirrors to find the fastest one"
    echo "  • Supports multiple Kali Linux versions"
    echo "  • Uses the best available download tool"
    echo "  • Verifies file integrity after download"
}

# Function to check dependencies
check_dependencies() {
    echo -e "${GREEN}[INFO]${NC} Checking dependencies..."

    local tools=("curl" "wget")
    local available_tools=()

    for tool in "${tools[@]}"; do
        if command -v "$tool" >/dev/null 2>&1; then
            echo -e "  ✓ ${GREEN}$tool${NC} - Available"
            available_tools+=("$tool")
        else
            echo -e "  ✗ ${RED}$tool${NC} - Not found"
        fi
    done

    # Check download tools
    local download_tools=("aria2c" "axel" "wget" "curl")
    local download_available=0

    for tool in "${download_tools[@]}"; do
        if command -v "$tool" >/dev/null 2>&1; then
            echo -e "  ✓ ${GREEN}$tool${NC} - Available (download tool)"
            download_available=1
        fi
    done

    if [ $download_available -eq 0 ]; then
        echo -e "${RED}[ERROR]${NC} No download tool available!"
        echo -e "Please install one of: aria2c, axel, wget, curl"
        exit 1
    fi

    echo ""
}

# Main function
main() {
    # Parse command line arguments
    case "$1" in
        -h|--help)
            show_help
            exit 0
            ;;
        -v|--version)
            echo "Kali Linux Smart Download Script v$SCRIPT_VERSION"
            exit 0
            ;;
        --auto)
            AUTO_MODE=1
            ;;
        *)
            AUTO_MODE=0
            ;;
    esac

    # Print banner
    print_banner

    # Check dependencies
    check_dependencies

    # Detect system information
    detect_system

    # Find fastest mirror
    find_fastest_mirror

    # Select Kali version
    if [ $AUTO_MODE -eq 0 ]; then
        select_kali_version
    else
        SELECTED_VERSION="default"
        echo -e "${YELLOW}[AUTO]${NC} Using default version"
    fi

    # Select download directory
    if [ $AUTO_MODE -eq 0 ]; then
        select_download_directory
    else
        DOWNLOAD_DIR="$HOME/Downloads"
        mkdir -p "$DOWNLOAD_DIR"
        echo -e "${YELLOW}[AUTO]${NC} Using default directory: $DOWNLOAD_DIR"
    fi

    # Construct download URL
    construct_download_url

    # Show summary
    show_summary

    # Confirm download
    if [ $AUTO_MODE -eq 0 ]; then
        echo -e "${BLUE}Start download? (y/N):${NC} "
        read -r confirm
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
            echo -e "${YELLOW}[CANCELLED]${NC} Download cancelled by user"
            exit 0
        fi
    fi

    # Download file
    download_file

    # Final message
    echo ""
    echo -e "${GREEN}[COMPLETED]${NC} Kali Linux download finished successfully!"
    echo -e "${BLUE}Next steps:${NC}"
    echo -e "  1. Verify the checksum matches official values"
    echo -e "  2. Write the ISO to a USB drive using Rufus, Etcher, or dd"
    echo -e "  3. Boot from the USB and install Kali Linux"
    echo ""
}

# Run main function
main "$@"

Usage Instructions

1. Save the script

# Save as kali-downloader.sh
nano kali-downloader.sh
# Paste the script content and save

2. Make it executable

chmod +x kali-downloader.sh

3. Run the script

# Interactive mode
./kali-downloader.sh

# Automatic mode (uses defaults)
./kali-downloader.sh --auto

# Show help
./kali-downloader.sh --help

Features

🌍 Smart Detection

  • Automatically detects system language and country
  • Identifies OS type (Linux, macOS, Windows)
  • Selects appropriate mirrors based on location

Speed Testing

  • Tests multiple mirrors simultaneously
  • Measures response time for each mirror
  • Automatically selects the fastest working mirror

📦 Version Selection

  • Multiple Kali Linux versions available:
  • Default (standard desktop)
  • Large (extended tools)
  • Everything (full package set)
  • Light (minimal installation)
  • Minimal (smallest footprint)

🛠️ Intelligent Download

  • Uses the best available download tool:
  • aria2c (multi-threaded, fastest)
  • axel (multi-threaded)
  • wget (standard)
  • curl (fallback)
  • Supports resume interrupted downloads
  • Shows real-time progress

🔍 Verification

  • Automatically verifies file integrity
  • Displays SHA256 checksum
  • Provides official verification links

🌈 User Experience

  • Colorful, informative output
  • Interactive menu system
  • Progress indicators
  • Error handling and recovery

Requirements

Dependencies (at least one required):

  • aria2c – Multi-threaded download (recommended)
  • axel – Multi-threaded download
  • wget – Standard download tool
  • curl – HTTP client

Installation of dependencies:

# Ubuntu/Debian
sudo apt update
sudo apt install aria2 wget curl

# CentOS/RHEL/Fedora
sudo yum install aria2 wget curl
# or
sudo dnf install aria2 wget curl

# macOS
brew install aria2 wget curl

# Windows (WSL)
sudo apt install aria2 wget curl

Advanced Usage

Command Line Options:

./kali-downloader.sh --help     # Show help
./kali-downloader.sh --version  # Show version
./kali-downloader.sh --auto     # Automatic mode

Environment Variables:

export KALI_VERSION="everything"  # Set default version
export DOWNLOAD_DIR="/custom/path" # Set custom download directory

This script provides an intelligent, automated way to download Kali Linux from the fastest available mirror based on your location and system configuration.

发表在 linux文章 | 留下评论

Kali Linux Download 官方站点及镜像站点整理

Kali Linux 官方下载网站及镜像站点整理,附一键下载脚本;

关键词:Kali Linux download, Kali Linux 官方下载, Kali Linux 镜像站点, Kali Linux 官方网站, Kali Linux 下载地址, Kali Linux 官方源地址, Kali Linux 下载教程, Kali Linux 官方镜像, Kali Linux 官网下载, Kali Linux 官方站点地址

For English:Kali Linux Download Websites and Mirror Sites Collection

For Chinese:Kali Linux Download 官方站点及镜像站点整理

🌐 官方下载网站

官方主站

  • 官方网站: https://www.kali.org/
  • 官方下载页面: https://www.kali.org/get-kali/
  • 官方文档: https://www.kali.org/docs/

官方直接下载链接

  • 最新版 ISO: https://cdimage.kali.org/kali-current/
  • 每周构建版: https://cdimage.kali.org/kali-weekly/
  • 历史版本: https://cdimage.kali.org/

🌍 国内镜像站点

教育网镜像

  1. 清华大学镜像站
  • 地址: https://mirrors.tuna.tsinghua.edu.cn/kali/
  • 特点: 速度快,稳定
  1. 中科大镜像站
  • 地址: https://mirrors.ustc.edu.cn/kali/
  • 特点: 同步及时
  1. 上海交通大学镜像站
  • 地址: https://mirror.sjtu.edu.cn/kali/
  • 特点: 华东地区访问快
  1. 华中科技大学镜像站
  • 地址: https://mirrors.hust.edu.cn/kali/
  • 特点: 华中地区优化

商业镜像

  1. 阿里云镜像站
  • 地址: https://mirrors.aliyun.com/kali/
  • 特点: 全国 CDN 加速
  1. 华为云镜像站
  • 地址: https://mirrors.huaweicloud.com/kali/
  • 特点: 企业级稳定性
  1. 腾讯云镜像站
  • 地址: https://mirrors.cloud.tencent.com/kali/
  • 特点: 南方地区访问优化

🌎 国际镜像站点

亚洲地区

  1. 日本镜像
  • 地址: https://kali.download/kali/
  • 特点: 官方推荐镜像
  1. 韩国镜像
  • 地址: https://ftp.kaist.ac.kr/kali/
  • 特点: 亚洲地区备选

欧洲地区

  1. 德国镜像
    • 地址: https://ftp.halifax.rwth-aachen.de/kali/
    • 特点: 欧洲地区优选
  2. 法国镜像
    • 地址: https://kali.mirror.garr.it/kali/
    • 特点: 欧洲高速镜像
  3. 英国镜像
    • 地址: https://www.mirrorservice.org/sites/ftp.kali.org/kali/
    • 特点: 英国教育网镜像

北美地区

  1. 美国镜像
    • 地址: https://kali.mirror.globo.tech/kali/
    • 特点: 美国西海岸镜像

📦 不同版本下载链接

桌面版

  • 标准版: kali-linux-default-amd64.iso
  • 大型版: kali-linux-large-amd64.iso
  • 全功能版: kali-linux-everything-amd64.iso

轻量版

  • 轻量版: kali-linux-light-amd64.iso
  • 最小版: kali-linux-minimal-amd64.iso

特殊用途版

  • NetHunter: 专为移动设备设计
  • ARM版: 适配ARM架构设备
  • Live版: 可直接启动的Live系统

⚡ 下载建议

选择镜像的建议

  1. 国内用户: 优先选择清华、中科大、阿里云等镜像
  2. 教育网用户: 优先选择教育网镜像站点
  3. 海外用户: 选择地理位置较近的官方镜像

下载方式

# 使用 wget 下载(推荐)
wget https://cdimage.kali.org/kali-current/kali-linux-default-amd64.iso

# 使用 aria2 多线程下载
aria2c -x 16 -s 16 https://cdimage.kali.org/kali-current/kali-linux-default-amd64.iso

# 使用 curl 下载
curl -O https://cdimage.kali.org/kali-current/kali-linux-default-amd64.iso

校验文件完整性

# 校验 SHA256 哈希值
sha256sum kali-linux-default-amd64.iso

# 校验 GPG 签名
gpg --verify kali-linux-default-amd64.iso.sig kali-linux-default-amd64.iso

🔧 相关工具

镜像写入工具

  1. Rufus (Windows)
  2. Etcher (跨平台)
  3. UNetbootin (跨平台)
  4. Ventoy (多镜像管理)

虚拟机镜像

  • VMware: 提供预配置的虚拟机镜像
  • VirtualBox: OVA 格式虚拟机
  • Hyper-V: VHDX 格式镜像

⚠️ 注意事项

  1. 安全提醒: 请从官方或可信镜像下载
  2. 校验文件: 下载后务必校验文件完整性
  3. 合法使用: 仅用于合法的安全测试目的
  4. 系统要求: 确认硬件满足最低配置要求

🔄 更新源配置

使用国内镜像源可以加快软件包更新速度:

# 编辑源列表
sudo nano /etc/apt/sources.list

# 添加国内镜像源(以清华为例)
deb https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main non-free contrib
deb-src https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main non-free contrib

这些镜像站点可以为您提供快速稳定的 Kali Linux 下载体验。建议根据您的地理位置和网络环境选择最适合的镜像站点。

一键下载脚本:

Kali Linux Smart Download Script

#!/bin/bash

# Kali Linux Smart Download Script
# Automatically detects language, country, and OS to select the fastest mirror

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# Global variables
SCRIPT_VERSION="1.0"
SELECTED_MIRROR=""
SELECTED_VERSION=""
DOWNLOAD_URL=""
DOWNLOAD_DIR=""
LANGUAGE=""
COUNTRY=""
OS_TYPE=""

# Mirror lists by region
declare -A MIRRORS_CN=(
    ["tsinghua"]="https://mirrors.tuna.tsinghua.edu.cn/kali"
    ["ustc"]="https://mirrors.ustc.edu.cn/kali"
    ["aliyun"]="https://mirrors.aliyun.com/kali"
    ["huaweicloud"]="https://mirrors.huaweicloud.com/kali"
    ["sjtu"]="https://mirror.sjtu.edu.cn/kali"
    ["hust"]="https://mirrors.hust.edu.cn/kali"
    ["tencent"]="https://mirrors.cloud.tencent.com/kali"
)

declare -A MIRRORS_GLOBAL=(
    ["official"]="https://http.kali.org/kali"
    ["jp"]="https://kali.download/kali"
    ["de"]="https://ftp.halifax.rwth-aachen.de/kali"
    ["fr"]="https://kali.mirror.garr.it/kali"
    ["uk"]="https://www.mirrorservice.org/sites/ftp.kali.org/kali"
    ["us"]="https://kali.mirror.globo.tech/kali"
)

# Kali versions
declare -A KALI_VERSIONS=(
    ["default"]="kali-linux-default-amd64.iso"
    ["large"]="kali-linux-large-amd64.iso"
    ["everything"]="kali-linux-everything-amd64.iso"
    ["light"]="kali-linux-light-amd64.iso"
    ["minimal"]="kali-linux-minimal-amd64.iso"
)

# Function to print banner
print_banner() {
    echo -e "${CYAN}"
    echo "  _  __       _ _    _     _     _ _     "
    echo " | |/ /      | | |  | |   (_)   | (_)    "
    echo " | ' / __ _  | | |  | |___ _ ___| |_ ___ "
    echo " |  < / _\` | | | |  | / __| / __| | / __|"
    echo " | . \ (_| | | | |__| \__ \ \__ \ | \__ \\"
    echo " |_|\_\__,_|_|_|\____/|___/_|___/_|_|___/"
    echo -e "${NC}"
    echo -e "${YELLOW}Kali Linux Smart Download Script v${SCRIPT_VERSION}${NC}"
    echo -e "${BLUE}Automatically selecting the fastest mirror for your location${NC}"
    echo ""
}

# Function to detect system information
detect_system() {
    echo -e "${GREEN}[INFO]${NC} Detecting system information..."

    # Detect language
    if [ -n "$LANG" ]; then
        LANGUAGE=$(echo "$LANG" | cut -d'_' -f1 | tr '[:upper:]' '[:lower:]')
    else
        LANGUAGE="en"
    fi

    # Detect country
    if command -v curl >/dev/null 2>&1; then
        COUNTRY=$(curl -s https://ipapi.co/country_code/ 2>/dev/null || echo "US")
    elif command -v wget >/dev/null 2>&1; then
        COUNTRY=$(wget -qO- https://ipapi.co/country_code/ 2>/dev/null || echo "US")
    else
        COUNTRY="US"
    fi

    # Detect OS type
    if [[ "$OSTYPE" == "linux-gnu"* ]]; then
        OS_TYPE="Linux"
    elif [[ "$OSTYPE" == "darwin"* ]]; then
        OS_TYPE="macOS"
    elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
        OS_TYPE="Windows"
    else
        OS_TYPE="Unknown"
    fi

    echo -e "${BLUE}Language:${NC} $LANGUAGE"
    echo -e "${BLUE}Country:${NC} $COUNTRY"
    echo -e "${BLUE}OS Type:${NC} $OS_TYPE"
    echo ""
}

# Function to test mirror speed
test_mirror_speed() {
    local mirror_url=$1
    local timeout=10

    echo -ne "${YELLOW}Testing${NC} $mirror_url ... "

    # Test connection speed
    if command -v curl >/dev/null 2>&1; then
        local start_time=$(date +%s%3N)
        local response=$(curl -s -o /dev/null -w "%{http_code}" --max-time $timeout "$mirror_url" 2>/dev/null)
        local end_time=$(date +%s%3N)
        local duration=$((end_time - start_time))
    elif command -v wget >/dev/null 2>&1; then
        local start_time=$(date +%s%3N)
        local response=$(wget -q --spider --timeout=$timeout "$mirror_url" 2>&1 && echo "200" || echo "0")
        local end_time=$(date +%s%3N)
        local duration=$((end_time - start_time))
    else
        echo -e "${RED}[FAIL]${NC} No download tool available"
        return 999
    fi

    if [ "$response" = "200" ] || [ "$response" = "301" ] || [ "$response" = "302" ]; then
        echo -e "${GREEN}[OK]${NC} (${duration}ms)"
        return $duration
    else
        echo -e "${RED}[FAIL]${NC}"
        return 999
    fi
}

# Function to find fastest mirror
find_fastest_mirror() {
    echo -e "${GREEN}[INFO]${NC} Finding the fastest mirror..."
    echo ""

    local fastest_time=999999
    local fastest_mirror=""
    local mirror_name=""

    # Test Chinese mirrors if in China or Chinese language
    if [[ "$COUNTRY" == "CN" ]] || [[ "$LANGUAGE" == "zh" ]]; then
        echo -e "${PURPLE}[Testing Chinese Mirrors]${NC}"
        for name in "${!MIRRORS_CN[@]}"; do
            local url="${MIRRORS_CN[$name]}"
            test_mirror_speed "$url"
            local result=$?
            if [ $result -lt $fastest_time ] && [ $result -ne 999 ]; then
                fastest_time=$result
                fastest_mirror=$url
                mirror_name=$name
            fi
        done
        echo ""
    fi

    # Test global mirrors
    echo -e "${PURPLE}[Testing Global Mirrors]${NC}"
    for name in "${!MIRRORS_GLOBAL[@]}"; do
        local url="${MIRRORS_GLOBAL[$name]}"
        test_mirror_speed "$url"
        local result=$?
        if [ $result -lt $fastest_time ] && [ $result -ne 999 ]; then
            fastest_time=$result
            fastest_mirror=$url
            mirror_name=$name
        fi
    done

    if [ -n "$fastest_mirror" ]; then
        SELECTED_MIRROR=$fastest_mirror
        echo ""
        echo -e "${GREEN}[SUCCESS]${NC} Fastest mirror selected: ${BLUE}$mirror_name${NC} (${fastest_time}ms)"
        echo -e "${BLUE}URL:${NC} $SELECTED_MIRROR"
    else
        echo -e "${RED}[ERROR]${NC} No working mirror found. Using official mirror."
        SELECTED_MIRROR="${MIRRORS_GLOBAL[official]}"
    fi
    echo ""
}

# Function to select Kali version
select_kali_version() {
    echo -e "${GREEN}[INFO]${NC} Select Kali Linux version:"
    echo ""

    local counter=1
    local versions_list=()

    for version in "${!KALI_VERSIONS[@]}"; do
        echo -e "${counter}. ${YELLOW}${version}${NC} - ${KALI_VERSIONS[$version]}"
        versions_list[$counter]=$version
        ((counter++))
    done

    echo ""
    echo -e "${BLUE}Enter your choice (1-${#versions_list[@]}):${NC} "
    read -r choice

    if [[ $choice -ge 1 && $choice -le ${#versions_list[@]} ]]; then
        SELECTED_VERSION="${versions_list[$choice]}"
        echo -e "${GREEN}[SELECTED]${NC} Version: $SELECTED_VERSION"
    else
        echo -e "${YELLOW}[DEFAULT]${NC} Using default version"
        SELECTED_VERSION="default"
    fi

    echo ""
}

# Function to select download directory
select_download_directory() {
    echo -e "${GREEN}[INFO]${NC} Select download directory:"
    echo ""

    # Default download locations
    local default_dir=""
    if [[ "$OS_TYPE" == "Windows" ]]; then
        default_dir="$HOME/Downloads"
    elif [[ "$OS_TYPE" == "macOS" ]]; then
        default_dir="$HOME/Downloads"
    else
        default_dir="$HOME/Downloads"
    fi

    echo -e "Default directory: ${BLUE}$default_dir${NC}"
    echo -e "Press Enter to use default, or enter custom path:"
    read -r custom_dir

    if [ -n "$custom_dir" ]; then
        DOWNLOAD_DIR="$custom_dir"
    else
        DOWNLOAD_DIR="$default_dir"
    fi

    # Create directory if it doesn't exist
    mkdir -p "$DOWNLOAD_DIR"

    echo -e "${GREEN}[SELECTED]${NC} Download directory: $DOWNLOAD_DIR"
    echo ""
}

# Function to construct download URL
construct_download_url() {
    local iso_file="${KALI_VERSIONS[$SELECTED_VERSION]}"

    # Handle different mirror URL structures
    if [[ "$SELECTED_MIRROR" == *"kali.org"* ]] || [[ "$SELECTED_MIRROR" == *"kali.download"* ]]; then
        DOWNLOAD_URL="${SELECTED_MIRROR}/current/$iso_file"
    else
        DOWNLOAD_URL="$SELECTED_MIRROR/$iso_file"
    fi

    echo -e "${GREEN}[INFO]${NC} Download URL constructed:"
    echo -e "${BLUE}$DOWNLOAD_URL${NC}"
    echo ""
}

# Function to download file
download_file() {
    local filename=$(basename "$DOWNLOAD_URL")
    local filepath="$DOWNLOAD_DIR/$filename"

    echo -e "${GREEN}[INFO]${NC} Starting download..."
    echo -e "${BLUE}File:${NC} $filename"
    echo -e "${BLUE}Destination:${NC} $filepath"
    echo ""

    # Choose download method
    if command -v aria2c >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} aria2c (multi-threaded)"
        aria2c -x 16 -s 16 -d "$DOWNLOAD_DIR" "$DOWNLOAD_URL"
    elif command -v axel >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} axel (multi-threaded)"
        axel -n 8 -o "$filepath" "$DOWNLOAD_URL"
    elif command -v wget >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} wget"
        cd "$DOWNLOAD_DIR" && wget "$DOWNLOAD_URL"
    elif command -v curl >/dev/null 2>&1; then
        echo -e "${YELLOW}[USING]${NC} curl"
        cd "$DOWNLOAD_DIR" && curl -O "$DOWNLOAD_URL"
    else
        echo -e "${RED}[ERROR]${NC} No download tool available!"
        exit 1
    fi

    local download_status=$?

    if [ $download_status -eq 0 ]; then
        echo ""
        echo -e "${GREEN}[SUCCESS]${NC} Download completed!"
        echo -e "${BLUE}Location:${NC} $filepath"
        echo -e "${BLUE}Size:${NC} $(ls -lh "$filepath" | awk '{print $5}')"

        # Verify file integrity
        verify_file "$filepath"
    else
        echo -e "${RED}[ERROR]${NC} Download failed!"
        exit 1
    fi
}

# Function to verify file integrity
verify_file() {
    local filepath=$1
    local filename=$(basename "$filepath")

    echo ""
    echo -e "${GREEN}[INFO]${NC} Verifying file integrity..."

    if command -v sha256sum >/dev/null 2>&1; then
        echo -e "${YELLOW}[CHECKING]${NC} SHA256 checksum..."
        sha256sum "$filepath"
    elif command -v shasum >/dev/null 2>&1; then
        echo -e "${YELLOW}[CHECKING]${NC} SHA256 checksum..."
        shasum -a 256 "$filepath"
    fi

    echo ""
    echo -e "${GREEN}[TIPS]${NC} Compare checksum with official values at:"
    echo -e "${BLUE}https://www.kali.org/get-kali/#kali-installer-images${NC}"
}

# Function to show download summary
show_summary() {
    echo ""
    echo -e "${CYAN}========== DOWNLOAD SUMMARY ==========${NC}"
    echo -e "${BLUE}Language:${NC} $LANGUAGE"
    echo -e "${BLUE}Country:${NC} $COUNTRY"
    echo -e "${BLUE}OS Type:${NC} $OS_TYPE"
    echo -e "${BLUE}Mirror:${NC} $SELECTED_MIRROR"
    echo -e "${BLUE}Version:${NC} $SELECTED_VERSION"
    echo -e "${BLUE}Download Directory:${NC} $DOWNLOAD_DIR"
    echo -e "${CYAN}=====================================${NC}"
    echo ""
}

# Function to show help
show_help() {
    echo "Kali Linux Smart Download Script"
    echo ""
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  -h, --help     Show this help message"
    echo "  -v, --version  Show script version"
    echo "  --auto         Run in automatic mode"
    echo ""
    echo "Features:"
    echo "  • Automatically detects your location and language"
    echo "  • Tests multiple mirrors to find the fastest one"
    echo "  • Supports multiple Kali Linux versions"
    echo "  • Uses the best available download tool"
    echo "  • Verifies file integrity after download"
}

# Function to check dependencies
check_dependencies() {
    echo -e "${GREEN}[INFO]${NC} Checking dependencies..."

    local tools=("curl" "wget")
    local available_tools=()

    for tool in "${tools[@]}"; do
        if command -v "$tool" >/dev/null 2>&1; then
            echo -e "  ✓ ${GREEN}$tool${NC} - Available"
            available_tools+=("$tool")
        else
            echo -e "  ✗ ${RED}$tool${NC} - Not found"
        fi
    done

    # Check download tools
    local download_tools=("aria2c" "axel" "wget" "curl")
    local download_available=0

    for tool in "${download_tools[@]}"; do
        if command -v "$tool" >/dev/null 2>&1; then
            echo -e "  ✓ ${GREEN}$tool${NC} - Available (download tool)"
            download_available=1
        fi
    done

    if [ $download_available -eq 0 ]; then
        echo -e "${RED}[ERROR]${NC} No download tool available!"
        echo -e "Please install one of: aria2c, axel, wget, curl"
        exit 1
    fi

    echo ""
}

# Main function
main() {
    # Parse command line arguments
    case "$1" in
        -h|--help)
            show_help
            exit 0
            ;;
        -v|--version)
            echo "Kali Linux Smart Download Script v$SCRIPT_VERSION"
            exit 0
            ;;
        --auto)
            AUTO_MODE=1
            ;;
        *)
            AUTO_MODE=0
            ;;
    esac

    # Print banner
    print_banner

    # Check dependencies
    check_dependencies

    # Detect system information
    detect_system

    # Find fastest mirror
    find_fastest_mirror

    # Select Kali version
    if [ $AUTO_MODE -eq 0 ]; then
        select_kali_version
    else
        SELECTED_VERSION="default"
        echo -e "${YELLOW}[AUTO]${NC} Using default version"
    fi

    # Select download directory
    if [ $AUTO_MODE -eq 0 ]; then
        select_download_directory
    else
        DOWNLOAD_DIR="$HOME/Downloads"
        mkdir -p "$DOWNLOAD_DIR"
        echo -e "${YELLOW}[AUTO]${NC} Using default directory: $DOWNLOAD_DIR"
    fi

    # Construct download URL
    construct_download_url

    # Show summary
    show_summary

    # Confirm download
    if [ $AUTO_MODE -eq 0 ]; then
        echo -e "${BLUE}Start download? (y/N):${NC} "
        read -r confirm
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
            echo -e "${YELLOW}[CANCELLED]${NC} Download cancelled by user"
            exit 0
        fi
    fi

    # Download file
    download_file

    # Final message
    echo ""
    echo -e "${GREEN}[COMPLETED]${NC} Kali Linux download finished successfully!"
    echo -e "${BLUE}Next steps:${NC}"
    echo -e "  1. Verify the checksum matches official values"
    echo -e "  2. Write the ISO to a USB drive using Rufus, Etcher, or dd"
    echo -e "  3. Boot from the USB and install Kali Linux"
    echo ""
}

# Run main function
main "$@"

Usage Instructions

1. Save the script

# Save as kali-downloader.sh
nano kali-downloader.sh
# Paste the script content and save

2. Make it executable

chmod +x kali-downloader.sh

3. Run the script

# Interactive mode
./kali-downloader.sh

# Automatic mode (uses defaults)
./kali-downloader.sh --auto

# Show help
./kali-downloader.sh --help

Features

🌍 Smart Detection

  • Automatically detects system language and country
  • Identifies OS type (Linux, macOS, Windows)
  • Selects appropriate mirrors based on location

Speed Testing

  • Tests multiple mirrors simultaneously
  • Measures response time for each mirror
  • Automatically selects the fastest working mirror

📦 Version Selection

  • Multiple Kali Linux versions available:
  • Default (standard desktop)
  • Large (extended tools)
  • Everything (full package set)
  • Light (minimal installation)
  • Minimal (smallest footprint)

🛠️ Intelligent Download

  • Uses the best available download tool:
  • aria2c (multi-threaded, fastest)
  • axel (multi-threaded)
  • wget (standard)
  • curl (fallback)
  • Supports resume interrupted downloads
  • Shows real-time progress

🔍 Verification

  • Automatically verifies file integrity
  • Displays SHA256 checksum
  • Provides official verification links

🌈 User Experience

  • Colorful, informative output
  • Interactive menu system
  • Progress indicators
  • Error handling and recovery

Requirements

Dependencies (at least one required):

  • aria2c – Multi-threaded download (recommended)
  • axel – Multi-threaded download
  • wget – Standard download tool
  • curl – HTTP client

Installation of dependencies:

# Ubuntu/Debian
sudo apt update
sudo apt install aria2 wget curl

# CentOS/RHEL/Fedora
sudo yum install aria2 wget curl
# or
sudo dnf install aria2 wget curl

# macOS
brew install aria2 wget curl

# Windows (WSL)
sudo apt install aria2 wget curl

Advanced Usage

Command Line Options:

./kali-downloader.sh --help     # Show help
./kali-downloader.sh --version  # Show version
./kali-downloader.sh --auto     # Automatic mode

Environment Variables:

export KALI_VERSION="everything"  # Set default version
export DOWNLOAD_DIR="/custom/path" # Set custom download directory

This script provides an intelligent, automated way to download Kali Linux from the fastest available mirror based on your location and system configuration.

发表在 linux文章 | 留下评论

fchownat系统调用及示例

fchownat函数详解

fchownat函数是Linux系统中用于改变文件所有者和组的高级函数,它是chown()和fchown()函数的增强版本。可以把fchownat想象成一个”精确的所有权修改器”,它不仅能够修改文件的所有者和组,还支持相对路径操作和符号链接控制。
fchownat的主要优势在于它提供了更多的控制选项,特别是相对于指定目录文件描述符的路径解析能力。这使得在复杂目录结构中进行批量文件所有权修改变得更加安全和高效。

本文链接

1. 函数介绍

fchownat函数是Linux系统中用于改变文件所有者和组的高级函数,它是chown()和fchown()函数的增强版本。可以把fchownat想象成一个”精确的所有权修改器”,它不仅能够修改文件的所有者和组,还支持相对路径操作和符号链接控制。

fchownat的主要优势在于它提供了更多的控制选项,特别是相对于指定目录文件描述符的路径解析能力。这使得在复杂目录结构中进行批量文件所有权修改变得更加安全和高效。

使用场景:

  • 系统管理工具中的文件权限管理
  • 批量修改目录树中文件的所有权
  • 安全的相对路径文件操作
  • 系统恢复和维护工具
  • 容器和虚拟化环境中的权限管理

2. 函数原型

#include <fcntl.h>
#include <unistd.h>

int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags);

3. 功能

fchownat函数的主要功能是修改指定文件的所有者和组。它支持相对于目录文件描述符的路径解析,并提供了控制符号链接行为的选项。

4. 参数

  • dirfd: 目录文件描述符
    • 类型:int
    • 含义:基准目录的文件描述符,用于相对路径解析
    • 特殊值:AT_FDCWD表示使用当前工作目录
  • pathname: 文件路径名
    • 类型:const char*
    • 含义:要修改所有权的文件路径名
    • 可以是相对路径(相对于dirfd)或绝对路径
  • owner: 新的所有者用户ID
    • 类型:uid_t
    • 含义:文件的新所有者用户ID
    • -1表示不改变所有者
  • group: 新的组ID
    • 类型:gid_t
    • 含义:文件的新组ID
    • -1表示不改变组
  • flags: 操作标志
    • 类型:int
    • 含义:控制操作行为的标志位
    • 常用值:
      • 0:默认行为
      • AT_SYMLINK_NOFOLLOW:不跟随符号链接(修改符号链接本身)
      • AT_EMPTY_PATH:允许空路径名(Linux 2.6.39+)

5. 返回值

  • 成功: 返回0
  • 失败: 返回-1,并设置errno错误码
    • EACCES:权限不足
    • EBADF:dirfd无效或不是目录
    • EFAULT:pathname指向无效内存
    • EIO:I/O错误
    • ELOOP:符号链接循环
    • ENAMETOOLONG:路径名过长
    • ENOENT:文件或路径不存在
    • ENOTDIR:路径前缀不是目录
    • EPERM:操作不被允许
    • EROFS:文件系统只读

6. 相似函数或关联函数

  • chown(): 改变文件所有权
  • fchown(): 通过文件描述符改变文件所有权
  • lchown(): 改变符号链接所有权(不跟随链接)
  • chmod(): 改变文件权限
  • fchmodat(): 相对于目录文件描述符改变文件权限
  • openat(): 相对于目录文件描述符打开文件
  • readlinkat(): 相对于目录文件描述符读取符号链接

7. 示例代码

示例1:基础fchownat使用 – 简单所有权修改

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

// 创建测试文件
int create_test_file(const char* filename, const char* content) {
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return -1;
    }
    
    if (write(fd, content, strlen(content)) == -1) {
        perror("写入文件失败");
        close(fd);
        return -1;
    }
    
    close(fd);
    printf("创建文件: %s\n", filename);
    return 0;
}

// 显示文件所有权信息
void show_file_ownership(const char* filename) {
    struct stat file_stat;
    
    if (stat(filename, &file_stat) == -1) {
        perror("获取文件状态失败");
        return;
    }
    
    // 获取用户名
    struct passwd* pwd = getpwuid(file_stat.st_uid);
    struct group* grp = getgrgid(file_stat.st_gid);
    
    printf("文件 %s 的所有权信息:\n", filename);
    printf("  用户ID: %d", (int)file_stat.st_uid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("  组ID: %d", (int)file_stat.st_gid);
    if (grp) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
    
    printf("  权限: %o\n", file_stat.st_mode & 0777);
}

// 获取当前用户和组信息
void show_current_user_info() {
    uid_t uid = getuid();
    gid_t gid = getgid();
    
    struct passwd* pwd = getpwuid(uid);
    struct group* grp = getgrgid(gid);
    
    printf("当前用户信息:\n");
    printf("  用户ID: %d", (int)uid);
    if (pwd) {
        printf(" (%s)", pwd->pw_name);
    }
    printf("\n");
    
    printf("  组ID: %d", (int)gid);
    if (grp) {
        printf(" (%s)", grp->gr_name);
    }
    printf("\n");
}

int main() {
    printf("=== 基础fchownat使用示例 ===\n");
    
    // 检查权限
    if (geteuid() != 0) {
        printf("警告: 修改文件所有权通常需要root权限\n");
        printf("某些操作可能失败\n");
    }
    
    show_current_user_info();
    
    // 创建测试文件
    const char* test_file = "fchownat_test.txt";
    if (create_test_file(test_file, "这是fchownat测试文件的内容\n") == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 显示初始所有权
    printf("\n1. 初始文件所有权:\n");
    show_file_ownership(test_file);
    
    // 获取root用户信息(用于测试)
    struct passwd* root_pwd = getpwnam("root");
    struct group* root_grp = getgrnam("root");
    
    uid_t root_uid = root_pwd ? root_pwd->pw_uid : 0;
    gid_t root_gid = root_grp ? root_grp->gr_gid : 0;
    
    // 使用fchownat修改所有权为root
    printf("\n2. 使用fchownat修改所有权为root:\n");
    if (fchownat(AT_FDCWD, test_file, root_uid, root_gid, 0) == 0) {
        printf("✓ 所有权修改成功\n");
    } else {
        printf("✗ 所有权修改失败: %s\n", strerror(errno));
    }
    
    show_file_ownership(test_file);
    
    // 使用fchownat只修改用户ID
    printf("\n3. 使用fchownat只修改用户ID:\n");
    uid_t current_uid = getuid();
    if (fchownat(AT_FDCWD, test_file, current_uid, -1, 0) == 0) {
        printf("✓ 用户ID修改成功\n");
    } else {
        printf("✗ 用户ID修改失败: %s\n", strerror(errno));
    }
    
    show_file_ownership(test_file);
    
    // 使用fchownat只修改组ID
    printf("\n4. 使用fchownat只修改组ID:\n");
    gid_t current_gid = getgid();
    if (fchownat(AT_FDCWD, test_file, -1, current_gid, 0) == 0) {
        printf("✓ 组ID修改成功\n");
    } else {
        printf("✗ 组ID修改失败: %s\n", strerror(errno));
    }
    
    show_file_ownership(test_file);
    
    // 演示错误处理
    printf("\n5. 错误处理演示:\n");
    
    // 尝试修改不存在的文件
    if (fchownat(AT_FDCWD, "nonexistent.txt", 0, 0, 0) == -1) {
        printf("修改不存在文件的所有权: %s (预期行为)\n", strerror(errno));
    }
    
    // 尝试使用无效的dirfd
    if (fchownat(999, test_file, 0, 0, 0) == -1) {
        printf("使用无效dirfd: %s (预期行为)\n", strerror(errno));
    }
    
    // 清理测试文件
    unlink(test_file);
    
    printf("\n=== 基础fchownat演示完成 ===\n");
    
    return 0;
}

示例2:相对路径和符号链接控制

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

// 创建测试目录结构
int create_test_directory_structure() {
    // 创建测试目录
    if (mkdir("fchownat_test_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        return -1;
    }
    
    if (mkdir("fchownat_test_dir/subdir", 0755) == -1 && errno != EEXIST) {
        perror("创建子目录失败");
        return -1;
    }
    
    // 创建测试文件
    int fd = open("fchownat_test_dir/test_file.txt", 
                  O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "测试文件内容", 12);
        close(fd);
        printf("创建测试文件: fchownat_test_dir/test_file.txt\n");
    }
    
    // 创建子目录中的文件
    fd = open("fchownat_test_dir/subdir/sub_file.txt", 
              O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "子目录文件内容", 14);
        close(fd);
        printf("创建子目录文件: fchownat_test_dir/subdir/sub_file.txt\n");
    }
    
    // 创建符号链接
    if (symlink("test_file.txt", "fchownat_test_dir/link_to_file") == 0) {
        printf("创建符号链接: fchownat_test_dir/link_to_file\n");
    }
    
    return 0;
}

// 显示目录内容和所有权
void show_directory_ownership(const char* dir_path) {
    printf("目录 %s 的内容和所有权:\n", dir_path);
    
    DIR* dir = opendir(dir_path);
    if (dir == NULL) {
        perror("打开目录失败");
        return;
    }
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        char full_path[512];
        snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
        
        struct stat file_stat;
        if (stat(full_path, &file_stat) == 0) {
            struct passwd* pwd = getpwuid(file_stat.st_uid);
            struct group* grp = getgrgid(file_stat.st_gid);
            
            char type_char = '?';
            if (S_ISREG(file_stat.st_mode)) type_char = 'f';
            else if (S_ISDIR(file_stat.st_mode)) type_char = 'd';
            else if (S_ISLNK(file_stat.st_mode)) type_char = 'l';
            
            printf("  %c %s", type_char, entry->d_name);
            if (pwd) printf(" (用户:%s", pwd->pw_name);
            if (grp) printf(" 组:%s", grp->gr_name);
            printf(")\n");
        }
    }
    
    closedir(dir);
}

int main() {
    printf("=== fchownat相对路径和符号链接控制示例 ===\n");
    
    if (geteuid() != 0) {
        printf("警告: 此演示需要root权限以修改文件所有权\n");
    }
    
    // 创建测试目录结构
    printf("1. 创建测试目录结构:\n");
    if (create_test_directory_structure() == -1) {
        exit(EXIT_FAILURE);
    }
    
    show_directory_ownership("fchownat_test_dir");
    
    // 演示相对路径操作
    printf("\n2. 演示相对路径操作:\n");
    
    // 打开基准目录
    int base_dirfd = open("fchownat_test_dir", O_RDONLY);
    if (base_dirfd == -1) {
        perror("打开基准目录失败");
        exit(EXIT_FAILURE);
    }
    
    printf("打开基准目录 fd=%d\n", base_dirfd);
    
    // 相对于基准目录修改文件所有权
    printf("相对于基准目录修改文件所有权:\n");
    
    // 修改test_file.txt的所有权
    if (fchownat(base_dirfd, "test_file.txt", 0, 0, 0) == 0) {
        printf("  ✓ 修改 test_file.txt 成功\n");
    } else {
        printf("  ✗ 修改 test_file.txt 失败: %s\n", strerror(errno));
    }
    
    // 修改子目录中文件的所有权
    if (fchownat(base_dirfd, "subdir/sub_file.txt", 0, 0, 0) == 0) {
        printf("  ✓ 修改 subdir/sub_file.txt 成功\n");
    } else {
        printf("  ✗ 修改 subdir/sub_file.txt 失败: %s\n", strerror(errno));
    }
    
    show_directory_ownership("fchownat_test_dir");
    
    // 演示AT_FDCWD的使用
    printf("\n3. 演示AT_FDCWD的使用:\n");
    
    // 切换到测试目录
    if (chdir("fchownat_test_dir") == -1) {
        perror("切换目录失败");
        close(base_dirfd);
        exit(EXIT_FAILURE);
    }
    
    printf("切换到测试目录\n");
    
    // 使用AT_FDCWD修改文件所有权
    if (fchownat(AT_FDCWD, "test_file.txt", 1, 1, 0) == 0) {
        printf("  ✓ 使用AT_FDCWD修改所有权成功\n");
    } else {
        printf("  ✗ 使用AT_FDCWD修改所有权失败: %s\n", strerror(errno));
    }
    
    // 切换回原目录
    chdir("..");
    show_directory_ownership("fchownat_test_dir");
    
    // 演示符号链接控制
    printf("\n4. 演示符号链接控制:\n");
    
    // 显示符号链接信息
    struct stat link_stat, target_stat;
    if (lstat("fchownat_test_dir/link_to_file", &link_stat) == 0 &&
        stat("fchownat_test_dir/link_to_file", &target_stat) == 0) {
        printf("符号链接信息:\n");
        printf("  链接文件所有权: UID=%d, GID=%d\n", 
               (int)link_stat.st_uid, (int)link_stat.st_gid);
        printf("  目标文件所有权: UID=%d, GID=%d\n", 
               (int)target_stat.st_uid, (int)target_stat.st_gid);
    }
    
    // 默认行为:修改目标文件所有权
    printf("默认行为(修改目标文件):\n");
    if (fchownat(base_dirfd, "link_to_file", 2, 2, 0) == 0) {
        printf("  ✓ 修改符号链接目标成功\n");
        // 检查目标文件所有权
        if (stat("fchownat_test_dir/link_to_file", &target_stat) == 0) {
            printf("  目标文件新所有权: UID=%d, GID=%d\n", 
                   (int)target_stat.st_uid, (int)target_stat.st_gid);
        }
    }
    
    // 使用AT_SYMLINK_NOFOLLOW:修改符号链接本身
    printf("使用AT_SYMLINK_NOFOLLOW(修改链接本身):\n");
    if (fchownat(base_dirfd, "link_to_file", 3, 3, AT_SYMLINK_NOFOLLOW) == 0) {
        printf("  ✓ 修改符号链接本身成功\n");
        // 检查链接文件所有权
        if (lstat("fchownat_test_dir/link_to_file", &link_stat) == 0) {
            printf("  链接文件新所有权: UID=%d, GID=%d\n", 
                   (int)link_stat.st_uid, (int)link_stat.st_gid);
        }
    }
    
    show_directory_ownership("fchownat_test_dir");
    
    // 清理资源
    close(base_dirfd);
    
    // 清理测试文件
    unlink("fchownat_test_dir/link_to_file");
    unlink("fchownat_test_dir/test_file.txt");
    unlink("fchownat_test_dir/subdir/sub_file.txt");
    rmdir("fchownat_test_dir/subdir");
    rmdir("fchownat_test_dir");
    
    printf("\n=== 相对路径和符号链接控制演示完成 ===\n");
    
    return 0;
}

示例3:批量文件所有权管理

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

#define MAX_FILES 1000

// 文件信息结构
typedef struct {
    char path[512];
    uid_t old_uid;
    gid_t old_gid;
    uid_t new_uid;
    gid_t new_gid;
    int changed;
} file_ownership_info_t;

file_ownership_info_t file_list[MAX_FILES];
int file_count = 0;

// 创建批量测试文件
int create_batch_test_files(const char* base_dir) {
    printf("创建批量测试文件...\n");
    
    // 创建基础目录
    if (mkdir(base_dir, 0755) == -1 && errno != EEXIST) {
        perror("创建基础目录失败");
        return -1;
    }
    
    // 创建子目录结构
    char dirs[][256] = {
        "documents", "images", "videos", "music", "archives"
    };
    
    for (int i = 0; i < 5; i++) {
        char full_path[512];
        snprintf(full_path, sizeof(full_path), "%s/%s", base_dir, dirs[i]);
        if (mkdir(full_path, 0755) == -1 && errno != EEXIST) {
            printf("创建目录失败 %s: %s\n", full_path, strerror(errno));
        }
    }
    
    // 创建测试文件
    srand(time(NULL));
    const char* extensions[] = {".txt", ".pdf", ".jpg", ".mp3", ".zip"};
    const char* contents[] = {"文档内容", "PDF内容", "图片数据", "音频数据", "压缩数据"};
    
    for (int i = 0; i < 50; i++) {
        int dir_index = rand() % 6;  // 0-4是子目录,5是根目录
        int ext_index = rand() % 5;
        
        char file_path[512];
        if (dir_index < 5) {
            snprintf(file_path, sizeof(file_path), "%s/%s/file_%03d%s", 
                     base_dir, dirs[dir_index], i, extensions[ext_index]);
        } else {
            snprintf(file_path, sizeof(file_path), "%s/root_file_%03d%s", 
                     base_dir, i, extensions[ext_index]);
        }
        
        int fd = open(file_path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (fd != -1) {
            write(fd, contents[ext_index], strlen(contents[ext_index]));
            close(fd);
            printf("创建文件: %s\n", file_path);
        }
    }
    
    return 0;
}

// 递归扫描目录中的文件
int scan_directory_files(int dirfd, const char* base_path) {
    DIR* dir = fdopendir(dirfd);
    if (dir == NULL) {
        close(dirfd);
        return -1;
    }
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        
        // 构造完整路径
        char full_path[1024];
        if (base_path[0] == '\0') {
            snprintf(full_path, sizeof(full_path), "%s", entry->d_name);
        } else {
            snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
        }
        
        // 获取文件状态
        struct stat file_stat;
        if (fstatat(dirfd, entry->d_name, &file_stat, AT_SYMLINK_NOFOLLOW) == 0) {
            if (file_count < MAX_FILES) {
                strncpy(file_list[file_count].path, full_path, 
                        sizeof(file_list[file_count].path) - 1);
                file_list[file_count].old_uid = file_stat.st_uid;
                file_list[file_count].old_gid = file_stat.st_gid;
                file_list[file_count].new_uid = -1;  // 保持不变
                file_list[file_count].new_gid = -1;  // 保持不变
                file_list[file_count].changed = 0;
                file_count++;
            }
            
            // 如果是目录,递归扫描
            if (S_ISDIR(file_stat.st_mode)) {
                int sub_dirfd = openat(dirfd, entry->d_name, O_RDONLY);
                if (sub_dirfd != -1) {
                    scan_directory_files(sub_dirfd, full_path);
                }
            }
        }
    }
    
    closedir(dir);
    return 0;
}

// 显示文件列表
void show_file_list(int max_show) {
    printf("文件列表 (共 %d 个文件):\n", file_count);
    printf("%-40s %-8s %-8s %-8s %-8s %-8s\n", 
           "路径", "旧UID", "旧GID", "新UID", "新GID", "状态");
    printf("%-40s %-8s %-8s %-8s %-8s %-8s\n", 
           "----", "------", "------", "------", "------", "----");
    
    int show_count = (max_show > 0 && max_show < file_count) ? max_show : file_count;
    
    for (int i = 0; i < show_count; i++) {
        const file_ownership_info_t* info = &file_list[i];
        printf("%-40s %-8d %-8d %-8d %-8d %-8s\n",
               info->path,
               (int)info->old_uid, (int)info->old_gid,
               (int)info->new_uid, (int)info->new_gid,
               info->changed ? "已修改" : "未修改");
    }
    
    if (show_count < file_count) {
        printf("... (还有 %d 个文件)\n", file_count - show_count);
    }
    printf("\n");
}

// 批量修改文件所有权
int batch_change_ownership(int base_dirfd, uid_t new_uid, gid_t new_gid, 
                          const char* pattern) {
    printf("批量修改文件所有权:\n");
    printf("  新UID: %d, 新GID: %d\n", (int)new_uid, (int)new_gid);
    if (pattern) {
        printf("  匹配模式: %s\n", pattern);
    }
    
    int changed_count = 0;
    
    for (int i = 0; i < file_count; i++) {
        file_ownership_info_t* info = &file_list[i];
        
        // 检查是否匹配模式(如果指定了模式)
        if (pattern && strstr(info->path, pattern) == NULL) {
            continue;
        }
        
        // 确定新的UID和GID
        uid_t target_uid = (new_uid == (uid_t)-1) ? info->old_uid : new_uid;
        gid_t target_gid = (new_gid == (gid_t)-1) ? info->old_gid : new_gid;
        
        // 只有当所有权需要改变时才执行
        if (target_uid != info->old_uid || target_gid != info->old_gid) {
            if (fchownat(base_dirfd, info->path, target_uid, target_gid, 0) == 0) {
                info->new_uid = target_uid;
                info->new_gid = target_gid;
                info->changed = 1;
                changed_count++;
                printf("  修改成功: %s (UID:%d->%d, GID:%d->%d)\n",
                       info->path, (int)info->old_uid, (int)target_uid,
                       (int)info->old_gid, (int)target_gid);
            } else {
                printf("  修改失败: %s (%s)\n", info->path, strerror(errno));
            }
        }
    }
    
    printf("总共修改了 %d 个文件的所有权\n", changed_count);
    return changed_count;
}

// 按文件类型修改所有权
int change_ownership_by_type(int base_dirfd, const char* extension, 
                            uid_t new_uid, gid_t new_gid) {
    printf("按文件类型修改所有权: %s\n", extension);
    
    int changed_count = 0;
    
    for (int i = 0; i < file_count; i++) {
        file_ownership_info_t* info = &file_list[i];
        
        // 检查文件扩展名
        char* dot = strrchr(info->path, '.');
        if (dot && strcmp(dot, extension) == 0) {
            if (fchownat(base_dirfd, info->path, new_uid, new_gid, 0) == 0) {
                info->new_uid = new_uid;
                info->new_gid = new_gid;
                info->changed = 1;
                changed_count++;
                printf("  修改成功: %s\n", info->path);
            } else {
                printf("  修改失败: %s (%s)\n", info->path, strerror(errno));
            }
        }
    }
    
    printf("按类型修改了 %d 个文件的所有权\n", changed_count);
    return changed_count;
}

int main() {
    printf("=== 批量文件所有权管理示例 ===\n");
    
    if (geteuid() != 0) {
        printf("警告: 批量所有权修改需要root权限\n");
    }
    
    const char* test_dir = "batch_ownership_test";
    
    // 创建批量测试文件
    printf("1. 创建批量测试文件:\n");
    if (create_batch_test_files(test_dir) == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 扫描文件
    printf("\n2. 扫描目录中的文件:\n");
    int base_dirfd = open(test_dir, O_RDONLY);
    if (base_dirfd == -1) {
        perror("打开测试目录失败");
        exit(EXIT_FAILURE);
    }
    
    file_count = 0;
    scan_directory_files(dup(base_dirfd), "");  // dup因为scan_directory_files会关闭fd
    
    printf("扫描完成,找到 %d 个文件\n", file_count);
    show_file_list(10);
    
    // 批量修改所有文件的所有权为root
    printf("3. 批量修改所有文件所有权为root:\n");
    batch_change_ownership(base_dirfd, 0, 0, NULL);
    
    show_file_list(10);
    
    // 按文件类型修改所有权
    printf("\n4. 按文件类型修改所有权:\n");
    struct passwd* test_user = getpwnam("nobody");
    struct group* test_group = getgrnam("nobody");
    uid_t test_uid = test_user ? test_user->pw_uid : 65534;
    gid_t test_gid = test_group ? test_group->gr_gid : 65534;
    
    change_ownership_by_type(base_dirfd, ".txt", test_uid, test_gid);
    change_ownership_by_type(base_dirfd, ".jpg", test_uid + 1, test_gid + 1);
    
    // 按目录修改所有权
    printf("\n5. 按目录修改所有权:\n");
    batch_change_ownership(base_dirfd, test_uid + 2, test_gid + 2, "documents/");
    batch_change_ownership(base_dirfd, test_uid + 3, test_gid + 3, "images/");
    
    // 显示最终结果
    printf("\n6. 最终文件所有权状态:\n");
    show_file_list(20);
    
    // 统计修改情况
    int changed_count = 0;
    int unchanged_count = 0;
    for (int i = 0; i < file_count; i++) {
        if (file_list[i].changed) {
            changed_count++;
        } else {
            unchanged_count++;
        }
    }
    
    printf("统计信息:\n");
    printf("  已修改文件: %d\n", changed_count);
    printf("  未修改文件: %d\n", unchanged_count);
    printf("  总文件数: %d\n", file_count);
    
    // 清理测试文件
    printf("\n7. 清理测试文件:\n");
    
    // 由于是递归目录,需要特殊清理
    DIR* dir = opendir(test_dir);
    if (dir) {
        struct dirent* entry;
        while ((entry = readdir(dir)) != NULL) {
            if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
                char full_path[512];
                snprintf(full_path, sizeof(full_path), "%s/%s", test_dir, entry->d_name);
                
                if (entry->d_type == DT_DIR) {
                    // 递归删除目录
                    DIR* subdir = opendir(full_path);
                    if (subdir) {
                        struct dirent* subentry;
                        while ((subentry = readdir(subdir)) != NULL) {
                            if (strcmp(subentry->d_name, ".") != 0 && 
                                strcmp(subentry->d_name, "..") != 0) {
                                char sub_path[512];
                                snprintf(sub_path, sizeof(sub_path), "%s/%s", 
                                         full_path, subentry->d_name);
                                unlink(sub_path);
                            }
                        }
                        closedir(subdir);
                        rmdir(full_path);
                    }
                } else {
                    unlink(full_path);
                }
            }
        }
        closedir(dir);
        rmdir(test_dir);
        printf("清理完成\n");
    }
    
    close(base_dirfd);
    
    printf("\n=== 批量文件所有权管理演示完成 ===\n");
    
    return 0;
}

示例4:高级所有权管理和安全特性

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

// 安全的所有权修改结构
typedef struct {
    char path[512];
    uid_t current_uid;
    gid_t current_gid;
    uid_t target_uid;
    gid_t target_gid;
    mode_t current_mode;
    time_t modify_time;
    int success;
    char error_msg[128];
} ownership_change_record_t;

#define MAX_RECORDS 1000
ownership_change_record_t change_records[MAX_RECORDS];
int record_count = 0;

// 创建安全测试环境
int create_secure_test_environment() {
    printf("创建安全测试环境...\n");
    
    // 创建测试目录
    if (mkdir("secure_ownership_test", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
        return -1;
    }
    
    // 创建敏感文件
    const char* sensitive_files[] = {
        "secure_ownership_test/config.txt",
        "secure_ownership_test/private.key",
        "secure_ownership_test/database.db",
        "secure_ownership_test/logs/app.log"
    };
    
    // 创建日志目录
    if (mkdir("secure_ownership_test/logs", 0755) == -1 && errno != EEXIST) {
        perror("创建日志目录失败");
    }
    
    // 创建敏感文件
    for (int i = 0; i < 4; i++) {
        int fd = open(sensitive_files[i], O_CREAT | O_WRONLY | O_TRUNC, 
                     (i == 1) ? 0600 : 0644);  // private.key使用更严格的权限
        if (fd != -1) {
            const char* content = (i == 1) ? "PRIVATE KEY DATA" : "SENSITIVE DATA";
            write(fd, content, strlen(content));
            close(fd);
            printf("创建敏感文件: %s\n", sensitive_files[i]);
        }
    }
    
    // 创建符号链接
    if (symlink("config.txt", "secure_ownership_test/link_to_config") == 0) {
        printf("创建符号链接\n");
    }
    
    return 0;
}

// 安全的所有权修改函数
int secure_chownat(int dirfd, const char* pathname, uid_t owner, gid_t group, 
                   int flags, const char* reason) {
    if (record_count >= MAX_RECORDS) {
        fprintf(stderr, "记录缓冲区已满\n");
        return -1;
    }
    
    ownership_change_record_t* record = &change_records[record_count];
    strncpy(record->path, pathname, sizeof(record->path) - 1);
    record->target_uid = owner;
    record->target_gid = group;
    record->modify_time = time(NULL);
    record->success = 0;
    record->error_msg[0] = '\0';
    
    // 获取当前状态
    struct stat current_stat;
    if (fstatat(dirfd, pathname, &current_stat, 
                (flags & AT_SYMLINK_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) == 0) {
        record->current_uid = current_stat.st_uid;
        record->current_gid = current_stat.st_gid;
        record->current_mode = current_stat.st_mode;
    } else {
        snprintf(record->error_msg, sizeof(record->error_msg), 
                 "获取当前状态失败: %s", strerror(errno));
        record_count++;
        return -1;
    }
    
    // 执行所有权修改
    if (fchownat(dirfd, pathname, owner, group, flags) == 0) {
        record->success = 1;
        printf("✓ 安全修改所有权: %s (%s)\n", pathname, reason ? reason : "无原因");
    } else {
        snprintf(record->error_msg, sizeof(record->error_msg), 
                 "%s", strerror(errno));
        printf("✗ 修改所有权失败: %s (%s) - %s\n", 
               pathname, reason ? reason : "无原因", strerror(errno));
    }
    
    record_count++;
    return record->success ? 0 : -1;
}

// 验证所有权修改的安全性
int verify_ownership_change(int dirfd, const char* pathname, 
                           uid_t expected_uid, gid_t expected_gid) {
    struct stat verify_stat;
    if (fstatat(dirfd, pathname, &verify_stat, 0) == 0) {
        if (verify_stat.st_uid == expected_uid && verify_stat.st_gid == expected_gid) {
            printf("✓ 所有权验证通过: %s (UID:%d, GID:%d)\n", 
                   pathname, (int)expected_uid, (int)expected_gid);
            return 1;
        } else {
            printf("✗ 所有权验证失败: %s (期望UID:%d,GID:%d 实际UID:%d,GID:%d)\n", 
                   pathname, (int)expected_uid, (int)expected_gid,
                   (int)verify_stat.st_uid, (int)verify_stat.st_gid);
            return 0;
        }
    } else {
        printf("✗ 验证失败,无法获取文件状态: %s\n", strerror(errno));
        return 0;
    }
}

// 显示修改记录
void show_change_records() {
    printf("\n=== 所有权修改记录 ===\n");
    printf("%-30s %-8s %-8s %-8s %-8s %-10s %s\n",
           "路径", "原UID", "原GID", "新UID", "新GID", "状态", "时间");
    printf("%-30s %-8s %-8s %-8s %-8s %-10s %s\n",
           "----", "------", "------", "------", "------", "----", "----");
    
    for (int i = 0; i < record_count; i++) {
        const ownership_change_record_t* record = &change_records[i];
        char time_str[32];
        strftime(time_str, sizeof(time_str), "%H:%M:%S", 
                 localtime(&record->modify_time));
        
        printf("%-30s %-8d %-8d %-8d %-8d %-10s %s\n",
               record->path,
               (int)record->current_uid, (int)record->current_gid,
               (int)record->target_uid, (int)record->target_gid,
               record->success ? "成功" : "失败",
               time_str);
        
        if (!record->success && record->error_msg[0] != '\0') {
            printf("  错误信息: %s\n", record->error_msg);
        }
    }
    printf("========================\n\n");
}

// 模拟权限检查
int check_permission_for_chown(uid_t current_uid, uid_t file_uid) {
    // Root用户可以修改任何文件的所有权
    if (current_uid == 0) {
        return 1;
    }
    
    // 文件所有者可以修改自己文件的组
    if (current_uid == file_uid) {
        return 1;
    }
    
    // 需要CAP_CHOWN能力的其他情况
    printf("警告: 非root用户修改他人文件所有权可能失败\n");
    return 0;
}

int main() {
    printf("=== 高级所有权管理和安全特性示例 ===\n");
    
    uid_t current_uid = getuid();
    printf("当前用户ID: %d (%s)\n", (int)current_uid, 
           current_uid == 0 ? "root" : "非root");
    
    // 创建安全测试环境
    printf("\n1. 创建安全测试环境:\n");
    if (create_secure_test_environment() == -1) {
        exit(EXIT_FAILURE);
    }
    
    // 打开测试目录
    int test_dirfd = open("secure_ownership_test", O_RDONLY);
    if (test_dirfd == -1) {
        perror("打开测试目录失败");
        exit(EXIT_FAILURE);
    }
    
    // 显示初始状态
    printf("\n2. 初始文件状态:\n");
    DIR* dir = fdopendir(dup(test_dirfd));
    if (dir) {
        struct dirent* entry;
        while ((entry = readdir(dir)) != NULL) {
            if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
                struct stat file_stat;
                if (fstatat(test_dirfd, entry->d_name, &file_stat, AT_SYMLINK_NOFOLLOW) == 0) {
                    struct passwd* pwd = getpwuid(file_stat.st_uid);
                    struct group* grp = getgrgid(file_stat.st_gid);
                    printf("  %s: UID=%d(%s) GID=%d(%s) 权限=%o\n",
                           entry->d_name,
                           (int)file_stat.st_uid, pwd ? pwd->pw_name : "unknown",
                           (int)file_stat.st_gid, grp ? grp->gr_name : "unknown",
                           file_stat.st_mode & 0777);
                }
            }
        }
        closedir(dir);
    }
    
    // 演示安全的所有权修改
    printf("\n3. 安全所有权修改演示:\n");
    
    // 获取测试用户
    struct passwd* test_user1 = getpwnam("nobody");
    struct passwd* test_user2 = getpwnam("daemon");
    uid_t user1_uid = test_user1 ? test_user1->pw_uid : 65534;
    uid_t user2_uid = test_user2 ? test_user2->pw_uid : 1;
    
    struct group* test_group1 = getgrnam("nobody");
    struct group* test_group2 = getgrnam("daemon");
    gid_t group1_gid = test_group1 ? test_group1->gr_gid : 65534;
    gid_t group2_gid = test_group2 ? test_group2->gr_gid : 1;
    
    // 安全修改配置文件所有权
    secure_chownat(test_dirfd, "config.txt", user1_uid, group1_gid, 0, 
                   "配置文件所有权转移");
    verify_ownership_change(test_dirfd, "config.txt", user1_uid, group1_gid);
    
    // 安全修改日志文件所有权
    secure_chownat(test_dirfd, "logs/app.log", user2_uid, group2_gid, 0, 
                   "日志文件所有权转移");
    verify_ownership_change(test_dirfd, "logs/app.log", user2_uid, group2_gid);
    
    // 演示符号链接处理
    printf("\n4. 符号链接处理演示:\n");
    
    // 获取符号链接当前状态
    struct stat link_stat, target_stat;
    if (fstatat(test_dirfd, "link_to_config", &link_stat, AT_SYMLINK_NOFOLLOW) == 0 &&
        fstatat(test_dirfd, "link_to_config", &target_stat, 0) == 0) {
        printf("符号链接状态:\n");
        printf("  链接文件: UID=%d, GID=%d\n", 
               (int)link_stat.st_uid, (int)link_stat.st_gid);
        printf("  目标文件: UID=%d, GID=%d\n", 
               (int)target_stat.st_uid, (int)target_stat.st_gid);
    }
    
    // 默认行为:修改目标文件
    secure_chownat(test_dirfd, "link_to_config", user1_uid + 1, group1_gid + 1, 0, 
                   "修改符号链接目标");
    verify_ownership_change(test_dirfd, "config.txt", user1_uid + 1, group1_gid + 1);
    
    // 使用AT_SYMLINK_NOFOLLOW:修改链接本身
    secure_chownat(test_dirfd, "link_to_config", user2_uid + 1, group2_gid + 1, 
                   AT_SYMLINK_NOFOLLOW, "修改符号链接本身");
    
    // 验证符号链接状态
    if (fstatat(test_dirfd, "link_to_config", &link_stat, AT_SYMLINK_NOFOLLOW) == 0) {
        printf("修改后链接文件所有权: UID=%d, GID=%d\n", 
               (int)link_stat.st_uid, (int)link_stat.st_gid);
    }
    
    // 演示错误处理和权限检查
    printf("\n5. 错误处理和权限检查演示:\n");
    
    // 尝试修改不存在的文件
    secure_chownat(test_dirfd, "nonexistent.txt", 0, 0, 0, "测试不存在文件");
    
    // 尝试使用无效的UID/GID
    secure_chownat(test_dirfd, "config.txt", 999999, 999999, 0, "测试无效UID/GID");
    
    // 尝试在只读文件系统上修改(如果可能)
    // 这个测试需要特殊的环境设置
    
    // 显示所有修改记录
    show_change_records();
    
    // 统计成功和失败的修改
    int success_count = 0, failure_count = 0;
    for (int i = 0; i < record_count; i++) {
        if (change_records[i].success) {
            success_count++;
        } else {
            failure_count++;
        }
    }
    
    printf("修改统计:\n");
    printf("  成功: %d\n", success_count);
    printf("  失败: %d\n", failure_count);
    printf("  总计: %d\n", record_count);
    
    // 清理测试环境
    printf("\n6. 清理测试环境:\n");
    
    // 删除文件
    const char* test_files[] = {
        "secure_ownership_test/link_to_config",
        "secure_ownership_test/config.txt",
        "secure_ownership_test/private.key",
        "secure_ownership_test/database.db",
        "secure_ownership_test/logs/app.log"
    };
    
    for (int i = 0; i < 5; i++) {
        if (unlink(test_files[i]) == 0) {
            printf("删除文件: %s\n", test_files[i]);
        }
    }
    
    // 删除目录
    rmdir("secure_ownership_test/logs");
    rmdir("secure_ownership_test");
    printf("删除测试目录\n");
    
    close(test_dirfd);
    
    printf("\n=== 高级所有权管理演示完成 ===\n");
    
    return 0;
}

编译和运行

# 编译示例1
sudo gcc -o fchownat_example1 fchownat_example1.c
sudo ./fchownat_example1

# 编译示例2
sudo gcc -o fchownat_example2 fchownat_example2.c
sudo ./fchownat_example2

# 编译示例3
sudo gcc -o fchownat_example3 fchownat_example3.c
sudo ./fchownat_example3

# 编译示例4
sudo gcc -o fchownat_example4 fchownat_example4.c
sudo ./fchownat_example4

重要注意事项

  1. 权限要求: 修改文件所有权通常需要root权限或CAP_CHOWN能力
  2. 相对路径: 使用dirfd参数支持相对路径操作
  3. 符号链接: 默认修改目标文件,使用AT_SYMLINK_NOFOLLOW修改链接本身
  4. 错误处理: 必须检查返回值并适当处理错误
  5. 安全性: 验证输入参数以防止安全问题
  6. 原子性: fchownat操作是原子的
  7. 审计: 建议记录所有权修改操作

最佳实践

  1. 权限检查: 在修改前检查必要的权限
  2. 输入验证: 验证路径和ID参数的有效性
  3. 错误处理: 完善的错误处理和日志记录
  4. 原子操作: 利用fchownat的原子性特性
  5. 安全审计: 记录所有权修改操作
  6. 批量操作: 使用相对路径提高批量操作效率
  7. 符号链接: 明确处理符号链接的行为

通过这些示例,你可以理解fchownat在文件所有权管理方面的强大功能,它为Linux系统提供了灵活、安全的文件权限控制能力,特别适用于系统管理、安全控制和批量文件操作等场景。

发表在 linux文章 | 留下评论

fanotify_mark系统调用及示例

fanotify_mark – 文件系统通知标记

函数介绍

fanotify_mark是一个Linux系统调用,用于在fanotify实例上添加、修改或删除文件系统对象的监视标记。fanotify是Linux的文件系统通知机制,可以监控文件访问和修改事件。

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

函数原型

#include <sys/fanotify.h>
#include <fcntl.h>
#include <unistd.h>

int fanotify_mark(int fanotify_fd, unsigned int flags,
                  uint64_t mask, int dirfd, const char *pathname);

功能

在fanotify文件描述符上设置监视标记,用于监控指定文件系统对象的事件。

参数

  • int fanotify_fd: fanotify实例的文件描述符(通过fanotify_init获得)
  • unsigned int flags: 操作标志
    • FAN_MARK_ADD: 添加标记
    • FAN_MARK_REMOVE: 删除标记
    • FAN_MARK_FLUSH: 刷新所有标记
    • FAN_MARK_DONT_FOLLOW: 不跟随符号链接
    • FAN_MARK_ONLYDIR: 只监视目录
    • FAN_MARK_MOUNT: 监视整个挂载点
    • FAN_MARK_FILESYSTEM: 监视整个文件系统
  • uint64_t mask: 事件掩码,指定要监视的事件类型
    • FAN_ACCESS: 文件被访问
    • FAN_OPEN: 文件被打开
    • FAN_CLOSE_WRITE: 可写文件被关闭
    • FAN_CLOSE_NOWRITE: 只读文件被关闭
    • FAN_MODIFY: 文件被修改
    • FAN_OPEN_PERM: 文件打开权限检查
    • FAN_ACCESS_PERM: 文件访问权限检查
  • int dirfd: 目录文件描述符(用于相对路径)
    • AT_FDCWD: 使用当前工作目录
  • const char *pathname: 要监视的文件或目录路径

返回值

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

特殊限制

  • 需要Linux 2.6.39以上内核支持
  • 需要CAP_SYS_ADMIN能力或特定权限
  • 某些操作需要root权限

相似函数

  • fanotify_init(): 初始化fanotify实例
  • inotify_add_watch(): inotify监视添加
  • read(): 读取fanotify事件

示例代码

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

// 全局变量用于信号处理
volatile sig_atomic_t keep_running = 1;

// 信号处理函数
void signal_handler(int sig) {
    printf("\n接收到信号 %d,准备退出...\n", sig);
    keep_running = 0;
}

// 事件类型转换为字符串
const char* event_type_to_string(uint64_t mask) {
    if (mask & FAN_ACCESS) return "ACCESS";
    if (mask & FAN_OPEN) return "OPEN";
    if (mask & FAN_CLOSE_WRITE) return "CLOSE_WRITE";
    if (mask & FAN_CLOSE_NOWRITE) return "CLOSE_NOWRITE";
    if (mask & FAN_MODIFY) return "MODIFY";
    if (mask & FAN_OPEN_PERM) return "OPEN_PERM";
    if (mask & FAN_ACCESS_PERM) return "ACCESS_PERM";
    return "UNKNOWN";
}

int main() {
    int fan_fd, fd, result;
    char buffer[4096];
    
    printf("=== Fanotify_mark 函数示例 ===\n");
    
    // 设置信号处理
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    // 创建测试文件
    fd = open("test_fanotify.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd != -1) {
        write(fd, "test content for fanotify", 25);
        close(fd);
        printf("创建测试文件: test_fanotify.txt\n");
    }
    
    // 初始化fanotify实例
    fan_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY);
    if (fan_fd == -1) {
        if (errno == EPERM) {
            printf("权限不足初始化fanotify: %s\n", strerror(errno));
            printf("说明: 需要CAP_SYS_ADMIN能力或root权限\n");
            unlink("test_fanotify.txt");
            exit(EXIT_FAILURE);
        } else {
            perror("fanotify_init失败");
            unlink("test_fanotify.txt");
            exit(EXIT_FAILURE);
        }
    }
    printf("成功初始化fanotify实例,文件描述符: %d\n", fan_fd);
    
    // 示例2: 添加监视标记
    printf("\n示例2: 添加监视标记\n");
    
    // 监视当前目录下的测试文件
    result = fanotify_mark(fan_fd, FAN_MARK_ADD, 
                          FAN_OPEN | FAN_CLOSE | FAN_ACCESS | FAN_MODIFY,
                          AT_FDCWD, "test_fanotify.txt");
    if (result == -1) {
        perror("添加监视标记失败");
    } else {
        printf("成功添加监视标记到 test_fanotify.txt\n");
        printf("监视事件: OPEN | CLOSE | ACCESS | MODIFY\n");
    }
    
    // 示例3: 监视目录
    printf("\n示例3: 监视目录\n");
    
    // 创建测试目录
    if (mkdir("fanotify_test_dir", 0755) == -1 && errno != EEXIST) {
        perror("创建测试目录失败");
    } else {
        printf("创建测试目录: fanotify_test_dir\n");
        
        // 监视整个目录
        result = fanotify_mark(fan_fd, FAN_MARK_ADD,
                              FAN_OPEN | FAN_CLOSE | FAN_ACCESS,
                              AT_FDCWD, "fanotify_test_dir");
        if (result == 0) {
            printf("成功添加目录监视标记\n");
        }
        
        // 在目录中创建文件进行测试
        fd = open("fanotify_test_dir/test_file.txt", O_CREAT | O_WRONLY, 0644);
        if (fd != -1) {
            write(fd, "test file in directory", 22);
            close(fd);
            printf("在目录中创建测试文件\n");
        }
    }
    
    // 示例4: 不同的标记操作
    printf("\n示例4: 不同的标记操作\n");
    
    // FAN_MARK_REMOVE - 删除标记
    result = fanotify_mark(fan_fd, FAN_MARK_REMOVE,
                          FAN_OPEN | FAN_CLOSE,
                          AT_FDCWD, "test_fanotify.txt");
    if (result == 0) {
        printf("成功删除部分监视标记\n");
    } else {
        printf("删除监视标记失败: %s\n", strerror(errno));
    }
    
    // FAN_MARK_FLUSH - 刷新所有标记
    result = fanotify_mark(fan_fd, FAN_MARK_FLUSH, 0, 0, NULL);
    if (result == 0) {
        printf("成功刷新所有监视标记\n");
    }
    
    // 重新添加标记进行后续测试
    fanotify_mark(fan_fd, FAN_MARK_ADD,
                  FAN_OPEN | FAN_CLOSE | FAN_ACCESS,
                  AT_FDCWD, "test_fanotify.txt");
    
    // 示例5: 错误处理演示
    printf("\n示例5: 错误处理演示\n");
    
    // 使用无效的fanotify文件描述符
    result = fanotify_mark(999, FAN_MARK_ADD, FAN_OPEN, AT_FDCWD, "test.txt");
    if (result == -1) {
        if (errno == EBADF) {
            printf("无效fanotify文件描述符错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的标志
    result = fanotify_mark(fan_fd, 0x1000, FAN_OPEN, AT_FDCWD, "test.txt");
    if (result == -1) {
        if (errno == EINVAL) {
            printf("无效标志错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 监视不存在的文件
    result = fanotify_mark(fan_fd, FAN_MARK_ADD, FAN_OPEN, AT_FDCWD, "nonexistent.txt");
    if (result == -1) {
        if (errno == ENOENT) {
            printf("监视不存在文件错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 示例6: 权限相关操作
    printf("\n示例6: 权限相关操作\n");
    
    printf("fanotify权限要求:\n");
    printf("1. 需要CAP_SYS_ADMIN能力\n");
    printf("2. 或者root权限\n");
    printf("3. 某些操作可能需要额外权限\n");
    printf("4. 文件访问权限仍然适用\n\n");
    
    // 示例7: 实际应用场景演示
    printf("示例7: 实际应用场景演示\n");
    
    printf("文件完整性监控系统:\n");
    printf("int setup_file_monitoring(const char* path) {\n");
    printf("    int fan_fd = fanotify_init(FAN_CLASS_CONTENT, O_RDONLY);\n");
    printf("    if (fan_fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 监视文件修改\n");
    printf("    fanotify_mark(fan_fd, FAN_MARK_ADD, \n");
    printf("                  FAN_CLOSE_WRITE | FAN_MODIFY,\n");
    printf("                  AT_FDCWD, path);\n");
    printf("    \n");
    printf("    return fan_fd;\n");
    printf("}\n\n");
    
    printf("实时备份系统:\n");
    printf("int setup_backup_monitoring(const char* directory) {\n");
    printf("    int fan_fd = fanotify_init(FAN_CLASS_CONTENT, O_RDONLY);\n");
    printf("    if (fan_fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 监视目录及其子目录的所有修改\n");
    printf("    fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT,\n");
    printf("                  FAN_CLOSE_WRITE | FAN_MOVED_TO | FAN_CREATE,\n");
    printf("                  AT_FDCWD, directory);\n");
    printf("    \n");
    printf("    return fan_fd;\n");
    printf("}\n\n");
    
    // 示例8: 事件处理演示
    printf("示例8: 事件处理演示\n");
    
    printf("典型的fanotify事件处理循环:\n");
    printf("while (keep_running) {\n");
    printf("    ssize_t len = read(fan_fd, buffer, sizeof(buffer));\n");
    printf("    if (len == -1 && errno != EAGAIN) {\n");
    printf("        perror(\"读取fanotify事件失败\");\n");
    printf("        break;\n");
    printf("    }\n");
    printf("    \n");
    printf("    struct fanotify_event_metadata *metadata;\n");
    printf("    for (metadata = (void*)buffer;\n");
    printf("         FAN_EVENT_OK(metadata, len);\n");
    printf("         metadata = FAN_EVENT_NEXT(metadata, len)) {\n");
    printf("        \n");
    printf("        // 处理事件\n");
    printf("        printf(\"事件: %%s, 文件描述符: %%d\\n\",\n");
    printf("               event_type_to_string(metadata->mask),\n");
    printf("               metadata->fd);\n");
    printf("        \n");
    printf("        // 关闭事件提供的文件描述符\n");
    printf("        close(metadata->fd);\n");
    printf("    }\n");
    printf("}\n\n");
    
    // 示例9: 不同监视类型说明
    printf("示例9: 不同监视类型说明\n");
    
    printf("FAN_MARK_ADD:\n");
    printf("  - 添加新的监视标记\n");
    printf("  - 可以与现有标记共存\n\n");
    
    printf("FAN_MARK_REMOVE:\n");
    printf("  - 删除指定的监视标记\n");
    printf("  - 只删除指定的事件类型\n\n");
    
    printf("FAN_MARK_FLUSH:\n");
    printf("  - 删除所有监视标记\n");
    printf("  - 清空整个监视设置\n\n");
    
    printf("FAN_MARK_DONT_FOLLOW:\n");
    printf("  - 不跟随符号链接\n");
    printf("  - 直接监视符号链接本身\n\n");
    
    printf("FAN_MARK_ONLYDIR:\n");
    printf("  - 只监视目录\n");
    printf("  - 如果路径不是目录则失败\n\n");
    
    printf("FAN_MARK_MOUNT:\n");
    printf("  - 监视整个挂载点\n");
    printf("  - 包括挂载点下的所有文件\n\n");
    
    printf("FAN_MARK_FILESYSTEM:\n");
    printf("  - 监视整个文件系统\n");
    printf("  - 包括所有挂载的实例\n\n");
    
    // 示例10: 事件类型说明
    printf("示例10: 事件类型说明\n");
    
    printf("基本事件类型:\n");
    printf("FAN_ACCESS: 文件被访问(读取)\n");
    printf("FAN_OPEN: 文件被打开\n");
    printf("FAN_CLOSE_WRITE: 可写文件被关闭\n");
    printf("FAN_CLOSE_NOWRITE: 只读文件被关闭\n");
    printf("FAN_MODIFY: 文件内容被修改\n\n");
    
    printf("权限检查事件类型:\n");
    printf("FAN_OPEN_PERM: 文件打开权限检查(需要响应)\n");
    printf("FAN_ACCESS_PERM: 文件访问权限检查(需要响应)\n\n");
    
    printf("创建和删除事件:\n");
    printf("FAN_CREATE: 文件或目录被创建\n");
    printf("FAN_DELETE: 文件或目录被删除\n");
    printf("FAN_MOVED_FROM: 文件被移出\n");
    printf("FAN_MOVED_TO: 文件被移入\n\n");
    
    // 示例11: 性能和资源考虑
    printf("示例11: 性能和资源考虑\n");
    
    printf("fanotify性能特点:\n");
    printf("1. 内核级别的通知机制\n");
    printf("2. 比轮询方式更高效\n");
    printf("3. 支持大量文件监视\n");
    printf("4. 低延迟事件通知\n\n");
    
    printf("资源使用考虑:\n");
    printf("1. 每个监视标记消耗内核资源\n");
    printf("2. 事件队列有大小限制\n");
    printf("3. 需要及时处理事件\n");
    printf("4. 避免监视过多文件\n\n");
    
    printf("优化建议:\n");
    printf("1. 合理选择监视的事件类型\n");
    printf("2. 及时处理和响应事件\n");
    printf("3. 避免不必要的监视标记\n");
    printf("4. 使用适当的fanotify类\n");
    printf("5. 监控系统资源使用\n\n");
    
    // 示例12: 安全考虑
    printf("示例12: 安全考虑\n");
    
    printf("fanotify安全特性:\n");
    printf("1. 需要特殊权限才能使用\n");
    printf("2. 不能监视没有访问权限的文件\n");
    printf("3. 提供权限检查事件类型\n");
    printf("4. 事件包含文件描述符便于验证\n\n");
    
    printf("安全使用建议:\n");
    printf("1. 验证事件中的文件描述符\n");
    printf("2. 谨慎使用权限检查事件\n");
    printf("3. 限制监视的文件范围\n");
    printf("4. 及时响应权限检查事件\n");
    printf("5. 避免泄露敏感文件信息\n\n");
    
    // 清理资源
    printf("清理测试资源...\n");
    close(fan_fd);
    unlink("test_fanotify.txt");
    unlink("fanotify_test_dir/test_file.txt");
    rmdir("fanotify_test_dir");
    
    printf("\n总结:\n");
    printf("fanotify_mark是fanotify文件系统监控的核心函数\n");
    printf("支持灵活的监视标记管理和多种监视类型\n");
    printf("需要适当的权限才能使用\n");
    printf("适用于文件完整性监控、实时备份等场景\n");
    printf("是Linux系统管理和安全监控的重要工具\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

fanotify_init系统调用及示例

fanotify_init – 初始化fanotify监控实例

fanotify_init是一个Linux系统调用,用于创建和初始化fanotify文件系统监控实例。fanotify是Linux内核提供的高级文件系统事件监控机制,可以监控文件访问、修改等事件。

函数原型

#include <sys/fanotify.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <fcntl.h>

int fanotify_init(unsigned int flags, unsigned int event_f_flags);

功能

创建一个新的fanotify监控实例,返回文件描述符用于后续的监控操作。

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

参数

  • unsigned int flags: fanotify实例标志
    • FAN_CLASS_NOTIF: 仅通知类事件
    • FAN_CLASS_CONTENT: 内容监控类事件
    • FAN_CLASS_PRE_CONTENT: 预内容监控类事件
    • FAN_CLOEXEC: 设置执行时关闭标志
    • FAN_NONBLOCK: 设置非阻塞模式
    • FAN_UNLIMITED_QUEUE: 无限制队列大小
    • FAN_UNLIMITED_MARKS: 无限制标记数量
  • unsigned int event_f_flags: 事件文件描述符标志
    • O_RDONLY: 只读打开事件文件
    • O_WRONLY: 只写打开事件文件
    • O_RDWR: 读写打开事件文件
    • O_LARGEFILE: 支持大文件
    • O_CLOEXEC: 执行时关闭

返回值

  • 成功时返回fanotify文件描述符(非负整数)
  • 失败时返回-1,并设置errno

特殊限制

  • 需要Linux 2.6.39以上内核支持
  • 需要CAP_SYS_ADMIN能力
  • 某些标志需要特定内核版本

相似函数

  • fanotify_mark(): 添加监控标记
  • read(): 读取fanotify事件
  • inotify_init(): 旧版文件监控机制

示例代码

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

// 系统调用包装
static int fanotify_init_wrapper(unsigned int flags, unsigned int event_f_flags) {
    return syscall(__NR_fanotify_init, flags, event_f_flags);
}

// 读取fanotify事件
int read_fanotify_events(int fanotify_fd) {
    char buffer[4096];
    ssize_t len;
    struct fanotify_event_metadata *metadata;
    
    len = read(fanotify_fd, buffer, sizeof(buffer));
    if (len == -1) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            return 0; // 非阻塞模式下无事件
        }
        perror("读取fanotify事件失败");
        return -1;
    }
    
    metadata = (struct fanotify_event_metadata *)buffer;
    while (FAN_EVENT_OK(metadata, len)) {
        if (metadata->vers != FANOTIFY_METADATA_VERSION) {
            fprintf(stderr, "不支持的元数据版本\n");
            return -1;
        }
        
        printf("事件: fd=%d, mask=0x%x, pid=%d\n", 
               metadata->fd, metadata->mask, metadata->pid);
        
        // 获取文件路径
        if (metadata->fd != FAN_NOFD) {
            char path[PATH_MAX];
            char proc_path[64];
            snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", metadata->fd);
            
            ssize_t path_len = readlink(proc_path, path, sizeof(path) - 1);
            if (path_len != -1) {
                path[path_len] = '\0';
                printf("  文件路径: %s\n", path);
            }
            
            close(metadata->fd);
        }
        
        metadata = FAN_EVENT_NEXT(metadata, len);
    }
    
    return 0;
}

int main() {
    int fanotify_fd;
    int result;
    
    printf("=== Fanotify_init 函数示例 ===\n");
    printf("当前用户 UID: %d\n", getuid());
    printf("当前有效 UID: %d\n", geteuid());
    
    // 示例1: 基本使用
    printf("\n示例1: 基本使用\n");
    
    // 需要root权限或CAP_SYS_ADMIN能力
    fanotify_fd = fanotify_init_wrapper(FAN_CLASS_NOTIF, O_RDONLY);
    if (fanotify_fd == -1) {
        if (errno == EPERM) {
            printf("权限不足创建fanotify实例: %s\n", strerror(errno));
            printf("说明: 需要root权限或CAP_SYS_ADMIN能力\n");
        } else {
            printf("创建fanotify实例失败: %s\n", strerror(errno));
        }
        printf("在root权限下重新运行此程序以查看完整功能\n");
        return 0;
    }
    
    printf("成功创建fanotify实例,文件描述符: %d\n", fanotify_fd);
    
    // 检查文件描述符属性
    int flags = fcntl(fanotify_fd, F_GETFD);
    if (flags != -1) {
        printf("fanotify文件描述符验证成功\n");
    }
    
    // 关闭fanotify实例
    close(fanotify_fd);
    printf("关闭fanotify实例\n");
    
    // 示例2: 使用不同标志
    printf("\n示例2: 使用不同标志\n");
    
    // 使用FAN_CLOEXEC标志
    fanotify_fd = fanotify_init_wrapper(FAN_CLASS_NOTIF | FAN_CLOEXEC, O_RDONLY);
    if (fanotify_fd != -1) {
        printf("使用FAN_CLOEXEC标志创建成功: %d\n", fanotify_fd);
        
        // 验证CLOEXEC标志
        flags = fcntl(fanotify_fd, F_GETFD);
        if (flags != -1 && (flags & FD_CLOEXEC)) {
            printf("FAN_CLOEXEC标志已正确设置\n");
        }
        
        close(fanotify_fd);
    }
    
    // 使用FAN_NONBLOCK标志
    fanotify_fd = fanotify_init_wrapper(FAN_CLASS_NOTIF | FAN_NONBLOCK, O_RDONLY);
    if (fanotify_fd != -1) {
        printf("使用FAN_NONBLOCK标志创建成功: %d\n", fanotify_fd);
        
        // 测试非阻塞特性
        char buffer[1024];
        ssize_t result = read(fanotify_fd, buffer, sizeof(buffer));
        if (result == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("非阻塞读取正确返回EAGAIN\n");
            }
        }
        
        close(fanotify_fd);
    }
    
    // 示例3: 不同类别的fanotify实例
    printf("\n示例3: 不同类别的fanotify实例\n");
    
    printf("fanotify类别说明:\n");
    
    // FAN_CLASS_NOTIF - 通知类
    fanotify_fd = fanotify_init_wrapper(FAN_CLASS_NOTIF, O_RDONLY);
    if (fanotify_fd != -1) {
        printf("FAN_CLASS_NOTIF (通知类) 创建成功\n");
        printf("  - 仅接收通知事件\n");
        printf("  - 不需要响应事件\n");
        printf("  - 权限要求较低\n");
        close(fanotify_fd);
    }
    
    // FAN_CLASS_CONTENT - 内容监控类
    fanotify_fd = fanotify_init_wrapper(FAN_CLASS_CONTENT, O_RDONLY);
    if (fanotify_fd != -1) {
        printf("FAN_CLASS_CONTENT (内容监控类) 创建成功\n");
        printf("  - 可以读取文件内容\n");
        printf("  - 需要更高权限\n");
        close(fanotify_fd);
    }
    
    // FAN_CLASS_PRE_CONTENT - 预内容监控类
    fanotify_fd = fanotify_init_wrapper(FAN_CLASS_PRE_CONTENT, O_RDONLY);
    if (fanotify_fd != -1) {
        printf("FAN_CLASS_PRE_CONTENT (预内容监控类) 创建成功\n");
        printf("  - 可以在操作前拦截\n");
        printf("  - 需要最高权限\n");
        close(fanotify_fd);
    }
    
    // 示例4: 错误处理演示
    printf("\n示例4: 错误处理演示\n");
    
    // 使用无效标志
    fanotify_fd = fanotify_init_wrapper(0x1000, O_RDONLY);
    if (fanotify_fd == -1) {
        if (errno == EINVAL) {
            printf("无效标志错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 使用无效的event_f_flags
    fanotify_fd = fanotify_init_wrapper(FAN_CLASS_NOTIF, 0x1000);
    if (fanotify_fd == -1) {
        if (errno == EINVAL) {
            printf("无效event_f_flags错误处理正确: %s\n", strerror(errno));
        }
    }
    
    // 示例5: 权限和能力说明
    printf("\n示例5: 权限和能力说明\n");
    
    printf("fanotify权限要求:\n");
    printf("FAN_CLASS_NOTIF:\n");
    printf("  - 需要对监控目录的读权限\n");
    printf("  - 一般用户可使用\n\n");
    
    printf("FAN_CLASS_CONTENT:\n");
    printf("  - 需要CAP_SYS_ADMIN能力\n");
    printf("  - 或root权限\n\n");
    
    printf("FAN_CLASS_PRE_CONTENT:\n");
    printf("  - 需要CAP_SYS_ADMIN能力\n");
    printf("  - 或root权限\n");
    printf("  - 最高权限级别\n\n");
    
    // 示例6: 实际使用场景
    printf("示例6: 实际使用场景\n");
    
    printf("fanotify典型应用场景:\n");
    printf("1. 文件完整性监控\n");
    printf("2. 入侵检测系统\n");
    printf("3. 文件访问审计\n");
    printf("4. 实时备份系统\n");
    printf("5. 安全策略执行\n");
    printf("6. 系统监控工具\n\n");
    
    // 示例监控代码框架
    printf("基本监控代码框架:\n");
    printf("int setup_file_monitoring(const char* path) {\n");
    printf("    int fanotify_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY);\n");
    printf("    if (fanotify_fd == -1) return -1;\n");
    printf("    \n");
    printf("    // 添加监控标记\n");
    printf("    if (fanotify_mark(fanotify_fd, FAN_MARK_ADD, \n");
    printf("                     FAN_OPEN | FAN_CLOSE_WRITE,\n");
    printf("                     AT_FDCWD, path) == -1) {\n");
    printf("        close(fanotify_fd);\n");
    printf("        return -1;\n");
    printf("    }\n");
    printf("    \n");
    printf("    return fanotify_fd;\n");
    printf("}\n\n");
    
    // 示例7: 事件处理循环
    printf("示例7: 事件处理循环\n");
    printf("fanotify事件处理循环示例:\n");
    printf("void monitor_files(int fanotify_fd) {\n");
    printf("    struct pollfd pfd = {.fd = fanotify_fd, .events = POLLIN};\n");
    printf("    \n");
    printf("    while (running) {\n");
    printf("        int ret = poll(&pfd, 1, 1000);  // 1秒超时\n");
    printf("        if (ret > 0 && (pfd.revents & POLLIN)) {\n");
    printf("            handle_fanotify_events(fanotify_fd);\n");
    printf("        }\n");
    printf("    }\n");
    printf("}\n\n");
    
    // 示例8: 与inotify的对比
    printf("示例8: 与inotify的对比\n");
    
    printf("fanotify vs inotify:\n");
    printf("fanotify优势:\n");
    printf("  - 可以监控整个文件系统\n");
    printf("  - 支持文件内容访问监控\n");
    printf("  - 可以获取文件描述符\n");
    printf("  - 支持权限检查\n");
    printf("  - 更高的性能\n\n");
    
    printf("inotify优势:\n");
    printf("  - 更广泛的内核支持\n");
    printf("  - 更简单的API\n");
    printf("  - 更低的权限要求\n");
    printf("  - 更好的可移植性\n\n");
    
    // 示例9: 性能和资源考虑
    printf("示例9: 性能和资源考虑\n");
    
    printf("fanotify性能特点:\n");
    printf("1. 内核级实现,性能优秀\n");
    printf("2. 批量事件处理\n");
    printf("3. 可配置的队列大小\n");
    printf("4. 低CPU开销\n\n");
    
    printf("资源使用考虑:\n");
    printf("1. 每个实例消耗内核资源\n");
    printf("2. 事件队列有大小限制\n");
    printf("3. 及时处理事件避免队列溢出\n");
    printf("4. 合理设置监控范围\n\n");
    
    // 示例10: 安全考虑
    printf("示例10: 安全考虑\n");
    
    printf("使用fanotify的安全注意事项:\n");
    printf("1. 需要适当权限\n");
    printf("2. 避免监控敏感目录\n");
    printf("3. 及时关闭不需要的实例\n");
    printf("4. 处理事件时注意安全\n");
    printf("5. 防止事件处理中的拒绝服务\n");
    printf("6. 限制监控的文件数量\n\n");
    
    printf("总结:\n");
    printf("fanotify_init是创建fanotify监控实例的基础函数\n");
    printf("提供了比inotify更强大的文件监控能力\n");
    printf("需要适当的权限和能力\n");
    printf("支持多种监控类别和标志\n");
    printf("是构建高级文件监控应用的重要工具\n");
    
    return 0;
}
发表在 linux文章 | 留下评论

sendmsg系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 recvmsg 和 sendmsg 函数,它们是功能最强大、最通用的套接字 I/O 函数,可以处理 read/writesend/recv 以及 sendto/recvfrom 的所有功能,并且还支持更高级的特性,如传输文件描述符、**发送和接收访问控制列表 **(ancillary data)。

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


1. 函数介绍

recvmsg 和 sendmsg 是 Linux 系统调用,它们提供了最灵活和最完整的套接字数据传输接口。它们是 read/writesend/recvsendto/recvfrom 的超集

  • sendmsg: 通过套接字发送数据。它允许你指定:
    • 要发送的数据(可以来自多个不连续的缓冲区,类似 writev)。
    • 目标地址(类似 sendto)。
    • 各种控制选项和标志。
    • 辅助数据(ancillary data),例如要通过 Unix 域套接字传递的文件描述符
  • recvmsg: 通过套接字接收数据。它允许你:
    • 将数据接收存储到多个不连续的缓冲区(类似 readv)。
    • 获取数据的来源地址(类似 recvfrom)。
    • 获取各种套接字状态信息。
    • 接收辅助数据(ancillary data),例如通过 Unix 域套接字传递的文件描述符

你可以把它们想象成一个多功能的包裹处理系统

  • 一个包裹(消息)可以包含主货物(常规数据)和特殊附件(辅助数据,如文件描述符)。
  • 主货物可以放在一个或多个箱子(缓冲区)里。
  • 包裹上贴有收件人地址(目标地址,用于发送)或发件人地址(源地址,用于接收)。
  • 包裹上还有特殊标记(标志位)指示如何处理它。

2. 函数原型

#include <sys/socket.h> // 必需

// 发送消息
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

// 接收消息
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

3. 功能

  • sendmsg: 根据 struct msghdr 结构中描述的所有信息(数据缓冲区、目标地址、辅助数据、标志)来构建并发送一个消息。
  • recvmsg: 从套接字接收一个消息,并将接收到的数据、源地址、辅助数据等信息填充到 struct msghdr 结构指向的缓冲区中。

4. 参数

这两个函数都通过一个核心的 struct msghdr 结构体来传递所有必要的信息。

struct msghdr 结构体

这是两个函数的核心,定义了消息的完整属性:

struct msghdr {
    void         *msg_name;       // 可选的地址 (struct sockaddr*)
    socklen_t     msg_namelen;    // 地址长度
    struct iovec *msg_iov;        // 缓冲区向量 (scatter/gather)
    size_t        msg_iovlen;     // 缓冲区向量的元素个数
    void         *msg_control;    // 辅助数据 (cmsghdr*)
    size_t        msg_controllen; // 辅助数据缓冲区大小
    int           msg_flags;      // 接收消息时的标志 (输出)
};
  • void *msg_name:
    • 发送时: 指向目标地址结构(如 sockaddr_in)的指针。对于面向连接的套接字(如 TCP),通常设为 NULL
    • 接收时: 指向用于存储源地址结构的缓冲区的指针。
  • socklen_t msg_namelen:
    • 发送时msg_name 指向的地址结构的大小。
    • 接收时输入时为 msg_name 缓冲区的大小;返回时为实际存储的地址结构的大小。
  • struct iovec *msg_iov: 这是一个 struct iovec 数组的指针,用于实现分散-聚集 I/O(scatter-gather I/O),即数据可以来自多个不连续的缓冲区(类似 readv/writev)。
    struct iovec 定义如下:struct iovec { void *iov_base; // 缓冲区起始地址 size_t iov_len; // 缓冲区长度 };
  • size_t msg_iovlenmsg_iov 数组中元素的个数。
  • void *msg_control: 指向用于发送或接收辅助数据(ancillary data)的缓冲区。辅助数据可以包含文件描述符、网络接口索引、IP 选项等。
  • size_t msg_controllen:
    • 发送时msg_control 缓冲区的大小。
    • 接收时输入时为 msg_control 缓冲区的大小;返回时为实际接收到的辅助数据的大小。
  • int msg_flags:
    • 发送时: 传递额外的发送标志(通常与 sendto/send 的 flags 参数相同)。
    • 接收时返回接收操作时应用的标志(例如,如果数据包被截断,可能会设置 MSG_TRUNC)。

函数参数

  • int sockfd: 一个有效的套接字文件描述符。
  • const struct msghdr *msg (sendmsg) / struct msghdr *msg (recvmsg): 指向描述消息属性的 msghdr 结构体的指针。
  • int flags: 控制发送或接收行为的标志位,与 send/recv/sendto/recvfrom 的 flags 参数类似。
    • 常见标志:MSG_DONTWAITMSG_PEEKMSG_WAITALLMSG_NOSIGNAL 等。

5. 返回值

  • 成功时:
    • 返回实际传输的字节数(即所有 msg_iov 缓冲区中数据的总和)。
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

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

  • send / sendto / recv / recvfromsendmsg/recvmsg 是这些函数的通用形式。
  • writev / readvsendmsg/recvmsg 结合 iovec 实现了类似的功能。
  • sendmmsg / recvmmsg: (Linux 特有) 可以在一次系统调用中发送或接收多个消息,性能更高。
  • CMSG_* 宏: 用于处理 msg_control 中的辅助数据(如 CMSG_FIRSTHDRCMSG_NXTHDRCMSG_DATA)。

7. 示例代码

示例 1:使用 sendmsg/recvmsg 替代 sendto/recvfrom (UDP)

这个例子展示了如何用 sendmsg 和 recvmsg 实现与 sendto 和 recvfrom 相同的功能(发送和接收 UDP 数据报)。

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

#define SERVER_PORT 8082
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() {
    int sock;
    struct sockaddr_in server_addr;
    struct msghdr msg;
    struct iovec iov[1]; // 只使用一个缓冲区
    char *message = "Hello from sendmsg/recvmsg client!";
    char buffer[BUFFER_SIZE];
    ssize_t bytes_sent, bytes_received;

    // 1. 创建 UDP 套接字
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 2. 配置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid address\n");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // --- 使用 sendmsg 发送 ---
    printf("Sending message using sendmsg...\n");

    // 准备 iovec
    iov[0].iov_base = message;
    iov[0].iov_len = strlen(message);

    // 准备 msghdr
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = &server_addr;        // 目标地址
    msg.msg_namelen = sizeof(server_addr);
    msg.msg_iov = iov;                  // 数据缓冲区向量
    msg.msg_iovlen = 1;                 // 向量元素个数

    // 发送消息
    bytes_sent = sendmsg(sock, &msg, 0);
    if (bytes_sent < 0) {
        perror("sendmsg failed");
        close(sock);
        exit(EXIT_FAILURE);
    } else {
        printf("sendmsg sent %zd bytes.\n", bytes_sent);
    }

    // --- 使用 recvmsg 接收 ---
    printf("Receiving reply using recvmsg...\n");

    struct sockaddr_in src_addr;
    socklen_t src_addr_len = sizeof(src_addr);

    // 准备 iovec for recv
    iov[0].iov_base = buffer;
    iov[0].iov_len = BUFFER_SIZE - 1;

    // 准备 msghdr for recv
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = &src_addr;          // 用于存储源地址
    msg.msg_namelen = src_addr_len;    // 输入:缓冲区大小
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    // 接收消息
    bytes_received = recvmsg(sock, &msg, 0);
    if (bytes_received < 0) {
        perror("recvmsg failed");
        close(sock);
        exit(EXIT_FAILURE);
    } else {
        buffer[bytes_received] = '\0';
        printf("recvmsg received %zd bytes from %s:%d: %s\n",
               bytes_received,
               inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port),
               buffer);
        // msg.msg_namelen 现在包含实际的地址大小
    }

    close(sock);
    return 0;
}
// msg_udp_server.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8082
#define BUFFER_SIZE 1024

int main() {
    int server_fd;
    struct sockaddr_in server_addr, client_addr;
    struct msghdr msg;
    struct iovec iov[1];
    char buffer[BUFFER_SIZE];
    char reply[] = "Echo via sendmsg: ";
    char reply_buffer[BUFFER_SIZE];
    ssize_t bytes_received, bytes_sent;

    server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_fd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    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_fd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("UDP server using sendmsg/recvmsg listening on port %d\n", PORT);

    while (1) {
        printf("Waiting for datagram...\n");

        socklen_t client_addr_len = sizeof(client_addr);
        iov[0].iov_base = buffer;
        iov[0].iov_len = BUFFER_SIZE - 1;

        memset(&msg, 0, sizeof(msg));
        msg.msg_name = &client_addr;
        msg.msg_namelen = client_addr_len;
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;

        bytes_received = recvmsg(server_fd, &msg, 0);
        if (bytes_received < 0) {
            perror("recvmsg failed");
            continue;
        }

        buffer[bytes_received] = '\0';
        printf("Received %zd bytes from %s:%d: %s\n",
               bytes_received,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),
               buffer);

        // 构造回复
        int reply_len = snprintf(reply_buffer, BUFFER_SIZE, "%s%s", reply, buffer);
        if (reply_len >= BUFFER_SIZE) reply_len = BUFFER_SIZE - 1;

        // 使用 sendmsg 发送回复
        iov[0].iov_base = reply_buffer;
        iov[0].iov_len = reply_len;

        memset(&msg, 0, sizeof(msg));
        msg.msg_name = &client_addr; // 发送到刚才接收数据的客户端
        msg.msg_namelen = sizeof(client_addr);
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;

        bytes_sent = sendmsg(server_fd, &msg, 0);
        if (bytes_sent < 0) {
            perror("sendmsg reply failed");
        } else {
            printf("Sent %zd bytes reply using sendmsg.\n", bytes_sent);
        }
    }

    return 0;
}

代码解释:

  1. 客户端和服务器都创建 UDP 套接字并进行必要的设置(服务器需要 bind)。
  2. 发送数据:
    • 准备一个 struct iovec 数组。这里只用一个元素指向消息缓冲区。
    • 准备一个 struct msghdr 结构体 msg
    • 设置 msg.msg_name 为目标地址,msg.msg_iov 为缓冲区向量,msg.msg_iovlen 为向量长度。
    • 调用 sendmsg(sock, &msg, 0) 发送数据。
  3. 接收数据:
    • 准备 struct iovec 和 struct msghdr
    • 设置 msg.msg_name 为用于存储源地址的缓冲区,msg.msg_namelen 为该缓冲区大小。
    • 设置 msg.msg_iov 和 msg.msg_iovlen
    • 调用 recvmsg(sock, &msg, 0) 接收数据。
    • 接收后,msg.msg_name 中包含了发送方的地址,msg.msg_namelen 被更新为实际地址大小。

示例 2:使用 sendmsg/recvmsg 进行 Scatter-Gather I/O

这个例子展示了如何使用 iovec 数组通过 sendmsg 发送来自多个缓冲区的数据。

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

#define SERVER_PORT 8083
#define SERVER_IP "127.0.0.1"

int main() {
    int sock;
    struct sockaddr_in server_addr;
    struct msghdr msg;
    struct iovec iov[3]; // 使用 3 个缓冲区
    char part1[] = "Part1-";
    char part2[] = "Part2-";
    char part3[] = "Part3-END";
    char recv_buffer[1024];
    ssize_t bytes_sent, bytes_received;

    sock = socket(AF_INET, SOCK_STREAM, 0); // 使用 TCP
    if (sock < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid address\n");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect failed");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // --- 使用 sendmsg 发送分散的数据 ---
    printf("Sending scattered data using sendmsg...\n");

    // 准备 iovec 数组
    iov[0].iov_base = part1;
    iov[0].iov_len = strlen(part1);
    iov[1].iov_base = part2;
    iov[1].iov_len = strlen(part2);
    iov[2].iov_base = part3;
    iov[2].iov_len = strlen(part3);

    // 准备 msghdr (TCP 不需要地址)
    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = iov;
    msg.msg_iovlen = 3;

    bytes_sent = sendmsg(sock, &msg, 0);
    if (bytes_sent < 0) {
        perror("sendmsg failed");
        close(sock);
        exit(EXIT_FAILURE);
    } else {
        printf("sendmsg sent %zd bytes (should be sum of parts: %zu).\n",
               bytes_sent, strlen(part1) + strlen(part2) + strlen(part3));
    }

    // 接收服务器的确认
    bytes_received = recv(sock, recv_buffer, sizeof(recv_buffer) - 1, 0);
    if (bytes_received > 0) {
        recv_buffer[bytes_received] = '\0';
        printf("Received confirmation: %s\n", recv_buffer);
    }

    close(sock);
    return 0;
}
// scatter_gather_server.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PORT 8083
#define BACKLOG 10

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_address;
    socklen_t client_addr_len = sizeof(client_address);
    struct msghdr msg;
    struct iovec iov[3];
    char buffer1[50], buffer2[50], buffer3[50]; // 接收缓冲区
    char confirmation[] = "Data received successfully!";
    int opt = 1;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Scatter-Gather TCP server listening on port %d\n", PORT);

    client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
    if (client_fd < 0) {
        perror("accept failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Client connected. Waiting for scattered data...\n");

    // --- 使用 recvmsg 接收数据到多个缓冲区 ---
    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;

    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = iov;
    msg.msg_iovlen = 3;

    ssize_t bytes_received = recvmsg(client_fd, &msg, 0);
    if (bytes_received < 0) {
        perror("recvmsg failed");
        close(client_fd);
        close(server_fd);
        exit(EXIT_FAILURE);
    } else {
        printf("recvmsg received %zd bytes.\n", bytes_received);
        // 确保字符串结束 (实际应用中需要更仔细地处理)
        size_t total_copied = 0;
        for (int i = 0; i < 3 && total_copied < (size_t)bytes_received; ++i) {
            size_t to_copy = iov[i].iov_len < (size_t)(bytes_received - total_copied) ?
                             iov[i].iov_len : (size_t)(bytes_received - total_copied);
            ((char*)iov[i].iov_base)[to_copy] = '\0';
            total_copied += to_copy;
        }
        printf("Data in buffers:\n  Buffer1: '%s'\n  Buffer2: '%s'\n  Buffer3: '%s'\n",
               buffer1, buffer2, buffer3);
    }

    // 发送确认
    send(client_fd, confirmation, strlen(confirmation), 0);

    close(client_fd);
    close(server_fd);
    return 0;
}

代码解释:

  1. 客户端使用 TCP 连接到服务器。
  2. 客户端将一个消息分割成三个部分,存放在 part1part2part3 三个不同的缓冲区中。
  3. 发送: 使用 sendmsg 和包含三个元素的 iovec 数组,将这三个缓冲区的数据一次性发送出去。这避免了三次 send 调用。
  4. 服务器 accept 连接。
  5. 接收: 服务器使用 recvmsg 和包含三个元素的 iovec 数组,将接收到的数据分散存储到 buffer1buffer2buffer3 三个不同的缓冲区中。
  6. 服务器打印出存储在各个缓冲区中的数据。

示例 3:通过 Unix 域套接字传递文件描述符 (辅助数据)

这个例子展示了 sendmsg/recvmsg 最强大的功能之一:传递文件描述符。这是进程间传递打开文件的一种高级方法。

// fd_passing.c
// 需要编译为两个程序: sender 和 receiver
// gcc -o sender fd_passing.c -DSENDER
// gcc -o receiver fd_passing.c -DRECEIVER

#include <sys/socket.h>
#include <sys/un.h> // Unix domain sockets
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SOCKET_PATH "/tmp/fd_pass_socket"

#ifdef SENDER
int main() {
    int sock, file_fd;
    struct sockaddr_un addr;
    struct msghdr msg;
    struct cmsghdr *cmsg;
    struct iovec iov[1];
    char ctrl_buf[CMSG_SPACE(sizeof(int))]; // 为一个 int (fd) 分配控制消息空间
    char data[] = "FD";
    int data_len = strlen(data);

    // 1. 创建要传递的文件
    file_fd = open("testfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (file_fd == -1) {
        perror("open testfile.txt");
        exit(EXIT_FAILURE);
    }
    write(file_fd, "This is data in the passed file.\n", 33);
    printf("Created and wrote to 'testfile.txt' (fd: %d)\n", file_fd);

    // 2. 创建 Unix 域套接字
    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket");
        close(file_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 连接到接收方
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect");
        close(file_fd);
        close(sock);
        exit(EXIT_FAILURE);
    }
    printf("Connected to receiver.\n");

    // 4. 准备消息结构
    iov[0].iov_base = data;
    iov[0].iov_len = data_len;

    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_control = ctrl_buf;
    msg.msg_controllen = sizeof(ctrl_buf);

    // 5. 准备辅助数据 (控制消息)
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS; // 传递权限 (文件描述符)
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
    memcpy(CMSG_DATA(cmsg), &file_fd, sizeof(int)); // 将文件描述符复制到控制消息数据部分

    msg.msg_controllen = cmsg->cmsg_len; // 更新控制消息长度

    // 6. 发送消息 (包含文件描述符)
    if (sendmsg(sock, &msg, 0) == -1) {
        perror("sendmsg");
        close(file_fd);
        close(sock);
        exit(EXIT_FAILURE);
    }

    printf("Sent message with file descriptor %d.\n", file_fd);
    // 注意:发送后,发送方通常应该 close 掉这个 fd
    // 但在此例中我们不 close,以演示 fd 已被传递

    close(sock);
    // close(file_fd); // 通常在这里关闭,但我们想演示它已被传递
    printf("Sender finished. Check if 'testfile.txt' is closed (lsof `pwd`/testfile.txt).\n");
    return 0;
}

#elif defined(RECEIVER)
int main() {
    int listen_sock, conn_sock, received_fd = -1;
    struct sockaddr_un addr, client_addr;
    socklen_t client_len;
    struct msghdr msg;
    struct cmsghdr *cmsg;
    struct iovec iov[1];
    char ctrl_buf[CMSG_SPACE(sizeof(int))];
    char data[10];
    ssize_t data_len;

    // 1. 创建 Unix 域套接字
    listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (listen_sock == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 清理可能存在的旧 socket 文件
    unlink(SOCKET_PATH);

    // 2. 绑定套接字
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

    if (bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        close(listen_sock);
        exit(EXIT_FAILURE);
    }

    // 3. 监听
    if (listen(listen_sock, 5) == -1) {
        perror("listen");
        close(listen_sock);
        exit(EXIT_FAILURE);
    }

    printf("Receiver listening on %s\n", SOCKET_PATH);

    // 4. 接受连接
    client_len = sizeof(client_addr);
    conn_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &client_len);
    if (conn_sock == -1) {
        perror("accept");
        close(listen_sock);
        exit(EXIT_FAILURE);
    }
    printf("Connection accepted.\n");

    // 5. 准备接收消息
    iov[0].iov_base = data;
    iov[0].iov_len = sizeof(data) - 1;

    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_control = ctrl_buf;
    msg.msg_controllen = sizeof(ctrl_buf);

    // 6. 接收消息
    data_len = recvmsg(conn_sock, &msg, 0);
    if (data_len == -1) {
        perror("recvmsg");
        close(conn_sock);
        close(listen_sock);
        exit(EXIT_FAILURE);
    }
    data[data_len] = '\0';
    printf("Received data: %s\n", data);

    // 7. 解析辅助数据 (控制消息) 以获取文件描述符
    cmsg = CMSG_FIRSTHDR(&msg);
    if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int)) &&
        cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
        memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
        printf("Received file descriptor: %d\n", received_fd);

        // 8. 使用接收到的文件描述符
        lseek(received_fd, 0, SEEK_SET); // 移到文件开头
        char buf[100];
        ssize_t bytes_read = read(received_fd, buf, sizeof(buf) - 1);
        if (bytes_read > 0) {
            buf[bytes_read] = '\0';
            printf("Read from received fd: %s", buf);
        }
        printf("Closing received file descriptor.\n");
        close(received_fd); // 关闭接收到的文件描述符
    } else {
        printf("No file descriptor received.\n");
    }

    close(conn_sock);
    close(listen_sock);
    unlink(SOCKET_PATH); // 清理 socket 文件
    printf("Receiver finished.\n");
    return 0;
}
#endif

编译和运行:

# Terminal 1
gcc -o receiver fd_passing.c -DRECEIVER
./receiver

# Terminal 2 (在 receiver 运行后)
gcc -o sender fd_passing.c -DSENDER
./sender

代码解释:

  1. 该代码通过宏定义 SENDER 和 RECEIVER 编译成两个不同的程序。
  2. **发送方 **(SENDER)
    • 创建一个名为 testfile.txt 的文件并写入数据。
    • 创建一个 Unix 域套接字并连接到接收方。
    • 准备一个 struct msghdr
    • 准备一个 iovec 来发送一些普通数据(“FD”)。
    • 关键: 准备辅助数据msg_control)。
      • 使用 CMSG_SPACE(sizeof(int)) 来分配足够的控制缓冲区。
      • 使用 CMSG_FIRSTHDR 获取第一个控制消息头指针。
      • 设置 cmsg_level 为 SOL_SOCKETcmsg_type 为 SCM_RIGHTS(表示传递文件描述符)。
      • 使用 CMSG_DATA(cmsg) 获取数据部分的指针,并将 file_fd 复制进去。
    • 调用 sendmsg 发送包含普通数据和辅助数据(文件描述符)的消息。
  3. **接收方 **(RECEIVER)
    • 创建并监听一个 Unix 域套接字。
    • accept 连接。
    • 准备 struct msghdr 和用于接收辅助数据的控制缓冲区 ctrl_buf
    • 调用 recvmsg 接收消息。
    • 关键: 解析接收到的辅助数据。
      • 使用 CMSG_FIRSTHDR 获取第一个控制消息头。
      • 检查 cmsg_levelcmsg_typecmsg_len 是否正确。
      • 如果正确,使用 CMSG_DATA(cmsg) 获取数据部分,并将文件描述符复制到 received_fd 变量中。
    • 现在,接收方可以像使用自己打开的文件一样使用 received_fd
    • 最后关闭接收到的文件描述符和套接字。

重要提示与注意事项:

  1. 通用性sendmsg/recvmsg 是最通用的套接字 I/O 函数,可以替代所有其他基本的发送和接收函数。
  2. Scatter-Gather: 通过 iovec 数组,可以高效地处理不连续的数据缓冲区,减少系统调用次数。
  3. 地址处理: 对于面向连接的套接字(如 TCP),msg_name 通常为 NULL。对于无连接的(如 UDP),它用于指定目标或获取源地址。
  4. 辅助数据: 这是 sendmsg/recvmsg 独有的强大功能。正确处理辅助数据需要使用 CMSG_* 系列宏,这比较复杂但非常有用(如传递文件描述符、设置网络接口等)。
  5. 性能: 在需要处理大量数据或复杂消息结构时,sendmsg/recvmsg 可能比多次调用 send/recv 更高效。
  6. sendmmsg/recvmmsg: 对于需要在一次系统调用中处理多个消息的高性能应用(如网络服务器),Linux 提供了这两个函数作为 sendmsg/recvmsg 的扩展。

总结:

sendmsg 和 recvmsg 是 Linux 套接字编程中最强大和灵活的 I/O 函数。它们不仅包含了其他所有基本套接字函数的功能,还引入了处理辅助数据的能力,这使得它们在高级进程间通信(如传递文件描述符)中不可或缺。虽然使用起来比 send/recv 复杂,但掌握它们对于编写高效、功能丰富的网络应用程序至关重要。

发表在 linux文章 | 留下评论

sendto系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 sendto 和 recvfrom 函数,它们是用于无连接(数据报)套接字(如 UDP)进行数据传输的核心系统调用,但也可以用于面向连接(流式)套接字。

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

sendto系统调用及示例


1. 函数介绍

sendto 和 recvfrom 是 Linux 系统调用,专门设计用于在套接字上传输数据报(datagrams)。它们与 send/write 和 recv/read 的主要区别在于:sendto 和 recvfrom 显式地处理目标地址和源地址

  • sendto: 将数据从套接字发送到指定的目标地址。对于 UDP 套接字,这会创建一个数据报并发送到指定的主机和端口。对于 TCP 套接字,如果尚未连接,调用会失败。
  • recvfrom: 从套接字接收数据,并获取数据的来源地址(发送方的 IP 地址和端口号)。对于 UDP 套接字,这会接收一个数据报。对于 TCP 套接字,地址信息通常不被使用,因为连接是点对点的。

你可以把它们想象成写信和收信

  • sendto: 你写一封信(数据),并在信封上明确写上收信人的地址(目标地址),然后投递出去。
  • recvfrom: 你收到一封信(数据),信封上写着寄件人的地址(源地址),你可以知道是谁寄来的。

2. 函数原型

#include <sys/socket.h> // 必需

// 发送数据到指定地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

// 从套接字接收数据,并获取源地址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

3. 功能

  • sendto:
    • 通过套接字 sockfd 发送 len 个字节的数据(从 buf 指向的缓冲区)。
    • 数据被发送到由 dest_addr 和 addrlen 指定的目标地址
    • 对于数据报(如 UDP)套接字,这会创建一个独立的数据报。
  • recvfrom:
    • 通过套接字 sockfd 接收最多 len 个字节的数据,并将其存储在 buf 指向的缓冲区中。
    • 如果 src_addr 和 addrlen 非 NULL,则将发送方的地址信息填充到 src_addr 指向的结构体中,并更新 *addrlen 为实际地址结构的大小。

4. 参数

这两个函数的参数非常相似,分别处理发送和接收。

sendto

  • int sockfd: 有效的套接字文件描述符。
  • const void *buf: 指向包含要发送数据的缓冲区的指针。
  • size_t len: 要发送的字节数。
  • int flags: 控制发送行为的标志位。常见的有:
    • 0: 使用默认行为。
    • MSG_DONTWAIT: 使发送操作非阻塞(如果套接字是阻塞的)。
    • MSG_NOSIGNAL: 在面向连接的套接字上,如果连接断开,不产生 SIGPIPE 信号。
  • const struct sockaddr *dest_addr: 指向目标地址结构的指针(例如 sockaddr_in 或 sockaddr_in6)。
  • socklen_t addrlendest_addr 指向的地址结构的大小。

recvfrom

  • int sockfd: 有效的套接字文件描述符。
  • void *buf: 指向用于存储接收数据的缓冲区的指针。
  • size_t len: 缓冲区 buf 的大小,也是期望接收的最大字节数。
  • int flags: 控制接收行为的标志位。常见的有:
    • 0: 使用默认行为。
    • MSG_DONTWAIT: 使接收操作非阻塞(如果套接字是阻塞的)。
    • MSG_PEEK: 查看传入的数据,但不从输入队列中移除。
    • MSG_WAITALL: 请求等待,直到读入请求的字节数。但当检测到错误或断开连接时,或套接字为非阻塞时,仍可能返回少于请求字节数的数据。
  • struct sockaddr *src_addr:
    • 如果不需要获取发送方地址,可以传入 NULL
    • 如果需要获取发送方地址,应传入指向 sockaddr_in 或 sockaddr_in6 等结构的指针。
  • socklen_t *addrlen:
    • 如果 src_addr 是 NULL,则 addrlen 也必须是 NULL
    • 如果 src_addr 非 NULL,则 addrlen 必须指向一个 socklen_t 变量。
    • 输入: 调用时,该变量应初始化为 src_addr 指向的缓冲区的大小(例如 sizeof(struct sockaddr_in))。
    • 输出: 返回时,该变量被更新为实际存储在 src_addr 中的地址结构的大小。

5. 返回值

  • 成功时:
    • sendto: 返回实际发送的字节数。对于数据报套接字,这通常等于 len(如果成功发送了整个数据报)。
    • recvfrom: 返回实际接收的字节数。如果返回 0,可能表示对端已关闭连接(对于面向连接的套接字)或收到了一个零长度的数据报(对于数据报套接字,理论上可能)。
  • 失败时:
    • 两个函数在失败时都返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EAGAIN/EWOULDBLOCK 非阻塞套接字上无数据可读/写,ECONNREFUSED 远程主机拒绝连接,EINTR 调用被信号中断等)。

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

  • send / write: 用于发送数据,但不指定目标地址(通常用于已连接的套接字)。
  • recv / read: 用于接收数据,但不获取源地址信息(通常用于已连接的套接字)。
  • connect: 对于数据报套接字,connect 可以设置默认目标地址,之后就可以使用 send/write 和 recv/read 而无需指定地址。
  • socket / bind / sendto / recvfrom: 构成了 UDP 网络编程的基本工具集。

7. 示例代码

示例 1:UDP 客户端 (使用 sendto 和 recvfrom)

这个例子演示了一个 UDP 客户端如何使用 sendto 向服务器发送消息,并使用 recvfrom 接收服务器的回复,同时获取服务器的地址。

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

#define SERVER_PORT 8081
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() {
    int sock;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char *message = "Hello UDP Server!";
    char buffer[BUFFER_SIZE];
    ssize_t bytes_sent, bytes_received;

    // 1. 创建 UDP 套接字
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    printf("UDP client socket created (fd: %d)\n", sock);

    // 2. 配置服务器地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid address/ Address not supported\n");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 3. 发送数据到服务器 (使用 sendto)
    printf("Sending message to %s:%d\n", SERVER_IP, SERVER_PORT);
    bytes_sent = sendto(sock, message, strlen(message), 0,
                        (const struct sockaddr *)&server_addr, sizeof(server_addr));
    if (bytes_sent < 0) {
        perror("sendto failed");
        close(sock);
        exit(EXIT_FAILURE);
    } else {
        printf("Sent %zd bytes: %s\n", bytes_sent, message);
    }

    // 4. 接收服务器的回复 (使用 recvfrom 并获取源地址)
    printf("Waiting for reply from server...\n");
    bytes_received = recvfrom(sock, buffer, BUFFER_SIZE - 1, 0,
                              (struct sockaddr *)&client_addr, &client_addr_len);
    if (bytes_received < 0) {
        perror("recvfrom failed");
        close(sock);
        exit(EXIT_FAILURE);
    } else {
        buffer[bytes_received] = '\0'; // 确保字符串结束
        printf("Received %zd bytes from server %s:%d: %s\n",
               bytes_received,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),
               buffer);
    }

    // 5. 关闭套接字
    close(sock);
    printf("UDP client socket closed.\n");

    return 0;
}

代码解释:

  1. 创建一个 AF_INET 和 SOCK_DGRAM 的 UDP 套接字。
  2. 填充 sockaddr_in 结构 server_addr,指定服务器的 IP 和端口。
  3. 关键: 调用 sendto(sock, message, ..., &server_addr, sizeof(server_addr)) 将数据发送到指定的服务器地址。
  4. 关键: 调用 recvfrom(sock, buffer, ..., &client_addr, &client_addr_len) 接收数据。
    • &client_addr 和 &client_addr_len 用于接收发送方(即服务器)的地址信息。
    • client_addr_len 在调用前初始化为 sizeof(client_addr)
  5. 打印接收到的数据和服务器的地址(IP 和端口)。
  6. 关闭套接字。

示例 2:UDP 服务器 (使用 recvfrom 和 sendto)

这个例子演示了一个 UDP 服务器如何使用 recvfrom 接收来自任意客户端的消息,并使用 sendto 将回复发送回消息的发送方。

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

#define PORT 8081
#define BUFFER_SIZE 1024

int main() {
    int server_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];
    ssize_t bytes_received, bytes_sent;
    char reply[] = "Echo: ";

    // 1. 创建 UDP 套接字
    server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_fd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    printf("UDP server socket created (fd: %d)\n", server_fd);

    // 2. 配置服务器地址结构
    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);

    // 3. (可选) 绑定套接字到地址和端口
    // 对于 UDP 服务器,绑定是常见的做法,以便客户端知道连接到哪个端口
    if (bind(server_fd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("UDP server bound to port %d\n", PORT);

    printf("UDP server is listening for messages...\n");

    while (1) {
        printf("Waiting for a datagram...\n");

        // 4. 接收数据报 (使用 recvfrom 并获取客户端地址)
        bytes_received = recvfrom(server_fd, buffer, BUFFER_SIZE - 1, 0,
                                  (struct sockaddr *)&client_addr, &client_addr_len);
        if (bytes_received < 0) {
            perror("recvfrom failed");
            continue; // 或 exit(EXIT_FAILURE);
        }

        buffer[bytes_received] = '\0'; // 确保字符串结束
        printf("Received %zd bytes from client %s:%d: %s\n",
               bytes_received,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),
               buffer);

        // 5. 构造回复消息
        char reply_buffer[BUFFER_SIZE];
        int reply_len = snprintf(reply_buffer, BUFFER_SIZE, "%s%s", reply, buffer);
        if (reply_len >= BUFFER_SIZE) {
            fprintf(stderr, "Reply message truncated.\n");
            reply_len = BUFFER_SIZE - 1;
        }

        // 6. 发送回复到客户端 (使用 sendto 和之前获取的客户端地址)
        printf("Sending reply to client %s:%d\n",
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        bytes_sent = sendto(server_fd, reply_buffer, reply_len, 0,
                            (const struct sockaddr *)&client_addr, client_addr_len);
        if (bytes_sent < 0) {
            perror("sendto reply failed");
        } else {
            printf("Sent %zd bytes as reply.\n", bytes_sent);
        }
    }

    // close(server_fd); // 不会执行到这里
    return 0;
}

代码解释:

  1. 创建一个 UDP 套接字。
  2. 配置服务器地址 server_addr,并调用 bind() 将套接字绑定到该地址和端口。这是 UDP 服务器的标准做法。
  3. 进入一个无限循环。
  4. 关键: 调用 recvfrom(server_fd, buffer, ..., &client_addr, &client_addr_len) 等待并接收数据报。
    • 该调用会阻塞,直到有数据报到达。
    • client_addr 和 client_addr_len 会被自动填充为发送该数据报的客户端的地址信息。
  5. 处理接收到的数据(这里简单地打印)。
  6. 关键: 调用 sendto(server_fd, reply_buffer, ..., &client_addr, client_addr_len) 将回复发送回刚才接收数据的那个客户端。地址信息直接来自上一步 recvfrom 的输出。
  7. 循环继续,处理下一个客户端的数据报。

示例 3:对比 sendto/recvfrom 与 connect + send/recv (UDP)

这个例子通过代码片段对比两种 UDP 客户端编程方式。

// 方式一:使用 sendto/recvfrom (显式地址)
void client_method_one() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in server_addr;
    // ... 配置 server_addr ...

    char *msg = "Hello";
    // 发送时必须指定地址
    sendto(sock, msg, strlen(msg), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));

    char buffer[1024];
    struct sockaddr_in src_addr;
    socklen_t src_len = sizeof(src_addr);
    // 接收时可以获取源地址
    recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&src_addr, &src_len);
    close(sock);
}

// 方式二:使用 connect + send/recv (隐式地址)
void client_method_two() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in server_addr;
    // ... 配置 server_addr ...

    // 使用 connect 设置默认目标地址
    connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

    char *msg = "Hello";
    // 发送时无需指定地址
    send(sock, msg, strlen(msg), 0);

    char buffer[1024];
    // 接收时通常不关心源地址 (因为已连接)
    recv(sock, buffer, sizeof(buffer), 0);
    close(sock);
}

代码解释:

  • 方式一 (sendto/recvfrom):
    • 每次发送都必须明确指定目标地址 (sendto)。
    • 接收时可以选择性地获取源地址 (recvfrom)。
    • 更加灵活,一个套接字可以与多个不同的目标通信。
  • 方式二 (connect + send/recv):
    • 通过 connect 一次性设置默认目标地址。
    • 后续的 send/write 和 recv/read 操作就像 TCP 一样,无需指定地址。
    • 简化了编程模型,但牺牲了灵活性(主要针对单个目标通信)。

重要提示与注意事项:

  1. 数据报边界: 对于 UDP (SOCK_DGRAM),sendto 发送的是一个完整的数据报recvfrom 接收的也是一个完整的数据报。这与 TCP (SOCK_STREAM) 的字节流不同。
  2. 无连接: UDP 是无连接的。服务器不需要 listen 和 accept。客户端不需要 connect(除非使用方式二)。
  3. 地址参数sendto 的 dest_addr 和 recvfrom 的 src_addr 是它们与 send/recv 的核心区别。
  4. 错误处理sendto 可能因为目标不可达而失败(ECONNREFUSED)。recvfrom 在没有数据时会阻塞(阻塞套接字)。
  5. 缓冲区大小recvfrom 的 len 参数是缓冲区大小,返回值是实际收到的字节数。确保缓冲区足够大。
  6. addrlen 初始化: 在调用 recvfrom 时,务必在之前将 *addrlen 初始化为目标缓冲区的大小。

总结:

sendto 和 recvfrom 是进行 UDP 网络编程(以及某些特殊情况下的 TCP 编程)的基础。它们提供了对数据报源地址和目标地址的直接控制,是实现无连接、不可靠但高效通信的关键工具。理解它们的参数和使用场景对于掌握网络编程至关重要。

发表在 linux文章 | 留下评论

set_mempolicy系统调用及示例

set_mempolicy系统调用及示例

set_mempolicy 是Linux系统调用,用于设置进程或内存区域的内存分配策略。它是NUMA(Non-Uniform Memory Access)系统中重要的内存管理工具,允许应用程序指定内存分配的节点偏好,优化内存访问性能。通过合理的内存策略设置,可以显著提高NUMA系统的性能。

1. 函数介绍

set_mempolicy 是Linux系统调用,用于设置进程或内存区域的内存分配策略。它是NUMA(Non-Uniform Memory Access)系统中重要的内存管理工具,允许应用程序指定内存分配的节点偏好,优化内存访问性能。通过合理的内存策略设置,可以显著提高NUMA系统的性能。

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

2. 函数原型

#include <numaif.h>
long set_mempolicy(int mode, const unsigned long *nodemask,
                   unsigned long maxnode);

3. 功能

set_mempolicy 设置当前进程的默认内存分配策略,影响后续的内存分配操作。它支持多种内存分配模式,允许指定特定的NUMA节点,帮助应用程序优化内存访问模式,减少跨节点内存访问的开销。

4. 参数

  • int mode: 内存分配模式
  • *const unsigned long nodemask: NUMA节点掩码(可为NULL)
  • unsigned long maxnode: 节点掩码中的最大节点数

5. 返回值

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

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

  • get_mempolicy: 获取当前内存策略
  • mbind: 为特定内存区域设置策略
  • migrate_pages: 迁移进程页面到指定节点
  • numa_*: NUMA库函数

7. 示例代码

示例1:基础set_mempolicy使用

#include <numaif.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
 * 检查系统NUMA支持
 */
int check_numa_support() {
    long max_nodes = sysconf(_SC_NPROCESSORS_ONLN);
    printf("=== NUMA支持检查 ===\n");
    printf("在线CPU数: %ld\n", max_nodes);
    
    // 检查是否支持NUMA
    int ret = get_mempolicy(NULL, NULL, 0, 0, 0);
    if (ret == -1 && errno == ENOSYS) {
        printf("系统不支持NUMA内存策略\n");
        return -1;
    }
    
    printf("系统支持NUMA内存策略\n");
    return 0;
}

/**
 * 演示基础set_mempolicy使用方法
 */
int demo_set_mempolicy_basic() {
    int mode;
    unsigned long nodemask;
    int ret;
    
    printf("=== 基础set_mempolicy使用示例 ===\n");
    
    // 检查NUMA支持
    if (check_numa_support() != 0) {
        return 0;  // 不支持NUMA时正常退出
    }
    
    // 获取当前内存策略
    ret = get_mempolicy(&mode, &nodemask, sizeof(nodemask) * 8, 0, 0);
    if (ret == 0) {
        printf("当前内存策略模式: %d\n", mode);
        printf("当前节点掩码: 0x%lx\n", nodemask);
    }
    
    // 设置默认内存策略为MPOL_PREFERRED(首选节点)
    printf("\n1. 设置首选节点策略:\n");
    unsigned long preferred_node = 0;  // 首选节点0
    ret = set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8);
    if (ret == 0) {
        printf("  成功设置首选节点策略,首选节点: %lu\n", preferred_node);
    } else {
        printf("  设置首选节点策略失败: %s\n", strerror(errno));
        if (errno == EINVAL) {
            printf("  可能的原因:系统不支持指定的策略或节点\n");
        }
    }
    
    // 设置策略为MPOL_BIND(绑定节点)
    printf("\n2. 设置绑定节点策略:\n");
    unsigned long bind_nodes = 0x3;  // 绑定到节点0和1
    ret = set_mempolicy(MPOL_BIND, &bind_nodes, sizeof(bind_nodes) * 8);
    if (ret == 0) {
        printf("  成功设置绑定节点策略,绑定节点: 0x%lx\n", bind_nodes);
    } else {
        printf("  设置绑定节点策略失败: %s\n", strerror(errno));
    }
    
    // 设置策略为MPOL_INTERLEAVE(交错分配)
    printf("\n3. 设置交错分配策略:\n");
    unsigned long interleave_nodes = 0xF;  // 交错分配到节点0-3
    ret = set_mempolicy(MPOL_INTERLEAVE, &interleave_nodes, sizeof(interleave_nodes) * 8);
    if (ret == 0) {
        printf("  成功设置交错分配策略,节点: 0x%lx\n", interleave_nodes);
    } else {
        printf("  设置交错分配策略失败: %s\n", strerror(errno));
    }
    
    // 恢复默认策略
    printf("\n4. 恢复默认策略:\n");
    ret = set_mempolicy(MPOL_DEFAULT, NULL, 0);
    if (ret == 0) {
        printf("  成功恢复默认内存策略\n");
    } else {
        printf("  恢复默认策略失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_set_mempolicy_basic();
}

示例2:不同内存策略演示

#include <numaif.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <malloc.h>

/**
 * 显示当前内存策略
 */
void show_current_policy() {
    int mode;
    unsigned long nodemask = 0;
    long maxnode = sizeof(nodemask) * 8;
    
    if (get_mempolicy(&mode, &nodemask, maxnode, 0, 0) == 0) {
        printf("当前策略: ");
        switch (mode) {
            case MPOL_DEFAULT:
                printf("MPOL_DEFAULT (默认策略)\n");
                break;
            case MPOL_PREFERRED:
                printf("MPOL_PREFERRED (首选节点)\n");
                break;
            case MPOL_BIND:
                printf("MPOL_BIND (绑定节点)\n");
                break;
            case MPOL_INTERLEAVE:
                printf("MPOL_INTERLEAVE (交错分配)\n");
                break;
            default:
                printf("未知模式 (%d)\n", mode);
                break;
        }
        if (nodemask != 0) {
            printf("节点掩码: 0x%lx\n", nodemask);
        }
    }
}

/**
 * 分配内存并检查分配位置
 */
void* allocate_and_check_memory(size_t size) {
    void *ptr = malloc(size);
    if (!ptr) {
        printf("内存分配失败\n");
        return NULL;
    }
    
    // 检查内存分配的节点位置(需要NUMA库支持)
    printf("  分配 %zu 字节内存\n", size);
    
    return ptr;
}

/**
 * 演示不同内存策略
 */
int demo_memory_policies() {
    void *memory_blocks[5];
    
    printf("=== 不同内存策略演示 ===\n");
    
    // 检查NUMA支持
    if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
        printf("系统不支持NUMA,跳过演示\n");
        return 0;
    }
    
    printf("系统支持NUMA内存策略\n\n");
    
    // 1. 默认策略
    printf("1. 默认策略 (MPOL_DEFAULT):\n");
    show_current_policy();
    memory_blocks[0] = allocate_and_check_memory(1024 * 1024);  // 1MB
    
    // 2. 首选节点策略
    printf("\n2. 首选节点策略 (MPOL_PREFERRED):\n");
    unsigned long preferred_node = 0;
    if (set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8) == 0) {
        show_current_policy();
        memory_blocks[1] = allocate_and_check_memory(1024 * 1024);
    } else {
        printf("  设置首选节点策略失败: %s\n", strerror(errno));
    }
    
    // 3. 绑定节点策略
    printf("\n3. 绑定节点策略 (MPOL_BIND):\n");
    unsigned long bind_nodes = 0x3;  // 节点0和1
    if (set_mempolicy(MPOL_BIND, &bind_nodes, sizeof(bind_nodes) * 8) == 0) {
        show_current_policy();
        memory_blocks[2] = allocate_and_check_memory(1024 * 1024);
    } else {
        printf("  设置绑定节点策略失败: %s\n", strerror(errno));
    }
    
    // 4. 交错分配策略
    printf("\n4. 交错分配策略 (MPOL_INTERLEAVE):\n");
    unsigned long interleave_nodes = 0xF;  // 节点0-3
    if (set_mempolicy(MPOL_INTERLEAVE, &interleave_nodes, sizeof(interleave_nodes) * 8) == 0) {
        show_current_policy();
        memory_blocks[3] = allocate_and_check_memory(4 * 1024 * 1024);  // 4MB
    } else {
        printf("  设置交错分配策略失败: %s\n", strerror(errno));
    }
    
    // 5. 恢复默认策略
    printf("\n5. 恢复默认策略:\n");
    if (set_mempolicy(MPOL_DEFAULT, NULL, 0) == 0) {
        show_current_policy();
        memory_blocks[4] = allocate_and_check_memory(1024 * 1024);
    } else {
        printf("  恢复默认策略失败: %s\n", strerror(errno));
    }
    
    // 释放内存
    for (int i = 0; i < 5; i++) {
        if (memory_blocks[i]) {
            free(memory_blocks[i]);
        }
    }
    
    return 0;
}

int main() {
    return demo_memory_policies();
}

示例3:NUMA感知内存分配

#include <numaif.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h>

/**
 * 获取系统NUMA信息
 */
void get_numa_info() {
    printf("=== 系统NUMA信息 ===\n");
    
    // 获取CPU数量
    long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
    printf("在线CPU数: %ld\n", nprocs);
    
    // 获取页面大小
    long page_size = sysconf(_SC_PAGESIZE);
    printf("页面大小: %ld 字节\n", page_size);
    
    // 显示当前CPU亲和性
    cpu_set_t cpuset;
    if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) {
        printf("当前CPU亲和性: ");
        for (int i = 0; i < CPU_SETSIZE && i < 64; i++) {
            if (CPU_ISSET(i, &cpuset)) {
                printf("%d ", i);
            }
        }
        printf("\n");
    }
}

/**
 * NUMA感知的大内存分配
 */
int demo_numa_aware_allocation() {
    const size_t large_size = 100 * 1024 * 1024;  // 100MB
    char *large_buffer;
    int ret;
    
    printf("=== NUMA感知内存分配演示 ===\n");
    
    // 检查NUMA支持
    if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
        printf("系统不支持NUMA,使用默认分配\n");
        large_buffer = malloc(large_size);
        if (large_buffer) {
            printf("成功分配 %zu MB 内存\n", large_size / (1024 * 1024));
            free(large_buffer);
        }
        return 0;
    }
    
    get_numa_info();
    
    printf("\n1. 使用默认策略分配大内存:\n");
    show_current_policy();
    large_buffer = malloc(large_size);
    if (large_buffer) {
        printf("  成功分配 %zu MB 内存\n", large_size / (1024 * 1024));
        // 初始化内存以确保实际分配
        memset(large_buffer, 0, 1024);
        free(large_buffer);
    }
    
    printf("\n2. 设置首选节点策略后分配:\n");
    unsigned long preferred_node = 0;
    ret = set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8);
    if (ret == 0) {
        show_current_policy();
        large_buffer = malloc(large_size);
        if (large_buffer) {
            printf("  成功分配 %zu MB 内存(倾向节点 %lu)\n", 
                   large_size / (1024 * 1024), preferred_node);
            memset(large_buffer, 0, 1024);
            free(large_buffer);
        }
    }
    
    printf("\n3. 设置绑定策略后分配:\n");
    unsigned long bind_nodes = 0x1;  // 仅绑定到节点0
    ret = set_mempolicy(MPOL_BIND, &bind_nodes, sizeof(bind_nodes) * 8);
    if (ret == 0) {
        show_current_policy();
        large_buffer = malloc(large_size);
        if (large_buffer) {
            printf("  成功分配 %zu MB 内存(绑定到节点 0x%lx)\n", 
                   large_size / (1024 * 1024), bind_nodes);
            memset(large_buffer, 0, 1024);
            free(large_buffer);
        }
    }
    
    printf("\n4. 设置交错策略后分配:\n");
    unsigned long interleave_nodes = 0x3;  // 交错到节点0和1
    ret = set_mempolicy(MPOL_INTERLEAVE, &interleave_nodes, sizeof(interleave_nodes) * 8);
    if (ret == 0) {
        show_current_policy();
        large_buffer = malloc(large_size);
        if (large_buffer) {
            printf("  成功分配 %zu MB 内存(交错到节点 0x%lx)\n", 
                   large_size / (1024 * 1024), interleave_nodes);
            memset(large_buffer, 0, 1024);
            free(large_buffer);
        }
    }
    
    // 恢复默认策略
    set_mempolicy(MPOL_DEFAULT, NULL, 0);
    
    return 0;
}

int main() {
    return demo_numa_aware_allocation();
}

示例4:多线程NUMA策略

#include <numaif.h>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h>

/**
 * 线程数据结构
 */
typedef struct {
    int thread_id;
    int preferred_node;
    size_t alloc_size;
    void *memory;
    int result;
} thread_data_t;

/**
 * 线程函数:设置NUMA策略并分配内存
 */
void* thread_function(void *arg) {
    thread_data_t *data = (thread_data_t*)arg;
    
    printf("线程 %d 启动,设置首选节点 %d\n", data->thread_id, data->preferred_node);
    
    // 设置线程的NUMA策略
    unsigned long preferred_node = data->preferred_node;
    if (set_mempolicy(MPOL_PREFERRED, &preferred_node, sizeof(preferred_node) * 8) != 0) {
        printf("线程 %d: 设置策略失败: %s\n", data->thread_id, strerror(errno));
        data->result = -1;
        return NULL;
    }
    
    // 分配内存
    data->memory = malloc(data->alloc_size);
    if (data->memory) {
        printf("线程 %d: 成功分配 %zu KB 内存\n", 
               data->thread_id, data->alloc_size / 1024);
        
        // 初始化内存
        memset(data->memory, data->thread_id, 1024);
        data->result = 0;
    } else {
        printf("线程 %d: 内存分配失败\n", data->thread_id);
        data->result = -1;
    }
    
    return NULL;
}

/**
 * 演示多线程NUMA策略
 */
int demo_multithread_numa() {
    const int num_threads = 4;
    pthread_t threads[num_threads];
    thread_data_t thread_data[num_threads];
    
    printf("=== 多线程NUMA策略演示 ===\n");
    
    // 检查NUMA支持
    if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
        printf("系统不支持NUMA,跳过多线程演示\n");
        return 0;
    }
    
    // 初始化线程数据
    for (int i = 0; i < num_threads; i++) {
        thread_data[i].thread_id = i;
        thread_data[i].preferred_node = i % 2;  // 交替使用节点0和1
        thread_data[i].alloc_size = (1024 + i * 512) * 1024;  // 1-3MB
        thread_data[i].memory = NULL;
        thread_data[i].result = 0;
    }
    
    // 创建线程
    printf("创建 %d 个线程,每个线程使用不同的NUMA节点策略\n", num_threads);
    
    for (int i = 0; i < num_threads; i++) {
        if (pthread_create(&threads[i], NULL, thread_function, &thread_data[i]) != 0) {
            printf("创建线程 %d 失败\n", i);
            return -1;
        }
    }
    
    // 等待所有线程完成
    for (int i = 0; i < num_threads; i++) {
        pthread_join(threads[i], NULL);
        printf("线程 %d 完成,结果: %s\n", 
               i, thread_data[i].result == 0 ? "成功" : "失败");
    }
    
    // 释放内存
    for (int i = 0; i < num_threads; i++) {
        if (thread_data[i].memory) {
            free(thread_data[i].memory);
        }
    }
    
    // 显示最终策略
    printf("\n主线程最终策略:\n");
    show_current_policy();
    
    return 0;
}

// 辅助函数声明
void show_current_policy();

int main() {
    return demo_multithread_numa();
}

void show_current_policy() {
    int mode;
    unsigned long nodemask = 0;
    long maxnode = sizeof(nodemask) * 8;
    
    if (get_mempolicy(&mode, &nodemask, maxnode, 0, 0) == 0) {
        printf("当前策略模式: %d", mode);
        switch (mode) {
            case MPOL_DEFAULT: printf(" (默认)"); break;
            case MPOL_PREFERRED: printf(" (首选)"); break;
            case MPOL_BIND: printf(" (绑定)"); break;
            case MPOL_INTERLEAVE: printf(" (交错)"); break;
        }
        printf(", 节点掩码: 0x%lx\n", nodemask);
    }
}

示例5:性能优化示例

#include <numaif.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
 * 性能测试结构
 */
typedef struct {
    const char *name;
    int mode;
    unsigned long nodemask;
    double time_taken;
    size_t memory_accessed;
} perf_test_t;

/**
 * 内存访问性能测试
 */
double test_memory_access_performance(size_t size, int iterations) {
    char *buffer;
    struct timeval start, end;
    
    // 分配测试内存
    buffer = malloc(size);
    if (!buffer) {
        printf("内存分配失败\n");
        return -1;
    }
    
    // 初始化内存
    memset(buffer, 0, size);
    
    // 开始性能测试
    gettimeofday(&start, NULL);
    
    // 执行内存访问测试
    for (int i = 0; i < iterations; i++) {
        // 顺序访问内存
        for (size_t j = 0; j < size; j += 64) {  // 64字节步长
            buffer[j] = (buffer[j] + 1) % 256;
        }
    }
    
    gettimeofday(&end, NULL);
    
    // 计算耗时
    double elapsed = (end.tv_sec - start.tv_sec) + 
                     (end.tv_usec - start.tv_usec) / 1000000.0;
    
    free(buffer);
    return elapsed;
}

/**
 * 演示不同策略的性能影响
 */
int demo_performance_impact() {
    const size_t test_size = 50 * 1024 * 1024;  // 50MB
    const int iterations = 10;
    perf_test_t tests[4];
    
    printf("=== NUMA策略性能影响演示 ===\n");
    printf("测试内存大小: %zu MB\n", test_size / (1024 * 1024));
    printf("测试迭代次数: %d\n", iterations);
    
    // 检查NUMA支持
    if (get_mempolicy(NULL, NULL, 0, 0, 0) == -1 && errno == ENOSYS) {
        printf("系统不支持NUMA,跳过性能测试\n");
        return 0;
    }
    
    // 初始化测试配置
    tests[0].name = "默认策略";
    tests[0].mode = MPOL_DEFAULT;
    tests[0].nodemask = 0;
    
    tests[1].name = "首选节点0";
    tests[1].mode = MPOL_PREFERRED;
    tests[1].nodemask = 0x1;
    
    tests[2].name = "绑定节点0";
    tests[2].mode = MPOL_BIND;
    tests[2].nodemask = 0x1;
    
    tests[3].name = "交错节点0-1";
    tests[3].mode = MPOL_INTERLEAVE;
    tests[3].nodemask = 0x3;
    
    // 执行性能测试
    for (int i = 0; i < 4; i++) {
        printf("\n测试 %d: %s\n", i + 1, tests[i].name);
        
        // 设置内存策略
        if (tests[i].mode == MPOL_DEFAULT) {
            if (set_mempolicy(MPOL_DEFAULT, NULL, 0) != 0) {
                printf("  设置策略失败: %s\n", strerror(errno));
                continue;
            }
        } else {
            if (set_mempolicy(tests[i].mode, &tests[i].nodemask, 
                             sizeof(tests[i].nodemask) * 8) != 0) {
                printf("  设置策略失败: %s\n", strerror(errno));
                continue;
            }
        }
        
        // 显示当前策略
        show_current_policy();
        
        // 执行性能测试
        tests[i].time_taken = test_memory_access_performance(test_size, iterations);
        tests[i].memory_accessed = test_size * iterations;
        
        if (tests[i].time_taken > 0) {
            double bandwidth = (tests[i].memory_accessed / (1024.0 * 1024.0)) / 
                              tests[i].time_taken;
            printf("  测试完成: %.3f 秒, 带宽: %.2f MB/s\n", 
                   tests[i].time_taken, bandwidth);
        } else {
            printf("  测试失败\n");
        }
    }
    
    // 显示性能对比结果
    printf("\n=== 性能对比结果 ===\n");
    double baseline_time = tests[0].time_taken;
    
    for (int i = 0; i < 4; i++) {
        if (tests[i].time_taken > 0) {
            printf("%-15s: %.3f 秒", tests[i].name, tests[i].time_taken);
            if (i > 0 && baseline_time > 0) {
                double improvement = (baseline_time - tests[i].time_taken) / 
                                   baseline_time * 100;
                printf(" (%+.1f%%)", improvement);
            }
            printf("\n");
        }
    }
    
    // 恢复默认策略
    set_mempolicy(MPOL_DEFAULT, NULL, 0);
    
    return 0;
}

void show_current_policy() {
    int mode;
    unsigned long nodemask = 0;
    long maxnode = sizeof(nodemask) * 8;
    
    if (get_mempolicy(&mode, &nodemask, maxnode, 0, 0) == 0) {
        printf("  当前策略: ");
        switch (mode) {
            case MPOL_DEFAULT: printf("默认"); break;
            case MPOL_PREFERRED: printf("首选节点"); break;
            case MPOL_BIND: printf("绑定节点"); break;
            case MPOL_INTERLEAVE: printf("交错分配"); break;
            default: printf("未知(%d)", mode); break;
        }
        if (nodemask != 0) {
            printf(" (节点掩码: 0x%lx)", nodemask);
        }
        printf("\n");
    }
}

int main() {
    return demo_performance_impact();
}

set_mempolicy 使用注意事项

系统要求:

  1. 内核版本: 需要支持NUMA的Linux内核(2.6.7+)
  2. 硬件支持: 需要NUMA架构的硬件平台
  3. 编译选项: 需要链接NUMA库(-lnuma)

策略模式详解:

  1. MPOL_DEFAULT: 使用系统默认策略
  2. MPOL_PREFERRED: 首选指定节点,失败时使用其他节点
  3. MPOL_BIND: 严格绑定到指定节点
  4. MPOL_INTERLEAVE: 在指定节点间交错分配

参数验证:

  1. mode有效性: 确保mode参数是有效的策略模式
  2. nodemask合法性: 确保节点掩码指定的节点存在
  3. maxnode范围: 确保不超过系统支持的最大节点数

错误处理:

  1. ENOSYS: 系统不支持NUMA策略
  2. EINVAL: 参数无效(模式或节点掩码)
  3. ENOMEM: 内存不足
  4. EPERM: 权限不足

性能考虑:

  1. 策略选择: 根据应用访问模式选择合适策略
  2. 节点亲和: 考虑CPU和内存节点的拓扑关系
  3. 内存局部性: 优化数据访问的局部性

最佳实践:

  1. 测试验证: 在实际硬件上测试策略效果
  2. 渐进应用: 从简单策略开始逐步优化
  3. 监控调优: 监控性能指标并调整策略
  4. 兼容处理: 处理不支持NUMA的系统环境

NUMA策略模式详解

MPOL_DEFAULT(默认模式):

  • 行为: 使用系统默认的内存分配策略
  • 特点: 不指定特定节点偏好
  • 适用: 一般应用或不确定优化方向时

MPOL_PREFERRED(首选模式):

  • 行为: 优先在指定节点分配内存
  • 特点: 分配失败时会使用其他可用节点
  • 适用: 希望优先使用特定节点但允许fallback的场景

MPOL_BIND(绑定模式):

  • 行为: 严格限制在指定节点分配内存
  • 特点: 分配失败时直接返回错误
  • 适用: 严格要求内存位置的应用

MPOL_INTERLEAVE(交错模式):

  • 行为: 在多个节点间轮询分配内存
  • 特点: 均匀分布内存负载
  • 适用: 大内存应用或需要负载均衡的场景

节点掩码操作

节点掩码设置示例:

// 单节点
unsigned long nodemask = 1UL << 0;  // 节点0

// 多节点
unsigned long nodemask = (1UL << 0) | (1UL << 1);  // 节点0和1

// 连续节点
unsigned long nodemask = (1UL << 4) - 1;  // 节点0-3

节点掩码操作函数:

// 设置节点位
#define NODE_SET(node, mask) ((mask) |= (1UL << (node)))

// 清除节点位
#define NODE_CLR(node, mask) ((mask) &= ~(1UL << (node)))

// 检查节点位
#define NODE_ISSET(node, mask) ((mask) & (1UL << (node)))

常见使用场景

1. 数据库应用:

// 为不同数据缓冲区设置不同的节点策略
set_mempolicy(MPOL_PREFERRED, &node_0, sizeof(node_0) * 8);  // 索引数据
set_mempolicy(MPOL_PREFERRED, &node_1, sizeof(node_1) * 8);  // 用户数据

2. 高性能计算:

// 为计算密集型任务绑定到本地内存节点
set_mempolicy(MPOL_BIND, &local_node, sizeof(local_node) * 8);

3. Web服务器:

// 交错分配大缓存以平衡内存负载
set_mempolicy(MPOL_INTERLEAVE, &all_nodes, sizeof(all_nodes) * 8);

总结

set_mempolicy 是NUMA系统中重要的内存管理工具,提供了:

  1. 灵活的策略控制: 支持多种内存分配策略
  2. 性能优化能力: 通过节点亲和优化内存访问
  3. 应用适应性: 适用于各种NUMA应用场景
  4. 系统兼容性: 在不支持NUMA的系统上安全降级

通过合理使用 set_mempolicy,可以显著提升NUMA系统的内存访问性能,特别是在内存密集型应用中效果明显。在实际应用中,需要根据具体的工作负载特征和系统拓扑结构来选择合适的内存策略。

发表在 linux文章 | 标签为 | 留下评论

set_robust_list系统调用及示例

set_robust_list 函数详解

set_robust_list 是Linux系统调用,用于设置进程的健壮互斥锁(robust mutex)列表。当持有健壮互斥锁的进程异常终止时,内核会自动释放这些锁,防止死锁的发生。这个机制对于构建高可靠性多线程应用程序非常重要。

1. 函数介绍

set_robust_list 是Linux系统调用,用于设置进程的健壮互斥锁(robust mutex)列表。当持有健壮互斥锁的进程异常终止时,内核会自动释放这些锁,防止死锁的发生。这个机制对于构建高可靠性多线程应用程序非常重要。

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

2. 函数原型

#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>

long set_robust_list(struct robust_list_head *head, size_t len);

3. 功能

set_robust_list 允许进程注册一个健壮互斥锁列表,当进程异常退出时(如被信号终止),内核会自动遍历这个列表并释放所有未释放的互斥锁,确保其他等待这些锁的线程不会永久阻塞。

4. 参数

  • *struct robust_list_head head: 指向健壮列表头的指针
  • size_t len: 列表头结构的大小(通常为sizeof(struct robust_list_head))

5. 返回值

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

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

  • get_robust_list: 获取当前健壮列表
  • pthread_mutexattr_setrobust: 设置pthread互斥锁的健壮属性
  • pthread_mutex_consistent: 标记互斥锁状态为一致

7. 示例代码

示例1:基础set_robust_list使用

#include <linux/futex.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

/**
 * 健壮列表头结构
 */
struct robust_list_head {
    struct robust_list *list;
    long futex_offset;
    unsigned long list_op_pending;
};

/**
 * 健壮列表节点结构
 */
struct robust_list {
    struct robust_list *next;
};

/**
 * 调用set_robust_list系统调用
 */
static inline long sys_set_robust_list(struct robust_list_head *head, size_t len) {
    return syscall(SYS_set_robust_list, head, len);
}

/**
 * 调用get_robust_list系统调用
 */
static inline long sys_get_robust_list(int pid, struct robust_list_head **head, size_t *len) {
    return syscall(SYS_get_robust_list, pid, head, len);
}

/**
 * 演示基础set_robust_list使用方法
 */
int demo_set_robust_list_basic() {
    struct robust_list_head head;
    struct robust_list_head *get_head;
    size_t len;
    long result;
    
    printf("=== 基础set_robust_list使用示例 ===\n");
    
    // 初始化健壮列表头
    head.list = NULL;
    head.futex_offset = 0;
    head.list_op_pending = 0;
    
    printf("1. 设置健壮列表:\n");
    result = sys_set_robust_list(&head, sizeof(head));
    if (result == 0) {
        printf("  成功设置健壮列表\n");
    } else {
        printf("  设置健壮列表失败: %s\n", strerror(errno));
        if (errno == ENOSYS) {
            printf("  系统不支持健壮列表功能\n");
            return 0;
        }
        return -1;
    }
    
    printf("2. 获取当前健壮列表:\n");
    result = sys_get_robust_list(0, &get_head, &len);
    if (result == 0) {
        printf("  成功获取健壮列表\n");
        printf("  列表地址: %p\n", (void*)get_head);
        printf("  列表大小: %zu 字节\n", len);
    } else {
        printf("  获取健壮列表失败: %s\n", strerror(errno));
    }
    
    printf("3. 验证列表设置:\n");
    if ((void*)get_head == (void*)&head && len == sizeof(head)) {
        printf("  ✓ 健壮列表设置正确\n");
    } else {
        printf("  ✗ 健壮列表设置可能有问题\n");
    }
    
    return 0;
}

int main() {
    return demo_set_robust_list_basic();
}

示例2:pthread健壮互斥锁演示

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>

/**
 * 共享资源结构
 */
typedef struct {
    pthread_mutex_t mutex;
    int counter;
    int active_threads;
} shared_resource_t;

/**
 * 线程数据结构
 */
typedef struct {
    int thread_id;
    shared_resource_t *resource;
    int terminate_signal;
} thread_data_t;

/**
 * 初始化健壮互斥锁
 */
int init_robust_mutex(pthread_mutex_t *mutex) {
    pthread_mutexattr_t attr;
    int result;
    
    // 初始化互斥锁属性
    result = pthread_mutexattr_init(&attr);
    if (result != 0) {
        printf("初始化互斥锁属性失败: %s\n", strerror(result));
        return -1;
    }
    
    // 设置互斥锁为健壮的
    result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
    if (result != 0) {
        printf("设置健壮属性失败: %s\n", strerror(result));
        pthread_mutexattr_destroy(&attr);
        return -1;
    }
    
    // 初始化互斥锁
    result = pthread_mutex_init(mutex, &attr);
    if (result != 0) {
        printf("初始化互斥锁失败: %s\n", strerror(result));
        pthread_mutexattr_destroy(&attr);
        return -1;
    }
    
    pthread_mutexattr_destroy(&attr);
    return 0;
}

/**
 * 线程函数
 */
void* worker_thread(void *arg) {
    thread_data_t *data = (thread_data_t*)arg;
    int result;
    
    printf("工作线程 %d 启动\n", data->thread_id);
    
    // 模拟工作循环
    for (int i = 0; i < 10 && !data->terminate_signal; i++) {
        // 获取互斥锁
        result = pthread_mutex_lock(&data->resource->mutex);
        if (result == EOWNERDEAD) {
            printf("线程 %d: 检测到锁持有者异常终止\n", data->thread_id);
            // 标记互斥锁状态为一致
            result = pthread_mutex_consistent(&data->resource->mutex);
            if (result != 0) {
                printf("线程 %d: 恢复互斥锁一致性失败: %s\n", 
                       data->thread_id, strerror(result));
                pthread_mutex_unlock(&data->resource->mutex);
                break;
            }
            printf("线程 %d: 成功恢复互斥锁一致性\n", data->thread_id);
        } else if (result != 0) {
            printf("线程 %d: 获取互斥锁失败: %s\n", data->thread_id, strerror(result));
            break;
        }
        
        // 访问共享资源
        data->resource->counter++;
        printf("线程 %d: 计数器 = %d\n", data->thread_id, data->resource->counter);
        
        // 模拟工作处理时间
        usleep(100000);  // 100ms
        
        // 释放互斥锁
        pthread_mutex_unlock(&data->resource->mutex);
        
        // 短暂休息
        usleep(50000);  // 50ms
    }
    
    // 减少活跃线程计数
    pthread_mutex_lock(&data->resource->mutex);
    data->resource->active_threads--;
    pthread_mutex_unlock(&data->resource->mutex);
    
    printf("工作线程 %d 结束\n", data->thread_id);
    return NULL;
}

/**
 * 演示pthread健壮互斥锁
 */
int demo_pthread_robust_mutex() {
    shared_resource_t resource = {0};
    pthread_t threads[3];
    thread_data_t thread_data[3];
    int result;
    
    printf("=== pthread健壮互斥锁演示 ===\n");
    
    // 初始化健壮互斥锁
    if (init_robust_mutex(&resource.mutex) != 0) {
        return -1;
    }
    
    printf("成功初始化健壮互斥锁\n");
    
    // 初始化共享资源
    resource.counter = 0;
    resource.active_threads = 3;
    
    // 创建工作线程
    printf("创建3个工作线程...\n");
    
    for (int i = 0; i < 3; i++) {
        thread_data[i].thread_id = i + 1;
        thread_data[i].resource = &resource;
        thread_data[i].terminate_signal = 0;
        
        result = pthread_create(&threads[i], NULL, worker_thread, &thread_data[i]);
        if (result != 0) {
            printf("创建线程 %d 失败: %s\n", i + 1, strerror(result));
            return -1;
        }
    }
    
    // 让线程运行一段时间
    printf("让线程运行5秒...\n");
    sleep(5);
    
    // 强制终止一个线程来演示健壮性
    printf("强制终止线程1来演示健壮性...\n");
    thread_data[0].terminate_signal = 1;
    pthread_kill(threads[0], SIGKILL);
    
    // 等待其他线程完成
    printf("等待其他线程完成...\n");
    for (int i = 1; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    // 显示最终结果
    printf("\n最终结果:\n");
    printf("  计数器值: %d\n", resource.counter);
    printf("  活跃线程: %d\n", resource.active_threads);
    
    // 清理互斥锁
    pthread_mutex_destroy(&resource.mutex);
    
    return 0;
}

int main() {
    return demo_pthread_robust_mutex();
}

示例3:健壮列表管理

#include <linux/futex.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdatomic.h>

/**
 * 自定义健壮列表节点
 */
typedef struct custom_robust_node {
    struct custom_robust_node *next;
    pthread_mutex_t *mutex;
    int node_id;
} custom_robust_node_t;

/**
 * 自定义健壮列表管理器
 */
typedef struct {
    struct robust_list_head head;
    custom_robust_node_t *nodes;
    int node_count;
    int max_nodes;
} robust_list_manager_t;

/**
 * 系统调用封装
 */
static inline long sys_set_robust_list(struct robust_list_head *head, size_t len) {
    return syscall(SYS_set_robust_list, head, len);
}

static inline long sys_get_robust_list(int pid, struct robust_list_head **head, size_t *len) {
    return syscall(SYS_get_robust_list, pid, head, len);
}

/**
 * 初始化健壮列表管理器
 */
int robust_list_init(robust_list_manager_t *manager, int max_nodes) {
    // 初始化列表头
    manager->head.list = NULL;
    manager->head.futex_offset = 0;
    manager->head.list_op_pending = 0;
    
    // 分配节点数组
    manager->nodes = calloc(max_nodes, sizeof(custom_robust_node_t));
    if (!manager->nodes) {
        perror("分配节点数组失败");
        return -1;
    }
    
    manager->node_count = 0;
    manager->max_nodes = max_nodes;
    
    // 设置健壮列表
    long result = sys_set_robust_list(&manager->head, sizeof(manager->head));
    if (result != 0) {
        printf("设置健壮列表失败: %s\n", strerror(errno));
        free(manager->nodes);
        return -1;
    }
    
    printf("健壮列表管理器初始化成功,最大节点数: %d\n", max_nodes);
    return 0;
}

/**
 * 添加互斥锁到健壮列表
 */
int robust_list_add_mutex(robust_list_manager_t *manager, pthread_mutex_t *mutex, int node_id) {
    if (manager->node_count >= manager->max_nodes) {
        printf("节点数已达上限\n");
        return -1;
    }
    
    int index = manager->node_count++;
    manager->nodes[index].mutex = mutex;
    manager->nodes[index].node_id = node_id;
    
    // 连接节点到列表
    if (index > 0) {
        manager->nodes[index-1].next = (struct robust_list*)&manager->nodes[index];
    }
    
    // 更新列表头
    manager->head.list = (struct robust_list*)&manager->nodes[0];
    
    printf("添加互斥锁到健壮列表,节点ID: %d\n", node_id);
    return 0;
}

/**
 * 演示健壮列表管理
 */
int demo_robust_list_management() {
    robust_list_manager_t manager;
    pthread_mutex_t mutex1, mutex2, mutex3;
    pthread_mutexattr_t attr;
    int result;
    
    printf("=== 健壮列表管理演示 ===\n");
    
    // 初始化管理器
    if (robust_list_init(&manager, 10) != 0) {
        return -1;
    }
    
    // 初始化互斥锁属性
    result = pthread_mutexattr_init(&attr);
    if (result != 0) {
        printf("初始化互斥锁属性失败: %s\n", strerror(result));
        return -1;
    }
    
    result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
    if (result != 0) {
        printf("设置健壮属性失败: %s\n", strerror(result));
        pthread_mutexattr_destroy(&attr);
        return -1;
    }
    
    // 创建多个健壮互斥锁
    printf("创建健壮互斥锁...\n");
    
    result = pthread_mutex_init(&mutex1, &attr);
    if (result != 0) {
        printf("初始化互斥锁1失败: %s\n", strerror(result));
        pthread_mutexattr_destroy(&attr);
        return -1;
    }
    
    result = pthread_mutex_init(&mutex2, &attr);
    if (result != 0) {
        printf("初始化互斥锁2失败: %s\n", strerror(result));
        pthread_mutex_destroy(&mutex1);
        pthread_mutexattr_destroy(&attr);
        return -1;
    }
    
    result = pthread_mutex_init(&mutex3, &attr);
    if (result != 0) {
        printf("初始化互斥锁3失败: %s\n", strerror(result));
        pthread_mutex_destroy(&mutex1);
        pthread_mutex_destroy(&mutex2);
        pthread_mutexattr_destroy(&attr);
        return -1;
    }
    
    pthread_mutexattr_destroy(&attr);
    
    // 将互斥锁添加到健壮列表
    robust_list_add_mutex(&manager, &mutex1, 1);
    robust_list_add_mutex(&manager, &mutex2, 2);
    robust_list_add_mutex(&manager, &mutex3, 3);
    
    // 测试互斥锁操作
    printf("测试互斥锁操作...\n");
    
    for (int i = 0; i < 3; i++) {
        result = pthread_mutex_lock(&mutex1);
        if (result == 0) {
            printf("成功获取互斥锁1\n");
            usleep(100000);
            pthread_mutex_unlock(&mutex1);
            printf("成功释放互斥锁1\n");
        } else if (result == EOWNERDEAD) {
            printf("检测到锁持有者异常终止,恢复一致性...\n");
            pthread_mutex_consistent(&mutex1);
            pthread_mutex_unlock(&mutex1);
        } else {
            printf("获取互斥锁1失败: %s\n", strerror(result));
        }
    }
    
    // 清理资源
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);
    pthread_mutex_destroy(&mutex3);
    free(manager.nodes);
    
    printf("健壮列表管理演示完成\n");
    return 0;
}

int main() {
    return demo_robust_list_management();
}

示例4:异常终止处理演示

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

/**
 * 共享资源结构
 */
typedef struct {
    pthread_mutex_t mutex;
    int shared_data;
    volatile int writer_active;
} shared_data_t;

/**
 * 初始化健壮互斥锁
 */
int init_robust_mutex(pthread_mutex_t *mutex) {
    pthread_mutexattr_t attr;
    int result;
    
    result = pthread_mutexattr_init(&attr);
    if (result != 0) {
        return -1;
    }
    
    result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
    if (result != 0) {
        pthread_mutexattr_destroy(&attr);
        return -1;
    }
    
    result = pthread_mutex_init(mutex, &attr);
    pthread_mutexattr_destroy(&attr);
    
    return result;
}

/**
 * 写者线程函数
 */
void* writer_thread(void *arg) {
    shared_data_t *data = (shared_data_t*)arg;
    int result;
    
    printf("写者线程启动 (PID: %d)\n", getpid());
    
    // 获取互斥锁
    result = pthread_mutex_lock(&data->mutex);
    if (result != 0) {
        printf("写者: 获取互斥锁失败: %s\n", strerror(result));
        return NULL;
    }
    
    printf("写者: 成功获取互斥锁\n");
    data->writer_active = 1;
    
    // 模拟长时间写操作
    printf("写者: 开始写操作...\n");
    for (int i = 0; i < 10; i++) {
        data->shared_data = i + 1;
        printf("写者: 写入数据 %d\n", data->shared_data);
        sleep(1);
    }
    
    // 正常释放互斥锁
    data->writer_active = 0;
    pthread_mutex_unlock(&data->mutex);
    printf("写者: 释放互斥锁\n");
    
    return NULL;
}

/**
 * 读者线程函数
 */
void* reader_thread(void *arg) {
    shared_data_t *data = (shared_data_t*)arg;
    int result;
    
    printf("读者线程启动 (PID: %d)\n", getpid());
    
    // 等待写者开始工作
    sleep(2);
    
    // 尝试获取互斥锁
    printf("读者: 尝试获取互斥锁...\n");
    result = pthread_mutex_lock(&data->mutex);
    
    if (result == EOWNERDEAD) {
        printf("读者: 检测到写者异常终止!\n");
        printf("读者: 正在恢复互斥锁一致性...\n");
        
        // 恢复互斥锁一致性
        result = pthread_mutex_consistent(&data->mutex);
        if (result == 0) {
            printf("读者: 成功恢复互斥锁一致性\n");
            printf("读者: 当前共享数据: %d\n", data->shared_data);
        } else {
            printf("读者: 恢复一致性失败: %s\n", strerror(result));
        }
    } else if (result == 0) {
        printf("读者: 成功获取互斥锁\n");
        printf("读者: 读取数据: %d\n", data->shared_data);
        pthread_mutex_unlock(&data->mutex);
    } else {
        printf("读者: 获取互斥锁失败: %s\n", strerror(result));
    }
    
    return NULL;
}

/**
 * 演示异常终止处理
 */
int demo_abnormal_termination() {
    shared_data_t data = {0};
    pthread_t writer_tid, reader_tid;
    int result;
    
    printf("=== 异常终止处理演示 ===\n");
    
    // 初始化健壮互斥锁
    if (init_robust_mutex(&data.mutex) != 0) {
        printf("初始化健壮互斥锁失败\n");
        return -1;
    }
    
    printf("成功初始化健壮互斥锁\n");
    
    // 创建写者进程
    pid_t writer_pid = fork();
    if (writer_pid == 0) {
        // 写者进程
        printf("写者进程启动\n");
        
        // 获取互斥锁
        result = pthread_mutex_lock(&data.mutex);
        if (result != 0) {
            printf("写者进程: 获取互斥锁失败: %s\n", strerror(result));
            exit(1);
        }
        
        printf("写者进程: 成功获取互斥锁\n");
        data.writer_active = 1;
        
        // 模拟工作一段时间后异常终止
        printf("写者进程: 开始工作,5秒后模拟异常终止...\n");
        sleep(5);
        
        printf("写者进程: 模拟异常终止(不释放锁)\n");
        // 注意:这里不调用pthread_mutex_unlock,模拟异常终止
        exit(1);  // 强制退出,不释放锁
    } else if (writer_pid > 0) {
        // 父进程(读者)
        printf("父进程(读者)启动\n");
        
        // 等待写者进程开始工作
        sleep(2);
        
        // 创建读者线程
        result = pthread_create(&reader_tid, NULL, reader_thread, &data);
        if (result != 0) {
            printf("创建读者线程失败: %s\n", strerror(result));
            kill(writer_pid, SIGKILL);
            pthread_mutex_destroy(&data.mutex);
            return -1;
        }
        
        // 等待写者进程异常终止
        int status;
        waitpid(writer_pid, &status, 0);
        printf("写者进程已终止 (状态: %d)\n", status);
        
        // 等待读者线程完成
        pthread_join(reader_tid, NULL);
        
        // 清理资源
        pthread_mutex_destroy(&data.mutex);
        
        printf("异常终止处理演示完成\n");
    } else {
        perror("fork 失败");
        pthread_mutex_destroy(&data.mutex);
        return -1;
    }
    
    return 0;
}

int main() {
    return demo_abnormal_termination();
}

示例5:健壮性测试工具

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>

/**
 * 健壮性测试配置
 */
typedef struct {
    int num_threads;
    int test_duration;
    int crash_probability;  // 0-100,表示异常终止概率
    int use_robust_mutex;
} robustness_test_config_t;

/**
 * 测试统计信息
 */
typedef struct {
    atomic_int lock_attempts;
    atomic_int lock_success;
    atomic_int lock_failures;
    atomic_int owner_dead_detected;
    atomic_int consistency_restored;
    atomic_int normal_terminations;
    atomic_int abnormal_terminations;
} test_stats_t;

/**
 * 测试线程数据
 */
typedef struct {
    int thread_id;
    pthread_mutex_t *mutex;
    test_stats_t *stats;
    robustness_test_config_t *config;
    volatile int *terminate_flag;
} test_thread_data_t;

/**
 * 初始化健壮互斥锁
 */
int init_test_mutex(pthread_mutex_t *mutex, int robust) {
    pthread_mutexattr_t attr;
    int result;
    
    result = pthread_mutexattr_init(&attr);
    if (result != 0) return result;
    
    if (robust) {
        result = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
        if (result != 0) {
            pthread_mutexattr_destroy(&attr);
            return result;
        }
    }
    
    result = pthread_mutex_init(mutex, &attr);
    pthread_mutexattr_destroy(&attr);
    
    return result;
}

/**
 * 测试线程函数
 */
void* test_thread(void *arg) {
    test_thread_data_t *data = (test_thread_data_t*)arg;
    int result;
    
    printf("测试线程 %d 启动\n", data->thread_id);
    
    srand(time(NULL) + data->thread_id);
    
    while (!(*data->terminate_flag)) {
        // 尝试获取互斥锁
        atomic_fetch_add(&data->stats->lock_attempts, 1);
        result = pthread_mutex_lock(data->mutex);
        
        if (result == 0) {
            atomic_fetch_add(&data->stats->lock_success, 1);
            
            // 模拟持有锁的工作
            usleep(10000 + (rand() % 50000));  // 10-60ms
            
            // 随机决定是否异常终止(模拟崩溃)
            if (data->config->crash_probability > 0 && 
                rand() % 100 < data->config->crash_probability) {
                printf("线程 %d: 模拟异常终止\n", data->thread_id);
                atomic_fetch_add(&data->stats->abnormal_terminations, 1);
                exit(1);  // 模拟崩溃,不释放锁
            }
            
            // 正常释放锁
            pthread_mutex_unlock(data->mutex);
            atomic_fetch_add(&data->stats->normal_terminations, 1);
            
        } else if (result == EOWNERDEAD) {
            atomic_fetch_add(&data->stats->owner_dead_detected, 1);
            printf("线程 %d: 检测到锁持有者异常终止\n", data->thread_id);
            
            // 恢复一致性
            result = pthread_mutex_consistent(data->mutex);
            if (result == 0) {
                atomic_fetch_add(&data->stats->consistency_restored, 1);
                printf("线程 %d: 成功恢复锁一致性\n", data->thread_id);
                pthread_mutex_unlock(data->mutex);
            } else {
                atomic_fetch_add(&data->stats->lock_failures, 1);
                printf("线程 %d: 恢复一致性失败: %s\n", data->thread_id, strerror(result));
            }
        } else {
            atomic_fetch_add(&data->stats->lock_failures, 1);
            printf("线程 %d: 获取锁失败: %s\n", data->thread_id, strerror(result));
        }
        
        // 短暂休息
        usleep(10000 + (rand() % 20000));  // 10-30ms
    }
    
    printf("测试线程 %d 结束\n", data->thread_id);
    return NULL;
}

/**
 * 显示测试统计
 */
void show_test_statistics(test_stats_t *stats) {
    printf("\n=== 测试统计结果 ===\n");
    printf("锁尝试次数: %d\n", atomic_load(&stats->lock_attempts));
    printf("成功获取锁: %d\n", atomic_load(&stats->lock_success));
    printf("获取锁失败: %d\n", atomic_load(&stats->lock_failures));
    printf("检测到异常终止: %d\n", atomic_load(&stats->owner_dead_detected));
    printf("恢复一致性: %d\n", atomic_load(&stats->consistency_restored));
    printf("正常终止: %d\n", atomic_load(&stats->normal_terminations));
    printf("异常终止: %d\n", atomic_load(&stats->abnormal_terminations));
    
    if (atomic_load(&stats->lock_attempts) > 0) {
        double success_rate = (double)atomic_load(&stats->lock_success) / 
                             atomic_load(&stats->lock_attempts) * 100;
        printf("锁获取成功率: %.2f%%\n", success_rate);
    }
}

/**
 * 演示健壮性测试
 */
int demo_robustness_testing() {
    pthread_mutex_t mutex;
    pthread_t *threads;
    test_thread_data_t *thread_data;
    test_stats_t stats = {0};
    volatile int terminate_flag = 0;
    
    robustness_test_config_t config = {
        .num_threads = 5,
        .test_duration = 30,  // 30秒
        .crash_probability = 5,  // 5%概率异常终止
        .use_robust_mutex = 1
    };
    
    printf("=== 健壮性测试工具 ===\n");
    printf("测试配置:\n");
    printf("  线程数: %d\n", config.num_threads);
    printf("  测试时长: %d 秒\n", config.test_duration);
    printf("  异常终止概率: %d%%\n", config.crash_probability);
    printf("  使用健壮互斥锁: %s\n", config.use_robust_mutex ? "是" : "否");
    
    // 初始化互斥锁
    if (init_test_mutex(&mutex, config.use_robust_mutex) != 0) {
        printf("初始化互斥锁失败\n");
        return -1;
    }
    
    // 分配线程数组
    threads = malloc(config.num_threads * sizeof(pthread_t));
    thread_data = malloc(config.num_threads * sizeof(test_thread_data_t));
    if (!threads || !thread_data) {
        printf("分配内存失败\n");
        pthread_mutex_destroy(&mutex);
        free(threads);
        free(thread_data);
        return -1;
    }
    
    // 创建测试线程
    printf("创建 %d 个测试线程...\n", config.num_threads);
    
    for (int i = 0; i < config.num_threads; i++) {
        thread_data[i].thread_id = i + 1;
        thread_data[i].mutex = &mutex;
        thread_data[i].stats = &stats;
        thread_data[i].config = &config;
        thread_data[i].terminate_flag = &terminate_flag;
        
        int result = pthread_create(&threads[i], NULL, test_thread, &thread_data[i]);
        if (result != 0) {
            printf("创建线程 %d 失败: %s\n", i + 1, strerror(result));
            // 清理已创建的线程
            terminate_flag = 1;
            for (int j = 0; j < i; j++) {
                pthread_join(threads[j], NULL);
            }
            pthread_mutex_destroy(&mutex);
            free(threads);
            free(thread_data);
            return -1;
        }
    }
    
    // 运行测试
    printf("开始测试,持续 %d 秒...\n", config.test_duration);
    sleep(config.test_duration);
    
    // 停止测试
    printf("停止测试...\n");
    terminate_flag = 1;
    
    // 等待所有线程结束
    for (int i = 0; i < config.num_threads; i++) {
        pthread_join(threads[i], NULL);
    }
    
    // 显示统计结果
    show_test_statistics(&stats);
    
    // 清理资源
    pthread_mutex_destroy(&mutex);
    free(threads);
    free(thread_data);
    
    return 0;
}

int main() {
    return demo_robustness_testing();
}

set_robust_list 使用注意事项

系统要求:

  1. 内核版本: 需要Linux 2.6.17或更高版本
  2. 架构支持: 支持所有主流架构
  3. 编译环境: 需要正确的系统头文件

功能限制:

  1. FUTEX支持: 需要内核支持FUTEX同步原语
  2. pthread支持: 需要支持健壮互斥锁的pthread实现
  3. 权限要求: 通常不需要特殊权限

错误处理:

  1. ENOSYS: 系统不支持健壮列表功能
  2. EINVAL: 参数无效
  3. EFAULT: 指针参数无效
  4. ENOMEM: 内存不足

性能考虑:

  1. 开销: 健壮互斥锁比普通互斥锁有额外开销
  2. 列表维护: 需要维护健壮列表结构
  3. 异常处理: 异常终止时需要额外处理时间

最佳实践:

  1. 选择性使用: 只在需要健壮性的场景使用
  2. 正确处理: 妥善处理EOWNERDEAD错误
  3. 及时清理: 正常情况下及时释放互斥锁
  4. 测试验证: 充分测试异常场景下的行为

健壮互斥锁工作原理

1. 正常操作流程:

线程A获取锁 -> 执行临界区 -> 释放锁 -> 线程B获取锁

2. 异常终止处理:

线程A获取锁 -> 异常终止(未释放锁) -> 内核检测 -> 自动释放锁 -> 线程B获取锁时得到EOWNERDEAD -> 调用pthread_mutex_consistent -> 恢复正常使用

3. 关键数据结构:

// 健壮列表头
struct robust_list_head {
    struct robust_list *list;        // 列表指针
    long futex_offset;               // futex偏移量
    unsigned long list_op_pending;   // 待处理操作
};

// 列表节点
struct robust_list {
    struct robust_list *next;        // 下一个节点
};

常见使用场景

1. 服务器应用:

// 为关键资源使用健壮互斥锁,防止服务器进程崩溃导致死锁
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);

2. 数据库系统:

// 保护数据库连接和事务资源,确保异常终止时资源能被正确释放

3. 实时系统:

// 在实时应用中确保关键资源不会因为进程异常而永久锁定

错误处理指南

EOWNERDEAD处理:

int result = pthread_mutex_lock(&mutex);
if (result == EOWNERDEAD) {
    // 检测到锁持有者异常终止
    result = pthread_mutex_consistent(&mutex);
    if (result == 0) {
        // 成功恢复一致性,可以正常使用锁
        // 注意:锁保护的数据可能处于不一致状态
    } else {
        // 恢复失败,应该释放锁并处理错误
        pthread_mutex_unlock(&mutex);
    }
}

总结

set_robust_list 和相关的健壮互斥锁机制提供了:

  1. 故障恢复: 进程异常终止时自动释放互斥锁
  2. 死锁预防: 防止因进程崩溃导致的永久阻塞
  3. 系统稳定性: 提高多线程应用的可靠性
  4. 透明处理: 对应用层提供相对透明的健壮性保证

通过合理使用健壮互斥锁,可以构建更加可靠的多线程应用程序,特别是在需要长时间运行和高可用性的系统中表现出色。在实际应用中,需要注意性能开销和正确的错误处理。

发表在 linux文章 | 留下评论

set_thread_area系统调用及示例

我们来深入学习 set_thread_area 系统调用,在 Linux 系统中,尤其是在 i386 (IA-32) 架构上,CPU 提供了一种特殊的机制来存储线程本地的数据,这就是 Thread Local Storage (TLS)。这种机制允许每个线程拥有自己独立的一份变量副本,即使变量名相同,不同线程访问的也是不同的数据。

1. 函数介绍

在 Linux 系统中,尤其是在 i386 (IA-32) 架构上,CPU 提供了一种特殊的机制来存储线程本地的数据,这就是 Thread Local Storage (TLS)。这种机制允许每个线程拥有自己独立的一份变量副本,即使变量名相同,不同线程访问的也是不同的数据。

在 i386 架构上,实现 TLS 的一种方法是利用 CPU 的 段寄存器(Segment Register),特别是 gs 段寄存器。CPU 可以被配置,使得访问 gs:0, gs:4, gs:8 这样的地址时,实际上访问的是内存中特定区域的数据。这个“特定区域”对每个线程来说是不同的。

set_thread_area 系统调用(以及配套的 get_thread_area)就是 Linux 内核提供给用户空间程序的接口,用于设置或获取当前线程的这种 TLS 描述符(Thread Local Storage Descriptor)。这个描述符告诉内核和 CPU:当这个线程使用 gs 段寄存器时,它的基地址应该设置在哪里。

简单来说,set_thread_area 是 i386 架构上,用户空间程序告诉内核“请为我配置一下 gs 段寄存器,让它指向我线程本地数据的起始位置”的方式。

重要提示:这是一个非常底层且架构特定(主要是 i386)的系统调用。现代的、可移植的代码通常使用编译器和 C 库提供的标准 TLS 支持(如 __thread 关键字),这些高级工具在底层可能会(也可能不会)使用 set_thread_area。

对于 Linux 编程小白:除非你在进行非常底层的系统编程、编写 C 库本身或者需要与旧的使用此机制的代码交互,否则你不需要直接了解或使用 set_thread_area。理解它有助于了解 TLS 在 i386 上的一种实现机制。

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

2. 函数原型

// 这是 i386 特定的系统调用
#include <asm/ldt.h>    // 包含 user_desc 结构体定义
#include <sys/syscall.h> // 包含系统调用号 SYS_set_thread_area
#include <unistd.h>      // 包含 syscall 函数

// 注意:标准 C 库通常不提供直接的包装函数
long syscall(SYS_set_thread_area, struct user_desc *u_info);
long syscall(SYS_get_thread_area, struct user_desc *u_info);

3. 功能

设置或修改调用线程的 Thread Local Storage (TLS) 描述符。这个描述符定义了 TLS 段(通常由 gs 段寄存器引用)在内存中的位置和属性。

4. 参数

  • u_info:
    • struct user_desc * 类型。
    • 一个指向 user_desc 结构体的指针。这个结构体包含了 TLS 描述符的详细信息。调用 set_thread_area 时,你需要填充这个结构体;调用 get_thread_area 时,内核会填充它。

struct user_desc 结构体 (简化版,定义在 <asm/ldt.h>):

struct user_desc {
    unsigned int  entry_number; // 描述符在 GDT/LDT 中的索引 (输入/输出)
    unsigned long base_addr;    // TLS 段的基地址 (输入)
    unsigned int  limit;        // 段的大小限制 (通常设为 0xfffff)
    unsigned int  seg_32bit:1;  // 是否为 32 位段 (通常设为 1)
    unsigned int  contents:2;   // 段内容类型 (通常设为 0)
    unsigned int  read_exec_only:1; // 是否只读/执行 (通常设为 0)
    unsigned int  limit_in_pages:1; // limit 是否以页为单位 (通常设为 1)
    unsigned int  seg_not_present:1; // 段是否存在 (通常设为 0)
    unsigned int  useable:1;        // 是否可用 (通常设为 1)
    // ... 可能还有其他字段,取决于内核版本
};

关键字段解释:

  • entry_number: 指定要设置的 GDT (Global Descriptor Table) 条目索引。如果传入 0xffffffff (或 -1),内核会选择一个可用的索引并返回给调用者。
  • base_addr: 这是最重要的字段,它指定了 TLS 数据在内存中的起始地址。线程通过 gs:offset 访问的数据就位于 base_addr + offset
  • 其他字段是 x86 段描述符的标准属性,对于 TLS 用途,通常使用上述的典型值。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
    • EFAULTu_info 指向无效的内存地址。
    • EINVALentry_number 无效,或者 user_desc 结构体中的某些字段值无效。
    • EPERM: 进程没有权限执行此操作(在某些安全模型下)。

6. 相似函数或关联函数

  • get_thread_area: 获取当前线程的 TLS 描述符。
  • arch_prctl: 在 x86-64 (AMD64) 架构上,用于设置 TLS,因为 x86-64 不使用 set_thread_area。例如,arch_prctl(ARCH_SET_FS, tls_base_address)
  • __thread 关键字 (GCC): C 编译器提供的标准 TLS 支持。这是编写可移植 TLS 代码的推荐方式。编译器和 C 库(如 glibc)会处理底层细节(可能使用 set_thread_area 或 arch_prctl)。
  • pthread_getspecific / pthread_setspecific: POSIX 线程库提供的另一种实现线程本地存储的方式,它不依赖于 CPU 的段寄存器机制。
  • syscall: 用于直接调用 Linux 系统调用的函数。

7. 示例代码

由于 set_thread_area 是底层且架构特定的,直接使用它编写用户程序比较复杂且不推荐。下面的示例主要用于展示其用法,但请注意这更多是教学或系统级编程的内容。

警告:此代码仅在 i386 架构上可能有效,且需要对 x86 汇编和内存布局有一定了解。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <asm/ldt.h> // 包含 struct user_desc
#include <errno.h>
#include <string.h>
#include <sys/mman.h> // 包含 mmap

// 注意:直接内联汇编访问 gs 段在不同编译器和优化级别下可能行为不同
// 这只是一个概念验证

int main() {
    struct user_desc u_info;
    long result;
    void *tls_mem;
    int my_tls_var_offset = 0; // 假设我们的 TLS 变量在 TLS 块的偏移 0 处

    printf("--- Demonstrating set_thread_area (i386 specific) ---\n");
    printf("This is a low-level example and may not work on all systems.\n");
    printf("Architecture: %s\n", __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ ? "Little Endian" : "Big Endian");
    printf("Pointer size: %zu bytes\n", sizeof(void*));

    // 1. 分配一块内存用于 TLS 数据
    // 这块内存将包含线程本地的变量
    tls_mem = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (tls_mem == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }
    printf("Allocated TLS memory at: %p\n", tls_mem);

    // 2. 在这块内存中存放一些数据
    *(int*)tls_mem = 12345;
    printf("Stored value %d at TLS memory location.\n", *(int*)tls_mem);

    // 3. 准备 user_desc 结构体
    memset(&u_info, 0, sizeof(u_info));
    u_info.entry_number = -1; // 请求内核分配一个 entry
    u_info.base_addr = (unsigned long) tls_mem; // 设置 TLS 块的基地址
    u_info.limit = 0xfffff; // 设置段限制 (4GB)
    u_info.seg_32bit = 1;   // 32位段
    u_info.contents = 0;    // 数据段
    u_info.read_exec_only = 0; // 可读写
    u_info.limit_in_pages = 1; // 限制以页为单位
    u_info.seg_not_present = 0; // 段存在
    u_info.useable = 1;     // 可用

    printf("Setting up TLS descriptor...\n");
    printf("  Base Address: 0x%lx\n", u_info.base_addr);
    printf("  Entry Number requested: %u\n", u_info.entry_number);

    // 4. 调用 set_thread_area 系统调用
    result = syscall(SYS_set_thread_area, &u_info);

    if (result == -1) {
        perror("set_thread_area");
        munmap(tls_mem, 4096);
        exit(EXIT_FAILURE);
    }

    printf("set_thread_area succeeded.\n");
    printf("  Assigned Entry Number: %u\n", u_info.entry_number);
    printf("  Base Address (returned): 0x%lx\n", u_info.base_addr);

    // 5. 理论上,现在可以通过 gs 段寄存器访问 tls_mem 指向的内存
    // 例如,访问偏移 my_tls_var_offset 处的 4 字节整数
    // 这需要内联汇编,且行为高度依赖于编译器和系统
    // 下面的代码是概念性的,实际运行可能失败或产生未定义行为
    /*
    int value_from_tls = 0;
    asm volatile (
        "movl %%gs:%1, %0"
        : "=r" (value_from_tls)
        : "m" (*(int*)my_tls_var_offset)
    );
    printf("Value read from TLS via GS register: %d\n", value_from_tls);
    */

    printf("\n--- Important Notes ---\n");
    printf("1. Direct use of GS register via inline assembly is complex and not portable.\n");
    printf("2. Modern code should use '__thread' keyword or pthread_getspecific/setspecific.\n");
    printf("3. This example is for educational purposes on i386 architecture.\n");

    // 6. 清理资源
    munmap(tls_mem, 4096);
    printf("Cleaned up TLS memory.\n");

    return 0;
}

使用标准 TLS (__thread) 的对比示例 (推荐方式):

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 使用 __thread 关键字声明线程本地变量
// 编译器和 C 库会处理底层 TLS 机制
__thread int my_tls_variable = 0;

void* worker_thread(void *arg) {
    long thread_id = (long)arg;
    // 每个线程修改自己的 my_tls_variable 副本
    my_tls_variable = thread_id * 100;
    printf("Thread %ld: my_tls_variable = %d\n", thread_id, my_tls_variable);
    sleep(1);
    printf("Thread %ld: my_tls_variable is still %d\n", thread_id, my_tls_variable);
    return NULL;
}

int main() {
    pthread_t t1, t2;
    printf("--- Using standard TLS (__thread) ---\n");

    // 主线程的 my_tls_variable
    my_tls_variable = 999;
    printf("Main thread: my_tls_variable = %d\n", my_tls_variable);

    // 创建线程
    if (pthread_create(&t1, NULL, worker_thread, (void*)1) != 0 ||
        pthread_create(&t2, NULL, worker_thread, (void*)2) != 0) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // 检查主线程的变量是否未受影响
    printf("Main thread: my_tls_variable is still %d\n", my_tls_variable);

    printf("Program finished using standard, portable TLS.\n");
    return 0;
}

编译和运行:

# 假设代码保存在 set_thread_area_example.c 和 standard_tls_example.c 中

# 编译标准 TLS 示例 (推荐,跨平台)
gcc -o standard_tls_example standard_tls_example.c -lpthread

# 编译 set_thread_area 示例 (仅适用于 i386,可能需要调整)
# 在 64 位系统上编译 32 位程序
gcc -m32 -o set_thread_area_example set_thread_area_example.c

# 运行标准示例
./standard_tls_example

# 尝试运行 i386 示例 (可能失败或需要在 32 位系统/i386 模拟器上运行)
# ./set_thread_area_example

总结:
对于 Linux 编程新手,请优先学习和使用标准的 TLS 机制,如 __thread 关键字。set_thread_area 是一个底层、架构特定的工具,主要用于系统级编程或与旧代码交互。理解它有助于深入学习操作系统和 CPU 架构知识。

发表在 linux文章 | 留下评论

set_tid_address系统调用及示例

我们来深入学习 set_tid_address 系统调用

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

在 Linux 系统中,进程和线程是程序执行的基本单位。每个线程都有一个唯一的标识符,叫做 Thread ID (TID)。对于主线程(也就是进程本身),它的 TID 通常和 Process ID (PID) 是相同的。但对于通过 clone() 或 pthread_create() 创建的子线程,它们会有自己独立的 TID。

有时候,一个线程(或进程)需要知道另一个线程何时退出。例如,在一个多线程服务器中,主线程可能需要清理已退出的工作线程的资源。

set_tid_address 系统调用提供了一种机制来实现这一点。当一个线程调用 set_tid_address 并传入一个内存地址(我们称之为“TID 地址”或 tidptr)后,内核会记住这个地址。当这个线程最终退出时,内核会执行一个非常重要的操作:将这个地址处的内存值(通常是一个 int 或 pid_t 变量)清零(设置为 0)

这样,程序的其他部分(比如父线程)就可以通过检查这个 tidptr 指向的内存位置的值来判断线程是否已经退出:如果值是 0,说明线程退出了;如果值非 0,说明线程还在运行(或者刚好是它的 TID)。

简单来说,set_tid_address 就是告诉内核:“当我死(退出)的时候,请帮我把这个地方的数字清零。”这样别人(通常是创建我的线程)就能通过看这个数字知道我是不是挂了。

重要提示:用户空间程序通常不会直接调用 set_tid_address。当你使用 pthread_create() 创建线程时,底层的 C 库(如 glibc NPTL)会自动为你调用 set_tid_address,并管理好这个 tidptr。这个系统调用主要是供 C 库实现线程功能时使用的。

对于 Linux 编程小白:你只需要知道,当你使用标准的 POSIX 线程库(pthread)时,线程退出通知机制(例如 pthread_join 能知道线程何时结束)在底层可能就是通过 set_tid_address 实现的。直接调用它比较少见,除非你在编写自己的线程库或者进行非常底层的系统编程。

2. 函数原型

// 标准 C 库通常不提供直接的包装函数
// 需要通过 syscall 直接调用
#include <sys/syscall.h> // 包含系统调用号 SYS_set_tid_address
#include <unistd.h>      // 包含 syscall 函数

long syscall(SYS_set_tid_address, int *tidptr);

3. 功能

设置当前线程在退出时用于清除的用户空间地址。内核会记录这个地址,当线程终止时,内核会将该地址指向的内存单元(通常是一个 int)的值设置为 0。

4. 参数

5. 返回值

  • 成功: 返回调用线程的 Thread ID (TID)。这使得调用者可以方便地知道自己(或被创建的线程)的 TID 是多少。
  • 失败: 理论上这个系统调用不应该失败,但如果 tidptr 指向无效内存,可能会返回错误。但在实践中,它几乎总是成功返回 TID。

6. 相似函数或关联函数

  • clone: 用于创建进程或线程的底层系统调用。clone 可以接受 CLONE_CHILD_CLEARTID 标志,该标志会使得新创建的子进程/线程在退出时自动调用类似 set_tid_address 的操作。
  • pthread_create / pthread_join: POSIX 线程库函数。pthread_create 会创建线程并可能在底层使用 set_tid_addresspthread_join 会等待线程结束,其底层实现可能依赖于 set_tid_address 提供的机制(例如通过 futex 等待 tidptr 变为 0)。
  • gettid: 获取当前线程的 TID。可以通过 syscall(SYS_gettid) 调用。
  • futex: 快速用户空间互斥锁系统调用。pthread_join 等函数可能使用 futex 来高效地等待由 set_tid_address 清零的变量。
  • wait / waitpid: 用于等待子进程结束。这是针对进程的,而 set_tid_address 是针对线程的。

7. 示例代码

由于 set_tid_address 通常由 C 库内部使用,直接调用它需要手动管理线程的创建和同步,这比较复杂。下面的示例将演示如何结合 clone 系统调用和 set_tid_address 来手动创建一个线程,并利用 set_tid_address 的机制来等待它退出。

警告:这是一个非常底层的示例,展示了 set_tid_address 和 clone 的用法。在实际编程中,强烈建议使用 pthread 库。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 clone
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h> // 包含 syscall, SYS_set_tid_address, SYS_gettid, SYS_clone
#include <sys/wait.h>
#include <sched.h>       // 包含 CLONE_* 常量
#include <errno.h>
#include <stdatomic.h>
#include <linux/futex.h> // 包含 futex 操作码
#include <sys/time.h>    // 包含 timespec
#include <limits.h>      // 包含 INT_MAX

// 定义线程栈大小
#define STACK_SIZE (1024 * 1024) // 1MB

// 线程函数
int thread_function(void *arg) {
    long thread_num = (long)arg;
    pid_t my_tid = syscall(SYS_gettid); // 获取自己的 TID
    printf("Child thread %ld (TID: %d) started.\n", thread_num, my_tid);

    // 模拟一些工作
    for (int i = 0; i < 5; ++i) {
        printf("Child thread %ld working... %d\n", thread_num, i);
        sleep(1);
    }

    printf("Child thread %ld (TID: %d) finishing.\n", thread_num, my_tid);
    // 线程函数返回时,内核会根据 set_tid_address 设置的地址,
    // 将该地址处的值清零,并可能唤醒等待的 futex。
    return 0;
}

// 模拟 pthread_join 的简单等待函数
// 等待 *tidptr 变为 0
void wait_for_thread_exit(int *tidptr) {
    // 循环检查 tidptr 指向的值
    // 在实际的库实现中,这里会使用 futex 系统调用来高效等待
    while(__atomic_load_n(tidptr, __ATOMIC_ACQUIRE) != 0) {
        printf("Main thread: Waiting for thread with TID %d to exit (tidptr=%d)...\n",
               *tidptr, *tidptr);
        // 简单的轮询等待(效率低)
        // 实际库会用 syscall(SYS_futex, tidptr, FUTEX_WAIT, 0, NULL, NULL, 0);
        sleep(1);
    }
    printf("Main thread: Detected thread has exited (tidptr is now 0).\n");
}

int main() {
    char *stack;          // 指向子线程栈的指针
    char *stack_top;      // 指向子线程栈顶的指针
    pid_t child_tid;      // 存储 clone 返回的子线程 TID
    int tid_location = 0; // 用于 set_tid_address 的变量

    printf("--- Demonstrating set_tid_address with clone ---\n");
    printf("Main thread PID/TID: %d\n", getpid()); // 主线程 PID 和 TID 相同

    // 1. 为子线程分配栈空间
    // 注意:栈是向下增长的,所以我们需要分配后调整指针
    stack = malloc(STACK_SIZE);
    if (stack == NULL) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }
    stack_top = stack + STACK_SIZE; // 栈顶指针

    printf("Allocated stack for child thread at %p (top at %p)\n", stack, stack_top);

    // 2. 使用 clone 创建子线程
    // CLONE_VM: 子线程与父线程共享进程的内存描述符 (虚拟内存空间)
    // CLONE_FS: 共享文件系统信息
    // CLONE_FILES: 共享文件描述符表
    // CLONE_SIGHAND: 共享信号处理函数表
    // CLONE_THREAD: 将子进程置于父进程的线程组中
    // CLONE_SYSVSEM: 共享 System V 信号量 undo 信息
    // CLONE_PARENT_SETTID: 将子线程的 TID 写入 ptid (我们不使用 ptid)
    // CLONE_CHILD_CLEARTID: 子线程退出时,清零 ctid 指向的值 (即 tid_location)
    // stack_top: 子线程的栈指针
    // &tid_location: ctid 参数,指向 tid_location
    child_tid = syscall(SYS_clone,
                        CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
                        CLONE_THREAD | CLONE_SYSVSEM |
                        CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID,
                        stack_top, NULL, &tid_location);

    if (child_tid == -1) {
        perror("clone");
        free(stack);
        exit(EXIT_FAILURE);
    }

    if (child_tid == 0) {
        // --- 在子线程中 ---
        // 子线程的第一件事通常是调用 set_tid_address
        // 但实际上,当我们使用 CLONE_CHILD_CLEARTID 标志时,
        // 内核已经在 clone 时为我们处理了类似 set_tid_address 的操作
        // 这里我们显式调用一次,展示其效果
        pid_t my_tid = syscall(SYS_gettid);
        long returned_tid = syscall(SYS_set_tid_address, &tid_location);
        printf("In child thread: My TID is %d, set_tid_address returned %ld\n", my_tid, returned_tid);
        // 初始化 tid_location 为自己的 TID
        __atomic_store_n(&tid_location, my_tid, __ATOMIC_RELEASE);
        printf("In child thread: Set tid_location to %d\n", tid_location);

        // 调用线程函数
        thread_function((void*)1);

        // 线程函数返回,线程退出
        // 内核会将 &tid_location 处的值清零
        free(stack); // 子线程释放栈(简化处理)
        exit(EXIT_SUCCESS); // 或者直接 return
    }

    // --- 回到主线程 ---
    printf("Main thread: clone returned child TID: %d\n", child_tid);
    printf("Main thread: tid_location variable address: %p\n", &tid_location);
    printf("Main thread: Initial value of tid_location: %d\n", tid_location);

    // 等待一小会儿,让子线程设置 tid_location
    sleep(1);
    printf("Main thread: Value of tid_location after child setup: %d\n", tid_location);

    // 3. 等待子线程退出
    // 我们可以轮询 tid_location,或者使用 futex (推荐)
    printf("Main thread: Waiting for child thread to exit...\n");
    wait_for_thread_exit(&tid_location);
    // 或者使用 futex: syscall(SYS_futex, &tid_location, FUTEX_WAIT, 0, NULL, NULL, 0);

    printf("Main thread: Child thread has exited.\n");
    printf("Main thread: Final value of tid_location: %d\n", tid_location);

    // 4. 清理 (主线程不再需要栈了,因为子线程已经退出并释放了)
    // free(stack); // 子线程已释放

    printf("Main thread: Program finished.\n");
    return 0;
}

使用标准 pthread 的对比示例 (推荐方式):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void* worker_thread(void *arg) {
    long thread_num = (long)arg;
    printf("Worker thread %ld started.\n", thread_num);

    for (int i = 0; i < 5; ++i) {
        printf("Worker thread %ld working... %d\n", thread_num, i);
        sleep(1);
    }

    printf("Worker thread %ld finishing.\n", thread_num);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;

    printf("--- Using standard pthread (Recommended) ---\n");
    printf("Main thread PID/TID: %d\n", getpid());

    // 创建线程
    if (pthread_create(&thread1, NULL, worker_thread, (void*)1) != 0 ||
        pthread_create(&thread2, NULL, worker_thread, (void*)2) != 0) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    printf("Main thread: Created worker threads.\n");

    // 等待线程结束 (这在底层可能使用了 set_tid_address 提供的机制)
    printf("Main thread: Waiting for worker threads to join...\n");
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    printf("Main thread: Both worker threads have finished.\n");
    printf("Main thread: Program finished using standard pthread library.\n");

    return 0;
}

编译和运行:

# 假设代码保存在 set_tid_address_example.c 和 pthread_example.c 中
# 需要链接 pthread 库

# 编译 pthread 示例 (推荐,简单,可移植)
gcc -o pthread_example pthread_example.c -lpthread

# 编译 set_tid_address 示例 (底层,复杂)
gcc -o set_tid_address_example set_tid_address_example.c

# 运行 pthread 示例
./pthread_example

# 运行 set_tid_address 示例
./set_tid_address_example

总结:
对于 Linux 编程新手,请优先学习和使用标准的 pthread 库来创建和管理线程。set_tid_address 是一个底层系统调用,主要用于 C 库实现线程功能,它提供了一种高效的线程退出通知机制。直接使用它需要深入了解系统调用、内存管理和线程同步,通常只在编写系统级代码时才会涉及。

发表在 linux文章 | 留下评论

setgid系统调用及示例

我们来深入学习 setgid 系统调用,在 Linux 系统中,每个进程都运行在一个特定的用户(User)和组(Group)上下文中。这个上下文决定了进程拥有哪些权限,比如能否读写某个文件、能否绑定到特权端口(端口号小于 1024)等。

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

setfsgid系统调用及示例

系统调用LinuxGuide

setpgid系统调用及示例_setpgid函数作用-CSDN博客

1. 函数介绍

在 Linux 系统中,每个进程都运行在一个特定的用户(User)和组(Group)上下文中。这个上下文决定了进程拥有哪些权限,比如能否读写某个文件、能否绑定到特权端口(端口号小于 1024)等。

每个进程通常有三类相关的用户/组 ID:

  • 真实 ID (Real ID): 登录系统时分配给用户的 ID。它标识了“你是谁”。
  • 有效 ID (Effective ID): 内核用来进行权限检查时使用的 ID。它决定了“你能做什么”。
  • 保存的设置 ID (Saved Set-ID): 用于在有效 ID 和真实 ID 之间来回切换的一个“备份”ID。

[setgid(Set Group ID)系统调用的主要作用是设置调用进程的有效组 ID (Effective GID)。根据调用者的权限和当前 ID 状态,它也可能同时修改保存的设置组 ID (Saved Set-GID)。

简单来说,setgid 让你的程序可以“以某个组的身份”去执行操作,从而获得或限制与该组相关的权限。](https://www.calcguide.tech/2025/08/23/setgid%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

一个常见的场景是:一个需要访问特定组才能读写的文件或设备的程序,可以通过 setgid 来获取相应的组权限。

2. 函数原型

#include <unistd.h> // 包含系统调用声明
#include <sys/types.h> // 包含 gid_t 类型定义

int setgid(gid_t gid);

3. 功能

设置调用进程的有效组 ID (Effective GID)。根据调用者的权限(是否为 root)和目标 gid,行为会有所不同。

4. 参数

  • gid:
    • gid_t 类型。
    • 指定要设置的新的有效组 ID。

5. 返回值

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

6. 行为规则

setgid 的具体行为取决于调用进程的权限:

  1. 如果调用者是特权用户 (超级用户, root):
    • 可以将有效 GID (egid) 设置为任意有效的组 ID。
    • 同时,真实 GID (rgid) 和保存的设置 GID (sgid) 也会被设置为相同的 gid 值。
    • 这是特权用户的强大能力。
  2. 如果调用者是普通用户:
    • gid 参数必须是调用进程的真实 GID (rgid) 或 保存的设置 GID (sgid) 之一。
    • 只能将有效 GID (egid) 设置为 rgid 或 sgid
    • 真实 GID (rgid) 和 保存的设置 GID (sgid) 不会被修改
    • 这是为了防止普通用户随意获取其他组的权限。

7. 错误码 (errno)

  • EINVALgid 参数无效(虽然在 Linux 中通常不会返回此错误)。
  • EPERM: 调用者没有权限执行此操作。对于普通用户,这意味着 gid 既不是 rgid 也不是 sgid

8. 相似函数或关联函数

  • setuid: 设置用户 ID,与 setgid 功能类似,但针对的是用户而非组。
  • setegid: 专门用于设置有效组 ID,行为比 setgid 更受限(普通用户只能设置为 rgid 或 sgid)。
  • setregid: 同时设置真实组 ID 和 有效组 ID。
  • setresgid: 同时设置 真实有效 和 保存的设置 组 ID,提供了最精细的控制。
  • getgid: 获取调用进程的真实组 ID。
  • getegid: 获取调用进程的有效组 ID。
  • getgroups: 获取调用进程所属的附加组列表。

9. 示例代码

下面的示例演示了 setgid 在不同权限下的行为,以及如何检查组 ID。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresgid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

void print_current_gids(const char* context) {
    gid_t rgid, egid, sgid;
    printf("[%s] Current GIDs - Real: %d, Effective: %d, Saved: %d\n",
           context, getgid(), getegid(), (getresgid(&rgid, &egid, &sgid) == 0) ? sgid : -1);
}

int main() {
    gid_t original_rgid, original_egid, original_sgid;
    gid_t target_gid;
    int result;

    printf("--- Demonstrating setgid ---\n");

    // 1. 获取并打印初始 GID
    if (getresgid(&original_rgid, &original_egid, &original_sgid) != 0) {
        perror("getresgid");
        exit(EXIT_FAILURE);
    }
    print_current_gids("Start");

    // 2. 检查是否以 root 权限运行
    if (geteuid() == 0) {
        printf("\nRunning as ROOT (Privileged User)\n");
        // 作为 root,可以设置为任意有效的 GID
        // 这里我们尝试设置为 'daemon' 组 (通常 GID 1, 但请检查你的系统)
        target_gid = 1;
        printf("Attempting to set GID to %d (usually 'daemon' group)...\n", target_gid);

        print_current_gids("Before setgid");
        result = setgid(target_gid);
        if (result == 0) {
            printf("setgid(%d) succeeded.\n", target_gid);
            print_current_gids("After setgid");
            printf("Note: All GIDs (Real, Effective, Saved) are now %d.\n", target_gid);
        } else {
            perror("setgid");
            printf("Failed to set GID to %d.\n", target_gid);
        }

    } else {
        printf("\nRunning as a REGULAR USER (UID: %d)\n", getuid());

        // 作为普通用户,只能设置为自己的 rgid 或 sgid
        target_gid = original_rgid; // 选择设置为自己的真实 GID (这不会改变任何东西)
        printf("Attempting to set GID to my Real GID (%d)...\n", target_gid);
        print_current_gids("Before setgid");
        result = setgid(target_gid);
        if (result == 0) {
            printf("setgid(%d) succeeded (as expected).\n", target_gid);
            print_current_gids("After setgid");
        } else {
            perror("setgid");
        }

        // 尝试设置为一个无效的 GID (比如一个不存在的或不属于我的 GID)
        // 这通常会失败
        target_gid = 9999; // 假设这是一个无效的或不属于当前用户的 GID
        printf("\nAttempting to set GID to an invalid/different GID (%d)...\n", target_gid);
        result = setgid(target_gid);
        if (result == -1) {
            if (errno == EPERM) {
                printf("setgid(%d) failed with EPERM (Operation not permitted) - as expected for a regular user.\n", target_gid);
                printf("This is because %d is not my Real GID (%d) or Saved Set-GID (%d).\n",
                       target_gid, original_rgid, original_sgid);
            } else {
                perror("setgid");
            }
            print_current_gids("After failed setgid");
        } else {
            printf("setgid(%d) unexpectedly succeeded.\n", target_gid);
            print_current_gids("After unexpected setgid");
        }
    }

    printf("\n--- Summary ---\n");
    printf("The setgid() function changes the Effective GID of the process.\n");
    printf("For root: It can change to any GID, and also changes Real and Saved GID.\n");
    printf("For regular users: It can only change Effective GID to Real or Saved GID.\n");

    return 0;
}

编译和运行:

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

# 1. 作为普通用户运行
./setgid_example

# 2. 作为 root 用户运行 (需要 sudo 权限)
# 注意:切换到 root 权限执行程序有风险,请小心!
sudo ./setgid_example

预期输出 (作为普通用户运行):

--- Demonstrating setgid ---
[Start] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000

Running as a REGULAR USER (UID: 1000)
Attempting to set GID to my Real GID (1000)...
[Before setgid] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000
setgid(1000) succeeded (as expected).
[After setgid] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000

Attempting to set GID to an invalid/different GID (9999)...
setgid(9999) failed with EPERM (Operation not permitted) - as expected for a regular user.
This is because 9999 is not my Real GID (1000) or Saved Set-GID (1000).
[After failed setgid] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000

--- Summary ---
The setgid() function changes the Effective GID of the process.
For root: It can change to any GID, and also changes Real and Saved GID.
For regular users: It can only change Effective GID to Real or Saved GID.

预期输出 (使用 sudo 以 root 权限运行):

--- Demonstrating setgid ---
[Start] Current GIDs - Real: 0, Effective: 0, Saved: 0

Running as ROOT (Privileged User)
Attempting to set GID to 1 (usually 'daemon' group)...
[Before setgid] Current GIDs - Real: 0, Effective: 0, Saved: 0
setgid(1) succeeded.
[After setgid] Current GIDs - Real: 1, Effective: 1, Saved: 1
Note: All GIDs (Real, Effective, Saved) are now 1.

总结:
setgid 是一个基础且重要的系统调用,用于管理进程的组权限。理解它的行为规则(尤其是特权用户和普通用户的区别)对于编写安全的 Linux 程序至关重要。在实际应用中,它常用于守护进程(daemons)或需要特定组权限的程序中。

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

发表在 linux文章 | 留下评论

setgroups系统调用及示例

setgroups 函数详解

1. 函数介绍

setgroups 是Linux系统调用,用于设置进程的附加组ID列表。每个进程都可以属于多个组,除了主组ID(由setgid设置)外,还可以通过附加组ID列表拥有多个组的权限。这对于实现细粒度的权限控制和访问控制非常重要。

csdn:setgroups系统调用及示例-CSDN博客

2. 函数原型

#include <grp.h>
#include <unistd.h>
int setgroups(size_t size, const gid_t *list);

3. 功能

setgroups 设置调用进程的附加组ID列表,替换当前的附加组集合。这允许进程获得多个组的权限,从而可以访问属于这些组的文件和资源。

4. 参数

  • size_t size: 附加组ID列表的大小(元素个数)
  • *const gid_t list: 指向组ID数组的指针

5. 返回值

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

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

  • getgroups: 获取当前附加组ID列表
  • setgid: 设置主组ID
  • setuid: 设置用户ID
  • initgroups: 根据用户初始化组列表

7. 示例代码

示例1:基础setgroups使用

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 显示当前用户和组信息
 */
void show_current_user_info() {
    uid_t uid = getuid();
    gid_t gid = getgid();
    uid_t euid = geteuid();
    gid_t egid = getegid();
    
    printf("=== 当前用户和组信息 ===\n");
    printf("真实用户ID: %d\n", uid);
    printf("真实组ID: %d\n", gid);
    printf("有效用户ID: %d\n", euid);
    printf("有效组ID: %d\n", egid);
    
    // 显示当前附加组
    int max_groups = getgroups(0, NULL);
    if (max_groups > 0) {
        gid_t *groups = malloc(max_groups * sizeof(gid_t));
        if (groups) {
            int num_groups = getgroups(max_groups, groups);
            if (num_groups > 0) {
                printf("附加组ID (%d个): ", num_groups);
                for (int i = 0; i < num_groups; i++) {
                    printf("%d ", groups[i]);
                }
                printf("\n");
                
                // 显示组名
                printf("附加组名: ");
                for (int i = 0; i < num_groups; i++) {
                    struct group *grp = getgrgid(groups[i]);
                    if (grp) {
                        printf("%s ", grp->gr_name);
                    } else {
                        printf("%d ", groups[i]);
                    }
                }
                printf("\n");
            }
            free(groups);
        }
    }
    printf("\n");
}

/**
 * 演示基础setgroups使用方法
 */
int demo_setgroups_basic() {
    gid_t current_groups[32];
    gid_t new_groups[] = {100, 200, 300, 400};  // 示例组ID
    int num_current, num_new = 4;
    int result;
    
    printf("=== 基础setgroups使用示例 ===\n");
    
    // 显示当前用户信息
    show_current_user_info();
    
    // 获取当前附加组
    num_current = getgroups(sizeof(current_groups) / sizeof(current_groups[0]), 
                           current_groups);
    if (num_current == -1) {
        printf("获取当前附加组失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("当前附加组数量: %d\n", num_current);
    if (num_current > 0) {
        printf("当前附加组ID: ");
        for (int i = 0; i < num_current; i++) {
            printf("%d ", current_groups[i]);
        }
        printf("\n");
    }
    
    // 尝试设置新的附加组(需要适当权限)
    printf("\n尝试设置新的附加组列表:\n");
    for (int i = 0; i < num_new; i++) {
        printf("  组ID: %d\n", new_groups[i]);
    }
    
    result = setgroups(num_new, new_groups);
    if (result == 0) {
        printf("✓ 成功设置附加组列表\n");
        
        // 验证设置结果
        gid_t verify_groups[32];
        int verify_count = getgroups(sizeof(verify_groups) / sizeof(verify_groups[0]), 
                                    verify_groups);
        if (verify_count > 0) {
            printf("验证附加组列表:\n");
            printf("  组数量: %d\n", verify_count);
            printf("  组ID: ");
            for (int i = 0; i < verify_count; i++) {
                printf("%d ", verify_groups[i]);
            }
            printf("\n");
        }
    } else {
        printf("✗ 设置附加组列表失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  原因:需要CAP_SETGID权限或root权限\n");
        } else if (errno == EINVAL) {
            printf("  原因:参数无效\n");
        }
        
        printf("注意:普通用户通常无法修改附加组列表\n");
    }
    
    return 0;
}

int main() {
    return demo_setgroups_basic();
}

示例2:权限检查和组管理

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 检查当前权限
 */
void check_permissions() {
    uid_t uid = getuid();
    uid_t euid = geteuid();
    gid_t gid = getgid();
    
    printf("=== 权限检查 ===\n");
    printf("真实用户ID: %d\n", uid);
    printf("有效用户ID: %d\n", euid);
    printf("主组ID: %d\n", gid);
    
    if (uid == 0 || euid == 0) {
        printf("✓ 当前具有root权限\n");
    } else {
        printf("✗ 当前没有root权限\n");
        printf("  提示:修改附加组需要CAP_SETGID权限或root权限\n");
    }
    printf("\n");
}

/**
 * 查找系统中的组
 */
void find_system_groups() {
    struct group *grp;
    int group_count = 0;
    
    printf("=== 系统中的部分组信息 ===\n");
    
    // 查找一些常见的组
    const char *common_groups[] = {"root", "daemon", "sys", "adm", "tty", "disk", NULL};
    
    for (int i = 0; common_groups[i]; i++) {
        grp = getgrnam(common_groups[i]);
        if (grp) {
            printf("  组名: %-10s ID: %d\n", grp->gr_name, grp->gr_gid);
            group_count++;
        }
    }
    
    printf("  找到 %d 个常用组\n\n", group_count);
}

/**
 * 演示权限检查和组管理
 */
int demo_permission_check() {
    gid_t test_groups[5];
    int num_groups = 0;
    
    printf("=== 权限检查和组管理演示 ===\n");
    
    // 检查当前权限
    check_permissions();
    
    // 查找系统组
    find_system_groups();
    
    // 准备测试组列表
    struct group *grp;
    
    // 尝试获取一些系统组ID
    grp = getgrnam("daemon");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 daemon (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    grp = getgrnam("sys");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 sys (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    grp = getgrnam("adm");
    if (grp) {
        test_groups[num_groups++] = grp->gr_gid;
        printf("添加组 adm (ID: %d) 到测试列表\n", grp->gr_gid);
    }
    
    if (num_groups == 0) {
        // 如果找不到系统组,使用示例ID
        test_groups[0] = 100;
        test_groups[1] = 200;
        test_groups[2] = 300;
        num_groups = 3;
        printf("使用示例组ID: 100, 200, 300\n");
    }
    
    // 显示当前附加组
    printf("\n当前附加组:\n");
    int current_count = getgroups(0, NULL);
    if (current_count > 0) {
        gid_t *current_groups = malloc(current_count * sizeof(gid_t));
        if (current_groups) {
            int actual_count = getgroups(current_count, current_groups);
            if (actual_count > 0) {
                printf("  ");
                for (int i = 0; i < actual_count; i++) {
                    struct group *g = getgrgid(current_groups[i]);
                    if (g) {
                        printf("%s(%d) ", g->gr_name, current_groups[i]);
                    } else {
                        printf("%d ", current_groups[i]);
                    }
                }
                printf("\n");
            }
            free(current_groups);
        }
    }
    
    // 尝试设置附加组
    printf("\n尝试设置附加组列表:\n");
    printf("  组数量: %d\n", num_groups);
    printf("  组ID: ");
    for (int i = 0; i < num_groups; i++) {
        struct group *g = getgrgid(test_groups[i]);
        if (g) {
            printf("%s(%d) ", g->gr_name, test_groups[i]);
        } else {
            printf("%d ", test_groups[i]);
        }
    }
    printf("\n");
    
    int result = setgroups(num_groups, test_groups);
    if (result == 0) {
        printf("✓ 附加组设置成功\n");
    } else {
        printf("✗ 附加组设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  需要root权限或CAP_SETGID能力\n");
        }
    }
    
    return 0;
}

int main() {
    return demo_permission_check();
}

示例3:initgroups替代实现

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 自定义initgroups实现
 */
int my_initgroups(const char *user, gid_t group) {
    struct group *grp;
    gid_t *groups = NULL;
    int ngroups = 0;
    int max_groups = 32;
    
    printf("=== 自定义initgroups实现 ===\n");
    printf("用户: %s, 主组: %d\n", user, group);
    
    // 分配初始组数组
    groups = malloc(max_groups * sizeof(gid_t));
    if (!groups) {
        perror("分配内存失败");
        return -1;
    }
    
    // 添加主组
    groups[ngroups++] = group;
    
    // 查找用户所属的所有组
    setgrent();  // 重新开始组文件扫描
    
    while ((grp = getgrent()) != NULL) {
        // 检查用户是否在该组的成员列表中
        if (grp->gr_mem) {
            for (char **member = grp->gr_mem; *member; member++) {
                if (strcmp(*member, user) == 0) {
                    // 确保不重复添加主组
                    if (grp->gr_gid != group) {
                        // 检查是否需要扩展数组
                        if (ngroups >= max_groups) {
                            max_groups *= 2;
                            gid_t *new_groups = realloc(groups, max_groups * sizeof(gid_t));
                            if (!new_groups) {
                                perror("重新分配内存失败");
                                free(groups);
                                endgrent();
                                return -1;
                            }
                            groups = new_groups;
                        }
                        
                        groups[ngroups++] = grp->gr_gid;
                        printf("  发现用户组: %s (ID: %d)\n", grp->gr_name, grp->gr_gid);
                    }
                    break;
                }
            }
        }
    }
    
    endgrent();
    
    printf("总共找到 %d 个组\n", ngroups);
    
    // 设置附加组
    int result = setgroups(ngroups, groups);
    if (result == 0) {
        printf("成功设置附加组列表\n");
    } else {
        printf("设置附加组失败: %s\n", strerror(errno));
    }
    
    free(groups);
    return result;
}

/**
 * 演示自定义initgroups
 */
int demo_custom_initgroups() {
    struct passwd *pwd;
    const char *username;
    
    printf("=== 自定义initgroups演示 ===\n");
    
    // 获取当前用户名
    pwd = getpwuid(getuid());
    if (pwd) {
        username = pwd->pw_name;
        printf("当前用户: %s (UID: %d, GID: %d)\n", 
               username, pwd->pw_uid, pwd->pw_gid);
    } else {
        printf("无法获取当前用户信息\n");
        return -1;
    }
    
    // 显示用户信息
    printf("\n用户详细信息:\n");
    printf("  用户名: %s\n", pwd->pw_name);
    printf("  用户ID: %d\n", pwd->pw_uid);
    printf("  主组ID: %d\n", pwd->pw_gid);
    printf("  主目录: %s\n", pwd->pw_dir);
    printf("  登录shell: %s\n", pwd->pw_shell);
    
    // 使用自定义initgroups
    printf("\n调用自定义initgroups:\n");
    int result = my_initgroups(username, pwd->pw_gid);
    
    if (result == 0) {
        printf("✓ 自定义initgroups执行成功\n");
        
        // 验证结果
        printf("\n验证附加组设置:\n");
        int max_groups = getgroups(0, NULL);
        if (max_groups > 0) {
            gid_t *groups = malloc(max_groups * sizeof(gid_t));
            if (groups) {
                int actual_count = getgroups(max_groups, groups);
                if (actual_count > 0) {
                    printf("  附加组数量: %d\n", actual_count);
                    printf("  附加组列表: ");
                    for (int i = 0; i < actual_count; i++) {
                        struct group *g = getgrgid(groups[i]);
                        if (g) {
                            printf("%s(%d) ", g->gr_name, groups[i]);
                        } else {
                            printf("%d ", groups[i]);
                        }
                    }
                    printf("\n");
                }
                free(groups);
            }
        }
    } else {
        printf("✗ 自定义initgroups执行失败\n");
    }
    
    return 0;
}

int main() {
    return demo_custom_initgroups();
}

示例4:组管理工具

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

/**
 * 组信息结构
 */
typedef struct {
    gid_t gid;
    char name[256];
    int is_member;
} group_info_t;

/**
 * 显示组列表
 */
void show_group_list(group_info_t *groups, int count) {
    printf("组列表 (%d 个组):\n", count);
    printf("%-8s %-20s %-10s\n", "GID", "组名", "成员状态");
    printf("----------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        printf("%-8d %-20s %-10s\n", 
               groups[i].gid, groups[i].name,
               groups[i].is_member ? "是" : "否");
    }
    printf("\n");
}

/**
 * 获取当前附加组
 */
int get_current_groups(gid_t **groups) {
    int count = getgroups(0, NULL);
    if (count <= 0) {
        return count;
    }
    
    *groups = malloc(count * sizeof(gid_t));
    if (!*groups) {
        return -1;
    }
    
    count = getgroups(count, *groups);
    return count;
}

/**
 * 检查组是否在列表中
 */
int is_group_in_list(gid_t gid, gid_t *groups, int count) {
    for (int i = 0; i < count; i++) {
        if (groups[i] == gid) {
            return 1;
        }
    }
    return 0;
}

/**
 * 演示组管理工具
 */
int demo_group_manager() {
    gid_t *current_groups = NULL;
    int current_count = 0;
    struct group *grp;
    group_info_t test_groups[10];
    int test_count = 0;
    
    printf("=== 组管理工具演示 ===\n");
    
    // 获取当前附加组
    current_count = get_current_groups(&current_groups);
    if (current_count < 0) {
        printf("获取当前附加组失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("当前进程附加组数量: %d\n", current_count);
    if (current_count > 0) {
        printf("当前附加组ID: ");
        for (int i = 0; i < current_count; i++) {
            printf("%d ", current_groups[i]);
        }
        printf("\n\n");
    }
    
    // 准备测试组列表
    printf("准备测试组列表:\n");
    
    // 添加一些常见组
    const char *group_names[] = {"daemon", "sys", "adm", "tty", "disk", "audio", NULL};
    
    for (int i = 0; group_names[i] && test_count < 10; i++) {
        grp = getgrnam(group_names[i]);
        if (grp) {
            test_groups[test_count].gid = grp->gr_gid;
            strncpy(test_groups[test_count].name, grp->gr_name, 
                    sizeof(test_groups[test_count].name) - 1);
            test_groups[test_count].name[sizeof(test_groups[test_count].name) - 1] = '\0';
            test_groups[test_count].is_member = is_group_in_list(grp->gr_gid, 
                                                                current_groups, current_count);
            test_count++;
            printf("  添加组: %s (ID: %d) %s\n", 
                   grp->gr_name, grp->gr_gid,
                   test_groups[test_count-1].is_member ? "[当前成员]" : "");
        }
    }
    
    // 如果没找到足够的系统组,添加示例组
    if (test_count < 3) {
        for (int i = test_count; i < 3 && i < 10; i++) {
            test_groups[i].gid = 1000 + i;
            snprintf(test_groups[i].name, sizeof(test_groups[i].name), "group_%d", 1000 + i);
            test_groups[i].is_member = 0;
            test_count++;
        }
    }
    
    printf("\n");
    show_group_list(test_groups, test_count);
    
    // 演示设置附加组
    printf("尝试设置新的附加组列表:\n");
    gid_t new_groups[5];
    int new_count = 0;
    
    // 选择前3个组进行设置
    for (int i = 0; i < test_count && new_count < 5 && i < 3; i++) {
        new_groups[new_count++] = test_groups[i].gid;
        printf("  添加组: %s (ID: %d)\n", test_groups[i].name, test_groups[i].gid);
    }
    
    int result = setgroups(new_count, new_groups);
    if (result == 0) {
        printf("✓ 附加组设置成功\n");
        
        // 验证设置结果
        printf("\n验证设置结果:\n");
        gid_t *verify_groups = NULL;
        int verify_count = get_current_groups(&verify_groups);
        if (verify_count > 0) {
            printf("  验证组数量: %d\n", verify_count);
            printf("  验证组ID: ");
            for (int i = 0; i < verify_count; i++) {
                printf("%d ", verify_groups[i]);
            }
            printf("\n");
        }
        if (verify_groups) {
            free(verify_groups);
        }
    } else {
        printf("✗ 附加组设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  需要root权限或CAP_SETGID能力\n");
        }
    }
    
    // 清理资源
    if (current_groups) {
        free(current_groups);
    }
    
    return 0;
}

int main() {
    return demo_group_manager();
}

示例5:安全组切换演示

#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <sys/capability.h>

/**
 * 显示进程权限信息
 */
void show_process_capabilities() {
    uid_t uid = getuid();
    gid_t gid = getgid();
    uid_t euid = geteuid();
    gid_t egid = getegid();
    
    printf("=== 进程权限信息 ===\n");
    printf("真实用户ID: %d\n", uid);
    printf("真实组ID: %d\n", gid);
    printf("有效用户ID: %d\n", euid);
    printf("有效组ID: %d\n", egid);
    
    // 显示附加组数量
    int ngids = getgroups(0, NULL);
    if (ngids >= 0) {
        printf("附加组数量: %d\n", ngids);
    }
    
    // 显示当前附加组
    if (ngids > 0) {
        gid_t *gids = malloc(ngids * sizeof(gid_t));
        if (gids) {
            int actual = getgroups(ngids, gids);
            if (actual > 0) {
                printf("附加组ID: ");
                for (int i = 0; i < actual; i++) {
                    printf("%d ", gids[i]);
                }
                printf("\n");
            }
            free(gids);
        }
    }
    printf("\n");
}

/**
 * 安全组切换演示
 */
int demo_secure_group_switching() {
    gid_t original_groups[32];
    gid_t test_groups[3] = {1000, 2000, 3000};
    int original_count, test_count = 3;
    
    printf("=== 安全组切换演示 ===\n");
    
    // 显示原始权限信息
    show_process_capabilities();
    
    // 保存原始附加组
    original_count = getgroups(sizeof(original_groups) / sizeof(original_groups[0]), 
                              original_groups);
    if (original_count == -1) {
        printf("获取原始附加组失败: %s\n", strerror(errno));
        return -1;
    }
    
    printf("原始附加组数量: %d\n", original_count);
    if (original_count > 0) {
        printf("原始附加组ID: ");
        for (int i = 0; i < original_count; i++) {
            printf("%d ", original_groups[i]);
        }
        printf("\n");
    }
    
    // 尝试设置测试组
    printf("\n1. 设置测试附加组:\n");
    printf("   测试组ID: ");
    for (int i = 0; i < test_count; i++) {
        printf("%d ", test_groups[i]);
    }
    printf("\n");
    
    int result = setgroups(test_count, test_groups);
    if (result == 0) {
        printf("   ✓ 测试组设置成功\n");
        show_process_capabilities();
    } else {
        printf("   ✗ 测试组设置失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   需要root权限或CAP_SETGID能力\n");
            printf("   演示将继续使用当前权限\n");
        }
    }
    
    // 演示临时组权限使用
    printf("\n2. 模拟使用组权限:\n");
    if (result == 0) {
        printf("   使用新设置的附加组权限...\n");
        // 这里可以进行需要特定组权限的操作
        printf("   模拟文件访问检查...\n");
    } else {
        printf("   使用当前附加组权限...\n");
    }
    
    // 恢复原始附加组
    printf("\n3. 恢复原始附加组:\n");
    if (original_count >= 0) {
        result = setgroups(original_count, original_groups);
        if (result == 0) {
            printf("   ✓ 原始附加组恢复成功\n");
        } else {
            printf("   ✗ 原始附加组恢复失败: %s\n", strerror(errno));
        }
    }
    
    show_process_capabilities();
    
    // 演示安全最佳实践
    printf("\n4. 安全最佳实践:\n");
    printf("   ✓ 始终保存原始权限状态\n");
    printf("   ✓ 在使用特殊权限后及时恢复\n");
    printf("   ✓ 检查权限操作的返回值\n");
    printf("   ✓ 记录权限变更日志\n");
    printf("   ✓ 最小权限原则:只请求必需的权限\n");
    
    // 显示组相关安全信息
    printf("\n5. 组安全相关信息:\n");
    printf("   Linux限制:最多支持 %d 个附加组\n", NGROUPS_MAX);
    printf("   权限要求:修改附加组需要CAP_SETGID能力\n");
    printf("   安全建议:避免在生产环境中频繁切换组权限\n");
    
    return 0;
}

int main() {
    return demo_secure_group_switching();
}

setgroups 使用注意事项

系统要求:

  1. 内核版本: 支持setgroups的Linux内核
  2. 权限要求: 需要CAP_SETGID能力或root权限
  3. 架构支持: 支持所有主流架构

参数限制:

  1. 数量限制: 系统限制附加组数量(通常NGROUPS_MAX)
  2. 组ID有效性: 组ID应该在系统中存在
  3. 空指针处理: list为NULL时size应该为0

错误处理:

  1. EPERM: 权限不足(需要CAP_SETGID或root权限)
  2. EINVAL: 参数无效(size过大或指针无效)
  3. ENOMEM: 内存不足
  4. EFAULT: 指针参数指向无效内存

性能考虑:

  1. 系统调用开销: 频繁调用有性能开销
  2. 内核数据结构: 需要更新内核中的组信息
  3. 权限检查: 每次调用都需要权限验证

安全考虑:

  1. 权限提升: 可能导致权限提升风险
  2. 审计日志: 建议记录权限变更操作
  3. 最小权限: 遵循最小权限原则
  4. 状态恢复: 及时恢复原始权限状态

最佳实践:

  1. 权限检查: 执行前检查是否具有足够权限
  2. 参数验证: 验证参数的有效性和安全性
  3. 错误处理: 妥善处理各种错误情况
  4. 状态保存: 保存原始状态以便恢复
  5. 日志记录: 记录权限变更操作
  6. 及时恢复: 使用完特殊权限后及时恢复

常见使用场景

1. 用户登录处理:

// 用户登录时初始化组权限
struct passwd *pwd = getpwnam(username);
if (pwd) {
    initgroups(username, pwd->pw_gid);
    setgid(pwd->pw_gid);
    setuid(pwd->pw_uid);
}

2. 服务权限管理:

// 服务启动时设置适当的组权限
gid_t service_groups[] = {GROUP_DAEMON, GROUP_LOG};
setgroups(2, service_groups);

3. 安全沙箱:

// 创建受限环境时移除不必要的组权限
setgroups(0, NULL);  // 清空附加组

组管理相关常量

系统限制:

#include <limits.h>
// NGROUPS_MAX: 系统支持的最大附加组数量
printf("最大附加组数量: %ld\n", sysconf(_SC_NGROUPS_MAX));

权限能力检查

检查CAP_SETGID能力:

#include <sys/capability.h>
// 检查进程是否具有设置组ID的能力

总结

setgroups 是Linux系统中重要的权限管理函数,提供了:

  1. 组权限控制: 精确控制进程的组权限
  2. 灵活配置: 支持动态设置附加组列表
  3. 安全机制: 通过权限检查保证系统安全
  4. 标准兼容: 符合POSIX标准

通过合理使用 setgroups,可以实现细粒度的权限控制,构建更加安全可靠的系统应用。在实际应用中,需要注意权限要求、错误处理和安全最佳实践。

发表在 linux文章 | 标签为 | 留下评论

sethostname系统调用及示例

我们来深入学习 sethostname 系统调用,在 Linux 系统(以及大多数 Unix-like 系统)中,每台计算机都有一个唯一的标识符,叫做 主机名 (hostname)。这个主机名用于在网络中识别这台机器。例如,当你在命令行输入 hostname 时,它会显示当前机器的主机名。sethostname 系统调用的作用就是设置这台运行着 Linux 内核的计算机的 主机名。这是一个系统级别的设置,会影响整个机器,而不仅仅是调用它的那个进程。

1. 函数介绍

在 Linux 系统(以及大多数 Unix-like 系统)中,每台计算机都有一个唯一的标识符,叫做 主机名 (hostname)。这个主机名用于在网络中识别这台机器。例如,当你在命令行输入 hostname 时,它会显示当前机器的主机名。sethostname 系统调用的作用就是设置这台运行着 Linux 内核的计算机的 主机名。这是一个系统级别的设置,会影响整个机器,而不仅仅是调用它的那个进程。

简单来说,sethostname 就是让你用程序来给你的 Linux 电脑“改名字”。

重要提示

  1. 需要权限:修改主机名是一个特权操作,通常只有 root 用户(超级用户)才有权限执行 sethostname。普通用户尝试调用它会失败。
  2. 影响范围:主机名是系统全局的属性。一旦通过 sethostname 修改,系统中所有查询主机名的地方(如 gethostnameuname 命令)都会返回新的名字。
  3. 持久性:通过 sethostname 设置的主机名是临时的。它只在当前的系统运行会话(直到关机或重启)中有效。系统重启后,主机会从配置文件(如 /etc/hostname)中读取并恢复原来的主机名。

2. 函数原型

#include <unistd.h>      // 包含系统调用声明
#include <sys/utsname.h> // 有时也需要,包含主机名长度常量

int sethostname(const char *name, size_t len);

3. 功能

设置内核维护的主机名。这个主机名可以通过 gethostname 系统调用或 uname 系统调用来查询。

4. 参数

  • name:
    • const char * 类型。
    • 一个指向以 null 结尾的字符串的指针,该字符串包含了新的主机名。
  • len:
    • size_t 类型。
    • 指定 name 字符串中实际包含的字符数(不包括末尾的 null 终止符 \0)。通常使用 strlen(name) 来获取这个长度。

5. 返回值

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

6. 错误码 (errno)

  • EFAULTname 指向了调用进程无法访问的内存地址。
  • EINVALlen 超过了系统允许的最大主机名长度(通常是 MAXHOSTNAMELEN,在 <sys/utsname.h> 或 <limits.h> 中定义,常见值是 64 或 256)。
  • EPERM: 调用进程没有权限(不是 root 用户)来更改主机名。

7. 相似函数或关联函数

  • gethostname: 用于获取当前的主机名。
  • uname: 系统调用,可以获取包括主机名在内的系统信息(系统名、版本、机器类型等)。对应的命令行工具也叫 uname
  • hostname: 命令行工具,用于显示或设置系统的主机名。它在底层就是调用 sethostname 和 gethostname
  • /etc/hostname: 在许多 Linux 发行版中,系统启动时会从这个文件读取主机名并使用 sethostname 设置。

8. 示例代码

下面的示例演示了如何使用 sethostname 来修改主机名,以及如何使用 gethostname 来查询它。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h> // 包含 gethostname, uname, MAXHOSTNAMELEN
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define HOST_NAME_BUFFER_SIZE MAXHOSTNAMELEN // 通常定义在 <sys/utsname.h> 或 <limits.h>

void print_current_hostname(const char* context) {
    char hostname[HOST_NAME_BUFFER_SIZE];
    if (gethostname(hostname, sizeof(hostname)) == 0) {
        printf("[%s] Current hostname is: '%s'\n", context, hostname);
    } else {
        perror("gethostname");
    }
}

int main(int argc, char *argv[]) {
    char new_hostname[HOST_NAME_BUFFER_SIZE];
    struct utsname uname_info; // 用于 uname 系统调用

    printf("--- Demonstrating sethostname ---\n");

    // 1. 显示当前主机名
    print_current_hostname("Initial");

    // 2. 使用 uname 系统调用获取更详细的系统信息
    if (uname(&uname_info) == 0) {
        printf("[uname] System name: %s\n", uname_info.sysname);
        printf("[uname] Node name (hostname): %s\n", uname_info.nodename);
        printf("[uname] Release: %s\n", uname_info.release);
        printf("[uname] Version: %s\n", uname_info.version);
        printf("[uname] Machine: %s\n", uname_info.machine);
    } else {
        perror("uname");
    }

    // 3. 检查命令行参数
    if (argc != 2) {
        printf("Usage: %s <new_hostname>\n", argv[0]);
        printf("Note: You need to run this program as root to change the hostname.\n");
        exit(EXIT_FAILURE);
    }

    strncpy(new_hostname, argv[1], sizeof(new_hostname) - 1);
    new_hostname[sizeof(new_hostname) - 1] = '\0'; // 确保 null 终止

    printf("\nAttempting to change hostname to: '%s'\n", new_hostname);

    // 4. 调用 sethostname
    // 注意:这需要 root 权限
    if (sethostname(new_hostname, strlen(new_hostname)) == -1) {
        perror("sethostname");
        if (errno == EPERM) {
            printf("Error: Permission denied. You must run this program as root (e.g., using sudo).\n");
        }
        exit(EXIT_FAILURE);
    }

    printf("sethostname('%s') succeeded.\n", new_hostname);

    // 5. 再次显示主机名以验证更改
    print_current_hostname("After sethostname");

    // 6. 再次使用 uname 验证
    if (uname(&uname_info) == 0) {
        printf("[uname after change] Node name (hostname): %s\n", uname_info.nodename);
    }

    printf("\n--- Important Notes ---\n");
    printf("1. The hostname change is TEMPORARY and only lasts until the system is rebooted.\n");
    printf("2. To make the change persistent, you need to update configuration files like /etc/hostname.\n");
    printf("3. You need ROOT privileges to call sethostname.\n");

    return 0;
}

编译和运行:

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

# 1. 不带参数运行 (会报错)
./sethostname_example

# 2. 带一个参数运行,但没有 root 权限 (会失败)
./sethostname_example MyNewTempHostname

# 3. 带一个参数运行,并使用 sudo 获取 root 权限 (应该成功)
# 注意:请将 MyNewTempHostname 替换为你想要的主机名
sudo ./sethostname_example MyNewTempHostname

# 4. 验证更改
hostname
uname -n

预期输出 (使用 sudo 运行):

# 假设原始主机名是 'old-hostname'
$ sudo ./sethostname_example NewTempName
--- Demonstrating sethostname ---
[Initial] Current hostname is: 'old-hostname'
[uname] System name: Linux
[uname] Node name (hostname): old-hostname
[uname] Release: 5.4.0-XX-generic
[uname] Version: #XX-Ubuntu SMP ...
[uname] Machine: x86_64

Attempting to change hostname to: 'NewTempName'
sethostname('NewTempName') succeeded.
[After sethostname] Current hostname is: 'NewTempName'
[uname after change] Node name (hostname): NewTempName

--- Important Notes ---
1. The hostname change is TEMPORARY and only lasts until the system is rebooted.
2. To make the change persistent, you need to update configuration files like /etc/hostname.
3. You need ROOT privileges to call sethostname.

# 验证命令
$ hostname
NewTempName
$ uname -n
NewTempName

重启后:
如果你重启系统,主机会名会恢复到 /etc/hostname 文件中配置的名称。

总结:
sethostname 是一个用于修改系统全局主机名的系统调用。它需要 root 权限,并且修改是临时的。理解它有助于你编写需要动态管理主机名的系统管理工具。在日常使用中,hostname 命令是更常见的设置主机名的方式。

FL:https://blog.csdn.net/timberwolf007/article/details/150643034?sharetype=blogdetail&sharerId=150643034&sharerefer=PC&sharesource=zidier215&spm=1011.2480.3001.8118

发表在 linux文章 | 留下评论