Linux カーネル デバイス ドライバーのメモリ管理に関する注意事項

Linux カーネル デバイス ドライバーのメモリ管理に関する注意事項
/************************
 * Linux メモリ管理 ****************************/

メモリ管理は、Unix カーネルの中で最も複雑なアクティビティです。メモリ管理について簡単に紹介し、例を使用してカーネル モードでメモリを取得する方法を説明します。

(1)各種住所

x86 プロセッサの場合、次の 3 種類のアドレスを区別する必要があります。

*論理アドレス

x86 のみがサポートされます。各論理アドレスは、セグメントとオフセットで構成されます。オフセットは、セグメントの先頭から実際のアドレスまでの距離を示します。

論理アドレスは 48 ビット、セグメント セレクタは 16 ビット、オフセットは 32 ビットです。 Linuxは論理アドレスのサポートが限定的である

*リニアアドレス

仮想アドレスとも呼ばれます。

32 ビットの符号なし整数、0x0000,0000 から 0xffff,ffff、合計アドレス範囲は 4GB です。アプリケーションでもドライバーでも、プログラム内で使用するアドレスは仮想アドレスです。

* 住所

CPU のアドレス ピンからメモリ バスに送信される電気信号に対応する 32 ビットの符号なし整数。メモリのアドレス指定に使用されます。

scanf.c などのプログラムを見つけて 2 回実行し、次の命令を実行して観察します。

$>pmap $(pid)
$>cat /proc/$(pid)/maps

(2)物理メモリと仮想メモリ

a. 物理メモリ

よく話題になる 256MB の RAM など、システムに実際に存在する RAM です。 x86 プロセッサと物理メモリは、実際の物理回線を介して接続されます。

さらに、x86 プロセッサはマザーボードを介して多くの周辺機器に接続されており、これらの周辺機器も実際の物理ラインを介してプロセッサに接続されています。

プロセッサの場合、ほとんどの周辺機器と RAM へのアクセス方法は同じです。つまり、プログラムは物理アドレスを発行して実際の物理デバイスにアクセスします。

周辺機器と RAM は 4G の物理メモリ空間を共有します。

b. 仮想メモリ

これは、アプリケーションのメモリ要求とハードウェア メモリ管理ユニット (MMU) の間にある、物理メモリの上に各プロセス用に構築される論理メモリです。MMU は、定義済みのページ テーブルに従って、アプリケーションが使用する仮想メモリを物理アドレスに変換し、物理アドレスを通じて実際の周辺機器または RAM にアクセスします。

仮想メモリには多くの用途と利点があります。

  • *複数のプロセスを同時に実行できます
  • * 必要なメモリが物理メモリよりも大きい場合でもアプリケーションは実行できます
  • * プロセスは、コードの一部のみがメモリにロードされたときにプログラムを実行できます。
  • * 各プロセスが利用可能な物理メモリのサブセットにアクセスできるようにする
  • *プロセスはライブラリ関数またはプログラムの単一のメモリイメージを共有できます
  • * プログラムは再配置可能であり、つまり、プログラムは物理メモリ内のどこにでも配置できる。
  • * プログラマーは物理メモリの構成を気にすることなく、マシンに依存しないコードを書くことができます。

(3)RAM使用量

Linux は実際の物理 RAM を 2 つの部分に分割します。数メガバイトはカーネル イメージ (つまり、カーネル コードとカーネルの静的データ構造) の保存に使用されます。残りの RAM は通常、仮想メモリ システムによって処理され、次の 3 つの方法で使用されます。

  • * キャッシュ、記述子、その他の動的カーネルデータ構造に対するカーネル要求を満たす
  • *一般メモリ領域とファイルメモリマッピングに対するプロセスの要求を満たす
  • * キャッシュの助けを借りて、ディスクやその他のバッファリングデバイスからより良いパフォーマンスを得る

仮想メモリが対処しなければならない主な問題の 1 つはメモリの断片化です。通常、カーネルは連続した物理メモリを使用するため、断片化が多すぎると要求が失敗する可能性があります。

/************************
 * カーネル内のメモリを取得する************************/

ユーザー空間と同様に、カーネル内でメモリを動的に割り当てたり解放したりできますが、ユーザー空間よりも多くの制限が適用されます。

(1)カーネル内のメモリ管理

カーネルは、メモリ管理の基本単位として物理ページを使用します。これは主に、メモリ管理ユニット (MMU) が仮想アドレスと物理アドレスをページ単位で変換するためです。仮想メモリの観点から見ると、ページは最小単位です。ほとんどの 32 ビット アーキテクチャは 4 KB ページをサポートしています。

a. ページ

カーネルは、システム内の各物理ページを表すために struct page を使用します。

<linux/mm.h> をインクルードすると、実際には <linux/mm_types.h> で定義されているページを使用できるようになります。

構造体ページ{
 page_flags_t フラグ;
 アトミック_t_count;
 アトミック_t_mapcount;
 符号なしロングプライベート;
 構造体アドレス空間 *マッピング;
 pgoff_t インデックス;
 構造体list_headlru;
 void *仮想;
};

Flags は、<linux/page-flags.h> で定義されているページのステータスを格納するために使用されます。ステータスには、ページがダーティかどうか、メモリ内でロックされているかどうかなどが含まれます。 _count にはページの参照カウントが格納されます。

ページ構造は仮想ページではなく物理ページに関連しています。この構造の目的は、物理メモリ自体を記述することであり、その中のデータを記述することではありません。

カーネルは、ページ構造に基づいてシステム内のすべてのページを管理します。カーネルは、ページを通じて、ページが空いているかどうか (つまり、ページが割り当てられているかどうか) を知ることができます。

ページが割り当てられている場合、カーネルは誰がそのページを所有しているかを知る必要もあります。

所有者は、ユーザー空間プロセス、動的に割り当てられたカーネル データ、静的カーネル コード、ページ キャッシュなどです。

このような構造は、システム内の各物理ページに割り当てられます。構造体のサイズが 40 バイトの場合、128 MB の物理メモリ (4K ページ) のうち 1 MB をページ構造体に割り当てる必要があります。

b. エリア

ハードウェアの制限により、カーネルはすべてのページを平等に扱うことができません。カーネルはゾーンを使用して、類似した特性を持つページをグループ化します。これらの機能には以下が含まれます:

  • *一部のハードウェアは特定のメモリアドレスを使用してのみDMAを実行できます
  • *一部のアーキテクチャでは、メモリ アドレス範囲が仮想アドレス範囲よりもはるかに大きいため、一部のメモリをカーネル空間に永続的にマップすることはできません。

これらの制限に対処するために、Linux は 3 つのゾーン (<linux/mmzone.h>) を使用します。

  • ZONE_DMA: このゾーンにはDMA操作を実行できるページが含まれています
  • ZONE_NORMAL: このゾーンには、通常どおりにマップできるページが含まれます。
  • ZONE_HIGHMEM: このゾーンにはハイエンドメモリ(896M以上)が含まれており、そのページはカーネルのアドレス空間に永続的にマップすることはできません。

x86 の場合、これら 3 つの領域に対応する物理メモリは次のとおりです。

  • ゾーン_DMA: <16MB
  • ゾーン_ノーマル: 16~896MB
  • ゾーン_HIGHMEM: >896MB

<linux/mmzone.h> の struct zone を参照してください。

システム内にはこのようなゾーン構造が 3 つだけあります。

(2)ページ割り当て

カーネルはメモリ管理にページを使用するため、カーネル内のページでシステムにメモリを割り当てるように要求することもできます。もちろん、ページ単位で割り当てるとメモリが無駄になる可能性があるため、ページ全体のメモリが必要であることが確実な場合にのみ呼び出します。

a. 割り当て

#include <linux/gfp.h>
1. 構造体ページ * alloc_pages(
    符号なし整数gfp_mask、 
    符号なし整数順序);
// 連続する 2 つの物理ページを割り当てます。
2. void *ページアドレス(
    構造体ページ *ページ);
//指定された物理ページの現在の仮想アドレスへのポインタを返します 3. unsigned long __get_free_pages(
    符号なし整数gfp_mask、 
    符号なし整数順序);
//上記2つの関数の組み合わせと同等 4. struct page * alloc_page(
    符号なし整数gfp_mask);
5. 符号なしlong __get_free_page(
    符号なし整数gfp_mask);
6. 符号なしロングget_zeroed_pa​​ge(
    符号なし整数gfp_mask);
//1ページのみ割り当てる

b.gfp_maskフラグ

このフラグは、メモリを割り当てるときにカーネルがどのように動作し、どこからメモリを割り当てるかを決定します。

#include <linux/gfp.h>
#GFP_ATOMIC を定義する
// アトミック割り当て、スリープなし、割り込み処理に使用できます。
#GFP_KERNEL を定義する 
// カーネルはスリープする可能性があり、プロセスコンテキストで使用される

c. リリースページ

void __free_pages(構造体page *page,
    符号なし整数順序);
void free_pages(符号なしlongアドレス,
    符号なし整数順序);
void free_page(符号なしロングアドレス);

知らせ!リリースできるのは、自分に属するページのみです。パラメータが正しくない場合、カーネルパニックが発生する可能性があります。

(3)kmallocによるメモリの取得

kmalloc は malloc と非常によく似ており、カーネルで最もよく使用されるメモリ割り当て関数です。

kmalloc は割り当てられたメモリ領域を 0 にクリアせず、割り当てられた領域は物理メモリ内で連続しています。

a. 割り当て

#include <linux/slab.h>
void *kmalloc(size_t サイズ、int フラグ)

サイズは割り当てる必要のあるメモリのサイズです

kmalloc の flags パラメータは、割り当て中の kmalloc の動作を制御できます。使用されるフラグは、alloc_page で使用されるフラグと一致しています。 kmalloc はハイエンドメモリを割り当てることができないことに注意してください。

b. リリース

#include <linux/slab.h>
 void kfree(const void *ptr);

解放するメモリがすでに解放されている場合、またはカーネルの他の部分に属するメモリを解放する場合、これは重大な結果を招く可能性があります。 kfree(NULL) を呼び出すのは安全です。

気をつけて!カーネルは、事前に定義された固定サイズのバイト配列のみを割り当てることができます。 kmalloc が処理できる最小のメモリ ブロックは 32 または 64 です。 kmalloc によって割り当てられるメモリは物理的に連続しているため、割り当て上限があり、通常は 128 KB を超えてはなりません。

(4)vmallocでメモリを取得する

vmalloc() によって割り当てられたメモリの仮想アドレスは連続していますが、物理アドレスは連続している必要はありません。これは malloc() の割り当て方法でもあります。 vmalloc は連続していないメモリ ブロックを割り当て、ページ テーブルを変更してメモリを論理空間内の連続した領域にマップします。

ほとんどの場合、連続した物理アドレスを持つメモリを取得する必要があるのはハードウェア デバイスのみであり、カーネルは vmalloc を通じて取得したメモリを使用できます。ただし、大きなメモリ ブロックをマッピングするときに vmalloc を使用しない限り、vmalloc によって大きな TLB ジッタが発生するため、主にパフォーマンスを考慮して、kmalloc がカーネルで使用されます。たとえば、モジュールが動的にロードされる場合、vmalloc によって割り当てられたメモリにロードされます。

vmalloc は <linux/vmalloc.h> で宣言され、<mm/vmalloc.c> で定義されています。使い方は malloc() と同じです。

 void* vmalloc(符号なしロングサイズ);
 void vfree(void *addr);

vmallocはスリープを引き起こす

(5)スラブ機構による記憶の獲得

データ構造の割り当てと解放は、カーネル内で最も一般的な操作の 1 つです。

一般的な方法は、使用可能な割り当てられたデータ構造ブロックを含む空きリストを作成することです。

データ構造を割り当てる必要があるたびに、メモリを再度申請する必要はありません。代わりに、この空きリンクリストからデータブロックを直接割り当て、構造を解放するときにメモリをこのリンクリストに戻すことができます。

これは実際には一種のオブジェクト キャッシュ (オブジェクトのキャッシュ) です。

Linux は、このタスクを実行するためにスラブ アロケータを提供します。

スラブ アロケータは、いくつかの基本原則間のバランスを追求します。

  • * 頻繁に使用されるデータ構造は頻繁に割り当てられ、解放されるため、キャッシュする必要があります。
  • * 頻繁な割り当てとリサイクルは、必然的にメモリの断片化につながります。この現象を回避するために、フリーリストのキャッシュを継続的に保存して、断片化を回避します。
  • * アロケータは、オブジェクトサイズ、ページサイズ、合計キャッシュサイズに基づいて最適化できます。

kmalloc はスラブの上に構築されます。

a. 新しいキャッシュを作成する

#include <linux/slab.h>
構造体 kmem_cache *kmem_cache_create(
   定数char *名前、 
   size_t サイズ、
   size_t アライン、
   符号なしロングフラグ、
   void(*ctor)(...));

name: キャッシュの名前。 /proc/slabinfo に表示される
サイズ: キャッシュ内の各要素のサイズ
align: キャッシュ内の最初のオブジェクトのオフセット。通常は 0
flags: 割り当てフラグ。一般的に使用される SLAB_HWCACHE_ALIGH はキャッシュ ラインのアライメントを示します (slab.h を参照)。

b. キャッシュを破棄する

#include <linux/slab.h>
kmem_cache_destroy 構造体 kmem_cache *cachep を無効にします。

このメソッドは、キャッシュ内のすべてのオブジェクトが解放された後に呼び出す必要があります。

c. キャッシュからオブジェクトを取得する

void *kmem_cache_alloc(
   構造体 kmem_cache *cachep、int フラグ);
フラグ:
   GFP_カーネル

d. オブジェクトをキャッシュに戻す

void kmem_cache_free(
   構造体 kmem_cache *cachep、void *objp);

kernel/fork.c を参照

(6)ハイエンドメモリのマッピング

ハイメモリ内のページはカーネルアドレス空間に永続的にマップすることはできないため、__GFP_HIGHMEM フラグを指定した alloc_pages() によって取得されたページは仮想アドレスを持つことができません。関数を通じて動的に割り当てる必要があります。

a. マッピング

特定のページ構造をカーネル アドレス空間にマップするには、次を使用します。

void *kmap(構造体page *page);

関数はスリープできる

b. マップ解除

void kunmap(構造体page* page);

要約する

以上がこの記事の全内容です。この記事の内容が皆様の勉強や仕事に何らかの参考学習価値をもたらすことを願います。123WORDPRESS.COM をご愛顧いただき、誠にありがとうございます。これについてもっと知りたい場合は、次のリンクをご覧ください。

以下もご興味があるかもしれません:
  • Linux 仮想メモリ設定のチュートリアルと実践
  • Linux システム診断: メモリの基礎を詳しく解説
  • Linux でメモリ使用量を確認する方法
  • Linux システムはなぜ「メモリ」を消費するのでしょうか?
  • Linux システム v の共有メモリ問題を解決する
  • CPU、マシンモデル、メモリなどの情報を表示するLinuxシステム
  • Linux で大容量メモリ ページを持つ Oracle データベースを最適化する方法
  • Linux仮想メモリについての簡単な説明

<<:  Vue で配列パラメータを渡すための get / delete メソッド

>>:  Windows 上の MySQL バージョン 5.7 でエンコードを UTF-8 に変更する方法

推薦する

IEのクラッシュバグ

コードをコピーコードは次のとおりです。 <スタイル タイプ="text/css&qu...

JS での Reduce Fold Unfold の使用法の詳細な説明

目次折りたたむ(減らす) for...of の使用whileループの使用折り畳み実装に近い展開する配...

jsはポップアップウィンドウをクリックすることでポップアップログインボックスを実装します

この記事では、ポップアップウィンドウをクリックしたときにポップアップログインボックスを実現するための...

高品質なコードを書く Web フロントエンド開発実践書の抜粋

(P4) Web 標準は一連の標準で構成されています。中心となる概念は、Web ページの構造、スタイ...

テーブルを使用する場合と CSS を使用する場合 (経験の共有)

TW のメインテキスト ページは、以前は小さなモニターと低解像度のユーザーを考慮して幅が 850 ピ...

Vue の状態管理: Vuex の代わりに Pinia を使用する

目次1. ピニアとは何ですか? 2. Piniaは使いやすい3. ユーザーエクスペリエンス1. ピニ...

Linux に nginx をインストールする方法

Nginx は C 言語で開発されており、Linux で実行することをお勧めします。もちろん、Win...

フィールドの文字セットの違いによる MySQL のインデックス失敗の解決策

インデックスとは何ですか?なぜインデックスを作成するのですか?インデックスは、列に特定の値を持つ行を...

ウェブ音楽プレーヤーを実現する js

この記事では、参考までに簡単なHTMLと音楽プレーヤーの制作コードを紹介します。具体的な内容は以下の...

CentOS VPS に SSH 経由で MySQL をインストールする方法

yum install mysql-serverと入力します。続行するにはYを押してくださいインスト...

フロントエンド HTML+CSS+JS を使用してシンプルな TODOLIST 関数を開発する (メモ帳)

目次1. 簡単な紹介2. スクリーンショットを実行する3. コードの紹介4. まとめ1. 簡単な紹介...

MySQL テーブルを作成するためによく使用される SQL ステートメントの概要

最近、私はプロジェクトに取り組んでおり、背景を記述するために SQL ステートメントを使用する必要が...

vue3とvue2の利点の比較

目次利点1: diffアルゴリズムの最適化利点2: ホイスト静的静的リフティング利点3: cache...

3列レイアウトを実現するCSS3フレキシブルボックスフレックス

タイトルの通り、高さは既知で、左と右の列の幅は 300 ピクセル、中央は適応型です。弾性ボックス自体...

ページ切り替え効果を作成するための純粋な CSS3 のサンプルコード

前に書いたものは複雑すぎるので、シンプルなコアにしましょう <html> <ヘッド...