| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <linux/futex.h> |
| #include <sys/syscall.h> |
| #include <sys/time.h> |
| #include <pthread.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #define nil ((void*)0) |
| |
| /* |
| * gcc implementation of src/pkg/runtime/linux/thread.c |
| */ |
| typedef struct Lock Lock; |
| typedef struct Note Note; |
| typedef uint32_t uint32; |
| |
| struct Lock |
| { |
| uint32 key; |
| uint32 sema; // ignored |
| }; |
| |
| struct Note |
| { |
| Lock lock; |
| uint32 pad; |
| }; |
| |
| static struct timespec longtime = |
| { |
| 1<<30, // 34 years |
| 0 |
| }; |
| |
| static int |
| cas(uint32 *val, uint32 old, uint32 new) |
| { |
| int ret; |
| |
| __asm__ __volatile__( |
| "lock; cmpxchgl %2, 0(%3)\n" |
| "setz %%al\n" |
| : "=a" (ret) |
| : "a" (old), |
| "r" (new), |
| "r" (val) |
| : "memory", "cc" |
| ); |
| |
| return ret & 1; |
| } |
| |
| static void |
| futexsleep(uint32 *addr, uint32 val) |
| { |
| int ret; |
| |
| ret = syscall(SYS_futex, (int*)addr, FUTEX_WAIT, val, &longtime, nil, 0); |
| if(ret >= 0 || errno == EAGAIN || errno == EINTR) |
| return; |
| fprintf(stderr, "futexsleep: %s\n", strerror(errno)); |
| *(int*)0 = 0; |
| } |
| |
| static void |
| futexwakeup(uint32 *addr) |
| { |
| int ret; |
| |
| ret = syscall(SYS_futex, (int*)addr, FUTEX_WAKE, 1, nil, nil, 0); |
| if(ret >= 0) |
| return; |
| fprintf(stderr, "futexwakeup: %s\n", strerror(errno)); |
| *(int*)0 = 0; |
| } |
| |
| static void |
| futexlock(Lock *l) |
| { |
| uint32 v; |
| |
| again: |
| v = l->key; |
| if((v&1) == 0){ |
| if(cas(&l->key, v, v|1)){ |
| // Lock wasn't held; we grabbed it. |
| return; |
| } |
| goto again; |
| } |
| |
| if(!cas(&l->key, v, v+2)) |
| goto again; |
| |
| futexsleep(&l->key, v+2); |
| for(;;){ |
| v = l->key; |
| if((int)v < 2) { |
| fprintf(stderr, "futexsleep: invalid key %d\n", (int)v); |
| *(int*)0 = 0; |
| } |
| if(cas(&l->key, v, v-2)) |
| break; |
| } |
| goto again; |
| } |
| |
| static void |
| futexunlock(Lock *l) |
| { |
| uint32 v; |
| |
| again: |
| v = l->key; |
| if((v&1) == 0) |
| *(int*)0 = 0; |
| if(!cas(&l->key, v, v&~1)) |
| goto again; |
| |
| // If there were waiters, wake one. |
| if(v & ~1) |
| futexwakeup(&l->key); |
| } |
| |
| static void |
| lock(Lock *l) |
| { |
| futexlock(l); |
| } |
| |
| static void |
| unlock(Lock *l) |
| { |
| futexunlock(l); |
| } |
| |
| void |
| noteclear(Note *n) |
| { |
| n->lock.key = 0; |
| futexlock(&n->lock); |
| } |
| |
| static void |
| notewakeup(Note *n) |
| { |
| futexunlock(&n->lock); |
| } |
| |
| static void |
| notesleep(Note *n) |
| { |
| futexlock(&n->lock); |
| futexunlock(&n->lock); |
| } |
| |
| /* |
| * runtime Cgo server. |
| * gcc half of src/pkg/runtime/cgocall.c |
| */ |
| |
| typedef struct CgoWork CgoWork; |
| typedef struct CgoServer CgoServer; |
| typedef struct Cgo Cgo; |
| |
| struct Cgo |
| { |
| Lock lock; |
| CgoServer *idle; |
| CgoWork *whead; |
| CgoWork *wtail; |
| }; |
| |
| struct CgoServer |
| { |
| CgoServer *next; |
| Note note; |
| CgoWork *work; |
| }; |
| |
| struct CgoWork |
| { |
| CgoWork *next; |
| Note note; |
| void (*fn)(void*); |
| void *arg; |
| }; |
| |
| Cgo cgo; |
| |
| static void newserver(void); |
| |
| void |
| initcgo(void) |
| { |
| newserver(); |
| } |
| |
| static void* go_pthread(void*); |
| |
| /* |
| * allocate servers to handle any work that has piled up |
| * and one more server to sit idle and wait for new work. |
| */ |
| static void |
| newserver(void) |
| { |
| CgoServer *f; |
| CgoWork *w, *next; |
| pthread_t p; |
| |
| lock(&cgo.lock); |
| // kick off new servers with work to do |
| for(w=cgo.whead; w; w=next) { |
| next = w; |
| w->next = nil; |
| f = malloc(sizeof *f); |
| memset(f, 0, sizeof *f); |
| f->work = w; |
| noteclear(&f->note); |
| notewakeup(&f->note); |
| if(pthread_create(&p, nil, go_pthread, f) < 0) { |
| fprintf(stderr, "pthread_create: %s\n", strerror(errno)); |
| *(int*)0 = 0; |
| } |
| } |
| cgo.whead = nil; |
| cgo.wtail = nil; |
| |
| // kick off one more server to sit idle |
| if(cgo.idle == nil) { |
| f = malloc(sizeof *f); |
| memset(f, 0, sizeof *f); |
| f->next = cgo.idle; |
| noteclear(&f->note); |
| cgo.idle = f; |
| if(pthread_create(&p, nil, go_pthread, f) < 0) { |
| fprintf(stderr, "pthread_create: %s\n", strerror(errno)); |
| *(int*)0 = 0; |
| } |
| } |
| unlock(&cgo.lock); |
| } |
| |
| static void* |
| go_pthread(void *v) |
| { |
| CgoServer *f; |
| CgoWork *w; |
| |
| // newserver queued us; wait for work |
| f = v; |
| goto wait; |
| |
| for(;;) { |
| // kick off new server to handle requests while we work |
| newserver(); |
| |
| // do work |
| w = f->work; |
| w->fn(w->arg); |
| notewakeup(&w->note); |
| f->work = nil; |
| |
| // take some work if available |
| lock(&cgo.lock); |
| if((w = cgo.whead) != nil) { |
| cgo.whead = w->next; |
| if(cgo.whead == nil) |
| cgo.wtail = nil; |
| unlock(&cgo.lock); |
| f->work = w; |
| continue; |
| } |
| |
| // otherwise queue |
| f->work = nil; |
| noteclear(&f->note); |
| f->next = cgo.idle; |
| cgo.idle = f; |
| unlock(&cgo.lock); |
| |
| wait: |
| // wait for work |
| notesleep(&f->note); |
| } |
| } |
| |