eBPF可观测之网络流量控制和管理traffic control浅尝

目录

工程背景

环境准备

安装工具​​​

安装依赖包

安装C依赖库

操作步骤

目录结构

代码展示

效果展示

拓展提升


工程背景

首先发表一个"暴论"

eBPF在可观测方面的应用,就是各种google。

不需要学习内核,只要掌握ebpf开发套路。

好比你开发 web 开发网站, 你了解socket 底层和内核吗? 一样不了解。 知道怎么调用就行了。

而且 eBPF 的开发也没多少复杂度, 更多的是 在内核态拦截(简化的c语言) 内核数据(不管是网络数据还是tracepoint数据), 最终都是要发给用户态(可以理解为java、golang),然后用户态具体做业务处理。

所以c语言也不需要怎么学,学了也没啥用。

更多的是要学会一些Linux知识。 譬如拦截网络数据,那就得 对tcp/ip协议了解的很清楚,知道怎么寻址。

至于说寻址代码怎么写,chatgpt都能把你生成。

因此,我们就只从其如何使用切入,用短平快的方式上手掌握。目标是用c语言处理内核态,发送到用户态用golang处理,至于把监控数据塞到mysql、prom之类的,那都属于可扩展内容。

另外,通过eBPF,我们可以做到一些应用层做不到或者不应该做到的事情。譬如ddos拦截应该放在eBPF,即网卡协议层面就应该拦截。而不是放在nginx上拦截,因为进入nginx已经到用户层了,这无疑会对系统负载造成巨大压力 。

对于网络流量控制和管理,一般有traffic control、tracepoint、XDP两种常用方式,区别如下:

  1. Traffic Control(TC):Traffic Control 是 Linux 内核中的一个子系统,用于网络流量的控制和管理。eBPF 可以与 TC 结合使用,通过编写 eBPF 程序来对网络流量进行更细粒度的控制和处理,例如流量分类、队列管理、带宽控制等。eBPF 可以在 TC 的不同阶段插入自定义的程序逻辑,以实现高级的流量控制功能。
  2. Tracepoint:Tracepoint 是 Linux 内核中的一种跟踪工具,用于收集系统和应用程序的运行时信息。eBPF 提供了一种机制,可以在 Tracepoint 上运行自定义的 eBPF 程序,以收集、分析和处理 Tracepoint 产生的事件数据。通过 eBPF,可以对系统的各种事件进行跟踪和监控,例如进程创建、系统调用、网络流量等,而无需修改内核代码。
  3. XDP(eXpress Data Path):XDP 是 Linux 内核中的一种高性能数据包处理框架,用于在网络驱动程序接收数据包之前对其进行处理。eBPF 可以与 XDP 结合使用,编写 eBPF 程序对数据包进行高效的过滤、修改和重定向操作。XDP 允许在数据包进入网络协议栈之前进行快速的数据包处理,适用于高性能网络应用,如防火墙、负载均衡和数据包捕获。

简而言之,

  • Tracepoint也可以用于网络,但是其并不修改内核,因此只能对一些事件进行跟踪监控。
  • tc作用在linux流量控制器traffic controller,既可作ingress又可作egress;而xdp作用在设备驱动上,一般就作ingress,同时性能更高。
  • tc是本身存在的,因此只需要创建一个clsact类型的队列作为程序挂载的入口,就像hook一样,可以更方便地修改报文,端口,地址等。而xdp需要将上下文从链路层、网络层、传输层一步步获取。

所以我们就通过TC去拦截veth设备上通过的网卡流量,甚至去篡改数据包以实现伪造源ip或者目标端口转发。

环境准备

eBPF依赖高内核版本的linux,所以我准备了:

  • 操作系统 ubuntu22 内核5.15
  • 安装docker golang openssl3(这里直接使用了腾讯云的容器专用的虚拟机镜像)

安装工具​​​

go get  github.com/cilium/ebpf/cmd/bpf2go
go install  github.com/cilium/ebpf/cmd/bpf2go

需要添加/go/bin到环境变量中,用于执行生成的工具文件
这是转换程序,允许在Go 代码中编译和嵌入eBPF 程序

安装依赖包

sudo apt install llvm
sudo apt install clang

安装C依赖库

sudo apt install libelf-dev
git clone --depth 1 https://github.com/libbpf/libbpf
cd src
make install

这个库运行报错,拿软链接尝试解决了问题

sudo ln -s /usr/include/x86_64-linux-gnu/asm /usr/include/asm

操作步骤

简要地描述一下操作的步骤

1.项目目录下执行make把操作内核态的c文件编译生成.go和.o文件
2.编写方法来加载bpf program对象,创建队列,挂载网卡,最后供main函数调用     
3.go run cmd/tc/main.go运行   
4.命令行查看go的输出                                  
或者 cat /sys/kernel/debug/tracing/trace_pipe查看bpf_printk输出(限于使用tc工具创建队列和挂载网卡)
tips:
1.通过perf list|grep sys_exit_execve 查看具体的tracepoint
2.通过cat /sys/kernel/debug/tracing/available_filter_functions|grep finish_task_switch 查看具体的kprobe(这里的名称用于用户态去link)
3.如需读取内核数据,如获取父进程pid,可以执行
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
包含了系统运行Linux 内核源代码中使使用的所有类型定义
4.https://github.com/torvalds/linux 查看源码获取内核函数的签名

目录结构

.
├── Makefile `用来加载环境变量并执行编译`
├── cebpf
│        ├── headers `用于存放bpf相关的头文件` 下载自源码
│        └── docker `容器间网络互访,包含了xdp,tc`
│            ├── doc.go `实际的编译命令存放的地方,通过makefile来指向`
│            ├── loader.go `创建队列,绑定网卡,从内核获取数据`
│            ├── dockertc.bpf.c         `原始bpf代码`
│            ├── mydockertc_bpfeb.o       `⬆️ 编译生成的文件`
│            ├── mydockertc_bpfeb.go      `⬇️ 包含了所要加载的bpf程序对象`
├── cmd
│        └── tc
│            └── main.go `主函数入口`

代码展示

1.Makefile

CLANG ?= clang
CFLAGS ?= -O2 -g -Wall -WerrorEBPF_ROOT = /home/ubuntu/app/goebpf/cebpf
MY_HEADERS = $(EBPF_ROOT)/headersall: generategenerate: export BPF_CLANG=$(CLANG)
generate: export BPF_CFLAGS=$(CFLAGS)
generate: export BPF_HEADERS=$(MY_HEADERS)
generate:go generate ./...

调用 go:generate 关键词来进行编译

2.doc.go

package docker//go:generate bpf2go  -cc $BPF_CLANG -cflags $BPF_CFLAGS -target amd64 mydockertc dockertc.bpf.c -- -I $BPF_HEADERS

通过这步编译命令,我们可以将 dockertc.bpf.c 编译出 .go 和 .o文件。

.go文件包含了bpg program对象,用于golang的用户态调用。

.o文件可以执行用于命令行tc可执行程序挂载网卡

有如下:

traffic control入门——命令行方式加载bpf程序
1.tc qdisc add dev docker0 clsact ---使用docker0创建一个队列
2.tc filter add dev docker0 ingress bpf direct-action obj mydockertc_x86_bpfel.o 
清理命令
tc qdisc del dev docker0 clsact
查看命令
tc filter show dev docker0 ingress

3.dockettc.bpf.c

//go:build ignore
#include <vmlinux.h>
#include <bpf_helpers.h>
#include <bpf_endian.h>
#include <bpf_tracing.h>
#include <bpf_legacy.h>#define ETH_HLEN 14 //以太网头部长度
#define IP_CSUM_OFF (ETH_HLEN + offsetof(struct iphdr, check))
#define TOS_OFF (ETH_HLEN + offsetof(struct iphdr, tos))
#define TCP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, check)) //csum的偏移量
#define IP_SRC_OFF (ETH_HLEN + offsetof(struct iphdr, saddr))
#define TCP_DPORT_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, dest)) //目标端口的偏移量
#define TCP_SPORT_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, source)) //目标端口的偏移量#define IS_PSEUDO 0x10char LICENSE[] SEC("license") = "GPL";struct tc_data_ip { __u32 sip; //源IP地址__u32 dip; //目的IP地址__u32 sport; //源端口__u32 dport; //目的端口
};//ringbuf
struct { //ringbuf,环形缓冲区,算是一种用户内核交互的优先选择__uint(type, BPF_MAP_TYPE_RINGBUF);__uint(max_entries,1<<20); //大概是10M大小
} tc_ip_map SEC(".maps");//从skb获取ip头部
static inline int iph_dr(struct __sk_buff *skb, struct iphdr *iph) //内连函数,编译时直接展开,减少函数调用开销
{int offset = sizeof(struct ethhdr); //计算以太网头部的偏移量return bpf_skb_load_bytes(skb, offset, iph, sizeof(*iph));
}//从skb获取tcp头部
static inline int tcph_dr(struct __sk_buff *skb, struct tcphdr *tcph) //内连函数,编译时直接展开,减少函数调用开销
{int offset = sizeof(struct ethhdr) + sizeof(struct iphdr); //计算以太网头部和ip头部的偏移量return bpf_skb_load_bytes(skb, offset, tcph, sizeof(*tcph));
}//改源ip的,没用上,先注释了
//todo 使用目标ip重定向的问题在于,就是old_ip一定得要真实存在才可以,否则连二层arp都通过不了,需要做arp欺骗
//static inline void set_tcp_ip_src(struct __sk_buff *skb, __u32 new_ip)
//{
//	__u32 old_ip = bpf_htonl(load_word(skb, IP_SRC_OFF));
//
//	bpf_l4_csum_replace(skb, TCP_CSUM_OFF, old_ip, new_ip, IS_PSEUDO | sizeof(new_ip));
//	bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(new_ip));
//	bpf_skb_store_bytes(skb, IP_SRC_OFF, &new_ip, sizeof(new_ip), 0);
//}static inline void set_tcp_dest_port(struct __sk_buff *skb, __u16 new_port)
{ //源码 —— https://github.com/torvalds/linux/blob/master/samples/bpf/tcbpf1_kern.c__u16 old_port = bpf_htons(load_half(skb, TCP_DPORT_OFF));bpf_l4_csum_replace(skb, TCP_CSUM_OFF, old_port, new_port, sizeof(new_port)); //1.修改校验和csumbpf_skb_store_bytes(skb, TCP_DPORT_OFF, &new_port, sizeof(new_port), 0); //2.重新存储到skb
}static inline void set_tcp_src_port(struct __sk_buff *skb, __u16 new_port)
{__u16 old_port = bpf_htons(load_half(skb, TCP_SPORT_OFF));bpf_l4_csum_replace(skb, TCP_CSUM_OFF, old_port, new_port, sizeof(new_port));bpf_skb_store_bytes(skb, TCP_SPORT_OFF, &new_port, sizeof(new_port), 0);
}SEC("classifier") //代表tc的流量分类
int mytc(struct __sk_buff *skb)
{struct iphdr ip;iph_dr(skb, &ip);struct tcphdr tcp;tcph_dr(skb, &tcp);//打包网络数据//如果ip包是tcp协议,才发送数据if(ip.protocol != IPPROTO_TCP){return 0;}//作用:将访问到172.17.0.3:8080重定向到172.17.0.3:80__u16 watch_port = bpf_ntohs(tcp.dest); //目标端口__u32 watch_ip = bpf_ntohl(0xAC110003);  //172.17.0.3if (watch_port == 8080 && ip.daddr == watch_ip) {set_tcp_dest_port(skb, bpf_htons(80)); //修改目标端口 A -> B 8080 -> 80tcph_dr(skb, &tcp); //重新读取skb数据到tcp}//这次修改的是tcp三次握手中第二次也就是服务端响应的端口,否则客户端接收到的源端口与目标端口不一致,会重置请求__u16 src_port = bpf_ntohs(tcp.source); //源端口if (src_port == 80 && ip.saddr == watch_ip) {set_tcp_src_port(skb, bpf_htons(8080)); //修改源端口 B -> A 80 -> 8080tcph_dr(skb, &tcp);}struct tc_data_ip *ipdata;ipdata=bpf_ringbuf_reserve(&tc_ip_map, sizeof(*ipdata), 0); //在ringbuf中预留缓冲区大小if(!ipdata){return 0;}ipdata->sip = bpf_ntohl(ip.saddr); //网络字节序转换为主机字节序 否则转换成xxx.xxx.xxx.xxx后会颠倒ipdata->dip = bpf_ntohl(ip.daddr);ipdata->sport = bpf_ntohs(tcp.source);ipdata->dport = bpf_ntohs(tcp.dest);bpf_ringbuf_submit(ipdata, 0); //提交数据return 0; //代表放行,是action的一种,混合了action和classifer,分类器类型需要指定成direct-action
}

源码+chatgpt,你懂的

4.mydockertc_x86_bpfel.go

// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64package dockerimport ("bytes"_ "embed""fmt""io""github.com/cilium/ebpf"
)// loadMydockertc returns the embedded CollectionSpec for mydockertc.
func loadMydockertc() (*ebpf.CollectionSpec, error) {reader := bytes.NewReader(_MydockertcBytes)spec, err := ebpf.LoadCollectionSpecFromReader(reader)if err != nil {return nil, fmt.Errorf("can't load mydockertc: %w", err)}return spec, err
}// loadMydockertcObjects loads mydockertc and converts it into a struct.
//
// The following types are suitable as obj argument:
//
//	*mydockertcObjects
//	*mydockertcPrograms
//	*mydockertcMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadMydockertcObjects(obj interface{}, opts *ebpf.CollectionOptions) error {spec, err := loadMydockertc()if err != nil {return err}return spec.LoadAndAssign(obj, opts)
}// mydockertcSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type mydockertcSpecs struct {mydockertcProgramSpecsmydockertcMapSpecs
}// mydockertcSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type mydockertcProgramSpecs struct {Mytc *ebpf.ProgramSpec `ebpf:"mytc"`
}// mydockertcMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type mydockertcMapSpecs struct {TcIpMap *ebpf.MapSpec `ebpf:"tc_ip_map"`
}// mydockertcObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadMydockertcObjects or ebpf.CollectionSpec.LoadAndAssign.
type mydockertcObjects struct {mydockertcProgramsmydockertcMaps
}func (o *mydockertcObjects) Close() error {return _MydockertcClose(&o.mydockertcPrograms,&o.mydockertcMaps,)
}// mydockertcMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadMydockertcObjects or ebpf.CollectionSpec.LoadAndAssign.
type mydockertcMaps struct {TcIpMap *ebpf.Map `ebpf:"tc_ip_map"`
}func (m *mydockertcMaps) Close() error {return _MydockertcClose(m.TcIpMap,)
}// mydockertcPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadMydockertcObjects or ebpf.CollectionSpec.LoadAndAssign.
type mydockertcPrograms struct {Mytc *ebpf.Program `ebpf:"mytc"`
}func (p *mydockertcPrograms) Close() error {return _MydockertcClose(p.Mytc,)
}func _MydockertcClose(closers ...io.Closer) error {for _, closer := range closers {if err := closer.Close(); err != nil {return err}}return nil
}// Do not access this directly.
//
//go:embed mydockertc_x86_bpfel.o
var _MydockertcBytes []byte

编译出来的文件,代码都是自动生成的

5.tc_loader.go

package dockerimport ("errors""fmt""github.com/cilium/ebpf/ringbuf""github.com/vishvananda/netlink""goebpf/pkg/helpers/nethelper""golang.org/x/sys/unix""log""os""os/signal""syscall""unsafe"
)type TcDataIp struct { //对应mydockertc.bpf.c中的structSip   uint32Dip   uint32Sport uint32Dport uint32
}// 在目标网卡添加clsact队列,使其成为eBPF监听的对象,来源——cillium源码
func attachIface(linkIndex int, fd int, name string) (deferFuncs []func()) {//2.1初始化队列attrs := netlink.QdiscAttrs{LinkIndex: linkIndex,// 0xffff 表示 “根”或“无父”句柄的队列规则Handle: netlink.MakeHandle(0xffff, 0),Parent: netlink.HANDLE_CLSACT, //eBPF专用 clsact}qdisc := &netlink.GenericQdisc{QdiscAttrs: attrs,QdiscType:  "clsact",}//2.2添加队列 —— 好比执行了 tc qdisc add dev docker0  clsactif err := netlink.QdiscAdd(qdisc); err != nil {log.Fatalln("QdiscAdd err: ", err)}deferFuncs = append(deferFuncs, func() { //监测完删除,否则下次无法创建if err := netlink.QdiscDel(qdisc); err != nil {fmt.Println("QdiscDel err: ", err.Error())}})//3.1初始化 eBPF分类器filterattrs := netlink.FilterAttrs{LinkIndex: linkIndex,Parent:    netlink.HANDLE_MIN_INGRESS | netlink.HANDLE_MIN_EGRESS,Handle:    netlink.MakeHandle(0, 1),Protocol:  unix.ETH_P_ALL, //所有协议Priority:  1,}filter := &netlink.BpfFilter{FilterAttrs:  filterattrs,Fd:           fd,Name:         name,DirectAction: true,}//3.2添加分类器 —— 好比执行了 tc filter add dev docker0 ingress bpf direct-action obj dockertcxdp_bpfel_x86.oif err := netlink.FilterAdd(filter); err != nil {log.Fatalln("FilterAdd err: ", err)}deferFuncs = append(deferFuncs, func() {err := netlink.FilterDel(filter)if err != nil {fmt.Println("FilterDel err : ", err.Error())}})return
}// 加载tc ebpf 程序
func LoaderTC() {veth := nethelper.GetVeths()//1 这步和其他的eBPF程序一样,加载转化过来的eBPF程序objs := &mydockertcObjects{}err := loadMydockertcObjects(objs, nil)if err != nil {log.Fatalln("loadDockertcxdpObjects err: ", err)}//2-3 给所有veth网卡添加clsact队列for _, v := range veth {deferFuncs := attachIface(v.Index, objs.Mytc.FD(), "mytc")for _, f := range deferFuncs {defer f()}}//4开个信号阻塞住并循环读取fmt.Println("开始TC监听")go func() {rd, err := ringbuf.NewReader(objs.TcIpMap)if err != nil {log.Fatalf("creating event reader: %s", err)}defer rd.Close()for { //循环读取内核maprecord, err := rd.Read()if err != nil {if errors.Is(err, ringbuf.ErrClosed) {log.Println("Received signal, exiting..")return}log.Printf("reading from reader: %s", err)continue}//对内核态传来的数据进行解析if len(record.RawSample) > 0 {data := (*TcDataIp)(unsafe.Pointer(&record.RawSample[0])) //经过两次强制转换//转换成网络字节序saddr := nethelper.ResolveIP(data.Sip, true)daddr := nethelper.ResolveIP(data.Dip, true)fmt.Printf("监测到来源地址: %s:%d------->目标地址: %s:%d\n",saddr.To4().String(), data.Sport,daddr.To4().String(), data.Dport,)}}}() //循环读取内核态传来的数据//开信号 好处是能执行deferch := make(chan os.Signal)signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGHUP)<-chfmt.Println("TC监听结束")
}

参考cilium源码

效果展示

拓展提升

其实,我们拿到了整一个数据包,可以进一步获得报文中的Payload,将其发送到用户态。用户态进行逐字节解析,仅需要知道http协议,mysql报文, redis报文的规定格式。即可判断,并通过一系列操作保存,并作审计用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/846090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是TLAB?

这个得从内存申请说起。 一般而言生成对象需要向堆中的新生代申请内存空间&#xff0c;而堆又是全局共享的&#xff0c;像新生代内存又是规整的&#xff0c;是通过一个指针来划分的。 内存是紧凑的&#xff0c;新对象创建指针就右移对象大小size即可&#xff0c;这叫指针加法…

怎么通过互联网远程控制电脑?

远程访问又称为网络远程控制&#xff0c;它使用户能够通过互联网连接两台设备以解决问题。进行控制的电脑称为控制端&#xff0c;被控制的电脑则称为被控端。在远程访问过程中&#xff0c;控制端电脑掌握整个连接的操作。远程控制软件会捕获被控端电脑的操作&#xff0c;并在主…

绘画智能体分享

这是您请求的故宫雪景图&#xff0c;角落有一只可爱的胖猫&#xff0c;采用了水墨画风格&#xff0c;类似于张大千的作品。希望您喜欢这幅画&#xff01; &#x1f3a8; 选项 1【转变风格】——将这幅画转变为梵高的后印象派风格&#xff0c;增添一些梵高特有的笔触和色彩。 &…

Superset二次开发之Github项目推送到GitLab仓库

以下是从GitHub克隆Superset项目并将其推送到GitLab的详细操作步骤 lab 地址: xxx lab 配置: 生成SSH密钥 ssh-keygen -t rsa -b 4096 -C "邮箱地址" 默认情况下密钥会生成在~/.ssh (/c/Users/Administrator/.ssh/id_rsa)目录下。 公钥添加到GitLab: 打开公钥文件…

短时间内如何顺利通过 Java 面试?

今天我们来探讨一个重要的话题&#xff1a;短时间内如何顺利通过 Java 面试&#xff1f; 在此之前&#xff0c;我正在精心编写一套完全面向小白的 Java 自学教程&#xff0c;我相信这套教程会非常适合正在努力提升的你。教程里面涵盖了丰富全面的编程教学内容、详细生动的视频…

上网行为监控软件大盘点:好用的上网行为管理软件一览

这是后台私信来的客户发来的图片&#xff0c;他们老板想要给自己公司安装上网行为监控软件。 可见&#xff0c;企业对员工上网行为的监控与管理已经是很多企业都开始重视的问题。 上网行为管理软件能够帮助企业确保网络资源得到合理分配&#xff0c;提升工作效率&#xff0c;…

ch5链路层和局域网

回顾TCP/IP参考模型&#xff0c;明确链路层和物理层在整个模型中的地位&#xff0c;简要提出链路层要解决的问题是单段链路的数据传输&#xff0c;物理层解决的是数字信号与电气信号之间的相互转换。 链路层概述 节点&#xff1a;主机和路由器(包括网桥和交换机) 链路&#xf…

QT 信号和槽教程,窗体和控件对象之间的沟通一般都使用信号和槽

Qt的信号和槽&#xff08;Signals and Slots&#xff09;机制是一种强大的对象间通信方式&#xff0c;它允许对象在完全解耦的情况下相互通信。以下是关于Qt信号和槽的简明教程&#xff1a; 基本概念 信号&#xff08;Signal&#xff09;&#xff1a;信号是由Qt对象发出的通知…

(2024,LoRA,全量微调,低秩,强正则化,缓解遗忘,多样性)LoRA 学习更少,遗忘更少

LoRA Learns Less and Forgets Less 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 引言 2. 背景 3. 实验设置 3.2 使用编码和数学基准测试来衡量学习&#xff08;目标域…

jpeg编码学习

正点原子stm32教程提到过jpeg解码库libjpeg&#xff0c;但是没有提到jpeg编码&#xff0c;我也好奇jpeg编码怎么实现&#xff0c;用代码怎么生成jpeg文件的。所以最近学习了jpeg编码&#xff0c;在这里做记录。 参考文章 jpeg图片格式详解 https://blog.csdn.net/yun_hen/art…

一文了解-电子公司组织架构

以下仅为一般研发设计公司的岗位&#xff0c; 不包括工厂。 总经理 柏柏有话说: 不多说&#xff0c;人中龙凤、群山俯首、众神膜拜&#xff01; 项目总监 产品总监 软件总监 测试总监 销售总监 人事总监 财务总监 采购总监 柏柏有话说: 这么说吧&#xff0c; 柏柏的前同龄同事…

共计3万字!从零开始创建一个小规模的稳定扩散模型!

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

MYSQL数据库细节详细分析

MYSQL数据库的数据类型(一般只需要用到这些) 整型类型&#xff1a;用于存储整数值&#xff0c;可以选择不同的大小范围来适应特定的整数值。 TINYINTSMALLINTMEDIUMINTINTBIGINT 浮点型类型&#xff1a;用于存储带有小数部分的数值&#xff0c;提供了单精度&#xff08;FLOA…

YOLOv5改进 | 卷积模块 | 将Conv替换为轻量化的GSConv【原理 + 完整代码】

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 目标检测是计算机视觉中一个重要的下游任务。对于边缘盒子的计算平台来说&#xff0c;一个大型模型很难实现实时检测的要求。而且&#xff0…

全志H616 通过Cedrus和v4l2_request API实现硬件编解码加速(香橙派zero2)

编译安装或加载cedrus驱动模块&#xff0c;加载v4l2-mem2mem Sunxi-Cedrus 致力于为全志 SoC 提供硬件加速的视频解码和编码支持&#xff0c;并将其引入主线 Linux 内核。此外&#xff0c;还为典型的基于 GNU/Linux 的系统提供了与内核驱动程序接口的其他用户空间组件。 Sunx…

北邮22级信通院DSP:IIR_DF系统3.0版:从H(p)到H(s):一种更为严谨精确的运算模式

北邮22信通一枚~ 跟随课程进度更新北邮信通院DSP的笔记、代码和文章&#xff0c;欢迎关注~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院DSP_青山入墨雨如画的博客-CSDN博客 承接上一篇博客 北邮22级信通院DSP&#xff1a;IIR_DF系统2.0版&#xff1a;…

用Python优雅地写LaTeX

latexify用于生成 LaTeX 数学公式的 Python 库。LaTeX 是一种基于 ΤΕΧ 的排版系统&#xff0c;对于展示复杂的数学公式表现极为出色。该项目可以用 Python 函数&#xff0c;轻松生成复杂的 LaTeX 数学公式描述。 安装库 查看版本号 0.4.2 案例演示 我们需要以装饰器的形式…

Nginx实战:浏览器缓存

浏览器缓存 浏览器缓存是为了加速浏览&#xff0c;浏览器在用户磁盘上&#xff0c;对最近请求过 的文档进行存储。当访问者再次请求这个页面时&#xff0c;浏览器就可以从 本地磁盘显示文档&#xff0c;这样&#xff0c;就可以加速页面的阅览&#xff0c;缓存的方式节 约了网络…

报表工具DataEase技术方案(二)

一、DataEase报表功能开发流程 1. 创建数据源 2. 创建数据集 可以创建多种来源的数据集&#xff0c;这里以SQL数据集为例。 数据集SQL中可以添加参数&#xff0c;仪表板展示数据时可以根据参数来筛选数据。 数据集添加计算字段 3. 创建仪表板 &#xff08;1&#xff09;组合…

参数设置错误导致的 OOM

参数设置错误导致的 OOM 前言事故分析事故原因事故复盘 前言 2024 年 5 月 10 日 14 时 19 分&#xff0c;C 公司开发人员向 A 公司开发人员反映某开放接口从 2024 年 5 月 10 日 14 时许开始无法访问和使用。该系统为某基础数据接口服务&#xff0c;基于 HTTP 协议进行通信。…