何とは言わない天然水飲みたさ

kernel config 生成(略記)のためのシェルスクリプト

この記事は、 Gentoo Advent Calendar 2016 の6日目の記事です。
昨日の記事は Gentoo on Windows 10 (Gentoo Advent Calender 2016 5日目)でした。

概要

そろそろ普段使いのラップトップが壊れそうなので、別マシンへの移行の準備をしておきたさが高まっています。 Windows 8.1 を gentoo で上書きして以来、これまで様々な設定をしてきましたが、 /etc にあるファイルの多くはコピーで済まされる設定ファイル[0]なので、移行にあたってそこまで問題になりません。

問題なのは、カーネルの .config です。 ハードウェア由来の設定が多く項目も多いため、安直にコピーしてちょっと改変すれば使えるというものでもありません。 そこで、以下のようなスクリプトが欲しくなったため、簡単に書いてみたというお話です。

  • 設定の理由がわかる(コメントが書ける)
  • 値の無効化(CONFIG_FOO=n)ができる
  • 興味のない項目は、何も書かずともよろしく設定しておいてほしい
  • 記述した設定に無効なもの(あるいは上書きされたもの)があれば、警告してほしい

対象読者

大雑把には、既に gentoo を使っていたりカーネルをコンパイルしている人向けです。

  • gentoo を使っていて、カーネルの .config をそこそこの頻度で弄っている人
  • 複数台のマシンで gentoo を使っている人
  • その他 distro でも、カーネルを野良でビルドしたい人

残念ながら gentoo の布教記事ではございません。

merge_config.sh

……と思って作ろうとしていましたが、よくよく調べてみると、 Linux カーネルのソース(gentoo であれば /usr/src/linux など)の scripts/kconfig/merge_config.sh に、かなり近い目的のものがありました。 中身を読んでみると、ファイル名のとおり、設定ファイルとして有効なファイル同士の merge 用途のものであり、コメントやインデント、省略記法などは使えないようです。 そこで方針を変えて、書きやすい設定を merge_config.sh で使えるよう変換して吐き出すスクリプト(というか実質正規表現)を書くことにしました。

追加の要件は以下のような感じです。

  • インデントを使える
  • コメントを書いても無効/不正扱いされない
  • CONFIG_FOO=n のように書いても警告されない
  • CONFIG_ のプレフィックスを省略できる

成果物

小さなコードなので、ライセンスは CC0 とします。 (要するにパブリックドメインです。)

#!/bin/sh

# These codes are licensed under CC0.
# https://creativecommons.org/publicdomain/zero/1.0/deed.ja

# Set up parameters.
DATETIME="$(date '+%F-%H%M%S')"
: ${SRC_DIR:=/usr/src/linux}
cd "$(dirname "$0")"
DIR="$(pwd)"
CONFFILE_SRC="${DIR}/config"
CONFFILE_TEMP="${DIR}/.temp.$(basename "${CONFFILE_SRC}")"

sed -r -n \
    -e 's/^\s*(CONFIG_)?([A-Za-z0-9_]+=.+)$/CONFIG_\2/' \
    -e 's/^\s*# (CONFIG_[A-Za-z0-9_]+) is not set$/\1=n/' \
    -e '/^CONFIG_/!d' -e 's/^(CONFIG_[^=]+)=n$/# \1 is not set/' \
    -e 'p' \
    "${CONFFILE_SRC}" | sort -u >"${CONFFILE_TEMP}"

# `merge_config.sh` should be executed in the kernel source directory.
cd "${SRC_DIR}"
cp -f .config ".config-${DATETIME}"

# -n: use allnoconfig instead of alldefconfig
# -r: list redundant entries when merging fragments
./scripts/kconfig/merge_config.sh -r "${CONFFILE_TEMP}"

rm "${CONFFILE_TEMP}"
genconfig.sh

というか実質 sed スクリプト。

つかいかた

  1. スクリプトを保存して x フラグを立てる
  2. スクリプトと同じディレクトリに config というファイルを作って設定を書く
  3. sudo ./genconfig.sh
  4. カーネルコンパイル。 私の場合: sudo genkernel --makeopts="-j4" --splash --no-clean --oldconfig all [1]

ソースコードのディレクトリはデフォルトで /usr/src/linux ですが、 SRC_DIR=/path/to/linux/source ./genconfig.sh のようにすれば別の場所を使うことができます。 そのディレクトリのパーミッションによっては、 sudo は必要ないかもしれません。

config ファイルの内容

文法はシンプルです。

  • 空白文字によりインデント可能
  • # で行コメント
    • ただし ^\s*# (CONFIG_[A-Za-z0-9_]+) is not set$ にマッチする行は # \1 is not set に変換される
    • # CONFIG_FOO is not set のような行を保存するためです
  • CONFIG_FOO=barFOO=barCONFIG_FOO=bar に変換される
    • 要するに CONFIG_ を省略可能
  • CONFIG_FOO=n# CONFIG_FOO is not set に変換される
    • merge_config.sh に「設定が反映されていない」という警告を出されるのを防ぐためです

で、たとえば実際に私が使っている設定(の抜粋)は以下のようになります。

    # www-client:google-chrome-46.0.2490.80_p1:
    # > CONFIG_COMPAT_VDSO causes segfaults (bug #556286)
        ## config COMPAT_VDSO
        ##      def_bool n
        ##      prompt "Disable the 32-bit vDSO (needed for glibc 2.3.3)"
        COMPAT_VDSO=n
    # www-client/google-chrome-54.0.2840.71:
    # > USER_NS is required for sandbox to work
        ## config USER_NS
        ##      bool "User namespace"
        USER_NS=y

    # media-sound/pulseaudio-7.0:
    # > A preallocated buffer-size of 2048 (kB) or higher is recommended for the HD-audio driver!
        ## config SND_HDA_PREALLOC_SIZE
        ##      int "Pre-allocated buffer size for HD-audio driver"
        SND_HDA_PREALLOC_SIZE=2048
        ## config TMPFS
        ##      bool "Tmpfs virtual memory file system support (former shm fs)"
        TMPFS=y
            ## config TMPFS_POSIX_ACL
            ##      bool "Tmpfs POSIX Access Control Lists"
            TMPFS_POSIX_ACL=y

    # sys-apps/lm_sensors-3.4.0_p20160725:
    # > sensors-detect requires CONFIG_I2C_CHARDEV to be enabled.
        ## config I2C_CHARDEV
        ##      tristate "I2C device interface"
        I2C_CHARDEV=m
            # Intel CPU.
            ## config SENSORS_CORETEMP
            ##      tristate "Intel Core/Core2/Atom temperature sensor"
            SENSORS_CORETEMP=m
config の例(抜粋)

CONFIG_COMPAT_VDSOCONFIG_I2C_CHARDEV など、名前からは何故これを有効/無効にしたのかわからないような設定は結構ありますが、こうしてコメントとインデントで整理して書いてやることで、設定の意図や必要性がある程度明確にわかるようになります。 私は面倒だったので使いませんでしたが、 vim などであれば fold marker を使って折り畳みできるようにすれば、より読み易くなるかもしれません。

解説

merge_config.sh

先述の通りですが、大雑把に言うと、既に .config として使えるファイルを複数指定すると、それらを merge して、ついでに指定されなかった項目を自動でよろしく設定してくれるものです。

そして、私のスクリプトがあてにしている、自動でよろしく設定する部分は、シェルスクリプトではなく make によって行われます[2]

# Use the merged file as the starting point for:
# alldefconfig: Fills in any missing symbols with Kconfig default
# allnoconfig: Fills in any missing symbols with # CONFIG_* is not set
make KCONFIG_ALLCONFIG=$TMP_FILE $OUTPUT_ARG $ALLTARGET
/usr/src/linux-4.8.11-gentoo/scripts/kconfig/merge_config.sh 151〜154行

$ALLTARGET は、デフォルトでは alldefconfig ですが、 -n オプションを付けて merge_config.sh を起動すると allnoconfig となります。

make alldefconfig

名前そのままですが、 alldefconfig は、与えられた config において未指定の項目に、デフォルト値を設定します。 同様に allnoconfig は、与えられた config において未指定の項目のすべてに、 n (no) を設定します。

他にも、 allyesconfig とか randconfig とかいろいろあります。 詳しくは make help とかで見られます。

$ pwd
/usr/src/linux-4.8.11-gentoo
$ make help
Cleaning targets:
  clean           - Remove most generated files but keep the config and
                    enough build support to build external modules
  mrproper        - Remove all generated files + config + various backup files
  distclean       - mrproper + remove editor backup and patch files

Configuration targets:
  config          - Update current config utilising a line-oriented program
  nconfig         - Update current config utilising a ncurses menu based
                    program
  menuconfig      - Update current config utilising a menu based program
  xconfig         - Update current config utilising a Qt based front-end
  gconfig         - Update current config utilising a GTK+ based front-end
  oldconfig       - Update current config utilising a provided .config as base
  localmodconfig  - Update current config disabling modules not loaded
  localyesconfig  - Update current config converting local mods to core
  silentoldconfig - Same as oldconfig, but quietly, additionally update deps
  defconfig       - New config with default from ARCH supplied defconfig
  savedefconfig   - Save current config as ./defconfig (minimal config)
  allnoconfig     - New config where all options are answered with no
  allyesconfig    - New config where all options are accepted with yes
  allmodconfig    - New config selecting modules when possible
  alldefconfig    - New config with all symbols set to default
  randconfig      - New config with random answer to all options
  listnewconfig   - List new options
  olddefconfig    - Same as silentoldconfig but sets new symbols to their
                    default value
  kvmconfig       - Enable additional options for kvm guest kernel support
  xenconfig       - Enable additional options for xen dom0 and guest kernel support
  tinyconfig      - Configure the tiniest possible kernel

(以下略)
kernel の make help

もし私のスクリプトで allnoconfig を使いたい場合、 ./scripts/kconfig/merge_config.sh の呼び出しで -n を追加しましょう。

./scripts/kconfig/merge_config.sh -n -r "${CONFFILE_TEMP}"
genconfig.sh の該当行をこのように変更する

まとめ

  • カーネルコンフィグ管理を手助けする簡単なスクリプトを書いた
    • make oldconfiggenkernel --menuconfig --oldconfigall で積み重ねてきた秘伝のタレを棄てて、設定はレシピから作りましょう
  • gentoo でカーネルをコンパイルすると言うとビビる人がいますが、実際のところコンフィグ作って make すればおしまいなので、こわくないです
    • なんなら項目ぜんぶ y (yes) とか m (module) とかにしとけば基本どうにかなりそう (コンパイル時間延びるけど)
  • gentooLFS の他にカーネルコンパイルが必要そうな distro を知りませんが、この記事の内容は gentoo でなくとも適用できます。 gentoo AdC なのにごめんなさい。

おまけ

portage のログ

/var/log/portage/elog/summary.log に、過去 portage で emerge されたパッケージのメッセージが記録されています。 これを見て検索をかければ、どのパッケージがどのような設定を要求しているか、ある程度[3]わかるでしょう。

vim で見易いよう、簡単な syntax を用意しておきました。 ご活用ください。

molokai で syntax を適用した場合の見た目
/var/log/portage/elog/summary.log を molokai colorscheme で見た例
au BufRead,BufNewFile */portage/elog/summary.log set filetype=portage-summary-log
~/.config/nvim/ftdetect/portage-summary-log.vim
if exists("b:current_syntax")
    finish
endif

syn case match
syn spell notoplevel


syn match logPackageAtom excludenl '[a-z-_]\+/[a-zA-Z0-9_\.-]\+' contained containedin=logPackageLine
syn match logPackageLine excludenl '^>>> Messages generated by process .*:$' contains=logPackageAtom

syn match logKernelConfig excludenl 'CONFIG_[A-Z0-9_]\+' containedin=ALL

syn match logQA excludenl '^QA: .*$'
syn match logQANoticeLabel excludenl '^QA Notice:'me=e-1
syn match logWarning excludenl '^WARN: .*'
syn match logError excludenl '^ERROR: .*'


hi link logPackageAtom Keyword
hi link logPackageLine Comment

hi link logKernelConfig Identifier

hi link logQA Label
hi link logQANoticeLabel Label
hi link logWarning Label
hi link logError Label

let b:current_syntax = "portage-summary-log"
~/.config/nvim/ftdetect/portage-summary-log.vim
適当なところに置いて使ってくださいな

次に欲しい機能

現状でそこそこ満足なので、今のところこれ以上リソースを注ぐつもりはありませんが、改良点があるとしたら以下の点です。

複数ファイルのマージ

先述のとおり merge_config.sh は複数ファイルのマージ機能を持っているので、たとえば設定ファイルを基礎用、ハードウェア用、各パッケージ用、各マシン特有設定など、複数に分割して管理できるようにするのは難しくないでしょう。

私がそれをしなかったのは、単にファイルをスクリプトの引数でとれるようにすると、パスの解決がちょっと面倒かもと思ったからです。 でも今よく考えたら readlink とかで絶対パス取得できそうなのでそのうち実装するかも。

依存性解析

たとえば CONFIG_FOO が前提条件として CONFIG_BAR を要求している場合、そっちも勝手に有効化してくれたら嬉しいです。 ですが実際には、 m にするか y にするか、或いは複数の OR 条件がある場合どれを満たすべきか、など自由度が高めなので、全自動にはできそうにありません。

あと、依存関係を取得するには Kconfig を読むことになるので、シェルスクリプトだと荷が重いかもしれません。 不可能ではないでしょうが、私なら別の言語を使います。

追記1 (2016/12/06 00:12): kernel-config-check.py

kernel-config-check.py というものを公開している方がいらっしゃるようで、これを使うと、インストール済のパッケージが要求するカーネルオプションを網羅できるようです。 すごい! 便利! 圧倒的感謝!!

kernel-config-check.py