ประโยชน์ของระบบปฏิบัติการที่ไม่ได้รับการยกเว้นคืออะไร และราคาสำหรับผลประโยชน์เหล่านี้


14

สำหรับ MCU โลหะที่แยกเขี้ยวการเปรียบเทียบกับโค้ดโฮมเมดที่มีพื้นหลังวนซ้ำพร้อมกับสถาปัตยกรรมขัดจังหวะตัวจับเวลาประโยชน์ของระบบปฏิบัติการที่ไม่ได้รับการยกเว้นคืออะไร ประโยชน์อะไรบ้างที่น่าดึงดูดพอสำหรับโครงการที่จะนำระบบปฏิบัติการที่ไม่ต้องเสียสละมาใช้แทนที่จะใช้โค้ดโฮมเมดที่มีสถาปัตยกรรมวนรอบด้านหลัง
.

คำอธิบายของคำถาม:

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

สิ่งที่ฉันพยายามทำคือเข้าใจวิธีเลือก RTOS ที่เหมาะสมที่สุดสำหรับโครงการโดยทั่วไป
เพื่อให้บรรลุสิ่งนี้ความเข้าใจที่ดีขึ้นเกี่ยวกับแนวคิดพื้นฐานและประโยชน์ที่น่าสนใจที่สุดจาก RTOS ประเภทต่างๆและราคาที่สอดคล้องกันจะช่วยได้เนื่องจากไม่มี RTOS ที่ดีที่สุดสำหรับการใช้งานทั้งหมด
ฉันอ่านหนังสือเกี่ยวกับระบบปฏิบัติการเมื่อไม่กี่ปีที่ผ่านมา แต่ฉันไม่ได้มีมันกับฉันอีกต่อไป ฉันค้นหาบนอินเทอร์เน็ตก่อนที่ผมจะโพสต์คำถามของฉันที่นี่และพบว่าข้อมูลนี้จะเป็นประโยชน์มากที่สุด: http://www.ustudy.in/node/5456
มีข้อมูลที่เป็นประโยชน์อื่น ๆ อีกมากมายเช่นการแนะนำตัวในเว็บไซต์ของ RTOS ที่แตกต่างกันบทความเปรียบเทียบการกำหนดเวลาแบบจองล่วงหน้าและกำหนดการที่ไม่ต้องเสียภาษีเป็นต้น
แต่ฉันไม่พบหัวข้อใด ๆ ที่กล่าวถึงเมื่อเลือก RTOS ที่ไม่ต้องห้ามและเมื่อใดควรเขียนโค้ดของคุณเองโดยใช้ตัวจับเวลาอินเตอร์รัปต์และลูปพื้นหลัง
ฉันมีคำตอบบางอย่างของตัวเอง แต่ฉันก็ไม่พอใจกับพวกเขามากพอ
ฉันอยากจะรู้คำตอบหรือมุมมองจากคนที่มีประสบการณ์มากขึ้นโดยเฉพาะในอุตสาหกรรม

ความเข้าใจของฉันคือ:
ไม่ว่าจะใช้หรือไม่ใช้ระบบปฏิบัติการ, รหัสการตั้งเวลาบางประเภทเป็นสิ่งที่จำเป็นเสมอแม้ว่าจะอยู่ในรูปแบบของรหัสเช่น:

    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 งานฉันคิดว่ามันไม่ยุ่งที่จะเขียนโค้ดของคุณเองโดยใช้ตัวจับเวลาขัดจังหวะและวนรอบพื้นหลัง

ระบบปฏิบัติการที่ไม่ต้องเสียค่าใช้จ่ายที่นี่จะอ้างถึงระบบปฏิบัติการเชิงพาณิชย์ / ฟรี / ระบบเดิมที่มีตัวกำหนดตารางเวลาที่ไม่ต้องเสียค่าธรรมเนียม
เมื่อฉันโพสต์คำถามนี้ฉันคิดว่าระบบปฏิบัติการบางอย่างเช่น:
(1) KISS Kernel (RTOS ขนาดเล็กที่ไม่ได้รับการยกเว้น - อ้างสิทธิ์โดยเว็บไซต์)
http://www.frontiernet.net/~rhode/kisskern.html
(2) uSmartX (RTOS ที่มีน้ำหนักเบา - อ้างสิทธิ์โดยเว็บไซต์)
(3) FreeRTOS (เป็น RTOS ที่ยึดเอาเสียก่อน แต่อย่างที่ฉันเข้าใจมันสามารถกำหนดค่าเป็น RTOS ที่ไม่ต้อง
เสียภาษีได้)
( 4) uC / OS (คล้ายกับ FreeRTOS) (5 ) รหัส OS / ตัวกำหนดตารางเวลาดั้งเดิมในบาง บริษัท (โดยปกติแล้วจะจัดทำและดูแลโดย บริษัท ภายใน)
(ไม่สามารถเพิ่มลิงก์เพิ่มเติมได้เนื่องจากข้อ จำกัด จากบัญชี StackOverflow ใหม่)

ดังที่ฉันเข้าใจระบบปฏิบัติการที่ไม่ต้องเสียสละคือชุดของรหัสเหล่านี้:
(1) ตัวจัดตารางเวลาโดยใช้กลยุทธ์ที่ไม่ต้องเสียภาษี
(2) สิ่งอำนวยความสะดวกสำหรับการสื่อสารระหว่างงาน, mutex, การซิงโครไนซ์และการควบคุมเวลา
(3) การจัดการหน่วยความจำ
(4) สิ่งอำนวยความสะดวกอื่น ๆ ที่เป็นประโยชน์ / ห้องสมุดเช่นไฟล์ระบบเครือข่าย stack, GUI และ ฯลฯ (FreeRTOS และ UC / OS ให้เหล่านี้ แต่ผมไม่แน่ใจว่าถ้าพวกเขายังคงทำงานเมื่อกำหนดการการกำหนดค่าเป็นไม่ใช่มาตรการ)
บางส่วนของ พวกเขาไม่ได้อยู่ที่นั่นเสมอ แต่ต้องใช้ตัวกำหนดตารางเวลา


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

คำตอบ:


13

เรื่องนี้มีกลิ่นค่อนข้างนอก แต่ฉันจะพยายามนำมันกลับมาในการติดตาม

การทำงานมัลติทาสกิ้งแบบ pre-emptive หมายความว่าระบบปฏิบัติการหรือเคอร์เนลสามารถระงับเธรดที่กำลังทำงานอยู่ในปัจจุบันและเปลี่ยนไปใช้เธรดอื่นตามการกำหนดการวิเคราะห์พฤติกรรมที่มีอยู่ เวลาส่วนใหญ่ที่เธรดที่ทำงานไม่มีแนวคิดว่ามีสิ่งอื่นเกิดขึ้นในระบบและสิ่งนี้หมายความว่าสำหรับรหัสของคุณคือคุณจะต้องระมัดระวังในการออกแบบเพื่อที่ว่าหากเคอร์เนลตัดสินใจหยุดเธรดที่อยู่ตรงกลาง การดำเนินการหลายขั้นตอน (เช่นการเปลี่ยนเอาต์พุต PWM การเลือกช่องทาง ADC ใหม่สถานะการอ่านจากอุปกรณ์ต่อพ่วง I2C ฯลฯ ) และปล่อยให้เธรดอื่นทำงานชั่วขณะหนึ่งซึ่งเธรดทั้งสองนี้จะไม่รบกวนซึ่งกันและกัน

ตัวอย่างโดยพลการ: สมมติว่าคุณใหม่สำหรับระบบฝังตัวแบบมัลติเธรดและคุณมีระบบเล็กน้อยที่มี I2C ADC, SPI LCD และ I2C EEPROM คุณตัดสินใจว่าจะเป็นการดีที่จะมีสองเธรด: หนึ่งซึ่งอ่านจาก ADC และเขียนตัวอย่างไปยัง EEPROM และอีกอันที่อ่าน 10 ตัวอย่างสุดท้ายเฉลี่ยพวกมันและแสดงบน SPI LCD การออกแบบที่ไม่มีประสบการณ์จะมีลักษณะเช่นนี้ (เรียบง่ายอย่างไม่มีการลด):

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);
    }
}

นี่เป็นตัวอย่างที่หยาบและรวดเร็ว อย่ารหัสเช่นนี้!

ตอนนี้จำไว้ว่าระบบปฏิบัติการมัลติทาสกิ้งแบบ pre-emptive สามารถระงับเธรดเหล่านี้ที่บรรทัดใดก็ได้ในรหัส

ลองคิดดู ลองนึกภาพว่าจะเกิดอะไรขึ้นหากระบบปฏิบัติการตัดสินใจหยุดชั่วคราวadc_thread()ระหว่างการตั้งค่าที่อยู่ EE เพื่อเขียนและเขียนข้อมูลจริง lcd_thread()จะวิ่งมั่วไปรอบ ๆ พร้อมกับอุปกรณ์ต่อพ่วง I2C เพื่ออ่านข้อมูลที่ต้องการและเมื่อadc_thread()ถึงคราวที่จะทำงานอีกครั้ง EEPROM จะไม่อยู่ในสภาพเดียวกับที่มันถูกทิ้งไว้ สิ่งต่าง ๆ จะไม่ทำงานได้ดีเลย ที่แย่กว่านั้นคือมันอาจใช้งานได้เกือบตลอดเวลา แต่ไม่ใช่ตลอดเวลาและคุณจะต้องพยายามที่จะคิดออกว่าทำไมโค้ดของคุณถึงไม่ทำงานเมื่อมันดูน่าจะดีกว่า!

นั่นเป็นตัวอย่างที่ดีที่สุด ระบบปฏิบัติการอาจตัดสินใจ pre-empt i2c_write()จากadc_thread()บริบทของและเริ่มทำงานอีกครั้งจากlcd_thread()บริบทของ! สิ่งต่าง ๆ อาจจะเลอะเทอะเร็วจริงๆ

เมื่อคุณเขียนโค้ดเพื่อทำงานในสภาพแวดล้อมมัลติทาสกิ้งแบบ pre-emptive คุณต้องใช้กลไกการล็อคเพื่อให้แน่ใจว่าหากรหัสของคุณถูกระงับในเวลาที่ไม่เหมาะสมซึ่งนรกทั้งหมดไม่แตกสลาย

ในทางกลับกันการทำงานมัลติทาสก์แบบร่วมมือหมายความว่าแต่ละเธรดอยู่ในการควบคุมเวลาที่มันยอมแพ้เวลาในการปฏิบัติ การเข้ารหัสนั้นง่ายกว่า แต่ต้องออกแบบอย่างระมัดระวังเพื่อให้แน่ใจว่าเธรดทั้งหมดมีเวลาเพียงพอในการรัน อีกตัวอย่างที่วางแผนไว้:

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 เพิ่มขึ้น อีกครั้งลองดูสักครู่

echo_thread()รอให้ไบต์ปรากฏที่ UART จากนั้นรับมาและรอจนกว่าจะมีพื้นที่ว่างในการเขียนจากนั้นจึงเขียนมัน หลังจากนั้นเสร็จก็เปิดเธรดอื่นให้ทำงาน seconds_counter()จะเพิ่มจำนวนการนับรอ 1,000ms จากนั้นให้โอกาสหัวข้ออื่นที่จะเรียกใช้ หากสองไบต์มาที่ UART ในขณะที่sleep()เกิดเหตุการณ์เช่นนี้คุณอาจพลาดที่จะเห็นพวกเขาเพราะ UART สมมุติของเราไม่มี FIFO ในการจัดเก็บอักขระขณะที่ CPU กำลังยุ่งอยู่กับการทำสิ่งอื่น

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

ทีนี้สิ่งนี้เกี่ยวข้องกับตัวจับเวลาและลูปแบ็คกราวนด์เหรอ? ตัวจับเวลาและลูปพื้นหลังนั้นคล้ายกับตัวอย่างมัลติทาสกิ้งแบบร่วมมือกันด้านบน:

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;
        }
    };
}

มันดูใกล้เคียงกับตัวอย่างของเธรดสหกรณ์ คุณมีตัวจับเวลาที่ตั้งค่าเหตุการณ์และวนรอบหลักที่มองหาพวกเขาและดำเนินการกับพวกเขาในแบบอะตอมมิก คุณไม่ต้องกังวลกับ "เธรด" ของ ADC และ LCD ที่รบกวนซึ่งกันและกันเพราะสิ่งหนึ่งจะไม่รบกวนซึ่งกันและกัน คุณยังต้องกังวลเกี่ยวกับ "ด้าย" ใช้เวลานานเกินไป เช่นจะเกิดอะไรขึ้นถ้าget_adc_data()ใช้เวลา 30 มิลลิวินาที? คุณจะพลาดสามโอกาสในการตรวจสอบตัวละครและสะท้อนออกมา

การใช้ loop + timer นั้นบ่อยครั้งกว่าจะง่ายกว่าการใช้ microkernel แบบมัลติทาสก์แบบร่วมมือกันเนื่องจากรหัสของคุณสามารถออกแบบให้เฉพาะกับงานในมือได้มากขึ้น คุณไม่ได้ทำงานหลายอย่างพร้อมกันมากเท่ากับการออกแบบระบบคงที่ซึ่งคุณให้เวลาแต่ละระบบย่อยในการทำงานในลักษณะที่เฉพาะเจาะจงและคาดเดาได้ แม้แต่ระบบมัลติทาสกิ้งแบบร่วมมือกันต้องมีโครงสร้างงานทั่วไปสำหรับแต่ละเธรดและเธรดถัดไปที่เรียกใช้จะถูกกำหนดโดยฟังก์ชันการกำหนดเวลาที่อาจซับซ้อนมากขึ้น

กลไกการล็อคสำหรับทั้งสามระบบเหมือนกัน แต่ค่าใช้จ่ายที่จำเป็นสำหรับแต่ละระบบนั้นแตกต่างกันมาก

โดยส่วนตัวแล้วฉันมักจะเขียนรหัสให้กับมาตรฐานล่าสุดนี้การใช้การวนซ้ำ + ตัวจับเวลา ฉันคิดว่าการทำเกลียวเป็นสิ่งที่ควรใช้อย่าง จำกัด ไม่เพียง แต่มีความซับซ้อนในการเขียนและดีบักเท่านั้น แต่ยังต้องการค่าใช้จ่ายเพิ่มเติมอีกด้วย (microkernel แบบมัลติทาสก์แบบ preemptive มักจะใหญ่กว่าตัวจับเวลาแบบเรียบง่ายและตัวติดตามเหตุการณ์ลูปหลัก)

นอกจากนี้ยังมีคำกล่าวว่าทุกคนที่ทำงานในเธรดจะได้รับการชื่นชม:

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

:-)


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

คุณช่วยงานนี้ให้มากกว่านี้หน่อยได้ไหม?
hailang

ขอบคุณครับ ฉันชอบประโยคที่คุณพูดจบ ฉันใช้เวลาสักครู่เพื่อรับรู้ :) กลับไปยังจุดคำตอบของคุณคุณมักจะใช้โค้ดในการวนลูป + ตัวจับเวลา จากนั้นในกรณีที่คุณละทิ้งการใช้งานนี้และหันไปใช้ระบบปฏิบัติการที่ไม่ได้รับการปกป้องสิ่งใดที่ทำให้คุณทำเช่นนั้น?
hailang

ฉันได้ไปกับระบบมัลติทาสก์ทั้งแบบร่วมมือและยึดเอาเสียก่อนเมื่อฉันใช้ระบบปฏิบัติการของคนอื่น ทั้ง Linux, ThreadX, ucOS-ii หรือ QNX แม้ในบางสถานการณ์ที่ผมเคยใช้ง่ายและมีประสิทธิภาพห่วงเหตุการณ์จับเวลา + ( poll()มาทันทีในใจ)
akohlsmith

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

6

การทำงานแบบมัลติทาสกิ้งนั้นเป็นสิ่งที่มีประโยชน์ในโครงการไมโครคอนโทรลเลอร์จำนวนมากแม้ว่าตัวกำหนดตารางเวลาแบบจองล่วงหน้าที่แท้จริงจะหนักเกินไปและไม่จำเป็นในกรณีส่วนใหญ่ ฉันทำโครงการไมโครคอนโทรลเลอร์ได้ดีกว่า 100 โครงการ ฉันเคยใช้การทำงานแบบมีส่วนร่วมหลายครั้ง แต่การสลับงานล่วงหน้ากับกระเป๋าที่เกี่ยวข้องนั้นยังไม่เหมาะสม

ปัญหาเกี่ยวกับ tasking ก่อนจองตามที่มอบหมายให้ tasking สหกรณ์คือ:

  1. เฮฟวี่เวทอีกมากมาย ตัวกำหนดเวลางานล่วงหน้าที่จองไว้นั้นซับซ้อนกว่าใช้พื้นที่โค้ดมากขึ้นและใช้เวลามากขึ้น พวกเขายังต้องขัดจังหวะอย่างน้อยหนึ่ง ซึ่งมักเป็นภาระที่ยอมรับไม่ได้ในแอปพลิเคชัน

  2. ต้องมี Mutexes รอบ ๆ โครงสร้างที่อาจเข้าถึงได้พร้อมกัน ในระบบความร่วมมือคุณจะไม่เรียก TASK_YIELD ในช่วงกลางของการดำเนินการปรมาณู ผลกระทบนี้จะอยู่ในคิวรัฐส่วนกลางที่ใช้ร่วมกันและมีอยู่หลายแห่ง

โดยทั่วไปแล้วการอุทิศงานให้กับงานเฉพาะนั้นสมเหตุสมผลเมื่อ CPU สามารถรองรับงานนี้และงานนั้นซับซ้อนพอกับการดำเนินการที่ขึ้นอยู่กับประวัติที่เพียงพอ โดยทั่วไปจะเป็นกรณีนี้เมื่อจัดการกระแสอินพุตการสื่อสาร สิ่งเหล่านี้มักจะถูกขับเคลื่อนอย่างหนักโดยขึ้นอยู่กับอินพุตก่อนหน้านี้บางส่วน ตัวอย่างเช่นอาจมี opcode bytes ตามด้วย data bytes ที่ไม่ซ้ำกันสำหรับแต่ละ opcode จากนั้นก็มีปัญหาเรื่องจำนวนไบต์เหล่านี้มาถึงคุณเมื่อมีอย่างอื่นที่รู้สึกอยากส่ง ด้วยภารกิจแยกต่างหากที่จัดการอินพุตสตรีมคุณสามารถทำให้มันปรากฏในโค้ดงานราวกับว่าคุณกำลังออกไปข้างนอกและรับไบต์ต่อไป

โดยรวมแล้วงานมีประโยชน์เมื่อมีบริบทของรัฐจำนวนมาก โดยทั่วไปงานจะมีสถานะของเครื่องจักรโดยที่พีซีเป็นตัวแปรสถานะ

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

บางครั้งคุณก็มีงานที่ซับซ้อนกว่านี้เล็กน้อย บ่อยครั้งที่สิ่งเหล่านี้สามารถแบ่งย่อยออกเป็นหลาย ๆ อย่างได้ คุณสามารถใช้การตั้งค่าสถานะภายในเป็นเหตุการณ์ในกรณีเหล่านั้น ฉันได้ทำสิ่งนี้หลาย ๆ ครั้งใน PICs ต่ำสุด

หากคุณมีโครงสร้างเหตุการณ์พื้นฐานดังกล่าวข้างต้น แต่ยังต้องตอบกลับสตรีมคำสั่งเหนือ UART เช่นกันมันมีประโยชน์ที่จะมีภารกิจแยกต่างหากจัดการกับสตรีม UART ที่ได้รับ ไมโครคอนโทรลเลอร์บางตัวมีทรัพยากรฮาร์ดแวร์ที่ จำกัด สำหรับการทำงานหลายอย่างเช่น PIC 16 ซึ่งไม่สามารถอ่านหรือเขียน call call ได้ ในกรณีเช่นนี้ฉันใช้สิ่งที่ฉันเรียกใช้งานหลอกสำหรับตัวประมวลผลคำสั่ง UART การวนรอบเหตุการณ์หลักยังคงจัดการทุกอย่าง แต่เหตุการณ์อย่างหนึ่งที่ต้องจัดการคือ UART ได้รับไบต์ใหม่ ในกรณีนั้นมันจะข้ามไปที่รูทีนที่รัน pseudo-task นี้ โมดูลคำสั่ง UART มีรหัสงานและที่อยู่ในการดำเนินการและค่าการลงทะเบียนบางส่วนของงานจะถูกบันทึกใน RAM ในโมดูลนั้น รหัสที่ถูกข้ามไปยังวนรอบเหตุการณ์จะบันทึกการลงทะเบียนปัจจุบันโหลดการลงทะเบียนงานที่บันทึกไว้ และข้ามไปยังที่อยู่รีสตาร์ทของงาน รหัสงานจะเรียกใช้แมโคร YIELD ที่ย้อนกลับซึ่งในที่สุดก็กระโดดกลับไปที่จุดเริ่มต้นของการวนรอบเหตุการณ์หลัก ในบางกรณีการวนซ้ำเหตุการณ์หลักจะเรียกใช้งานหลอกหนึ่งครั้งต่อรอบโดยปกติจะอยู่ที่ด้านล่างเพื่อให้เป็นเหตุการณ์ที่มีลำดับความสำคัญต่ำ

ใน PIC 18 และสูงกว่าฉันใช้ระบบ tasking แบบร่วมมือจริงเนื่องจาก call stack สามารถอ่านและเขียนได้โดยเฟิร์มแวร์ บนระบบเหล่านี้ที่อยู่รีสตาร์ทสถานะอื่น ๆ อีกสองสามและตัวชี้สแต็กข้อมูลจะถูกเก็บไว้ในบัฟเฟอร์หน่วยความจำสำหรับแต่ละงาน หากต้องการให้งานอื่นทั้งหมดทำงานครั้งเดียวภารกิจจะเรียก TASK_YIELD สิ่งนี้จะบันทึกสถานะงานปัจจุบันดูผ่านรายการสำหรับงานที่มีอยู่ถัดไปโหลดสถานะจากนั้นเรียกใช้งาน

ในสถาปัตยกรรมนี้วนเหตุการณ์หลักเป็นเพียงงานอื่นโดยมีการเรียก TASK_YIELD ที่ด้านบนของลูป

รหัสมัลติทาสกิ้งสำหรับ PIC ของฉันทั้งหมดนั้นฟรี ที่จะเห็นมันติดตั้งเครื่องมือพัฒนา PICปล่อยที่http://www.embedinc.com/pic/dload.htm ค้นหาไฟล์ที่มี "งาน" ในชื่อของพวกเขาในไดเรกทอรี SOURCE> PIC สำหรับ PIC 8 บิตและไดเรกทอรี SOURCE> DSPIC สำหรับ PIC 16 บิต


อาจจำเป็นต้องใช้ mutexes ในระบบมัลติทาสก์ที่ทำงานร่วมกันแม้ว่าจะเป็นของหายากก็ตาม ตัวอย่างทั่วไปคือ ISR ที่ต้องการเข้าถึงส่วนที่สำคัญ สิ่งนี้สามารถหลีกเลี่ยงได้ตลอดเวลาด้วยการออกแบบที่ดีขึ้นหรือเลือกที่เก็บข้อมูลที่เหมาะสมสำหรับข้อมูลสำคัญ
akohlsmith

@akoh: ใช่ฉันเคยใช้ mutex ในบางครั้งเพื่อจัดการทรัพยากรที่ใช้ร่วมกันเช่นการเข้าถึงบัส SPI ประเด็นของฉันคือการที่ mutex ไม่จำเป็นต้องมีอยู่แล้วในระบบ pre-emptive ฉันไม่ได้ตั้งใจจะพูดว่าพวกเขาไม่จำเป็นหรือไม่เคยใช้ในระบบสหกรณ์ นอกจากนี้ mutex ในระบบสหกรณ์ยังสามารถทำได้ง่ายเหมือนกับการหมุนวนใน TASK_YIELD วนรอบการตรวจสอบเพียงบิตเดียว ในระบบ pre-emptive จะต้องมีการสร้างไว้ในเคอร์เนล
Olin Lathrop

@OlinLathrop: ฉันคิดว่าข้อได้เปรียบที่สำคัญที่สุดของระบบที่ไม่ต้องเสียสละเมื่อพูดถึง mutexes ก็คือพวกมันจำเป็นต้องทำก็ต่อเมื่อมีการโต้ตอบโดยตรงกับการขัดจังหวะ เกินเวลาที่ต้องการใช้ระหว่างการโทร "รับผลตอบแทน" หรือต้องการเก็บทรัพยากรที่มีการป้องกันรอบการโทรซึ่งอาจ "ให้ผลตอบแทน" (เช่น "เขียนข้อมูลลงในไฟล์") ในบางโอกาสเมื่อมีอัตราผลตอบแทนภายใน "เขียนข้อมูล" โทรจะได้รับปัญหาเราได้รวม ...
SuperCat

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

สวัสดีแลงฉันชอบคำตอบของคุณมาก ข้อมูลมันเกินกว่าคำถามของฉัน มันมีประสบการณ์เชิงปฏิบัติมากมาย
hailang

1

แก้ไข: (ฉันจะออกจากโพสต์ก่อนหน้าของฉันด้านล่าง; บางทีมันอาจจะช่วยให้ใครซักคนในวันหนึ่ง)

OS แบบมัลติทาสกิ้งทุกประเภทและรูทีนการบริการขัดจังหวะไม่ได้ - หรือไม่ควร - สถาปัตยกรรมระบบการแข่งขัน มันมีไว้สำหรับงานที่แตกต่างกันในระดับต่าง ๆ ของระบบ อินเทอร์รัปต์มีไว้สำหรับการเรียงลำดับรหัสสั้น ๆ เพื่อจัดการกับงานบ้านในทันทีเช่นการรีสตาร์ทอุปกรณ์อาจจะทำการสำรวจอุปกรณ์ที่ไม่ขัดจังหวะการจับเวลาในซอฟต์แวร์ ฯลฯ โดยปกติจะสันนิษฐานว่าพื้นหลังจะทำการประมวลผลเพิ่มเติมที่ไม่สำคัญอีกต่อไป ตอบสนองความต้องการได้ทันที หากสิ่งที่คุณต้องทำคือรีสตาร์ทตัวจับเวลาและสลับ LED หรือพัลส์อุปกรณ์อื่น ISR มักจะทำทั้งหมดในเบื้องหน้าอย่างปลอดภัย มิฉะนั้นจะต้องแจ้งพื้นหลัง (โดยการตั้งค่าสถานะหรือจัดคิวข้อความ) ว่ามีบางสิ่งที่ต้องทำและปล่อยโปรเซสเซอร์

ฉันได้เห็นโครงสร้างโปรแกรมที่ง่ายมากที่มีพื้นหลังวนรอบเป็นห่วงที่ไม่ได้ทำงาน: for(;;){ ; }. งานทั้งหมดทำในตัวจับเวลา ISR สิ่งนี้สามารถทำงานได้เมื่อโปรแกรมจำเป็นต้องทำซ้ำการทำงานบางอย่างที่รับประกันว่าจะเสร็จในเวลาที่น้อยกว่า การประมวลผลสัญญาณบางชนิดมีอยู่ในใจ

โดยส่วนตัวแล้วฉันเขียน ISR ที่ทำความสะอาดออกไปและให้พื้นหลังทำสิ่งอื่นที่ต้องทำแม้ว่ามันจะง่ายเหมือนการคูณและเพิ่มที่สามารถทำได้ในเสี้ยววินาทีของช่วงเวลา ทำไม? สักวันหนึ่งฉันจะได้ความคิดที่ชัดเจนในการเพิ่มฟังก์ชั่น "แบบง่าย" เข้ากับโปรแกรมของฉันและ "heck มันแค่ใช้ ISR สั้น ๆ ที่จะทำ" และทันใดนั้นสถาปัตยกรรมที่เรียบง่ายของฉันก่อนหน้านี้ก็เพิ่มปฏิสัมพันธ์ที่ฉันไม่ได้วางแผน ในและเกิดขึ้นอย่างไม่สอดคล้องกัน มันไม่สนุกเลยที่จะทำการดีบั๊ก


(ก่อนหน้านี้การเปรียบเทียบการทำงานแบบมัลติทาสกิ้งสองประเภท)

การสลับงาน: MT แบบจองล่วงหน้าจะดูแลการสลับงานให้กับคุณรวมถึงการทำให้แน่ใจว่าไม่มีเธรดที่ได้รับ CPU ที่หิวโหยและเธรดลำดับความสำคัญสูงจะสามารถทำงานได้ทันทีที่พร้อม Cooperative MT ต้องการโปรแกรมเมอร์เพื่อให้แน่ใจว่าไม่มีเธรดที่เก็บตัวประมวลผลนานเกินไป คุณจะต้องตัดสินใจด้วยว่านานเกินไป ซึ่งหมายความว่าหมายความว่าเมื่อใดก็ตามที่คุณแก้ไขโค้ดคุณจะต้องระวังว่าตอนนี้ส่วนใดของรหัสเกินเวลาควอนตัมนั้นหรือไม่

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

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

ทำความเข้าใจเกี่ยวกับรหัส: หัวข้อที่เขียนสำหรับ PMT นั้นค่อนข้างเขียนราวกับว่าพวกเขาสามารถยืนอยู่คนเดียว หัวข้อที่เขียนสำหรับ CMT จะถูกเขียนเป็นส่วนและขึ้นอยู่กับโครงสร้างของโปรแกรมที่คุณเลือกอาจจะยากสำหรับผู้อ่านที่จะติดตาม

การใช้รหัสไลบรารีที่ไม่ปลอดภัย: คุณจะต้องตรวจสอบว่าแต่ละฟังก์ชันห้องสมุดที่คุณโทรภายใต้ PMT ปลอดภัยสำหรับเธรด printf () และ scanf () และตัวแปรส่วนใหญ่มักจะไม่ปลอดภัยต่อเธรด ด้วย CMT คุณจะรู้ว่าจะไม่มีการเปลี่ยนแปลงเธรดเกิดขึ้นยกเว้นเมื่อคุณให้หน่วยประมวลผลเฉพาะ

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

วิธีไฮบริดสามารถทำงานได้ดีในระบบประเภทนี้: CMT เพื่อจัดการเครื่องสถานะ (และฮาร์ดแวร์ส่วนใหญ่) ที่ทำงานเป็นเธรดเดียวและเธรดหนึ่งหรือสองเธรดเพื่อทำการคำนวณอีกต่อไปที่เริ่มทำงานโดยรัฐ เปลี่ยนแปลง


ขอบคุณสำหรับคำตอบของคุณ JRobert แต่มันไม่เหมาะกับคำถามของฉัน มันเปรียบเทียบ OS ที่ยึดเอาไว้กับ OS ที่ไม่ยึดเอาเสียก่อน แต่จะไม่เปรียบเทียบ OS ที่ไม่ยึดเอาไว้กับที่ไม่ใช่ระบบปฏิบัติการ
hailang

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