Синхронизация работы зависимых блоков ведется через очереди сообщений. Так, по крайней мере, решается в PSOS. В очередях типично присутствуют счетчики свободных пакетов. Для самой верхней задачи это выходная очередь - набиваем пока есть свободные, далее ждем пока разгребут. Ниже по течению расположенная задача ждет во входной очереди (это та самая которая для верхней задачи выходная) пакет, как только он появился и он представляет собой полный запрос, то она начинает обработку этого запроса и не выставляет никаких признаков занятости, а просто занимается своим делом. Выше расположенная задача может даже в это время передать в очередь еще какие-то запросы, но они будут вынуты из очереди после того как ниже расположенная обработает уже поспутившие данные. Запуск нижней задачи выполняется автоматически самой механикой очередей (в структурках описания очереди указываются адреса куда передать управление при появлении заполненного пакета).
Таким образом, если задатчик скорости наполнения самой верхней очереди (более близкой к пользователю) не сильно быстрый, то нижние слои будут успевать подхватывать из очереди валящиеся данные и обрабатывать, и очередь не переполнится.
Очередь в обратную сторону работает примерно так же. То есть, фактически у вас будет не
Задача П
Задача М
а
Задача П: прием
Задача П: передача
Задача М: прием
Задача М: передача
Как-то так.