【埋め込み式】C言語におけるvolatileキーワード


00.目次
文書ディレクトリ
  • 00. ディレクトリ
  • 01. volatile概要
  • 02. volatile適用シーン
  • 03. volatile適用例
  • 04. 組み込みシステムにおけるアプリケーション
  • 05. volatile公式説明
  • volatile
  • 06. 付録
  • 01.volatileの概要
    volatileはC言語のキーワードです.変数をvolatileとして定義すると、コンパイラにこの変数が予想外に変更される可能性があることを示す.この場合、コンパイラはこの変数の値を仮定せず、オプティマイザはこの変数を使用するたびに彼の値を再読み込みしなければならない.
    02.volatileアプリケーションシーン
    プログラムではvolatile変数は、次のような場合によく使用されます.
  • パラレルデバイスのハードウェアレジスタ(例えば、ステータスレジスタ)
  • サービス中断サブルーチンでアクセスする非自動変数(すなわちグローバル変数)
  • マルチスレッドアプリケーションでいくつかのタスクによって共有される変数
  • 一般変数の場合、その格納位置はメモリにありますが、プロセッサのレジスタに格納される可能性もあります.プログラムでは,レジスタの内容が変更されていない限り,変数へのアクセスはメモリにアクセスする必要はなく,レジスタの変数を直接使用する必要がある.
    たとえば、volatile変数は、プログラムで次のように定義できます.
    void test()
    {
        volatile char temp;
    }
    

    変数tempがvolatileタイプとして定義されると、コンパイラによって最適化されず、temp変数にアクセスするたびにメモリに値が読み出されます.
    実際、コンパイラの最適化では、tempのような関数スタックに構築された変数は外部で変更される可能性は低い.プログラムでは,一般に変更されやすい変数はポインタが指す内容である.
    03.volatile適用例
    C言語コンパイラには一般的に最適化機能があり,コードを最適化する.例:
    int tmp, a1, a2;
    tmp = (unsigned int *)0x4004;
    a1 = *tmp;
    a2 = *tmp;
    

    一部のコンパイラでは、このコードがコンパイラによって最適化される可能性が高く、最適化の結果は次のコードに等しい.
    int tmp, a1, a2;
    tmp = (unsigned int *)0x4004;
    a1 = *tmp;
    a2 = a1;
    

    この最適化は一般的には誤りはないが,特殊な場合には誤りを引き起こす可能性がある.例えば、1回目の読み取り操作(a 1=*tmp)後、*tmpの内容が更新された可能性があり、この場合、2回目の読み取り操作で読み出された内容は1回目とは異なる.本来のプログラムの意味も,2つの異なる時刻に2つの異なる値を読み出すことであるが,最適化されたプログラムは同じ値しか読み出すことができない.これはvolatileキーワードを使用する必要があります.上記のプログラムの英嘎携程は以下の形式である.
    volatile unsigned int *tmp;
    int a1, a2;
    tmp = (volatile unsigned int *)0x4004;
    a1 = *tmp;
    a2 = *tmp;
    

    まとめ
    volatileは、組み込みシステムにおいて、外部によって変更されたり、内部によって並列に変更されたりする可能性のあるデータに一般的に使用される.
    04.組み込みシステムにおける応用
    プログラムにおけるGPIO関連レジスタの定義
    #define PINSEL0 (*((volatile unsigned long *) 0xE002C000))
    #define PINSEL1 (*((volatile unsigned long *) 0xE002C004))
    #define PINSEL2 (*((volatile unsigned long *) 0xE002C008))
    #define PINSEL3 (*((volatile unsigned long *) 0xE002C00C))
    

    レジスタの定義はvolatileで修飾され、コンパイル中にコンパイラによって最適化され、予想外の結果を生じないようにしなければならない.
    05.volatile公式説明
    原文は以下の通り
    volatile
    Indicates that a variable can be changed by a background routine.
    Keyword volatile is an extreme opposite of const . It indicates that a variable may be changed in a way which is absolutely unpredictable by analysing the normal program flow (for example, a variable which may be changed by an interrupt handler). This keyword uses the following syntax:
    volatile data-definition;
    

    Every reference to the variable will reload the contents from memory rather than take advantage of situations where a copy can be in a register.
    変数がバックグラウンドプログラムで変更できることを示します
    キーワードvolatileとconstはまったく逆です.変数が何らかの方法で変化する可能性があることを示していますが、この方法は正常なプログラムフローを分析することで完全に予測できません.(たとえば、割り込みハンドラによって変数が変更される場合があります).キーワードの使用構文は次のとおりです.
    volatile data-definition;
    

    変数コンテンツへの参照のたびに、変数からレジスタへのコピーではなくメモリから再ロードされます.
    割り込みハンドラで変数解釈を修正するのは適切ではないかもしれませんが、GPIOを例にとると最適です.まず変数とは何ですか?変数はアドレスが組み込まれたメモリ領域です.GPIOのデータレジスタにはアドレスがあり、サイズは一般的に32 bitなので、このデータレジスタは変数と考えられ、読み書きができます.GPIOが入力に設定されている場合、GPIOデータレジスタという変数を修正するのはこのGPIOのピンで、あなたのプログラムをどのように分析しても、このGPIOデータレジスタの中の値がどれだけあるかを知ることはできません.それを読まなければなりません.あなたが今読んでいるデータと次の瞬間に読んでいるデータはまったく違うかもしれません.簡単に言えば、あなたが望んでいるデータは同期していません.volatile修飾を使用すると、GPIOレジスタに対応する変数を参照するたびにレジスタの中に読むように強制されます.
    コンパイラが操作変数を最適化しないようにする文
    a.cファイル
    int main(void)
    {
    	volatile char a;
    
    	a = 5;
    	a = 7;
    	
    	return 0;
    }
    

    b.cファイル
    int main(void)
    {
    	char a;
    
    	a = 5;
    	a = 7;
    	
    	return 0;
    }
    

    コンパイル生成アセンブリファイル
    deng@itcast:~/tmp$ arm-linux-gcc -S a.c  -o a.s 
    deng@itcast:~/tmp$ arm-linux-gcc -S b.c -o b.s
    deng@itcast:~/tmp$ diff a.s b.s
    12c12
    <       .file   "a.c"
    ---
    >       .file   "b.c"
    deng@itcast:~/tmp$ 
    

    2つのアセンブリファイルの差は大きくないことがわかりました.次に最適化レベルを調整します.
    コンパイル生成アセンブリファイル
    deng@itcast:~/tmp$ arm-linux-gcc -O3 -S b.c -o b.s
    deng@itcast:~/tmp$ arm-linux-gcc -O3 -S a.c  -o a.s 
    deng@itcast:~/tmp$ diff a.s b.s
    12c12
    <       .file   "a.c"
    ---
    >       .file   "b.c"
    18c18
    <       @ args = 0, pretend = 0, frame = 8
    ---
    >       @ args = 0, pretend = 0, frame = 0
    21,24d20
    <       sub     sp, sp, #8
    <       mov     r3, #5
    <       strb    r3, [sp, #4]
    <       mov     r3, #7
    26,27d21
    <       strb    r3, [sp, #4]
    <       add     sp, sp, #8
    deng@itcast:~/tmp$ 
    

    volatile修飾されていないファイルb.cが見られ、最適化後、アセンブリに対応するa=5;a=7;の2つの文が直接最適化されなくなった.a=1;a=0; aはGPIOを制御する文だと仮定し、GPIOを先に高くしてから低くして、あるタイミングを実現するつもりだったが、結果を最適化して開くと、この2つの文は直接廃棄された.これにより、ハードウェアをデバッグするときに不思議に感じます.だからこのような状況はa.cのようにvolatileで修飾しなければならない.
    コンパイラ最適化変数のアクセスオブジェクト(memory or register)の防止
    a.cファイル
    int main(void)
    {
    	int b;
    	int c;
    	volatile int* a = (int*)0x30000000;
    	
    	b = *a;
    	c = *a;
    	
    	return c + b;
    }
    

    b.cファイル
    int main(void)
    {
    	int b;
    	int c;
    	int* a = (int*)0x30000000;
    	
    	b = *a;
    	c = *a;
    	
    	return c + b;
    }
    

    対応するa.sファイルを生成
    mov     r3, #805306368
    ldr     r2, [r3]		@ b = *a;
    ldr     r0, [r3]		@ c = *a;
    add     r0, r0, r2		@ b + c;
    bx      lr
    

    対応するb.sファイルを生成
    mov     r3, #805306368
    ldr     r0, [r3]		@ b = *a;
    mov     r0, r0, asl #1  	@ b << 2;     b * 2;    b + b;    add r0, r0, r0(         )
    bx      lr
    

    b.sが最適化された後、1回目に*aの値をとると、アドレス0 x 3000000から取り出された(ldr r0, [r3])、2回目には直接取られず、r 0を直接使用した値であることがわかる.ここでr 0は*aのキャッシュである.
    volatileで修飾された変数にアクセスすると、キャッシュ内の値ではなくメモリ内の値に強制的にアクセスします.
    volatileキーワードはコンパイラコンパイルの結果に影響し、volatileで宣言された変数でこの変数がいつでも変化する可能性があることを示し、この変数に関する演算はコンパイル最適化を行わず、エラーを避ける
    int main(void)
    {
    	int b;
    	volatile int* a = (int*)0x30000000;
    	
    	b = (*a) * (*a);
    	
    	return b;
    }
    

    対応するアセンブリの生成
    mov     r3, #805306368
    ldr     r2, [r3]	①
    ldr     r0, [r3]	②
    mul     r0, r2, r0
    bx      lr
    

    プログラムの本意は平方を計算することです.このコードが①行アセンブルまで実行されたときにスケジューリングされた場合、しばらくしてから②行を実行し続けると、R 2が完全に可能になります!=R0.計算された結果R 0は必然的にその二乗値に等しくない.
    06.付録
    6.1 volatile公式説明