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 ファジークエリの使用法 (通常、ワイルドカード、組み込み関数)

目次1. MySQL ワイルドカード ファジー クエリ (%,_) 1-1. ワイルドカードの分類1...

ウェブページ作成時に標準 HTML コードを使用する際のポイント

<br />多くのウェブサイト デザイナーが犯す最も一般的な間違いは、ウェブページが I...

MySQL マルチバージョン同時実行制御 MVCC の実装

目次MVCCとはMVCC 実装MVCC はファントム リードを解決しますか? MVCCとはMVCC ...

nginx を使用して http を https に変換するサンプルコード

最近、小さなプログラムを書いています。その小さなプログラムの公式ウェブサイトはhttpsを使用する必...

Linux ファイアウォールの状態確認方法の例

Linuxファイアウォールの状態を確認する方法1. 基本操作 # ファイアウォールのステータスを表示...

jsを使用して動的な背景を実現する

この記事の例では、動的な背景を実現するためのjsの具体的なコードを参考までに共有しています。具体的な...

MySQL でデータ テーブルを作成し、主キーと外部キーの関係を確立する方法の詳細な説明

序文MySQL テーブルの主キーと外部キーを作成するときは、次の点に注意する必要があります。主キーと...

Docker環境にJenkinsコンテナをインストールする詳細なチュートリアル

推奨される Docker 学習教材: https://www.runoob.com/docker/d...

JavaScript ではおそらく switch 文を使う必要はない

目次スイッチも複雑なコードブロックもありませんPythonからのインスピレーション辞書を使用してスイ...

HTMLデザインパターンの日々の勉強ノート

HTML デザインパターン学習ノート今週は主にHTMLデザインパターンを学びました。学習内容をまとめ...

MySQLデータベースでスロークエリログを有効にする方法の詳細な説明

データベースはスロークエリログを有効にします設定ファイルを変更する設定ファイルmy.iniに次の2つ...

Linux での MySQL データベースのマスター スレーブ同期レプリケーション構成

Linux での MySQL データベースのマスター/スレーブ同期構成の利点は、この方法をバックアッ...

React を使って小さなプログラムを書くための Remax フレームワークのコンパイル プロセス分析 (推奨)

Remax は、実行時に構文制限のないソリューションを採用した React を使用して小規模なプロ...

Javascript での JSBridge に関する予備的研究

目次JSBridgeの起源JSBridgeの双方向通信原理JSはネイティブを呼び出すネイティブコール...

VMware 仮想マシンのインストール Linux システムのグラフィック チュートリアル

この記事では、LinuxシステムのVMwareインストールの具体的な手順を参考までに紹介します。具体...