diff options
author | Davide Libenzi <davidel@xmailserver.org> | 2007-05-11 01:23:16 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-05-11 11:29:36 -0400 |
commit | b215e283992899650c4271e7385c79e26fb9a88e (patch) | |
tree | 3f950814510422606821f1b0b373d65e4d9ed303 | |
parent | 6d18c9220965b437287c3a7e803725c24992ceac (diff) |
signal/timer/event: timerfd core
This patch introduces a new system call for timers events delivered though
file descriptors. This allows timer event to be used with standard POSIX
poll(2), select(2) and read(2). As a consequence of supporting the Linux
f_op->poll subsystem, they can be used with epoll(2) too.
The system call is defined as:
int timerfd(int ufd, int clockid, int flags, const struct itimerspec *utmr);
The "ufd" parameter allows for re-use (re-programming) of an existing timerfd
w/out going through the close/open cycle (same as signalfd). If "ufd" is -1,
s new file descriptor will be created, otherwise the existing "ufd" will be
re-programmed.
The "clockid" parameter is either CLOCK_MONOTONIC or CLOCK_REALTIME. The time
specified in the "utmr->it_value" parameter is the expiry time for the timer.
If the TFD_TIMER_ABSTIME flag is set in "flags", this is an absolute time,
otherwise it's a relative time.
If the time specified in the "utmr->it_interval" is not zero (.tv_sec == 0,
tv_nsec == 0), this is the period at which the following ticks should be
generated.
The "utmr->it_interval" should be set to zero if only one tick is requested.
Setting the "utmr->it_value" to zero will disable the timer, or will create a
timerfd without the timer enabled.
The function returns the new (or same, in case "ufd" is a valid timerfd
descriptor) file, or -1 in case of error.
As stated before, the timerfd file descriptor supports poll(2), select(2) and
epoll(2). When a timer event happened on the timerfd, a POLLIN mask will be
returned.
The read(2) call can be used, and it will return a u32 variable holding the
number of "ticks" that happened on the interface since the last call to
read(2). The read(2) call supportes the O_NONBLOCK flag too, and EAGAIN will
be returned if no ticks happened.
A quick test program, shows timerfd working correctly on my amd64 box:
http://www.xmailserver.org/timerfd-test.c
[akpm@linux-foundation.org: add sys_timerfd to sys_ni.c]
Signed-off-by: Davide Libenzi <davidel@xmailserver.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | fs/Makefile | 1 | ||||
-rw-r--r-- | fs/timerfd.c | 227 | ||||
-rw-r--r-- | include/linux/syscalls.h | 2 | ||||
-rw-r--r-- | include/linux/timerfd.h | 17 | ||||
-rw-r--r-- | init/Kconfig | 10 | ||||
-rw-r--r-- | kernel/sys_ni.c | 1 |
6 files changed, 258 insertions, 0 deletions
diff --git a/fs/Makefile b/fs/Makefile index cd8a57aeac04..39625da9e2d6 100644 --- a/fs/Makefile +++ b/fs/Makefile | |||
@@ -24,6 +24,7 @@ obj-$(CONFIG_INOTIFY_USER) += inotify_user.o | |||
24 | obj-$(CONFIG_EPOLL) += eventpoll.o | 24 | obj-$(CONFIG_EPOLL) += eventpoll.o |
25 | obj-$(CONFIG_ANON_INODES) += anon_inodes.o | 25 | obj-$(CONFIG_ANON_INODES) += anon_inodes.o |
26 | obj-$(CONFIG_SIGNALFD) += signalfd.o | 26 | obj-$(CONFIG_SIGNALFD) += signalfd.o |
27 | obj-$(CONFIG_TIMERFD) += timerfd.o | ||
27 | obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o | 28 | obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o |
28 | 29 | ||
29 | nfsd-$(CONFIG_NFSD) := nfsctl.o | 30 | nfsd-$(CONFIG_NFSD) := nfsctl.o |
diff --git a/fs/timerfd.c b/fs/timerfd.c new file mode 100644 index 000000000000..e329e37f15a8 --- /dev/null +++ b/fs/timerfd.c | |||
@@ -0,0 +1,227 @@ | |||
1 | /* | ||
2 | * fs/timerfd.c | ||
3 | * | ||
4 | * Copyright (C) 2007 Davide Libenzi <davidel@xmailserver.org> | ||
5 | * | ||
6 | * | ||
7 | * Thanks to Thomas Gleixner for code reviews and useful comments. | ||
8 | * | ||
9 | */ | ||
10 | |||
11 | #include <linux/file.h> | ||
12 | #include <linux/poll.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/fs.h> | ||
15 | #include <linux/sched.h> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/list.h> | ||
18 | #include <linux/spinlock.h> | ||
19 | #include <linux/time.h> | ||
20 | #include <linux/hrtimer.h> | ||
21 | #include <linux/anon_inodes.h> | ||
22 | #include <linux/timerfd.h> | ||
23 | |||
24 | struct timerfd_ctx { | ||
25 | struct hrtimer tmr; | ||
26 | ktime_t tintv; | ||
27 | spinlock_t lock; | ||
28 | wait_queue_head_t wqh; | ||
29 | int expired; | ||
30 | }; | ||
31 | |||
32 | /* | ||
33 | * This gets called when the timer event triggers. We set the "expired" | ||
34 | * flag, but we do not re-arm the timer (in case it's necessary, | ||
35 | * tintv.tv64 != 0) until the timer is read. | ||
36 | */ | ||
37 | static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr) | ||
38 | { | ||
39 | struct timerfd_ctx *ctx = container_of(htmr, struct timerfd_ctx, tmr); | ||
40 | unsigned long flags; | ||
41 | |||
42 | spin_lock_irqsave(&ctx->lock, flags); | ||
43 | ctx->expired = 1; | ||
44 | wake_up_locked(&ctx->wqh); | ||
45 | spin_unlock_irqrestore(&ctx->lock, flags); | ||
46 | |||
47 | return HRTIMER_NORESTART; | ||
48 | } | ||
49 | |||
50 | static void timerfd_setup(struct timerfd_ctx *ctx, int clockid, int flags, | ||
51 | const struct itimerspec *ktmr) | ||
52 | { | ||
53 | enum hrtimer_mode htmode; | ||
54 | ktime_t texp; | ||
55 | |||
56 | htmode = (flags & TFD_TIMER_ABSTIME) ? | ||
57 | HRTIMER_MODE_ABS: HRTIMER_MODE_REL; | ||
58 | |||
59 | texp = timespec_to_ktime(ktmr->it_value); | ||
60 | ctx->expired = 0; | ||
61 | ctx->tintv = timespec_to_ktime(ktmr->it_interval); | ||
62 | hrtimer_init(&ctx->tmr, clockid, htmode); | ||
63 | ctx->tmr.expires = texp; | ||
64 | ctx->tmr.function = timerfd_tmrproc; | ||
65 | if (texp.tv64 != 0) | ||
66 | hrtimer_start(&ctx->tmr, texp, htmode); | ||
67 | } | ||
68 | |||
69 | static int timerfd_release(struct inode *inode, struct file *file) | ||
70 | { | ||
71 | struct timerfd_ctx *ctx = file->private_data; | ||
72 | |||
73 | hrtimer_cancel(&ctx->tmr); | ||
74 | kfree(ctx); | ||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | static unsigned int timerfd_poll(struct file *file, poll_table *wait) | ||
79 | { | ||
80 | struct timerfd_ctx *ctx = file->private_data; | ||
81 | unsigned int events = 0; | ||
82 | unsigned long flags; | ||
83 | |||
84 | poll_wait(file, &ctx->wqh, wait); | ||
85 | |||
86 | spin_lock_irqsave(&ctx->lock, flags); | ||
87 | if (ctx->expired) | ||
88 | events |= POLLIN; | ||
89 | spin_unlock_irqrestore(&ctx->lock, flags); | ||
90 | |||
91 | return events; | ||
92 | } | ||
93 | |||
94 | static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count, | ||
95 | loff_t *ppos) | ||
96 | { | ||
97 | struct timerfd_ctx *ctx = file->private_data; | ||
98 | ssize_t res; | ||
99 | u32 ticks = 0; | ||
100 | DECLARE_WAITQUEUE(wait, current); | ||
101 | |||
102 | if (count < sizeof(ticks)) | ||
103 | return -EINVAL; | ||
104 | spin_lock_irq(&ctx->lock); | ||
105 | res = -EAGAIN; | ||
106 | if (!ctx->expired && !(file->f_flags & O_NONBLOCK)) { | ||
107 | __add_wait_queue(&ctx->wqh, &wait); | ||
108 | for (res = 0;;) { | ||
109 | set_current_state(TASK_INTERRUPTIBLE); | ||
110 | if (ctx->expired) { | ||
111 | res = 0; | ||
112 | break; | ||
113 | } | ||
114 | if (signal_pending(current)) { | ||
115 | res = -ERESTARTSYS; | ||
116 | break; | ||
117 | } | ||
118 | spin_unlock_irq(&ctx->lock); | ||
119 | schedule(); | ||
120 | spin_lock_irq(&ctx->lock); | ||
121 | } | ||
122 | __remove_wait_queue(&ctx->wqh, &wait); | ||
123 | __set_current_state(TASK_RUNNING); | ||
124 | } | ||
125 | if (ctx->expired) { | ||
126 | ctx->expired = 0; | ||
127 | if (ctx->tintv.tv64 != 0) { | ||
128 | /* | ||
129 | * If tintv.tv64 != 0, this is a periodic timer that | ||
130 | * needs to be re-armed. We avoid doing it in the timer | ||
131 | * callback to avoid DoS attacks specifying a very | ||
132 | * short timer period. | ||
133 | */ | ||
134 | ticks = (u32) | ||
135 | hrtimer_forward(&ctx->tmr, | ||
136 | hrtimer_cb_get_time(&ctx->tmr), | ||
137 | ctx->tintv); | ||
138 | hrtimer_restart(&ctx->tmr); | ||
139 | } else | ||
140 | ticks = 1; | ||
141 | } | ||
142 | spin_unlock_irq(&ctx->lock); | ||
143 | if (ticks) | ||
144 | res = put_user(ticks, buf) ? -EFAULT: sizeof(ticks); | ||
145 | return res; | ||
146 | } | ||
147 | |||
148 | static const struct file_operations timerfd_fops = { | ||
149 | .release = timerfd_release, | ||
150 | .poll = timerfd_poll, | ||
151 | .read = timerfd_read, | ||
152 | }; | ||
153 | |||
154 | asmlinkage long sys_timerfd(int ufd, int clockid, int flags, | ||
155 | const struct itimerspec __user *utmr) | ||
156 | { | ||
157 | int error; | ||
158 | struct timerfd_ctx *ctx; | ||
159 | struct file *file; | ||
160 | struct inode *inode; | ||
161 | struct itimerspec ktmr; | ||
162 | |||
163 | if (copy_from_user(&ktmr, utmr, sizeof(ktmr))) | ||
164 | return -EFAULT; | ||
165 | |||
166 | if (clockid != CLOCK_MONOTONIC && | ||
167 | clockid != CLOCK_REALTIME) | ||
168 | return -EINVAL; | ||
169 | if (!timespec_valid(&ktmr.it_value) || | ||
170 | !timespec_valid(&ktmr.it_interval)) | ||
171 | return -EINVAL; | ||
172 | |||
173 | if (ufd == -1) { | ||
174 | ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); | ||
175 | if (!ctx) | ||
176 | return -ENOMEM; | ||
177 | |||
178 | init_waitqueue_head(&ctx->wqh); | ||
179 | spin_lock_init(&ctx->lock); | ||
180 | |||
181 | timerfd_setup(ctx, clockid, flags, &ktmr); | ||
182 | |||
183 | /* | ||
184 | * When we call this, the initialization must be complete, since | ||
185 | * anon_inode_getfd() will install the fd. | ||
186 | */ | ||
187 | error = anon_inode_getfd(&ufd, &inode, &file, "[timerfd]", | ||
188 | &timerfd_fops, ctx); | ||
189 | if (error) | ||
190 | goto err_tmrcancel; | ||
191 | } else { | ||
192 | file = fget(ufd); | ||
193 | if (!file) | ||
194 | return -EBADF; | ||
195 | ctx = file->private_data; | ||
196 | if (file->f_op != &timerfd_fops) { | ||
197 | fput(file); | ||
198 | return -EINVAL; | ||
199 | } | ||
200 | /* | ||
201 | * We need to stop the existing timer before reprogramming | ||
202 | * it to the new values. | ||
203 | */ | ||
204 | for (;;) { | ||
205 | spin_lock_irq(&ctx->lock); | ||
206 | if (hrtimer_try_to_cancel(&ctx->tmr) >= 0) | ||
207 | break; | ||
208 | spin_unlock_irq(&ctx->lock); | ||
209 | cpu_relax(); | ||
210 | } | ||
211 | /* | ||
212 | * Re-program the timer to the new value ... | ||
213 | */ | ||
214 | timerfd_setup(ctx, clockid, flags, &ktmr); | ||
215 | |||
216 | spin_unlock_irq(&ctx->lock); | ||
217 | fput(file); | ||
218 | } | ||
219 | |||
220 | return ufd; | ||
221 | |||
222 | err_tmrcancel: | ||
223 | hrtimer_cancel(&ctx->tmr); | ||
224 | kfree(ctx); | ||
225 | return error; | ||
226 | } | ||
227 | |||
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index e049f14a75b7..fc637be1d9cf 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h | |||
@@ -605,6 +605,8 @@ asmlinkage long sys_set_robust_list(struct robust_list_head __user *head, | |||
605 | size_t len); | 605 | size_t len); |
606 | asmlinkage long sys_getcpu(unsigned __user *cpu, unsigned __user *node, struct getcpu_cache __user *cache); | 606 | asmlinkage long sys_getcpu(unsigned __user *cpu, unsigned __user *node, struct getcpu_cache __user *cache); |
607 | asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemask); | 607 | asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemask); |
608 | asmlinkage long sys_timerfd(int ufd, int clockid, int flags, | ||
609 | const struct itimerspec __user *utmr); | ||
608 | 610 | ||
609 | int kernel_execve(const char *filename, char *const argv[], char *const envp[]); | 611 | int kernel_execve(const char *filename, char *const argv[], char *const envp[]); |
610 | 612 | ||
diff --git a/include/linux/timerfd.h b/include/linux/timerfd.h new file mode 100644 index 000000000000..cf2b10d75731 --- /dev/null +++ b/include/linux/timerfd.h | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * include/linux/timerfd.h | ||
3 | * | ||
4 | * Copyright (C) 2007 Davide Libenzi <davidel@xmailserver.org> | ||
5 | * | ||
6 | */ | ||
7 | |||
8 | #ifndef _LINUX_TIMERFD_H | ||
9 | #define _LINUX_TIMERFD_H | ||
10 | |||
11 | |||
12 | #define TFD_TIMER_ABSTIME (1 << 0) | ||
13 | |||
14 | |||
15 | |||
16 | #endif /* _LINUX_TIMERFD_H */ | ||
17 | |||
diff --git a/init/Kconfig b/init/Kconfig index db707204b751..02c167de9646 100644 --- a/init/Kconfig +++ b/init/Kconfig | |||
@@ -502,6 +502,16 @@ config SIGNALFD | |||
502 | 502 | ||
503 | If unsure, say Y. | 503 | If unsure, say Y. |
504 | 504 | ||
505 | config TIMERFD | ||
506 | bool "Enable timerfd() system call" if EMBEDDED | ||
507 | depends on ANON_INODES | ||
508 | default y | ||
509 | help | ||
510 | Enable the timerfd() system call that allows to receive timer | ||
511 | events on a file descriptor. | ||
512 | |||
513 | If unsure, say Y. | ||
514 | |||
505 | config SHMEM | 515 | config SHMEM |
506 | bool "Use full shmem filesystem" if EMBEDDED | 516 | bool "Use full shmem filesystem" if EMBEDDED |
507 | default y | 517 | default y |
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c index 807e9bb8fcdb..b18f62549515 100644 --- a/kernel/sys_ni.c +++ b/kernel/sys_ni.c | |||
@@ -144,3 +144,4 @@ cond_syscall(sys_ioprio_get); | |||
144 | 144 | ||
145 | /* New file descriptors */ | 145 | /* New file descriptors */ |
146 | cond_syscall(sys_signalfd); | 146 | cond_syscall(sys_signalfd); |
147 | cond_syscall(sys_timerfd); | ||