xref: /dragonfly/test/debug/umtx.c (revision b7cb6098b29b5d9c4d9d8aace381eca12dbd801f)
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