首页 > 知识分享:一文教你如何优雅的阅读linux内核源码
头像
尖毛草哇
编辑于 2024-12-30 12:11 湖北
+ 关注

知识分享:一文教你如何优雅的阅读linux内核源码

首先,我目前见到的最常见的编辑器是vscode和neovim,本文所介绍的方法完美适配这两个编辑器~

阅读代码的痛点

  1. 难以正确跳转

读代码应该都遇到过一个问题就是,有时候很多的同名函数,在编译链接的过程你不知道具体链接到了哪个,虽然vscode和neovim的ctag都有全局的搜索,但是满屏的同名函数一个一个去找,实在是不优雅~

  1. 对复杂的结构嵌套没有全局视角

例如如果遇到复杂的驱动,以v4l2举例,一个struct不仅包含很多个struct,而且为了减少->的使用,还会将里面包含的struct的一些成员提到顶层,以及还会有指向父struct(暂且这么叫)的指针,如果第一次梳理源码,这种复杂的结构会给阅读代码带来了极大麻烦

  1. 不同版本之间代码变动,接口弃用,但不知道原因

在版本更新的过程中,经常遇见一些情况导致以前使用的接口在新版中弃用,那么为了更好的使用新版本,就需要对搞懂这些原因

ok,uu们如果你也苦于上面的情况,希望这篇文章能帮大家更好的阅读源码~

解决方法

优雅跳转

主要使用的是compile_commands.json这个文件,这个是clangd的lsp使用到的,其中记录了一个文件编译过程中使用到的文件,如果有这个文件,那么完成正确的跳转,就不在话下了~

  1. 首先需要编译linux源码,不管是clangd,还是交叉编译链都可以。

2.1 linux kernel源码,如果用clangd构建指定好LLVM还是可以使用下面的指令得到 compile_commands.json 这个文件的。

./scripts/gen_compile_commands.py 

2.2 但是对于 aarch64-linux-gnu-gcc 这个交叉编译链,会生成clangd无法识别的指令,所以需要在linux kernel源码目录下

touch .clangd

并且在 .clangd 中输入下面的内容

CompileFlags:       
  Remove: -mabi=lp64
  1. 再启动lsp,就可以发现可以直接跳转啦~(vscode是clangd的插件,nvim如果使用的是lazyvim,不需要额外配置)

ps:这里使用的clangd,可能在执行过程中缺少一些程序包,apt install就好

复杂的嵌套结构可视化

原理

内核每个源文件编译会生成 .xx.o.cmd 的隐藏文件,里面包含了依赖源文件和头文件列表

      如imx219.c对应的 .imx219.o.cmd  里
     3  source_drivers/media/i2c/imx219.o := ~/linux/vender/nvidia/drivers/media/i2c/imx219.c
     4
     5 deps_drivers/media/i2c/imx219.o := \
      6     $(wildcard include/config/of.h) \
      7   include/linux/slab.h \
      8     $(wildcard include/config/debug/slab.h) \
      9     $(wildcard include/config/debug/objects.h) \

主要就是使用doxygen来生成,并且可以配合一下graph软件,生成可视化的图

步骤

  1. 安装软件
 $ sudo -H pip3 install filetype pygraphviz
$ sudo apt-get install build-essential python3-dev libssl-dev libffi-dev libxml2 libxml2-dev libxslt1-dev zlib1g-dev
$ sudo apt-get install python3-pygraphviz 
$ sudo apt-get install doxygen-latex doxygen-doc doxygen-gui graphviz
$ doxywizard //doxygen启动
配置
  1. 内核源码中实际编译的文件列表
//kernel_src_dump.py
#!/usr/bin/env python
import sys
import os
import os.path
import time
import re
import argparse
 
KERNEL_BASE  = os.getcwd()
KERNEL_SRC = os.getcwd()
KERNEL_OUT = os.getcwd()

def kernel_depfile_2_flist(depfile, filelist) :
    fobj = open(depfile, "r")
    for line in fobj :
        txt = line.strip()
        if 0 == len(txt) :
            continue
        if txt.startswith("cmd_") :
            continue
        if txt.startswith("deps_") :
            continue
        if txt.startswith("$(deps_") :
            continue
        if txt.endswith(".o)") :
            continue
 
        if txt.endswith(" \\") :
            txt = txt[:-2]
 
        if txt.startswith("$(wildcard ") and txt.endswith(")"):
            idx_start = len("$(wildcard ")
            tmp_txt = txt[idx_start : -1]
            pre_path = KERNEL_OUT
            relative_path = os.path.join(pre_path, tmp_txt)
            abs_path = relative_path
            if KERNEL_BASE in KERNEL_OUT:
                pre_path = KERNEL_OUT[len(KERNEL_BASE) + 1::]
                relative_path = os.path.join(pre_path, tmp_txt)
                abs_path = os.path.join(KERNEL_BASE, relative_path)
            if os.path.exists(abs_path) : 
                print(relative_path)
                pass
        else :
            tmp_txt = re.sub(r".*:= *","", txt)
            if len(tmp_txt) == 0 :
                continue
            if tmp_txt.find(KERNEL_BASE) >= 0 :
                idx_start = len(KERNEL_BASE) + 1
                full_txt = tmp_txt[idx_start:]
            else :
                if tmp_txt.endswith(".mod.c") :
                    continue
                pre_path = KERNEL_OUT
                relative_path = os.path.join(pre_path, tmp_txt)
                if KERNEL_BASE in KERNEL_OUT:
                    pre_path = KERNEL_OUT[len(KERNEL_BASE) + 1::]
                    relative_path = os.path.join(pre_path, tmp_txt)
                full_txt = relative_path
            print(full_txt)
    fobj.close()
 
def depfile_find(path, callback, args):
    for root, dirs, files in os.walk(path):  # path 为根目录
        for fname in files :
            rootname = str(root)
            if rootname.startswith(os.path.join(KERNEL_OUT,"scripts")) :
                continue
            full_name = os.path.join(rootname, fname)
            matchObj = re.search( r'.*\.o\.cmd', full_name, re.M|re.I)
            if matchObj :
                # print(full_name)
                # print("callback type: %s" % str(type(callback)))
                # if "<class 'function'>" == str(type(callback)) :
                # py_ver = sys.version[:3]
                if "function" in str(type(callback)) :
                    callback(full_name, args)
 
if __name__ == '__main__' :
    path = KERNEL_OUT
    depfile_find(path, kernel_depfile_2_flist, "undefined")
    # dump all files in path: include/generated
    path = os.path.join(KERNEL_OUT,"include", "generated")
    for root, dirs, files in os.walk(path):  # path 为根目录
        rootname = str(root)
        for fname in files :
            full_txt = os.path.join(rootname, fname)
            strip_path = full_txt
            if KERNEL_BASE in full_txt:
                strip_path = full_txt[len(KERNEL_BASE) + 1::]
            print(strip_path)
$ python3 kernel_dump.py -s $PWD | uniq -u |sort |uniq> kernel.txt  //递归查找子目录中 .o.cmd文件提取出依赖的源文件和头文件列表
          //加sort 和uniq 是去除隔行重复的行
          //运行时间要几分钟,可以进到drivers/media下测试 内容少比较快
  1. 按文件列表拷贝文件
$ python3 copy_file.py kernel.txt  //运行,可成功看到生成out目录,里面文件有拷贝过来,

//copy_file.py
# coding:utf-8 
import os 
import sys 
import shutil
 
src_kernel = '~/kernel-4.9/'  
out_dir = 'out/' 
abs_src_dir = '~/Linux_for_Tegra/source/public/kernel'  
self_out_dir = 'out/self_nvidia/'  

def copyFile(src):
  if os.path.isabs(src): 
    
    dst = src.replace(abs_src_dir, self_out_dir) 
  else:
    
    dst = os.path.join(out_dir, src)
    src = os.path.join(src_kernel, src
  dirs = os.path.dirname(dst) 
  #print("dst dir:"+dirs) 
  if not(os.path.exists(dirs) and os.path.isdir(dirs)):
    try:
      os.makedirs(dirs)  
    except OSError:
      print("dirs already exists"+dirs)    
    
  try:  
    shutil.copyfile(src,dst)
    print("copy ok:"+src)
  except:
    print("copy err:"+src)   
    
    
if __name__ == '__main__': 
  if len(sys.argv) != 2: #//检测参数个数,不满足报错
    print('params must be 1,the params is the file of contain the list of cutAndPastFile List')
    exit(0) 

  file_pa1 = sys.argv[1]  #//第二个参数,为包含文件列表的文件
  f = open(file_pa1, 'r') #//打开文件
  lst_file = f.readlines() #//读取文件里的一行(一行就是一个待拷贝的文件路径)   
  f.close()  

  for filename in lst_file:   #//循环读取文件的每一行,进行拷贝
    filename = filename.replace('\n', '') #//去除换行符的影响
    if filename != '': 
      copyFile(filename)  #//拷贝指定文件
  1. doxywizard 启动doxygen构建
  2. windows下可视化

alt

查看linux邮件

事情的起因是发现 gpio_request 被标记成弃用了,想找一下改这个patch和邮件,查看一下原因。

首先

# gpiolib-legacy.c是该函数存在的文件
git blame drivers/gpio/gpiolib-legacy.c

alt

然后得到该change的commit

git show f402886

alt

得到了该commit的标题,如红框所示。

最后在这个网址进行搜索,找到邮件 List archives on lore.kernel.org 搜索 lore.kernel -> listing of currently hosted archives -> 搜索标题)

全部评论

(2) 回帖
加载中...
话题 回帖

近期热帖

热门推荐