识别和分类恶意软件样本的工具YARA

YARA 是一个用于识别和分类恶意软件样本的工具,广泛应用于恶意软件分析、威胁情报、入侵检测等领域。它通过编写规则(YARA Rules)来匹配文件中的特定字符串、十六进制模式、正则表达式等特征。


一、YARA 的基本使用方法

1. 安装 YARA

Linux(Ubuntu/Debian)

sudo apt-get install yara

macOS

brew install yara

Python 安装(推荐用于集成)

pip install yara-python

注意:yara-python 是 YARA 的 Python 绑定,允许你在 Python 脚本中使用 YARA。


2. 编写 YARA 规则(.yar 文件)

创建一个简单的 YARA 规则文件,例如 example.yar

rule HelloWorld
{
    meta:
        author = "YourName"
        description = "Detects the string 'Hello, World!'"

    strings:
        $hello = "Hello, World!" ascii

    condition:
        $hello
}

3. 使用命令行运行 YARA

yara example.yar target_file.txt

如果 target_file.txt 中包含 Hello, World!,则会输出:

HelloWorld target_file.txt

二、YARA 集成到 Python 脚本(示例 Demo)

示例:使用 yara-python 扫描文件

import yara

# 编译规则
rules = yara.compile(filepath='example.yar')

# 扫描目标文件
matches = rules.match('target_file.txt')

# 输出结果
if matches:
    print("匹配到规则:")
    for match in matches:
        print(match)
else:
    print("未匹配到任何规则")

示例:从字符串加载规则(无需文件)

import yara

# 直接在代码中定义规则
rule_source = '''
rule HelloWorld
{
    strings:
        $hello = "Hello, World!" ascii
    condition:
        $hello
}
'''

# 编译规则
rules = yara.compile(source=rule_source)

# 扫描文件
matches = rules.match('target_file.txt')
print(matches)

示例:扫描目录中的所有文件

import yara
import os

def scan_directory(directory, rules):
    for root, dirs, files in os.walk(directory):
        for file in files:
            filepath = os.path.join(root, file)
            try:
                matches = rules.match(filepath)
                if matches:
                    print(f"[+] 匹配: {filepath} -> {matches}")
            except Exception as e:
                print(f"[-] 错误扫描 {filepath}: {e}")

# 加载规则
rules = yara.compile(filepath='example.yar')

# 扫描目录
scan_directory('/path/to/scan', rules)

三、高级 YARA 规则示例

检测 PE 文件中的特定导入函数(Windows 恶意软件常见)

import "pe"

rule SuspiciousPE
{
    meta:
        description = "检测包含可疑 API 调用的 PE 文件"

    condition:
        pe.is_pe and
        any of ($suspicious_funcs) in (pe.imported_functions)
    
    strings:
        $suspicious_funcs = "VirtualAllocEx"
        $suspicious_funcs = "WriteProcessMemory"
        $suspicious_funcs = "CreateRemoteThread"
}

注意:使用 pe 模块需要目标文件是有效的 PE 文件。


四、YARA 与 SIEM/SOC 集成思路

  1. 定时扫描文件系统:使用 Python 脚本定期扫描上传目录或临时目录。
  2. 与文件上传服务集成:在 Web 应用中,用户上传文件后自动调用 YARA 扫描。
  3. 结合 ELK/Splunk:将扫描结果发送到日志系统进行告警。
  4. 沙箱联动:在动态分析沙箱中运行样本后,使用 YARA 提取特征。

五、实用技巧

  • 使用 --rules 参数查看已编译规则结构:yara -r example.yar /path/to/files
  • 忽略大小写:使用 nocase 修饰符$a = "virus" nocase
  • 正则表达式支持:$re = /https?:\/\/[a-zA-Z0-9\.\/]*/
  • 使用 uint32be(0) 检测文件头:$mz = { 4D 5A } // PE 文件头 condition: $mz at 0

六、常见问题

  • 编译错误:检查语法,YARA 对缩进和标点敏感。
  • 性能问题:避免过于宽泛的规则,使用 asciiwidenocase 精确控制。
  • 权限问题:扫描系统文件可能需要管理员权限。

七、资源推荐

  • YARA 官方文档:https://yara.readthedocs.io/
  • YARA Rules 仓库:
    • https://github.com/Yara-Rules/rules
    • https://github.com/Neo23x0/signature-base
  • 在线规则测试:https://yara-web.vercel.app/

总结

YARA 是一个强大灵活的模式匹配工具,适合用于:

  • 恶意软件检测
  • 威胁狩猎(Threat Hunting)
  • 自动化分析流水线
  • 安全产品集成(EDR、AV、沙箱)

通过 yara-python,你可以轻松将其集成到你的安全工具或平台中。

如需更复杂的集成(如多线程扫描、规则热加载、Web API 封装),可进一步封装为 REST 服务(使用 Flask/FastAPI)。

发表在 linux文章 | 留下评论

安装并使用libmagic库

如果你遇到的错误:

detect.c:5:10: fatal error: magic.h: No such file or directory
    5 | #include <magic.h>
      |          ^~~~~~~~~
compilation terminated.

说明 编译器找不到 magic.h 头文件,这是 libmagic 开发包的一部分。虽然你可能已经安装了 file 命令,但 没有安装其开发头文件(devel/dev 包)


✅ 解决方案:安装 libmagic 的开发包

🔧 Ubuntu / Debian 系统

你需要安装 libmagic-dev

sudo apt update
sudo apt install -y libmagic-dev

✅ 这个包会提供:

  • /usr/include/magic.h ← 编译时需要
  • /usr/lib/x86_64-linux-gnu/libmagic.so ← 链接时需要

安装完成后,重新编译:

gcc -o detect detect.c -lmagic

✅ 应该可以成功编译。


🔧 CentOS / RHEL / Rocky Linux / AlmaLinux

sudo yum install -y file-devel
# 或者在较新版本中:
sudo dnf install -y file-devel

然后编译:

gcc -o detect detect.c -lmagic

🔧 Fedora

sudo dnf install -y file-devel

🔧 openSUSE

sudo zypper install -y libmagic-devel

🔍 验证头文件是否已安装

运行以下命令,检查 magic.h 是否存在:

ls /usr/include/magic.h

✅ 正常输出:

/usr/include/magic.h

如果没有输出,说明 libmagic-dev 还没装好。


📦 验证库文件是否存在

find /usr/lib* -name "libmagic.*" | grep -v pkgconfig

你应该看到类似:

/usr/lib/x86_64-linux-gnu/libmagic.so
/usr/lib/x86_64-linux-gnu/libmagic.a

🛠️ 重新编译(确认成功)

// detect.c - 使用 libmagic 识别卫星数据类型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <magic.h>  // libmagic 头文件

// 打印错误并退出
void die(magic_t magic) {
    fprintf(stderr, "Error: %s\n", magic_error(magic));
    magic_close(magic);
    exit(1);
}

// 分析单个文件
void analyze_file(const char *filename) {
    magic_t magic;

    // 1. 创建 magic handle
    magic = magic_open(MAGIC_MIME_TYPE | MAGIC_MIME_ENCODING);
    if (!magic) {
        fprintf(stderr, "Failed to initialize libmagic\n");
        exit(1);
    }

    // 2. 加载 magic 数据库
    if (magic_load(magic, NULL) != 0) {  // NULL 表示使用默认数据库
        die(magic);
    }

    // 3. 获取 MIME 类型和编码
    const char *mime = magic_file(magic, filename);
    const char *encoding = NULL;

    // 分离 MIME 和编码(magic_file 返回 "type; charset=xxx")
    char *semicolon = strchr(mime, ';');
    char mime_type[64] = {0};
    char charset[32] = {0};

    if (semicolon) {
        strncpy(mime_type, mime, semicolon - mime);
        sscanf(semicolon, "; charset=%s", charset);
    } else {
        strcpy(mime_type, mime);
        strcpy(charset, "unknown");
    }

    // 4. 打印结果
    printf("📄 %s:\n", filename);
    printf("   MIME 类型: %s\n", mime_type);
    printf("   字符编码: %s\n", charset);

    // 5. 卫星场景智能判断
    if (strcmp(mime_type, "text/plain") == 0) {
        printf("   🛰️ 判定: 明文遥测/日志文件 → 可直接解析\n");
    } else if (strstr(mime_type, "image/")) {
        printf("   🛰️ 判定: 遥感图像 → 保存为可视化数据\n");
    } else if (strcmp(mime_type, "application/gzip") == 0 ||
               strcmp(mime_type, "application/zip") == 0) {
        printf("   🛰️ 判定: 压缩数据 → 需解压后进一步分析\n");
    } else if (strcmp(mime_type, "application/x-executable") == 0) {
        printf("   🛰️ 判定: 固件更新包 → 验证签名后升级\n");
    } else if (strcmp(mime_type, "application/octet-stream") == 0) {
        printf("   🛰️ 判定: 二进制流 → 可能为加密或自定义协议\n");
    } else {
        printf("   🛰️ 判定: 未知类型 → 需人工分析\n");
    }
    printf("\n");

    // 6. 释放资源
    magic_close(magic);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "用法: %s <文件1> [文件2] ...\n", argv[0]);
        exit(1);
    }

    printf("🔍 启动 libmagic C 接口 - 卫星数据类型识别\n");
    printf("------------------------------------------------\n\n");

    for (int i = 1; i < argc; i++) {
        analyze_file(argv[i]);
    }

    return 0;
}
gcc -o detect detect.c -lmagic

✅ 成功后运行:

./detect telemetry.txt

你应该看到输出:

📄 telemetry.txt:
   MIME 类型: text/plain
   字符编码: us-ascii
   🛰️ 判定: 明文遥测/日志文件 → 可直接解析

❌ 常见错误排查

错误原因解决
magic.h: No such file or directory缺少 libmagic-dev安装 libmagic-dev
undefined reference to 'magic_open'忘了 -lmagic编译时加 -lmagic
error while loading shared libraries: libmagic.so.1运行时库缺失安装 libmagic1(Ubuntu)或 file(其他)

如果是 64 位系统但提示库找不到,可尝试软链接(极少见):

sudo ln -s /usr/lib/x86_64-linux-gnu/libmagic.so /usr/lib/libmagic.so

✅ 总结

你遇到的问题是典型的 “缺少开发头文件” 错误。

只需一步修复(Ubuntu/Debian):

sudo apt install -y libmagic-dev

然后重新编译:

gcc -o detect detect.c -lmagic

✅ 问题解决。


如果你现在运行 ./detect * 能正确识别 PNG、GZIP、ELF 等文件类型,说明你的 C 语言调用 libmagic 环境已完全就绪

发表在 linux文章 | 留下评论

✅ 正确流程:使用自定义 magic 的标准做法

编写 sat.magic(纯规则) 
         ↓
    file -C -m sat.magic   → 生成 magic.mgc(二进制数据库)
         ↓
C 程序中 magic_load("magic.mgc")  → 成功加载
         ↓
调用 magic_file() → 正确识别

✅ 第一步:创建正确的 sat.magic(仅规则,无扩展字段)

cd ~/satellite-analysis-asscii/smagic
nano sat.magic

✅ 内容(严格兼容 file -C):

# Satellite Telemetry Frame
0       belong      0xAA55CCDD
>8      string      \x01\x02\x03\x04
>8      string      SAT-TELEMETRY
# (Satellite Telemetry Packet)

# GBK Chinese Text Detection
0       byte        > 0xA0
>&0     byte        < 0xFF
>1      byte        > 0xA0
>&1     byte        < 0xFF
# (Chinese GBK Text)

✅ 关键:

  • 不要写 name=desc=mime=
  • 注释用 # (Description) 格式
  • 使用英文或 ASCII

✅ 第二步:编译生成 magic.mgc

file -C -m sat.magic

✅ 正确输出:

Creating magic.mgc from sat.magic

👉 生成了 magic.mgc,这是 唯一能被 magic_load() 正确加载的文件


✅ 第三步:修改你的 C 程序,加载 magic.mgc

你的 a.out 是从某个 .c 文件编译来的,假设是 detect_encoding.c

编辑它:

vim detect_encoding.c

修改 magic_load 部分:

// 原代码(错误):
// if (magic_load(magic, NULL) != 0) { ... }

// 新代码:先加载默认库,再加载自定义库
if (magic_load(magic, NULL) != 0) {
    fprintf(stderr, "Error loading default magic: %s\n", magic_error(magic));
    magic_close(magic);
    exit(1);
}

// 加载自定义 magic.mgc
if (magic_load(magic, "./smagic/magic.mgc") != 0) {
    fprintf(stderr, "Error loading custom magic: %s\n", magic_error(magic));
    magic_close(magic);
    exit(1);
}

✅ 注意路径:./smagic/magic.mgc


✅ 第四步:重新编译并运行

gcc -o a.out detect_encoding.c -lmagic

生成测试文件(确保有 GBK 文本)

cd ~/satellite-analysis-asscii

# 生成 GBK 编码文件
echo "卫星状态:正常
时间:2025-04-05
消息:系统在线" > gbk.txt

iconv -f UTF-8 -t GBK gbk.txt > gbk.bin

运行程序

./a.out gbk.bin

✅ 预期输出:

🔍 启动 libmagic 文本编码识别(C 语言版)
------------------------------------------------

📄 gbk.bin:
   检测编码: iso-8859-1 → ISO-8859-1(西欧)

⚠️ 注意:libmagic 仍可能将 GBK 误判为 iso-8859-1,因为两者都是“可打印扩展 ASCII”,但至少 你的自定义 magic 已正确加载


✅ 如何让 GBK 被正确识别?—— 使用 cchardet

libmagic 不擅长文本编码识别。你应该:

1. 安装 cchardet

pip3 install cchardet

2. 创建 detect_gbk.py

#!/usr/bin/env python3
import cchardet
import sys

with open(sys.argv[1], 'rb') as f:
    raw = f.read()
result = cchardet.detect(raw)
print(f"文件: {sys.argv[1]}")
print(f"检测编码: {result['encoding']}")
print(f"置信度: {result['confidence']:.2f}")

3. 运行

python3 detect_gbk.py gbk.bin

✅ 输出:

检测编码: GBK
置信度: 0.99

🎯 成功识别!


✅ 最终建议:分工明确

任务工具
识别文件类型(PNG/GZIP/ELF)✅ libmagic(C)
识别文本编码(UTF-8/GBK/ASCII)✅ cchardet(Python)
识别自定义协议帧(0xAA55…)✅ libmagic + 自定义 magic.mgc

🎯 你现在可以:

  • 用 file -C 生成 magic.mgc
  • 用 C 程序加载它,识别自定义卫星帧
  • 用 Python + cchardet 精确识别 GBK 编码
发表在 linux文章 | 留下评论

生成一系列 多语言、多编码的测试文本文件

生成一系列 多语言、多编码的测试文本文件,文件扩展名为 .txt,用于测试 libmagicchardetcchardet 等工具对不同语种和编码的识别能力。

这些文件将覆盖:

  • ✅ 主要语言:中文、日文、韩文、阿拉伯文、俄文、西欧文、UTF-16 等
  • ✅ 常见编码:UTF-8、GBK、Big5、Shift_JIS、EUC-KR、ISO-8859-1、UTF-16LE/BE
  • ✅ 文件命名规范:<语言>_<编码>.txt

🛠️ 第一步:创建工作目录

mkdir ~/encoding-test
cd ~/encoding-test

🧪 第二步:生成各文种编码测试文件(.txt)

1. UTF-8(通用 Unicode)

cat > zh_utf8.txt << 'EOF'
中文测试:卫星状态正常
时间:2025-04-05T12:00:00Z
消息:系统在线,载荷激活
EOF

cat > ja_utf8.txt << 'EOF'
日本語テスト:衛星ステータス正常
時刻:2025-04-05T12:00:00Z
メッセージ:システム起動中
EOF

cat > ko_utf8.txt << 'EOF'
한국어 테스트: 위성 상태 정상
시간: 2025-04-05T12:00:00Z
메시지: 시스템 온라인
EOF

cat > ar_utf8.txt << 'EOF'
اختبار عربي: الحالة طبيعية
الوقت: 2025-04-05T12:00:00Z
الرسالة: النظام يعمل
EOF

cat > ru_utf8.txt << 'EOF'
Тест на русском: Состояние нормальное
Время: 2025-04-05T12:00:00Z
Сообщение: Система работает
EOF

cat > en_utf8.txt << 'EOF'
English Test: Status Nominal
Time: 2025-04-05T12:00:00Z
Message: System Online
EOF

cat > fr_utf8.txt << 'EOF'
Test français : État normal
Heure : 2025-04-05T12:00:00Z
Message : Système en ligne
EOF

2. GBK(简体中文)

# 从 UTF-8 转为 GBK
iconv -f UTF-8 -t GBK zh_utf8.txt -o zh_gbk.txt

3. Big5(繁体中文)

iconv -f UTF-8 -t BIG5 zh_utf8.txt -o zh_big5.txt

4. Shift_JIS(日文)

iconv -f UTF-8 -t SHIFT_JIS ja_utf8.txt -o ja_shift_jis.txt

5. EUC-KR(韩文)

iconv -f UTF-8 -t EUC-KR ko_utf8.txt -o ko_euc_kr.txt

6. ISO-8859-1(西欧,如法语、德语)

iconv -f UTF-8 -t ISO-8859-1 fr_utf8.txt -o fr_latin1.txt

7. UTF-16LE(小端 Unicode)

echo "UTF-16LE Test: 多语言混合" | iconv -t UTF-16LE -o mixed_utf16le.txt

8. UTF-16BE(大端 Unicode)

echo "UTF-16BE Test: Satellite Message" | iconv -t UTF-16BE -o en_utf16be.txt

9. ASCII(纯英文,无扩展字符)

cat > en_ascii.txt << 'EOF'
Satellite Telemetry Log
Status: Nominal
Time: 2025-04-05T12:00:00Z
EOF

10. UTF-8 with BOM(带签名)

# 手动生成带 BOM 的 UTF-8
printf '\xEF\xBB\xBF' > zh_utf8_bom.txt
cat zh_utf8.txt >> zh_utf8_bom.txt

✅ 第三步:验证文件生成成功

ls -l *.txt

你应该看到类似:

-rw-r--r-- 1 user user  123 Apr  5 12:00 zh_utf8.txt
-rw-r--r-- 1 user user  123 Apr  5 12:00 zh_gbk.txt
-rw-r--r-- 1 user user  123 Apr  5 12:00 zh_big5.txt
-rw-r--r-- 1 user user  123 Apr  5 12:00 ja_shift_jis.txt
-rw-r--r-- 1 user user  123 Apr  5 12:00 ko_euc_kr.txt
-rw-r--r-- 1 user user  123 Apr  5 12:00 fr_latin1.txt
-rw-r--r-- 1 user user  123 Apr  5 12:00 en_utf16le.txt
...

🔍 第四步:使用 file 命令识别类型

file *.txt

✅ 预期输出示例:

en_ascii.txt:        ASCII text
en_utf8.txt:         UTF-8 Unicode text
zh_gbk.txt:          ISO-8859 text
zh_big5.txt:         ISO-8859 text
ja_shift_jis.txt:    ISO-8859 text
ko_euc_kr.txt:       ISO-8859 text
fr_latin1.txt:       ISO-8859 text
mixed_utf16le.txt:   Little-endian UTF-16 Unicode text
en_utf16be.txt:      Big-endian UTF-16 Unicode text
zh_utf8_bom.txt:     UTF-8 Unicode (with BOM) text

⚠️ 注意:zh_gbk.txt 被识别为 ISO-8859 text —— 这正是我们之前说的:libmagic 无法区分 GBK 和 Latin-1


🧪 第五步:使用 cchardet 精确识别编码

pip3 install cchardet

创建 detect_all.py

#!/usr/bin/env python3
import cchardet
import glob

for filepath in sorted(glob.glob("*.txt")):
    with open(filepath, 'rb') as f:
        raw = f.read()
    result = cchardet.detect(raw)
    encoding = result['encoding']
    confidence = result['confidence']
    print(f"{filepath:20} → {encoding:10} (置信度: {confidence:.2f})")

运行:

python3 detect_all.py

✅ 预期输出(示例):

en_ascii.txt         → ascii       (置信度: 1.00)
en_utf16be.txt       → UTF-16BE    (置信度: 1.00)
en_utf8.txt          → UTF-8       (置信度: 1.00)
fr_latin1.txt        → ISO-8859-1  (置信度: 1.00)
ja_shift_jis.txt     → SHIFT_JIS   (置信度: 0.99)
ko_euc_kr.txt        → EUC-KR      (置信度: 0.99)
zh_big5.txt          → Big5        (置信度: 0.99)
zh_gbk.txt           → GB2312      (置信度: 0.99)
zh_utf8_bom.txt      → UTF-8       (置信度: 1.00)
zh_utf8.txt          → UTF-8       (置信度: 1.00)

✅ 成功识别 GBK 为 GB2312(GB2312 是 GBK 的子集,可接受)


✅ 总结:各文种编码测试文件清单

语言编码文件名
中文(简体)UTF-8zh_utf8.txt
中文(简体)GBKzh_gbk.txt
中文(繁体)Big5zh_big5.txt
日文Shift_JISja_shift_jis.txt
韩文EUC-KRko_euc_kr.txt
俄文UTF-8ru_utf8.txt
阿拉伯文UTF-8ar_utf8.txt
法文ISO-8859-1fr_latin1.txt
英文ASCIIen_ascii.txt
英文UTF-16LEen_utf16le.txt
英文UTF-16BEen_utf16be.txt
中文UTF-8 with BOMzh_utf8_bom.txt

🛰️ 在卫星通信中的应用

你可以用这些文件:

  • 测试地面站软件的编码自动识别能力
  • 训练 chardet 模型(可选)
  • 构建“多语言遥测解析引擎”
  • 自动化处理来自不同国家卫星的数据

发表在 linux文章 | 留下评论

Linux 3.0 内核系统调用

首先需要明确一点:系统调用的具体列表和编号会随着内核版本演进而变化,增加新的调用或废弃旧的调用。虽然核心功能(如文件 I/O、进程管理)相对稳定,但细节上会有差异。请注意,最准确的信息始终来自于查阅对应内核版本的源代码或权威文档。

### Linux 3.0 内核系统调用 (基于 x86_64 架构)
Linux 3.0 是一个相对成熟的内核版本。其系统调用接口已经非常丰富和稳定。
#### **系统调用分类与接口**
1. **进程控制 (Process Control)**
* `fork` (57): 创建一个子进程。
* `vfork` (58): 创建子进程,但在子进程调用 `exec` 或 `_exit` 前阻塞父进程。
* `clone` (56): 创建子进程或线程,比 `fork`/`vfork` 更灵活,允许共享内存空间等。
* `execve` (59): 用新程序替换当前进程镜像。
* `exit` (60): 终止调用进程。
* `exit_group` (231): 终止线程组中的所有线程。
* `wait4` (61): 等待子进程状态变化。
* `waitid` (247): 等待子进程状态变化(提供比 `wait4` 更丰富的信息)。
* `kill` (62): 发送信号给进程。
* `tkill` (200): 发送信号给指定线程 (已废弃,推荐使用 `tgkill`)。
* `tgkill` (234): 发送信号给指定进程内的指定线程。
* `getpid` (39): 获取调用进程的进程 ID (PID)。
* `getppid` (110): 获取调用进程的父进程 ID (PPID)。
* `getuid` (102): 获取真实用户 ID。
* `geteuid` (107): 获取有效用户 ID。
* `getgid` (104): 获取真实组 ID。
* `getegid` (108): 获取有效组 ID。
* `setuid` (105): 设置用户 ID。
* `setgid` (106): 设置组 ID。
* `getgroups` (115): 获取附加组 ID 列表。
* `setgroups` (116): 设置附加组 ID 列表。
* `setreuid` (113): 设置真实和有效用户 ID。
* `setregid` (114): 设置真实和有效组 ID。
* `setresuid` (117): 设置真实、有效和保存的用户 ID。
* `setresgid` (119): 设置真实、有效和保存的组 ID。
* `getresuid` (118): 获取真实、有效和保存的用户 ID。
* `getresgid` (120): 获取真实、有效和保存的组 ID。
* `setsid` (112): 创建新的会话。
* `getsid` (124): 获取会话 ID。
* `setpgid` (109): 设置进程组 ID。
* `getpgid` (121): 获取进程组 ID。
* `getpgrp` (111): 获取当前进程的进程组 ID。
* `prctl` (157): 操作进程属性(如设置进程名、安全模块等)。
* `arch_prctl` (158): 特定于架构的进程控制(x86_64 上用于设置 FS/GS 段基址)。
* `personality` (135): 设置进程执行域(personality)。
* `getpriority` (140): 获取进程/进程组的调度优先级。
* `setpriority` (141): 设置进程/进程组的调度优先级。
* `sched_setscheduler` (144): 设置进程的调度策略和参数。
* `sched_getscheduler` (145): 获取进程的调度策略。
* `sched_yield` (24): 主动让出 CPU。
* `sched_get_priority_max` (146): 获取指定调度策略的最大优先级。
* `sched_get_priority_min` (147): 获取指定调度策略的最小优先级。
* `sched_rr_get_interval` (148): 获取 SCHED_RR 策略的时间片。
* `nanosleep` (35): 高精度睡眠。
* `getitimer` (36): 获取间隔计时器值。
* `setitimer` (38): 设置间隔计时器值。

2. **文件 I/O (File Input/Output)**
* `open` (2): 打开或创建文件。
* `openat` (257): 类似 `open`,但允许指定相对路径的基准目录描述符。
* `creat` (85): 创建新文件(等同于 `open` 带 `O_CREAT|O_WRONLY|O_TRUNC` 标志)。
* `close` (3): 关闭打开的文件描述符。
* `read` (0): 从文件描述符读取数据。
* `write` (1): 向文件描述符写入数据。
* `pread64` (17): 从文件指定偏移量读取数据(原子操作)。
* `pwrite64` (18): 向文件指定偏移量写入数据(原子操作)。
* `readv` (19): 从文件描述符读取数据到多个缓冲区(分散读)。
* `writev` (20): 从多个缓冲区写入数据到文件描述符(集中写)。
* `lseek` (8): 设置文件偏移量。
* `fcntl` (72): 对打开的文件描述符进行各种控制操作(如复制描述符、设置标志)。
* `dup` (32): 复制文件描述符。
* `dup2` (33): 复制文件描述符,并允许指定新的描述符号。
* `dup3` (292): 类似 `dup2`,但允许设置 `O_CLOEXEC` 标志。
* `select` (23): I/O 多路复用,监视多个文件描述符。
* `poll` (7): I/O 多路复用,监视多个文件描述符。
* `epoll_create` (213): 创建 epoll 实例。
* `epoll_create1` (291): 创建 epoll 实例,允许设置标志。
* `epoll_ctl` (233): 控制 epoll 实例(添加/修改/删除监视的文件描述符)。
* `epoll_wait` (232): 等待 epoll 实例上的事件。
* `pipe` (22): 创建管道。
* `pipe2` (293): 创建管道,允许设置标志(如 `O_CLOEXEC`, `O_NONBLOCK`)。

3. **文件系统控制 (File System Control)**
* `stat` (4): 获取文件状态信息。
* `lstat` (6): 获取文件状态信息(不跟随符号链接)。
* `fstat` (5): 获取打开文件描述符对应的文件状态信息。
* `newstat` (106, 64-bit 版本): 获取文件状态信息(64位兼容)。
* `newlstat` (107, 64-bit 版本): 获取文件状态信息(不跟随符号链接,64位兼容)。
* `newfstat` (108, 64-bit 版本): 获取打开文件描述符对应的文件状态信息(64位兼容)。
* `statfs` (137): 获取文件系统统计信息。
* `fstatfs` (138): 获取打开文件描述符所在文件系统的统计信息。
* `access` (21): 检查调用进程是否可以访问文件(按实际用户ID和组ID)。
* `chmod` (90): 改变文件权限。
* `fchmod` (91): 改变打开文件描述符对应的文件权限。
* `chown` (92): 改变文件所有者和组。
* `fchown` (93): 改变打开文件描述符对应的文件所有者和组。
* `lchown` (94): 改变符号链接本身的所有者和组。
* `truncate` (76): 将文件截断或扩展到指定长度。
* `ftruncate` (77): 将打开文件描述符对应的文件截断或扩展到指定长度。
* `utime` (132): 改变文件的访问时间和修改时间。
* `utimes` (235): 改变文件的访问时间和修改时间(使用 `timeval` 结构)。
* `link` (86): 创建硬链接。
* `linkat` (265): 创建硬链接,允许指定相对路径基准。
* `symlink` (88): 创建符号链接。
* `symlinkat` (266): 创建符号链接,允许指定相对路径基准。
* `readlink` (89): 读取符号链接的内容。
* `readlinkat` (267): 读取符号链接的内容,允许指定相对路径基准。
* `unlink` (87): 删除目录项(通常用于删除文件)。
* `unlinkat` (263): 删除目录项,允许指定相对路径基准和标志。
* `rename` (82): 重命名文件或目录。
* `renameat` (264): 重命名文件或目录,允许指定相对路径基准。
* `mkdir` (83): 创建目录。
* `mkdirat` (258): 创建目录,允许指定相对路径基准。
* `rmdir` (84): 删除空目录。
* `chdir` (80): 改变当前工作目录。
* `fchdir` (81): 通过文件描述符改变当前工作目录。
* `getcwd` (79): 获取当前工作目录路径。
* `umask` (95): 设置或获取文件模式创建掩码。
* `mknod` (133): 创建特殊文件(设备文件、FIFO)。
* `mknodat` (259): 创建特殊文件,允许指定相对路径基准。
* `getdents` (78): 读取目录内容(旧接口)。
* `getdents64` (217): 读取目录内容(新接口,支持 64 位 inode)。

4. **内存管理 (Memory Management)**
* `brk` (12): 改变数据段大小。
* `sbrk` (12, 库函数封装): 改变数据段大小。
* `mmap` (9): 将文件或设备映射到内存,或分配匿名内存。
* `munmap` (11): 解除内存映射。
* `mremap` (25): 重新映射虚拟内存地址。
* `msync` (26): 将映射区域的修改同步到文件。
* `mprotect` (10): 设置内存页的保护属性。
* `mincore` (27): 确定内存页是否在物理内存中。
* `madvise` (28): 给内核提供关于内存访问模式的建议。
* `shmget` (29): 分配 System V 共享内存段。
* `shmat` (30): 连接 System V 共享内存段。
* `shmdt` (67): 断开 System V 共享内存段连接。
* `shmctl` (31): 控制 System V 共享内存段。

5. **信号处理 (Signal Handling)**
* `signal` (48, 库函数封装): 设置信号处理函数(不推荐直接使用,推荐 `sigaction`)。
* `sigaction` (13): 检查或修改信号的处理动作。
* `sigprocmask` (14): 检查或修改信号掩码。
* `sigpending` (15): 检查挂起的信号。
* `sigsuspend` (16): 等待信号。
* `sigaltstack` (131): 设置或获取信号栈信息。

6. **时间管理 (Time Management)**
* `time` (201): 获取当前时间(秒)。
* `gettimeofday` (96): 获取当前时间(秒和微秒)。
* `settimeofday` (164): 设置系统时间。
* `clock_gettime` (228): 获取指定时钟的时间。
* `clock_settime` (227): 设置指定时钟的时间。
* `clock_getres` (229): 获取指定时钟的精度。

7. **套接字 (Sockets)**
* `socket` (41): 创建套接字。
* `bind` (49): 将套接字绑定到地址。
* `connect` (42): 建立到另一个套接字的连接。
* `listen` (50): 监听套接字上的连接。
* `accept` (43): 接受一个套接字连接。
* `getsockname` (51): 获取套接字本地地址。
* `getpeername` (52): 获取套接字对端地址。
* `socketpair` (53): 创建一对已连接的套接字。
* `send` (44): 通过套接字发送消息。
* `recv` (45): 通过套接字接收消息。
* `sendto` (46): 通过套接字发送数据报。
* `recvfrom` (47): 通过套接字接收数据报。
* `shutdown` (48): 关闭套接字的全部或部分连接。
* `setsockopt` (54): 设置套接字选项。
* `getsockopt` (55): 获取套接字选项。
* `sendmsg` (46): 通过套接字发送消息(支持辅助数据)。
* `recvmsg` (47): 通过套接字接收消息(支持辅助数据)。

8. **用户和组管理 (User and Group Management)**
* (已在进程控制部分列出:`getuid`, `geteuid`, `getgid`, `getegid`, `setuid`, `setgid`, `getgroups`, `setgroups`, `setreuid`, `setregid`, `setresuid`, `setresgid`, `getresuid`, `getresgid`)

9. **系统信息和控制 (System Information and Control)**
* `uname` (63): 获取系统名称、版本等信息。
* `sysinfo` (179): 获取系统统计信息。
* `times` (100): 获取进程时间。
* `getrusage` (98): 获取资源使用信息。
* `syslog` (103): 读写内核日志缓冲区。
* `iopl` (172): 设置 I/O 权限级别(需要特权)。
* `ioperm` (173): 设置端口 I/O 权限位图(需要特权)。

10. **其他 (Miscellaneous)**
* `ioctl` (16): 设备特定的 I/O 操作。
* `fcntl` (72): 文件描述符控制(已在文件 I/O 部分列出)。
* `mount` (165): 挂载文件系统。
* `umount2` (166): 卸载文件系统。
* `pivot_root` (155): 改变根文件系统。
* `swapon` (167): 启用交换空间。
* `swapoff` (168): 禁用交换空间。
* `reboot` (169): 重启或关闭系统(需要特权)。
* `init_module` (171): 加载内核模块(需要特权)。
* `delete_module` (176): 卸载内核模块(需要特权)。
* `kexec_load` (246): 加载新的内核以供 `kexec` 使用。
* `acct` (163): 启用或禁用进程记账。
* `capget` (125): 获取线程的能力。
* `capset` (126): 设置线程的能力。
* `ptrace` (101): 进程跟踪。
* `sysfs` (139): 获取关于系统文件系统的信息。
* `ustat` (136): 获取文件系统统计信息(已废弃)。

### Linux 5.x 内核系统调用 (基于 x86_64 架构)

Linux 5.x 是一个较新的内核系列,它在保持向后兼容的同时,引入了许多新特性和系统调用。

#### **系统调用分类与接口 (与 3.0 相比的主要变化)**

核心分类基本一致,但在具体调用上有所增减和演进:

1. **新增的系统调用 (New Syscalls):**
* `io_uring_setup` (425): 设置 io_uring 异步 I/O 接口。
* `io_uring_enter` (426): 启动/提交 io_uring 操作。
* `io_uring_register` (427): 注册文件/缓冲区等供 io_uring 使用。
* `openat2` (437): `openat` 的扩展版本,提供更多控制选项。
* `pidfd_send_signal` (424): 通过 PID 文件描述符发送信号,更安全。
* `pidfd_open` (434): 为进程 ID 打开一个文件描述符。
* `clone3` (435): `clone` 的扩展版本,提供更丰富的参数。
* `close_range` (436): 关闭一个范围内的文件描述符。
* `fsconfig` (431): 配置和管理文件系统挂载参数。
* `fsmount` (432): 创建挂载实例。
* `fsopen` (430): 打开文件系统。
* `fspick` (433): 选择文件系统挂载点。
* `move_mount` (429): 移动挂载点。
* `open_tree` (428): 打开目录树以进行挂载操作。
* `landlock_create_ruleset` (444+): Landlock LSM 安全模块相关。
* `landlock_add_rule` (445+): Landlock LSM 安全模块相关。
* `landlock_restrict_self` (446+): Landlock LSM 安全模块相关。
* `memfd_secret` (447+): 创建一个内存文件描述符,其内容对内核其他部分保密。
* `process_mrelease` (448+): 释放与进程相关的内存。
* `futex_waitv` (449+): `futex` 的扩展,支持等待多个 futex。
* `set_mempolicy_home_node` (450+): 设置内存策略的首选 NUMA 节点。

2. **演进和改进 (Evolution & Improvements):**
* **`statx` (332):** 一个新的、更强大和灵活的获取文件状态信息的系统调用,旨在替代 `stat/lstat/fstat` 系列。它提供了更丰富的元数据和更好的性能。
* **`copy_file_range` (326):** 在两个文件描述符之间高效地复制数据,内核层面优化。
* **`preadv2` (327), `pwritev2` (328):** `preadv`/`pwritev` 的增强版,支持额外的标志(如 `RWF_NOWAIT`, `RWF_HIPRI`)。
* **`pkey_mprotect` (329):** 与内存保护密钥(Memory Protection Keys)一起使用,提供比 `mprotect` 更细粒度的保护。
* **`pkey_alloc` (330), `pkey_free` (331):** 分配和释放内存保护密钥。
* **`statfs`/`fstatfs` 行为改进:** 对某些文件系统的支持和信息返回可能更完善。
* **`mount`/`umount` 相关:** 旧的 `mount` 系统调用仍然存在,但新的文件系统 API (`fsopen`, `fsconfig`, `fsmount` 等) 提供了更现代、更安全的挂载方式。
* **`seccomp` 增强:** 与安全相关的系统调用可能有更新,用于构建更严格的沙箱。

3. **废弃或不推荐 (Deprecated/Obsolete):**
* 一些旧的、功能重叠或有安全问题的调用可能被标记为废弃,鼓励使用新接口。例如,某些特定架构的旧调用可能不再推荐。
* `sysfs` (139): 在某些场景下可能被新的接口替代或使用减少。

4. **核心功能保持稳定 (Core Functionality Remains Stable):**
* `read`, `write`, `open`, `close`, `fork`, `execve`, `mmap`, `socket`, `bind`, `connect` 等基础且核心的系统调用在 5.x 中依然存在,保证了向后兼容性。它们的编号和基本语义通常不变。

#### **总结**

* **Linux 3.0:** 代表了内核成熟期的一个稳定版本,包含了当时绝大多数常用和必要的系统调用。其分类清晰,是学习经典 Linux 系统编程的良好起点。
* **Linux 5.x:** 在 3.0 的基础上,增加了许多现代化特性,特别是在异步 I/O (`io_uring`)、安全 (`Landlock`, `memfd_secret`)、内存管理 (`pkey_*`)、文件系统操作 (新挂载 API) 和进程控制 (`clone3`, `pidfd_*`) 方面。这些新调用旨在提高性能、安全性、灵活性和易用性。

**学习建议:**

1. **从基础开始:** 先掌握 3.0 中列出的核心系统调用,理解它们的工作原理和使用场景。
2. **查阅手册:** 始终使用 `man 2 syscall_name` 来获取最准确的文档。
3. **实践编码:** 通过编写小程序来实践这些调用。
4. **关注演进:** 学习 5.x 新增的调用,特别是那些能显著提升性能或安全性的功能(如 `io_uring`, `statx`)。
5. **检查兼容性:** 如果你的程序需要在不同内核版本上运行,务必检查所使用的系统调用的可用性。

希望这份详细的分类和介绍能帮助你更好地理解 Linux 系统编程!

发表在 linux文章 | 留下评论

Linux C 编程核心函数详解

## Linux C 编程核心函数详解

### 1. `fork` – 进程管理

#### 函数介绍及归类
* **类别:** 系统调用 (System Call)
* **头文件:** `#include `
* **作用:** 创建一个当前进程的副本(子进程)。这是 Linux 下创建新进程的主要方式。

#### 函数原型
“`c
#include
pid_t fork(void);
“`

#### 功能
`fork()` 调用一次,返回两次。它会创建一个与调用进程(父进程)几乎完全相同的子进程。子进程会从 `fork()` 返回后的下一条指令开始执行。

#### 参数
无参数。

#### 返回值
* **在父进程中:** 返回新创建的子进程的进程 ID (PID),这是一个正整数。
* **在子进程中:** 返回 0。
* **失败时:** 在父进程中返回 -1,并设置全局变量 `errno` 来指示错误原因(如系统资源不足)。

#### 相似函数或关联函数
* `vfork()`: 类似于 `fork()`,但在子进程调用 `exec` 或 `_exit` 之前,父进程会被阻塞。通常用于紧接着调用 `exec` 的场景,效率可能稍高。
* `wait()`, `waitpid()`: 父进程用来等待子进程结束。
* `getpid()`: 获取当前进程的 PID。
* `getppid()`: 获取当前进程的父进程 PID。

#### 示例代码 (C89)
“`c
#include /* printf, perror */
#include /* fork, getpid, getppid */
#include /* pid_t */
#include /* wait */

int main() {
pid_t pid;
int status;

/* 调用 fork 创建子进程 */
pid = fork();

if (pid < 0) { /* fork 失败 */ perror("fork failed"); return 1; /* 返回非零值表示错误 */ } else if (pid == 0) { /* 这段代码在子进程中执行 */ printf("Child Process:\n"); printf(" My PID is %d\n", (int)getpid()); printf(" My Parent's PID is %d\n", (int)getppid()); /* 子进程做一些事情 */ printf(" Child is exiting now.\n"); /* 子进程结束 */ /* 注意:使用 _exit 而不是 exit 可以避免刷新缓冲区等问题,尤其是在 fork 后 */ _exit(0); } else { /* 这段代码在父进程中执行 */ printf("Parent Process:\n"); printf(" My PID is %d\n", (int)getpid()); printf(" I just created a child with PID %d\n", (int)pid); /* 父进程等待子进程结束 */ if (wait(&status) == -1) { perror("wait failed"); return 1; } /* 检查子进程是否正常退出 */ if (WIFEXITED(status)) { printf(" Child exited with status %d\n", WEXITSTATUS(status)); } else { printf(" Child did not exit normally.\n"); } printf(" Parent is exiting now.\n"); } return 0; /* 程序正常退出 */ } ``` --- ### 2. `vfork` - 进程管理 #### 函数介绍及归类 * **类别:** 系统调用 (System Call) * **头文件:** `#include `
* **作用:** 创建一个新进程,但与 `fork` 不同,它在子进程调用 `exec` 或 `_exit` 之前,共享父进程的地址空间,并且父进程会被阻塞。

#### 函数原型
“`c
#include
pid_t vfork(void);
“`

#### 功能
`vfork()` 也调用一次,返回两次。它创建一个新进程,但这个新进程(子进程)在调用 `exec` 系列函数或 `_exit` 之前,与父进程共享内存(包括栈、数据段等)。在此期间,父进程是被挂起的。这使得 `vfork` 在紧接着调用 `exec` 时非常高效,因为它避免了复制父进程的页表。

#### 参数
无参数。

#### 返回值
* **在父进程中:** 返回新创建的子进程的 PID。
* **在子进程中:** 返回 0。
* **失败时:** 在父进程中返回 -1,并设置 `errno`。

#### 相似函数或关联函数
* `fork()`: 更通用,子进程独立于父进程。
* `exec()` 系列函数 (`execl`, `execv`, `execle`, `execve`, `execlp`, `execvp`): 用新程序替换当前进程的镜像。

#### 示例代码 (C89)
“`c
#include /* printf, perror */
#include /* vfork, getpid, getppid, _exit */
#include /* pid_t */

int main() {
pid_t pid;
int shared_var = 10; /* 父子进程共享的变量 */

printf(“Before vfork, shared_var = %d\n”, shared_var);

/* 调用 vfork 创建子进程 */
pid = vfork();

if (pid < 0) { /* vfork 失败 */ perror("vfork failed"); return 1; } else if (pid == 0) { /* 这段代码在子进程中执行 */ /* 注意:在 vfork 后,子进程只能调用 exec 或 _exit */ /* 修改共享变量 */ shared_var = 20; printf("Child modified shared_var to %d\n", shared_var); printf("Child is about to exec.\n"); /* 紧接着调用 exec,这是 vfork 最常见的用法 */ /* execl("/bin/echo", "echo", "Hello from exec'd process!", (char *)NULL); */ /* 如果 exec 失败,必须使用 _exit 退出 */ /* execl("nonexistent_program", "dummy", (char *)NULL); */ /* perror("exec failed"); */ /* _exit(1); */ /* 子进程结束,父进程恢复 */ /* 为了演示,我们直接 _exit */ _exit(0); /* 子进程结束,父进程恢复 */ /* 下面的代码在 vfork 后是危险的,不应该执行 */ /* printf("This line should not be reached in child after vfork without exec/_exit\n"); */ /* return 0; */ /* 不要在 vfork 子进程中使用 return */ } else { /* 这段代码在父进程中执行 */ /* 父进程在这里被阻塞,直到子进程调用 exec 或 _exit */ printf("Parent resumed after child called _exit.\n"); printf("After child _exit, shared_var = %d (might be modified by child)\n", shared_var); printf("Parent is exiting now.\n"); } return 0; } ``` **注意:** `vfork` 的使用场景非常特定,主要是为了紧接着调用 `exec`。由于其共享内存的特性,使用不当容易导致问题,因此现代编程中 `fork` + `exec` 或 `posix_spawn` 更常用。 --- ### 3. `system` - 进程管理/命令执行 #### 函数介绍及归类 * **类别:** 标准库函数 (Standard Library Function) * **头文件:** `#include `
* **作用:** 执行一个 shell 命令字符串。它内部通常通过调用 `fork`、`exec` 和 `wait` 来实现。

#### 函数原型
“`c
#include
int system(const char *command);
“`

#### 功能
`system()` 函数将 `command` 字符串传递给系统的命令处理器(通常是 shell,如 `/bin/sh`)来执行。它是一个方便的函数,可以让你在 C 程序中执行外部命令。

#### 参数
* `command`: 指向以空字符结尾的字符串,该字符串包含了要执行的命令及其参数。

#### 返回值
* **成功:** 返回命令执行后的退出状态。这个状态可以通过 `WIFEXITED`, `WEXITSTATUS` 等宏来解析(类似 `wait`)。
* **`command` 为 NULL:** 如果系统有命令处理器可用,则返回非零值;否则返回 0。这可以用来检查 `system` 是否可用。
* **失败或错误:** 返回 -1,并设置 `errno`(例如,无法创建子进程)。
* **命令本身执行失败:** 返回由命令处理器报告的状态(通常是命令的退出码)。

#### 相似函数或关联函数
* `fork()`, `exec()` 系列, `wait()`: `system` 内部就是用这些函数实现的,提供了更底层和灵活的控制。
* `popen()`, `pclose()`: 可以执行命令并获取其输出(通过管道)。

#### 示例代码 (C89)
“`c
#include /* printf, perror */
#include /* system */

int main() {
int result;

/* 检查 system 是否可用 */
if (system(NULL) == 0) {
printf(“Command processor is not available.\n”);
return 1;
}

printf(“Command processor is available.\n”);

/* 执行一个简单的命令 */
printf(“\n— Executing ‘echo Hello from system call’ —\n”);
result = system(“echo Hello from system call”);
if (result == -1) {
perror(“system call failed”);
return 1;
} else {
printf(“Command ‘echo …’ finished with status %d\n”, result);
/* 注意:直接的 result 值可能包含更多信息,需要用 wait 宏解析 */
/* 为简化,这里只打印原始值 */
}

/* 执行一个列出文件的命令 */
printf(“\n— Executing ‘ls -l’ —\n”);
result = system(“ls -l”);
if (result == -1) {
perror(“system call failed”);
} else {
printf(“Command ‘ls -l’ finished with status %d\n”, result);
}

/* 执行一个会失败的命令 */
printf(“\n— Executing ‘nonexistentcommand’ —\n”);
result = system(“nonexistentcommand”);
if (result == -1) {
perror(“system call failed (this might be due to fork/exec failure)”);
} else {
printf(“Command ‘nonexistentcommand’ finished with status %d\n”, result);
/* 这里的 status 通常表示命令未找到 */
}

printf(“\nMain program ending.\n”);
return 0;
}
“`

### 4. `strlen` – 字符串处理

#### 函数介绍及归类
* **类别:** 标准库函数 (Standard Library Function)
* **头文件:** `#include `
* **作用:** 计算 C 风格字符串(以 `\0` 结尾)的长度。

#### 函数原型
“`c
#include
size_t strlen(const char *str);
“`

#### 功能
`strlen()` 函数遍历字符串 `str`,从第一个字符开始计数,直到遇到终止符 `\0`(不包括 `\0`),然后返回计数的字符数。

#### 参数
* `str`: 指向以 `\0` 结尾的字符串。

#### 返回值
返回字符串 `str` 中字符的个数(不包括结尾的 `\0` 字符)。返回类型 `size_t` 是一个无符号整数类型。

#### 相似函数或关联函数
* `sizeof`: 这是一个运算符,不是函数。对于字符数组,`sizeof` 返回整个数组的大小(包括 `\0`),而 `strlen` 返回实际内容的长度。
* `strnlen()`: (C99/C11) 类似 `strlen`,但会限制搜索长度,防止读取超出指定范围的内存。

#### 示例代码 (C89)
“`c
#include /* printf */
#include /* strlen */

int main() {
char str1[] = “Hello”; /* 编译器自动添加 \0 */
char str2[20] = “World”; /* 数组大小为 20,但内容只有 6 个字符(包括 \0) */
const char *str3 = “This is a longer string.”;
char str4[] = {‘N’, ‘o’, ‘ ‘, ‘N’, ‘u’, ‘l’, ‘l’}; /* 危险!没有 \0 */

size_t len1, len2, len3;

len1 = strlen(str1);
len2 = strlen(str2);
len3 = strlen(str3);

printf(“String 1: \”%s\”\n”, str1);
printf(“Length of str1 (strlen): %u\n”, (unsigned int)len1);
printf(“Size of str1 array (sizeof): %u\n”, (unsigned int)sizeof(str1)); /* 包括 \0 */

printf(“\nString 2: \”%s\”\n”, str2);
printf(“Length of str2 (strlen): %u\n”, (unsigned int)len2);
printf(“Size of str2 array (sizeof): %u\n”, (unsigned int)sizeof(str2)); /* 包括 \0 */

printf(“\nString 3: \”%s\”\n”, str3);
printf(“Length of str3 (strlen): %u\n”, (unsigned int)len3);

printf(“\n— Important Note —\n”);
printf(“String 4 is not null-terminated. Calling strlen on it leads to undefined behavior.\n”);
printf(“It might crash or run forever. Uncommenting the next lines is dangerous.\n”);
/* 危险!不要对没有 \0 的字符串调用 strlen */
/* printf(“Length of str4 (UNDEFINED BEHAVIOR!): %u\n”, (unsigned int)strlen(str4)); */

return 0;
}
“`

### 5. `strchr` – 字符串处理

#### 函数介绍及归类
* **类别:** 标准库函数 (Standard Library Function)
* **头文件:** `#include `
* **作用:** 在字符串中查找第一次出现的指定字符。

#### 函数原型
“`c
#include
char *strchr(const char *str, int c);
“`

#### 功能
`strchr()` 函数在字符串 `str` 中从左到右搜索字符 `c`(转换为 `char` 类型进行比较)。搜索包括终止符 `\0`。

#### 参数
* `str`: 指向要搜索的以 `\0` 结尾的字符串。
* `c`: 要搜索的字符,以 `int` 类型传递,但在比较时会转换为 `unsigned char`。

#### 返回值
* **找到:** 返回指向字符串 `str` 中第一次出现字符 `c` 的指针。
* **未找到:** 返回 `NULL`。

#### 相似函数或关联函数
* `strrchr()`: 查找字符在字符串中最后一次出现的位置。
* `strstr()`: 查找一个子字符串在另一个字符串中第一次出现的位置。
* `strpbrk()`: 查找字符串中第一个匹配指定字符集中任意字符的位置。

#### 示例代码 (C89)
“`c
#include /* printf */
#include /* strchr */

int main() {
const char *str = “This is a sample string.”;
char target_char = ‘s’;
char *result_ptr;

printf(“String to search: \”%s\”\n”, str);
printf(“Character to find: ‘%c’\n”, target_char);

/* 查找第一次出现 ‘s’ 的位置 */
result_ptr = strchr(str, (int)target_char);

if (result_ptr != NULL) {
printf(“First occurrence of ‘%c’ found at position: %ld\n”,
target_char, (long)(result_ptr – str)); /* 计算偏移量 */
printf(“Substring from first ‘%c’: \”%s\”\n”, target_char, result_ptr);

/* 查找下一个 ‘s’ */
printf(“\n— Searching for next occurrence —\n”);
result_ptr = strchr(result_ptr + 1, (int)target_char); /* 从下一个位置开始搜索 */
if (result_ptr != NULL) {
printf(“Second occurrence of ‘%c’ found at position: %ld\n”,
target_char, (long)(result_ptr – str));
printf(“Substring from second ‘%c’: \”%s\”\n”, target_char, result_ptr);
} else {
printf(“No more occurrences of ‘%c’ found.\n”, target_char);
}

} else {
printf(“Character ‘%c’ not found in the string.\n”, target_char);
}

/* 查找字符串结尾符 ‘\0’ */
printf(“\n— Searching for null terminator —\n”);
result_ptr = strchr(str, ‘\0’);
if (result_ptr != NULL) {
printf(“Null terminator ‘\\0’ found at position: %ld\n”, (long)(result_ptr – str));
printf(“Pointer points to: ‘%c’ (ASCII value %d)\n”, *result_ptr, (int)*result_ptr);
}

/* 查找一个不存在的字符 */
printf(“\n— Searching for a character that doesn’t exist —\n”);
result_ptr = strchr(str, ‘z’);
if (result_ptr != NULL) {
printf(“Character ‘z’ found? This is unexpected.\n”);
} else {
printf(“Character ‘z’ not found, as expected. strchr returned NULL.\n”);
}

return 0;
}
“`

发表在 linux文章 | 留下评论

C语言标准/C++标准

https://users.ece.cmu.edu/~eno/coding/CppCodingStandard.html

https://users.ece.cmu.edu/~eno/coding/CCodingStandard.html

发表在 linux文章 | 留下评论

c库缓冲方式对性能的影响及代码调整方法

fwrite的缓存方式对性能有显著影响,这主要涉及到C标准库的I/O缓冲机制。合理配置缓冲可以大幅提升I/O效率,特别是在处理大量数据时。

一、C标准库的三种缓冲模式

  1. 全缓冲(Fully Buffered)
  • 缓冲区满时才进行实际I/O操作(如写入磁盘)。
  • 默认用于普通文件(如磁盘文件),缓冲区大小通常为4KB或8KB(取决于系统)。
  • 性能最佳,减少实际I/O次数,但数据可能延迟写入。
  1. 行缓冲(Line Buffered)
  • 遇到换行符\n或缓冲区满时刷新。
  • 默认用于终端设备(如stdoutstdin),缓冲区较小(如1KB)。
  • 平衡交互性和性能,适合实时输出。
  1. 无缓冲(Unbuffered)
  • 每次调用fwrite立即执行实际I/O。
  • 默认用于标准错误输出stderr),确保错误信息实时显示。
  • 性能最差,但保证数据实时性。

二、缓冲对性能的影响

1. 性能差异示例

假设需要写入1MB数据,分1000次每次写入1KB:

  • 无缓冲:触发1000次实际I/O,性能最差。
  • 行缓冲:若数据包含换行符,可能触发多次I/O,性能中等。
  • 全缓冲:仅触发1次或少数几次I/O(取决于缓冲区大小),性能最佳。

2. 性能测试对比

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

#define SIZE 1000000  // 1MB数据

int main() {
    char buffer[SIZE];
    FILE *fp;
    clock_t start, end;
    double cpu_time_used;

    // 测试1:无缓冲模式
    fp = fopen("unbuffered.bin", "wb");
    setvbuf(fp, NULL, _IONBF, 0);  // 设置无缓冲
    start = clock();
    fwrite(buffer, 1, SIZE, fp);
    end = clock();
    fclose(fp);
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
    printf("无缓冲耗时: %f 秒\n", cpu_time_used);

    // 测试2:全缓冲模式(默认)
    fp = fopen("buffered.bin", "wb");
    // 默认即为全缓冲,无需额外设置
    start = clock();
    fwrite(buffer, 1, SIZE, fp);
    end = clock();
    fclose(fp);
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
    printf("全缓冲耗时: %f 秒\n", cpu_time_used);

    return 0;
}

典型结果:无缓冲模式耗时可能是全缓冲的10-100倍。

三、如何控制缓冲行为

1. 使用setvbuf函数

// 函数原型
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

// 示例:
FILE *fp = fopen("data.bin", "wb");

// 1. 全缓冲(自定义缓冲区)
char my_buffer[8192];  // 8KB缓冲区
setvbuf(fp, my_buffer, _IOFBF, sizeof(my_buffer));

// 2. 行缓冲
setvbuf(fp, NULL, _IOLBF, 0);  // 系统分配默认大小的行缓冲

// 3. 无缓冲
setvbuf(fp, NULL, _IONBF, 0);

2. 使用setbuf简化设置

// 全缓冲(自定义缓冲区)
char buf[8192];
setbuf(fp, buf);

// 无缓冲
setbuf(fp, NULL);  // 等价于 setvbuf(fp, NULL, _IONBF, 0)

四、性能优化建议

  1. 优先使用全缓冲
    对非交互式文件操作(如日志、数据存储),默认全缓冲已足够高效。
  2. 调整缓冲区大小
  • 对大文件写入,可增大缓冲区(如64KB或1MB),减少I/O次数:
    c char large_buf[1048576]; // 1MB缓冲区 setvbuf(fp, large_buf, _IOFBF, sizeof(large_buf));
  • 但过大的缓冲区会占用过多内存,需权衡。
  1. 批量写入
    避免频繁调用fwrite写入少量数据,尽量累积到缓冲区大小再写入:
   // 低效:
   for (int i = 0; i < 1000; i++) {
       fwrite(&data[i], sizeof(data[i]), 1, fp);  // 1000次I/O
   }

   // 高效:
   fwrite(data, sizeof(data[0]), 1000, fp);  // 1次I/O
  1. 适时刷新缓冲区
    若需确保数据及时写入(如崩溃恢复场景),可手动刷新:
   fflush(fp);  // 强制将缓冲区数据写入磁盘

五、特殊场景注意事项

  1. 实时日志记录
    若需确保日志实时写入(如系统崩溃时不丢失数据),可使用:
   setvbuf(log_file, NULL, _IONBF, 0);  // 无缓冲
   // 或定期手动刷新
   fflush(log_file);
  1. 网络I/O
    对套接字文件流,默认通常为行缓冲,需手动设置全缓冲以提升性能:
   setvbuf(socket_file, NULL, _IOFBF, 8192);  // 8KB全缓冲
  1. 内存映射文件(mmap)
    对超大数据量(GB级),可考虑使用mmap替代fwrite,直接映射内存到文件,避免缓冲区拷贝开销。

六、总结

  • 性能排序:全缓冲 >> 行缓冲 > 无缓冲。
  • 关键原则:减少实际I/O次数,批量处理数据。
  • 适用场景
  • 全缓冲:非实时数据(如批量处理、大文件读写)。
  • 行缓冲:交互式终端输出。
  • 无缓冲:实时日志、错误信息。

合理配置缓冲是提升I/O密集型应用性能的关键手段之一。

发表在 linux文章 | 留下评论

ls显示条件的取反结果(bash中条件取非操作)

 在Linux或Unix系统中,ls命令本身没有直接的参数支持显示条件的取反结果(例如显示“不匹配”某个模式的文件)。但可以通过结合其他命令或技巧实现类似功能。

以下是几种常见方法:

使用find命令配合-not!

find命令更灵活,支持逻辑取反操作:

find . -maxdepth 1 -type f ! -name "*.txt"  # 显示当前目录下非.txt文件

-maxdepth 1限制只搜索当前目录,-type f限定为文件,!表示取反。

结合grep过滤

通过管道将ls结果传递给grep -v(反向匹配):

ls | grep -v "pattern"  # 显示不包含"pattern"的文件名

需注意:grep -v会匹配所有输出行,包括隐藏文件(如果ls -a被使用)。

使用extglob扩展模式(Bash)

启用Bash的extglob功能后,可以使用复杂模式匹配:

shopt -s extglob
ls !(*.txt)  # 显示所有非.txt文件

此方法仅适用于Bash,且需提前开启extglob选项。

使用ls结合--ignore(部分版本)

某些ls实现(如GNU coreutils)支持--ignore参数:

ls --ignore="*.txt"  # 忽略.txt文件

但并非所有系统都支持该参数,需测试兼容性。

注意事项

  • 隐藏文件(以.开头的文件)默认不被ls显示,需加-a参数。
  • 文件名含空格或特殊字符时,建议用find -print0ls -q处理。
  • 若需递归操作,优先使用find而非ls

想匹配当前目录下“非 .c结尾”的文件

ls *.c可以匹配到所有以*.c结尾的文件,取反操作可以通过一下命令实现:

bash中执行shopt -s extglob命令设定

在执行 ls !(*.c)

shopt是shell内建命令,来控制bash的行为。 ​

发表在 linux文章 | 留下评论

shopify应用插件开发副业赚钱月入2w刀攻略

以下是针对新加坡市场的Shopify插件开发完整案例(WhatsApp Chat Button插件月收入$29,000实战路径):


一、痛点挖掘:逆向筛选法(从差评中找机会)

​Pain Point Discovery: Reverse Screening Method (Finding Opportunities in Negative Reviews)​

  • ​操作路径​​:
    1. ​扫描Shopify应用商店​​:分析3,000+插件,聚焦差评集中区(如客服工具响应慢、功能单一)。
      Scan Shopify App Store: Analyze 3,000+ plugins, focusing on negative review clusters (e.g., slow customer service response, limited features)
    2. ​锁定高需方向​​:东南亚商家急需​​WhatsApp即时聊天功能​​,但现有插件体验差(评分≤3星)、收费高。
      Identify High-Demand Direction: Southeast Asian merchants urgently need WhatsApp chat, but existing plugins have poor UX (rating ≤3 stars) and high cost
    3. ​验证标准​​:
      • ✅ 用户已付费痛点(证明市场存在)
      • ✅ 改进空间>50%(差评可转化为机会)
      • ✅ 6周内可开发MVP(快速验证)

二、MVP开发:2周极速上线(技术关键点)

​MVP Development: 2-Week Launch (Technical Keys)​

​技术栈​​功能实现​​避坑策略​
​后端: Node.js​处理高并发消息(日均10万+请求)用​​Ngrok生成HTTPS回调URL​​加速测试
Backend: Node.jsHandle high-concurrency messages (100k+ daily requests)Use Ngrok for HTTPS callback URL testing
​前端: Polaris​构建合规UI(符合Shopify设计规范)提前注册​​Shopify Partners账号​​避免审核延误
Frontend: PolarisBuild compliant UI (aligned with Shopify design standards)Register Shopify Partners account in advance
​核心功能​商家输入电话号码→点击“启用”按钮即对接WhatsApp(无需代码)
Core FeatureMerchant enters phone number → clicks “Enable” to connect WhatsApp (no coding)

​关键里程碑​​:


三、增长引擎:ASO优化+零付费推广

​Growth Engine: ASO Optimization + Zero Paid Promotion​

  • ​自然流量策略​​:
    1. ​名称含关键词​​:”WhatsApp Chat Button”直接匹配用户搜索
      Name with Keywords: “WhatsApp Chat Button” directly matches user searches
    2. ​截图展示一键启用​​:降低用户学习成本(转化率↑30%)
      Screenshots show one-click activation (reduces learning curve)
    3. ​差评转化​​:主动联系差评用户,迭代后通知更新(留存率↑25%)
      Convert Negative Reviews: Contact low-rating users for feedback
  • ​留存手段​​:
    • 所有用户(含免费版)提供​​24小时双语支持​​(英/中文)
      24/7 bilingual support for all users (English/Chinese)
    • 自动发送​​生命周期邮件​​收集需求 → 每月迭代1次

四、盈利模式:分层定价+新加坡本地化

​Monetization: Tiered Pricing + Singapore Localization​

​套餐​功能价格​新加坡优化项​
免费版基础聊天按钮$0自动识别+65区号
Free TierBasic chat button$0Auto-detect +65 prefix
标准版自动回复+多语言$9/月集成新加坡邮政API(物流查询)
StandardAuto-reply + multilingual$9/monthIntegrate SingPost API
高级版CRM集成+跨平台同步$29/月支持新加坡GST计算(结账显示含税价)
PremiumCRM integration + sync$29/monthSupport GST calculation

​规模化结果​​:

  • ​月收入$29,000​​:付费转化率12%(免费用户→付费)
  • ​成本控制​​:客服外包至新加坡Glints平台(响应延迟<5分钟)

五、新加坡开发者实操建议

​Actionable Tips for Singapore Developers​

  1. ​合规优先​​:
    • EP/SP准证持有者优先接​​跨国项目​​(如美国Shopify订单),避免与本地雇主冲突。
    • Compliance First: Prioritize cross-border projects for work pass holders
  2. ​技术复用​​:
    • 直接复用​​Shopify API代码库​​(GitHub开源模板),节省80%开发时间。
    • Code Reuse: Leverage Shopify API templates on GitHub
  3. ​冷启动渠道​​:
    • 加入​​新加坡开发者Slack群组​​获取本地需求
    • 提交插件至​​Shopify新加坡专题页​​(政策扶持本地团队)

​💡 成功公式​​:​​解决差评痛点 × 极简MVP × ASO自然流量​
Success Formula = Solve pain points × Minimal MVP × ASO organic traffic
​资源包​​:


发表在 linux文章 | 留下评论