1 /*
2 * UMTX file[:offset] command
3 *
4 * $DragonFly: src/test/debug/umtx.c,v 1.1 2005/01/14 04:15:12 dillon Exp $
5 */
6
7 #include <sys/types.h>
8 #include <sys/wait.h>
9 #include <sys/mman.h>
10 #include <sys/stat.h>
11 #include <errno.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <string.h>
16 #include <fcntl.h>
17 #include <assert.h>
18 #include <signal.h>
19
20 int cmp_and_exg(volatile int *lockp, int old, int new);
21
22 struct umtx {
23 volatile int lock;
24 };
25
26 #define MTX_LOCKED 0x80000000
27
28 static int userland_get_mutex(struct umtx *mtx, int timo);
29 static int userland_get_mutex_contested(struct umtx *mtx, int timo);
30 static void userland_rel_mutex(struct umtx *mtx);
31 static void userland_rel_mutex_contested(struct umtx *mtx);
32 static void docleanup(int signo);
33
34 static struct umtx *cleanup_mtx_contested;
35 static struct umtx *cleanup_mtx_held;
36
37 int verbose_opt;
38
39 int
main(int ac,char ** av)40 main(int ac, char **av)
41 {
42 char *path;
43 char *str;
44 off_t off = 0;
45 pid_t pid;
46 int ch;
47 int fd;
48 int pgsize;
49 int pgmask;
50 int timo = 0;
51 struct stat st;
52 struct umtx *mtx;
53
54 signal(SIGINT, docleanup);
55
56 while ((ch = getopt(ac, av, "t:v")) != -1) {
57 switch(ch) {
58 case 't':
59 timo = strtol(optarg, NULL, 0);
60 break;
61 case 'v':
62 verbose_opt = 1;
63 break;
64 default:
65 fprintf(stderr, "unknown option: -%c\n", optopt);
66 exit(1);
67 }
68 }
69 ac -= optind;
70 av += optind;
71
72 if (ac < 2) {
73 fprintf(stderr, "umtx file[:offset] command\n");
74 exit(1);
75 }
76 path = av[0];
77 if ((str = strchr(path, ':')) != NULL) {
78 *str++ = 0;
79 off = strtoull(str, NULL, 0);
80 }
81 if ((fd = open(path, O_RDWR|O_CREAT, 0666)) < 0) {
82 perror("open");
83 exit(1);
84 }
85 if (fstat(fd, &st) < 0) {
86 perror("fstat");
87 exit(1);
88 }
89 if (off + 4 > st.st_size) {
90 int v = 0;
91 lseek(fd, off, 0);
92 write(fd, &v, sizeof(v));
93 }
94 pgsize = getpagesize();
95 pgmask = pgsize - 1;
96 str = mmap(NULL, pgsize, PROT_READ|PROT_WRITE, MAP_SHARED,
97 fd, off & ~(off_t)pgmask);
98 mtx = (struct umtx *)(str + ((int)off & pgmask));
99 if (userland_get_mutex(mtx, timo) < 0) {
100 fprintf(stderr, "Mutex at %s:%ld timed out\n", path, off);
101 exit(1);
102 }
103 if (verbose_opt)
104 fprintf(stderr, "Obtained mutex at %s:%ld\n", path, off);
105 if ((pid = fork()) == 0) {
106 execvp(av[1], av + 1);
107 _exit(0);
108 } else if (pid > 0) {
109 while (waitpid(pid, NULL, 0) != pid)
110 ;
111 } else {
112 fprintf(stderr, "Unable to exec %s\n", av[1]);
113 }
114 userland_rel_mutex(mtx);
115 close(fd);
116 return(0);
117 }
118
119 static int
userland_get_mutex(struct umtx * mtx,int timo)120 userland_get_mutex(struct umtx *mtx, int timo)
121 {
122 int v;
123
124 for (;;) {
125 v = mtx->lock;
126 if ((v & MTX_LOCKED) == 0) {
127 /*
128 * not locked, attempt to lock.
129 */
130 if (cmp_and_exg(&mtx->lock, v, v | MTX_LOCKED) == 0) {
131 cleanup_mtx_held = mtx;
132 return(0);
133 }
134 } else {
135 /*
136 * Locked, bump the contested count and obtain the contested
137 * mutex.
138 */
139 if (cmp_and_exg(&mtx->lock, v, v + 1) == 0) {
140 cleanup_mtx_contested = mtx;
141 return(userland_get_mutex_contested(mtx, timo));
142 }
143 }
144 }
145 }
146
147 static int
userland_get_mutex_contested(struct umtx * mtx,int timo)148 userland_get_mutex_contested(struct umtx *mtx, int timo)
149 {
150 int v;
151
152 for (;;) {
153 v = mtx->lock;
154 assert(v & ~MTX_LOCKED); /* our contesting count still there */
155 if ((v & MTX_LOCKED) == 0) {
156 /*
157 * not locked, attempt to remove our contested count and
158 * lock at the same time.
159 */
160 if (cmp_and_exg(&mtx->lock, v, (v - 1) | MTX_LOCKED) == 0) {
161 cleanup_mtx_contested = NULL;
162 cleanup_mtx_held = mtx;
163 return(0);
164 }
165 } else {
166 /*
167 * Still locked, sleep and try again.
168 */
169 if (verbose_opt)
170 fprintf(stderr, "waiting on mutex timeout=%d\n", timo);
171 if (timo == 0) {
172 umtx_sleep(&mtx->lock, v, 0);
173 } else {
174 if (umtx_sleep(&mtx->lock, v, 1000000) < 0) {
175 if (errno == EAGAIN && --timo == 0) {
176 cleanup_mtx_contested = NULL;
177 userland_rel_mutex_contested(mtx);
178 return(-1);
179 }
180 }
181 }
182 }
183 }
184 }
185
186 static void
userland_rel_mutex(struct umtx * mtx)187 userland_rel_mutex(struct umtx *mtx)
188 {
189 int v;
190
191 for (;;) {
192 v = mtx->lock;
193 assert(v & MTX_LOCKED); /* we still have it locked */
194 if (v == MTX_LOCKED) {
195 /*
196 * We hold an uncontested lock, try to set to an unlocked
197 * state.
198 */
199 if (cmp_and_exg(&mtx->lock, MTX_LOCKED, 0) == 0) {
200 if (verbose_opt)
201 fprintf(stderr, "releasing uncontested mutex\n");
202 return;
203 }
204 } else {
205 /*
206 * We hold a contested lock, unlock and wakeup exactly
207 * one sleeper. It is possible for this to race a new
208 * thread obtaining a lock, in which case any contested
209 * sleeper we wake up will simply go back to sleep.
210 */
211 if (cmp_and_exg(&mtx->lock, v, v & ~MTX_LOCKED) == 0) {
212 umtx_wakeup(&mtx->lock, 1);
213 if (verbose_opt)
214 fprintf(stderr, "releasing contested mutex\n");
215 return;
216 }
217 }
218 }
219 }
220
221 static void
userland_rel_mutex_contested(struct umtx * mtx)222 userland_rel_mutex_contested(struct umtx *mtx)
223 {
224 int v;
225
226 for (;;) {
227 if (cmp_and_exg(&mtx->lock, v, v - 1) == 0)
228 return;
229 v = mtx->lock;
230 assert(v & ~MTX_LOCKED);
231 }
232 }
233
234 static void
docleanup(int signo)235 docleanup(int signo)
236 {
237 printf("cleanup\n");
238 if (cleanup_mtx_contested)
239 userland_rel_mutex_contested(cleanup_mtx_contested);
240 if (cleanup_mtx_held)
241 userland_rel_mutex(cleanup_mtx_held);
242 exit(1);
243 }
244
245 __asm(
246 " .text\n"
247 "cmp_and_exg:\n"
248 " movl 4(%esp),%ebx\n"
249 " movl 8(%esp),%eax\n"
250 " movl 12(%esp),%edx\n"
251 " lock cmpxchgl %edx,(%ebx)\n"
252 " jz 1f\n"
253 " movl $-1,%eax\n"
254 " ret\n"
255 "1:\n"
256 " subl %eax,%eax\n"
257 " ret\n"
258 );
259
260