Читаем Real-Time Interrupt-driven Concurrency полностью

Теперь давайте рассмотрим непосредственно критическую секцию. В этом примере мы должны увеличить динамический приоритет минимум до 2, чтобы избежать гонки данных. В архитектуре Cortex-M динамический приоритет можно изменить записью в регистр BASEPRI.

Семантика регистра BASEPRI такова:

   • Запись 0 в BASEPRI отключает его функциональность.

   • Запись ненулевого значения в BASEPRI изменяет уровень приоритета, требуемого для вытеснения прерывания. Однако, это имеет эффект, только когда записываемое значение меньше, чем уровень приоритета текущего контекста выполнения, но обращаем внимание, что более низкий уровень аппаратного приоритета означает более высокий логический приоритет

Таким образом, динамический приоритет в любой момент времени может быть рассчитан как

#![allow(unused)]

fn main() {

dynamic_priority = max(hw2logical(BASEPRI), hw2logical(static_priority))

}

Где static_priority - приоритет, запрограммированный в NVIC для текущего прерывания, или логический 0, когда текущий контекств - это idle.

В этом конкретном примере мы можем реализовать критическую секцию так:

ПРИМЕЧАНИЕ: это упрощенная реализация

#![allow(unused)]

fn main() {

impl rtic::Mutex for resources::x {

type T = u64;

fn lock(&mut self, f: F) -> R

where

F: FnOnce(&mut u64) -> R,

{

unsafe {

// начать критическую секцию: увеличить динамический приоритет до `2`

asm!("msr BASEPRI, 192" : : : "memory" : "volatile");

// запустить пользовательский код в критической секции

let r = f(&mut x);

// окончить критическую секцию: восстановить динамический приоритет до статического значения (`1`)

asm!("msr BASEPRI, 0" : : : "memory" : "volatile");

r

}

}

}

}

В данном случае важно указать "memory" в блоке asm!. Это не даст компилятору менять местами операции вокруг него. Это важно, поскольку доступ к переменной x вне критической секции привело бы к гонке данных.

Важно отметить, что сигнатура метода lock препятствет его вложенным вызовам. Это необходимо для безопасности памяти, так как вложенные вызовы привели бы к созданию множественных уникальных ссылок (&mut-) на x, ломая правила заимствования Rust. Смотреть ниже:

#![allow(unused)]

fn main() {

#[interrupt(binds = UART0, priority = 1, resources = [x])]

fn foo(c: foo::Context) {

// resource proxy

let mut res: resources::x = c.resources.x;

res.lock(|x: &mut u64| {

res.lock(|alias: &mut u64| {

//~^ ошибка: `res` уже был заимствован уникально (`&mut-`)

// ..

});

});

}

}

<p id="Вложенность"><strong><a l:href="#Вложенность">Вложенность</a></strong></p>

Вложенные вызовы lock на том же ресурсе должны отклоняться компилятором для безопасности памяти, однако вложенные вызовы lock на разных ресурсах - нормальная операция. В этом случае мы хотим убедиться, что вложенные критические секции никогда не приведут к понижению динамического приоритета, так как это плохо, и мы хотим оптимизировать несколько записей в регистр BASEPRI и compiler fences. Чтобы справиться с этим, мы проследим динамический приоритет задачи, с помощью стековой переменной и используем ее, чтобы решить, записывать BASEPRI или нет. На практике, стековая переменная будет соптимизирована компилятором, но все еще будет предоставлять информацию компилятору.

Рассмотрим такую программу:

#![allow(unused)]

fn main() {

#[rtic::app(device = ..)]

mod app {

struct Resources {

#[init(0)]

x: u64,

#[init(0)]

y: u64,

}

#[init]

fn init() {

rtic::pend(Interrupt::UART0);

}

#[interrupt(binds = UART0, priority = 1, resources = [x, y])]

fn foo(c: foo::Context) {

let mut x = c.resources.x;

let mut y = c.resources.y;

y.lock(|y| {

*y += 1;

*x.lock(|x| {

x += 1;

});

*y += 1;

});

// середина

x.lock(|x| {

*x += 1;

y.lock(|y| {

*y += 1;

});

*x += 1;

})

}

#[interrupt(binds = UART1, priority = 2, resources = [x])]

Перейти на страницу:

Похожие книги

Компьютерные сети. 6-е изд.
Компьютерные сети. 6-е изд.

Перед вами шестое издание самой авторитетной книги по современным сетевым технологиям, написанное признанным экспертом Эндрю Таненбаумом в соавторстве со специалистом компании Google Дэвидом Уэзероллом и профессором Чикагского университета Ником Фимстером. Первая версия этого классического труда появилась на свет в далеком 1980 году, и с тех пор каждое издание книги неизменно становилось бестселлером. В книге последовательно изложены основные концепции, определяющие современное состояние компьютерных сетей и тенденции их развития. Авторы подробно объясняют устройство и принципы работы аппаратного и программного обеспечения, рассматривают все аспекты и уровни организации сетей — от физического до прикладного. Изложение теоретических принципов дополняется яркими, показательными примерами функционирования интернета и компьютерных сетей различного типа. Большое внимание уделяется сетевой безопасности. Шестое издание полностью переработано с учетом изменений, произошедших в сфере сетевых технологий за последние годы, и, в частности, освещает такие технологии, как DOCSIS, 4G и 5G, беспроводные сети стандарта 802.11ax, 100-гигабитные сети Ethernet, интернет вещей, современные транспортные протоколы CUBIC TCP, QUIC и BBR, программно-конфигурируемые сети и многое другое.

Дэвид Уэзеролл , Ник Фимстер , Эндрю Таненбаум

Учебные пособия, самоучители