LED を使って Linux カーネルを使い始める方法を探る

LED を使って Linux カーネルを使い始める方法を探る

画像の説明を追加してください

序文

最近、プロジェクトで LED サブシステムを使用する必要があります。組み込み Linux でライトをオンにするのは比較的簡単です。特定のライトに対応するディレクトリ内の対応するファイルに特定の値を書き込むだけで、LED をオン/オフ/点滅させることができます。

# echo 1 > /sys/class/leds/green/brightness // LEDをオンにする
# echo 0 > /sys/class/leds/green/brightness // LEDをオフにする
# echo heartbeat > /sys/class/leds/green/trigger // LED をハートビートのように点滅させる

LEDトリガー

もちろん、プロジェクトで使用されている照明機能は、上で紹介したものよりも少し複雑です。ハードディスクのライトに似ており、ハードディスクの読み取りまたは書き込み時に LED が点滅します。このファイルを cat すると、mmc0 という単語が含まれているので、この関数はトリガー ファイルに関連しているはずだと漠然と感じています。

次に、mmc0 をトリガーに書き込み、どのような効果があるかを確認します。

# echo mmc0 > /sys/class/leds/green/trigger
# cat /sys/class/leds/green/trigger
なし rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock 
kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock 
kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock 
[mmc0] ハートビート デフォルトオン ir-power-click axp20x-usb-online

ディスクへのデータの書き込み

# タッチ aa | 同期

不思議なことに、ディスクにデータを書き込むコマンドが実行されるたびに、ボード上の緑色のライトが 1 回点滅することを発見しました。

情報を調べてみると、これは LED トリガーに関連する機能であることがわかりました。

一瞬、私は思った

  • mmc0 を tigger に書き込むと、LED がハードディスク ライトに変わるのはなぜですか?
  • 明るさファイルに 1/0 を書き込むことでライトのオン/オフを制御できるのはなぜですか?
  • トリガー ファイルにタイマーが書き込まれると、なぜ LED が点滅するのでしょうか? 同時に、delay_on と delay_off の 2 つのファイルが生成され、それらを使用してライトの点滅頻度を制御できるのでしょうか?

さまざまな疑問が頭に浮かび、これらの機能の背後にある原理を理解したいと思いました。

探索を始める

問題が行動を促します。まず知りたい質問をリストアップします。

さまざまなライトに対応するディレクトリはどこから来るのでしょうか?

  • トリガー内のトリガーはどのように生成されますか?
  • 明るさに 1/0 を書き込むと LED がオン/オフになるのはなぜですか?
  • タイマーをトリガーに書き込むときに、delay_on と delay_off という 2 つのファイルが表示されるのはなぜですか?

調査を始めましょう。まずは昨夜私が考えたことから始めましょう。led_classdev_register("aaa") は LED ディレクトリを生成します。

LEDデバイス登録

led_classdev_register() が LED ライトに対応するディレクトリを生成するかどうかを確認する実験をしてみましょう。

実行できる場所をランダムに見つけ、次のコード行を追加して、aaaディレクトリがledsディレクトリに生成されることを期待しました。

	構造体 led_classdev *cdev;
	整数 戻り値;
	cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
	もし (!cdev)
		-ENOMEM を返します。
	cdev->名前 = "aaa";
	// cdev->brightness_set = ebsa110_led_set;
	// cdev->brightness_get = ebsa110_led_get;
	// cdev->default_trigger = "ハートビート";
	ret = led_classdev_register(NULL、cdev);
	戻り値 < 0 の場合
		cdevを解放します。
		ret を返します。
	}

コンパイル、書き込み、実行、表示

# ls /sys/class/leds/
aaaグリーン

案の定、私が表示したいと思っていたディレクトリ aaa が leds の下に生成され、私の自信は大きく高まりました!

その後、基礎となる呼び出し関係をトレースしました。

led_classdev_register()
	of_led_classdev_register() // led_classdev クラスの新しいオブジェクトを登録します。
		led_classdev_next_name()
		デバイス作成グループ()
		led_add_brightness_hw_changed()
		list_add_tail() // LEDのリストに追加
		led_update_明るさ()
		//led_trigger_set_default()

LEDディレクトリ

特定のライトがどのように登録されるかがわかったので、次は leds ディレクトリがどのように生成されるかを知りたいと思います。コードを検索しましたが、見つけるのは難しくありません。以下は、leds ディレクトリの生成に関連する関数呼び出し関係です。

subsys_initcall(leds_init);
leds_init() // leds クラスを作成します。つまり、/sys/class/leds ディレクトリを生成します。class_create()
		__class_create()
			クラスレジスタ()
				kset_register()

類推によって理解する

後で情報を確認すると、/sys/class/leds はクラスであることがわかりました。クラスはカーネルサブシステムを表します。カーネルにはこのようなサブシステムが多数あります。

/sys/class/ 内の各ディレクトリはクラスであり、サブシステムでもあります。

# ls /sys/class/
ata_device extcon mdio_bus ptp サウンド
ata_link gpio メモリ pwm spi_master
ata_port グラフィックス その他 rc サーマル
bdi hwmon mmc_host レギュレータ tty
ブロック i2c アダプタ ネット rtc udc
bsg i2c-dev phy scsi_device vc
DMA 入力 電源 SCSI ディスク vtconsole
drm leds pps scsi_host ウォッチドッグ

各クラスには、green、aaaなどの特定のインスタンス化オブジェクトがあります。

# ls /sys/class/leds/
aaaグリーン

各オブジェクトには、明るさ、トリガーなどの対応するメンバーメソッド/属性があります。

# ls /sys/class/leds/aaa/
明るさ電源トリガー
max_brightness サブシステム uevent

C++ のクラスにとても似ています。実際、それはクラスであり、単純な比較です

ここに画像の説明を挿入

後で時間ができたら、特定のクラスでの登録ロジックをゆっくりと勉強します。ルート探索を続けます。ここで、私の探索ルートが変わったことに注意してください。もはや LED サブシステムの探索に限定されず、LED サブシステムの外側のカーネルにまで拡張され始めました。

クラスディレクトリの生成

次に、クラス ディレクトリについて説明します。leds ディレクトリの取得元がわかったので、その上のクラス ディレクトリの取得元がどこなのか疑問に思います。

コードに従って

クラス初期化()
	kset_create_and_add("class", NULL, NULL); // 構造体 kset を動的に作成し、sysfs に追加します
		kset_create()
			kobject_set_name()
		kset_register()
			kset_init()
			kobject_add_internal()
				オブジェクト取得()
				kobj_kset_join()
					kset_get()
					リストの末尾を追加()
						リストに追加()
						{
							次->前 = 新しい;
							new->next = 次へ;
							新しい->前 = 前;
						}
				create_dir() // ディレクトリを作成する

カーネルを起動する()

実際、classes_init() までトレースした後は、下のどのコードをトレースするかを考える必要はなく、上に向かってトレースを続けるだけです。

/* カーネル */
カーネルを起動する()
	rest_init() // 残りの非__initを実行します。これで完了です
		kernel_thread(kernel_init, NULL, CLONE_FS);
		カーネル初期化()
			kernel_init_freeable()
			/*
			 * マシンは初期化されました。どのデバイスも
			 *まだ触れられていないが、CPUサブシステムは起動しており、
			 * 実行中であり、メモリとプロセスの管理が機能します。
			 *
			 * これで、ようやく実際の作業を開始できます。
			 */
			基本的なセットアップ()
			driver_init() // サブシステムを初期化します。
				devtmpfs_init()
				デバイス初期化()
				バス初期化()
				classes_init() // 単に classes_init()
				ファームウェア初期化()
				ハイパーバイザー初期化()
				プラットフォームバス初期化()
				cpu_dev_init()
				メモリデバイス初期化()
				コンテナ_dev_init()
				の_core_init()

上で述べたように、私は偶然 start_kernel() を追跡し、それが夢の始まりとなりました。カーネル コードを追跡することがこんなにも興味深いものであることに初めて気付きました。

カーネルを起動しています…

start_kernel() までトレースした後、この文字列「Starting kernel ...」がどこに印刷されているのか気になって仕方ありませんでした。uboot を起動するたびにこの文を見ることができます。見つけられたら素晴らしいと思いませんか?残念ながら、カーネルコード内では見つけられませんでした。

ユーブート

最初はstart_kernel()でStarting kernel ...が出力されると思っていましたが、カーネルコード内には見つかりませんでした。このとき、ubbot に出力されているか気になりました。カーネルのロードを開始する前にこの文を出力するのは妥当です。

ubootで検索したら

ブートジャンプLinux()
	アナウンスとクリーンアップ()
		printf("\nカーネルを開始しています...%s\n", fake ? "(トレース用の偽の実行)" : "");

ここで uboot が終了し、カーネルが実行されようとしています。

完全な通話関係を添付します

uboot からカーネルへ、そして /sys/class へ移動し、leds クラスを登録して LED ライトをインスタンス化します。

/* uboot */
ブートジャンプLinux()
	アナウンスとクリーンアップ()
		printf("\nカーネルを起動しています...%s\n"); // printf() 
		bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF、"start_kernel");
		クリーンアップ前Linux()
	kernel_entry(0, machid, r2);
/* カーネル */
カーネルを起動する()
	rest_init() // 残りの非__initを実行します。これで完了です
		kernel_thread(kernel_init, NULL, CLONE_FS);
		カーネル初期化()
			kernel_init_freeable()
			/*
			 * マシンは初期化されました。どのデバイスも
			 *まだ触れられていないが、CPUサブシステムは起動しており、
			 * 実行中であり、メモリとプロセスの管理が機能します。
			 *
			 * これで、ようやく実際の作業を開始できます。
			 */
			基本的なセットアップ()
			driver_init() // サブシステムを初期化します。
				devtmpfs_init()
				デバイス初期化()
				バス初期化()
				クラス初期化()
					kset_create_and_add("class", NULL, NULL); // 構造体 kset を動的に作成し、sysfs に追加します
						kset_create()
							kobject_set_name()
						kset_register()
							kset_init()
							kobject_add_internal()
								オブジェクト取得()
								kobj_kset_join()
									kset_get()
									リストの末尾を追加()
										リストに追加()
										{
											次->前 = 新しい;
											new->next = 次へ;
											新しい->前 = 前;
										}
								ディレクトリを作成します。
				ファームウェア初期化()
				ハイパーバイザー初期化()
				プラットフォームバス初期化()
				cpu_dev_init()
				メモリデバイス初期化()
				コンテナ_dev_init()
				の_core_init()

subsys_initcall(leds_init);
leds_init() // LED クラス (/sys/class/leds ディレクトリ) を作成します class_create()
		__class_create()
			クラスレジスタ()
				kset_register()

led_classdev_register()
	of_led_classdev_register() // led_classdev クラスの新しいオブジェクトを登録します。
		led_classdev_next_name()
		デバイス作成グループ()
		led_add_brightness_hw_changed()
		list_add_tail() // LEDのリストに追加
		led_update_明るさ()
		//led_trigger_set_default()

人生の入り口

上記は、LED サブシステムの観点から見た Linux カーネルの研究です。 Linux カーネルを研究するための入り口を見つけました。

巨大なものに直面すると、私たちはしばしば恐怖を感じます。この恐怖は私たちがさらに研究することを妨げ、その結果、私たちはその物に対する理解を欠き、それを克服することができなくなります。

2つの例:

  • ある有名なマラソンランナーが、自分の成功体験をみんなに話していました。彼は、いつも事前に車でルートを見て、基準点を書き留め、距離をいくつかの区間に分割して、各区間をうまく走ったと話していました。
  • 私が高校時代に物理で良い成績をとったのは、問題を解く方法が他の人と違っていたからです。他の人はテスト用紙を受け取ると最後の問題をじっと見つめ、すぐに答えを知りたがります。私はまず問題をざっと読み、いくつかの既知の条件を見つけ、物理的な公式に基づいて未知の量を推測しようとしました。少しずつ推測し、よく見ると答えが目の前にあることもありました。

質問、主題、スキルなどの小さなものから、仕事、生活、さらには人生全体などの大きなものまで。入り口を見つけることができれば幸運であり、それを成功とより良い未来につなげていきます。 今後とも 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Linux カーネル デバイス ドライバー Linux カーネル 基本メモの概要
  • Linuxカーネルの動作原理を示す図

<<:  MySQL IDは1から増加し始め、不連続IDの問題を素早く解決します

>>:  テーブルの幅を固定して、テキストによって幅が変わらないように設定

推薦する

時点別のMySQLデータベース復旧実績

はじめに: 時間ポイントによる MySQL データベースの復旧どの企業にとっても、データは最も価値の...

MySQL での Join の使用に関する詳細な説明

前の章では、1 つのテーブルからデータを読み取る方法を学習しました。これは比較的簡単ですが、実際のア...

Linux および Unix サーバーのセキュリティを強化する方法

ネットワーク セキュリティは非常に重要なトピックであり、サーバーはネットワーク セキュリティにおける...

vue-resource インターセプターの使用に関する詳細な説明

序文インターセプター最近のフロントエンド フレームワークでは、インターセプターは基本的に非常に基本的...

MySQL 5.7 で業務を停止せずに従来のレプリケーションを GTID レプリケーションに変更する例

GTID の利点により、従来のファイル POS ベースのレプリケーションを GTID ベースのレプリ...

JavaScript関数導入の詳しい説明

目次機能紹介関数関数の作成コンストラクタは関数を作成する関数宣言は関数を作成する関数式関数を作成する...

Dockerにおけるオーバーレイネットワークの詳細な説明

Docker 公式ドキュメントからの翻訳、原文: https://docs.docker.com/n...

CSS で点線の境界線のスクロール効果を実装するサンプルコード

マウスをある領域の上に置くと、その領域に点線の境界線と線のアニメーションが表示されるというクールな効...

FirefoxのWeb開発者を使用してWebページのスタイルを無効にする方法

前提条件: Web開発者プラグインがインストールされている操作手順: [ツール] -> [We...

Excel ファイルを MySQL データベースにインポートする方法

この記事では、ExcelファイルをMySQLデータベースにインポートする方法を参考までに紹介します。...

埋め込みJavaScriptと外部リンクの基本的な応用方法

目次埋め込みJavaScriptと外部リンクの基本的な応用JavaScript の記述方法には、イン...

IDEA が MySQL データベースに接続できない問題の 6 つの解決策

この記事では、IDEA が MySQL データベースに接続できない問題に対する 6 つの解決策を主に...

dockerにmysqlをインストールした後にNavicatが接続できない問題に対する完璧な解決策

1. Dockerがイメージをプルするdocker pull mysql (デフォルトで最新バージョ...

Linux でユーザーにルート権限を追加する方法の概要

1. ユーザーを追加します。まず、adduser コマンドを使用して共通ユーザーを追加します。コマン...

Vueは複数列レイアウトドラッグを実装します

この記事では、マルチカラムレイアウトドラッグを実装するためのVueの具体的なコードを参考までに共有し...