Saya telah membaca artikel perbincangan tentang konsistensi jujukan dan konsistensi cache sebelum ini, dan saya mempunyai pemahaman yang lebih jelas tentang perbezaan dan hubungan antara kedua-dua konsep ini. Dalam kernel Linux, terdapat banyak mekanisme penyegerakan dan halangan, yang saya ingin ringkaskan di sini.
Sebelum ini saya selalu berfikir bahawa banyak mekanisme dalam Linux adalah untuk memastikan konsistensi cache, tetapi sebenarnya, kebanyakan konsistensi cache dicapai melalui mekanisme perkakasan. Hanya apabila menggunakan arahan dengan awalan kunci, ia mempunyai kaitan dengan caching (walaupun ini pastinya tidak ketat, tetapi dari sudut pandangan semasa, ini berlaku dalam kebanyakan kes). Selalunya, kami mahu memastikan konsistensi berurutan.
Koheren cache bermakna dalam sistem berbilang pemproses, setiap CPU mempunyai cache L1 sendiri. Memandangkan kandungan sekeping memori yang sama mungkin dicache dalam cache L1 CPU berbeza, apabila CPU menukar kandungan cachenya, ia mesti memastikan bahawa CPU lain juga boleh membaca kandungan terkini apabila membaca data ini. Tetapi jangan risau, kerja kompleks ini dilakukan sepenuhnya oleh perkakasan Dengan melaksanakan protokol MESI, perkakasan boleh menyelesaikan kerja koheren cache dengan mudah. Walaupun berbilang CPU menulis pada masa yang sama, tidak akan ada masalah. Sama ada dalam cache sendiri, cache CPU lain, atau dalam memori, CPU sentiasa boleh membaca data terkini Beginilah cara konsistensi cache berfungsi.
Konsistensi jujukan yang dipanggil merujuk kepada konsep yang sama sekali berbeza daripada konsistensi cache, walaupun kedua-duanya adalah produk pembangunan pemproses. Oleh kerana teknologi pengkompil terus berkembang, ia mungkin mengubah susunan operasi tertentu untuk mengoptimumkan kod anda. Konsep pelaksanaan berbilang isu dan luar pesanan telah lama wujud dalam pemproses. Hasilnya ialah susunan arahan sebenar yang dilaksanakan akan berbeza sedikit daripada susunan pelaksanaan kod semasa pengaturcaraan. Sudah tentu, ini bukan apa-apa di bawah satu pemproses Lagipun, selagi kod anda sendiri tidak lulus, tiada siapa yang akan peduli. Tetapi ini tidak berlaku dengan berbilang pemproses Urutan arahan dilengkapkan pada satu pemproses mungkin mempunyai kesan yang besar pada kod yang dilaksanakan pada pemproses lain. Oleh itu, terdapat konsep konsistensi berjujukan, yang memastikan bahawa susunan pelaksanaan benang pada satu pemproses adalah sama dari perspektif benang pada pemproses lain. Penyelesaian kepada masalah ini tidak boleh diselesaikan oleh pemproses atau pengkompil sahaja, tetapi memerlukan campur tangan perisian.
Cara campur tangan perisian juga sangat mudah, iaitu memasukkan penghalang ingatan. Malah, istilah halangan memori dicipta oleh pembangun pemproses, yang menyukarkan kita untuk memahami. Halangan memori dengan mudah boleh membawa kita kepada konsistensi cache, dan juga meragui sama ada kita boleh melakukan ini untuk membenarkan CPU lain melihat cache yang diubah suai Adalah salah untuk berfikir demikian. Apa yang dipanggil halangan memori, dari perspektif pemproses, digunakan untuk mensiri operasi baca dan tulis Dari perspektif perisian, ia digunakan untuk menyelesaikan masalah konsistensi berurutan. Tidakkah pengkompil mahu mengganggu susunan pelaksanaan kod Tidakkah pemproses mahu melaksanakan kod di luar susunan Apabila anda memasukkan penghalang memori, ia adalah sama dengan memberitahu pengkompil bahawa susunan arahan sebelum dan selepas? halangan tidak boleh diterbalikkan Ia memberitahu pemproses bahawa ia hanya boleh menunggu arahan sebelum halangan Selepas arahan dilaksanakan, arahan di belakang halangan boleh mula dilaksanakan. Sudah tentu, halangan memori boleh menghentikan pengkompil daripada mengacaukan, tetapi pemproses masih mempunyai cara. Tidakkah terdapat konsep berbilang isu, pelaksanaan di luar pesanan, dan penyiapan berurutan dalam pemproses semasa halangan memori, ia hanya perlu memastikan bahawa operasi baca dan tulis arahan sebelumnya mesti diselesaikan sebelum operasi baca dan tulis arahan berikut selesai. Oleh itu, terdapat tiga jenis halangan ingatan: halangan baca, halangan tulis dan halangan baca tulis. Sebagai contoh, sebelum x86, operasi tulis telah dijamin selesai mengikut urutan, jadi halangan tulis tidak diperlukan Walau bagaimanapun, sesetengah pemproses ia32 kini mempunyai operasi tulis yang selesai tidak teratur, jadi halangan tulis juga diperlukan.
Malah, sebagai tambahan kepada arahan penghalang baca-tulis khas, terdapat banyak arahan yang dilaksanakan dengan fungsi penghalang baca-tulis, seperti arahan dengan awalan kunci. Sebelum kemunculan arahan penghalang baca dan tulis khas, Linux bergantung pada kunci untuk terus hidup.
Mengenai tempat untuk memasukkan halangan baca dan tulis, ia bergantung kepada keperluan perisian. Halangan baca-tulis tidak dapat mencapai konsistensi berjujukan sepenuhnya, tetapi benang pada berbilang pemproses tidak akan sentiasa merenung perintah pelaksanaan anda Selagi ia memastikan bahawa apabila ia melihat ke atas, ia menganggap bahawa anda mematuhi konsistensi jujukan, yang pelaksanaan tidak akan menyebabkan anda Tiada situasi yang tidak dijangka dalam kod. Keadaan yang dipanggil tidak dijangka, sebagai contoh, utas anda mula-mula memberikan nilai kepada pembolehubah a, dan kemudian memberikan nilai kepada pembolehubah b Akibatnya, utas yang berjalan pada pemproses lain melihat dan mendapati bahawa b telah diberikan nilai, tetapi a belum diberikan nilai. (Nota Ketidakkonsistenan ini bukan disebabkan oleh ketidakkonsistenan cache, tetapi oleh ketidakkonsistenan dalam susunan operasi tulis pemproses diselesaikan daripada a dan tugasan b.
Dengan SMP, benang mula berjalan pada berbilang pemproses pada masa yang sama. Selagi ia adalah benang, terdapat keperluan komunikasi dan penyegerakan. Nasib baik, sistem SMP menggunakan memori yang dikongsi, yang bermaksud bahawa semua pemproses melihat kandungan memori yang sama Walaupun terdapat cache L1 bebas, pemprosesan konsisten cache masih dikendalikan oleh perkakasan. Jika benang pada pemproses berbeza ingin mengakses data yang sama, mereka memerlukan bahagian kritikal dan penyegerakan. Penyegerakan apa yang bergantung pada? Dalam sistem UP sebelum ini, kami bergantung pada semaphore di bahagian atas dan mematikan sampukan dan membaca, mengubah suai dan menulis arahan di bahagian bawah. Kini dalam sistem SMP, mematikan gangguan telah dimansuhkan Walaupun masih perlu untuk menyegerakkan benang pada pemproses yang sama, ia tidak lagi mencukupi untuk bergantung padanya sahaja. Baca ubah suai arahan tulis? Tidak lagi. Apabila operasi baca dalam arahan anda selesai dan operasi tulis tidak dijalankan, pemproses lain mungkin melakukan operasi baca atau tulis. Protokol koheren cache adalah lanjutan, tetapi ia belum cukup maju untuk meramalkan arahan yang mengeluarkan operasi baca ini. Jadi x86 mencipta arahan dengan awalan kunci. Apabila arahan ini dilaksanakan, semua baris cache yang mengandungi alamat baca dan tulis dalam arahan akan menjadi tidak sah dan bas memori akan dikunci. Dengan cara ini, jika pemproses lain ingin membaca atau menulis alamat yang sama atau alamat pada baris cache yang sama, mereka tidak boleh melakukannya dari cache (baris yang berkaitan dalam cache telah tamat tempoh), dan mereka tidak boleh melakukannya dari cache. bas memori (seluruh bas memori telah gagal dikunci), akhirnya mencapai tujuan pelaksanaan atom. Sudah tentu, bermula dari pemproses P6, jika alamat yang hendak diakses oleh arahan awalan kunci sudah ada dalam cache, tidak perlu mengunci bas memori dan operasi atom boleh diselesaikan (walaupun saya mengesyaki ini adalah kerana penambahan biasa dalaman berbilang pemproses Kerana cache L2).
Oleh kerana bas memori akan dikunci, operasi baca dan tulis yang belum selesai akan diselesaikan sebelum melaksanakan arahan dengan awalan kunci, yang juga berfungsi sebagai penghalang ingatan.
Pada masa kini, penyegerakan benang antara berbilang pemproses menggunakan kunci putaran di bahagian atas dan membaca, mengubah suai dan menulis arahan dengan awalan kunci di bahagian bawah. Sudah tentu, penyegerakan sebenar juga termasuk melumpuhkan penjadualan tugas pemproses, menambah gangguan tugas dan menambah semaphore di luar. Pelaksanaan kunci putaran semacam ini dalam Linux telah melalui empat generasi pembangunan dan telah menjadi lebih cekap dan berkuasa.
\#ifdef CONFIG_SMP \#define smp_mb() mb() \#define smp_rmb() rmb() \#define smp_wmb() wmb() \#else \#define smp_mb() barrier() \#define smp_rmb() barrier() \#define smp_wmb() barrier() \#endif
CONFIG_SMP就是用来支持多处理器的。如果是UP(uniprocessor)系统,就会翻译成barrier()。
#define barrier() asm volatile(“”: : :”memory”)
barrier()的作用,就是告诉编译器,内存的变量值都改变了,之前存在寄存器里的变量副本无效,要访问变量还需再访问内存。这样做足以满足UP中所有的内存屏障。
\#ifdef CONFIG_X86_32 /* \* Some non-Intel clones support out of order store. wmb() ceases to be a \* nop for these. */ \#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2) \#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2) \#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM) \#else \#define mb() asm volatile("mfence":::"memory") \#define rmb() asm volatile("lfence":::"memory") \#define wmb() asm volatile("sfence" ::: "memory") \#endif
如果是SMP系统,内存屏障就会翻译成对应的mb()、rmb()和wmb()。这里CONFIG_X86_32的意思是说这是一个32位x86系统,否则就是64位的x86系统。现在的linux内核将32位x86和64位x86融合在同一个x86目录,所以需要增加这个配置选项。
可以看到,如果是64位x86,肯定有mfence、lfence和sfence三条指令,而32位的x86系统则不一定,所以需要进一步查看cpu是否支持这三条新的指令,不行则用加锁的方式来增加内存屏障。
SFENCE,LFENCE,MFENCE指令提供了高效的方式来保证读写内存的排序,这种操作发生在产生弱排序数据的程序和读取这个数据的程序之间。 SFENCE——串行化发生在SFENCE指令之前的写操作但是不影响读操作。 LFENCE——串行化发生在SFENCE指令之前的读操作但是不影响写操作。 MFENCE——串行化发生在MFENCE指令之前的读写操作。 sfence:在sfence指令前的写操作当必须在sfence指令后的写操作前完成。 lfence:在lfence指令前的读操作当必须在lfence指令后的读操作前完成。 mfence:在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。
至于带lock的内存操作,会在锁内存总线之前,就把之前的读写操作结束,功能相当于mfence,当然执行效率上要差一些。
说起来,现在写点底层代码真不容易,既要注意SMP问题,又要注意cpu乱序读写问题,还要注意cache问题,还有设备DMA问题,等等。
多处理器间同步的实现
多处理器间同步所使用的自旋锁实现,已经有专门的文章介绍
Atas ialah kandungan terperinci Penjelasan terperinci tentang halangan memori dalam kernel Linux. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!