ホームページ >システムチュートリアル >Linux >Linux 中級 - 「ドライバー」 ハードウェアを制御するために習得する必要がある低レベルの知識

Linux 中級 - 「ドライバー」 ハードウェアを制御するために習得する必要がある低レベルの知識

WBOY
WBOY転載
2024-02-12 15:21:131267ブラウズ
Linux中级——“驱动” 控制硬件必须学会的底层知识

駆動された認知

1.ドライバーとは

ドライバーは、基礎となるハードウェア デバイスの操作をカプセル化し、上位層に関数インターフェイスを提供します。

デバイス分類: Linux システムは、デバイスを キャラクター デバイス、ブロック デバイス、およびネットワーク デバイスの 3 つのカテゴリに分類します。

  • キャラクター デバイス: バイトごとの読み取りと書き込みのみが可能なデバイスを指します。デバイス メモリ内の特定のデータはランダムに読み取ることができません。データは順番に読み取る必要があります。キャラクタ デバイスはストリーム指向のデバイスです。一般的なキャラクタ デバイスには、マウス、キーボード、シリアル ポート、コンソール、LED デバイスが含まれます。キャラクタ デバイス ドライバは通常、少なくともオープン、クローズ、読み取り、および書き込みのシステム コールを実装する必要があります , キャラクターターミナル (/dev/console) とシリアルポート (/dev/ttyS0 および同様のデバイス) は 2 つのキャラクターデバイスであり、「ストリーム」の抽象的な概念をよく示しています。
  • ブロックデバイス: デバイス上の任意の場所から特定の長さのデータを読み取ることができるデバイスを指します。ブロック デバイスには、ハード ドライブ、ディスク、USB フラッシュ ドライブ、SD カードなどが含まれます。
  • ネットワーク デバイス: ネットワーク デバイスは、ネットワーク カードなどのハードウェア デバイスである場合もありますが、ループバック インターフェイス (lo) などの純粋なソフトウェア デバイスである場合もあります。ネットワーク インターフェイスは、送信を担当します。データパケットを受信して​​います。 Linux中级——“驱动” 控制硬件必须学会的底层知识

呼び出しプロセス全体について説明する例を示しましょう

    上位層では、C 言語のオープン関数
  1. open("/dev/pin4",O_RDWR); /dev の下の pin4 を呼び出して、読み取りおよび書き込み可能な方法で開きます。 **==上位層のオープン呼び出し カーネルでソフト割り込みが発生します。割り込み番号は 0X80 で、ユーザー空間からカーネル空間に入ります ==**
  2. open は
  3. system_call (カーネル関数) を呼び出し、system_call は /dev/pin4 デバイス名に基づいて必要なデバイス番号を見つけます。
  4. 次に、仮想ファイル
  5. VFS (上位層を統合して正確なハードウェアを呼び出すために ) に転送し、VFS で sys_open を呼び出します。sys_open がドライバーを見つけます。 リンク リスト で、 メジャー デバイス番号とマイナー デバイス番号 に基づいてピン 4 のオープン関数を見つけます。 ピン 4 のオープンはレジスタ操作です
ここに画像の説明を挿入します

#「

ドライバーは単に

ドライバーを追加するだけであると書きます

:ドライバーを追加すると何が行われるのでしょうか?

機器名######
    デバイス番号
  1. ## デバイスドライバ機能(IOポートを駆動するための操作レジスタ)

==要約==dev の下の pin4 ピンをオープンする場合、プロセスは次のとおりです。 ユーザー モードは open # を呼び出します。 ## ("/de/pin4", O_RDWR)、カーネルの場合、上位層から open 関数を呼び出すとソフト割り込みがトリガーされます (システム コールの場合は特別で、割り込み番号は 0x80 で、0x80 は 0x80 を表します)システム イベント) コール)、 システムはカーネル状態 に入り、system_call に進みます。これは、このソフト割り込みの割り込みサービス プログラムへの入り口と考えることができ、その後、渡されたシステムコール番号に基づいて対応するコールを決定し、システムコールサービスプログラム(この場合、VFSsys_open が呼び出されます)を呼び出します。 sys_open は、デバイス名とデバイス番号に基づいて、カーネル ドライバー リスト から関連するドライバー関数 を検索します (各ドライバー関数はノード )。 **= =ドライバー関数にはレジスタを通じて IO ポートを制御するコードが含まれており、これにより IO ポートを制御して関連関数を実装できます==**。

2. 各状態の詳細説明

#「

ユーザーモード:

# これは、ユーザーがプログラムを作成し、プログラムを実行するレベルを指します。
ユーザー モードでは、開発中に C と C ライブラリの基礎が必要です。C ライブラリは、ファイル、プロセス、プロセス間通信、スレッド、ネットワーク、およびインターフェイス (GTk)
    。 C ライブラリ (Linux 標準ライブラリに含まれている必要があります):
  • Clibary で、カーネルの動作を制御するプログラムのインターフェイスを提供します。open、read、write、fork、pthread実装はここでカプセル化され、記述されたアプリケーションによって呼び出されます。C ライブラリ内のさまざまな API は、カーネル状態を呼び出し、カーネルの動作を制御します。 #「
カーネル状態:

  • ユーザーが特定のハードウェア デバイスを使用したい場合は、前の記事で説明したように、カーネル モード デバイス ドライバー,ハードウェアを動作させるためにが必要です WireringPi ライブラリ , は、ユーザーがハードウェア デバイスを制御するためのインターフェイス を提供します。wiringPi ライブラリがない場合は、wiringPi ライブラリの機能を自分で実装する、つまりデバイス ドライバを自分で記述する必要があります。このようにして、別のタイプの基板が入手できたら、開発も完了することができます。

  • Linux ではすべてがファイルです. あらゆる種類のファイルとデバイス (下図に示すマウス、キーボード、画面、フラッシュ、メモリ、ネットワーク カードなど) はすべてファイルです。 ファイルなのでファイル操作機能を利用してこれらの機器を操作することができますLinux中级——“驱动” 控制硬件必须学会的底层知识

  • 1 つの質問は、open や read などのファイル操作関数は、開かれているファイルがどのハードウェア デバイスであるかをどのようにして知るのかということです。 open関数に対応するファイル名を入力して、対応するデバイスを制御します。 ②==デバイス番号(メジャーデバイス番号とマイナーデバイス番号)==を渡します。さらに、これらのドライバーの場所と、これらのドライバーの実装方法も理解する必要があります。各ハードウェア デバイスは、異なるドライバーに対応します (これらのドライバーは独自に実装されます)

  • Linux デバイス管理はファイル システムと密接に統合されていますさまざまなデバイスは、==devices File= というファイル形式で /dev ディレクトリに保存されます =*。アプリケーションは、通常のデータ ファイルを操作するのと同じように、これらのデバイス ファイルを開いたり、閉じたり、読み書きしたり、デバイス上で操作を完了したりできます。 **これらのデバイスを管理するために、システムはデバイスに番号を付けます**。*各デバイス番号は、==メジャー デバイス番号== と ==マイナー デバイス番号==* に分割されます (図に示すように)。下の図) を表示します:)。 *Linux中级——“驱动” 控制硬件必须学会的底层知识*メジャーデバイス番号**は、さまざまな種類のデバイスを区別するために使用されます。 devices であり、最初のデバイス番号 は、同じタイプの複数のデバイスを区別するために使用されます。 一般的に使用されるデバイスについては、Linux には従来の番号があります。たとえば、ハードディスクのメジャー デバイス番号は 3 です。 ****キャラクタ デバイスまたはブロック デバイスには、メジャー デバイス番号とマイナー デバイス番号

    があります。
    ==メジャーデバイス番号とマイナーデバイス番号を総称してデバイス番号==**と呼びます。

    #「

    メジャーデバイス番号
    は、特定のドライバーを表すために使用されます。 マイナーデバイス番号

    は、このドライバーを使用する各デバイスを表すために使用されます。

    #たとえば、組み込みシステムには 2 つの LED インジケーターがあり、LED ライトを個別にオンまたはオフにする必要があります。次に、LED ライト用の キャラクター デバイス ドライバ を作成し、その プライマリ デバイス番号をデバイス番号 5 として登録します。 セカンダリ デバイス番号は 1 と 2 です。 ## それぞれ。 #。ここで、セカンダリデバイス番号はそれぞれ 2 つの LED ライトを表します。

==ドライバーのリンクされたリスト==

#「

すべてのデバイスのドライバーの管理、追加または検索
追加は、ドライバーの作成が完了してカーネルにロードされた後に行われますSearch
はドライバーを呼び出しており、アプリケーション層のユーザー空間はオープン関数 を使用して検索します。 ドライバーがリンク リストに挿入される順序は、

デバイス番号

によって取得されます。つまり、メジャー デバイス番号とマイナー デバイス番号は、異なるデバイスを区別するだけでなく、デバイスの種類と異なる種類のデバイスを区別するだけでなく、ドライバを区別する役割も果たします プログラムはリンク リストの特定の位置にロードされます. 以下に紹介するドライバ コードの開発は、ドライバを追加することに他なりません。 (デバイス番号、デバイス名、デバイス ドライバー関数の追加) および ドライバーの呼び出し

#system_call 関数はどのようにして詳細なシステム コール サービス ルーチンを見つけますか?
    システム コール番号からシステム コール テーブル sys_call_table を見つけます。
  • ソフト割り込み命令 INT 0x80 が実行されているとき、システム コール番号は eax レジスタ に配置されます。system_call 関数は、eax レジスタを読み取ってそれを取得し、それを 4 で乗算して生成します。オフセット アドレスを指定し、sys_call_table をベース アドレスとして取得します。ベースアドレスとオフセットアドレスを加算すると、詳細なシステムコールサービスルーチンのアドレスが得られます。次にシステムコールサービスルーチンに入ります。 補充:

各システムコールはシステムコール番号に対応しており、システムコール番号はカーネル内の対応する処理関数に対応している。

    すべてのシステム コールは割り込み 0x80 によってトリガーされます。
  1. システム コールを使用する場合、システム コール番号は eax レジスタを通じてカーネルに渡され、システム コールの入力パラメータは ebx、ecx...
  2. を通じてカーネルに渡されます。
  3. システムコールの戻り値も関数と同様にeaxに格納されており、eax
  4. からすべて取り出す必要があります。
  5. 3. キャラクターデバイスドライバーの動作原理

キャラクター デバイス ドライバーの動作原理 Linux の世界では、すべてがファイルであり、すべてのハードウェア デバイス操作はアプリケーション層でファイル操作に抽象化されます。アプリケーション層がハードウェア デバイスにアクセスしたい場合は、ハードウェアに対応するドライバーを呼び出す必要があることがわかっています。 Linux カーネルには非常に多くのドライバーが含まれていますが、アプリケーションはどのようにして基礎となるドライバーを正確に呼び出すことができるのでしょうか?

==知っておくべき知識:==

  1. Linux ファイル システムでは、各ファイルは struct inode 構造によって記述され、この構造には ファイル タイプ、アクセス許可 などのファイルのすべての情報が記録されます。
  2. Linux オペレーティング システムでは、各ドライバーはアプリケーション層の
  3. /dev ディレクトリまたは /sys などの他のディレクトリに対応するファイルを持ちます。
  4. Linux オペレーティング システムでは、
  5. 各ドライバーにはデバイス番号があります。
  6. Linux オペレーティング システムでは、ファイルが開かれるたびに、Linux オペレーティング システム
  7. が VFS 層で ****struct file 構造を割り当て、開いているファイル を記述します。
Linux中级——“驱动” 控制硬件必须学会的底层知识(1) open 関数でデバイスファイルをオープンすると、対応する struct inode 構造体に記述されている情報から、次に操作するデバイスの種類(キャラクタデバイスかブロックデバイスか)がわかります。デバイス ファイルに追加すると、構造体ファイル構造も割り当てられます。

(2) struct inode 構造体に記録されているデバイス番号に基づいて、対応するドライバーが見つかります。ここではキャラクターデバイスを例に挙げます。 Linux オペレーティング システムでは、各キャラクター デバイスに struct cdev 構造があります。この構造は、キャラクタ デバイスのすべての情報を記述します。その中で最も重要なのは、キャラクタ デバイスの操作機能インターフェイスです。

(3) struct cdev 構造体を見つけた後、Linux カーネルは、struct cdev 構造体が配置されているメモリ空間の最初のアドレスを struct inode 構造体の i_cdev メンバーに記録し、に記録されている関数操作インターフェイスを使用します。 struct cdev 構造体 アドレスは、struct ファイル構造体の f_ops メンバーに記録されます。

(4) タスクが完了すると、VFS 層はファイル記述子 (fd) をアプリケーションに返します。この fd は struct ファイル構造に対応します。次に、上位層アプリケーションは fd を通じて struct ファイルを見つけ、その struct ファイル内でキャラクターデバイスを操作するための関数インターフェース file_operation を見つけます。

このうち、cdev_init と cdev_add は、それぞれキャラクタ デバイスと file_operation 関数の操作インターフェイスのバインドを完了し、キャラクタ ドライバをカーネルに登録するために、ドライバのエントリ関数内で呼び出されています。

フレームワークに基づいてドライバー コードを作成します:

    上位層呼び出しコード: オペレーション駆動型上位層コード (pin4test.c):
  • rree
-カーネル ドライバー **==最も単純なキャラクター デバイス ドライバー フレームワーク==**:

キャラクターデバイスドライバーフレームワークコード リーリー

デバイス名を手動で作成する

  • 上記のキャラクター デバイス ドライバー コードには、コードが dev の下にデバイスを自動的に生成できるようにするための が含まれています。さらに、デバイス名 を手動で作成することもできます。使用する命令: sudo mknod デバイス名 デバイス タイプ (c はキャラクター デバイス ドライバーを表します) メジャー デバイス番号 マイナー デバイス番号 b: ブロック (バッファーされた) 特殊ファイルを作成します。 c、u: キャラクター (バッファなし) スペシャル ファイルを作成します。 p: FIFO を作成します。 手動で作成したデバイス名を削除して、rm だけを実行します。以下に示すように:

ドライバー フレームワークの実行プロセス:

  • 上位層プログラム を通じてデバイスを開きます。ドライバーがない場合は、実行時にエラーが報告されます。カーネル ドライバーでは、上位層システムが open および wirte を呼び出します。関数は sys_call をトリガーし、sys_call は sys_open、 および sys_write、sys_open、および sys_write を呼び出し、カーネルの ## で メジャー デバイス番号 を渡します。 # ドライバーのリンクされたリスト デバイス ドライバーを見つけて、開いて内部に書き込むを実行します。プロセス全体をスムーズに進めるために、最初にドライバー (デバイス ドライバー ファイル) を準備する必要があります。

  • デバイス ドライバー ファイルには固定フレームがあります:

    1. module_init(pin4_drv_init); //pin4_drv_initfunction
    2. を呼び出すための入り口
    3. int __init pin4_drv_init(void) //実際のドライバーエントリ
    4. ドライバーエントリーdevno = MKDEV(major,minor); // デバイス番号を作成
    5. register_chrdev(major, module_name,&pin4_fops); //登録ドライバーは、上で準備した構造をカーネル ドライバーのリンク リストに追加するようにカーネルに指示します
    6. pin4_class=class_create(THIS_MODULE,"myfirstdemo");//デバイスは dev のコードによって自動的に生成され、クラスが作成されます
    7. pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //デバイスファイルを作成します。
    8. 主なことは、/dev に上位層が開くための追加ファイルを持たせることです。
    9. そうでない場合は、デバイスを手動で作成することもできます。sudo mknod デバイス名デバイス タイプ (c はキャラクター デバイス ドライバーを表します) メジャー デバイス番号マイナー デバイス番号

ドライバー モジュール コードのコンパイル

ドライバー モジュール コードのコンパイル

ドライバー モジュール コードのコンパイル (モジュールのコンパイルには、構成されたカーネル ソース コードが必要です。コンパイルと接続後に生成されるカーネル モジュールのサフィックスは、**.ko です。コンパイル プロセスは、最初にカーネル ソース コード ディレクトリで、最上位の Makefile ファイルを読み取り、モジュール ソース コードが配置されているディレクトリに戻ります。): **

  • 使用下面的的代码:(就是上面的驱动架构代码)
#include             //file_operations声明
#include     //module_init  module_exit声明
#include       //__init  __exit 宏定义声明
#include         //class  devise声明
#include    //copy_from_user 的头文件
#include      //设备号  dev_t 类型声明
#include           //ioremap iounmap的头文件


static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;                //设备号
static int major =231;                     //主设备号
static int minor =0;                       //次设备号
static char *module_name="pin4";   //模块名

//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
        printk("pin4_open\n");  //内核的打印函数和printf类似

        return 0;
}
//read函数
static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos)
{
        printk("pin4_read\n");  //内核的打印函数和printf类似

        return 0;
}

//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{

        printk("pin4_write\n");  //内核的打印函数和printf类似
        return 0;
}

static struct file_operations pin4_fops = {

        .owner = THIS_MODULE,
        .open  = pin4_open,
        .write = pin4_write,
        .read  = pin4_read,
};
//static限定这个结构体的作用,仅仅只在这个文件。
int __init pin4_drv_init(void)   //真实的驱动入口
{

        int ret;
        devno = MKDEV(major,minor);  //创建设备号
  ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

        pin4_class=class_create(THIS_MODULE,"myfirstdemo");//让代码在dev下自动>生成设备
        pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件


        return 0;
}

void __exit pin4_drv_exit(void)
{

        device_destroy(pin4_class,devno);
        class_destroy(pin4_class);
        unregister_chrdev(major, module_name);  //卸载驱动
}
module_init(pin4_drv_init);  //入口,内核加载驱动的时候,这个宏会被调用,去调用pin4_drv_init这个函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
  • 在导入虚拟机的内核代码中找到字符设备驱动的那一个文件夹:/SYSTEM/linux-rpi-4.19.y/drivers/char将以上代码复制到一个文件中,然后下一步要做的是就是:将上面的驱动代码编译生成模块,再修改Makefile。(你放那个文件下,就改哪个文件下的Makefile)
  • 文件内容如下图所示:(-y表示编译进内核,-m表示生成驱动模块,CONFIG_表示是根据config生成的) 所以只需要将obj-m += pin4drive.o添加到Makefile中即可。下图:Makefile文件图Linux中级——“驱动” 控制硬件必须学会的底层知识
  • 编译生成驱动模块,将生成的**.ko文件发送给树莓派**然后回/SYSTEM/linux-rpi-4.19.y下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules进行编译生成驱动模块。然后将生成的.ko文件发送给树莓派:scp drivers/char/pin4driver.ko pi@192.168.0.104:/home/pi编译生成驱动模块会生成以下几个文件:Linux中级——“驱动” 控制硬件必须学会的底层知识
  • .o的文件是object文件,.ko是kernel object,与.o的区别在于其多了一些sections,比如.modinfo.modinfo section是由kernel source里的modpost工具生成的, 包括MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE, device ID table以及模块依赖关系等等。depmod 工具根据.modinfo section生成modules.dep, modules.*map等文件,以便modprobe更方便的加载模块。

  • コンパイルプロセスでは、次の手順を実行しました:
  1. まず、Linux カーネルが配置されているディレクトリに移動し、pin4drive.o ファイルをコンパイルします。
  2. MODPOST を実行すると、一時的な pin4drive.mod.c ファイルが生成され、このファイルに基づいて pin4drive.mod.o がコンパイルされます (
  3. )
  4. 次に、pin4drive.o ファイルと pin4drive.mod.o ファイルを接続して、モジュール ターゲット ファイル pin4drive.ko、
  5. を取得します。
  6. 最後に、Linux カーネルが配置されているディレクトリをそのまま残します。

pin4test.c (上位層の呼び出しコード) をクロスコンパイルして Raspberry Pi に送信すると、送信された .ko がpi ディレクトリ ファイル pin4test の 2 つのファイルは、以下に示すとおりです。 カーネルドライバーをロードしますLinux中级——“驱动” 控制硬件必须学会的底层知识

次に、コマンドを使用します: sudo insmod pin4drive.koカーネル ドライバーをロードします (insmod を介して module_init マクロを呼び出し、構造全体をドライバーのリンク リストにロードするのと同じです) ロードが完了したら

dev

以下に、pin4 という名前のデバイス ドライバーが表示されます (これはコード行に関連しています static char *module_name=”pin4″; //ドライバー コード内のモジュール名)、デバイス番号もコードに関連します。 lsmodドライバがインストールされていることが確認できます。

もう一度 ./pin4test を実行して上位コードを実行しましょう

上位コードを実行すると次のエラーが発生します: 権限がないことを示します
    コマンドを使用します:
  • sudo chmod 666 /dev/ pin4pin4 にアクセス許可を与え、誰もが正常に開くことができるようにします。 Linux中级——“驱动” 控制硬件必须学会的底层知识 その後、再度実行してくださいpin4test
  • 表面上は何も出力されていませんが、実際にはカーネル内に印刷情報がありますが、上位層からは見えません。カーネル
によって出力された情報を表示したい場合は、コマンド

dmesg |grep pin4 を使用できます。以下の図に示すように: ドライバー呼び出しが成功したことを示します ドライバーをインストールした後、コマンド sudo rmmod ドライバー名 (ko を記述する必要はありません) を使用してドライバーをアンインストールできます。

ドライバー モジュールの生成を仮想マシン上で生成する必要がある理由

  • ドライバー モジュールの生成を仮想マシン上で生成する必要があるのはなぜですか?ラズベリーパイは動かないのですか?

    ドライバー モジュールの生成にはコンパイル環境が必要です (Linux ソース コードとコンパイルには、システム バージョンと同じ Linux カーネル ソース コードをダウンロードする必要があります)。Raspberry Pi でもコンパイルできますが、 Raspberry Pi でコンパイルするとより効率的ですが、非常に低いため、非常に長い時間がかかります。この記事では、Raspberry Pi ドライバーのローカル コンパイルについて説明します。

以上がLinux 中級 - 「ドライバー」 ハードウェアを制御するために習得する必要がある低レベルの知識の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlxlinux.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。