aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfgang Draxinger <Wolfgang.Draxinger@draxit.de>2015-04-06 01:12:40 +0200
committerWolfgang Draxinger <Wolfgang.Draxinger@draxit.de>2015-04-06 01:12:40 +0200
commitd803582a32ed50400c57c75764300a7ab6b192e9 (patch)
tree4735fe92c69542d3b43c31b25fc3fbbc71ab9105
downloadfmlock-d803582a32ed50400c57c75764300a7ab6b192e9.tar.gz
fmlock-d803582a32ed50400c57c75764300a7ab6b192e9.tar.bz2
initial commit
-rw-r--r--Makefile6
-rw-r--r--README12
-rw-r--r--fmlock.c130
3 files changed, 148 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1998cb7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,6 @@
+fmlock: fmlock.c
+
+clean:
+ -rm fmlock
+
+.PHONY: clean
diff --git a/README b/README
new file mode 100644
index 0000000..684a286
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+== fmlock -- map a file into memory and lock it to RAM ==
+
+fmlock is a small tool intended to load a file into the
+block I/O cache and lock it there. This is done by mmap-ing
+the whole file and calling mlock on the address space
+occupied by the file. Then the program puts itself to sleep
+until a signal arrives. The purpose of this is that (large)
+files can be preloaded into RAM, specifically the block I/O
+cache for other processes to read from. This is for example
+required to measure the RAM bandwidth and CPU limited
+throughput of data processing code, working on real data
+without getting effected by I/O bottlenecks.
diff --git a/fmlock.c b/fmlock.c
new file mode 100644
index 0000000..4ea68d0
--- /dev/null
+++ b/fmlock.c
@@ -0,0 +1,130 @@
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+int heat_the_cache(int fd)
+{
+ char buf[1024];
+ int rv;
+ do {
+ retry_read:
+ rv = read(fd, buf, sizeof(buf));
+ if( -1 == rv
+ && EINTR == errno ) {
+ goto retry_read;
+ }
+ } while( 0 < rv );
+
+ return rv != 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int i, fd_null, n_fileslocked;
+ fd_set phony_fdset;
+ char *buf;
+
+ if( 2 > argc ) {
+ fprintf(stderr,
+ "Usage:\n%s [filenames]\n",
+ argv[0] );
+ return 1;
+ }
+
+ n_fileslocked = 0;
+ for(i = 1; i < argc; ++i) {
+ char const * const filename = argv[i];
+ int fd;
+ struct stat st;
+ void *ptr;
+
+ retry_open:
+ fd = open(filename, O_RDONLY);
+ if( -1 == fd ) {
+ if( EINTR == errno ) {
+ goto retry_open;
+ } else
+ {
+ fprintf(stderr,
+ "error open('%s'): %s\n",
+ filename,
+ strerror(errno) );
+ continue;
+ }
+ }
+
+ if( -1 == fstat(fd, &st) ) {
+ fprintf(stderr,
+ "error fstat(fd['%s']): %s\n",
+ filename,
+ strerror(errno) );
+ goto finish_file;
+ }
+
+ ptr = mmap(
+ NULL,
+ st.st_size,
+ PROT_READ,
+ MAP_SHARED | MAP_LOCKED,
+ fd,
+ 0 );
+ if( MAP_FAILED == ptr ) {
+ fprintf(stderr,
+ "error mmap(fd['%s'], 0..%lld): %s\n",
+ filename,
+ (long long)st.st_size,
+ strerror(errno) );
+ goto finish_file;
+ }
+
+ if( -1 == mlock(ptr, st.st_size) ) {
+ fprintf(stderr,
+ "error mlock(ptr[fd['%s']]=%p): %s\n",
+ filename,
+ ptr,
+ strerror(errno) );
+ goto finish_file;
+ }
+ ++n_fileslocked;
+
+ heat_the_cache(fd);
+
+ finish_file:
+ close(fd);
+ }
+
+ if( !n_fileslocked ) {
+ return 1;
+ }
+
+ /* At this point the program shall sleep until a terminating
+ * signal arrives. To do so a nice side effect of the definition
+ * of /dev/null behavior is used: read on a /dev/null fd always
+ * return 0, which correspond to EOF which is a "no content
+ * available for read (yet)" situation for which select waits.
+ * So by selecting for a read a fd on /dev/null we can put the
+ * process to sleep. */
+ fd_null = open("/dev/null", O_RDONLY);
+ if( -1 == fd_null ) {
+ fprintf(stderr,
+ "error open('/dev/null'): %s\n",
+ strerror(errno) );
+ return 1;
+ }
+ FD_ZERO(&phony_fdset);
+ FD_SET(fd_null, &phony_fdset);
+ if( -1 == select(fd_null, &phony_fdset, NULL, NULL, NULL) ) {
+ fprintf(stderr,
+ "error select(...): %s\n",
+ strerror(errno) );
+ return 1;
+ }
+
+ return 0;
+}