从零开始的内核ebpf开发之旅


引言

内核研究与开发是计算机底层处于与硬件打交道的部位,ebpf可以理解为是内核开发的一个模块。在研究ebpf开发之前需要对计算机的一些基础知识学习了解,懂得计算机的基本组成和操作系统的基本原理和运行机制,了解Linux内核设计的机制和相关源码的阅读与理解,再深入内核模块观察ebpf的设计思路,进而做到对ebpf的开发与实现。

在此之前,首先需要储备一些基本的计算机知识。

基础知识储备

计算机组成原理

学习计算机组成原理可以对计算机的基础架构有所理解,了解计算机中常见的术语和概念。

计算机组成原理知识要点见:计算机组成原理学习笔记

操作系统

操作系统作为人和计算机交互的桥梁,理解其工作原理对后续内核开发有很好的帮助,对操作系统的术语了解知道其背后的道理是开发的基础。

清华大学操作系统课程入口

操作系统知识要点见:操作系统学习笔记

C语言

C语言是Linux内核开发主要使用的编程语言和开发工具,需要熟悉其基本语法和结构。

C语言中文网入口

C语言API入口

Linux基础

了解Linux基本组成和常用的shell命令,熟悉Linux的文件架构。

FHS(Filesystem Hierarchy Standard):

FHS依据文件系统使用的频繁与否与是否允许使用者随意更动, 而将目录定义成为四种交互作用的形态,用表格来说有点像底下这样:

可分享的(shareable) 不可分享的(unshareable)
不变的(static) /usr (软件放置处) /etc (配置文件)
/opt (第三方协力软件) /boot (开机与核心档)
可变动的(variable) /var/mail (使用者邮件信箱) /var/run (程序相关)
/var/spool/news (新闻组) /var/lock (程序相关)
  • 可分享的:可以分享给其他系统挂载使用的目录,所以包括执行文件与用户的邮件等数据, 是能够分享给网络上其他主机挂载用的目录;

  • 不可分享的:自己机器上面运作的装置文件或者是与程序有关的socket文件等, 由于仅与自身机器有关,所以当然就不适合分享给其他主机了.

  • 不变的:有些数据是不会经常变动的,跟随着distribution而不变动. 例如函式库、文件说明文件、系统管理员所管理的主机服务配置文件等等;

  • 可变动的:经常改变的数据,例如登录文件、一般用户可自行收受的新闻组等.

事实上,FHS针对目录树架构仅定义出三层目录底下应该放置什么数据而已,分别是底下这三个目录的定义:

  1. / (root, 根目录):与开机系统有关;
  2. /usr (unix software resource):与软件安装/执行有关;
  3. /var (variable):与系统运作过程有关.

根目录 (/) 的意义与内容:

概要:

  1. 所有的目录都是由根目录衍生出来的(根目录是整个系统最重要的一个目录)
  2. 与开机/还原/系统修复等动作有关. (由于系统开机时需要特定的开机软件、核心文件、开机所需程序、 函式库等等文件数据,若系统出现错误时,根目录也必须要包含有能够修复文件系统的程序才行)
  3. FHS标准建议:根目录(/)所在分割槽应该越小越好, 且应用程序所安装的软件最好不要与根目录放在同一个分割槽内,保持根目录越小越好.(因为越大的分割槽妳会放入越多的数据,如此一来根目录所在分割槽就可能会有较多发生错误的机会,如此不但效能较佳,根目录所在的文件系统也较不容易发生问题.)

根目录(/)底下目录FHS定义的说明:
| 目录 | 应放置文件内容 |
| —— | ———————————————————— |
| /bin | 系统有很多放置执行文件的目录,但/bin比较特殊.因为/bin放置的是在单人维护模式下还能够被操作的指令. 在/bin底下的指令可以被root与一般账号所使用,主要有:cat, chmod, chown, date, mv, mkdir, cp, bash等等常用的指令. |
| /boot | 这个目录主要在放置开机会使用到的文件,包括Linux核心文件以及开机选单与开机所需配置文件等等. Linux kernel常用的档名为:vmlinuz,如果使用的是grub这个开机管理程序, 则还会存在/boot/grub/这个目录喔! |
| /dev | 在Linux系统上,任何装置与接口设备都是以文件的型态存在于这个目录当中的. 你只要透过存取这个目录底下的某个文件,就等于存取某个装置啰~ 比要重要的文件有/dev/null, /dev/zero, /dev/tty, /dev/lp, /dev/hd, /dev/sd*等等 |
| /etc | 系统主要的配置文件几乎都放置在这个目录内,例如人员的账号密码文件、 各种服务的启始档等等.一般来说,这个目录下的各文件属性是可以让一般使用者查阅的, 但是只有root有权力修改.FHS建议不要放置可执行文件(binary)在这个目录中喔.比较重要的文件有: /etc/inittab, /etc/init.d/, /etc/modprobe.conf, /etc/X11/, /etc/fstab, /etc/sysconfig/ 等等.另外,其下重要的目录有:/etc/init.d/:所有服务的预设启动 script 都是放在这里的,例如要启动或者关闭 iptables 的话:『 /etc/init.d/iptables start』、『/etc/init.d/iptables stop』/etc/xinetd.d/:这就是所谓的super daemon管理的各项服务的配置文件目录./etc/X11/:与 X Window 有关的各种配置文件都在这里,尤其是 xorg.conf 这个 X Server 的配置文件. |
| /home | 这是系统默认的用户家目录(home directory).在你新增一个一般使用者账号时, 默认的用户家目录都会规范到这里来.比较重要的是,家目录有两种代号喔: ~:代表目前这个用户的家目录,而 ~dmtsai :则代表 dmtsai 的家目录! |
| /lib | 系统的函式库非常的多,而/lib放置的则是在开机时会用到的函式库, 以及在/bin或/sbin底下的指令会呼叫的函式库而已. 什么是函式库呢?妳可以将他想成是『外挂』,某些指令必须要有这些『外挂』才能够顺利完成程序的执行之意. 尤其重要的是/lib/modules/这个目录, 因为该目录会放置核心相关的模块(驱动程序)喔! |
| /media | media是『媒体』的英文,顾名思义,这个/media底下放置的就是可移除的装置啦! 包括软盘、光盘、DVD等等装置都暂时挂载于此.常见的档名有:/media/floppy, /media/cdrom等等. |
| /mnt | 如果妳想要暂时挂载某些额外的装置,一般建议妳可以放置到这个目录中. 在古早时候,这个目录的用途与/media相同啦!只是有了/media之后,这个目录就用来暂时挂载用了. |
| /opt | 这个是给第三方协力软件放置的目录.什么是第三方协力软件啊? 举例来说,KDE这个桌面管理系统是一个独立的计划,不过他可以安装到Linux系统中,因此KDE的软件就建议放置到此目录下了. 另外,如果妳想要自行安装额外的软件(非原本的distribution提供的),那么也能够将你的软件安装到这里来. 不过,以前的Linux系统中,我们还是习惯放置在/usr/local目录下呢! |
| /root | 系统管理员(root)的家目录.之所以放在这里,是因为如果进入单人维护模式而仅挂载根目录时, 该目录就能够拥有root的家目录,所以我们会希望root的家目录与根目录放置在同一个分割槽中. |
| /sbin | Linux有非常多指令是用来设定系统环境的,这些指令只有root才能够利用来『设定』系统,其他用户最多只能用来『查询』而已. 放在/sbin底下的为开机过程中所需要的,里面包括了开机、修复、还原系统所需要的指令. 至于某些服务器软件程序,一般则放置到/usr/sbin/当中.至于本机自行安装的软件所产生的系统执行文件(system binary), 则放置到/usr/local/sbin/当中了.常见的指令包括:fdisk, fsck, ifconfig, init, mkfs等等. |
| /srv | srv可以视为『service』的缩写,是一些网络服务启动之后,这些服务所需要取用的数据目录. 常见的服务例如WWW, FTP等等.举例来说,WWW服务器需要的网页数据就可以放置在/srv/www/里面. |
| /tmp | 这是让一般使用者或者是正在执行的程序暂时放置文件的地方. 这个目录是任何人都能够存取的,所以你需要定期的清理一下.当然,重要数据不可放置在此目录啊! 因为FHS甚至建议在开机时,应该要将/tmp下的数据都删除唷! |

除上 FHS 中定义的目录说明外, 底下是几个在Linux当中非常重要的目录:

目录 应放置文件内容
/lost+found 这个目录是使用标准的ext2/ext3文件系统格式才会产生的一个目录,目的在于当文件系统发生错误时, 将一些遗失的片段放置到这个目录下.这个目录通常会在分割槽的最顶层存在, 例如你加装一颗硬盘于/disk中,那在这个系统下就会自动产生一个这样的目录『/disk/lost+found』
/proc 这个目录本身是一个『虚拟文件系统(virtual filesystem)』喔!他放置的数据都是在内存当中, 例如系统核心、行程信息(process)、周边装置的状态及网络状态等等.因为这个目录下的数据都是在内存当中, 所以本身不占任何硬盘空间啊!比较重要的文件例如:/proc/cpuinfo, /proc/dma, /proc/interrupts, /proc/ioports, /proc/net/* 等等.
/sys 这个目录其实跟/proc非常类似,也是一个虚拟的文件系统,主要也是记录与核心相关的信息. 包括目前已加载的核心模块与核心侦测到的硬件装置信息等等.这个目录同样不占硬盘容量喔!

不可与根目录分开的目录(与开机过程有关):
根目录与开机有关,开机过程中仅有根目录会被挂载, 其他分割槽则是在开机完成之后才会持续的进行挂载的行为.就是因为如此,因此根目录下与开机过程有关的目录, 就不能够与根目录放到不同的分割槽去!

  • /etc:配置文件
  • /bin:重要执行档
  • /dev:所需要的装置文件
  • /lib:执行档所需的函式库与核心所需的模块
  • /sbin:重要的系统执行文件

/usr 的意义与内容:

概要:

  1. 依据FHS的基本定义,/usr里面放置的数据属于可分享的与不可变动的(shareable, static), 如果你知道如何透过网络进行分割槽的挂载,那么/usr确实可以分享给局域网络内的其他主机来使用!
  2. usr(Unix Software Resource 即Unix操作系统软件资源) FHS建议所有软件开发者,应该将他们的数据合理的分别放置到这个目录下的次目录,而不要自行建立该软件自己独立的目录.
  3. 所有系统默认的软件(distribution发布者提供的软件)都会放置到/usr底下,因此这个目录有点类似Windows 系统的『C:\Windows\ + C:\Program files\』这两个目录的综合体,系统刚安装完毕时,这个目录会占用最多的硬盘容量.

一般来说,/usr的次目录建议有底下这些:
| 目录 | 应放置文件内容 |
| ————- | ———————————————————— |
| /usr/X11R6/ | 为X Window System重要数据所放置的目录,之所以取名为X11R6是因为最后的X版本为第11版,且该版的第6次释出之意. |
| /usr/bin/ | 绝大部分的用户可使用指令都放在这里!请注意到他与/bin的不同之处.(是否与开机过程有关) |
| /usr/include/ | c/c++等程序语言的档头(header)与包含档(include)放置处,当我们以tarball方式 (*.tar.gz 的方式安装软件)安装某些数据时,会使用到里头的许多包含档喔! |
| /usr/lib/ | 包含各应用软件的函式库、目标文件(object file),以及不被一般使用者惯用的执行档或脚本(script). 某些软件会提供一些特殊的指令来进行服务器的设定,这些指令也不会经常被系统管理员操作, 那就会被摆放到这个目录下啦.要注意的是,如果你使用的是X86_64的Linux系统, 那可能会有/usr/lib64/目录产生喔! |
| /usr/local/ | 系统管理员在本机自行安装自己下载的软件(非distribution默认提供者),建议安装到此目录, 这样会比较便于管理.举例来说,你的distribution提供的软件较旧,你想安装较新的软件但又不想移除旧版, 此时你可以将新版软件安装于/usr/local/目录下,可与原先的旧版软件有分别啦! 你可以自行到/usr/local去看看,该目录下也是具有bin, etc, include, lib…的次目录喔! |
| /usr/sbin/ | 非系统正常运作所需要的系统指令.最常见的就是某些网络服务器软件的服务指令(daemon)啰! |
| /usr/share/ | 放置共享文件的地方,在这个目录下放置的数据几乎是不分硬件架构均可读取的数据, 因为几乎都是文本文件嘛!在此目录下常见的还有这些次目录:/usr/share/man:联机帮助文件/usr/share/doc:软件杂项的文件说明/usr/share/zoneinfo:与时区有关的时区文件 |
| /usr/src/ | 一般原始码建议放置到这里,src有source的意思.至于核心原始码则建议放置到/usr/src/linux/目录下. |

/var 的意义与内容:

概要:

/var目录主要针对常态性变动的文件,包括缓存(cache)、登录档(log file)以及某些软件运作所产生的文件, 包括程序文件(lock file, run file),或者例如MySQL数据库的文件等等. 所以/var在系统运作后才会渐渐占用硬盘容量的目录

常见的次目录有:

目录 应放置文件内容
/var/cache/ 应用程序本身运作过程中会产生的一些暂存档;
/var/lib/ 程序本身执行的过程中,需要使用到的数据文件放置的目录.在此目录下各自的软件应该要有各自的目录. 举例来说,MySQL的数据库放置到/var/lib/mysql/而rpm的数据库则放到/var/lib/rpm去!
/var/lock/ 某些装置或者是文件资源一次只能被一个应用程序所使用,如果同时有两个程序使用该装置时, 就可能产生一些错误的状况,因此就得要将该装置上锁(lock),以确保该装置只会给单一软件所使用. 举例来说,刻录机正在刻录一块光盘,你想一下,会不会有两个人同时在使用一个刻录机烧片? 如果两个人同时刻录,那片子写入的是谁的数据?所以当第一个人在刻录时该刻录机就会被上锁, 第二个人就得要该装置被解除锁定(就是前一个人用完了)才能够继续使用啰.
/var/log/ 重要到不行!这是登录文件放置的目录!里面比较重要的文件如/var/log/messages, /var/log/wtmp(记录登入者的信息)等.
/var/mail/ 放置个人电子邮件信箱的目录,不过这个目录也被放置到/var/spool/mail/目录中! 通常这两个目录是互为链接文件啦!
/var/run/ 某些程序或者是服务启动后,会将他们的PID放置在这个目录下喔! 至于PID的意义我们会在后续章节提到的.
/var/spool/ 这个目录通常放置一些队列数据,所谓的『队列』就是排队等待其他程序使用的数据啦! 这些数据被使用后通常都会被删除.举例来说,系统收到新信会放置到/var/spool/mail/中, 但使用者收下该信件后该封信原则上就会被删除.信件如果暂时寄不出去会被放到/var/spool/mqueue/中, 等到被送出后就被删除.如果是工作排程数据(crontab),就会被放置到/var/spool/cron/目录中!

针对FHS,各家distributions的异同:

由于FHS仅是定义出最上层(/)及次层(/usr, /var)的目录内容应该要放置的文件或目录数据, 因此,在其他次目录层级内,就可以随开发者自行来配置了.举例来说,CentOS的网络设定数据放在 /etc/sysconfig/network-scripts/ 目录下,但是SuSE则是将网络放置在 /etc/sysconfig/network/ 目录下,目录名称可是不同的呢!不过只要记住大致的FHS标准,差异性其实有限啦!

Linux 命令大全查询表

Linux 命令大全
1、文件管理
cat chattr chgrp chmod
chown cksum cmp diff
diffstat file find git
gitview indent cut ln
less locate lsattr mattrib
mc mdel mdir mktemp
more mmove mread mren
mtools mtoolstest mv od
paste patch rcp rm
slocate split tee tmpwatch
touch umask which cp
whereis mcopy mshowfat rhmask
scp awk
2、文档编辑
col colrm comm csplit
ed egrep ex fgrep
fmt fold grep ispell
jed joe join look
mtype pico rgrep sed
sort spell tr expr
uniq wc
3、文件传输
lprm lpr lpq lpd
bye ftp uuto uupick
uucp uucico tftp ncftp
ftpshut ftpwho ftpcount
4、磁盘管理
cd df dirs du
edquota eject mcd mdeltree
mdu mkdir mlabel mmd
mrd mzip pwd quota
mount mmount rmdir rmt
stat tree umount ls
quotacheck quotaoff lndir repquota
quotaon
5、磁盘维护
badblocks cfdisk dd e2fsck
ext2ed fsck fsck.minix fsconf
fdformat hdparm mformat mkbootdisk
mkdosfs mke2fs mkfs.ext2 mkfs.msdos
mkinitrd mkisofs mkswap mpartition
swapon symlinks sync mbadblocks
mkfs.minix fsck.ext2 fdisk losetup
mkfs sfdisk swapoff
6、网络通讯
apachectl arpwatch dip getty
mingetty uux telnet uulog
uustat ppp-off netconfig nc
httpd ifconfig minicom mesg
dnsconf wall netstat ping
pppstats samba setserial talk
traceroute tty newaliases uuname
netconf write statserial efax
pppsetup tcpdump ytalk cu
smbd testparm smbclient shapecfg
7、系统管理
adduser chfn useradd date
exit finger fwhios sleep
suspend groupdel groupmod halt
kill last lastb login
logname logout ps nice
procinfo top pstree reboot
rlogin rsh sliplogin screen
shutdown rwho sudo gitps
swatch tload logrotate uname
chsh userconf userdel usermod
vlock who whoami whois
newgrp renice su skill
w id free
8、系统设置
reset clear alias dircolors
aumix bind chroot clock
crontab declare depmod dmesg
enable eval export pwunconv
grpconv rpm insmod kbdconfig
lilo liloconfig lsmod minfo
set modprobe ntsysv mouseconfig
passwd pwconv rdate resize
rmmod grpunconv modinfo time
setup sndconfig setenv setconsole
timeconfig ulimit unset chkconfig
apmd hwclock mkkickstart fbset
unalias SVGATextMode
9、备份压缩
ar bunzip2 bzip2 bzip2recover
gunzip unarj compress cpio
dump uuencode gzexe gzip
lha restore tar uudecode
unzip zip zipinfo
10、设备管理
setleds loadkeys rdev dumpkeys
MAKEDEV

Linux内核

Linux内核学习策略

Linux学习建议配套远古版本的Linux内核源码学习,有助于帮助理解内核设计的思路,下载并阅读Linux内核1.0版本的源码去学习,该版本基本包含了内核基本部件,后续的版本都是在此基础上扩充功能,但是基本的内在没有变化。

内核源码不同版本间的阅读与对比可参考Bootlin,其中1.0源码目录结构如下,其中对主要文件目录进行解释:

boot Linux系统引导与启动相关代码
drivers 驱动相关代码
fs 文件系统相关代码
ibcs
include 头文件代码
init main.c初始化系统代码
ipc 进程通信相关代码
kernel 系统中断体系结构,进程周期等相关代码
lib 通用函数库代码
mm 内存管理相关代码
net 网络相关代码
tools 工具相关代码
zBoot

对照着内核设计的源代码进行学习,会从根源上思考这样设计的目的是什么。

Linux内核开发环境配置

内核开发环境和源码安装配置:Linux内核开发环境配置

Linux内核简介

Linux 内核的用途是什么?

Linux 内核有 4 项工作:

  1. 内存管理:追踪记录有多少内存存储了什么以及存储在哪里
  2. 进程管理:确定哪些进程可以使用中央处理器(CPU)、何时使用以及持续多长时间
  3. 设备驱动程序:充当硬件与进程之间的调解程序/解释程序
  4. 系统调用和安全防护:从流程接受服务请求

在正确实施的情况下,内核对于用户是不可见的,它在自己的小世界(称为内核空间)中工作,并从中分配内存和跟踪所有内容的存储位置。用户所看到的内容(例如 Web 浏览器和文件)则被称为用户空间。这些应用通过系统调用接口(SCI)与内核进行交互。

举例来说,内核就像是一个为高管(硬件)服务的忙碌的个人助理。助理的工作就是将员工和公众(用户)的消息和请求(进程)转交给高管,记住存放的内容和位置(内存),并确定在任何特定的时间谁可以拜访高管、会面时间有多长。

Linux内核学习路线和框架图

Linux Security Coaching

Linux内核基础学习资料

Linux内核与系统驱动保护入口

该视频资料详细介绍了Linux内核的知识点及其在内核中的实现进行比对,很有参考价值。

MakeFile详解

Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。详细介绍如下:

MakeFile详解

Makefile文件负责编写程序的编译与运行规则,免去命令行使用Clang去逐步编译分析。

GDB详解

GDB是一个强大的调试工具,通过它可以实现C程序代码bug的调试。

GDB详解

Linux崩溃调试

Linux内核Crash下的问题解决方案:

Linux调试之崩溃

EBPF基础

什么是ebpf?

Linux 内核一直是实现监控/可观测性、网络和安全功能的理想地方。 不过很多情况下这并非易事,因为这些工作需要修改内核源码或加载内核模块, 最终实现形式是在已有的层层抽象之上叠加新的抽象。 eBPF 是一项革命性技术,它能在内核中运行沙箱程序(sandbox programs), 而无需修改内核源码或者加载内核模块。

eBPF 催生了一种全新的软件开发方式。基于这种方式,我们不仅能对内核行为进行 编程,甚至还能编写跨多个子系统的处理逻辑,而传统上这些子系统是完全独立、 无法用一套逻辑来处理的。

安全:

观测和理解所有的系统调用的能力,以及在 packet 层和 socket 层审视所有的网络操作的能力, 这两者相结合,为系统安全提供了革命性的新方法。 以前,系统调用过滤、网络层过滤和进程上下文跟踪是在完全独立的系统中完成的; eBPF 的出现统一了可观测性和各层面的控制能力,使我们有更加丰富的上下文和更精细的控制能力, 因而能创建更加安全的系统。

网络:

eBPF 的两大特色 —— 可编程和高性能 —— 使它能满足所有的网络包处理需求。 可编程意味着无需离开内核中的包处理上下文,就能添加额外的协议解析器或任何转发逻辑, 以满足不断变化的需求。高性能的 JIT 编译器使 eBPF 程序能达到几乎与原生编译的内核态代码一样的执行性能。

跟踪 & 性能分析:

eBPF 程序能够加载到 trace points、内核及用户空间应用程序中的 probe points, 这种能力使我们对应用程序的运行时行为(runtime behavior)和系统本身 (system itself)提供了史无前例的可观测性。应用端和系统端的这种观测能力相结合, 能在排查系统性能问题时提供强大的能力和独特的信息。BPF 使用了很多高级数据结构, 因此能非常高效地导出有意义的可观测数据,而不是像很多同类系统一样导出海量的原始采样数据。

观测 & 监控:

相比于操作系统提供的静态计数器(counters、gauges),eBPF 能在内核中收集和聚合自定义 metric, 并能从不同数据源来生成可观测数据。这既扩展了可观测性的深度,也显著减少了整体系统开销, 因为现在可以选择只收集需要的数据,并且后者是直方图或类似的格式,而非原始采样数据。

Linux驱动模块开发

简介

Linux 内核的整体结构已经非常庞大,而其包含的组件也非常多。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。Linux 提供了这样的一种机制,这种机制被称为模块(Module)。使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中。

举例

先来看一个最简单的内核模块“Hello World”,代码如下:

#include <linux/init.h>
#include <linux/module.h>

static int hello_init(void) /初始化函数/
{
 printk(KERN_INFO " Hello World enter\n");
 return 0;
}

static void hello_exit(void) /卸载函数/
{
 printk(KERN_INFO " Hello World exit\n ");
}

module_init(hello_init); /模块初始化/
module_exit(hello_exit); /卸载模块/

MODULE_LICENSE("Dual BSD/GPL"); /许可声明/

MODULE_AUTHOR("Linux");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("a simplest module");

这个模块定义了两个函数, 一个在模块加载到内核时被调用( hello_init )以及一个在模块被去除时被调用( hello_exit ). moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核, 该模块带有一个自由的许可证.

注:内核模块中用于输出的函数是内核空间的 printk()而非用户空间的 printf(),具体用法参考附件 printk函数介绍。

几个常用命令

加载模块

通过“insmod ./hello.ko”命令可以加载,加载时输出“Hello World enter”。

卸载模块

通过“rmmod hello”命令可以卸载,卸载时输出“Hello World exit”。

查看系统中已经加载的模块列表

在Linux中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系,例如:

root@imx6:~$ lsmod
Module      Size    Used by
hello      1568    0 
ohci1394     32716   0 
ide_scsi     16708   0 
ide_cd      39392   0 
cdrom      36960   1 ide_cd
查看某个具体模块的详细信息

使用modinfo <模块名>命令可以获得模块的信息,包括模块作者、模块的说明、模块所支持 的参数以及 vermagic:

root@imx6:~$ modinfo hello.ko
filename:   hello.ko
license:   Dual BSD/GPL
author:    Song Baohua
description: A simple Hello World Module
alias:    a simplest module
vermagic:   2.6.15.5 686 gcc-3.2
depends: 

Linux 内核模块程序的结构

一个Linux内核模块主要由如下几个部分组成:

1、模块加载函数(一般需要) 当通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。

2、模块卸载函数(一般需要) 当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块卸载函数相反的功能。

3、模块许可证声明(必须) 许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。在Linux 2.6内核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。大多数情况下,内核模块应遵循GPL兼容许可权。Linux 2.6内核模块最常见的是以MODULE_LICENSE( “Dual BSD/GPL” )语句声明模块采BSD/GPL双LICENSE。

4、模块参数(可选) 模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。

5、模块导出符号(可选) 内核模块可以导出符号(symbol,对应于函数或变量),这样其它模块可以使用本模块中的变量或函数。

6、模块作者等信息声明(可选) 用于申明模块作者的相关信息,一般用于备注作者姓名、邮箱等。

模块加载函数

Linux 内核模块加载函数一般以_ _init 标识声明,典型的模块加载函数如下:

static int _ _init initialization_function(void)
{
/* 初始化代码 */
}
module_init(initialization_function);

模块加载函数必须以“module_init(函数名)”的形式被指定。它返回整型值,若初始化成功,应返回 0。而在初始化失败时,应该返回错误编码。在 Linux 内核里,错误编码是一个负值。

在 Linux 2.6 内核中,可以使用 request_module(const char *fmt, …)函数加载内核模块,驱动开发人员可以通过调用。

request_module(module_name);
/**** 或者 ****/
request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));

注意:在 Linux 中,所有标识为_ init 的函数在连接的时候都放在.init.text 这个区段内,此外,所有的 init 函数在区段.initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 _init 函数,并在初始化完成后,释放 init 区段(包括.init.text、.initcall.init 等)。

模块卸载函数
static void _ _exit cleanup_function(void)
{
/* 释放代码 */
}
module_exit(cleanup_function);

模块卸载函数在模块卸载的时候执行,不返回任何值,必须以“module_exit(函数名)”的形式来指定。通常来说,模块卸载函数要完成与模块加载函数相反的功能,如下所示。

若模块加载函数注册了 XXX,则模块卸载函数应该注销 XXX。

若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。

若模块加载函数申请了硬件资源(中断、DMA 通道、I/O 端口和 I/O 内存等)的占用,则模块卸载函数应释放这些硬件资源。

若模块加载函数开启了硬件,则卸载函数中一般要关闭之。

模块参数

用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了 1 个整型参数和 1 个字符指针参数:

static char *book_name = " dissecting Linux Device Driver ";
static int num = 4 000;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);

参数类型可以是 byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或 invbool(布尔的反),在模块被编译时会将 module_param 中声明的类型与变量定义的类型进行比较,判断是否一致。

在装载内核模块时,用户可以向模块传递参数,形式为“insmode(或 modprobe)模块名 参数名=参数值”,如果不传递,参数将使用模块内定义的缺省值。

内核模块的符号导出

模块可以使用如下宏导出符号到内核符号表:

EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);

导出的符号将可以被其他模块使用,使用前声明一下即可。EXPORT_SYMBOL_GPL()只适用于包含 GPL 许可权的模块。

模块声明与描述

在Linux内核模块中,我们可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别声明模块的作者、描述、版本、设备表和别名,例如:

MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);

对于USB、PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE。

MakeFile

Kernel modules
obj-m += hello.o
Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0

build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules #modules表示编译成模块的意思
#CURDIR是make的内嵌变量,自动设置为当前目录

clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
该 Makefile 文件应该与源代码 hello.c 位于同一目录,开启其中的 EXTRA_CFLAGS=-g -O0可以得到包含调试信息的 hello.ko 模块。运行 make 命令得到的模块可直接在 PC 上运行。

注:uname 的更多用法详见附件

如果一个模块包括多个.c 文件(如 file1.c、file2.c),则应该以如下方式编写 Makefile:

obj-m := modulename.o
modulename-objs := file1.o file2.o

obj-m是个makefile变量,它的值可以是一串.o文件的表列

EBPF详解

ebpf详细学习笔记和记录:

EBPF(Berkeley Packet Filter)学习记录

在这里不赘述ebpf的历史等没有太多学习意义的信息,主要从实际开发角度需要去展开必要介绍。

Seccross项目理解

开发记录及相关源码分析记录:

SECCROSS项目解读

开发记录


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录