0%

C# 泛型和委托的世界里,有两个经常让人皱眉的词:协变(Covariance)逆变(Contravariance)
它们的名字很抽象,但作用很实际:

它们允许你在泛型类型之间进行“安全”的类型替换,而不需要写一堆类型转换代码。

1. 为什么会有协变和逆变

先看一个简单例子:

1
2
3
4
5
class Animal { }
class Cat : Animal { }

IEnumerable<Animal> animals = new List<Cat>(); // 这样写可以
List<Animal> animalsList = new List<Cat>(); // ❌ 编译错误

为什么 IEnumerable<Animal> 可以指向 List<Cat>,而 List<Animal> 不行?
答案就是:协变和逆变是编译器提供的“类型兼容规则”,而 IEnumerable<T> 支持协变,但 iLst<T> 不支持。

阅读全文 »

一、eBPF 是什么?

1. 简单理解

eBPF 就是一个让你安全地在 Linux 内核中运行自定义代码的机制

它可以在内核中运行经过验证的“沙盒字节码”,而不需要修改内核源码,也不需要编写复杂的内核模块。

2. eBPF 的核心优势

  • 安全:内核会对 eBPF 程序进行严格验证(Verifier

  • 高性能:字节码会被 JIT 编译成本地机器码

  • 灵活:几乎可以挂到内核的任何地方(网络、进程、文件系统等)

  • 热插拔:加载/卸载不影响系统运行

二、eBPF 运行架构

下面这张图,可以帮你快速理解 eBPF 的工作原理:

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
+------------------------------+
| 用户态 (User Space) |
| |
| eBPF 程序 (C / bpftrace) |
| ↓ clang 编译 |
| eBPF 字节码 (.o) |
+--------------+---------------+
|
v
+--------------+---------------+
| 内核态 (Kernel Space) |
| |
| eBPF 验证器 (Verifier) |
| - 检查安全性 |
| - 防止死循环 |
| - 检查内存访问范围 |
| |
| JIT 编译器 (JIT Compiler) |
| - 转成本地机器码执行 |
| |
| 挂载到 Hook 点 |
| - kprobe/tracepoint |
| - XDP/tc |
| - socket filter |
+------------------------------+

三、eBPF 常用挂钩点

类型 说明 场景
Socket Filter 套接字层数据包过滤 tcpdump、网络分析
kprobe/kretprobe 内核函数入口/返回点插桩 系统调用跟踪
tracepoint 预定义内核事件 性能分析
tc BPF 网络流量控制 限速、QoS
XDP 网络驱动最底层数据包处理 DDoS 防护

四、eBPF 实战:追踪 TCP 连接

我们做一个小实验:当进程建立 TCP 连接时,打印 PID、目标 IP、端口。

1. 内核态 eBPF 程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// tcpconnect.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

char LICENSE[] SEC("license") = "GPL";

SEC("kprobe/tcp_v4_connect")
int tcp_connect(struct pt_regs *ctx)
{
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
u32 pid = bpf_get_current_pid_tgid() >> 32;

__be32 daddr = 0;
__be16 dport = 0;

bpf_probe_read_kernel(&daddr, sizeof(daddr), &sk->__sk_common.skc_daddr);
bpf_probe_read_kernel(&dport, sizeof(dport), &sk->__sk_common.skc_dport);

bpf_printk("PID %d -> %pI4:%d\n", pid, &daddr, bpf_ntohs(dport));
return 0;
}

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// loader.c
#include <stdio.h>
#include <bpf/libbpf.h>
#include <unistd.h>

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}

int main()
{
struct bpf_object *obj;
struct bpf_program *prog;
struct bpf_link *link;
int err;

libbpf_set_print(libbpf_print_fn);

obj = bpf_object__open_file("tcpconnect.bpf.o", NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "打开 eBPF 对象失败\n");
return 1;
}

err = bpf_object__load(obj);
if (err) {
fprintf(stderr, "加载 eBPF 程序失败\n");
return 1;
}

prog = bpf_object__find_program_by_name(obj, "tcp_connect");
if (!prog) {
fprintf(stderr, "找不到 eBPF 程序\n");
return 1;
}

link = bpf_program__attach_kprobe(prog, false, "tcp_v4_connect");
if (libbpf_get_error(link)) {
fprintf(stderr, "附加 kprobe 失败\n");
return 1;
}

printf("成功加载并附加 eBPF 程序。按 Ctrl+C 停止\n");
while (1) {
sleep(1);
}

bpf_link__destroy(link);
bpf_object__close(obj);
return 0;
}

3. 编译运行

1
2
3
4
5
6
7
8
# 编译内核态
clang -O2 -g -target bpf -D__TARGET_ARCH_x86 -c tcpconnect.bpf.c -o tcpconnect.bpf.o

# 编译用户态
gcc loader.c -o loader -lbpf

# 运行
sudo ./loader

4. 查看日志

1
sudo cat /sys/kernel/debug/tracing/trace_pipe

示例输出:

1
2
PID 1234 -> 93.184.216.34:80
PID 5678 -> 142.250.72.238:443

五、eBPF 的实际应用

  1. 性能分析

    • bcc / bpftrace 脚本库
  2. 网络安全

    • XDP 高速包过滤
  3. 故障排查

    • 内核函数动态追踪
  4. 容器可观测性

    • Cilium、Pixie

六、总结

eBPF 已经成为 Linux 内核可观测性和网络安全的核心技术。
它的强大在于:

  • 不改内核源码

  • 高性能

  • 高安全

  • 热插拔

对于开发者和运维工程师来说,掌握 eBPF,不仅可以提高故障排查效率,还能为安全和性能优化带来新的可能性。

1. 为什么需要优化?

MongoDB 虽然灵活,但如果不加优化,可能出现:

  • 查询性能低(扫描全表)

  • 分片热点(某一分片压力过大)

  • 内存占用高(缓存无效)

  • 写入延迟大(锁竞争)

优化的核心目标是:减少扫描量,减少锁竞争,充分利用索引与缓存

阅读全文 »

1. 聚合(Aggregation Framework)

聚合是 MongoDB 中最强大的分析与数据处理工具,相当于 SQL 中的 GROUP BYJOINWHEREORDER BY 等组合。

1.1 聚合管道的基本原理

MongoDB 的聚合是一个数据流管道:

1
输入文档  →  $match$group$sort$project  →  输出结果

每个阶段都会对数据进行筛选、计算、排序或重塑。

阅读全文 »

1. 什么是 MongoDB?

MongoDB 是一种 面向文档的 NoSQL 数据库,与传统的关系型数据库(MySQLPostgreSQL)不同,它不使用表格和行,而是使用 BSON(二进制 JSON)存储数据。
简单来说,它是一个 JSON 存储与查询引擎

MongoDB 的核心特性:

  • 文档型存储:每条数据是一个 JSON 对象。

  • 无固定模式(Schema-less):字段可以灵活增加或减少。

  • 高扩展性:支持分片(Sharding)与副本集(Replica Set)。

  • 强大的查询语言:支持聚合(Aggregation)、全文检索(Full-Text Search)。

  • 天然适合大数据与高并发场景

阅读全文 »

一、什么是 Makefile

Makefile 是为 make 命令准备的脚本文件,它描述了项目中文件之间的依赖关系以及如何编译它们。它允许你只输入一条命令 make,即可自动编译项目中所有需要更新的部分。

二、Makefile 基本语法

最基本的格式如下:

1
2
目标: 依赖
命令

例如:

1
2
hello.o: hello.c
gcc -c hello.c -o hello.o

含义:当 hello.c 更新时,执行 gcc -c hello.c -o hello.o 生成 hello.o。

make 会自动根据文件的时间戳判断哪些目标需要重新生成,这就是它的高效之处。

阅读全文 »

1. 找到sysent

1. find_kernel_base

    #define MACH_KERNEL_BASE 0xFFFFFE0007004000 // macOS 内核文件的基地址 for "/System/Library/Kernels/kernel.release.t8101"
    使用 vm_kernel_unslide_or_perm_external 找到 kernel_slide
    base_address = kernel_slide + MACH_KERNEL_BASE

完整调用流程

sequenceDiagram
    participant UserSpace as 用户空间程序
    participant IOKit as IOKit框架
    participant Driver as 内核驱动
    participant UserClient as UserClient实例

    Note over UserSpace,UserClient: 阶段1:服务发现
    UserSpace->>IOKit: IOServiceGetMatchingServices(kIOMainPortDefault, matchingDict, &iterator)
    IOKit->>Driver: 内核遍历服务列表
    Driver-->>IOKit: 返回匹配的服务对象
    IOKit-->>UserSpace: 通过iterator返回服务句柄

    Note over UserSpace,UserClient: 阶段2:建立连接
    UserSpace->>IOKit: IOServiceOpen(service, mach_task_self(), type, &connection)
    IOKit->>Driver: 调用驱动的newUserClient()

    alt 自定义newUserClient实现
        Driver->>Driver: 1. 安全检查(task_is_privileged等)
        Driver->>Driver: 2. 通过OSTypeAlloc创建UserClient
        Driver->>UserClient: initWithTask(owningTask, type...)
        UserClient-->>Driver: 返回初始化结果
        Driver-->>IOKit: 返回UserClient实例
    else 默认实现
        IOKit->>UserClient: 自动创建标准UserClient
        UserClient->>UserClient: 默认initWithTask()
        UserClient-->>IOKit: 返回实例
    end

    IOKit-->>UserSpace: 返回connection句柄

    Note over UserSpace,UserClient: 阶段3:命令交互
    loop 多次调用
        UserSpace->>UserClient: IOConnectCallMethod(connection, selector, ...)
        UserClient->>UserClient: 根据sMethods分派到具体方法
        UserClient->>Driver: 调用驱动方法(如performCalculation)
        Driver-->>UserClient: 返回数据/状态
        UserClient-->>UserSpace: 返回结果
    end

    Note over UserSpace,UserClient: 阶段4:断开连接
    UserSpace->>UserClient: IOServiceClose(connection)
    UserClient->>UserClient: clientClose()
    UserClient->>Driver: 通知驱动(可选)
    UserClient->>UserClient: 释放资源

IODataQueueMemory

用户层代码

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
void ControllerImpl::MsgRecvThreadFunc() {
kern_return_t kr;
UInt32 dataSize;
IODataQueueMemory* queueMappedMemory;
vm_size_t queueMappedMemorySize;
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
mach_port_t recvPort;
TestMessage msg;

// allocate a Mach port to receive notifications from the IODataQueue
if ((recvPort = IODataQueueAllocateNotificationPort()) == MACH_PORT_NULL) {
return;
}

// this will call registerNotificationPort() inside our user client class
kr = IOConnectSetNotificationPort(m_nConnection, QUEUETYPE_MONITOR, recvPort, 0);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), recvPort);
return;
}

// this will call clientMemoryForType() inside our user client class
kr = IOConnectMapMemory(m_nConnection, kIODefaultMemoryType, mach_task_self(), &address, &size, kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
mach_port_destroy(mach_task_self(), recvPort);
return;
}

queueMappedMemory = (IODataQueueMemory*)address;
queueMappedMemorySize = size;

while (!m_bStop && IODataQueueWaitForAvailableData(queueMappedMemory, recvPort) == kIOReturnSuccess) {
while (!m_bStop && IODataQueueDataAvailable(queueMappedMemory)) {
dataSize = sizeof(msg);
kr = IODataQueueDequeue(queueMappedMemory, &msg, &dataSize);
if (kr != kIOReturnSuccess) {
continue;
}

Callback(msg);

} // end while (IODataQueueDataAvailable(queueMappedMemory))
} // end while (IODataQueueWaitForAvailableData

exit:

kr = IOConnectUnmapMemory(m_nConnection, kIODefaultMemoryType, mach_task_self(), address);
if (kr != kIOReturnSuccess) {
}

mach_port_destroy(mach_task_self(), recvPort);

}

什么是 macOS 内核扩展(KEXT)?

macOS 内核扩展是一种动态链接库(通常后缀为 .kext),用于扩展 XNU 内核的功能。KEXT 可被用于编写驱动程序、系统安全模块、文件系统支持等底层功能。

macOS Catalina(10.15)之后,Apple 推出 System ExtensionsDriverKit,逐步替代传统 KEXT,但 KEXT 仍广泛用于底层研究和安全相关开发。

阅读全文 »

HTTPS 通信中,浏览器如何判断一个网页使用的证书是否已经 吊销Revoked)?证书一旦吊销,就意味着它已不再可信,但如何通知用户浏览器,是一个非常关键的安全机制。

本篇文章将全面梳理浏览器判断证书吊销状态的几种主流机制,及其优缺点和实际表现。

阅读全文 »