分类 Linux 相关 下的文章

Linux kernel 的编译选项(flags)

最近在一个树莓派上实验 BPF 工具, 首先安装了 BCC 和 bpftrace. 不过安装之后有些不能用. 根据官方安装文档, 要使用这些工具, Linux kernel 在编译的时候, 需要打开某些 flags. 根据文档, 这些 flags 在这些地方:

Kernel compile flags can usually be checked by looking at
/proc/config.gz or /boot/config-<kernel-version>.

但是我的树莓派却没有这些文件:

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 5.15.32-v7+ #1538 SMP Thu Mar 31 19:38:48 BST 2022 armv7l GNU/Linux
pi@raspberrypi:~ $ cat /etc/issue
Raspbian GNU/Linux 11 \n \l

pi@raspberrypi:~ $ ls -lah /proc/config.gz
ls: cannot access '/proc/config.gz': No such file or directory
pi@raspberrypi:~ $ ls -lah /boot/* | grep config
-rwxr-xr-x 1 root root 1.7K Oct  4  2018 /boot/config.txt
pi@raspberrypi:~ $ cat /boot/config.txt | grep BPF

可以看到, 我最新版本的 Raspbian 里面没有 /proc/config.gz 文件. 虽然能在 /boot/ 目录看到一个 config.txt, 却没有找到 BPF 相关的配置.

于是去查看 proc 伪文件系统的 man 文档, 可以看到说 /proc/config.gz 其实是由 /lib/modules/$(uname -r)/build/.config 复制过来的. 于是去查看这个原始文件:

pi@raspberrypi:~ $ cat /lib/modules/$(uname -r)/build/.config | grep BPF
CONFIG_BPF=y
CONFIG_HAVE_EBPF_JIT=y
# BPF subsystem
CONFIG_BPF_SYSCALL=y
# CONFIG_BPF_JIT is not set
# CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set
# CONFIG_BPF_PRELOAD is not set
# end of BPF subsystem
CONFIG_CGROUP_BPF=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
# CONFIG_BPFILTER is not set
# CONFIG_NET_CLS_BPF is not set
# CONFIG_NET_ACT_BPF is not set
# CONFIG_BPF_STREAM_PARSER is not set
CONFIG_LWTUNNEL_BPF=y
CONFIG_BPF_LIRC_MODE2=y
# CONFIG_NBPFAXI_DMA is not set
CONFIG_BPF_EVENTS=y
# CONFIG_TEST_BPF is not set

所以, 其实这些 kernel 的编译配置都是在存在这个文件的.

gprof

gprof 是什么?

gprof 是一个用来分析应用程序把时间花费在哪的性能分析工具.

gprof 怎么用?

  1. 编译和链接程序使用 -pg 选项;
  2. 执行程序, 产生 gmon.out 数据文件;
  3. 使用 gprof 读取 gmon.out 数据文件产生报告;
//使用 **-pg** 参数编译你的程序
pi@raspberrypi:~ $ echo 'int main(){}' | gcc -o hello -xc -pg -O3 -
pi@raspberrypi:~ $ ls 
hello
pi@raspberrypi:~ $ ./hello
pi@raspberrypi:~ $ ls -lah 
gmon.out  hello

pi@raspberrypi:~ $ which gprof
/usr/bin/gprof

pi@raspberrypi:~ $ gprof hello gmon.out

gprof - how it works?

要想使用 gprof 做性能分析, 必须在编译程序的时候注入一些代码, 这些代码在运行时搜集代码的执行情况, 把搜集的数据放到当前路径下的 gmon.out 文件. 然后 gprof 读这个文件, 生成 report.
实现细节: https://sourceware.org/binutils/docs/gprof/Implementation.html
使用 -pg 选项编译的时候, 每个函数的开头都条用 mcount (monitor count). mcount 在不同的平台实现不同, 不过代码都在 profiling 的 lib 里面, 它负责记录并维护这些 call graph 的数据: 包括当前函数以及谁 call 的当前函数. 一般当前函数以及父函数都是通过遍历栈帧的方式获得的.

The mcount routine, included in the profiling library, is responsible for recording in an in-memory call graph table both its parent routine (the child) and its parent's parent. This is typically done by examining the stack frame to find both the address of the child, and the return address in the original parent. Since this is a very machine-dependant operation, mcount itself is typically a short assembly-language stub routine that extracts the required information, and then calls __mcount_internal (a normal C function) with two arguments - frompc and selfpc. __mcount_internal is responsible for maintaining the in-memory call graph, which records frompc, selfpc, and the number of times each of these call arcs was transversed.

库函数的调用记录是通过特殊的库版本实现的, 通常这些库都是使用-pg选项生成的.

为什么是 pg ?

在 gprof 之前, 有个性能分析程序是 prof, 要使用 prof 这个工具, gcc 给的参数是 -p, 那么轮到 gprof 的时候, 就使用参数 -pg

pg 之外的其它编译参数

-g line-by-line profiling
-a if 的每个分支, loop 的每次循环

refer:
https://hpc.llnl.gov/software/development-environment-software/gprof
https://en.wikipedia.org/wiki/Gprof

关于 Linux poll 的 timeout 参数的解释

最近研究 JDK 和 Netty 里面 socket timeout 的实现, 代码层面从 Java 代码一直追踪到 Linux 上的系统调用 poll. 当查看 poll 的手册的时候, 注意到一个不正常的描述.

首先, 通过 google 找到的是这个手册: https://linux.die.net/man/2/poll. 这个手册里面有关于 poll 的 timeout 参数的描述是:

The timeout argument specifies the minimum number of milliseconds that poll() will block.

注意, 它使用的是 "minimum", 那么对于正数的 timeout 值, 我可以有这些可能的理解:

  1. 不管有没有感兴趣的事件发生, 我最少必须等 timeout 的毫秒数;
  2. 在 timeout 值的时间窗口内, 有感兴趣的事情发生, 就直接返回, 若没有, 最少等 minimum 毫秒数;
    那么, 对于以上不管哪种理解, 最少的等待时间是 timeout 毫秒数, 那么最大的等待时间呢? 如果在 timeout 毫秒数内么有感兴趣的事件发生, 要等多久呢?

怀着这个问题, 我又返回去看 JDK 里的代码:
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/windows/native/java/net/SocketInputStream.c#l101
还是无法理解.

于是重新 google 搜索, 找到了这个问答:
https://stackoverflow.com/questions/529975/what-does-poll-do-with-a-timeout-of-0
pollMan.png
回答里面明显表明这是最大等待时间, 于是重新找另外的 Linux man page:
https://man7.org/linux/man-pages/man2/poll.2.html

The timeout argument specifies the number of milliseconds that poll() should block waiting for a file descriptor to become ready.

这里的意思, 就没有任何歧义了, 就是最大等待时间.