-Linuxキャラクタデバイスドライバ[Linuxカーネルの深い理解]

シリーズ コンテンツ
Linuxカーネルの深い理解 Linuxのキャラクタデバイスドライバ

環境:

プラットフォーム カーネルのバージョン Androidのバージョン
RK3399 Linux4.4 Android7.1

1、カーネルモジュールの開発

1.1カーネルモジュール

カーネルモジュールとアプリケーションは多くの違いがあります。カーネルモジュールはカーネル内の場所を占有している自分自身を登録した後、完成した初期化機能のこの部分では、サービスを待つために来て、モジュールは、出口の最後ではなく、アプリケーション・プロセスがクリーンアップに対処するためではない任意の時点で終了させることができるようにする必要があります。しかし、カーネルモジュールのexit関数、確かに慎重にすべての作業が初期化を復元するために行わ取り消すこと。そうしないと、システムリセットの前に残りのシステムに残されています。モジュール機能の最も顕著な特徴は、カーネルが開発サイクルを短縮することができ、あなたは常に、再起動の長い期間を経て行かないように、そのアンインストール機能かもしれません。

1.2、ロードおよびアンロードモジュール

モジュールのセットアップ完了し、その後、カーネルにロードされinsmodたカーネルのロードを完了するために、ロード・モジュール、コードとデータモジュールを達成するために。ドライバモジュールがロードされたときにモジュールのパラメータを指定することができます。自明のカーネルヘッダの重要性。カーネルモジュールのためのいくつかの必要なヘッダファイルがありますが、ほとんどすべてのモジュールの使用されます。

ファイル名 コンテンツ
linux/module.h 最初の文書は、ロード・モジュールのために必要な機能やシンボルの数が多いです
linux/init.h ヘッダーには、初期化とクリーンアップ機能を指定するために使用されます
moudleparam.h ヘッダファイルは、問題のモジュールのパラメータが含まれてい

私たちは、それは知っているLinuxのサポートでGPL、あなたのモジュールはまた、もちろん、必須ではありません、それを指定する必要がありますので、一般的な公衆ライセンスとして。この目的のためには、次の行が含まれている必要があります。
MODULE_LICENSE(”GPL”);

モジュールはまた、記述の性質を定義することができます。

定義 コンテンツ
MODULE_AUTHOR モジュールの声明
MODULE_DESCRIPION モジュールの機能の簡単な声明
MODULE_VERSION バージョン情報

lsmodカーネルにロードされたモジュール情報に、現在知られているrmmodカーネルモジュールを削除するためのツール。

1.3モジュールの初期化と出口

登録されているすべての機能モジュールの初期化機能モジュール。次のように実際のモード初期化機能は、多くの場合、定義されています。

static int __init initfunc(void)
{
//Initialization——Handle
)
module_init(init_func)

初期化機能を宣言する必要がありstatic、このファイルに加えが表示されていない、無意味では、それは通常、このような文です。__initただ、シンボルは単に初期設定で使用する前に示します。moudle initしかし、それは必須不可欠です。このマクロ定義初期化関数は、位置をマークすることで、オブジェクト・コード・モジュールに特別なセクションを追加します。このマクロがなければ、初期化関数を呼び出しません。
それは、モジュールが削除されたときに呼び出され、インタフェースのキャンセルのために通常、クリーンアップ機能モジュールの重要な部分です。目的は、占有システムリソースに戻すことです。次のようにこの関数が定義されています。

static void __exit cleanup_function(void)
{
/*Cleanup*/
)
module_exit(cleanup_function)

宣言されたvoidクリーンアップは戻り値は機能しませんので。exitタグが似ているinitだけで、モジュールのアンロードのために、。最後に、moudle exitまた、クリーンアップ機能を発見するカーネルのためです。何のクリーンアップ機能がない場合、カーネルモジュールをアンロードすることはできません。

1.4初期化エラー処理

カーネルの初期化関数への登録プロセスでは、あなたは常に戻り値の成功を確認する必要があります。必要な措置は、障害に対処する場合にキャストする必要があります。案の定は、登録プロセスのエラーが発生し、あなたが登録した失敗する前に何らかのアクションを撤回しなければなりません。かかわらず、これらの中核登録情報の。何らかのエラーが発生したのであれば、モジュールは、以前の取り消しすべて登録されたロールバックとは逆の順序で自分自身を登録することができなければなりません。そうでなければ、そこが原因ポインタが存在してキャンセルされていない、不安定な、不安定な状態にあるカーネルは、最後の手段の下で、多くの場合、唯一の方法は、システムを再起動するので、初期化のポイントにエラー処理がずさんなことはできません。

2、メジャー番号とマイナー番号

Linuxファイルシステム内のファイルの形式で文字描画装置、そのような特別なファイルは、透明性の根本的な違いの多くは、操作のファイル名を介してアクセスすることができます。これらのファイルは、デバイスファイルと呼ばれます。大会は、に位置している/devことができ、ディレクトリlsコマンドを表示します。一般的な文字を使用cキャラクタデバイスファイルをマークします。

慣例と、対応するドライバは、装置本体識別番号に接続されています。実施例4の仮想コンソール、シリアル端子を表します。カーネルによって割り当てられた第2のデバイスの数が管理している間。これは、カーネルがデバイスの意味の範囲内のファイルを決定する決定します。このデバイス番号、ドライバに関連付けられているだけマーキング装置の効果。

内部2.1は、デバイス番号を示し

カーネル<linux/types.h>定義されたdev_tタイプがデバイスを保持するプライマリおよびセカンダリデバイス番号を含む番号を表すために使用され、一次および二次部分が含まれます。カーネルの古いバージョンでは、データ型は唯一表す2561つのマスタデバイスの数と256、今までそれが信じられているが、十分な範囲ではなく、仮定のいくつかの将来のために再拡張したカーネルの新しいバージョンで変更される可能性があること、マイナー番号をdev_t範囲を表現することができます。dev_t32ビット、12マスタとしてのビット数、および残りの20副デバイス番号としてビット。もちろん、おそらくそれはまた関連している可能性のある変更は、最高とは直接使用するべきではありませんが、従うべき<linux/kdev_t.h>定義されたマクロを。メジャー番号とマイナー番号を取得することは使用する必要があります。

MAJOR(dev_t dev);
MINOR(dev_t dev);

一方、私が知っているメジャーとマイナーデバイス番号もに変換することができますdev_t:入力
MKDEV(int major, int minor);
データ仕様の将来の変更がブロックされているどんなにの変化の結果を。

2.2、デバイス番号の割り当ておよび割り当て解除

ドライバは文字を書き始め、最初に使用する1つのまたは複数のデバイス番号を取得する必要があります。古い方法は、前提は、彼らが必要とする利用できる番号の範囲を明確に認識して、手動登録モードを使用することです。で以下を使用<linux/fs.h>関数宣言:
int register_chrdev_region(dev_t first, unsigned int count, char *name);

パラメータ コンテンツ
first 一般に、デバイス番号、デバイス番号の開始時間割り当てを要請0
count 番号は、連続したリクエストの数であるcount次のマスタデバイス番号にオーバーフローが発生するには大きすぎません
name デバイス名は、登録が成功した後にすることができます/proc/devicesについての知識を押収しました。

問題は、その新たな戦略をして、可能なメジャー番号を知っている私たちはしばしば困難であるLinux動的割り当て機構デバイス番号で、その結果、カーネル開発コミュニティに提示します。カーネルの動的割り当てによってマスタデバイス利用できる番号:

int allocchrdevregion(devt *dev, unsigned int firstminor, unsigned int count, char *name);

パラメータ コンテンツ
dev 最初の番号を保存する結果の分布成功
firstminor リクエストのマイナー番号、多くの場合、0
count 通し番号要求の数
name 名前

割り当てたデバイス番号を要求するいずれかの方法で、解放された後に以下の機能が使用されていない使用します。
void unregister_chrdev_region(dev_t first,unsigned int count);
この機能は、モジュール通常で呼び出されるcleanup関数。

3、重要なデータ構造

登録デバイス数がそれを駆動開始の製造における最初の一歩である、ドライブへの必要性が考慮を必要とすることを多くのコンポーネントがあります。しかし、どのような場合には、ドライバーの大半はに関与することになり3、非常に重要なカーネルデータ構造でありfile_operationsfileかつinode構造。

3.1、ファイル操作

デバイス番号とデバイスドライバがマークされ、その後、登録番号の後に取得するには、次のステップでは、機器の操作や管理にデバイス番号を関連付ける方法を検討することです。次に、<linux/fs.h>定義されたfile_operation構造は、この接続のためのリンクを確立することです。

この構造定義は、関数ポインタのセット、デバイスオブジェクトの方法を含みます。これは、デバイスのファイルシステムを求めています。file_opermionあるいは、この構造体へのポインタが慣性と呼ばれfops、各開口デバイスファイル(カーネルが有するfile
とグループの関連付けの動作ファイル、コンテンツ記述で表される構造)。これらのfops各フィールドは、達成すべき操作手順駆動と呼ばれます。いくつかの操作のためにサポートしていない場合は、設定することができますNULL

観察するとfile_operations、さまざまな方法が定義されたときのサポートに、あなたは多くのパラメータに注意しますされて__user修正されたユーザ空間のポインタがポインタである、それは直接使用することができないことを示し、マーク。以下簡単には機能の動作を説明するデバイスに実装する必要があります。

メンバー コンテンツ
struct module *owner このメンバは表示され、すべての人のモジュールの構造体へのポインタのみを持っています。通常、初期化<linux/module.h>定義されたマクロTHIS MODULE
ssize_t(*read)(struct file*,char __user*,size_t,loft_t*) この部材は、リードに対するデバイスの実装の動作方法であって
ssize_t(*write)(struct file*,const char __user*,size_t,loft_t*) デバイスの実装のメンバーの書き込み操作方法
int(*open)(struct inode*,struct file*) これは、典型的には最初の動作として、デバイスドライバによって行われるが、文は必ずしも達成するために必要とされません
この方法。
int(*ioctl)(struct inode*,struct file*,unsigned int,unsigned long) この方法は、デバイスに特定のコマンドを実行することができます
int(*release)(struct inode*,struct file*); 操作によって参照されるファイルの構造

3.2、ファイル構造

第二に定義されている重要なデータ構造を駆動構造。<linux/fs.h>struct file

文件结构是内核在open之时创建用来代表一个打开的任意文件。该结构由内核传递给前述定义的文件操作函数,这样一来,打开的内核中表示的文件就与文件操作可以关联起来。文件不使用后关闭时,内核才释放file结构。同样内核源码中我们将struct file指针惯称之为filp(”file pointer”)。
下面简单举例说明struct file比较重要的成员:

成员 内容
mode_t f_mode 表示文件模式。如用FMODE_READ标示文件可读,而FMODE_WRITE标示可写
unsigned int f_flags 表示文件标志,如常见的O_RDONLYO_NONBLOCK等标记
struct file_operations *f_op 表示和文件关联的操作
void *private_data 可用来保存私有状态信息,在跨系统调用的使用中非常有用。

3.3、inode 结构

inode结构是内核中唯一表示文件结点的结构,而file只是内核中表示打开的文件结构。二者含义不可混淆。既然文件可以被打开多次,那么就会有多个file结构,但是唯一标示的文件结点inode却始终只有一个。

虽然inode结构包含了很多文件信息,但是通常只有两个字段在驱动编程中有用武之地。一个是dev_t i_rdev,这表示设备文件的结点,其中有设备编号信息。另外一个是struct cdev *i_cdev,表示了字符设备的内部表示。

i_rdev类型后来发生了改变,使得大量驱动程序被破坏。为防止这类情况,增加了可移植性的宏编码方式从inode中获取主次设备编号:

unsigned int iminor(struct inode*inode); //获取次设备编号
unsigned int imajor(struct inode*inode); //获取主设备编号

4、字符驱动

4.1、字符设备注册

前述部分,提到字符设备在内核中是由struct cdev来表示的。故而在调用设备操作函数前,注册分配一个或几个这样的结构是必须的。该结构及相关辅助函数定义在<linux/cdev.h>中。
旧版的注册字符设备驱动的方式是:
int register_chrdev(unsigned int major,const char *name,struct file_operations *fops);
系统中移除设备的函数接口是:
int unregister_chrdev(unsigned int major,const char *name)

4.2、读和写

那么核心的问题就是如何解决用户缓存区和内核的数据安全交互。这也是这个函数的核心功能。内核提供了类似memcopy功能的函数,来实现跨越内核和用户的数据传递。
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);

unsigned long copy_from_user(void *to,const void_user *from,unsigned long count);

2个函数除了实现数据copy传递功能外,还对__user用户指针进行安全检查。若为无效指针,就不会进行copy操作。

实际设备的read方法就是使用copy_to_user从内核设备中读数据到用户空间。
write方法使用copy_from_user实现相反的功能。

4.3、ioctl接口

绝大多数设备驱动有着对硬件控制特殊操作能力。而ioctl方法往往是最容易最直接的选择。该方法实现设备文件用户空间的ioctl系统调用。

ioctl方法驱动实现内核原型:
int(*ioctl)(struct inode *node,struct file *flip,unsigned int cmd,unsigned long arg)

inodeflip指针对应于ioctl系统调用中的file描述符fd。而cmd参数对应于系统调用的命令参数,由用户传递进来。而不论系统调用中可选参数部分是指针还是长整型。内核中的实现均使用unsigned long类型来表示。其实由于多种命令选择,可以想象,实现ioctl可能需要选择判断cmd参数的switch结构。
通常可以在头文件中预定义命令编号的方式去实现。

首先应该考虑的是ioctl命令编号在系统中应当是唯一存在的。因为可能发生访问错误设备却使用正确命令的情况。为避免这种错误,Linux中将这些编码规范划分了位段。旧的使用16位整数,高8位是关联这个设备的”魔幻”数而低8位是一个序号,在设备内唯一。使用这种老传统的驱动程序仍非常之多。现在有了新的划分,选择编号可以先参考include/asm/octl.hDocumentation下的ioctl-number.txt文件。新的位段有四种字段:type(即魔幻数)、number(序号)、direction(传送方向)和size(数据大小)。

可以使用定义在<linux/ioctl.h>中的宏来帮助建立命令编号:

命令 内容
_IO(type, nr) 用于构建无参数命令编号
_IOR(type, nr, datatype) 用于构建从驱动中读数据的命令编号
_IOW(type, nr,datatype) 用于构建向驱动中写数据的命令编号
_IOWR(type, nr, datatype) 用于双向传送

参考头文件中有关这些宏的细节。

关于ioctl的实现,也涉及到参数在用户空间和内核的交互传递。有一组定义在<asrn/uaccess.h>中的函数实现特意为数据大小为1248字节进行拷贝传递。不使用copy_to_user等函数是因为它们传输单数据更加快速方便。

put_user(datum,ptr)
传递依赖于sizeof(ptr)大小的datum到用户空间。

get_user(local,ptr)
这个宏定义用来从用户空间接收单个数据并存储于变量local

发布了253 篇原创文章 · 获赞 93 · 访问量 12万+

おすすめ

転載: blog.csdn.net/qq_33487044/article/details/104092307