Linux内核超级装备eBPF技术详细研究
Linux内核超级装备eBPF技术详细研究
文章目录
- Linux内核超级装备eBPF技术详细研究
- eBPF定义
- 定义一(ebpf.io的定义)
- 定义二(Cilium的定义)
- eBPF的应用
- 一个采用eBPF跟踪所有TCP连接的例子
- 了解基于eBPF工具bcc
- 基于eBPF的工具`bpftrace`
- Golang的eBPF库
- eBPF程序的种类
- 跟踪程序
- 网络
- 详细分类介绍
- eBPF执行流程
- BPF程序的资源
- 需要注意的一些限制
- BPF程序的spilling/filling
- BPF程序指令
- BPF帮助函数(helper functions)
- 环境准备
- 系统调用探针BPF程序示例
- 编译和运行程序
- 代码分析
- 参考文献
eBPF定义
定义一(ebpf.io的定义)
eBPF (which is no longer an acronym for anything) is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in a privileged context such as the operating system kernel.
定义二(Cilium的定义)
BPF is a highly flexible and efficient virtual machine-like construct in the Linux kernel allowing to execute bytecode at various hook points in a safe manner.
内核大神Brendan用了比喻的说法,更加形象地介绍了这个技术。eBPF对于Linux就相当于JavaScript对于HTML一样。JavaScript给静态的HTML网站带了了动态的内容和效果,已经丰富的交互能力。JavaScript程序运行在一个虚拟机的安全沙盒中。eBPF也对Linxu内核提供了类似的功能,程序员可以通过编写字节码,从而让程序工作在内核的沙盒环境中。eBPF更像是内核中JavaScript的V8虚拟机引擎。直接编写eBPF非常困难,因此一般都会采用bcc,bpftrace这些框架来编写eBPF程序。如今,也可以采用Golang,Rust和Python的框架进行eBPF编程。
eBPF的应用
eBPF是一个非常重要的将内核态功能映射到用户态的技术,最早是用于对内核中的程序进行监控产生的。如今已经被广泛应用于各类需要挂入内核以提供内核监控,以及提高性能的应用中。主要应用于以下几类应用领域:
- Security 通过直接提取和解析内核中socket的数据报文,可以提供新型的安全预警和过滤机制。通过eBPF可以构建一个全面透明和可控的操作系统内核级的安全系统,提高非常高级别的安全防护机制。
- Tracing & Profiling 只是BPF技术最早被发明出来时的使用场景和用途。通过eBPF可以将探针插入内核态以及用户态的应用中去。从而追踪和分析应用程序的运行时信息和状态,提供深度的程序运行情况的洞悉。通过这个技术,我们能够更加深入分析系统的性能,提供调优和缺陷查找能力。
- Networking 结合了可编程性和高性能的特点,eBPF天生适合对网络数据报文进行预处理和分析,能够在内核层级对网络协议和数据进行高效处理和分发,提高网络应用的性能和可靠性。
- Observability & Monitoring 通过eBPF可以不依赖操作系统提供的静态计数器和监控机制。直接获取内核中的定制化的度量指标,并能够通过事件驱动的方式通过广泛的数据来源进行系统监控。
一个采用eBPF跟踪所有TCP连接的例子
tcplife
是bcc
工具中一个专门用来追踪Linux系统中所有TCP连接情况的程序,采用了eBPF技术实现。运行tcplife后的结果如下:
# tcplife
PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS
22597 recordProg 127.0.0.1 46644 127.0.0.1 28527 0 0 0.23
3277 redis-serv 127.0.0.1 28527 127.0.0.1 46644 0 0 0.28
22598 curl 100.66.3.172 61620 52.205.89.26 80 0 1 91.79
22604 curl 100.66.3.172 44400 52.204.43.121 80 0 1 121.38
22624 recordProg 127.0.0.1 46648 127.0.0.1 28527 0 0 0.22
3277 redis-serv 127.0.0.1 28527 127.0.0.1 46648 0 0 0.27
22647 recordProg 127.0.0.1 46650 127.0.0.1 28527 0 0 0.21
3277 redis-serv 127.0.0.1 28527 127.0.0.1 46650 0 0 0.26
[...]
PID是进程ID,发送和接收字节数(TX_KB,RX_KB),持续时间(MS)。这样的程序也可以用一般的内核代码编写,但是如果是这样的话可能永远不可能达到如此的性能和安全性。首先原生内核程序会过滤每一个网络报文,而不是只是TCP协议报文,这就会增加大量额外开销。同时抓取所有的数据包也对安全性产生了一定的隐患。
了解基于eBPF工具bcc
bcc
本身提供了70多个可以直接使用的基于eBPF技术的工具,涵盖了内核层的很多功能和追踪。这些工具如下图所示:这些工具的用法可以通过https://github.com/iovisor/bcc查看
基于eBPF的工具bpftrace
Brendan Gregg提供的bpftrace教程:The bpftrace One-Liner Tutorial。写的非常详细。在这里选取一部分进行解释和学习参考用。
- 查询Probe
bpftrace -l 'tracepoint:syscalls:sys_enter_*'
-l用于根据查询条件列出所有的应用程序探针点(Probes),其中探针点(Probes)是指的一个可以抓取时间数据的测量点
- 插入触发点
bpftrace -e 'BEGIN { printf("hello world\n"); }'
Attaching 1 probe...
hello world
^C
如上面的命令所示,BEGIN就是在程序的前面加入一个probe触发点,可以再此处定义变量或者输出一些文字。这里是输出一个hello world字符串。
- 打开文件
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
snmp-pass /proc/cpuinfo
snmp-pass /proc/stat
snmpd /proc/net/dev
snmpd /proc/net/if_inet6
^C
上面的命令在呼叫系统调用openst
的时候触发追踪事件,将进程名称comm
和系统调用中的文件名参数filename
打印出来。运行后,我们可以看到所有调用了打开文件openat系统调用的程序都被追踪并输出了文件名。除了comm
,我们也可以获得pid
(进程ID)和tid
(线程ID)。
- 统计进程数量
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Attaching 1 probe...
^C
@[bpftrace]: 6
@[systemd]: 24
@[snmp-pass]: 96
@[sshd]: 125
上述代码运行后会显示目前运行的某个进程名称的总数。
@用于设定一个map,@后可以跟一个map名称,这里没有设定名称。中括号[]里面的变量作为map的key值。count()
是一个map的方法,用于统计某个map中某个key出现的次数。
- 输出read()的bytes数分布直方图
bpftrace -e 'tracepoint:syscalls:sys_exit_read /pid == 18644/ { @bytes = hist(args->ret); }'
Attaching 1 probe...
^C@bytes:
[0, 1] 12 |@@@@@@@@@@@@@@@@@@@@ |
[2, 4) 18 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[4, 8) 0 | |
[8, 16) 0 | |
[16, 32) 0 | |
[32, 64) 30 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[64, 128) 19 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[128, 256) 1 |@
/.../
这个是一个过滤器,用于过滤查询条件,限制范围。这里直接设置了进程的ID。通过对read结束时ret参数的统计,来获得最终读取的字节数。然后通过map的直方图方法hist对数据进行统计。除了hist以外,map方法还有前面用过的count()
,lhist()
-线性直方图,sum()
,avg()
,min()
以及max()
。
…剩下的部分可以直接参考原文。
性能火焰图,上述的火焰图广泛用于系统性能评估和调优,也是通过eBPF技术实现的。上述是Mysql的CPU火焰图,显示了各个层级的stack的CPU占用。
Golang的eBPF库
目前基于Go的eBPF框架有Dropbox、Cilium、Aqua和Calico这几个库,通常这些库完成的工作也是将eBPF程序和Map载入内核,通过文件描述符和Map进行关联。并且可以和eBPF Map进行交互(CRUD)操作。不同的库实现形式,使用范围不尽相同。
- Calico用bpftool和IProute实现的CLI做了一个Go的包装
- Aqua实现了对libbpf这个库的Go包装
- Drobox只支持了一部分功能,API比较简洁
- IO Visor开发的gobpf是bcc的Go语言版本。
- Cilium维护了一个纯Go语言的BPF库,将eBPF系统调用抽象为Go接口
eBPF程序的种类
更具《Linux内核观测技术》中的说法,主要更具功能将BPF程序分成两种类型,分别是跟踪和网络。
跟踪程序
这类程序用于更好的了解系统和应用程序当前的状态和行为。主要能够提供实时的系统行为和硬件的直接信息。另一方面可以访问特定应用程序内存区域,跟踪并提取运行进程中的信息。此外,还可以直接访问为每一个进程分配的资源,其中包括文件描述符,CPU和内存等信息。
网络
网络络包追踪和过滤是BPF程序最开始被设计出来时的应用方向。通过BPF我们可以对网络流量数据包处理的各个阶段进行监控和处理,能够在内核层级实现对于数据包的流量控制,安全检查,过滤,甚至可以跳过全部或者部分内核网络协议栈,直接处理网络数据并转发。提供了对于网络底层的强大追踪和控制能力。
上面是根据主要功能类型将BPF程序进行了分类,下面会根据eBPF程序的具体功能更详细的分类介绍
详细分类介绍
- socket过滤器程序
- Kprobe程序
- 跟踪点程序
- XDP程序
- Perf事件程序
- cgroup套接字程序
- cgroup打开套接字程序
- socket选项程序
- socket映射程序
- cgroup设备程序
- socket消息传递程序
- 原始跟踪点程序
- cgroup套接字地址程序
- socket重用端口程序
- 流量解析程序
- 其他BPF程序
全部程序种类列表如下:
enum bpf_prog_type {BPF_PROG_TYPE_UNSPEC,BPF_PROG_TYPE_SOCKET_FILTER,BPF_PROG_TYPE_KPROBE,BPF_PROG_TYPE_SCHED_CLS,BPF_PROG_TYPE_SCHED_ACT,BPF_PROG_TYPE_TRACEPOINT,BPF_PROG_TYPE_XDP,BPF_PROG_TYPE_PERF_EVENT,BPF_PROG_TYPE_CGROUP_SKB,BPF_PROG_TYPE_CGROUP_SOCK,BPF_PROG_TYPE_LWT_IN,BPF_PROG_TYPE_LWT_OUT,BPF_PROG_TYPE_LWT_XMIT,BPF_PROG_TYPE_SOCK_OPS,BPF_PROG_TYPE_SK_SKB,BPF_PROG_TYPE_CGROUP_DEVICE,BPF_PROG_TYPE_SK_MSG,BPF_PROG_TYPE_RAW_TRACEPOINT,BPF_PROG_TYPE_CGROUP_SOCK_ADDR,BPF_PROG_TYPE_LWT_SEG6LOCAL,BPF_PROG_TYPE_LIRC_MODE2,BPF_PROG_TYPE_SK_REUSEPORT,BPF_PROG_TYPE_FLOW_DISSECTOR,BPF_PROG_TYPE_CGROUP_SYSCTL,BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,BPF_PROG_TYPE_CGROUP_SOCKOPT,BPF_PROG_TYPE_TRACING,BPF_PROG_TYPE_STRUCT_OPS,BPF_PROG_TYPE_EXT,BPF_PROG_TYPE_LSM,BPF_PROG_TYPE_SK_LOOKUP,BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
};
eBPF执行流程
上图简要描述了eBPF的执行流程,BPF需要用C语言进行编写,通过LLVM/Clang编译为BPF字节码,运行时通过用户空间中的程序(C/Golang/Rust/Python…)将字节码注入内核态的BPF虚拟机中,首先会通过BPF验证器(Verifier)检查代码的正确性和安全性,通过后才能够给到JIT进行编译,生成本地可以执行代码。这些代码是通过BPF的挂载点和某个内核函数绑定,并在内核函数调用的时候被启动执行。BPF和用户态数据的传递是依靠BPF Maps机制执行的,这个Map支持多种数据类型,通过Map可以将监控数据实时传输到用户态程序,用户空间程序也可以通过Map将运行时的参数等传递给EPF程序。
BPF最神奇之处就是它可以通过通过消息传递的方式,监控内核态程序的状态以及影响和改变内核态程序的执行过程,而不需要修改内核程序本身。这是一个非常强大的工具,因此被称为Linux内核中最神奇的技术,没有之一。
另外一点需要强调的是,BPF程序运行在内核中,而且每次运行必然是事件驱动的。例如:
- 在网络设备或者驱动的数据入口挂载的BPF程序,会在每次有数据包接收时被运行。
- kprobe探针类型的BPF程序会在每次某个挂载函数地址运行时被触发,并运行。
BPF程序的资源
一个eBPF程序可以使用的资源包括11个64位寄存器(可以分成32位子寄存器),一个程序计数器以及一个512byte的BPF栈空间。其中寄存器名称为r0~r10
。其中r10
寄存器是只读寄存器,储存一个帧地址指针,用于访问BPF的栈空间。其余四个寄存器都是通用可读写的寄存器。
BPF程序可以调用内核中预定义的帮助函数,这些帮助函数只会定义在内核(core kernel)中,而不可能存在与任何模块(modules)里。调用帮助函数是各个寄存器功能设定如下:
r0
储存帮助函数的返回值r1~r5
保存帮助函数的调用参数r6~r9
帮助函数预留的寄存器
BPF设计的寄存器配置能够被目前主流的任何CPU架构满足,因此,通常BPF寄存器都是和实际的硬件寄存器一一对应的。JIT只需要处理功能调用指令,而不需要关心参数和返回值的存放地址(总是在固定的寄存器中)。这使得运行BPF帮助函数的性能和效率非常高。不过这也造成帮助函数的限制,那就是不支持6个和6个以上参数。
r0
中保存的返回值定义,会由于BPF程序类型的不同而有区别。在执行BPF程序前,r1
寄存器通常保存的是程序的上下文信息(context),context指的是BPF程序的运行参数(类似C语言中argc/argv运行参数对)。BPF只能有一个context,context是由程序类型决定的。例如网络程序的context是内核网络数据报文包(skb
)作为入参。
BPF程序都是基于64位处理的,主要是为了兼容主流的64位架构以及64位数表示的指针类型。BPF程序支持尾部调用(tail call)用于从一个BPF程序跳转到另外一个BPF程序执行,这种内联上线为33个调用。这个功能通常用于将BPF程序分成不同的执行阶段。
需要注意的一些限制
早期的cBPF程序指令数量限制在4096条,从5.1版内核开始,eBPF程序的指令数量提升到一百万条。内核中的BPF检查器通常会拒绝含有循环操作的字节码,这是由于BPF程序是运行于内核中的,检查器必须要确保内核运行的稳定性和安全性。
BPF程序的spilling/filling
由于寄存器数量的限制,有时候需要将r~r5
寄存器中保存的参数复制到BPF栈空间中,这个操作称为spilling,而将栈空间的参数写回参数寄存器中的操作称为filling。
BPF程序指令
目前为止,BPF拥有87条不同的指令。每条指令都有相同的长度和结构。大数端系统的指令结构如下:
其中off
和imm
都是有符号类型的数据。op定义了实际的操作,dst_reg
和 src_reg
提供了额外的寄存器使用信息,off在某些指令中用于设置地址或者栈空间的偏移量(offset)或者是跳转指令的位置。imm
设置常量或者直接使用的值。op
本身也有不同部分构成,前四为是指令code,一位source标志和三位指令类型代码。
目前BPF支持的系统架构包括:x86_64,
arm64,
ppc64,
s390x,
mips64,
sparc64和arm。不同架构都有专门的内核eBPF JIT编译器。所有的BPF操作都通过bpf()
系统调用完成。包括加载BPF字节码程序到内核以及操作Maps进行数据交互。
BPF帮助函数(helper functions)
内核中定义了很多帮助函数,用于获取/加载数据到内核。不同的BPF程序类型通常只能调用一部分对应的帮助函数。根据使用的参数数量不同,内核中提供了不同的帮助函数的宏定义BPF_CALL_0()~BPF_CALL_5()
。更新Map元素的帮助函数定义如下:
BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key,void *, value, u64, flags)
{WARN_ON_ONCE(!rcu_read_lock_held());return map->ops->map_update_elem(map, key, value, flags);
}const struct bpf_func_proto bpf_map_update_elem_proto = {.func = bpf_map_update_elem,.gpl_only = false,.ret_type = RET_INTEGER,.arg1_type = ARG_CONST_MAP_PTR,.arg2_type = ARG_PTR_TO_MAP_KEY,.arg3_type = ARG_PTR_TO_MAP_VALUE,.arg4_type = ARG_ANYTHING,
};
帮助函数和系统调用类似,函数标准定义如下:
u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)
环境准备
eBPF程序一般有两个部分组成
- 内核中运行的eBPF程序,用C语言编写,编译为内核文件。通常需要使用clang/llvm编译为elf格式的文件。
- Go语言编写的用于加载和测试eBPF的程序,在用户态运行,用于配置和读取eBPF程序数据。
我们需要安装以下一些工具:
最近一些年,由于eBPF的发展比较快,Linux内核的更新速度也非常快,eBPF在不同的内核版本中还是有不少的兼容性和功能上的区别,建议最好是使用比较新的内核版本v5.10以上。我们在编写和测试eBPF程序前应该确认当前的内核版本,笔者的开发系统:
- 安装llvm编译器,clang 9.0以上, 下面的代码是安装llvm和clang
sudo apt update -y
sudo apt install -y llvm
sudo apt install -y clang
安装完整的开发环境如下:
$ sudo apt-get install -y make gcc libssl-dev bc libelf-dev libcap-dev \clang gcc-multilib llvm libncurses5-dev git pkg-config libmnl-dev bison flex \graphviz
系统调用探针BPF程序示例
编译和运行程序
在示例程序中我们采用Cilium开源的ebpf-go库进行程序生成和内核加载。我们可以从https://github.com/cilium/ebpf下载这个项目。实际开发并不需要完整的项目代码,将examples文件夹中kprobe文件夹中的内容拷贝到一个测试用的文件夹中。我将Kprobe探针程序放到~/testbpf/kprobe
这个文件夹中,另外需要将headers文件夹拷贝到~/testbpf
文件夹中,所有c的bpf库调用都要依赖头文件,BPF程序不允许引用任何外部库和模块。我们在kprobe文件夹中只需要保留main.go和kprobe.c这两个文件。首先设置编译工具clang:
export BPF_CLANG=clang
然后运行go mod init,生成mod文件并下载所需的go的ebpf库
go mod init kprobe
生成字节码和辅助的go程序,并编译和执行
$ go generate
$ go build
我们可以看到通过generate我们生成了BPF程序的字节码文件和辅助的go程序文件。编译后生成kprobe程序,运行后,效果如下:
xxxx@ubuntu:~/testbpf/kprobe$ sudo ./kprobe
2022/10/06 15:59:11 Waiting for events..
2022/10/06 15:59:13 sys_execve called 16 times
2022/10/06 15:59:15 sys_execve called 32 times
2022/10/06 15:59:17 sys_execve called 32 times
2022/10/06 15:59:19 sys_execve called 43 times
2022/10/06 15:59:21 sys_execve called 47 times
2022/10/06 15:59:23 sys_execve called 47 times
2022/10/06 15:59:25 sys_execve called 62 times
2022/10/06 15:59:27 sys_execve called 62 times
2022/10/06 15:59:29 sys_execve called 73 times
2022/10/06 15:59:31 sys_execve called 77 times
这个程序就是每两秒显示一次execve系统程序调用的总次数。
代码分析
eBPF程序的代码如下:
// +build ignore#include "common.h"char __license[] SEC("license") = "Dual MIT/GPL";struct bpf_map_def SEC("maps") kprobe_map = {.type = BPF_MAP_TYPE_ARRAY,.key_size = sizeof(u32),.value_size = sizeof(u64),.max_entries = 1,
};SEC("kprobe/sys_execve")
int kprobe_execve() {u32 key = 0;u64 initval = 1, *valp;valp = bpf_map_lookup_elem(&kprobe_map, &key);if (!valp) {bpf_map_update_elem(&kprobe_map, &key, &initval, BPF_ANY);return 0;}__sync_fetch_and_add(valp, 1);return 0;
}
代码主要是创建了一个map对象kprobe_map,用于内核于用户空间程序数据交换,这个map是BPF_MAP_TYPE_ARRAY类型的,最大元素1个。程序非常简单,就是每次系统调用被调用,探针BPF程序就能够触发并将map中的值+1。
main.go程序如下:
// This program demonstrates attaching an eBPF program to a kernel symbol.
// The eBPF program will be attached to the start of the sys_execve
// kernel function and prints out the number of times it has been called
// every second.
package mainimport ("log""time""github.com/cilium/ebpf/link""github.com/cilium/ebpf/rlimit"
)// $BPF_CLANG and $BPF_CFLAGS are set by the Makefile.
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf kprobe.c -- -I../headersconst mapKey uint32 = 0func main() {// Name of the kernel function to trace.fn := "sys_execve"//fn := "sys_bind"// Allow the current process to lock memory for eBPF resources.if err := rlimit.RemoveMemlock(); err != nil {log.Fatal(err)}// Load pre-compiled programs and maps into the kernel.objs := bpfObjects{}if err := loadBpfObjects(&objs, nil); err != nil {log.Fatalf("loading objects: %v", err)}defer objs.Close()// Open a Kprobe at the entry point of the kernel function and attach the// pre-compiled program. Each time the kernel function enters, the program// will increment the execution counter by 1. The read loop below polls this// map value once per second.kp, err := link.Kprobe(fn, objs.KprobeExecve, nil)if err != nil {log.Fatalf("opening kprobe: %s", err)}defer kp.Close()// Read loop reporting the total amount of times the kernel// function was entered, once per second.ticker := time.NewTicker(2 * time.Second)defer ticker.Stop()log.Println("Waiting for events..")for range ticker.C {var value uint64if err := objs.KprobeMap.Lookup(mapKey, &value); err != nil {log.Fatalf("reading map: %v", err)}log.Printf("%s called %d times\n", fn, value)}
}
参考文献
- eBPF Documentation
- BPF and XDP Reference Guide
- Learn eBPF Tracing: Tutorial and Examples
- BPF helper functions
Linux内核超级装备eBPF技术详细研究相关推荐
- Linux内核UDP性能优化(超详细讲解)
现在很多人都在诟病Linux内核协议栈收包效率低,不管他们是真的懂还是一点都不懂只是听别人说的,反正就是在一味地怼Linux内核协议栈,他们的武器貌似只有DPDK. 但是,即便Linux内核协议栈收包 ...
- Linux内核架构:CPU架构详细介绍(图例解析)
一. 概述 CPU架构是CPU商给CPU产品定的一个规范,主要目的是为了区分不同类型的CPU.目前市场上的CPU分类主要分有两大阵营,一个是intel.AMD为首的复杂指令集CPU,另一个是以IBM. ...
- linux内核bug问题排查过程详细报告
Linux Kernel BUG:soft lockup CPU#1 stuck分析 1.线上内核bug日志 kernel: Deltaway too big! 1842872967520006986 ...
- 用Linux内核的瑞士军刀-eBPF实现socket转发offload
我们已经对eBPF将网络转发offload到XDP(eXpress Data Path)耳熟能详,作为Linux内核的一把 "瑞士军刀" ,eBPF能做的事情可不止一件,它是一个多 ...
- linux内核如何修改lowmem,技术内幕:Android对Linux内核的增强 Low Memory Killer
6 09 2013 技术内幕:Android对Linux内核的增强 Low Memory Killer Low Memory Killer(低内存管理) 对于PC来说,内存是 至关重要.如果某个程序发 ...
- Linux内核Kernel panic常见问题(详细)总结
目录 一.简介 1.1 hard panic 1.2 soft panic 二.常见问题 2.1 源码分析 2.2 硬件问题 2.3 系统过热 2.4 文件系统引起 2.5 内核更新 2.6 处理pa ...
- linux内核实时调度,基于Linux内核的实时调度机制的研究和实现
摘要: 实时操作系统在当前的各个领域得到广泛应用,越来越引起人们的重视.Linux操作系统的源代码开放.内核模块化设计及内核的高度可裁减性使其在嵌入式实时操作系统研究领域备受重视.但其面向通用多任务分 ...
- Liunx树莓派(ARM)开发篇—第十四章、树莓派Linux内核编译步骤(超详细、图文结合)
对树莓派内核源码进行配置后,下一步要进行树莓派Linux内核编译 以下步骤均在PC端虚拟机上完成 准备材料:PC端虚拟机.树莓派交叉编译工具.树莓派内核(交叉编译工具.内核下载步骤见树莓派内核源码) ...
- Linux内核中断系统处理机制-详细分析
原文地址::https://blog.csdn.net/weixin_42092278/article/details/81989449 相关文章 1.Linux中断管理 (1)Linux中断管理机制 ...
最新文章
- 为什么DL模型能够正确分类?SCOUTER(ICCV21')从“正”“反”方面说服你。
- Java并发编程之CAS
- shadow fight 1.6.0 内购
- document.getElementById vs jQuery $()
- Java网络编程从入门到精通(1):Internet地址概述
- 360金融产品总监赵鑫:互金产品经理应该关心这些东西
- php 二维数组 根据某个字段排序
- Oracle11g数据库监听配置
- 果粉失望!iPhone 12系列依旧刘海屏,将升级Face ID元件
- 在职工象棋赛上弃子拿下一盘
- CHIL-SQL-FOREIGN KEY 约束
- 【图像修复】基于matlab GUI三维图像复原【含Matlab源码 963期】
- Oracle数据库出现“本地计算机上的OracleOraDB11g_homeTNSListener服务启动后停止.....”问题解决方案...
- oracle财务数据权限思考
- IPC$局域网入侵详解
- Excel公式大全Excel公式大全-入门
- 北上资金 python_股票数据抓取——北上基金持股数据(selenium抓取数据),爬取,之,北向,资金,通过...
- Gitlab CI/CD 中使用 ssh-key,登录远程主机部署,执行远程主机的特定脚本
- 推荐系统实战(5)——基于内容的推荐算法(CB)
- JAVA复健篇01_基本概念
热门文章
- unity打包游戏后物体的移动速度不一样?
- 搜索引擎下拉菜单 提示关键词 接口API (百度长尾词 淘宝长尾词)
- P2P平台方案——亿网软通“互联网+”金融解决方案
- Git系列之修改历史提交信息
- 看板方法:向交警蜀黍学习怎么做软件
- 校招产品经理面经篇四
- 熊猫的python小课_朋友圈里那个可爱的小熊猫Python编程的学习笔记,学编程,不难!...
- 芯片设计流程及各步骤使用工具简介
- 未来已来——工作空间 WorkSpace 和物联网 IoT (2)
- 请问mysql 中的ZEROFILL是什么意思