วิธีการนำส่วนที่สำคัญไปใช้กับ ARM Cortex A9


15

ฉันกำลังย้ายรหัสดั้งเดิมจากแกน ARM926 ไปยัง CortexA9 รหัสนี้เป็น baremetal และไม่รวมระบบปฏิบัติการหรือไลบรารีมาตรฐานที่กำหนดเองทั้งหมด ฉันมีความล้มเหลวที่ดูเหมือนจะเกี่ยวข้องกับสภาพการแข่งขันที่ควรได้รับการป้องกันโดยการแบ่งส่วนที่สำคัญของรหัส

ฉันต้องการความคิดเห็นเกี่ยวกับวิธีการของฉันเพื่อดูว่าส่วนที่สำคัญของฉันอาจใช้ไม่ได้กับ CPU นี้หรือไม่ ฉันใช้ GCC ฉันสงสัยว่ามีข้อผิดพลาดเล็กน้อย

นอกจากนี้มีไลบรารี opensource ที่มี primitives ประเภทนี้สำหรับ ARM (หรือแม้แต่ไลบราล็อก / เซมาฟอร์ไลบรารี่ที่มีน้ำหนักเบา) หรือไม่?

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "orr r1, %[key], #0xC0\n\t"\
    "msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );

#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))

รหัสถูกใช้ดังนี้:

/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);

<access registers, shared globals, etc...>

ARM_INT_UNLOCK(key);

ความคิดของ "กุญแจ" คือการอนุญาตให้ส่วนที่สำคัญที่ซ้อนกันและสิ่งเหล่านี้จะใช้ที่จุดเริ่มต้นและจุดสิ้นสุดของฟังก์ชั่นในการสร้างฟังก์ชั่น reentrant

ขอบคุณ!


1
โปรดอ้างอิงถึงinfocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/…อย่าทำเช่นนั้นในการฝัง asm btw ทำให้ฟังก์ชั่นเป็นบทความไม่
Jason Hu

ฉันไม่รู้อะไรเกี่ยวกับ ARM แต่ฉันคาดหวังว่าสำหรับฟังก์ชั่น mutex (หรือฟังก์ชั่น cross-thread หรือ cross-process sync) คุณควรใช้ clobber "memory" เพื่อให้แน่ใจว่า a) ค่าหน่วยความจำทั้งหมดที่แคชในรีจิสเตอร์ กลับสู่หน่วยความจำก่อนดำเนินการ asm และ b) ค่าใด ๆ ในหน่วยความจำที่เข้าถึงได้หลังจากที่ asm โหลดใหม่อีกครั้ง โปรดทราบว่าการดำเนินการโทร (ตามที่ HuStmpHrrr แนะนำ) ควรดำเนินการโดยเด็ดขาดสำหรับคุณ

นอกจากนี้ในขณะที่ฉันยังพูดไม่ได้ ARM ข้อ จำกัด ของคุณสำหรับ 'key_' ดูไม่ถูกต้อง เนื่องจากคุณบอกว่าสิ่งนี้มีวัตถุประสงค์เพื่อใช้สำหรับการกลับเข้ามาใหม่ประกาศว่าเป็น "= r" ในล็อคดูเหมือนน่าสงสัย '=' หมายความว่าคุณตั้งใจจะเขียนทับมันและค่าที่มีอยู่นั้นไม่สำคัญ ดูเหมือนว่าคุณตั้งใจจะใช้ '+' เพื่อระบุความตั้งใจที่จะอัปเดตค่าที่มีอยู่ และอีกครั้งสำหรับการปลดล็อกการใส่รายชื่อเป็นอินพุตจะบอก gcc ว่าคุณไม่ต้องการเปลี่ยน แต่ถ้าฉันไม่ผิดคุณก็ทำ (เปลี่ยน) ฉันเดาว่าควรแสดงรายการนี้เป็น '+' ด้วย

1
+1 สำหรับการเข้ารหัสในชุดประกอบสำหรับแกนที่มีสเป็คสูง อย่างไรก็ตามสิ่งนี้อาจเกี่ยวข้องกับโหมดสิทธิ์หรือไม่
Dzarda

ฉันค่อนข้างมั่นใจว่าคุณจะต้องใช้ldrexและstrexทำอย่างถูกต้อง นี่คือหน้าเว็บที่แสดงวิธีการใช้ldrexและstrexการใช้งาน spinlock

คำตอบ:


14

ส่วนที่ยากที่สุดในการจัดการส่วนที่สำคัญที่ไม่มีระบบปฏิบัติการไม่ได้เป็นการสร้าง mutex แต่เป็นการหาว่าควรจะเกิดอะไรขึ้นหากรหัสต้องการใช้ทรัพยากรที่ไม่สามารถใช้งานได้ในปัจจุบัน คำแนะนำในการโหลดแบบเอกสิทธิ์เฉพาะบุคคลและแบบมีเงื่อนไขเก็บทำให้ง่ายต่อการสร้างฟังก์ชั่น "สลับ" ซึ่งกำหนดให้ตัวชี้ไปที่จำนวนเต็มจะเก็บค่าใหม่ทางอะตอม แต่กลับสิ่งที่มีจำนวนเต็มชี้ไปที่:

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

ด้วยฟังก์ชั่นด้านบนเราสามารถป้อน mutex ได้ง่ายๆ

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

ในกรณีที่ไม่มีระบบปฏิบัติการปัญหาหลักมักจะอยู่ที่รหัส "ไม่สามารถรับ mutex" ได้ หากการขัดจังหวะเกิดขึ้นเมื่อทรัพยากรที่มีการป้องกัน mutex ไม่ว่างอาจจำเป็นต้องให้รหัสการจัดการการขัดจังหวะตั้งค่าสถานะและบันทึกข้อมูลบางอย่างเพื่อระบุสิ่งที่ต้องการทำแล้วมีรหัสคล้ายหลักที่ได้รับ ตรวจสอบ mutex เมื่อใดก็ตามที่จะปล่อย mutex เพื่อดูว่ามีการขัดจังหวะที่ต้องการทำบางสิ่งบางอย่างในขณะที่มีการจัดทำ mutex หรือไม่และหากเป็นเช่นนั้นให้ดำเนินการในนามของการขัดจังหวะ

แม้ว่าจะเป็นไปได้ที่จะหลีกเลี่ยงปัญหาเกี่ยวกับการขัดจังหวะที่ต้องการใช้ทรัพยากรที่มีการป้องกันแบบ mutex โดยเพียงแค่ปิดการใช้งานการขัดจังหวะ (และการปิดใช้งานการขัดจังหวะสามารถกำจัดความต้องการ mutex ชนิดอื่น ๆ ) โดยทั่วไป

การประนีประนอมที่มีประโยชน์สามารถใช้การตั้งค่าสถานะตามที่อธิบายไว้ข้างต้น แต่มีรหัสบรรทัดหลักที่จะปล่อย mutex ปิดการใช้งานการขัดจังหวะและตรวจสอบการตั้งค่าสถานะดังกล่าวก่อนที่จะทำ (เปิดใช้งานการขัดจังหวะอีกครั้ง วิธีการดังกล่าวไม่ต้องการให้อินเตอร์รัปต์ปิดใช้งานนานเกินไป แต่จะป้องกันความเป็นไปได้ว่าถ้าโค้ดบรรทัดหลักทดสอบการตั้งค่าอินเตอร์รัปต์หลังจากปล่อย mutex มีอันตรายที่ระหว่างเวลาที่เห็นแฟล็กและเวลาที่มัน การกระทำตามนั้นมันอาจได้รับการจองโดยรหัสอื่นที่ได้มาและเผยแพร่ mutex และและกระทำตามการขัดจังหวะการตั้งค่าสถานะ; หากรหัสบรรทัดหลักไม่ทดสอบการตั้งค่าอินเตอร์รัปต์หลังจากปล่อย mutex

ในกรณีใด ๆ สิ่งที่สำคัญที่สุดคือการมีวิธีการที่รหัสที่พยายามใช้ทรัพยากรที่มีการป้องกันแบบ mutex เมื่อมันไม่สามารถใช้งานได้จะมีวิธีการทำซ้ำความพยายามเมื่อทรัพยากรนั้นถูกปล่อยออกมา


7

นี่เป็นวิธีที่หนักในการทำส่วนที่สำคัญ ปิดการใช้งานการขัดจังหวะ มันอาจไม่ทำงานหากระบบของคุณมี / จัดการกับความผิดพลาดของข้อมูล มันจะเพิ่มความล่าช้าในการขัดจังหวะ irqflags.h ลินุกซ์มีแมโครบางอย่างที่จัดการนี้ cpsieและcpsidคำแนะนำที่มีประโยชน์อาจจะ; อย่างไรก็ตามพวกเขาไม่ได้บันทึกสถานะและจะไม่อนุญาตให้ทำรัง cpsไม่ใช้การลงทะเบียน

สำหรับซีรี่ส์Cortex-Aนั้นldrex/strexมีประสิทธิภาพมากกว่าและสามารถทำงานเพื่อสร้างmutexสำหรับส่วนที่สำคัญหรือสามารถใช้กับอัลกอริทึมที่ไม่มีล็อคเพื่อกำจัดส่วนที่สำคัญ

ในความรู้สึกบางอย่างที่ldrex/strexดูเหมือน swpARMv5 อย่างไรก็ตามพวกเขามีความซับซ้อนมากขึ้นในการใช้ในทางปฏิบัติ คุณต้องมีแคชที่ใช้งานได้และหน่วยความจำเป้าหมายที่ldrex/strexต้องการจะอยู่ในแคช เอกสาร ARM เกี่ยวกับldrex/strexค่อนข้างคลุมเครือเนื่องจากพวกเขาต้องการกลไกในการทำงานกับ CPU ที่ไม่ใช่ Cortex-A อย่างไรก็ตามสำหรับ Cortex-A กลไกในการเก็บแคชของ CPU ในท้องถิ่นให้ตรงกับ CPU อื่น ๆ นั้นเป็นวิธีเดียวกับที่ใช้ในการใช้ldrex/strexคำสั่ง สำหรับ Cortex-A ซีรีส์granual สำรอง (ขนาดของldrex/strexหน่วยความจำที่สงวนไว้) จะเหมือนกับบรรทัดแคช คุณต้องจัดหน่วยความจำให้ตรงกับบรรทัดแคชหากคุณต้องการแก้ไขค่าหลายค่าเช่นเดียวกับรายการที่ลิงก์ซ้ำกัน

ฉันสงสัยว่ามีข้อผิดพลาดเล็กน้อย

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

คุณต้องให้แน่ใจว่าลำดับไม่สามารถจองไว้แล้ว มิฉะนั้นคุณอาจได้รับตัวแปรหลักสองตัวเมื่อเปิดใช้งานอินเตอร์รัปต์และการปล่อยล็อคจะไม่ถูกต้อง คุณสามารถใช้swpคำสั่งกับหน่วยความจำคีย์เพื่อให้แน่ใจในความมั่นคงของ ARMv5 แต่คำสั่งนี้เลิกใช้กับ Cortex-A ldrex/strexเพื่อให้ทำงานได้ดีขึ้นสำหรับระบบมัลติซีพียู

ทั้งหมดนี้ขึ้นอยู่กับประเภทของการตั้งเวลาระบบของคุณ ดูเหมือนว่าคุณจะมีการฉีดและการขัดจังหวะ คุณมักจะต้องมีส่วนสำคัญแบบดั้งเดิมเพื่อให้ hooks บางตัวอยู่ในตัวกำหนดตารางเวลาขึ้นอยู่กับระดับ (ระบบ / พื้นที่ผู้ใช้ / ฯลฯ ) ที่คุณต้องการให้ส่วนที่สำคัญทำงานด้วย

นอกจากนี้มีไลบรารี opensource ที่มี primitives ประเภทนี้สำหรับ ARM (หรือแม้แต่ไลบราล็อก / เซมาฟอร์ไลบรารี่ที่มีน้ำหนักเบา) หรือไม่?

การเขียนแบบพกพาทำได้ยาก นั่นคือไลบรารีดังกล่าวอาจมีอยู่ใน ARM CPU บางรุ่นและสำหรับ OS ที่เฉพาะเจาะจง


2

ฉันเห็นปัญหาที่อาจเกิดขึ้นกับส่วนที่สำคัญเหล่านั้น มีคำเตือนและแนวทางแก้ไขสำหรับสิ่งเหล่านี้ทั้งหมด แต่โดยสรุป

  • ไม่มีสิ่งใดที่ทำให้คอมไพเลอร์ย้ายโค้ดข้ามมาโครเหล่านี้เพื่อเพิ่มประสิทธิภาพหรือเหตุผลอื่น ๆ
  • พวกเขาบันทึกและกู้คืนบางส่วนของหน่วยประมวลผลสถานะคอมไพเลอร์คาดว่าแอสเซมบลีแบบอินไลน์จะปล่อยให้อยู่คนเดียว (เว้นแต่จะมีการบอกเป็นอย่างอื่น)
  • ไม่มีสิ่งใดขัดขวางการขัดจังหวะไม่ให้เกิดขึ้นระหว่างการเรียงลำดับและการเปลี่ยนสถานะระหว่างเมื่ออ่านและเขียน

ปิดแรกคุณแน่นอนต้องมีอุปสรรคในหน่วยความจำของคอมไพเลอร์ การดำเนินการเหล่านี้เป็น GCC clobbers โดยทั่วไปนี่เป็นวิธีที่จะบอกคอมไพเลอร์ "ไม่คุณไม่สามารถย้ายการเข้าถึงหน่วยความจำผ่านแอสเซมบลีอินไลน์ชิ้นนี้เพราะมันอาจส่งผลต่อผลลัพธ์ของการเข้าถึงหน่วยความจำ" โดยเฉพาะคุณต้องใช้ทั้งสอง"memory"และ"cc"clobbers ทั้งในแมโครเริ่มต้นและสิ้นสุด สิ่งเหล่านี้จะป้องกันสิ่งอื่น ๆ (เช่นการเรียกใช้ฟังก์ชัน) จากการจัดลำดับใหม่สัมพันธ์กับชุดประกอบแบบอินไลน์เช่นกันเพราะคอมไพเลอร์รู้ว่าพวกเขาอาจมีการเข้าถึงหน่วยความจำ ฉันเห็น GCC สำหรับสถานะการพัก ARM ในรหัสเงื่อนไขลงทะเบียนในชุดประกอบแบบอินไลน์ด้วย"memory"clobbers ดังนั้นคุณจึงจำเป็นต้องใช้"cc"clobber

ประการที่สองส่วนสำคัญเหล่านี้กำลังบันทึกและกู้คืนมากกว่าการเปิดใช้อินเทอร์รัปต์ โดยเฉพาะพวกเขากำลังบันทึกและกู้คืนCPSRส่วนใหญ่(การลงทะเบียนสถานะโปรแกรมปัจจุบัน) (ลิงก์สำหรับ Cortex-R4 เพราะฉันไม่สามารถหาไดอะแกรมที่ดีสำหรับ A9 ได้ แต่ควรเหมือนกัน) มีข้อ จำกัด ที่ลึกซึ้งซึ่งชิ้นส่วนของรัฐสามารถแก้ไขได้จริง แต่มีความจำเป็นมากกว่าที่นี่

เหนือสิ่งอื่นใดซึ่งรวมถึงรหัสเงื่อนไข (ซึ่งcmpจัดเก็บผลลัพธ์ของคำแนะนำเช่นนั้นคำสั่งตามเงื่อนไขที่ตามมาสามารถกระทำกับผลลัพธ์ได้) คอมไพเลอร์จะสับสนกับสิ่งนี้อย่างแน่นอน วิธีนี้สามารถแก้ไขได้อย่างง่ายดายโดยใช้"cc"Clobber ดังกล่าวข้างต้น อย่างไรก็ตามการทำเช่นนี้จะทำให้โค้ดล้มเหลวทุกครั้งดังนั้นจึงไม่เหมือนกับสิ่งที่คุณเห็นปัญหา แม้ว่าจะมีระเบิดเวลาฟ้องในการปรับเปลี่ยนรหัสอื่น ๆ แบบสุ่มอาจทำให้คอมไพเลอร์ทำบางสิ่งที่แตกต่างกันเล็กน้อยซึ่งจะถูกทำลายโดยสิ่งนี้

นอกจากนี้ยังจะพยายามที่จะบันทึก / เรียกคืนบิตไอทีซึ่งจะใช้ในการดำเนินการดำเนินการตามเงื่อนไขที่นิ้วหัวแม่มือ โปรดทราบว่าหากคุณไม่เคยใช้รหัส Thumb มันไม่สำคัญเลย ฉันไม่เคยคิดเลยว่าชุดประกอบแบบอินไลน์ของ GCC เกี่ยวข้องกับบิตไอทีอย่างไรนอกเหนือจากการสรุปไม่ได้หมายความว่าคอมไพเลอร์จะต้องไม่ใส่ชุดประกอบแบบอินไลน์ในบล็อกไอทีและคาดว่าการชุมนุมจะสิ้นสุดนอกบล็อกไอที ฉันไม่เคยเห็น GCC สร้างรหัสที่ละเมิดสมมติฐานเหล่านี้และฉันได้ทำชุดอินไลน์ที่ซับซ้อนด้วยการเพิ่มประสิทธิภาพอย่างหนักดังนั้นฉันจึงมั่นใจว่าพวกเขามีเหตุผล ซึ่งหมายความว่าอาจไม่ได้พยายามเปลี่ยนบิตไอทีซึ่งในกรณีนี้ทุกอย่างเรียบร้อย ความพยายามในการปรับเปลี่ยนบิตเหล่านี้จัดอยู่ในประเภท "ไม่แน่นอนทางสถาปัตยกรรม"ดังนั้นมันสามารถทำสิ่งเลวร้ายทุกประเภท แต่อาจจะไม่ทำอะไรเลย

ประเภทสุดท้ายของบิตที่จะถูกบันทึก / กู้คืน (นอกเหนือจากที่ปิดการใช้งานจริง) เป็นบิตโหมด สิ่งเหล่านี้อาจไม่เปลี่ยนแปลงดังนั้นจึงอาจไม่สำคัญ แต่ถ้าคุณมีรหัสใด ๆ ที่เปลี่ยนโหมดโดยเจตนาส่วนที่ถูกขัดจังหวะอาจทำให้เกิดปัญหาได้ การเปลี่ยนระหว่างสิทธิพิเศษและโหมดผู้ใช้เป็นกรณีเดียวที่ฉันคาดหวัง

ประการที่สามมีอะไรป้องกันการขัดจังหวะจากการเปลี่ยนชิ้นส่วนอื่น ๆ ของ CPSR ระหว่างMRSและในMSR ARM_INT_LOCKการเปลี่ยนแปลงใด ๆ ดังกล่าวอาจถูกเขียนทับ ในระบบที่เหมาะสมที่สุดการขัดจังหวะแบบอะซิงโครนัสจะไม่เปลี่ยนสถานะของรหัสที่พวกเขากำลังขัดจังหวะ (รวมถึง CPSR) หากพวกเขาทำมันจะยากมากที่จะให้เหตุผลเกี่ยวกับรหัสที่จะทำ อย่างไรก็ตามเป็นไปได้ (การเปลี่ยนบิตการปิดใช้งาน FIQ ดูเหมือนจะเป็นไปได้มากที่สุดสำหรับฉัน) ดังนั้นคุณควรพิจารณาว่าระบบของคุณทำสิ่งนี้หรือไม่

นี่คือวิธีที่ฉันจะใช้สิ่งเหล่านี้ในวิธีที่จะจัดการกับปัญหาที่อาจเกิดขึ้นทั้งหมดที่ฉันชี้ให้เห็น:

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "ands %[key], %[key], #0xC0\n\t"\
    "cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
    "tst %[key], #0x40\n\t"\
    "beq 0f\n\t"\
    "cpsie f\n\t"\
    "0: tst %[key], #0x80\n\t"\
    "beq 1f\n\t"\
    "cpsie i\n\t"
    "1:\n\t" :: [key]"r" (key_) : "memory", "cc")

ให้แน่ใจว่าจะรวบรวมด้วย-mcpu=cortex-a9เพราะอย่างน้อยบางรุ่น GCC (เช่นเหมือง) เริ่มต้นซีพียู ARM เก่าซึ่งไม่สนับสนุนและcpsiecpsid

ผมใช้andsแทนเพียงandในARM_INT_LOCKจึงเป็นคำแนะนำ 16 บิตถ้าเรื่องนี้ถูกนำมาใช้ในรหัสหัวแม่มือ การ"cc"อุดตันนั้นเป็นสิ่งจำเป็นต่อไปดังนั้นจึงเป็นประโยชน์อย่างมากต่อประสิทธิภาพ / ขนาดโค้ด

0และ1เป็นฉลากท้องถิ่นสำหรับการอ้างอิง

สิ่งเหล่านี้ควรใช้งานได้ในวิธีเดียวกับเวอร์ชั่นของคุณ ARM_INT_LOCKเป็นเพียงเป็นไปอย่างรวดเร็ว / ขนาดเล็กเป็นหนึ่งในต้นฉบับของคุณ น่าเสียดายที่ฉันไม่สามารถหาวิธีที่จะทำARM_INT_UNLOCKอย่างปลอดภัยได้ทุกที่ใกล้กับคำแนะนำเล็กน้อย

หากระบบของคุณมีข้อ จำกัด เมื่อ IRQ และ FIQ ถูกปิดการใช้งานสิ่งนี้อาจทำให้ง่ายขึ้น ตัวอย่างเช่นหากพวกเขาปิดการใช้งานด้วยกันเสมอคุณสามารถรวมเป็นหนึ่งcbz+ cpsie ifเช่นนี้:

#define ARM_INT_UNLOCK(key_) asm volatile (\
    "cbz %[key], 0f\n\t"\
    "cpsie if\n\t"\
    "0:\n\t" :: [key]"r" (key_) : "memory", "cc")

อีกทางเลือกหนึ่งถ้าคุณไม่สนใจ FIQ เลยก็เหมือนกับว่าคุณแค่เปิด / ปิดการใช้งานทั้งหมด

ถ้าคุณรู้ว่าไม่มีอะไรอื่นที่เคยมีการเปลี่ยนแปลงใด ๆ ของบิตของรัฐอื่น ๆ ใน CPSR ระหว่างการล็อคและปลดล็อคแล้วคุณยังสามารถใช้ดำเนินการกับบางสิ่งบางอย่างคล้ายกับรหัสเดิมของคุณยกเว้นมีทั้ง"memory"และ"cc"clobbers ทั้งในARM_INT_LOCKและARM_INT_UNLOCK


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.