Linux」カテゴリーアーカイブ

[Linux] device tree について

2.6.32 から 4.4 へ

Linux driver の参考書は kernel 2.6.32 辺りの知識で書かれていて,組込みの世界ではあまり新しい kernel を積極的に取り込まないこともあって,私がいる部門でも長い間内部で保持されてきた kernel を使って driver を書いていたのですが,独自の分岐で進化した kernel を maintainance する人材もいなくなり,昨年からいろいろ一新する上で kernel も vanilla kernel に追従することになりました.

昨今では自社向けソースをすべて内部で抱え続けるのも難しく,外に公開していける部分は OpenSource に貢献して協力関係を築いていくのも至極当然の流れでしょう.何故今までそうして来なかったのか不思議なくらいですが…という前置きはおいといて,そんな 2.6 時代の知識から突然最新 LTS の v4.4 の開発手法や kernel API に刷新する必要が出てきたわけです.

device tree とは

最もインパクトがあったのは device tree の導入でした.eLinuxの解説に記載がありますが,OpenFirmware の ePAPR 仕様で規定されているものを元にしたデバイス固有情報を記述するフォーマットです.Linux 以外にも FreeBSD や U-boot でも採用されていて,標準化の動きがあります.
(OpenFirmware とか PAPR と聞くと Mac 互換機の CHRP や BeOS を思い出してしまうのですが…)

実際の記述方法は devicetree.org の文法解説や Linux source の Documentation/devicetree/bindings/ に記載されていますが,実際の arch/arm/boot/dts/ 以下の各 SoC 向けソースが参考になります.

device tree の driver への取り込み方

device tree を導入すると,driver 側の記述の仕方も変わってきます.元々 module_init() や module_exit() で最初と最後を書くだけだったのが,device tree から情報を取り込む操作が必要になるわけですが,その操作は platform driver の framework がベースになります.

例えば,以下のように device tree に foo driver の記述を行うとします.識別文字列の他に,使いたいレジスタアドレスとサイズが書かれています.

/ {
    foo {
        compatible = "hoge,foo";
        reg = <0x12340000 0x200>;
    };
}; 

platform driver は,起動関数・終了関数・識別テーブルを定義します.

static struct platform_driver foo_driver = { 
    .probe  = foo_probe,
    .remove = foo_remove,
    .driver = { 
        .name = "foodriver",
        .of_match_table = foo_of_match,
    },
};
module_platform_driver(foo_driver);

識別テーブルの方は compatible で device tree に適合する識別文字列を定義します.

static const struct of_device_id foo_of_match[] = { 
    { .compatible = "hoge,foo",},
    {},
};
MODULE_DEVICE_TABLE(of, foo_of_match);

このように定義しておいて,起動関数 foo_probe() では device tree の情報を取り込んで,実際の動作に反映させていきます.

static void __init foo_probe(struct platform_device *pdev)
{
    sturce resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    void __iomem *vptr = devm_ioremap_resource(res);
    :
}

この他にも device tree 情報を取り込む platform 関数や of 関数はたくさんあり,方法もさまざまです.
一つサンプルを作って自分なりに確立しておくのが良いかと思います.

[Linux] process と CPU割り当て

CPU migration

Linux では SMP 構成をとる場合,できるだけ CPU を公平に使うようにスケジューリングされます.そのため,各プロセスの CPU 使用率を元に再スケジューリングのタイミングでプロセスの CPU migration (CPU 間の移動)が発生します.

migration で CPU 間をプロセスが移動するためには,一度動作を止めてコンテキスト(レジスタや状態情報など)を移動させて再開,という処理が行われますが,キャッシュ共有する「CPUコア間」に比べて共有しない「CPUクラスタ間」は移動にかかる時間が掛かるため,移動に重みづけしてスケジューラが次の CPU を判断します.

migration 動作を見る

ではプロセッサの migration でどうやって動いてるんだろう,と*手っ取り早く*調べたいと思ってたところ,
busybox の ps では CPU 割り付けが扱われないため,procps パッケージの ps を使うと確認できました.

$ ps -eo pid,psr,comm

ということで,下記のような shell script で回しておけば,出力ログを加工してグラフ化することで遷移の可視化もできます(かなり古典的手法ですが).

#!/bin/sh
(
    while :; do
        ps -eo pid,psr,comm | grep $PROG;
    done
) | awk '{print $1,",",$2}'

CPUやスケジューリングポリシーを指定

プログラムで直接 sched_setaffinity() や sched_setscheduler() を呼び出せばいいのですが,常にプログラムを書き換えられるわけではないので,できれば shell で指定できる方が都合がいいです.util-linux パッケージを使うと可能になります.

taskset で起動プロセスの CPU を固定することができます.例えば CPU1 でプロセスを起動したい場合は
以下のように指定します.

$ taskset -c 1 PROG

chrt で起動プロセスのポリシーを指定することができます.例えば SCHED_FIFO の priority=50 で
起動したいなら次のようにします.

$ chrt -f 50 PROG

また,kernel の起動オプションで ‘isolcpus’ を付けてCPUを指定すると,そのCPUはプロセスのCPU割り付け対象から外されます.全ての CPU を指定すると全て CPU0 でプロセスを起動するようになります.SMP 構成ながら Asymmetric に CPU を使うこともできそうです.

isolcpus=0-3

余談ですが…

SMP 構成の組込みシステムの場合,応答速度や処理量で破綻する可能性があるのであまり CPU を移動するのはうれしくないですが,特定の CPU に処理が集中するのも不利になるため適度に分散させたいため,スケジュールポリシーの選択は重要になってきます.

SCHED_FIFO,RR や CPU 割り付けを固定で行うこれまでのリアルタイム設計は複雑なシステムになるほど不都合になるため,SCHED_DEADLINE (EDF) や新たに提案されている Energy-Aware などの heuristic スケジューリングにも期待したいところです.

VMware Player 環境の再構築

システムの置き換え

これまで HDD 2台でそれぞれ Windows, Ubuntu を置いて、Windows から VMware で Ubuntu を起動していました。システムドライブを SSD に置き換えるにあたり、Windows も Ubuntu も SSD に置くことにしたのですが、ここでハマってしまったので記録しておきます。

Windows7 のインストールの際に SSD(250GB) を NTFS と ext4 に分けることを想定してパーティションを設定し、Windows7 のインストール、Windows10への upgrade、VMware Player のインストールまでは特にハマるところはなく進みました。

仮想環境の構築に失敗

VMware Player で仮想マシンを作成し、以前と同じように設定した後、HDD は IDE 物理ディスクの「PhysicalDrive0 のパーティション3(ext4想定の空き)」を指定して Ubuntu Desktop のインストールを開始。

後半で Grub のインストール先を指定しますが、上記インストールパーティションを指定すると失敗します。Grub は MBR にインストールするものですが、VMware は物理ディスクの場合もブート時に MBR を見に行くためそのままでは失敗します。

Grub は基本パーティションの Linux FileSystem にある /boot/grub/ を見に行くため、最初に Linux をインストールしてから Windows をインストールすれば解決しそうですが、Grub から Windows を起動すると、VMware Player の Grub からホスト OS のWindows が選択できてしまうためこれは惨事になるのが予想できます。

もう一つのシステムディスク登場

結局、SSD の MBR に Grub をインストールせずに VMware Player から論理パーティション上の Ubuntu が起動するには、もう一つディスクを用意して MBR を Grub にすればよいわけですが、仮想環境なので仮想ディスクを使えばよいわけです。

VMware Player で「仮想マシンの設定の編集」→「(ハードウェアタブ)追加」→「ハード ディスク」で通常のディスクを作成し、Grub が起動できればいいので “とりあえず” Ubuntu Server をインストールしました。Grub はこの仮想ディスクである /dev/sdb の MBR にインストールします。

物理パーティションOSの起動

この時点で VMware Player から Grub 経由で Ubuntu Server が起動しますし、物理パーティションの Ubuntu Desktop も認識しているので、メニューから選択すれば起動できます。しかしこちらがメインなので timeout してすぐに起動するように設定します。Grub の設定を行う Ubuntu Server 側で以下の設定値を変更します。

$ sudo vi /etc/default/grub
-GRUB_DEFAULT=0
+GRUB_DEFAULT=4
-GRUB_TIMEOUT=10
+GRUB_TIMEOUT=3
$ sudo update-grub

GRUB_DEFAULT はメニューのデフォルトの選択値です。私の環境では4つ目に Ubuntu Desktop があったので “4” を指定しています。また、GRUB_TIMEOUT は自動選択までの待ち時間で 3秒に設定し直しています。最後に update-grub で /boot/grub/grub.cfg を更新します。

これでめでたく SSD の Ubuntu Desktop が起動できました。これまで使っていた HDD も「IDE 物理ディスク」として設定してマウントすることでそのまま使うことができます。