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 データベース コマンドラインの中国語文字化け問題を解決する

推薦する

プロセスごとにネットワーク帯域幅を監視する Linux ツール Nethogs のインストールと展開

概要Linux 用のオープン ソース ネットワーク監視ツールは数多くあります。たとえば、帯域幅の使用...

Nginx の場所に関する一般的なルールの優先順位の問題

目次1. 場所/マッチング2. 場所 = / 一致Locaitonには、完全一致(=)、プレフィック...

JavaScript 正規表現の説明

目次1. 正規表現の作成2. 使用モード2.1 シンプルモードの使用2.2 特殊文字の使用3. 応用...

MySQLクエリが遅い理由

目次1. 遅いところはどこですか? 2. 不要なデータをクエリしましたか? 1. 不要なレコードをク...

見落とされがちなMETAタグの特殊効果(ページ遷移効果)

Web デザインで js を使用すると、多くのページ効果を実現できますが、HTML タグの META...

MySql インデックスを表示および最適化する方法

MySQL はハッシュ インデックスと Btree インデックスをサポートしています。 InnoDB...

Intellij IDEA による Docker イメージの展開方法の手順の迅速な実装

目次1. Dockerはリモートアクセスを可能にする2. Intellij IDEAにDockerプ...

Win10 に Tomcat サーバーをインストールし、環境変数を構成する詳細なチュートリアル (画像とテキスト)

目次JDKをダウンロードしてインストールするTomcat 圧縮パッケージをダウンロードTomcatの...

jQueryはシンプルなポップアップウィンドウ効果を実装します

この記事では、簡単なポップアップウィンドウ効果を実現するためのjQueryの具体的なコードを参考まで...

HTML の大なり、小なり、スペース、引用符などでよく使用されるエスケープ コードのリスト。

表は以下のとおりです。 HTMLソースコード結果を表示説明する&lt; <未満記号また...

nodejs + koa + typescript の統合と自動再起動に関する問題

目次バージョンノートプロジェクトを作成する依存関係をインストールするコンテンツの記入src/serv...

HTMLインライン要素とブロックレベル要素の基本概念と使用例

HTML タグには、インライン要素とブロックレベル要素の 2 種類があります。まず、インライン要素と...

Linux の sudo 脆弱性により不正な特権アクセスが発生する可能性がある

Linux で新たに発見された sudo の脆弱性を悪用すると、特定のユーザーが root としてコ...

nginx ssl を設定して https アクセスを実装する手順 (初心者向け)

序文サーバーを展開した後、私は大きな喜びを感じながら自分の Web サイトにアクセスし、見たものすべ...

LinuxにMySQLをインストールするための詳細なチュートリアル

すべてのプラットフォーム用の MySQL ダウンロードは、MySQL ダウンロードから入手できます。...