TGTGInsighttelegram intelligenceLIVE / telegram public index
Back to channels
Welcome to the Black Parade avatar

TGINSIGHT CHAT

Welcome to the Black Parade

@TheB1ackParade

Music

Death has many faces, I look forward to seeing this one.

Subscribers787Current channel subscribers
Tracked posts907Indexed post count
Recent reach7,925Sum of recent post views
Recent posts

Recent posts

Page 4 of 76 · 907 posts

Posted Jan 20

我服了,kernel 6.1 的 patch version 居然引入了新的 bug,下面的 bpf 在 6.1.152 是好的,但 6.1.158 ~ 6.1.160(HEAD) 都会在 bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_CGROUP_SKB}) 的时候报错 EINVAL (不是 verifer error)。 struct { __uint(type, BPF_MAP_TYPE_PROG_ARRAY); __uint(max_entries, 3); __uint(key_size, sizeof(__u32)); __uint(value_size, sizeof(__u32)); } jmp_table SEC(".maps"); SEC("cgroup_skb/ingress") int cgroup_skb_ingress(struct __sk_buff *skb) { bpf_tail_call(skb, &jmp_table, 0); return 1; } SEC("cgroup_skb/egress") int cgroup_skb_egress(struct __sk_buff *skb) { bpf_tail_call(skb, &jmp_table, 0); return 1; } https://github.com/jschwinger233/kernel-6.1.160-bpf_tail_call 在更高的 minor version 上 (6.6/6.14) 也都没问题,唯有 6.1 最近几个 patch versions... 开源之美,是美酒千杯,我怎能不醉 😀

948 views

Posted Jan 13

啊?啊??啊???我这2025全球排名第十九的粉丝直接从床上弹起来撕裂手术伤口痛得龇牙咧嘴🙋‍♀️

782 views

Posted Jan 10

虽然没有很喜欢《怪奇物语》(对不起觉得是神剧的朋友们 🤔),但是里面三次响起 《Heroes》 by David Bowie 都让我非常感动并且写入每一季的短评里。 第一次是 S1E3 片尾曲,从湖里打捞出 Will 的尸体,Will 妈 Joyce 刚从屋子里通过电灯和次元墙里的寄生兽发生了第三类接触。这里的英雄时刻其实是在紧接着的 E4 开头,Joyce 虽然被次元墙里的怪物吓到惊慌失措,但听到 Will 的声音后立刻跑回去,抄起斧头砸墙并坐在墙边坚定守候。 第二次是 S3E8 片尾曲,Hopper 牺牲了,小孩们长大了,DND 也扔掉了,Will 一家被折磨了三年终于要搬家去加州了,苦尽甘来,所有没有放弃追求幸福的人都是英雄,Hopper 更是英雄。 第三次是 S5E8 片尾曲,在八分钟速通夺心魔之后(😅) 四人帮的最后一次 DND 跑团给了 11 一个开放的美好想象,一曲 Heroes 肝肠断,天涯何处觅知音。 Heroes 另一个令人印象深刻的 BGM 是二战电影《乔乔兔》 的结局,在经历过希特勒青年团、斯嘉丽被纳粹处死、犹太小姐姐、这些人生巨大转折之后,美国人占领城市,两个情窦初开的小孩在德语版的 Heroes 里跳舞: we can be heroes, just for one day. https://www.youtube.com/watch?v=BfL5V3WHhqM Heroes 是一首伟大的歌曲,它本身歌词也是在描绘一对恋人在柏林墙下接吻,子弹从他们头顶飞过,所有人类美好品质凝结在那一刻。 后来很多人翻唱过这首歌,包括我最爱的新浪潮 Depeche Mode: https://www.youtube.com/watch?v=q6yzrZfgQvI Lady Gaga 在格莱美 2016: https://youtu.be/5o9houkS9MM?t=296 Prince 😋: https://www.youtube.com/watch?v=8kOTOEvgh-A Coldplay: https://www.youtube.com/watch?v=vGwySl3SS_c 封面也经典,后来 Daft Punk 翻拍过: https://www.reddit.com/r/DaftPunk/comments/biys5n/picture_of_thomas_making_the_heroes_pose/ 荒木飞吕彦也曾: https://www.gcores.com/articles/99053 (其实我也想翻拍😭) 其实葬礼放 Heroes 也挺好的,只可惜自己不能参加自己的葬礼。。。 Let everything happen to you beauty and terror Just keep going No feeling is final - Rainer Maria Rilke

802 views

Posted Jan 10

最近一直在高强度依赖 socket cookie 构建模型,在考虑生命期(sk cookie 在 sk release 之后被复用吗)和数值分部(如果足够局部也许能很方便用 robin hood map 做性能优化)的时候看了一眼内核的 cookie 生成代码,很有趣 🤔 static __always_inline u64 gen_cookie_next(struct gen_cookie *gc) { struct pcpu_gen_cookie *local = this_cpu_ptr(gc->local); u64 val; if (likely(local_inc_return(&local->nesting) == 1)) { val = local->last; if (__is_defined(CONFIG_SMP) && unlikely((val & (COOKIE_LOCAL_BATCH - 1)) == 0)) { s64 next = atomic64_add_return(COOKIE_LOCAL_BATCH, &gc->forward_last); val = next - COOKIE_LOCAL_BATCH; } local->last = ++val; } else { val = atomic64_dec_return(&gc->reverse_last); } local_dec(&local->nesting); return val; } 核心思想是 likely(nesting == 1) 内层 + unlikely(val & 0xfff == 0) 外层的快路径:每个 CPU 分配 COOKIE_LOCAL_BATCH (4096) 个 per-cpu local ID,在非重入情况下直接 ++val 这个 local ID 作为 cookie 返回,零锁零原子指令,性能巨魔,只有在用完 4096 个 cookie 之后,走一次慢路径,用原子指令分配新的 4096 slot;或者在重入情况下走另一条原子指令直接返回。 (手机看下图缩进混乱警告🔞) cpu0 cpu0 (used up) (val=4096*2+2048) │ │ ▼ ▼ ┌─────────┬─────────┬─────────┐ │ 4096 │ 4096 │ 4096 │ └─────────┴─────────┴─────────┘ ▲ │ cpu2 (val=4096+100) 利用这个思想我们可以设计出极致性能的线程安全 ID 生成器,在用户态甚至更简单一点因为可以用 TLS thread local,或者更简单一点显式使用 thread local generator,比如 go 可以写成这样 const Batch = 4096 type Global struct { forward_last atomic.Uint64 } type Local struct { last uint64 _ [64 - 8]byte // Cacheline forward_last *atomic.Uint64 } func NewGlobal() *Global { return &Global{} } func (g *Global) GetLocal() *Local { return &Local{forward_last: &g.forward_last} } func (l *Local) NextID() uint64 { val := l.last if (val & (Batch - 1)) == 0 { base := l.forward_last.Add(Batch) - Batch val = base } val++ l.last = val return val } 在合适的场景下,如 “goroutine 总量不多但每个 goroutine 高并发地生成拳交唯一 ID”的场景下(其实和内核的 per cpu ptr 类似),这个算法完爆 atomic.Uint64 和 sync.Mutex $ taskset -c 1,2,4 ./go_nextid.test -test.bench=. cpu: Intel(R) Core(TM) Ultra 7 155U BenchmarkNextID_Local-3 1000000000 0.2906 ns/op BenchmarkNextID_Local_Fixed/G=16-3 1000000000 0.1252 ns/op BenchmarkNextID_Local_Fixed/G=128-3 1000000000 0.1352 ns/op BenchmarkNextID_Local_Fixed/G=1024-3 1000000000 0.1436 ns/op BenchmarkAtomic_Add-3 316950108 3.804 ns/op BenchmarkAtomic_Add_Fixed/G=16-3 56497480 21.83 ns/op BenchmarkAtomic_Add_Fixed/G=128-3 54910852 21.90 ns/op BenchmarkAtomic_Add_Fixed/G=1024-3 55797655 22.03 ns/op BenchmarkMutex-3 100000000 10.78 ns/op BenchmarkMutex_Fixed/G=16-3 15218778 85.92 ns/op BenchmarkMutex_Fixed/G=128-3 12138136 103.6 ns/op BenchmarkMutex_Fixed/G=1024-3 10970743 106.1 ns/op 也就快了区区一百倍吧,逃(

497 views

Posted Jan 9

最近又在和 bpf verifier 肉搏了,但这次处理地很有条理,得意分享一下 🤔 u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; bpf_skb_load_bytes(skb, ..., ..., copy_len); 上面的 bpf c 会被 6.1 内核拒绝,原因是 bpf_skb_load_bytes 第四参数不能为零值: 350: (85) call bpf_skb_load_bytes#26 invalid zero-sized read 显然应该加上 copy_len 非零的检查 u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; if (!copy_len) return; bpf_skb_load_bytes(skb, ..., ..., copy_len); 但是依然相同的零值错误 ; if (!copy_len) 342: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ; if (!copy_len) 343: (15) if r4 == 0x0 goto pc-153 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ... 351: (85) call bpf_skb_load_bytes#26 invalid zero-sized read 仔细看一下, if (!copy_len) 的分支是生效的, if r4 == 0x0 goto pc-153,但是 verifier 没有更新 r4 的下界 umin。 也许应该把非零检查改为下界检查? u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; if (copy_len > 0) goto cont2; return; cont2: bpf_skb_load_bytes(skb, ..., ..., copy_len); 但这次还是一样的 invalid zero-sized read 😀 ; if (copy_len > 0) 342: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ; if (copy_len > 0) 343: (15) if r4 == 0x0 goto pc-153 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ... 351: (85) call bpf_skb_load_bytes#26 invalid zero-sized read processed 12902 insns (limit 1000000) max_states_per_insn 11 total_states 287 peak_states 167 mark_read 17 对着 verifier log 冥想足够长的时间,发现 if (copy_len>0) 被 clang 编译为了 r4 &= 255 和 if r4 == 0x0 goto,难怪 verifier 无法生成 r4 的 umin。 clang 太坏了,它怎么就不能生成 if r4 > 0 goto 呢? 生气了,手冲 asm,这下 clang 不会给我乱编译字节码了吧! u8 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; asm volatile goto( "if %[len] s> 0 goto %l[cont2]\n\t" : : [len] "r"(copy_len) : "memory" : cont2 ); return; cont2: bpf_skb_load_bytes(skb, ..., ..., copy_len); 然后还是得到 zero read ; asm volatile goto( 342: (65) if r4 s> 0x0 goto pc+1 344: R0=48 R1=0 R2=0 R3=32 R4=scalar(id=1099,umin=1,umax=999,var_off=(0x0; 0x3ff)) R5=fp-17 R6=scalar(id=1093,smin=-120,smax=4294967295) R7=ctx(off=0,imm=0) R8=0 R9=scalar(id=1094,umin=7,umax=127,var_off=(0x3; 0x7c)) R10=fp0 fp-16=mmmmmm?? fp-24=00000000 fp-32=00000000 fp-40=00000000 fp-48=00000000 fp-56=mmmm0000 fp-64=00000000 fp-72=00000000 fp-80=00000000 fp-88=00000000 fp-96=00000000 fp-104=00000000 fp-112=00000000 fp-120=00000000 fp-128=00000000 fp-136=00000000 fp-144=00000000 fp-152=00000000 fp-160=00000000 fp-168=00000000 fp-176=00000000 fp-184=00000000 fp-192=00000000 fp-200=00000000 fp-208=00000000 fp-216=00000000 fp-224=00000000 fp-232=00000000 fp-240=00000000 fp-248=00000000 fp-256=00000000 fp-264=00000000 fp-272=00000000 fp-280=00000000 fp-288=00000000 fp-296=00000000 fp-304=00000000 fp-312=00000000 fp-320=00000000 fp-328=mmmmmmmm fp-336=map_value fp-344=map_value ; offset += 1 + key_size + 2; 344: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) ... 352: (85) call bpf_skb_load_bytes#26 invalid zero-sized read 等等,上面的日志好像已经对了,在我们手动注入 if r4 s> 0x0 goto pc+1 之后,verifier 给 r4 打上了正确的下界 umin=1 R4=scalar(id=1099,umin=1,umax=999,var_off=(0x0; 0x3ff)) 但是随后的 r4 &= 255 又把 umin=1 抹平了 344: (57) r4 &= 255 ; R4_w=scalar(umax=255,var_off=(0x0; 0xff)) 为什么 clang 生成了一句 r4 &= 255?对着 Piplup 小企鹅发呆了一会儿,我意识到是 copy_len 的类型导致的,我的类型定义是 u8 copy_len 因为它够小够用,然而 bpf_skb_load_bytes 类型声明里第四参数是 u32,所以相当于有个隐式的类型转换 bpf_skb_load_bytes(skb, ..., ..., (u32)copy_len); 这个类型转化导致 clang 生成了一句 r4 &= 255 字节码,导致 verifier 六亲不认销毁 umin。 最后把 copy_len 改为 u32,大成功! u32 copy_len = key_size; if (copy_len > MAX_REDIS_KEY_SIZE) copy_len = MAX_REDIS_KEY_SIZE; asm volatile goto( "if %[len] s> 0 goto %l[cont2]\n\t" : : [len] "r"(copy_len) : "memory" : cont2 ); return; cont2: bpf_skb_load_bytes(skb, ..., ..., copy_len); 我感到自己已经逐渐理解 verifier 了,升格为弱等神力了 🤔

480 views

Posted Jan 8

圣诞前还在和我热烈讨论使用 go 1.23 iterator yield 来实现更漂亮的 API,昨晚就宣布退休退出讨论。我还以为你会感到有趣,没想只是工作罢了,你终究还是不爱这项目 🤔有时爱美在无法永恒,你若勇敢爱了就要勇敢分 🤔

523 views

Posted Jan 4

读文档时发现下面这句话是歧义句: This program type isn't allowed to read from and write to all fields of the context since doing so might break assumptions in the kernel or because data isn't available at the point where the program is hooked into the kernel. 它可以被理解成 a. all the fields aren't allowed to read and write b. not all the fields are allowed to read and write (i.e. some are allowed) 我记得之前在知乎看过 费事发财 老师科普过自然语言的歧义似乎讲过这个,大概是 De dicto vs de re: https://en.wikipedia.org/wiki/De_dicto_and_de_re a. De dicto: Not allowed [to access-all-fields] ¬allow(∀field: RW(field)) b. De re: For each field, not allowed [to access that field] ∀field: ¬allow(RW(field)) 中文也有一样的歧义表达,请品:这种程序类型不允许读写上下文的所有字段。 ref: https://en.wikipedia.org/wiki/Scope_(formal_semantics)

777 views

Posted Jan 2

好想买一个武藤游戏的头套 🥹

651 views

Posted Dec 31

Welcome to the Black Parade pinned a photo

views

Posted Dec 31

在新年来临之际写了一份频道介绍,建立阅读预期😓 本频道包含以上内容⬆️

817 views

Posted Dec 28

😭 L死了。

788 views

Posted Dec 24

啥玩意儿,我也就半年没看海贼王,已经快进到肌肉裸男和大胸美女亲密接触了吗?

860 views
123456•••10•••15•••20•••25•••30•••35•••40•••45•••50•••55•••60•••65•••70•••7576