LinuxドライバーGNU拡張C

1構造の割り当て

構造体などのメンバーに値を割り当てます。

struct  st1 {
    int a;
    int b;
}

 通常、次のように、値を{}の形式で直接割り当てることができます。

struct st1 st1 = {1,2,3); 

しかし、Linuxの割り当てスタイルは次のとおりです。

struct st1 st1 = {
    .a = 1,
    .b = 2,
}

注:このスタイルの利点(つまり、メンバー変数の前にドット「。」を追加すること)は、メンバー変数の順序ではない値を割り当てることができることです。など:

struct st1 st1 = {  
    .b = 2,  
    .a = 1, 
}; 

インライン関数インライン

cファイルでは、小さな関数を頻繁に消費し、スタックスペースまたはスタックメモリを大量に消費するという問題を解決するために、インライン修飾子が導入され、インライン関数として表現されています。インライン関数はinlineキーワードを使用して定義され、関数本体と宣言を組み合わせる必要があります。そうしないと、コンパイラはそれを通常の関数として扱います。インライン関数は通常、ヘッダーファイルに配置されます。

inline void function(int x); //仅仅是声明函数,没有任何效果 
inline void function(int x) //正确 
{
    return x;
}

3 Typeof 使用、offsetof 、container_of分析

3.1キーワードtypeofは、式のデータ型を取得するために使用されます

@ 1文字* chptr;

typeof (*chptr) ch;    //等价于char ch  
typeof  (ch) *chptr1;  //等价于char *chptr1  
typeof (chptr1) array[5]; //char  *array[5],chptr1的数据类型为char * 

@ 2 typeofはLinuxカーネルで一般的に使用されています

#define  min(x,y)  ({                   \  
     typeof(x)  __min1 = (x);        \  
     typeof(y) __min2 = (y);             \  
     (void)  (& __min1 == & __min2);     \  
    __min1 < __min2  ? __min1 :min2})

typeofを介してxとyのデータを取得し、2つの一時変数を定義して、xとyをそれぞれ2つの一時変数に割り当てて、最終的な比較を行います。
さらに、マクロ定義における(void)(&__min1 =&__ min2)ステートメントの役割は、xとyが異なるデータ型に属してはならないことを警告することです。

@ 3つの例は次のように使用されます。

#include <stdio.h>  
#define  min(x,y)  ({         \  
     typeof(x)  __min1 = (x); \  
     typeof(y) __min2 = (y);  \  
     (void)  (& __min1 == & __min2); \  
     __min1 < __min2  ? __min1 :min2})  
int main()  
{  
    int a=4;  
    int b=6;  
     int min_data;  
     min_data=min(a,b);  
     printf(“the min=%d\n”,min_data);  
     return 0;  
}

実行結果:

the min=4

に変更した場合:

#include <stdio.h>  
#define  min(x,y)  ({         \  
     typeof(x)  __min1 = (x); \  
     typeof(y) __min2 = (y);  \  
     (void)  (& __min1 == & __min2); \  
     __min1 < __min2  ? __min1 :min2})  
int main()  
{  
     int a=4;  
     float b=6;  
     int min_data;  
     min_data=min(a,b);  
     printf(“the min=%d\n”,min_data);  
     return 0;  
}

次の警告が表示されます。

main.c: In function ‘main’:
main.c:17:9: warning: comparison of distinct pointer types lacks acast [enabled by default]

3.2分析のオフセット

offsetofの意味:次のように定義された、構造体の最初のアドレスを基準にした、構造体のメンバー変数のメモリ位置のオフセットを取得します。 

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

注:アドレス0の内容にはアクセスできませんが、アドレス0のアドレスには引き続きアクセスできます。ここでは、アドレス演算子(TYPE *)0を使用しています。これは、アドレス0が強制的にタイプTYPEに変換されることを意味します。 ((TYPE *)0)-> MEMBERは、タイプ0のメンバーMEMBERをアドレス0から検索します。

ここで、offsetofの原理は、次のように定義されているfile_node構造と組み合わせて解釈されます。

struct file_node{
  char c;
  struct list_head node;
};

実際のパラメーターを以下に代入します。

offset( struct file_node, node );

最終的には次のようになります。

((size_t)&((struct file_node*)0)->node);

これは、pのメンバーノードのアドレスを見つけることですが、pは0であり、メンバーノードのアドレスは、構造体struct file_nodeのメンバーノードのオフセットである0アドレスから計算されます。つまり、オフセットマクロはTYPEのMEMBERのオフセットを計算します。

3.3 container_of分析

container_ofの意味:構造体のメンバー変数のメモリアドレスに従って、構造体の最初のメモリアドレスを取得します。定義は次のとおりです。 

#define container_of(ptr, type, member) ({             \
         const typeof( ((type *)0)->member ) *__mptr = (ptr);     \
         (type *)( (char *)__mptr - offsetof(type,member) );})

つまり、構造体変数内のドメインメンバー変数のポインターに従って、構造体変数全体へのポインターが取得されます。たとえば、次のように定義されている構造変数があります。

struct demo_struct {
    type1 member1;
    type2 member2;
    type3 member3;
    type4 member4;
};
struct demo_struct demo;

同時に、別の場所で、変数のデモにある次のようなドメインメンバー変数へのポインターを取得しました。

type3 *memp = //从某处获得的member3成员的指针

この時点で、構造体変数全体へのポインタを取得する必要がある場合は、次のように実行できます。

struct demo_struct *demop = container_of(memp, struct demo_struct, member3);

次に、このプロセスを分析します。まず、次のようにマクロの定義に従ってcontainer_of(memp、struct demo_struct、type3)を展開します。

struct demo_struct *demop = ({ \
const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp); \
(struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})

@ 1行2解析

ここでtypeofはGNU Cの標準Cへの拡張であり、その役割は変数に従って変数の型を取得することです。したがって、上記のコードの2行目の関数は

  1. typeofを使用して、構造ドメイン変数member3のタイプをtype3として取得します。
  2. type3ポインター型の一時変数__mptrが定義され、実際の構造変数のドメイン変数のポインターmempの値が一時変数__mptrに割り当てられます。

2つのステップの後:

const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp);
//转换成
type3 *__mptr = (memp);

@ 2 Line 3分析

実際のメモリ内の構造変数のデモの場所は次のとおりであると仮定します。

   struct demo
 +-------------+ 0xA000
 |   member1          |
 +-------------+ 0xA004
 |   member2          |
 |                    |
 +-------------+ 0xA010
 |   member3          |
 |                    |
 +-------------+ 0xA018
 |   member4          |
 +-------------+------+

次に、上記のコードの2行目を実行した後、__ mptrの値は0xA010です;上記のコードの3行目を見てください。

(struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})

offsetofは、アドレス0を基準にした構造体のドメインメンバーのオフセットアドレス、つまり、構造体変数の最初のアドレスを基準にしたドメインメンバー変数のオフセットを取得するためです。したがって、offsetof(struct demo_struct、member3)呼び出しによって返される値は、デモ変数に対するmember3のオフセットです。上記の変数アドレス分布図によると、offsetof(struct demo_struct、member3)は0x10を返します。

@ 3現時点での包括的な分析:

__mptr==0xA010
offsetof(struct demo_struct, member3)==0x10

したがって、(char *)__ mptr-((size_t)&((struct demo_struct *)0)-> member3)== 0xA010-0x10 == 0xA000、これは構造体変数demoの最初のアドレスです。したがって、container_ofは、構造体変数のドメインメンバー変数のポインターに従って、構造体変数全体へのポインターを取得する関数を実装します。


GNU C式の複合文

4.1複合ステートメントの説明

標準Cでは、式は演算子とオペランドの組み合わせを指し、複合ステートメントは中括弧で囲まれた1つ以上のステートメントで構成されるコードブロックを指します。式では複合ステートメントを使用できません。ただし、GNU Cでは、括弧で囲まれた複合ステートメントを式に含めることができます。この式のタイプは、複合ステートメントでセミコロンで終わる最後のサブステートメント式のタイプであり、その値は最後のサブ式の値でもあります。使用例は次のとおりです。

#include <stdio.h>  
main()  
{  
    int  a = ({
               int b =4;  
               int c =3;  
               b+c;  
               b+c-2;  
              });  
    printf("a = %d\n",a);  
    return 0;  
}

出力は次のとおりです。

a = 5

注:aの値は、複合ステートメントの最後のステートメントの値であり、そのデータ型は最後のステートメントのデータ型と一致します。

4.2 Linuxカーネルでの複合ステートメントの適用

@ 1は、minの実装など、マクロの定義でよく使用されます。

#define  min(x,y)  ({         \  
     typeof(x)  __min1 = (x); \  
     typeof(y) __min2 = (y);  \   
     __min1 < __min2  ? __min1 :min2;})

最小化のための安全なマクロがここで定義されています。

@ 2標準Cでは、通常、次のように定義されます。

#define min(x,y) ((x) < (y) ? (x) : (y))

@ 3 @ 1および@ 2と比較して、@ 1の書き込みは、min_t(x ++、++ y)での@ 2の副作用を回避できます。つまり、この定義はxとyをそれぞれ2回計算します。パラメーターに副作用があると、誤った結果が生成されます。GNUCでは、ステートメント式を使用してパラメーターを1回だけ計算し、エラーを回避しています。したがって、ステートメント式は、カーネルのマクロ定義で一般的に使用されます。


5 GNU Cのラベル要素

標準Cでは、配列または構造体変数の初期値は固定された順序で出現する必要があります。GNUCでは、インデックスまたは構造体ドメイン名を指定することにより、初期化値を任意の順序で出現させることができます。配列のインデックスを指定する方法は、値を初期化する前に "[INDEX] ="を書き込むことです。範囲を指定するには、 "[FIRST ... LAST] ="の形式を使用します。

5.1配列アプリケーション1、指定された要素を初期化

配列の初期化リストで「[インデックス] =値」の形式を使用して、指定された要素(インデックスで指定)を初期化します。使用例は次のとおりです。

#include <stdio.h>  
int main(void)  
{  
    int i;  
    int arr[6] = {[3] =10,11,[0]=5,6};  
 
    for (i=0;i<6;i++)  
           printf("a[%d]=%d\n",i,arr[i]);  
 
    return 0;  
}

実行結果は次のとおりです。

a[0]=5
a[1]=6
a[2]=0
a[3]=10
a[4]=11
a[5]=0

[3] = 10,11のように、指定された初期化項目の後に複数の値がある場合。次に、これらの冗長な値を使用して後続の配列要素を初期化します。つまり、値11を使用してarr [4]を初期化します。C言語の配列の場合、1つ以上の要素を初期化した後、初期化されていない要素は自動的に0に初期化されます。後者はNULL(ポインター変数の場合)です。同時に、初期化のない配列のすべての要素の値は未定義になります。

5.2配列アプリケーション2、範囲内のいくつかの要素は同じ値に初期化されます

GNU Cは "[first ... last] = value"の形式もサポートします。つまり、範囲内のいくつかの要素は同じ値に初期化されます。使用例は次のとおりです。

#include <stdio.h>  
int main()  
{  
    int i;  
    int arr[]={ [0 ... 3] =1,[4 ... 5]=2,[6 ... 9] =3};  
    for(i=0; i<sizeof(arr)/sizeof(arr[0]);i++ )
        printf("arr[%d]:%d\n",i,arr[i]);  
    return 0;  
}

実行結果は次のとおりです。

arr[0]:1
arr[1]:1
arr[2]:1
arr[3]:1
arr[4]:2
arr[5]:2
arr[6]:3
arr[7]:3
arr[8]:3
arr[9]:3

6 GNU Cの匿名の和集合または構造

GNU Cでは、名前を指定せずに構造体で共用体(または構造体)を宣言できるため、構造体メンバーを直接使用するように、共用体(または構造体)のメンバーを直接使用できます。共用体(または構造)のメンバーを直接使用します。使用例は次のとおりです。

#include <stdio.h>
struct test_struct {
    char * name;
    union {
        char gender;
        int id;
    }; 
    int num; 
};  
int main(void)  
{  
    struct test_struct test_struct={"jibo",'F',28};  
    printf("test_struct.gender=%c,test_struct.id=%d\n",test_struct.gender,test_sturct.id);
    return 0;  
}

実行結果は次のとおりです。

test_struct.gender=F,test_struct.id=70

注:Linuxカーネルでは、匿名の共用体(または構造)が一般的に使用されています。


7 GNU C分岐ステートメント

条件付き選択ステートメントの場合、gccには最適化のための組み込みの命令があります。この状態が頻繁に発生するか、まれに発生する場合、コンパイラーはこの命令に従って条件付きブランチの選択を最適化できます。カーネルはこの命令をマクロにカプセル化します。つまり、可能性が高い()かつ一意的に()です。次に例を示します。

if (foo){  
    /**/
}

この選択をめったに起こらないブランチとしてマークしたい場合:

if (unlikely(foo)){  
    /**/  
}

逆に、通常は正しい選択としてブランチをマークする場合:

if(likely(foo)) {  
    /**/  
}

注:ありそうな()と一意的に()の詳細な分析については、Linuxカーネルソースコードの同様の()と一意的に()を参照してください。


8長さゼロの配列

8.1存在する理由

標準Cでは、長さがゼロの配列は禁止されています。これには、配列の最小長が1バイトであることが必要です。しかし、GNU拡張Cでは、長さ0の配列を定義できます。それでは、長さ0の配列をサポートする理由、その利点、使用方法、長さ0の配列の主な用途は何ですか。

  • 目的:不定の長さの構造にアクセスするときのスペースと利便性を節約するため。
  • 使用法:構造体の最後に、長さ0の配列を宣言して、構造体を可変長にします。

コンパイラーの場合、長さ0の配列は現時点ではスペースを占有しません。これは、配列名自体がスペースを占有しないためです。これは単なるオフセットであり、配列名自体のシンボルは変更不可能なアドレス定数を表します(注:配列名前は決してポインタにはなりません!)、しかし、この配列のサイズについては、動的に割り当てることができます。使用例は次のとおりです。これで、構造体デモ構造がプログラムに割り当てられ、その直後にLENバイトの長さが割り当てられます。次のメソッドを使用して取得できます。

struct demo  
{  
    int a;  
    char b[256];  
    char follow[0];  
}; 

このように、demo-> followを使用して、構造デモの後続の空間データにアクセスできます。もちろん、ポインタを使用してこの目的を達成することもできます。以下に示すように:

struct demo{  
    int a;  
    char b[256];  
    char *follow;     
};  
struct demo *demo=(struct demo *)malloc(sizeof(struct demo)+LEN);

長さゼロの配列の効果も実現できますが、追加のcharポインターが割り当てられます。追加のデータスペースが割り当てられると、スペースの無駄になります。

charバイト[0]のような長さ0の配列が構造体の最後に定義されている場合、それは構造体が不定の長さであり、配列を使用して拡張できることを意味します。構造には長さ情報が含まれている必要があります。構造自体は情報ヘッダーに似ています。同時に、この構造はヒープを介してのみメモリを割り当てることができます。その利点は、このメソッドが、構造体でポインター変数を宣言してから動的に割り当てるよりも効率的であることです。

8.2直接アクセス

配列の内容にアクセスするとき、間接的なアクセスは必要ないため、2つのアクセスを回避します。使用例は次のとおりです。

#include <stdio.h>  
#include <stdlib.h>  
  
struct  test{  
    int count;  
    //reverse is array name;the array is no item;  
    //the array address follow test struct  
    int reverse[0];  
};  
   
int main()  
{  
    int i;  
    struct test *ptest = (struct test *)malloc(sizeof(struct test)+sizeof(int)*10);  
    for(i=0;i<10;i++){  
            ptest->reverse[i]=i+1;  
    }  
    for(i=0;i<10;i++){  
            printf("reverse[%d]=%d \n",i,ptest->reverse[i]);  
    }  
    printf("sizeof(struct test) =%d\n",sizeof(struct test));  
    int a = *(&ptest->count +1 );  
    printf("a=%d\n",a);  
    return 0;  
}

実行結果は次のとおりです。

reverse[0]=1
reverse[1]=2
reverse[2]=3
reverse[3]=4
reverse[4]=5
reverse[5]=6
reverse[6]=7
reverse[7]=8
reverse[8]=9
reverse[9]=10
sizeof(struct test) =4
a=1

テスト構造の逆配列はスペースをとらないことがわかります。sizeof(構造体テスト)はメモリ空間を占有します4。そして、count変数の後に長さがゼロの配列の内容が続いていることがわかります。


9レンジマーカー

GCCは、範囲マーカーを拡張します。これは、値の範囲を表すために使用できます。これは、Cプログラムの多くの場所で使用できます。最も一般的な用途は、switch / caseステートメントです。使用例は次のとおりです。

static int sd_major(int major_idx)  
{  
    switch (major_idx) {  
    case 0:  
        return SCSI_DISK0_MAJOR;  
    case 1 ... 7:  
        return SCSI_DISK1_MAJOR + major_idx - 1;  
    case 8 ... 15:  
        return SCSI_DISK8_MAJOR + major_idx - 8;  
    default:  
        BUG();  
        return 0;   /* shut up gcc */  
    }  
}

範囲タグは、配列内のいくつかの連続する要素を初期化するためにも使用できます。上記のセクション5で説明した配列。


10関数、変数、データ型に属性を割り当てる

属性は、情報やコマンドをコンパイラーに転送するためにプログラマーが使用するツールであり、通常、プログラムをコンパイルするときに特別な処理を完了するようコンパイラーに指示するために使用されます。属性は、関数、変数、タイプなど、さまざまな種類のオブジェクトに割り当てることができます。属性を指定するときは、キーワード "__attribute__"の後に2つの括弧で囲まれた属性のリストを指定する必要があります。属性のリスト内の属性はコンマで区切られます。使用方法は以下の通りです。

__attrbitue__((attr_1,attr_2,attr_3)) 

10.1ノーリターン

関数属性noreturnは関数に使用され、関数が決して戻らないことを示します。これにより、コンパイラーはわずかに最適化されたコードを生成できます。最も重要なことは、初期化されていない変数などの不要な警告メッセージを排除することです。

10.2形式(ARCHETYPE、STRING-INDEX、FIRST-TO-CHECK)

関数属性formatは関数に使用されます。つまり、関数はprintf、scanf、またはstrftimeスタイルのパラメーターを使用します。
このタイプの関数を使用するときに犯す最も簡単な間違いは、フォーマット文字列がパラメーターと一致しないことです。フォーマット属性を指定すると、コンパイラーはフォーマット文字列に従ってパラメーターをチェックできますタイプ。
パラメーターの説明:

        "archetype"指定是哪种风格;
        "string-index"指定传入函数的第几个参数是格式化字符串
        "first-to-check"指定从函数的第几个参数开始按上述规则进行检查

ファイルinclude / linux / kernel.hでは、例は次のように使用されます。 

asmlinkage int printk(const char * fmt, ...)  __attribute__ ((format (printf, 1, 2)));

10.3未使用

関数属性と未使用属性は、関数と変数に使用され、関数または変数が使用されない可能性があることを示します。この属性は、コンパイラが警告メッセージを生成しないようにすることができます。

10.4非推奨

関数属性(非推奨)は、その関数が非推奨であり、再度使用してはならないことを示します。廃止された関数を使用しようとすると、警告が表示されます。この属性をタイプと変数に適用して、開発者がそれらをできるだけ使用しないようにすることもできます。

10.5セクション( "セクション名")

__attribute__の関数属性であるセクション属性は、関数またはデータを、出力セクションではなく「section_name」という名前の入力セクションに配置します。
注:Linuxドライバーの設計では、モジュールロード関数の前に__initマクロがあり、属性セクション属性も使用されます。以下に示すように:

#define __init __attribute__ ((__section__(".init.text"))) 

Linuxカーネルでは、__ initとしてマークされたすべての関数は、リンク時に.init.textセクションに配置されます。さらに、すべての__init関数は、セクション.initcall.initに関数ポインターを保存します。初期化中に、カーネルはこれらの関数ポインターを通じてこれらの__init関数を呼び出し、初期化後にinitセクションを解放します。Linuxカーネルソースコードでは、セグメントに関連する重要なマクロ定義は、__ init、__ initdata、__ exit、__ exitdataおよび同様のマクロです。

10.6アライメント済み(ALIGNMENT)

関数:変数を定義するときに、__ attribut__を追加して、メモリアラインメントを使用するか、数バイトのメモリアラインメントを使用するかを決定します。使用例は次のとおりです。

struct i387_fxsave_struct {  
    unsigned short cwd;  
    unsigned short swd;  
    unsigned short twd;  
    unsigned short fop;  
} __attribute__ ((aligned (16)));

この構造タイプを表す変数は、16バイトに揃えられます。

10.7パック

function属性は、コンパイル時に構造の最適化された配置をキャンセルし、実際に占有されているバイト数に従って配置するようコンパイラーに指示します。

10.8割り込み( "")

ARMプラットフォームでは、「__ attribute((interrupt( "IRQ")))」は、変更された関数が割り込みハンドラであることを意味します。


11変数パラメーターマクロ

GNU Cでは、マクロは関数と同様に、可変数のパラメーターを受け入れることができます。使用例は次のとおりです。

include/linux/kernel.h  
#define pr_debug(fmt,arg...) \  
     printk(KERN_DEBUG fmt,10rg)

注:argは残りのパラメーターを示します。これはゼロ以上にすることができます。これらのパラメーターとパラメーター間のコンマはargの値を構成します。マクロが展開されたら、argを置き換えます。たとえば、

pr_debug("%s:%d",filename,line)

に拡大

printk("<7>" "%s:%d", filename, line)

「##」を使用する理由は、argがどのパラメーターとも一致しない場合に対処するためです。現時点では、argの値は空です。この特殊なケースでは、GNU Cプリプロセッサーは「##」の前のコンマを破棄します。

pr_debug("success!\n")  

に拡大

printk("<7>" "success!\n")

注:マクロ定義の最後にコンマはありません。


12組み込み関数

GNU Cは多数の組み込み関数を提供します。それらの多くは、対応するCライブラリ関数と同じ関数を持つmemcpyなどの標準Cライブラリ関数の組み込みバージョンです。名前が通常__builtinで始まる他の組み込み関数があります。

12.1 __builtin_return_address(LEVEL)

組み込み関数__builtin_return_addressは、現在の関数またはその呼び出し元の戻りアドレスを返します。パラメーターLEVELは、スタック上の検索フレームの数を指定します。0は現在の関数の戻りアドレスを示し、1は現在の関数の呼び出し元の戻りアドレスを示します。たとえば、ファイルkernel / sched.cに次の定義があります。

printk(KERN_ERR "schedule_timeout: wrong timeout" "value %lx from %p\n", timeout,__builtin_return_address(0));

12.2 __builtin_constant_p(EXP)

組み込み関数__builtin_constant_pは、値がコンパイル時の定数かどうかを判別するために使用されます。パラメーターEXPの値が定数の場合、この関数は1を返し、それ以外の場合は0を返します。たとえば、ファイルinclude / asm-i386 / bitops.hには次の定義があります。

    (__builtin_constant_p(nr) ?     \  
      constant_test_bit((nr),(addr)) :   \  
       variable_test_bit((nr),(addr)))

注:パラメータが定数の場合、多くの計算または演算の実装がより最適化されます。GNUCでは、上記のメソッドを使用して、パラメータが定数かどうかに応じて、定数バージョンまたは非定数バージョンをコンパイルできます。パラメータが一定の場合、最適化されたコードをコンパイルします。

12.3 __builtin_expect(EXP、C)

組み込み関数__builtin_expectは、コンパイラーに分岐予測情報を提供するために使用されます。その戻り値は整数式EXPの値であり、Cの値はコンパイル時の定数でなければなりません。この定義は、ファイルinclude / linux / compiler.hにあります。

#define       likely(x)         __builtin_expect((x),1)  
#define       unlikely(x)      __builtin_expect((x),0)

ファイルkernel / sched.cで次のように使用されます。

if(unlikely(in_interrupt())) {  
    printk("Scheduling in interrupt\n");  
    BUG();  
}

この組み込み関数のセマンティクスは、EXPの期待値がCであることです。コンパイラーは、この情報に基づいてステートメントブロックの順序を適切に再配置できるため、プログラムは期待される条件下での実行効率が高くなります。上記の例は、割り込みコンテキストになることはまれであることを示しています。
 

 

 

 

元の記事289件を公開 賞賛された47件 30,000回以上の閲覧

おすすめ

転載: blog.csdn.net/vviccc/article/details/105175497