使用vim打造内核模块开发环境
写在前面
我一直犹豫要不要写本文,因为本人水平有限,无法把内核模块(下面简称ko)的开发环境搞得像C++开发环境那么炫酷,(C++开发环境的搭建可以见我这个文章:https://zp001.blog.csdn.net/article/details/138594804),也没有自信我这个方案就是最好的方案,只是自己用得比较顺手而已。
这里面有客观原因,也有主观原因:
- 客观原因
- KO开发不是你完整开发一个程序,而是要在别人的项目(Linux)中使用别人的Makefile和源码开发一个小模块,所以编译你开发的这个小模块依赖于整个Linux源码的Makefile,而非单单那个KO的Makefile;再者,编译KO是有严格的编译器要求的,即是说,要使用编译Linux内核的那个编译器(gcc),并且要使用那个gcc版本。另一方面,我们知道,在vim中使用lsp,其实是依靠clangd这个东西时时刻刻在编译你的源码,这意味着你要配置clangd使用特定版本的gcc在特定的Linux内核环境中进行编译,这个折腾的难度是挺高的。
- 一般情况下,KO开发都要适配好几个版本的Linux内核,这意味着你得依赖于好几个版本的Linux源码和GCC。这意味着,就算你折腾clangd,使得它能实时编译某个版本的源码,也难以适配其他版本的源码。
- 主观原因:
- 主观的原因在于,迎着上述障碍去折腾,必要性也不是太大。KO开发是一门老手艺,存在于行业几十年了,这说明老一辈程序员是能获得足够的工具支撑的,这便是vim的tagging技术。tag技术就是把C代码的元素打上各种标签,分辨出哪些字符是函数名、哪些字符是结构体名字等等,说白了,就是关键字符提取。可想而知,它其实是不懂C语言的,效果自然比不上LSP,但是由于C语言是一门简单的语言,不像C++那么复杂,所以KO开发中,仍然是足够好的工具。
- 使用基于tag技术的vim,搭建开发环境比较简单,就算我们在多个Linux搭建多套环境也不会太麻烦。这在适配多个Linux内核的工作中,是比较方便的。
- 最后一个主观原因是,开发KO和开发web app不太一样,开发者需要非常严谨地对待自己的代码,使得自己的代码水平接近于Linux内核源码的水平,这要求是非常高的。这个要求会使得你会非常熟悉自己的代码,哪个struct里面有哪些成员是烂熟于胸的,这会减少对“自动补全”这类功能的需求。
总之,我对内核模块的开发环境,原则是“够用”,而不是“完美”。
内核模块开发环境搭建思路
搭建思路是:
熟练掌握VIM的内置功能+使用tag技术+熟记自己的代码=良好的KO开发环境
。- 使用一个桌面linux,我选fedora,通过podman打造多个Linux内核的初步开发和编译环境;等写完代码、能通过多个容器的编译后,再分发到各个具体的要发布的Linux版本上去调试和发布。所以vim的环境是需要多套的。
下面具体讲在fedora上面如何搭建开发环境。
内核模块开发环境的方案概要
如上所述,我的方案是:
- vim选择:
原生vim(不是neovim)+插件管理器+tag插件+tag工具
。 - 开发linux选择:
fedora39
- 多版本内核编译:
fedora39自带的podman
+真正的目的linux版本虚拟机编译和调试
其中vim方案中,用到的软件如下:
工具/插件 | 说明 | 备注 |
---|---|---|
vim | 本文中,vim指vimx,通过fedora自带的repo安装 | 注1 |
gtags | GNU Global source code tagging system,就靠它识别代码了,通过fedora自带的repo安装 | 注2 |
python3 | 通过fedora自带的repo安装,版本12 | 注3 |
vim-plug | vim的插件管理器,下文讲安装办法 | |
gutentags | 自动管理gtags,通过插件管理器安装 | |
leaderf | 模糊查找神器,跳转神器,通过插件管理器安装 | 注4 |
kana-textobject系列 | 增强vim的textobject功能,通过插件管理器安装 | |
sgur/vim-textobj-parameter | 增强vim的textobject功能,通过插件管理器安装 | |
vim-easymotion | 在vim中快速跳转,通过插件管理器安装 | |
nerdtree | 文件夹浏览,通过插件管理器安装 | |
taglist | 函数列表插件,可选,下文讲安装办法 | |
gtags.vim | gtags的vim插件,可选,下文讲安装办法 |
注1:vimx比vim多了系统粘贴板等图形化界面的功能,通过yum -y install vim-X11
安装
注2:安装办法yum -y install global-6.6.5-11.fc39.x86_64
注3:Leaderf插件需要用到这个软件包sudo yum -y install python-devel
注4:Leaderf依赖于ripgrep,通过yum -y install ripgrep
安装
有这些,在功能上就足够了,在这里就不多说其他美化的插件了。
vim开发环境的搭建
vim有插件管理器可以自动安装和管理vim插件,但是有三个东西不能通过插件管理器安装,分别是:
- 插件管理器本身
- gtags的vim插件
- taglist插件
插件管理器vim-plug
这里选择vim-plug插件管理器,算主流选择吧。
项目地址是:https://github.com/junegunn/vim-plug
安装办法很简单,一句命令即可:
curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
gtags的vim插件
当安装好global之后,其实就自带这个插件了,只需复制到vim相关目录即可:
cp /usr/share/gtags/gtags.vim $HOME/.vim/plugin
taglist插件
这是个老插件了。
在有Leaderf之后,使用Leaderf function
命令比它好用多了,这个插件就用得很少了。
安装办法也很简单,在下面网址中下载,然后解压到~/.vim
,确保~/.vim/plugin/taglist.vim
文件存在即可。
https://www.vim.org/scripts/script.php?script_id=273
安装过程
❯ pwd
/home/zp001/.vim
❯ unzip taglist_46.zip
Archive: taglist_46.zip
inflating: plugin/taglist.vim
inflating: doc/taglist.txt
leaderf function效果
taglist效果
Leaderf function方便的地方
leaderf可以模糊搜索,定位函数更方便
使用插件管理器安装其他插件
编辑.vimrc
加入以下内容
call plug#begin()
" 必装插件
Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
Plug 'Yggdroot/LeaderF', { 'do': ':LeaderfInstallCExtension' }
Plug 'ludovicchabant/vim-gutentags'
Plug 'kana/vim-textobj-user'
Plug 'kana/vim-textobj-indent'
Plug 'kana/vim-textobj-syntax' " key: y
Plug 'kana/vim-textobj-function', { 'for':['c', 'cpp', 'vim', 'java'] } " key: f/F, dont work with cpp
Plug 'sgur/vim-textobj-parameter' " key: ,
Plug 'easymotion/vim-easymotion'
" 其他建议安装的插件
Plug 'ghifarit53/tokyonight-vim' " vim主题
Plug 'terryma/vim-multiple-cursors' " 多光标操作,在代码中重命名变量什么的场景很有用
Plug 'tpope/vim-commentary' " 快速注释代码
Plug 'vim-airline/vim-airline' " 更好看的状态栏
Plug 'vim-airline/vim-airline-themes' " 更好看的状态栏
call plug#end()
重新打开vim
,输入命令:PlugInstall
,即可看到插件管理器在下载安装所有插件
插件的配置和使用
vim-gutentags、Leaderf、gtags.vim的配置
刚接触这些插件的时候,vim-gutentags、Leaderf、gtags.vim是很容易让人混淆的,我当初也概莫能外,这里澄清一下。
- vim-gutentags是自动生成gtags索引文件的插件
- Leaderf是模糊搜索的插件,它可以使用gtags的索引文件
- gtags.vim是正宗的gtags索引文件的使用者。
上文已经提到,gtags.vim是可以不装的。
解决vim-gutentags和Leaderf冲突
关于这3个东西,第一件事要提的是:vim-gutentags和Leaderf这两个插件的功能有冲突。他们俩都会自动生成gtags索引文件,幸好Leaderf预料到了此种情况并给出了办法。
文档地址:https://github.com/Yggdroot/LeaderF/blob/master/doc/leaderf.txt
g:Lf_GtagsGutentags *g:Lf_GtagsGutentags*
if you use https://github.com/ludovicchabant/vim-gutentags to generate
gtags; Firstly, you should let g:Lf_GtagsAutoGenerate = 0 and let g:Lf_GtagsGutentags = 1.
Then, you should config gutentags like this: g:Lf_CacheDirectory = expand('~')
and g:gutentags_cache_dir = expand(g:Lf_CacheDirectory.'/LeaderF/gtags')
Default value is 0
我的配置如下:
" for Leaderf
let g:Lf_GtagsAutoGenerate = 0
let g:Lf_GtagsGutentags = 1
let g:Lf_CacheDirectory = expand('~')
" for gutentags
let g:gutentags_modules = ['ctags', 'gtags_cscope'] " 让gutentags自动产生ctags和gtags的索引文件
let g:gutentags_project_root = ['.root'] " gutentags把有.root文件的目录当成是项目的根目录,来自动帮你管理索引文件
let g:gutentags_cache_dir = expand(g:Lf_CacheDirectory.'/LeaderF/gtags')
" for gtags.vim
set cscopeprg='gtags-cscope' " 使用 gtags-cscope 代替 cscope,这会enable老的cscope命令
let Gtags_Close_When_Single = 1 " 如果搜索只有一个,就不打开搜索结果窗口啦,直接跳转
澄清gtags.vim和Leaderf的冲突
gtags.vim和Leaderf都是gtags索引文件的使用者,功能上有重叠的地方。但是Leaderf除了可以使用gtags索引,还可以使用ripgrep,功能比gtags.vim更多,所以相较之下,要二选一的话就选Leaderf了。
我使用gtags已经很长时间了,所以舍不得丢弃,其实两者是可以共存的,并无冲突。
我的配置如下:
" for gtags.vim
map <C-\><C-]> :GtagsCursor<CR>
nmap <C-\>d :Gtags -d <C-R>=expand("<cword>")<CR>
nmap <C-\>r :Gtags -r <C-R>=expand("<cword>")<CR>
nmap <C-\>g :Gtags -g <C-R>=expand("<cword>")<CR>
nmap <C-\>f :Gtags -f <C-R>=expand("<cword>")<CR>
" for Leaderf
nmap <F7> :Leaderf file --popup
nmap <F8> :Leaderf rg --popup
nmap <leader>fn :Leaderf function<CR>
nmap <Leader>fg :Leaderf rg <C-R>=expand("<cword>")<CR>
nmap <Leader>fe :Leaderf rg -F <C-R>=expand("<cword>")<CR>
noremap <leader>gr :<C-U><C-R>=printf("Leaderf gtags -r %s --auto-jump", expand("<cword>"))<CR>
noremap <leader>gd :<C-U><C-R>=printf("Leaderf gtags -d %s --auto-jump", expand("<cword>"))<CR>
noremap <leader>go :<C-U><C-R>=printf("Leaderf gtags --recall %s", "")<CR>
noremap <leader>gn :<C-U><C-R>=printf("Leaderf gtags --next %s", "")<CR>
noremap <leader>gp :<C-U><C-R>=printf("Leaderf gtags --previous %s", "")<CR>
好了,现在gtags和Leaderf都配置好了,那日常中想用哪个都可以了。
gutentags的使用
这是个装完就不用管的插件,真爽。
它负责自动更新gtags索引,它做到了,我们负责写代码,它在后台默默更新,不打扰我们。
跳转到函数定义
跳转到函数定义是最常用的操作需求了吧。现在有4种办法可以做到了。
-
使用ctags快捷键,转到当前光标所在的词的定义。
C+]
-
使用gtags快捷键,转到当前光标所在的词的定义。
<C-\><C-]>
-
使用gtags快捷键,可以输入一个词,转到它的定义,默认是当前光标所在词。
<C-\>d
-
使用Leaderf快捷键,可以输入一个词,转到它的定义,默认是当前光标所在词。
<leader>gd
好了,跳过去学会了,那怎么跳回来呢?C-o
还有一个常见的场景,我跳过去函数定义,只是为了查看函数的参数,我还是要编辑当前行的代码来调用这个函数的,怎么操作才方便呢?
我的办法是,先:vs
命令左右分屏,然后在右侧屏幕C+]
调到函数定义,然后回到左侧屏幕编写代码。
虽然有点土,但是用惯了也还好。
注:C是ctrl键
搜索调用函数的位置
现在有两个办法来搜索了。
- 使用gtags。
<C-\>
- 使用Leaderf。
<leader>gr
两个办法的区别在于。gtags会吧搜索结果放在quickfix窗口里,Leaderf则会上下分屏显示。
使用Leaderf随意搜索
如果没有Leaderf,那么在项目中随意搜索某个字符,就要使用grep或者vimgrep命令,有了Leaderf之后,功能没变化,但界面就变炫酷很多了,性能也提升了,因为它调用了rg命令来加速。
比如现在可以按<F8>
弹出浮窗来搜索,还可以实时预览。
如果不喜欢浮窗这种浮夸的展现方式(比如我),就使用<Leader>fg
,它会低调地上下分屏展示,老实说,这种方式实用很多。
这功能或许是使用Leaderf的最大原因吧。
使用vim-easymotion来快速跳转
vim是全键盘使用的,但不用鼠标的话,遇到要大幅度移动光标就比较麻烦。传统vim的移动办法有下面几个:
- relativenumber。使用相对行号。这样就可以输入
20j
这样的指令快速向下移动20行,非常方便。但这种方便只限于行之间的跳转,如果要向下20行并移动到行内的某个字符,还是需要额外的操作。 - 使用f/F和w/W在行内进行操作。这也算方便,但不算很方便,通常要按好几下才能去到想去的位置。
vim-easymotion插件便是改善移动速度的插件,按以下配置快捷键之后,按f
就可以去到屏幕内任意一个字符,按<leader>w
就能去到屏幕内任意一个单词。
map f <Plug>(easymotion-bd-f)
nmap f <Plug>(easymotion-overwin-f)
map <Leader>w <Plug>(easymotion-bd-w)
nmap <Leader>w <Plug>(easymotion-overwin-w)
使用额外的textobject来快速修改代码元素
对vim有深度体验的人对textobject的功能应该是印象深刻的,比如ciw
能快速修改一个单词,ci)
能快速修改括号里面的内容,非常方便。
但遗憾的是,内置的textobject种类太少了,比如我想修改C函数的参数,就没法修改了。现在就好了,我们装了一堆textobject,只要输入ci,
就能快速修改函数参数,vif
就可以快速选中整个函数的函数体,非常方便。
代码补全问题
老是会有人问我为什么不使用代码补全?在部分人眼里,代码补全就是coc、ycm和lsp,插件中没有这些就认为没有代码补全。
其实vim自带就有代码补全。
按C-x
C-n
就能打开补全弹窗,使用C-n
和C-p
上下选择,C-y
应用补全。
是的,目前的补全功能没有lsp那么好用,我也说过为什么内核态编程难以利用lsp,使用这种补全足够了。
使用podman支持多版本OS编译
podman run --it myol79:v1 /bin/bash
yum -y install make gcc kernel-devel
[root@94c6f77a7ab8 3.10.0-1160.118.1.0.1.el7.x86_64]# pwd
/usr/src/kernels/3.10.0-1160.118.1.0.1.el7.x86_64 # 记住这个路径
❯ podman container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
94c6f77a7ab8 localhost/myol79:v1 /bin/bash About a minute ago Exited (0) 14 seconds ago exciting_kirch
❯ podman container commit 94c
Getting image source signatures
Copying blob 95ad618ac3f2 skipped: already exists
Copying blob b30ea34ed42b skipped: already exists
Copying blob d81baaf4481c done |
Copying config 4c427f1cb2 done |
Writing manifest to image destination
4c427f1cb2d9950166ab61da0da51f0ac4a6f6f6d5c0431541dc8c6b04071c17
❯ podman image tag 4c4 myol79:v2 # 给它打个标签
修改项目Makefile,把内核源码路径修改为容器里面的路径,下面是一个例子:
obj-m := biodeadlock.o
biodeadlock-objs := biodeadlock_main.o
# KSRC = /lib/modules/`uname -r`/build
KSRC = /usr/src/kernels/3.10.0-1160.118.1.0.1.el7.x86_64
PWD = `pwd`
default:
make -C $(KSRC) M=$(PWD) modules;
clean:
rm *.o biodeadlock.mod.c biodeadlock.mod modules.order Module.symvers *.ko
然后这样编译
podman run --rm --privileged -v "$PWD":/root/myapp -w /root/myapp myol79:v2 make
常见问题
开启粘贴板支持
yum -y install vim-X11
这会安装一个vimx命令,如之前所述,这是一个enable了图形界面功能的vim版本,使用这个替换vim即可。
leaderf的报错
解决:yum -y install python-devel,重新打开vim,执行LeaderfInstallCExtension