はじめに

ここではキーボード入力に対してウインドウシステムがどういう処理を行っているかを読解します。ウインドウシステムは以下を対象とします。

  • xorg-server-1.4.2
  • xf86-input-keyboard-1.3.1

なお、プラットフォームはLinuxとします。

読解戦略

X.Orgは巨大なプログラムです。mainの始めから読んでいくとくじけてしまいます。というわけで今回はXクライアントに情報を渡しているところから逆算(その関数を呼んでるところをgrep)していこうと思います。

PostKbdEvent(xf86-input-keyboard/src/kbd.c)

キーボードの処理なので当然、xf86-input-keyboardでやってるだろうとsrcディレクトリをlsすると以下のファイルが見つかります。

Makefile.am Makefile.in
at_scancode.c
bsd_KbdMap.c bsd_kbd.c bsd_kbd.h
hurd_kbd.c
kbd.c
lnx_KbdMap.c lnx_kbd.c lnx_kbd.h
sco_KbdMap.c sco_kbd.c sco_kbd.h
sun_kbd.c sun_kbd.h sun_kbdMap.c
xf86Keymap.h
xf86OSKbd.h

bsdとかlnxとか付いてるのはOS specificな処理、kbd.cは汎用の処理をしてそうだとkbd.cを見るとPostKbdEvent関数にてxf86PostKeyboardEvent関数を呼び出しています。とてもキーボードイベントを送ってそうな雰囲気なのでPostKbdEvent関数がどう呼ばれるかを見ていくことにしましょう。

というわけでPostKbdEventで検索するとKbdPreInit関数で設定されています。

KbdPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
{
    InputInfoPtr pInfo;
    KbdDevPtr pKbd;
    ...
    pInfo->private = pKbd;
    pKbd->PostEvent = PostKbdEvent;

次にPostEventで検索するとInitKBD関数で呼んでますがInitってぐらいだから最初だけでキーボード押したときに毎回は呼ばれなそうです。というわけでlnx_kbd.cの方を見るとstdReadInput関数で呼ばれてます。しかもファイル(デバイスファイルと思われる)から読み込んでていい感じです。

stdReadInput(InputInfoPtr pInfo)
{
    KbdDevPtr pKbd = (KbdDevPtr) pInfo->private;
    unsigned char rBuf[64];
    int nBytes, i;
    if ((nBytes = read( pInfo->fd, (char *)rBuf, sizeof(rBuf))) > 0) {
       for (i = 0; i < nBytes; i++)
           pKbd->PostEvent(pInfo, rBuf[i] & 0x7f,
                           rBuf[i] & 0x80 ? FALSE : TRUE);
       }
}

続いてstdReadInputを検索するとOpenKeyboard関数で設定されています。

OpenKeyboard(InputInfoPtr pInfo)
{
    ...
           pInfo->read_input = stdReadInput;

InputInfoPtrはキーボード特有っぽくないので捜索の手をxorg-serverの方に移してread_inputをgrepします。すると、hw/xfree86/common/xf86Events.cにてxf86Wakeup関数とxf86SigioReadInput関数で呼ばれています。それぞれ以下のように呼ばれるようです。

main (dix/main.c)
→Dispatch (dix/dispatch.c)
→WaitForSomething (os/WaitFor.c)
→WakeupHandler (dix/dixutils.c)
→xf86Wakeup
何かI/O入力
→xf86SIGIO (hw/xfree86/os-support/shared/sigio.c)
→xf86SigioReadInput

というわけで定期的なポーリング or シグナルによりキーボード入力を読み込んでXクライアントにイベントを渡しています。と思ったけど、シグナルハンドラ追加するxf86AddEnabledDevice関数呼んでないのでシグナル経由はなさそう。

KbdPreInit(xf86-input-keyboard/src/kbd.c)

今までで何をトリガーPostKbdEvent関数が呼び出されるかは見てきたので次にPostKbdEventを設定しているKbdPreInitの呼び出され方や、さらっと流していたデバイスファイル(と思われる)の設定等を見ていきましょう。

KbdPreInitまで

というわけでKbdPreInitで検索すると以下が引っかかります。

_X_EXPORT InputDriverRec KBD = {
        1,
        "kbd",
        NULL,
        KbdPreInit,
        NULL,
        NULL,
        0
};

次にKBDで検索すると、xf86KbdPlug関数が引っかかります。

xf86KbdPlug(pointer     module,
            pointer     options,
            int         *errmaj,
            int         *errmin)
{
    ...
    xf86AddInputDriver(&KBD, module, 0);
    ...
}

そしてすぐ下にxf86KbdPlug関数を含んだkbdModuleDataがあります。見た感じ、モジュールをロードするときのエントリーポイントっぽいです。

_X_EXPORT XF86ModuleData kbdModuleData = {
    &xf86KbdVersionRec,
    xf86KbdPlug,
    xf86KbdUnplug
};

ではモジュールをロードしてるところをというところなのですがkbdModuleDataでは引っかからないと思うので型名のXF86ModuleDataでgrepをかけます。すると、hw/xfree86/loader/loadmod.cのdoModuleData関数で使われています。モジュールの検索とかロードの詳細は気にしないこととして以下の部分。予想通りにkbdModuleDataを取得して呼び出しを行っています。

ModuleDescPtr ret = NULL;
...
strcpy(p, name);
strcat(p, "ModuleData");
initdata = LoaderSymbol(p);
if (initdata) {
    ModuleSetupProc setup;
    ...
    setup = initdata->setup;
    ...
    if (setup)
        ret->SetupProc = setup;
...
if (ret->SetupProc) {
    ret->TearDownData = ret->SetupProc(ret, options, errmaj, errmin);

さて、doLoadModuleに至るまでの道ですが以下のようになっています。

main (dix/main.c)
→InitOutput (hw/xfree86/common/xf86Init.c)
→xf86LoadModules (hw/xfree86/common/xf86Init.c)
→LoadModule (hw/xfree86/loader/loadmod.c)
→doLoadModule

InitOutput関数はOutputとなってますがxorg.confの読み込みとかいろいろやっています。先ほどのxf86Wakeup関数の登録もやってますね。

というわけでInputDriverが登録されたわけで、登録したInputDriverのPreInitはInitInput関数で呼び出されています。

InitInput(argc, argv)
{
    ...
            if ((pDrv = xf86LookupInputDriver((*pDev)->driver)) == NULL) {
                ...
            }
            ...
            pInfo = pDrv->PreInit(pDrv, *pDev, 0);

xf86OpenConsole

次にKbdPreInit関数に進んでデバイスファイル(と思われる)の設定を見ます。xf86OSKbdPreInit関数でOS固有の設定をした後でKbdDevPtrに設定されたOpenKeyboard関数を呼び出しています。xf86InfoのconsoleFdをstdReadInputで読むファイルとして設定しています。

s = xf86SetStrOption(pInfo->options, "Device", NULL);
if (s == NULL) {
   pInfo->fd = xf86Info.consoleFd;
   pKbd->isConsole = TRUE;

次にconsoleFdでgrepするとhw/xfree86/os-support/linux/lnx_init.cのxf86OpenConsole関数がひっかかります。この関数にて/dev/tty?をオープンしています。つまり、Xサーバはttyからキーボード入力を読んでいるということのようです。確かに、"ps a"するとXサーバにttyが関連付いていることがわかります。

$ ps a
  PID TTY      STAT   TIME COMMAND
 2812 tty7     S<s+   0:59 /usr/bin/X11/X

ちなみにxf86OpenConsole関数は先ほどのInitOutput関数から呼ばれています。

xf86PostKeyboardEventの先

xf86PostKeyboardEvent関数を呼び出している元を見たので次にxf86PostKeyboardEvent呼び出し以降どうやってXクライアントにイベントを送信しているかを見ていきましょう。

まず、xf86PostKeyboardEvent(hw/xfree86/common/xf86Xinput.c)→mieqEnqueue(mi/mieq.c)と呼び出されることでイベントがキューに格納されています。

キューはいつ処理されるのかとmieq.c内を見るとmieqProcessInputEvents関数で処理されています。mieqProcessInputEvents関数は以下のように呼び出されています。

いろんなとこ
→UpdateCurrentTime (dix/dispatch.c)
→ProcessInputEvents (hw/xfree86/common/xf86Events.c)
→mieqProcessInputEvents (mi/mieq.c)

さて、mieqProcessInputEvents関数にて以下のように呼び出しが行われています。

dev->public.processInputProc(e->event, dev, e->nevents);

KbdPreInit関数ではpInfo->devにはNULLを入れてます。このままではSEGVです。というわけでdevにどのような値が入っているかを調べるために再びInitInput関数に目を向けると、

InitInput (hw/xfree86/common/xf86Init.c)
→xf86ActivateDevice (hw/xfree86/common/xf86Xinput.c)
→AddInputDevice (dix/devices.c)

と呼ばれてdevは割り当てられるのですが、processInputProcはNoopDDAです。しばらく考えた後、AddInputDevice関数から返ってきた後にRegisterOtherDevice関数(Xi/exevents.c)呼んでいるのでProcessOtherEventが設定されているらしいということがわかりました。

その後、以下のように呼び出しが行われ、Xクライアントに送るデータとしてバッファに詰め込まれてます。

ProcessOtherEvent (Xi/exevents.c)
→DeliverDeviceEvents (dix/events.c)
→DeliverEventsToWindow (dix/events.c)
→TryClientEvents (dix/events.c)
→WriteEventsToClient (dix/events.c)
→WriteToClient (os/io.c)

Xクライアントへの送信はいろんなところで呼ばれているFlushAllOutput→FlushClientにより行われています。_XSERVTransWritev関数で送信しているようですがgrepしても定義が見つかりません。仕方ないので/usr/include/X11をgrepしてもありません。Writevで検索したところ引っかかりました。

/usr/include/X11/Xtrans.h

#ifdef XSERV_t
#define TRANS(func) _XSERVTrans##func
#endif

int TRANS(Writev)(
    XtransConnInfo,     /* ciptr */
    struct iovec *,     /* buf */
    int                 /* size */
);

Xtrans.hはあらかじめXSERV_tとかをdefineしてincludeすると_XSERVTransWritevのように関数宣言が行われるという仕組みらしいです。Xnestみたいにクライアント&サーバみたいなのを実装するためでしょうか?

_XSERVTransWritev関数の定義はos/xstrans.cにて以下のようにX11/Xtrans/transport.cがincludeされ、生成されています。

#include <X11/Xtrans/transport.c>

余談:xorg-serverのネームスペース

xorg-serverのソースを見ているとdixとかddxとかという単語を見かけます。多分、以下の意味です。

dix
Device Independent X
ddx
Device Dependent X

miは何の略かわかりません。知ってたら教えてください。

おわりに

今回はキーボード入力をXサーバがどのように処理しているか見てきました。わかったこととしては以下のことがあります。

  • キーボード入力は/dev/tty?から読み取っている

Xクライアントの接続とかクライアントのリクエストとかウインドウの管理とかは無視しました。それではみなさんもよいコードリーディングを。


トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2008-08-31 (日) 01:24:17 (5732d)