2016年2月14日日曜日

【Linux】 んんん? kernel3.16だとモジュールを作れないんだけど

今日はずいぶんと暖かかったですね.まるで春のような陽気でした.

webcamがどのように制御されているのかを知りたくて、Linuxのioctl()について知りたくなり、モジュールの作り方を知る必要に迫られお勉強中です.Linux風に述べると「カーネル3.16のカーネルモジュールを自作してカーネルに登録する」といった行為なのだと思ふ.

このカーネルモジュールとは、いわゆるデバイスドライバとも呼ばれるものなのですが、カーネルのバージョンによって関数が消滅したり、マクロが消滅したり、関数の引数が変わったり、、、細々と変化していて、ネットや書籍情報に当たってもなかなか解を得られずに困りました.

すなわち大雑把にいうと、直近の10年ぐらいの期間において、
 ・Kernel 2.4以前         オライリーの書籍あり
 ・Kernel 2.6            オライリーの書籍あり
 ・Kernel 3.0以降~現在まで    参考書籍なし!
この3期間で関数等が細々と異なります.仔細にいえば、もっと短期間でマクロが消滅したりとかもあります.
わたしはソフト屋ではないのでカーネルの近傍まで降りていくのはこれが初体験なのですが、どうやらカーネル近傍はバージョン毎にチクチクと仕様が異なるのが当たり前な玄人のセカイのようです.

わたしがいま使っているKona-Linux3.0のカーネルは3.16なんです.ネットや書籍の解説は、2.4や2.6についてなら豊富なのですが、なぜか3.0以降については情報が希少です.Kernel2.6と3.xは似た者同士なので玄人さんには解説は不要だという事情があるのかもしれません.

以下は、Kernel3.16で簡単なキャラクタデバイスを動かしてみたレポートです.
ソースコードはこちらのページを参考にさせていただきました.http://goo.gl/cNsqub
このページはKernel2.6が最新だった時期に書かれたようで、3.xでの動作は未確認であるようです.

---------
【準備】
Kona-Linux3.0のわたしの環境では、この2つをあらかじめやりました.ソースコードを取り込んでるようなもんです.これをやらないと、ヘッダファイルは欠落しとるし、パスは不在だしで、モジュールのコンパイルが全然通りません.
apt-get install linux-source
apt-get install linux-headers-`uname -r`

【Makefile】
以下の内容にて、make一発でコンパイルできます.
obj-m := octest.o
all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

【モジュールoctestの動かし方】
Cソースコードの説明は下の方でしますので、先にモジュールの使い方を述べます.

0)最初に一度だけ、キャラクタデバイスを生成します.
mknod /dev/octest c 60 0
chmod a+rw /dev/octest
これによって、/dev/octestというキャラクタデバイスが出来ます.

1)makeして出来る、octest.ko というファイルが目的物です.

2)モジュールoctestをカーネルに登録します.
insmod octest.ko
2度やるとエラーで却下されます.

3)デバイスoctestからreadします.
cat /dev/octest
すると画面に、デバイスから受領した文字列が表示されます.
 called read()
 called read()
 called read()
 called read()
 called read()
モジュールoctestの機能はこれだけです.

4)モジュールoctestをカーネルから撤去します.
rmmod octest.ko

5)dmesgコマンドで、2~4の動作がカーネルからレポートされたのを見てとれます.
 [2073922.815005] install 'octest' into major 60
 [2073938.253968] oc_open
 [2073938.254007] octest read() requested: 131072 bytes   returned: 14 bytes
 [2073938.254085] octest read() requested: 131072 bytes   returned: 14 bytes
 [2073938.254127] octest read() requested: 131072 bytes   returned: 14 bytes
 [2073938.254170] octest read() requested: 131072 bytes   returned: 14 bytes
 [2073938.254224] octest read() requested: 131072 bytes   returned: 14 bytes
 [2073938.254300] oc_close
 [2073944.542165] remove 'octest' from major 60

すなわち、自作モジュールをカーネルに登録し、モジュールの起動はcatでdev/octestから読む事であり、最後にモジュールをカーネルから撤去する、という流れとなります.

【Cソースコード
以下の赤字が全てです.main()はありません.ファイル名はoctest.cです.エラー処理はほとんどしてません.コンパイルするときにMOD_INC_USE_COUNTマクロが無いと怒られるので削除しちゃった.ゆるせっ、orz

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

↓ここで60とoctestというマジックナンバーが登場するのは、デバイスを生成したときの引数と一致させています.(mknod /dev/octest c 60 0)
static int devmajor=60;
static char *devname="octest";
static int lines;

↓insmodされたときに呼ばれるルーチン.printk()してるだけです.dmesgで表示された文字列はprintk()により打たれた文字列です.
static int octest_open(struct inode * inode, struct file * file)
{
  printk("oc_open\n");
  lines = 5;
  return 0;
}

↓rmmodされたときに呼ばれるルーチン.printf()してるだけです.
static int octest_close(struct inode * inode, struct file * file)
{
  printk("oc_close\n");
  return 0;
}

↓cat /dev/octestでreadされたときに返す文字列を"called read()"にしてます.
static char *message="called read()\n";

↓cat /dev/octestでreadされたときに呼ばれるルーチン.ユーザーメモリへ"called read()"を5回返した後にはNULLを返すようにしてあります.あとはprintk()をやってます.
static ssize_t octest_read(struct file * file, char * buff, size_t count, loff_t *pos)
{
  if(lines!=0)
    {
      int len, k;
      len=strlen(message);
      if(len>count) len=count;
      k = copy_to_user(buff,message,len);
      printk("octest read() requested: %d bytes   returned: %d bytes\n",(int)count,len);
      lines--;
      return len;
    }
  else return 0;
}

↓この謎の構造体こそが、モジュールを理解する上での最大の肝でしょう.cat /dev/octestでreadされたとき、insmodeでopenされたとき、rmmodでcloseされたとき、それぞれの時点でoctest_read()とoctest_open()とoctest_close()とがcallされるように関数ポインタをバコッと嵌め込んでいるのはここなのですから.file_operations構造体の正体については後述します.
static struct file_operations octest_fops = {
  .read = octest_read,
  .open = octest_open,
  .release = octest_close,
};

↓insmodされた最初にこのルーチンがcallされます.init_module()という関数名は決めうちです.ここで重要なのは、register_chrdev(devmajor,devname,&octest_fops)です./dev/octestに先ほどのfile_operations構造体をヒモ付けしているのがここです.つまり/dev/octestへの様々なリクエストを、自作ルーチンであるoctest_read()とoctest_open()とoctest_close()にヒモ付けしているのがここなんです.
int init_module(void)
{
  printk("install '%s' into major %d\n",devname,devmajor);
  if(register_chrdev(devmajor,devname,&octest_fops))
    {
      printk("device registration error\n");
      return -EBUSY;
    }
  return 0;
}

↓rmmodされたときにこのルーチンがcallされます.cleanup_module()という関数名は決めうちです.unregister_chrdev()によって、ヒモ付けを解除しています.
void cleanup_module(void)
{
  printk("remove '%s' from major %d\n",devname,devmajor);
  unregister_chrdev(devmajor,devname);
};

------
モジュールを理解する上での最大の肝と思うfile_operations構造体についてです.同構造体は、Linuxソース配下のinclude/linux/fs.hに書かれています.
(*read)とか(*open)とか(*release)というOSサービス関数が構造体メンバとして見えてます.(*ioctl)も見えてますがさておきます.こいつらはポインタですから最初は空っぽですが、上のソースコードで.read = octest_read,などとするコトによりcallされてほしい関数の実アドレスをセットしていたわけです.やっと繋がった.
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
       中略
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        中略
        long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
        int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

モジュールについての当初のイメージでは、周辺機器を操作する自作関数がカーネルを使いこなすという能動的なイメージを持っていたわたしでした.
だがしかしモジュールとはそうではなくて、カーネルから自作関数を適宜呼び出していただくという受動的な立場なのだと理解しました.
モジュールにmain()が無い理由は、各ルーチンはカーネルから呼び出される運命だからなのでした.

蛇足ながら、上のソースコードは、C++でやればいいものを苦労してCで記述しているように見えてしまうわたしです.octest_open()はコンストラクタだし、octest_close()はデストラクタだし、octest_read()はメソッドだし、わざわざ関数ポインタ構造体を活用するのもCゆえの苦労かなと.C++が歓迎された業界事情が垣間見られるように思いました.

#関数よ、カーネルのヒモになるのだっっ

かしこ


人気ブログランキングへ

0 件のコメント:

コメントを投稿