shm: allow accessing multiple shm mapping concurrently

Use a basic linked list to store the currently active mappings.

Note that we don't actually need to implement a full lock-free
atomic linked list here. The signal handler will never write to
the list, it will only read it. Only the main thread will write.
We need to always expose a consistent view of the list to the
signal handler (the main thread might be interrupted at any point
by the signal handler).
This commit is contained in:
Simon Ser 2022-09-23 13:47:52 +02:00 committed by Simon Zeni
parent 6c277e3c39
commit 270b8dd342

View file

@ -55,6 +55,7 @@ struct wlr_shm_mapping {
struct wlr_shm_sigbus_data { struct wlr_shm_sigbus_data {
struct wlr_shm_mapping *mapping; struct wlr_shm_mapping *mapping;
struct sigaction prev_action; struct sigaction prev_action;
struct wlr_shm_sigbus_data *_Atomic next;
}; };
struct wlr_shm_buffer { struct wlr_shm_buffer {
@ -122,13 +123,14 @@ static struct wlr_shm_mapping *mapping_create(int fd, size_t size) {
} }
static void mapping_consider_destroy(struct wlr_shm_mapping *mapping) { static void mapping_consider_destroy(struct wlr_shm_mapping *mapping) {
struct wlr_shm_mapping *mapping_in_use = NULL; if (!mapping->dropped) {
if (sigbus_data != NULL) { return;
mapping_in_use = sigbus_data->mapping;
} }
if (mapping_in_use == mapping || !mapping->dropped) { for (struct wlr_shm_sigbus_data *cur = sigbus_data; cur != NULL; cur = cur->next) {
return; if (cur->mapping == mapping) {
return;
}
} }
munmap(mapping->data, mapping->size); munmap(mapping->data, mapping->size);
@ -181,13 +183,22 @@ static bool buffer_get_shm(struct wlr_buffer *wlr_buffer,
} }
static void handle_sigbus(int sig, siginfo_t *info, void *context) { static void handle_sigbus(int sig, siginfo_t *info, void *context) {
struct wlr_shm_sigbus_data data = *sigbus_data; assert(sigbus_data != NULL);
struct wlr_shm_mapping *mapping = data.mapping; struct sigaction prev_action = sigbus_data->prev_action;
// Check whether the offending address is inside of the wl_shm_pool's mapped
// space
uintptr_t addr = (uintptr_t)info->si_addr; uintptr_t addr = (uintptr_t)info->si_addr;
uintptr_t mapping_start = (uintptr_t)mapping->data; struct wlr_shm_mapping *mapping = NULL;
if (addr < mapping_start || addr >= mapping_start + mapping->size) { for (struct wlr_shm_sigbus_data *data = sigbus_data; data != NULL; data = data->next) {
// The offending address is outside of the wl_shm_pool's mapped space uintptr_t mapping_start = (uintptr_t)data->mapping->data;
size_t mapping_size = data->mapping->size;
if (addr >= mapping_start && addr < mapping_start + mapping_size) {
mapping = data->mapping;
break;
}
}
if (mapping == NULL) {
goto reraise; goto reraise;
} }
@ -202,10 +213,10 @@ static void handle_sigbus(int sig, siginfo_t *info, void *context) {
return; return;
reraise: reraise:
if (data.prev_action.sa_flags & SA_SIGINFO) { if (prev_action.sa_flags & SA_SIGINFO) {
data.prev_action.sa_sigaction(sig, info, context); prev_action.sa_sigaction(sig, info, context);
} else { } else {
data.prev_action.sa_handler(sig); prev_action.sa_handler(sig);
} }
} }
@ -218,21 +229,20 @@ static bool buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
return false; return false;
} }
if (sigbus_data != NULL) {
wlr_log(WLR_ERROR, "Cannot concurrently access data of two wlr_shm_buffers");
return false;
}
// Install a SIGBUS handler. SIGBUS is triggered if the client shrinks the // Install a SIGBUS handler. SIGBUS is triggered if the client shrinks the
// backing file, and then we try to access the mapping. // backing file, and then we try to access the mapping.
struct sigaction new_action = {
.sa_sigaction = handle_sigbus,
.sa_flags = SA_SIGINFO | SA_NODEFER,
};
struct sigaction prev_action; struct sigaction prev_action;
if (sigaction(SIGBUS, &new_action, &prev_action) != 0) { if (sigbus_data == NULL) {
wlr_log_errno(WLR_ERROR, "sigaction failed"); struct sigaction new_action = {
return false; .sa_sigaction = handle_sigbus,
.sa_flags = SA_SIGINFO | SA_NODEFER,
};
if (sigaction(SIGBUS, &new_action, &prev_action) != 0) {
wlr_log_errno(WLR_ERROR, "sigaction failed");
return false;
}
} else {
prev_action = sigbus_data->prev_action;
} }
struct wlr_shm_mapping *mapping = buffer->pool->mapping; struct wlr_shm_mapping *mapping = buffer->pool->mapping;
@ -240,6 +250,7 @@ static bool buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
buffer->sigbus_data = (struct wlr_shm_sigbus_data){ buffer->sigbus_data = (struct wlr_shm_sigbus_data){
.mapping = mapping, .mapping = mapping,
.prev_action = prev_action, .prev_action = prev_action,
.next = sigbus_data,
}; };
sigbus_data = &buffer->sigbus_data; sigbus_data = &buffer->sigbus_data;
@ -252,14 +263,24 @@ static bool buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
static void buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { static void buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {
struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
struct wlr_shm_sigbus_data data = *sigbus_data; if (sigbus_data == &buffer->sigbus_data) {
sigbus_data = buffer->sigbus_data.next;
if (sigaction(SIGBUS, &data.prev_action, NULL) != 0) { } else {
wlr_log_errno(WLR_ERROR, "sigaction failed"); for (struct wlr_shm_sigbus_data *cur = sigbus_data; cur != NULL; cur = cur->next) {
if (cur->next == &buffer->sigbus_data) {
cur->next = buffer->sigbus_data.next;
break;
}
}
} }
sigbus_data = NULL;
mapping_consider_destroy(data.mapping); if (sigbus_data == NULL) {
if (sigaction(SIGBUS, &buffer->sigbus_data.prev_action, NULL) != 0) {
wlr_log_errno(WLR_ERROR, "sigaction failed");
}
}
mapping_consider_destroy(buffer->sigbus_data.mapping);
} }
static const struct wlr_buffer_impl buffer_impl = { static const struct wlr_buffer_impl buffer_impl = {