ホームページ > 記事 > システムチュートリアル > 基礎となるハードウェア デバイス ドライバーがリンク リスト (ドライバー) に挿入される順序
ドライバーとは:
ドライバーは、基礎となるハードウェア デバイスの操作をカプセル化し、下位層に関数ソケットを提供します。
機器の分類:
Linux システムでは、デバイスをキャラクター デバイス、ブロック デバイス、ネットワーク デバイスの 3 つのカテゴリに分類します。
キャラクター デバイス: バイトごとの読み取りと書き込みのみが可能なデバイスを指します。デバイスのビデオ メモリ内の特定のデータはランダムに読み取ることができません。データは順番に読み取る必要があります。キャラクター デバイスはストリーム指向のデバイスですLinux ドライバー開発一般的なキャラクター デバイスには、マウス、キーボード、シリアル ポート、コンソール、LED デバイスなどがあります。ブロックデバイス: デバイス上の任意の場所から一定の厚さのデータを読み取ることができるデバイスを指します。ブロック デバイスには、ハード ドライブ、ディスク、USB フラッシュ ドライブ、SD カードなどが含まれます。ネットワーク デバイス: ネットワーク デバイスは、ネットワーク カードなどのハードウェア デバイスである場合もありますが、ループバック ソケット (lo) などの純粋なソフトウェア デバイスである場合もあります。ネットワーク ソケットは、データ パケットの送受信を担当します。駆動された認知:
まず、プロセスを説明し、ドライバーを理解するのに役立つ画像を見てください。
ユーザーモード:
カーネル状態:
ドライバー配列: すべてのデバイスのドライバーを管理し、追加または検索します。追加は、ドライバーをコンパイルしてカーネルにロードした後に行われます。検索ではドライバーが呼び出され、アプリケーション層のユーザー空間は open 関数を使用して検索します。ドライバーが配列に挿入される順序はデバイス番号によって取得されます。つまり、メジャー デバイス番号とマイナー デバイス番号は、異なる種類のデバイスと異なる種類のデバイスを区別するだけでなく、ロードすることもできます。ドライバー コードの開発は、ドライバーを追加 (デバイス番号、デバイス名、およびデバイス ドライバー関数を追加) し、ドライバーを呼び出すだけです。
補充:
各システム コールはシステム コール番号に対応し、システム コール番号はカーネル内の対応する処理関数に対応します。すべてのシステム コールは割り込み 0x80 によってトリガーされます。システムコールを使用する場合、システムコール番号はeaxレジスタを介してカーネルに渡され、システムコールの入力パラメータはebx、ecx...を介して順番にカーネルに渡されます。関数と同様に、戻り値はシステム コールは eax に保存されており、すべてのパラメータは eax から取得する必要があります。キャラクター デバイス ドライバーを削除する方法
キャラクター デバイス ドライバーの動作原理 Linux の世界では、すべてがファイルであり、アプリケーション層で動作する場合、すべてのハードウェア デバイス操作はファイル操作として具体化されます。アプリケーション層がハードウェア デバイスにアクセスしたい場合は、ハードウェアに対応するドライバーを呼び出す必要があることがわかっています。 Linux カーネルには非常に多くのドライバーが含まれていますが、アプリケーションはどのようにして基礎となるドライバーを正確に呼び出すことができるのでしょうか?
補充:
(1) open関数でデバイスファイルをオープンすると、デバイスファイルに対応するstructinode構造体とstructfileに記述されている情報から操作対象のデバイスの種類(キャラクタデバイスかブロックデバイスか)を知ることができます。構造体が割り当てられます。
(2) structinode 構造体に記録されているデバイス番号に基づいて、対応するドライバーが見つかります。ここではキャラクターデバイスを例に挙げます。 Linux オペレーティング システムでは、各キャラクター デバイスに structcdev 構造があります。この構造体はキャラクタ デバイスのすべての情報を記述します。その中で最も重要なのはキャラクタ デバイスの操作関数ソケットです。
(3) structcdev 構造体を見つけた後、Linux カーネルは、structcdev 構造体が配置されているビデオ メモリ空間の最初のアドレスを structinode 構造体の i_cdev メンバーに記録し、関数操作ソケット アドレスを structinode 構造体の i_cdev メンバーに記録します。本体の f_ops メンバーの structfile 構造体の structcdev 構造体。
(4) タスクが完了すると、VFS 層はファイル記述子 (fd) をアプリケーションに返します。この fd は structfile 構造に対応します。次に、下位層アプリケーションは fd を通じて structfile を見つけ、その structfile 内で関数ソケット file_operation を見つけて、キャラクターデバイスを操作します。
このうち、cdev_init と cdev_add はドライバーのエントリ関数ですでに呼び出されており、それぞれキャラクターデバイスと file_operation 関数の操作ソケットのバインドを完了し、キャラクタードライバーをカーネルに登録します。
関連動画のおすすめ
無料学習アドレス: LinuxC/C 開発 (フロントエンド/オーディオおよびビデオ/ゲーム/組み込み/高性能ネットワーク/ストレージ/インフラストラクチャ/セキュリティ)
C/C Linux サーバー アーキテクトの学習教材が必要で、qun579733396 を追加して取得します (教材には、C/C、Linux、golang テクノロジー、Nginx、ZeroMQ、MySQL、Redis、fastdfs、MongoDB、ZK、ストリーミング メディア、CDNlinux 入力メソッドが含まれます) 、P2P、K8S、Docker、TCP/IP、インタープリター、DPDK、ffmpeg など)、無料共有
キャラクターデバイス、キャラクターデバイスドライバー、およびデバイスにアクセスするユーザー空間プログラムの間の関係
図に示すように、cdev 構造体は Linux カーネルでキャラクターデバイスを記述するために使用され、そのメンバー dev_t はキャラクターの一意性を決定するデバイス番号 (メジャーデバイス番号とマイナーデバイス番号に分割される) を定義するために使用されます。デバイス。共通の open()、read()、write() など、キャラクター デバイス ドライバーによってそのメンバー file_operations を通じて VFS に提供されるソケット関数を定義します。
Linux キャラクター デバイス ドライバーでは、モジュール ロード関数は register_chrdev_region() または alloc_chrdev_region() を通じてデバイス番号を静的または動的に取得し、cdev_init() を通じて cdev と file_operations 間の接続を構築し、cdev_add を通じてそれをシステムに追加します。 () 登録を完了するための CDV。モジュール アンインストール関数は、cdev_del() を通じて cdev をログアウトし、unregister_chrdev_region() を通じてデバイス番号を解放します。
デバイスにアクセスするユーザー空間プログラムは、open()、read()、write() などの Linux システム コールを使用して file_operations を「呼び出し」、キャラクター デバイス ドライバーによって VFS に提供されるソケット関数を定義します。
ドライバー開発の手順
Linux カーネルはさまざまな種類のドライバーで構成されており、カーネルのソース コードの約 85% はさまざまな種類のドライバーのコードです。カーネルにはあらゆる種類のドライバーがあり、特定のボードに合わせて同様のドライバーに基づいて変更できます。
ドライバーを作成する際の難しさは、ハードウェアの特定の動作ではなく、既存のドライバーのフレームワークを理解し、このフレームワークにハードウェアを追加することです。
一般的に、Linux デバイス ドライバーをコンパイルする一般的なプロセスは次のとおりです。
以下では、単純なキャラクター デバイス ドライバー フレームワーク コードを使用して、ドライバーを開発およびコンパイルします。
ドライバー フレームワークに基づくコード開発
下位層の呼び出しコード
リーリー
ドライバー フレームワーク コード
リーリー
ドライバー開発で最も難しいのは、フレームワークのコードを理解し、その上でデバイスを追加および変更することであると言われていますが、以下でフレームワークのロジックについて学びましょう。
主導型フレームワーク設計プロセス
1. メジャーデバイス番号を決定します
2. 構造タイプ file_operations を定義します。
3. 対応する drv_open/drv_read/drv_write およびその他の関数を実装し、file_operations 構造体に記入します。4. ドライバー エントリの実装: ドライバーをインストールするときに、このエントリ関数が呼び出されて作業を実行します:
①カーネルに file_operations 構造体を伝えます: ドライバー register_chrdev.
を登録します。②クラスclass_create.
を作成します。③デバイス device_create.
を作成します。5. エクスポートの実装: このエクスポート関数は、作業を実行するためにドライバーがアンインストールされた場合にのみ呼び出されます:
①file_operations 構造体をカーネルから登録解除します: unregister_chrdev.
②クラスclass_create.
を破棄します。③デバイス ノード device_destroy.
を破棄します。6. その他の規定: GPL 契約、入口ローディング
1. メインデバイスと変数定義を決定する
リーリー
2. file_operations 構造体を定義し、それをカーネル ドライバー配列にロードします。
これは Linux カーネルの file_operations 構造です
下位層の呼び出し関数に応じて構造体のメンバを定義する
リーリー3. 構造体メンバpin4_readなどの関数を実装します
リーリー
4. 運転席入口
リーリー
其中pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在dev手动生成设备,除此之外还可以自动生成设备,在dev目录下sudomknod+设备名子+设备类型(c表示字符设备驱动)+主设备号+次设备号。
5、出口
void __exit pin4_drv_exit(void) { device_destroy(pin4_class, devno); class_destroy(pin4_class); unregister_chrdev(major, module_name);//卸载驱动 }
6、GPI合同,入口加载,出口加载
module_init(pin4_drv_init);//入口,内核加载该驱动(insmod)的时候,这个宏被使用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
驱动模块代码编译和测试
编译阶段
驱动模块代码编译(模块的编译须要配置过的内核源码,编译、连接后生成的内核模块后缀为.ko,编译过程首先会到内核源码目录下,读取顶楼的Makefile文件,之后再返回模块源码所在目录。)
#include //file_operations声明 #include //module_initmodule_exit声明 #include //__init__exit 宏定义声明 #include //classdevise声明 #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"; //模块名 static int pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("pin4_readn"); return 0; } //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_openn");//内核的打印函数和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_writen"); return 0; } static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, .read= pin4_read, }; 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自动生成设备 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);//入口,内核加载该驱动(insmod)的时候,这个宏被使>用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
将该驱动代码拷贝到linux-rpi-4.14.y/drivers/char目录下文件中(也可选择设备目录下其它文件)
更改该文件夹下Makefile(驱动代码放在那个目录,就更改该目录下的Makefile),将前面的代码编译生成模块,文件内容如右图所示:(-y表示编译进内核,-m表示生成驱动模块,CONFIG_表示是按照config生成的),所以只须要将obj-m+=pin4drive.o添加到Makefile中即可。
回到linux-rpi-4.14.y/编译驱动文件
使用指令:ARCH=armCROSS_COMPILE=arm-linux-gnueabihf-KERNEL=kernel7makemodules进行编译生成驱动模块。
编译生成驱动模块会生成以下几个文件:
.o的文件是object文件,.ko是kernelobject,与.o的区别在于其多了一些sections,例如.modinfo。.modinfosection是由kernelsource里的modpost工具生成的,包括MODULE_AUTHOR,MODULE_DESCRIPTION,MODULE_LICENSE,deviceIDtable以及模块依赖关系等等。depmod工具按照.modinfosection生成modules.dep,modules.*map等文件,便于modprobe更便捷的加载模块。
编译过程中,经历了这样的步骤:先步入Linux内核所在的目录,并编译出pin4drive.o文件,运行MODPOST会生成临时的pin4drive.mod.c文件linux 驱动 开发,而后依据此文件编译出pin4drive.mod.o,然后联接pin4drive.o和pin4drive.mod.o文件得到模块目标文件pin4drive.ko,最后离开Linux内核所在的目录。
将生成的.ko文件发送给猕猴桃派:[email protected]:/home/pi
将pin4test.c(下层调用代码)进行交叉编译后发送给猕猴桃派,就可以看见pi目录下存在发送过来的.ko文件和pin4test这两个文件,
加载内核驱动
sudo insmod pin4drive.ko
加载内核驱动(相当于通过insmod调用了module_init这个宏,之后将整个结构体加载到驱动数组中)加载完成后就可以在dev下边见到名子为pin4的设备驱动(这个和驱动代码上面staticchar*module_name="pin4";//模块名这行代码有关),设备号也和代码上面相关。
lsmod查看系统的驱动模块,执行下层代码,赋于权限
查看内核复印的信息,
dmesg |grep pin4
如右图所示:表示驱动调用成功
在装完驱动后可以使用指令:sudormmod+驱动名(不须要写ko)将驱动卸载。
调用流程:
我们下层空间的open去查找dev下的驱动(文件名),文件名背后包含了驱动的主设备号和次设备号,此时用户open触发一个系统调用linux是什么系统,系统调用经过vfs(虚拟文件系统),vfs按照文件名背后的设备号去调用sys_open去判定,找到内核中驱动数组的驱动位置,再去调用驱动上面自己的dev_open函数
为何生成驱动模块须要在虚拟机上生成?猕猴桃派不行吗?
生成驱动模块须要编译环境(linux源码而且编译,须要下载和系统版本相同的Linux内核源代码),也可以在猕猴桃派里面编译,但在猕猴桃派里编译,效率会很低,要特别久。
以上が基礎となるハードウェア デバイス ドライバーがリンク リスト (ドライバー) に挿入される順序の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。