From 54506ab44a9ac61e79b3a5c632db56bde41beded Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 11 Sep 2007 23:12:24 +0000 Subject: [PATCH] on systems where we know that POSIX shm is mapped to /dev/shm, add the ability to cleanup stale SHM segments. (Right now only Linux) git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/lennart@1810 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/daemon/cmdline.c | 9 +++- src/daemon/daemon-conf.h | 3 +- src/daemon/main.c | 11 ++++- src/pulsecore/shm.c | 118 +++++++++++++++++++++++++++++++++++++++++++++-- src/pulsecore/shm.h | 2 + 5 files changed, 134 insertions(+), 9 deletions(-) diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c index a3f1ff0..d14002f 100644 --- a/src/daemon/cmdline.c +++ b/src/daemon/cmdline.c @@ -64,7 +64,8 @@ enum { ARG_NO_CPU_LIMIT, ARG_DISABLE_SHM, ARG_DUMP_RESAMPLE_METHODS, - ARG_SYSTEM + ARG_SYSTEM, + ARG_CLEANUP_SHM }; /* Tabel for getopt_long() */ @@ -94,6 +95,7 @@ static struct option long_options[] = { {"no-cpu-limit", 2, 0, ARG_NO_CPU_LIMIT}, {"disable-shm", 2, 0, ARG_DISABLE_SHM}, {"dump-resample-methods", 2, 0, ARG_DUMP_RESAMPLE_METHODS}, + {"cleanup-shm", 2, 0, ARG_CLEANUP_SHM}, {NULL, 0, 0, 0} }; @@ -114,6 +116,7 @@ void pa_cmdline_help(const char *argv0) { " --dump-conf Dump default configuration\n" " --dump-modules Dump list of available modules\n" " --dump-resample-methods Dump available resample methods\n" + " --cleanup-shm Cleanup stale shared memory segments\n" " -k --kill Kill a running daemon\n" " --check Check for a running daemon\n\n" @@ -188,6 +191,10 @@ int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d case ARG_DUMP_RESAMPLE_METHODS: conf->cmd = PA_CMD_DUMP_RESAMPLE_METHODS; break; + + case ARG_CLEANUP_SHM: + conf->cmd = PA_CMD_CLEANUP_SHM; + break; case 'k': case ARG_KILL: diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h index 7633427..4d37861 100644 --- a/src/daemon/daemon-conf.h +++ b/src/daemon/daemon-conf.h @@ -41,7 +41,8 @@ typedef enum pa_daemon_conf_cmd { PA_CMD_DUMP_MODULES, PA_CMD_KILL, PA_CMD_CHECK, - PA_CMD_DUMP_RESAMPLE_METHODS + PA_CMD_DUMP_RESAMPLE_METHODS, + PA_CMD_CLEANUP_SHM } pa_daemon_conf_cmd_t; #ifdef HAVE_SYS_RESOURCE_H diff --git a/src/daemon/main.c b/src/daemon/main.c index 87f3f01..4509e7f 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -58,13 +58,12 @@ #include #endif -#include "../pulsecore/winsock.h" - #include #include #include #include +#include #include #include #include @@ -83,6 +82,7 @@ #include #include #include +#include #include "cmdline.h" #include "cpulimit.h" @@ -496,6 +496,13 @@ int main(int argc, char *argv[]) { goto finish; + case PA_CMD_CLEANUP_SHM: + + if (pa_shm_cleanup() >= 0) + retval = 0; + + goto finish; + default: pa_assert(conf->cmd == PA_CMD_DAEMON); } diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index 78b968e..f17d946 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -33,17 +33,22 @@ #include #include #include +#include +#include +#include #ifdef HAVE_SYS_MMAN_H #include #endif +#include + #include #include #include #include #include -#include +#include #include "shm.h" @@ -51,8 +56,29 @@ #define MADV_REMOVE 9 #endif -#define MAX_SHM_SIZE (1024*1024*20) +#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*20)) + +#ifdef __linux__ +/* On Linux we know that the shared memory blocks are files in + * /dev/shm. We can use that information to list all blocks and + * cleanup unused ones */ +#define SHM_PATH "/dev/shm/" +#else +#undef SHM_PATH +#endif +#define SHM_MARKER ((int) 0xbeefcafe) + +/* We now put this SHM marker at the end of each segment. It's optional to not require a reboot when upgrading, though */ +struct shm_marker { + pa_atomic_t marker; /* 0xbeefcafe */ + pa_atomic_t pid; + void *_reserverd1; + void *_reserverd2; + void *_reserverd3; + void *_reserverd4; +}; + static char *segment_name(char *fn, size_t l, unsigned id) { pa_snprintf(fn, l, "/pulse-shm-%u", id); return fn; @@ -67,6 +93,13 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { pa_assert(size < MAX_SHM_SIZE); pa_assert(mode >= 0600); + /* Each time we create a new SHM area, let's first drop all stale + * ones */ + pa_shm_cleanup(); + + /* Round up to make it aligned */ + size = PA_ALIGN(size); + if (!shared) { m->id = 0; m->size = size; @@ -93,6 +126,8 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { } else { #ifdef HAVE_SHM_OPEN + struct shm_marker *marker; + pa_random(&m->id, sizeof(m->id)); segment_name(fn, sizeof(fn), m->id); @@ -101,7 +136,9 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { goto fail; } - if (ftruncate(fd, m->size = size) < 0) { + m->size = size + PA_ALIGN(sizeof(struct shm_marker)); + + if (ftruncate(fd, m->size) < 0) { pa_log("ftruncate() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -111,6 +148,12 @@ int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { goto fail; } + /* We store our PID at the end of the shm block, so that we + * can check for dead shm segments later */ + marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - PA_ALIGN(sizeof(struct shm_marker))); + pa_atomic_store(&marker->pid, (int) getpid()); + pa_atomic_store(&marker->marker, SHM_MARKER); + close(fd); m->do_unlink = 1; #else @@ -229,7 +272,8 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { segment_name(fn, sizeof(fn), m->id = id); if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) { - pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + if (errno != EACCES) + pa_log("shm_open() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -238,7 +282,7 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { goto fail; } - if (st.st_size <= 0 || st.st_size > MAX_SHM_SIZE) { + if (st.st_size <= 0 || st.st_size > MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker)) || PA_ALIGN(st.st_size) != st.st_size) { pa_log("Invalid shared memory segment size"); goto fail; } @@ -271,3 +315,67 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { } #endif /* HAVE_SHM_OPEN */ + +int pa_shm_cleanup(void) { + +#ifdef SHM_PATH + DIR *d; + struct dirent *de; + + if (!(d = opendir(SHM_PATH))) { + pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno)); + return -1; + } + + while ((de = readdir(d))) { + pa_shm seg; + unsigned id; + pid_t pid; + char fn[128]; + struct shm_marker *m; + + if (strncmp(de->d_name, "pulse-shm-", 10)) + continue; + + if (pa_atou(de->d_name + 10, &id) < 0) + continue; + + if (pa_shm_attach_ro(&seg, id) < 0) + continue; + + if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) { + pa_shm_free(&seg); + continue; + } + + m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker))); + + if (pa_atomic_load(&m->marker) != SHM_MARKER) { + pa_shm_free(&seg); + continue; + } + + if (!(pid = (pid_t) pa_atomic_load(&m->pid))) { + pa_shm_free(&seg); + continue; + } + + if (kill(pid, 0) == 0 || errno != ESRCH) { + pa_shm_free(&seg); + continue; + } + + pa_shm_free(&seg); + + /* Ok, the owner of this shms segment is dead, so, let's remove the segment */ + segment_name(fn, sizeof(fn), id); + + if (shm_unlink(fn) < 0 && errno != EACCES) + pa_log_warn("Failed to remove SHM segment %s: %s\n", fn, pa_cstrerror(errno)); + } + + closedir(d); +#endif + + return 0; +} diff --git a/src/pulsecore/shm.h b/src/pulsecore/shm.h index e695a2a..270591d 100644 --- a/src/pulsecore/shm.h +++ b/src/pulsecore/shm.h @@ -41,4 +41,6 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size); void pa_shm_free(pa_shm *m); +int pa_shm_cleanup(void); + #endif -- 2.7.4