नॉन-प्रीमेप्टिव ओएस के क्या लाभ हैं? और इन लाभों के लिए कीमत?


14

एक वर्जित धातु MCU के लिए, बैकग्राउंड लूप और टाइमर इंटरप्ट आर्किटेक्चर के साथ होममेड कोड की तुलना में, गैर-अप्रभावी ओएस के क्या लाभ हैं? पृष्ठभूमि लूप आर्किटेक्चर के साथ होममेड कोड का उपयोग करने के बजाय गैर-प्रीमेप्टिव ओएस को अपनाने के लिए इन लाभों में से कौन सा लाभ पर्याप्त आकर्षक है?

प्रश्न का स्पष्टीकरण:

मैं वास्तव में उन सभी की सराहना करता हूं जिन्होंने मेरे प्रश्न का उत्तर दिया है। मुझे लगता है कि उत्तर लगभग वहीं रहा है। मैं यहाँ अपने प्रश्न के लिए इस स्पष्टीकरण को जोड़ता हूँ जो मेरा अपना विचार दिखाता है और प्रश्न को कम करने या इसे अधिक सटीक बनाने में मदद कर सकता है।

जो मैं करने की कोशिश कर रहा हूं वह यह समझना है कि सामान्य रूप से किसी परियोजना के लिए सबसे उपयुक्त आरटीओएस का चयन कैसे करें।
इसे प्राप्त करने के लिए, बुनियादी अवधारणाओं की बेहतर समझ और विभिन्न प्रकार के आरटीओएस से सबसे आकर्षक लाभ और संबंधित मूल्य में मदद मिलेगी, क्योंकि सभी अनुप्रयोगों के लिए सबसे अच्छा आरटीओएस नहीं है।
मैंने कुछ साल पहले ओएस के बारे में किताबें पढ़ी थीं, लेकिन अब मैं उनके साथ नहीं हूं। मैंने अपना प्रश्न यहां पोस्ट करने से पहले इंटरनेट पर खोजा और पाया कि यह जानकारी सबसे अधिक उपयोगी थी: http://www.ustudy.in/node/5456
विभिन्न आरटीओएस की वेबसाइट में परिचय, पूर्व-खाली समय-निर्धारण और गैर-पूर्व-निर्धारण कार्यक्रम और आदि की तुलना करने वाले लेखों की तरह बहुत सी अन्य उपयोगी जानकारी हैं।
लेकिन मुझे ऐसा कोई भी विषय नहीं मिला जो गैर-निवारक आरटीओएस का चयन करने के लिए उल्लेख किया गया हो और जब टाइमर अवरोध और पृष्ठभूमि लूप का उपयोग करके अपना कोड लिखना बेहतर होता है।
मेरे अपने कुछ उत्तर हैं लेकिन मैं उनसे संतुष्ट नहीं हूँ।
मैं वास्तव में अधिक एक्सपायर लोगों से जवाब जानना चाहता हूं या देखना चाहता हूं, खासकर उद्योग अभ्यास में।

मेरी अब तक की समझ है:
कोई बात नहीं या ओएस का उपयोग न करें, कुछ विशेष प्रकार के शेड्यूलिंग कोड हमेशा आवश्यक होते हैं, यहां तक ​​कि यह कोड के रूप में है:

    in the timer interrupt which occurs every 10ms  
    if(it's 10ms)  
    {  
      call function A / execute task A;  
    }  
    if(it's 50ms)  
    {  
      call function B / execute task B;  
    }  

लाभ 1:
एक गैर-निवारक ओएस शेड्यूलिंग कोड के लिए जिस तरह से / प्रोग्रामिंग शैली को डिजाइन करता है, ताकि इंजीनियर उसी दृश्य को साझा कर सकें, भले ही वे पहले एक ही परियोजना में न हों। फिर अवधारणा कार्य के बारे में एक ही दृष्टिकोण के साथ, इंजीनियर विभिन्न कार्यों पर काम कर सकते हैं और उनका परीक्षण कर सकते हैं, उन्हें यथासंभव स्वतंत्र रूप से प्रोफाइल कर सकते हैं।
लेकिन हम वास्तव में इससे कितना लाभ उठा पा रहे हैं? यदि इंजीनियर एक ही परियोजना में काम कर रहे हैं, तो वे गैर-प्रीमेप्टिव ओएस का उपयोग किए बिना उसी दृश्य को साझा करने का तरीका पा सकते हैं।
यदि एक इंजीनियर दूसरे प्रोजेक्ट या कंपनी से है, तो उसे लाभ होगा यदि वह पहले ओएस को जानता था। लेकिन अगर वह नहीं था, तो फिर से, यह उसके लिए एक नया ओएस या कोड का एक नया टुकड़ा सीखने के लिए बड़ा अंतर नहीं है।

लाभ 2:
यदि ओएस कोड का अच्छी तरह से परीक्षण किया गया है, तो यह डिबगिंग से समय बचाता है। यह वास्तव में एक अच्छा लाभ है।
लेकिन अगर आवेदन में केवल 5 कार्य हैं, तो मुझे लगता है कि टाइमर अवरोध और पृष्ठभूमि लूप का उपयोग करके अपना कोड लिखना वास्तव में गड़बड़ नहीं है।

यहां एक गैर-प्रीमेप्टिव ओएस को गैर-प्रीमेप्टिव शेड्यूलर के साथ एक वाणिज्यिक / मुफ्त / विरासत ओएस के लिए संदर्भित किया जाता है।
: जब मैं इस प्रश्न पोस्ट, मैं मुख्य रूप से जैसे कुछ OSes के बारे में सोच
(- अपनी वेबसाइट ने दावा किया है एक छोटे NonPreemptive RTOS) (1) KISS कर्नेल
http://www.frontiernet.net/~rhode/kisskern.html
(2) uSmartX (हल्के RTOS - अपनी वेबसाइट ने दावा किया है)
(3) FreeRTOS (यह एक रिक्तिपूर्व RTOS है, लेकिन के रूप में मैं समझता हूँ, यह अच्छी तरह के रूप में एक गैर रिक्तिपूर्व RTOS के रूप में विन्यस्त किया जा सकता)
(4) uC / ओएस (FreeRTOS के रूप में समान)
(5 ) कुछ कंपनियों में विरासत ओएस / अनुसूचक कोड (आमतौर पर कंपनी द्वारा आंतरिक रूप से बनाया और बनाए रखा जाता है)
(नए स्टैकऑवरफ़्लो खाते से सीमा के कारण अधिक लिंक नहीं जोड़ सकते हैं)

जैसा कि मैं समझता हूं, एक गैर-प्रीमैप्टिव ओएस इन कोडों का एक संग्रह है:
(1) गैर-प्रीमिटिव रणनीति का उपयोग करके एक अनुसूचक।
(2) अंतर-कार्य संचार, म्यूटेक्स, सिंक्रनाइज़ेशन और समय नियंत्रण के लिए सुविधाएं।
(३) स्मृति प्रबंधन।
(4) अन्य उपयोगी सुविधाओं / फाइल सिस्टम, नेटवर्क स्टैक, जीयूआई और आदि जैसे पुस्तकालयों (FreeRTOS और यू सी / ओएस इन प्रदान करता है, लेकिन मुझे यकीन है कि अगर वे अब भी काम जब अनुसूचक गैर रिक्तिपूर्व के रूप में कॉन्फ़िगर किया गया है नहीं कर रहा हूँ)
में से कुछ वे हमेशा वहाँ नहीं हैं। लेकिन अनुसूचक एक चाहिए।


यह बहुत संक्षेप में है। यदि आपके पास एक वर्कलोड है जिसे मल्टीथ्रेडेड करने की आवश्यकता है और आप ओवरहेड वहन कर सकते हैं, तो थ्रेडिंग ओएस का उपयोग करें। अन्यथा अधिकांश मामलों के लिए एक सरल समय या कार्य-आधारित "अनुसूचक" पर्याप्त है। और यह पता लगाने के लिए कि क्या प्रीमेप्टिव या कोऑपरेटिव मल्टीटास्किंग सबसे अच्छा है ... मुझे लगता है कि यह ओवरहेड के लिए नीचे आता है और आपको मल्टीटास्किंग पर कितना नियंत्रण करना है जो आपको करने की आवश्यकता है।
akohlsmith

जवाबों:


13

यह कुछ हद तक विषय से बदबू आ रही है, लेकिन मैं इसे वापस पटरी पर लाने की कोशिश करूंगा।

प्री-इप्टिव मल्टीटास्किंग का मतलब है कि ऑपरेटिंग सिस्टम या कर्नेल वर्तमान में चल रहे थ्रेड को निलंबित कर सकता है और जो भी शेड्यूलिंग ह्यूरिस्टिक्स के आधार पर होता है, उसके आधार पर दूसरे पर स्विच कर सकता है। ज्यादातर बार चलने वाले थ्रेड्स की कोई अवधारणा नहीं होती है कि सिस्टम पर अन्य चीजें चल रही हैं, और आपके कोड के लिए इसका क्या मतलब है, आपको इसे डिजाइन करने के लिए सावधान रहना चाहिए ताकि यदि कर्नेल एक के बीच में एक थ्रेड को निलंबित करने का फैसला करता है मल्टी-स्टेप ऑपरेशन (एक PWM आउटपुट को बदलते हुए, एक नए ADC चैनल का चयन करते हुए, I2C परिधीय से रीडिंग स्टेटस, आदि) और एक और थ्रेड को कुछ समय के लिए चलने दें, ताकि ये दोनों थ्रेड एक दूसरे के साथ हस्तक्षेप न करें।

एक मनमाना उदाहरण: मान लें कि आप मल्टीथ्रेडेड एम्बेडेड सिस्टम में नए हैं और आपके पास I2C ADC, SPI LCD और I2C EEPROM के साथ एक छोटा सिस्टम है। आपने तय किया कि दो धागे रखना एक अच्छा विचार होगा: एक जो एडीसी से पढ़ता है और ईईपीआरओएम को नमूने लिखता है, और एक जो अंतिम 10 नमूने पढ़ता है, उन्हें औसत करता है और उन्हें एसपीआई एलसीडी पर प्रदर्शित करता है। अनुभवहीन डिजाइन कुछ इस तरह दिखाई देगा (सकल सरलीकृत):

char i2c_read(int i2c_address, char databyte)
{
    turn_on_i2c_peripheral();
    wait_for_clock_to_stabilize();

    i2c_generate_start();
    i2c_set_data(i2c_address | I2C_READ);
    i2c_go();
    wait_for_ack();
    i2c_set_data(databyte);
    i2c_go();
    wait_for_ack();
    i2c_generate_start();
    i2c_get_byte();
    i2c_generate_nak();
    i2c_stop();
    turn_off_i2c_peripheral();
}

char i2c_write(int i2c_address, char databyte)
{
    turn_on_i2c_peripheral();
    wait_for_clock_to_stabilize();

    i2c_generate_start();
    i2c_set_data(i2c_address | I2C_WRITE);
    i2c_go();
    wait_for_ack();
    i2c_set_data(databyte);
    i2c_go();
    wait_for_ack();
    i2c_generate_start();
    i2c_get_byte();
    i2c_generate_nak();
    i2c_stop();
    turn_off_i2c_peripheral();
}

adc_thread()
{
    int value, sample_number;

    sample_number = 0;

    while (1) {
        value = i2c_read(ADC_ADDR);
        i2c_write(EE_ADDR, EE_ADDR_REG, sample_number);
        i2c_write(EE_ADDR, EE_DATA_REG, value);

        if (sample_number < 10) {
            ++sample_number;
        } else {
            sample_number = 0;
        }
    };
}

lcd_thread()
{
    int i, avg, sample, hundreds, tens, ones;

    while (1) {
        avg = 0;
        for (i=0; i<10; i++) {
            i2c_write(EE_ADDR, EE_ADDR_REG, i);
            sample = i2c_read(EE_ADDR, EE_DATA_REG);
            avg += sample;
        }

        /* calculate average */
        avg /= 10;

        /* convert to numeric digits for display */
        hundreds = avg / 100;
        tens = (avg % 100) / 10;
        ones = (avg % 10);

        spi_write(CS_LCD, LCD_CLEAR);
        spi_write(CS_LCD, '0' + hundreds);
        spi_write(CS_LCD, '0' + tens);
        spi_write(CS_LCD, '0' + ones);
    }
}

यह बहुत ही क्रूड और तेज उदाहरण है। इस तरह कोड मत करो!

अब याद रखें, पूर्व-खाली मल्टीटास्किंग ओएस इन थ्रेड्स को कोड में किसी भी लाइन (वास्तव में किसी भी विधानसभा निर्देश) पर निलंबित कर सकता है और दूसरे थ्रेड को चलाने का समय दे सकता है।

उसके बारे में सोचना। कल्पना कीजिए कि अगर adc_thread()वास्तविक डेटा लिखने और लिखने के लिए ईई पते की सेटिंग के बीच ओएस ने निलंबित करने का फैसला किया तो क्या होगा । lcd_thread()चला जाएगा, I2C परिधीय के साथ चारों ओर चकमा यह आवश्यक डेटा पढ़ने के लिए, और जब adc_thread()फिर से चलाने के लिए अपनी बारी मिली, तो EEPROM उसी स्थिति में नहीं होगा जो इसे छोड़ दिया गया था। चीजें बहुत अच्छी तरह से काम नहीं करेंगी। इससे भी बदतर, यह अधिकांश समय भी काम कर सकता है, लेकिन हर समय नहीं, और आप यह जानने की कोशिश में पागल हो जाएंगे कि आपका कोड काम क्यों नहीं कर रहा है जब इसे पसंद करना चाहिए!

यह एक सबसे अच्छा उदाहरण है; ओएस के संदर्भ i2c_write()से पूर्व-खाली करने का निर्णय ले सकता adc_thread()है और इसे फिर से चलाना शुरू कर सकता lcd_thread()है! चीजें वास्तव में बहुत तेजी से गड़बड़ हो सकती हैं।

जब आप एक पूर्व-खाली मल्टीटास्किंग वातावरण में काम करने के लिए कोड लिख रहे हैं, तो आपको यह सुनिश्चित करने के लिए लॉकिंग तंत्र का उपयोग करना होगा कि यदि आपका कोड एक इनोप्ट्यून समय में निलंबित हो गया है कि सभी नरक ढीले नहीं टूटते हैं।

दूसरी ओर, सहकारी मल्टीटास्किंग का मतलब है कि प्रत्येक थ्रेड नियंत्रण में है जब यह अपने निष्पादन का समय देता है। कोडिंग सरल है, लेकिन यह सुनिश्चित करने के लिए कोड को सावधानीपूर्वक डिज़ाइन किया जाना चाहिए कि सभी थ्रेड्स को चलाने के लिए पर्याप्त समय मिले। एक और विरोधाभासी उदाहरण:

char getch()
{
    while (! (*uart_status & DATA_AVAILABLE)) {
        /* do nothing */
    }

    return *uart_data_reg;
}

void putch(char data)
{
    while (! (*uart_status & SHIFT_REG_EMPTY)) {
        /* do nothing */
    }

    *uart_data_reg = data;
}

void echo_thread()
{
    char data;

    while (1) {
        data = getch();
        putch(data);
        yield_cpu();
    }
}

void seconds_counter()
{
    int count = 0;

    while (1) {
        ++count;
        sleep_ms(1000);
        yield_cpu();
    }
}

यह कोड काम नहीं करेगा कि आप कैसे सोचते हैं, या यहां तक ​​कि अगर यह काम करने लगता है, तो यह काम नहीं करेगा क्योंकि इको थ्रेड की डेटा दर बढ़ जाती है। फिर से, चलो इसे देखने के लिए एक मिनट लेते हैं।

echo_thread()एक बाइट के UART में आने का इंतजार करता है और फिर इसे प्राप्त करता है और तब तक इंतजार करता है जब तक कि इसे लिखने के लिए जगह नहीं है, तब इसे लिखते हैं। उसके बाद किया जाता है यह अन्य धागे को चलाने की बारी देता है। seconds_counter()एक गिनती में वृद्धि होगी, 1000ms की प्रतीक्षा करें और फिर अन्य थ्रेड्स को चलाने का मौका दें। अगर दो बाइट्स UART में आते हैं, जबकि यह लंबे समय sleep()से हो रहा है, तो आप उन्हें देखने से चूक सकते हैं क्योंकि हमारे काल्पनिक UART में वर्णों को संग्रहीत करने के लिए कोई FIFO नहीं है जबकि CPU अन्य चीजों को करने में व्यस्त है।

इस बहुत खराब उदाहरण को लागू करने का सही तरीका यह होगा कि yield_cpu()आप कभी व्यस्त लूप कहां रखें। यह चीजों को साथ ले जाने में मदद करेगा, लेकिन अन्य मुद्दों का कारण बन सकता है। उदाहरण के लिए यदि समय महत्वपूर्ण है और आप सीपीयू को एक और धागे की उपज देते हैं जो आपकी अपेक्षा से अधिक समय लेता है, तो आप अपना समय निकाल सकते हैं। एक पूर्व-खाली मल्टीटास्किंग ओएस में यह समस्या नहीं होगी क्योंकि यह थ्रेड्स को जबरन निलंबित करता है ताकि यह सुनिश्चित हो सके कि सभी थ्रेड्स सही ढंग से निर्धारित हैं।

अब इसे टाइमर और बैकग्राउंड लूप के साथ क्या करना है? टाइमर और बैकग्राउंड लूप ऊपर के सहकारी मल्टीटास्किंग उदाहरण के समान हैं:

void timer_isr(void)
{
    ++ticks;
    if ((ticks % 10)) == 0) {
        ten_ms_flag = TRUE;
    }

    if ((ticks % 100) == 0) {
        onehundred_ms_flag = TRUE;
    }

    if ((ticks % 1000) == 0) {
        one_second_flag = TRUE;
    }
}

void main(void)
{
    /* initialization of timer ISR, etc. */

    while (1) {
        if (ten_ms_flag) {
            if (kbhit()) {
                putch(getch());
            }
            ten_ms_flag = FALSE;
        }

        if (onehundred_ms_flag) {
                    get_adc_data();
            onehundred_ms_flag = FALSE;
        }

        if (one_second_flag) {
            ++count;
                    update_lcd();
            one_second_flag = FALSE;
        }
    };
}

यह सहकारी सूत्रण उदाहरण के बहुत करीब लग रहा है; आपके पास एक टाइमर है जो घटनाओं को सेट करता है और एक मुख्य लूप है जो उनके लिए दिखता है और परमाणु फैशन में उन पर कार्य करता है। आपको एडीसी और एलसीडी "थ्रेड्स" के बारे में चिंता करने की ज़रूरत नहीं है, क्योंकि वे एक दूसरे के साथ हस्तक्षेप नहीं करेंगे। आपको अभी भी एक "धागा" के बारे में चिंता करने की ज़रूरत है; उदाहरण के लिए क्या होता है अगर get_adc_data()30ms लेता है? आप एक चरित्र की जाँच करने और उसे प्रतिध्वनित करने के तीन अवसर याद करेंगे।

लूप + टाइमर कार्यान्वयन आमतौर पर सहकारिता से लागू करने के लिए बहुत सरल है, क्योंकि आपके कोड को हाथ में कार्य के लिए और अधिक विशिष्ट बनाया जा सकता है। आप वास्तव में इतनी मल्टीटास्किंग नहीं कर रहे हैं कि एक निश्चित प्रणाली को डिजाइन करना जहां आप प्रत्येक सबसिस्टम को अपने कार्यों को बहुत विशिष्ट और अनुमानित तरीके से करने के लिए कुछ समय देते हैं। यहां तक ​​कि सहकारी रूप से मल्टीटास्कड प्रणाली के लिए प्रत्येक थ्रेड के लिए एक सामान्य कार्य संरचना होनी चाहिए और चलाने के लिए अगला धागा एक शेड्यूलिंग फ़ंक्शन द्वारा निर्धारित किया जाता है जो काफी जटिल हो सकता है।

सभी तीन प्रणालियों के लिए लॉकिंग तंत्र समान हैं, लेकिन प्रत्येक के लिए आवश्यक ओवरहेड काफी अलग है।

व्यक्तिगत रूप से, मैं लगभग हमेशा इस अंतिम मानक को कोड करता हूं, लूप + टाइमर कार्यान्वयन। मुझे लगता है कि थ्रेडिंग एक ऐसी चीज है जिसका इस्तेमाल बहुत कम इस्तेमाल किया जाना चाहिए। न केवल लिखना और डिबग करना अधिक जटिल है, बल्कि इसके लिए अधिक ओवरहेड की भी आवश्यकता होती है (एक प्रीमेप्टिव मल्टीटास्किंग माइक्रोकर्नेल हमेशा एक बेवकूफ सरल टाइमर और मुख्य लूप इवेंट फॉलोअर से बड़ा होता है)।

एक कहावत यह भी है कि धागे पर काम करने वाला कोई भी व्यक्ति सराहना करेगा:

if you have a problem and use threads to solve it, yoeu ndup man with y pemro.bls

:-)


विस्तृत उदाहरण के साथ आपके उत्तर के लिए बहुत बहुत धन्यवाद, akohlsmith। हालाँकि, मैं आपके जवाब से यह निष्कर्ष नहीं निकाल सकता कि आप सहकारी मल्टीटास्किंग के बजाय सरल टाइमर और पृष्ठभूमि लूप आर्किटेक्चर क्यों चुनते हैं । मुझे गलत मत समझो मैं वास्तव में आपके उत्तर की सराहना करता हूं, जो विभिन्न समय-निर्धारण के बारे में बहुत सारी उपयोगी जानकारी प्रदान करता है। मुझे अभी बात नहीं मिली है।
हेलंग

क्या आप इस पर थोड़ा और काम कर सकते हैं?
१।

धन्यवाद, akohlsmith मुझे अंत में आपके द्वारा दिया गया वाक्य पसंद है। इसे पहचानने में मुझे कुछ समय लगा :) अपने जवाब के बिंदु पर वापस, आप लगभग हमेशा लूप + टाइमर कार्यान्वयन के लिए कोड करते हैं। फिर, जिन मामलों में आपने इस कार्यान्वयन को छोड़ दिया और गैर-निवारक ओएस की ओर मुड़ गए, आपने ऐसा क्या किया?
हेलंग

जब मैं किसी और के ओएस को चला रहा था तो मैं सहकारी और प्रीमेप्टिव मल्टीटास्किंग सिस्टम दोनों के साथ चला गया हूं। या तो लिनक्स, थ्रेडएक्स, ucOS-ii या QNX। यहां तक ​​कि उन स्थितियों में से कुछ में, मैंने सरल और प्रभावी टाइमर + इवेंट लूप का उपयोग किया है ( poll()तुरंत दिमाग में आता है)।
एकोहेल्समिथ

मैं एम्बेडेड में थ्रेडिंग या मल्टीटास्किंग का प्रशंसक नहीं हूं, लेकिन मुझे पता है कि जटिल प्रणालियों के लिए यह एकमात्र एकमात्र विकल्प है। डिब्बाबंद माइक्रो-ऑपरेटिंग सिस्टम आपको चीजों को प्राप्त करने और चलाने का एक त्वरित तरीका देता है और बार-बार डिवाइस ड्राइवरों को भी प्रदान करता है।
एकोहेल्समिथ

6

बहुत सारे माइक्रोकंट्रोलर प्रोजेक्ट्स में मल्टी-टास्किंग एक उपयोगी एब्स्ट्रैक्शन हो सकता है, हालांकि एक सच्चा पूर्व-खाली शेड्यूलर ज्यादातर मामलों में बहुत भारी और अनावश्यक होगा। मैंने 100 से अधिक माइक्रोकंट्रोलर परियोजनाओं पर अच्छा काम किया है। मैंने कई बार सहकारी टास्किंग का उपयोग किया है, लेकिन इसके संबंधित सामान के साथ पूर्व-खाली कार्य स्विच करना अभी तक उचित नहीं है।

सहकारिता के कार्य के रूप में पूर्व-अनुकरणीय समस्याएँ निम्नलिखित हैं:

  1. बहुत अधिक भारी। पूर्व-खाली कार्य शेड्यूलर अधिक जटिल हैं, अधिक कोड स्थान लेते हैं, और अधिक चक्र लेते हैं। उन्हें कम से कम एक रुकावट की भी आवश्यकता होती है। यह अक्सर आवेदन पर एक अस्वीकार्य बोझ है।

  2. म्यूटेक्स को उन संरचनाओं के आसपास की आवश्यकता होती है जिन्हें समवर्ती रूप से एक्सेस किया जा सकता है। एक सहकारी प्रणाली में, आप सिर्फ TASK_YIELD को परमाणु ऑपरेशन नहीं होने के बीच में नहीं कहते हैं। यह प्रभाव कतार, साझा वैश्विक स्थिति, और ढेर सारी जगहों पर दिखाई देता है।

सामान्य तौर पर, किसी कार्य को किसी विशेष कार्य के लिए समर्पित करने से यह समझ में आता है कि सीपीयू इस बात का समर्थन कर सकता है और यह कार्य पर्याप्त इतिहास-निर्भर संचालन के साथ काफी जटिल है जो इसे कुछ अलग-अलग घटनाओं में तोड़ना बोझिल होगा। यह आमतौर पर संचार इनपुट स्ट्रीम को संभालने के दौरान होता है। इस तरह की चीजें आमतौर पर कुछ पिछले इनपुट के आधार पर भारी रूप से संचालित होती हैं। उदाहरण के लिए, प्रत्येक opcode के लिए अद्वितीय डेटा बाइट्स के बाद opcode बाइट्स हो सकते हैं। फिर इन बाइट्स का मुद्दा आप पर आता है जब कुछ और उन्हें भेजने का मन करता है। एक अलग कार्य इनपुट स्ट्रीम को संभालने के साथ, आप इसे कार्य कोड में प्रकट कर सकते हैं जैसे कि आप बाहर जा रहे हैं और अगली बाइट प्राप्त कर रहे हैं।

कुल मिलाकर, कार्य तब उपयोगी होते हैं जब राज्य का बहुत संदर्भ होता है। कार्य मूल रूप से पीसी के साथ राज्य मशीन होते हैं जो राज्य चर होते हैं।

कई चीजें जो एक माइक्रो को करनी होती हैं, उन्हें घटनाओं के एक सेट के जवाब के रूप में व्यक्त किया जा सकता है। नतीजतन, मेरे पास आमतौर पर एक मुख्य ईवेंट लूप होता है। यह क्रम में प्रत्येक संभावित घटना की जांच करता है, फिर शीर्ष पर वापस कूदता है और फिर से यह सब करता है। जब किसी ईवेंट को संभालना केवल कुछ चक्रों से अधिक होता है, तो मैं आमतौर पर ईवेंट को संभालने के बाद इवेंट लूप की शुरुआत में वापस कूद जाता हूं। इसका प्रभाव यह है कि घटनाओं की एक अंतर्निहित प्राथमिकता होती है, जहां उन्हें सूची में जांचा जाता है। कई सरल प्रणालियों पर, यह काफी अच्छा है।

कभी-कभी आपको कुछ अधिक जटिल कार्य मिलते हैं। इन्हें अक्सर अलग-अलग चीजों की एक छोटी संख्या के अनुक्रम में तोड़ा जा सकता है। आप उन मामलों में घटनाओं के रूप में आंतरिक झंडे का उपयोग कर सकते हैं। मैं कम अंत PIC पर इस तरह की बात कई बार किया है।

यदि आपके पास ऊपर की तरह मूल घटना संरचना है, लेकिन उदाहरण के लिए, UART पर एक कमांड स्ट्रीम का जवाब देना है, तो प्राप्त UART स्ट्रीम को अलग कार्य संभालना उपयोगी है। कुछ माइक्रोकंट्रोलर्स के पास मल्टी-टास्किंग के लिए सीमित हार्डवेयर संसाधन होते हैं, जैसे कि PIC 16 जो स्वयं कॉल स्टैक नहीं पढ़ या लिख ​​सकता है। ऐसे मामलों में, मैं UART कमांड प्रोसेसर के लिए एक छद्म कार्य का उपयोग करता हूं। मुख्य ईवेंट लूप अभी भी सब कुछ संभालता है, लेकिन इसे संभालने की एक घटना यह है कि UART द्वारा एक नया बाइट प्राप्त किया गया था। उस स्थिति में यह एक ऐसी दिनचर्या से जुड़ जाता है जो इस छद्म कार्य को चलाता है। UART कमांड मॉड्यूल में कार्य कोड होता है, और निष्पादन पता और कार्य के कुछ रजिस्टर मान उस मॉड्यूल में RAM में सहेजे जाते हैं। ईवेंट लूप द्वारा कूद गया कोड वर्तमान रजिस्टरों को बचाता है, सहेजे गए कार्य रजिस्टरों को लोड करता है, और टास्क रिस्टार्ट एड्रेस को जम्प करता है। कार्य कोड एक YIELD मैक्रो को उलटा करता है, जो बाद में मुख्य ईवेंट लूप की शुरुआत में वापस कूदता है। कुछ मामलों में मुख्य ईवेंट लूप पास के अनुसार एक बार छद्म कार्य को चलाता है, आमतौर पर निचले स्तर पर यह एक कम प्राथमिकता वाली घटना है।

पीआईसी 18 और उच्चतर पर, मैं एक सच्चे सहकारी तंत्र का उपयोग करता हूं क्योंकि कॉल स्टैक फर्मवेयर द्वारा पठनीय और पठनीय है। इन प्रणालियों पर, पुनः आरंभ करने का पता, कुछ अन्य राज्य के टुकड़े, और डेटा स्टैक पॉइंटर को प्रत्येक कार्य के लिए मेमोरी बफर में रखा जाता है। अन्य सभी कार्यों को एक बार चलाने के लिए, एक कार्य TASK_YIELD कहता है। यह वर्तमान कार्य स्थिति को बचाता है, अगले उपलब्ध कार्य के लिए सूची को देखता है, अपने राज्य को लोड करता है, फिर इसे चलाता है।

इस वास्तुकला में, लूप के शीर्ष पर TASK_YIELD को कॉल करने के साथ मुख्य ईवेंट लूप सिर्फ एक और कार्य है।

PIC के लिए मेरा सभी मल्टी-टास्किंग कोड मुफ्त में उपलब्ध है। इसे देखने के लिए, http://www.embedinc.com/pic/dload.htm पर PIC डेवलपमेंट टूल्स रिलीज़ को स्थापित करें । 8 बिट PICs के लिए SOURCE> PIC निर्देशिका में उनके नाम के साथ "कार्य" फ़ाइलों के लिए देखें, और 16 बिट PICs के लिए SOURCE> DSPIC निर्देशिका।


म्यूटेक्स अभी भी सहकारी मल्टीटास्केड सिस्टम में आवश्यक हो सकता है, हालांकि यह दुर्लभ है। विशिष्ट उदाहरण एक ISR है जिसे एक महत्वपूर्ण अनुभाग तक पहुंचने की आवश्यकता है। यह लगभग हमेशा बेहतर डिजाइन के माध्यम से या महत्वपूर्ण डेटा के लिए एक उपयुक्त डेटा कंटेनर चुनने से बचा जा सकता है।
akohlsmith

@akoh: हां, मैंने कुछ मौकों पर एक साझा संसाधन को संभालने के लिए म्यूटेक्स का उपयोग किया है, जैसे एसपीआई बस तक पहुंच। मेरा कहना यह था कि म्यूटेक्स को स्वाभाविक रूप से उस हद तक जरूरी नहीं है कि वे एक पूर्व-खाली प्रणाली में हैं। मेरे कहने का मतलब यह नहीं है कि उन्हें कभी भी सहकारिता प्रणाली में उपयोग करने की आवश्यकता नहीं है। इसके अलावा, सहकारी प्रणाली में एक म्यूटेक्स TASK_YIELD लूप में किसी एक बिट की जाँच करने के लिए सरल है। पूर्व-खाली प्रणाली में उन्हें आम तौर पर कर्नेल में निर्मित करने की आवश्यकता होती है।
ओलिन लेट्रोप

@OlinLathrop: मुझे लगता है कि जब म्यूटेक्स की बात आती है तो गैर-प्रीमैप्टिव सिस्टम का सबसे महत्वपूर्ण लाभ यह है कि वे केवल तब आवश्यक होते हैं जब इंटरप्ट के साथ सीधे इंटरैक्ट करते हैं (जो कि स्वभाव से प्रीमैच्योर हैं) या जब या तो किसी को एक संरक्षित संसाधन रखने की आवश्यकता होती है "उपज" कॉल के बीच बिताए जाने वाले समय से अधिक, या एक कॉल के आसपास एक संरक्षित संसाधन रखना चाहता है जो "उपज" (उदाहरण के लिए "फ़ाइल में डेटा लिखें") हो सकता है। कुछ अवसरों पर जब एक "राइट डेटा" कॉल के भीतर एक समस्या होती है, तो मुझे समस्या होती है ...
सुपरकैट

... यह जांचने का एक तरीका कि कितना डेटा तुरंत लिखा जा सकता है, और एक विधि (जिससे उपज की संभावना हो सकती है) यह सुनिश्चित करने के लिए कि कुछ मात्रा उपलब्ध है (गंदे फ़्लैश ब्लॉकों के पुनर्ग्रहण को तेज करने और उपयुक्त संख्या प्राप्त होने तक प्रतीक्षा करने के लिए) ।
सुपरकैट

हाय ओलिन, मुझे तुम्हारा जवाब बहुत पसंद है। इसकी जानकारी मेरे सवालों से बहुत परे है। इसमें बहुत सारे व्यावहारिक अनुभव शामिल हैं।
हेलंग

1

संपादित करें: (मैं अपनी पिछली पोस्ट को नीचे छोड़ दूंगा; हो सकता है कि यह किसी दिन किसी की मदद करे।)

मल्टीटास्किंग ओएस किसी भी तरह के और इंटरप्ट सर्विस सर्विस रूट नहीं हैं - या नहीं होना चाहिए - प्रतिस्पर्धा प्रणाली आर्किटेक्चर। वे सिस्टम के विभिन्न स्तरों पर विभिन्न नौकरियों के लिए हैं। तत्काल कोड अनुक्रम के लिए रुकावट वास्तव में अभिप्रेत होती है जैसे किसी उपकरण को फिर से शुरू करना, संभवत: गैर-बाधित उपकरणों को मतदान करना, सॉफ्टवेयर में टाइमकीपिंग इत्यादि। तत्काल जरूरतें पूरी हुई हैं। यदि आपको केवल एक टाइमर को पुनरारंभ करने और एक एलईडी या पल्स दूसरे डिवाइस को टॉगल करने की आवश्यकता है, तो आईएसआर आमतौर पर अग्रभूमि में यह सब सुरक्षित रूप से कर सकता है। अन्यथा इसे पृष्ठभूमि को सूचित करने की जरूरत है (एक झंडा सेट करके, या एक संदेश को पंक्तिबद्ध करके) कि कुछ करने की ज़रूरत है, और प्रोसेसर को छोड़ दें।

मैंने बहुत ही सरल प्रोग्राम संरचनाएं देखी हैं, जिनकी पृष्ठभूमि लूप सिर्फ एक बेकार लूप है for(;;){ ; }:। सभी काम टाइमर ISR में किया गया था। यह तब काम कर सकता है जब कार्यक्रम को कुछ निरंतर ऑपरेशन को दोहराने की आवश्यकता होती है जो टाइमर अवधि से कम समय में समाप्त होने की गारंटी होती है; कुछ सीमित प्रकार के सिग्नल प्रोसेसिंग दिमाग में आते हैं।

व्यक्तिगत रूप से, मैं ISRs लिखता हूं जो एक आउट को साफ करते हैं, और पृष्ठभूमि को किसी और चीज पर ले जाने की ज़रूरत होती है, भले ही वह एक गुणा के रूप में सरल हो और एक टाइमर अवधि के एक अंश में किया जा सकता है। क्यों? किसी दिन, मुझे अपने कार्यक्रम में एक और "सरल" फ़ंक्शन जोड़ने के लिए उज्ज्वल विचार मिलेगा, और "बिल्ली, इसे करने के लिए बस एक छोटा आईएसआर लेगा" और अचानक मेरी पहले की सरल वास्तुकला में कुछ इंटरैक्शन बढ़ जाते हैं, जिनकी मैंने योजना नहीं बनाई थी। असंगत रूप से और घटित होता है। वे डिबग करने के लिए ज्यादा मजेदार नहीं हैं।


(दो प्रकार के मल्टी-टास्किंग के पहले की तुलनात्मक पोस्ट)

कार्य स्विचिंग: पूर्व-खाली एमटी आपके लिए कार्य स्विचिंग का ध्यान रखता है जिसमें यह सुनिश्चित करना भी शामिल है कि कोई थ्रेड सीपीयू-भूखा न हो जाए, और उच्च प्राथमिकता वाले थ्रेड्स तैयार होते ही चलने लगते हैं। सहकारी एमटी को प्रोग्रामर को यह सुनिश्चित करने की आवश्यकता होती है कि कोई थ्रेड प्रोसेसर को एक समय में बहुत लंबे समय तक न रखे। आपको यह भी तय करना होगा कि कितना लंबा है। इसका मतलब यह भी है कि जब भी आप कोड को संशोधित करते हैं, तो आपको इस बात से अवगत होना होगा कि क्या कोई कोड खंड अब उस समय-मात्रा से अधिक है।

गैर-परमाणु संचालन की रक्षा: एक पीएमटी के साथ, आपको यह सुनिश्चित करना होगा कि ऑपरेशन के बीच में थ्रेड स्वैप न हो जो कि विभाजित नहीं होना चाहिए। कुछ डिवाइस-रजिस्टर जोड़े को पढ़ना / लिखना जो एक विशेष क्रम में या उदाहरण के लिए, अधिकतम समय के भीतर संभाला जाना चाहिए। CMT के साथ यह बहुत आसान है - बस इस तरह के ऑपरेशन के बीच में प्रोसेसर का उत्पादन न करें।

डिबगिंग: आम तौर पर सीएमटी के साथ आसान होता है, जब से आप योजना बनाते हैं कि थ्रेड स्विच कब / कहाँ होगा। पीएमटी के साथ गैर थ्रेड-सुरक्षित संचालन से संबंधित थ्रेड्स और बग्स के बीच दौड़ की स्थिति विशेष रूप से डिबग करने के लिए कठिन है क्योंकि थ्रेड परिवर्तन संभाव्य हैं, इसलिए दोहराए जाने योग्य नहीं हैं।

कोड को समझना: पीएमटी के लिए लिखे गए थ्रेड्स बहुत अधिक लिखे गए हैं जैसे कि वे अकेले खड़े हो सकते हैं। एक CMT के लिए लिखे गए थ्रेड्स को सेगमेंट के रूप में लिखा जाता है और आपके द्वारा चुने गए प्रोग्राम संरचना के आधार पर, पाठक के लिए अनुसरण करना कठिन हो सकता है।

गैर-थ्रेड-सुरक्षित लाइब्रेरी कोड का उपयोग करना: आपको यह सत्यापित करना होगा कि प्रत्येक लाइब्रेरी फ़ंक्शन जिसे आप पीएमटी थ्रेड-सेफ के तहत कहते हैं। प्रिंटफ () और स्कैनफ () और उनके वेरिएंट लगभग हमेशा थ्रेड-सेफ नहीं होते हैं। सीएमटी के साथ, आपको पता चल जाएगा कि जब आप विशेष रूप से प्रोसेसर का उत्पादन करते हैं, तो कोई थ्रेड परिवर्तन नहीं होगा।

एक यांत्रिक डिवाइस को नियंत्रित करने और / या बाहरी घटनाओं को ट्रैक करने के लिए एक परिमित राज्य मशीन-चालित प्रणाली अक्सर CMT के लिए अच्छे उम्मीदवार होते हैं, क्योंकि प्रत्येक घटना के बाद, मोटर शुरू करने या रोकने, झंडा सेट करने, अगला राज्य चुनने के लिए बहुत कुछ नहीं होता है। , इस प्रकार, राज्य-परिवर्तन के कार्य स्वाभाविक रूप से संक्षिप्त हैं।

इस प्रकार की प्रणालियों में एक हाइब्रिड दृष्टिकोण वास्तव में अच्छी तरह से काम कर सकता है: राज्य मशीन का प्रबंधन करने के लिए सीएमटी (और इसलिए, अधिकांश हार्डवेयर) एक धागे के रूप में चल रहा है, और किसी भी लंबे समय तक चलने वाले कम्प्यूटेशन को करने के लिए एक या दो और धागे एक राज्य द्वारा बंद कर दिए गए हैं। परिवर्तन।


आपके उत्तर के लिए धन्यवाद, JRobert। लेकिन यह मेरे सवाल के अनुरूप नहीं है। यह प्रीमेप्टिव ओएस बनाम नॉन-प्रीमेप्टिव ओएस की तुलना करता है, लेकिन यह गैर-प्रीमेप्टिव ओएस बनाम नॉन-ओएस की तुलना नहीं करता है।
हेलंग

सही - क्षमा करें। मेरे संपादन को आपके प्रश्न को बेहतर तरीके से संबोधित करना चाहिए।
JRobert
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.