이것은 다소 주제가 아닌 냄새가 나지만 다시 추적하려고합니다.
선점 형 멀티 태스킹이란 운영 체제 또는 커널이 현재 실행중인 스레드를 일시 중단하고 해당 예약 휴리스틱에 따라 다른 스레드로 전환 할 수 있음을 의미합니다. 대부분의 경우 스레드가 실행 중이면 시스템에 다른 일이 발생한다는 개념이 없으며 코드에서 의미하는 바는 커널이 스레드 중간에 스레드를 일시 중단하도록 결정할 때 신중하게 설계해야한다는 것입니다. 다단계 작동 (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);
}
}
이것은 매우 조잡하고 빠른 예입니다. 이런 식으로 코딩하지 마십시오!
이제 선점 형 멀티 태스킹 OS는 이러한 스레드 중 하나를 코드의 모든 줄 (실제로 모든 어셈블리 명령에서)에서 일시 중단하고 다른 스레드가 실행되도록 시간을 줄 수 있음을 기억하십시오.
생각 해봐 OS adc_thread()
가 실제 데이터를 쓰고 쓰기 위해 EE 주소 설정 사이 를 중단하기로 결정한 경우 어떻게 될지 상상해보십시오 .lcd_thread()
필요한 데이터를 읽기 위해 I2C 주변 장치로 돌아가고, adc_thread()
차례를 다시 실행할 때 EEPROM이 남은 상태와 같지 않습니다. 일이 전혀 잘되지 않을 것입니다. 더 나쁜 것은, 대부분의 시간에도 작동하지만 항상 그렇지는 않을 것입니다. 코드가 필요할 때 코드가 작동하지 않는 이유를 알아 내려고 미치게 될 것입니다!
가장 좋은 예입니다. OS가의 컨텍스트 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 동안 기다린 다음 다른 스레드가 실행될 기회를 제공합니다. 그 동안 2 바이트가 UART에 들어 오면sleep()
가상 UART에 CPU가 다른 작업을 수행하는 동안 문자를 저장할 FIFO가 없기 때문에이를 보지 못할 수 있습니다.
이 매우 열악한 예제를 구현하는 올바른 방법 yield_cpu()
은 바쁜 루프가있는 곳에 두는 것 입니다. 이렇게하면 문제가 해결되지만 다른 문제가 발생할 수 있습니다. 예를 들어 타이밍이 중요하고 예상보다 오래 걸리는 다른 스레드에 CPU를 양도하면 타이밍이 중단 될 수 있습니다. 선점 형 멀티 태스킹 OS는 모든 스레드가 올바르게 예약되도록 스레드를 강제로 일시 중단하므로이 문제가 발생하지 않습니다.
이제 이것이 타이머 및 백그라운드 루프와 어떤 관련이 있습니까? 타이머 및 백그라운드 루프는 위의 협동 멀티 태스킹 예제와 매우 유사합니다.
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()
30ms 가 걸리면 어떻게됩니까 ? 캐릭터를 확인하고 반향 할 수있는 세 가지 기회를 놓치게됩니다.
루프 + 타이머 구현은 종종 공동 작업 멀티 태스킹 마이크로 커널보다 구현하기가 훨씬 간단합니다. 코드를 현재 작업에보다 구체적으로 설계 할 수 있기 때문입니다. 각 서브 시스템이 특정 작업을 매우 구체적이고 예측 가능한 방식으로 수행 할 시간을주는 고정 시스템을 설계하는 것만 큼 멀티 태스킹은 아닙니다. 협력 적으로 멀티 태스킹 된 시스템조차도 각 스레드에 대해 일반적인 작업 구조를 가져야하며 다음 스레드 실행은 상당히 복잡한 스케줄링 기능에 의해 결정됩니다.
세 시스템 모두에 대한 잠금 메커니즘은 동일하지만 각 시스템에 필요한 오버 헤드는 상당히 다릅니다.
개인적으로 저는 거의 항상이 마지막 표준 인 루프 + 타이머 구현으로 코딩합니다. 스레딩은 매우 드물게 사용해야하는 것으로 나타났습니다. 쓰기 및 디버그가 더 복잡 할뿐만 아니라 더 많은 오버 헤드가 필요합니다 (선점 형 멀티 태스킹 마이크로 커널은 항상 어리석게 간단한 타이머 및 메인 루프 이벤트 추종자보다 클 것입니다).
스레드 작업을하는 사람이라면 누구나 알게 될 것입니다.
if you have a problem and use threads to solve it, yoeu ndup man with y pemro.bls
:-)