Linuxカーネルとは正確には何ですか?どうすれば効率的に勉強できますか?学習ルートのマインドマップ付き

この記事では、主にLinuxカーネルとは何かを説明し、Linuxカーネルとは何かを読者がすばやく理解し、Linuxカーネルを理解できるように、Linuxカーネルの役割と機能を複数の図で示します。

Linuxカーネルは1300万行を超えるコードで、世界最大のオープンソースプロジェクトの1つですが、カーネルとは何で、何に使用されているのでしょうか。

å¾ç

 

å¾ç

å¾ç

カーネルとは


カーネルは、コンピューターのハードウェアとインターフェイスする、簡単に交換できるソフトウェアの最低レベルです。「ユーザーモード」で実行されているすべてのアプリケーションを物理ハードウェアに接続し、サーバーと呼ばれるプロセスがプロセス間通信(IPC)を使用して相互に情報を取得できるようにします。

カーネルはタイプに分割する必要がありますか?

はい、そうです。

3.1マイクロカーネル

マイクロカーネルは、管理する必要があるもの(CPU、メモリ、IPC)のみを管理します。コンピューター内のほとんどすべてがアクセサリと見なされ、ユーザーモードで処理できます。マイクロカーネルには移植性の利点があります。オペレーティングシステムが同じ方法でハードウェアにアクセスしようとしている限り、ビデオカードやオペレーティングシステムを変更するかどうかを心配する必要がないからです。マイクロカーネルはまた、メモリとインストールスペースをほとんど消費せず、特定のプロセスのみがユーザーモードで実行され、ユーザーモードには管理者モードの高い特権がないため、より安全になる傾向があります。

 

å¾ç

3.1.1長所

  • 移植性
  • 小さな設置スペース
  • 小さなメモリフットプリント
  • 安全性

3.1.2短所

  • ドライバーを通じて、ハードウェアはより抽象的です
  • ドライバーがユーザーモードであるため、ハードウェアの応答が遅い場合があります
  • プロセスは情報を取得するためにキューで待機する必要があります
  • プロセスは待機せずに他のプロセスにアクセスすることはできません

3.2シングルコア

シングルコアは、CPU、メモリ、IPCだけでなく、デバイスドライバー、ファイルシステム管理、システムサーバー呼び出しも含まれているため、マイクロカーネルの反対です。プログラムがメモリまたは実行中の他のプロセスから情報を取得する必要がある場合、キューで待機せずに情報にアクセスするためのより直接的な回線があるため、シングルコアはハードウェアおよびマルチタスクへのアクセスに優れています。ミッションは達成されました。ただし、これにより問題が発生する可能性があります。管理モードで実行するものが多いほど、動作が正常でない場合、システムがクラッシュする原因になるためです。

 

å¾ç

3.2.1長所

  • プログラムのハードウェアへのより直接的なアクセス
  • プロセス間のより簡単なコミュニケーション
  • デバイスをサポートしている場合は、追加のインストールなしで機能するはずです
  • プロセッサ時間を待機するキューがないため、プロセスの反応が速くなります

3.2.2短所

  • 設置量が多い
  • 大きなメモリフットプリント
  • すべての操作が管理モードで実行されているため、あまり安全ではありません

混合カーネル

ハイブリッドカーネルは、ユーザーモードで実行するものと管理モードで実行するものを選択できます。通常、デバイスドライバーとファイルシステムI / Oはユーザーモードで実行されますが、IPCとサーバーの呼び出しはマネージャーモードのままになります。これは両方の長所ですが、ドライバーの責任はすべてハードウェアメーカーが負担するため、通常はハードウェアメーカーがより多くの作業を行う必要があります。また、マイクロカーネルに固有の遅延の問題がある場合もあります。

 

å¾ç

4.1長所

  • 開発者は、ユーザーモードで実行するものと管理モードで実行するものを選択できます
  • モノリシックカーネルよりもインストールスペースが小さい
  • 他のモデルよりも柔軟性があります

4.2短所

  • マイクロカーネルと同じプロセス遅延に苦しむ
  • デバイスドライバーはユーザーが管理する必要があります(通常)

Linuxカーネルファイルはどこにありますか?
Ubuntuのカーネルファイルは、vmlinux-versionと呼ばれる/ bootフォルダーに保存されます。vmlinuzという名前はUNIXの世界に由来します。1960年代には単にカーネルを「unix」と呼んでいたため、1990年代にカーネルが最初に開発されたとき、Linuxはカーネルを「Linux」と呼び始めました。

 

å¾ç

マルチタスクを容易にするために仮想メモリを開発する場合は、ファイルの前に「vm」を付けて、カーネルが仮想メモリをサポートしていることを示します。しばらくの間、Linuxカーネルはvmlinuxと呼ばれていましたが、カーネルが大きくなりすぎて使用可能なブートメモリに収まらなくなったため、カーネルイメージを圧縮し、最後のxをzに変更して、zlibで圧縮されたことを示しました。同じ圧縮が常に使用されるわけではなく、通常はLZM​​AまたはBZIP2に置き換えられます。一部のカーネルは、単にzImageと呼ばれます。

バージョン番号はABC形式Dになります。Bは2.6、Cはバージョン、Dは1つまたは複数のパッチです。

 

å¾ç

/ bootフォルダーには、initrd.img-version、system.map-version、config-versionと呼ばれる他の非常に重要なファイルがあります。initrdファイルは、実際のカーネルファイルを抽出して実行するための小さなRAMディスクとして使用されます。このシステム。マップファイルは、カーネルが完全にロードされる前のメモリ管理に使用されます。構成ファイルは、カーネルイメージをコンパイルするときにロードするオプションとモジュールをカーネルに指示します。

Linuxカーネルアーキテクチャ


Linuxカーネルはモノリシックであるため、他のタイプのカーネルよりも最大のスペースと複雑さを占めます。これは、Linuxの初期にかなり多くの論争を引き起こした設計機能であり、単一のカーネルに固有の同じ設計上の欠陥がまだいくつかあります。

 

å¾ç

これらの欠点を解決するために、Linuxカーネル開発者が行うことの1つは、実行時にカーネルモジュールをロードおよびアンロードできるようにすることです。つまり、カーネルの機能を動的に追加または削除できます。これにより、カーネルにハードウェア機能を追加できるだけでなく、低レベルの仮想化などのサーバープロセスを実行するモジュールを含めることができますが、場合によってはコンピューターを再起動せずにカーネル全体を置き換えることもできます。


再起動せずにWindowsサービスパックにアップグレードできるかどうか想像してみてください...

カーネルモジュール


Windowsが利用可能なすべてのドライバーをインストールし、必要なドライバーを開くだけでよい場合はどうなりますか?これは基本的にカーネルモジュールがLinuxに対して行うことです。ローダブルカーネルモジュール(LKM)とも呼ばれるカーネルモジュールは、使用可能なすべてのメモリを消費することなく、カーネルをすべてのハードウェアで動作させるために不可欠です。

 

å¾ç

モジュールは通常、デバイス、ファイルシステム、システムコールなどの関数を基本カーネルに追加します。lkmのファイル拡張子は.koで、通常は/ lib / modulesディレクトリに保存されます。モジュールの特性により、起動時にmenuconfigコマンドを使用するか、/ boot / configファイルを編集するか、modprobeコマンドを使用してモジュールを動的にロードおよびアンロードすることにより、モジュールをロードするかロードしないかを設定できます。カーネルをカスタマイズします。

サードパーティおよびクローズドソースモジュールは、Ubuntuなどの一部のディストリビューションで利用できますが、これらのモジュールのソースコードが利用できないため、デフォルトでインストールされない場合があります。ソフトウェアの開発者(つまり、nVidia、ATIなど)はソースコードを提供しませんが、独自のモジュールを構築し、配布に必要な.koファイルをコンパイルします。これらのモジュールはビールのように無料ですが、スピーチのように無料ではありません。したがって、メンテナは無料でないソフトウェアを提供することでカーネルを「汚染」すると考えているため、一部のディストリビューションには含まれていません。

カーネルは魔法ではありませんが、コンピュータが正しく機能するためには不可欠です。LinuxカーネルはOSXやWindowsとは異なり、カーネルレベルのドライバーが含まれており、「箱から出してすぐに」多くのものを作成できます。ソフトウェアとハ​​ードウェアがどのように連携するか、およびコンピューターの起動に必要なファイルについて理解を深めてください。

Linuxカーネル学習経験の要約

オープニング

コアを学ぶために、誰もが独自の学習方法を持っており、慈悲深い人は慈悲深い人を見て、賢い人は知恵を見る。学習の過程でまとめたものは以下のとおりですが、私自身はもっと効率的だと思いますので、皆さんと共有したいと思います。

これは私が編集した学習ルートのマインドマップです。

 

Linuxカーネル学習関連のビデオ、ドキュメントをクリックできます:入手するための学習資料

 

私の学習方法

当初、主な問題は理解するかどうかではなく、知っているかどうかにあると思います。特定のサブシステムの実現には特定の戦略と方法が採用されており、研究で行う必要があるのはそこにあることを知ることだけです。次に、説明されている戦略または方法の理解です。

私自身の学習経験によると、最初にカーネルを学び始めたとき、私がしなければならないことは、カーネルの一般的なフレームワークを頭の中で確立し、各サブシステムの設計概念と構築アイデア、およびこれらの概念を理解することだと思います。アイデアはマクロの視点から見られます。枝や葉が取り除かれた大きな木の幹のように、一目で明確なコンテキストが表示されます。もちろん、特定の実装方法や機能が含まれますが、この時点で遭遇する関数またはメソッドはカーネル実装にあります。上位レベルが主な(必須の)関数です。これらの関数、それらが目指す設計アイデア、実装される関数、および目標について学びました。私がここでよく知っていることも事実です。main関数によって呼び出される他の補助関数は、branch、branchs、leavesに相当するため、早すぎる必要はありません。この時点で、カーネルサブシステムフレームワークとコード実装の関係が最初に確立されます。関連付けは実際には非常に単純です。たとえば、関数の名前を見ると、その関数がどのサブシステム用で、どの関数であるかがわかります。実装します。

今回読みたいのはLKD3だと思います。この本は、主に各サブシステムを説明するための概念、設計、大規模な実装方法からの一般的な議論であり、特定の関連関数実装のコード説明が含まれることはめったにありません( ULK3と比較して、この本は主に特定の機能コードの特定の実装の詳細な分析について書かれています。もちろん、それを読むこともできますが、この本を早く読むと非常に苦痛で退屈に感じます。基本的には関数の実装)、現在のニーズを満たすだけでなく、実際のコードに早く入り込むのを防ぐために、非常に少数ですが、なくてはなりません。これは非常に優れています。そして、この本はまた、いくつかの重要な点についてプログラムを書くときに注意が必要な事項を示しており、それはガイドの提案と見なすことができます。主なサブシステムには、メモリ管理、プロセス管理とスケジューリング、システムコール、割り込みと例外、カーネル同期、時間とタイマーの管理、仮想ファイルシステム、ブロックI / O層、デバイスとモジュールが含まれます。(ここでの順序は、実際にはLKD3カタログの順序です)。

勉強していた時、3冊の本を横向きに見ました。まずサブシステム専用のLKD3を見ました。主な目的はデザインの原理と考え方を理解することでした。もちろん、いくつかのメインの紹介にも出くわしました。機能ですが、それらのほとんどは、上記で紹介したアイデアと原則によってどのような機能が達成されるかに基づいていますか?この本は、機能自体の実現について詳細な分析を行っていません。次に、ULK3とPLKAの同じサブシステムを調べますが、基になる特定の関数のコードを注意深く分析するのではなく、理解せずに大まかに見てください。ある本のある時点で行き詰まり、よく理解できないことがあるからです。別の本では、同じ問題の説明がさまざまな角度から出てきて、どれがどれかわからないことがあります。 。それは、神の悟りのように、あなたを突然悟らせることができます。私はしばしばこの状況に遭遇します。

一部の関数本体の実装が学習プロセス中に完全に無視されるわけではありません。コードの実装を完全に理解したい限り、誰もあなたを止めることはありません。繰り返し読む過程でゆっくりと深くなっていきました。たとえば、VFSでファイルを開くにはパスを分析する必要があり、考慮すべき詳細(.././など)はたくさんありますが、コードの実装はよく理解されています。別の例として、CFSスケジューリングでは、プロセスに割り当てられたタイムスライスは、シェドゥルレイテンシ、キュー内のプロセス数、およびその適切な値(動的優先度を使用)に従って計算されます。これを監視しない理由はありません。あまりにも重要であり、また非常に重要です。興味深いです。

ULK3には、基本的にトピックの冒頭の段落にある設計原理とアイデアの一般的な紹介もあります。しかし、それはこの原則とアイデアをサポートする主な機能の実現の具体的な分析に関するものです。また、最初の段落では、機能の機能を1つの文に要約してから、1、2、3で機能を実装します。またはa、b、cの手順。の形式で説明します。私はそれを選択的に見て、時にはソースインサイトで開かれたソースコードと比較して、コードが基本的に本に記載されている手順に従って実装されていることを確認しました。これは知覚意識を高めることです。これらの手順は、さまざまな実現目的のためにさまざまな安全性と有効性のチェックと混合されているため、理解できない場合はスキップしてください。これは、機能本体の実現を全体的に把握することを妨げるものではありません。

PLKAはLKD3とULK3の間にあります。PLKAの作者(写真を見ると、本当にハンサムなドイツ人で、そのような熟練したスキルを持っている)は、本来の意図や意図に関係なく、ULKを見たに違いないと思います。要するに、PLKAはULKとは異なり、詳細な説明は機能は補足されます。機能本体全体の機能の理解を妨げることなく、いくつかの特殊なケースの処理、有効性チェックなど、機能本体の隅と隅を削除します。彼はこれらすべてを説明し、宣言を行いました。また、LKD3と同様に、特定の時点で有益なプログラミングの提案も行われます。著者は、同じ主な機能のさまざまな説明にさえ焦点を合わせています。このように、勉強する私たちにとって、それは私たちの理解を深めるのに役立ちます。また、非常に重要な点は、PLKAが対象とする2.6.24カーネルバージョン、ULKは2.6.11、LKD3は2.6.34だと思います。いくつかの点で、PLKAは現代の実現に近づいています。実際、作成者がそれぞれ11または24を選択する理由は、バージョンリリースツリーで、これら2つのバージョンがいくつかの面で大幅な変更を加えたか、画期的なターニングポイントであるためです(これらの情報のほとんどは導入部にあります)本の中で、具体的な詳細は思い出せません)。

Intel V3、X86 CPUの場合、この本は当然システムプログラミングの権威です。カーネルの実装の一部は、この本にあります。したがって、上記の3冊の本のサブシステムを読むときは、V3の対応する章にいくつかの基本的なサポート情報があることを忘れないでください。

読む過程で、かなり多くの質問がありますが、それは間違いありません。デザインのアイデアを理解するには大きすぎますが、コード行の目的を理解するには小さすぎます。あらゆる面で、あらゆる種類の質問、あなたはあなたが理解していないことを完全に記録することができます(しかし、私はこれをしませんでした、私はすべての質問を書き留めませんでした、私は私が重要だと思ういくつかの問題だけをマークしました)、それを一枚の紙に書きました、いや、本、私は非常に多くの質問があると確信しています、さもなければカーネル関連のフォーラムはずっと前に閉じられるかもしれません。実際、ほとんどの問題(その多くはそのようなことがあるかどうかについての質問です)は簡単に解決できます。100回振り返って本を読むことをいとわない限り、意味があります。あなたへ。何度か読んでみると、表裏の接続に問題がないことがわかります。私も同じことをし、特定のサブシステムについて何度かそれを見て、それを直接体験しました。

これらのサブシステムを順番に学習すると、前の章で後の章が引用される可能性があります。PLKAの作成者が述べたように、後方参照をまったく持たないことは不可能です。彼にできることは、これを最小限に抑えることだけです。理解を損なうことなく引用してください。現在の問題の。理解できません、それは問題ではありません、ただそれをスキップしてください。次の章にも前の章への参照がありますが、この質問はより簡単です。戻って対応する紹介を読むことができます。その時点で理解していなかったことが、現時点でその設計の目的を知っている可能性があります。 。そして特定のアプリケーション。理解の欠如は一時的なものです。たとえば、カーネルのさまざまなサブシステム間の相互作用と参照は、メモリ管理の章で学習したメモリ割り当てや解放関数などの関数インターリーブ呼び出しを実装するためのコードに反映され、最初にメモリを理解します。ドライバーやモジュールを学ぶと、これらの関数呼び出しに遭遇するので、受け入れるのが簡単で、途方に暮れることもありません。別の例として、システム時間とタイマーの管理を理解し、割り込みと例外の下部を振り返ります。 。ハーフスケジューリングの実現により、理解が深まります。

サブシステム管理には、多くのデータ構造が必要です。サブシステム間の相互作用の1つの方法は、各サブシステムの主要なデータ構造がポインターメンバーを介して相互に参照することです。学習プロセスでは、リファレンスブックは特定のサブシステムを説明するときにデータ構造の主要メンバーの目的を説明しますが、すべてを網羅しているわけではなく(task_structなどのメンバーが多い場合)、他のサブシステムが基づいています関数実装への参照は説明される場合と説明されない場合があり、この変数がさらに説明される場所であると言えます。ですから、わからない点は気にせず、とりあえず手放してください。後で見ることができます。各サブシステムを理解した後、接続を確立できます。実際、私はまだ概念とフレームワークを最初に理解することの重要性を強調しています。

フレームワークを確立するステップを完了した後、ドライバー、ネットワーク、ファイルシステムなどのより興味深いサブシステムを選択できます。この時点で、基礎となるコードの実装をより深く理解することができます。最初にコードを掘り下げるよりも簡単です。理解できない場合や、特定の側面の実装を忘れた場合は、対応するサブを見つけることができます。システムでは、ギャップを見つけて埋める場所がわかっているため、現在の機能の調査を完了しただけでなく、以前のコンテンツを確認および確認することもできます。これが統合の時期です。

「Linux仮想メモリの詳細な理解」(2.4カーネルバージョン)、LDD3、「Linuxネットワークテクノロジーインサイダーの詳細な理解」、ほとんどすべてのサブシステムは説明するために本の容量を必要とするため、学習を開始することは適切ではありません特定のモジュール詳細すぎる場合は、各サブシステムを十分に理解するまで待ってから、特定のサブシステムを対象を絞って学習してください。現時点では、他のシステムを呼び出すことで、私たちはもはや途方に暮れ、複雑で、理解できないと感じることはありません。

たとえば、LDD3の次の章:モジュールの構築と実行、同時実行と競合状態、時間、遅延と遅延操作、メモリの割り当て、割り込み処理などはすべてドライバー開発のサブシステムをサポートしていますが、本ではこれらのサブシステムについて説明しています。は説明専用の章ですが、詳細レベルはPLKA、ULK3、LKD3の3冊に匹敵します。これらの3冊を読んだ後、LDD3のこれらの章を読むことは、沸騰したお湯を飲むのとほとんど同じであることがわかります。カジュアルすぎるはい、LDD3の説明はLKD3の説明よりも大雑把なので。優れた基盤を築いた後、PCI、USB、TTYドライバー、ブロックデバイスドライバー、ネットワークカードドライバー、理解して学習する必要のあるものがよりターゲットになります。これらのサブシステムは汎用サブシステムです。理解した後、これらのサブシステムに基づくサブシステムの開発—ドライバー(ハードウェア特性をさらにターゲットにする必要があります)およびネットワーク(さまざまなプロトコルをさらに理解する必要があります)—比較的言えば、学習の難しさは大幅に軽減されます。学習の進行が大幅に加速され、学習効率が大幅に向上します。言うのは簡単です。このような効果を達成するための前提条件は、落ち着いて、注意深く読み、透けて見える必要があることです。PLKAとULK3はレンガと同じくらい厚いので、気が遠くなります。興味がなければ、熱意も、忍耐力も、何があっても、時間がかかるので時間がかかります。ドライバー開発のための良い基盤を築く必要があるという意味ではありません。基盤を築くと、開発がより簡単かつ効率的になり、カーネルコードを制御する能力が強化されると言っています。これは私の個人的な意見であり、参考のために私自身の学習方法です。

API感想

「使用するテクノロジーの重要性を知ることと比較して、特定の分野の専門家であることが重要ではありません。特定のAPI呼び出しを知ることは、まったくメリットがありません。必要なときに確認するだけです。」この文の起源私が見た翻訳されたブログから。私が強調したいのは、この文はアプリケーションプログラミングにより適しているということですが、カーネルAPIは正確には当てはまりません。

カーネルは非常に複雑で、習得するのは簡単ではありませんが、ある程度習得すると、カーネルコードを作成する場合でも、最後にAPIインターフェイスに注意を払うことになりますが、ほとんどの場合これらのAPIのうち、クロスプラットフォームであり、移植性を満たしています。カーネルハッカーは基本的にこれらのインターフェースを標準化して文書化しており、あなたがしなければならないのはそれらを呼び出すことだけです。もちろん、それを使用するときは、移植性のトピックに関するカーネルのコーディング規則に精通していることが最善です。そうすれば、移植性のあるコードを書くことができます。アプリケーションと同様に、開発者が提供するダイナミックライブラリAPIを使用することも、オープンソースAPIを使用することもできます。同じことがAPIの呼び出しですが、違いは、アプリケーションAPIを使用するよりもカーネルAPIを使用することについて知っておくべきことがたくさんあるということです。

オペレーティングシステムの実装を理解すると(これらの実装はすべてアプリケーションの基本的なサポートです)、アプリケーションを作成するとき、アプリケーションで使用されるマルチスレッド、タイマー、同期ロックメカニズムなど共有ライブラリを使用するときは待機しますAPIについては、オペレーティングシステムに連絡して、APIのドキュメントの説明と、カーネルで知っているこれらの側面の対応するサポート実装を組み合わせてください。これにより、使用するものを選択できます。最も多くを選択するためのAPIインターフェイス効率的な実装方法。システムプログラミングをよく理解していれば、アプリケーションプログラミングにとっても有益であり、非常に有益であるとも言えます。

デザインと実現の本質、知っている、または理解している

オペレーティングシステムは、基盤となるハードウェアとアプリケーションソフトウェア間のインターフェイスであり、そのさまざまなサブシステムの実現は、ハードウェアの特性に大きく依存します。この本がこれらのサブシステムの設計と実装を紹介したとき、私たちはそれを読み、それを知っています。さらに考えてみると、アーキテクチャ全体をこのように編成する必要がある理由と、ローカル関数をこのように処理する必要がある理由もちろん、その理由を知っておいてください。チップがこのように設計されているために特定の機能が実装されていることがわかっていて、CPUがこれを実行している場合、質問は基本的にここで終わります。さらなる研究は、チップアーキテクチャの設計と実装です。プログラマーにとって、システムプログラマーであろうとアプリケーションプログラマーであろうと、私たちの仕事の性質は柔らかく、これらは本当に十分に難しいので、フットプリントを調査することによって多くの質問が解決されました。

たとえば、ULK3で説明されている割り込みと例外の実装、根本的な原因は、Intel x86シリーズがこのように設計されていることです。IntelV3マニュアルの対応する章に移動し、ULK3で説明されているコード実装のコメントを見つけることができます。 。時間とタイマーの管理もあります。IntelV3のAPICの概要でも十分な情報を入手できます。オペレーティングシステムは、ソフトウェアメソッド定義を実装するためにこれらのハードウェア特性に基づいています。

それはまたその文です。それは理解するかどうかの問題ではなく、知っているか知らないかという問題です。時々、あなたがそれを知っていれば、あなたは理解するでしょう。学習プロセス全体を通して、知ること、理解すること、知ること、理解すること、知ること...が繰り返されます。なぜ最初と最後に知っているのに、理解は中間のステップにすぎないのですか?世界にはすべて独自の法則があります。人間は発見するだけです。実践が最初です。実践は知るプロセスです。実践は経験を生み出します。経験の要約は理論です。理論は実践から生まれます。理論は理解する必要があります。カーネルを研究し、徹底的に研究し、行き来してチップに戻りました。チップは物質であり、チップの機能は自然界の物質の物理的および電子的特性に基づいています。ソースにさかのぼると、これはそれが意味することでもあります。

コードを書く

紙の上ではいつも浅く、個人的にやらなければならないことは絶対に知っています。本を読むことは絶対に不可能です。教科書に記載されているプログラミングの提案に従って、自分でコードを入力する必要があります。最初に、モジュールの形でテストするか、カーネルの開発バージョンを自分でコンパイルします。マシンの場合、UMLを使用してデバッグし、カーネルがどのステップに進むかを制御します。プログラムの実行プロセスを確認するためのシングルステップデバッグは、本の説明よりも直感的で明確です。必ず手を汚してください。

何もないより本がない方がいい。      

終わり

興味のある力は無限大です。興味は情熱をもたらすことができます。仕事と興味を組み合わせることができれば、仕事には熱意があります。仕事は単なる仕事ではなく、一種の楽しみです。

Linux、私の興味、私のモチベーション、私の方向性、私の未来!

 

おすすめ

転載: blog.csdn.net/Linuxhus/article/details/114294447