Try   HackMD

VSCode 開發 Linux Kernel Module 的準備

contributed by < Shiritai >

我將改進版的腳本連同說明文件放置於 make2intelliSense repository,歡迎檢閱、使用與不吝指教 :)

Include Path 設定

fibdrv 這個作業專案為例,如果純粹將 reposiroty clone 好後使用 VSCode 開啟比如 fibdrv.c,相信會看到貼心的 IntelliSense 的警告。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

這是因為預設的 include path 並不包含我們需要的核心標頭檔。

常規做法

由前車之鑑 12 得知 IntelliSense 需要調整 C/C++ Extension 設定檔: c_cpp_properties.json

  1. 準備 Linux 設定檔模板

    ​​​​{
    ​​​​    "configurations": [
    ​​​​        {
    ​​​​            "name": "Linux",
    ​​​​            "browse": {
    ​​​​                "limitSymbolsToIncludedHeaders": true,
    ​​​​                "databaseFilename": ""
    ​​​​            },
    ​​​​            "intelliSenseMode": "linux-gcc-x64",
    ​​​​            "compilerPath": "/usr/bin/gcc",
    ​​​​            "cStandard": "c99",
    ​​​​            "cppStandard": "gnu++17"
    ​​​​        }
    ​​​​    ],
    ​​​​    "version": 4
    ​​​​}
    
  2. 加入 includePath 設定

    ​​​​...
    ​​​​"name": "Linux",
    ​​​​"includePath": [
    ​​​​    "${workspaceFolder}/**",
    ​​​​    "/usr/include",
    ​​​​    "/usr/local/include",
    ​​​​    "/usr/src/LINUX_KERNEL_HEADER/include",
    ​​​​    "/usr/src/LINUX_KERNEL_HEADER/arch/x86/include",
    ​​​​    "/usr/src/LINUX_KERNEL_HEADER/arch/x86/include/generated",
    ​​​​    "/usr/lib/gcc/x86_64-linux-gnu/GCC_VERSION/include"
    ​​​​],
    ​​​​"browse": {
    ​​​​...
    

    要調整兩點。

    • LINUX_KERNEL_HEADER 換成自己的 kernel header 路徑名。
    • GCC_VERSION 換成自己的 GCC 版本。

    基本上 includePath 中前五項都是開發 Kernel Module 的常客,當然第七項編譯器本身也是。第六項本次實作會用到,不過未來開發其他模組的話可能會引入其他的標頭檔。

    尋找標頭檔的方法為至 include 資料夾下尋找被 include 的標頭檔,將其所在的資料夾加入 includePath

defines 設定

又以 lab0-c 作業中探討 list_sort.c 為例,即使直接將 linux 這個 repository clone 下來並修正 include path 問題,還是會發現一些巨集或函式無法被識別,而被 IntelliSense 自動猜測為 int FUNC_NAME()

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

likely 和 unlikely 兩巨集為編譯器提供的巨集,為了提示編譯器判斷分支發生的可能性,編譯器便可以根據此提示進行最佳化,此文提供了詳細的說明

使用時須引入 <linux/compiler.h>,同時可以進入原始碼查看巨集的細節。發現與 __KERNEL__ 以及其他數個巨集有關:

以下展示部分,前面還有另一個定義。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

這些巨集定義都影響著 IntelliSense 的運作,若不告訴它我們編譯時定義的巨集,IntelliSense 就不會正常運作。

常規做法

defines 是為了解決條件編譯的問題,當中加入的字串會使 IntelliSense 在 indexing 時看見需指定條件編譯後才會編譯到的程式碼。

基本上加入以下巨集定義就能解決大部分條件編譯的影響。

...
"compilerPath": "/usr/bin/gcc",
"defines": [
    "__GNUC__",
    "__KERNEL__",
    "MODULE"
],
"cStandard": "c99",
...

由於前面設定正確的 include path,並加入 __KERNEL__ 等巨集, IntelliSense 復活了 :)

Include Path 的例子

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Include Path + 巨集設定的例子

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

編譯後目錄雜亂的問題

正常進行編譯後,以 Linux 核心為例,可能會看到目錄看見以下:

不怎麼美觀的目錄

不僅不美觀,VSCode 更會去遞迴的解析這些不必要解析的檔案,從而降低 VSCode 的速度,增加搜尋程式碼的負擔。

對此,VSCode 有很多設定可以調整對特定檔案的排除,不過最便捷的應該是以下兩種。

  1. 直接套用 .gitignore 的設定

    ​​​​// .vscode/settings.json
    ​​​​{
    ​​​​    "explorer.excludeGitIgnore": true,
    ​​​​}
    
  2. 利用 files.exclude 決定檔案的去留

    ​​​​// .vscode/settings.json
    ​​​​"files.exclude": {
    ​​​​    "**/*.a": true,
    ​​​​    "**/*.cmd": true,
    ​​​​    "**/*.d": true,
    ​​​​    "**/*.elf": true,
    ​​​​    ...
    ​​​​    "**/Module.symvers": true,
    ​​​​    "**/Thumbs.db": true,
    ​​​​    "**/client": true,
    ​​​​    "**/dkms.conf": true,
    ​​​​    "**/modules.order": true,
    ​​​​    "**/out": true
    ​​​​}
    

    後者若要手動添加會有點麻煩

腳本法

在開發已經打好基礎之模組的情況下,本方法依賴完好的 Makefile.gitignore 便可自動生成設定檔,因為 MakeFile 是告訴我們編譯方法的第一手資料、.gitignore 則是告訴我們哪些檔案沒有顯示的必要。

直接手動追蹤 Makefile 固然可行,但若以 Linux 核心原始碼為例,如果打開其 Makefile,我們會看到

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

以 Linux 6.3.0 為例,兩千一百多行的 Makefile別開玩笑了,這不是肉眼可以追蹤的量吧。對小的專案沒問題,但我們的目標是 Linux 核心

對此,本網站提供很暴力但可行的解決方法: 利用 make--just-print 功能 (其會印出若真的運行時會執行的命令),以獲得包含 includePathdefines 等訊息,透過 Python 腳本解析後生成方才提及之常規做法的模板。

本方法的特色有二

  • 一次擷取所有需要的 include 路徑
  • 巨集可以對個別電腦客製化

但由於一次粗暴的解析所有編譯時加入的巨集,故有兩缺點:

  • 解析出的巨集量過多,導致 VSCode 解析程式碼變得緩慢
  • 不同程式/模組所需的巨集不同,一次定義好所有巨集不保證適用於所有模組

    Note: 有改善的空間,比如針對不同次編譯為 VSCode 客製化編譯時的巨集定義。不過需要更查詢 VSCode 的官方文件

針對第一個缺點,我提出了改進版的腳本。當中加入了一些例外處理與客製化設定,另根據Makefile 語法和官方文件練習寫簡單的 Makefile

具體請參考此腳本

腳本開發紀錄

  • 加入對排除檔案的調整

    • exclude: 手動加入想排除之 pattern 的列表
    • bypass: 不希望排除之 pattern 的列表
      預設加入 .vscode 避免其被忽略。
      ​​​​​​​​bypass = {
      ​​​​​​​​    '.vscode'
      ​​​​​​​​}
      
    • replace: 將指定偵測到的排除檔案置換成自己想要的 pattern
      • 預設為以下,如此便不會因為 Linux kernel 中有 .* (匹配以 . 為首之任意長度字串) 忽略 .vscode 資料夾。
        ​​​​​​​​​​​​replace = {
        ​​​​​​​​​​​​    ".*": ".[!v]*" # exclude files start with . but not .v
        ​​​​​​​​​​​​}
        

    VSCode 採用 Unix 下經典的 glob 語法匹配檔名 pattern,但不同於 bash,VSCode 並不支援 glob 的擴充語法,不如 regex 靈活。不過語法規則少很多,相信看維基就能理解了。

    可能的改進:

    1. 之後考慮將 gen_file_exclude.pyexclude, replace, bypass 三個參數開放在執行 make 時客製化。
    2. 追求更加自動化: 自動偵測指定之不想被屏蔽之檔案可能會被哪些 pattern 屏蔽。
  • make clean 命令: 嘗試刪除目標資料夾下 .vscode 內的設定檔

  • 針對 c_cpp_properties.json,新增自動偵測以下客製化設定

    • 編譯器路徑
    • 使用之 C/C++ 語言標準
      • 會採用最低標準,比如若有 gnu11gnu99,則會採用 gnu99
      • 若都沒有指定語言標準,則預設標準為 c99,這可能會使 IntelliSense 有些許不符合預期。

    方法為使用 regex,正好把以前不願面對的它重新學了一遍。

    以下保留學習的參考連結給也想學 regex 的人。