MIRACLE
メールサービス申込 ユーザー登録 パートナー情報
お問い合わせ FAQ サイトマップ
MIRACLE LINUXの特長 製品紹介 サービス案内 購入 サポート 技術フォーラム

« 2006年5月 | メイン | 2006年7月 »

2006年6月30日 (金)

タブをつくる

タブと聞くとあなたは、「4文字スペース空けるやつ」と、「GUIのウィンドウを構成するパーツで、FireFoxとかのタブブラウザみたいなもの」のどちらをイメージしますか?
私はどちらかというと「スペース空けるやつ」をイメージする側ですが、今日は、GUI側のお話です。
X WindowでGUIを構成するときに利用されるOpenMotifでもタブを使うことができます。
-----
$ man XmTabStack
XmTabStack(3X)                                                 XmTabStack(3X)

NAME
       XmTabStack - The TabStack widget class

SYNOPSIS
       #include <Xm/TabStack.h>

DESCRIPTION
       The  XmTabStack  widget  manages a group of widgets such that only one
       widget in the group is visible at a time.  Each  child  is  associated
       with  a "tab" that displays a text label and/or a pixmap. By selecting
       the "tab" the user interactively determines which child is  displayed.
       This widget exhibits behavior similar to the Microsoft Windows(TM) Tab
       Control.
....
-----
といった形で説明があります。

普通の開発者の立場だと、このウィジェットを利用して、いろいろなGUIのコンポーネントを作成すると思います。
ですが、今日はちょっと視点を変えて、このタブがOpenMotifの中でどのように作られているのか見てみたいと思います。

タブを使うと、このようなGUIのウィンドウを作ることができます。

Tab1_2

タブの表示に関わっているのは、OpenMotifのlib/Xm/TabStack.c、TabBox.cのあたりのソースです。
TabStack.cは、タブのパーツのうち、下半分の表示を担っています。

そして、今日のメインは、TabBox.cで、このソースが、タブの切り替えに使う上半分のタブの部分の処理を行っています。

X Windowでは、隠されていたウィンドウが前面に出されて表示されるとき、Exposeというイベントが発生します。

TabBox.cでは、それぞれのイベントが発生したときに呼び出される関数が定義されていますが、そのうちexposeイベントには、"Redisplay()"という関数が定義されています。

------

    /* destroy            */    NULL,
    /* resize             */    NULL,
    /* expose             */    Redisplay,
    /* set_values         */    NULL,
    /* set values hook    */    NULL,
-------

つまり、ウィンドウの再描画は、このRedisplay()で行われているということが分かりますので、Redisplay()を見ていくことにします。

で、早速、Redisplay()の動作をおおざっぱに書くと、

------
Redisplay()
  ->   Redisplaytabs()
     ->       DrawTab()

------

となっていることが分かります。

RedisplayTabs()の中を見てみると、次のようになっています。

-----

    geom = XmTabBox__actual(tab);
    for( i = 0; i < count; ++i )
    {
        if( XiRectInRegion(region, geom[i].x, geom[i].y, geom[i].width,
                           geom[i].height) )
        {
            info = _XmTabbedStackListGet(XmTabBox_tab_list(tab), i);

            DrawTab(tab, info, &(geom[i]),
                    (Boolean)(XmTabBox__selected(tab) == i),
                    (Boolean)(XmTabBox__keyboard(tab) == i));
        }
    }
-----

countには、タブが何個あるか入っています。つまりfor文で、タブの数だけループをまわして、それぞれのタブに対して、DrawTab()を呼び出しているのです。

ただし注意点として、DrawTab()を呼び出す前に、XiRectInRegion()で、そのタブが、再描画の必要な領域にあるかどうか確認しています。
なので、もともと他のウィンドウに隠されていなかったタブに関しては、改めて再描画はされないということです。

次回は、実際のDrawTab()の処理を見てみたいと思います。

2006年6月29日 (木)

1度だけboot

かつてブートローダとしてliloが主流だったころ、カーネルのテストをするときには、"lilo -R"にお世話になりました。-Rオプションを付けると、次の再起動の1度だけ指定したカーネルで立ち上げることができますが、lilo.confは変更しなくて良いので、便利なコマンドでした。

MIRACLE V4.0で同じようなことをしたいとき、例えばGRUBの2つ目のエントリ(0,1,2,3,...の1に相当)のカーネルで起動したいときは、次のようにします。
# grub
grub> savedefault --default=1 --once

あと、起動に失敗した時に備えて、kernelオプションに、"panic=10"などを付け加えておくと、panicしたときに勝手に元のカーネルで再起動してくれて便利です。

2006年6月28日 (水)

最強の割り込み

カーネルストール。これは厄介な敵です。

カーネルPANICならば、最近のカーネルに備わっているdump機能が働いて、カーネルダンプが取れたりするので、さっさとrebootするのを待つばかりです。

一方でカーネルストールの場合、よくあるのはログインしようとしてもなぜかプロンプトが返ってこないとか、キーは入力できないけどマウスカーソルの位置だけ動くとか、微妙感漂う状況に陥ることがあります。

今日はそんな時の強い味方、NMIについて調べてみましょう。

まず、NMIって何でしょう。一般人の味方 Googleで調べてみます。

http://www.nifty.com/webapp/digitalword/word/020/02083.htm

こんなページがひっかかりました。何々? 「Non-Maskable Interrupt - マスク不可能な割り込みのこと。ソフトウェアでこのNMIを禁止することはできない。」と書かれています。

「割り込み」って何でしょう?  いろいろなデバイスは、自身が働いたことをCPUに伝えるために、「割り込み」という仕組みを使います。CPUが処理している最中に、「割り込み」、「割り込み」、「割り込み」と好き勝手に割り込むわけです。一方でCPUはコンピュータの中の王様(KING)なので、「勝手な割り込みは許さーん」と割り込みを禁止することができます。これが普通のデバイスと割り込みの関係です。

ところがNMIは違います。その名の通り、割り込みを禁止することができないのです。NMIによって割り込まれると、CPUは否応なしにそのNMI割り込みを処理しなければなりません。そのため、NMI割り込みは最強なのです。

このNMIを利用した機能に、「NMI watchdog」と呼ばれる機能があります。NMI watchdogとは、NMIの割り込みを利用して、OSのストールを検出するための機能です。

まず、通常動作中、タイマー割り込みと呼ばれる定期的な割り込みの中で、割り込み回数のカウンタが増えていきます。

linux-2.6.9-11.19AX/arch/i386/kernel/apic.c
-----
void smp_apic_timer_interrupt(struct pt_regs regs)
{
        union irq_ctx   *curctx;
        union irq_ctx   *irqctx;
        int             cpu;
        u32             *isp;

        /*
         * the NMI deadlock-detector uses this.
         */
        cpu = smp_processor_id();
        irq_stat[cpu].apic_timer_irqs++; ← ここでカウンタ増加
-----

この処理とは別に、定期的なNMI割り込みの中で、このカウンタの数値を確認します。
linux-2.6.9-11.19AX/arch/i386/kernel/nmi.c
-----
void nmi_watchdog_tick (struct pt_regs * regs)
{
        /*
         * Since current_thread_info()-> is always on the stack, and we
         * always switch the stack NMI-atomically, it's safe to use
         * smp_processor_id().
         */
        int sum, cpu = smp_processor_id();
        sum = irq_stat[cpu].apic_timer_irqs;
        if (last_irq_sums[cpu] == sum) {  /* もし前回と同じ値なら、タイマー割り込みが処理されていない */
                /*
                 * Ayiee, looks like this CPU is stuck ...
                 * wait a few IRQs (5 seconds) before doing the oops ...
                 */
                alert_counter[cpu]++;
                if (alert_counter[cpu] == 30*nmi_hz)
                        die_nmi(regs, "NMI Watchdog detected LOCKUP");  /* 問題発生を検出 */
        } else {
                last_irq_sums[cpu] = sum;
                alert_counter[cpu] = 0;
        }
-----

上記のコードでは、NMI割り込みのたびに、カウンタの数値をチェックし、カウンタの数値が変わらない時間が一定時間続くと、die_nmi() を呼び出しNMI独自のエラー処理を行うというものです。例えば割り込みが禁止された状態でカーネルがストールすると、タイマー割り込みの処理もできないので、カウンタの数値を変更する処理が行われません。しかし、NMIは割り込み禁止にすることができないので、たとえタイマー割り込みが行えなくとも、NMI割り込みによるカウンタのチェックは有効なのです。

こうして、カーネルは自分自身がストールしたことを検出し、カーネルダンプを取ったり、rebootしたりといった荒技を行うことで、システムの状態を元に戻すことができます。

さてこのNMI watchdogの機能ですが、使う際に1つだけ注意があります。それは、使えるマシンが限られているということです。カーネルのブートオプションに、nmi_watchdog=1、もしくはnmi_watchdog=2として付けてみて、/proc/interruptsを確認してみてください。

-----

$ cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
  0:        139          0          0  184539452    IO-APIC-edge  timer
  1:          0          0          0         74    IO-APIC-edge  keyboard
  2:          0          0          0          0          XT-PIC  cascade
  9:          0          0          0          0   IO-APIC-level  acpi
14:          0          2          0         62    IO-APIC-edge  ide0
15:          0          0          0          0    IO-APIC-edge  libata
16:          0          0          0          0   IO-APIC-level  usb-uhci
18:          0          0          0          0   IO-APIC-level  usb-uhci
19:          0          0          0          0   IO-APIC-level  usb-uhci
23:          0          0          0          0   IO-APIC-level  ehci-hcd
24:          0          0          0    6935854   IO-APIC-level  eth0
30:          0          0          0    1676288   IO-APIC-level  aic79xx
31:          0          0          0         30   IO-APIC-level  aic79xx
NMI:    1481143    1360317          0          0
LOC:  184534393  184534414  184534414  184534414
ERR:          0
MIS:          0
-----

NMIの数値が増えていれば、NMI割り込みが発生していますので、NMI watchdogの機能を利用することができます。この数値が0のままのサーバの場合、残念ながらNMI watchdogの機能を利用することができません。

参考: http://slacksite.com/slackware/nmi.html

 

2006年6月26日 (月)

カーネルアップデート危機一発

最近はkernelパッケージをアップデートすると、カーネルがインストールされ、GRUBの設定が更新され、さらにはデフォルトの起動カーネルまで変更されます。
あれは昔、MIRACLE V2.0の頃の手順といえば、
1. kernelパッケージをinstall
2. lilo.confを手動で書き換え
3. /sbin/liloを実行
という手順が必要で、3を忘れた日には新しいカーネルで立ち上がらず「撃沈!」な場面に遭遇することがありました。rpmのオプションで-Uvhなどで完全に置き換えてしまった日には、とても悲しい出来事となったりもしました。

それでは最近のkernelパッケージはどこが変わったんでしょうか? 実際にkernelパッケージをインストールした時に、何が行われているか見てみましょう。
で、いきなりですかkernelパッケージのインストールスクリプトの内容を見てみます。
-----
# rpm -q --scripts kernel
preinstall scriptlet (using /bin/sh):
/sbin/modprobe loop 2> /dev/null > /dev/null  || :
exit 0
postinstall scriptlet (using /bin/sh):
[ -x /usr/sbin/module_upgrade ] && /usr/sbin/module_upgrade
[ -x /sbin/new-kernel-pkg ] && /sbin/new-kernel-pkg --package kernel --mkinitrd --depmod --install 2.6.9-11.19AX
preuninstall scriptlet (using /bin/sh):
/sbin/modprobe loop 2> /dev/null > /dev/null  || :
[ -x /sbin/new-kernel-pkg ] && /sbin/new-kernel-pkg --rminitrd --rmmoddep --remove 2.6.9-11.19AX
-----
中身を整理すると、pre, post, preunの3箇所のスクリプトが設定されていることが分かります。
1. pre
   loopモジュールをmodprobeでロードしているだけのようです。
2. post, preun
   module_upgradeというコマンドとnew-kernel-pkgというコマンドを実行しています。

new-kernel-pkgでは、カーネルのバージョンが指定されていますので、カーネルのアップデートには、このコマンドが大きく関わっていそうです。こんなときには、迷わずfileコマンドです。
-----
# file /sbin/new-kernel-pkg
/sbin/new-kernel-pkg: Bourne-Again shell script text executable
-----
ラッキー! シェルスクリプトなら、いろいろ手間をかけずにそのまま読めてしまいます。思い立ったが吉日ということで、早速眺めてみます。
-----
install() {
    # XXX kernel should be able to be specified also (or work right on ia64)
    if [ ! -f $bootPrefix/$kernelName-$version ] ; then
        [ -n "$verbose" ] && echo "kernel for $version does not exist, not running grubby"
        return
    fi
...
----
さっそくinstall()から始まる関数があります。たぶんインストール時に実行されるのでしょう。
さらにinstall()の中を見ると...
------
    if [ -n "$cfgGrub" ]; then
        [ -n "$verbose" ] && echo "adding $version to $grubConfig"

        if [ -n "$banner" ]; then
            title="$banner ($version)"
        elif [ -f /etc/asianux-release ]; then
            title="$(sed 's/ release.*$//' < /etc/asianux-release) ($version)"
        else
            title="Asianux ($version)"
        fi
        /sbin/grubby --add-kernel=$bootPrefix/$kernelName-$version $INITRD    \
                     --copy-default $makedefault --title "$title"           \
                     --args="root=$rootdevice $kernargs"            \
                     --remove-kernel="TITLE=$title"
    else
        [ -n "$verbose" ] && echo "$grubConfig does not exist, not running grubby"
    fi
------
ということで、ConfigファイルがGrubだったら...の条件文がありました。この中で、grubbyというコマンドにたくさんのオプションを指定しています。どうやらこのgrubbyコマンドが重要な役割を担っているようです。

よく知らないコマンドが出てきたときには、manコマンド。これ常識ですね。

-----

$ man grubby

-----

をすると、

-----

GRUBBY(8)                                                           GRUBBY(8)

NAME
       grubby - command line tool for configuring grub, lilo, and elilo

SYNOPSIS
       grubby [--add-kernel=kernel-path] [--args=args]
              [--bad-image-okay] [--boot-filesystem=bootfs]
              [--bootloader-probe] [--config-file path]
              [--copy-default] [--default-kernel] [--grub]
              [--info=kernel-path] [--initrd=initrd-path]
              [--lilo] [--make-default] [-o path]
              [--remove-kernel=kernel-path]
              [--set-default=kernel-path] [--title=entry-title]

DESCRIPTION
       grubby  is a command line tool for updating and displaying information
       about the configuration files for the grub, lilo,  elilo  (ia64),  and
       yaboot  (powerpc)  boot  loaders.  It is primarily designed to be used
       from scripts which install new kernels and need  to  find  information
       about the current boot environment.
-----

最初の概要だけでも読んでみると... GRUBの設定を行うコマンドラインツールということみたいです。おそらくこのコマンドがGRUBの設定の更新とかを行っているみたいですね。

ということで、kernelパッケージのアップデートにはgrubbyコマンドが大きく関わっているということが分かりました。

あとは、manを読むなり、実際に実行してみるなりして、何をするコマンドか調べてみると良いでしょう。

2006年6月23日 (金)

時間は無限?

マシンを立ち上げるとLinuxカーネルが立ち上がります。そして、マシンを停止するまでカーネルが動きつづけるわけです。この間、カーネルは刻々と時を刻んでいきます。
そこでカーネルでの時間の管理に使われるのが、jiffiesという変数です。
kernel-2.4では、kernel/timer.cで次のように定義されています。
----
unsigned long volatile jiffies __cacheline_aligned;
----

unsigned longなので、32bit環境では、0〜0xffffffffの範囲を表すことができます。
このjiffiesは、timer割り込みと呼ばれる処理の際に1ずつ増加していきます。timer割り込みの頻度は、"HZ"として定数で定義されています。
linux-2.4.21/include/asm-i386/param.h
----
#ifndef HZ
#define HZ 100
#endif
----
ということで、kernel-2.4のIA32環境では、1秒間に100回のタイマー割り込みが発生し、jiffiesが1秒間に100増えていくということになります。

それでは、jiffiesの値は無限に増やせるのでしょうか?
さきほど示したように、32bit環境では、jiffiesの最大値は0xffffffffなので、その時間までしかカウントできないことになります。計算してみると、約497日(1年5ヶ月)で最大値を迎えることになります。これが噂のjiffiesのオーバーフローです。jiffiesがオーバーフローすると、また0に戻ってカウントしていきます。

さて、jiffiesがオーバーフローすると何か困ることがあるでしょうか?
例えば、今の時間がjiffies変数に入っていて、n秒後にタイムアウトを判断するために、次のようなコードを書いたとします。
----

timeout = jiffies + n;

タイマーセット(timeout);

----

数秒後に、タイムアウト時間が過ぎているかどうか調べてみます。

----

if (jiffies < timeout){

   printk("まだタイムアウトしてないよ!\n");

}

----

こんなコードを書いた時、「噂のjiffiesのオーバーフロー」が発生するとどうなるでしょう? timeoutが32bitの最大値ぎりぎり(例えば 0xfffffff9)に設定されたりしていると、オーバーフロー直後にチェックすると、

-----

if ( 3 < 0xffffffff9 )

  → 真なので、「まだタイムアウトしてないよ!」出力。

-----

といったことになって、正しく処理できなくなってしまいます。

そこで、カーネルではそんな悲しい出来事が起きないように、ちょっとした工夫をしています。

それはここです。
linux-2.4.21/include/sched.h

-----

#define time_after(a,b)         \
        (typecheck(unsigned long, a) && \
         typecheck(unsigned long, b) && \
         ((long)(b) - (long)(a) < 0))
----

jiffiesのようなタイムアウトの判断に使うためのマクロが用意されています。ポイントはここですね。
----
((long)(b) - (long)(a) < 0))
----

こういう計算にすると、jiffiesがオーバーフローしても大丈夫ですね。ポイントは「キャスト」です。(キャストといっても装甲が飛んで軽くなるあれ系とは関係ありません。)

kernel 2.4の多くのコードがこのマクロを使うように書き換えられていますが、まだまだ完璧ではないようです。ちなみにkernel 2.6ではjiffiesが64bit化されているので、オーバーフローまで何日かかることやら...。

2006年6月14日 (水)

initrdのその中へ

みなさん、Linuxを使っているとカーネルの入れ替えとかすると思います。
最近はブートローダは、GRUBが主流なので、/etc/grub.confを手で書き換えたりすることもありますよね。そのなかで、kernelといっしょにinitrdも設定しますが、これの中身を覗いたことありますか? 普段は、kernelパッケージのインストール時に自動的に作成されるので、気にする機会は少ないと思います。

しかし、カーネルの起動に失敗して、「/パーティションをmountできません... PANIC!」みたいなメッセージが出てくることがあります。このときには、initrdの中身が大きく関わってきますので、ちょっと覗いてみましょう。

まずは、initrdイメージを展開してみます。もちろんシステムを壊さないように、コピーしてからです。
----
# cd /tmp
# cp /boot/initrd-2.6.9-11.25AXsmp.img .
----

これがいったい何者か分かりませんので、そういったときにはfileコマンドの出番です。
----
# file initrd-2.6.9-11.25AXsmp.img
initrd-2.6.9-11.25AXsmp.img: gzip compressed data, from Unix, max compression
----

どうやらgzipで圧縮されたデータのようなので、解凍してみます。gzipの-dオプションは、ファイルの拡張子が.gzでないと受け付けてくれないので...
----
# mv initrd-2.6.9-11.25AXsmp.img initrd-2.6.9-11.25AXsmp.img.gz
# gzip -d initrd-2.6.9-11.25AXsmp.img.gz
----

さて、解凍したファイルをもういちどfileコマンドで調べてみます。
----
# file initrd-2.6.9-11.25AXsmp.img
initrd-2.6.9-11.25AXsmp.img: ASCII cpio archive (SVR4 with no CRC)
----

どうやら、cpioでまとめられたファイルのようなので、さらに展開します。
----
# mkdir img
# cd img
# cat ../initrd-2.6.9-11.25AXsmp.img | cpio -id
3689 blocks
----

ようやく中身が取り出せたので、ファイル一覧を見てみます。
----
# find .
.
./loopfs
./lib
./lib/jbd.ko
./lib/libata.ko
./lib/sd_mod.ko
./lib/qla2xxx.ko
./lib/qla6312.ko
./lib/qla2300.ko
./lib/scsi_mod.ko
./lib/ext3.ko
./lib/ata_piix.ko
./lib/qla2xxx_conf.ko
./sysroot
./dev
./dev/tty1
./dev/ram
./dev/tty2
./dev/systty
./dev/tty4
./dev/tty3
./dev/console
./dev/null
./bin
./bin/udevstart
./bin/modprobe
./bin/udev
./bin/nash
./bin/hotplug
./bin/insmod
./proc
./init
./sys
./sbin
./etc
./etc/udev
./etc/udev/udev.conf
----

今回のシステムでは、これだけのファイルが入っていました。
この中で注目は、initです。中身を見てみると...
----
#!/bin/nash

mount -t proc /proc /proc
setquiet
echo Mounted /proc filesystem
echo Mounting sysfs
mount -t sysfs none /sys
echo Creating /dev
mount -o mode=0755 -t tmpfs none /dev
mknod /dev/console c 5 1
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mkdir /dev/pts
mkdir /dev/shm
echo Starting udev
/sbin/udevstart
echo -n "/sbin/hotplug" > /proc/sys/kernel/hotplug
echo "Loading scsi_mod.ko module"
insmod /lib/scsi_mod.ko
echo "Loading sd_mod.ko module"
insmod /lib/sd_mod.ko
echo "Loading libata.ko module"
insmod /lib/libata.ko
echo "Loading ata_piix.ko module"
insmod /lib/ata_piix.ko
echo "Loading qla2xxx_conf.ko module"
insmod /lib/qla2xxx_conf.ko
echo "Loading qla2xxx.ko module"
insmod /lib/qla2xxx.ko ql2xmaxqdepth=16 qlport_down_retry=30 ql2xloginretrycount=30 ql2xfailover=1 ql2xlbType=1
echo "Loading qla2300.ko module"
insmod /lib/qla2300.ko
echo "Loading qla6312.ko module"
insmod /lib/qla6312.ko
echo "Loading jbd.ko module"
insmod /lib/jbd.ko
echo "Loading ext3.ko module"
insmod /lib/ext3.ko
echo "Loading ext3.ko module"
insmod /lib/ext3.ko
/sbin/udevstart
echo Creating root device
mkrootdev /dev/root
umount /sys
echo Mounting root filesystem
mount -o defaults --ro -t ext3 /dev/root /sysroot
mount -t tmpfs --bind /dev /sysroot/dev
echo Switching to new root
switchroot /sysroot
umount /initrd/dev
----

先頭が"#!/bin/nash"で始まっていますので、どうやらシェルスクリプトのようですね。そういえば、先ほど展開したファイルの中に、"/bin/nash"がありました。
さらに見ていくとscsi_modやata_piixなどいくつかのSCSIドライバを読み込んでいます。
そして最後に
----
echo Mounting root filesystem
mount -o defaults --ro -t ext3 /dev/root /sysroot
----
として、/パーティションをmountしています。そういえば、マシンの起動時に「Mounting root filesystem」のメッセージを見たことありませんか? あのメッセージはここだったんですね。
つまり、MIRACLE LINUXを起動すると、
1. initrd内のinitスクリプトが実行され
2. ディスクデバイスのドライバがロードされ
3. /パーティションがmount
されるということです。

なので、適切なデバイスドライバがinitrdに含まれていないと、/パーティションをマウントできないので、PANICしてしまうということです。

2006年6月 8日 (木)

Number -17

17番といっても、Redsの赤丸急上昇中 長谷部選手のことではありません。

最近のkernelでは、17という数字に関係した機能が1つあるというお話です。
みなさんは、syslogメッセージで次のようなメッセージを見たことはありますか?
messages:Apr 20 10:50:26 localhost kernel: oom-killer: gfp_mask=0xd0

これは、oom-killer、つまりOut Of Memoryが発生したことを知らせるカーネルのメッセージです。Out Of Memoryとは、カーネル内でのメモリのやりくりがどうにもつかなくてメモリが足りない状況に陥ったことを表します。このような場合、カーネルは仕方ないので、プロセスをkillしてメモリを確保しようとします。そのためoom-killerと呼ばれています。

このとき、カーネルがどのプロセスをkillするかは、いろいろな要素を加味して計算した結果で選択するのですが、必ずしも影響の小さいプロセスをkillしてくれるとは限りません。そのため、時にはアプリケーション内で最も重要なプロセスがkillされる可能性もあります。

そんな事態を少しでも避けたいというシステム管理者の思いが通じたのか、oom-killerによってkillされる対象から特定のプロセスを除外することができるようになりました。

linux-2.6.16/mm/oom_kill.cに次のようなコードがあります。
-----
                if (p->oomkilladj == OOM_DISABLE)
                        continue;
-----
ここで、pはプロセスを表します。この部分は、プロセスのoomkilladj変数がOOM_DISABLEだと、oom_killerの対象からはずすためのコードになっています。

このoomkilladjがどこで設定されるかというと、
linux-2.6.16/fs/proc/base.c
-----
static ssize_t oom_adjust_write(struct file *file, const char __user *buf,
                                size_t count, loff_t *ppos)
{
        struct task_struct *task = proc_task(file->f_dentry->d_inode);
        char buffer[8], *end;
        int oom_adjust;

        if (!capable(CAP_SYS_RESOURCE))
                return -EPERM;
        memset(buffer, 0, 8);
        if (count > 6)
                count = 6;
        if (copy_from_user(buffer, buf, count))
                return -EFAULT;
        oom_adjust = simple_strtol(buffer, &end, 0);
        if ((oom_adjust < -16 || oom_adjust > 15) && oom_adjust != OOM_DISABLE)
                return -EINVAL;
        if (*end == '\n')
                end++;
       task->oomkilladj = oom_adjust; ←ここです。
        if (end - buffer == 0)
                return -EIO;
        return end - buffer;
}
-----
ということで、/procで設定できるようです。
確認のために、/procの各プロセスのファイルを見てみると、
-----
$ ls
attr     cwd      fd        mem      oom_score  stat    task
auxv     environ  loginuid  mounts   root       statm   wchan
cmdline  exe      maps      oom_adj  smaps      status
-----
と、まさにそのもののファイルがありました。

さて、最後になりましたが、OOM_DISABLEの定数を確認します。
linux-2.6.16/include/linux/mm.h
-----
/* /proc/<pid>/oom_adj set to -17 protects from the oom-killer */
#define OOM_DISABLE -17
-----
ということで、-17を設定することでoom_killerから守ることができるという説明も付いてますね。

だからといって全てのプロセスに-17を設定してしまうと、カーネルがメモリが足りなくて困っているのにどうしようもなくなって、もっとひどい事態になると思いますので、設定は慎重に。

2006年6月 5日 (月)

ドライバはデバイスを認識できるか?

前回、あるシステムでaacraidドライバが必要になる例を紹介しました。
今回は、ドライバがデバイスを利用できるかどうか調べてみます。

前回の例と同様にaacraidドライバを例にします。
今回システムに搭載されているデバイスのベンダIDとデバイスIDは、それぞれ0x9005  0x0285でした。

それではいきなりですが、カーネルソースに含まれているドライバのソースをチェックしてみます。今回は、MIRACLE V4.0のkernel-2.6.9-11.25AXを使います。
aacraidのソースは、drivers/scsi/aacraidにあるので、とりあえず"PCI"でgrepしてみます。
-----
$ cd drivers/scsi/aacraid
$ grep PCI *
README:Christoph Hellwig <hch@infradead.org>    (updates for new-style PCI probing and SCSI host registration,
aacraid.h:                                              //       Local  |   PCIName
aacraid.h:      struct pci_dev          *pdev;          /* Our PCI interface */
aacraid.h:#define FSACTL_GET_PCI_INFO                   CTL_CODE(2119, METHOD_BUFFERED)
commctrl.c:     pci_info.slot = PCI_SLOT(dev->pdev->devfn);
commctrl.c:     case FSACTL_GET_PCI_INFO:
comminit.c:      * with the GART on x86-64. I've btw never tried DMA from PCI space
commsup.c: *    Allocate and map the shared PCI space for the FIB blocks used tocommsup.c: *    Free the PCI mappings and the memory allocated for FIB blocks
commsup.c: *    Allocate the PCI space for the fibs, map it and then intialise the
linit.c:        { 0x9005, 0x0285, 0x9005, 0x028a, 0, 0, 18 }, /* ASR-2020ZCR SCSI PCI-X ZCR (Skyhawk) */
linit.c:        { 0x9005, 0x0285, 0x9005, 0x028b, 0, 0, 19 }, /* ASR-2025ZCR SCSI SO-DIMM PCI-X ZCR (Terminator) */
linit.c:        { 0x9005, 0x0286, 0x9005, 0x028c, 0, 0, 20 }, /* ASR-2230S + ASR-2230SLP PCI-X (Lancer) */
linit.c:        { 0x9005, 0x0285, 0x9005, 0x028e, 0, 0, 23 }, /* ASR-2020SA SATA PCI-X ZCR (Skyhawk) */
linit.c:        { 0x9005, 0x0285, 0x9005, 0x028f, 0, 0, 24 }, /* ASR-2025SA SATA SO-DIMM PCI-X ZCR (Terminator) */
linit.c:        { 0x9005, 0x0285, 0x9005, 0x0290, 0, 0, 25 }, /* AAR-2410SA PCI SATA 4ch (Jaguar II) */
linit.c:        { 0x9005, 0x0285, 0x1028, 0x0291, 0, 0, 26 }, /* CERC SATA RAID 2 PCI SATA 6ch (DellCorsair) */
linit.c:        { 0x9005, 0x0285, 0x9005, 0x0292, 0, 0, 27 }, /* AAR-2810SA PCI SATA 8ch (Corsair-8) */
linit.c:        { 0x9005, 0x0285, 0x9005, 0x0293, 0, 0, 28 }, /* AAR-21610SA PCI SATA 16ch (Corsair-16) */
linit.c:        { 0x9005, 0x0285, 0x9005, 0x0294, 0, 0, 29 }, /* ESD SO-DIMM PCI-X SATA ZCR (Prowler) */
linit.c:        { 0x9005, 0x0285, 0x0E11, 0x0295, 0, 0, 30 }, /* AAR-2610SA PCI SATA 6ch */
linit.c:        { 0x9005, 0x0285, 0x1028, PCI_ANY_ID, 0, 0, 41 }, /* Dell Catchall */
linit.c:        { 0x9005, 0x0285, 0x17aa, PCI_ANY_ID, 0, 0, 42 }, /* Legend Catchall */
linit.c:        { 0x9005, 0x0285, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 43 }, /* Adaptec Catch All */
linit.c:        { 0x9005, 0x0286, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 44 }, /* Adaptec Rocket Catch All */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "ASR-2020ZCR     ", 2 }, /* ASR-2020ZCR SCSI PCI-X ZCR (Skyhawk) */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "ASR-2025ZCR     ", 2 }, /* ASR-2025ZCR SCSI SO-DIMM PCI-X ZCR (Terminator) */
linit.c:        { aac_rkt_init, "aacraid",  "ADAPTEC ", "ASR-2230S PCI-X ", 2 }, /* ASR-2230S + ASR-2230SLP PCI-X (Lancer) */
linit.c:        { aac_rkt_init, "aacraid",  "ADAPTEC ", "ASR-2130S PCI-X ", 1 }, /* ASR-2130S (Lancer) */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "ASR-2020SA       ", 1 }, /* ASR-2020SA SATA PCI-X ZCR (Skyhawk) */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "ASR-2025SA       ", 1 }, /* ASR-2025SA SATA SO-DIMM PCI-X ZCR (Terminator) */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "AAR-2410SA SATA ", 1 }, /* AAR-2410SA PCI SATA 4ch (Jaguar II) */
linit.c:        { aac_rx_init, "aacraid",  "DELL    ", "CERC SR2        ", 1 }, /* CERC SATA RAID 2 PCI SATA 6ch (DellCorsair) */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "AAR-2810SA SATA ", 1 }, /* AAR-2810SA PCI SATA 8ch (Corsair-8) */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "AAR-21610SA SATA", 1 }, /* AAR-21610SA PCI SATA 16ch (Corsair-16) */
linit.c:        { aac_rx_init, "aacraid",  "ADAPTEC ", "SO-DIMM SATA ZCR", 1 }, /* ESD SO-DIMM PCI-X SATA ZCR (Prowler) */
linit.c:        register_ioctl32_conversion(FSACTL_GET_PCI_INFO, NULL);
linit.c:        unregister_ioctl32_conversion(FSACTL_GET_PCI_INFO);
----------

いろいろ出てきましたが、真ん中あたりに気になるものがあります。
------
linit.c:        { 0x9005, 0x0285, 0x17aa, PCI_ANY_ID, 0, 0, 42 }, /* Legend Catchall */
linit.c:        { 0x9005, 0x0285, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 43 }, /* Adaptec Catch All */
linit.c:        { 0x9005, 0x0286, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 44 }, /* Adaptec Rocket Catch All */
------
このあたりです。
あれ?  0x9005, 0x0285ですね。
これはもしや....ということで、linit.cを覗いてみます。

-----

/*
* Because of the way Linux names scsi devices, the order in this table has
* become important.  Check for on-board Raid first, add-in cards second.
*
* Note: The last field is used to index into aac_drivers below.
*/
static struct pci_device_id aac_pci_tbl[] = {
        { 0x1028, 0x0001, 0x1028, 0x0001, 0, 0, 0 }, /* PERC 2/Si (Iguana/PERC2Si) */
        { 0x1028, 0x0002, 0x1028, 0x0002, 0, 0, 1 }, /* PERC 3/Di (Opal/PERC3Di) */
        { 0x1028, 0x0003, 0x1028, 0x0003, 0, 0, 2 }, /* PERC 3/Si (SlimFast/PERC3Si */
        { 0x1028, 0x0004, 0x1028, 0x00d0, 0, 0, 3 }, /* PERC 3/Di (Iguana FlipChip/PERC3DiF */
        { 0x1028, 0x0002, 0x1028, 0x00d1, 0, 0, 4 }, /* PERC 3/Di (Viper/PERC3DiV) */
        { 0x1028, 0x0002, 0x1028, 0x00d9, 0, 0, 5 }, /* PERC 3/Di (Lexus/PERC3DiL) */
        { 0x1028, 0x000a, 0x1028, 0x0106, 0, 0, 6 }, /* PERC 3/Di (Jaguar/PERC3DiJ) */
        { 0x1028, 0x000a, 0x1028, 0x011b, 0, 0, 7 }, /* PERC 3/Di (Dagger/PERC3DiD) */
        { 0x1028, 0x000a, 0x1028, 0x0121, 0, 0, 8 }, /* PERC 3/Di (Boxster/PERC3DiB) */
        { 0x9005, 0x0283, 0x9005, 0x0283, 0, 0, 9 }, /* catapult */
        { 0x9005, 0x0284, 0x9005, 0x0284, 0, 0, 10 }, /* tomcat */
        { 0x9005, 0x0285, 0x9005, 0x0286, 0, 0, 11 }, /* Adaptec 2120S (Crusader) */
        { 0x9005, 0x0285, 0x9005, 0x0285, 0, 0, 12 }, /* Adaptec 2200S (Vulcan)
*/
        { 0x9005, 0x0285, 0x9005, 0x0287, 0, 0, 13 }, /* Adaptec 2200S (Vulcan-2m) */
        { 0x9005, 0x0285, 0x17aa, 0x0286, 0, 0, 14 }, /* Legend S220 (Legend Crusader) */
        { 0x9005, 0x0285, 0x17aa, 0x0287, 0, 0, 15 }, /* Legend S230 (Legend Vulcan) */
-----

どうやら、このデバイスドライバが利用できるカードのIDが含まれているようですね。

ということで、デバイスドライバは、利用できるデバイスの一覧を持っているようです。

試しに、Intel Gigabit Ethernetのe1000のソースも見てみましょう。

すると、drivers/net/e1000/e1000_hw.h に次のようなデバイスIDのリストを持っていました。

-----

/* PCI Device IDs */
#define E1000_DEV_ID_82542               0x1000
#define E1000_DEV_ID_82543GC_FIBER       0x1001
#define E1000_DEV_ID_82543GC_COPPER      0x1004
#define E1000_DEV_ID_82544EI_COPPER      0x1008
#define E1000_DEV_ID_82544EI_FIBER       0x1009
#define E1000_DEV_ID_82544GC_COPPER      0x100C
#define E1000_DEV_ID_82544GC_LOM         0x100D
#define E1000_DEV_ID_82540EM             0x100E
#define E1000_DEV_ID_82540EM_LOM         0x1015
#define E1000_DEV_ID_82540EP_LOM         0x1016
#define E1000_DEV_ID_82540EP             0x1017
#define E1000_DEV_ID_82540EP_LP          0x101E
#define E1000_DEV_ID_82545EM_COPPER      0x100F
#define E1000_DEV_ID_82545EM_FIBER       0x1011
#define E1000_DEV_ID_82545GM_COPPER      0x1026
#define E1000_DEV_ID_82545GM_FIBER       0x1027
#define E1000_DEV_ID_82545GM_SERDES      0x1028
#define E1000_DEV_ID_82546EB_COPPER      0x1010
#define E1000_DEV_ID_82546EB_FIBER       0x1012
#define E1000_DEV_ID_82546EB_QUAD_COPPER 0x101D
#define E1000_DEV_ID_82541EI             0x1013
#define E1000_DEV_ID_82541EI_MOBILE      0x1018
#define E1000_DEV_ID_82541ER             0x1078
#define E1000_DEV_ID_82547GI             0x1075
#define E1000_DEV_ID_82541GI             0x1076
#define E1000_DEV_ID_82541GI_MOBILE      0x1077
#define E1000_DEV_ID_82541GI_LF          0x107C
#define E1000_DEV_ID_82546GB_COPPER      0x1079
#define E1000_DEV_ID_82546GB_FIBER       0x107A
#define E1000_DEV_ID_82546GB_SERDES      0x107B
#define E1000_DEV_ID_82546GB_PCIE        0x108A
#define E1000_DEV_ID_82547EI             0x1019
#define E1000_DEV_ID_82573E              0x108B
#define E1000_DEV_ID_82573E_IAMT         0x108C
-----

ちなみにIntelのベンダIDは、8086 です。

ということで、それぞれのデバイスドライバごとにリストの持ちかたは違いますが、利用できるデバイスのIDを持っているということが分かりました。

みなさんも、あるドライバが自分のマシンで使えるかどうか調べてみたいときには、このようにしてドライバのソースを覗いてみてはいかがでしょう。もちろん実際に問題なく動作するかどうかは保証できませんが、少なくとも動作する可能性については確認できます。

2006年6月 1日 (木)

ディスクの認識

MIRACLE LINUXがインストールできない時のほとんどは、ディスクを認識できないというパターンです。それでは、MIRACLE LINUXでのディスクの認識とはどのようになっているのでしょうか?

まずディスクを認識するためには、ディスクが接続されているデバイスを動かすためのデバイスドライバが必要になります。一般的にはSATA、SCSIなどのデバイスドライバが必要です。

このデバイスドライバは、OSが提供するものなので、OSの中にデバイスと、デバイスドライバを対応付けるデータがあります。そのデータというのが、/usr/share/hwdata/pcitableファイルです。
このファイルは、次のような形式になっています。
------------------------------
0x1028  0x0001  "aacraid"
0x1028  0x0002  "aacraid"
0x1028  0x0003  "aacraid"
0x1028  0x0004  "aacraid"
0x1028  0x000a  "aacraid"
0x1028  0x000e  "megaraid_mbox"
0x1028  0x000f  "megaraid_mbox"
----------------------------------------

このデータは、左側から、ベンダID、デバイスID、デバイスドライバのモジュール名を表しています。

OSは、システムに搭載されているデバイスの情報と一致するエントリを探し、必要なデバイスドライバを利用できるように設定します。
システムに搭載されているデバイスの情報は、lspciコマンドで取得します。
------
$ /sbin/lspci
00:00.0 Host bridge: Intel Corporation E7525 Memory Controller Hub (rev 0c)
00:02.0 PCI bridge: Intel Corporation E7525/E7520/E7320 PCI Express Port A (rev 0c)
00:03.0 PCI bridge: Intel Corporation E7525/E7520/E7320 PCI Express Port A1 (rev 0c)
00:04.0 PCI bridge: Intel Corporation E7525/E7520 PCI Express Port B (rev 0c)
00:1d.0 USB Controller: Intel Corporation 82801EB/ER (ICH5/ICH5R) USB UHCI Controller #1 (rev 02)
00:1d.1 USB Controller: Intel Corporation 82801EB/ER (ICH5/ICH5R) USB UHCI Controller #2 (rev 02)
00:1d.2 USB Controller: Intel Corporation 82801EB/ER (ICH5/ICH5R) USB UHCI #3 (rev 02)
00:1d.3 USB Controller: Intel Corporation 82801EB/ER (ICH5/ICH5R) USB UHCI Controller #4 (rev 02)
00:1d.7 USB Controller: Intel Corporation 82801EB/ER (ICH5/ICH5R) USB2 EHCI Controller (rev 02)
00:1e.0 PCI bridge: Intel Corporation 82801 PCI Bridge (rev c2)
00:1f.0 ISA bridge: Intel Corporation 82801EB/ER (ICH5/ICH5R) LPC Interface Bridge (rev 02)
00:1f.2 IDE interface: Intel Corporation 82801EB (ICH5) SATA Controller (rev 02)

00:1f.3 SMBus: Intel Corporation 82801EB/ER (ICH5/ICH5R) SMBus Controller (rev 02)
01:00.0 Ethernet controller: Broadcom Corporation NetXtreme BCM5721 Gigabit Ethernet PCI Express (rev 11)
02:00.0 PCI bridge: Intel Corporation 6700PXH PCI Express-to-PCI Bridge A (rev 09)
02:00.1 PIC: Intel Corporation 6700/6702PXH I/OxAPIC Interrupt Controller A (rev 09)
02:00.2 PCI bridge: Intel Corporation 6700PXH PCI Express-to-PCI Bridge B (rev 09)
02:00.3 PIC: Intel Corporation 6700PXH I/OxAPIC Interrupt Controller B (rev 09)
03:03.0 SCSI storage controller: Adaptec AIC-7902B U320 (rev 10)
03:03.1 SCSI storage controller: Adaptec AIC-7902B U320 (rev 10)
04:01.0 RAID bus controller: Adaptec AAC-RAID (rev 01)
06:04.0 VGA compatible controller: ATI Technologies Inc Radeon RV100 QY [Radeon 7000/VE]
------------

例えば、このRAIDコントローラがどのデバイスドライバで動くのか確認してみたいと思います。

-------

04:01.0 RAID bus controller: Adaptec AAC-RAID (rev 01)

-------

そこでlspci -nでベンダIDとデバイスIDを確認します。
------
$ /sbin/lspci -n
00:00.0 Class 0600: 8086:359e (rev 0c)
00:02.0 Class 0604: 8086:3595 (rev 0c)
00:03.0 Class 0604: 8086:3596 (rev 0c)
00:04.0 Class 0604: 8086:3597 (rev 0c)
00:1d.0 Class 0c03: 8086:24d2 (rev 02)
00:1d.1 Class 0c03: 8086:24d4 (rev 02)
00:1d.2 Class 0c03: 8086:24d7 (rev 02)
00:1d.3 Class 0c03: 8086:24de (rev 02)
00:1d.7 Class 0c03: 8086:24dd (rev 02)
00:1e.0 Class 0604: 8086:244e (rev c2)
00:1f.0 Class 0601: 8086:24d0 (rev 02)
00:1f.2 Class 0101: 8086:24d1 (rev 02)
00:1f.3 Class 0c05: 8086:24d3 (rev 02)
01:00.0 Class 0200: 14e4:1659 (rev 11)
02:00.0 Class 0604: 8086:0329 (rev 09)
02:00.1 Class 0800: 8086:0326 (rev 09)
02:00.2 Class 0604: 8086:032a (rev 09)
02:00.3 Class 0800: 8086:0327 (rev 09)
03:03.0 Class 0100: 9005:801d (rev 10)
03:03.1 Class 0100: 9005:801d (rev 10)
04:01.0 Class 0104: 9005:0285 (rev 01)
06:04.0 Class 0300: 1002:5159
-----

このうち、さきほどのRAIDコントローラは、一番左側の列のPCIのIDが一致するものを探します。すると、これが該当することが分かります。
-----
04:01.0 Class 0104: 9005:0285 (rev 01)
-----
つまり、ベンダID 9005、デバイス ID 0285であることが分かります。
これを、pcitableファイルのエントリから探すと、
-----
0x9005  0x0285  "aacraid"
-----

ということで、aacraidドライバが必要になるということが分かります。

ちなみに、このpcitableですが元々のベンダIDやデバイスIDの情報については、Linux PCI ID Repositoryより入手することができます。 そして各ディストリビューションは、対応ドライバを組み込む際に、ドライバ名をpcitableファイルに追加していくことになります。

会社情報 採用情報 個人情報保護方針 商標等取り扱い事項 English
Copyright(c)2000-2006 MIRACLE LINUX CORPORATION. All Rights Reserved.