แผนที่หน่วยความจำ stack และ heap
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ
- จำแนกตัวแปรในโปรแกรมตัวอย่างได้ว่าอยู่ใน stack, heap หรือหน่วยความจำแบบ static
- อ่านค่าพื้นที่ stack ที่เหลือของ task จากตัวนับที่ SDK ให้มา และตัดสินได้ว่าใกล้ล้นหรือไม่
- อธิบายว่าทำไมไม่ควรจองหน่วยความจำแบบ dynamic ใน ISR หรือในลูปเวลาจริง
ใช้เวลาประมาณ 70 นาที (แนวคิด 15 · ฝึก 25 · แล็บ 25 · เช็ก 5)
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ทวนจากบทเรียน 1.1 สองข้อ
uint8_tบวกเกิน 255 แล้วเกิดอะไรขึ้น และทำไมตัวอย่างของ SDK จึงขยายเป็น 64 บิตก่อนคูณ- ตัวแปรแบบไหนที่ต้องเป็น
volatileและvolatileช่วยเรื่องcount++ที่ถูก interrupt แทรกได้หรือไม่
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”เปิด examples/02_where_it_lives.c ทายก่อนรัน ว่าที่อยู่ของ local_buf (ตัวแปร local ใน main)
กับ g_zeroed (ตัวแปร global) ตัวไหนมีค่ามากกว่า แล้วรัน
gcc -std=c11 -Wall -Wextra -o where_it_lives examples/02_where_it_lives.c./where_it_livesตัวเลขบนเครื่องของคุณจะไม่ตรงกับของเพื่อน แต่จะเห็นที่อยู่แบ่งเป็นกลุ่มชัดเจน ค่าคงที่กับตัวแปร global อยู่ใกล้กัน
ก้อนที่ malloc ได้มาอยู่อีกย่านหนึ่ง ตัวแปร local อยู่ไกลออกไปอีกย่าน และบรรทัด depth 1, 2, 3 แสดงว่า
ยิ่งเรียกฟังก์ชันลึก ที่อยู่ของ stack frame ยิ่ง ลดลง บนไมโครคอนโทรลเลอร์กลุ่มเหล่านี้มีอยู่เหมือนกัน
ต่างกันตรงที่ไม่มีระบบปฏิบัติการสุ่มให้ linker script เป็นคนวางทุกกลุ่ม และเราอ่านมันได้
1. แผนที่หน่วยความจำ: ใครวางอะไรไว้ตรงไหน
หัวข้อที่มีชื่อว่า “1. แผนที่หน่วยความจำ: ใครวางอะไรไว้ตรงไหน”| ส่วน (section) | เก็บอะไร | อยู่ที่ไหนบน PSOC™ Edge E84 ในแม่แบบของ SDK |
|---|---|---|
.text .rodata |
โค้ด ค่าคงที่ const ข้อความ |
flash ภายนอกแบบ QSPI ซึ่ง CPU รันโค้ดจากตรงนั้นได้เลย (XIP) |
.data |
ตัวแปร static ที่มีค่าเริ่มต้น เช่น int g = 42; |
ค่าเริ่มต้นเก็บใน flash แล้วคัดลอกมาไว้ RAM ตอนบูต |
.bss |
ตัวแปร static ที่ไม่ได้ให้ค่า หรือให้ค่า 0 | RAM ถูกล้างเป็นศูนย์ตอนบูต ไม่กินที่ใน flash |
| heap | ก้อนที่ได้จาก malloc() |
RAM ส่วนที่เหลือหลัง .bss |
| stack | ตัวแปร local ที่อยู่ของการกลับจากฟังก์ชัน | บนสุดของ RAM ก้อนนั้น โตลงหาที่อยู่ต่ำ |
ทั้งหมดนี้อ่านได้จาก linker script ของ CM33 non-secure ใน SDK
(pse84_ns_cm33.ld)
เช่น .data ถูกวางว่า > m33_data AT > m33_nvm_sel แปลว่ารันใน RAM แต่เก็บต้นฉบับไว้ใน flash และตาราง copy
ที่บรรทัด 190 เขียนกำกับไว้ว่า “From load address in ext flash” ส่วน heap เป็นส่วนที่ขยายจนเต็ม คือจากท้าย .bss ถึงก่อน stack
และ stack เริ่มที่ __StackTop = ORIGIN(m33_data) + LENGTH(m33_data) ขนาดเริ่มต้น 0x1000 ไบต์ (บรรทัด 47)
ผลที่ตามมาซึ่งคอมเมนต์ใน linker script เดียวกันบันทึกไว้จากบอร์ดจริงคือ ทุกไบต์ที่ .bss โตขึ้น คือไบต์ที่ heap หายไป
(“every byte of .bss costs a byte of heap one for one”, บรรทัด 265) เมื่อเพิ่มหน้าจอใหม่เข้าไป heap เหลือน้อยจน mTLS ล้มด้วย
WARN: Malloc failed arena=121812 used=121308 free=504 largest_free=0 และคอมเมนต์สรุปว่า “Eleven rounds of code review could not see that;
the board said it in ten minutes.” ตัวแปร static ไม่ฟรี มันกินที่จากคนอื่นเสมอ
2. stack ต่อ task และ high-water mark
หัวข้อที่มีชื่อว่า “2. stack ต่อ task และ high-water mark”ในระบบที่ใช้ FreeRTOS แต่ละ task มี stack ของตัวเอง ขนาดกำหนดตอนสร้าง task เป็นจำนวน word (4 ไบต์บน CPU 32 บิต)
เช่น task heartbeat ของแม่แบบ mtb-only สร้างด้วย xTaskCreate(bento_heartbeat_task, "HB", 256, NULL, 1, NULL)
(proj_cm33_ns/main.c บรรทัด 309)
ส่วน stack ขนาด 0x1000 ใน linker script เป็นของ main() ก่อน scheduler เริ่ม และของ interrupt handler หลังจากนั้น
จะรู้ว่า task ใช้ stack ไปลึกแค่ไหน FreeRTOS ใช้วิธีระบายสี: ตอนสร้าง task มันเขียนไบต์ 0xA5 ทั่ว stack
(tskSTACK_FILL_BYTE ใน tasks.c) แล้ว uxTaskGetStackHighWaterMark() นับว่ายังเหลือ 0xA5 ที่ไม่เคยถูกเขียนทับกี่ word
ค่านี้คือ ค่าต่ำสุดตลอดอายุของ task ลดได้อย่างเดียว ยิ่งน้อยยิ่งใกล้ล้น SDK เปิดตัวนับแบบเดียวกันให้อ่านได้หลายจุด เช่น
ai_engine_stack_words() กับ ai_engine_stack_free_words() ของ task inference ซึ่งตัวอย่าง
07_engine_health.c
อธิบายว่าค่าหลังเป็น “ALL-TIME MINIMUM. It only ever falls.” และการอ่านต้องสแกน stack จึงควรอ่านราว 1 ครั้งต่อวินาที ไม่ใช่ในลูปวาดจอ
อย่าพึ่งแค่ตัวตรวจอัตโนมัติ configCHECK_FOR_STACK_OVERFLOW ถูกตั้งเป็น 2 ในทั้งสองคอร์ และ hook ของ CM33 จะพิมพ์
FATAL: Stack overflow in task '...' แต่คอมเมนต์ใน linker script เตือนว่ามัน “only samples at a context switch”
(บรรทัด 307) stack ที่ล้นไปทับข้อมูลข้าง ๆ ระหว่างสองครั้งที่ตรวจ จึงอาจสร้างความเสียหายก่อน hook จะทันทำงาน
วิธีที่ใช้ได้จริงคือวัด high-water mark ตอนระบบทำงานหนักที่สุด แล้วเผื่อที่ว่างไว้
หลักตัดสินที่หลักสูตรนี้ใช้ในแบบฝึก (เป็นหลักของเรา ไม่ใช่ตัวเลขจาก SDK หรือ FreeRTOS): เหลือน้อยกว่า 32 word ถือว่า วิกฤต เหลือน้อยกว่าหนึ่งในสี่ของทั้งหมดถือว่า ต่ำ ควรขยาย stack หรือย้ายบัฟเฟอร์ใหญ่ออกจาก stack
3. heap: ทำไมไม่จองใน ISR หรือในลูปเวลาจริง
หัวข้อที่มีชื่อว่า “3. heap: ทำไมไม่จองใน ISR หรือในลูปเวลาจริง”แม่แบบของ SDK ตั้ง configHEAP_ALLOCATION_SCHEME เป็น heap_3
(FreeRTOSConfig.h ของ CM33 บรรทัด 189)
ซึ่ง pvPortMalloc() ของ FreeRTOS เรียก malloc() ของ newlib ตรง ๆ โดยหยุด scheduler ไว้ระหว่างนั้น (vTaskSuspendAll() แล้ว xTaskResumeAll())
การจองหน่วยความจำแบบ dynamic ไม่เหมาะกับ ISR และลูปเวลาจริงด้วยเหตุผลสี่ข้อ
- ไม่ปลอดภัยในบริบท ISR FreeRTOS อนุญาตให้ ISR เรียกเฉพาะฟังก์ชันที่ลงท้ายด้วย
FromISRและxTaskResumeAll()ไม่ใช่หนึ่งในนั้น - เวลาไม่แน่นอน
malloc()ต้องค้นหาช่องว่าง เวลาที่ใช้ขึ้นกับสภาพ heap ในตอนนั้น ลูปที่ต้องตรงเวลาจึงเดาเวลาของตัวเองไม่ได้ - fragmentation จองและคืนก้อนขนาดต่างกันไปนาน ๆ heap อาจเหลือรวมหลายร้อยไบต์แต่ไม่มีช่องต่อเนื่องใหญ่พอ
hook
vApplicationMallocFailedHook()ของ CM33 จึงพิมพ์ทั้งfreeและlargest_freeเพราะ “Malloc failed” เฉย ๆ ไม่บอกว่า heap หมดหรือแค่แตกเป็นชิ้น (main.c บรรทัด 411-425) - จัดการความล้มเหลวไม่ได้ ใน ISR ไม่มีที่ให้รอ ไม่มีที่ให้ลองใหม่ และไม่ควรพิมพ์อะไรเลย
ทางออกคือจองทุกอย่างตอนเริ่มระบบหรือใช้บัฟเฟอร์ static SDK ทำแบบนี้ให้ดูในหลายที่ เช่น littlefs ใน variant mtb-only
ถูก build ด้วย LFS2_NO_MALLOC “because every buffer is supplied statically; a code path that would need the heap fails to compile
instead of quietly allocating” (variants/mtb-only.mk บรรทัด 35-39)
และตัวอย่าง 10_littlefs_basics.c ประกาศบัฟเฟอร์ 512 ไบต์เป็น static char s_buf[512]; พร้อมเหตุผลว่า
“the runner task’s stack is not the place for it”
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”examples/02_where_it_lives.c ทำงานเป็นสามท่า
- ท่าที่ 1 พิมพ์ที่อยู่ของวัตถุแปดอย่าง พร้อมป้ายว่าอยู่ส่วนไหน ให้คุณตรวจด้วยตาว่าที่อยู่จับกลุ่มตามป้ายจริง
- ท่าที่ 2 พิมพ์ขนาดของแต่ละก้อน ว่ากินที่จาก stack, heap หรือข้อมูลอ่านอย่างเดียว
- ท่าที่ 3 เรียกฟังก์ชันซ้อนสามชั้น แต่ละชั้นพิมพ์ที่อยู่ของตัวแปร local ของตัวเอง ให้เห็นว่า stack โตลง
ลองแก้แล้วทายก่อนรัน
- ย้าย
uint8_t local_buf[64]ออกไปไว้นอกmainป้ายที่ถูกต้องของมันเปลี่ยนเป็นอะไร - ใส่
staticหน้าuint8_t local_buf[64](ยังอยู่ในmain) ที่อยู่ของมันย้ายไปกลุ่มไหน - เปลี่ยนเงื่อนไข
level < 3เป็นlevel < 100000แล้วรัน โปรแกรมเป็นอย่างไร และบนไมโครคอนโทรลเลอร์ที่ไม่มีระบบปฏิบัติการคอยจับ จะเกิดอะไรแทน
ฝึกเติม
หัวข้อที่มีชื่อว่า “ฝึกเติม”เปิด practice/02_stack_watermark.c ไฟล์นี้จำลองวิธีที่ FreeRTOS วัด stack ด้วยอาร์เรย์หนึ่งก้อน มีช่องให้เติม 3 จุด
stack_paint()ระบายทุกไบต์ด้วย0xA5high_water_bytes()นับไบต์0xA5ที่ต่อเนื่องจากปลายที่ stack ยังไปไม่ถึงpercent_used()คิดเปอร์เซ็นต์แบบเดียวกับตัวอย่าง 07_engine_health และต้องไม่หารด้วยศูนย์
gcc -std=c11 -Wall -Wextra -o watermark practice/02_stack_watermark.c && ./watermarkสังเกต test ที่เรียก simulate_use() แบบตื้นลงหลังจากเคยลึก ค่าที่คาดไว้ไม่เปลี่ยน เพราะ high-water mark คือค่าต่ำสุดตลอดอายุ
ลองเองก่อนอย่างน้อย 15 นาที แล้วเปิด solution/02_stack_watermark.c
จุดที่ควรเทียบคือ percent_used() ในเฉลยตรวจทั้ง total == 0 และ free > total ก่อนลบ
เพราะการลบ unsigned ที่ติดลบจะวนกลับเป็นเลขมหาศาล (ความรู้จากบทเรียน 1.1)
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”ตอบคำถาม 5 ข้อใน quiz.yaml (บนเว็บไซต์อยู่ท้ายหน้านี้) ครอบคลุมเป้าหมายทั้งสามข้อ ตอบถูกตั้งแต่ 4 ข้อขึ้นไปถือว่าจบบทเรียน
งาน: อ่านตัวนับ stack ของ task จริงสองตัวบนบอร์ด แล้วตัดสินว่าปลอดภัยหรือไม่
- build แม่แบบของ SDK โดยเปิดตัวอย่าง (ขั้นตอนเต็มอยู่ใน บทเรียน 2.1)
แล้วถอดสาย USB ให้สุดและเสียบใหม่
Terminal window make build -j ENABLE_PAGE_EXAMPLES=1make program - บนจอ แตะการ์ด SDK Examples เลือก
cm55/display/00_display_bringupแล้วกด Run this example จดสองค่า คือstack %lu wordsของ GFX task และstack never used: %lu words (all-time low) - เลือก
cm55/edge_ai/07_engine_healthแล้วรัน จดบรรทัดinference task stack: ... words granted, ... still free at its worst (...% used)ตัวอย่างนี้อ่านสองค่านี้ก่อนตรวจว่ามีโมเดลทำงานอยู่ไหม จึงได้ค่า stack แม้จะไม่มีโมเดลทำงาน (ถ้าได้ข้อความว่า task ไม่เคยถูกสร้าง ให้จดไว้ นั่นคือผลการวัดเหมือนกัน) - คำนวณเปอร์เซ็นต์ที่ใช้ของ GFX task เอง แล้วตัดสินทั้งสอง task ด้วยหลักของแบบฝึก (วิกฤต ต่ำ หรือปลอดภัย)
- เปิด 10_littlefs_basics.c
เลือกตัวแปรห้าตัว เช่น
s_buf,k_known_paths,s_allow_write,present,nแล้วจำแนกว่าแต่ละตัวอยู่ใน stack, heap หรือ static พร้อมเหตุผลหนึ่งประโยค
หลักฐานที่เก็บไว้ใน portfolio: ภาพถ่ายหน้าจอผลของทั้งสองตัวอย่าง ตารางค่าที่จด การคำนวณ คำตัดสิน และตารางจำแนกตัวแปรห้าตัว
- อ่าน linker script ของ CM33 ช่วง
.cy_csr_buffers(บรรทัด 261-324) ทีมย้ายบัฟเฟอร์ static ขนาด 8 KB และ stack ของ task หนึ่งออกจาก.bssไปไว้ใน RAM อีกก้อน เพื่อคืนที่ให้ heap แล้วใส่ASSERTให้ build ล้มถ้าวันหนึ่งบัฟเฟอร์เหล่านั้นหลุดกลับมา ลองอธิบายว่าทำไมการ “ให้ build ล้ม” ดีกว่าการเขียนเตือนไว้ในเอกสาร - เอกสาร FreeRTOS หัวข้อ memory management: เทียบ heap_1 ถึง heap_5 และสังเกตว่า
เมื่อใช้ heap_3 ค่า
configTOTAL_HEAP_SIZEในไฟล์ config ไม่ได้กำหนดขนาด heap จริง
บทถัดไป: บทเรียน 1.3 struct, pointer และบัฟเฟอร์วงแหวน
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- ในโปรแกรมที่คุณเคยเขียน มีบัฟเฟอร์ใหญ่ตัวไหนที่เป็นตัวแปร local และถ้าย้ายโปรแกรมนั้นมาไว้ใน task ที่มี stack 256 word จะเกิดอะไร
- ถ้าโปรแกรมทำงานได้ปกติมาหนึ่งชั่วโมง แล้วเริ่ม
Malloc failedทั้งที่ไม่มีการรั่ว คุณจะเก็บหลักฐานอะไรเพื่อแยกว่า heap หมดหรือแตกเป็นชิ้น
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- SDK: cm55/edge_ai/07_engine_health.c (ai_engine_stack_words และ ai_engine_stack_free_words)
- SDK: cm33/storage/10_littlefs_basics.c (บัฟเฟอร์และสัญญาของ API หน่วยเก็บ)
- SDK: cm55/display/00_display_bringup.c (high-water mark ของ GFX task)
- SDK: linker script ของ CM33 non-secure (pse84_ns_cm33.ld)
- Appendix X — Traps and anti-patterns (เอกสาร SDK สร้างจาก commit ef72c1b)
- FreeRTOS documentation
- Infineon FreeRTOS @ release-v10.6.202 (รุ่นที่แม่แบบของ SDK ดึงมา): task.h
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
ในฟังก์ชัน void task(void) { static uint8_t buf[512]; uint8_t tmp[16]; uint8_t *p = malloc(32); } ข้อใดจำแนกถูก (เป้าหมายข้อ 1)
- buf อยู่ใน stack เพราะประกาศในฟังก์ชัน, tmp อยู่ใน stack, ก้อน 32 ไบต์อยู่ใน heap
- buf เป็น static (.bss), tmp อยู่ใน stack, ตัวชี้ p อยู่ใน stack แต่ก้อน 32 ไบต์ที่มันชี้อยู่ใน heap
- buf เป็น static (.data), tmp อยู่ใน heap, p อยู่ใน heap
- ทั้งสามอยู่ใน heap เพราะเป็นอาร์เรย์
ดูเฉลย
คำตอบ: B. buf เป็น static (.bss), tmp อยู่ใน stack, ตัวชี้ p อยู่ใน stack แต่ก้อน 32 ไบต์ที่มันชี้อยู่ใน heap
static ในฟังก์ชันมีอายุเท่าโปรแกรม และไม่มีค่าเริ่มต้นจึงอยู่ .bss ตัวแปร local ธรรมดาอยู่ใน stack frame ส่วน malloc ให้ก้อนใน heap โดยตัวชี้ที่เก็บที่อยู่ของก้อนนั้นเป็นตัวแปร local อยู่ใน stack
-
จาก linker script ของ CM33 ใน SDK ข้อใดถูก (เลือกได้หลายข้อ) (เป้าหมายข้อ 1)
- ค่าเริ่มต้นของ .data เก็บใน flash และถูกคัดลอกมาไว้ RAM ตอนบูต
- heap เริ่มหลัง .bss ดังนั้นเพิ่มตัวแปร static ขนาดใหญ่แล้ว heap เล็กลง
- .bss กินพื้นที่ใน flash เท่ากับขนาดของมันเพื่อเก็บค่าศูนย์
- stack อยู่บนสุดของ RAM ก้อนนั้นและโตลงหาที่อยู่ต่ำ
ดูเฉลย
คำตอบ: A. ค่าเริ่มต้นของ .data เก็บใน flash และถูกคัดลอกมาไว้ RAM ตอนบูต · B. heap เริ่มหลัง .bss ดังนั้นเพิ่มตัวแปร static ขนาดใหญ่แล้ว heap เล็กลง · D. stack อยู่บนสุดของ RAM ก้อนนั้นและโตลงหาที่อยู่ต่ำ
.data วางแบบ > m33_data AT > m33_nvm_sel และมีตาราง copy จาก flash ไป RAM ส่วน .heap ขยายจากท้าย .bss จนถึงก่อน stack คอมเมนต์จึงเตือนว่าทุกไบต์ของ .bss กินไบต์ของ heap ส่วน .bss เป็น NOLOAD ถูกล้างเป็นศูนย์ตอนบูต ไม่ต้องเก็บค่าใน flash
-
ตัวอย่าง 07_engine_health รายงานว่า task inference ได้ stack 2048 words และ stack_free_words เป็น 96 ข้อสรุปใดดีที่สุด (เป้าหมายข้อ 2)
- ใช้ไป 4.7% ปลอดภัยมาก
- ใช้ไปราว 95% ตอนที่ลึกที่สุด เหลือน้อยกว่าหนึ่งในสี่ ควรขยาย stack หรือหาบัฟเฟอร์ใหญ่ที่อยู่บน stack
- ค่านี้เป็นค่าตอนนี้เท่านั้น อีกวินาทีอาจกลับมาเหลือ 2000 words ไม่ต้องสนใจ
- task ล้นไปแล้ว เพราะเลขไม่เป็นศูนย์
ดูเฉลย
คำตอบ: B. ใช้ไปราว 95% ตอนที่ลึกที่สุด เหลือน้อยกว่าหนึ่งในสี่ ควรขยาย stack หรือหาบัฟเฟอร์ใหญ่ที่อยู่บน stack
(2048 - 96) * 100 / 2048 ได้ราว 95% และ stack_free_words เป็นค่าต่ำสุดตลอดอายุ ลดได้อย่างเดียว ไม่กลับขึ้น ค่า 96 words ยังไม่ล้นแต่ต่ำกว่าหนึ่งในสี่ของทั้งหมดมาก ตามหลักของหลักสูตรถือว่าต่ำ
-
ทำไมการตั้ง configCHECK_FOR_STACK_OVERFLOW = 2 อย่างเดียวจึงไม่พอ (เป้าหมายข้อ 2)
- เพราะมันตรวจเฉพาะตอนสลับ task stack ที่ล้นระหว่างนั้นทับข้อมูลข้าง ๆ ได้ก่อน hook จะทำงาน
- เพราะมันทำให้เฟิร์มแวร์ช้าจนใช้งานไม่ได้
- เพราะมันทำงานเฉพาะบน CM55
- เพราะมันลบค่า 0xA5 ออกจาก stack
ดูเฉลย
คำตอบ: A. เพราะมันตรวจเฉพาะตอนสลับ task stack ที่ล้นระหว่างนั้นทับข้อมูลข้าง ๆ ได้ก่อน hook จะทำงาน
คอมเมนต์ใน linker script ของ SDK เขียนไว้ว่ามัน only samples at a context switch จึงควรวัด high-water mark ตอนระบบทำงานหนัก แล้วเผื่อที่ว่าง แทนการรอให้ hook จับได้
-
ข้อใดเป็นเหตุผลที่ไม่ควรเรียก malloc() ใน ISR หรือในลูปที่ต้องตรงเวลา (เลือกได้หลายข้อ) (เป้าหมายข้อ 3)
- เวลาที่ malloc ใช้ขึ้นกับสภาพ heap ในตอนนั้น จึงเดาเวลาไม่ได้
- heap อาจเหลือรวมพอแต่ไม่มีช่องต่อเนื่องใหญ่พอ (fragmentation) แล้วล้มกลางทาง
- pvPortMalloc ของ heap_3 หยุดและปล่อย scheduler ซึ่งไม่ใช่ฟังก์ชัน FromISR
- malloc คืนหน่วยความจำจาก flash ซึ่งช้ากว่า RAM
ดูเฉลย
คำตอบ: A. เวลาที่ malloc ใช้ขึ้นกับสภาพ heap ในตอนนั้น จึงเดาเวลาไม่ได้ · B. heap อาจเหลือรวมพอแต่ไม่มีช่องต่อเนื่องใหญ่พอ (fragmentation) แล้วล้มกลางทาง · C. pvPortMalloc ของ heap_3 หยุดและปล่อย scheduler ซึ่งไม่ใช่ฟังก์ชัน FromISR
heap อยู่ใน RAM ไม่ใช่ flash ข้อที่เหลือเป็นเหตุผลจริงทั้งหมด SDK จึงจองบัฟเฟอร์แบบ static เช่น s_buf[512] และ build littlefs ด้วย LFS2_NO_MALLOC ให้โค้ดที่ต้องใช้ heap คอมไพล์ไม่ผ่านไปเลย
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"แผนที่หน่วยความจำ stack และ heap" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Memory map, stack and heap" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/tesaiot-pse84-devkit-sdk/tree/ef72c1b658178eee8c38b1e47d28b006f80a59b5 · SDK examples and docs are linked at this commit, not copied into this course. Lessons quote short excerpts (at most 25 lines) with a link to the file at this commit and the credit (Apache-2.0, tesaiot-pse84-devkit-sdk).
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA