Skip to content

Commit 3b6d9e9

Browse files
thejpstergmmyung
andauthored
Add signals with atomic usize (#21)
* Add critical-section dependency and signal module * Add --all-features flag to fix CI * Turn on CAS support if required. * Add a feature to control portable-atomic/critical-section. --------- Co-authored-by: Myung Gyungmin <gmmyung@kaist.ac.kr>
1 parent 381a624 commit 3b6d9e9

File tree

7 files changed

+225
-16
lines changed

7 files changed

+225
-16
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
with:
4040
use-cross: true
4141
command: build
42-
args: --target=${{ matrix.TARGET }}
42+
args: --target=${{ matrix.TARGET }} --all-features
4343

4444
test:
4545
name: Tests

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
## Unreleased
44

5-
* [#23] - Add `strncasecmp`
5+
* [#23] - Add `strncasecmp`
6+
* [#21] - Add signal API
7+
8+
[#23]: https://github.com/rust-embedded-community/tinyrlibc/pull/21
9+
[#21]: https://github.com/rust-embedded-community/tinyrlibc/pull/21
610

711
## v0.3.0 (2022-10-18)
812

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ readme = "README.md"
99
repository = "https://github.com/thejpster/tinyrlibc"
1010

1111
[dependencies]
12+
portable-atomic = { version = "1.6.0", optional = true }
1213

1314
[dev-dependencies]
1415
static-alloc = "0.2.4"
@@ -42,7 +43,6 @@ all = [
4243
"isdigit",
4344
"isalpha",
4445
"isupper",
45-
"alloc",
4646
]
4747
abs = []
4848
strcmp = []
@@ -68,3 +68,5 @@ isdigit = []
6868
isalpha = []
6969
isupper = []
7070
alloc = []
71+
signal = ["dep:portable-atomic"]
72+
signal-cs = ["portable-atomic/critical-section"]

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP
3636
* calloc
3737
* realloc
3838
* free
39+
* signal (optional)
40+
* signal
41+
* raise
42+
* abort
3943

4044
## Non-standard helper functions
4145

src/lib.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,6 @@ mod malloc;
1616
#[cfg(feature = "alloc")]
1717
pub use self::malloc::{calloc, free, malloc, realloc};
1818

19-
// A new global allocator is required for the tests, but not for the library itself.
20-
// This is because the default alloc crate uses the system allocator, collides with
21-
// the one in this crate, and causes a link error.
22-
#[cfg(all(feature = "alloc", test))]
23-
use static_alloc::Bump;
24-
#[cfg(all(feature = "alloc", test))]
25-
#[global_allocator]
26-
static ALLOCATOR: Bump<[u8; 1024 * 1024]> = Bump::uninit();
27-
2819
mod itoa;
2920
#[cfg(feature = "itoa")]
3021
pub use self::itoa::itoa;
@@ -91,6 +82,11 @@ mod strchr;
9182
#[cfg(feature = "strchr")]
9283
pub use self::strchr::strchr;
9384

85+
#[cfg(feature = "signal")]
86+
mod signal;
87+
#[cfg(feature = "signal")]
88+
pub use self::signal::{abort, raise, signal};
89+
9490
mod snprintf;
9591

9692
mod ctype;

src/malloc.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const MAX_ALIGN: usize = 16;
1212
/// Rust implementation of C library function `malloc`
1313
///
1414
/// See [malloc](https://linux.die.net/man/3/malloc) for alignment details.
15-
#[no_mangle]
15+
#[cfg_attr(not(test), no_mangle)]
1616
pub unsafe extern "C" fn malloc(size: CSizeT) -> *mut u8 {
1717
// size + MAX_ALIGN for to store the size of the allocated memory.
1818
let layout = alloc::alloc::Layout::from_size_align(size + MAX_ALIGN, MAX_ALIGN).unwrap();
@@ -29,7 +29,7 @@ pub unsafe extern "C" fn malloc(size: CSizeT) -> *mut u8 {
2929
/// Rust implementation of C library function `calloc`
3030
///
3131
/// See [calloc](https://linux.die.net/man/3/calloc) for alignment details.
32-
#[no_mangle]
32+
#[cfg_attr(not(test), no_mangle)]
3333
pub unsafe extern "C" fn calloc(nmemb: CSizeT, size: CSizeT) -> *mut u8 {
3434
let total_size = nmemb * size;
3535
let layout = alloc::alloc::Layout::from_size_align(total_size + MAX_ALIGN, MAX_ALIGN).unwrap();
@@ -46,7 +46,7 @@ pub unsafe extern "C" fn calloc(nmemb: CSizeT, size: CSizeT) -> *mut u8 {
4646
/// Rust implementation of C library function `realloc`
4747
///
4848
/// See [realloc](https://linux.die.net/man/3/realloc) for alignment details.
49-
#[no_mangle]
49+
#[cfg_attr(not(test), no_mangle)]
5050
pub unsafe extern "C" fn realloc(ptr: *mut u8, size: CSizeT) -> *mut u8 {
5151
if ptr.is_null() {
5252
return malloc(size);
@@ -64,7 +64,7 @@ pub unsafe extern "C" fn realloc(ptr: *mut u8, size: CSizeT) -> *mut u8 {
6464
}
6565

6666
/// Rust implementation of C library function `free`
67-
#[no_mangle]
67+
#[cfg_attr(not(test), no_mangle)]
6868
pub unsafe extern "C" fn free(ptr: *mut u8) {
6969
if ptr.is_null() {
7070
return;

src/signal.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//! Rust implementation of the C standard library's `signal` related functions.
2+
//!
3+
//! Copyright (c) Gyungmin Myung <gmmyung@kaist.ac.kr>
4+
//! Licensed under the Blue Oak Model Licence 1.0.0
5+
6+
use core::{cell::RefCell, default};
7+
use portable_atomic::{AtomicUsize, Ordering};
8+
9+
/// An initialiser for our array.
10+
///
11+
/// We turn off the clippy warning because it's wrong - and there's no other
12+
/// way to initialise an array of atomics.
13+
#[allow(clippy::declare_interior_mutable_const)]
14+
const SIG_DFL_ATOMIC: AtomicUsize = AtomicUsize::new(SIG_DFL);
15+
16+
/// Our array of registered signal handlers.
17+
///
18+
/// Signals in C are either 0, 1, -1, or a function pointer. We cast function
19+
/// pointers into `usize` so they can be stored in this array.
20+
static SIGNAL_HANDLERS: [AtomicUsize; 16] = [SIG_DFL_ATOMIC; 16];
21+
22+
/// A signal handler - either a function pointer or a magic integer.
23+
pub type SignalHandler = usize;
24+
25+
/// Indicates we should use the default signal handler
26+
const SIG_DFL: usize = 0;
27+
28+
/// Indicates we should use the default signal handler
29+
const SIG_IGN: usize = 1;
30+
31+
/// Indicates we should use the default signal handler
32+
const SIG_ERR: usize = usize::MAX;
33+
34+
/// The TERM signal
35+
const SIGTERM: i32 = 15;
36+
37+
/// The SEGV signal
38+
const SIGSEGV: i32 = 11;
39+
40+
/// The INT signal
41+
const SIGINT: i32 = 2;
42+
43+
/// The ILL signal
44+
const SIGILL: i32 = 4;
45+
46+
/// The ABRT signal
47+
const SIGABRT: i32 = 6;
48+
49+
/// The FPE signal
50+
const SIGFPE: i32 = 8;
51+
52+
/// The list of support signals.
53+
///
54+
/// Only ANSI C signals are now supported.
55+
///
56+
/// SIGSEGV, SIGILL, SIGFPE are not supported on bare metal, but handlers are
57+
/// invoked when raise() is called.
58+
///
59+
/// We will index `SIGNAL_HANDLERS` by any integer in this list, so ensure that
60+
/// the array is made larger if required.
61+
///
62+
/// TODO: Support SIGSEGV, SIGILL, SIGFPE by using the `cortex-m-rt` or
63+
/// `riscv-rt` crate.
64+
const SIGNALS: [i32; 6] = [SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE];
65+
66+
/// An empty handler function that does nothing
67+
fn ignore_handler(_sig: i32) {}
68+
69+
/// The default handler functions
70+
///
71+
/// Performs a panic.
72+
fn default_handler(_sig: i32) {
73+
// TODO: This should call core::intrinsics::abort() but that's unstable.
74+
panic!("Aborted");
75+
}
76+
77+
/// Rust implementation of the C standard library's `signal` function.
78+
///
79+
/// Using `not(test)` ensures we don't replace the actual OS `signal` function
80+
/// when running tests!
81+
#[cfg_attr(all(not(test), feature = "signal"), no_mangle)]
82+
pub unsafe extern "C" fn signal(sig: i32, handler: SignalHandler) -> SignalHandler {
83+
if !SIGNALS.contains(&sig) {
84+
return SIG_ERR;
85+
}
86+
SIGNAL_HANDLERS[sig as usize].swap(handler, Ordering::Relaxed)
87+
}
88+
89+
/// Rust implementation of the C standard library's `raise` function.
90+
///
91+
/// Using `not(test)` ensures we don't replace the actual OS `raise` function
92+
/// when running tests!
93+
#[cfg_attr(all(not(test), feature = "signal"), no_mangle)]
94+
pub extern "C" fn raise(sig: i32) -> i32 {
95+
if !SIGNALS.contains(&sig) {
96+
return -1;
97+
}
98+
let handler = SIGNAL_HANDLERS[sig as usize].load(Ordering::Relaxed);
99+
match handler {
100+
SIG_DFL => {
101+
default_handler(sig);
102+
}
103+
SIG_IGN => {
104+
ignore_handler(sig);
105+
}
106+
_ => unsafe {
107+
let handler_fn: unsafe extern "C" fn(core::ffi::c_int) = core::mem::transmute(handler);
108+
handler_fn(sig);
109+
},
110+
}
111+
0
112+
}
113+
114+
#[cfg_attr(all(not(test), feature = "signal"), no_mangle)]
115+
pub extern "C" fn abort() {
116+
raise(SIGABRT);
117+
}
118+
119+
#[cfg(test)]
120+
mod tests {
121+
use super::*;
122+
123+
struct State {
124+
inner: std::sync::Mutex<()>,
125+
}
126+
127+
impl State {
128+
fn lock(&self) -> std::sync::MutexGuard<()> {
129+
// Ensure we have exclusive access
130+
let guard = self.inner.lock().unwrap();
131+
// Reset the global signal handler list to defaults
132+
for sig in SIGNAL_HANDLERS.iter() {
133+
sig.store(SIG_DFL, Ordering::SeqCst);
134+
}
135+
guard
136+
}
137+
}
138+
139+
/// Used to ensure we don't run multiple signal test concurrently, because
140+
/// they share some global state.
141+
///
142+
/// If a test fails, the lock will be poisoned and all subsequent tests will
143+
/// fail.
144+
static TEST_LOCK: State = State {
145+
inner: std::sync::Mutex::new(()),
146+
};
147+
148+
#[test]
149+
fn test_signal() {
150+
let _guard = TEST_LOCK.lock();
151+
static COUNT: AtomicUsize = AtomicUsize::new(0);
152+
extern "C" fn count_handler(_sig: i32) {
153+
COUNT.fetch_add(1, Ordering::Relaxed);
154+
}
155+
let count_handler_ptr = count_handler as *const fn(i32) as usize;
156+
let old_handler = unsafe { signal(SIGTERM, count_handler_ptr) };
157+
assert_eq!(old_handler, SIG_DFL);
158+
(0..10).for_each(|_| {
159+
raise(SIGTERM);
160+
});
161+
let old_handler = unsafe { signal(SIGTERM, SIG_DFL) };
162+
assert_eq!(COUNT.load(Ordering::Relaxed), 10);
163+
assert_eq!(old_handler, count_handler_ptr);
164+
}
165+
166+
#[test]
167+
fn test_abort() {
168+
let _guard = TEST_LOCK.lock();
169+
let result = std::panic::catch_unwind(|| {
170+
abort();
171+
});
172+
assert!(result.is_err());
173+
}
174+
175+
#[test]
176+
fn test_signal_error() {
177+
let _guard = TEST_LOCK.lock();
178+
let err = unsafe { signal(1000, SIG_DFL) };
179+
assert_eq!(err, SIG_ERR);
180+
}
181+
182+
#[test]
183+
fn test_raise() {
184+
let result = std::panic::catch_unwind(|| raise(SIGTERM));
185+
assert!(result.is_err());
186+
}
187+
188+
#[test]
189+
fn test_ignore() {
190+
let _guard = TEST_LOCK.lock();
191+
let old_handler = unsafe { signal(SIGTERM, SIG_IGN) };
192+
assert_eq!(old_handler, SIG_DFL);
193+
// Shouldn't cause a panic
194+
raise(SIGTERM);
195+
let old_handler = unsafe { signal(SIGTERM, SIG_DFL) };
196+
assert_eq!(old_handler, SIG_IGN);
197+
}
198+
199+
#[test]
200+
fn test_raise_error() {
201+
assert!(raise(1000) == -1);
202+
}
203+
}

0 commit comments

Comments
 (0)