2005-03-26 10:40:43

by Jan Engelhardt

[permalink] [raw]
Subject: gettimeofday call

Hello list,


I suppose that calling gettimeofday() repeatedly (to add a timestamp to
some data) within the kernel is cheaper than doing it in userspace, is it?


Jan Engelhardt
--
No TOFU for me, please.


2005-03-26 15:36:33

by Kyle Moffett

[permalink] [raw]
Subject: Re: gettimeofday call

On Mar 26, 2005, at 05:40, Jan Engelhardt wrote:
> Hello list,
>
>
> I suppose that calling gettimeofday() repeatedly (to add a timestamp to
> some data) within the kernel is cheaper than doing it in userspace, is
> it?

Well, the following daemon works on most archs that support mmap at only
the cost of a couple read-barriers per gettimeofday(), as opposed to a
whole context switch. OTOH, some archs are getting a vDSO that also
supports a fully-userspace gettimeofday() automatically. libc doesn't
have support code for that, though, and it isn't so portable.

Cheers,
Kyle Moffett

-----BEGIN GEEK CODE BLOCK-----
Version: 3.12
GCM/CS/IT/U d- s++: a18 C++++>$ UB/L/X/*++++(+)>$ P+++(++++)>$
L++++(+++) E W++(+) N+++(++) o? K? w--- O? M++ V? PS+() PE+(-) Y+
PGP+++ t+(+++) 5 X R? tv-(--) b++++(++) DI+ D+ G e->++++$ h!*()>++$ r
!y?(-)
------END GEEK CODE BLOCK------


/*
* NOTE: Make sure you set read_memory_barrier() and
write_memory_barrier()
* properly for your architecture, otherwise this code is not likely to
work
*/

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>

static void handler(int signal);

static void usage(const char *arg0, int err, const char *fmt, ...)
__attribute__((__noreturn__));

int main(int argc, char **argv);

enum status_t {
STATUS_RUNNING,
STATUS_STOPPED,
STATUS_RESTART,
};

static enum status_t status = STATUS_STOPPED;
static int last_signal = 0;

static void usage(const char *arg0, int err, const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
if (fmt) {
if (err) {
char buf[41] = { };
strerror_r(err, buf, 40);
fprintf(stderr,"Error: %s: ",buf);
err = 0;
} else {
fprintf(stderr,"Error: ");
}
vfprintf(stderr,fmt,ap);
fprintf(stderr,"\n");
}
va_end(ap);

if (err) {
char buf[41] = { };
strerror_r(err, buf, 40);
fprintf(stderr,"Error: %s\n",buf);
err = 0;
}

if (!arg0) arg0 = "[" __FILE__ "]";

fprintf(stderr,
"Usage: %s ( -h | <timefile> ) \n"
" -h: Display this help text.\n"
" timefile: The name of the file in which to provide\n"
" shared access to a monotonic time via mmap.\n",
arg0);
exit(1);
}

static void handler (int signal) {
last_signal = signal;
}

/* These are for PPC only, fix for your arch */
#define read_memory_barrier() __asm__ __volatile__ ("sync": : :"memory")
#define write_memory_barrier() __asm__ __volatile__ ("eieio": :
:"memory")

/*
* The "old_time" is the currently stored value. NOTE: This value is
* designed to be read and written locklessly, assuming that reading
* and writing a "long" is atomic on your platform:
* To read:
* do {
* sec1 = time[2];
* read_memory_barrier();
* usec = time[1];
* read_memory_barrier();
* sec2 = time[0];
* read_memory_barrier();
* } while(sec1 != sec2);
* To write:
* time[0] = sec;
* write_memory_barrier();
* time[1] = usec;
* write_memory_barrier();
* time[2] = sec;
*/
#if 0
struct timeval read_time_nolk(volatile long *time) {
struct timeval res;
long lastsecs;

do {
res.tv_sec = time[2];
read_memory_barrier();
res.tv_usec = time[1];
read_memory_barrier();
lastsecs = time[0];
read_memory_barrier();
} while (lastsecs != res.tv_sec);

return res;
}
#endif

static inline void write_time_nolk(volatile long *time, struct timeval
val) {
time[0] = val.tv_sec;
write_memory_barrier();
time[1] = val.tv_usec;
write_memory_barrier();
time[2] = val.tv_sec;
write_memory_barrier();
}

int main(int argc, char **argv) {
int fd; void *mem;
long nulltimebuf[3] = { 0, 0, 0 };

volatile long *oldtime = NULL;
struct timeval newtime = { 0, 0 };
struct timeval zerotime = { 0, 0 };

if (argc <= 0)
usage(NULL,0,"Missing first argument!");

if (argc != 2)
usage(argv[0],0,"Invalid arguments!");

if (strlen(argv[1]) == 0)
usage(argv[0],0,"Invalid argument!");

if (!strcmp(argv[1],"-h"))
usage(argv[0],0,NULL);

/* Trap most signals */
signal(SIGHUP,&handler);
signal(SIGINT,&handler);
signal(SIGQUIT,&handler);
signal(SIGTERM,&handler);

startup:
/* Open the file */
fd = open(argv[1], O_RDWR|O_CREAT|O_EXCL, 0666);
if (fd < 0)
usage(argv[0],errno,"Could not open file: '%s'",argv[1]);

/* Extend the file */
if (0 > write(fd, nulltimebuf, 3*sizeof(long)))
usage(argv[0],errno,"Could not resize file: '%s'",argv[1]);

/* Mmap the file */
mem = mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if (mem == MAP_FAILED)
usage(argv[0],errno,"Could not map file: '%s'",argv[1]);

oldtime = (volatile long *)mem;
write_time_nolk(oldtime,zerotime);

/* Enter the run loop */
status = STATUS_RUNNING;
while(status == STATUS_RUNNING) {
/* Get a new timestamp */
int err = gettimeofday(&newtime,NULL);
if (err) usage(argv[0],errno,"Could not get the time");

/* Bound the microseconds within 10^6 */
newtime.tv_usec %= 1000000;

/* Check for the time going backwards. Since we're the only
* writer, we can afford to ignore memory barriers here */
if (oldtime[0] > newtime.tv_sec || (
oldtime[0] == newtime.tv_sec &&
oldtime[1] > newtime.tv_usec
)) {
/* Ahh, crud, just spew a warning */
fprintf(stderr,"WARNING: Time regression: "
"%lu seconds, %lu microseconds\n",
oldtime[0] - newtime.tv_sec,
oldtime[1] - newtime.tv_usec);
} else {
/* Ok, the time is fine, so store it in memory */
write_time_nolk(oldtime,newtime);
}

/* Check our signal status, if we've received one, then we *
* need to handle it and restart or clean up and quit. */
switch(last_signal) {
case 0:
/* Since we've got no signal, sleep and repeat */
usleep(1000);
break;

case SIGHUP:
status = STATUS_RESTART;
break;

case SIGQUIT:
case SIGINT:
case SIGTERM:
status = STATUS_STOPPED;
break;

default:
fprintf(stderr,"WARNING: Unknown signal: %d\n",
last_signal);
status = STATUS_STOPPED;
break;
}
}

fprintf(stderr,"Caught signal, cleaning up...\n");

/* First prevent new accesses */
if (unlink(argv[1]))
usage(argv[0],errno,"Could not delete file: '%s'",argv[1]);

/* Now tell listening processes that we're stopped */
write_time_nolk(oldtime,zerotime);

if (munmap(mem,4096))
usage(argv[0],errno,"Could not unmap file: '%s'",argv[1]);

if (close(fd))
usage(argv[0],errno,"Could not close file: '%s'",argv[1]);

if (status == STATUS_RESTART) {
fprintf(stderr,"Restarting...\n");
goto startup;
}

fprintf(stderr,"Quitting...\n");
exit(0);
}

2005-03-26 17:42:24

by Jan Engelhardt

[permalink] [raw]
Subject: Re: gettimeofday call

>> I suppose that calling gettimeofday() repeatedly (to add a timestamp to
>> some data) within the kernel is cheaper than doing it in userspace, is it?
>
> Well, the following daemon works on most archs that support mmap at only
[...]

Ah, it does not need to be that complex.

Just comparing two approaches:

--1--
/* KERNEL: Calling read() on a character device */
static int u_read(...) {
...
gettimeofday(tv);
enqueue_in_buffer(tv);
...
}

+nothing needed in userspace

--2--
/* KERNEL: No gettimeofday */

Userspace:
while(read(fd, buf, sizeof(buf)) {
gettimeofday(&buf.tv);
...
}


(In either case, at some point, userspace has a timestamp.)
I think that -1- is faster it does not require an additional syscall from
userspace to sys_gettimeofday().


Jan Engelhardt
--
No TOFU for me, please.

2005-03-27 01:24:09

by Chris Wedgwood

[permalink] [raw]
Subject: Re: gettimeofday call

On Sat, Mar 26, 2005 at 11:40:27AM +0100, Jan Engelhardt wrote:

> I suppose that calling gettimeofday() repeatedly (to add a timestamp
> to some data) within the kernel is cheaper than doing it in
> userspace, is it?

Calls to do_gettimeofday are used in various places for this already.
See sock_get_timestamp for example.