Linuxカーネルマクロcontainer_ofの詳細な分析

Linuxカーネルマクロcontainer_ofの詳細な分析

1. 前述の通り

数年前、Linux ドライバーのコードを読んでいたときにこのマクロを見ました。長い間 Baidu で検索して使い方は知っていましたが、実装プロセスと原理についてはまだ漠然とした理解しかありませんでした。

container_of マクロは Linux カーネル コードで非常に多く使用されています。Linux プログラミングが好きな学生にとって、その実装方法を理解することは、将来カーネル コードを読んだりカーネル ドライバーを書いたりするときに非常に役立ちます。もちろん、これを理解したら何でもできると言っているわけではありません。カーネルは奥が深く、広範囲です。まずマクロから学び、次にミクロを学ぶ必要があります。一口で太っちょになれるとは思わないでください。この記事では主にこの関数の実装原理を分析しており、皆さんの学習プロセスに役立つことを願っています。

Android7.1/カーネル/ドライバー/入力
カーネル/ドライバー/入力$ grep -rn container_of ./|wc -l
710
android7.1/カーネル/ドライバー/入力$

grep -rn container_of ./|wc -l を使用して、kernel/drivers/input/ ディレクトリに container_of が出現する回数をカウントします。合計で 710 回使用されています。

2. container_ofの役割

container_of の機能は、構造体のメンバ変数のアドレスを介して構造体のアドレスを取得することです。あなたの名前が Li Guangming で、XXX という弟がいるとします。警察の叔父は、あなたの弟 XXX が悪いことをしたと知りましたが、警察の叔父はあなたの弟の名前を知らなかったため、あなたを逮捕して尋問しましたが、あなたは非常に頑固で、彼に話すことを拒否しました。警察の叔父はあなたの名前を入手し、あなたの家族の戸籍簿を調べたところ、あなたの弟が見つかりました。あなたの弟 XXX の名前は Li Xiaoming であることが判明しました。この事件解決方法は手がかりを追う方法と呼ばれます。

カーネル関数の呼び出しでは、構造体のメンバーのアドレスが渡されることが多く、その後関数は構造体内の他のメンバー変数を使用する必要があるため、この問題が発生します。これは、C でオブジェクト指向プログラミングを実装する方法でもあると思います。

例えばこのコード

静的voidセンサーサスペンド(構造体early_suspend *h)          
{                          
  構造体sensor_private_data *センサー =               
      container_of(h、構造体sensor_private_data、早期サスペンド);  
  if (センサー->オペレーション->サスペンド)                     
    センサー->ops->suspend(センサー->クライアント);             
}

early_suspend は、sensor_private_data のメンバーです。sensor_private_data 構造体変数のアドレスは、このメンバーのアドレスを通じて取得され、それによって内部のメンバー変数 client が呼び出されます。この方法は非常にエレガントです。ここでは、よりエレガントな言葉「エレガント」を使いました。

ここで簡単に説明すると、渡された h はどこか別の場所で定義されていて、オペレーティング システムがそれにメモリ領域を割り当てている必要があります。h に領域を割り当てるということは、その親にもメモリがあることを意味します。そうでなければ、手がかりをたどって NULL を見つけるのは愚かなことです。

3. container_ofの使い方

Container_of は 3 つのパラメータを渡す必要があります。最初のパラメータはポインタ、2 番目のパラメータは構造体の型、3 番目のパラメータは 2 番目のパラメータに対応する構造体のメンバーです。

container_of(ptr, type, member)

  • ptr: 構造体内のメンバーのアドレスを表します
  • type: 構造体の型を示す struct sensorsen_private_data
  • メンバー: 構造体のメンバー early_suspend 型にこのメンバーが含まれている必要があることを示します。変更することはできません。
  • 構造体の最初のアドレスを返します

4. コンテナで使用される知識ポイントの分析

4.1. ({}) の役割

({}) まず、この式について説明します。多くの人が理解し、さまざまな場所で見たことがあるかもしれませんが、注目していないかもしれません。この式は、最後の式の値を返します。たとえば、x=({a;b;c;d;}) の場合、x の最終値は d になります。

コード例:

#include <stdio.h>
void main(void)
{
  整数a = ({1;2;4;}) + 10;
  printf("%d\n",a);//a=14
}

4.2. typeofは変数の型を取得します

これはめったに見られません。このキーワードはC言語のキーワードの拡張であり、変数の型を返します。詳細については、GCCの紹介を参照してください。
https://gcc.gnu.org/onlinedocs/gcc/Typeof.html

++式の型を参照する別の方法はtypeofを使用することです。このキーワードを使用する構文はsizeofに似ていますが、構文はtypedefで定義された型名のように意味的に動作します。++

コード例:

void main(void)
{
  整数a = 6;
  typeof(a) b =9;
  printf("%d %d\n",a,b);
}

4.3. (struct st*)0の役割

誰もが定規を使ったことがあると思います。例えば、定規を使って本の長さを測りたい場合、まず定規の0目盛りの位置を見つけ、この0目盛りの位置を使って本の端を合わせ、揃える必要があります。本の反対側にある定規の目盛りを確認すると、本の長さがわかります。

次に、構造物の長さを測定する必要があります。定規を使用して測定することもできます。0 目盛りの位置を見つけるだけです。同様に、0 スケールの位置がわからなくても、最初のスケールと最後のスケールを減算することで構造の長さを計算できます。

しかし、C の定規とは何でしょうか? sizeof を思い浮かべるかもしれませんが、残念ながらこれは私たちのニーズを満たしていないので、定規として本当に完璧な (struct st *) があります。

構造体st{
  整数a;
  整数 b;
}*p_st、n_st;
void main(void)
{
  printf("%p\n",&((struct st*)0)->b);
}

上記のコード

(構造体st*)0

これは、この構造を 0 スケールに置いて測定を開始することを意味します。では、どこまで測定するのでしょうか?

&((構造体st*)0)->b)

これは、位置 b まで測定するときに反映されます。したがって、上記の出力は 4 になります。

上記の説明を読んだ後、次の 2 つのコードは同じ機能を持っていることがわかります。

typeof ((struct st*)0)->b) c; // bの型を取ってcを宣言する
整数c;

実際、これは 0 だけではなく、他の数値にも当てはまります。たとえば、次のコードでは、コンパイラは数値ではなく型を重視します。

printf("%p\n",&((struct st*)4)->b -4);

この記事は数日前に書いたものですが、この核心を証明する特に良い方法が見つからなかったため、直接投稿したくありませんでした。上記を読んだ後、この種の測定についてある程度理解できたはずです。配列の最初のアドレスを 0 に設定する必要がある場合、どうすればよいでしょうか。

数分の遅延があると仮定して、少し考えてみましょう。

コードは次のとおりです。

構造体A{
  短い配列[100];
};
int main(int argc, char *argv[])
{
  整数 i = 10;
  A*a = (A*)0;
  printf("%p %d %d\n",a,sizeof(short), &a->array[20]);
  getchar();
  1 を返します。
}
// 出力 00000000 2 40

==struct A *== を使用せずに、配列のアドレスを位置 0 に直接配置する方法はありますか?まだこれより良い解決策は見つかっていません。何か良い提案があれば、メッセージを残してください。

4.4、オフセット(タイプ、メンバー)

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*​​)0)->MEMBER)

size_t が分からない場合は、Baidu で検索してみてください。符号なし整数です。32 ビットと 64 ビットでは長さが異なるため、構造体のオフセット長を取得するには offsetof を使用します。

4.5. const int* p の関数

上記のマクロ定義には小さな知識ポイントもあります

const typeof( ((type *)0)->member ) *__mptr

上記のコードは次のように短縮できます。

定数 int * __mptr

これは何を示しているのでしょうか?これは、__mptr が指す整数データが​​ const (定数) であることを示します。

これにはさらに2つの知識が関係する

int * const __mptr; // は __mptr の値が変更できないことを意味します // また const int * const __mptr; // は __mptr が変更できず、それが指すコンテンツも変更できないことを意味します

5. container_ofの分析

上記の知識ポイントを読むと、container_of マクロが非常に明確になります。解析部分を以下のコードコメントに記します。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*​​)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \
    (type *)( (char *)__mptr - offsetof(type,member) );})
//----- 区切り線 struct st{
  整数a;
  整数 b;
}*pt;
//例としてこれを使用します container_of(&pt->a,struct st,a)
 const typeof( ((struct st *)0)->a ) *__mptr = (const typeof( ((struct st *)0)->a ) *)(&pt->a);
const int *__mptr = (int *)(&pt->a);//最初の文を解析した後、実際に a のアドレスを取得します。
(type *)( (char *)__mptr - offsetof(type,member) );
//これは (struct st *)( (char *)__mptr - ((unsigned int) &((struct st*)0)->a)); になります。
//この文は、a のアドレスから構造体への a のオフセット アドレス長を引いたものが構造体のアドレス位置であることを意味します。

6. サンプルコード

上記の説明を読んだ後、少なくともこのマクロの感覚はつかめるはずです。コードを書いてテストし、コードに自分を統合してください。この方法でのみ、人間とコードの一体化の状態を実現できます。

コードは次のとおりです。

#include <stdio.h>
#include<stddef.h>
#include<stdlib.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*​​)0)->MEMBER)
/*ptr メンバ ポインタ* 型構造体 (struct Stu など)
* メンバー メンバー変数、ポインタに対応* */
#define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \
    (type *)( (char *)__mptr - offsetof(type,member) );})
typedef 構造体 Stu{
    年齢;
    文字名[10];
    整数ID;
    符号なしロング電話番号;
}*p_stu、str_stu;
void print_all(void *p_str)
{
  p_stu m1p_stu = NULL;
  m1p_stu = container_of(p_str、構造体Stu、年齢);
  printf("年齢:%d\n",m1p_stu->年齢);
  printf("名前:%s\n",m1p_stu->名前);
  printf("id:%d\n",m1p_stu->id);
  printf("phone_num:%d\n",m1p_stu->phone_num);
}
void main(void)
{
  p_stu m_stu = (p_stu)malloc(sizeof(str_stu));
  m_stu->年齢 = 25;
  m_stu->id = 1;
  m_stu->name[0] = 'w';
  m_stu->名前[1]='e';
  m_stu->名前[2]='i';
  m_stu->名前[3]='q';
  m_stu->名前[4]='i';
  m_stu->名前[5]='f';
  m_stu->名前[6]='a';
  m_stu->name[7]='\0';
  m_stu->電話番号=13267;
  /* 構造体メンバーのポインタを渡す */
  print_all(&m_stu->年齢);
  printf("メイン終了\n");
  if(m_stu!=NULL)
    m_stuを解放します。
}

7. プログラム出力

年齢:25
名前:ウェイキファ
id:1
電話番号:13267
メインエンド

要約する

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

以下もご興味があるかもしれません:
  • LinuxカーネルマクロContainer_Ofの詳細な説明
  • C言語コンテナof()関数のケース詳細説明
  • Linux カーネルプログラミングにおけるコンテナの of() 関数の紹介
  • Linuxカーネルのcontainer_of関数の詳細な説明
  • C言語マクロ関数コンテナof()の紹介

<<:  Vue要素のバックグラウンド認証プロセスの分析

>>:  Windows 10 での mysql5.5 データベース コマンドラインの中国語文字化け問題を解決する

推薦する

count(1)、count(*)、count(列名)の実行の違いの詳細な説明

実施効果: 1. count(1) と count(*)テーブル内のデータ量が多い場合、テーブルを分...

JavaScript でプライベート変数を宣言する 2 つの方法

序文JavaScript は、キーワードを使用してプライベート変数を宣言できる他の言語とは異なります...

Vueのスロットの詳細な説明

Vue でのコードの再利用により、mixnis が提供されます。テンプレートの再利用により、スロット...

MySQL で GTID モードをオンラインで有効または無効にする

目次基本的な概要GTIDをオンラインで有効にする1. GTID検証ENFORCE_GTID_CONS...

HTML で相対パスを使用してディレクトリのすべてのレベルのファイルを取得する方法の詳細な説明

相対パスの概念現在のファイルの場所を参照ポイントとして使用して、ターゲット ファイルへのパスを確立し...

ES5とES6の違いを分析する

目次概要関数シグネチャオプションパラメータ非厳密モード例外処理実用要約する概要ご存知のとおり、ES6...

MySQL実践スキル: 2つのテーブルに異なるデータがあるかどうかを比較する方法の分析

この記事では、MySQL が 2 つのテーブルを比較して、異なるデータがあるかどうかを確認する方法を...

jQuery はシャッター効果を実現します (li 配置を使用)

この記事では、ブラインド効果を実現するためのjQueryの具体的なコードを参考までに紹介します。具体...

MySQL 8.0 に移行する際の注意点 (要約)

パスワードモードPDO::__construct(): サーバーがクライアントに不明な認証方法を要求...

Linuxアカウントファイル制御管理の詳細な手順

Linux システムでは、ユーザーが手動で作成したさまざまなアカウントに加えて、システムまたはプログ...

Reactでコンポーネントロジックを共有する3つの方法

簡単に説明すると、これら 3 つの方法は、レンダリング プロップ、高階コンポーネント、カスタム フッ...

Linuxにpipパッケージをインストールする方法

1. システムの Python バージョンに応じて、pip インストール パッケージをダウンロードし...

Vue3コンポーネントの開発詳細

目次1. はじめに2. コンポーネント開発1. コンポーネントの構成2. ヘッダーコンポーネントの開...

Apache での ab パフォーマンス テスト結果を分析する

私はパフォーマンス テストを行うために常に Loadrunner を使用してきました。 Loadru...

Winows Server 2019 アクティベーション コードとボリューム ライセンス エディション KMS インストール キー GVLK

最近、社内文書の整理とファイルサーバーの構成を予定しています。以前はサーバー2003を使い慣れていま...