Linux用のドライバーの作成

画像



ウィキペディアによると、ドライバーはコンピュータープログラムであり、別のプログラム(通常はオペレーティングシステム)が何らかのデバイスのハードウェアにアクセスするためです。 そして今日は、ドライバー用に何らかのブランクを作成します。 実際、鉄は一切使用しません。 必要に応じて、この便利な機能を自分で追加できます。



今日作成するものは、より正確にLKM (Linuxカーネルモジュールまたはカーネルブートモジュール)と呼ばれます。 ドライバーはLKMの種類の1つであると言う価値があります。



2.6行のカーネル用のモジュールを作成します。 2.6のLKMは2.4とは異なります。 これは投稿の範囲に含まれていないため、違いについては触れません。



キャラクターデバイス/ dev / testを作成し、モジュールで処理します。 キャラクターデバイスを/ devディレクトリに配置する必要はなく、「古代の魔法の儀式」の一部にすぎないことをすぐに予約したいと思います。





理論のビット



要するに、LKMは、すでに実行されているLinuxカーネルの機能を拡張するコードを含むオブジェクトです。 つまり ユーザーではなく、カーネル空間で動作します。 そのため、実稼働サーバーで実験しないでください。 モジュールに忍び込むエラーの場合、カーネルパニックを取得します。 私はあなたに警告したと仮定します。



カーネルモジュールには、少なくとも2つの関数(初期化関数と出力関数)が必要です。 最初は、モジュールがカーネル空間にロードされるときに呼び出され、2番目は、アンロードされるときにそれぞれ呼び出されます。 これらの関数は、マクロ定義module_initおよびmodule_exitを使用して定義されます。



printk()関数について少し説明する価値があります。 この関数の主な目的は、イベントと警告を記録するメカニズムを実装することです。 つまり、この関数は、カーネルログに情報を書き込むためのものです。



なぜなら ドライバーはカーネル空間で実行されるため、ユーザーのアドレス空間から区切られます。 そして、特定の結果を返すことができるようにしたいと思います。 このために、 put_user()関数が使用されます。 彼女は、カーネル空間からユーザーにデータを投げるだけです。



キャラクターデバイスについて少し言いたいです。



コマンドls -l /dev/sda*



実行します。 次のようなものが表示されます。

brw-rw---- 1 root disk 8, 0 2010-10-11 10:23 /dev/sda

brw-rw---- 1 root disk 8, 1 2010-10-11 10:23 /dev/sda1

brw-rw---- 1 root disk 8, 2 2010-10-11 10:23 /dev/sda2

brw-rw---- 1 root disk 8, 5 2010-10-11 10:23 /dev/sda5









「ディスク」という単語と日付の間には、コンマで区切られた2つの数字があります。 最初の番号は、メジャーデバイス番号と呼ばれます。 メジャー番号は、このデバイスのサービスに使用されるドライバーを示します。 各ドライバーには、固有のメジャー番号があります。



デバイスファイルは、 mknodコマンドを使用して作成されます(例: mknod /dev/test c 12



。 このコマンドを使用して、デバイス/ dev / testを作成し、メジャー番号(12)を示します。



私は理論に深く入りません 興味のある人-彼はそれについてより詳細に読むことができます。 最後にリンクを提供します。



始める前に





いくつかの「魔法の」コマンドを知っておく必要があります。





モジュールをコンパイルするには、現在のカーネルのヘッ​​ダーが必要です。



debian / ubutnuでは、次のように簡単に配置できます(たとえば、2.6.26-2-686の場合)。

apt-get install linux-headers-2.6.26-2-686





または、現在のカーネル用のパッケージを自分でビルドします: fakeroot make-kpkg kernel_headers







ソースコード





#include <linux/kernel.h> /* printk() .. */

#include <linux/module.h> /* , */

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

#include <linux/fs.h>

#include <asm/uaccess.h> /* put_user */



// , Modinfo

MODULE_LICENSE( "GPL" );

MODULE_AUTHOR( "Alex Petrov <petroff.alex@gmail.com>" );

MODULE_DESCRIPTION( "My nice module" );

MODULE_SUPPORTED_DEVICE( "test" ); /* /dev/testdevice */



#define SUCCESS 0

#define DEVICE_NAME "test" /* */



//

static int device_open( struct inode *, struct file * );

static int device_release( struct inode *, struct file * );

static ssize_t device_read( struct file *, char *, size_t, loff_t * );

static ssize_t device_write( struct file *, const char *, size_t, loff_t * );



// , static, .

static int major_number; /* */

static int is_device_open = 0; /* ? */

static char text[ 5 ] = "test\n" ; /* , */

static char * text_ptr = text; /* */



//

static struct file_operations fops =

{

.read = device_read,

.write = device_write,

.open = device_open,

.release = device_release

};



// . . main()

static int __init test_init( void )

{

printk( KERN_ALERT "TEST driver loaded!\n" );



//

major_number = register_chrdev( 0, DEVICE_NAME, &fops );



if ( major_number < 0 )

{

printk( "Registering the character device failed with %d\n" , major_number );

return major_number;

}



//

printk( "Test module is loaded!\n" );



printk( "Please, create a dev file with 'mknod /dev/test c %d 0'.\n" , major_number );



return SUCCESS;

}



//

static void __exit test_exit( void )

{

//

unregister_chrdev( major_number, DEVICE_NAME );



printk( KERN_ALERT "Test module is unloaded!\n" );

}



//

module_init( test_init );

module_exit( test_exit );



static int device_open( struct inode *inode, struct file *file )

{

text_ptr = text;



if ( is_device_open )

return -EBUSY;



is_device_open++;



return SUCCESS;

}



static int device_release( struct inode *inode, struct file *file )

{

is_device_open--;

return SUCCESS;

}



static ssize_t



device_write( struct file *filp, const char *buff, size_t len, loff_t * off )

{

printk( "Sorry, this operation isn't supported.\n" );

return -EINVAL;

}



static ssize_t device_read( struct file *filp, /* include/linux/fs.h */

char *buffer, /* buffer */

size_t length, /* buffer length */

loff_t * offset )

{

int byte_read = 0;



if ( *text_ptr == 0 )

return 0;



while ( length && *text_ptr )

{

put_user( *( text_ptr++ ), buffer++ );

length--;

byte_read++;

}



return byte_read;

}



* This source code was highlighted with Source Code Highlighter .








モジュールの組み立て





さて、これで小さなMakefileを書くことができます。



obj-m += test.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









そして、そのパフォーマンスを確認します。



root @ joker:/ tmp / test# make

make -C /lib/modules/2.6.26-2-openvz-amd64/build M=/tmp/test modules

make[1]: Entering directory `/usr/src/linux-headers-2.6.26-2-openvz-amd64'

CC [M] /tmp/1/test.o

Building modules, stage 2.

MODPOST 1 modules

CC /tmp/test/test.mod.o

LD [M] /tmp/test/test.ko

make[1]: Leaving directory `/usr/src/linux-headers-2.6.26-2-openvz-amd64'










私たちが得たものを見てみましょう:



root @ joker:/ tmp / test# ls -la

drwxr-xr-x 3 root root 4096 21 12:32 .

drwxrwxrwt 12 root root 4096 21 12:33 ..

-rw-r--r-- 1 root root 219 21 12:30 demo.sh

-rw-r--r-- 1 root root 161 21 12:30 Makefile

-rw-r--r-- 1 root root 22 21 12:32 modules.order

-rw-r--r-- 1 root root 0 21 12:32 Module.symvers

-rw-r--r-- 1 root root 2940 21 12:30 test.c

-rw-r--r-- 1 root root 10364 21 12:32 test.ko

-rw-r--r-- 1 root root 104 21 12:32 .test.ko.cmd

-rw-r--r-- 1 root root 717 21 12:32 test.mod.c

-rw-r--r-- 1 root root 6832 21 12:32 test.mod.o

-rw-r--r-- 1 root root 12867 21 12:32 .test.mod.o.cmd

-rw-r--r-- 1 root root 4424 21 12:32 test.o

-rw-r--r-- 1 root root 14361 21 12:32 .test.o.cmd

drwxr-xr-x 2 root root 4096 21 12:32 .tmp_versions










それでは、コンパイルしたばかりのモジュールに関する情報を見てみましょう。



ルート@ジョーカー:/ tmp / test# modinfo test.ko

filename: test.ko

description: My nice module

author: Alex Petrov <druid@joker.botik.ru>

license: GPL

depends:

vermagic: 2.6.26-2-openvz-amd64 SMP mod_unload modversions









最後に、モジュールをカーネルにインストールします。



ルート@ジョーカー:/ tmp / test# insmod test.ko



リスト付きのモジュールがあるかどうか見てみましょう:



root @ joker:/ tmp / test# lsmod | grepテスト



test 6920 0









そして、ログに記録されたもの:



root @ joker:/ tmp / test# dmesg | しっぽ



[829528.598922] Test module is loaded!

[829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'.









私たちのモジュールは、何をする必要があるかを教えてくれます。



彼のアドバイスに従ってください:



root @ joker:/ tmp / test# mknod / dev / test c 249 0



最後に、モジュールが機能するかどうかを確認しましょう。



ルート@ジョーカー:/ tmp / test# cat / dev / test



test







私たちのモジュールは、ユーザーからのデータの受信をサポートしていません。



root @ joker:/ tmp / test# echo 1> / dev / test



bash: echo: :







モジュールがアクションについて何を言っているか見てみましょう:



root @ joker:/ tmp / test# dmesg | しっぽ



[829528.598922] Test module is loaded!

[829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'.

[829747.462715] Sorry, this operation isn't supported.









削除する:



root @ joker:/ tmp / test# rmmod test



そして、彼が私たちに別れを告げるのを見てみましょう:



root @ joker:/ tmp / test# dmesg | しっぽ



[829528.598922] Test module is loaded!

[829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'.

[829747.462715] Sorry, this operation isn't supported.

[829893.681197] Test module is unloaded!









迷惑をかけないようにデバイスファイルを削除します。



root @ joker:/ tmp / test# rm / dev / test



おわりに





この「ワーク」のさらなる開発はあなた次第です。 デバイスにインターフェースを提供する実際のドライバーに変換するか、Linuxカーネルのさらなる研究に使用できます。



デバイスファイルを使用してsudoを作成するという完全にクレイジーなアイデアを思いつきました。 つまり / dev / testにコマンドを送信すると、rootとして実行されます。



文学





最後に、 LKMPG(Linux Kernel Module Programming Guide)スペルブックへのリンクを提供します



UPD:

上記のMakefileを使用してモジュールを構築していない場合もあります。

解決策:

1行だけでMakefileを作成します: obj-m + = test.o

そして、次のようにアセンブリを実行します。

make -C / usr / src / linux-headers-`uname -r` SUBDIRS = $ PWDモジュール



UPD2:

ソースのエラーを修正しました。

パーサーはバグが多く、「MODULE_DEscriptION( "My nice module");」を保存します。 module_descriptionでは、当然すべての文字は大文字です。



UPD3:

segoonは投稿にいくつかの修正を送信しました。



1)device_open()関数には、競合状態があります:



static int device_open(struct inode * inode、struct file * file)

{

text_ptr = text;



if(is_device_open)<<<<

return -EBUSY;



is_device_open ++; <<<<



成功を返す;

}



あるプロセスが別のプロセスによって実行時にis_device_openを増やす場合

if(is_device_open)とis_device_open ++の間のコマンドプロセス、その後

その結果、ファイルは2回開きます。 アトミックアクションの場合、使用する必要があります

atomic_XXX()シリーズの関数。



アトミック操作は、データを扱うすべての場所で使用する必要があります。 この場合、およびclose()で。



2)device_write()をまったく書き込めませんでした、なぜなら のハンドラー

デフォルトでは、write()自体はエラーを返します。



3)put_user()の場合、結果を確認することは必須です。 ゼロでない場合、

どちらかが必要

a)結果-EFAULTを返し、何もなかったふりをする(つまり、

これで、内部バッファから不完全に読み込まれたデータを削除しないでください。

データが一定であり、何も変更する必要がない場合

b)すでに書き込まれたバイト数を返します(これは部分読み取りと呼ばれ、

POSIXで許可されています)。 この場合、0を返さないことを確認する必要があります。

read()= 0は、ファイルが終了したことを意味しますが、そうではありません。



4)カーネルでは、0ではなく

const定数はSUCCESSです。 例外があります。たとえば、

ネットワークパケットハンドラー、ただし-EXXXが返される場所(コード

エラー)、または0(すべて順調)、使用されるのは定数0です。



より多くの機能をより適切なアナログに置き換えることができますが、これは

初心者による記事の理解が複雑になります:)



All Articles