[Linuxの基本]組み込みLinux ---- ueventメカニズム:uevent主成分分析(詳細な記事)

前書き:

この記事では、主にueventメカニズムとは何か、およびueventメカニズムを使用してコード分析を通じてデバイスノードを生成するプロセスを紹介します。また、この記事は2つのパートに分かれています。最初のパートでは、いくつかの準備知識とueventの原理を紹介し、2番目のパートでは、ueventメカニズムを使用してデバイスノードを作成するコードの紹介を行います。

Linuxカーネル:linux-2.6.22.6

使用した開発ボード:JZ2440 V3(S2C2440A)

ステートメント:

この記事は主にWeiDongshan先生のビデオを見た後に書かれ、いくつかのブログコンテンツと組み合わされているため、記事の他の記事にコンテンツが含まれている可能性があります。私の記事があなたを侵害していると思われる場合は、教えてください。続行します。記事と一緒に。それを修正し、テキストに何か間違っている可能性がある場合は私を修正してください。ありがとうございました。

パート1:予備知識とueventの原則

以下では、準備について説明します。説明する前に、ueventメカニズムを紹介するフレームワーク図を見てみましょう。

                  写真は次のとおりです。Linuxデバイスモデル(3)_Uevent

私たちの多くは明確ではないかもしれません:ueventメカニズムとは何ですか、そしてueventメカニズムは正確に何をしますか?それのどの側面が私たちの研究に値するのでしょうか?

ドライバーを最初に学習したときは、ueventメカニズムを使用しませんでした。つまり、プログラムでclass_create関数とclass_device_create関数を使用して、ユーザースペースにデバイスドライバーのデバイスノードを自動的に作成しませんでした。当時、デバイスノードを作成するには、ユーザースペースでmknodコマンドを手動で使用する必要がありました。また、class_create関数とclass_device_create関数を使用すると、手動でこの作業を行う代わりに、ユーザースペースにデバイスノードが作成されます。そして、これはueventメカニズムのマクロ理解です。また、デバイスノードはデバイスドライバー用に作成されており、デバイスドライバーとデバイスドライバーはリストの形式でバスバスに接続されていますが、デバイス-ドライバー-次のレベルのバスはsysfsレイヤーですしたがって、これはsysfs + mdevの組み合わせにつながります。そして、この組み合わせのペアは、私たちにとってueventメカニズムの原理を説明します。まずsysfsを理解しましょう。

1.1、sysfs

sysfsは、カーネルによって提供されるメモリベースの仮想ファイルシステムであり、/ sysディレクトリにマウントされ(mountでチェックして/ sysタイプsysfs(rw、nosuid、nodev、noexec、relatime)でsysfsを取得します)、デバイスを担当しますツリーフォームは、直感的なデバイスとドライバーの情報をユーザーの名前空間に提供します同時に、sysfsは、現在システムに接続されているデバイスを別の観点から表示します。

  • / sys / blockは履歴から残り、ブロックデバイスを保存し、デバイス名(sdaなど)を使用して/ sys / devicesへのシンボリックリンクを提供します。
  • / sys / busは、バスタイプによって分類されます。バスに接続されているデバイスのシンボリックリンクは、/ sys / devicesを指すバスディレクトリにあります。特定のバスディレクトリの下のdriversディレクトリには、カーネルのstructbus_typeに対応するバスに必要なすべてのドライバのシンボリックリンクが含まれています。
  • / sys / classはデバイス機能によって分類されます。たとえば、入力デバイスは/ sys / class / inputの下にあり、グラフィックスデバイスは/ sys / class / graphicsの下にあります。これは、の対応するデバイスを指すシンボリックリンクです。対応するカーネルStructクラスの/ sys / devicesディレクトリ。
  • / sys / dev  は、デバイスドライバー(キャラクターデバイス/ブロックデバイス)に従って階層化され、カーネル内のstructdevice_driverに対応する/ sys / devicesへのmajor:minorという名前のシンボリックリンクを提供します。
  • / sys / devicesには、さまざまなバスに登録されている検出されたすべての物理デバイスが含まれます。プラットフォームデバイスとシステムデバイスを除くすべての物理デバイスは、バス上のトポロジに従って表示されます。プラットフォームデバイスは通常、チップまたは低速バス上のさまざまなコントローラと周辺機器を指し、CPUによって直接アドレス指定できます。システムデバイスは周辺機器ではなく、CPU、タイマーなど、吸盤内部のコア構造です。通常、関連するドライバーはありませんが、構造体デバイスに対応するように構成するためのアーキテクチャ関連のコードがいくつかあります。カーネル。

上記は、sysディレクトリ内のバス、デバイス、ドライバ、およびクラス対応するファイルを示しています。これらの関係は次のとおりです

  • デバイスは、すべてのデバイス情報を保存するさまざまなデバイスを説明するために使用されます。
  • ドライバはデバイスを駆動するために使用され、それによって駆動できるすべてのデバイスのリンクリストが保存されます。
  • バスは、CPUとデバイスを接続するブリッジであり、バスにマウントされているデバイスのすべてのリンクリストと、これらのデバイスを駆動するドライバーのリンクリストを保存します。
  • クラスは、デバイスのタイプを記述するために使用され、このタイプのデバイスのすべてのデバイスリンクリスト保存します

以下では、次のレベルのバス、デバイス、ドライバー、およびクラスの構造を紹介します。sysfsの機能は、Linuxの統合デバイスモデルに基づいています。このモデルの構造は、kobject、kset、ktypeです。同時に、上のブロック図から、ueventがkobject構造に基づいて実装されていることがわかります。

kobject:統合デバイスモデルの最も基本的なオブジェクト。

struct kobject {
 	const char *name;  //name,该Kobject的名称,同时也是sysfs中的目录名称。
			    //由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。
			   //如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。
        struct list_head    entry; //entry,用于将Kobject加入到Kset中的list_head。 
        struct kobject      *parent; //parent,指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。
        struct kset     *kset; //kset,该kobject属于的Kset。可以为NULL。
				//如果存在,且没有指定parent,则会把Kset作为parent
				//(别忘了Kset是一个特殊的Kobject)。
        struct kobj_type    *ktype;  //ktype,该Kobject属于的kobj_type。每个Kobject必须有一个ktype,或者Kernel会提示错误。
        struct sysfs_dirent *sd;   //sd,该Kobject在sysfs中的表示。
 
        struct kref     kref;  //kref,"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。
        unsigned int state_initialized:1; //state_initialized,指示该Kobject是否已经初始化,
					  //以在Kobject的Init,Put,Add等操作时进行异常校验。
        unsigned int state_in_sysfs:1;   //state_in_sysfs,指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。	
        unsigned int state_add_uevent_sent:1;  // state_add_uevent_sent/state_remove_uevent_sent,记录是否已经向用户空间发送ADD uevent,
						//如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,
						//以便让用户空间正确处理。
        unsigned int state_remove_uevent_sent:1;
        unsigned int uevent_suppress:1;  //uevent_suppress,如果该字段为1,则表示忽略所有上报的uevent事件。
    };

:Ueventは、この関数を介して「ユーザースペース通知」関数の実装を提供します。カーネルにkobjectの追加、削除、変更、およびその他のアクションがある場合、ユーザースペースに通知されます。

Ktype:Kobjectの属性操作セットを表します(厳密に言えば、Kobjectを含むデータ構造です)(汎用性のため、複数のKobjectが同じ属性操作セットを共有する可能性があるため、Ktypeは独立しています)。

struct kobj_type {
     void (*release)(struct kobject *kobj); //release,通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。
     const struct sysfs_ops *sysfs_ops;  //sysfs_ops,该种类型的Kobject的sysfs文件系统接口。
     struct attribute **default_attrs; //default_attrs,该种类型的Kobject的atrribute列表
					//(所谓attribute,就是sysfs文件系统中的一个文件)。
					//将会在Kobject添加到内核时,一并注册到sysfs中。
     const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); 
					//child_ns_type/namespace,和文件系统(sysfs)的命名空间有关
     const void *(*namespace)(struct kobject *kobj);
 };

実際、ここでの実装はKobjectの派生に似ており、異なるkobj_typesを含むkobjectは異なるサブクラスと見なすことができます。ポリモーフィズムは、同じ機能を実装することによって実現されます。このような設計では、埋め込まれたkobjectの各データ構造(kset、device、deivce_driverなど)は、独自のkobj_typeを実装し、そのコールバック関数を定義する必要があります。

Kset:同様のKobjectを集約するために使用される特別なKobject(したがって、「/ sys /」ファイルシステム内のディレクトリの形式でも表示されます)(これらのKobjectは同じ属性または異なる属性を持つことができます)

struct kset {
     struct list_head list; //list用于保存该kset下所有的kobject的链表。
     spinlock_t list_lock;  //list自旋锁
     struct kobject kobj;  //kobj,该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。
     const struct kset_uevent_ops *uevent_ops; //uevent_ops,该kset的uevent操作函数集。
						//当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,
						//或者过滤event(kset可以决定哪些event可以上报)。
						//因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。
 };

ksetのuevent操作コールバック関数は次のとおりです。

struct kset_uevent_ops {
     int (* const filter)(struct kset *kset, struct kobject *kobj);
		//filter,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,
		//阻止不希望上报的event,从而达到从整体上管理的目的。
     const char *(* const name)(struct kset *kset, struct kobject *kobj);
		//name,该接口可以返回kset的名称。如果一个kset没有合法的名称,
		//则其下的所有Kobject将不允许上报uvent
     int (* const uevent)(struct kset *kset, struct kobject *kobj,
         struct kobj_uevent_env *env);
	//uevent,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。
	//因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了。
 };

KsetとKtypeの関連付けに注意してください。Kobjectはメンバーksetを使用して、それが属するksetを見つけ、そのktypeをkobj.ktypeに設定します。ksetメンバーが指定されていない場合、関係を確立するためにktypeが使用されます。

kobjectは独自のksetのuevent操作関数を呼び出すため、ksetはその動作を制御できます。kobjectがどのksetにも属していない場合、ueventは送信できません。

要約すると、KtypeとKobjectメカニズム全体の理解は次のとおりです。

Kobjectのコア機能は、参照カウントを保持することです。カウントが0に減少すると、Kobjectが占有していたメモリスペースが自動的に解放されます(この記事で説明するkobjectモジュールによって)。これにより、Kobjectを動的に割り当てる必要があることが決定されます(この方法でのみ動的に解放できます)

Kobjectの使用シナリオのほとんどは、大きなデータ構造(kset、device_driverなど)に埋め込まれているため、これらの大きなデータ構造も動的に割り当てて解放する必要がありますでは、リリースのタイミングは何ですか?埋め込まれたKobjectが解放されたときです。しかし、Kobjectの解放はKobjectモジュールによって自動的に行われます(参照カウントが0の場合)。それではそれ自体を含む大きなデータ構造を解放するにはどうすればよいでしょうか。

ここでKtypeが役に立ちます。Ktypeのリリースコールバック関数がKobjectのメモリ空間(Kobjectを含むデータ構造でさえ)を解放する責任があることを私たちは知っています。それでは、誰がKtypeとその内部関数を実装しますか?上位のデータ構造が配置されているモジュールです!Kobjectが埋め込まれているデータ構造が明確であるため、Kobjectポインターとそれ自体のデータ構造タイプを使用して、解放する必要のある上位データ構造のポインターを見つけて、解放します。

これについて言えば、それははるかに明確です。したがって、kset、device、device_driverなどのKobjectが埋め込まれたすべてのデータ構造は、Ktypeを実装し、そのコールバック関数を定義する必要があります。同様に、sysfs関連の操作はktypeの転送を経由する必要があります。これは、sysfsがKobjectを認識し、実際のファイル操作の本体が埋め込みKobjectの上位データ構造であるためです。

ちなみに、KobjectはLinuxカーネルにおけるオブジェクト指向思考の究極の現れですが、C言語の利点はここにはないので、Linuxカーネルはより巧妙な(そして非常に長い)手段で実装する必要があります。

1.2、mdevの原則

上記のsysfsを分析した後、mdevの分析を開始し、mdevを分析することでsysfsとの関係を理解し​​ます。mdevは、busybox / util-linux /mdev.cファイルにあるbusyboxコードパッケージにあり、uevent_helper関数によって呼び出されます。2つのことが主にmdevで行われます。

最初のもの:

mdev -sコマンドが実行されると、mdevは/ sys / blockをスキャンします(ブロックデバイスは/ sys / blockディレクトリに保存され、カーネルバージョン2.6.25以降、ブロックデバイスも/ sys / class / blockディレクトリに保存されます。 .mdev scans / sys / blockは下位互換性のためです)および/ sys / classディレクトリ内のdev属性ファイル。デバイス番号はdev属性ファイルから取得されます(dev属性ファイルはデバイス番号を「メジャー」の形式で保存します) :minor \ n ")、そして、dev属性ファイルを含むディレクトリの名前をデバイス名device_nameとして使用します(つまり、dev属性ファイルを含むディレクトリはdevice_nameと呼ばれ、/ sys / classの間のディレクトリの一部です。 device_nameはサブシステムと呼ばれます。つまり、各dev属性ファイルが配置されているパスは/ sys / class / subsystem / device_name / dev)として表すことができ、対応するデバイスファイルは/ devディレクトリに作成されますたとえば、cat / sys / class / tty / tty0 / devは4:0を取得し、サブシステムはttyであり、device_nameはtty0です。

2番目のこと:

ueventイベント(以前はhotplugイベントと呼ばれていました)が原因でmdevが呼び出されると、mdevは、ueventイベントによって渡された環境変数(ueventイベントを引き起こしたデバイスのアクションとデバイスのデバイスパス)を介して取得します。次に、ueventイベントの原因となったアクションを特定します。そして、別のアクションに従って対応する操作を実行します。アクションがaddの場合、新しいデバイスがシステムに追加されます。デバイスが仮想デバイスであるか実際の物理デバイスであるかに関係なく、mdevはデバイスパスの下のdev属性ファイルを介してデバイス番号を取得し、デバイスパスの最後のディレクトリ(dev属性ファイルを含むディレクトリ)がデバイス名として使用され、対応するデバイスファイルが/ devディレクトリに作成されます。アクションが削除の場合、つまりデバイスがシステムから削除されている場合は、デバイスパスの最後のディレクトリ名が/ devディレクトリのファイル名であるデバイスファイルを削除します。アクションが追加でも削除でもない場合、mdevは何もしません。

上記から、デバイスがシステムに追加またはシステムから削除されたときにmdevによってデバイスファイルを自動的に作成および削除する場合は、次の3つのことを行う必要があります。

  1. / sys / classの特定のサブシステムディレクトリ内。
  2. 名前としてデバイス名device_nameを使用してディレクトリを作成します。
  3. また、dev属性ファイルはdevice_nameディレクトリに含まれている必要があります。dev属性ファイルは、デバイス番号を「major:minor \ n」の形式で出力します。

上記の内容から、sysfsがueventメカニズムの準備作業を行う、つまり対応するディレクトリを作成するのに対し、mdevはsysfsに基づいてsysfsによって作成されたディレクトリまたはファイルを呼び出すことによってデバイスノードの作成を実装することがわかります。

パート2:コードの紹介を組み合わせ、ueventメカニズムを使用してデバイスノードを作成する

ここで、コードと組み合わせてueventメカニズムを分析します。このメカニズムを分析するには、class_createとclass_device_createの2つの関数からこのプロセスがどのように実装されているかを分析する必要があります。最初にclass_createを分析しましょう:

class_create(THIS_MODULE,"buttonsdrv");
    class_register(cls);
        kobject_set_name(&cls->subsys.kobj, "%s", cls->name); //将类的名字led_class赋值给对应的kset
	subsys_set_kset(cls, class_subsys);
	subsystem_register(&cls->subsys);
	    kset_register(s);  //创建class设备类目录
		kset_add(k);
	            kobject_add(&k->kobj);
			kobject_shadow_add(kobj, NULL);
		            parent = kobject_get(kobj->parent); // parent即class_kset.kobj,即/sysfs/class对应的目录
			    list_add_tail(&kobj->entry,&kobj->kset->list);
			    create_dir(kobj, shadow_parent);  //创建一个class设备类目录
				sysfs_create_dir(kobj, shadow_parent); //该接口是sysfs文件系统接口,代表创建一个目录,不再展开。

上記から、kobjectがsysfsのディレクトリ(dir)に対応していることがわかります。kobjectを登録するときに、kobject_add(&k-> kobj);を呼び出して、クラスデバイスディレクトリを作成します。同時に、class_create関数がclass_device_create関数のカタログの準作業を実行していることがわかります。

記事の冒頭で紹介した写真から、kobjectの状態が変化すると(追加、削除など)、ユーザースペースに通知され、ユーザースペースは受信後に対応する処理を実行できることがわかります。イベント通知。

ueventが時間をユーザースペースに報告する方法は2つあります。

  1. kmodモジュールを介して、ユーザースペースで実行可能プログラムまたはスクリプトを直接呼び出します。
  2. ネットリンク通信メカニズムを介して、カーネルスペースからユーザースペースにイベントを渡します。

この記事では主に、kmodモジュールを介してユーザースペースで実行可能プログラムまたはスクリプトを直接呼び出す方法について説明しますkobject.hを介して、ueventモジュールは次のAPIを提供します(これらのAPIの実装は「lib / kobject_uevent.c」ファイルにあります)。

int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
                         char *envp[]);
  
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
   
int kobject_action_type(const char *buf, size_t count,enum kobject_action *type);

class_device_create関数からの分析から始めて、彼がどのようにしてkobject_uevent関数に到達したかを確認しましょう。class_device_create関数の階層関係を見てみましょう。

class_device_create(buttonsdrv_class,NULL,MKDEV(auto_major,0),NULL,"buttonsdrv");
	class_device_register(class_dev);
		class_device_add(class_dev);
			class_dev = class_device_get(class_dev);
			parent_class = class_get(class_dev->class);
			parent_class_dev = class_device_get(class_dev->parent);
			kobject_set_name(&class_dev->kobj, "%s", class_dev->class_id);
			kobject_add(&class_dev->kobj);
			class_device_create_file(class_dev, attr);
			class_device_add_groups(class_dev);
			make_deprecated_class_device_links(class_dev);
			kobject_uevent(&class_dev->kobj, KOBJ_ADD);

前のコードから、class_createとclass_device_createが多くの同様の作業、つまりディレクトリの作成を行っていることがわかります。kobject_uevent関数に到達すると、それらは異なります。kobjec_uevent関数を分析してみましょう

/**
 * 通过终端事件通知用户层
 *
 * @action: 发生的事件 (通常是 KOBJ_ADD 和 KOBJ_REMOVE)
 * @kobj: 事件发生的kobject 结构体
 *
 */
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
	return kobject_uevent_env(kobj, action, NULL);
}

彼はkobject_uevent_env関数を呼び出しました。上記の概要では、イベントを送信する必要があることがわかっていたので、どのようなイベントがありますか?

linux-3.5 / include / linux /kobject.hを見てみましょ

 enum kobject_action {   
     KOBJ_ADD,  //ADD/REMOVE,Kobject(或上层数据结构)的添加/移除事件。
     KOBJ_REMOVE,    
     KOBJ_CHANGE, //CHANGE,Kobject(或上层数据结构)的状态或者内容发生改变。
		  //CHANGE,如果设备驱动需要上报的事件不再上面事件的范围内,
		  //或者是自定义的事件,可以使用该event,并携带相应的参数。
     KOBJ_MOVE,  //MOVE,Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)。
     KOBJ_ONLINE, //ONLINE/OFFLINE,Kobject(或上层数据结构)的上线/下线事件,其实是是否使能。
     KOBJ_OFFLINE,
     KOBJ_MAX 
 };

以下では、kobject_uevent_env関数の分析を続けます

/**
 * 发送一个带有环境变量的事件
 *
 * @action: 发生的事件(通常为KOBJ_MOVE)
 * @kobj: 事件发生的kobject结构体
 * @envp_ext: 环境变量数据指针
 *
 */
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
			char *envp_ext[])
{
	action_string = action_to_string(action);
 
	/* 查找当前kobject或其parent是否从属于某个kset;如果都不从属于某个kset,则返回错误。(说明一个kobject若没有加入kset,是不会上报uevent的) */
	top_kobj = kobj;
	while (!top_kobj->kset && top_kobj->parent) {
		top_kobj = top_kobj->parent;
	}
	if (!top_kobj->kset) {
		pr_debug("kobject attempted to send uevent without kset!\n");
		return -EINVAL;
	}
 
	kset = top_kobj->kset;
	uevent_ops = kset->uevent_ops;
 
	/*  如果所属的kset有uevent_ops->filter,则调用该函数,若该函数返回0,则过滤此次上报。(kset 可以通过filter接口过滤不希望上报的event) */
	if (uevent_ops && uevent_ops->filter)
		if (!uevent_ops->filter(kset, kobj)) {
			pr_debug("kobject filter function caused the event to drop!\n");
			return 0;
		}
 
	/*判断所属的kset是否有合法的名称,若uevent_ops->name存在就用其返回的名称作为subsystem;若uevent_ops->name不存在就用kset本身的kobject的名称作为subsystem;若没有合法的名称,则不上报uevent */
	if (uevent_ops && uevent_ops->name)
		subsystem = uevent_ops->name(kset, kobj);
	else
		subsystem = kobject_name(&kset->kobj);
	if (!subsystem) {
		pr_debug("unset subsytem caused the event to drop!\n");
		return 0;
	}
 
	/* 分配一个此次上报的环境变量 */
	envp = kzalloc(NUM_ENVP * sizeof (char *), GFP_KERNEL);
	if (!envp)
		return -ENOMEM;
 
	/*分配一个此次上报的用于保存环境变量的buffer, */
	buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
	if (!buffer) {
		retval = -ENOMEM;
		goto exit;
	}
 
	/* 获得该kobject在sysfs中路径 */
	devpath = kobject_get_path(kobj, GFP_KERNEL);
	if (!devpath) {
		retval = -ENOENT;
		goto exit;
	}
 
	/* uevent_helper的环境变量*/
	envp[i++] = "HOME=/";
	envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
 
	/* 添加环境变量 */
	scratch = buffer;
	envp [i++] = scratch;
	scratch += sprintf(scratch, "ACTION=%s", action_string) + 1;
	envp [i++] = scratch;
	scratch += sprintf (scratch, "DEVPATH=%s", devpath) + 1;
	envp [i++] = scratch;
	scratch += sprintf(scratch, "SUBSYSTEM=%s", subsystem) + 1;
	for (j = 0; envp_ext && envp_ext[j]; j++)
		envp[i++] = envp_ext[j];
	/* just reserve the space, overwrite it after kset call has returned */
	envp[i++] = seq_buff = scratch;
	scratch += strlen("SEQNUM=18446744073709551616") + 1;
 
	/* 如果 uevent_ops->uevent 存在,调用该接口,添加kset统一的环境变量到env指针 */
	if (uevent_ops && uevent_ops->uevent) {
		retval = uevent_ops->uevent(kset, kobj,
				  &envp[i], NUM_ENVP - i, scratch,
				  BUFFER_SIZE - (scratch - buffer));
		if (retval) {
			pr_debug ("%s - uevent() returned %d\n",
				  __FUNCTION__, retval);
			goto exit;
		}
	}
 
	/* 调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号 */
	spin_lock(&sequence_lock);
	seq = ++uevent_seqnum;
	spin_unlock(&sequence_lock);
	sprintf(seq_buff, "SEQNUM=%llu", (unsigned long long)seq);
 
 
	/* 以uevent_helper、 subsystem 以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,调用kmod模块提供的call_usermodehelper函数,上报uevent。 */
	if (uevent_helper[0]) {
		char *argv [3];
 
		argv [0] = uevent_helper;
		argv [1] = (char *)subsystem;
		argv [2] = NULL;
		call_usermodehelper (argv[0], argv, envp, 0);
	}
}

ueventモジュールがkmodを介してueventを報告すると、ユーザースペース実行可能ファイル(またはスクリプト、略してueventヘルパー)を呼び出して、call_usermodehelper関数を介してイベントを処理します。ueventヘルパーのパスは、uevent_helper配列に格納されます。カーネルのコンパイル時に、CONFIG_UEVENT_HELPER_PATH構成アイテムを介してueventヘルパーを静的に指定できます。

ただし、このメソッドは各ueventのプロセスをフォークします。カーネルでサポートされるデバイスの数が増えると、このメソッドはシステムの起動時に致命的となります(メモリオーバーフローなどを引き起こす可能性があります)。したがって、この方法は以前のカーネルバージョンでのみ使用され、カーネルでは推奨されなくなりました。したがって、カーネルをコンパイルするときは、この構成項目を空白のままにする必要があります。システムの起動後、ほとんどのデバイスの準備が整いました。必要に応じてueventヘルパーを再割り当てして、システム操作中のホットプラグイベントを検出できます。

これは、ヘルパーのパスを「/ sys / kernel / uevent_helper」ファイルに書き込むことで実現できます。実際、カーネルはuevent_helper配列をsysfsファイルシステムの形式でユーザースペースに開き、ユーザースペースプログラムの変更とアクセスを行います。詳細については、「。/kernel /ksysfs.c」の対応するコードを参照してください。

/etc/init.d/rcSスクリプトにecho "/ sbin / mdev"> / proc / sys / kernel / hotplugを追加すると、cat / sys / kernel / uevent_helperが/ sbin / mdevであることがわかります。これは、/ proc / sys / kernel / hotplugの実行可能ファイルパスが最終的に/ sys / kernel / uevent_helperに書き込まれることを意味します/ sys / kernel / uevent_helperのlsmod、rmmod、/ kernel / mainが実行されるときに、手動で "/ kernel / main"> uevent_helper(前の/ sbin / mdevが上書きされます)をエコーし​​、時間が報告されたことを示します。ユーザースペース。

Busyboxでデバイスノードを作成する方法を見てみましょう。

mdevが出てくる番です。前述の説明はすべて、sysfsファイルシステムにディレクトリまたはファイルを作成し、アプリケーションがアクセスするデバイスファイルは/ dev /ディレクトリに作成する必要があります。この作業はmdevによって行われます。

mdevの原則は、/ etc / mdev.confファイルで定義されているデバイスファイルに名前を付けるためのルールを説明し、ルールに基づく環境変数の要件に基づいてデバイスファイルを作成することです。mdev.confはユーザーレベルで指定されるため、より柔軟性があります。この記事は、mdev構成スクリプトの分析を拡張することを意図していません。関連知識については、私の翻訳を参照してください:mdev.conf translation

対応するmdevプログラムはBusybox / util-linux /mdev.cにあります。

int mdev_main(int argc UNUSED_PARAM, char **argv)
    xchdir("/dev");
    if (argv[1] && strcmp(argv[1], "-s")//系统启动时mdev –s才会执行这个分支
    else
    action = getenv("ACTION");
    env_path = getenv("DEVPATH");
    G.subsystem = getenv("SUBSYSTEM");
    snprintf(temp, PATH_MAX, "/sys%s", env_path);//到/sysfs/devices/led目录
    make_device(temp, /*delete:*/ 0);
    strcpy(dev_maj_min, "/dev"); //读出dev属性文件,得到设备号
    open_read_close(path, dev_maj_min + 1, 64);    
    ….
    mknod(node_name, rule->mode | type, makedev(major, minor)) //最终mknod创建节点

最終的には、mknodが/ devディレクトリにデバイスファイルを作成したことを追跡します。

参照:

参考文献は詳細に説明しています、それは一見の価値があります!-----原作者の努力に感謝します!

 

おすすめ

転載: blog.csdn.net/u014674293/article/details/115083363