Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Про реентерабельность
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > Cредства разработки для МК > GNU/OpenSource средства разработки
_Pasha
Только не говорите, что знали и часто пользуетесь! smile.gif

В общем, тем, кому позарез нада получить сабж, оказалось все очень просто

Код
void somefunc (volatile int param)

  volatile int local1;
  volatile char local2;
//body
local1 |= param;
return;
}


Как объявили все volatile - компилятор все, включая входные параметры, кладет в стек-фрейм.

И логика проста: без соответствующих оптимизаций функции генерятся реентерабельными. Volatile как-раз и отключает эту оптимизацию локальных переменных.

В общем, в мануале ГЦЦ настолько туманно это описано, что побудило поделиться радостной новостью.
Rst7
Вы меня, конечно, извините, но это - "ацкий боян". IAR кстати, действует аналогично.

Только одна фигня есть - неатомарный доступ к переменным длинной больше байта на AVR-архитектуре.

Ну а кроме всего прочего - это совершенно бредовый способ обеспечения повторного входа. Куда безопаснее работать с указателем на блок параметров. Или с классом в плюсах.

Кроме того, если уж так хочется извращений - покурите еще одну фичу гнуся - вычисляемый goto. Может натолкнет на какие мысли.
Сергей Борщ
Цитата(_Pasha @ Feb 16 2009, 01:51) *
Как объявили все volatile - компилятор все, включая входные параметры, кладет в стек-фрейм.
А как стековый фрейм связан с реентерабельностью?
_Pasha
Цитата(Сергей Борщ @ Feb 16 2009, 03:37) *
А как стековый фрейм связан с реентерабельностью?
Обеспечивает копию блока локальных переменных - одно из необходимых условий. 
ЗЫ: Кстати, в WinAVR красивый код коррекции стека
Код
in r0,SREG
cli
.... атомарные операции
out SPL,YL
out SREG,r0
out SPH,YH

Поскольку сам так никогда не делал - на первый взгляд показалось, что бага - прерывания разрешаются при восстановлении SREG до того как завершена коррекция стека. На самом деле все правильно, т.к. следующая после out SREG,r0 команда гарантированно выполнится и прерывание наступит только после out SPH,YH. Таким образом, прерывания разрешаются максимально раньше
aesok
Цитата(_Pasha @ Feb 16 2009, 10:10) *
Обеспечивает копию блока локальных переменных - одно из необходимых условий. 


Вот условия реентерабельности:

"Таким образом, функция, которая зависит только от своих параметров, не использует глобальные и статические переменные и вызывает только реентерабельные функции, будет реентерабельной."

Нет никакой разницы где храняться параметры и локальные переменные, в стеке или в регистрах.

Анатолий.

Цитата(_Pasha @ Feb 16 2009, 10:10) *
ЗЫ: Кстати, в WinAVR красивый код коррекции стека
Код
in r0,SREG
cli
.... атомарные операции
out SPL,YL
out SREG,r0
out SPH,YH


PS: Немного не в тему, в xmega, запись в регистр стека будет выглядеть намного короче;
Код
out SPL,YL
out SPH,YH


В Атмеле догадались сделать это:
Цитата
To prevent corruption when updating the Stack Pointer from software, a write to SPL will automatically
disable interrupts for up to 4 instructions or until the next I/O memory write.
Rst7
Цитата
Вот условия реентерабельности:


Тут _Pasha имеет в виду не классическое понятие реетерабельности как работу функции в многопоточном приложении, а как некий костыль, позволяющий организовать многопоточность с минимумом затрат памяти. Т.е. выход из процедуры в произвольном месте через longjmp в прерывании и повторный заход в процедуру приведет к продолжению работы в прерванном месте. Конечно, это все некислый костыль, имеет массу ограничений и т.д. Но иногда можно извратиться, если уж очень надо.
_Pasha
Цитата(Rst7 @ Feb 16 2009, 03:13) *
Куда безопаснее работать с указателем на блок параметров.
Тоже был во власти этого заблуждения sad.gif


Цитата(Rst7 @ Feb 16 2009, 11:26) *
как некий костыль, позволяющий организовать многопоточность с минимумом затрат памяти.


В общем, по большому счету  я  сморозил глупость, потому что когда  блок локальных параметров не помещается в регистры, компилятор все равно кладет все в стек. И тогда уж
Цитата
Нет никакой разницы где храняться параметры и локальные переменные, в стеке или в регистрах.
поскольку регистры -то все равно сохраняются/восстанавливаются. А объявления volatile в первом посте - просто проиллюстрировали концепцию размещения локальных переменных. 
Сергей Борщ
Цитата(Rst7 @ Feb 16 2009, 10:26) *
Тут _Pasha имеет в виду не классическое понятие реетерабельности
Тогда неплохо бы приводить свое определение термина, раз уж оно расходится с общепринятым.
Цитата(Rst7 @ Feb 16 2009, 10:26) *
Т.е. выход из процедуры в произвольном месте через longjmp в прерывании и повторный заход в процедуру приведет к продолжению работы в прерванном месте.
Как там было у классиков? "и из обломков мотоцикла в следующий запойный период устроил стационарный двигатель, который был очень похож на настоящий, но не работал.". Перед выходом по longjump делается сохранение точки возврата при помощи setjump, которая совершенно корректно сохраняет на стеке все необходимые регистры в которых могут находиться локальные переменные.

Цитата(_Pasha @ Feb 16 2009, 09:10) *
Обеспечивает копию блока локальных переменных - одно из необходимых условий.
??? Необходимых кому? Почему одну копию, а не две или три?
Rst7
Че то я уже не пойму. Судя по предыдущей теме, никакого сохранения и восстановления регистров не было. Посему нужен был костыль, чтобы переменные хранились не в регистрах, а в озу.

Цитата
поскольку регистры -то все равно сохраняются/восстанавливаются.


Теперь они почему-то сохраняются/восстанавливаются - тогда зачем это очень опасное извращение?
demiurg_spb
Цитата(aesok @ Feb 16 2009, 10:51) *
В Атмеле догадались сделать это:
....
Лучше поздно чем никогда. А не XMega они так не планируют доработать?
А ещё лучше бы переключаемые за 1-2 такта банки регистров забабахали штук этак 8.
ReAl
Цитата(aesok @ Feb 16 2009, 09:51) *
В Атмеле догадались сделать это:
Вот интересно, если бы они читали RU.EMBEDDED лет 10 назад, где именно об этом и говорилось при обсуждении AVR в качестве очень простой правки, улучшающей жизнь (и приводился пример - x86 и запрет прерываний на некоторое время после записи в SS, чтобы пару SS:SP атомарно обновлять), то может это и раньше появилось бы, где-то вместе с movw ?

Кстати, эти temp-регистры, которе появились при каждом 16-битном SFR - можно бы бы обойтись одним, если бы так же запрещать прерывания при обращении к слову.
А так даже персональные мало спасают - а если я и в основном коде хочу читать регистр таймера, и в прерывании - всё равно в основном коде надо запретить прерывания при чтении.
Если же запрет прерываний есть, то достаточно одного регистра temp на все 16-битные SFR, в целом меньше места заняло бы. И SPH:SPL просто обрабатывался бы за компанию, ему и не нужен temp-регистр, но проще одинаково обработать
Rst7
Лучше бы - не ядро править, а в компиляторы вменяемые способы работы с многобайтовыми переменными добавили. В смысле - чтобы оно само умело загрузку-выгрузку short/long в критическую секцию оборачивать. А то макросы-костыли класса ATOMIC_STORE_SHORT/ATOMIC_LOAD_SHORT уже достали, читабельности коду они аж никак не добавляют sad.gif
ReAl
Цитата(Rst7 @ Feb 16 2009, 12:12) *
Лучше бы - не ядро править, а в компиляторы вменяемые способы работы с многобайтовыми переменными добавили. В смысле - чтобы оно само умело загрузку-выгрузку short/long в критическую секцию оборачивать. А то макросы-костыли класса ATOMIC_STORE_SHORT/ATOMIC_LOAD_SHORT уже достали, читабельности коду они аж никак не добавляют sad.gif

Одно другому не мешает. Не поленились же ещё в 8086 запретить прерывания на одну команду после записи SS. Тут тоже можно было не кучу регистров в кристалл вставлять, а запрет прерывания при обращении к младшему байту.

Что касается атомарнсти, так dxp что-то в таком духе давным давно показывал (только там не шаблон был, а просто класс для одного конкретного типа, в те времена IAR ещё шаблоны не поддерживал).
foo.cpp:
Код
#include <stdint.h>
#include <avr/interrupt.h>

class crit_sect  {
    uint8_t _sreg;
public:
    crit_sect() : _sreg(SREG) { cli(); }
    ~crit_sect() { SREG = _sreg; }
};

// не претендуя на полноту
template<typename T> class atomic {
    volatile T t;
public:
    inline operator T () {
        T temp;
        {    crit_sect cs;
            temp = t;
        }
        return temp;
    }

    inline atomic<T>& operator = (const T ti) {
        {    crit_sect cs;
            t = ti;
        }
        return *this;
    }

    inline atomic<T>& operator++() {
        {    crit_sect cs;
            ++t;
        }
        return *this;
    }
};


atomic<uint16_t> va, vb;
uint16_t c;

void foo() {
    vb = va + c;
    ++va;
}


avr-gcc -Os -mmcu=atmega88 -S foo.cpp
foo.s:
Код
// operator++ не захотел инлайнить, счёл слишком толстой функцией, ключи крутить лень
// да и не нужно, главное принцип
_ZN6atomicIjEppEv:
    movw r30,r24
    in r18,95-0x20
    cli
    ld r24,Z
    ldd r25,Z+1
    adiw r24,1
    std Z+1,r25
    st Z,r24
    out 95-0x20,r18
    movw r24,r30
    ret
/* function atomic<T>& atomic<T>::operator++() [with T = unsigned int] size 12 (11) */


_Z3foov:
    in r24,95-0x20
    cli
    lds r18,va
    lds r19,(va)+1
    out 95-0x20,r24

    lds r24,c
    lds r25,(c)+1
    add r18,r24
    adc r19,r25

    in r24,95-0x20
    cli
    sts (vb)+1,r19
    sts vb,r18
    out 95-0x20,r24

    ldi r24,lo8(va)
    ldi r25,hi8(va)
    rcall _ZN6atomicIjEppEv
    movw r30,r24

    in r18,95-0x20
    cli
    ld r24,Z
    ldd r25,Z+1
    out 95-0x20,r18

    sbiw r24,5
    brne .L6
    sts (c)+1,__zero_reg__
    sts c,__zero_reg__
.L6:
    ret


Собственно, никто ведь не заставляет писать "если на С++, то до конца". Можно использовать только то, что генерирует код не толще тех макросов, о которіх шла речь, но удобнее в использовании.

====
мдя...

1) подсунть в качестве T структуру так просто не удаётся (в варианте с исключённым operator++, естественно)

2) ну да, ну да, в обработчике прерывания доступ тоже будет обрамляться запретом/восстановлением прерываний. Но и если компелятор будет сам уметь оборачивать обращение прямо и вражений, то ему ещё придётся объяснять, что вот тут-то оборачивать и не надо.
А так - просто добавить в шаблон

Код
public:
        T noatomic_get() { return t; }
        void noatomic_assign(T i) { t = i; }
и в обработчиках пользоваться ими.

Эх, нет в жизни совершенства! (С) Лис.
Rst7
Нет, с плюсами конечно, неплохо получается. Но хотелось бы еще и разрешение прерываний перед последним доступом. Типа минимизации времени нахождения в состоянии запрета. Без больших извращений.

А с IAR'ом отдельный разговор. Тот запросто во внутрь этой секции может какую-нибудь математику внести. Завтра покажу.
ReAl
Цитата(Rst7 @ Feb 16 2009, 23:52) *
Нет, с плюсами конечно, неплохо получается. Но хотелось бы еще и разрешение прерываний перед последним доступом. Типа минимизации времени нахождения в состоянии запрета. Без больших извращений.
"Ну, барин, тут одному не справиться" (С) "Формула любви"
Тут точно асм придётся звать на помощь. И шаблоны лесом пустить.
Огрызок в одну сторону, а то спать хочу.
Код
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

class atomic_u32
{
    uint32_t t;
public:
    inline operator uint32_t () {
        uint32_t temp;
        uint8_t sreg_sv;
        __asm__ __volatile__ (
            "in %1, %2 \n\t"
            "cli    \n\t"
            "ld %A0, %a3+    \n\t"
            "ld %B0, %a3+    \n\t"
            "ld %C0, %a3+    \n\t"
            "out %2, %1    \n\t"
            "ld %D0, %a3    \n\t"
            : "=r" (temp)
            : "r"  (sreg_sv), "I" (_SFR_IO_ADDR(SREG)),
                 "e"(&t) /* компилятор выберет Z или Y или X */
        );
        return temp;
    }

};

atomic_u32 va;
uint32_t b;

void foo() {
    b = va;
}


Код
_Z3foov:
    ldi r24,lo8(va)
    ldi r25,hi8(va)
    movw r30,r24

    in r24, 63
    cli    
    ld r24, Z+    
    ld r25, Z+    
    ld r26, Z+    
    out 63, r24    
    ld r27, Z    
    
    sts b,r24
    sts (b)+1,r25
    sts (b)+2,r26
    sts (b)+3,r27

    ret
_Pasha
Цитата(Rst7 @ Feb 16 2009, 12:26) *
Тут _Pasha имеет в виду не классическое понятие реетерабельности как работу функции в многопоточном приложении, а как некий костыль, позволяющий организовать многопоточность с минимумом затрат памяти.

Продолжим нашу шизофрению. Как выяснилось, при размещении в стеке переменных достаточный размер переключаемого контекста AVR будет 6 байт! Это даже меньше, чем я на асме делал, протупил - получалось 8 байт.

Код
typedef struct

{

 uint16_t pc,sp,framePTR;

} small_ctx_t;


Т.е. для организации точки входа сопрограммы используется косвенный goto, указатель стека текущей сопрограммы и указатель на локальные данные, который можно получить __builtin_frame_ptr(0). Ввиду того, что жесткого реалтайма не будет, на сохранение регистров в сопрограммах можно вообще не обращать внимание, в смысле - запретить. В общем, 6 байт - это намного меньше, чем в setjmp()
ReAl
Цитата(_Pasha @ Feb 17 2009, 01:30) *
Код
typedef struct
{
 uint16_t pc,sp,framePTR;
} small_ctx_t;

Т.е. для организации точки входа сопрограммы используется косвенный goto, указатель стека текущей сопрограммы и указатель на локальные данные, который можно получить __builtin_frame_ptr(0).
Что-то в этом есть. В отличие от указателем на структуру с данным процесса-сопрограммы, это даёт возможность переключаться не только в "корневой" функции, но и в любом месте по глубине вызовов. Но это уже приведёт к потере части преимущества по стеку (да, все регистры уже не сохраняются, но для каждого сопроцесса нужно всё равно резервировать свой стек достаточной глубины).
Правда, по сравнению с вытесняющмим системами, остаётся преимущество по времени синхронного переключения. А вот с асинхронным (из прерывания) - всё равно надо весь контекст сохранять, так как промежуточные вычисления на регистрах делаются, архитектура-то не стековая.

Хотя для этого органичнее стековая архитектура. О которой ругаются в теме про архитектуру контроллера.
На AVR такая модель не ложится, это на PDP-11 ещё можно было эмулировать - делать add 4(R3),8(R3) и только R3 да SP и сохранять (или вообще только SP для четырёх сопроцессов с базами в R0..R3, но они не смогли бы разделять между собой подпрограммы из-за разных баз), хотя, вероятно, выигрыш на переключении растерялся бы на проигрыше в эффектичности самих вычислений.

Что-то я недоспал и всё не соображу. И уже бегу.
Но интересно.
_Pasha
Цитата(ReAl @ Feb 17 2009, 10:50) *
возможность переключаться не только в "корневой" функции, но и в любом месте по глубине вызовов.
Если вызываемые процедуры построены также. На счет стека - се ля ви - сколько процессов, столько и стеков.sad.gif
ReAl
Цитата(_Pasha @ Feb 17 2009, 10:27) *
Если вызываемые процедуры построены также. На счет стека - се ля ви - сколько процессов, столько и стеков.sad.gif
Вот этого и не понимаю - "за что боролись".
Стеки каждому свои.
На стеке место нужно под всю глубину вызовов.
Место под регистры на стеках не нужны только если все переключения задач синхронные.
Если могут быть асинхронные переключения (из прерываний), то на стеках нужно место под полное сохранение контекста, с регистрами, так как прерывание может произойти в промежуточных операциях до записи результата в переменную на стеке.

Итого при наличии асинхронных переключений экономии памяти на стеках нет вообще.
При наличии только синхронных переключений эконоимия есть, но на не такая ощутимая. А время реакции наверное таки хуже, чем у вытесняющей ОС.
Так что получается что-то среднее между
  • вызовом функции шага каждого процесса do_XXX_step(), делающей небольшой кусок работы и сохраняющей рещультат в статичесокй структуре состояния процесса
    либо
    сопрограммными переключениями на верхнем уровне процесса, когда стек тоже в некотором смысле "очищен"
  • вытесняющей ОС

Причем у "среднего" память расходуется почти как у второго, а всё остальное - практически как у первого.

Или я чего-то не понимаю по сонности своей в последние пару недель?

---------------------------

Где-то пробегал линк на систему над автоматами, в которой (если я правилно помню, тогда пробежался по диагонали) всё происходит синхронно, при каждом автомате набор стандартных функций - "обычный" "сделать кусок", и "обработчики прерываний высокого уровня". У планировщика очередь, где стоят указатели на функции для автоматов которые надо выполнить и таймерные переменные (функция запускается кода дотикает). Кажется ещё приоритеты при этих указателях.
В прерываниях делается самое быстрое типа сохранения байта UART в памяти и в очередь помещается указатель на соответствующий "обработчик высокого уровня" с флагом "как только, так сразу".
Ну а планировщик после синхронного возврата из очередной функции пробегается по очереди и решает, по какому указателю вызвать следующую. Которая тоже может поместить в очередь нечто, например, "сигнальную" функцию от другого автомата.
Всё чинно, синхронно, расход стека минимальный, никаких практически блокировок доступа к структурам не нужно, так как всё синхронно.
Всё это сделано горстью макросов и выглядит довольно красиво и читабельно.

Не уверен, что я правильно напел Битлз по телефону, но если кто-то таке встречал - дайте линк. А то у меня потерялся.
_Pasha
В общем, пока ясно одно: нельзя смешивать синхронное и асинхронное переключения.
ReAl
Цитата(ReAl @ Feb 17 2009, 18:40) *
Не уверен, что я правильно напел Битлз по телефону, но если кто-то таке встречал - дайте линк. А то у меня потерялся.
Шорт побори, это было TinyTimber (Vit aka sensor_ua показал когда-то)
В прерываниях тоже всё работает - выходит, что обработчик прерывания сохраняет всё то, что не обязаны сохранять вызываемые подпрограммы, а планировщик в конце обработчика прерывания сохраняет через setjump остальное и по longjump переходт на другой процесс.
Когда на данный процесс переключатся опять по longjump - оно всплывёт уже непосредственно перед эпилогом обработчика прерывания с восстановленными callee-saved регистрами, эпилог восстановит caller-saved и SREG. Всё пучком.
Только в примере (по ссылке от Vit, эти исходники ненагугливаются по слову TinyTimber) сумма блока описания задачи (с буфером jumpbuf) и стека для задачи занимают где-то столько же, сколько и обычные стеки для AVR в scmRTOS, что вполне естественно.
Т.е. явных преимуществ по памяти нет, оно, как уже отмечалось, было бы при только синхонном переключении и только на верхних уровнях, в "корневых" функциях автоматов (тогда на всех один стек).

Если я правильно понял при беглом просмотре - преимущество просматривается в, фактически, диамическом создании/удалени задач, массивы описателей потоков и стеков - это массивы запущенных и выолняющегося плюс отложенных "методов", а не "процессов". Т.е. "спящий процесс" - это объект, у которого не запущен ни один "метод", т.е. никакой его код не работает, только сохраняется состояние автомата в его переменных, до которых ОС нет дела. А когда, например, "драйвер"-обработчик прерывания UART отошлёт весь пакет, тогда он из прерывания заланирует отработку соответствущего "метода" соответствующего "объекта" путём постановки "сообщения" в очередь. И он (метод) через сообщение привяжется к свободному блоку задачи/стеку, обработается через run() этого свободного "слота выполнения" (сразу или когда таковой освободится).

Очередь сообщений TinyTimber тоже где-то эквиваленна по памяти набору событий/мьютексов, будет или нет преимущство зя счёт динамического распределения - тяжело сказать. И беда может обнаружиться только во время выполнения, а в случае scmRTOS просто при компиляции не хватит памяти для всех заявленных объектов ОС.

Как-нибудь надо будет ещё покурить эту тему...
_Pasha
Еще одна ТАСКАЛКА (от слова task) получилась.
Нажмите для просмотра прикрепленного файла
Несколько слов
Код
typedef struct Tag_TCB
{
    void (*task)(struct Tag_TCB *tcb);
    void *param;
    void *entry;
    struct Tag_TCB *next;
    
    unsigned lock:1;// lock counter
    unsigned stop:1;// stop flag
    unsigned exit:1;// request to delete from list
    unsigned PID:5; // process ID
} TCB_t;

*param - для создания статических локальных переменных с учетом требований реентерабельности.
*entry - для использования переопределяемых точек входа с помощью гнусной фичи labels as values
В общем - мне пока понравилась такая мысль:
с одной стороны - через вызов system() можно отдавать управление другим задачам во время ожидания событий
с другой стороны, чтоб не мучать стек обратными вызовами, можно использовать переопределяемые точки входа и смело выходить из задачи, пока не наступит нужное нам событие.

Как Вам такой гибрид, уважаемый ReAl ?
PS к тому же system() можно спрятать внутри любой реентерабельной функции, а точки входа - низзя.
Теперь в TCB добавить поля для сообщений - и ось на подходе smile.gif
ReAl
Цитата(_Pasha @ Sep 13 2009, 10:01) *
Еще одна ТАСКАЛКА (от слова task) получилась.
Как Вам такой гибрид, уважаемый ReAl ?
PS к тому же system() можно спрятать внутри любой реентерабельной функции, а точки входа - низзя.
Да, system() некоторые вещи сделает красивее, но при этом если несколько задач из списка позовут system() (да ещё и из функции - а иначе кайфу в вызове system() никакого по сравнению с просто установокй метки и возвратом) - стек как-то глубоко пойдёт, надо помнить о возможности.
В этом смысле вместо
Код
STD_TASK(wait2)
{
    ENTRY(step1)
    {
    if(TCNT1 != 0xff00)
            system();
    }    
}
"стандартный" protothread-овский
Код
    PT_WAIT_UNTIL(pt, TCNT1 != 0xff00);
по стеку экономнее, так как ставит метку и даёт возврат из функции, а там уже просматривается список потоков.
Никто не мешает комбинировать.

И управляющую структуру я бы выделил в виде заголовка
Код
typedef struct Tag_TCB_header
{
    void (*task)(struct Tag_TCB *tcb);
    void *entry;
    struct Tag_TCB *next;
    
    unsigned lock:1;// lock counter
    unsigned stop:1;// stop flag
    unsigned exit:1;// request to delete from list
    unsigned PID:5; // process ID
   // тут может ещё сразу таймерную переменную для таймаутов/sleep()
} TCB_header_t;
а локальные параметры не через указатель, а как продолжением структуры
Код
typedef struct tag_some_task_tcb
{
    TCB_header_t th;
    unsigned some_param;
    unsigned char some_buf[8];
} some_task_tcb_t;
при обращении не будет лишнего уровня косвенности.

А вообще Vit на protothread-подобных вещах стаю собак сожрал, это с ним обсуждать надо.
_Pasha
Цитата(ReAl @ Sep 13 2009, 12:21) *
Да, system() некоторые вещи сделает красивее, но...

Никто и не заставляет всюду им пользоваться smile.gif В тяжелых случаях перед вызовом должна быть проверка стека.
Цитата
В этом смысле вместо
Код
STD_TASK(wait2)
{
    ENTRY(step1)
    {
    if(TCNT1 != 0xff00)
            system();
    }    
}
"стандартный" protothread-овский

Не-не. Если есть ENTRY(), то можно обойтись без system()

ЗЫ: Да и что толку от его тотального использования - в залоченном состоянии задача не обработает ни одного нового события.

Код
STD_TASK(wait2)
{
ALLOW_ENTRY;
.................
................
ENTRY(step1)
{
if(TCNT1 != 0xff00) return; /// просто вышли и попали опять в system()  :)
// а вот если тут был бы цикл с ожиданием - ясное дело, надо system() вызывать
................
................
}    
}


Отдельное спасибо за то, что напомнили про анонимные метки в PT_WAIT_UNTIL
Добавлю.
Но именные метки оставить надо, для возможного повторного использования точки входа.

Цитата
И управляющую структуру я бы выделил в виде заголовка
а локальные параметры не через указатель, а как продолжением структуры
при обращении не будет лишнего уровня косвенности.

Имхо, правильнее просто передавать указатель на локальные переменные из system(), вместо полного TCB. Тогда обращение к полям TCB будет доступно только через осевые сервисы.

Ага. И чтобы можно было пользоваться break /continue внутри ENTRY надо сделать
Код
#define ENTRY(label) tcb->entry = &&label; label: for(;;)
ReAl
Цитата(_Pasha @ Sep 13 2009, 14:55) *
Имхо, правильнее просто передавать указатель на локальные переменные из system(), вместо полного TCB. Тогда обращение к полям TCB будет доступно только через осевые сервисы.
Ну и что в этом хорошего? Точнее, что хорошего в том, что эти сервисы вместо коротких inline-вызовов/макросов, обращающихся к полям TCB смещением к тому же указателю, который используеся для локальных переменных потока - вместо этого будут чем-то более толстым и медленным?
_Pasha
Цитата(ReAl @ Sep 13 2009, 20:02) *
Ну и что в этом хорошего?

Ничего хорошего sad.gif Однако, чем проще TCB - тем реже он будет использоваться.
Но сливать вместе заголовок и параметры не хоцца.
Допустим, есть две пары одинаковых процессов - пишут данные в очередь и забирают . В этом случае имеем 2 функции, 2 пары заголовков в списке задач и два блока данных, описывающих очереди. Т.е. в каждой паре указатели на локальные параметры одинаковые - плодить сущности смысла нету.

Все-таки есть смысл в первоначальной структуре заголовка, т.к void* param нуждается в типизации, которую и можно сделать при входе в функцию один раз. К тому же, задачи могут вообще не иметь локальных статических параметров.

Как всегда - выбор между примерно равноправными вариантами...
ReAl
Цитата(_Pasha @ Sep 13 2009, 23:19) *
Допустим, есть две пары одинаковых процессов - пишут данные в очередь и забирают . В этом случае имеем 2 функции, 2 пары заголовков в списке задач и два блока данных, описывающих очереди. Т.е. в каждой паре указатели на локальные параметры одинаковые - плодить сущности смысла нету.
У каждой очереди есть свой персональный блок указателей/флагов - для любого построения системы.
А у двух одинаковых процессов локальные данные должны быть свои и указатели на локальные переменные не могут быть одинаковые.
Вот эти локальные переменные в TCB пусть и сидят.
А вот функция одна для обеих "писателей" и одна для обеих "читателей".

Цитата(_Pasha @ Sep 13 2009, 23:19) *
К тому же, задачи могут вообще не иметь локальных статических параметров.
В этом случае для моего варианта в структуре TCB будет одно поле - заголовок.
_Pasha
Понял, спасибо. Но не сразу smile.gif Если локальных параметров нету, то нах он нужен, этот param=NULL
_Pasha
Кстати, о protothread

parallel.h
CODE

#ifndef PARALLEL_H
#define PARALLEL_H 1
#include <stddef.h>

typedef void* thread_t;

#define THREADFUNC thread_t __attribute__((noinline))
#define THREAD(name) if(name != NULL) goto *name
static inline thread_t thread_label(void) __attribute__((always_inline));


thread_t thread_label(void)
{
__label__ lab;
return &&lab;
lab:
;
}

#endif


Это есть самый минимальный набор для построения тредов.
Это самый обрезанный и самый простой protothread-подобный в мире smile.gif
_Pasha
Решил скрестить прототредоподобную кооперативку и функцию system() или idle() или как там ея..
Я про блокировку рекурсивного вызова при использовании system() внутри треда . Получается вот что
CODE

#include <stddef.h>
#define THREAD_LOCK (NULL+1)

static void *run1(void **lc) __attribute__((noinline));
static void *run2(void **lc) __attribute__((noinline));
void system(void) __attribute__((noinline));

void *run1(void **lc)
{// min example
void *pc = *lc;
*lc = THREAD_LOCK;
if(pc != NULL) goto *pc;
return NULL;
}

void system(void)
{
static void *pc1=NULL; if(pc1 != THREAD_LOCK) pc1 = run1(&pc1);
static void *pc2=NULL; if(pc2 != THREAD_LOCK) pc2 = run2(&pc2);
}


Немного кривовато, но по-другому не получается. Пробовал по-всякому - оптимизирует, зараза, начисто.
А есть ли еще варианты блокировки без привлечения дополнительных переменных?
upd: состояние THREAD_LOCK наверное лучше обозначить как pc = <адрес функции-треда> чтобы не влазить куда не надо.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.