Bài 49.426 phút đọc
Vùng tới hạn
Sau bài này bạn sẽ làm được
- Chỉ ra dữ liệu chia sẻ nào cần bảo vệ và dữ liệu nào không
- Cài vùng tới hạn lưu và khôi phục trạng thái ngắt
- Giải thích vì sao tắt ngắt lâu phá vỡ ràng buộc thời gian thực
- Dùng bộ đệm vòng một bên ghi một bên đọc để tránh khóa
Ngắt có thể cắt ngang chương trình chính ở giữa bất kỳ lệnh nào. Nếu cả hai cùng đụng vào một dữ liệu thì có lúc dữ liệu đó ở trạng thái dở dang. Vùng tới hạn là cách nói với phần cứng đừng cắt ngang đoạn này.
#Cái gì cần bảo vệ
Vùng tới hạn
Một đoạn mã phải chạy trọn vẹn, không bị ngắt chen vào giữa. Trên Cortex-M, cách đơn giản nhất để tạo ra nó là tạm tắt ngắt.
| Dữ liệu chia sẻ | Cần bảo vệ không | Vì sao |
|---|---|---|
| Cờ uint8_t, ISR ghi 1, main ghi 0 | Không | Ghi một byte là một lệnh, không cắt được giữa chừng |
| Biến uint32_t, chỉ một bên ghi | Không | Ghi một từ 32 bit đã căn chỉnh cũng là một lệnh |
| Biến uint32_t, cả hai bên cùng ghi | Có | Phép đọc sửa ghi có ba lệnh, xem Bài 48.1 |
| Biến uint64_t, dù chỉ một bên ghi | Có | Cortex-M ghi 64 bit bằng hai lệnh, đọc ra được nửa cũ nửa mới |
| Struct nhiều trường phải nhất quán | Có | ISR có thể đọc lúc mới cập nhật được một nửa |
| Bộ đệm vòng, một bên ghi một bên đọc | Không | Thiết kế của nó đã tránh được, xem mục cuối |
| Bộ đệm vòng, nhiều bên cùng ghi | Có | Hai bên cùng tăng chỉ số ghi |
#Vào và ra vùng tới hạn
Bật lại vô điều kiện
uint64_t doc_tick_te(void)
{
__disable_irq();
uint64_t v = g_tong_tick;
__enable_irq(); /* BAT ngat, du truoc do co the dang TAT */
return v;
}
/* Neu ham nay duoc goi tu ben trong mot vung toi han khac,
hoac tu ben trong mot ISR, thi dong __enable_irq bat ngat
len giua chung, pha vo bao dam cua doan bao ve ben ngoai. */Lưu và khôi phục
uint64_t doc_tick(void)
{
uint32_t primask = __get_PRIMASK(); /* luu trang thai */
__disable_irq();
uint64_t v = g_tong_tick;
__set_PRIMASK(primask); /* khoi phuc, khong bat bua */
return v;
}PRIMASK
Thanh ghi một bit của lõi Cortex-M. Bằng 1 thì mọi ngắt bị chặn, trừ NMI và HardFault. Bằng 0 thì ngắt hoạt động bình thường. Lệnh
__disable_irq đặt nó lên 1, __enable_irq đặt về 0.vung-toi-han.h
#ifndef VUNG_TOI_HAN_H
#define VUNG_TOI_HAN_H
#include <stdint.h>
#include "cmsis_gcc.h"
/* Mau hai macro, dung thanh cap. Bien tam co ten gach duoi
de giam nguy co trung ten, xem Bai 48.2. */
#define VAO_VUNG_TOI_HAN() \
uint32_t _primask_luu = __get_PRIMASK(); \
__disable_irq()
#define RA_VUNG_TOI_HAN() \
__set_PRIMASK(_primask_luu)
#endifdung.c
uint64_t doc_tick(void)
{
VAO_VUNG_TOI_HAN();
uint64_t v = g_tong_tick;
RA_VUNG_TOI_HAN();
return v;
}
void cap_nhat_cau_hinh(uint16_t a, uint16_t b)
{
VAO_VUNG_TOI_HAN();
g_cau_hinh.nguong_tren = a; /* hai truong phai nhat quan */
g_cau_hinh.nguong_duoi = b;
RA_VUNG_TOI_HAN();
}#Vùng tới hạn lồng nhau
long-nhau.c
void ham_trong(void)
{
VAO_VUNG_TOI_HAN(); /* primask luc nay dang la 1 */
lam_gi_do();
RA_VUNG_TOI_HAN(); /* khoi phuc ve 1, tuc VAN TAT */
}
void ham_ngoai(void)
{
VAO_VUNG_TOI_HAN(); /* primask luc nay la 0, luu lai 0 */
ham_trong(); /* ham trong khong bat ngat len */
lam_them(); /* van duoc bao ve */
RA_VUNG_TOI_HAN(); /* khoi phuc ve 0, tuc BAT lai */
}#Giữ vùng tới hạn ngắn nhất có thể
Bảo vệ cả hàm
void xu_ly_te(void)
{
VAO_VUNG_TOI_HAN();
uint32_t n = g_so_mau;
uint32_t tong = 0u;
for (uint32_t i = 0; i < n; ++i)
tong += g_bo_dem[i]; /* vong lap 256 lan */
in_ket_qua(tong / n); /* goi UART, hang tram micro giay */
RA_VUNG_TOI_HAN();
}
/* Ngat bi tat suot ca vong lap VA ca luc gui UART.
Do tre ngat tang tu 0.2 micro giay len hang tram. */Chỉ bảo vệ phép chép
void xu_ly(void)
{
uint32_t ban_sao[256];
uint32_t n;
VAO_VUNG_TOI_HAN();
n = g_so_mau;
memcpy(ban_sao, (const void *)g_bo_dem, n * sizeof *ban_sao);
RA_VUNG_TOI_HAN(); /* ra NGAY sau khi chep xong */
/* Tu day tinh toan tren ban sao, khong ai dung toi */
uint32_t tong = 0u;
for (uint32_t i = 0; i < n; ++i) tong += ban_sao[i];
in_ket_qua(tong / n);
}terminal
> do-do-tre --vung-toi-han-dai
do tre ngat lon nhat: 412.8 us
> do-do-tre --vung-toi-han-ngan
do tre ngat lon nhat: 1.4 us Giam 295 lan chi bang cach chuyen phan tinh toan ra ngoai.
#Cấu trúc không cần khóa
Cách tốt nhất để xử lý vùng tới hạn là thiết kế sao cho không cần chúng. Bộ đệm vòng một bên ghi một bên đọc là ví dụ kinh điển.
ring-khong-khoa.c
#include <stdint.h>
#define N 256u
#define MASK (N - 1u)
_Static_assert((N & MASK) == 0u, "N phai la luy thua cua 2");
static volatile uint8_t dem[N];
static volatile uint16_t chi_so_ghi = 0u; /* CHI ISR ghi */
static volatile uint16_t chi_so_doc = 0u; /* CHI main ghi */
/* Goi tu ISR */
int ring_them(uint8_t b)
{
uint16_t ke = (uint16_t)((chi_so_ghi + 1u) & MASK);
if (ke == chi_so_doc) return -1; /* day */
dem[chi_so_ghi] = b; /* ghi du lieu TRUOC */
chi_so_ghi = ke; /* roi moi cong bo */
return 0;
}
/* Goi tu vong lap chinh */
int ring_lay(uint8_t *ra)
{
if (chi_so_doc == chi_so_ghi) return -1; /* rong */
*ra = dem[chi_so_doc];
chi_so_doc = (uint16_t)((chi_so_doc + 1u) & MASK);
return 0;
}| Số bên ghi | Số bên đọc | Cần khóa không |
|---|---|---|
| 1 | 1 | Không |
| 1 | Nhiều | Không, nếu mỗi bên đọc giữ chỉ số riêng |
| Nhiều | 1 | Có |
| Nhiều | Nhiều | Có |
Cờ nguyên tử bằng lệnh độc quyền
co-doc-quyen.c
#include "cmsis_gcc.h"
/* Lay khoa mot lan, khong cho. Tra ve 1 neu lay duoc. */
static inline int thu_lay_khoa(volatile uint32_t *khoa)
{
if (__LDREXW(khoa) != 0u) { /* da co nguoi giu */
__CLREX(); /* huy danh dau doc quyen */
return 0;
}
return __STREXW(1u, khoa) == 0u; /* ghi thanh cong thi lay duoc */
}
static inline void tra_khoa(volatile uint32_t *khoa)
{
__DMB(); /* bao dam moi lenh ghi truoc da xong */
*khoa = 0u;
}
/* Uu diem so voi tat ngat: KHONG lam tang do tre ngat.
Nhuoc diem: neu khong lay duoc thi phai xu ly, khong the cho
trong ISR duoc. */Tự làm thử
- Viết bộ đếm 64 bit tăng trong ISR và đọc từ vòng lặp chính, chạy mười phút và đếm số lần đọc ra giá trị vô lý.
- Cài cặp macro vào ra vùng tới hạn, gọi lồng nhau ba tầng, và in
PRIMASKở từng tầng. - Đo độ trễ ngắt với vùng tới hạn bao cả một vòng lặp 256 lần và với vùng tới hạn chỉ bao phép chép.
- Cài mẫu đổi chỗ hai bộ đệm và đo thời gian vùng tới hạn với bộ đệm 256, 1024 và 4096 byte.
- Cho hai ISR cùng gọi
ring_themở hai mức ưu tiên khác nhau, gửi dữ liệu nhanh, và đếm số byte mất.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Chỉ cần bảo vệ khi có phép đọc sửa ghi tranh chấp, hoặc khi dữ liệu rộng hơn một lệnh truy cập, hoặc khi nhiều trường phải nhất quán.
- Luôn lưu và khôi phục
PRIMASKchứ không bật lại vô điều kiện, để vùng tới hạn lồng nhau vẫn đúng. - Vùng tới hạn phải ngắn nhất có thể. Chép dữ liệu ra rồi tính bên ngoài, hoặc đổi chỗ hai con trỏ bộ đệm.
- Bộ đệm vòng một bên ghi một bên đọc không cần khóa, vì mỗi chỉ số chỉ do một bên ghi.
- Bảo đảm đó vỡ ngay khi có hai bên cùng ghi. Cho chúng cùng mức ưu tiên hoặc tách bộ đệm riêng.