Table of Contents
למה דווקא ב- 0 (ב) ,0) דברים ב-Creded
בפיתוח מערכות משובצות, אינטראקציה חומרה היא הגשר בין תוכנה לעולם הפיזי.מיקרובקרים ומעבדים לתקשר עם חיישנים, אקטוטורים, היקפי זיכרון ממופת, ומכשירים חיצוניים באמצעות רישומים וכתובות זיכרון שיכולים לשנות באופן סינכרוני. שפת התכנות C מספקת את המיומנות מסוג FLT:1 כדי לטפל בשינויים בלתי צפויים כאלה.
מאמר זה מתרחב במטרה של FLT:3, חפור לתוך מכניקה של אופטימיזציה של מאגד, מציג תבניות אינטראקציה של חומרת העולם האמיתי, ובהיר את הפגיעות הנפוצות.אתה תלמד בדיוק היכן להציב את ה-FLT:4 בקוד C שלך ומדוע זה נשאר חיוני למרות ההתקדמות המודרנית כמו FLT:5 או C++FLT 6.
מה עושה מילת המפתח 7?
ברמת השפה, אומר ה-FLT:8 ל-Matr כי הערך של משתנה עשוי להשתנות באמצעות מעבר לזרם התוכנית הרגיל - כגון חומרה, שגרת שירות מפריעה (ISR), או חוט קבוע פועל על ליבת אחרת.
- (ב) ,0) ,הנחה של הוראת כפל 1 מהכתובת הזיכרון של המשתנה בכל פעם שהמשתנה קורא בקוד המקור (לא צ'ינג ברשומות על פני קורא).
- (ב) ויקרא י"א: "ה', כ"א, ל"א'" (ב)" (ב"ב)" (בתרגום חופשי: "לא חתמת או תיקון של כתיבה).
- (ב) [ה]הנחה את הרצף המדויק של גישה למשתנה זה כפי שנכתב במקור, ביחס ל-FLT:9 גישה (למרות שלא בהכרח ביחס ל- non-ph10 Accesss, שהוא טעות נפוצה).
ערבויות אלה הן בדיוק מה שנדרש כאשר תוכנית C חייבת אינטראקציה עם רישום חומרה ממופת זיכרון שמשנה את המדינה בהתבסס על אירועים חיצוניים.לדוגמה, רישום סטטוס UART עשוי להצביע על כך שחתימה מוכנה לקריאה, אבל המדר עשוי לייעל את הלולאה אשר סקרים שנרשמו, בהנחה שהערך לעולם לא ישתנה.
כיצד אופטימיזציה של Compiler יוצרת בעיות
מודרני C מפיץ (GCC, Clang, IAR, ARM Compiler) ליישם אופטימיזציה אגרסיביים כמו ההרחבה קבועה, חיסול קוד מת, העברת קוד השחלות, ורישום הקצאה.חשבו על הלולאה המדומה הזו:
int *flag = (int *)0x20000000;
while (*flag == 0) {
// wait for hardware
}
ללא ההרחבה, המדור עשוי לנתח את גוף הלולאה ולהבחין כי (FLT:13 לעולם לא נכתב בתוך הלולאה.זה יכול אז להשחית את העומס של FLT:14 לפני הלולאה, להשוות אותו לאפס פעם אחת, וליצור לולאה אינסופית לעולם לא לבדוק את כתובת החומרה האמיתית שוב.
(הדגשה על כך) כ-FLT:16 כ"FLT:16" מאלץ את המדר להנפיק עומס טרי על כל ההצתה, ולהבטיח שהתוכנית רואה את מצב החומרה בפועל.
מתי ואיפה להשתמש ב-FLT:17 מילת מפתח
מילת המפתח של LT:18 צריך להיות מיושם בכל מצב שבו משתנה על ידי שחקן עצמאי מחוץ להיקף של חוט הנוכחי (או דרך ביצוע עיקרי).
- I/O רושם (רישום היקפי)
- משתנים משותפים בין ISR לבין הלולאה העיקרית
- מגוון של חוטים מרובים בסביבות חשוף-מטאל או RTOS (עם זהירות - FLT:19 לבד לא מספק אטומיות)
- שינויים גלובליים שונו על ידי DMA העברות
- תגיות: POSIX-likeסביבות
זיכרון-מרשם I/O
זהו המקרה הנפוץ ביותר בשימוש ב- C. רוב המיקרו-בקרים מפה לפקדים ולסטטוס נרשם לחלל כתובת הזיכרון של המעבד.לדוגמה, על ARM Cortex-M MCU, רישום הנתונים של GPIO עשוי לחיות בכתובת בכתובת בכתובת (FLT:20) גישה אליו באמצעות מסמן ל-FLT:21 מבטיחה כי כל אחד מהם כותב למעשה את ה- pin החומרה, קורא כל רמה הנוכחית.
#define GPIOA_ODR ( (volatile uint32_t *) 0x40020014 )
#define GPIOA_IDR ( (volatile uint32_t *) 0x40020010 )
void toggle_led(void) {
*GPIOA_ODR ^= (1 << 5); // toggle bit 5 – compiler will generate a load-modify-store
}
ללא ההרחבה (FLT:23), המאגד יכול לשלב מספר רב של כתיבה או סדר מחדש אותם, גרימת ג'ושים או כישלונות שקטים.
שינויים משתנים על ידי Interrupt Service Routines
כאשר ISR מעדכן משתנה עולמי שהלאה העיקרית קוראת, שתי הגישות חייבות להיות ⁇ :24 , דוגמאות אופייניות: הגדלת דלפק מתקתק, הגדרת דגל אירוע, או למלא חיץ מ- UART ISR.
volatile uint32_t system_tick = 0;
void SysTick_Handler(void) {
system_tick++; // ISR modifies this
}
void main_loop(void) {
while (1) {
uint32_t current_tick = system_tick; // main loop reads
// ...
}
}
אם לא היה [ה] אלא אם כן, [ה], [ה], [ה], [ה], [ה], [ה], [ה],] לא היה [ה] אלא אם כן], [ההההתערות], [ה], [התחילה] [ה] [ה] [ה] [ה] [ה]] [ה] [התתתת]]] [ה] [ה] [ה]]]],] [ה] [ה],],],], [הההההההההההה'], [ה'[ה'], [ה'ו[ה'],], [ה'], [ה'[ה'] [ה'], [ה'], [ה'], [ה'], [ה'], [ה'],], [ה'],], [ה'[ה'],],], אם], [ה'], [ה']'[ה']']']'[ה'[ה'[ה'[ה'
זיכרון משותף וזיכרון משותף
בקרים ישירות לזיכרון (DMA) יכולים להעתיק נתונים בין היקפים וזיכרון ללא התערבות CPU. דפוס טיפוסי הוא:
- ה- CPU מציג העברה DMA כדי למלא חיץ מ- ADC.
- הבקר DMA כותב נתונים לתוך חיץ זיכרון.
- ה-CPU קורא כי buffer לאחר ההעברה משלימה (השליטה בדגל או באמצעות הפרעה).
אם ה-buffer הוכרז כמערך פשוט, המאגד עשוי לייעל את הקריאה, להאמין שהמידע אינו נכתב על ידי CPU.ה-buffer חייב להיות הוכרז כ-FLT:30 (או להשתמש בנקודת מפנה FLT:31) כדי להבטיח שה-CPU קורא את הערכים הכתובים בפועל.
דוגמה: בדיקת מצב קשה
בואו להרחיב את הדוגמה המקורית לתרחיש מציאותי יותר - מחכה לעסקה SPI להשלים על ידי קריאת רישום סטטוס.
// Memory-mapped SPI peripheral registers
typedef struct {
volatile uint32_t CR; // control register
volatile uint32_t SR; // status register
volatile uint32_t DR; // data register
} SPI_TypeDef;
#define SPI1_BASE 0x40013000
#define SPI1 ((SPI_TypeDef *) SPI1_BASE)
void spi_send_byte(uint8_t data) {
// Wait until transmit buffer empty (bit 1 in SR set)
while ( !(SPI1->SR & (1 << 1)) ) {
// busy wait
}
// Write data to data register
SPI1->DR = data;
// Wait for transmission to complete (bit 7 in SR set)
while ( !(SPI1->SR & (1 << 7)) ) {
// busy wait
}
}
(ב) כי (ב[[1924]], [[1924]], [[1924]]]], [[1924]]]], [[1924]]]], [[1924]]]], [[1924]]]]]], [[1924]]]]]], [[1924]]]]]]]]]], [[1924]]]]]]]], [[1924]]]]]]]], [[1924]]]]]], [[1924]]
מעבר ל-FLT:37: מלכודות נפוצות ומגבלות
מילת המפתח של FLT:38 היא חזקה, אך לעיתים קרובות לא מובן מאליו שיש להכיר בה מגבלות חשובות:
אין ערובה לאטומים
(ב) [ה] [ה] [ה] [ה] [ה]] [ה] [ה]], [ה], [ה], [ה]] [ה]][ה]]]], [ה'[ה]], [ה'] [ה']'[ב']'[ה']']'[ה']']']'[ה'[ה']']']'[ה']']''[ה'[ה']'[ב'[ה'[ה'[ה'[ה']']']']'[ה'[ה'[ה'[ה'[ב']']'[ה']']'[ה'[ב']'[ב']']']']']']'[ה'[ה']'[ה'[ה'[ב'[ב']'[ה'[ב']']'['[ה'[ב'[ב']']'[ה']']'[ב'['['['['['[
אין זיכרון הקובע הבטחות
(ב) לא למנוע את המדור או ה- CPU מסידור מחדש של אי-FLT:43 גישה סביב FLT:44 גישה ל- C סטנדרטי רק מפרט כי גישה לאותו אובייקט FLT:45 לא מוקרן על ידי כבוד אחד לשני.
לא מצע לסינכרון נכון
בסביבות מרובות-הנקראות (RTOS או SMP), אין די למשתנה משותף.מספר חוטים עשויים לקרוא ולכתוב את אותו משתנה, וללא סינכרוניזציה נאותה (מתאים, סמפורים, או פעולות אטומיות), עדיין ניתן לקבל תנאי גזע ונוף לא עקבי של זיכרון.
(ב) ב[[1924]], [[1924]]]]
זה מפתה להזרים את כל המשתנה העולמי "בדיוק במקרה", אבל זה נוגד את יעילות יתר למנוע את המפלט מקידוד קוד לגיטימי, לנפיחות במחזורי גישה לזיכרון, ויכול להסתיר בעיות עיצוב אמיתיות.
- משתנים אשר נקראים רק או נכתבו בתוך חוט אחד ללא שינוי חיצוני.
- לולאות קריטיות שבהן המשתנה אינו נוגע בחומרה או ב-ISR.
- כתחליף לפעילות אטומית נאותה כאשר מעורבים מספר CPUs או ההקשרים האפשריים.
- על משתנים המשמשים את (FLT:53) - אובייקט FLT:54 פירושו התוכנה אינה יכולה לשנות אותה, אבל חומרה יכולה (למשל, רישום סטטוס לקריאה בלבד).
קוד אמיתי: UART RX עם אינטרפוגות ו- Ping-Pong Buffers
קחו בחשבון מקלט UART המשתמש בשחיקה כפולה, ה-ISR כותב מקבל עווית לתוך חיץ אחד, בעוד הלולאה העיקרית מעבדת את השני.
#define BUF_SIZE 64
volatile char buffer_a[BUF_SIZE];
volatile char buffer_b[BUF_SIZE];
volatile int active_buffer = 0; // 0 = buffer A, 1 = buffer B
volatile int bytes_received = 0;
void UART_IRQHandler(void) {
char data = UART->DR; // hardware register
if (active_buffer == 0) {
if (bytes_received < BUF_SIZE) {
buffer_a[bytes_received++] = data;
}
} else {
if (bytes_received < BUF_SIZE) {
buffer_b[bytes_received++] = data;
}
}
}
int main(void) {
while (1) {
if (bytes_received > 0) {
// Process data from active_buffer
// Swap buffers after processing
int current_buf = active_buffer;
char *data_ptr = (current_buf == 0) ? buffer_a : buffer_b;
int count = bytes_received;
// ... process data_ptr[0..count-1] ...
// Reset and switch
bytes_received = 0;
active_buffer = current_buf ^ 1;
}
}
}
כל הציצים והמשתנה השולט הם FLT:57 כך שהלאה העיקרית רואה את הנתונים האחרונים שנכתב על ידי ISR. Note: גם כאן, יש סיכון של לולאה העיקרית קריאה FLT:58 בעוד ISR מעדכנת את זה - אבל על ידי MCU עם לולאות ראשיות חד-פעמיות שיכולה לירות בכל עת, LTF משולב עם מנגנונים מורכבים מספיקים יכול להיות יותר, או יותר, כדי למנוע שימוש בטכניקות מורכבות.
המונחים: specific
מדגמים שונים עשויים לטפל במקרים שונים במקצת במקרים של קצה (C11, סעיף 6.7.3) מפרט את דרישות המינימום, אך המדפים יכולים להציע ערבויות חזקות יותר או חלשות יותר:
- [01:0]GCC/Clang: FLT:1hil התייחס ל-FLT:61 כסטנדרט; הם אינם מסדירים את ה-FLT:62 גישה זו לזו, אך עשויים להזמין מחדש את הלא-ראופל:63 סביבם.
- (ב) ,0) ,IAR Embedded Workbench: ⁇ FLT:1 מספק סמנטיה נוספת: כברירת מחדל, כל הגישה ל-FLT:66 אובייקטים מטופלים כאטומים בגודל האובייקט (עד 32 ביטים) וסידור זה יכול להיות מסוכן אם אתה מסתמך על הסדר חלש.
- (ב) ,0) ,ARM Compiler (צבא: חליל:1) דומה ל- GCC.
- [01:0]2VC: ⁇ 1] מבחינה היסטורית, MSVC נתן ל-FLT:67 לרכוש / שחרור סמנטיים לקריאה וכותב, אך החל מ-V 2015 מצב קונפורציה סטנדרטי (FLT:68) מסיר את הערבויות המכוונות.
תמיד להתייעץ עם התיעוד של המדור שלך ולבחון את ההרכבה שנוצרת כאשר ההתנהגות הנכונה היא קריטית.
חלופות וגישות מודרניות
בעוד ש-FLT:70 נשאר חיוני לרישום חומרה ותקשורת ISR, כמה מקרים של שימוש טובים יותר מוגשים על ידי תכונות שפה חדשות יותר:
| Use Case | Recommended Tool |
|---|---|
| Reading/writing memory-mapped I/O | volatile qualified pointer |
| Variable shared between ISR and main loop (single core) | volatile + disabling interrupts when accessing multi-word variables |
| Variable shared between multiple threads (SMP, RTOS) | _Atomic (C11) or compiler intrinsics + memory barriers |
| Flag or status bit touched by both threads and ISRs | stdatomic.h with atomic_flag or atomic_int |
| DMA buffers written by peripheral, read by CPU | volatile qualified pointer (or ensure compiler doesn’t optimize via proper barriers) |
ב C++, תבנית ה-FLT:78 מספקת הן אטומיות והן צו זיכרון, אך עבור חומרה רשומה גישה, FLT:79 הוא עדיין התבנית הסטנדרטית - C++20'sFLT:80 אינה תחליף אותה ל- I/O.
טעויות נפוצות וכיצד להימנע מהן
⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
טעות נפוצה היא להכריז על פנקס חומרה מבלי להשפיל את הסוג המצוין לטיפוס (FLT:82)
int *reg = (int *)0x40000000; // WRONG – not volatile
while (*reg == 0) ; // may be optimized
תיקון:
volatile int *reg = (volatile int *)0x40000000; // read volatile-qualified
מדרש: 8
אם אתה רוצה שהנקודה תהיה ניתנת למדידה על ידי חומרה (ר'), אתה יכול להשתמש ב-FLT:86, אבל זה אומר שהמשתנה המצביע יכול להשתנות, לא הנתונים שהוא מצביע עליהם.
שימוש ב-FLT:88, וניתן להזיזו את הסעיף הקריטי ללא הפרעה
נניח שיש לך נוגד 64 סיביות על 8 סיביות MCU.הלאה העיקרית קוראת אותו גבוה ונמוך. An ISR יכול לעדכן את הערך בין שני סוגיית ה-Bit קורא, נותן ערך מושחת.
סיכום
מילת המפתח ב-C היא כלי בסיסי להבטיח אינטראקציה נכונה של חומרה במערכות משובצות.זה מונע מהמדור מסלקת קריאה וכותב למקומות זיכרון שניתן לשנות על ידי אירועים חיצוניים - בין אם זה רישום חומרה, שגרה בשירות מפריע, או בקרי DMA.עם זאת, FLT:92 הוא לא כדור כסף: הוא לא מספק אטומיות, אינו מאחסן את הסדרי הזיכרון, ואינו יכול להחליף את העיוות הנכון של מערכת ההפעלה שלך, או את השגויה, באופן מדויק יותר, באופן מיידי, או את הביצועים של חומרת ה-ה, לא ניתן להחליף את ה-ה, באופן מדויק של חומרת ה-ה, כלומר, לא ניתן להחליף את ה-ה, לא ניתן להחליף את ה-ה, באופן מדויק של שימוש נכון, לא ניתן להחליף את הביצועים של חומרת-ה, או את ה-ה, באופן קבוע, באופן מדויק של חומרת-ה, באופן מדויק של שימוש נכון, באופן מדויק של שימוש בהגדרות של שימוש ב-מתאים של חומרת-ה, לא ניתן להחליף את ה-ה, באופן קבוע, כלומר, באופן מדויק של חומרת ה-ה, כלומר, לא ניתן להחליף את ה-מתאים, לא ניתן להחליף את ה-ה, לא ניתן להחליף את הביצועים
עבור כל מפתח מוטבע, מאסטרינג:93 הוא טקס של מעבר.שלב אותו עם הבנה מוצקה של התנהגותו של המדור שלך, מפת הזיכרון החומרה, ואת מודל הזיכרון של האדריכלות, ואתה לא תוכל למנוע שיעור שלם של כישלונות עדינים, קשים עד כדי-דהבוקג.
עוד קריאה
- (ב) ויקרא י"א: ויקרא י"ד: ויקרא י"א)
- (ב) ◄ ⁇ ⁇ ⁇
- (ב) ,0) ,(ה) ,(ה) ,(ה) ,(ה) , ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
- (ב) ,0) ,ב"ד: "ה'" (ב"ד: ).