Linuxデバイスドライバ開発

オライリー第三版(2.6系)を参考にドライバ開発を行います。動作確認はカーネル3.2で実施します。

デバイスドライバにreadを実装する

前回はデバイスドライバにopenとcloseを実装した。

public2016.hatenablog.com


さすがにそれだけじゃ寂しいからreadを実装してみる。

ただ個人的にはread(とwriteも)を真面目に実装するよりは、「ioctl」を使えばいいじゃないって思ってる。もちろんドライバによってはread/writeが最適なものもあるんだろうけどハードウェアを制御しようと思うとreadの「前回読み込んだ続きを読み込む」って挙動は少し使いにくく感じる。

まぁとりあえず見てきましょ。


readを実装するためには・・・

file_operations構造体にreadに対応する関数を追加

前回はopenとcloseだけだったけど色々追加。

static struct file_operations test_driver_fops ={
    .owner          = THIS_MODULE,
    .open           = test_driver_open,
    .release        = test_driver_release,
    .read           = test_driver_read,
    .write          = test_driver_write,
    .unlocked_ioctl = test_driver_ioctl,
};

今回は使わないけどwriteとunlocked_ioctlも入れておく


次は〜

なんでもいいから読み込むデータを用意する

普通はハードウェアのデータを読み込むんだけど適当なハードがないからグローバル変数で代替しとく。

char gb_buf[101];


ドライバをロードした時の処理内で値を詰めておく。
データの終わりを示すために−1を入れておく。

static int __init test_driver_init(void)
{
  //
    //色々な処理
    //

    int i;
    for(i=0;i<100;i++)
    {
        gb_buf[i]=(char)i;
    }
    gb_buf[99]=-1;
   return 0;
}

お次は・・・

データの先頭アドレスをprivate_dataに突っ込む

private_dataとはfile構造体(fopenの戻り値のfile構造体とは別物!!)のメンバで、ドライバ開発者が好き勝手使用出来る領域として用意されている(と思う)。


このfile構造体変数はfile_operationsに定義した関数をカーネルが呼び出すときに渡してくれるのだ。
つまりこの仕組みを使うと、各関数間で簡単にデータの共有ができるのだ。


もっと具体的にいえば関連付けられた以下の関数群が呼び出される時、

test_driver_open
test_driver_release
test_driver_read
test_driver_write
test_driver_ioctl

これらの関数の間で1つのfile構造体変数を共有できる、ということなのだね。


処理の流れとしては・・・

open時にprivate_dataにグローバル配列の先頭を突っ込んで

//open
filp->private_data = gb_buf;
   //(char gb_buf[101]の先頭)

read開始時はprivate_dataを読みだして、読み出しの先頭として扱って

//read{
char* p_gb_buf=file->private_data;

read終了時はprivate_dataを最後のポインタで更新する

file->private_data=&p_gb_buf[count];
//}read

※注意※

readだけならこの実装でも問題無いけどwriteがあると問題!write関数に渡されるfile->private_dataも当然共有しているからreadした分writeが影響を受けてしまう。read/writeを真面目に実装しようと何らかの手段でread用インデックスと、write用インデックスを管理しないといけなさそう


最後は〜

read関数を実装する

かなり雑な作りだけどとりあえず書いたread関数。

ssize_t test_driver_read(struct file * file, char * buff, size_t count, loff_t *pos)
{
    char* p_gb_buf=file->private_data;
    char* p_temp=kmalloc(count, GFP_KERNEL);
    int i;

    printk(KERN_DEBUG "read call count=%d\n",count);

    for(i=0;i<count;++i)
    {
        if(p_gb_buf[i] ==-1)
        {
            break;
        }
        p_temp[i]=p_gb_buf[i];
    }
   if (copy_to_user(buff, p_temp, i)) {
      kfree(p_temp);
      return -EFAULT;
   }
    file->private_data=&p_gb_buf[i];
    kfree(p_temp);
    return count;
}

重要なポイントは test_driver_readの引数の「buff」の扱い。

buffはユーザランドのデータに紐付いてるもので、readの結果はこのbuffに書き込む必要があるんだけど、実はカーネルのデータをユーザランドに常に正しく転送するのはムヅカシイ


例えば・・・

buff[0]=100;

とすることでユーザランドに転送することもできるんだけどはてさてこのコード、成功したのか?

・・・

答えは、このコードだけでは転送に成功したのか失敗したのか分からない、だね。
だいたいの場合は成功するんだけど、データ転送なんて常に正しくできるか分からない。


だから転送時は先人の開発したcopy_to_user関数を使いましょ。転送失敗時の処理が書けるしいいね。

copy_to_user(user_buff, const data, count)

ちなみにcopy_to_userは排他処理必須なんだけど、このコードでは排他してなにので注意です。

一応全ソース類をのっけておく。バグってるかもしれないので実行は自己責任でオナシャス
全部同じ階層にぶちこんで上から説明通りに実行すれば出来るはずデス

次回は「ioctl」の説明をするよ!

test_driver.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>    

#define TEST_DRIVER_MINOR_COUNT 4

//===================================
//プロトタイプ宣言
//===================================
//デバイスドライバの初期化・終了関数
static __init int test_driver_init(void);
static __exit void test_driver_exit(void);


//ファイル操作に紐付いた関数群
int test_driver_open(struct inode *inode, struct file *filp);
int test_driver_release(struct inode *inode, struct file *filp);
ssize_t test_driver_read(struct file * file, char * buff, size_t count, loff_t *pos);
ssize_t test_driver_write(struct file * file, const char * buff, size_t count, loff_t *pos);
long test_driver_ioctl( struct file *filp,unsigned int cmd, unsigned long arg);

//===================================
//グローバル変数
//===================================
static dev_t dev_id; 
static struct cdev c_dev; 

static struct file_operations test_driver_fops ={
    .owner          = THIS_MODULE,
    .open           = test_driver_open,
    .release        = test_driver_release,
    .read           = test_driver_read,
    .write          = test_driver_write,
    .unlocked_ioctl = test_driver_ioctl,
};


char gb_buf[101];
//===================================
//ファイル操作に紐付いた関数群
//===================================
//openに対応する関数
int test_driver_open(struct inode *inode, struct file *filp) 
{
    int dev_id_tmp = inode->i_rdev;
    filp->private_data = gb_buf;
    //filp->private_data = &inode->i_rdev;
    printk(KERN_DEBUG "open call minor no= %d\n",MINOR(dev_id_tmp));
    return 0;
}

//closeに対応する関数
int test_driver_release(struct inode *inode, struct file *filp)
{
    printk(KERN_DEBUG "release call\n");
    return 0;
}

//readに対応する関数
ssize_t test_driver_read(struct file * file, char * buff, size_t count, loff_t *pos)
{
    printk(KERN_DEBUG "read call count=%d\n",count);
    char* p_gb_buf=file->private_data;
    char* p_temp=kmalloc(count, GFP_KERNEL);
    
    int i;
    for(i=0;i<count;++i)
    {
        if(p_gb_buf[i] ==-1)
        {
            break;
        }
        p_temp[i]=p_gb_buf[i];
        //buff[i]=p_gb_buf[i];
    }
   if (copy_to_user(buff, p_temp, i)) {
      kfree(p_temp);
      return -EFAULT;
   }
    file->private_data=&p_gb_buf[i];
    kfree(p_temp);
    return i;
}

//writeに対応する関数
ssize_t test_driver_write(struct file * file, const char * buff, size_t count, loff_t *pos)
{
    return 0;
}


//ioctlに対応する関数
long test_driver_ioctl( struct file *filp,unsigned int cmd, unsigned long arg)
{
    printk(KERN_DEBUG "ioctl call\n");

    return 0;
}
//===================================
//デバイスドライバの初期化・終了関数
//===================================
//モジュール初期化関数
static int __init test_driver_init(void)
{
   int ret=0;
   printk(KERN_DEBUG "↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓\n");
   printk(KERN_DEBUG "モジュール初期化関数呼び出し\n");
   ret = alloc_chrdev_region(&dev_id, 0, TEST_DRIVER_MINOR_COUNT, "test_driver_basic");
   if(ret<0)
    {
       printk(KERN_DEBUG "alloc_chrdev_region fail!!\n");
        return -1;
    }
    printk(KERN_DEBUG "獲得したメジャー番号 = %d\n",MAJOR(dev_id));
    
    
    cdev_init(&c_dev, &test_driver_fops);
    c_dev.owner = THIS_MODULE;
    ret = cdev_add(&c_dev, dev_id, TEST_DRIVER_MINOR_COUNT);
   if(ret!=0)
    {
       printk(KERN_DEBUG "cdev_add fail!!\n");
        return -1;
    }
    int i;
    for(i=0;i<100;i++)
    {
        gb_buf[i]=(char)i;
    }
    gb_buf[99]=-1;
   return 0;
}

//モジュール終了関数
static void __exit test_driver_exit(void)
{
    printk(KERN_DEBUG "モジュール終了処理呼び出し\n");
    cdev_del(&c_dev);
    unregister_chrdev_region(dev_id, TEST_DRIVER_MINOR_COUNT);
    printk(KERN_DEBUG "↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑\n");
}

//===================================
// マクロ
//===================================
module_init(test_driver_init);
module_exit(test_driver_exit);
MODULE_DESCRIPTION("device_driver_test");
MODULE_LICENSE("GPL2");


make でビルドして
sudo make install でロードしてくださいな

Makefile

obj-m := driverModule.o
driverModule-objs := test_driver.o
PWD := $(shell pwd)
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
install:
	insmod driverModule.ko
uninstall:
	rmmod driverModule.ko
clean:
	rm -f *.o *.ko *.mod.c Module.* module* *~

バイスファイルを作るシェル
sudo ./make_device_file.sh でOk
make_device_file.sh

#!/bin/bash
DEVICE="test_driver_basic"
MAJOR=`awk "\\$2==\"$DEVICE\" {print \\$1}" /proc/devices`
echo $MAJOR
rm -rf ./test_dev
mknod ./test_dev c $MAJOR 0
exit 0

ユーザランドのテストプログラム。雑・・・
gcc test.c
でビルドしてa.outを実行してね。
test.c

#include <stdio.h>
#include <fcntl.h>      // O_RDWR
#include <unistd.h>
#include <string.h>
int main()
{
    ssize_t read_size;
    int i;
    char buf[10];
    int fd=open("./test_dev",O_RDWR);
    printf("get fd =%d \n",fd);
    if(fd<0)
    {
        return -1;
    }
    int k;
    for(k=0;k<20;k++)
    {
        if(k==5)
        {
            close(fd);
            printf("close/open\n");
            fd=open("./test_dev",O_RDWR);
        }
        read_size = read(fd, buf, 7);
        if(read_size==0)
        {
            break;
        }
        printf("read size = %d\n ",read_size);
        for(i=0;i<10;++i)
        {
            printf("read buf[%d] = %d\n",i,buf[i]);
        }
        memset(buf, '\0', 10);
        printf("======%d=====\n",k);
    }

    close(fd);
    return 0;
}


実行結果

=======================
get fd =3 
read size = 7
 read buf[0] = 0
read buf[1] = 1
read buf[2] = 2
read buf[3] = 3
read buf[4] = 4
read buf[5] = 5
read buf[6] = 6
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======0=====
read size = 7
 read buf[0] = 7
read buf[1] = 8
read buf[2] = 9
read buf[3] = 10
read buf[4] = 11
read buf[5] = 12
read buf[6] = 13
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======1=====
read size = 7
 read buf[0] = 14
read buf[1] = 15
read buf[2] = 16
read buf[3] = 17
read buf[4] = 18
read buf[5] = 19
read buf[6] = 20
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======2=====
read size = 7
 read buf[0] = 21
read buf[1] = 22
read buf[2] = 23
read buf[3] = 24
read buf[4] = 25
read buf[5] = 26
read buf[6] = 27
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======3=====
read size = 7
 read buf[0] = 28
read buf[1] = 29
read buf[2] = 30
read buf[3] = 31
read buf[4] = 32
read buf[5] = 33
read buf[6] = 34
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======4=====
close/open
read size = 7
 read buf[0] = 0
read buf[1] = 1
read buf[2] = 2
read buf[3] = 3
read buf[4] = 4
read buf[5] = 5
read buf[6] = 6
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======5=====
read size = 7
 read buf[0] = 7
read buf[1] = 8
read buf[2] = 9
read buf[3] = 10
read buf[4] = 11
read buf[5] = 12
read buf[6] = 13
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======6=====
read size = 7
 read buf[0] = 14
read buf[1] = 15
read buf[2] = 16
read buf[3] = 17
read buf[4] = 18
read buf[5] = 19
read buf[6] = 20
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======7=====
read size = 7
 read buf[0] = 21
read buf[1] = 22
read buf[2] = 23
read buf[3] = 24
read buf[4] = 25
read buf[5] = 26
read buf[6] = 27
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======8=====
read size = 7
 read buf[0] = 28
read buf[1] = 29
read buf[2] = 30
read buf[3] = 31
read buf[4] = 32
read buf[5] = 33
read buf[6] = 34
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======9=====
read size = 7
 read buf[0] = 35
read buf[1] = 36
read buf[2] = 37
read buf[3] = 38
read buf[4] = 39
read buf[5] = 40
read buf[6] = 41
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======10=====
read size = 7
 read buf[0] = 42
read buf[1] = 43
read buf[2] = 44
read buf[3] = 45
read buf[4] = 46
read buf[5] = 47
read buf[6] = 48
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======11=====
read size = 7
 read buf[0] = 49
read buf[1] = 50
read buf[2] = 51
read buf[3] = 52
read buf[4] = 53
read buf[5] = 54
read buf[6] = 55
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======12=====
read size = 7
 read buf[0] = 56
read buf[1] = 57
read buf[2] = 58
read buf[3] = 59
read buf[4] = 60
read buf[5] = 61
read buf[6] = 62
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======13=====
read size = 7
 read buf[0] = 63
read buf[1] = 64
read buf[2] = 65
read buf[3] = 66
read buf[4] = 67
read buf[5] = 68
read buf[6] = 69
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======14=====
read size = 7
 read buf[0] = 70
read buf[1] = 71
read buf[2] = 72
read buf[3] = 73
read buf[4] = 74
read buf[5] = 75
read buf[6] = 76
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======15=====
read size = 7
 read buf[0] = 77
read buf[1] = 78
read buf[2] = 79
read buf[3] = 80
read buf[4] = 81
read buf[5] = 82
read buf[6] = 83
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======16=====
read size = 7
 read buf[0] = 84
read buf[1] = 85
read buf[2] = 86
read buf[3] = 87
read buf[4] = 88
read buf[5] = 89
read buf[6] = 90
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======17=====
read size = 7
 read buf[0] = 91
read buf[1] = 92
read buf[2] = 93
read buf[3] = 94
read buf[4] = 95
read buf[5] = 96
read buf[6] = 97
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======18=====
read size = 1
 read buf[0] = 98
read buf[1] = 0
read buf[2] = 0
read buf[3] = 0
read buf[4] = 0
read buf[5] = 0
read buf[6] = 0
read buf[7] = 0
read buf[8] = 0
read buf[9] = 0
======19=====

ユーザランドからデバイスドライバを呼び出す

前回のハローワールドは「module_init」と「module_exit」というカーネルの機能を使用して、デバイスドライバをロードした時とアンロードした時にsyslogに文字を表示した。
public2016.hatenablog.com


さてハローワールドデバイスドライバはロードしてアンロードされるまでの間はどんな仕事をしているかというと・・・





なにもしない!





何もしません。メモリを無駄に食うだけ。

デバイスドライバは基本的に命令されたことをやるだけなので命令がなければ動けない。

で、前述のドライバはロードとアンロード命令に対応する機能しかないからロード後は動かない。

例えば「グッドナイトワールド」とsyslogに出す関数を実装しても、その関数は実行されないわけ。どうにかしてこのドライバに命令してグッドナイトワールド関数を実行したくても、ドライバ自身に「命令を受け付けるクチ」がないのでどうしようもない。



そんなドライバ何の役にも立たないから、今回は「命令を受け付けるクチ」を用意するよ。

ユーザランド側の処理

「命令を受け付けるクチ」を作るにはmknodコマンドを使う

mknod ファイル名 c メジャー番号 マイナー番号

ファイル名=「命令を受け付けるクチ」

デバイスドライバユーザランドではファイルとして表現されるんです。ファイル?なんでデバイスがファイル?って感じだよね。パソコンってマウスで操作したり音楽を鳴らせたりするけど、そういった機能を実現してるのは「メモリにデータを書き込む」とか「キャッシュから読みだしたデータをCPUが処理する」とかいった処理が複雑に関連してできてて、そう考えるとファイルとして読み書きするってのもそんなに違和感なくない?

c ファイルの種類を示す文字

cはキャラクタデバイス。他にもb(ブロックデバイス)もある。ほとんどのデバイスはキャラクタデバイスとして実装するのが適しているからとりあえずしばらくはc固定で。

メジャー番号=ファイルとデバイスドライバを紐付ける超重要な数値

システム上は/proc/devicesで管理されてる

メジャー番号 ドライバ内で設定した名前

cat /proc/devices 

Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 21 sg
 29 fb
 99 ppdev
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
216 rfcomm
252 hidraw
253 bsg
254 rtc

マイナー番号

1つのデバイスドライバで複数のハードウェアを制御する場合、ハードウェア毎に割り振ったりして使う。プリンターのドライバ一個入れておけばそのメーカーの複数型番のプリンターが使えるみたいな感じ。型番が違うけどだいたい処理は同じだから同じデバイスドライバで制御して、微妙な差異分はマイナー番号で型番を特定して型番毎に処理を分岐させたり。



まぁとりあえず作ってみましょ。

sudo mknod ./testdev c 250 0

普通はカレントディレクトリに作ったりしないけど細かいことは気にしない!


llコマンドで見てみると・・・

crw-r--r--  1 root   root       250, 0  92 22:51 testdev

まず先頭がcになっている。これはさっき指定したc。250, 0も指定したメジャー番号とマイナー番号。よしよしちゃんと作れた。これで呼び出し側の準備はオッケー

デバイスドライバ側の処理

前回作ったハローワールドをベースにいくつか処理を追加した。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>


//プロトタイプ
static __init int test_driver_init(void);
static __exit void test_driver_exit(void);
int test_driver_open(struct inode *inode, struct file *filp);
int test_driver_release(struct inode *inode, struct file *filp);

//グローバル変数
static struct file_operations test_driver_fops ={
    .owner          = THIS_MODULE,
    .open           = test_driver_open,
    .release        = test_driver_release,
};
static dev_t dev_id; 
struct cdev c_dev; 

// マクロ
module_init(test_driver_init);
module_exit(test_driver_exit);
MODULE_LICENSE("GPL2");


//デバイスドライバオープン関数
int test_driver_open(struct inode *inode, struct file *filp) 
{
    printk(KERN_DEBUG "open call \n");
    return 0;
}

//デバイスドライバクローズ関数
int test_driver_release(struct inode *inode, struct file *filp)
{
    printk(KERN_DEBUG "release call\n");
    return 0;
}


//モジュール初期化関数
static int __init test_driver_init(void)
{
    printk(KERN_DEBUG "init call\n");
    dev_id = MKDEV(250 ,0);
    register_chrdev_region(dev_id,1,"test_driver_base");
    
    cdev_init(&c_dev, &test_driver_fops);
    cdev_add(&c_dev, dev_id,4);
    
   return 0;
}

//モジュール終了関数
static void __exit test_driver_exit(void)
{
    printk(KERN_DEBUG "_init exit\n");
    cdev_del(&c_dev);
    unregister_chrdev_region(dev_id, 1);
}

ハローワールドからの超重要な変更1つ目は

cdev構造体変数を追加
struct cdev c_dev; 

この構造体変数はキャラクタデバイスを表す超超重要な変数。カーネルはこの変数を介して作成したデバイスドライバに仕事を振ってくる。


実際にc_devをどう使ってるかも見ていくよ〜。




変更2つ目は

ファイル操作時の関数登録

ハローワールドの時はデバイスのロードとアンロードだけをハンドリングしたけど、それだけじゃユーザランドからの呼び出し口がないからmknodでファイルを作った。そうするとカーネルさんがこのファイルへの操作(open/close/read/write等)をハンドリングしてドライバに伝えてくれるから、それに備えた実装をする必要がある

それを実現しているのがコレ↓

cdev_init(&c_dev, &test_driver_fops);

さっき述べた超超重要な構造体変数(c_dev)と、ファイル操作関数ポインタ群(file_operations構造体)を渡している。
ファイル操作関数ポインタ群っていうのは「mknodで作ったファイルがopenされたら、このドライバに定義しているtest_driver_openを呼んでね」という意味の関数ポインタなどを含んだ変数だ。

static struct file_operations test_driver_fops ={
    .owner          = THIS_MODULE,
    .open           = test_driver_open,
    .release        = test_driver_release,
};

変更3つ目は

メジャー番号の取得

ファイル操作時の関数登録が終わって意気揚々とmknodで作ったファイルをオープン!・・・

しても失敗します。

なぜならtestdevはメジャー番号250のドライバに紐付いているから。

crw-r--r--  1 root   root       250, 0  92 22:51 testdev

ファイル操作時の関数登録しただけではまだどのファイル(メジャー番号)とも紐付いていないのだ。




メジャー番号を登録しているのはこの2行

dev_id = MKDEV(250 ,0);
register_chrdev_region(dev_id,1,"test_driver_base");

これを実行すると/proc/devices にメジャー番号とドライバ名前(任意)の関連が書き込まれる

sudo cat /proc/devices 
Character devices:
・
・
250 test_driver_base
・
・

後処理もわすれずに

c_dev変数はカーネルからの呼び出しにつながっているから、終了時は明示的な開放が必要。メジャー番号も同様。

cdev_del(&c_dev);
unregister_chrdev_region(dev_id, 1);

ユーザランドからデバイスドライバを呼び出す

さてようやく準備ができた
デバイスドライバをビルド(ハローワールドと同じ手順)してロードして・・・

ユーザランド側のコードをチョロチョロっと書いてみる

test_user.c

#include <stdio.h>
#include <fcntl.h> 
#include <unistd.h>
int main()
{
    int fd=open("./testdev",O_RDWR);
    printf("fd =%d \n",fd);
    close(fd);
    return 0;
}

普通にビルド

gcc test_user.c


実行し、成功するとfc=正数となる
syslogにもprintkで書いたopenやcloseのメッセージが出る。


ユーザランドからデバイスドライバの最もシンプルな呼び出し、完了!

次回はreadを追加するよー。
public2016.hatenablog.com

デバイスドライバのハローワールド

デバイスドライバと言うとなんだか物々しく聞こえて難しく感じてしまう。

でも実際のところ普通のプログラムとの違いとしては
1 デバイスドライバカーネル空間で動作し、普通のプログラムはユーザ空間で動作する。
2 カーネルに用意されているデバイスドライバ用の仕組みに乗っかる必要がある
くらいのものだ。

普通のプログラムとは

test_hutuuu.c

#include <stdio.h>
int main()
{
    printf("はろーわーるど\n");
    return 0;
}

こんな簡単なので

gcc ./test_hutuuu.c

ビルドして

./a.out 

実行すれば

はろーわーるど

と結果が出る。


ユーザ空間でプログラムを実行する場合は
「mainがエントリーポイント」
ということが決まっている。そういう仕組み。


さてさてデバイスドライバの簡単なコードは・・・

test_driver.c

#include <linux/init.h>
#include <linux/module.h>


//モジュール初期化関数
static int __init test_driver_init(void)
{
    printk(KERN_DEBUG "ハローワールド\n");
    return 0;
}

//モジュール終了関数
static void __exit test_driver_exit(void)
{
    printk(KERN_DEBUG "ばいばい\n");
}


module_init(test_driver_init);
module_exit(test_driver_exit);
MODULE_LICENSE("GPL2");

mainのハローワールドと比べると少し複雑。


ビルド用にmakefileをtest_driver.cと同じ階層に作って
makefile

obj-m := driverModule.o
driverModule-objs := test_driver.o
PWD := $(shell pwd)
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
install:
	insmod driverModule.ko
uninstall:
	rmmod driverModule.ko
clean:
	rm -f *.o *.ko *.mod.c Module.* module* *~

※コピペする場合は先頭の空白は全てタブに置き換えてね。makefileの謎ルールなので

make

ビルドして


できあがったカーネルオブジェクト(*.ko)をカーネルにロード!とその前に・・・

新しい端末を起動しsyslogを表示する。ドライバではprintfではなくprintkを使うのでカーネルログを見る

sudo tail -f /var/log/syslog

(tailを使うと追記分も表示されるから便利)

sudo insmod driverModule.ko

準備ができた所でドライバをロード!


するとsyslogに

Sep  1 22:00:56 debian kernel: [ 1052.234220] ハローワールド

こんなメッセージが出るはず。ハローワールドを実行するにも一苦労だ!



デバイスドライバのハローワールドで特に重要なのは
・「module_init」でロード時のエントリーポイントを指定できる
・「module_exit」でアンロード時の後処理関数を指定できる
ということ。

//カーネルモジュールをロードしたときは
//  初期化(init)処理でtest_driver_initを実行してね、とカーネルに通知
module_init(test_driver_init);

//カーネルモジュールをアンロードしたときは
//  後始末としてtest_driver_exitを実行してね、とカーネルに通知
module_exit(test_driver_exit);

module_initとmodule_exitを使用しない場合でも、実はビルドもロードも普通にできる。でもそんなことをするとロードされたことをドライバは検知できないから、ただメモリ上に存在するだけで何もしないプログラムになってまう・・・。何の役にも立たない・・・。

module_initはデバイスドライバとすべての起点となる重要なものなのだ。

module_initとmodule_exitはカーネルが用意してくれたデバイスドライバのための仕組みなので、盛大に利用しましょう!


public2016.hatenablog.com
次はユーザランドからの呼び出しについて