我们可以使用ftrace来把内核调试,但是我们发现一个问题就是,我需要反复的echo这echo那,为了避免这类问题,linux提供了trace-cmd工具,其目的是方便ftrace的使用,仅此而已。
首先我们安装trace-cmd
apt install trace-cmd
然后我们可以看看trace-cmd可以获取哪些信息:
# trace-cmd list -h trace-cmd version 2.8.3 usage: trace-cmd list [-e [regex]][-t][-o][-f [regex]] -e list available events -F show event format -R show event triggers -l show event filters -t list available tracers -o list available options -f [regex] list available functions to filter on -P list loaded plugin files (by path) -O list plugin options -B list defined buffer instances -C list the defined clocks (and active one)
我们看看record支持哪些参数
# trace-cmd record -h trace-cmd version 2.8.3 usage: trace-cmd record [-v][-e event [-f filter]][-p plugin][-F][-d][-D][-o file] \ [-q][-s usecs][-O option ][-l func][-g func][-n func] \ [-P pid][-N host:port][-t][-r prio][-b size][-B buf][command ...] [-m max][-C clock] -e run command with event enabled -f filter for previous -e event -R trigger for previous -e event -p run command with plugin enabled -F filter only on the given process -P trace the given pid like -F for the command -c also trace the children of -F (or -P if kernel supports it) -C set the trace clock -T do a stacktrace on all events -l filter function name -g set graph function -n do not trace function -m max size per CPU in kilobytes -M set CPU mask to trace -v will negate all -e after it (disable those events) -d disable function tracer when running -D Full disable of function tracing (for all users) -o data output file [default trace.dat] -O option to enable (or disable) -r real time priority to run the capture threads -s sleep interval between recording (in usecs) [default: 1000] -S used with --profile, to enable only events in command line -N host:port to connect to (see listen) -t used with -N, forces use of tcp in live trace -b change kernel buffersize (in kilobytes per CPU) -B create sub buffer and following events will be enabled here -k do not reset the buffers after tracing. -i do not fail if an event is not found -q print no output to the screen --quiet print no output to the screen --module filter module name --by-comm used with --profile, merge events for related comms --profile enable tracing options needed for report --profile --func-stack perform a stack trace for function tracer (use with caution) --max-graph-depth limit function_graph depth --no-filter include trace-cmd threads in the trace
我相信对于了解ftrace的人来说,这些就无需解释了
我们可以把trace-cmd的使用习惯和perf对齐。如下讲解一下trace-cmd调试function trace和function graph trace,还是以usb_submit_urb为例。
trace-cmd record -p function -l usb_submit_urb --func-stack 此时我们会在本地记录trace.dat文件,我们通过report查看即可
# trace-cmd report | less CPU 1 is empty CPU 2 is empty CPU 3 is empty CPU 4 is empty CPU 5 is empty CPU 6 is empty CPU 7 is empty cpus=8 <idle>-0 [000] 569.464380: function: usb_submit_urb <idle>-0 [000] 569.464385: kernel_stack: <stack trace> => usb_submit_urb (ffffffc008d28b28) => hid_irq_in (ffffffc0090770e4) => __usb_hcd_giveback_urb (ffffffc008d26338) => usb_giveback_urb_bh (ffffffc008d264ac) => tasklet_action_common.isra.0 (ffffffc00808f020) => tasklet_hi_action (ffffffc00808f33c) => __do_softirq (ffffffc0080104b0) => irq_exit (ffffffc00808ee40) => __handle_domain_irq (ffffffc0080fea0c) => gic_handle_irq (ffffffc008010128) => el1_irq (ffffffc008011c08) => cpuidle_enter_state (ffffffc008ff95dc) => cpuidle_enter (ffffffc008ff9984) => call_cpuidle (ffffffc0080c9b18) => do_idle (ffffffc0080c9e18) => cpu_startup_entry (ffffffc0080ca040) => rest_init (ffffffc0095071d0) => arch_call_rest_init (ffffffc009ed0a34) => start_kernel (ffffffc009ed0f94) <idle>-0 [000] 569.468361: function: usb_submit_urb <idle>-0 [000] 569.468365: kernel_stack: <stack trace>
trace-cmd record -p function_graph -g usb_submit_urb 此时为了能够查看括号的注释,我们report如下:
# trace-cmd report -O tailprint CPU 1 is empty CPU 2 is empty CPU 3 is empty CPU 4 is empty CPU 5 is empty CPU 6 is empty CPU 7 is empty cpus=8 <idle>-0 [000] 1094.765032: funcgraph_entry: | finish_task_switch() { <idle>-0 [000] 1094.765034: funcgraph_entry: | _raw_spin_unlock_irq() { <idle>-0 [000] 1094.765035: funcgraph_entry: 0.584 us | do_raw_spin_unlock(); <idle>-0 [000] 1094.765037: funcgraph_exit: 2.334 us | } /* _raw_spin_unlock_irq */ <idle>-0 [000] 1094.765037: funcgraph_exit: 6.417 us | } /* finish_task_switch */ <idle>-0 [000] 1102.416579: funcgraph_entry: | usb_submit_urb() { <idle>-0 [000] 1102.416584: funcgraph_entry: | usb_hcd_submit_urb() { <idle>-0 [000] 1102.416585: funcgraph_entry: | usb_get_urb() { <idle>-0 [000] 1102.416586: funcgraph_entry: 0.875 us | usb_get_urb.part.0(); <idle>-0 [000] 1102.416587: funcgraph_exit: 2.917 us | } /* usb_get_urb */
根据上面的文章,我们知道了EulerMaker(一)功能简介和EulerMaker(二)界面介绍,从功能上,我们知道了它是改进了标准发行版的系统构建工具,取yocto的分层思想而诞生的一个系统构建平台,从界面上,我们可以简单的点点就能完成软件包的构建和操作系统镜像的定制。本文主要根据其日志步骤来分析其基本原理
系统构建有两条路,以自顶向下和自底向上,之前我解释过,如下:
自顶向下是描述操作系统默认是一个单独的发行版本,对于不同的需求,通过增加或移除软件包的方式来定制它。可以理解为减法 自底向上是描述一个操作系统版本,是根据其根本需求来从零逐步增加软件包来实现一个系统版本。可以理解为加法
可以知道,如果我们一味的选择自顶向下构建,那么我们需要具备一个全功能的操作系统,然后按照一个包一个包修改定制的方式来定制,类似于现在常见的发行版本,如debian,ubuntu,suse等。但它的缺点是定制能力不强,只能提供一个客户大而全的系统,而不是适用于客户业务场景的系统
而自底向上构建,我们需要针对每个源码,每个细节进行构建,然后整合成一个操作系统,类似于gentoo,yocto,buildroot等。它的缺点显而易见,从源码构建太慢,优点也十分突出,具备高定制能力,能够提供适用于客户场景的系统
目前来看,openeuler意识到了自顶向下的方式不太适用于当前市场,无论是服务器,桌面,手机,平板,电脑,车载还是嵌入式其他设备,都需要适合其场景的操作系统,而不是一个操作系统版本用在各个行业,所以openeuler提供了eulermaker来借鉴yocto的思维,实现自底向上构建,也就是俗称的:“搭积木的方式构建系统”
当前市场来看,所有受欢迎的电子设备产品,都是自底向上构建的,例如,安卓,苹果,特斯拉,鸿蒙,欧拉等,其因为良好的适配性,带来良好的用户体验,从而占领着各个软件行业市场。
根据构建日志,我们可以分析其构建的原理
2024-10-09 17:05:42 starting DOCKER
根据此,我们可以知道EulerMaker默认在docker中构建
2024-10-09 17:06:19 RESULT_ROOT=/result/rpmbuild/2024-10-09/dc-64g/openeuler-24.03-lts-aarch64/aarch64-common-gitee.com-src-openeuler-bi/cbs.6536572
根据此,我们可以知道EulerMaker使用rpmbuild来执行软件包的构建
2024-10-09 17:06:48 Running transaction check 2024-10-09 17:06:48 Transaction check succeeded. 2024-10-09 17:06:48 Running transaction test 2024-10-09 17:06:48 Transaction test succeeded. 2024-10-09 17:06:48 Running transaction
根据此,我们可以知道其调用了yum/dnf来进行依赖拉包解决
2024-10-09 17:07:07 Complete! 2024-10-09 17:07:07 Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.C1k8v5 2024-10-09 17:07:07 + umask 022 2024-10-09 17:07:07 + cd /home/lkp/rpmbuild/BUILD 2024-10-09 17:07:07 + cd /home/lkp/rpmbuild/BUILD 2024-10-09 17:07:07 + rm -rf binutils-2.41 2024-10-09 17:07:07 + /usr/lib/rpm/rpmuncompress -x /home/lkp/rpmbuild/SOURCES/binutils-2.41.tar.xz
根据此,我们可以大概率知道正在根据spec来运行构建操作,根据上面知道的,构建操作是通过rpmbuild来触发的。那么这个spec和文件如何获取呢?我们留意如下仓库:
https://gitee.com/src-openeuler/binutils
文件如下:
可以发现,这里存放了spec文件,源码tar.xz,补丁文件和服务于EulerMaker的binutils.yaml文件
至此,我们可以知道,EulerMaker的构建工程是通过创建了一个docker环境,docker环境内部根据https://gitee.com/src-openeuler
指定软件名的yaml文件来触发,通过spec文件来实施rpmbuild构建rpm包的
根据构建日志,我们可以分析其构建的原理
2024-09-26 09:35:08 starting DOCKER
这里还是使用docker
2024-09-26 09:35:47 + . /lkp/lkp/src/lib/chroot_libs.sh 2024-09-26 09:35:47 + trap restore_local_repo EXIT 2024-09-26 09:35:47 + make_ebs_iso 2024-09-26 09:35:47 ++ uname -m 2024-09-26 09:35:47 + ARCH=aarch64 2024-09-26 09:35:47 + '[' aarch64 '!=' aarch64 ']' 2024-09-26 09:35:47 + '[' '!' -n iso_normal ']' 2024-09-26 09:35:47 + '[' iso_normal == iso ']' 2024-09-26 09:35:47 + '[' iso_normal == embedded ']' 2024-09-26 09:35:47 + '[' '!' -n 2203sp2iso ']' 2024-09-26 09:35:47 + '[' '!' -n 1 ']' 2024-09-26 09:35:47 + '[' '!' -n https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS-SP2/everything/aarch64/ ']' 2024-09-26 09:35:47 + yum install -y util-linux lsof
这里通过猜测可以发现和构建版本相关,make_ebs_iso是一个shell函数,描述iso的构建方式
2024-09-26 09:35:48 + backup_local_repo 2024-09-26 09:35:48 + '[' -d /etc/yum.repos.d ']' 2024-09-26 09:35:48 + rm -rf /etc/yum.repos.d.bak 2024-09-26 09:35:48 + mv /etc/yum.repos.d /etc/yum.repos.d.bak 2024-09-26 09:35:48 + config_ebs_repo 2024-09-26 09:35:48 + export yum_conf=/tmp/ebs-repo.conf 2024-09-26 09:35:48 + yum_conf=/tmp/ebs-repo.conf 2024-09-26 09:35:48 + rm -rf /tmp/ebs-repo.conf 2024-09-26 09:35:48 + touch /tmp/ebs-repo.conf 2024-09-26 09:35:48 + i=1 2024-09-26 09:35:48 + for repo in ${repo_url[@]} 2024-09-26 09:35:48 + cat 2024-09-26 09:35:48 + let i=i+1 2024-09-26 09:35:48 + '[' iso_normal == embedded ']' 2024-09-26 09:35:48 + '[' iso_normal == iso_normal ']' 2024-09-26 09:35:48 + chroot_run 'cd /lkp/lkp/src/tests/; bash make_iso_image.sh' 2024-09-26 09:35:48 + chroot_init 2024-09-26 09:35:48 + root_path=/usr1/openeuler
这里佐证了上面的猜测,在docker中实施了chroot命令,类似于运行chroot -c make_iso_image.sh
2024-09-26 09:36:33 Total 4.0 MB/s | 157 MB 00:38 2024-09-26 09:36:33 Running transaction check 2024-09-26 09:36:34 Transaction check succeeded. 2024-09-26 09:36:34 Running transaction test 2024-09-26 09:36:34 Transaction test succeeded. 2024-09-26 09:36:34 Running transaction
这里已经在chroot中通过yum安装二进制软件包了
2024-09-26 09:37:56 + pkgs='edk2-aarch64 2024-09-26 09:37:56 grub2-efi-aa64 2024-09-26 09:37:56 grub2-efi-aa64-modules 2024-09-26 09:37:56 kae-driver 2024-09-26 09:37:56 kae-openssl 2024-09-26 09:37:56 kae-zip 2024-09-26 09:37:56 uadk_engine 2024-09-26 09:37:56 libwd 2024-09-26 09:37:56 shim-aa64'
这里已经在制作grub2了
2024-09-26 09:48:46 + yumdownloader --resolve --installroot=/result/tmp/tmp --destdir=/result/tmp/iso/Packages/ yum.noarch xfsprogs.aarch64 wget.aarch64 vim-minimal.aarch64 util-linux.aarch64 uadk_engine.aarch64 tuned.noarch tmux.aarch64 systemd.aarch64 sysfsutils.aarch64 sudo.aarch64 sssd.aarch64 shim-aa64 shadow.aarch64 sg3_utils.aarch64 setup.noarch selinux-policy-targeted.noarch selinux-policy-mls.noarch security-tool.aarch64 rsyslog.aarch64 rpm.aarch64 rootfiles.noarch rng-tools.aarch64 realmd.aarch64 rdma-core.aarch64 procps-ng.aarch64 policycoreutils.aarch64 passwd.aarch64 parted.aarch64 openssh-server.aarch64 openssh-clients.aarch64 openssh.aarch64 openEuler-release.aarch64 openEuler-latest-release.aarch64 ncurses.aarch64 mdadm.aarch64 man-db.aarch64 lvm2.aarch64 lsscsi.aarch64 lshw.aarch64 lorax.aarch64 linux-firmware.noarch libwd.aarch64 libteam.aarch64 less.aarch64 kexec-tools.aarch64 kernel-tools.aarch64 kernel.aarch64 kdump-anaconda-addon.noarch kbd.aarch64 iscsi-initiator-utils irqbalance.aarch64 iputils.aarch64 iprutils.aarch64 iproute.aarch64 initscripts.aarch64 hostname.aarch64 grubby.aarch64 grub2-tools-extra.aarch64 grub2-tools.aarch64 grub2-efi-aa64-modules.noarch grub2-efi-aa64-cdboot.aarch64 grub2-efi-aa64.aarch64 glibc-all-langpacks.aarch64 glibc.aarch64 gfs2-utils.aarch64 firewalld.noarch filesystem.aarch64 fcoe-utils.aarch64 efibootmgr.aarch64 edk2-aarch64.noarch e2fsprogs.aarch64 dracut-network.aarch64 dracut-config-rescue.aarch64 dracut-config-generic.aarch64 dosfstools.aarch64 dnf-plugins-core.noarch dnf.noarch device-mapper-multipath curl.aarch64 cryptsetup.aarch64 cronie.aarch64 coreutils.aarch64 chrony.aarch64 bash.aarch64 basesystem.noarch authselect-compat.aarch64 authselect.aarch64 audit.aarch64 NetworkManager-config-server.noarch NetworkManager.aarch64 2024-09-26 09:48:47 Unable to detect release version (use '--releasever' to specify release version)
这里通过yumdownloader 批量拉包安装
2024-09-26 09:50:42 /opt/oemaker 2024-09-26 09:50:42 + cp /result/tmp/iso/GPG_tmp/etc/pki/rpm-gpg/RPM-GPG-KEY-openEuler /result/tmp/iso 2024-09-26 09:50:42 + rm -rf /result/tmp/iso/GPG_tmp 2024-09-26 09:50:42 + '[' 0 -ne 0 ']' 2024-09-26 09:50:42 + echo 'Waiting for lorax to finish...' 2024-09-26 09:50:42 + '[' standard == debug ']' 2024-09-26 09:50:42 + '[' standard == standard ']' 2024-09-26 09:50:42 + gen_standard_iso 2024-09-26 09:50:42 Waiting for lorax to finish... 2024-09-26 09:50:42 + '[' aarch64 == x86_64 ']' 2024-09-26 09:50:42 + '[' aarch64 == aarch64 ']' 2024-09-26 09:50:42 + mkisofs -R -J -T -r -l -d -joliet-long -allow-multidot -allow-leading-dots -no-bak -V 2203sp2iso-1-1-aarch64 -o /result/2203sp2iso-1-1-aarch64-dvd.iso -e images/efiboot.img -no-emul-boot /result/tmp/iso 2024-09-26 09:50:42 Warning: creating filesystem that does not conform to ISO-9660. 2024-09-26 09:50:42 Size of boot image is 14688 sectors -> No emulation
这里我们知道,通过oemaker封装了mkisofs来生成iso的镜像
至此,我们可以知道,EulerMaker的镜像定制是通过创建了一个docker环境,docker环境内部沿用了常用的构建工具例如livebuild/koji,创建了chroot环境,在chroot中进行yum安装软件包,然后通过oemaker来制作iso镜像文件
虽然我们上面可以发现EulerMaker好像和koji等其他构建平台相差无几,但是我们需要知道的是,分层是一种思想,也就意味着,在触发版本构建时,分层可以在网页中实施,所以我们日志分析不出来的。
不过我们需要明确知道的是,EulerMaker确实借鉴了yocto的分层思想,从而使得构建系统版本能够以搭积木的方式实施,而不是在原有的发行版本上增加和减少软件包。
根据上面通过日志分析原理来看,我们可以发现,eulermaker通过封装了rpmbuild来实现了源码构建工程,在网页上通过设置的方式实现了分层概念,对于镜像定制,底层使用了oemaker来制作iso。其设计思路等价于如下:
koji+yocto分层思想
这样的设计是基于原有构建方案的优化,极大的改善了系统的碎片化问题,同时也降低了使用者的使用门槛,是真正意义上的全场景OS制作工具。
对于eulermaker的使用方法,推荐可以参考:https://docs.openeuler.org/zh/docs/24.03_LTS/docs/EulerMaker/EulerMaker%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97.html
,本文根据官方文章,对eulermaker进行了使用,提供使用说明
对于第一次使用eulermaker的同学,需要先进行注册,如下:
https://eulermaker.compass-ci.openeuler.openatom.cn
这里可以看到,右上角有名字是tangfeng的账号已经登录,而下面存在构建工程/镜像定制两大功能
这里需要设置软件包名字,描述,git地址,分支。这里注意的是,需要与https://gitee.com/organizations/src-openeuler/projects
的工程名字匹配,如下为示例
这里需要配置构建目标,软件源,分层url.
先进入软件包详情,点击构建历史
此时我们点击jobID 可以看到任务日志
等待构建完成之后,可以点击下载看到生成的rpm包
此时选择嵌入式场景
设置镜像定制,流水线,格式,架构,如下:
点击定制业务包,设置repo地址,rpm,驱动,命令,库,分区,参数等等
点击构建历史,点击查看日志
等等系统构建完成之后,点击下载镜像
至此,我们通过openeuler发布的eulermaker网页端完成了其构建工程和镜像定制的两大功能项。可以发现,相比于任何的livebuild/koji工具还是yocto,这种方式操作简单,只要用户懂得操作系统构建的基本知识就可以完成一个操作系统的构建,一个软件包工程的编译。
openeuler推出了一个支持全场景的操作系统构建方案,这个方案名字为EulerMaker,鉴于最近对yocto的研究,发现yocto能够良好的实现全场景操作系统,故本文开始研究EulerMaker
EulerMaker是一款软件包构建平台,完成源码到二进制软件包及系统镜像的构建,支持开发者通过搭积木方式,组装和定制出适合自己需求的场景化OS。主要提供增量/全量构建,软件包分层定制与镜像定制的能力。对于EulerMaker的意义,如下图可以图示
根据图片我们可以知道,EulerMaker旨在通过一个构建平台,完成基于OpenEuler系列的所有形态的操作系统构建,包括服务器,桌面,嵌入式等。
我们可以知道,对于标准的发行版系统,都具备其自身的构建方式,例如debian系列,可以通过livebuild来进行构建。但是这样的工具并不灵活,我们需要一个类似于yocto一样灵活的构建工具。对此,吴峰光博士有如下言论
“服务器领域的 OBS 主打能力是什么?几大主流的 Linux 发行版它都支持,比如可以给 Redhat 打包,也可以给 Debian 打包。兼容并包是它的核心设计目标,适应了 Linux 多样化的现状。 但我们认为,多样化在早期对 Linux 发展有利,但长期而言,Linux 生态的碎片化是一个需要被解决的问题。” “嵌入式领域的 Bitbake 采用了面向任务和过程的 DSL 描述语言,这使得它非常灵活强大,但自由度和复杂性过高,以学习曲线陡峭知名。 现在流行的理念是如 YAML、JSON 等通用、声明式的配置语言,和函数式编程,以实现低门槛、易理解、可控可重复的构建过程。”
根据上述言论,我们可以知道,EulerMaker是基于类似于livebuild和yocto中间形态的构建工具,它引入了yocto分层的设计思想,解决了livebuild/koji这种构建工具的不灵活的问题,对于仍采用livebuild/koji这种二进制构建机制,其所以也避免了yocto默认只能源码编译的缺点。
至于为什么要重新设计一个构建系统,其原因可能如下:
livebuild/koji默认没有分层的思想,无法实现操作系统的定制化需求,只能集成全功能的操作系统 yocto具备分层的思想,但其学习曲线陡峭,难度高 EulerMaker在livebuild/koji的基础上,实现分层思想,集大家之所长 而实际上,个人观点是,如果抛开yocto所谓的学习曲线陡峭的缺点,如果我们直接使用yocto实现二进制的包构建,其实无需重新开发一个EulerMaker也能良好的达到目的。
根据上面我们知道,为了优化livebuild/koji这类构建平台无法实现定制化需求的问题,从而推出了EulerMaker,那就意味着,EulerMaker的核心优势在于镜像的定制。如下:
镜像定制支持自定义软件包,配置等,输出服务器,虚拟机,docker等各类镜像,针对不同的场景,开发者对于不同的镜像源有着不同的定制化需求, 例如:减少冗余的软件包、网络参数配置等等,为了满足不同领域的各个开发者的定制化需求,EulerMaker提供了针对镜像源的定制化能力,用户可以线性化,模块化定制自身需要的镜像源,满足自己的定制需求。
根据上面的说法,我们可以知道,不同的服务器,不同的环境,不同的场景(docker/虚拟机/实机系统)其配置是不一样的。哪怕一个ip地址不一样,最后形成的操作系统镜像,也是不一样的。所以EulerMaker实现如下功能:
对于不同的定制系统,定义layers来区分,这里借鉴了yocto的思想
对于不同的定制系统,通过包来进行按需构建,这里延申了livebuild/koji的优势,对于单独的包编译,可以通过rpmbuild/dh_make来单独编译,同时也不丢失yocto的从源码编译的优势
按需裁剪,极致裁剪,这里通过封装了mkiso之类的工具,推出了oemaker来实现输出镜像的定制,这里无论是koji/livebuild/yocto都是具备的
根据EulerMaker的调研情况来看,我们可以知道EulerMaker是一个优秀的构建工具,它确实能够提供操作系统的全场景定制构建能力,随着操作系统的不断发展,定制性越来越强,功能全的操作系统和更适合业务的操作系统,往往需要更适合业务的操作系统。所以EulerMaker的存在能够在国内操作系统环境上作为一个优秀的先行者,为操作系统的优秀落地带来很好的领头作用。从而更好的发展国内操作系统行业。
我们从kprobe到perf到bpf都提到了调试,这里eBPF是必不可少的一个内容,本文以一个hello world程序为例,以kprobe和kretprobe中观测的do_sys_openat2为例,实践一个最简单的eBPF程序
我们知道eBPF是由BPF演进而来,随着内核可观测的逐渐发展,eBPF逐渐火热,已经成为当前最热门的内核技术之一了,我们调试内核的研发,或多或少都有了解eBPF程序。eBPF的主要框图如下:
关于eBPF的核心原理介绍,用不着我解释,如果想要了解的,可以查看文档如下:
https://ebpf.io/what-is-ebpf/
为了编写eBPF,我们需要两个步骤
为eBPF编写c程序来映射一个event 使用python打印这个event的内容 关于bpf map,可以参考
https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
我以https://github.com/iovisor/bcc/blob/master/examples/tracing/hello_perf_output.py
为例,使用了BPF_PERF_OUTPUT定义
为了能够获取pid,time,进程名等信息,这里需要使用bpf的接口,如下参考
https://docs.ebpf.io/linux/helper-function/
故实现代码如下:
#include <uapi/linux/openat2.h> #include <linux/sched.h> struct data_t { u32 pid; u64 ts; char comm[TASK_COMM_LEN]; }; BPF_PERF_OUTPUT(events); int kylin(struct pt_regs *ctx) { struct data_t data = { }; data.pid = bpf_get_current_pid_tgid(); data.ts = bpf_ktime_get_ns(); bpf_get_current_comm(&data.comm, sizeof(data.comm)); events.perf_submit(ctx, &data, sizeof(data)); return 0; }
这里构造了结构体data_t,其内容通过bpf的api填充,并将event映射出去。
c写好了之后,需要编写python,其目的是获取event的结构,然后打印出来,如下:
#!/usr/bin/env python3 from bcc import BPF from bcc.utils import printb b = BPF(src_file="kylin.c") b.attach_kprobe(event="do_sys_openat2", fn_name="kylin") print("%-18s %-16s %-6s" % ("TIME(s)", "COMM", "PID")) start = 0 def print_event(cpu, data, size): global start event = b["events"].event(data) if start == 0: start = event.ts time_s = (float(event.ts - start)) / 1000000000 printb(b"%-18.9f %-16s %-6d" % (time_s, event.comm, event.pid)) b["events"].open_perf_buffer(print_event) while 1: try: b.perf_buffer_poll() except KeyboardInterrupt: exit()
值得注意的是,我这里借助的还是kprobe,因为kprobe和kretprobe我也是观测的"do_sys_openat2",代码借鉴examples/tracing/hello_perf_output.py
至此,代码编写完成,我们直接试验
# python3 kylin.py TIME(s) COMM PID 0.000000000 systemd-journal 313 0.000131833 systemd-journal 313 0.000204458 systemd-journal 313
可以发现,我们正确的抓到了time,comm和pid
# pidof systemd-journald 313
根据上面的例子,我们可以初步的了解了eBPF的程序如何编写。