ملفات تعريف الذاكرة على STM32 و Microcontrollers الأخرى: ثابت تحليل حجم المكدس

مرحبا يا هبر!

في المقال الأخير ، ذكرت ذلك بنفسي وسألت في التعليقات - حسناً ، جيدًا ، وبفضل الدس العلمي ، اخترنا حجم الحزمة ، يبدو أنه لا يوجد شيء يسقط ، لكن هل يمكننا أن نقيم بطريقة أكثر موثوقية ما الذي يساويه والذين أكلوا كثيرًا؟

نجيب باختصار: نعم ، لكن لا.

لا ، باستخدام طرق التحليل الثابت ، من المستحيل قياس حجم الرصة المطلوبة من قبل البرنامج بدقة - ولكن مع ذلك ، يمكن أن تكون هذه الطرق في متناول اليد.

الجواب أطول قليلا - تحت الخفض.

كما هو معروف على نطاق واسع لدائرة ضيقة من الناس ، يتم تخصيص المكان على المكدس ، في الواقع ، للمتغيرات المحلية التي تستخدمها وظيفة التنفيذ الحالية - باستثناء المتغيرات مع المعدل الثابت ، والتي يتم تخزينها في الذاكرة المخصصة بشكل ثابت ، في منطقة bss ، لأنه معانيها بين المكالمات وظيفة.

عند تنفيذ الوظيفة ، يضيف المترجم مساحة على المكدس للمتغيرات التي يحتاجها ، وعند الانتهاء ، يحرر هذه المساحة مرة أخرى. يبدو أن كل شيء بسيط ، لكن - وهذا جريء جدًا ولكن - لدينا العديد من المشكلات:

  1. استدعاء وظائف داخل وظائف أخرى تحتاج أيضا إلى مكدس
  2. في بعض الأحيان ، تستدعي الوظائف وظائف أخرى ليس من خلال مرجعها المباشر ، ولكن بواسطة مؤشر إلى وظيفة
  3. من حيث المبدأ ، من الممكن - على الرغم من أنه ينبغي تجنبه بكل الوسائل - استدعاء دالة متكررة عندما تدعو A إلى B و B إلى C و C داخل نفسه تستدعي A مرة أخرى
  4. في أي وقت قد يحدث انقطاع ، يكون معالجها هو نفس الوظيفة التي تريد قطعة المكدس الخاصة بها
  5. إذا كان لديك تسلسل هرمي من المقاطعات ، فقد تحدث مقاطعة أخرى داخل المقاطعة!

بشكل لا لبس فيه ، يجب حذف استدعاءات الوظائف العودية من هذه القائمة ، لأن وجودها هو عذر لعدم التفكير في حجم الرصة ، ولكن للذهاب معربًا عن رأيك إلى مؤلف الشفرة. كل شيء آخر ، للأسف ، لا يمكن شطبه في الحالة العامة (على الرغم من أنه قد يكون هناك فروق دقيقة على وجه الخصوص: على سبيل المثال ، يمكن أن يكون لكل المقاطعات الأولوية نفسها حسب التصميم ، على سبيل المثال ، كما في RIOT OS ، ولن يكون هناك مقاطعات متداخلة).

الآن تخيل لوحة زيتية:

  • الدالة A ، تناول 100 بايت على المكدس ، يستدعي الدالة B ، والتي تحتاج إلى 50 بايت
  • في وقت تنفيذ B ، من الواضح أن A لم تنته بعد ، لذلك لم يتم تحرير 100 بايت ، لذلك لدينا بالفعل 150 بايت على المكدس
  • تستدعي الدالة B الوظيفة C ، وهي تفعل ذلك من خلال مؤشر يمكن أن يشير إلى نصف دزينة من الوظائف المختلفة التي تستهلك من 5 إلى 50 بايت من المكدس ، وفقًا لمنطق البرنامج
  • في وقت التشغيل C ، تحدث مقاطعة مع معالج ثقيل يعمل طويلاً ويستهلك 20 بايت من المكدس
  • أثناء معالجة المقاطعة ، تحدث مقاطعة أخرى ذات أولوية أعلى ، يريد معالجها 10 بايت من المكدس

في هذا التصميم الجميل ، مع المصادفة الناجحة بشكل خاص لجميع الظروف ، سيكون لديك خمس وظائف نشطة على الأقل في نفس الوقت - A و B و C واثنان من معالجات المقاطعة. علاوة على ذلك ، لا يحتوي أحدهما على ثابت لاستهلاك المكدس ، لأنه قد يكون مجرد وظيفة مختلفة في ممرات مختلفة ، وفهمًا لإمكانية أو استحالة مقاطعة بعضنا البعض ، يجب على الأقل معرفة ما إذا كان لديك مقاطعات ذات أولويات مختلفة على الإطلاق ، وكحد أقصى - لفهم ما إذا كان يمكن أن تتداخل مع بعضها البعض.

من الواضح أنه بالنسبة لأي محلل أكواد ثابت تلقائي ، تكون هذه المهمة قريبة جدًا من الساحقة ، ويمكن تنفيذها فقط بتقريب تقريبي للتقدير العلوي:

  • جمع كدسات كافة معالجات المقاطعة
  • تلخيص مكدسات من الوظائف التي تعمل في نفس الفرع رمز
  • حاول إيجاد كل المؤشرات إلى الوظائف ومكالماتها ، واتخاذ الحد الأقصى لحجم المكدس بين الوظائف التي تشير إليها هذه المؤشرات كحجم المكدس

في معظم الحالات ، تحصل ، من ناحية ، على تقدير مرتفع للغاية ، ومن ناحية أخرى ، فرصة لتخطي بعض مكالمات الوظائف الصعبة بشكل خاص عبر المؤشرات.

لذلك ، في الحالة العامة ، يمكننا ببساطة أن نقول: هذه المهمة لا يتم حلها تلقائيًا . الحل اليدوي - الشخص الذي يعرف منطق هذا البرنامج - يتطلب حفر عدد غير قليل من الأرقام.

ومع ذلك ، يمكن أن يكون التقدير الثابت لحجم المكدس مفيدًا للغاية في تحسين البرامج - على الأقل لغرض بسيط وهو فهم من يأكل كم وليس الكثير.

هناك نوعان من الأدوات المفيدة للغاية لهذا في مجموعة أدوات GNU / gcc:

  • العلم -ststack الاستخدام
  • فائدة cflow

إذا قمت بإضافة -fstack-use إلى إشارات gcc (على سبيل المثال ، إلى Makefile في السطر مع CFLAGS) ، فبالنسبة لكل ملف مترجم٪ filename٪ .c ، سينشئ المحول البرمجي الملف٪ filename٪ .su ، حيث سيكون هناك نص بسيط وواضح.

خذ ، على سبيل المثال ، target.su لهذا الملبس الضخم :

target.c:159:13:save_settings 8 static target.c:172:13:disable_power 8 static target.c:291:13:adc_measure_vdda 32 static target.c:255:13:adc_measure_current 24 static target.c:76:6:cpu_setup 0 static target.c:81:6:clock_setup 8 static target.c:404:6:dma1_channel1_isr 24 static target.c:434:6:adc_comp_isr 40 static target.c:767:6:systick_activity 56 static target.c:1045:6:user_activity 104 static target.c:1215:6:gpio_setup 24 static target.c:1323:6:target_console_init 8 static target.c:1332:6:led_bit 8 static target.c:1362:6:led_num 8 static 

نرى هنا الاستهلاك الفعلي للمكدس لكل وظيفة تظهر فيه ، والتي يمكننا من خلالها استخلاص بعض الاستنتاجات لأنفسنا - حسنًا ، على سبيل المثال ، من المفيد محاولة التحسين في المقام الأول ، إذا واجهنا نقصًا في ذاكرة الوصول العشوائي.

في الوقت نفسه ، الانتباه ، لا يوفر هذا الملف فعليًا معلومات دقيقة حول الاستهلاك الفعلي للمكدس للوظائف التي تسمى منها الوظائف الأخرى !

لفهم الاستهلاك الكلي ، نحتاج إلى بناء شجرة اتصال وتلخيص مجموعات من جميع الوظائف المدرجة في كل فرع من فروعها. يمكن القيام بذلك ، على سبيل المثال ، من خلال الأداة المساعدة GNU cflow عن طريق تعيينها على ملف واحد أو أكثر.

العادم هنا نحصل على ترتيب أكبر وزنًا ، وسأقدم جزءًا منه فقط لنفس الهدف. ج:

 olegart@oleg-npc /mnt/c/Users/oleg/Documents/Git/dap42 (umdk-emb) $ cflow src/stm32f042/umdk-emb/target.c adc_comp_isr() <void adc_comp_isr (void) at src/stm32f042/umdk-emb/target.c:434>: TIM_CR1() ADC_DR() ADC_ISR() DMA_CCR() GPIO_BSRR() GPIO_BRR() ADC_TR1() ADC_TR1_HT_VAL() ADC_TR1_LT_VAL() TIM_CNT() DMA_CNDTR() DIV_ROUND_CLOSEST() NVIC_ICPR() clock_setup() <void clock_setup (void) at src/stm32f042/umdk-emb/target.c:81>: rcc_clock_setup_in_hsi48_out_48mhz() crs_autotrim_usb_enable() rcc_set_usbclk_source() dma1_channel1_isr() <void dma1_channel1_isr (void) at src/stm32f042/umdk-emb/target.c:404>: DIV_ROUND_CLOSEST() gpio_setup() <void gpio_setup (void) at src/stm32f042/umdk-emb/target.c:1215>: rcc_periph_clock_enable() button_setup() <void button_setup (void) at src/stm32f042/umdk-emb/target.c:1208>: gpio_mode_setup() gpio_set_output_options() gpio_mode_setup() gpio_set() gpio_clear() rcc_peripheral_enable_clock() tim2_setup() <void tim2_setup (void) at src/stm32f042/umdk-emb/target.c:1194>: rcc_periph_clock_enable() rcc_periph_reset_pulse() timer_set_mode() timer_set_period() timer_set_prescaler() timer_set_clock_division() timer_set_master_mode() adc_setup_common() <void adc_setup_common (void) at src/stm32f042/umdk-emb/target.c:198>: rcc_periph_clock_enable() gpio_mode_setup() adc_set_clk_source() adc_calibrate() adc_set_operation_mode() adc_disable_discontinuous_mode() adc_enable_external_trigger_regular() ADC_CFGR1_EXTSEL_VAL() adc_set_right_aligned() adc_disable_temperature_sensor() adc_disable_dma() adc_set_resolution() adc_disable_eoc_interrupt() nvic_set_priority() nvic_enable_irq() dma_channel_reset() dma_set_priority() dma_set_memory_size() dma_set_peripheral_size() dma_enable_memory_increment_mode() dma_disable_peripheral_increment_mode() dma_enable_transfer_complete_interrupt() dma_enable_half_transfer_interrupt() dma_set_read_from_peripheral() dma_set_peripheral_address() dma_set_memory_address() dma_enable_circular_mode() ADC_CFGR1() memcpy() console_reconfigure() tic33m_init() strlen() tic33m_display_string() 

وهذا ليس حتى نصف الشجرة.

لفهم الاستهلاك الفعلي للمكدس ، نحتاج إلى أخذ الاستهلاك لكل من الوظائف المذكورة فيه ولجمع هذه القيم لكل فرع من الفروع.

وبينما لا نزال لا نأخذ في الاعتبار استدعاءات الوظائف عن طريق المؤشرات والمقاطعات ، بما في ذلك. متداخلة (وبالتحديد في هذا الرمز ، يمكن تداخلها).

كما قد تتخيل ، فإن القيام بذلك في كل مرة تقوم فيها بتغيير الكود هو ، بعبارة خفيفة ، صعبة - ولهذا السبب لا يفعلها أحد عادة.

ومع ذلك ، من الضروري فهم مبادئ تعبئة الرصة - يمكن أن يؤدي ذلك إلى قيود معينة على رمز المشروع ، مما يزيد من موثوقيتها من وجهة نظر منع تجاوز سعة الرصة (على سبيل المثال ، حظر المقاطعات المتداخلة أو استدعاء الوظائف بواسطة المؤشرات) ، ويمكن استخدام -ststack بشكل كبير المساعدة في تحسين الكود على الأنظمة التي تفتقر إلى ذاكرة الوصول العشوائي.

Source: https://habr.com/ru/post/ar443544/


All Articles