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:
parent
6c277e3c39
commit
270b8dd342
|
@ -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,14 +123,15 @@ 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) {
|
||||||
|
if (cur->mapping == mapping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
munmap(mapping->data, mapping->size);
|
munmap(mapping->data, mapping->size);
|
||||||
free(mapping);
|
free(mapping);
|
||||||
|
@ -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,28 +229,28 @@ 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 prev_action;
|
||||||
|
if (sigbus_data == NULL) {
|
||||||
struct sigaction new_action = {
|
struct sigaction new_action = {
|
||||||
.sa_sigaction = handle_sigbus,
|
.sa_sigaction = handle_sigbus,
|
||||||
.sa_flags = SA_SIGINFO | SA_NODEFER,
|
.sa_flags = SA_SIGINFO | SA_NODEFER,
|
||||||
};
|
};
|
||||||
struct sigaction prev_action;
|
|
||||||
if (sigaction(SIGBUS, &new_action, &prev_action) != 0) {
|
if (sigaction(SIGBUS, &new_action, &prev_action) != 0) {
|
||||||
wlr_log_errno(WLR_ERROR, "sigaction failed");
|
wlr_log_errno(WLR_ERROR, "sigaction failed");
|
||||||
return false;
|
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;
|
||||||
|
|
||||||
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;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sigaction(SIGBUS, &data.prev_action, NULL) != 0) {
|
if (sigbus_data == NULL) {
|
||||||
|
if (sigaction(SIGBUS, &buffer->sigbus_data.prev_action, NULL) != 0) {
|
||||||
wlr_log_errno(WLR_ERROR, "sigaction failed");
|
wlr_log_errno(WLR_ERROR, "sigaction failed");
|
||||||
}
|
}
|
||||||
sigbus_data = NULL;
|
}
|
||||||
|
|
||||||
mapping_consider_destroy(data.mapping);
|
mapping_consider_destroy(buffer->sigbus_data.mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct wlr_buffer_impl buffer_impl = {
|
static const struct wlr_buffer_impl buffer_impl = {
|
||||||
|
|
Loading…
Reference in New Issue