NOTE: This is a version of Documentation/memory-barriers.txt translated into Korean. This document is maintained by SeongJae Park <sj38.park@gmail.com>. If you find any difference between this document and the original file or a problem with the translation, please contact the maintainer of this file. Please also note that the purpose of this file is to be easier to read for non English (read: Korean) speakers and is not intended as a fork. So if you have any comments or updates for this file please update the original English file first. The English version is definitive, and readers should look there if they have any doubt. =================================== ì´ ë¬¸ì„œëŠ” Documentation/memory-barriers.txt ì˜ í•œê¸€ 번ì—입니다. ì—ìžï¼š 박성재 <sj38.park@gmail.com> =================================== ========================= 리눅스 ì»¤ë„ ë©”ëª¨ë¦¬ 배리어 ========================= ì €ìž: David Howells <dhowells@redhat.com> Paul E. McKenney <paulmck@linux.vnet.ibm.com> Will Deacon <will.deacon@arm.com> Peter Zijlstra <peterz@infradead.org> ======== ë©´ì±…ì¡°í• ======== ì´ ë¬¸ì„œëŠ” 명세서가 아닙니다; ì´ ë¬¸ì„œëŠ” 완벽하지 ì•Šì€ë°, ê°„ê²°ì„±ì„ ìœ„í•´ ì˜ë„ëœ ë¶€ë¶„ë„ ìžˆê³ , ì˜ë„하진 않았지만 ì‚¬ëžŒì— ì˜í•´ 쓰였다보니 ë¶ˆì™„ì „í•œ ë¶€ë¶„ë„ ìžˆìŠµë‹ˆë‹¤. ì´ ë¬¸ì„œëŠ” 리눅스ì—ì„œ ì œê³µí•˜ëŠ” 다양한 메모리 ë°°ë¦¬ì–´ë“¤ì„ ì‚¬ìš©í•˜ê¸° 위한 안내서입니다만, ë”ê°€ ì´ìƒí•˜ë‹¤ 싶으면 (그런게 ë§Žì„ ê²ë‹ˆë‹¤) ì§ˆë¬¸ì„ ë¶€íƒë“œë¦½ë‹ˆë‹¤. ì¼ë¶€ ì´ìƒí•œ ì ë“¤ì€ ê³µì‹ì ì¸ ë©”ëª¨ë¦¬ ì¼ê´€ì„± 모ë¸ê³¼ tools/memory-model/ ì— ìžˆëŠ” ê´€ë ¨ 문서를 ì°¸ê³ í•´ì„œ í•´ê²°ë 수 ìžˆì„ ê²ë‹ˆë‹¤. 그러나, ì´ ë©”ëª¨ë¦¬ 모ë¸ì¡°ì°¨ë„ ê·¸ 관리ìžë“¤ì˜ ì˜ê²¬ì˜ 집합으로 ë´ì•¼ì§€, ì ˆëŒ€ ì˜³ì€ ì˜ˆì–¸ìžë¡œ ì‹ ë´‰í•´ì„ ì•ˆë ê²ë‹ˆë‹¤. 다시 ë§í•˜ì§€ë§Œ, ì´ ë¬¸ì„œëŠ” 리눅스가 í•˜ë“œì›¨ì–´ì— ê¸°ëŒ€í•˜ëŠ” 사í•ì— 대한 명세서가 아닙니다. ì´ ë¬¸ì„œì˜ ëª©ì ì€ ë‘가지입니다: (1) ì–´ë–¤ íŠ¹ì • ë°°ë¦¬ì–´ì— ëŒ€í•´ ê¸°ëŒ€í• ìˆ˜ 있는 ìµœì†Œí•œì˜ ê¸°ëŠ¥ì„ ëª…ì„¸í•˜ê¸° 위해서, ê·¸ë¦¬ê³ (2) 사용 가능한 ë°°ë¦¬ì–´ë“¤ì— ëŒ€í•´ 어떻게 사용해야 í•˜ëŠ”ì§€ì— ëŒ€í•œ 안내를 ì œê³µí•˜ê¸° 위해서. ì–´ë–¤ 아키í…ì³ëŠ” íŠ¹ì •í•œ ë°°ë¦¬ì–´ë“¤ì— ëŒ€í•´ì„œëŠ” 여기서 ì´ì•¼ê¸°í•˜ëŠ” ìµœì†Œí•œì˜ ìš”êµ¬ì‚¬í•ë“¤ë³´ë‹¤ ë§Žì€ ê¸°ëŠ¥ì„ ì œê³µí• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤ë§Œ, 여기서 ì´ì•¼ê¸°í•˜ëŠ” 요구사í•ë“¤ì„ 충족하지 않는 아키í…ì³ê°€ 있다면 ê·¸ 아키í…ì³ê°€ ìž˜ëª»ëœ ê²ƒì´ëž€ ì ì„ ì•Œì•„ë‘시기 ë°”ëžë‹ˆë‹¤. ë˜í•œ, íŠ¹ì • 아키í…ì³ì—ì„œ ì¼ë¶€ 배리어는 해당 아키í…ì³ì˜ 특수한 ë™ìž‘ ë°©ì‹ìœ¼ë¡œ ì¸í•´ 해당 ë°°ë¦¬ì–´ì˜ ëª…ì‹œì ì‚¬ìš©ì´ ë¶ˆí•„ìš”í•´ì„œ no-op ì´ ë ìˆ˜ë„ ìžˆìŒì„ 알아ë‘시기 ë°”ëžë‹ˆë‹¤. ì—ìž: 본 ë²ˆì— ì—ì‹œ 완벽하지 ì•Šì€ë°, ì´ ì—ì‹œ 부분ì 으로는 ì˜ë„ëœ ê²ƒì´ê¸°ë„ 합니다. 여타 ê¸°ìˆ ë¬¸ì„œë“¤ì´ ê·¸ë ‡ë“¯ 완벽한 ì´í•´ë¥¼ 위해서는 번ì—문과 ì›ë¬¸ì„ 함께 ì½ìœ¼ì‹œë˜ 번ì—ë¬¸ì„ í•˜ë‚˜ì˜ ê°€ì´ë“œë¡œ 활용하시길 추천드리며, 발견ë˜ëŠ” ì˜¤ì— ë“±ì— ëŒ€í•´ì„œëŠ” ì–¸ì œë“ ì˜ê²¬ì„ 부íƒë“œë¦½ë‹ˆë‹¤. 과한 번ì—으로 ì¸í•œ 오해를 최소화하기 위해 ì• ë§¤í•œ ë¶€ë¶„ì´ ìžˆì„ ê²½ìš°ì—는 ì–´ìƒ‰í•¨ì´ ìžˆë”ë¼ë„ ì›ëž˜ì˜ 용어를 차용합니다. ===== 목차: ===== (*) ì¶”ìƒ ë©”ëª¨ë¦¬ 액세스 모ë¸. - 디바ì´ìŠ¤ 오í¼ë ˆì´ì…˜. - 보장사í•. (*) 메모리 배리어란 무엇ì¸ê°€? - 메모리 ë°°ë¦¬ì–´ì˜ ì¢…ë¥˜. - 메모리 ë°°ë¦¬ì–´ì— ëŒ€í•´ ê°€ì •í•´ì„ ì•ˆë 것. - ë°ì´í„° ì˜ì¡´ì„± 배리어 (ì—사ì ). - 컨트롤 ì˜ì¡´ì„±. - SMP 배리어 ì§ë§žì¶”기. - 메모리 배리어 ì‹œí€€ìŠ¤ì˜ ì˜ˆ. - ì½ê¸° 메모리 배리어 vs 로드 예측. - Multicopy ì›ìžì„±. (*) 명시ì ì»¤ë„ ë°°ë¦¬ì–´. - 컴파ì¼ëŸ¬ 배리어. - CPU 메모리 배리어. - MMIO 쓰기 배리어. (*) 암묵ì ì»¤ë„ ë©”ëª¨ë¦¬ 배리어. - ë½ Acquisition 함수. - ì¸í„°ëŸ½íŠ¸ 비활성화 함수. - 슬립과 웨ì´í¬ì—… 함수. - ê·¸ì™¸ì˜ í•¨ìˆ˜ë“¤. (*) CPU ê°„ ACQUIRING ë°°ë¦¬ì–´ì˜ íš¨ê³¼. - Acquire vs 메모리 액세스. - Acquire vs I/O 액세스. (*) 메모리 배리어가 필요한 ê³³ - 프로세서간 ìƒí˜¸ ìž‘ìš©. - ì–´í† ë¯¹ 오í¼ë ˆì´ì…˜. - 디바ì´ìŠ¤ 액세스. - ì¸í„°ëŸ½íŠ¸. (*) ì»¤ë„ I/O ë°°ë¦¬ì–´ì˜ íš¨ê³¼. (*) ê°€ì •ë˜ëŠ” 가장 ì™„í™”ëœ ì‹¤í–‰ 순서 모ë¸. (*) CPU ìºì‹œì˜ ì˜í–¥. - ìºì‹œ ì¼ê´€ì„±. - ìºì‹œ ì¼ê´€ì„± vs DMA. - ìºì‹œ ì¼ê´€ì„± vs MMIO. (*) CPU ë“¤ì´ ì €ì§€ë¥´ëŠ” ì¼ë“¤. - ê·¸ë¦¬ê³ , Alpha ê°€ 있다. - ê°€ìƒ ë¨¸ì‹ ê²ŒìŠ¤íŠ¸. (*) 사용 예. - ìˆœí™˜ì‹ ë²„í¼. (*) ì°¸ê³ ë¬¸í—Œ. ======================= ì¶”ìƒ ë©”ëª¨ë¦¬ 액세스 ëª¨ë¸ ======================= 다ìŒê³¼ ê°™ì´ ì¶”ìƒí™”ëœ ì‹œìŠ¤í…œ 모ë¸ì„ ìƒê°í•´ 봅시다: : : : : : : +-------+ : +--------+ : +-------+ | | : | | : | | | | : | | : | | | CPU 1 |<----->| Memory |<----->| CPU 2 | | | : | | : | | | | : | | : | | +-------+ : +--------+ : +-------+ ^ : ^ : ^ | : | : | | : | : | | : v : | | : +--------+ : | | : | | : | | : | | : | +---------->| Device |<----------+ : | | : : | | : : +--------+ : : : í”„ë¡œê·¸ëž¨ì€ ì—¬ëŸ¬ 메모리 액세스 오í¼ë ˆì´ì…˜ì„ ë°œìƒì‹œí‚¤ê³ , ê°ê°ì˜ CPU 는 그런 í”„ë¡œê·¸ëž¨ë“¤ì„ ì‹¤í–‰í•©ë‹ˆë‹¤. 추ìƒí™”ëœ CPU 모ë¸ì—ì„œ 메모리 오í¼ë ˆì´ì…˜ë“¤ì˜ 순서는 매우 완화ë˜ì–´ ìžˆê³ , CPU 는 í”„ë¡œê·¸ëž¨ì´ ì¸ê³¼ê´€ê³„를 어기지 않는 ìƒíƒœë¡œ 관리ëœë‹¤ê³ ë³´ì¼ ìˆ˜ë§Œ 있다면 메모리 오í¼ë ˆì´ì…˜ì„ ìžì‹ ì´ ì›í•˜ëŠ” ì–´ë–¤ ìˆœì„œëŒ€ë¡œë“ ìž¬ë°°ì¹˜í•´ ë™ìž‘시킬 수 있습니다. 비슷하게, 컴파ì¼ëŸ¬ ë˜í•œ í”„ë¡œê·¸ëž¨ì˜ ì •ìƒì ë™ìž‘ì„ í•´ì¹˜ì§€ 않는 í•œë„ ë‚´ì—서는 ì–´ë–¤ ìˆœì„œë¡œë“ ìžì‹ ì´ ì›í•˜ëŠ” 대로 ì¸ìŠ¤íŠ¸ëŸì…˜ì„ 재배치 í• ìˆ˜ 있습니다. ë”°ë¼ì„œ ìœ„ì˜ ë‹¤ì´ì–´ê·¸ëž¨ì—ì„œ í•œ CPUê°€ ë™ìž‘시키는 메모리 오í¼ë ˆì´ì…˜ì´ 만들어내는 변화는 해당 오í¼ë ˆì´ì…˜ì´ CPU 와 ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ 부분들 사ì´ì˜ ì¸í„°íŽ˜ì´ìŠ¤(ì ì„ )를 지나가면서 ì‹œìŠ¤í…œì˜ ë‚˜ë¨¸ì§€ ë¶€ë¶„ë“¤ì— ì¸ì§€ë©ë‹ˆë‹¤. 예를 들어, 다ìŒì˜ ì¼ë ¨ì˜ ì´ë²¤íŠ¸ë“¤ì„ ìƒê°í•´ 봅시다: CPU 1 CPU 2 =============== =============== { A == 1; B == 2 } A = 3; x = B; B = 4; y = A; 다ì´ì–´ê·¸ëž¨ì˜ 가운ë°ì— 위치한 메모리 ì‹œìŠ¤í…œì— ë³´ì—¬ì§€ê²Œ ë˜ëŠ” ì•¡ì„¸ìŠ¤ë“¤ì€ ë‹¤ìŒì˜ ì´ 24ê°œì˜ ì¡°í•©ìœ¼ë¡œ 재구성ë 수 있습니다: STORE A=3, STORE B=4, y=LOAD A->3, x=LOAD B->4 STORE A=3, STORE B=4, x=LOAD B->4, y=LOAD A->3 STORE A=3, y=LOAD A->3, STORE B=4, x=LOAD B->4 STORE A=3, y=LOAD A->3, x=LOAD B->2, STORE B=4 STORE A=3, x=LOAD B->2, STORE B=4, y=LOAD A->3 STORE A=3, x=LOAD B->2, y=LOAD A->3, STORE B=4 STORE B=4, STORE A=3, y=LOAD A->3, x=LOAD B->4 STORE B=4, ... ... ë”°ë¼ì„œ 다ìŒì˜ 네가지 ì¡°í•©ì˜ ê°’ë“¤ì´ ë‚˜ì˜¬ 수 있습니다: x == 2, y == 1 x == 2, y == 3 x == 4, y == 1 x == 4, y == 3 한발 ë” ë‚˜ì•„ê°€ì„œ, í•œ CPU ê°€ 메모리 ì‹œìŠ¤í…œì— ë°˜ì˜í•œ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì˜ 결과는 다른 CPU ì—ì„œì˜ ë¡œë“œ 오í¼ë ˆì´ì…˜ì„ 통해 ì¸ì§€ë˜ëŠ”ë°, ì´ ë•Œ ìŠ¤í† ì–´ê°€ ë°˜ì˜ëœ 순서와 다른 순서로 ì¸ì§€ë ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. 예로, ì•„ëž˜ì˜ ì¼ë ¨ì˜ ì´ë²¤íŠ¸ë“¤ì„ ìƒê°í•´ 봅시다: CPU 1 CPU 2 =============== =============== { A == 1, B == 2, C == 3, P == &A, Q == &C } B = 4; Q = P; P = &B D = *Q; D ë¡œ ì½í˜€ì§€ëŠ” ê°’ì€ CPU 2 ì—ì„œ P 로부터 ì½í˜€ì§„ ì£¼ì†Œê°’ì— ì˜ì¡´ì ì´ê¸° ë•Œë¬¸ì— ì—¬ê¸°ì—” 분명한 ë°ì´í„° ì˜ì¡´ì„±ì´ 있습니다. 하지만 ì´ ì´ë²¤íŠ¸ë“¤ì˜ 실행 결과로는 ì•„ëž˜ì˜ ê²°ê³¼ë“¤ì´ ëª¨ë‘ ë‚˜íƒ€ë‚ ìˆ˜ 있습니다: (Q == &A) and (D == 1) (Q == &B) and (D == 2) (Q == &B) and (D == 4) CPU 2 는 *Q ì˜ ë¡œë“œë¥¼ ìš”ì²í•˜ê¸° ì „ì— P 를 Q ì— ë„£ê¸° ë•Œë¬¸ì— D ì— C 를 집어넣는 ì¼ì€ ì—†ìŒì„ 알아ë‘세요. 디바ì´ìŠ¤ 오í¼ë ˆì´ì…˜ ------------------- ì¼ë¶€ 디바ì´ìŠ¤ëŠ” ìžì‹ ì˜ ì»¨íŠ¸ë¡¤ ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ ë©”ëª¨ë¦¬ì˜ íŠ¹ì • ì˜ì—으로 매핑해서 ì œê³µí•˜ëŠ”ë°(Memory mapped I/O), 해당 컨트롤 ë ˆì§€ìŠ¤í„°ì— ì ‘ê·¼í•˜ëŠ” 순서는 매우 중요합니다. 예를 들어, ì–´ë“œë ˆìŠ¤ í¬íŠ¸ ë ˆì§€ìŠ¤í„° (A) 와 ë°ì´í„° í¬íŠ¸ ë ˆì§€ìŠ¤í„° (D) 를 통해 ì ‘ê·¼ë˜ëŠ” 내부 ë ˆì§€ìŠ¤í„° ì§‘í•©ì„ ê°–ëŠ” ì´ë”ë„· 카드를 ìƒê°í•´ 봅시다. ë‚´ë¶€ì˜ 5번 ë ˆì§€ìŠ¤í„°ë¥¼ ì½ê¸° 위해 다ìŒì˜ 코드가 사용ë 수 있습니다: *A = 5; x = *D; 하지만, ì´ê±´ 다ìŒì˜ ë‘ ì¡°í•© 중 하나로 만들어질 수 있습니다: STORE *A = 5, x = LOAD *D x = LOAD *D, STORE *A = 5 ë‘번째 ì¡°í•©ì€ ë°ì´í„°ë¥¼ ì½ì–´ì˜¨ _후ì—_ 주소를 ì„¤ì •í•˜ë¯€ë¡œ, 오ë™ìž‘ì„ ì¼ìœ¼í‚¬ ê²ë‹ˆë‹¤. ë³´ìž¥ì‚¬í• -------- CPU ì—게 ê¸°ëŒ€í• ìˆ˜ 있는 ìµœì†Œí•œì˜ ë³´ìž¥ì‚¬í• ëª‡ê°€ì§€ê°€ 있습니다: (*) ì–´ë–¤ CPU ë“ , ì˜ì¡´ì„±ì´ 존재하는 메모리 ì•¡ì„¸ìŠ¤ë“¤ì€ í•´ë‹¹ CPU ìžì‹ ì—게 있어서는 순서대로 메모리 ì‹œìŠ¤í…œì— ìˆ˜í–‰ ìš”ì²ë©ë‹ˆë‹¤. 즉, 다ìŒì— 대해서: Q = READ_ONCE(P); D = READ_ONCE(*Q); CPU 는 다ìŒê³¼ ê°™ì€ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ 시퀀스를 수행 ìš”ì²í•©ë‹ˆë‹¤: Q = LOAD P, D = LOAD *Q ê·¸ë¦¬ê³ ê·¸ 시퀀스 ë‚´ì—ì„œì˜ ìˆœì„œëŠ” í•ìƒ 지켜집니다. 하지만, DEC Alpha ì—ì„œ READ_ONCE() 는 메모리 배리어 ëª…ë ¹ë„ ë‚´ê²Œ ë˜ì–´ 있어서, DEC Alpha CPU 는 다ìŒê³¼ ê°™ì€ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ë“¤ì„ 내놓게 ë©ë‹ˆë‹¤: Q = LOAD P, MEMORY_BARRIER, D = LOAD *Q, MEMORY_BARRIER DEC Alpha ì—ì„œ 수행ë˜ë“ ì•„ë‹ˆë“ , READ_ONCE() 는 컴파ì¼ëŸ¬ë¡œë¶€í„°ì˜ ì•…ì˜í–¥ ë˜í•œ ì œê±°í•©ë‹ˆë‹¤. (*) íŠ¹ì • CPU ë‚´ì—ì„œ 겹치는 ì˜ì—ì˜ ë©”ëª¨ë¦¬ì— í–‰í•´ì§€ëŠ” 로드와 ìŠ¤í† ì–´ ë“¤ì€ í•´ë‹¹ CPU 안ì—서는 순서가 바뀌지 ì•Šì€ ê²ƒìœ¼ë¡œ 보여집니다. 즉, 다ìŒì— 대해서: a = READ_ONCE(*X); WRITE_ONCE(*X, b); CPU 는 다ìŒì˜ 메모리 오í¼ë ˆì´ì…˜ ì‹œí€€ìŠ¤ë§Œì„ ë©”ëª¨ë¦¬ì— ìš”ì²í• ê²ë‹ˆë‹¤: a = LOAD *X, STORE *X = b ê·¸ë¦¬ê³ ë‹¤ìŒì— 대해서는: WRITE_ONCE(*X, c); d = READ_ONCE(*X); CPU 는 다ìŒì˜ 수행 ìš”ì²ë§Œì„ 만들어 냅니다: STORE *X = c, d = LOAD *X (로드 오í¼ë ˆì´ì…˜ê³¼ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ì´ 겹치는 메모리 ì˜ì—ì— ëŒ€í•´ 수행ëœë‹¤ë©´ 해당 오í¼ë ˆì´ì…˜ë“¤ì€ ê²¹ì¹œë‹¤ê³ í‘œí˜„ë©ë‹ˆë‹¤). ê·¸ë¦¬ê³ _반드시_ ë˜ëŠ” _ì ˆëŒ€ë¡œ_ ê°€ì •í•˜ê±°ë‚˜ ê°€ì •í•˜ì§€ ë§ì•„야 하는 ê²ƒë“¤ì´ ìžˆìŠµë‹ˆë‹¤: (*) 컴파ì¼ëŸ¬ê°€ READ_ONCE() 나 WRITE_ONCE() ë¡œ 보호ë˜ì§€ ì•Šì€ ë©”ëª¨ë¦¬ 액세스를 ë‹¹ì‹ ì´ ì›í•˜ëŠ” 대로 í• ê²ƒì´ë¼ëŠ” ê°€ì •ì€ _ì ˆëŒ€ë¡œ_ í•´ì„ ì•ˆë©ë‹ˆë‹¤. ê·¸ê²ƒë“¤ì´ ì—†ë‹¤ë©´, 컴파ì¼ëŸ¬ëŠ” 컴파ì¼ëŸ¬ 배리어 섹션ì—ì„œ 다루게 ë , ëª¨ë“ "ì°½ì˜ì ì¸" ë³€ê²½ë“¤ì„ ë§Œë“¤ì–´ë‚¼ ê¶Œí•œì„ ê°–ê²Œ ë©ë‹ˆë‹¤. (*) 개별ì ì¸ ë¡œë“œì™€ ìŠ¤í† ì–´ë“¤ì´ ì£¼ì–´ì§„ 순서대로 ìš”ì²ë 것ì´ë¼ëŠ” ê°€ì •ì€ _ì ˆëŒ€ë¡œ_ 하지 ë§ì•„야 합니다. ì´ ë§ì€ 곧: X = *A; Y = *B; *D = Z; 는 다ìŒì˜ 것들 중 ì–´ëŠ ê²ƒìœ¼ë¡œë“ ë§Œë“¤ì–´ì§ˆ 수 있다는 ì˜ë¯¸ìž…니다: X = LOAD *A, Y = LOAD *B, STORE *D = Z X = LOAD *A, STORE *D = Z, Y = LOAD *B Y = LOAD *B, X = LOAD *A, STORE *D = Z Y = LOAD *B, STORE *D = Z, X = LOAD *A STORE *D = Z, X = LOAD *A, Y = LOAD *B STORE *D = Z, Y = LOAD *B, X = LOAD *A (*) 겹치는 메모리 ì•¡ì„¸ìŠ¤ë“¤ì€ í•©ì³ì§€ê±°ë‚˜ ë²„ë ¤ì§ˆ 수 있ìŒì„ _반드시_ ê°€ì •í•´ì•¼ 합니다. 다ìŒì˜ 코드는: X = *A; Y = *(A + 4); 다ìŒì˜ 것들 중 ëë“ ë 수 있습니다: X = LOAD *A; Y = LOAD *(A + 4); Y = LOAD *(A + 4); X = LOAD *A; {X, Y} = LOAD {*A, *(A + 4) }; ê·¸ë¦¬ê³ : *A = X; *(A + 4) = Y; 는 ë‹¤ìŒ ì¤‘ ëë“ ë 수 있습니다: STORE *A = X; STORE *(A + 4) = Y; STORE *(A + 4) = Y; STORE *A = X; STORE {*A, *(A + 4) } = {X, Y}; ê·¸ë¦¬ê³ ë³´ìž¥ì‚¬í•ì— 반대ë˜ëŠ” 것들(anti-guarantees)ì´ ìžˆìŠµë‹ˆë‹¤: (*) ì´ ë³´ìž¥ì‚¬í•ë“¤ì€ bitfield ì—는 ì ìš©ë˜ì§€ 않는ë°, 컴파ì¼ëŸ¬ë“¤ì€ bitfield 를 ìˆ˜ì •í•˜ëŠ” 코드를 ìƒì„±í• ë•Œ ì›ìžì„± 없는(non-atomic) ì½ê³ -ìˆ˜ì •í•˜ê³ -쓰는 ì¸ìŠ¤íŠ¸ëŸì…˜ë“¤ì˜ ì¡°í•©ì„ ë§Œë“œëŠ” 경우가 많기 때문입니다. ë³‘ë ¬ ì•Œê³ ë¦¬ì¦˜ì˜ ë™ê¸°í™”ì— bitfield 를 ì‚¬ìš©í•˜ë ¤ 하지 마ì‹ì‹œì˜¤. (*) bitfield ë“¤ì´ ì—¬ëŸ¬ ë½ìœ¼ë¡œ 보호ë˜ëŠ” ê²½ìš°ë¼ í•˜ë”ë¼ë„, í•˜ë‚˜ì˜ bitfield ì˜ ëª¨ë“ í•„ë“œë“¤ì€ í•˜ë‚˜ì˜ ë½ìœ¼ë¡œ 보호ë˜ì–´ì•¼ 합니다. 만약 í•œ bitfield ì˜ ë‘ í•„ë“œê°€ 서로 다른 ë½ìœ¼ë¡œ 보호ëœë‹¤ë©´, 컴파ì¼ëŸ¬ì˜ ì›ìžì„± 없는 ì½ê³ -ìˆ˜ì •í•˜ê³ -쓰는 ì¸ìŠ¤íŠ¸ëŸì…˜ ì¡°í•©ì€ í•œ í•„ë“œì—ì˜ ì—…ë°ì´íŠ¸ê°€ ê·¼ì²˜ì˜ í•„ë“œì—ë„ ì˜í–¥ì„ ë¼ì¹˜ê²Œ í• ìˆ˜ 있습니다. (*) ì´ ë³´ìž¥ì‚¬í•ë“¤ì€ ì ì ˆí•˜ê²Œ ì •ë ¬ë˜ê³ í¬ê¸°ê°€ 잡힌 ìŠ¤ì¹¼ë¼ ë³€ìˆ˜ë“¤ì— ëŒ€í•´ì„œë§Œ ì ìš©ë©ë‹ˆë‹¤. "ì ì ˆí•˜ê²Œ í¬ê¸°ê°€ 잡힌" ì´ë¼í•¨ì€ 현재로ì¨ëŠ” "char", "short", "int" ê·¸ë¦¬ê³ "long" ê³¼ ê°™ì€ í¬ê¸°ì˜ ë³€ìˆ˜ë“¤ì„ ì˜ë¯¸í•©ë‹ˆë‹¤. "ì ì ˆí•˜ê²Œ ì •ë ¬ëœ" ì€ ìžì—°ìŠ¤ëŸ° ì •ë ¬ì„ ì˜ë¯¸í•˜ëŠ”ë°, ë”°ë¼ì„œ "char" ì— ëŒ€í•´ì„œëŠ” 아무 ì œì•½ì´ ì—†ê³ , "short" ì— ëŒ€í•´ì„œëŠ” 2ë°”ì´íŠ¸ ì •ë ¬ì„, "int" ì—는 4ë°”ì´íŠ¸ ì •ë ¬ì„, ê·¸ë¦¬ê³ "long" ì— ëŒ€í•´ì„œëŠ” 32-bit 시스템ì¸ì§€ 64-bit 시스템ì¸ì§€ì— ë”°ë¼ 4ë°”ì´íŠ¸ ë˜ëŠ” 8ë°”ì´íŠ¸ ì •ë ¬ì„ ì˜ë¯¸í•©ë‹ˆë‹¤. ì´ ë³´ìž¥ì‚¬í•ë“¤ì€ C11 표준ì—ì„œ 소개ë˜ì—ˆìœ¼ë¯€ë¡œ, C11 ì „ì˜ ì˜¤ëž˜ëœ ì»´íŒŒì¼ëŸ¬(예를 들어, gcc 4.6) 를 ì‚¬ìš©í• ë•Œì—” 주ì˜í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. í‘œì¤€ì— ì´ ë³´ìž¥ì‚¬í•ë“¤ì€ "memory location" ì„ ì •ì˜í•˜ëŠ” 3.14 ì„¹ì…˜ì— ë‹¤ìŒê³¼ ê°™ì´ ì„¤ëª…ë˜ì–´ 있습니다: (ì—ìž: ì¸ìš©ë¬¸ì´ë¯€ë¡œ 번ì—하지 않습니다) memory location either an object of scalar type, or a maximal sequence of adjacent bit-fields all having nonzero width NOTE 1: Two threads of execution can update and access separate memory locations without interfering with each other. NOTE 2: A bit-field and an adjacent non-bit-field member are in separate memory locations. The same applies to two bit-fields, if one is declared inside a nested structure declaration and the other is not, or if the two are separated by a zero-length bit-field declaration, or if they are separated by a non-bit-field member declaration. It is not safe to concurrently update two bit-fields in the same structure if all members declared between them are also bit-fields, no matter what the sizes of those intervening bit-fields happen to be. ========================= 메모리 배리어란 무엇ì¸ê°€? ========================= ì•žì—ì„œ 봤듯ì´, ìƒí˜¸ê°„ ì˜ì¡´ì„±ì´ 없는 메모리 오í¼ë ˆì´ì…˜ë“¤ì€ ì‹¤ì œë¡œëŠ” 무작위ì 순서로 수행ë 수 있으며, ì´ëŠ” CPU 와 CPU ê°„ì˜ ìƒí˜¸ìž‘ìš©ì´ë‚˜ I/O ì— ë¬¸ì œê°€ ë 수 있습니다. ë”°ë¼ì„œ 컴파ì¼ëŸ¬ì™€ CPU ê°€ 순서를 ë°”ê¾¸ëŠ”ë° ì œì•½ì„ ê±¸ 수 있ë„ë¡ ê°œìž…í• ìˆ˜ 있는 ì–´ë–¤ ë°©ë²•ì´ í•„ìš”í•©ë‹ˆë‹¤. 메모리 배리어는 그런 개입 수단입니다. 메모리 배리어는 배리어를 사ì´ì— ë‘” ì•žê³¼ ë’¤ ì–‘ì¸¡ì˜ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ë“¤ ê°„ì— ë¶€ë¶„ì 순서가 존재하ë„ë¡ í•˜ëŠ” 효과를 ì¤ë‹ˆë‹¤. ì‹œìŠ¤í…œì˜ CPU 들과 여러 디바ì´ìŠ¤ë“¤ì€ ì„±ëŠ¥ì„ ì˜¬ë¦¬ê¸° 위해 ëª…ë ¹ì–´ 재배치, 실행 ìœ ì˜ˆ, 메모리 오í¼ë ˆì´ì…˜ë“¤ì˜ ì¡°í•©, 예측ì 로드(speculative load), 브랜치 예측(speculative branch prediction), 다양한 ì¢…ë¥˜ì˜ ìºì‹±(caching) ë“±ì˜ ë‹¤ì–‘í•œ 트ë¦ì„ ì‚¬ìš©í• ìˆ˜ 있기 ë•Œë¬¸ì— ì´ëŸ° ê°•ì œë ¥ì€ ì¤‘ìš”í•©ë‹ˆë‹¤. 메모리 ë°°ë¦¬ì–´ë“¤ì€ ì´ëŸ° 트ë¦ë“¤ì„ 무효로 하거나 ì–µì œí•˜ëŠ” 목ì 으로 사용ë˜ì–´ì ¸ì„œ 코드가 여러 CPU 와 디바ì´ìŠ¤ë“¤ ê°„ì˜ ìƒí˜¸ìž‘ìš©ì„ ì •ìƒì 으로 ì œì–´í• ìˆ˜ 있게 í•´ì¤ë‹ˆë‹¤. 메모리 ë°°ë¦¬ì–´ì˜ ì¢…ë¥˜ -------------------- 메모리 배리어는 ë„¤ê°œì˜ ê¸°ë³¸ 타입으로 분류ë©ë‹ˆë‹¤: (1) 쓰기 (ë˜ëŠ” ìŠ¤í† ì–´) 메모리 배리어. 쓰기 메모리 배리어는 ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ ì»´í¬ë„ŒíŠ¸ë“¤ì— 해당 배리어보다 ì•žì„œ ëª…ì‹œëœ ëª¨ë“ STORE 오í¼ë ˆì´ì…˜ë“¤ì´ 해당 배리어 ë’¤ì— ëª…ì‹œëœ ëª¨ë“ STORE 오í¼ë ˆì´ì…˜ë“¤ë³´ë‹¤ ë¨¼ì € ìˆ˜í–‰ëœ ê²ƒìœ¼ë¡œ ë³´ì¼ ê²ƒì„ ë³´ìž¥í•©ë‹ˆë‹¤. 쓰기 배리어는 ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì— 대한 부분ì 순서 세우기입니다; 로드 오í¼ë ˆì´ì…˜ë“¤ì— 대해서는 ì–´ë–¤ ì˜í–¥ë„ ë¼ì¹˜ì§€ 않습니다. CPU 는 ì‹œê°„ì˜ íë¦„ì— ë”°ë¼ ë©”ëª¨ë¦¬ ì‹œìŠ¤í…œì— ì¼ë ¨ì˜ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì„ 하나씩 ìš”ì²í•´ 집어넣습니다. 쓰기 배리어 ì•žì˜ ëª¨ë“ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì€ 쓰기 배리어 ë’¤ì˜ ëª¨ë“ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ë³´ë‹¤ _ì•žì„œ_ 수행ë ê²ë‹ˆë‹¤. [!] 쓰기 ë°°ë¦¬ì–´ë“¤ì€ ì½ê¸° ë˜ëŠ” ë°ì´í„° ì˜ì¡´ì„± 배리어와 함께 ì§ì„ 맞춰 사용ë˜ì–´ì•¼ë§Œ í•¨ì„ ì•Œì•„ë‘세요; "SMP 배리어 ì§ë§žì¶”기" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. (2) ë°ì´í„° ì˜ì¡´ì„± 배리어. ë°ì´í„° ì˜ì¡´ì„± 배리어는 ì½ê¸° ë°°ë¦¬ì–´ì˜ ë³´ë‹¤ ì™„í™”ëœ í˜•íƒœìž…ë‹ˆë‹¤. ë‘ê°œì˜ ë¡œë“œ 오í¼ë ˆì´ì…˜ì´ ìžˆê³ ë‘번째 ê²ƒì´ ì²«ë²ˆì§¸ ê²ƒì˜ ê²°ê³¼ì— ì˜ì¡´í•˜ê³ ìžˆì„ ë•Œ(예: ë‘번째 로드가 ì°¸ì¡°í• ì£¼ì†Œë¥¼ 첫번째 로드가 ì½ëŠ” 경우), ë‘번째 로드가 ì½ì–´ì˜¬ ë°ì´í„°ëŠ” 첫번째 ë¡œë“œì— ì˜í•´ ê·¸ 주소가 얻어진 ë’¤ì— ì—…ë°ì´íŠ¸ ë¨ì„ 보장하기 위해서 ë°ì´í„° ì˜ì¡´ì„± 배리어가 í•„ìš”í• ìˆ˜ 있습니다. ë°ì´í„° ì˜ì¡´ì„± 배리어는 ìƒí˜¸ ì˜ì¡´ì ì¸ ë¡œë“œ 오í¼ë ˆì´ì…˜ë“¤ 사ì´ì˜ 부분ì 순서 세우기입니다; ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì´ë‚˜ ë…립ì ì¸ ë¡œë“œë“¤, ë˜ëŠ” 중복ë˜ëŠ” ë¡œë“œë“¤ì— ëŒ€í•´ì„œëŠ” ì–´ë–¤ ì˜í–¥ë„ ë¼ì¹˜ì§€ 않습니다. (1) ì—ì„œ 언급했듯ì´, ì‹œìŠ¤í…œì˜ CPU ë“¤ì€ ë©”ëª¨ë¦¬ ì‹œìŠ¤í…œì— ì¼ë ¨ì˜ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì„ ë˜ì ¸ ë„£ê³ ìžˆìœ¼ë©°, ê±°ê¸°ì— ê´€ì‹¬ì´ ìžˆëŠ” 다른 CPU 는 ê·¸ 오í¼ë ˆì´ì…˜ë“¤ì„ 메모리 ì‹œìŠ¤í…œì´ ì‹¤í–‰í•œ 결과를 ì¸ì§€í• 수 있습니다. ì´ì²˜ëŸ¼ 다른 CPU ì˜ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ì˜ ê²°ê³¼ì— ê´€ì‹¬ì„ ë‘ê³ ìžˆëŠ” CPU ê°€ 수행 ìš”ì²í•œ ë°ì´í„° ì˜ì¡´ì„± 배리어는, 배리어 ì•žì˜ ì–´ë–¤ 로드 오í¼ë ˆì´ì…˜ì´ 다른 CPU ì—ì„œ ë˜ì ¸ ë„£ì€ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ê³¼ ê°™ì€ ì˜ì—ì„ í–¥í–ˆë‹¤ë©´, 그런 ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì´ 만들어내는 결과가 ë°ì´í„° ì˜ì¡´ì„± 배리어 ë’¤ì˜ ë¡œë“œ 오í¼ë ˆì´ì…˜ë“¤ì—게는 ë³´ì¼ ê²ƒì„ ë³´ìž¥í•©ë‹ˆë‹¤. ì´ ìˆœì„œ 세우기 ì œì•½ì— ëŒ€í•œ ê·¸ë¦¼ì„ ë³´ê¸° ìœ„í•´ì„ "메모리 배리어 ì‹œí€€ìŠ¤ì˜ ì˜ˆ" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. [!] 첫번째 로드는 반드시 _ë°ì´í„°_ ì˜ì¡´ì„±ì„ ê°€ì ¸ì•¼ì§€ 컨트롤 ì˜ì¡´ì„±ì„ ê°€ì ¸ì•¼ 하는게 ì•„ë‹˜ì„ ì•Œì•„ë‘ì‹ì‹œì˜¤. 만약 ë‘번째 로드를 위한 주소가 첫번째 ë¡œë“œì— ì˜ì¡´ì ì´ì§€ë§Œ ê·¸ ì˜ì¡´ì„±ì€ ì¡°ê±´ì ì´ì§€ ê·¸ 주소 ìžì²´ë¥¼ ê°€ì ¸ì˜¤ëŠ”ê²Œ 아니ë¼ë©´, ê·¸ê²ƒì€ _컨트롤_ ì˜ì¡´ì„±ì´ê³ , ì´ ê²½ìš°ì—는 ì½ê¸° 배리어나 그보다 ê°•ë ¥í•œ 무언가가 필요합니다. ë” ìžì„¸í•œ ë‚´ìš©ì„ ìœ„í•´ì„œëŠ” "컨트롤 ì˜ì¡´ì„±" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. [!] ë°ì´í„° ì˜ì¡´ì„± 배리어는 보통 쓰기 배리어들과 함께 ì§ì„ 맞춰 사용ë˜ì–´ì•¼ 합니다; "SMP 배리어 ì§ë§žì¶”기" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. (3) ì½ê¸° (ë˜ëŠ” 로드) 메모리 배리어. ì½ê¸° 배리어는 ë°ì´í„° ì˜ì¡´ì„± 배리어 ê¸°ëŠ¥ì˜ ë³´ìž¥ì‚¬í•ì— ë”í•´ì„œ 배리어보다 ì•žì„œ ëª…ì‹œëœ ëª¨ë“ LOAD 오í¼ë ˆì´ì…˜ë“¤ì´ 배리어 ë’¤ì— ëª…ì‹œë˜ëŠ” ëª¨ë“ LOAD 오í¼ë ˆì´ì…˜ë“¤ë³´ë‹¤ ë¨¼ì € 행해진 것으로 ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ ì»´í¬ë„ŒíŠ¸ë“¤ì— 보여질 ê²ƒì„ ë³´ìž¥í•©ë‹ˆë‹¤. ì½ê¸° 배리어는 로드 오í¼ë ˆì´ì…˜ì— 행해지는 부분ì 순서 세우기입니다; ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ì— 대해서는 ì–´ë–¤ ì˜í–¥ë„ ë¼ì¹˜ì§€ 않습니다. ì½ê¸° 메모리 배리어는 ë°ì´í„° ì˜ì¡´ì„± 배리어를 내장하므로 ë°ì´í„° ì˜ì¡´ì„± 배리어를 ëŒ€ì‹ í• ìˆ˜ 있습니다. [!] ì½ê¸° 배리어는 ì¼ë°˜ì 으로 쓰기 배리어들과 함께 ì§ì„ 맞춰 사용ë˜ì–´ì•¼ 합니다; "SMP 배리어 ì§ë§žì¶”기" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. (4) 범용 메모리 배리어. 범용(general) 메모리 배리어는 배리어보다 ì•žì„œ ëª…ì‹œëœ ëª¨ë“ LOAD 와 STORE 오í¼ë ˆì´ì…˜ë“¤ì´ 배리어 ë’¤ì— ëª…ì‹œëœ ëª¨ë“ LOAD 와 STORE 오í¼ë ˆì´ì…˜ë“¤ë³´ë‹¤ ë¨¼ì € ìˆ˜í–‰ëœ ê²ƒìœ¼ë¡œ ì‹œìŠ¤í…œì˜ ë‚˜ë¨¸ì§€ ì»´í¬ë„ŒíŠ¸ë“¤ì— ë³´ì´ê²Œ ë¨ì„ 보장합니다. 범용 메모리 배리어는 로드와 ìŠ¤í† ì–´ 모ë‘ì— ëŒ€í•œ 부분ì 순서 세우기입니다. 범용 메모리 배리어는 ì½ê¸° 메모리 배리어, 쓰기 메모리 배리어 모ë‘를 내장하므로, ë‘ ë°°ë¦¬ì–´ë¥¼ ëª¨ë‘ ëŒ€ì‹ í• ìˆ˜ 있습니다. ê·¸ë¦¬ê³ ë‘ê°œì˜ ëª…ì‹œì ì´ì§€ ì•Šì€ íƒ€ìž…ì´ ìžˆìŠµë‹ˆë‹¤: (5) ACQUIRE 오í¼ë ˆì´ì…˜. ì´ íƒ€ìž…ì˜ ì˜¤í¼ë ˆì´ì…˜ì€ ë‹¨ë°©í–¥ì˜ íˆ¬ê³¼ì„± 배리어처럼 ë™ìž‘합니다. ACQUIRE 오í¼ë ˆì´ì…˜ ë’¤ì˜ ëª¨ë“ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ë“¤ì´ ACQUIRE 오í¼ë ˆì´ì…˜ í›„ì— ì¼ì–´ë‚œ 것으로 ì‹œìŠ¤í…œì˜ ë‚˜ë¨¸ì§€ ì»´í¬ë„ŒíŠ¸ë“¤ì— ë³´ì´ê²Œ ë ê²ƒì´ ë³´ìž¥ë©ë‹ˆë‹¤. LOCK 오í¼ë ˆì´ì…˜ê³¼ smp_load_acquire(), smp_cond_acquire() 오í¼ë ˆì´ì…˜ë„ ACQUIRE 오í¼ë ˆì´ì…˜ì— í¬í•¨ë©ë‹ˆë‹¤. smp_cond_acquire() 오í¼ë ˆì´ì…˜ì€ 컨트롤 ì˜ì¡´ì„±ê³¼ smp_rmb() 를 사용해서 ACQUIRE ì˜ ì˜ë¯¸ì 요구사í•(semantic)ì„ ì¶©ì¡±ì‹œí‚µë‹ˆë‹¤. ACQUIRE 오í¼ë ˆì´ì…˜ ì•žì˜ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ë“¤ì€ ACQUIRE 오í¼ë ˆì´ì…˜ 완료 í›„ì— ìˆ˜í–‰ëœ ê²ƒì²˜ëŸ¼ ë³´ì¼ ìˆ˜ 있습니다. ACQUIRE 오í¼ë ˆì´ì…˜ì€ ê±°ì˜ í•ìƒ RELEASE 오í¼ë ˆì´ì…˜ê³¼ ì§ì„ 지어 사용ë˜ì–´ì•¼ 합니다. (6) RELEASE 오í¼ë ˆì´ì…˜. ì´ íƒ€ìž…ì˜ ì˜¤í¼ë ˆì´ì…˜ë“¤ë„ 단방향 투과성 배리어처럼 ë™ìž‘합니다. RELEASE 오í¼ë ˆì´ì…˜ ì•žì˜ ëª¨ë“ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ë“¤ì€ RELEASE 오í¼ë ˆì´ì…˜ ì „ì— ì™„ë£Œëœ ê²ƒìœ¼ë¡œ ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ ì»´í¬ë„ŒíŠ¸ë“¤ì— 보여질 ê²ƒì´ ë³´ìž¥ë©ë‹ˆë‹¤. UNLOCK ë¥˜ì˜ ì˜¤í¼ë ˆì´ì…˜ë“¤ê³¼ smp_store_release() 오í¼ë ˆì´ì…˜ë„ RELEASE 오í¼ë ˆì´ì…˜ì˜ ì¼ì¢…입니다. RELEASE 오í¼ë ˆì´ì…˜ ë’¤ì˜ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ë“¤ì€ RELEASE 오í¼ë ˆì´ì…˜ì´ 완료ë˜ê¸° ì „ì— í–‰í•´ì§„ 것처럼 ë³´ì¼ ìˆ˜ 있습니다. ACQUIRE 와 RELEASE 오í¼ë ˆì´ì…˜ì˜ ì‚¬ìš©ì€ ì¼ë°˜ì 으로 다른 메모리 ë°°ë¦¬ì–´ì˜ í•„ìš”ì„±ì„ ì—†ì•±ë‹ˆë‹¤ (하지만 "MMIO 쓰기 배리어" 서브섹션ì—ì„œ 설명ë˜ëŠ” 예외를 알아ë‘세요). ë˜í•œ, RELEASE+ACQUIRE ì¡°í•©ì€ ë²”ìš© 메모리 배리어처럼 ë™ìž‘í• ê²ƒì„ ë³´ìž¥í•˜ì§€ -않습니다-. 하지만, ì–´ë–¤ ë³€ìˆ˜ì— ëŒ€í•œ RELEASE 오í¼ë ˆì´ì…˜ì„ 앞서는 메모리 ì•¡ì„¸ìŠ¤ë“¤ì˜ ìˆ˜í–‰ 결과는 ì´ RELEASE 오í¼ë ˆì´ì…˜ì„ ë’¤ì´ì–´ ê°™ì€ ë³€ìˆ˜ì— ëŒ€í•´ ìˆ˜í–‰ëœ ACQUIRE 오í¼ë ˆì´ì…˜ì„ 뒤따르는 메모리 액세스ì—는 보여질 ê²ƒì´ ë³´ìž¥ë©ë‹ˆë‹¤. 다르게 ë§í•˜ìžë©´, 주어진 ë³€ìˆ˜ì˜ í¬ë¦¬í‹°ì»¬ 섹션ì—서는, 해당 ë³€ìˆ˜ì— ëŒ€í•œ ì•žì˜ í¬ë¦¬í‹°ì»¬ 섹션ì—ì„œì˜ ëª¨ë“ ì•¡ì„¸ìŠ¤ë“¤ì´ ì™„ë£Œë˜ì—ˆì„ ê²ƒì„ ë³´ìž¥í•©ë‹ˆë‹¤. 즉, ACQUIRE 는 ìµœì†Œí•œì˜ "ì·¨ë“" ë™ìž‘처럼, ê·¸ë¦¬ê³ RELEASE 는 ìµœì†Œí•œì˜ "공개" 처럼 ë™ìž‘한다는 ì˜ë¯¸ìž…니다. atomic_t.txt ì— ì„¤ëª…ëœ ì–´í† ë¯¹ 오í¼ë ˆì´ì…˜ë“¤ 중 ì¼ë¶€ëŠ” ì™„ì „ížˆ 순서잡힌 것들과 (배리어를 사용하지 않는) ì™„í™”ëœ ìˆœì„œì˜ ê²ƒë“¤ ì™¸ì— ACQUIRE 와 RELEASE ë¶€ë¥˜ì˜ ê²ƒë“¤ë„ ì¡´ìž¬í•©ë‹ˆë‹¤. 로드와 ìŠ¤í† ì–´ë¥¼ ëª¨ë‘ ìˆ˜í–‰í•˜ëŠ” ì¡°í•©ëœ ì–´í† ë¯¹ 오í¼ë ˆì´ì…˜ì—ì„œ, ACQUIRE 는 해당 오í¼ë ˆì´ì…˜ì˜ 로드 부분ì—만 ì ìš©ë˜ê³ RELEASE 는 해당 오í¼ë ˆì´ì…˜ì˜ ìŠ¤í† ì–´ 부분ì—만 ì ìš©ë©ë‹ˆë‹¤. 메모리 ë°°ë¦¬ì–´ë“¤ì€ ë‘ CPU ê°„, ë˜ëŠ” CPU 와 디바ì´ìŠ¤ ê°„ì— ìƒí˜¸ìž‘ìš©ì˜ ê°€ëŠ¥ì„±ì´ ìžˆì„ ë•Œì—만 필요합니다. 만약 ì–´ë–¤ ì½”ë“œì— ê·¸ëŸ° ìƒí˜¸ìž‘ìš©ì´ ì—†ì„ ê²ƒì´ ë³´ìž¥ëœë‹¤ë©´, 해당 코드ì—서는 메모리 배리어를 ì‚¬ìš©í• í•„ìš”ê°€ 없습니다. ì´ê²ƒë“¤ì€ _최소한ì˜_ 보장사í•ë“¤ìž„ì„ ì•Œì•„ë‘세요. 다른 아키í…ì³ì—서는 ë” ê°•ë ¥í•œ 보장사í•ì„ ì œê³µí• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤ë§Œ, 그런 보장사í•ì€ 아키í…ì³ ì¢…ì†ì 코드 ì´ì™¸ì˜ 부분ì—서는 ì‹ ë¢°ë˜ì§€ _ì•Šì„_ ê²ë‹ˆë‹¤. 메모리 ë°°ë¦¬ì–´ì— ëŒ€í•´ ê°€ì •í•´ì„ ì•ˆë 것 ------------------------------------- 리눅스 ì»¤ë„ ë©”ëª¨ë¦¬ ë°°ë¦¬ì–´ë“¤ì´ ë³´ìž¥í•˜ì§€ 않는 ê²ƒë“¤ì´ ìžˆìŠµë‹ˆë‹¤: (*) 메모리 배리어 ì•žì—ì„œ ëª…ì‹œëœ ì–´ë–¤ 메모리 ì•¡ì„¸ìŠ¤ë„ ë©”ëª¨ë¦¬ 배리어 ëª…ë ¹ì˜ ìˆ˜í–‰ 완료 ì‹œì 까지 _완료_ ë 것ì´ëž€ ë³´ìž¥ì€ ì—†ìŠµë‹ˆë‹¤; 배리어가 하는 ì¼ì€ CPU ì˜ ì•¡ì„¸ìŠ¤ íì— íŠ¹ì • íƒ€ìž…ì˜ ì•¡ì„¸ìŠ¤ë“¤ì€ ë„˜ì„ ìˆ˜ 없는 ì„ ì„ ê¸‹ëŠ” 것으로 ìƒê°ë 수 있습니다. (*) í•œ CPU ì—ì„œ 메모리 배리어를 수행하는게 ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ CPU 나 í•˜ë“œì›¨ì–´ì— ì–´ë–¤ ì§ì ‘ì ì¸ ì˜í–¥ì„ ë¼ì¹œë‹¤ëŠ” ë³´ìž¥ì€ ì¡´ìž¬í•˜ì§€ 않습니다. 배리어 ìˆ˜í–‰ì´ ë§Œë“œëŠ” ê°„ì ‘ì ì˜í–¥ì€ ë‘번째 CPU ê°€ 첫번째 CPU ì˜ ì•¡ì„¸ìŠ¤ë“¤ì˜ ê²°ê³¼ë¥¼ ë°”ë¼ë³´ëŠ” 순서가 ë©ë‹ˆë‹¤ë§Œ, ë‹¤ìŒ í•ëª©ì„ 보세요: (*) 첫번째 CPU ê°€ ë‘번째 CPU ì˜ ë©”ëª¨ë¦¬ ì•¡ì„¸ìŠ¤ë“¤ì˜ ê²°ê³¼ë¥¼ ë°”ë¼ë³¼ ë•Œ, _ì„¤ë ¹_ ë‘번째 CPU ê°€ 메모리 배리어를 사용한다 í•´ë„, 첫번째 CPU _ë˜í•œ_ ê·¸ì— ë§žëŠ” 메모리 배리어를 사용하지 않는다면 ("SMP 배리어 ì§ë§žì¶”기" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”) ê·¸ 결과가 올바른 순서로 보여진다는 ë³´ìž¥ì€ ì—†ìŠµë‹ˆë‹¤. (*) CPU ë°”ê¹¥ì˜ í•˜ë“œì›¨ì–´[*] ê°€ 메모리 ì•¡ì„¸ìŠ¤ë“¤ì˜ ìˆœì„œë¥¼ 바꾸지 않는다는 ë³´ìž¥ì€ ì¡´ìž¬í•˜ì§€ 않습니다. CPU ìºì‹œ ì¼ê´€ì„± ë©”ì»¤ë‹ˆì¦˜ì€ ë©”ëª¨ë¦¬ ë°°ë¦¬ì–´ì˜ ê°„ì ‘ì ì˜í–¥ì„ CPU 사ì´ì— ì „íŒŒí•˜ê¸´ 하지만, 순서대로 ì „íŒŒí•˜ì§€ëŠ” ì•Šì„ ìˆ˜ 있습니다. [*] 버스 ë§ˆìŠ¤í„°ë§ DMA 와 ì¼ê´€ì„±ì— 대해서는 다ìŒì„ ì°¸ê³ í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤: Documentation/PCI/pci.txt Documentation/DMA-API-HOWTO.txt Documentation/DMA-API.txt ë°ì´í„° ì˜ì¡´ì„± 배리어 (ì—사ì ) ----------------------------- 리눅스 ì»¤ë„ v4.15 기준으로, smp_read_barrier_depends() ê°€ READ_ONCE() ì— ì¶”ê°€ë˜ì—ˆëŠ”ë°, ì´ëŠ” ì´ ì„¹ì…˜ì— ì£¼ì˜ë¥¼ 기울여야 하는 ì‚¬ëžŒë“¤ì€ DEC Alpha 아키í…ì³ ì „ìš© 코드를 만드는 사람들과 READ_ONCE() ìžì²´ë¥¼ 만드는 사람들 ë¿ìž„ì„ ì˜ë¯¸í•©ë‹ˆë‹¤. 그런 ë¶„ë“¤ì„ ìœ„í•´, ê·¸ë¦¬ê³ ì—ì‚¬ì— ê´€ì‹¬ 있는 ë¶„ë“¤ì„ ìœ„í•´, 여기 ë°ì´í„° ì˜ì¡´ì„± ë°°ë¦¬ì–´ì— ëŒ€í•œ ì´ì•¼ê¸°ë¥¼ ì 습니다. ë°ì´í„° ì˜ì¡´ì„± ë°°ë¦¬ì–´ì˜ ì‚¬ìš©ì— ìžˆì–´ 지켜야 하는 사í•ë“¤ì€ 약간 ë¯¸ë¬˜í•˜ê³ , ë°ì´í„° ì˜ì¡´ì„± 배리어가 사용ë˜ì–´ì•¼ 하는 ìƒí™©ë„ í•ìƒ 명백하지는 않습니다. ì„¤ëª…ì„ ìœ„í•´ 다ìŒì˜ ì´ë²¤íŠ¸ 시퀀스를 ìƒê°í•´ 봅시다: CPU 1 CPU 2 =============== =============== { A == 1, B == 2, C == 3, P == &A, Q == &C } B = 4; <쓰기 배리어> WRITE_ONCE(P, &B) Q = READ_ONCE(P); D = *Q; 여기엔 분명한 ë°ì´í„° ì˜ì¡´ì„±ì´ 존재하므로, ì´ ì‹œí€€ìŠ¤ê°€ ëë‚¬ì„ ë•Œ Q 는 &A ë˜ëŠ” &B ì¼ ê²ƒì´ê³ , ë”°ë¼ì„œ: (Q == &A) 는 (D == 1) 를, (Q == &B) 는 (D == 4) 를 ì˜ë¯¸í•©ë‹ˆë‹¤. 하지만! CPU 2 는 B ì˜ ì—…ë°ì´íŠ¸ë¥¼ ì¸ì‹í•˜ê¸° ì „ì— P ì˜ ì—…ë°ì´íŠ¸ë¥¼ ì¸ì‹í• 수 ìžˆê³ , ë”°ë¼ì„œ 다ìŒì˜ 결과가 가능합니다: (Q == &B) and (D == 2) ???? ì´ëŸ° 결과는 ì¼ê´€ì„±ì´ë‚˜ ì¸ê³¼ 관계 ìœ ì§€ê°€ 실패한 것처럼 ë³´ì¼ ìˆ˜ë„ ìžˆê² ì§€ë§Œ, ê·¸ë ‡ì§€ 않습니다, ê·¸ë¦¬ê³ ì´ í˜„ìƒì€ (DEC Alpha 와 ê°™ì€) 여러 CPU ì—ì„œ ì‹¤ì œë¡œ 발견ë 수 있습니다. ì´ ë¬¸ì œ ìƒí™©ì„ ì œëŒ€ë¡œ 해결하기 위해, ë°ì´í„° ì˜ì¡´ì„± 배리어나 그보다 ê°•í™”ëœ ë¬´ì–¸ê°€ê°€ 주소를 ì½ì–´ì˜¬ 때와 ë°ì´í„°ë¥¼ ì½ì–´ì˜¬ ë•Œ 사ì´ì— 추가ë˜ì–´ì•¼ë§Œ 합니다: CPU 1 CPU 2 =============== =============== { A == 1, B == 2, C == 3, P == &A, Q == &C } B = 4; <쓰기 배리어> WRITE_ONCE(P, &B); Q = READ_ONCE(P); <ë°ì´í„° ì˜ì¡´ì„± 배리어> D = *Q; ì´ ë³€ê²½ì€ ì•žì˜ ì²˜ìŒ ë‘가지 ê²°ê³¼ 중 í•˜ë‚˜ë§Œì´ ë°œìƒí• 수 ìžˆê³ , ì„¸ë²ˆì§¸ì˜ ê²°ê³¼ëŠ” ë°œìƒí• 수 ì—†ë„ë¡ í•©ë‹ˆë‹¤. [!] ì´ ìƒë‹¹ížˆ ë°˜ì§ê´€ì ì¸ ìƒí™©ì€ ë¶„ë¦¬ëœ ìºì‹œë¥¼ 가지는 기계들ì—ì„œ 가장 잘 ë°œìƒí•˜ëŠ”ë°, 예를 들면 í•œ ìºì‹œ ë±…í¬ëŠ” ì§ìˆ˜ ë²ˆí˜¸ì˜ ìºì‹œ ë¼ì¸ë“¤ì„ ì²˜ë¦¬í•˜ê³ , 다른 ë±…í¬ëŠ” 홀수 ë²ˆí˜¸ì˜ ìºì‹œ ë¼ì¸ë“¤ì„ 처리하는 ê²½ìš°ìž„ì„ ì•Œì•„ë‘시기 ë°”ëžë‹ˆë‹¤. í¬ì¸í„° P 는 ì§ìˆ˜ 번호 ìºì‹œ ë¼ì¸ì— ì €ìž¥ë˜ì–´ ìžˆê³ , 변수 B 는 홀수 번호 ìºì‹œ ë¼ì¸ì— ì €ìž¥ë˜ì–´ ìžˆì„ ìˆ˜ 있습니다. 여기서 ê°’ì„ ì½ì–´ì˜¤ëŠ” CPU ì˜ ìºì‹œì˜ 홀수 번호 처리 ë±…í¬ëŠ” 열심히 ì¼ê°ì„ ì²˜ë¦¬ì¤‘ì¸ ë°˜ë©´ 홀수 번호 처리 ë±…í¬ëŠ” í• ì¼ ì—†ì´ í•œê°€í•œ 중ì´ë¼ë©´ í¬ì¸í„° P (&B) ì˜ ìƒˆë¡œìš´ ê°’ê³¼ 변수 B ì˜ ê¸°ì¡´ ê°’ (2) 를 ë³¼ 수 있습니다. ì˜ì¡´ì ì“°ê¸°ë“¤ì˜ ìˆœì„œë¥¼ 맞추는ë°ì—는 ë°ì´í„° ì˜ì¡´ì„± 배리어가 필요치 ì•Šì€ë°, ì´ëŠ” 리눅스 커ë„ì´ ì§€ì›í•˜ëŠ” CPU ë“¤ì€ (1) 쓰기가 ì •ë§ë¡œ ì¼ì–´ë‚ 지, (2) 쓰기가 ì–´ë””ì— ì´ë£¨ì–´ì§ˆì§€, ê·¸ë¦¬ê³ (3) 쓰여질 ê°’ì„ í™•ì‹¤ížˆ 알기 ì „ê¹Œì§€ëŠ” 쓰기를 수행하지 않기 때문입니다. 하지만 "컨트롤 ì˜ì¡´ì„±" 섹션과 Documentation/RCU/rcu_dereference.txt 파ì¼ì„ ì£¼ì˜ ê¹Šê²Œ ì½ì–´ 주시기 ë°”ëžë‹ˆë‹¤: 컴파ì¼ëŸ¬ëŠ” 매우 ì°½ì˜ì ì¸ ë§Žì€ ë°©ë²•ìœ¼ë¡œ 종ì†ì„±ì„ ê¹° 수 있습니다. CPU 1 CPU 2 =============== =============== { A == 1, B == 2, C = 3, P == &A, Q == &C } B = 4; <쓰기 배리어> WRITE_ONCE(P, &B); Q = READ_ONCE(P); WRITE_ONCE(*Q, 5); ë”°ë¼ì„œ, Q ë¡œì˜ ì½ê¸°ì™€ *Q ë¡œì˜ ì“°ê¸° 사ì´ì—는 ë°ì´í„° 종ì†ì„± 배리어가 필요치 않습니다. 달리 ë§í•˜ë©´, ë°ì´í„° 종ì†ì„± 배리어가 ì—†ë”ë¼ë„ ë‹¤ìŒ ê²°ê³¼ëŠ” ìƒê¸°ì§€ 않습니다: (Q == &B) && (B == 4) ì´ëŸ° íŒ¨í„´ì€ ë“œë¬¼ê²Œ 사용ë˜ì–´ì•¼ í•¨ì„ ì•Œì•„ ë‘시기 ë°”ëžë‹ˆë‹¤. 무엇보다ë„, ì˜ì¡´ì„± 순서 ê·œì¹™ì˜ ì˜ë„는 쓰기 ìž‘ì—…ì„ -예방- í•´ì„œ 그로 ì¸í•´ ë°œìƒí•˜ëŠ” 비싼 ìºì‹œ ë¯¸ìŠ¤ë„ ì—†ì• ë ¤ëŠ” 것입니다. ì´ íŒ¨í„´ì€ ë“œë¬¼ê²Œ ë°œìƒí•˜ëŠ” ì—러 ì¡°ê±´ ê°™ì€ê²ƒë“¤ì„ 기ë¡í•˜ëŠ”ë° ì‚¬ìš©ë 수 있으며, CPUì˜ ìžì—°ì ì¸ ìˆœì„œ ë³´ìž¥ì´ ê·¸ëŸ° 기ë¡ë“¤ì„ 사ë¼ì§€ì§€ 않게 í•´ì¤ë‹ˆë‹¤. ë°ì´í„° ì˜ì¡´ì„±ì— ì˜í•´ ì œê³µë˜ëŠ” ì´ ìˆœì„œê·œì¹™ì€ ì´ë¥¼ í¬í•¨í•˜ê³ 있는 CPU ì— ì§€ì—ì ìž„ì„ ì•Œì•„ë‘시기 ë°”ëžë‹ˆë‹¤. ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ "Multicopy ì›ìžì„±" ì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. ë°ì´í„° ì˜ì¡´ì„± 배리어는 매우 중요한ë°, 예를 들어 RCU 시스템ì—ì„œ ê·¸ë ‡ìŠµë‹ˆë‹¤. include/linux/rcupdate.h ì˜ rcu_assign_pointer() 와 rcu_dereference() 를 ì°¸ê³ í•˜ì„¸ìš”. 여기서 ë°ì´í„° ì˜ì¡´ì„± 배리어는 RCU ë¡œ 관리ë˜ëŠ” í¬ì¸í„°ì˜ íƒ€ê²Ÿì„ í˜„ìž¬ 타겟ì—ì„œ ìˆ˜ì •ëœ ìƒˆë¡œìš´ 타겟으로 바꾸는 ìž‘ì—…ì—ì„œ 새로 ìˆ˜ì •ëœ íƒ€ê²Ÿì´ ì´ˆê¸°í™”ê°€ 완료ë˜ì§€ ì•Šì€ ì±„ë¡œ 보여지는 ì¼ì´ ì¼ì–´ë‚˜ì§€ 않게 í•´ì¤ë‹ˆë‹¤. ë” ë§Žì€ ì˜ˆë¥¼ ìœ„í•´ì„ "ìºì‹œ ì¼ê´€ì„±" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. 컨트롤 ì˜ì¡´ì„± ------------- í˜„ìž¬ì˜ ì»´íŒŒì¼ëŸ¬ë“¤ì€ 컨트롤 ì˜ì¡´ì„±ì„ ì´í•´í•˜ê³ 있지 않기 ë•Œë¬¸ì— ì»¨íŠ¸ë¡¤ ì˜ì¡´ì„±ì€ 약간 다루기 ì–´ë ¤ìš¸ 수 있습니다. ì´ ì„¹ì…˜ì˜ ëª©ì ì€ ì—¬ëŸ¬ë¶„ì´ ì»´íŒŒì¼ëŸ¬ì˜ 무시로 ì¸í•´ ì—¬ëŸ¬ë¶„ì˜ ì½”ë“œê°€ ë§ê°€ì§€ëŠ” 걸 ë§‰ì„ ìˆ˜ 있ë„ë¡ ë•ëŠ”ê²ë‹ˆë‹¤. 로드-로드 컨트롤 ì˜ì¡´ì„±ì€ ë°ì´í„° ì˜ì¡´ì„± 배리어만으로는 ì •í™•ížˆ ë™ìž‘í• ìˆ˜ê°€ 없어서 ì½ê¸° 메모리 배리어를 필요로 합니다. ì•„ëž˜ì˜ ì½”ë“œë¥¼ 봅시다: q = READ_ONCE(a); if (q) { <ë°ì´í„° ì˜ì¡´ì„± 배리어> /* BUG: No data dependency!!! */ p = READ_ONCE(b); } ì´ ì½”ë“œëŠ” ì›í•˜ëŠ” ëŒ€ë¡œì˜ íš¨ê³¼ë¥¼ 내지 ëª»í• ìˆ˜ 있는ë°, ì´ ì½”ë“œì—는 ë°ì´í„° ì˜ì¡´ì„±ì´ ì•„ë‹ˆë¼ ì»¨íŠ¸ë¡¤ ì˜ì¡´ì„±ì´ 존재하기 때문으로, ì´ëŸ° ìƒí™©ì—ì„œ CPU 는 실행 ì†ë„를 ë” ë¹ ë¥´ê²Œ 하기 위해 분기 ì¡°ê±´ì˜ ê²°ê³¼ë¥¼ ì˜ˆì¸¡í•˜ê³ ì½”ë“œë¥¼ 재배치 í• ìˆ˜ 있어서 다른 CPU 는 b ë¡œë¶€í„°ì˜ ë¡œë“œ 오í¼ë ˆì´ì…˜ì´ a ë¡œë¶€í„°ì˜ ë¡œë“œ 오í¼ë ˆì´ì…˜ë³´ë‹¤ ë¨¼ì € ë°œìƒí•œ 걸로 ì¸ì‹í• 수 있습니다. ì—¬ê¸°ì— ì •ë§ë¡œ í•„ìš”í–ˆë˜ ê±´ 다ìŒê³¼ 같습니다: q = READ_ONCE(a); if (q) { <ì½ê¸° 배리어> p = READ_ONCE(b); } 하지만, ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ì€ 예측ì 으로 수행ë˜ì§€ 않습니다. 즉, ë‹¤ìŒ ì˜ˆì—서와 ê°™ì´ ë¡œë“œ-ìŠ¤í† ì–´ 컨트롤 ì˜ì¡´ì„±ì´ 존재하는 경우ì—는 순서가 -지켜진다-는 ì˜ë¯¸ìž…니다. q = READ_ONCE(a); if (q) { WRITE_ONCE(b, 1); } 컨트롤 ì˜ì¡´ì„±ì€ 보통 다른 íƒ€ìž…ì˜ ë°°ë¦¬ì–´ë“¤ê³¼ ì§ì„ 맞춰 사용ë©ë‹ˆë‹¤. ê·¸ë ‡ë‹¤ê³¤ 하나, READ_ONCE() ë„ WRITE_ONCE() ë„ ì„ íƒì‚¬í•ì´ ì•„ë‹ˆë¼ í•„ìˆ˜ì‚¬í•ìž„ì„ ë¶€ë”” 명심하세요! READ_ONCE() ê°€ 없다면, 컴파ì¼ëŸ¬ëŠ” 'a' ë¡œë¶€í„°ì˜ ë¡œë“œë¥¼ 'a' ë¡œë¶€í„°ì˜ ë˜ë‹¤ë¥¸ 로드와 ì¡°í•©í• ìˆ˜ 있습니다. WRITE_ONCE() ê°€ 없다면, 컴파ì¼ëŸ¬ëŠ” 'b' ë¡œì˜ ìŠ¤í† ì–´ë¥¼ 'b' ë¡œì˜ ë˜ë¼ëŠ ìŠ¤í† ì–´ë“¤ê³¼ ì¡°í•©í• ìˆ˜ 있습니다. ë‘ ê²½ìš° ëª¨ë‘ ìˆœì„œì— ìžˆì–´ ìƒë‹¹ížˆ 비ì§ê´€ì ì¸ ê²°ê³¼ë¥¼ ì´ˆëž˜í• ìˆ˜ 있습니다. ì´ê±¸ë¡œ ëì´ ì•„ë‹Œê²Œ, 컴파ì¼ëŸ¬ê°€ 변수 'a' ì˜ ê°’ì´ í•ìƒ 0ì´ ì•„ë‹ˆë¼ê³ ì¦ëª…í• ìˆ˜ 있다면, ì•žì˜ ì˜ˆì—ì„œ "if" ë¬¸ì„ ì—†ì• ì„œ 다ìŒê³¼ ê°™ì´ ìµœì í™” í• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤: q = a; b = 1; /* BUG: Compiler and CPU can both reorder!!! */ 그러니 READ_ONCE() 를 반드시 사용하세요. 다ìŒê³¼ ê°™ì´ "if" ë¬¸ì˜ ì–‘ê°ˆëž˜ ë¸Œëžœì¹˜ì— ëª¨ë‘ ì¡´ìž¬í•˜ëŠ” ë™ì¼í•œ ìŠ¤í† ì–´ì— ëŒ€í•´ 순서를 ê°•ì œí•˜ê³ ì‹¶ì€ ê²½ìš°ê°€ ìžˆì„ ìˆ˜ 있습니다: q = READ_ONCE(a); if (q) { barrier(); WRITE_ONCE(b, 1); do_something(); } else { barrier(); WRITE_ONCE(b, 1); do_something_else(); } 안타ê¹ê²Œë„, í˜„ìž¬ì˜ ì»´íŒŒì¼ëŸ¬ë“¤ì€ ë†’ì€ ìµœì í™” ë ˆë²¨ì—서는 ì´ê±¸ 다ìŒê³¼ ê°™ì´ ë°”ê¿”ë²„ë¦½ë‹ˆë‹¤: q = READ_ONCE(a); barrier(); WRITE_ONCE(b, 1); /* BUG: No ordering vs. load from a!!! */ if (q) { /* WRITE_ONCE(b, 1); -- moved up, BUG!!! */ do_something(); } else { /* WRITE_ONCE(b, 1); -- moved up, BUG!!! */ do_something_else(); } ì´ì œ 'a' ì—ì„œì˜ ë¡œë“œì™€ 'b' ë¡œì˜ ìŠ¤í† ì–´ 사ì´ì—는 ì¡°ê±´ì 관계가 없기 ë•Œë¬¸ì— CPU 는 ì´ë“¤ì˜ 순서를 바꿀 수 있게 ë©ë‹ˆë‹¤: ì´ëŸ° ê²½ìš°ì— ì¡°ê±´ì 관계는 반드시 필요한ë°, ëª¨ë“ ì»´íŒŒì¼ëŸ¬ 최ì 화가 ì´ë£¨ì–´ì§€ê³ ë‚œ í›„ì˜ ì–´ì…ˆë¸”ë¦¬ 코드ì—ì„œë„ ë§ˆì°¬ê°€ì§€ìž…ë‹ˆë‹¤. ë”°ë¼ì„œ, ì´ ì˜ˆì—ì„œ 순서를 지키기 위해서는 smp_store_release() 와 ê°™ì€ ëª…ì‹œì 메모리 배리어가 필요합니다: q = READ_ONCE(a); if (q) { smp_store_release(&b, 1); do_something(); } else { smp_store_release(&b, 1); do_something_else(); } ë°˜ë©´ì— ëª…ì‹œì 메모리 배리어가 없다면, ì´ëŸ° ê²½ìš°ì˜ ìˆœì„œëŠ” ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì´ 서로 다를 ë•Œì—만 보장ë˜ëŠ”ë°, 예를 들면 다ìŒê³¼ ê°™ì€ ê²½ìš°ìž…ë‹ˆë‹¤: q = READ_ONCE(a); if (q) { WRITE_ONCE(b, 1); do_something(); } else { WRITE_ONCE(b, 2); do_something_else(); } 처ìŒì˜ READ_ONCE() 는 컴파ì¼ëŸ¬ê°€ 'a' ì˜ ê°’ì„ ì¦ëª…해내는 ê²ƒì„ ë§‰ê¸° 위해 ì—¬ì „ížˆ 필요합니다. ë˜í•œ, 로컬 변수 'q' 를 ê°€ì§€ê³ í•˜ëŠ” ì¼ì— 대해 주ì˜í•´ì•¼ 하는ë°, 그러지 않으면 컴파ì¼ëŸ¬ëŠ” ê·¸ ê°’ì„ ì¶”ì¸¡í•˜ê³ ë˜ë‹¤ì‹œ 필요한 조건관계를 ì—†ì• ë²„ë¦´ 수 있습니다. 예를 들면: q = READ_ONCE(a); if (q % MAX) { WRITE_ONCE(b, 1); do_something(); } else { WRITE_ONCE(b, 2); do_something_else(); } 만약 MAX ê°€ 1 ë¡œ ì •ì˜ëœ ìƒìˆ˜ë¼ë©´, 컴파ì¼ëŸ¬ëŠ” (q % MAX) 는 0ì´ëž€ ê²ƒì„ ì•Œì•„ì±„ê³ , ìœ„ì˜ ì½”ë“œë¥¼ 아래와 ê°™ì´ ë°”ê¿”ë²„ë¦´ 수 있습니다: q = READ_ONCE(a); WRITE_ONCE(b, 2); do_something_else(); ì´ë ‡ê²Œ ë˜ë©´, CPU 는 변수 'a' ë¡œë¶€í„°ì˜ ë¡œë“œì™€ 변수 'b' ë¡œì˜ ìŠ¤í† ì–´ 사ì´ì˜ 순서를 지켜줄 필요가 없어집니다. barrier() 를 추가해 í•´ê²°í•´ ë³´ê³ ì‹¶ê² ì§€ë§Œ, 그건 ë„ì›€ì´ ì•ˆë©ë‹ˆë‹¤. ì¡°ê±´ 관계는 사ë¼ì¡Œê³ , barrier() 는 ì´ë¥¼ ë˜ëŒë¦¬ì§€ 못합니다. ë”°ë¼ì„œ, ì´ ìˆœì„œë¥¼ 지켜야 한다면, MAX ê°€ 1 보다 í¬ë‹¤ëŠ” 것ì„, 다ìŒê³¼ ê°™ì€ ë°©ë²•ì„ ì‚¬ìš©í•´ 분명히 해야 합니다: q = READ_ONCE(a); BUILD_BUG_ON(MAX <= 1); /* Order load from a with store to b. */ if (q % MAX) { WRITE_ONCE(b, 1); do_something(); } else { WRITE_ONCE(b, 2); do_something_else(); } 'b' ë¡œì˜ ìŠ¤í† ì–´ë“¤ì€ ì—¬ì „ížˆ 서로 ë‹¤ë¦„ì„ ì•Œì•„ë‘세요. 만약 ê·¸ê²ƒë“¤ì´ ë™ì¼í•˜ë©´, ì•žì—ì„œ ì´ì•¼ê¸°í–ˆë“¯, 컴파ì¼ëŸ¬ê°€ ê·¸ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì„ 'if' 문 바깥으로 ë„집어낼 수 있습니다. ë˜í•œ ì´ì§„ 조건문 í‰ê°€ì— 너무 ì˜ì¡´í•˜ì§€ ì•Šë„ë¡ ì¡°ì‹¬í•´ì•¼ 합니다. 다ìŒì˜ 예를 봅시다: q = READ_ONCE(a); if (q || 1 > 0) WRITE_ONCE(b, 1); 첫번째 조건만으로는 브랜치 ì¡°ê±´ ì „ì²´ë¥¼ 거짓으로 만들 수 ì—†ê³ ë‘번째 ì¡°ê±´ì€ í•ìƒ ì°¸ì´ê¸° 때문ì—, 컴파ì¼ëŸ¬ëŠ” ì´ ì˜ˆë¥¼ 다ìŒê³¼ ê°™ì´ ë°”ê¿”ì„œ 컨트롤 ì˜ì¡´ì„±ì„ ì—†ì• ë²„ë¦´ 수 있습니다: q = READ_ONCE(a); WRITE_ONCE(b, 1); ì´ ì˜ˆëŠ” 컴파ì¼ëŸ¬ê°€ 코드를 추측으로 ìˆ˜ì •í• ìˆ˜ ì—†ë„ë¡ ë¶„ëª…ížˆ 해야 한다는 ì ì„ ê°•ì¡°í•©ë‹ˆë‹¤. 조금 ë” ì¼ë°˜ì 으로 ë§í•´ì„œ, READ_ONCE() 는 컴파ì¼ëŸ¬ì—게 주어진 로드 오í¼ë ˆì´ì…˜ì„ 위한 코드를 ì •ë§ë¡œ 만들ë„ë¡ í•˜ì§€ë§Œ, 컴파ì¼ëŸ¬ê°€ ê·¸ë ‡ê²Œ 만들어진 ì½”ë“œì˜ ìˆ˜í–‰ 결과를 사용하ë„ë¡ ê°•ì œí•˜ì§€ëŠ” 않습니다. ë˜í•œ, 컨트롤 ì˜ì¡´ì„±ì€ if ë¬¸ì˜ then ì ˆê³¼ else ì ˆì— ëŒ€í•´ì„œë§Œ ì ìš©ë©ë‹ˆë‹¤. ìƒì„¸ížˆ ë§í•´ì„œ, 컨트롤 ì˜ì¡´ì„±ì€ if ë¬¸ì„ ë’¤ë”°ë¥´ëŠ” 코드ì—는 ì ìš©ë˜ì§€ 않습니다: q = READ_ONCE(a); if (q) { WRITE_ONCE(b, 1); } else { WRITE_ONCE(b, 2); } WRITE_ONCE(c, 1); /* BUG: No ordering against the read from 'a'. */ 컴파ì¼ëŸ¬ëŠ” volatile íƒ€ìž…ì— ëŒ€í•œ 액세스를 재배치 í• ìˆ˜ ì—†ê³ ì´ ì¡°ê±´ í•˜ì˜ 'b' ë¡œì˜ ì“°ê¸°ë¥¼ 재배치 í• ìˆ˜ 없기 ë•Œë¬¸ì— ì—¬ê¸°ì— ìˆœì„œ ê·œì¹™ì´ ì¡´ìž¬í•œë‹¤ê³ ì£¼ìž¥í•˜ê³ ì‹¶ì„ ê²ë‹ˆë‹¤. ë¶ˆí–‰ížˆë„ ì´ ê²½ìš°ì—, 컴파ì¼ëŸ¬ëŠ” 다ìŒì˜ ê°€ìƒì˜ pseudo-assembly 언어 코드처럼 'b' ë¡œì˜ ë‘ê°œì˜ ì“°ê¸° 오í¼ë ˆì´ì…˜ì„ conditional-move ì¸ìŠ¤íŠ¸ëŸì…˜ìœ¼ë¡œ 번ì—í• ìˆ˜ 있습니다: ld r1,a cmp r1,$0 cmov,ne r4,$1 cmov,eq r4,$2 st r4,b st $1,c ì™„í™”ëœ ìˆœì„œ ê·œì¹™ì˜ CPU 는 'a' ë¡œë¶€í„°ì˜ ë¡œë“œì™€ 'c' ë¡œì˜ ìŠ¤í† ì–´ 사ì´ì— ì–´ë–¤ ì¢…ë¥˜ì˜ ì˜ì¡´ì„±ë„ 갖지 ì•Šì„ ê²ë‹ˆë‹¤. ì´ ì»¨íŠ¸ë¡¤ ì˜ì¡´ì„±ì€ ë‘ê°œì˜ cmov ì¸ìŠ¤íŠ¸ëŸì…˜ê³¼ ê±°ê¸°ì— ì˜ì¡´í•˜ëŠ” ìŠ¤í† ì–´ ì—게만 ì ìš©ë ê²ë‹ˆë‹¤. 짧게 ë§í•˜ìžë©´, 컨트롤 ì˜ì¡´ì„±ì€ 주어진 if ë¬¸ì˜ then ì ˆê³¼ else ì ˆì—게만 (ê·¸ë¦¬ê³ ì´ ë‘ ì ˆ ë‚´ì—ì„œ 호출ë˜ëŠ” 함수들ì—게까지) ì ìš©ë˜ì§€, ì´ if ë¬¸ì„ ë’¤ë”°ë¥´ëŠ” 코드ì—는 ì ìš©ë˜ì§€ 않습니다. 컨트롤 ì˜ì¡´ì„±ì— ì˜í•´ ì œê³µë˜ëŠ” ì´ ìˆœì„œê·œì¹™ì€ ì´ë¥¼ í¬í•¨í•˜ê³ 있는 CPU ì— ì§€ì—ì 입니다. ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ "Multicopy ì›ìžì„±" ì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. 요약하ìžë©´: (*) 컨트롤 ì˜ì¡´ì„±ì€ ì•žì˜ ë¡œë“œë“¤ì„ ë’¤ì˜ ìŠ¤í† ì–´ë“¤ì— ëŒ€í•´ 순서를 맞춰ì¤ë‹ˆë‹¤. 하지만, ê·¸ ì™¸ì˜ ì–´ë–¤ ìˆœì„œë„ ë³´ìž¥í•˜ì§€ -않습니다-: ì•žì˜ ë¡œë“œì™€ ë’¤ì˜ ë¡œë“œë“¤ 사ì´ì—ë„, ì•žì˜ ìŠ¤í† ì–´ì™€ ë’¤ì˜ ìŠ¤í† ì–´ë“¤ 사ì´ì—ë„ìš”. ì´ëŸ° 다른 í˜•íƒœì˜ ìˆœì„œê°€ 필요하다면 smp_rmb() 나 smp_wmb()를, ë˜ëŠ”, ì•žì˜ ìŠ¤í† ì–´ë“¤ê³¼ ë’¤ì˜ ë¡œë“œë“¤ 사ì´ì˜ 순서를 위해서는 smp_mb() 를 사용하세요. (*) "if" ë¬¸ì˜ ì–‘ê°ˆëž˜ 브랜치가 ê°™ì€ ë³€ìˆ˜ì—ì˜ ë™ì¼í•œ ìŠ¤í† ì–´ë¡œ 시작한다면, ê·¸ ìŠ¤í† ì–´ë“¤ì€ ê° ìŠ¤í† ì–´ ì•žì— smp_mb() 를 넣거나 smp_store_release() 를 사용해서 ìŠ¤í† ì–´ë¥¼ 하는 ì‹ìœ¼ë¡œ 순서를 맞춰줘야 합니다. ì´ ë¬¸ì œë¥¼ 해결하기 위해 "if" ë¬¸ì˜ ì–‘ê°ˆëž˜ ë¸Œëžœì¹˜ì˜ ì‹œìž‘ 지ì ì— barrier() 를 넣는 것만으로는 충분한 í•´ê²°ì´ ë˜ì§€ 않는ë°, ì´ëŠ” ì•žì˜ ì˜ˆì—ì„œ 본것과 ê°™ì´, 컴파ì¼ëŸ¬ì˜ 최ì 화는 barrier() ê°€ ì˜ë¯¸í•˜ëŠ” 바를 ì§€í‚¤ë©´ì„œë„ ì»¨íŠ¸ë¡¤ ì˜ì¡´ì„±ì„ ì†ìƒì‹œí‚¬ 수 있기 때문ì´ë¼ëŠ” ì ì„ ë¶€ë”” 알아ë‘시기 ë°”ëžë‹ˆë‹¤. (*) 컨트롤 ì˜ì¡´ì„±ì€ ì•žì˜ ë¡œë“œì™€ ë’¤ì˜ ìŠ¤í† ì–´ 사ì´ì— 최소 하나ì˜, 실행 ì‹œì ì—ì„œì˜ ì¡°ê±´ê´€ê³„ë¥¼ 필요로 하며, ì´ ì¡°ê±´ê´€ê³„ëŠ” ì•žì˜ ë¡œë“œì™€ 관계ë˜ì–´ì•¼ 합니다. 만약 컴파ì¼ëŸ¬ê°€ ì¡°ê±´ 관계를 최ì 화로 없앨수 있다면, ìˆœì„œë„ ìµœì 화로 ì—†ì• ë²„ë ¸ì„ ê²ë‹ˆë‹¤. READ_ONCE() 와 WRITE_ONCE() ì˜ ì£¼ì˜ ê¹Šì€ ì‚¬ìš©ì€ ì£¼ì–´ì§„ ì¡°ê±´ 관계를 ìœ ì§€í•˜ëŠ”ë° ë„ì›€ì´ ë 수 있습니다. (*) 컨트롤 ì˜ì¡´ì„±ì„ ìœ„í•´ì„ ì»´íŒŒì¼ëŸ¬ê°€ 조건관계를 ì—†ì• ë²„ë¦¬ëŠ” ê²ƒì„ ë§‰ì•„ì•¼ 합니다. ì£¼ì˜ ê¹Šì€ READ_ONCE() 나 atomic{,64}_read() ì˜ ì‚¬ìš©ì´ ì»¨íŠ¸ë¡¤ ì˜ì¡´ì„±ì´ 사ë¼ì§€ì§€ 않게 í•˜ëŠ”ë° ë„ì›€ì„ ì¤„ 수 있습니다. ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ "컴파ì¼ëŸ¬ 배리어" ì„¹ì…˜ì„ ì°¸ê³ í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. (*) 컨트롤 ì˜ì¡´ì„±ì€ 컨트롤 ì˜ì¡´ì„±ì„ 갖는 if ë¬¸ì˜ then ì ˆê³¼ else ì ˆê³¼ ì´ ë‘ ì ˆ ë‚´ì—ì„œ 호출ë˜ëŠ” 함수들ì—만 ì ìš©ë©ë‹ˆë‹¤. 컨트롤 ì˜ì¡´ì„±ì€ 컨트롤 ì˜ì¡´ì„±ì„ 갖는 if ë¬¸ì„ ë’¤ë”°ë¥´ëŠ” 코드ì—는 ì ìš©ë˜ì§€ -않습니다-. (*) 컨트롤 ì˜ì¡´ì„±ì€ 보통 다른 íƒ€ìž…ì˜ ë°°ë¦¬ì–´ë“¤ê³¼ ì§ì„ 맞춰 사용ë©ë‹ˆë‹¤. (*) 컨트롤 ì˜ì¡´ì„±ì€ multicopy ì›ìžì„±ì„ ì œê³µí•˜ì§€ -않습니다-. ëª¨ë“ CPU ë“¤ì´ íŠ¹ì • ìŠ¤í† ì–´ë¥¼ ë™ì‹œì— 보길 ì›í•œë‹¤ë©´, smp_mb() 를 사용하세요. (*) 컴파ì¼ëŸ¬ëŠ” 컨트롤 ì˜ì¡´ì„±ì„ ì´í•´í•˜ê³ 있지 않습니다. ë”°ë¼ì„œ 컴파ì¼ëŸ¬ê°€ ì—¬ëŸ¬ë¶„ì˜ ì½”ë“œë¥¼ ë§ê°€ëœ¨ë¦¬ì§€ ì•Šë„ë¡ í•˜ëŠ”ê±´ ì—¬ëŸ¬ë¶„ì´ í•´ì•¼ 하는 ì¼ìž…니다. SMP 배리어 ì§ë§žì¶”기 -------------------- CPU ê°„ ìƒí˜¸ìž‘ìš©ì„ ë‹¤ë£° ë•Œì— ì¼ë¶€ íƒ€ìž…ì˜ ë©”ëª¨ë¦¬ 배리어는 í•ìƒ ì§ì„ 맞춰 사용ë˜ì–´ì•¼ 합니다. ì ì ˆí•˜ê²Œ ì§ì„ 맞추지 ì•Šì€ ì½”ë“œëŠ” ì‚¬ì‹¤ìƒ ì—ëŸ¬ì— ê°€ê¹ìŠµë‹ˆë‹¤. 범용 ë°°ë¦¬ì–´ë“¤ì€ ë²”ìš© 배리어ë¼ë¦¬ë„ ì§ì„ 맞추지만 multicopy ì›ìžì„±ì´ 없는 ëŒ€ë¶€ë¶„ì˜ ë‹¤ë¥¸ íƒ€ìž…ì˜ ë°°ë¦¬ì–´ë“¤ê³¼ë„ ì§ì„ 맞춥니다. ACQUIRE 배리어는 RELEASE 배리어와 ì§ì„ 맞춥니다만, 둘 다 범용 배리어를 í¬í•¨í•´ 다른 ë°°ë¦¬ì–´ë“¤ê³¼ë„ ì§ì„ 맞출 수 있습니다. 쓰기 배리어는 ë°ì´í„° ì˜ì¡´ì„± 배리어나 컨트롤 ì˜ì¡´ì„±, ACQUIRE 배리어, RELEASE 배리어, ì½ê¸° 배리어, ë˜ëŠ” 범용 배리어와 ì§ì„ 맞춥니다. 비슷하게 ì½ê¸° 배리어나 컨트롤 ì˜ì¡´ì„±, ë˜ëŠ” ë°ì´í„° ì˜ì¡´ì„± 배리어는 쓰기 배리어나 ACQUIRE 배리어, RELEASE 배리어, ë˜ëŠ” 범용 배리어와 ì§ì„ 맞추는ë°, 다ìŒê³¼ 같습니다: CPU 1 CPU 2 =============== =============== WRITE_ONCE(a, 1); <쓰기 배리어> WRITE_ONCE(b, 2); x = READ_ONCE(b); <ì½ê¸° 배리어> y = READ_ONCE(a); ë˜ëŠ”: CPU 1 CPU 2 =============== =============================== a = 1; <쓰기 배리어> WRITE_ONCE(b, &a); x = READ_ONCE(b); <ë°ì´í„° ì˜ì¡´ì„± 배리어> y = *x; ë˜ëŠ”: CPU 1 CPU 2 =============== =============================== r1 = READ_ONCE(y); <범용 배리어> WRITE_ONCE(x, 1); if (r2 = READ_ONCE(x)) { <묵시ì 컨트롤 ì˜ì¡´ì„±> WRITE_ONCE(y, 1); } assert(r1 == 0 || r2 == 0); 기본ì 으로, ì—¬ê¸°ì„œì˜ ì½ê¸° 배리어는 "ë” ì™„í™”ëœ" íƒ€ìž…ì¼ ìˆœ ìžˆì–´ë„ í•ìƒ 존재해야 합니다. [!] 쓰기 배리어 ì•žì˜ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ì€ ì¼ë°˜ì 으로 ì½ê¸° 배리어나 ë°ì´í„° ì˜ì¡´ì„± 배리어 ë’¤ì˜ ë¡œë“œ 오í¼ë ˆì´ì…˜ê³¼ 매치ë 것ì´ê³ , ë°˜ëŒ€ë„ ë§ˆì°¬ê°€ì§€ìž…ë‹ˆë‹¤: CPU 1 CPU 2 =================== =================== WRITE_ONCE(a, 1); }---- --->{ v = READ_ONCE(c); WRITE_ONCE(b, 2); } \ / { w = READ_ONCE(d); <쓰기 배리어> \ <ì½ê¸° 배리어> WRITE_ONCE(c, 3); } / \ { x = READ_ONCE(a); WRITE_ONCE(d, 4); }---- --->{ y = READ_ONCE(b); 메모리 배리어 ì‹œí€€ìŠ¤ì˜ ì˜ˆ ------------------------- 첫째, 쓰기 배리어는 ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì˜ 부분ì 순서 세우기로 ë™ìž‘합니다. ì•„ëž˜ì˜ ì´ë²¤íŠ¸ 시퀀스를 보세요: CPU 1 ======================= STORE A = 1 STORE B = 2 STORE C = 3 <쓰기 배리어> STORE D = 4 STORE E = 5 ì´ ì´ë²¤íŠ¸ 시퀀스는 메모리 ì¼ê´€ì„± ì‹œìŠ¤í…œì— ì›ì†Œë¼ë¦¬ì˜ 순서가 존재하지 않는 집합 { STORE A, STORE B, STORE C } ê°€ ì—ì‹œ ì›ì†Œë¼ë¦¬ì˜ 순서가 존재하지 않는 집합 { STORE D, STORE E } 보다 ë¨¼ì € ì¼ì–´ë‚œ 것으로 ì‹œìŠ¤í…œì˜ ë‚˜ë¨¸ì§€ ìš”ì†Œë“¤ì— ë³´ì´ë„ë¡ ì „ë‹¬ë©ë‹ˆë‹¤: +-------+ : : | | +------+ | |------>| C=3 | } /\ | | : +------+ }----- \ -----> ì‹œìŠ¤í…œì˜ ë‚˜ë¨¸ì§€ ìš”ì†Œì— | | : | A=1 | } \/ 보여질 수 있는 ì´ë²¤íŠ¸ë“¤ | | : +------+ } | CPU 1 | : | B=2 | } | | +------+ } | | wwwwwwwwwwwwwwww } <--- 여기서 쓰기 배리어는 배리어 ì•žì˜ | | +------+ } ëª¨ë“ ìŠ¤í† ì–´ê°€ 배리어 ë’¤ì˜ ìŠ¤í† ì–´ | | : | E=5 | } ì „ì— ë©”ëª¨ë¦¬ ì‹œìŠ¤í…œì— ì „ë‹¬ë˜ë„ë¡ | | : +------+ } 합니다 | |------>| D=4 | } | | +------+ +-------+ : : | | CPU 1 ì— ì˜í•´ 메모리 ì‹œìŠ¤í…œì— ì „ë‹¬ë˜ëŠ” | ì¼ë ¨ì˜ ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ V 둘째, ë°ì´í„° ì˜ì¡´ì„± 배리어는 ë°ì´í„° ì˜ì¡´ì 로드 오í¼ë ˆì´ì…˜ë“¤ì˜ 부분ì 순서 세우기로 ë™ìž‘합니다. ë‹¤ìŒ ì¼ë ¨ì˜ ì´ë²¤íŠ¸ë“¤ì„ 보세요: CPU 1 CPU 2 ======================= ======================= { B = 7; X = 9; Y = 8; C = &Y } STORE A = 1 STORE B = 2 <쓰기 배리어> STORE C = &B LOAD X STORE D = 4 LOAD C (gets &B) LOAD *C (reads B) ì—¬ê¸°ì— ë³„ë‹¤ë¥¸ ê°œìž…ì´ ì—†ë‹¤ë©´, CPU 1 ì˜ ì“°ê¸° 배리어ì—ë„ ë¶ˆêµ¬í•˜ê³ CPU 2 는 CPU 1 ì˜ ì´ë²¤íŠ¸ë“¤ì„ ì™„ì „ížˆ 무작위ì 순서로 ì¸ì§€í•˜ê²Œ ë©ë‹ˆë‹¤: +-------+ : : : : | | +------+ +-------+ | CPU 2 ì— ì¸ì§€ë˜ëŠ” | |------>| B=2 |----- --->| Y->8 | | ì—…ë°ì´íŠ¸ ì´ë²¤íŠ¸ | | : +------+ \ +-------+ | 시퀀스 | CPU 1 | : | A=1 | \ --->| C->&Y | V | | +------+ | +-------+ | | wwwwwwwwwwwwwwww | : : | | +------+ | : : | | : | C=&B |--- | : : +-------+ | | : +------+ \ | +-------+ | | | |------>| D=4 | ----------->| C->&B |------>| | | | +------+ | +-------+ | | +-------+ : : | : : | | | : : | | | : : | CPU 2 | | +-------+ | | 분명히 ìž˜ëª»ëœ ---> | | B->7 |------>| | B ì˜ ê°’ ì¸ì§€ (!) | +-------+ | | | : : | | | +-------+ | | X ì˜ ë¡œë“œê°€ B ì˜ ---> \ | X->9 |------>| | ì¼ê´€ì„± ìœ ì§€ë¥¼ \ +-------+ | | 지연시킴 ----->| B->2 | +-------+ +-------+ : : ì•žì˜ ì˜ˆì—ì„œ, CPU 2 는 (B ì˜ ê°’ì´ ë ) *C ì˜ ê°’ ì½ê¸°ê°€ C ì˜ LOAD ë’¤ì— ì´ì–´ì§ì—ë„ B ê°€ 7 ì´ë¼ëŠ” 결과를 얻습니다. 하지만, 만약 ë°ì´í„° ì˜ì¡´ì„± 배리어가 C ì˜ ë¡œë“œì™€ *C (즉, B) ì˜ ë¡œë“œ 사ì´ì— 있었다면: CPU 1 CPU 2 ======================= ======================= { B = 7; X = 9; Y = 8; C = &Y } STORE A = 1 STORE B = 2 <쓰기 배리어> STORE C = &B LOAD X STORE D = 4 LOAD C (gets &B) <ë°ì´í„° ì˜ì¡´ì„± 배리어> LOAD *C (reads B) 다ìŒê³¼ ê°™ì´ ë©ë‹ˆë‹¤: +-------+ : : : : | | +------+ +-------+ | |------>| B=2 |----- --->| Y->8 | | | : +------+ \ +-------+ | CPU 1 | : | A=1 | \ --->| C->&Y | | | +------+ | +-------+ | | wwwwwwwwwwwwwwww | : : | | +------+ | : : | | : | C=&B |--- | : : +-------+ | | : +------+ \ | +-------+ | | | |------>| D=4 | ----------->| C->&B |------>| | | | +------+ | +-------+ | | +-------+ : : | : : | | | : : | | | : : | CPU 2 | | +-------+ | | | | X->9 |------>| | | +-------+ | | C ë¡œì˜ ìŠ¤í† ì–´ ì•žì˜ ---> \ ddddddddddddddddd | | ëª¨ë“ ì´ë²¤íŠ¸ 결과가 \ +-------+ | | ë’¤ì˜ ë¡œë“œì—게 ----->| B->2 |------>| | ë³´ì´ê²Œ ê°•ì œí•œë‹¤ +-------+ | | : : +-------+ 셋째, ì½ê¸° 배리어는 로드 오í¼ë ˆì´ì…˜ë“¤ì—ì˜ ë¶€ë¶„ì 순서 세우기로 ë™ìž‘합니다. ì•„ëž˜ì˜ ì¼ë ¨ì˜ ì´ë²¤íŠ¸ë¥¼ 봅시다: CPU 1 CPU 2 ======================= ======================= { A = 0, B = 9 } STORE A=1 <쓰기 배리어> STORE B=2 LOAD B LOAD A CPU 1 ì€ ì“°ê¸° 배리어를 쳤지만, 별다른 ê°œìž…ì´ ì—†ë‹¤ë©´ CPU 2 는 CPU 1 ì—ì„œ 행해진 ì´ë²¤íŠ¸ì˜ 결과를 무작위ì 순서로 ì¸ì§€í•˜ê²Œ ë©ë‹ˆë‹¤. +-------+ : : : : | | +------+ +-------+ | |------>| A=1 |------ --->| A->0 | | | +------+ \ +-------+ | CPU 1 | wwwwwwwwwwwwwwww \ --->| B->9 | | | +------+ | +-------+ | |------>| B=2 |--- | : : | | +------+ \ | : : +-------+ +-------+ : : \ | +-------+ | | ---------->| B->2 |------>| | | +-------+ | CPU 2 | | | A->0 |------>| | | +-------+ | | | : : +-------+ \ : : \ +-------+ ---->| A->1 | +-------+ : : 하지만, 만약 ì½ê¸° 배리어가 B ì˜ ë¡œë“œì™€ A ì˜ ë¡œë“œ 사ì´ì— 존재한다면: CPU 1 CPU 2 ======================= ======================= { A = 0, B = 9 } STORE A=1 <쓰기 배리어> STORE B=2 LOAD B <ì½ê¸° 배리어> LOAD A CPU 1 ì— ì˜í•´ 만들어진 부분ì 순서가 CPU 2 ì—ë„ ê·¸ëŒ€ë¡œ ì¸ì§€ë©ë‹ˆë‹¤: +-------+ : : : : | | +------+ +-------+ | |------>| A=1 |------ --->| A->0 | | | +------+ \ +-------+ | CPU 1 | wwwwwwwwwwwwwwww \ --->| B->9 | | | +------+ | +-------+ | |------>| B=2 |--- | : : | | +------+ \ | : : +-------+ +-------+ : : \ | +-------+ | | ---------->| B->2 |------>| | | +-------+ | CPU 2 | | : : | | | : : | | 여기서 ì½ê¸° 배리어는 ----> \ rrrrrrrrrrrrrrrrr | | B ë¡œì˜ ìŠ¤í† ì–´ ì „ì˜ \ +-------+ | | ëª¨ë“ ê²°ê³¼ë¥¼ CPU 2 ì— ---->| A->1 |------>| | ë³´ì´ë„ë¡ í•œë‹¤ +-------+ | | : : +-------+ ë” ì™„ë²½í•œ ì„¤ëª…ì„ ìœ„í•´, A ì˜ ë¡œë“œê°€ ì½ê¸° 배리어 ì•žê³¼ ë’¤ì— ìžˆìœ¼ë©´ 어떻게 ë 지 ìƒê°í•´ 봅시다: CPU 1 CPU 2 ======================= ======================= { A = 0, B = 9 } STORE A=1 <쓰기 배리어> STORE B=2 LOAD B LOAD A [first load of A] <ì½ê¸° 배리어> LOAD A [second load of A] A ì˜ ë¡œë“œ ë‘개가 ëª¨ë‘ B ì˜ ë¡œë“œ ë’¤ì— ìžˆì§€ë§Œ, 서로 다른 ê°’ì„ ì–»ì–´ì˜¬ 수 있습니다: +-------+ : : : : | | +------+ +-------+ | |------>| A=1 |------ --->| A->0 | | | +------+ \ +-------+ | CPU 1 | wwwwwwwwwwwwwwww \ --->| B->9 | | | +------+ | +-------+ | |------>| B=2 |--- | : : | | +------+ \ | : : +-------+ +-------+ : : \ | +-------+ | | ---------->| B->2 |------>| | | +-------+ | CPU 2 | | : : | | | : : | | | +-------+ | | | | A->0 |------>| 1st | | +-------+ | | 여기서 ì½ê¸° 배리어는 ----> \ rrrrrrrrrrrrrrrrr | | B ë¡œì˜ ìŠ¤í† ì–´ ì „ì˜ \ +-------+ | | ëª¨ë“ ê²°ê³¼ë¥¼ CPU 2 ì— ---->| A->1 |------>| 2nd | ë³´ì´ë„ë¡ í•œë‹¤ +-------+ | | : : +-------+ 하지만 CPU 1 ì—ì„œì˜ A ì—…ë°ì´íŠ¸ëŠ” ì½ê¸° 배리어가 완료ë˜ê¸° ì „ì—ë„ ë³´ì¼ ìˆ˜ë„ ìžˆê¸´ 합니다: +-------+ : : : : | | +------+ +-------+ | |------>| A=1 |------ --->| A->0 | | | +------+ \ +-------+ | CPU 1 | wwwwwwwwwwwwwwww \ --->| B->9 | | | +------+ | +-------+ | |------>| B=2 |--- | : : | | +------+ \ | : : +-------+ +-------+ : : \ | +-------+ | | ---------->| B->2 |------>| | | +-------+ | CPU 2 | | : : | | \ : : | | \ +-------+ | | ---->| A->1 |------>| 1st | +-------+ | | rrrrrrrrrrrrrrrrr | | +-------+ | | | A->1 |------>| 2nd | +-------+ | | : : +-------+ 여기서 보장ë˜ëŠ” ê±´, 만약 B ì˜ ë¡œë“œê°€ B == 2 ë¼ëŠ” 결과를 봤다면, A ì—ì˜ ë‘번째 로드는 í•ìƒ A == 1 ì„ ë³´ê²Œ ë 것ì´ë¼ëŠ” ê²ë‹ˆë‹¤. A ì—ì˜ ì²«ë²ˆì§¸ 로드ì—는 그런 ë³´ìž¥ì´ ì—†ìŠµë‹ˆë‹¤; A == 0 ì´ê±°ë‚˜ A == 1 ì´ê±°ë‚˜ 둘 중 í•˜ë‚˜ì˜ ê²°ê³¼ë¥¼ 보게 ë ê²ë‹ˆë‹¤. ì½ê¸° 메모리 배리어 VS 로드 예측 ------------------------------- ë§Žì€ CPUë“¤ì´ ë¡œë“œë¥¼ 예측ì 으로 (speculatively) 합니다: ì–´ë–¤ ë°ì´í„°ë¥¼ 메모리ì—ì„œ 로드해야 하게 ë 지 ì˜ˆì¸¡ì„ í–ˆë‹¤ë©´, 해당 ë°ì´í„°ë¥¼ 로드하는 ì¸ìŠ¤íŠ¸ëŸì…˜ì„ ì‹¤ì œë¡œëŠ” ì•„ì§ ë§Œë‚˜ì§€ 않았ë”ë¼ë„ 다른 로드 ìž‘ì—…ì´ ì—†ì–´ 버스 (bus) ê°€ 아무 ì¼ë„ í•˜ê³ ìžˆì§€ 않다면, ê·¸ ë°ì´í„°ë¥¼ 로드합니다. ì´í›„ì— ì‹¤ì œ 로드 ì¸ìŠ¤íŠ¸ëŸì…˜ì´ 실행ë˜ë©´ CPU ê°€ ì´ë¯¸ ê·¸ ê°’ì„ ê°€ì§€ê³ ìžˆê¸° ë•Œë¬¸ì— ê·¸ 로드 ì¸ìŠ¤íŠ¸ëŸì…˜ì€ 즉시 완료ë©ë‹ˆë‹¤. 해당 CPU 는 ì‹¤ì œë¡œëŠ” ê·¸ ê°’ì´ í•„ìš”ì¹˜ 않았다는 ì‚¬ì‹¤ì´ ë‚˜ì¤‘ì— ë“œëŸ¬ë‚ ìˆ˜ë„ ìžˆëŠ”ë° - 해당 로드 ì¸ìŠ¤íŠ¸ëŸì…˜ì´ 브랜치로 우회ë˜ê±°ë‚˜ í–ˆì„ ìˆ˜ ìžˆê² ì£ - , ê·¸ë ‡ê²Œ ë˜ë©´ ì•žì„œ ì½ì–´ë‘” ê°’ì„ ë²„ë¦¬ê±°ë‚˜ ë‚˜ì¤‘ì˜ ì‚¬ìš©ì„ ìœ„í•´ ìºì‹œì— 넣어둘 수 있습니다. 다ìŒì„ ìƒê°í•´ 봅시다: CPU 1 CPU 2 ======================= ======================= LOAD B DIVIDE } 나누기 ëª…ë ¹ì€ ì¼ë°˜ì 으로 DIVIDE } 긴 ì‹œê°„ì„ í•„ìš”ë¡œ 합니다 LOAD A 는 ì´ë ‡ê²Œ ë 수 있습니다: : : +-------+ +-------+ | | --->| B->2 |------>| | +-------+ | CPU 2 | : :DIVIDE | | +-------+ | | 나누기 하ëŠë¼ ë°”ìœ ---> --->| A->0 |~~~~ | | CPU 는 A ì˜ LOAD 를 +-------+ ~ | | 예측해서 수행한다 : : ~ | | : :DIVIDE | | : : ~ | | 나누기가 ë나면 ---> ---> : : ~-->| | CPU 는 해당 LOAD 를 : : | | ì¦‰ê° ì™„ë£Œí•œë‹¤ : : +-------+ ì½ê¸° 배리어나 ë°ì´í„° ì˜ì¡´ì„± 배리어를 ë‘번째 로드 ì§ì „ì— ë†“ëŠ”ë‹¤ë©´: CPU 1 CPU 2 ======================= ======================= LOAD B DIVIDE DIVIDE <ì½ê¸° 배리어> LOAD A 예측으로 얻어진 ê°’ì€ ì‚¬ìš©ëœ ë°°ë¦¬ì–´ì˜ íƒ€ìž…ì— ë”°ë¼ì„œ 해당 ê°’ì´ ì˜³ì€ì§€ ê²€í† ë˜ê²Œ ë©ë‹ˆë‹¤. 만약 해당 메모리 ì˜ì—ì— ë³€í™”ê°€ 없었다면, 예측으로 얻어ë‘ì—ˆë˜ ê°’ì´ ì‚¬ìš©ë©ë‹ˆë‹¤: : : +-------+ +-------+ | | --->| B->2 |------>| | +-------+ | CPU 2 | : :DIVIDE | | +-------+ | | 나누기 하ëŠë¼ ë°”ìœ ---> --->| A->0 |~~~~ | | CPU 는 A ì˜ LOAD 를 +-------+ ~ | | 예측한다 : : ~ | | : :DIVIDE | | : : ~ | | : : ~ | | rrrrrrrrrrrrrrrr~ | | : : ~ | | : : ~-->| | : : | | : : +-------+ 하지만 다른 CPU ì—ì„œ ì—…ë°ì´íŠ¸ë‚˜ 무효화가 있었다면, ê·¸ ì˜ˆì¸¡ì€ ë¬´íš¨í™”ë˜ê³ ê·¸ ê°’ì€ ë‹¤ì‹œ ì½í˜€ì§‘니다: : : +-------+ +-------+ | | --->| B->2 |------>| | +-------+ | CPU 2 | : :DIVIDE | | +-------+ | | 나누기 하ëŠë¼ ë°”ìœ ---> --->| A->0 |~~~~ | | CPU 는 A ì˜ LOAD 를 +-------+ ~ | | 예측한다 : : ~ | | : :DIVIDE | | : : ~ | | : : ~ | | rrrrrrrrrrrrrrrrr | | +-------+ | | 예측성 ë™ìž‘ì€ ë¬´íš¨í™” ë˜ê³ ---> --->| A->1 |------>| | ì—…ë°ì´íŠ¸ëœ ê°’ì´ ë‹¤ì‹œ ì½í˜€ì§„다 +-------+ | | : : +-------+ MULTICOPY ì›ìžì„± ---------------- Multicopy ì›ìžì„±ì€ ì‹¤ì œì˜ ì»´í“¨í„° 시스템ì—ì„œ í•ìƒ ì œê³µë˜ì§€ëŠ” 않는, 순서 ë§žì¶”ê¸°ì— ëŒ€í•œ ìƒë‹¹ížˆ ì§ê´€ì ì¸ ê°œë…으로, íŠ¹ì • ìŠ¤í† ì–´ê°€ ëª¨ë“ CPU 들ì—게 ë™ì‹œì— 보여지게 ë¨ì„, 달리 ë§í•˜ìžë©´ ëª¨ë“ CPU ë“¤ì´ ëª¨ë“ ìŠ¤í† ì–´ë“¤ì´ ë³´ì—¬ì§€ëŠ” 순서를 ë™ì˜í•˜ê²Œ ë˜ëŠ” 것입니다. 하지만, ì™„ì „í•œ multicopy ì›ìžì„±ì˜ ì‚¬ìš©ì€ ê°€ì¹˜ìžˆëŠ” 하드웨어 최ì í™”ë“¤ì„ ë¬´ëŠ¥í•˜ê²Œ 만들어버릴 수 있어서, 보다 ì™„í™”ëœ í˜•íƒœì˜ ``다른 multicopy ì›ìžì„±'' ë¼ëŠ” ì´ë¦„ì˜, íŠ¹ì • ìŠ¤í† ì–´ê°€ ëª¨ë“ -다른- CPU 들ì—게는 ë™ì‹œì— 보여지게 하는 ë³´ìž¥ì„ ëŒ€ì‹ ì œê³µí•©ë‹ˆë‹¤. ì´ ë¬¸ì„œì˜ ë’·ë¶€ë¶„ë“¤ì€ ì´ ì™„í™”ëœ í˜•íƒœì— ëŒ€í•´ 논하게 ë©ë‹ˆë‹¤ë§Œ, 단순히 ``multicopy ì›ìžì„±'' ì´ë¼ê³ ë¶€ë¥´ê² ìŠµë‹ˆë‹¤. 다ìŒì˜ 예가 multicopy ì›ìžì„±ì„ 보입니다: CPU 1 CPU 2 CPU 3 ======================= ======================= ======================= { X = 0, Y = 0 } STORE X=1 r1=LOAD X (reads 1) LOAD Y (reads 1) <범용 배리어> <ì½ê¸° 배리어> STORE Y=r1 LOAD X CPU 2 ì˜ Y ë¡œì˜ ìŠ¤í† ì–´ì— ì‚¬ìš©ë˜ëŠ” X ë¡œë“œì˜ ê²°ê³¼ê°€ 1 ì´ì—ˆê³ CPU 3 ì˜ Y 로드가 1ì„ ë¦¬í„´í–ˆë‹¤ê³ í•´ë´…ì‹œë‹¤. ì´ëŠ” CPU 1 ì˜ X ë¡œì˜ ìŠ¤í† ì–´ê°€ CPU 2 ì˜ X ë¡œë¶€í„°ì˜ ë¡œë“œë¥¼ ì•žì„œê³ CPU 2 ì˜ Y ë¡œì˜ ìŠ¤í† ì–´ê°€ CPU 3 ì˜ Y ë¡œë¶€í„°ì˜ ë¡œë“œë¥¼ ì•žì„¬ì„ ì˜ë¯¸í•©ë‹ˆë‹¤. ë˜í•œ, ì—¬ê¸°ì„œì˜ ë©”ëª¨ë¦¬ ë°°ë¦¬ì–´ë“¤ì€ CPU 2 ê°€ ìžì‹ ì˜ ë¡œë“œë¥¼ ìžì‹ ì˜ ìŠ¤í† ì–´ ì „ì— ìˆ˜í–‰í•˜ê³ , CPU 3 ê°€ Y ë¡œë¶€í„°ì˜ ë¡œë“œë¥¼ X ë¡œë¶€í„°ì˜ ë¡œë“œ ì „ì— ìˆ˜í–‰í•¨ì„ ë³´ìž¥í•©ë‹ˆë‹¤. 그럼 "CPU 3 ì˜ X ë¡œë¶€í„°ì˜ ë¡œë“œëŠ” 0 ì„ ë¦¬í„´í• ìˆ˜ 있ì„까요?" CPU 3 ì˜ X 로드가 CPU 2 ì˜ ë¡œë“œë³´ë‹¤ ë’¤ì— ì´ë£¨ì–´ì¡Œìœ¼ë¯€ë¡œ, CPU 3 ì˜ X ë¡œë¶€í„°ì˜ ë¡œë“œëŠ” 1 ì„ ë¦¬í„´í•œë‹¤ê³ ì˜ˆìƒí•˜ëŠ”게 당연합니다. ì´ëŸ° 예ìƒì€ multicopy ì›ìžì„±ìœ¼ë¡œë¶€í„° 나옵니다: CPU B ì—ì„œ ìˆ˜í–‰ëœ ë¡œë“œê°€ CPU A ì˜ ê°™ì€ ë³€ìˆ˜ë¡œë¶€í„°ì˜ ë¡œë“œë¥¼ 뒤따른다면 (ê·¸ë¦¬ê³ CPU A ê°€ ìžì‹ ì´ ì½ì€ 값으로 ë¨¼ì € 해당 ë³€ìˆ˜ì— ìŠ¤í† ì–´ 하지 않았다면) multicopy ì›ìžì„±ì„ ì œê³µí•˜ëŠ” 시스템ì—서는, CPU B ì˜ ë¡œë“œê°€ CPU A ì˜ ë¡œë“œì™€ ê°™ì€ ê°’ ë˜ëŠ” ê·¸ 나중 ê°’ì„ ë¦¬í„´í•´ì•¼ë§Œ 합니다. 하지만, 리눅스 커ë„ì€ ì‹œìŠ¤í…œë“¤ì´ multicopy ì›ìžì„±ì„ ì œê³µí• ê²ƒì„ ìš”êµ¬í•˜ì§€ 않습니다. ì•žì˜ ë²”ìš© 메모리 ë°°ë¦¬ì–´ì˜ ì‚¬ìš©ì€ ëª¨ë“ multicopy ì›ìžì„±ì˜ ë¶€ì¡±ì„ ë³´ìƒí•´ì¤ë‹ˆë‹¤. ì•žì˜ ì˜ˆì—ì„œ, CPU 2 ì˜ X ë¡œë¶€í„°ì˜ ë¡œë“œê°€ 1 ì„ ë¦¬í„´í–ˆê³ CPU 3 ì˜ Y ë¡œë¶€í„°ì˜ ë¡œë“œê°€ 1 ì„ ë¦¬í„´í–ˆë‹¤ë©´, CPU 3 ì˜ X ë¡œë¶€í„°ì˜ ë¡œë“œëŠ” 1ì„ ë¦¬í„´í•´ì•¼ë§Œ 합니다. 하지만, ì˜ì¡´ì„±, ì½ê¸° 배리어, 쓰기 배리어는 í•ìƒ non-multicopy ì›ìžì„±ì„ ë³´ìƒí•´ 주지는 않습니다. 예를 들어, CPU 2 ì˜ ë²”ìš© 배리어가 ì•žì˜ ì˜ˆì—ì„œ 사ë¼ì ¸ì„œ 아래처럼 ë°ì´í„° ì˜ì¡´ì„±ë§Œ 남게 ë˜ì—ˆë‹¤ê³ 해봅시다: CPU 1 CPU 2 CPU 3 ======================= ======================= ======================= { X = 0, Y = 0 } STORE X=1 r1=LOAD X (reads 1) LOAD Y (reads 1) <ë°ì´í„° ì˜ì¡´ì„±> <ì½ê¸° 배리어> STORE Y=r1 LOAD X (reads 0) ì´ ë³€í™”ëŠ” non-multicopy ì›ìžì„±ì´ 만연하게 합니다: ì´ ì˜ˆì—ì„œ, CPU 2 ì˜ X ë¡œë¶€í„°ì˜ ë¡œë“œê°€ 1ì„ ë¦¬í„´í•˜ê³ , CPU 3 ì˜ Y ë¡œë¶€í„°ì˜ ë¡œë“œê°€ 1 ì„ ë¦¬í„´í•˜ëŠ”ë°, CPU 3 ì˜ X ë¡œë¶€í„°ì˜ ë¡œë“œê°€ 0 ì„ ë¦¬í„´í•˜ëŠ”ê²Œ ì™„ì „ížˆ 합법ì 입니다. 핵심ì€, CPU 2 ì˜ ë°ì´í„° ì˜ì¡´ì„±ì´ ìžì‹ ì˜ ë¡œë“œì™€ ìŠ¤í† ì–´ë¥¼ 순서짓지만, CPU 1 ì˜ ìŠ¤í† ì–´ì— ëŒ€í•œ 순서는 보장하지 않는다는 것입니다. ë”°ë¼ì„œ, ì´ ì˜ˆì œê°€ CPU 1 ê³¼ CPU 2 ê°€ ìŠ¤í† ì–´ 버í¼ë‚˜ í•œ ìˆ˜ì¤€ì˜ ìºì‹œë¥¼ ê³µìœ í•˜ëŠ”, multicopy ì›ìžì„±ì„ ì œê³µí•˜ì§€ 않는 시스템ì—ì„œ 수행ëœë‹¤ë©´ CPU 2 는 CPU 1 ì˜ ì“°ê¸°ì— ì´ë¥¸ ì ‘ê·¼ì„ í• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ë”°ë¼ì„œ, ëª¨ë“ CPU ë“¤ì´ ì—¬ëŸ¬ ì ‘ê·¼ë“¤ì˜ ì¡°í•©ëœ ìˆœì„œì— ëŒ€í•´ì„œ ë™ì˜í•˜ê²Œ 하기 위해서는 범용 배리어가 필요합니다. 범용 배리어는 non-multicopy ì›ìžì„±ë§Œ ë³´ìƒí• 수 있는게 아니ë¼, -ëª¨ë“ - CPU ë“¤ì´ -ëª¨ë“ - 오í¼ë ˆì´ì…˜ë“¤ì˜ 순서를 ë™ì¼í•˜ê²Œ ì¸ì‹í•˜ê²Œ 하는 추가ì ì¸ ìˆœì„œ ë³´ìž¥ì„ ë§Œë“¤ì–´ëƒ…ë‹ˆë‹¤. 반대로, release-acquire ì§ì˜ ì—°ê²°ì€ ì´ëŸ° 추가ì ì¸ ìˆœì„œëŠ” ì œê³µí•˜ì§€ 않는ë°, 해당 ì—°ê²°ì— ë“¤ì–´ìžˆëŠ” CPU ë“¤ë§Œì´ ë©”ëª¨ë¦¬ ì ‘ê·¼ì˜ ì¡°í•©ëœ ìˆœì„œì— ëŒ€í•´ ë™ì˜í• 것으로 보장ë¨ì„ ì˜ë¯¸í•©ë‹ˆë‹¤. 예를 들어, 존경스런 Herman Hollerith ì˜ ì½”ë“œë¥¼ C 코드로 변환하면: int u, v, x, y, z; void cpu0(void) { r0 = smp_load_acquire(&x); WRITE_ONCE(u, 1); smp_store_release(&y, 1); } void cpu1(void) { r1 = smp_load_acquire(&y); r4 = READ_ONCE(v); r5 = READ_ONCE(u); smp_store_release(&z, 1); } void cpu2(void) { r2 = smp_load_acquire(&z); smp_store_release(&x, 1); } void cpu3(void) { WRITE_ONCE(v, 1); smp_mb(); r3 = READ_ONCE(u); } cpu0(), cpu1(), ê·¸ë¦¬ê³ cpu2() 는 smp_store_release()/smp_load_acquire() ìŒì˜ ì—°ê²°ì— ì°¸ì—¬ë˜ì–´ 있으므로, 다ìŒê³¼ ê°™ì€ ê²°ê³¼ëŠ” 나오지 ì•Šì„ ê²ë‹ˆë‹¤: r0 == 1 && r1 == 1 && r2 == 1 ë” ë‚˜ì•„ê°€ì„œ, cpu0() 와 cpu1() 사ì´ì˜ release-acquire 관계로 ì¸í•´, cpu1() ì€ cpu0() ì˜ ì“°ê¸°ë¥¼ ë´ì•¼ë§Œ 하므로, 다ìŒê³¼ ê°™ì€ ê²°ê³¼ë„ ì—†ì„ ê²ë‹ˆë‹¤: r1 == 1 && r5 == 0 하지만, release-acquire ì— ì˜í•´ ì œê³µë˜ëŠ” 순서는 해당 ì—°ê²°ì— ë™ì°¸í•œ CPU 들ì—만 ì ìš©ë˜ë¯€ë¡œ cpu3() ì—, ì ì–´ë„ ìŠ¤í† ì–´ë“¤ 외ì—는 ì ìš©ë˜ì§€ 않습니다. ë”°ë¼ì„œ, 다ìŒê³¼ ê°™ì€ ê²°ê³¼ê°€ 가능합니다: r0 == 0 && r1 == 1 && r2 == 1 && r3 == 0 && r4 == 0 비슷하게, 다ìŒê³¼ ê°™ì€ ê²°ê³¼ë„ ê°€ëŠ¥í•©ë‹ˆë‹¤: r0 == 0 && r1 == 1 && r2 == 1 && r3 == 0 && r4 == 0 && r5 == 1 cpu0(), cpu1(), ê·¸ë¦¬ê³ cpu2() 는 ê·¸ë“¤ì˜ ì½ê¸°ì™€ 쓰기를 순서대로 보게 ë˜ì§€ë§Œ, release-acquire ì²´ì¸ì— 관여ë˜ì§€ ì•Šì€ CPU ë“¤ì€ ê·¸ ìˆœì„œì— ì´ê²¬ì„ 가질 수 있습니다. ì´ëŸ° ì´ê²¬ì€ smp_load_acquire() 와 smp_store_release() ì˜ êµ¬í˜„ì— ì‚¬ìš©ë˜ëŠ” ì™„í™”ëœ ë©”ëª¨ë¦¬ 배리어 ì¸ìŠ¤íŠ¸ëŸì…˜ë“¤ì€ í•ìƒ 배리어 ì•žì˜ ìŠ¤í† ì–´ë“¤ì„ ë’¤ì˜ ë¡œë“œë“¤ì— ì•žì„¸ìš¸ 필요는 없다는 사실ì—ì„œ 기ì¸í•©ë‹ˆë‹¤. ì´ ë§ì€ cpu3() 는 cpu0() ì˜ u ë¡œì˜ ìŠ¤í† ì–´ë¥¼ cpu1() ì˜ v ë¡œë¶€í„°ì˜ ë¡œë“œ ë’¤ì— ì¼ì–´ë‚œ 것으로 ë³¼ 수 있다는 뜻입니다, cpu0() 와 cpu1() ì€ ì´ ë‘ ì˜¤í¼ë ˆì´ì…˜ì´ ì˜ë„ëœ ìˆœì„œëŒ€ë¡œ ì¼ì–´ë‚¬ìŒì— ëª¨ë‘ ë™ì˜í•˜ëŠ”ë°ë„ ë§ìž…니다. 하지만, smp_load_acquire() 는 ë§ˆìˆ ì´ ì•„ë‹˜ì„ ëª…ì‹¬í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. 구체ì 으로, ì´ í•¨ìˆ˜ëŠ” 단순히 순서 ê·œì¹™ì„ ì§€í‚¤ë©° ì¸ìžë¡œë¶€í„°ì˜ ì½ê¸°ë¥¼ 수행합니다. ì´ê²ƒì€ ì–´ë–¤ íŠ¹ì •í•œ ê°’ì´ ì½íž 것ì¸ì§€ëŠ” 보장하지 -않습니다-. ë”°ë¼ì„œ, 다ìŒê³¼ ê°™ì€ ê²°ê³¼ë„ ê°€ëŠ¥í•©ë‹ˆë‹¤: r0 == 0 && r1 == 0 && r2 == 0 && r5 == 0 ì´ëŸ° 결과는 ì–´ë–¤ ê²ƒë„ ìž¬ë°°ì¹˜ ë˜ì§€ 않는, 순차ì ì¼ê´€ì„±ì„ 가진 ê°€ìƒì˜ 시스템ì—ì„œë„ ì¼ì–´ë‚ 수 있ìŒì„ 기억해 ë‘시기 ë°”ëžë‹ˆë‹¤. 다시 ë§í•˜ì§€ë§Œ, ë‹¹ì‹ ì˜ ì½”ë“œê°€ ëª¨ë“ ì˜¤í¼ë ˆì´ì…˜ë“¤ì˜ ì™„ì „í•œ 순서를 필요로 한다면, 범용 배리어를 사용하ì‹ì‹œì˜¤. ================== 명시ì ì»¤ë„ ë°°ë¦¬ì–´ ================== 리눅스 커ë„ì€ ì„œë¡œ 다른 단계ì—ì„œ ë™ìž‘하는 다양한 ë°°ë¦¬ì–´ë“¤ì„ ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤: (*) 컴파ì¼ëŸ¬ 배리어. (*) CPU 메모리 배리어. (*) MMIO 쓰기 배리어. 컴파ì¼ëŸ¬ 배리어 --------------- 리눅스 커ë„ì€ ì»´íŒŒì¼ëŸ¬ê°€ 메모리 액세스를 재배치 하는 ê²ƒì„ ë§‰ì•„ì£¼ëŠ” 명시ì ì¸ ì»´íŒŒì¼ëŸ¬ 배리어를 ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤: barrier(); ì´ê±´ 범용 배리어입니다 -- barrier() ì˜ ì½ê¸°-ì½ê¸° 나 쓰기-쓰기 ë³€ì¢…ì€ ì—†ìŠµë‹ˆë‹¤. 하지만, READ_ONCE() 와 WRITE_ONCE() 는 íŠ¹ì • ì•¡ì„¸ìŠ¤ë“¤ì— ëŒ€í•´ì„œë§Œ ë™ìž‘하는 barrier() ì˜ ì™„í™”ëœ í˜•íƒœë¡œ ë³¼ 수 있습니다. barrier() 함수는 다ìŒê³¼ ê°™ì€ íš¨ê³¼ë¥¼ 갖습니다: (*) 컴파ì¼ëŸ¬ê°€ barrier() ë’¤ì˜ ì•¡ì„¸ìŠ¤ë“¤ì´ barrier() ì•žì˜ ì•¡ì„¸ìŠ¤ë³´ë‹¤ 앞으로 재배치ë˜ì§€ 못하게 합니다. 예를 들어, ì¸í„°ëŸ½íŠ¸ 핸들러 코드와 ì¸í„°ëŸ½íŠ¸ 당한 코드 사ì´ì˜ í†µì‹ ì„ ì‹ ì¤‘ížˆ 하기 위해 사용ë 수 있습니다. (*) 루프ì—ì„œ, 컴파ì¼ëŸ¬ê°€ 루프 ì¡°ê±´ì— ì‚¬ìš©ëœ ë³€ìˆ˜ë¥¼ 매 ì´í„°ë ˆì´ì…˜ë§ˆë‹¤ 메모리ì—ì„œ 로드하지 ì•Šì•„ë„ ë˜ë„ë¡ ìµœì í™” 하는걸 방지합니다. READ_ONCE() 와 WRITE_ONCE() 함수는 싱글 ì“°ë ˆë“œ 코드ì—서는 ë¬¸ì œ 없지만 ë™ì‹œì„±ì´ 있는 코드ì—서는 ë¬¸ì œê°€ ë 수 있는 ëª¨ë“ ìµœì 화를 막습니다. ì´ëŸ° ë¥˜ì˜ ìµœì í™”ì— ëŒ€í•œ 예를 몇가지 들어보면 다ìŒê³¼ 같습니다: (*) 컴파ì¼ëŸ¬ëŠ” ê°™ì€ ë³€ìˆ˜ì— ëŒ€í•œ 로드와 ìŠ¤í† ì–´ë¥¼ 재배치 í• ìˆ˜ ìžˆê³ , ì–´ë–¤ 경우ì—는 CPUê°€ ê°™ì€ ë³€ìˆ˜ë¡œë¶€í„°ì˜ ë¡œë“œë“¤ì„ ìž¬ë°°ì¹˜í• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ëŠ” 다ìŒì˜ 코드가: a[0] = x; a[1] = x; x ì˜ ì˜ˆì „ ê°’ì´ a[1] ì—, 새 ê°’ì´ a[0] ì— ìžˆê²Œ í• ìˆ˜ 있다는 뜻입니다. 컴파ì¼ëŸ¬ì™€ CPUê°€ ì´ëŸ° ì¼ì„ 못하게 í•˜ë ¤ë©´ 다ìŒê³¼ ê°™ì´ í•´ì•¼ 합니다: a[0] = READ_ONCE(x); a[1] = READ_ONCE(x); 즉, READ_ONCE() 와 WRITE_ONCE() 는 여러 CPU ì—ì„œ í•˜ë‚˜ì˜ ë³€ìˆ˜ì— ê°€í•´ì§€ëŠ” ì•¡ì„¸ìŠ¤ë“¤ì— ìºì‹œ ì¼ê´€ì„±ì„ ì œê³µí•©ë‹ˆë‹¤. (*) 컴파ì¼ëŸ¬ëŠ” ê°™ì€ ë³€ìˆ˜ì— ëŒ€í•œ ì—°ì†ì ì¸ ë¡œë“œë“¤ì„ ë³‘í•©í• ìˆ˜ 있습니다. 그런 병합 작업으로 컴파ì¼ëŸ¬ëŠ” 다ìŒì˜ 코드를: while (tmp = a) do_something_with(tmp); 다ìŒê³¼ ê°™ì´, 싱글 ì“°ë ˆë“œ 코드ì—서는 ë§ì´ ë˜ì§€ë§Œ 개발ìžì˜ ì˜ë„와 ì „í˜€ 맞지 않는 방향으로 "최ì í™”" í• ìˆ˜ 있습니다: if (tmp = a) for (;;) do_something_with(tmp); 컴파ì¼ëŸ¬ê°€ ì´ëŸ° ì§“ì„ í•˜ì§€ 못하게 í•˜ë ¤ë©´ READ_ONCE() 를 사용하세요: while (tmp = READ_ONCE(a)) do_something_with(tmp); (*) 예컨대 ë ˆì§€ìŠ¤í„° ì‚¬ìš©ëŸ‰ì´ ë§Žì•„ 컴파ì¼ëŸ¬ê°€ ëª¨ë“ ë°ì´í„°ë¥¼ ë ˆì§€ìŠ¤í„°ì— ë‹´ì„ ìˆ˜ 없는 경우, 컴파ì¼ëŸ¬ëŠ” 변수를 다시 ë¡œë“œí• ìˆ˜ 있습니다. ë”°ë¼ì„œ 컴파ì¼ëŸ¬ëŠ” ì•žì˜ ì˜ˆì—ì„œ 변수 'tmp' ì‚¬ìš©ì„ ìµœì 화로 ì—†ì• ë²„ë¦´ 수 있습니다: while (tmp = a) do_something_with(tmp); ì´ ì½”ë“œëŠ” 다ìŒê³¼ ê°™ì´ ì‹±ê¸€ ì“°ë ˆë“œì—서는 완벽하지만 ë™ì‹œì„±ì´ 존재하는 경우엔 치명ì ì¸ ì½”ë“œë¡œ 바뀔 수 있습니다: while (a) do_something_with(a); 예를 들어, 최ì í™”ëœ ì´ ì½”ë“œëŠ” 변수 a ê°€ 다른 CPU ì— ì˜í•´ "while" 문과 do_something_with() 호출 사ì´ì— 바뀌어 do_something_with() ì— 0ì„ ë„˜ê¸¸ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ë²ˆì—ë„, 컴파ì¼ëŸ¬ê°€ 그런 ì§“ì„ í•˜ëŠ”ê±¸ 막기 위해 READ_ONCE() 를 사용하세요: while (tmp = READ_ONCE(a)) do_something_with(tmp); ë ˆì§€ìŠ¤í„°ê°€ 부족한 ìƒí™©ì„ 겪는 경우, 컴파ì¼ëŸ¬ëŠ” tmp 를 스íƒì— ì €ìž¥í•´ë‘˜ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. 컴파ì¼ëŸ¬ê°€ 변수를 다시 ì½ì–´ë“¤ì´ëŠ”ê±´ ì´ë ‡ê²Œ ì €ìž¥í•´ë‘ê³ í›„ì— ë‹¤ì‹œ ì½ì–´ë“¤ì´ëŠ”ë° ë“œëŠ” 오버헤드 때문입니다. ê·¸ë ‡ê²Œ 하는게 싱글 ì“°ë ˆë“œ 코드ì—서는 ì•ˆì „í•˜ë¯€ë¡œ, ì•ˆì „í•˜ì§€ ì•Šì€ ê²½ìš°ì—는 컴파ì¼ëŸ¬ì—게 ì§ì ‘ ì•Œë ¤ì¤˜ì•¼ 합니다. (*) 컴파ì¼ëŸ¬ëŠ” ê·¸ ê°’ì´ ë¬´ì—‡ì¼ì§€ ì•Œê³ ìžˆë‹¤ë©´ 로드를 아예 ì•ˆí• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. 예를 들어, 다ìŒì˜ 코드는 변수 'a' ì˜ ê°’ì´ í•ìƒ 0ìž„ì„ ì¦ëª…í• ìˆ˜ 있다면: while (tmp = a) do_something_with(tmp); ì´ë ‡ê²Œ 최ì í™” ë˜ì–´ë²„릴 수 있습니다: do { } while (0); ì´ ë³€í™˜ì€ ì‹±ê¸€ ì“°ë ˆë“œ 코드ì—서는 ë„ì›€ì´ ë˜ëŠ”ë° ë¡œë“œì™€ 브랜치를 ì œê±°í–ˆê¸° 때문입니다. ë¬¸ì œëŠ” 컴파ì¼ëŸ¬ê°€ 'a' ì˜ ê°’ì„ ì—…ë°ì´íŠ¸ 하는건 í˜„ìž¬ì˜ CPU 하나 ë¿ì´ë¼ëŠ” ê°€ì • 위ì—ì„œ ì¦ëª…ì„ í–ˆë‹¤ëŠ”ë° ìžˆìŠµë‹ˆë‹¤. 만약 변수 'a' ê°€ ê³µìœ ë˜ì–´ 있다면, 컴파ì¼ëŸ¬ì˜ ì¦ëª…ì€ í‹€ë¦° ê²ƒì´ ë ê²ë‹ˆë‹¤. 컴파ì¼ëŸ¬ëŠ” ê·¸ ìžì‹ ì´ ìƒê°í•˜ëŠ” ê²ƒë§Œí¼ ë§Žì€ ê²ƒì„ ì•Œê³ ìžˆì§€ ëª»í•¨ì„ ì»´íŒŒì¼ëŸ¬ì—게 알리기 위해 READ_ONCE() 를 사용하세요: while (tmp = READ_ONCE(a)) do_something_with(tmp); 하지만 컴파ì¼ëŸ¬ëŠ” READ_ONCE() ë’¤ì— ë‚˜ì˜¤ëŠ” ê°’ì— ëŒ€í•´ì„œë„ ëˆˆê¸¸ì„ ë‘ê³ ìžˆìŒì„ 기억하세요. 예를 들어, 다ìŒì˜ 코드ì—ì„œ MAX 는 ì „ì²˜ë¦¬ê¸° 매í¬ë¡œë¡œ, 1ì˜ ê°’ì„ ê°–ëŠ”ë‹¤ê³ í•´ë´…ì‹œë‹¤: while ((tmp = READ_ONCE(a)) % MAX) do_something_with(tmp); ì´ë ‡ê²Œ ë˜ë©´ 컴파ì¼ëŸ¬ëŠ” MAX 를 ê°€ì§€ê³ ìˆ˜í–‰ë˜ëŠ” "%" 오í¼ë ˆì´í„°ì˜ 결과가 í•ìƒ 0ì´ë¼ëŠ” ê²ƒì„ ì•Œê²Œ ë˜ê³ , 컴파ì¼ëŸ¬ê°€ 코드를 실질ì 으로는 존재하지 않는 것처럼 최ì í™” 하는 ê²ƒì´ í—ˆìš©ë˜ì–´ 버립니다. ('a' ë³€ìˆ˜ì˜ ë¡œë“œëŠ” ì—¬ì „ížˆ 행해질 ê²ë‹ˆë‹¤.) (*) 비슷하게, 컴파ì¼ëŸ¬ëŠ” 변수가 ì €ìž¥í•˜ë ¤ 하는 ê°’ì„ ì´ë¯¸ ê°€ì§€ê³ ìžˆë‹¤ëŠ” ê²ƒì„ ì•Œë©´ ìŠ¤í† ì–´ ìžì²´ë¥¼ ì œê±°í• ìˆ˜ 있습니다. ì´ë²ˆì—ë„, 컴파ì¼ëŸ¬ëŠ” í˜„ìž¬ì˜ CPU ë§Œì´ ê·¸ ë³€ìˆ˜ì— ê°’ì„ ì“°ëŠ” 오로지 í•˜ë‚˜ì˜ ì¡´ìž¬ë¼ê³ ìƒê°í•˜ì—¬ ê³µìœ ëœ ë³€ìˆ˜ì— ëŒ€í•´ì„œëŠ” ìž˜ëª»ëœ ì¼ì„ 하게 ë©ë‹ˆë‹¤. 예를 들어, 다ìŒê³¼ ê°™ì€ ê²½ìš°ê°€ ìžˆì„ ìˆ˜ 있습니다: a = 0; ... 변수 a ì— ìŠ¤í† ì–´ë¥¼ 하지 않는 코드 ... a = 0; 컴파ì¼ëŸ¬ëŠ” 변수 'a' ì˜ ê°’ì€ ì´ë¯¸ 0ì´ë¼ëŠ” ê²ƒì„ ì•Œê³ , ë”°ë¼ì„œ ë‘번째 ìŠ¤í† ì–´ë¥¼ ì‚ì œí• ê²ë‹ˆë‹¤. 만약 다른 CPU ê°€ ê·¸ ì‚¬ì´ ë³€ìˆ˜ 'a' ì— ë‹¤ë¥¸ ê°’ì„ ì¼ë‹¤ë©´ 황당한 결과가 나올 ê²ë‹ˆë‹¤. 컴파ì¼ëŸ¬ê°€ 그런 ìž˜ëª»ëœ ì¶”ì¸¡ì„ í•˜ì§€ ì•Šë„ë¡ WRITE_ONCE() 를 사용하세요: WRITE_ONCE(a, 0); ... 변수 a ì— ìŠ¤í† ì–´ë¥¼ 하지 않는 코드 ... WRITE_ONCE(a, 0); (*) 컴파ì¼ëŸ¬ëŠ” 하지 ë§ë¼ê³ 하지 않으면 메모리 ì•¡ì„¸ìŠ¤ë“¤ì„ ìž¬ë°°ì¹˜ í• ìˆ˜ 있습니다. 예를 들어, 다ìŒì˜ 프로세스 ë ˆë²¨ 코드와 ì¸í„°ëŸ½íŠ¸ 핸들러 사ì´ì˜ ìƒí˜¸ìž‘ìš©ì„ ìƒê°í•´ 봅시다: void process_level(void) { msg = get_message(); flag = true; } void interrupt_handler(void) { if (flag) process_message(msg); } ì´ ì½”ë“œì—는 컴파ì¼ëŸ¬ê°€ process_level() ì„ ë‹¤ìŒê³¼ ê°™ì´ ë³€í™˜í•˜ëŠ” ê²ƒì„ ë§‰ì„ ìˆ˜ë‹¨ì´ ì—†ê³ , ì´ëŸ° ë³€í™˜ì€ ì‹±ê¸€ì“°ë ˆë“œì—ì„œë¼ë©´ ì‹¤ì œë¡œ 훌ë¥í•œ ì„ íƒì¼ 수 있습니다: void process_level(void) { flag = true; msg = get_message(); } ì´ ë‘ê°œì˜ ë¬¸ìž¥ 사ì´ì— ì¸í„°ëŸ½íŠ¸ê°€ ë°œìƒí•œë‹¤ë©´, interrupt_handler() 는 ì˜ë¯¸ë¥¼ ì•Œ 수 없는 메세지를 ë°›ì„ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ê±¸ 막기 위해 다ìŒê³¼ ê°™ì´ WRITE_ONCE() 를 사용하세요: void process_level(void) { WRITE_ONCE(msg, get_message()); WRITE_ONCE(flag, true); } void interrupt_handler(void) { if (READ_ONCE(flag)) process_message(READ_ONCE(msg)); } interrupt_handler() 안ì—ì„œë„ ì¤‘ì²©ëœ ì¸í„°ëŸ½íŠ¸ë‚˜ NMI 와 ê°™ì´ ì¸í„°ëŸ½íŠ¸ 핸들러 ì—ì‹œ 'flag' 와 'msg' ì— ì ‘ê·¼í•˜ëŠ” ë˜ë‹¤ë¥¸ ë¬´ì–¸ê°€ì— ì¸í„°ëŸ½íŠ¸ ë 수 있다면 READ_ONCE() 와 WRITE_ONCE() 를 사용해야 í•¨ì„ ê¸°ì–µí•´ ë‘세요. 만약 그런 ê°€ëŠ¥ì„±ì´ ì—†ë‹¤ë©´, interrupt_handler() 안ì—서는 문서화 목ì ì´ ì•„ë‹ˆë¼ë©´ READ_ONCE() 와 WRITE_ONCE() 는 필요치 않습니다. (ê·¼ëž˜ì˜ ë¦¬ëˆ…ìŠ¤ 커ë„ì—ì„œ ì¤‘ì²©ëœ ì¸í„°ëŸ½íŠ¸ëŠ” 보통 잘 ì¼ì–´ë‚˜ì§€ ì•ŠìŒë„ 기억해 ë‘세요, ì‹¤ì œë¡œ, ì–´ë–¤ ì¸í„°ëŸ½íŠ¸ 핸들러가 ì¸í„°ëŸ½íŠ¸ê°€ í™œì„±í™”ëœ ì±„ë¡œ 리턴하면 WARN_ONCE() ê°€ 실행ë©ë‹ˆë‹¤.) 컴파ì¼ëŸ¬ëŠ” READ_ONCE() 와 WRITE_ONCE() ë’¤ì˜ READ_ONCE() 나 WRITE_ONCE(), barrier(), ë˜ëŠ” 비슷한 ê²ƒë“¤ì„ ë‹´ê³ ìžˆì§€ ì•Šì€ ì½”ë“œë¥¼ 움ì§ì¼ 수 ìžˆì„ ê²ƒìœ¼ë¡œ ê°€ì •ë˜ì–´ì•¼ 합니다. ì´ íš¨ê³¼ëŠ” barrier() 를 í†µí•´ì„œë„ ë§Œë“¤ 수 있지만, READ_ONCE() 와 WRITE_ONCE() ê°€ 좀 ë” ì•ˆëª© ë†’ì€ ì„ íƒìž…니다: READ_ONCE() 와 WRITE_ONCE()는 컴파ì¼ëŸ¬ì— 주어진 메모리 ì˜ì—ì— ëŒ€í•´ì„œë§Œ 최ì í™” ê°€ëŠ¥ì„±ì„ í¬ê¸°í•˜ë„ë¡ í•˜ì§€ë§Œ, barrier() 는 컴파ì¼ëŸ¬ê°€ 지금까지 ê¸°ê³„ì˜ ë ˆì§€ìŠ¤í„°ì— ìºì‹œí•´ ë†“ì€ ëª¨ë“ ë©”ëª¨ë¦¬ ì˜ì—ì˜ ê°’ì„ ë²„ë ¤ì•¼ 하게 하기 때문입니다. ë¬¼ë¡ , 컴파ì¼ëŸ¬ëŠ” READ_ONCE() 와 WRITE_ONCE() ê°€ ì¼ì–´ë‚œ ìˆœì„œë„ ì§€ì¼œì¤ë‹ˆë‹¤, CPU 는 당연히 ê·¸ 순서를 지킬 ì˜ë¬´ê°€ 없지만요. (*) 컴파ì¼ëŸ¬ëŠ” 다ìŒì˜ 예ì—서와 ê°™ì´ ë³€ìˆ˜ì—ì˜ ìŠ¤í† ì–´ë¥¼ ë‚ ì¡°í•´ë‚¼ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤: if (a) b = a; else b = 42; 컴파ì¼ëŸ¬ëŠ” 아래와 ê°™ì€ ìµœì 화로 브랜치를 ì¤„ì¼ ê²ë‹ˆë‹¤: b = 42; if (a) b = a; 싱글 ì“°ë ˆë“œ 코드ì—ì„œ ì´ ìµœì 화는 ì•ˆì „í• ë¿ ì•„ë‹ˆë¼ ë¸Œëžœì¹˜ 갯수를 줄여ì¤ë‹ˆë‹¤. 하지만 안타ê¹ê²Œë„, ë™ì‹œì„±ì´ 있는 코드ì—서는 ì´ ìµœì 화는 다른 CPU ê°€ 'b' 를 ë¡œë“œí• ë•Œ, -- 'a' ê°€ 0ì´ ì•„ë‹Œë°ë„ -- ê°€ì§œì¸ ê°’, 42를 보게 ë˜ëŠ” 경우를 가능하게 합니다. ì´ê±¸ 방지하기 위해 WRITE_ONCE() 를 사용하세요: if (a) WRITE_ONCE(b, a); else WRITE_ONCE(b, 42); 컴파ì¼ëŸ¬ëŠ” 로드를 만들어낼 ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì¼ë°˜ì 으로는 ë¬¸ì œë¥¼ ì¼ìœ¼í‚¤ì§€ 않지만, ìºì‹œ ë¼ì¸ ë°”ìš´ì‹±ì„ ì¼ìœ¼ì¼œ 성능과 í™•ìž¥ì„±ì„ ë–¨ì–´ëœ¨ë¦´ 수 있습니다. ë‚ ì¡°ëœ ë¡œë“œë¥¼ 막기 ìœ„í•´ì„ READ_ONCE() 를 사용하세요. (*) ì •ë ¬ëœ ë©”ëª¨ë¦¬ ì£¼ì†Œì— ìœ„ì¹˜í•œ, í•œë²ˆì˜ ë©”ëª¨ë¦¬ 참조 ì¸ìŠ¤íŠ¸ëŸì…˜ìœ¼ë¡œ 액세스 가능한 í¬ê¸°ì˜ ë°ì´í„°ëŠ” í•˜ë‚˜ì˜ í° ì•¡ì„¸ìŠ¤ê°€ ì—¬ëŸ¬ê°œì˜ ìž‘ì€ ì•¡ì„¸ìŠ¤ë“¤ë¡œ 대체ë˜ëŠ” "로드 í‹°ì–´ë§(load tearing)" ê³¼ "ìŠ¤í† ì–´ í‹°ì–´ë§(store tearing)" ì„ ë°©ì§€í•©ë‹ˆë‹¤. 예를 들어, 주어진 아키í…ì³ê°€ 7-bit imeediate field 를 갖는 16-bit ìŠ¤í† ì–´ ì¸ìŠ¤íŠ¸ëŸì…˜ì„ ì œê³µí•œë‹¤ë©´, 컴파ì¼ëŸ¬ëŠ” 다ìŒì˜ 32-bit ìŠ¤í† ì–´ë¥¼ 구현하는ë°ì— ë‘ê°œì˜ 16-bit store-immediate ëª…ë ¹ì„ ì‚¬ìš©í•˜ë ¤ í• ê²ë‹ˆë‹¤: p = 0x00010002; ìŠ¤í† ì–´ í• ìƒìˆ˜ë¥¼ ë§Œë“¤ê³ ê·¸ ê°’ì„ ìŠ¤í† ì–´ 하기 위해 ë‘개가 넘는 ì¸ìŠ¤íŠ¸ëŸì…˜ì„ 사용하게 ë˜ëŠ”, ì´ëŸ° ì¢…ë¥˜ì˜ ìµœì 화를 GCC 는 ì‹¤ì œë¡œ í•¨ì„ ë¶€ë”” 알아 ë‘ì‹ì‹œì˜¤. ì´ ìµœì 화는 싱글 ì“°ë ˆë“œ 코드ì—서는 성공ì ì¸ ìµœì í™” 입니다. ì‹¤ì œë¡œ, ê·¼ëž˜ì— ë°œìƒí•œ (ê·¸ë¦¬ê³ ê³ ì³ì§„) 버그는 GCC ê°€ volatile ìŠ¤í† ì–´ì— ë¹„ì •ìƒì 으로 ì´ ìµœì 화를 사용하게 했습니다. 그런 버그가 없다면, 다ìŒì˜ 예ì—ì„œ WRITE_ONCE() ì˜ ì‚¬ìš©ì€ ìŠ¤í† ì–´ í‹°ì–´ë§ì„ 방지합니다: WRITE_ONCE(p, 0x00010002); Packed êµ¬ì¡°ì²´ì˜ ì‚¬ìš© ì—ì‹œ 다ìŒì˜ 예처럼 로드 / ìŠ¤í† ì–´ í‹°ì–´ë§ì„ ìœ ë°œí• ìˆ˜ 있습니다: struct __attribute__((__packed__)) foo { short a; int b; short c; }; struct foo foo1, foo2; ... foo2.a = foo1.a; foo2.b = foo1.b; foo2.c = foo1.c; READ_ONCE() 나 WRITE_ONCE() ë„ ì—†ê³ volatile ë§ˆí‚¹ë„ ì—†ê¸° 때문ì—, 컴파ì¼ëŸ¬ëŠ” ì´ ì„¸ê°œì˜ ëŒ€ìž…ë¬¸ì„ ë‘ê°œì˜ 32-bit 로드와 ë‘ê°œì˜ 32-bit ìŠ¤í† ì–´ë¡œ ë³€í™˜í• ìˆ˜ 있습니다. ì´ëŠ” 'foo1.b' ì˜ ê°’ì˜ ë¡œë“œ í‹°ì–´ë§ê³¼ 'foo2.b' ì˜ ìŠ¤í† ì–´ í‹°ì–´ë§ì„ ì´ˆëž˜í• ê²ë‹ˆë‹¤. ì´ ì˜ˆì—ì„œë„ READ_ONCE() 와 WRITE_ONCE() ê°€ í‹°ì–´ë§ì„ ë§‰ì„ ìˆ˜ 있습니다: foo2.a = foo1.a; WRITE_ONCE(foo2.b, READ_ONCE(foo1.b)); foo2.c = foo1.c; ê·¸ë ‡ì§€ë§Œ, volatile ë¡œ 마í¬ëœ ë³€ìˆ˜ì— ëŒ€í•´ì„œëŠ” READ_ONCE() 와 WRITE_ONCE() ê°€ 필요치 않습니다. 예를 들어, 'jiffies' 는 volatile ë¡œ 마í¬ë˜ì–´ 있기 때문ì—, READ_ONCE(jiffies) ë¼ê³ í• í•„ìš”ê°€ 없습니다. READ_ONCE() 와 WRITE_ONCE() ê°€ ì‹¤ì€ volatile ìºìŠ¤íŒ…으로 구현ë˜ì–´ 있어서 ì¸ìžê°€ ì´ë¯¸ volatile ë¡œ 마í¬ë˜ì–´ 있다면 ë˜ë‹¤ë¥¸ 효과를 내지는 않기 때문입니다. ì´ ì»´íŒŒì¼ëŸ¬ ë°°ë¦¬ì–´ë“¤ì€ CPU ì—는 ì§ì ‘ì 효과를 ì „í˜€ 만들지 않기 때문ì—, ê²°êµì€ 재배치가 ì¼ì–´ë‚ ìˆ˜ë„ ìžˆìŒì„ 부디 기억해 ë‘ì‹ì‹œì˜¤. CPU 메모리 배리어 ----------------- 리눅스 커ë„ì€ ë‹¤ìŒì˜ ì—¬ëŸê°œ 기본 CPU 메모리 배리어를 ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤: TYPE MANDATORY SMP CONDITIONAL =============== ======================= =========================== 범용 mb() smp_mb() 쓰기 wmb() smp_wmb() ì½ê¸° rmb() smp_rmb() ë°ì´í„° ì˜ì¡´ì„± READ_ONCE() ë°ì´í„° ì˜ì¡´ì„± 배리어를 ì œì™¸í•œ ëª¨ë“ ë©”ëª¨ë¦¬ 배리어는 컴파ì¼ëŸ¬ 배리어를 í¬í•¨í•©ë‹ˆë‹¤. ë°ì´í„° ì˜ì¡´ì„±ì€ 컴파ì¼ëŸ¬ì—ì˜ ì¶”ê°€ì ì¸ ìˆœì„œ ë³´ìž¥ì„ í¬í•¨í•˜ì§€ 않습니다. ë°©ë°±: ë°ì´í„° ì˜ì¡´ì„±ì´ 있는 경우, 컴파ì¼ëŸ¬ëŠ” 해당 로드를 올바른 순서로 ì¼ìœ¼í‚¬ 것으로 (예: `a[b]` 는 a[b] 를 로드 하기 ì „ì— b ì˜ ê°’ì„ ë¨¼ì € 로드한다) 기대ë˜ì§€ë§Œ, C 언어 사양ì—는 컴파ì¼ëŸ¬ê°€ b ì˜ ê°’ì„ ì¶”ì¸¡ (예: 1 ê³¼ ê°™ìŒ) í•´ì„œ b 로드 ì „ì— a 로드를 하는 코드 (예: tmp = a[1]; if (b != 1) tmp = a[b]; ) 를 만들지 않아야 한다는 ë‚´ìš© ê°™ì€ ê±´ 없습니다. ë˜í•œ 컴파ì¼ëŸ¬ëŠ” a[b] 를 로드한 í›„ì— b 를 ë˜ë‹¤ì‹œ ë¡œë“œí• ìˆ˜ë„ ìžˆì–´ì„œ, a[b] 보다 ìµœì‹ ë²„ì „ì˜ b ê°’ì„ ê°€ì§ˆ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ëŸ° ë¬¸ì œë“¤ì˜ í•´ê²°ì±…ì— ëŒ€í•œ ì˜ê²¬ ì¼ì¹˜ëŠ” ì•„ì§ ì—†ìŠµë‹ˆë‹¤ë§Œ, ì¼ë‹¨ READ_ONCE() 매í¬ë¡œë¶€í„° 보기 시작하는게 ì¢‹ì€ ì‹œìž‘ì´ ë ê²ë‹ˆë‹¤. SMP 메모리 ë°°ë¦¬ì–´ë“¤ì€ ìœ ë‹ˆí”„ë¡œì„¸ì„œë¡œ 컴파ì¼ëœ 시스템ì—서는 컴파ì¼ëŸ¬ 배리어로 바뀌는ë°, í•˜ë‚˜ì˜ CPU 는 스스로 ì¼ê´€ì„±ì„ ìœ ì§€í•˜ê³ , 겹치는 액세스들 ì—ì‹œ 올바른 순서로 행해질 것으로 ìƒê°ë˜ê¸° 때문입니다. 하지만, ì•„ëž˜ì˜ "Virtual Machine Guests" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì‹ì‹œì˜¤. [!] SMP 시스템ì—ì„œ ê³µìœ ë©”ëª¨ë¦¬ë¡œì˜ ì ‘ê·¼ë“¤ì„ ìˆœì„œ 세워야 í• ë•Œ, SMP 메모리 배리어는 _반드시_ 사용ë˜ì–´ì•¼ í•¨ì„ ê¸°ì–µí•˜ì„¸ìš”, ê·¸ëŒ€ì‹ ë½ì„ 사용하는 ê²ƒìœ¼ë¡œë„ ì¶©ë¶„í•˜ê¸´ 하지만 ë§ì´ì£ . Mandatory ë°°ë¦¬ì–´ë“¤ì€ SMP 시스템ì—ì„œë„ UP 시스템ì—ì„œë„ SMP 효과만 í†µì œí•˜ê¸°ì—는 불필요한 오버헤드를 갖기 ë•Œë¬¸ì— SMP 효과만 í†µì œí•˜ë©´ ë˜ëŠ” ê³³ì—는 사용ë˜ì§€ 않아야 합니다. 하지만, ëŠìŠ¨í•œ 순서 ê·œì¹™ì˜ ë©”ëª¨ë¦¬ I/O 윈ë„우를 통한 MMIO ì˜ íš¨ê³¼ë¥¼ í†µì œí• ë•Œì—는 mandatory ë°°ë¦¬ì–´ë“¤ì´ ì‚¬ìš©ë 수 있습니다. ì´ ë°°ë¦¬ì–´ë“¤ì€ ì»´íŒŒì¼ëŸ¬ì™€ CPU ëª¨ë‘ ìž¬ë°°ì¹˜ë¥¼ 못하ë„ë¡ í•¨ìœ¼ë¡œì¨ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ë“¤ì´ 디바ì´ìŠ¤ì— 보여지는 순서ì—ë„ ì˜í–¥ì„ 주기 때문ì—, SMP ê°€ ì•„ë‹Œ 시스템ì´ë¼ í• ì§€ë¼ë„ í•„ìš”í• ìˆ˜ 있습니다. ì¼ë¶€ ê³ ê¸‰ 배리어 í•¨ìˆ˜ë“¤ë„ ìžˆìŠµë‹ˆë‹¤: (*) smp_store_mb(var, value) ì´ í•¨ìˆ˜ëŠ” íŠ¹ì • ë³€ìˆ˜ì— íŠ¹ì • ê°’ì„ ëŒ€ìž…í•˜ê³ ë²”ìš© 메모리 배리어를 칩니다. UP 컴파ì¼ì—서는 컴파ì¼ëŸ¬ 배리어보다 ë”í•œ ê²ƒì„ ì¹œë‹¤ê³ ëŠ” 보장ë˜ì§€ 않습니다. (*) smp_mb__before_atomic(); (*) smp_mb__after_atomic(); ì´ê²ƒë“¤ì€ ê°’ì„ ë¦¬í„´í•˜ì§€ 않는 (ë”하기, 빼기, ì¦ê°€, ê°ì†Œì™€ ê°™ì€) ì–´í† ë¯¹ í•¨ìˆ˜ë“¤ì„ ìœ„í•œ, 특히 ê·¸ê²ƒë“¤ì´ ë ˆí¼ëŸ°ìŠ¤ ì¹´ìš´íŒ…ì— ì‚¬ìš©ë 때를 위한 함수들입니다. ì´ í•¨ìˆ˜ë“¤ì€ ë©”ëª¨ë¦¬ 배리어를 ë‚´í¬í•˜ê³ 있지는 않습니다. ì´ê²ƒë“¤ì€ ê°’ì„ ë¦¬í„´í•˜ì§€ 않으며 ì–´í† ë¯¹í•œ (set_bit ê³¼ clear_bit ê°™ì€) 비트 ì—°ì‚°ì—ë„ ì‚¬ìš©ë 수 있습니다. í•œ 예로, ê°ì²´ 하나를 무효한 것으로 í‘œì‹œí•˜ê³ ê·¸ ê°ì²´ì˜ ë ˆí¼ëŸ°ìŠ¤ 카운트를 ê°ì†Œì‹œí‚¤ëŠ” ë‹¤ìŒ ì½”ë“œë¥¼ 보세요: obj->dead = 1; smp_mb__before_atomic(); atomic_dec(&obj->ref_count); ì´ ì½”ë“œëŠ” ê°ì²´ì˜ ì—…ë°ì´íŠ¸ëœ death 마í¬ê°€ ë ˆí¼ëŸ°ìŠ¤ ì¹´ìš´í„° ê°ì†Œ ë™ìž‘ *ì „ì—* ë³´ì¼ ê²ƒì„ ë³´ìž¥í•©ë‹ˆë‹¤. ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ Documentation/atomic_{t,bitops}.txt 문서를 ì°¸ê³ í•˜ì„¸ìš”. (*) dma_wmb(); (*) dma_rmb(); ì´ê²ƒë“¤ì€ CPU 와 DMA 가능한 디바ì´ìŠ¤ì—ì„œ ëª¨ë‘ ì•¡ì„¸ìŠ¤ 가능한 ê³µìœ ë©”ëª¨ë¦¬ì˜ ì½ê¸°, 쓰기 ìž‘ì—…ë“¤ì˜ ìˆœì„œë¥¼ 보장하기 위해 consistent memory ì—ì„œ 사용하기 위한 것들입니다. 예를 들어, 디바ì´ìŠ¤ì™€ 메모리를 ê³µìœ í•˜ë©°, 디스í¬ë¦½í„° ìƒíƒœ ê°’ì„ ì‚¬ìš©í•´ 디스í¬ë¦½í„°ê°€ 디바ì´ìŠ¤ì— ì†í•´ 있는지 아니면 CPU ì— ì†í•´ 있는지 í‘œì‹œí•˜ê³ , 공지용 ì´ˆì¸ì¢…(doorbell) ì„ ì‚¬ìš©í•´ ì—…ë°ì´íŠ¸ëœ 디스í¬ë¦½í„°ê°€ 디바ì´ìŠ¤ì— 사용 가능해졌ìŒì„ 공지하는 디바ì´ìŠ¤ ë“œë¼ì´ë²„를 ìƒê°í•´ 봅시다: if (desc->status != DEVICE_OWN) { /* 디스í¬ë¦½í„°ë¥¼ ì†Œìœ í•˜ê¸° ì „ì—는 ë°ì´í„°ë¥¼ ì½ì§€ ì•ŠìŒ */ dma_rmb(); /* ë°ì´í„°ë¥¼ ì½ê³ 씀 */ read_data = desc->data; desc->data = write_data; /* ìƒíƒœ ì—…ë°ì´íŠ¸ ì „ ìˆ˜ì •ì‚¬í•ì„ ë°˜ì˜ */ dma_wmb(); /* ì†Œìœ ê¶Œì„ ìˆ˜ì • */ desc->status = DEVICE_OWN; /* ì—…ë°ì´íŠ¸ëœ 디스í¬ë¦½í„°ì˜ 디바ì´ìŠ¤ì— 공지 */ writel(DESC_NOTIFY, doorbell); } dma_rmb() 는 디스í¬ë¦½í„°ë¡œë¶€í„° ë°ì´í„°ë¥¼ ì½ì–´ì˜¤ê¸° ì „ì— ë””ë°”ì´ìŠ¤ê°€ ì†Œìœ ê¶Œì„ ë‚´ë ¤ë†“ì•˜ì„ ê²ƒì„ ë³´ìž¥í•˜ê³ , dma_wmb() 는 디바ì´ìŠ¤ê°€ ìžì‹ ì´ ì†Œìœ ê¶Œì„ ë‹¤ì‹œ 가졌ìŒì„ 보기 ì „ì— ë””ìŠ¤í¬ë¦½í„°ì— ë°ì´í„°ê°€ ì“°ì˜€ì„ ê²ƒì„ ë³´ìž¥í•©ë‹ˆë‹¤. ì°¸ê³ ë¡œ, writel() ì„ ì‚¬ìš©í•˜ë©´ ìºì‹œ ì¼ê´€ì„±ì´ 있는 메모리 (cache coherent memory) 쓰기가 MMIO ì˜ì—ì—ì˜ ì“°ê¸° ì „ì— ì™„ë£Œë˜ì—ˆì„ ê²ƒì„ ë³´ìž¥í•˜ë¯€ë¡œ writel() ì•žì— wmb() 를 ì‹¤í–‰í• í•„ìš”ê°€ ì—†ìŒì„ 알아ë‘시기 ë°”ëžë‹ˆë‹¤. writel() 보다 ë¹„ìš©ì´ ì €ë ´í•œ writel_relaxed() 는 ì´ëŸ° ë³´ìž¥ì„ ì œê³µí•˜ì§€ 않으므로 ì—¬ê¸°ì„ ì‚¬ìš©ë˜ì§€ 않아야 합니다. writel_relaxed() 와 ê°™ì€ ì™„í™”ëœ I/O ì ‘ê·¼ìžë“¤ì— 대한 ìžì„¸í•œ ë‚´ìš©ì„ ìœ„í•´ì„œëŠ” "ì»¤ë„ I/O ë°°ë¦¬ì–´ì˜ íš¨ê³¼" 섹션ì„, consistent memory ì— ëŒ€í•œ ìžì„¸í•œ ë‚´ìš©ì„ ìœ„í•´ì„ Documentation/DMA-API.txt 문서를 ì°¸ê³ í•˜ì„¸ìš”. MMIO 쓰기 배리어 ---------------- 리눅스 커ë„ì€ ë˜í•œ memory-mapped I/O 쓰기를 위한 특별한 ë°°ë¦¬ì–´ë„ ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤: mmiowb(); ì´ê²ƒì€ mandatory 쓰기 ë°°ë¦¬ì–´ì˜ ë³€ì¢…ìœ¼ë¡œ, ì™„í™”ëœ ìˆœì„œ ê·œì¹™ì˜ I/O ì˜ì—ì—ìœ¼ë¡œì˜ ì“°ê¸°ê°€ 부분ì 으로 순서를 맞추ë„ë¡ í•´ì¤ë‹ˆë‹¤. ì´ í•¨ìˆ˜ëŠ” CPU->하드웨어 사ì´ë¥¼ 넘어서 ì‹¤ì œ 하드웨어ì—까지 ì¼ë¶€ ìˆ˜ì¤€ì˜ ì˜í–¥ì„ ë¼ì¹©ë‹ˆë‹¤. ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ "Acquire vs I/O 액세스" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. ========================= 암묵ì ì»¤ë„ ë©”ëª¨ë¦¬ 배리어 ========================= 리눅스 커ë„ì˜ ì¼ë¶€ í•¨ìˆ˜ë“¤ì€ ë©”ëª¨ë¦¬ 배리어를 ë‚´ìž¥í•˜ê³ ìžˆëŠ”ë°, ë½(lock)ê³¼ ìŠ¤ì¼€ì¥´ë§ ê´€ë ¨ í•¨ìˆ˜ë“¤ì´ ëŒ€ë¶€ë¶„ìž…ë‹ˆë‹¤. ì—¬ê¸°ì„ _최소한ì˜_ ë³´ìž¥ì„ ì„¤ëª…í•©ë‹ˆë‹¤; íŠ¹ì • 아키í…ì³ì—서는 ì´ ì„¤ëª…ë³´ë‹¤ ë” ë§Žì€ ë³´ìž¥ì„ ì œê³µí• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤ë§Œ 해당 아키í…ì³ì— 종ì†ì ì¸ ì½”ë“œ ì™¸ì˜ ë¶€ë¶„ì—서는 그런 ë³´ìž¥ì„ ê¸°ëŒ€í•´ì„ ì•ˆë ê²ë‹ˆë‹¤. ë½ ACQUISITION 함수 ------------------- 리눅스 커ë„ì€ ë‹¤ì–‘í•œ ë½ êµ¬ì„±ì²´ë¥¼ ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤: (*) 스핀 ë½ (*) R/W 스핀 ë½ (*) 뮤í…스 (*) 세마í¬ì–´ (*) R/W 세마í¬ì–´ ê° êµ¬ì„±ì²´ë§ˆë‹¤ ëª¨ë“ ê²½ìš°ì— "ACQUIRE" 오í¼ë ˆì´ì…˜ê³¼ "RELEASE" 오í¼ë ˆì´ì…˜ì˜ ë³€ì¢…ì´ ì¡´ìž¬í•©ë‹ˆë‹¤. ì´ ì˜¤í¼ë ˆì´ì…˜ë“¤ì€ ëª¨ë‘ ì ì ˆí•œ 배리어를 ë‚´í¬í•˜ê³ 있습니다: (1) ACQUIRE 오í¼ë ˆì´ì…˜ì˜ ì˜í–¥: ACQUIRE ë’¤ì—ì„œ ìš”ì²ëœ 메모리 오í¼ë ˆì´ì…˜ì€ ACQUIRE 오í¼ë ˆì´ì…˜ì´ ì™„ë£Œëœ ë’¤ì— ì™„ë£Œë©ë‹ˆë‹¤. ACQUIRE ì•žì—ì„œ ìš”ì²ëœ 메모리 오í¼ë ˆì´ì…˜ì€ ACQUIRE 오í¼ë ˆì´ì…˜ì´ ì™„ë£Œëœ í›„ì— ì™„ë£Œë 수 있습니다. (2) RELEASE 오í¼ë ˆì´ì…˜ì˜ ì˜í–¥: RELEASE ì•žì—ì„œ ìš”ì²ëœ 메모리 오í¼ë ˆì´ì…˜ì€ RELEASE 오í¼ë ˆì´ì…˜ì´ 완료ë˜ê¸° ì „ì— ì™„ë£Œë©ë‹ˆë‹¤. RELEASE ë’¤ì—ì„œ ìš”ì²ëœ 메모리 오í¼ë ˆì´ì…˜ì€ RELEASE 오í¼ë ˆì´ì…˜ 완료 ì „ì— ì™„ë£Œë 수 있습니다. (3) ACQUIRE vs ACQUIRE ì˜í–¥: ì–´ë–¤ ACQUIRE 오í¼ë ˆì´ì…˜ë³´ë‹¤ ì•žì—ì„œ ìš”ì²ëœ ëª¨ë“ ACQUIRE 오í¼ë ˆì´ì…˜ì€ ê·¸ ACQUIRE 오í¼ë ˆì´ì…˜ ì „ì— ì™„ë£Œë©ë‹ˆë‹¤. (4) ACQUIRE vs RELEASE implication: ì–´ë–¤ RELEASE 오í¼ë ˆì´ì…˜ë³´ë‹¤ ì•žì„œ ìš”ì²ëœ ACQUIRE 오í¼ë ˆì´ì…˜ì€ ê·¸ RELEASE 오í¼ë ˆì´ì…˜ë³´ë‹¤ ë¨¼ì € 완료ë©ë‹ˆë‹¤. (5) 실패한 ì¡°ê±´ì ACQUIRE ì˜í–¥: ACQUIRE 오í¼ë ˆì´ì…˜ì˜ ì¼ë¶€ ë½(lock) ë³€ì¢…ì€ ë½ì´ 곧바로 íšë“하기ì—는 불가능한 ìƒíƒœì´ê±°ë‚˜ ë½ì´ íšë“ 가능해지ë„ë¡ ê¸°ë‹¤ë¦¬ëŠ” ë„중 시그ë„ì„ ë°›ê±°ë‚˜ í•´ì„œ ì‹¤íŒ¨í• ìˆ˜ 있습니다. 실패한 ë½ì€ ì–´ë–¤ ë°°ë¦¬ì–´ë„ ë‚´í¬í•˜ì§€ 않습니다. [!] ì°¸ê³ : ë½ ACQUIRE 와 RELEASE ê°€ 단방향 배리어여서 나타나는 í˜„ìƒ ì¤‘ 하나는 í¬ë¦¬í‹°ì»¬ 섹션 ë°”ê¹¥ì˜ ì¸ìŠ¤íŠ¸ëŸì…˜ì˜ ì˜í–¥ì´ í¬ë¦¬í‹°ì»¬ 섹션 ë‚´ë¶€ë¡œë„ ë“¤ì–´ì˜¬ 수 있다는 것입니다. RELEASE í›„ì— ìš”ì²ë˜ëŠ” ACQUIRE 는 ì „ì²´ 메모리 ë°°ë¦¬ì–´ë¼ ì—¬ê²¨ì§€ë©´ 안ë˜ëŠ”ë°, ACQUIRE ì•žì˜ ì•¡ì„¸ìŠ¤ê°€ ACQUIRE í›„ì— ìˆ˜í–‰ë 수 ìžˆê³ , RELEASE í›„ì˜ ì•¡ì„¸ìŠ¤ê°€ RELEASE ì „ì— ìˆ˜í–‰ë ìˆ˜ë„ ìžˆìœ¼ë©°, ê·¸ ë‘ê°œì˜ ì•¡ì„¸ìŠ¤ê°€ 서로를 ì§€ë‚˜ì¹ ìˆ˜ë„ ìžˆê¸° 때문입니다: *A = a; ACQUIRE M RELEASE M *B = b; 는 다ìŒê³¼ ê°™ì´ ë ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤: ACQUIRE M, STORE *B, STORE *A, RELEASE M ACQUIRE 와 RELEASE ê°€ ë½ íšë“ê³¼ í•´ì œë¼ë©´, ê·¸ë¦¬ê³ ë½ì˜ ACQUIRE 와 RELEASE ê°€ ê°™ì€ ë½ ë³€ìˆ˜ì— ëŒ€í•œ 것ì´ë¼ë©´, 해당 ë½ì„ ì¥ê³ 있지 ì•Šì€ ë‹¤ë¥¸ CPU ì˜ ì‹œì•¼ì—는 ì´ì™€ ê°™ì€ ìž¬ë°°ì¹˜ê°€ ì¼ì–´ë‚˜ëŠ” 것으로 ë³´ì¼ ìˆ˜ 있습니다. 요약하ìžë©´, ACQUIRE ì— ì´ì–´ RELEASE 오í¼ë ˆì´ì…˜ì„ 순차ì 으로 실행하는 행위가 ì „ì²´ 메모리 배리어로 ìƒê°ë˜ì–´ì„ -안ë©ë‹ˆë‹¤-. 비슷하게, ì•žì˜ ë°˜ëŒ€ ì¼€ì´ìŠ¤ì¸ RELEASE 와 ACQUIRE ë‘ê°œ 오í¼ë ˆì´ì…˜ì˜ 순차ì 실행 ì—ì‹œ ì „ì²´ 메모리 배리어를 ë‚´í¬í•˜ì§€ 않습니다. ë”°ë¼ì„œ, RELEASE, ACQUIRE ë¡œ ê·œì •ë˜ëŠ” í¬ë¦¬í‹°ì»¬ ì„¹ì…˜ì˜ CPU ìˆ˜í–‰ì€ RELEASE 와 ACQUIRE 를 가로지를 수 있으므로, 다ìŒê³¼ ê°™ì€ ì½”ë“œëŠ”: *A = a; RELEASE M ACQUIRE N *B = b; 다ìŒê³¼ ê°™ì´ ìˆ˜í–‰ë 수 있습니다: ACQUIRE N, STORE *B, STORE *A, RELEASE M ì´ëŸ° 재배치는 ë°ë“œë½ì„ ì¼ìœ¼í‚¬ ìˆ˜ë„ ìžˆì„ ê²ƒì²˜ëŸ¼ ë³´ì¼ ìˆ˜ 있습니다. 하지만, 그런 ë°ë“œë½ì˜ ì¡°ì§ì´ 있다면 RELEASE 는 단순히 완료ë 것ì´ë¯€ë¡œ ë°ë“œë½ì€ ì¡´ìž¬í• ìˆ˜ 없습니다. ì´ê²Œ 어떻게 올바른 ë™ìž‘ì„ í• ìˆ˜ 있ì„까요? 우리가 ì´ì•¼ê¸° í•˜ê³ ìžˆëŠ”ê±´ 재배치를 하는 CPU ì— ëŒ€í•œ ì´ì•¼ê¸°ì´ì§€, 컴파ì¼ëŸ¬ì— 대한 ê²ƒì´ ì•„ë‹ˆëž€ ì ì´ í•µì‹¬ìž…ë‹ˆë‹¤. 컴파ì¼ëŸ¬ (ë˜ëŠ”, 개발ìž) ê°€ 오í¼ë ˆì´ì…˜ë“¤ì„ ì´ë ‡ê²Œ 재배치하면, ë°ë“œë½ì´ ì¼ì–´ë‚ 수 -있습-니다. 하지만 CPU ê°€ 오í¼ë ˆì´ì…˜ë“¤ì„ 재배치 했다는걸 ìƒê°í•´ 보세요. ì´ ì˜ˆì—ì„œ, 어셈블리 코드 ìƒìœ¼ë¡œëŠ” ì–¸ë½ì´ ë½ì„ 앞서게 ë˜ì–´ 있습니다. CPU ê°€ ì´ë¥¼ 재배치해서 ë’¤ì˜ ë½ ì˜¤í¼ë ˆì´ì…˜ì„ ë¨¼ì € 실행하게 ë©ë‹ˆë‹¤. 만약 ë°ë“œë½ì´ 존재한다면, ì´ ë½ ì˜¤í¼ë ˆì´ì…˜ì€ ê·¸ì € ìŠ¤í•€ì„ í•˜ë©° 계ì†í•´ì„œ ë½ì„ ì‹œë„합니다 (ë˜ëŠ”, 한참 후ì—ê² ì§€ë§Œ, ìž ë“니다). CPU 는 ì–¸ì 가는 (어셈블리 코드ì—서는 ë½ì„ 앞서는) ì–¸ë½ ì˜¤í¼ë ˆì´ì…˜ì„ 실행하는ë°, ì´ ì–¸ë½ ì˜¤í¼ë ˆì´ì…˜ì´ ìž ìž¬ì ë°ë“œë½ì„ í•´ê²°í•˜ê³ , ë½ ì˜¤í¼ë ˆì´ì…˜ë„ ë’¤ì´ì–´ 성공하게 ë©ë‹ˆë‹¤. 하지만 만약 ë½ì´ ìž ì„ ìžëŠ” 타입ì´ì—ˆë‹¤ë©´ìš”? 그런 ê²½ìš°ì— ì½”ë“œëŠ” 스케쥴러로 ë“¤ì–´ê°€ë ¤ í• ê±°ê³ , 여기서 ê²°êµì€ 메모리 배리어를 만나게 ë˜ëŠ”ë°, ì´ ë©”ëª¨ë¦¬ 배리어는 ì•žì˜ ì–¸ë½ ì˜¤í¼ë ˆì´ì…˜ì´ 완료ë˜ë„ë¡ ë§Œë“¤ê³ , ë°ë“œë½ì€ ì´ë²ˆì—ë„ í•´ê²°ë©ë‹ˆë‹¤. ìž ì„ ìžëŠ” 행위와 ì–¸ë½ ì‚¬ì´ì˜ 경주 ìƒí™© (race) ë„ ìžˆì„ ìˆ˜ ìžˆê² ìŠµë‹ˆë‹¤ë§Œ, ë½ ê´€ë ¨ ê¸°ëŠ¥ë“¤ì€ ê·¸ëŸ° 경주 ìƒí™©ì„ ëª¨ë“ ê²½ìš°ì— ì œëŒ€ë¡œ í•´ê²°í• ìˆ˜ 있어야 합니다. ë½ê³¼ 세마í¬ì–´ëŠ” UP 컴파ì¼ëœ 시스템ì—ì„œì˜ ìˆœì„œì— ëŒ€í•´ ë³´ìž¥ì„ í•˜ì§€ 않기 때문ì—, 그런 ìƒí™©ì—ì„œ ì¸í„°ëŸ½íŠ¸ 비활성화 오í¼ë ˆì´ì…˜ê³¼ 함께가 아니ë¼ë©´ ì–´ë–¤ ì¼ì—ë„ - 특히 I/O 액세스와 ê´€ë ¨í•´ì„œëŠ” - ì œëŒ€ë¡œ 사용ë 수 ì—†ì„ ê²ë‹ˆë‹¤. "CPU ê°„ ACQUIRING 배리어 효과" ì„¹ì…˜ë„ ì°¸ê³ í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. 예를 들어, 다ìŒê³¼ ê°™ì€ ì½”ë“œë¥¼ ìƒê°í•´ 봅시다: *A = a; *B = b; ACQUIRE *C = c; *D = d; RELEASE *E = e; *F = f; ì—¬ê¸°ì„ ë‹¤ìŒì˜ ì´ë²¤íŠ¸ 시퀀스가 ìƒê¸¸ 수 있습니다: ACQUIRE, {*F,*A}, *E, {*C,*D}, *B, RELEASE [+] {*F,*A} 는 ì¡°í•©ëœ ì•¡ì„¸ìŠ¤ë¥¼ ì˜ë¯¸í•©ë‹ˆë‹¤. 하지만 다ìŒê³¼ ê°™ì€ ê±´ ë¶ˆê°€ëŠ¥í•˜ì£ : {*F,*A}, *B, ACQUIRE, *C, *D, RELEASE, *E *A, *B, *C, ACQUIRE, *D, RELEASE, *E, *F *A, *B, ACQUIRE, *C, RELEASE, *D, *E, *F *B, ACQUIRE, *C, *D, RELEASE, {*F,*A}, *E ì¸í„°ëŸ½íŠ¸ 비활성화 함수 ---------------------- ì¸í„°ëŸ½íŠ¸ë¥¼ 비활성화 하는 함수 (ACQUIRE 와 ë™ì¼) 와 ì¸í„°ëŸ½íŠ¸ë¥¼ 활성화 하는 함수 (RELEASE 와 ë™ì¼) 는 컴파ì¼ëŸ¬ 배리어처럼만 ë™ìž‘합니다. ë”°ë¼ì„œ, 별ë„ì˜ ë©”ëª¨ë¦¬ 배리어나 I/O 배리어가 필요한 ìƒí™©ì´ë¼ë©´ ê·¸ ë°°ë¦¬ì–´ë“¤ì€ ì¸í„°ëŸ½íŠ¸ 비활성화 함수 ì™¸ì˜ ë°©ë²•ìœ¼ë¡œ ì œê³µë˜ì–´ì•¼ë§Œ 합니다. 슬립과 웨ì´í¬ì—… 함수 -------------------- 글로벌 ë°ì´í„°ì— í‘œì‹œëœ ì´ë²¤íŠ¸ì— ì˜í•´ 프로세스를 ìž ì— ë¹ íŠ¸ë¦¬ëŠ” 것과 깨우는 ê²ƒì€ í•´ë‹¹ ì´ë²¤íŠ¸ë¥¼ 기다리는 태스í¬ì˜ íƒœìŠ¤í¬ ìƒíƒœì™€ ê·¸ ì´ë²¤íŠ¸ë¥¼ 알리기 위해 사용ë˜ëŠ” 글로벌 ë°ì´í„°, ë‘ ë°ì´í„°ê°„ì˜ ìƒí˜¸ìž‘용으로 ë³¼ 수 있습니다. ì´ê²ƒì´ ì˜³ì€ ìˆœì„œëŒ€ë¡œ ì¼ì–´ë‚¨ì„ 분명히 하기 위해, 프로세스를 ìž ì— ë“¤ê²Œ 하는 기능과 깨우는 ê¸°ëŠ¥ì€ ëª‡ê°€ì§€ 배리어를 ë‚´í¬í•©ë‹ˆë‹¤. ë¨¼ì €, ìž ì„ ìž¬ìš°ëŠ” ìª½ì€ ì¼ë°˜ì 으로 다ìŒê³¼ ê°™ì€ ì´ë²¤íŠ¸ 시퀀스를 따릅니다: for (;;) { set_current_state(TASK_UNINTERRUPTIBLE); if (event_indicated) break; schedule(); } set_current_state() ì— ì˜í•´, íƒœìŠ¤í¬ ìƒíƒœê°€ ë°”ë€ í›„ 범용 메모리 배리어가 ìžë™ìœ¼ë¡œ 삽입ë©ë‹ˆë‹¤: CPU 1 =============================== set_current_state(); smp_store_mb(); STORE current->state <범용 배리어> LOAD event_indicated set_current_state() 는 다ìŒì˜ 것들로 ê°ì‹¸ì§ˆ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤: prepare_to_wait(); prepare_to_wait_exclusive(); ì´ê²ƒë“¤ ì—ì‹œ ìƒíƒœë¥¼ ì„¤ì •í•œ 후 범용 메모리 배리어를 삽입합니다. ì•žì˜ ì „ì²´ 시퀀스는 다ìŒê³¼ ê°™ì€ í•¨ìˆ˜ë“¤ë¡œ í•œë²ˆì— ìˆ˜í–‰ 가능한ë°, ì´ê²ƒë“¤ì€ ëª¨ë‘ ì˜¬ë°”ë¥¸ ìž¥ì†Œì— ë©”ëª¨ë¦¬ 배리어를 삽입합니다: wait_event(); wait_event_interruptible(); wait_event_interruptible_exclusive(); wait_event_interruptible_timeout(); wait_event_killable(); wait_event_timeout(); wait_on_bit(); wait_on_bit_lock(); ë‘번째로, 깨우기를 수행하는 코드는 ì¼ë°˜ì 으로 다ìŒê³¼ ê°™ì„ ê²ë‹ˆë‹¤: event_indicated = 1; wake_up(&event_wait_queue); ë˜ëŠ”: event_indicated = 1; wake_up_process(event_daemon); wake_up() ë¥˜ì— ì˜í•´ 쓰기 메모리 배리어가 ë‚´í¬ë©ë‹ˆë‹¤. 만약 ê·¸ê²ƒë“¤ì´ ë”가를 깨운다면요. ì´ ë°°ë¦¬ì–´ëŠ” íƒœìŠ¤í¬ ìƒíƒœê°€ 지워지기 ì „ì— ìˆ˜í–‰ë˜ë¯€ë¡œ, ì´ë²¤íŠ¸ë¥¼ 알리기 위한 STORE 와 íƒœìŠ¤í¬ ìƒíƒœë¥¼ TASK_RUNNING 으로 ì„¤ì •í•˜ëŠ” STORE 사ì´ì— 위치하게 ë©ë‹ˆë‹¤. CPU 1 CPU 2 =============================== =============================== set_current_state(); STORE event_indicated smp_store_mb(); wake_up(); STORE current->state <쓰기 배리어> <범용 배리어> STORE current->state LOAD event_indicated í•œë²ˆë” ë§í•©ë‹ˆë‹¤ë§Œ, ì´ ì“°ê¸° 메모리 배리어는 ì´ ì½”ë“œê°€ ì •ë§ë¡œ ë”가를 깨울 ë•Œì—만 실행ë©ë‹ˆë‹¤. ì´ê±¸ 설명하기 위해, X 와 Y 는 ëª¨ë‘ 0 으로 초기화 ë˜ì–´ 있다는 ê°€ì • í•˜ì— ì•„ëž˜ì˜ ì´ë²¤íŠ¸ 시퀀스를 ìƒê°í•´ 봅시다: CPU 1 CPU 2 =============================== =============================== X = 1; STORE event_indicated smp_mb(); wake_up(); Y = 1; wait_event(wq, Y == 1); wake_up(); load from Y sees 1, no memory barrier load from X might see 0 위 ì˜ˆì œì—ì„œì˜ ê²½ìš°ì™€ 달리 깨우기가 ì •ë§ë¡œ 행해졌다면, CPU 2 ì˜ X 로드는 1 ì„ ë³¸ë‹¤ê³ ë³´ìž¥ë 수 ìžˆì„ ê²ë‹ˆë‹¤. 사용 가능한 깨우기류 함수들로 다ìŒê³¼ ê°™ì€ ê²ƒë“¤ì´ ìžˆìŠµë‹ˆë‹¤: complete(); wake_up(); wake_up_all(); wake_up_bit(); wake_up_interruptible(); wake_up_interruptible_all(); wake_up_interruptible_nr(); wake_up_interruptible_poll(); wake_up_interruptible_sync(); wake_up_interruptible_sync_poll(); wake_up_locked(); wake_up_locked_poll(); wake_up_nr(); wake_up_poll(); wake_up_process(); [!] ìž ìž¬ìš°ëŠ” 코드와 깨우는 ì½”ë“œì— ë‚´í¬ë˜ëŠ” 메모리 ë°°ë¦¬ì–´ë“¤ì€ ê¹¨ìš°ê¸° ì „ì— ì´ë£¨ì–´ì§„ ìŠ¤í† ì–´ë¥¼ ìž ìž¬ìš°ëŠ” 코드가 set_current_state() 를 호출한 í›„ì— í–‰í•˜ëŠ” ë¡œë“œì— ëŒ€í•´ 순서를 맞추지 _않는다는_ ì ì„ ê¸°ì–µí•˜ì„¸ìš”. 예를 들어, ìž ìž¬ìš°ëŠ” 코드가 다ìŒê³¼ ê°™ê³ : set_current_state(TASK_INTERRUPTIBLE); if (event_indicated) break; __set_current_state(TASK_RUNNING); do_something(my_data); 깨우는 코드는 다ìŒê³¼ 같다면: my_data = value; event_indicated = 1; wake_up(&event_wait_queue); event_indecated ì—ì˜ ë³€ê²½ì´ ìž ìž¬ìš°ëŠ” 코드ì—게 my_data ì—ì˜ ë³€ê²½ í›„ì— ì´ë£¨ì–´ì§„ 것으로 ì¸ì§€ë 것ì´ë¼ëŠ” ë³´ìž¥ì´ ì—†ìŠµë‹ˆë‹¤. ì´ëŸ° 경우ì—는 양쪽 코드 ëª¨ë‘ ê°ê°ì˜ ë°ì´í„° 액세스 사ì´ì— 메모리 배리어를 ì§ì ‘ ì³ì•¼ 합니다. ë”°ë¼ì„œ ì•žì˜ ìž¬ìš°ëŠ” 코드는 다ìŒê³¼ ê°™ì´: set_current_state(TASK_INTERRUPTIBLE); if (event_indicated) { smp_rmb(); do_something(my_data); } ê·¸ë¦¬ê³ ê¹¨ìš°ëŠ” 코드는 다ìŒê³¼ ê°™ì´ ë˜ì–´ì•¼ 합니다: my_data = value; smp_wmb(); event_indicated = 1; wake_up(&event_wait_queue); ê·¸ì™¸ì˜ í•¨ìˆ˜ë“¤ ------------- ê·¸ì™¸ì˜ ë°°ë¦¬ì–´ë¥¼ ë‚´í¬í•˜ëŠ” í•¨ìˆ˜ë“¤ì€ ë‹¤ìŒê³¼ 같습니다: (*) schedule() ê³¼ ê·¸ ìœ ì‚¬í•œ ê²ƒë“¤ì´ ì™„ì „í•œ 메모리 배리어를 ë‚´í¬í•©ë‹ˆë‹¤. ============================== CPU ê°„ ACQUIRING ë°°ë¦¬ì–´ì˜ íš¨ê³¼ ============================== SMP 시스템ì—ì„œì˜ ë½ ê¸°ëŠ¥ë“¤ì€ ë”ìš± ê°•ë ¥í•œ í˜•íƒœì˜ ë°°ë¦¬ì–´ë¥¼ ì œê³µí•©ë‹ˆë‹¤: ì´ ë°°ë¦¬ì–´ëŠ” ë™ì¼í•œ ë½ì„ 사용하는 다른 CPU ë“¤ì˜ ë©”ëª¨ë¦¬ 액세스 순서ì—ë„ ì˜í–¥ì„ ë¼ì¹©ë‹ˆë‹¤. ACQUIRE VS 메모리 액세스 ------------------------ 다ìŒì˜ 예를 ìƒê°í•´ 봅시다: ì‹œìŠ¤í…œì€ ë‘ê°œì˜ ìŠ¤í•€ë½ (M) ê³¼ (Q), ê·¸ë¦¬ê³ ì„¸ê°œì˜ CPU 를 ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤; ì—¬ê¸°ì— ë‹¤ìŒì˜ ì´ë²¤íŠ¸ 시퀀스가 ë°œìƒí•©ë‹ˆë‹¤: CPU 1 CPU 2 =============================== =============================== WRITE_ONCE(*A, a); WRITE_ONCE(*E, e); ACQUIRE M ACQUIRE Q WRITE_ONCE(*B, b); WRITE_ONCE(*F, f); WRITE_ONCE(*C, c); WRITE_ONCE(*G, g); RELEASE M RELEASE Q WRITE_ONCE(*D, d); WRITE_ONCE(*H, h); *A ë¡œì˜ ì•¡ì„¸ìŠ¤ë¶€í„° *H ë¡œì˜ ì•¡ì„¸ìŠ¤ê¹Œì§€ê°€ ì–´ë–¤ 순서로 CPU 3 ì—게 ë³´ì—¬ì§ˆì§€ì— ëŒ€í•´ì„œëŠ” ê° CPU ì—ì„œì˜ ë½ ì‚¬ìš©ì— ì˜í•´ ë‚´í¬ë˜ì–´ 있는 ì œì•½ì„ ì œì™¸í•˜ê³ ëŠ” ì–´ë–¤ ë³´ìž¥ë„ ì¡´ìž¬í•˜ì§€ 않습니다. 예를 들어, CPU 3 ì—게 다ìŒê³¼ ê°™ì€ ìˆœì„œë¡œ 보여지는 ê²ƒì´ ê°€ëŠ¥í•©ë‹ˆë‹¤: *E, ACQUIRE M, ACQUIRE Q, *G, *C, *F, *A, *B, RELEASE Q, *D, *H, RELEASE M 하지만 다ìŒê³¼ ê°™ì´ ë³´ì´ì§€ëŠ” ì•Šì„ ê²ë‹ˆë‹¤: *B, *C or *D preceding ACQUIRE M *A, *B or *C following RELEASE M *F, *G or *H preceding ACQUIRE Q *E, *F or *G following RELEASE Q ACQUIRE VS I/O 액세스 ---------------------- íŠ¹ì •í•œ (특히 NUMA ê°€ ê´€ë ¨ëœ) 환경 하ì—ì„œ ë‘ê°œì˜ CPU ì—ì„œ ë™ì¼í•œ 스핀ë½ìœ¼ë¡œ 보호ë˜ëŠ” ë‘ê°œì˜ í¬ë¦¬í‹°ì»¬ 섹션 ì•ˆì˜ I/O 액세스는 PCI ë¸Œë¦¿ì§€ì— ê²¹ì³ì§„ I/O 액세스로 ë³´ì¼ ìˆ˜ 있는ë°, PCI 브릿지는 ìºì‹œ ì¼ê´€ì„± í”„ë¡œí† ì½œê³¼ í•©ì„ ë§žì¶°ì•¼ í• ì˜ë¬´ê°€ 없으므로, 필요한 ì½ê¸° 메모리 배리어가 ìš”ì²ë˜ì§€ 않기 때문입니다. 예를 들어서: CPU 1 CPU 2 =============================== =============================== spin_lock(Q) writel(0, ADDR) writel(1, DATA); spin_unlock(Q); spin_lock(Q); writel(4, ADDR); writel(5, DATA); spin_unlock(Q); 는 PCI ë¸Œë¦¿ì§€ì— ë‹¤ìŒê³¼ ê°™ì´ ë³´ì¼ ìˆ˜ 있습니다: STORE *ADDR = 0, STORE *ADDR = 4, STORE *DATA = 1, STORE *DATA = 5 ì´ë ‡ê²Œ ë˜ë©´ í•˜ë“œì›¨ì–´ì˜ ì˜¤ë™ìž‘ì„ ì¼ìœ¼í‚¬ 수 있습니다. ì´ëŸ° 경우엔 ìž¡ì•„ë‘” 스핀ë½ì„ ë‚´ë ¤ë†“ê¸° ì „ì— mmiowb() 를 수행해야 하는ë°, 예를 들면 다ìŒê³¼ 같습니다: CPU 1 CPU 2 =============================== =============================== spin_lock(Q) writel(0, ADDR) writel(1, DATA); mmiowb(); spin_unlock(Q); spin_lock(Q); writel(4, ADDR); writel(5, DATA); mmiowb(); spin_unlock(Q); ì´ ì½”ë“œëŠ” CPU 1 ì—ì„œ ìš”ì²ëœ ë‘ê°œì˜ ìŠ¤í† ì–´ê°€ PCI ë¸Œë¦¿ì§€ì— CPU 2 ì—ì„œ ìš”ì²ëœ ìŠ¤í† ì–´ë“¤ë³´ë‹¤ ë¨¼ì € ë³´ì—¬ì§ì„ 보장합니다. ë˜í•œ, ê°™ì€ ë””ë°”ì´ìŠ¤ì—ì„œ ìŠ¤í† ì–´ë¥¼ ì´ì–´ 로드가 수행ë˜ë©´ ì´ ë¡œë“œëŠ” 로드가 수행ë˜ê¸° ì „ì— ìŠ¤í† ì–´ê°€ 완료ë˜ê¸°ë¥¼ ê°•ì œí•˜ë¯€ë¡œ mmiowb() ì˜ í•„ìš”ê°€ 없어집니다: CPU 1 CPU 2 =============================== =============================== spin_lock(Q) writel(0, ADDR) a = readl(DATA); spin_unlock(Q); spin_lock(Q); writel(4, ADDR); b = readl(DATA); spin_unlock(Q); ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ Documentation/driver-api/device-io.rst 를 ì°¸ê³ í•˜ì„¸ìš”. ========================= 메모리 배리어가 필요한 ê³³ ========================= ì„¤ë ¹ SMP 커ë„ì„ ì‚¬ìš©í•˜ë”ë¼ë„ 싱글 ì“°ë ˆë“œë¡œ ë™ìž‘하는 코드는 올바르게 ë™ìž‘하는 것으로 보여질 것ì´ê¸° 때문ì—, í‰ë²”í•œ 시스템 ìš´ì˜ì¤‘ì— ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜ 재배치는 ì¼ë°˜ì 으로 ë¬¸ì œê°€ ë˜ì§€ 않습니다. 하지만, 재배치가 ë¬¸ì œê°€ _ë 수 있는_ 네가지 í™˜ê²½ì´ ìžˆìŠµë‹ˆë‹¤: (*) 프로세서간 ìƒí˜¸ ìž‘ìš©. (*) ì–´í† ë¯¹ 오í¼ë ˆì´ì…˜. (*) 디바ì´ìŠ¤ 액세스. (*) ì¸í„°ëŸ½íŠ¸. 프로세서간 ìƒí˜¸ ìž‘ìš© -------------------- ë‘ê°œ ì´ìƒì˜ 프로세서를 가진 ì‹œìŠ¤í…œì´ ìžˆë‹¤ë©´, ì‹œìŠ¤í…œì˜ ë‘ê°œ ì´ìƒì˜ CPU 는 ë™ì‹œì— ê°™ì€ ë°ì´í„°ì— 대한 ìž‘ì—…ì„ í• ìˆ˜ 있습니다. ì´ëŠ” ë™ê¸°í™” ë¬¸ì œë¥¼ ì¼ìœ¼í‚¬ 수 ìžˆê³ , ì´ ë¬¸ì œë¥¼ 해결하는 ì¼ë°˜ì ë°©ë²•ì€ ë½ì„ 사용하는 것입니다. 하지만, ë½ì€ ìƒë‹¹ížˆ ë¹„ìš©ì´ ë¹„ì‹¸ì„œ 가능하면 ë½ì„ 사용하지 ì•Šê³ ì¼ì„ 처리하는 ê²ƒì´ ë‚«ìŠµë‹ˆë‹¤. ì´ëŸ° 경우, ë‘ CPU 모ë‘ì— ì˜í–¥ì„ ë¼ì¹˜ëŠ” 오í¼ë ˆì´ì…˜ë“¤ì€ 오ë™ìž‘ì„ ë§‰ê¸° 위해 ì‹ ì¤‘í•˜ê²Œ 순서가 ë§žì¶°ì ¸ì•¼ 합니다. 예를 들어, R/W 세마í¬ì–´ì˜ ëŠë¦° 수행경로 (slow path) 를 ìƒê°í•´ 봅시다. 세마í¬ì–´ë¥¼ 위해 대기를 하는 í•˜ë‚˜ì˜ í”„ë¡œì„¸ìŠ¤ê°€ ìžì‹ ì˜ ìŠ¤íƒ ì¤‘ ì¼ë¶€ë¥¼ ì´ ì„¸ë§ˆí¬ì–´ì˜ 대기 프로세스 ë¦¬ìŠ¤íŠ¸ì— ë§í¬í•œ 채로 있습니다: struct rw_semaphore { ... spinlock_t lock; struct list_head waiters; }; struct rwsem_waiter { struct list_head list; struct task_struct *task; }; íŠ¹ì • 대기 ìƒíƒœ 프로세스를 깨우기 위해, up_read() 나 up_write() 함수는 다ìŒê³¼ ê°™ì€ ì¼ì„ 합니다: (1) ë‹¤ìŒ ëŒ€ê¸° ìƒíƒœ 프로세스 ë ˆì½”ë“œëŠ” 어디있는지 알기 위해 ì´ ëŒ€ê¸° ìƒíƒœ 프로세스 ë ˆì½”ë“œì˜ next í¬ì¸í„°ë¥¼ ì½ìŠµë‹ˆë‹¤; (2) ì´ ëŒ€ê¸° ìƒíƒœ í”„ë¡œì„¸ìŠ¤ì˜ task êµ¬ì¡°ì²´ë¡œì˜ í¬ì¸í„°ë¥¼ ì½ìŠµë‹ˆë‹¤; (3) ì´ ëŒ€ê¸° ìƒíƒœ 프로세스가 세마í¬ì–´ë¥¼ íšë“í–ˆìŒì„ 알리기 위해 task í¬ì¸í„°ë¥¼ 초기화 합니다; (4) 해당 태스í¬ì— 대해 wake_up_process() 를 호출합니다; ê·¸ë¦¬ê³ (5) 해당 대기 ìƒíƒœ í”„ë¡œì„¸ìŠ¤ì˜ task 구조체를 ìž¡ê³ ìžˆë˜ ë ˆí¼ëŸ°ìŠ¤ë¥¼ í•´ì œí•©ë‹ˆë‹¤. 달리 ë§í•˜ìžë©´, ë‹¤ìŒ ì´ë²¤íŠ¸ 시퀀스를 수행해야 합니다: LOAD waiter->list.next; LOAD waiter->task; STORE waiter->task; CALL wakeup RELEASE task ê·¸ë¦¬ê³ ì´ ì´ë²¤íŠ¸ë“¤ì´ 다른 순서로 수행ëœë‹¤ë©´, 오ë™ìž‘ì´ ì¼ì–´ë‚ 수 있습니다. 한번 세마í¬ì–´ì˜ ëŒ€ê¸°ì¤„ì— ë“¤ì–´ê°”ê³ ì„¸ë§ˆí¬ì–´ ë½ì„ 놓았다면, 해당 대기 프로세스는 ë½ì„ 다시는 잡지 않습니다; ëŒ€ì‹ ìžì‹ ì˜ task í¬ì¸í„°ê°€ 초기화 ë˜ê¸¸ 기다립니다. ê·¸ ë ˆì½”ë“œëŠ” 대기 í”„ë¡œì„¸ìŠ¤ì˜ ìŠ¤íƒì— 있기 때문ì—, ë¦¬ìŠ¤íŠ¸ì˜ next í¬ì¸í„°ê°€ ì½í˜€ì§€ê¸° _ì „ì—_ task í¬ì¸í„°ê°€ 지워진다면, 다른 CPU 는 해당 대기 프로세스를 시작해 ë²„ë¦¬ê³ up*() 함수가 next í¬ì¸í„°ë¥¼ ì½ê¸° ì „ì— ëŒ€ê¸° í”„ë¡œì„¸ìŠ¤ì˜ ìŠ¤íƒì„ 마구 건드릴 수 있습니다. ê·¸ë ‡ê²Œ ë˜ë©´ ìœ„ì˜ ì´ë²¤íŠ¸ ì‹œí€€ìŠ¤ì— ì–´ë–¤ ì¼ì´ ì¼ì–´ë‚˜ëŠ”지 ìƒê°í•´ ë³´ì£ : CPU 1 CPU 2 =============================== =============================== down_xxx() Queue waiter Sleep up_yyy() LOAD waiter->task; STORE waiter->task; Woken up by other event <preempt> Resume processing down_xxx() returns call foo() foo() clobbers *waiter </preempt> LOAD waiter->list.next; --- OOPS --- ì´ ë¬¸ì œëŠ” 세마í¬ì–´ ë½ì˜ 사용으로 í•´ê²°ë ìˆ˜ë„ ìžˆê² ì§€ë§Œ, ê·¸ë ‡ê²Œ ë˜ë©´ 깨어난 í›„ì— down_xxx() 함수가 불필요하게 스핀ë½ì„ ë˜ë‹¤ì‹œ 얻어야만 합니다. ì´ ë¬¸ì œë¥¼ 해결하는 ë°©ë²•ì€ ë²”ìš© SMP 메모리 배리어를 추가하는 ê²ë‹ˆë‹¤: LOAD waiter->list.next; LOAD waiter->task; smp_mb(); STORE waiter->task; CALL wakeup RELEASE task ì´ ê²½ìš°ì—, 배리어는 ì‹œìŠ¤í…œì˜ ë‚˜ë¨¸ì§€ CPU 들ì—게 ëª¨ë“ ë°°ë¦¬ì–´ ì•žì˜ ë©”ëª¨ë¦¬ 액세스가 배리어 ë’¤ì˜ ë©”ëª¨ë¦¬ 액세스보다 ì•žì„œ ì¼ì–´ë‚œ 것으로 ë³´ì´ê²Œ 만ë“니다. 배리어 ì•žì˜ ë©”ëª¨ë¦¬ ì•¡ì„¸ìŠ¤ë“¤ì´ ë°°ë¦¬ì–´ ëª…ë ¹ ìžì²´ê°€ 완료ë˜ëŠ” ì‹œì 까지 완료ëœë‹¤ê³ 는 보장하지 _않습니다_. (ì´ê²Œ ë¬¸ì œê°€ ë˜ì§€ ì•Šì„) ë‹¨ì¼ í”„ë¡œì„¸ì„œ 시스템ì—ì„œ smp_mb() 는 ì‹¤ì œë¡œëŠ” ê·¸ì € 컴파ì¼ëŸ¬ê°€ CPU 안ì—ì„œì˜ ìˆœì„œë¥¼ 바꾸거나 하지 ì•Šê³ ì£¼ì–´ì§„ 순서대로 ëª…ë ¹ì„ ë‚´ë¦¬ë„ë¡ í•˜ëŠ” 컴파ì¼ëŸ¬ ë°°ë¦¬ì–´ì¼ ë¿ìž…니다. ì˜¤ì§ í•˜ë‚˜ì˜ CPU 만 있으니, CPU ì˜ ì˜ì¡´ì„± 순서 ë¡œì§ì´ ê·¸ ì™¸ì˜ ëª¨ë“ ê²ƒì„ ì•Œì•„ì„œ ì²˜ë¦¬í• ê²ë‹ˆë‹¤. ì–´í† ë¯¹ 오í¼ë ˆì´ì…˜ ----------------- ì–´í† ë¯¹ 오í¼ë ˆì´ì…˜ì€ ê¸°ìˆ ì 으로 프로세서간 ìƒí˜¸ìž‘용으로 분류ë˜ë©° ê·¸ 중 ì¼ë¶€ëŠ” ì „ì²´ 메모리 배리어를 ë‚´í¬í•˜ê³ ë˜ ì¼ë¶€ëŠ” ë‚´í¬í•˜ì§€ 않지만, 커ë„ì—ì„œ ìƒë‹¹ížˆ ì˜ì¡´ì 으로 사용하는 기능 중 하나입니다. ë” ë§Žì€ ë‚´ìš©ì„ ìœ„í•´ì„ Documentation/atomic_t.txt 를 ì°¸ê³ í•˜ì„¸ìš”. 디바ì´ìŠ¤ 액세스 --------------- ë§Žì€ ë””ë°”ì´ìŠ¤ê°€ 메모리 매핑 기법으로 ì œì–´ë 수 있는ë°, ê·¸ë ‡ê²Œ ì œì–´ë˜ëŠ” 디바ì´ìŠ¤ëŠ” CPU ì—는 단지 íŠ¹ì • 메모리 ì˜ì—ì˜ ì§‘í•©ì²˜ëŸ¼ ë³´ì´ê²Œ ë©ë‹ˆë‹¤. ë“œë¼ì´ë²„는 그런 디바ì´ìŠ¤ë¥¼ ì œì–´í•˜ê¸° 위해 ì •í™•ížˆ 올바른 순서로 올바른 메모리 액세스를 만들어야 합니다. 하지만, ì•¡ì„¸ìŠ¤ë“¤ì„ ìž¬ë°°ì¹˜ 하거나 조합하거나 병합하는게 ë” íš¨ìœ¨ì ì´ë¼ íŒë‹¨í•˜ëŠ” ì˜ë¦¬í•œ CPU 나 컴파ì¼ëŸ¬ë“¤ì„ 사용하면 ë“œë¼ì´ë²„ ì½”ë“œì˜ ì¡°ì‹¬ìŠ¤ëŸ½ê²Œ 순서 맞춰진 ì•¡ì„¸ìŠ¤ë“¤ì´ ë””ë°”ì´ìŠ¤ì—는 ìš”ì²ëœ 순서대로 ë„착하지 못하게 í• ìˆ˜ 있는 - 디바ì´ìŠ¤ê°€ 오ë™ìž‘ì„ í•˜ê²Œ í• - ìž ìž¬ì ë¬¸ì œê°€ ìƒê¸¸ 수 있습니다. 리눅스 ì»¤ë„ ë‚´ë¶€ì—ì„œ, I/O 는 어떻게 ì•¡ì„¸ìŠ¤ë“¤ì„ ì ì ˆížˆ 순차ì ì´ê²Œ 만들 수 있는지 ì•Œê³ ìžˆëŠ”, - inb() 나 writel() ê³¼ ê°™ì€ - ì ì ˆí•œ 액세스 ë£¨í‹´ì„ í†µí•´ ì´ë£¨ì–´ì ¸ì•¼ë§Œ 합니다. ì´ê²ƒë“¤ì€ ëŒ€ë¶€ë¶„ì˜ ê²½ìš°ì—는 명시ì 메모리 배리어 와 함께 사용ë 필요가 없습니다만, 다ìŒì˜ ë‘가지 ìƒí™©ì—서는 명시ì 메모리 배리어가 í•„ìš”í• ìˆ˜ 있습니다: (1) ì¼ë¶€ 시스템ì—ì„œ I/O ìŠ¤í† ì–´ëŠ” ëª¨ë“ CPU ì— ì¼ê´€ë˜ê²Œ 순서 맞춰지지 않는ë°, ë”°ë¼ì„œ _ëª¨ë“ _ ì¼ë°˜ì ì¸ ë“œë¼ì´ë²„ë“¤ì— ë½ì´ 사용ë˜ì–´ì•¼ë§Œ í•˜ê³ ì´ í¬ë¦¬í‹°ì»¬ ì„¹ì…˜ì„ ë¹ ì ¸ë‚˜ì˜¤ê¸° ì „ì— mmiowb() ê°€ ê¼ í˜¸ì¶œë˜ì–´ì•¼ 합니다. (2) 만약 액세스 í•¨ìˆ˜ë“¤ì´ ì™„í™”ëœ ë©”ëª¨ë¦¬ 액세스 ì†ì„±ì„ 갖는 I/O 메모리 윈ë„우를 사용한다면, 순서를 ê°•ì œí•˜ê¸° ìœ„í•´ì„ _mandatory_ 메모리 배리어가 필요합니다. ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ Documentation/driver-api/device-io.rst 를 ì°¸ê³ í•˜ì‹ì‹œì˜¤. ì¸í„°ëŸ½íŠ¸ -------- ë“œë¼ì´ë²„는 ìžì‹ ì˜ ì¸í„°ëŸ½íŠ¸ 서비스 ë£¨í‹´ì— ì˜í•´ ì¸í„°ëŸ½íŠ¸ ë‹¹í• ìˆ˜ 있기 ë•Œë¬¸ì— ë“œë¼ì´ë²„ì˜ ì´ ë‘ ë¶€ë¶„ì€ ì„œë¡œì˜ ë””ë°”ì´ìŠ¤ ì œì–´ ë˜ëŠ” 액세스 부분과 ìƒí˜¸ ê°„ì„í• ìˆ˜ 있습니다. 스스로ì—게 ì¸í„°ëŸ½íŠ¸ 당하는 걸 불가능하게 í•˜ê³ , ë“œë¼ì´ë²„ì˜ í¬ë¦¬í‹°ì»¬í•œ 오í¼ë ˆì´ì…˜ë“¤ì„ ëª¨ë‘ ì¸í„°ëŸ½íŠ¸ê°€ 불가능하게 ëœ ì˜ì—ì— ì§‘ì–´ë„£ê±°ë‚˜ 하는 방법 (ë½ì˜ í•œ 형태) 으로 ì´ëŸ° ìƒí˜¸ ê°„ì„ì„ - 최소한 부분ì 으로ë¼ë„ - ì¤„ì¼ ìˆ˜ 있습니다. ë“œë¼ì´ë²„ì˜ ì¸í„°ëŸ½íŠ¸ ë£¨í‹´ì´ ì‹¤í–‰ ì¤‘ì¸ ë™ì•ˆ, 해당 ë“œë¼ì´ë²„ì˜ ì½”ì–´ëŠ” ê°™ì€ CPU ì—ì„œ 수행ë˜ì§€ ì•Šì„ ê²ƒì´ë©°, í˜„ìž¬ì˜ ì¸í„°ëŸ½íŠ¸ê°€ 처리ë˜ëŠ” 중ì—는 ë˜ë‹¤ì‹œ ì¸í„°ëŸ½íŠ¸ê°€ ì¼ì–´ë‚˜ì§€ 못하ë„ë¡ ë˜ì–´ 있으니 ì¸í„°ëŸ½íŠ¸ 핸들러는 ê·¸ì— ëŒ€í•´ì„œëŠ” ë½ì„ 잡지 ì•Šì•„ë„ ë©ë‹ˆë‹¤. 하지만, ì–´ë“œë ˆìŠ¤ ë ˆì§€ìŠ¤í„°ì™€ ë°ì´í„° ë ˆì§€ìŠ¤í„°ë¥¼ 갖는 ì´ë”ë„· 카드를 다루는 ë“œë¼ì´ë²„를 ìƒê°í•´ 봅시다. 만약 ì´ ë“œë¼ì´ë²„ì˜ ì½”ì–´ê°€ ì¸í„°ëŸ½íŠ¸ë¥¼ 비활성화시킨 채로 ì´ë”ë„· 카드와 ëŒ€í™”í•˜ê³ ë“œë¼ì´ë²„ì˜ ì¸í„°ëŸ½íŠ¸ 핸들러가 호출ë˜ì—ˆë‹¤ë©´: LOCAL IRQ DISABLE writew(ADDR, 3); writew(DATA, y); LOCAL IRQ ENABLE <interrupt> writew(ADDR, 4); q = readw(DATA); </interrupt> 만약 순서 ê·œì¹™ì´ ì¶©ë¶„ížˆ 완화ë˜ì–´ 있다면 ë°ì´í„° ë ˆì§€ìŠ¤í„°ì—ì˜ ìŠ¤í† ì–´ëŠ” ì–´ë“œë ˆìŠ¤ ë ˆì§€ìŠ¤í„°ì— ë‘번째로 행해지는 ìŠ¤í† ì–´ ë’¤ì— ì¼ì–´ë‚ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤: STORE *ADDR = 3, STORE *ADDR = 4, STORE *DATA = y, q = LOAD *DATA 만약 순서 ê·œì¹™ì´ ì¶©ë¶„ížˆ 완화ë˜ì–´ ìžˆê³ ë¬µì‹œì ìœ¼ë¡œë“ ëª…ì‹œì ìœ¼ë¡œë“ ë°°ë¦¬ì–´ê°€ 사용ë˜ì§€ 않았다면 ì¸í„°ëŸ½íŠ¸ 비활성화 섹션ì—ì„œ ì¼ì–´ë‚œ 액세스가 바깥으로 새어서 ì¸í„°ëŸ½íŠ¸ ë‚´ì—ì„œ ì¼ì–´ë‚œ 액세스와 ì„žì¼ ìˆ˜ ìžˆë‹¤ê³ - ê·¸ë¦¬ê³ ê·¸ ë°˜ëŒ€ë„ - ê°€ì •í•´ì•¼ë§Œ 합니다. 그런 ì˜ì— 안ì—ì„œ ì¼ì–´ë‚˜ëŠ” I/O ì•¡ì„¸ìŠ¤ë“¤ì€ ì—„ê²©í•œ 순서 ê·œì¹™ì˜ I/O ë ˆì§€ìŠ¤í„°ì— ë¬µì‹œì I/O 배리어를 형성하는 ë™ê¸°ì (synchronous) 로드 오í¼ë ˆì´ì…˜ì„ í¬í•¨í•˜ê¸° ë•Œë¬¸ì— ì¼ë°˜ì 으로는 ì´ëŸ°ê²Œ ë¬¸ì œê°€ ë˜ì§€ 않습니다. 만약 ì´ê±¸ë¡œëŠ” 충분치 않다면 mmiowb() ê°€ 명시ì 으로 사용ë 필요가 있습니다. í•˜ë‚˜ì˜ ì¸í„°ëŸ½íŠ¸ 루틴과 별ë„ì˜ CPU ì—ì„œ 수행중ì´ë©° 서로 í†µì‹ ì„ í•˜ëŠ” ë‘ ë£¨í‹´ 사ì´ì—ë„ ë¹„ìŠ·í•œ ìƒí™©ì´ ì¼ì–´ë‚ 수 있습니다. 만약 그런 경우가 ë°œìƒí• ê°€ëŠ¥ì„±ì´ ìžˆë‹¤ë©´, 순서를 보장하기 위해 ì¸í„°ëŸ½íŠ¸ 비활성화 ë½ì´ 사용ë˜ì–´ì ¸ì•¼ë§Œ 합니다. ====================== ì»¤ë„ I/O ë°°ë¦¬ì–´ì˜ íš¨ê³¼ ====================== I/O ë©”ëª¨ë¦¬ì— ì•¡ì„¸ìŠ¤í• ë•Œ, ë“œë¼ì´ë²„는 ì ì ˆí•œ 액세스 함수를 사용해야 합니다: (*) inX(), outX(): ì´ê²ƒë“¤ì€ 메모리 공간보다는 I/O ê³µê°„ì— ì´ì•¼ê¸°ë¥¼ í•˜ë ¤ëŠ” ì˜ë„ë¡œ 만들어졌습니다만, 그건 기본ì 으로 CPU 마다 다른 컨셉입니다. i386 ê³¼ x86_64 í”„ë¡œì„¸ì„œë“¤ì€ íŠ¹ë³„í•œ I/O 공간 액세스 사ì´í´ê³¼ ëª…ë ¹ì–´ë¥¼ ì‹¤ì œë¡œ ê°€ì§€ê³ ìžˆì§€ë§Œ, 다른 ë§Žì€ CPU 들ì—는 그런 ì»¨ì…‰ì´ ì¡´ìž¬í•˜ì§€ 않습니다. 다른 것들 중ì—ì„œë„ PCI 버스가 I/O 공간 ì»¨ì…‰ì„ ì •ì˜í•˜ëŠ”ë°, ì´ëŠ” - i386 ê³¼ x86_64 ê°™ì€ CPU ì—ì„œ - CPU ì˜ I/O 공간 컨셉으로 쉽게 매치ë©ë‹ˆë‹¤. 하지만, ëŒ€ì²´í• I/O ê³µê°„ì´ ì—†ëŠ” CPU ì—서는 CPU ì˜ ë©”ëª¨ë¦¬ ë§µì˜ ê°€ìƒ I/O 공간으로 매핑ë ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ ê³µê°„ìœ¼ë¡œì˜ ì•¡ì„¸ìŠ¤ëŠ” (i386 등ì—서는) ì™„ì „í•˜ê²Œ ë™ê¸°í™” ë©ë‹ˆë‹¤ë§Œ, ì¤‘ê°„ì˜ (PCI 호스트 브리지와 ê°™ì€) ë¸Œë¦¬ì§€ë“¤ì€ ì´ë¥¼ ì™„ì „ížˆ 보장하진 ì•Šì„ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ê²ƒë“¤ì˜ ìƒí˜¸ê°„ì˜ ìˆœì„œëŠ” ì™„ì „í•˜ê²Œ 보장ë©ë‹ˆë‹¤. 다른 íƒ€ìž…ì˜ ë©”ëª¨ë¦¬ 오í¼ë ˆì´ì…˜, I/O 오í¼ë ˆì´ì…˜ì— 대한 순서는 ì™„ì „í•˜ê²Œ 보장ë˜ì§€ëŠ” 않습니다. (*) readX(), writeX(): ì´ê²ƒë“¤ì´ 수행 ìš”ì²ë˜ëŠ” CPU ì—ì„œ 서로ì—게 ì™„ì „ížˆ 순서가 ë§žì¶°ì§€ê³ ë…립ì 으로 수행ë˜ëŠ”ì§€ì— ëŒ€í•œ 보장 여부는 ì´ë“¤ì´ 액세스 하는 메모리 윈ë„ìš°ì— ì •ì˜ëœ íŠ¹ì„±ì— ì˜í•´ ê²°ì •ë©ë‹ˆë‹¤. 예를 들어, ìµœì‹ ì˜ i386 아키í…ì³ ë¨¸ì‹ ì—서는 MTRR ë ˆì§€ìŠ¤í„°ë¡œ ì´ íŠ¹ì„±ì´ ì¡°ì •ë©ë‹ˆë‹¤. ì¼ë°˜ì 으로는, 프리페치 (prefetch) 가능한 디바ì´ìŠ¤ë¥¼ 액세스 하는게 아니ë¼ë©´, ì´ê²ƒë“¤ì€ ì™„ì „ížˆ 순서가 ë§žì¶°ì§€ê³ ê²°í•©ë˜ì§€ 않게 보장ë ê²ë‹ˆë‹¤. 하지만, (PCI 브리지와 ê°™ì€) ì¤‘ê°„ì˜ í•˜ë“œì›¨ì–´ëŠ” ìžì‹ ì´ ì›í•œë‹¤ë©´ ì§‘í–‰ì„ ì—°ê¸°ì‹œí‚¬ 수 있습니다; ìŠ¤í† ì–´ ëª…ë ¹ì„ ì‹¤ì œë¡œ 하드웨어로 ë‚´ë ¤ë³´ë‚´ê¸°(flush) 위해서는 ê°™ì€ ìœ„ì¹˜ë¡œë¶€í„° 로드를 하는 ë°©ë²•ì´ ìžˆìŠµë‹ˆë‹¤ë§Œ[*], PCI ì˜ ê²½ìš°ëŠ” ê°™ì€ ë””ë°”ì´ìŠ¤ë‚˜ 환경 구성 ì˜ì—ì—ì„œì˜ ë¡œë“œë§Œìœ¼ë¡œë„ ì¶©ë¶„í• ê²ë‹ˆë‹¤. [*] 주ì˜! 쓰여진 것과 ê°™ì€ ìœ„ì¹˜ë¡œë¶€í„°ì˜ ë¡œë“œë¥¼ ì‹œë„하는 ê²ƒì€ ì˜¤ë™ìž‘ì„ ì¼ìœ¼í‚¬ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤ - 예로 16650 Rx/Tx 시리얼 ë ˆì§€ìŠ¤í„°ë¥¼ ìƒê°í•´ 보세요. 프리페치 가능한 I/O 메모리가 사용ë˜ë©´, ìŠ¤í† ì–´ ëª…ë ¹ë“¤ì´ ìˆœì„œë¥¼ 지키ë„ë¡ í•˜ê¸° 위해 mmiowb() 배리어가 í•„ìš”í• ìˆ˜ 있습니다. PCI 트랜ìžì…˜ 사ì´ì˜ ìƒí˜¸ìž‘ìš©ì— ëŒ€í•´ ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ PCI 명세서를 ì°¸ê³ í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤. (*) readX_relaxed(), writeX_relaxed() ì´ê²ƒë“¤ì€ readX() 와 writeX() ëž‘ 비슷하지만, ë” ì™„í™”ëœ ë©”ëª¨ë¦¬ 순서 ë³´ìž¥ì„ ì œê³µí•©ë‹ˆë‹¤. 구체ì 으로, ì´ê²ƒë“¤ì€ ì¼ë°˜ì 메모리 액세스 (예: DMA 버í¼) ì—ë„ LOCK ì´ë‚˜ UNLOCK 오í¼ë ˆì´ì…˜ë“¤ì—ë„ ìˆœì„œë¥¼ 보장하지 않습니다. LOCK ì´ë‚˜ UNLOCK 오í¼ë ˆì´ì…˜ë“¤ì— 맞춰지는 순서가 필요하다면, mmiowb() 배리어가 사용ë 수 있습니다. ê°™ì€ ì£¼ë³€ 장치ì—ì˜ ì™„í™”ëœ ì•¡ì„¸ìŠ¤ë¼ë¦¬ëŠ” 순서가 지켜ì§ì„ 알아 ë‘시기 ë°”ëžë‹ˆë‹¤. (*) ioreadX(), iowriteX() ì´ê²ƒë“¤ì€ inX()/outX() 나 readX()/writeX() 처럼 ì‹¤ì œë¡œ 수행하는 ì•¡ì„¸ìŠ¤ì˜ ì¢…ë¥˜ì— ë”°ë¼ ì ì ˆí•˜ê²Œ 수행ë 것입니다. =================================== ê°€ì •ë˜ëŠ” 가장 ì™„í™”ëœ ì‹¤í–‰ 순서 ëª¨ë¸ =================================== 컨셉ì 으로 CPU 는 주어진 í”„ë¡œê·¸ëž¨ì— ëŒ€í•´ 프로그램 ê·¸ ìžì²´ì—는 ì¸ê³¼ì„± (program causality) ì„ ì§€í‚¤ëŠ” 것처럼 ë³´ì´ê²Œ 하지만 ì¼ë°˜ì 으로는 순서를 ê±°ì˜ ì§€ì¼œì£¼ì§€ ì•ŠëŠ”ë‹¤ê³ ê°€ì •ë˜ì–´ì•¼ë§Œ 합니다. (i386 ì´ë‚˜ x86_64 ê°™ì€) ì¼ë¶€ CPU ë“¤ì€ ì½”ë“œ ìž¬ë°°ì¹˜ì— (powerpc 나 frv 와 ê°™ì€) 다른 ê²ƒë“¤ì— ë¹„í•´ ê°•í•œ ì œì•½ì„ ê°–ì§€ë§Œ, 아키í…ì³ ì¢…ì†ì 코드 ì´ì™¸ì˜ 코드ì—서는 ìˆœì„œì— ëŒ€í•œ ì œì•½ì´ ê°€ìž¥ ì™„í™”ëœ ê²½ìš° (DEC Alpha) 를 ê°€ì •í•´ì•¼ 합니다. ì´ ë§ì€, CPU ì—게 주어지는 ì¸ìŠ¤íŠ¸ëŸì…˜ 스트림 ë‚´ì˜ í•œ ì¸ìŠ¤íŠ¸ëŸì…˜ì´ ì•žì˜ ì¸ìŠ¤íŠ¸ëŸì…˜ì— 종ì†ì ì´ë¼ë©´ ì•žì˜ ì¸ìŠ¤íŠ¸ëŸì…˜ì€ ë’¤ì˜ ì¢…ì†ì ì¸ìŠ¤íŠ¸ëŸì…˜ì´ 실행ë˜ê¸° ì „ì— ì™„ë£Œ[*]ë 수 있어야 한다는 ì œì•½ (달리 ë§í•´ì„œ, ì¸ê³¼ì„±ì´ 지켜지는 것으로 ë³´ì´ê²Œ 함) 외ì—는 ìžì‹ ì´ ì›í•˜ëŠ” 순서대로 - 심지어 ë³‘ë ¬ì ìœ¼ë¡œë„ - ê·¸ ìŠ¤íŠ¸ë¦¼ì„ ì‹¤í–‰í• ìˆ˜ 있ìŒì„ ì˜ë¯¸í•©ë‹ˆë‹¤ [*] ì¼ë¶€ ì¸ìŠ¤íŠ¸ëŸì…˜ì€ 하나 ì´ìƒì˜ ì˜í–¥ - ì¡°ê±´ 코드를 바꾼다ë˜ì§€, ë ˆì§€ìŠ¤í„°ë‚˜ 메모리를 바꾼다ë˜ì§€ - ì„ ë§Œë“¤ì–´ë‚´ë©°, 다른 ì¸ìŠ¤íŠ¸ëŸì…˜ì€ 다른 íš¨ê³¼ì— ì¢…ì†ì ì¼ ìˆ˜ 있습니다. CPU 는 최종ì 으로 아무 íš¨ê³¼ë„ ë§Œë“¤ì§€ 않는 ì¸ìŠ¤íŠ¸ëŸì…˜ 시퀀스는 ì—†ì• ë²„ë¦´ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. 예를 들어, 만약 ë‘ê°œì˜ ì—°ì†ë˜ëŠ” ì¸ìŠ¤íŠ¸ëŸì…˜ì´ 둘 다 ê°™ì€ ë ˆì§€ìŠ¤í„°ì— ì§ì ‘ì ì¸ ê°’ (immediate value) ì„ ì§‘ì–´ë„£ëŠ”ë‹¤ë©´, 첫번째 ì¸ìŠ¤íŠ¸ëŸì…˜ì€ ë²„ë ¤ì§ˆ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. 비슷하게, 컴파ì¼ëŸ¬ ì—ì‹œ í”„ë¡œê·¸ëž¨ì˜ ì¸ê³¼ì„±ë§Œ 지켜준다면 ì¸ìŠ¤íŠ¸ëŸì…˜ ìŠ¤íŠ¸ë¦¼ì„ ìžì‹ ì´ ë³´ê¸°ì— ì˜¬ë°”ë¥´ë‹¤ ìƒê°ë˜ëŠ”대로 재배치 í• ìˆ˜ 있습니다. =============== CPU ìºì‹œì˜ ì˜í–¥ =============== ìºì‹œëœ 메모리 오í¼ë ˆì´ì…˜ë“¤ì´ 시스템 ì „ì²´ì— ì–´ë–»ê²Œ ì¸ì§€ë˜ëŠ”지는 CPU 와 메모리 사ì´ì— 존재하는 ìºì‹œë“¤, ê·¸ë¦¬ê³ ì‹œìŠ¤í…œ ìƒíƒœì˜ ì¼ê´€ì„±ì„ 관리하는 메모리 ì¼ê´€ì„± ì‹œìŠ¤í…œì— ìƒë‹¹ 부분 ì˜í–¥ì„ 받습니다. í•œ CPU ê°€ ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ 부분들과 ìºì‹œë¥¼ 통해 ìƒí˜¸ìž‘용한다면, 메모리 ì‹œìŠ¤í…œì€ CPU ì˜ ìºì‹œë“¤ì„ í¬í•¨í•´ì•¼ 하며, CPU 와 CPU ìžì‹ ì˜ ìºì‹œ 사ì´ì—ì„œì˜ ë™ìž‘ì„ ìœ„í•œ 메모리 배리어를 ê°€ì ¸ì•¼ 합니다. (메모리 배리어는 논리ì 으로는 ë‹¤ìŒ ê·¸ë¦¼ì˜ ì ì„ ì—ì„œ ë™ìž‘합니다): <--- CPU ---> : <----------- Memory -----------> : +--------+ +--------+ : +--------+ +-----------+ | | | | : | | | | +--------+ | CPU | | Memory | : | CPU | | | | | | Core |--->| Access |----->| Cache |<-->| | | | | | | Queue | : | | | |--->| Memory | | | | | : | | | | | | +--------+ +--------+ : +--------+ | | | | : | Cache | +--------+ : | Coherency | : | Mechanism | +--------+ +--------+ +--------+ : +--------+ | | | | | | | | : | | | | | | | CPU | | Memory | : | CPU | | |--->| Device | | Core |--->| Access |----->| Cache |<-->| | | | | | | Queue | : | | | | | | | | | | : | | | | +--------+ +--------+ +--------+ : +--------+ +-----------+ : : íŠ¹ì • 로드나 ìŠ¤í† ì–´ëŠ” 해당 오í¼ë ˆì´ì…˜ì„ ìš”ì²í•œ CPU ì˜ ìºì‹œ ë‚´ì—ì„œ ë™ìž‘ì„ ì™„ë£Œí• ìˆ˜ë„ ìžˆê¸° ë•Œë¬¸ì— í•´ë‹¹ CPU ì˜ ë°”ê¹¥ì—는 ë³´ì´ì§€ ì•Šì„ ìˆ˜ 있지만, 다른 CPU ê°€ ê´€ì‹¬ì„ ê°–ëŠ”ë‹¤ë©´ ìºì‹œ ì¼ê´€ì„± ë©”ì»¤ë‹ˆì¦˜ì´ í•´ë‹¹ ìºì‹œë¼ì¸ì„ 해당 CPU ì—게 ì „ë‹¬í•˜ê³ , 해당 메모리 ì˜ì—ì— ëŒ€í•œ 오í¼ë ˆì´ì…˜ì´ ë°œìƒí• 때마다 ê·¸ ì˜í–¥ì„ ì „íŒŒì‹œí‚¤ê¸° 때문ì—, 해당 오í¼ë ˆì´ì…˜ì€ ë©”ëª¨ë¦¬ì— ì‹¤ì œë¡œ 액세스를 한것처럼 ë‚˜íƒ€ë‚ ê²ƒìž…ë‹ˆë‹¤. CPU 코어는 í”„ë¡œê·¸ëž¨ì˜ ì¸ê³¼ì„±ì´ ìœ ì§€ëœë‹¤ê³ 만 여겨진다면 ì¸ìŠ¤íŠ¸ëŸì…˜ë“¤ì„ ì–´ë–¤ ìˆœì„œë¡œë“ ìž¬ë°°ì¹˜í•´ì„œ ìˆ˜í–‰í• ìˆ˜ 있습니다. ì¼ë¶€ ì¸ìŠ¤íŠ¸ëŸì…˜ë“¤ì€ 로드나 ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ì„ ë§Œë“œëŠ”ë° ì´ ì˜¤í¼ë ˆì´ì…˜ë“¤ì€ ì´í›„ 수행ë 메모리 액세스 íì— ë“¤ì–´ê°€ê²Œ ë©ë‹ˆë‹¤. 코어는 ì´ ì˜¤í¼ë ˆì´ì…˜ë“¤ì„ 해당 íì— ì–´ë–¤ ìˆœì„œë¡œë“ ì›í•˜ëŠ”대로 ë„£ì„ ìˆ˜ ìžˆê³ , 다른 ì¸ìŠ¤íŠ¸ëŸì…˜ì˜ 완료를 기다리ë„ë¡ ê°•ì œë˜ê¸° ì „ê¹Œì§€ëŠ” ìˆ˜í–‰ì„ ê³„ì†í•©ë‹ˆë‹¤. 메모리 배리어가 하는 ì¼ì€ CPU 쪽ì—ì„œ 메모리 쪽으로 넘어가는 ì•¡ì„¸ìŠ¤ë“¤ì˜ ìˆœì„œ, ê·¸ë¦¬ê³ ê·¸ ì•¡ì„¸ìŠ¤ì˜ ê²°ê³¼ê°€ ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ 관찰ìžë“¤ì—게 ì¸ì§€ë˜ëŠ” 순서를 ì œì–´í•˜ëŠ” 것입니다. [!] CPU ë“¤ì€ í•ìƒ 그들 ìžì‹ ì˜ ë¡œë“œì™€ ìŠ¤í† ì–´ëŠ” 프로그램 순서대로 ì¼ì–´ë‚œ 것으로 보기 때문ì—, 주어진 CPU ë‚´ì—서는 메모리 배리어를 ì‚¬ìš©í• í•„ìš”ê°€ _없습니다_. [!] MMIO 나 다른 디바ì´ìŠ¤ ì•¡ì„¸ìŠ¤ë“¤ì€ ìºì‹œ ì‹œìŠ¤í…œì„ ìš°íšŒí• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. 우회 여부는 디바ì´ìŠ¤ê°€ 액세스 ë˜ëŠ” 메모리 윈ë„ìš°ì˜ íŠ¹ì„±ì— ì˜í•´ ê²°ì •ë ìˆ˜ë„ ìžˆê³ , CPU ê°€ ê°€ì§€ê³ ìžˆì„ ìˆ˜ 있는 특수한 디바ì´ìŠ¤ í†µì‹ ì¸ìŠ¤íŠ¸ëŸì…˜ì˜ ì‚¬ìš©ì— ì˜í•´ì„œ ê²°ì •ë ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ìºì‹œ ì¼ê´€ì„± ----------- 하지만 ì‚¶ì€ ì•žì—ì„œ ì´ì•¼ê¸°í•œ 것처럼 단순하지 않습니다: ìºì‹œë“¤ì€ ì¼ê´€ì ì¼ ê²ƒìœ¼ë¡œ 기대ë˜ì§€ë§Œ, ê·¸ ì¼ê´€ì„±ì´ 순서ì—ë„ ì ìš©ë ê±°ë¼ëŠ” ë³´ìž¥ì€ ì—†ìŠµë‹ˆë‹¤. í•œ CPU ì—ì„œ 만들어진 변경 사í•ì€ 최종ì 으로는 ì‹œìŠ¤í…œì˜ ëª¨ë“ CPU ì—게 보여지게 ë˜ì§€ë§Œ, 다른 CPU 들ì—ê²Œë„ ê°™ì€ ìˆœì„œë¡œ ë³´ì´ê²Œ ë ê±°ë¼ëŠ” ë³´ìž¥ì€ ì—†ë‹¤ëŠ” 뜻입니다. ë‘ê°œì˜ CPU (1 & 2) ê°€ ë‹¬ë ¤ ìžˆê³ , ê° CPU ì— ë‘ê°œì˜ ë°ì´í„° ìºì‹œ(CPU 1 ì€ A/B 를, CPU 2 는 C/D 를 갖습니다)ê°€ ë³‘ë ¬ë¡œ ì—°ê²°ë˜ì–´ 있는 ì‹œìŠ¤í…œì„ ë‹¤ë£¬ë‹¤ê³ ìƒê°í•´ 봅시다: : : +--------+ : +---------+ | | +--------+ : +--->| Cache A |<------->| | | | : | +---------+ | | | CPU 1 |<---+ | | | | : | +---------+ | | +--------+ : +--->| Cache B |<------->| | : +---------+ | | : | Memory | : +---------+ | System | +--------+ : +--->| Cache C |<------->| | | | : | +---------+ | | | CPU 2 |<---+ | | | | : | +---------+ | | +--------+ : +--->| Cache D |<------->| | : +---------+ | | : +--------+ : ì´ ì‹œìŠ¤í…œì´ ë‹¤ìŒê³¼ ê°™ì€ íŠ¹ì„±ì„ ê°–ëŠ”ë‹¤ ìƒê°í•´ 봅시다: (*) 홀수번 ìºì‹œë¼ì¸ì€ ìºì‹œ A, ìºì‹œ C ë˜ëŠ” ë©”ëª¨ë¦¬ì— ìœ„ì¹˜í• ìˆ˜ 있ìŒ; (*) ì§ìˆ˜ë²ˆ ìºì‹œë¼ì¸ì€ ìºì‹œ B, ìºì‹œ D ë˜ëŠ” ë©”ëª¨ë¦¬ì— ìœ„ì¹˜í• ìˆ˜ 있ìŒ; (*) CPU 코어가 í•œê°œì˜ ìºì‹œì— ì ‘ê·¼í•˜ëŠ” ë™ì•ˆ, 다른 ìºì‹œëŠ” - ë”í‹° ìºì‹œë¼ì¸ì„ ë©”ëª¨ë¦¬ì— ë‚´ë¦¬ê±°ë‚˜ 추측성 로드를 하거나 하기 위해 - ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ ë¶€ë¶„ì— ì•¡ì„¸ìŠ¤ 하기 위해 버스를 ì‚¬ìš©í• ìˆ˜ 있ìŒ; (*) ê° ìºì‹œëŠ” ì‹œìŠ¤í…œì˜ ë‚˜ë¨¸ì§€ 부분들과 ì¼ê´€ì„±ì„ 맞추기 위해 해당 ìºì‹œì— ì ìš©ë˜ì–´ì•¼ í• ì˜¤í¼ë ˆì´ì…˜ë“¤ì˜ í를 ê°€ì§; (*) ì´ ì¼ê´€ì„± í는 ìºì‹œì— ì´ë¯¸ 존재하는 ë¼ì¸ì— 가해지는 í‰ë²”í•œ ë¡œë“œì— ì˜í•´ì„œëŠ” 비워지지 않는ë°, íì˜ ì˜¤í¼ë ˆì´ì…˜ë“¤ì´ ì´ ë¡œë“œì˜ ê²°ê³¼ì— ì˜í–¥ì„ ë¼ì¹ 수 있다 í• ì§€ë¼ë„ 그러함. ì´ì œ, 첫번째 CPU ì—ì„œ ë‘ê°œì˜ ì“°ê¸° 오í¼ë ˆì´ì…˜ì„ 만드는ë°, 해당 CPU ì˜ ìºì‹œì— ìš”ì²ëœ 순서로 오í¼ë ˆì´ì…˜ì´ ë„달ë¨ì„ 보장하기 위해 ë‘ ì˜¤í¼ë ˆì´ì…˜ 사ì´ì— 쓰기 배리어를 사용하는 ìƒí™©ì„ ìƒìƒí•´ 봅시다: CPU 1 CPU 2 COMMENT =============== =============== ======================================= u == 0, v == 1 and p == &u, q == &u v = 2; smp_wmb(); v ì˜ ë³€ê²½ì´ p ì˜ ë³€ê²½ ì „ì— ë³´ì¼ ê²ƒì„ ë¶„ëª…ížˆ 함 <A:modify v=2> v 는 ì´ì œ ìºì‹œ A ì— ë…ì ì 으로 존재함 p = &v; <B:modify p=&v> p 는 ì´ì œ ìºì‹œ B ì— ë…ì ì 으로 존재함 ì—¬ê¸°ì„œì˜ ì“°ê¸° 메모리 배리어는 CPU 1 ì˜ ìºì‹œê°€ 올바른 순서로 ì—…ë°ì´íŠ¸ ëœ ê²ƒìœ¼ë¡œ ì‹œìŠ¤í…œì˜ ë‹¤ë¥¸ CPU ë“¤ì´ ì¸ì§€í•˜ê²Œ 만ë“니다. 하지만, ì´ì œ ë‘번째 CPU ê°€ ê·¸ ê°’ë“¤ì„ ì½ìœ¼ë ¤ 하는 ìƒí™©ì„ ìƒê°í•´ 봅시다: CPU 1 CPU 2 COMMENT =============== =============== ======================================= ... q = p; x = *q; ìœ„ì˜ ë‘ê°œì˜ ì½ê¸° 오í¼ë ˆì´ì…˜ì€ 예ìƒëœ 순서로 ì¼ì–´ë‚˜ì§€ ëª»í• ìˆ˜ 있는ë°, ë‘번째 CPU ì˜ í•œ ìºì‹œì— 다른 ìºì‹œ ì´ë²¤íŠ¸ê°€ ë°œìƒí•´ v 를 ë‹´ê³ ìžˆëŠ” ìºì‹œë¼ì¸ì˜ 해당 ìºì‹œì—ì˜ ì—…ë°ì´íŠ¸ê°€ 지연ë˜ëŠ” 사ì´, p 를 ë‹´ê³ ìžˆëŠ” ìºì‹œë¼ì¸ì€ ë‘번째 CPU ì˜ ë‹¤ë¥¸ ìºì‹œì— ì—…ë°ì´íŠ¸ ë˜ì–´ë²„ë ¸ì„ ìˆ˜ 있기 때문입니다. CPU 1 CPU 2 COMMENT =============== =============== ======================================= u == 0, v == 1 and p == &u, q == &u v = 2; smp_wmb(); <A:modify v=2> <C:busy> <C:queue v=2> p = &v; q = p; <D:request p> <B:modify p=&v> <D:commit p=&v> <D:read p> x = *q; <C:read *q> ìºì‹œì— ì—…ë°ì´íŠ¸ ë˜ê¸° ì „ì˜ v 를 ì½ìŒ <C:unbusy> <C:commit v=2> 기본ì 으로, ë‘ê°œì˜ ìºì‹œë¼ì¸ ëª¨ë‘ CPU 2 ì— ìµœì¢…ì 으로는 ì—…ë°ì´íŠ¸ ë 것ì´ì§€ë§Œ, 별ë„ì˜ ê°œìž… ì—†ì´ëŠ”, ì—…ë°ì´íŠ¸ì˜ 순서가 CPU 1 ì—ì„œ 만들어진 순서와 ë™ì¼í• 것ì´ë¼ëŠ” ë³´ìž¥ì´ ì—†ìŠµë‹ˆë‹¤. ì—¬ê¸°ì— ê°œìž…í•˜ê¸° ìœ„í•´ì„ , ë°ì´í„° ì˜ì¡´ì„± 배리어나 ì½ê¸° 배리어를 로드 오í¼ë ˆì´ì…˜ë“¤ 사ì´ì— 넣어야 합니다 (v4.15 부터는 READ_ONCE() 매í¬ë¡œì— ì˜í•´ 무조건ì 으로 ê·¸ë ‡ê²Œ ë©ë‹ˆë‹¤). ì´ë ‡ê²Œ í•¨ìœ¼ë¡œì¨ ìºì‹œê°€ ë‹¤ìŒ ìš”ì²ì„ 처리하기 ì „ì— ì¼ê´€ì„± í를 처리하ë„ë¡ ê°•ì œí•˜ê²Œ ë©ë‹ˆë‹¤. CPU 1 CPU 2 COMMENT =============== =============== ======================================= u == 0, v == 1 and p == &u, q == &u v = 2; smp_wmb(); <A:modify v=2> <C:busy> <C:queue v=2> p = &v; q = p; <D:request p> <B:modify p=&v> <D:commit p=&v> <D:read p> smp_read_barrier_depends() <C:unbusy> <C:commit v=2> x = *q; <C:read *q> ìºì‹œì— ì—…ë°ì´íŠ¸ ëœ v 를 ì½ìŒ ì´ëŸ° ë¶€ë¥˜ì˜ ë¬¸ì œëŠ” DEC Alpha 계열 프로세서들ì—ì„œ 발견ë 수 있는ë°, ì´ë“¤ì€ ë°ì´í„° 버스를 좀 ë” ìž˜ 사용해 ì„±ëŠ¥ì„ ê°œì„ í• ìˆ˜ 있는, ë¶„í• ëœ ìºì‹œë¥¼ ê°€ì§€ê³ ìžˆê¸° 때문입니다. ëŒ€ë¶€ë¶„ì˜ CPU 는 í•˜ë‚˜ì˜ ì½ê¸° 오í¼ë ˆì´ì…˜ì˜ 메모리 액세스가 다른 ì½ê¸° 오í¼ë ˆì´ì…˜ì— ì˜ì¡´ì ì´ë¼ë©´ ë°ì´í„° ì˜ì¡´ì„± 배리어를 ë‚´í¬ì‹œí‚µë‹ˆë‹¤ë§Œ, 모ë‘ê°€ 그런건 아니기 ë•Œë¬¸ì— ì´ì ì— ì˜ì¡´í•´ì„ 안ë©ë‹ˆë‹¤. 다른 CPU ë“¤ë„ ë¶„í• ëœ ìºì‹œë¥¼ ê°€ì§€ê³ ìžˆì„ ìˆ˜ 있지만, 그런 CPU ë“¤ì€ í‰ë²”í•œ 메모리 액세스를 ìœ„í•´ì„œë„ ì´ ë¶„í• ëœ ìºì‹œë“¤ 사ì´ì˜ ì¡°ì •ì„ í•´ì•¼ë§Œ 합니다. Alpha 는 가장 약한 메모리 순서 시맨틱 (semantic) ì„ ì„ íƒí•¨ìœ¼ë¡œì¨ 메모리 배리어가 명시ì 으로 사용ë˜ì§€ ì•Šì•˜ì„ ë•Œì—는 그런 ì¡°ì •ì´ í•„ìš”í•˜ì§€ 않게 했으며, ì´ëŠ” Alpha ê°€ ë‹¹ì‹œì— ë” ë†’ì€ CPU í´ë½ ì†ë„를 가질 수 있게 했습니다. 하지만, (다시 ë§í•˜ê±´ëŒ€, v4.15 ì´í›„부터는) Alpha 아키í…ì³ ì „ìš© 코드와 READ_ONCE() 매í¬ë¡œ 내부ì—서를 ì œì™¸í•˜ê³ ëŠ” smp_read_barrier_depends() ê°€ 사용ë˜ì§€ 않아야 í•¨ì„ ì•Œì•„ë‘시기 ë°”ëžë‹ˆë‹¤. ìºì‹œ ì¼ê´€ì„± VS DMA ------------------ ëª¨ë“ ì‹œìŠ¤í…œì´ DMA 를 하는 디바ì´ìŠ¤ì— 대해서까지 ìºì‹œ ì¼ê´€ì„±ì„ ìœ ì§€í•˜ì§€ëŠ” 않습니다. 그런 경우, DMA 를 ì‹œë„하는 디바ì´ìŠ¤ëŠ” RAM 으로부터 ìž˜ëª»ëœ ë°ì´í„°ë¥¼ ì½ì„ 수 있는ë°, ë”í‹° ìºì‹œ ë¼ì¸ì´ CPU ì˜ ìºì‹œì— ë¨¸ë¬´ë¥´ê³ ìžˆê³ , ë°”ë€ ê°’ì´ ì•„ì§ RAM ì— ì¨ì§€ì§€ ì•Šì•˜ì„ ìˆ˜ 있기 때문입니다. ì´ ë¬¸ì œë¥¼ 해결하기 ìœ„í•´ì„ , 커ë„ì˜ ì ì ˆí•œ 부분ì—ì„œ ê° CPU ìºì‹œì˜ ë¬¸ì œë˜ëŠ” ë¹„íŠ¸ë“¤ì„ í”ŒëŸ¬ì‹œ (flush) 시켜야만 합니다 (ê·¸ë¦¬ê³ ê·¸ê²ƒë“¤ì„ ë¬´íš¨í™” - invalidation - 시킬 ìˆ˜ë„ ìžˆê² ì£ ). ë˜í•œ, 디바ì´ìŠ¤ì— ì˜í•´ RAM ì— DMA ë¡œ 쓰여진 ê°’ì€ ë””ë°”ì´ìŠ¤ê°€ 쓰기를 완료한 í›„ì— CPU ì˜ ìºì‹œì—ì„œ RAM 으로 쓰여지는 ë”í‹° ìºì‹œ ë¼ì¸ì— ì˜í•´ ë®ì–´ì¨ì§ˆ ìˆ˜ë„ ìžˆê³ , CPU ì˜ ìºì‹œì— 존재하는 ìºì‹œ ë¼ì¸ì´ 해당 ìºì‹œì—ì„œ ì‚ì œë˜ê³ 다시 ê°’ì„ ì½ì–´ë“¤ì´ê¸° ì „ê¹Œì§€ëŠ” RAM ì´ ì—…ë°ì´íŠ¸ ë˜ì—ˆë‹¤ëŠ” 사실 ìžì²´ê°€ ìˆ¨ê²¨ì ¸ 버릴 ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ ë¬¸ì œë¥¼ 해결하기 ìœ„í•´ì„ , 커ë„ì˜ ì ì ˆí•œ 부분ì—ì„œ ê° CPU ì˜ ìºì‹œ ì•ˆì˜ ë¬¸ì œê°€ ë˜ëŠ” ë¹„íŠ¸ë“¤ì„ ë¬´íš¨í™” 시켜야 합니다. ìºì‹œ ê´€ë¦¬ì— ëŒ€í•œ ë” ë§Žì€ ì •ë³´ë¥¼ ìœ„í•´ì„ Documentation/core-api/cachetlb.rst 를 ì°¸ê³ í•˜ì„¸ìš”. ìºì‹œ ì¼ê´€ì„± VS MMIO ------------------- Memory mapped I/O 는 ì¼ë°˜ì 으로 CPU ì˜ ë©”ëª¨ë¦¬ 공간 ë‚´ì˜ í•œ 윈ë„ìš°ì˜ íŠ¹ì • 부분 ë‚´ì˜ ë©”ëª¨ë¦¬ 지ì—ì— ì´ë£¨ì–´ì§€ëŠ”ë°, ì´ ìœˆë„우는 ì¼ë°˜ì ì¸, RAM 으로 향하는 윈ë„우와는 다른 íŠ¹ì„±ì„ ê°–ìŠµë‹ˆë‹¤. 그런 특성 ê°€ìš´ë° í•˜ë‚˜ëŠ”, ì¼ë°˜ì 으로 그런 액세스는 ìºì‹œë¥¼ ì™„ì „ížˆ ìš°íšŒí•˜ê³ ë””ë°”ì´ìŠ¤ 버스로 곧바로 향한다는 것입니다. ì´ ë§ì€ MMIO 액세스는 ë¨¼ì € 시작ë˜ì–´ì„œ ìºì‹œì—ì„œ ì™„ë£Œëœ ë©”ëª¨ë¦¬ 액세스를 ì¶”ì›”í• ìˆ˜ 있다는 뜻입니다. ì´ëŸ° 경우엔 메모리 배리어만으로는 충분치 ì•Šê³ , 만약 ìºì‹œëœ 메모리 쓰기 오í¼ë ˆì´ì…˜ê³¼ MMIO 액세스가 ì–´ë–¤ ë°©ì‹ìœ¼ë¡œë“ ì˜ì¡´ì ì´ë¼ë©´ 해당 ìºì‹œëŠ” ë‘ ì˜¤í¼ë ˆì´ì…˜ 사ì´ì— ë¹„ì›Œì ¸(flush)야만 합니다. ====================== CPU ë“¤ì´ ì €ì§€ë¥´ëŠ” ì¼ë“¤ ====================== 프로그래머는 CPU ê°€ 메모리 오í¼ë ˆì´ì…˜ë“¤ì„ ì •í™•ížˆ ìš”ì²í•œëŒ€ë¡œ 수행해 줄 것ì´ë¼ê³ ìƒê°í•˜ëŠ”ë°, 예를 들어 다ìŒê³¼ ê°™ì€ ì½”ë“œë¥¼ CPU ì—게 넘긴다면: a = READ_ONCE(*A); WRITE_ONCE(*B, b); c = READ_ONCE(*C); d = READ_ONCE(*D); WRITE_ONCE(*E, e); CPU 는 ë‹¤ìŒ ì¸ìŠ¤íŠ¸ëŸì…˜ì„ 처리하기 ì „ì— í˜„ìž¬ì˜ ì¸ìŠ¤íŠ¸ëŸì…˜ì„ 위한 메모리 오í¼ë ˆì´ì…˜ì„ ì™„ë£Œí• ê²ƒì´ë¼ ìƒê°í•˜ê³ , ë”°ë¼ì„œ 시스템 외부ì—ì„œ 관찰하기ì—ë„ ì •í•´ì§„ 순서대로 오í¼ë ˆì´ì…˜ì´ 수행ë 것으로 예ìƒí•©ë‹ˆë‹¤: LOAD *A, STORE *B, LOAD *C, LOAD *D, STORE *E. 당연하지만, ì‹¤ì œë¡œëŠ” 훨씬 ì—‰ë§ìž…니다. ë§Žì€ CPU 와 컴파ì¼ëŸ¬ì—ì„œ ì•žì˜ ê°€ì •ì€ ì„±ë¦½í•˜ì§€ ëª»í•˜ëŠ”ë° ê·¸ ì´ìœ 는 다ìŒê³¼ 같습니다: (*) 로드 오í¼ë ˆì´ì…˜ë“¤ì€ ì‹¤í–‰ì„ ê³„ì† í•´ë‚˜ê°€ê¸° 위해 곧바로 완료ë 필요가 있는 경우가 ë§Žì€ ë°˜ë©´, ìŠ¤í† ì–´ 오í¼ë ˆì´ì…˜ë“¤ì€ 종종 별다른 ë¬¸ì œ ì—†ì´ ìœ ì˜ˆë 수 있습니다; (*) 로드 오í¼ë ˆì´ì…˜ë“¤ì€ 예측ì 으로 수행ë 수 있으며, 필요없는 ë¡œë“œì˜€ë‹¤ê³ ì¦ëª…ëœ ì˜ˆì¸¡ì ë¡œë“œì˜ ê²°ê³¼ëŠ” ë²„ë ¤ì§‘ë‹ˆë‹¤; (*) 로드 오í¼ë ˆì´ì…˜ë“¤ì€ 예측ì 으로 수행ë 수 있으므로, 예ìƒëœ ì´ë²¤íŠ¸ì˜ 시퀀스와 다른 ì‹œê°„ì— ë¡œë“œê°€ ì´ë¤„질 수 있습니다; (*) 메모리 액세스 순서는 CPU 버스와 ìºì‹œë¥¼ 좀 ë” ìž˜ ì‚¬ìš©í• ìˆ˜ 있ë„ë¡ ìž¬ë°°ì¹˜ ë 수 있습니다; (*) 로드와 ìŠ¤í† ì–´ëŠ” ì¸ì ‘í•œ 위치ì—ì˜ ì•¡ì„¸ìŠ¤ë“¤ì„ ì¼ê´„ì 으로 ì²˜ë¦¬í• ìˆ˜ 있는 메모리나 I/O 하드웨어 (메모리와 PCI 디바ì´ìŠ¤ 둘 다 ì´ê²Œ ê°€ëŠ¥í• ìˆ˜ 있습니다) ì— ëŒ€í•´ ìš”ì²ë˜ëŠ” 경우, 개별 오í¼ë ˆì´ì…˜ì„ 위한 트랜ìžì…˜ ì„¤ì • ë¹„ìš©ì„ ì•„ë¼ê¸° 위해 ì¡°í•©ë˜ì–´ 실행ë 수 있습니다; ê·¸ë¦¬ê³ (*) 해당 CPU ì˜ ë°ì´í„° ìºì‹œê°€ ìˆœì„œì— ì˜í–¥ì„ ë¼ì¹ ìˆ˜ë„ ìžˆê³ , ìºì‹œ ì¼ê´€ì„± ë©”ì»¤ë‹ˆì¦˜ì´ - ìŠ¤í† ì–´ê°€ ì‹¤ì œë¡œ ìºì‹œì— ë„달한다면 - ì´ ë¬¸ì œë¥¼ 완화시킬 수는 있지만 ì´ ì¼ê´€ì„± 관리가 다른 CPU 들ì—ë„ ê°™ì€ ìˆœì„œë¡œ ì „ë‹¬ëœë‹¤ëŠ” ë³´ìž¥ì€ ì—†ìŠµë‹ˆë‹¤. ë”°ë¼ì„œ, ì•žì˜ ì½”ë“œì— ëŒ€í•´ 다른 CPU ê°€ 보는 결과는 다ìŒê³¼ ê°™ì„ ìˆ˜ 있습니다: LOAD *A, ..., LOAD {*C,*D}, STORE *E, STORE *B ("LOAD {*C,*D}" 는 ì¡°í•©ëœ ë¡œë“œìž…ë‹ˆë‹¤) 하지만, CPU 는 스스로는 ì¼ê´€ì ì¼ ê²ƒì„ ë³´ìž¥í•©ë‹ˆë‹¤: CPU _ìžì‹ _ ì˜ ì•¡ì„¸ìŠ¤ë“¤ì€ ìžì‹ ì—게는 메모리 배리어가 ì—†ìŒì—ë„ ë¶ˆêµ¬í•˜ê³ ì •í™•ížˆ 순서 세워진 것으로 보여질 것입니다. 예를 들어 다ìŒì˜ 코드가 주어졌다면: U = READ_ONCE(*A); WRITE_ONCE(*A, V); WRITE_ONCE(*A, W); X = READ_ONCE(*A); WRITE_ONCE(*A, Y); Z = READ_ONCE(*A); ê·¸ë¦¬ê³ ì™¸ë¶€ì˜ ì˜í–¥ì— ì˜í•œ ê°„ì„ì´ ì—†ë‹¤ê³ ê°€ì •í•˜ë©´, 최종 결과는 다ìŒê³¼ ê°™ì´ ë‚˜íƒ€ë‚ ê²ƒì´ë¼ê³ 예ìƒë 수 있습니다: U == *A ì˜ ìµœì´ˆ ê°’ X == W Z == Y *A == Y ì•žì˜ ì½”ë“œëŠ” CPU ê°€ 다ìŒì˜ 메모리 액세스 시퀀스를 만들ë„ë¡ í• ê²ë‹ˆë‹¤: U=LOAD *A, STORE *A=V, STORE *A=W, X=LOAD *A, STORE *A=Y, Z=LOAD *A 하지만, 별다른 ê°œìž…ì´ ì—†ê³ í”„ë¡œê·¸ëž¨ì˜ ì‹œì•¼ì— ì´ ì„¸ìƒì´ ì—¬ì „ížˆ ì¼ê´€ì ì´ë¼ê³ ë³´ì¸ë‹¤ëŠ” 보장만 지켜진다면 ì´ ì‹œí€€ìŠ¤ëŠ” ì–´ë–¤ ì¡°í•©ìœ¼ë¡œë“ ìž¬êµ¬ì„±ë 수 있으며, ê° ì•¡ì„¸ìŠ¤ë“¤ì€ í•©ì³ì§€ê±°ë‚˜ ë²„ë ¤ì§ˆ 수 있습니다. ì¼ë¶€ 아키í…ì³ì—ì„œ CPU 는 ê°™ì€ ìœ„ì¹˜ì— ëŒ€í•œ ì—°ì†ì ì¸ ë¡œë“œ 오í¼ë ˆì´ì…˜ë“¤ì„ 재배치 í• ìˆ˜ 있기 ë•Œë¬¸ì— ì•žì˜ ì˜ˆì—ì„œì˜ READ_ONCE() 와 WRITE_ONCE() 는 반드시 존재해야 í•¨ì„ ì•Œì•„ë‘세요. 그런 ì¢…ë¥˜ì˜ ì•„í‚¤í…ì³ì—ì„œ READ_ONCE() 와 WRITE_ONCE() 는 ì´ ë¬¸ì œë¥¼ 막기 위해 필요한 ì¼ì„ ëê°€ ëë“ ì§€ 하게 ë˜ëŠ”ë°, 예를 들어 Itanium ì—서는 READ_ONCE() 와 WRITE_ONCE() ê°€ 사용하는 volatile ìºìŠ¤íŒ…ì€ GCC ê°€ 그런 재배치를 방지하는 특수 ì¸ìŠ¤íŠ¸ëŸì…˜ì¸ ld.acq 와 stl.rel ì¸ìŠ¤íŠ¸ëŸì…˜ì„ ê°ê° 만들어 ë‚´ë„ë¡ í•©ë‹ˆë‹¤. 컴파ì¼ëŸ¬ ì—ì‹œ ì´ ì‹œí€€ìŠ¤ì˜ ì•¡ì„¸ìŠ¤ë“¤ì„ CPU ê°€ ë³´ê¸°ë„ ì „ì— í•©ì¹˜ê±°ë‚˜ 버리거나 뒤로 미뤄버릴 수 있습니다. 예를 들어: *A = V; *A = W; 는 다ìŒê³¼ ê°™ì´ ë³€í˜•ë 수 있습니다: *A = W; ë”°ë¼ì„œ, 쓰기 배리어나 WRITE_ONCE() ê°€ 없다면 *A ë¡œì˜ V ê°’ì˜ ì €ìž¥ì˜ íš¨ê³¼ëŠ” 사ë¼ì§„ë‹¤ê³ ê°€ì •ë 수 있습니다. 비슷하게: *A = Y; Z = *A; 는, 메모리 배리어나 READ_ONCE() 와 WRITE_ONCE() ì—†ì´ëŠ” 다ìŒê³¼ ê°™ì´ ë³€í˜•ë 수 있습니다: *A = Y; Z = Y; ê·¸ë¦¬ê³ ì´ LOAD 오í¼ë ˆì´ì…˜ì€ CPU 바깥ì—는 아예 ë³´ì´ì§€ 않습니다. ê·¸ë¦¬ê³ , ALPHA ê°€ 있다 --------------------- DEC Alpha CPU 는 가장 ì™„í™”ëœ ë©”ëª¨ë¦¬ ìˆœì„œì˜ CPU 중 하나입니다. ë¿ë§Œ 아니ë¼, Alpha CPU ì˜ ì¼ë¶€ ë²„ì „ì€ ë¶„í• ëœ ë°ì´í„° ìºì‹œë¥¼ ê°€ì§€ê³ ìžˆì–´ì„œ, ì˜ë¯¸ì 으로 관계ë˜ì–´ 있는 ë‘ê°œì˜ ìºì‹œ ë¼ì¸ì´ 서로 다른 ì‹œê°„ì— ì—…ë°ì´íŠ¸ ë˜ëŠ”게 가능합니다. ì´ê²Œ ë°ì´í„° ì˜ì¡´ì„± 배리어가 ì •ë§ í•„ìš”í•´ì§€ëŠ” 부분ì¸ë°, ë°ì´í„° ì˜ì¡´ì„± 배리어는 메모리 ì¼ê´€ì„± 시스템과 함께 ë‘ê°œì˜ ìºì‹œë¥¼ ë™ê¸°í™” 시켜서, í¬ì¸í„° 변경과 새로운 ë°ì´í„°ì˜ ë°œê²¬ì„ ì˜¬ë°”ë¥¸ 순서로 ì¼ì–´ë‚˜ê²Œ 하기 때문입니다. 리눅스 커ë„ì˜ ë©”ëª¨ë¦¬ 배리어 모ë¸ì€ Alpha ì— ê¸°ì´ˆí•´ì„œ ì •ì˜ë˜ì—ˆìŠµë‹ˆë‹¤ë§Œ, v4.15 부터는 리눅스 커ë„ì´ READ_ONCE() ë‚´ì— smp_read_barrier_depends() 를 추가해서 Alpha ì˜ ë©”ëª¨ë¦¬ 모ë¸ë¡œì˜ ì˜í–¥ë ¥ì´ í¬ê²Œ 줄어들긴 했습니다. ìœ„ì˜ "ìºì‹œ ì¼ê´€ì„±" ì„œë¸Œì„¹ì…˜ì„ ì°¸ê³ í•˜ì„¸ìš”. ê°€ìƒ ë¨¸ì‹ ê²ŒìŠ¤íŠ¸ ---------------- ê°€ìƒ ë¨¸ì‹ ì—ì„œ ë™ìž‘하는 ê²ŒìŠ¤íŠ¸ë“¤ì€ ê²ŒìŠ¤íŠ¸ ìžì²´ëŠ” SMP ì§€ì› ì—†ì´ ì»´íŒŒì¼ ë˜ì—ˆë‹¤ í•´ë„ SMP ì˜í–¥ì„ ë°›ì„ ìˆ˜ 있습니다. ì´ê±´ UP 커ë„ì„ ì‚¬ìš©í•˜ë©´ì„œ SMP 호스트와 결부ë˜ì–´ ë°œìƒí•˜ëŠ” 부작용입니다. ì´ ê²½ìš°ì—는 mandatory 배리어를 사용해서 ë¬¸ì œë¥¼ í•´ê²°í• ìˆ˜ ìžˆê² ì§€ë§Œ 그런 í•´ê²°ì€ ëŒ€ë¶€ë¶„ì˜ ê²½ìš° 최ì ì˜ í•´ê²°ì±…ì´ ì•„ë‹™ë‹ˆë‹¤. ì´ ë¬¸ì œë¥¼ 완벽하게 해결하기 위해, 로우 ë ˆë²¨ì˜ virt_mb() ë“±ì˜ ë§¤í¬ë¡œë¥¼ ì‚¬ìš©í• ìˆ˜ 있습니다. ì´ê²ƒë“¤ì€ SMP ê°€ 활성화 ë˜ì–´ 있다면 smp_mb() 등과 ë™ì¼í•œ 효과를 갖습니다만, SMP 와 SMP ì•„ë‹Œ 시스템 모ë‘ì— ëŒ€í•´ ë™ì¼í•œ 코드를 만들어냅니다. 예를 들어, ê°€ìƒ ë¨¸ì‹ ê²ŒìŠ¤íŠ¸ë“¤ì€ (SMP ì¼ ìˆ˜ 있는) 호스트와 ë™ê¸°í™”를 í• ë•Œì—는 smp_mb() ê°€ ì•„ë‹ˆë¼ virt_mb() 를 사용해야 합니다. ì´ê²ƒë“¤ì€ smp_mb() ë¥˜ì˜ ê²ƒë“¤ê³¼ ëª¨ë“ ë¶€ë¶„ì—ì„œ ë™ì¼í•˜ë©°, 특히, MMIO ì˜ ì˜í–¥ì— 대해서는 간여하지 않습니다: MMIO ì˜ ì˜í–¥ì„ ì œì–´í•˜ë ¤ë©´, mandatory 배리어를 사용하시기 ë°”ëžë‹ˆë‹¤. ======= 사용 예 ======= ìˆœí™˜ì‹ ë²„í¼ ----------- 메모리 배리어는 ìˆœí™˜ì‹ ë²„í¼ë¥¼ ìƒì„±ìž(producer)와 소비ìž(consumer) 사ì´ì˜ ë™ê¸°í™”ì— ë½ì„ 사용하지 ì•Šê³ êµ¬í˜„í•˜ëŠ”ë°ì— 사용ë 수 있습니다. ë” ìžì„¸í•œ ë‚´ìš©ì„ ìœ„í•´ì„ ë‹¤ìŒì„ ì°¸ê³ í•˜ì„¸ìš”: Documentation/core-api/circular-buffers.rst ========= ì°¸ê³ ë¬¸í—Œ ========= Alpha AXP Architecture Reference Manual, Second Edition (Sites & Witek, Digital Press) Chapter 5.2: Physical Address Space Characteristics Chapter 5.4: Caches and Write Buffers Chapter 5.5: Data Sharing Chapter 5.6: Read/Write Ordering AMD64 Architecture Programmer's Manual Volume 2: System Programming Chapter 7.1: Memory-Access Ordering Chapter 7.4: Buffering and Combining Memory Writes ARM Architecture Reference Manual (ARMv8, for ARMv8-A architecture profile) Chapter B2: The AArch64 Application Level Memory Model IA-32 Intel Architecture Software Developer's Manual, Volume 3: System Programming Guide Chapter 7.1: Locked Atomic Operations Chapter 7.2: Memory Ordering Chapter 7.4: Serializing Instructions The SPARC Architecture Manual, Version 9 Chapter 8: Memory Models Appendix D: Formal Specification of the Memory Models Appendix J: Programming with the Memory Models Storage in the PowerPC (Stone and Fitzgerald) UltraSPARC Programmer Reference Manual Chapter 5: Memory Accesses and Cacheability Chapter 15: Sparc-V9 Memory Models UltraSPARC III Cu User's Manual Chapter 9: Memory Models UltraSPARC IIIi Processor User's Manual Chapter 8: Memory Models UltraSPARC Architecture 2005 Chapter 9: Memory Appendix D: Formal Specifications of the Memory Models UltraSPARC T1 Supplement to the UltraSPARC Architecture 2005 Chapter 8: Memory Models Appendix F: Caches and Cache Coherency Solaris Internals, Core Kernel Architecture, p63-68: Chapter 3.3: Hardware Considerations for Locks and Synchronization Unix Systems for Modern Architectures, Symmetric Multiprocessing and Caching for Kernel Programmers: Chapter 13: Other Memory Models Intel Itanium Architecture Software Developer's Manual: Volume 1: Section 2.6: Speculation Section 4.4: Memory Access