ドライバとカーネルのインターフェース


この章では、Linuxに実装されている、デバイスドライバが使用するカーネル内のサービスルーチンおよびマクロについて解説します。

  1. スケジューリング関連サービス

    1. schedule():プロセスの再スケジューリング

      宣言 #include <linux/sched.h>
      asmlinkage void schedule(void);
      機能 プロセスの再スケジューリングを行います。OSは、現在のプロセス・コントロール・リストより優先度が最も高いプロセスを選択し、そのプロセスに制御を渡します(実行可能状態にします)。この関数を呼んだプロセス以外に制御が渡った場合、コールしたプロセスは「スリープ状態」に遷移します。

    2. sleep_on():プロセスをスリープ状態にする

      宣言 #include <linux/sched.h>
      void sleep_on(struct wait_queue **p);
      void interruptible_sleep_on(struct wait_queue **p);
      機能

      当該プロセスを「スリープ状態」に遷移させます。すなわち、他のプロセスに制御を譲ります。この関数は、割込み禁止状態で発行しなければなりません。

      スリープ状態のプロセスは、ドライバ内のsleep_on()を発行した次のステップに進まなくなります。永久に進まない場合そのプロセスは、俗に言う「お亡くなりになった」状態になりますが、これを避けるのが後述のwake_up()です。ドライバでsleep_on()を発行するのは、一般的にハードウェアにI/O命令を出して、そのI/O完了を待っているときです。

      DOSのようなシングルタスクのOSの場合は、ここでfor()ループなどで完了を待つのですが、LinuxのようなマルチタスクのOSでこれをやると、他のプロセスに制御が回らなくなり、システム全体が重くなってしまうので、これを避けるためにsleep_on()を使用します。

      sleep_on()とinterruptible_sleep_on()の使い分けが、良く分かっていなかったりします(^_^);。interruptible_sleep_on()は、「上位プロセスで SIGINT などを発行してシステムコールを中断できるようにする」のではないかと推測していますが、どうでしょう。誰か知ってたら教えてください。

    3. wake_up():プロセスを起こす

      宣言 #include <linux/sched.h>
      void wake_up(struct wait_queue **q);
      機能 「スリープ状態」(I/Oウェイト中)のプロセスを、「実行可能状態」に遷移させます。スリープ中のプロセスのコンテキストは、引数で識別されます。指定したプロセスがスリープ状態でなければ、なにもしません。

  2. タイマー機能

    1. add_timer():タイマー監視を開始する

      宣言 #include <linux/timer.h>
      void add_timer(struct timer_list * timer);
      struct timer_list {
        struct timer_list *next; /* NULLのまま */
        struct timer_list *prev; /* NULLのまま */
        unsigned long expires; /* タイムアウト時間(10ms単位)*/
        unsigned long data; /* 任意。複数のイベントを1つの
          タイマーハンドラで管理する場合、この引数で認識する。*/
        void (*function)(unsigned long); /* タイマーハンドラ関数。
          引数として'data'が渡される。 */
      };
      機能 カーネルのタイマーリストに監視したいイベントを追加します。 'expires' ×10ミリ秒経過すると、関数 'function' に制御が移ります。

    2. del_timer():タイマー監視を中止する

      宣言 #include <linux/timer.h>
      int del_timer(struct timer_list * timer);
      機能 カーネルのタイマーリストからイベントを削除します。タイムアウトして 'function' が呼ばれた後は、実行する必要はありません。

    3. udelay():μ秒単位の遅延

      宣言 #include <asm/delay.h>
      __inline__ void udelay(unsigned long usecs);
      機能 1ミリ秒以下の非常に短時間の遅延をしたい場合に使用します。当該マクロを発行しても、スケジューリングは行われません。

  3. メモリ操作関連

    1. verify_area():ユーザ領域の妥当性検査

      宣言 #include <linux/mm.h>
      int verify_area(int type, const void * addr, unsigned long size);
      機能 ユーザ領域(ユーザから渡された引数)の妥当性(整合性および権限)を検査します。戻り値≦0の場合、ユーザ領域は使用不可(上位の引数誤り)です。なお、ioctl()システムコールの第3引数(intまたは 任意のポインタ)がポインタの場合、そのポインタは連続したメモリ領域の先頭を指している必要があります。つまり、その引数が構造体などのポインタであり、そのメンバがさらにポインタである場合は、メモリ空間が異なるため、ドライバ側ではその実体にはアクセスすることができません。

    2. memcpy_fromfs():ユーザ領域からのメモリ転送

      宣言 #include <linux/asm-i386/segment.h>
      void memcpy_fromfs(void *to, void *from, int size);
      機能 ユーザ領域からカーネル領域へのメモリコピーを行います。
      ※システムコールの引数にポインタが指定された場合、カーネル側(ドライバ)にポインタが渡されますが、ユーザAPとカーネルではメモリ空間が異なるため、ドライバ側ではポインタが指している領域に直接にはアクセスできません。その場合当該関数を使用して、ポインタが指している実体を、カーネル側で用意した内部バッファにコピーしてからアクセスしてやる操作が必要となります。

    3. memcpy_tofs():ユーザ領域へのメモリ転送

      宣言 #include <linux/asm-i386/segment.h>
      void memcpy_tofs(void *to, void *from, int size);
      機能 カーネル領域からユーザ領域へのメモリコピー。memcpy_fromfs()を参照のこと。

    4. memcmp():メモリ内容の比較

      宣言 #include <asm/string.h>
      inline int memcmp(const void * cs,const void * ct,size_t count)
      機能 memcmp(3) ライブラリ関数と同様です。カーネル空間内の変数を比較します。

    5. kmalloc():動的メモリ取得

      宣言 #include <linux/malloc.h>
      void *kmalloc(int size, int priority);
      機能 malloc(3) ライブラリ関数と同様です。カーネル空間内のヒープを取得します。

  4. リソース関連

    1. request_irq():IRQの使用宣言

      宣言 #include <linux/sched.h>
      #include <linux/signal.h>
      int request_irq(
        unsigned int irq, /* IRQ番号 */
        void (*handler)(int, struct pt_regs *), /* 割り込みハンドラ */
        unsigned long flags, /* フラグ(SA_INTERRUPT) */
        const char *device); /* デバイス名 */
      機能 IRQと割り込みハンドラをカーネルに登録します。IRQの使用状況は /proc/interruptで確認できます。

    2. free_irq():IRQの解放

      宣言 #include <linux/sched.h>
      void free_irq(unsigned int irq); /* arch/i386/kernel/irq.c */
      機能 使用していたIRQを解放します。

    3. request_dma():DMAチャネルの予約

      宣言 #include <asm/dma.h> int request_dma(
        unsigned int dmanr, /* DMA番号 */
        char * device_id); /* デバイス名 */
      機能 DMAチャネルを予約します。使用状況は /proc/dmaで確認できます。

    4. free_dma():DMAの解放

      宣言 #include <linux/sched.h>
      free_dma(unsigned int dmanr);
      機能 使用中のDMAを解放します。

    5. request_region():I/Oポートの予約

      宣言 #include <linux/ioport.h>
      int request_region(
      unsigned int from, /* 開始I/Oアドレス */
      unsigned int extent, /* 使用バイト数 */
      const char * device_id); /* デバイス名 */
      機能 I/Oポートアドレスを予約します。使用状況は /proc/ioportsで確認できます。

    6. release_region():I/Oポートの解放

      宣言 #include <linux/ioport.h>
      int release_region(
      unsigned int from, /* 開始I/Oアドレス */
      unsigned int extent); /* 使用バイト数 */
      機能 使用中のI/Oポートを解放します。

    7. check_region():I/Oポートの検査

      宣言 #include <linux/ioport.h>
      int check_region(
      unsigned int from, /* 開始I/Oアドレス */
      unsigned int extent); /* 使用バイト数 */
      機能 I/Oポートが使用されているかどうかを検査します。

    8. DMAサポートマクロ群

      宣言 #include <asm/dma.h>
      clear_dma_ff(DMAチャネル番号);
      set_dma_mode(DMAチャネル番号, dma_mode); /* full address */
      set_dma_addr(DMAチャネル番号, 先頭アドレス+転送済みバイト数);
      set_dma_count(DMAチャネル番号, 転送カウント);
      enable_dma(DMAチャネル番号);
      機能 static __inline__ void enable_dma(unsigned int dmanr)
      指定のDMAチャネルを使用可能にします(単一マスク・ビットのクリア)

      static __inline__ void disable_dma(unsigned int dmanr)
      指定のDMAチャネルを使用不可にします(単一マスク・ビットの設定)

      static __inline__ void clear_dma_ff(unsigned int dmanr)
      'DMA Pointer Flip Flop'をクリアします。

      コーディング・サンプル

      clear_dma_ff(DMA);
      outb(下位バイト、アドレス);
      outb(上位バイト、アドレス);
      clear_dma_ff(DMA);
      下位バイト = inb(アドレス);
      上位バイト = inb(アドレス);
      ★この処理は、割り込み不可の状態で行なうこと!

      static __inline__ void set_dma_mode(unsigned int dmanr, char mode)
      指定のDMAチャネルのモード設定を行ないます。

      static __inline__ void set_dma_addr(unsigned int dmanr, unsigned int a)
      指定のDMAチャネルに対して、転送アドレスとページビットの設定を行ないます。DMAフリップフロップはクリアされているものと見なしています。

      static __inline__ void set_dma_count(unsigned int dmanr, unsigned int count)
      指定のDMAチャネルに対して、転送サイズの設定(DMA1−3は最大64K、DMA5−7は最大128K)を行ないます。注意:1を引く、またはワード転送の場合2で割る、などの計算は内部で行っています。

      static __inline__ int get_dma_residue(unsigned int dmanr)
      DMAの転送残りカウントを取得します。

  5. その他

    1. register_chrdev():ハンドラの登録

      宣言 #include <linux/fs.h>
      int register_chrdev( /* linux/fs/devices.c */
      unsigned int major, /* デバイスメジャー番号 */
      const char * name, /* 内部デバイス名 */
      struct file_operations *fops) /* ファイル操作構造体 */
      struct file_operations { /* ファイル操作関数群管理構造体 */
        int (*lseek) (struct inode *, struct file *, off_t, int);
        int (*read) (struct inode *, struct file *, char *, int);
        int (*write) (struct inode *, struct file *, char *, int);
        int (*readdir) (struct inode *, struct file *, struct dirent *, int);
        int (*select) (struct inode *, struct file *, int, select_table *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        int (*mmap) (struct inode *, struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        void (*release) (struct inode *, struct file *);
        int (*fsync) (struct inode *, struct file *);
        int (*fasync) (struct inode *, struct file *, int);
        int (*check_media_change) (dev_t dev);
        int (*revalidate) (dev_t dev);
      };
      機能 仮想ファイルシステムにキャラクタ・デバイスとして登録します。この後、割り込みハンドラ(ボトム・ハーフ・ルーチン)が有効になります。
      file_operations 構造体の各エントリは、アプリケーションが対応するシステムコールをコールした際の対応ハンドラとなります。

    2. printk():カーネル用 printf()

      宣言 #include <linux/kernal.h>
      asmlinkage int printk(const char *fmt, ...); /* printk.c */
      機能 printf(3) のサブセットのようなものです。出力は起動時ならコンソールに表示され、起動後は syslogdデーモンを介して /var/log/syslogなどに書き込まれます。

    3. send_sig():自分自身(のプロセス)にシグナルを送る

      宣言 #include <linux/sched.h>
      int send_sig(unsigned long sig,struct task_struct * p,int priv) /* exit.c */
      機能

    4. notify_parent():親プロセスに知らせる

      宣言 #include <linux/sched.h>
      void notify_parent(struct task_struct * tsk) /* exit.c */
      機能 自分自身が死んだことを親プロセスに知らせます。

    5. outb():ポートへの1バイト出力

      宣言 #include <asm/io.h>
      void outb(unsigned char value, unsigned address);
      機能

    6. inb():ポートからの1バイト入力

      宣言 #include <asm/io.h>
      void inb(unsigned address);
      機能