Pertanyaan Kata kunci 'Volatil' dalam program ISR dan multithread?


Saya membaca tentang penggunaan C volatile kata kunci dalam register perangkat keras yang dipetakan memori, ISR, dan program multithread.

1) daftar

uint8_t volatile * pReg;
while (*pReg == 0) { // do sth } // pReg point to status register 

2) ISR

int volatile flag = 0;
int main() 
{
    while(!flag) { // do sth }
}
interrupt void rx_isr(void)
{
    //change flag
}

3) multithread

int volatile var = 0;
int task1()
{
    while (var == 0) { // do sth }
}
int task2()
{
    var++;
}

Saya dapat melihat mengapa kompiler dapat keliru mengoptimalkan while dalam kasus 1) jika volatile tidak ada, karena perubahan variabel dibuat perangkat keras, kompiler mungkin tidak melihat perubahan variabel yang dibuat kode.

Tetapi untuk kasus 2) dan 3), mengapa volatile pernah dibutuhkan? Dalam kedua kasus, variabel dideklarasikan global, dan kompilator dapat melihatnya digunakan di lebih dari satu tempat. Jadi mengapa kompiler akan mengoptimalkan while loop jika variabelnya tidak volatile?

Apakah karena kompilator oleh-desain tidak tahu "asynchronous call" (dalam kasus ISR), atau multithreading? Tapi ini tidak mungkin, kan?

Selain itu, kasus 3) tampak seperti program umum di multithreading tanpa volatile kata kunci. Katakanlah saya menambahkan beberapa penguncian ke variabel global (no volatile kata kunci):

int var = 0;
int task1()
{
    lock();   // some mutex
    while (var == 0) { do sth }
    release()
}
int task2()
{
    lock();
    var++;
    release();
}

Itu terlihat cukup normal bagiku. Jadi, saya benar-benar membutuhkannya volatile dalam multithreading? Kenapa saya belum pernah melihat volatile kualifikasi ditambahkan ke variabel untuk menghindari optimasi dalam program multithread sebelumnya?


7
2017-10-05 02:24


asal


Jawaban:


Titik utama penggunaan volatile kata kunci adalah untuk mencegah compiler dari menghasilkan kode yang menggunakan register CPU sebagai cara cepat untuk merepresentasikan variabel. Kekuatan ini mengkompilasi kode ke akses lokasi memori yang tepat dalam RAM pada setiap akses ke variabel untuk mendapatkan nilai terbaru yang mungkin telah diubah oleh entitas lain. Dengan menambahkan volatile kami memastikan bahwa kode kami menyadari setiap perubahan yang dilakukan pada variabel oleh orang lain seperti perangkat keras atau ISR dan tidak ada masalah koherensi yang terjadi.

Tidak ada volatile kata kunci, kompilator mencoba untuk menghasilkan kode lebih cepat dengan membaca isi variabel dari RAM ke dalam register CPU sekali dan gunakan nilai cache dalam satu lingkaran atau fungsi. Mengakses RAM bisa puluhan kali lebih lambat daripada mengakses daftar CPU.

Saya sudah memiliki pengalaman di item 1 dan 2 tapi saya tidak berpikir Anda perlu mendefinisikan variabel sebagai volatile dalam lingkungan multi threded. Menambahkan mekanisme kunci / buka kunci diperlukan untuk menyelesaikan masalah sinkronisasi dan tidak terkait dengan apa volatile adalah tentang.


8
2017-10-05 05:09



Compiler ini memang untuk memungkinkan apa-apa lain mengubah variabel Anda kecuali beberapa kondisi tertentu terpenuhi. Salah satunya adalah akses yang mudah menguap; yang lain adalah hambatan kompilator tertentu.

Cara naif untuk memprogram kode multithread yang mungkin Anda pikirkan memang rentan terhadap kesalahan dan akan dianggap perilaku tidak terdefinisi. Jika Anda memiliki kode multithread yang benar, maka optimasi itu masih legal (seperti di final Anda task1, di mana loop masih UB dan mungkin dibuang), atau primitif sinkronisasi akan mengandung hambatan yang diperlukan (biasanya dalam nyali beberapa variabel atom).

Untuk membulatkan keadaan, inilah versi terkoreksi dari contoh multithread:

 for (;;)
 {
     lock();
     if (var != 0) { unlock(); break; }
     unlock();
 }

Implementasi dari unlock() fungsi memperkenalkan penghalang kompilator yang memastikan bahwa loop tidak dapat dioptimalkan.


2
2017-10-05 02:34



Apakah karena kompilator oleh-desain tidak tahu "asynchronous call" (dalam kasus ISR), atau multithreading? Tapi ini tidak mungkin, kan?

Ya, memang seperti itu.

Dalam C compiler tidak memiliki gagasan konkurensi, sehingga diperbolehkan untuk mengatur ulang dan mengakses memori cache, selama tampilan dari satu thread tidak dapat melihat perbedaannya.

Itulah mengapa Anda perlu mudah menguap (memblokir jenis pengoptimalan ini untuk suatu variabel), hambatan memori (memblokirnya pada satu titik program untuk semua variabel) atau bentuk sinkronisasi lain seperti mengunci (yang biasanya bertindak sebagai penghalang memori).


1
2017-10-07 14:52



Anda dapat dengan bebas menghindari variabel volatil dalam perangkat lunak multi-berulir dengan menggunakan hambatan. Anda dapat menemukan banyak contoh di sumber kernel linux. Juga menggunakan penghalang daripada kompilator yang mudah menguap untuk menghasilkan kode yang jauh lebih efisien.


0
2017-10-05 12:43



Untuk kasus 2),

Saya telah menulis kode yang sama dengan kasus 2) dalam pertanyaan Anda berkali-kali, dan TIDAK memenuhi masalah apa pun. Saya rasa ini karena kompilator modern dapat menangani situasi ini. Katakanlah, compiler dapat "melihat" Saya mengubah "flag" di dalam "rx_isr", dan tidak menambahkan pengoptimalan apa pun. Namun, ini tidak aman karena alasan berikut:

1) tingkat optimasi kompilator Anda, yang dapat mempengaruhi alasan berikut 3)

2) memanggil metode isr Anda, mungkin fungsi pointer keluar dari tampilan kompilator

3) implementasi compiler, compiler yang berbeda mungkin memiliki definisi yang berbeda dari "lihat bendera berubah dalam isr"

...

Jadi, agar aman dan portabel semaksimal mungkin, cukup tambahkan "volatile".


0
2018-01-23 08:43