Linuxキャラクターデバイスドライバー(1)--- cdev、file_operations、inode、ファイル構造の詳細、上位レベルのアプリケーションが基になるドライバーにアクセスする方法

参考資料:

      「Linuxドライバー開発入門と実際の戦闘」、概念とソースコードは、正確さのために主に「Linuxドライバー開発入門と実際の戦闘」を参照します。同時に、他のネチズンに共有してくれたことに心から感謝します。ほとんどのコンテンツは手入力です。間違いや脱落を訂正してください。ありがとうございます。

 Linuxデバイスドライバーのキャラクターデバイスドライバー
 https://www.linuxprobe.com/linux-device-driver.html

 Linuxキャラクターデバイスドライバー構造(1)-cdev構造、デバイス番号関連の知識分析
 https://blog.csdn.net/zqixiao_09/article/details/50839042


序文


    シリコンを蓄積しないと、1ステップで数千マイルも到達できず、小さな小川を蓄積しないと川はありません。
    

1.キャラクターデバイスとブロックデバイスの概念
    キャラクターデバイス:データの読み取りと書き込みを1バイトずつしか行えず、デバイスメモリ内の特定のデータをランダムに読み取ることができないデバイスを指します。データの読み取りは、データのシーケンスに従う必要があります。キャラクターデバイスはストリーム指向のデバイスであり、一般的なキャラクターデバイスには、マウス、キーボード、シリアルポート、コンソール、およびLEDデバイスが含まれます。

    ブロックデバイス:デバイスの任意の場所から特定の長さのデータを読み取ることができるデバイスを指します。読み取られたデータは順番に並んでいる必要はなく、デバイスの特定の場所に配置できます。ブロックデバイスには、ハードディスク、ディスク、Uディスク、SDカードが含まれます。

    各キャラクターデバイスまたはブロックデバイスは、/ devディレクトリ内のデバイスファイルに対応しています。Linuxユーザープログラムは、ドライバーを使用して、デバイスファイル(またはデバイスノード)を通じてキャラクターデバイスとブロックデバイスを操作します。

2. cdev構造
    は、cdev構造を使用してLinuxカーネルのキャラクターデバイスを記述します。この構造は、すべてのキャラクターデバイスを抽象化したものであり、多数のキャラクターデバイスに共通の機能が含まれています。メンバーdev_tを介してデバイス番号(メジャーデバイス番号とマイナーデバイス番号に分割)を定義して、キャラクターデバイスの一意性を判別します。メンバーfile_operationsを介して、キャラクターデバイスドライバーがVFSに提供するインターフェース関数(一般的なopen()、read()、write()など)を定義します。

    1)cdevj结构体:
    struct cdev {         struct kobject kobj;             struct module * owner;          struct file_operations * ops;         struct list_head list;             dev_t dev;         符号なし整数カウント。                     };






  •     kobj:カーネルデバイスドライバーモデルの管理に使用される組み込みkobject構造体で、通常は使用されません。
  •     owner:参照カウントに使用される、構造を含むモジュールへのポインター。通常はTHIS_MODULE;オーナーメンバーの存在は、ドライバーとカーネルモジュール間の密接な関係を反映しています。構造体モジュールは、カーネルによるモジュールの抽象化です。キャラクターデバイスのメンバーは、デバイスが属するモジュールを反映できます。ドライバーの記述では一般に、ユーザーによって明示的に初期化されます。owner= THIS_MODULE。
  •     ops:キャラクターデバイス操作セットへのポインター。オペレーションセットは、open()、read()、write()など、VFSにキャラクターデバイスドライバーによって提供されるインターフェイス関数を定義します。cdev_init()関数では、cdev構造体にリンクされています。
  •     dev:キャラクターデバイスの開始デバイス番号。デバイスは複数のデバイス番号を持つことができます。
  •     count:キャラクターデバイスによって駆動されるデバイスの数; rmmodを使用してモジュールをアンインストールするときに、countメンバーが0でない場合、システムはモジュールのアンインストールを許可しません。devメンバーとcountメンバーには、cdev_addで有効な値が割り当てられます。
  •     リスト:この構造は、このドライバーを使用してキャラクターデバイスをリンクリストに接続します。リスト構造は二重リンクリストであり、他の構造を二重リンクリストに接続するために使用されます。この構造はLinuxカーネルで広く使用されており、習得する必要があります。

    struct list_head {         struct list_head * next、* prev;     };

    2)cdevとiノードの関係を
    図に示します。cdev構造のリストメンバーは、iノード構造のi_devicesメンバーに接続されます。その中でも、i_devicesはlist_head構造でもあります。cdev構造とinodeノードが二重にリンクされたリストを形成するようにします。iノード構造は、/ devディレクトリ内のデバイスファイルを表します。

                                                    

                                                                       図1 cdevとinodeの関係
    各キャラクターデバイスには/ devディレクトリにデバイスファイルがあり、デバイスファイルを開くことは対応するキャラクターデバイスを開くことと同じです。たとえば、アプリケーションがデバイスファイルAを開くと、システムはiノードノードを生成します。このように、cノードの文字構造は、iノードノードのi_cdevフィールドを介して見つけることができます。cdevのopsポインターを介して、デバイスAの操作関数を見つけることができます。
    
    例:cdevデバイス構造の構築
    struct xxx_dev {         struct cdev cdev;         char * data;         struct semaphore sem;         ...     }; 3. File_operations構造





    
    

    この構造は、キャラクターデバイスで最も重要な構造の1つです。file_operations構造のメンバー関数ポインターは、キャラクターデバイスドライバーデザインのメインコンテンツです。これらの関数は、実際にはLinuxのアプリケーションで使用されていますopen()、read() 、Write()、close()、seek()、ioctl()およびその他のシステムコールが最終的に呼び出されます。

struct file_operations {     / *構造を所有するモジュールの数、通常はTHIS_MODULE * /     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 *);

    ssize_t(* aio_read)(struct kiocb *、const struct iovec *、unsigned long、loff_t);
    ssize_t(* aio_write)(struct kiocb *、const struct iovec *、unsigned long、loff_t);
    int(* readdir)(struct file *、void *、filldir_t);
   
    / *ノンブロッキング読み取りまたは書き込みが現在可能かどうかを判断するポーリング関数* /
    unsigned int(* poll)(struct file *、struct poll_table_struct *);

    / *デバイスI / Oコマンドを実行する* /
    int(* ioctl)(struct inode *、struct file *、unsigned int、unsigned long);

    long(* unlocked_ioctl)(struct file *、unsigned int、unsigned long);
    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);
  
    / * Close device file * /
    int(* release)(struct inode *、struct file *);

    int(* fsync)(struct file *、struct dentry *、int datasync);
    int(* aio_fsync)(struct kiocb *、int datasync);
    int(* fasync)(int、struct file *、int);
    int(* lock)(struct file *、int、struct file_lock *);
    ssize_t(* sendpage)(struct file *、struct page *、int、size_t、loff_t *、int);
    unsigned long(* get_unmapped_area)(struct file *、unsigned long、unsigned long、unsigned long、unsigned long);
    int(* check_flags)(int);
    int(* flock)(struct file *、int、struct file_lock *);
    ssize_t(* splice_write)(struct pipe_inode_info *、struct file *、loff_t *、size_t、unsigned int);
    ssize_t(* splice_read)(struct file *、loff_t *、struct pipe_inode_info *、size_t、unsigned int);
    int(* setlease)(struct file *、long、struct file_lock **);
};

  • オーナーメンバーは、この構造を所有するモジュールへのポインターです。このメンバーは、モジュールの参照カウントを維持するために使用されます。モジュールがまだ使用されている場合、モジュールはrmmodでアンインストールできません。通常、THIS_MODULEに初期化されます。
  • llseek()関数は、ファイル内の現在の読み取り/書き込み位置を変更し、新しい位置を返すために使用されます。loff_tパラメーターのタイプは「long long」です。「long long」タイプ「long long」タイプは、32ビットマシンでも64ビット幅であり、64ビットマシンとの互換性のためです。
  • read()関数は、デバイスからデータを取得するために使用され、成功すると読み取られたバイト数を返し、失敗すると負のエラーコードを返します。
  • write()関数は、デバイスにデータを書き込むために使用されます。この関数は、成功すると書き込まれたバイト数を返し、失敗すると負のエラーコードを返します。
  • ioctl()関数は、デバイス固有のコマンドを実行する方法を提供します。たとえば、デバイスをリセットする場合、これは読み取り操作でも書き込み操作でもないため、read()およびwrite()メソッドで実装することは適切ではありません。未定義のコマンドがアプリケーションのioctlに渡されると、デバイスがこのコマンドをサポートしていないことを示す-ENOTTYのエラーが返されます。
  • open()はデバイスを開くために使用され、デバイスはこの関数で初期化できます。この関数がNULLにコピーされた場合、デバイスは常に正常に開かれ、デバイスには影響しません。
  • release()関数は、open()関数で要求されたリソースを解放するために使用され、ファイル参照カウントが0のときにシステムによって呼び出されます。これは参照プログラムのclose()メソッドに対応しますが、close()メソッドが呼び出されるたびに、release()関数が起動され、デバイスファイルのすべてのオープンが解放された後に呼び出されます。


4. cdev構造とfile_operations構造の関係:
    一般的に、ドライバー開発者は、特定のデバイスの特定のデータをcdev構造に入れて、新しい構造を形成します。次の図に示すように、「カスタムキャラクターデバイスには特定のデバイスのデータが含まれています。この「カスタムデバイス」にはcdev構造が含まれています。cdev構造にはfile_operationsへのポインターがあります。このように、file_operationsの関数を使用できます。 「カスタムキャラクターデバイス」でキャラクターデバイスなどのデータを操作し、デバイスを制御する役割。                      

                            

                                                                                 図2


5.
    iノード構造カーネルはiノード構造を使用してファイルを内部的に表現しますが、実際には物理ハードウェア上の特定のファイルを表し、ファイルにはそれに対応するiノードが1つだけあります。Inodeは通常、file_operations構造体の関数のパラメーターとして渡されます。たとえば、open()関数は、現在開いているファイルノードを示すiノードポインタを渡します。iノードのメンバーにはシステムによって適切な値が割り当てられており、ドライバーはノードの情報を変更せずに使用するだけでよいことに注意してください。
    open()関数は次のとおりです:int(* open)(struct inode *、struct file *);
    inode構造には、多くのファイル関連情報が含まれています。ここでは、ドライバーの作成に役立つフィールドだけを紹介します。
struct inode {     dev_t i_rdev; / *デバイス番号* /     struct cdev * i_cdev; / * cdevは、キャラクターデバイスを表すカーネルの内部構造* /


    struct list_head i_devices;
};
    i_rdev:デバイスファイルに対応するデバイス番号を示します。
    i_devices:図1に示すように、このメンバーは他のiノードノードのi_devicesメンバーを指します。struct list_headの二重リンクリストを形成します。
    i_cdev:このメンバーはcdev構造を指します。
    
    
6.ファイル構造
    ファイル構造は開いているファイルを表し、1つのファイルが複数のファイル構造に対応できるという特徴があります。これは、カーネルが再度開かれたときに作成され、ファイルで動作するすべての関数に渡されます。最後のクローズ関数まで、カーネルはファイルのすべてのインスタンスが閉じられた後、このデータ構造を解放します。
    カーネルソースコードでは、構造体ファイルへのポインターは通常filpと呼ばれ、ファイル構造には次の重要なメンバーがあります。

struct file {     mode_t     fmode; / * FMODE_READ 、FMODE_WRITEなどのファイルモード* /     ... loff_t f_pos; / * loff_t f_posは、ファイルの読み取り位置と書き込み位置を示します。loff_tは64ビットの数値であり、必要に応じて32ビットに変換する必要があります。* /     unsigned int f_flags; / *次のようなファイルフラグ:O_NONBLOCK * /     struct file_operations * f_op;     void * private_data; / *変換されたデバイス記述構造体ポインターを格納するために使用* /     ... };







  •   mode_t f_mode:このファイルモードは、FMODE_READ、FMODE_WRITEを通じて、ファイルを読み取り可能、書き込み可能、​​またはその両方として識別します。ファイルの読み取り/書き込み権限を確認するために、openまたはioctl関数でこのフィールドを確認する必要がある場合があります。カーネル自体がoctlおよびその他の操作を実行するときにその権限を確認する必要があるため、読み取りまたは書き込み権限を直接確認する必要はありません。
  •   loff_t f_pos:現在の読み取りおよび書き込みファイルの位置。64ビットです。現在のファイルの現在の場所を知りたい場合、ドライバーはその場所を変更せずにこの値を読み取ることができます。読み取りおよび書き込みの場合、最後のパラメーターとしてloff_tポインターを受け取ると、それらの読み取りおよび書き込み操作を使用してファイル位置を更新します。filp-> f_pos操作を直接実行する必要はありません。llseekメソッドの目的は、ファイルの場所を変更することです。
  •   unsigned int f_flags:O_RDONLY、O_NONBLOCK、O_SYNCなどのファイルフラグ。ドライバーはO_NONBLOCKフラグをチェックして、非ブロッキング操作が要求されているかどうかを確認する必要があります。他のフラグはほとんど使用されません。特に、読み取り/書き込み権限を確認する場合は、f_flagsではなくf_modeを使用してください。すべてのフラグは、ヘッダーファイル<linux / fcntl.h>で定義されています。
  •     struct file_operations * f_op:ファイルがさまざまな操作を迅速に実行する必要がある場合、カーネルはこのポインターを、ファイルのオープン、読み取り、書き込み、およびその他の機能の実現の一部として割り当てます。filp-> f_opの値は、カーネルによって次の参照として保存されたことはありません。つまり、ファイルに関連するさまざまな操作を変更できるため、非常に効率的です。
  •   void * private_data:ドライバーがopenメソッドを呼び出す前に、openシステムコールはこのポインターをNULLに設定します。必要なデータフィールドとして自由に使用することも、無視することもできます。たとえば、割り当てられたデータを指すように指定できますが、ファイル構造体がカーネルによって破棄される前に、これらをreleaseメソッドで解放することを忘れないでください。データメモリ空間。Private_dataは、システムコール中にさまざまな状態情報を保存するのに非常に役立ちます。
  •     struct dentry * f_dentry:ファイルに関連付けられたディレクトリエントリ(歯科)構造。デバイスドライバーの作成者は、通常、filp​​-> f_dentry-> d_inodeとしてiノード構造にアクセスする場合を除いて、dentry構造を気にする必要はありません。

 

7.上位層アプリケーションは下位層ドライバーにどのようにアクセスしますか?
    1)各キャラクターデバイスxxxは、デバイスファイル/ dev / xxxに対応しています。open( "/ dev / xxx"、O_RDWR)関数を使用してデバイスファイルを開く場合。Linuxシステムは、VFSレイヤーでstructファイル構造を割り当てて、開かれたデバイスファイルを記述します。
    2)Linuxシステムでは、各ファイルには説明するstruct iノード構造があり、この構造はファイルのすべての情報を記録します。openがデバイスファイル/ dev / xxxを開くと、Linuxファイルシステムは、ファイル名に従ってファイルに対応するstruct iノード構造を見つけます。構造体は、xxxデバイスのデバイス番号dev_t i_rdev、デバイスタイプmode_t imode、および文字型デバイスを記述する構造体struct cdev i_cdevを記録します。imodeがキャラクターデバイスタイプの場合は、キャラクターデバイスのcdevマップに移動し、デバイス番号i_rdevに従ってキャラクターデバイスを記述する対応する構造体i_cdevを見つけます。i_cdev構造体の最初のアドレスをiノード構造体のi_cdevメンバーに返します。
    3)キャラクターデバイス操作関数セットは、キャラクターデバイスの構造体i_cdev、つまりstruct file_operations xxx_ops構造体に記録されています。キャラクターデバイスに対応するすべての操作関数がこの構造で定義されています。const struct file_operations xxx_ops構造体のアドレスをコピーし、VFSレイヤーのstruct file構造体のconst struct file_operations * f_op構造体ポインターに割り当てます。
    4)VFS層は、ファイル記述子(fd)をアプリケーション層に返します。このfdは、structファイル構造に対応しています。次に、上位アプリケーションはfdを介して構造体ファイルを見つけ、その構造体ファイルによってキャラクターデバイスを操作するための関数インターフェイスを見つけます。
    5)アプリケーション層は、fdを使用してread()、write()、およびioctl()関数を呼び出し、デバイスを操作できます。

 

おすすめ

転載: blog.csdn.net/the_wan/article/details/108433530