15 Free AI Programming Tools for Intelligent Code Generation and Writing

15 Free AI Programming Tools for Intelligent Code Generation and Writing

https://www.calcguide.tech/2025/08/24/15-free-ai-programming-tools-for-intelligent-code-generation-and-writing/

https://www.calcguide.tech/2025/08/24/15个免费的ai编程工具,智能自动编写和生成代码/

🚀 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 IntegrationGitHub CopilotStudent FreeStrongest AI Assistant50+VS Code, etc.Amazon CodeWhispererCompletely FreeSecurity Scanning15+VS Code, etc.TabnineBasic FreeDeep Learning30+Multi-editorReplit GhostwriterCompletely FreeOnline IDE IntegrationWeb LanguagesReplitCodeiumCompletely FreeMulti-language Support70+Multi-editorCodiumAIBasic FreeTest GenerationMainstream LanguagesMulti-editorSourcegraph CodyPersonal FreeContext UnderstandingEnterprise LevelMulti-platformKiteCompletely FreeDetailed Documentation10+Multi-editorMintlifyCompletely FreeDocumentation GenerationMulti-languageGitHubPiecesPersonal FreeCode SnippetsMulti-languageCross-platformBlackBox AIFree VersionBrowser ExtensionWeb DevelopmentBrowserContinueCompletely FreeLocal RunningPrivacy FocusMulti-editorHugging Face CodeFree/Open SourceModel CollectionML LanguagesOnlineStenographyFree/Open SourceDoc GenerationMulti-languageGitHubMutable AIPersonal FreeNatural LanguageBeginner FriendlyMulti-tool

💡 Usage Recommendations

🎯 Selection Guide

Beginners: Replit Ghostwriter, Mutable AI

Professional Developers: GitHub Copilot, Amazon CodeWhisperer

Open Source Enthusiasts: Continue, Hugging Face Code

Documentation Needs: Mintlify, Stenography

Test-Driven Development: CodiumAI

🔧 Installation Steps (Codeium Example)

1
2
3
4
5
6
7
8
9
10
11
12
# 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

Clear Instructions: Use specific tech stack and requirement descriptions

Provide Context: Give relevant code context

Step-by-step Verification: Carefully check generated code logic

Combined Usage: Better results with multiple tools

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.

https://www.calcguide.tech/2025/08/24/15-free-ai-programming-tools-for-intelligent-code-generation-and-writing/

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

https://www.calcguide.tech/2025/08/24/15个免费的ai编程工具,智能自动编写和生成代码/

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

  • 官网: 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/

  • 特点:

  • 自然语言编程

  • 代码生成和解释

  • 免费个人版

  • 与开发工具集成

  • 适用: 初学者友好

💡 使用建议

🎯 选择指南

初学者: Replit Ghostwriter, Mutable AI

专业开发者: GitHub Copilot, Amazon CodeWhisperer

开源爱好者: Continue, Hugging Face Code

文档需求: Mintlify, Stenography

测试驱动: CodiumAI

🔧 安装步骤(以 Codeium 为例)

1
2
3
4
5
6
7
8
9
10
11
12
# VS Code 安装
1. 打开 VS Code
2. 扩展市场搜索 "Codeium"
3. 点击安装
4. 重启 VS Code
5. 开始使用 AI 编程

# JetBrains 安装
1. 打开 IDE
2. File → Settings → Plugins
3. 搜索 "Codeium"
4. 安装并重启

🚀 最佳实践

明确指令: 使用具体的技术栈和需求描述

上下文提供: 给出相关的代码上下文

逐步验证: 生成后仔细检查代码逻辑

组合使用: 不同工具配合使用效果更佳

持续学习: 关注新功能和更新

15个免费的AI编程工具,智能自动编写和生成代码

15个免费的AI编程工具,智能自动编写和生成代

https://www.calcguide.tech/2025/08/24/15个免费的ai编程工具,智能自动编写和生成代码/

https://www.calcguide.tech/2025/08/24/15-free-ai-programming-tools-for-intelligent-code-generation-and-writing/

🚀 顶级免费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语言ReplitCodeium完全免费多语言支持70+多编辑器CodiumAI基础免费测试生成主流语言多编辑器Sourcegraph Cody个人免费上下文理解企业级多平台Kite完全免费详细文档10+多编辑器Mintlify完全免费文档生成多语言GitHubPieces个人免费代码片段多语言跨平台

💡 使用建议

🎯 选择指南

初学者: Replit Ghostwriter, Mutable AI

专业开发者: GitHub Copilot, Amazon CodeWhisperer

开源爱好者: Continue, Hugging Face Code

文档需求: Mintlify, Stenography

测试驱动: CodiumAI

🔧 安装步骤(以 Codeium 为例)

1
2
3
4
5
6
7
8
9
10
11
12
# VS Code 安装
1. 打开 VS Code
2. 扩展市场搜索 "Codeium"
3. 点击安装
4. 重启 VS Code
5. 开始使用 AI 编程

# JetBrains 安装
1. 打开 IDE
2. File → Settings → Plugins
3. 搜索 "Codeium"
4. 安装并重启

🚀 最佳实践

明确指令: 使用具体的技术栈和需求描述

上下文提供: 给出相关的代码上下文

逐步验证: 生成后仔细检查代码逻辑

组合使用: 不同工具配合使用效果更佳

持续学习: 关注新功能和更新

这些免费的 AI 编程工具可以帮助开发者提高编码效率,从简单的代码补全到复杂的程序生成,为不同水平的开发者提供了丰富的选择。

rt_sigpending系统调用及示例

我们来深入学习 rt_sigpending 系统调用,摘要:rt_sigpending系统调用用于检查被阻塞但尚未处理的待处理信号集。通过sigpending函数调用,可获取当前被屏蔽信号的”等待中”状态,类似查看”信号邮箱”中的未读邮件。使用时需配合sigprocmask设置信号屏蔽字,并结合sigset_t相关函数操作信号集。示例代码演示了如何阻塞SIGUSR1/SIGUSR2信号,在10秒内捕获待处理信号,最后解除阻塞使信号被处理。该机制适用于需要延迟处理特定信号的场景。(149字)

1. 函数介绍

在 Linux 中,你可以使用 sigprocmask 来阻塞(或屏蔽)某些信号,这意味着即使这些信号被发送到你的进程,它们也不会立即被处理,而是进入一种“等待中”(pending)的状态。

rt_sigpending(通常通过用户空间的 sigpending 函数调用)的作用就是让你检查当前有哪些信号正处于这种“等待中”的状态。这在你需要知道在屏蔽信号期间发生了哪些信号时非常有用。

你可以把它想象成一个“信号邮箱”的查看器:当信号被阻塞时,它们就像邮件一样被“投递”到你的邮箱里(变成 pending),但你暂时不“阅读”它们。sigpending 就是让你打开邮箱看看里面有哪些“未读邮件”(待处理信号)。

2. 函数原型

1
2
3
4
#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 来检查哪些信号在等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#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;
}

编译和运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 假设代码保存在 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 的信号被处理。

https://www.calcguide.tech/2025/08/24/rt-sigpending系统调用及示例/

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

Linux内核kfifo实现详解

Linux内核kfifo实现详解

Linux内核kfifo是一种高效的无锁环形缓冲区实现,其核心设计包括:1)使用2的幂次方大小缓冲区,通过位运算替代取模运算提高性能;2)分离的in/out索引设计,避免锁机制;3)内存屏障确保数据一致性。kfifo通过位运算优化索引计算(position & mask),并采用两段复制策略处理环形缓冲区的边界条件,在单生产者单消费者场景下实现高效无锁操作。

  1. kfifo设计原理

1.1 核心思想

Linux内核kfifo(kernel FIFO)是一个高效、无锁的环形缓冲区实现,专为内核环境设计。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* kfifo的核心数据结构
* 为什么这样设计?
*/
struct __kfifo {
unsigned int in; // 入队索引
unsigned int out; // 出队索引
unsigned int mask; // 掩码,用于快速取模运算
unsigned int esize; // 元素大小
void *data; // 数据缓冲区指针
};

1.2 关键设计决策

1.2.1 2的幂次方大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 为什么要求缓冲区大小是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 索引分离设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 为什么使用分离的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;

  1. 内存布局优化

2.1 缓冲区大小计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 确保缓冲区大小是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 位运算优化详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 位运算优化示例
*/
// 传统方法:使用取模运算
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 (循环)

  1. 无锁设计原理

3.1 单生产者单消费者无锁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 无锁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 内存屏障的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 内存屏障的重要性
* 防止编译器和CPU重排序导致的数据不一致
*/

// 生产者端
smp_wmb(); // write memory barrier
// 确保数据写入缓冲区的操作在更新in索引之前完成
fifo->in += len;

// 消费者端
smp_mb(); // memory barrier
// 确保读取数据的操作在更新out索引之前完成
fifo->out += len;

  1. 边界条件处理

4.1 环形缓冲区的两段复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 环形缓冲区的挑战:数据可能跨越缓冲区边界
* 需要分两段复制
*/

// 示例:缓冲区大小为8,当前状态
// &#91;0]&#91;1]&#91;2]&#91;3]&#91;4]&#91;5]&#91;6]&#91;7]
// ^out ^in
// 数据在位置&#91;3]&#91;4]&#91;5]&#91;6]&#91;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 空/满状态判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 空/满状态判断的巧妙设计
*/

// 判断是否为空
#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

  1. 类型安全的宏设计

5.1 泛型支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 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 编译时检查

1
2
3
4
5
6
7
8
9
10
11
/**
* 编译时类型检查
*/
#define __KFIFO_PEEK(data, out, mask) \
((data)&#91;(out) & (mask)])

#define __KFIFO_POKE(data, in, mask, val) \
( (data)&#91;(in) & (mask)] = (val) )

// 这些宏确保在编译时就能发现类型错误

  1. 性能优化技术

6.1 缓存友好性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 缓存友好的数据布局
*/

struct __kfifo {
unsigned int in; // 控制信息集中存储
unsigned int out; // 提高缓存命中率
unsigned int mask;
unsigned int esize;
void *data; // 数据指针单独存储
};

// 为什么这样布局?
// 1. 控制信息连续存储,提高缓存局部性
// 2. 频繁访问的in/out字段相邻,减少缓存行加载
// 3. data指针单独存储,避免数据移动时的拷贝

6.2 编译器优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 利用编译器优化
*/

// 内联函数减少函数调用开销
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)

  1. 实际应用场景

7.1 内核中的典型应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 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 用户空间移植

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 用户空间kfifo实现要点
*/

// 1. 替换内核内存分配函数
// kmalloc -> malloc
// kfree -> free

// 2. 替换内核同步原语
// spin_lock -> pthread_mutex_lock
// smp_mb -> __sync_synchronize 或者使用互斥锁

// 3. 替换内核特定宏
// likely/unlikely -> 保持或移除
// container_of -> 自定义实现

// 4. 错误处理
// 内核返回负数错误码 -> 用户空间返回-1并设置errno

  1. 设计优势总结

8.1 性能优势

O(1)时间复杂度:所有操作都是常数时间

位运算优化:避免除法运算

缓存友好:数据局部性好

无锁设计:单生产者单消费者场景下无需锁

8.2 内存优势

紧凑布局:控制信息集中存储

零拷贝支持:直接内存访问

动态分配:按需分配内存

8.3 使用优势

类型安全:编译时类型检查

接口简洁:易于使用

广泛测试:内核级稳定性保证

8.4 可扩展性

泛型支持:支持任意数据类型

可配置大小:动态调整缓冲区大小

多线程支持:提供同步版本

这个设计体现了Linux内核对性能、可靠性和简洁性的极致追求,是系统编程的经典范例。

Linux内核kfifo实现详解-CSDN博客

https://www.calcguide.tech/2025/08/24/linux内核kfifo实现详解/

https://www.calcguide.tech/2025/08/23/getitimer系统调用及示例/

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. 函数原型

1
2
3
4
#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 来阻塞和解除阻塞信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#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;
}

编译和运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 假设代码保存在 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 的三种主要操作:

SIG_BLOCK:在开始时阻塞 SIGUSR1。

SIG_SETMASK:中间阶段,将屏蔽字设置为只阻塞 SIGINT,从而解除了对 SIGUSR1 的阻塞。

SIG_UNBLOCK:最后阶段,解除了对 SIGINT 的阻塞。

通过这种方式,你可以精确地控制在程序执行的不同阶段哪些信号可以打扰你的程序。

https://www.calcguide.tech/2025/08/24/rt-sigprocmask系统调用及示例/

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

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. 函数原型

1
2
3
4
5
6
7
8
// 这是底层系统调用,直接在用户空间调用比较复杂且需要特殊权限
#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 的包装函数

用户空间更常用、更安全的替代函数:

1
2
3
4
#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 类型。

  • 要发送的信号编号,例如 SIGUSR1, SIGRTMIN 等。注意,不能发送 SIGKILL 和 SIGSTOP。

uinfo:

  • siginfo_t * 类型。

  • 一个指向 siginfo_t 结构体的指针。这个结构体包含了你想随信号一起传递给目标进程的所有详细信息。调用者需要自己填充这个结构体的各个字段。

5. 返回值

  • 成功: 返回 0。

失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:

  • EAGAIN: (对于实时信号) 已达到接收者排队信号的最大数量限制 (RLIMIT_SIGPENDING)。

  • EPERM: 调用者没有权限发送信号给目标进程(例如,普通用户不能向 root 进程发送任意伪造的信号)。

  • EINVAL: sig 是无效的信号号,或者 uinfo 中的 si_code 是无效的或不允许由用户设置的。

  • ESRCH: 找不到 pid 指定的进程或进程组。

6. 相似函数或关联函数

  • sigqueue: 用户空间更安全、更常用的发送信号和数据的方法。它只允许设置 siginfo_t 中的 si_value 字段,其他字段由内核自动填充。

  • kill: 发送一个不带附加数据的信号。

siginfo_t: 包含信号详细信息的结构体。主要字段包括:

  • si_signo: 信号编号(内核会设置)。

  • si_errno: 如果非零,表示伴随信号的错误代码(内核会设置)。

  • si_code: 信号产生的原因代码(例如 SI_USER, SI_QUEUE, SI_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 权限的情况下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#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 的简单对比示例 (推荐方式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#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;
}

编译和运行:

1
2
3
4
5
6
7
8
9
10
# 假设代码保存在 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 是一个底层工具,功能强大但使用不当有安全风险,通常由系统库内部使用。

https://www.calcguide.tech/2025/08/24/rt-sigqueueinfo系统调用及示例/

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

rt_sigreturn系统调用及示例

我们来深入学习 rt_sigreturn 系统调用

1. 函数介绍

rt_sigreturn 是一个非常特殊的系统调用,它不像你平时使用的 printf 或 open 那样由程序员直接在代码中调用。相反,它是由 Linux 内核在特定情况下自动调用的,是信号处理机制中不可或缺的一部分。

想象一下这个场景:

你的程序正在正常运行。

突然,一个信号(比如 SIGALRM 定时器信号)到达了。

内核需要暂停你的程序,保存它当前的执行状态(比如 CPU 寄存器的值、程序计数器等),然后跳转到你为这个信号设置的处理函数去执行。

当你的信号处理函数执行完毕后,程序需要恢复到被信号打断之前的那个精确状态,然后继续执行。

rt_sigreturn 的作用就是:在信号处理函数执行完毕后,由内核调用它来恢复程序被中断前的执行状态,并使程序从中断点继续执行。

你可以把它看作是信号处理机制的“返回票”:内核用“去程票”(保存状态并跳转到处理函数),信号处理函数执行完后,内核用“回程票”(rt_sigreturn)把你送回原来的地方。

对于 Linux 编程小白:你通常不需要知道 rt_sigreturn 的存在,也不需要直接与它交互。它在幕后默默地工作,保证了信号处理完成后程序能正确恢复执行。了解它有助于你更深入地理解信号机制是如何工作的。

2. 函数原型

1
2
3
4
// 这是内核系统调用,用户空间程序不会直接调用它。
// 它的原型在内核源码中类似这样 (概念性):
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 在起作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#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;
}

代码执行流程解释:

main 函数开始执行。

通过 sigaction 设置 SIGALRM 的处理函数 alarm_handler。

通过 setitimer 设置一个 3 秒后触发的定时器。

main 函数进入 while 循环。

在循环的第一次迭代中,程序调用 sleep(2)。

大约 3 秒后,定时器到期,内核向进程发送 SIGALRM 信号。

内核中断 main 程序的执行,保存其当前状态(寄存器、程序计数器等)。

内核切换到用户态,并调用我们设置的 alarm_handler 函数。

alarm_handler 执行其内部的循环和 sleep。

alarm_handler 执行完毕并 return。

此时(由 C 运行时库安排),内核执行 rt_sigreturn 系统调用。

rt_sigreturn 恢复 main 程序被中断时的所有状态。

程序执行流回到 sleep(2) 调用之后的代码。

sleep 函数发现它被中断了,于是返回剩余的睡眠时间(在这种情况下大约是 1 秒)。

main 程序检查标志,确认信号已处理,然后继续下一次 while 循环。

编译和运行:

1
2
3
4
5
6
# 假设代码保存在 sigreturn_example.c 中
gcc -o sigreturn_example sigreturn_example.c

# 运行程序
./sigreturn_example

预期输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 如何在幕后确保程序在信号处理后能正确恢复执行。

https://www.calcguide.tech/2025/08/24/rt-sigreturn系统调用及示例/

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

rt_sigtimedwait系统调用及示例

我们来深入学习 rt_sigtimedwait 系统调用

1. 函数介绍

在 Linux 系统中,信号是一种重要的进程间通信和通知机制。通常,我们会为信号设置一个处理函数(使用 sigaction),当信号到来时,内核会中断程序的正常执行流程,转而去执行我们的处理函数。这是一种异步的处理方式。

但有时候,我们希望程序能够主动地、同步地等待某个信号的到来。也就是说,程序执行到某一点,就停下来,专门等着某个信号发生,信号来了,程序再接着往下走。这有点像在火车站等车,车来了(信号到了),你再上车(继续执行)。

rt_sigtimedwait(用户空间通常通过 sigtimedwait 或 sigwaitinfo 函数调用)就是这样一个工具。它允许你的程序明确地说:“我现在要等着信号 SIGUSR1 或 SIGRTMIN 中的任意一个到来”。它比 pause()(等待任意信号)或 sigsuspend()(等待信号但不指定具体哪个)更加精确。

更棒的是,它还可以:

  • 获取信号的详细信息:比如信号是谁发的?附带了什么数据?(通过 siginfo_t 结构体)

  • 设置等待超时:我不想一直等下去,最多等 5 秒钟,如果 5 秒内信号没来,就继续干别的事。这避免了程序无限期地挂起。

简单来说,sigtimedwait 就像是一个功能强大的“信号接收器”,你可以指定接收哪些信号,可以得到信号的“包裹单”(详细信息),还可以设置一个“闹钟”(超时时间)。

2. 函数原型

1
2
3
4
5
6
7
8
9
#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. 相似函数或关联函数

  • sigwaitinfo: sigtimedwait 的一个特例,等价于 sigtimedwait(set, info, NULL),即无限期等待。

  • sigsuspend: 临时改变信号掩码并挂起等待,但不指定等待哪个信号,也不获取信号信息或设置超时。

  • pause: 简单地挂起进程直到收到任何信号。

  • sigprocmask: 用于检查或修改当前进程的信号屏蔽字。在使用 sigtimedwait 之前,通常需要先阻塞要等待的信号。

  • siginfo_t: 包含信号详细信息的结构体。

  • sigqueue: 用于向另一个进程发送信号和数据,常与 sigtimedwait/sigwaitinfo 配对使用,实现进程间数据传递。

7. 示例代码

下面是一个综合示例,演示如何使用 sigwaitinfo(无限等待)和 sigtimedwait(带超时)来等待信号,并获取信号附带的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#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;
}

编译和运行:

1
2
3
4
5
6
# 假设代码保存在 sigtimedwait_example.c 中
gcc -o sigtimedwait_example sigtimedwait_example.c

# 运行程序
./sigtimedwait_example

预期输出 (时间点可能略有差异):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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.

关键点总结:

预先阻塞信号:在调用 sigtimedwait/sigwaitinfo 之前,必须使用 sigprocmask(SIG_BLOCK, …) 阻塞你打算等待的信号。这是确保信号能被这些函数捕获的关键步骤。

原子性等待:这两个函数原子性地执行“解除对指定信号的阻塞”和“挂起等待”操作,避免了在设置掩码和挂起之间可能发生的竞态条件。

精确等待:通过 set 参数,你可以精确指定等待哪一组信号。

超时控制:sigtimedwait 的 timeout 参数让你可以避免程序无限期地等待。

信息获取:通过 info 参数,你可以获得信号的来源、发送方式(kill vs sigqueue)以及附带的数据(使用 sigqueue 发送时),这使得信号成为一种强大的进程间通信机制。

https://www.calcguide.tech/2025/08/24/rt-sigtimedwait系统调用及示例/

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

rt_sigsuspend系统调用及示例

我们来深入学习 rt_sigsuspend 系统调用

1. 函数介绍

在 Linux 信号编程中,一个常见的需求是:让程序等待某个特定信号的到来。你可能想暂时忽略其他所有信号,只允许一个或几个特定的信号来“唤醒”你的程序。

pause() 函数可以挂起程序直到收到任何信号,但这不够精确。sigprocmask() 可以设置信号掩码(决定哪些信号被阻塞),但它和 pause() 组合使用时存在竞态条件(Race Condition)风险。

什么是竞态条件?想象一下,如果你先用 sigprocmask() 解除对某个信号的阻塞,然后立即调用 pause() 等待它。在这两条语句执行的间隙,如果那个信号恰好到达了,会发生什么?信号会被处理,但 pause() 还没开始执行,所以程序就错过了这个信号,可能会永远挂起在 pause() 上。

rt_sigsuspend(用户空间通过 sigsuspend 调用)就是为了解决这个问题而设计的。它是一个原子操作,会一次性完成两件事:

临时替换当前的信号掩码。

挂起进程,等待信号。

因为这两步是原子性完成的,中间没有间隙,所以彻底避免了竞态条件。

简单来说,sigsuspend 就是“安全地等待信号”的标准方法。

2. 函数原型

1
2
3
4
#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 及其操作函数 (sigemptyset, sigaddset, sigfillset 等): 用于创建和操作信号集。

  • sigaction: 用于设置信号处理函数。

7. 示例代码

下面是一个典型的例子,展示如何使用 sigsuspend 来安全地等待一个特定信号(例如 SIGUSR1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#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 唤醒进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#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;
}

编译和运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 假设代码保存在 sigsuspend_example.c 中
gcc -o sigsuspend_example sigsuspend_example.c

# 终端 1: 运行程序
./sigsuspend_example
# 程序会输出 PID

# 终端 2:
# 发送 SIGUSR1 唤醒程序
# kill -USR1 <PID>

# 或者在终端 1 按 Ctrl+C 发送 SIGINT (根据最终示例的逻辑,这可能不会唤醒,但会终止程序)

这个最终的示例清晰地展示了 sigsuspend 的正确用法:

先用 sigprocmask 设置一个基础的信号掩码。

构造一个用于 sigsuspend 的临时掩码,该掩码精确地控制了哪些信号可以唤醒进程。

在循环中调用 sigsuspend,原子地应用临时掩码并挂起。

信号处理函数设置一个标志。

sigsuspend 返回后,检查标志以确认是哪个信号导致的唤醒。

sigsuspend 自动恢复之前的信号掩码。

https://www.calcguide.tech/2025/08/24/rt-sigsuspend系统调用及示例/

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