/* * Author : WangBoJing , email : 1989wangbojing@gmail.com * * Copyright Statement: * -------------------- * This software is protected by Copyright and the information contained * herein is confidential. The software may not be copied and the information * contained herein may not be used or disclosed except with the written * permission of Author. (C) 2017 * * **** ***** ***** *** * ** *** *** * * * ** * ** * * ** ** * ** * * ** * * ** * ** ** * * ** * *** ** * ** * *********** ***** ***** ** **** * ** * ** ** ** ** ** ** * ** * ** ** * ** * ** * ** * ** * * ** ** ** * ** * ** ** * ** * ** * ** * ** * * ** ** ** * ** * ** ** * ** ** ** * ** * ** ** * ** ** ** * ** * ** * * ** ** ** * ** * ** ** * ** * ** ** * *** ** * * ** * ** ** * *** ** * ** * * ** ** * ** ** * ** ** * ** ** * ** ** * * ** * ** ** ***** * **** * ***** **** * * ***** **** * */ #include "nty_coroutine.h" pthread_key_t global_sched_key; static pthread_once_t sched_key_once = PTHREAD_ONCE_INIT; // https://github.com/halayli/lthread/blob/master/src/lthread.c#L58 #ifdef _USE_UCONTEXT static void _save_stack(nty_coroutine *co) { char* top = co->sched->stack + co->sched->stack_size; char dummy = 0; assert(top - &dummy <= NTY_CO_MAX_STACKSIZE); if (co->stack_size < top - &dummy) { co->stack = realloc(co->stack, top - &dummy); assert(co->stack != NULL); } co->stack_size = top - &dummy; memcpy(co->stack, &dummy, co->stack_size); } static void _load_stack(nty_coroutine *co) { memcpy(co->sched->stack + co->sched->stack_size - co->stack_size, co->stack, co->stack_size); } static void _exec(void *lt) { nty_coroutine *co = (nty_coroutine*)lt; co->func(co->arg); co->status |= (BIT(NTY_COROUTINE_STATUS_EXITED) | BIT(NTY_COROUTINE_STATUS_FDEOF) | BIT(NTY_COROUTINE_STATUS_DETACH)); nty_coroutine_yield(co); } #else int _switch(nty_cpu_ctx *new_ctx, nty_cpu_ctx *cur_ctx); #ifdef __i386__ __asm__ ( " .text \n" " .p2align 2,,3 \n" ".globl _switch \n" "_switch: \n" "__switch: \n" "movl 8(%esp), %edx # fs->%edx \n" "movl %esp, 0(%edx) # save esp \n" "movl %ebp, 4(%edx) # save ebp \n" "movl (%esp), %eax # save eip \n" "movl %eax, 8(%edx) \n" "movl %ebx, 12(%edx) # save ebx,esi,edi \n" "movl %esi, 16(%edx) \n" "movl %edi, 20(%edx) \n" "movl 4(%esp), %edx # ts->%edx \n" "movl 20(%edx), %edi # restore ebx,esi,edi \n" "movl 16(%edx), %esi \n" "movl 12(%edx), %ebx \n" "movl 0(%edx), %esp # restore esp \n" "movl 4(%edx), %ebp # restore ebp \n" "movl 8(%edx), %eax # restore eip \n" "movl %eax, (%esp) \n" "ret \n" ); #elif defined(__x86_64__) __asm__ ( " .text \n" " .p2align 4,,15 \n" ".globl _switch \n" ".globl __switch \n" "_switch: \n" "__switch: \n" " movq %rsp, 0(%rsi) # save stack_pointer \n" " movq %rbp, 8(%rsi) # save frame_pointer \n" " movq (%rsp), %rax # save insn_pointer \n" " movq %rax, 16(%rsi) \n" " movq %rbx, 24(%rsi) # save rbx,r12-r15 \n" " movq %r12, 32(%rsi) \n" " movq %r13, 40(%rsi) \n" " movq %r14, 48(%rsi) \n" " movq %r15, 56(%rsi) \n" " movq 56(%rdi), %r15 \n" " movq 48(%rdi), %r14 \n" " movq 40(%rdi), %r13 # restore rbx,r12-r15 \n" " movq 32(%rdi), %r12 \n" " movq 24(%rdi), %rbx \n" " movq 8(%rdi), %rbp # restore frame_pointer \n" " movq 0(%rdi), %rsp # restore stack_pointer \n" " movq 16(%rdi), %rax # restore insn_pointer \n" " movq %rax, (%rsp) \n" " ret \n" ); #endif static void _exec(void *lt) { #if defined(__lvm__) && defined(__x86_64__) __asm__("movq 16(%%rbp), %[lt]" : [lt] "=r" (lt)); #endif nty_coroutine *co = (nty_coroutine*)lt; co->func(co->arg); co->status |= (BIT(NTY_COROUTINE_STATUS_EXITED) | BIT(NTY_COROUTINE_STATUS_FDEOF) | BIT(NTY_COROUTINE_STATUS_DETACH)); #if 1 nty_coroutine_yield(co); #else co->ops = 0; _switch(&co->sched->ctx, &co->ctx); #endif } static inline void nty_coroutine_madvise(nty_coroutine *co) { size_t current_stack = (co->stack + co->stack_size) - co->ctx.esp; assert(current_stack <= co->stack_size); if (current_stack < co->last_stack_size && co->last_stack_size > co->sched->page_size) { size_t tmp = current_stack + (-current_stack & (co->sched->page_size - 1)); assert(madvise(co->stack, co->stack_size-tmp, MADV_DONTNEED) == 0); } co->last_stack_size = current_stack; } #endif extern int nty_schedule_create(int stack_size); void nty_coroutine_free(nty_coroutine *co) { if (co == NULL) return ; co->sched->spawned_coroutines --; #if 1 if (co->stack) { free(co->stack); co->stack = NULL; } #endif if (co) { free(co); } } static void nty_coroutine_init(nty_coroutine *co) { #ifdef _USE_UCONTEXT getcontext(&co->ctx); co->ctx.uc_stack.ss_sp = co->sched->stack; co->ctx.uc_stack.ss_size = co->sched->stack_size; co->ctx.uc_link = &co->sched->ctx; // printf("TAG21\n"); makecontext(&co->ctx, (void (*)(void)) _exec, 1, (void*)co); // printf("TAG22\n"); #else void **stack = (void **)(co->stack + co->stack_size); stack[-3] = NULL; stack[-2] = (void *)co; co->ctx.esp = (void*)stack - (4 * sizeof(void*)); co->ctx.ebp = (void*)stack - (3 * sizeof(void*)); co->ctx.eip = (void*)_exec; #endif co->status = BIT(NTY_COROUTINE_STATUS_READY); } void nty_coroutine_yield(nty_coroutine *co) { co->ops = 0; #ifdef _USE_UCONTEXT if ((co->status & BIT(NTY_COROUTINE_STATUS_EXITED)) == 0) { _save_stack(co); } swapcontext(&co->ctx, &co->sched->ctx); #else _switch(&co->sched->ctx, &co->ctx); #endif } int nty_coroutine_resume(nty_coroutine *co) { if (co->status & BIT(NTY_COROUTINE_STATUS_NEW)) { nty_coroutine_init(co); } #ifdef _USE_UCONTEXT else { _load_stack(co); } #endif nty_schedule *sched = nty_coroutine_get_sched(); sched->curr_thread = co; #ifdef _USE_UCONTEXT swapcontext(&sched->ctx, &co->ctx); #else _switch(&co->ctx, &co->sched->ctx); nty_coroutine_madvise(co); #endif sched->curr_thread = NULL; #if 1 if (co->status & BIT(NTY_COROUTINE_STATUS_EXITED)) { if (co->status & BIT(NTY_COROUTINE_STATUS_DETACH)) { nty_coroutine_free(co); } return -1; } #endif return 0; } void nty_coroutine_renice(nty_coroutine *co) { co->ops ++; #if 1 if (co->ops < 5) return ; #endif TAILQ_INSERT_TAIL(&nty_coroutine_get_sched()->ready, co, ready_next); nty_coroutine_yield(co); } void nty_coroutine_sleep(uint64_t msecs) { nty_coroutine *co = nty_coroutine_get_sched()->curr_thread; if (msecs == 0) { TAILQ_INSERT_TAIL(&co->sched->ready, co, ready_next); nty_coroutine_yield(co); } else { nty_schedule_sched_sleepdown(co, msecs); } } void nty_coroutine_detach(void) { nty_coroutine *co = nty_coroutine_get_sched()->curr_thread; co->status |= BIT(NTY_COROUTINE_STATUS_DETACH); } static void nty_coroutine_sched_key_destructor(void *data) { free(data); } static void __attribute__((constructor(1000))) nty_coroutine_sched_key_creator(void) { assert(pthread_key_create(&global_sched_key, nty_coroutine_sched_key_destructor) == 0); assert(pthread_setspecific(global_sched_key, NULL) == 0); return ; } // coroutine --> // create // int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg) { assert(pthread_once(&sched_key_once, nty_coroutine_sched_key_creator) == 0); nty_schedule *sched = nty_coroutine_get_sched(); if (sched == NULL) { nty_schedule_create(0); sched = nty_coroutine_get_sched(); if (sched == NULL) { printf("Failed to create scheduler\n"); return -1; } } nty_coroutine *co = calloc(1, sizeof(nty_coroutine)); if (co == NULL) { printf("Failed to allocate memory for new coroutine\n"); return -2; } #ifdef _USE_UCONTEXT co->stack = NULL; co->stack_size = 0; #else int ret = posix_memalign(&co->stack, getpagesize(), sched->stack_size); if (ret) { printf("Failed to allocate stack for new coroutine\n"); free(co); return -3; } co->stack_size = sched->stack_size; #endif co->sched = sched; co->status = BIT(NTY_COROUTINE_STATUS_NEW); // co->id = sched->spawned_coroutines ++; co->func = func; #if CANCEL_FD_WAIT_UINT64 co->fd = -1; co->events = 0; #else co->fd_wait = -1; #endif co->arg = arg; co->birth = nty_coroutine_usec_now(); *new_co = co; TAILQ_INSERT_TAIL(&co->sched->ready, co, ready_next); return 0; }