├── .gitignore ├── common.mk ├── scripts ├── install-git-hooks ├── pre-push.hook ├── aspell-pws ├── pre-commit.hook └── commit-msg.hook ├── .clang-format ├── tests ├── test-mutex.c ├── test-cond.c ├── test-context.c └── test-yield.c ├── LICENSE ├── Makefile ├── README.md ├── include └── fiber.h └── src └── fiber.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.o.d 3 | tests/*.ok 4 | tests/test-cond 5 | tests/test-mutex 6 | tests/test-yield 7 | tests/test-context 8 | -------------------------------------------------------------------------------- /common.mk: -------------------------------------------------------------------------------- 1 | UNAME_S := $(shell uname -s) 2 | ifeq ($(UNAME_S),Darwin) 3 | PRINTF = printf 4 | else 5 | PRINTF = env printf 6 | endif 7 | 8 | # Control the build verbosity 9 | ifeq ("$(VERBOSE)","1") 10 | Q := 11 | VECHO = @true 12 | else 13 | Q := @ 14 | VECHO = @$(PRINTF) 15 | endif 16 | 17 | PASS_COLOR = \e[32;01m 18 | NO_COLOR = \e[0m 19 | -------------------------------------------------------------------------------- /scripts/install-git-hooks: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! test -d .git; then 4 | echo "Execute scripts/install-git-hooks in the top-level directory." 5 | exit 1 6 | fi 7 | 8 | ln -sf ../../scripts/pre-commit.hook .git/hooks/pre-commit || exit 1 9 | chmod +x .git/hooks/pre-commit 10 | 11 | ln -sf ../../scripts/commit-msg.hook .git/hooks/commit-msg || exit 1 12 | chmod +x .git/hooks/commit-msg 13 | 14 | ln -sf ../../scripts/pre-push.hook .git/hooks/pre-push || exit 1 15 | chmod +x .git/hooks/pre-push 16 | 17 | touch .git/hooks/applied || exit 1 18 | 19 | echo 20 | echo "Git hooks are installed successfully." 21 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | ForEachMacros: 17 | - foreach 18 | - Q_FOREACH 19 | - BOOST_FOREACH 20 | - list_for_each 21 | - list_for_each_safe 22 | - list_for_each_entry 23 | - list_for_each_entry_safe 24 | - hlist_for_each_entry 25 | - rb_list_foreach 26 | - rb_list_foreach_safe 27 | -------------------------------------------------------------------------------- /tests/test-mutex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "fiber.h" 6 | 7 | static fiber_mutex_t mtx; 8 | static int g_val_array[8]; 9 | 10 | static void func(void *data) 11 | { 12 | (void) data; 13 | for (int i = 0; i < 64; ++i) { 14 | fiber_mutex_lock(&mtx); 15 | for (int j = 0; j < 8; ++j) 16 | printf("%d ", ++g_val_array[j]); 17 | printf("\n"); 18 | /* recursive locks are not allowed */ 19 | assert(fiber_mutex_lock(&mtx) == -1); 20 | fiber_mutex_unlock(&mtx); 21 | } 22 | } 23 | 24 | int main() 25 | { 26 | fiber_init(1); 27 | fiber_mutex_init(&mtx); 28 | 29 | fiber_t thread[16]; 30 | for (int i = 0; i < 16; ++i) 31 | fiber_create(&thread[i], &func, NULL); 32 | 33 | fiber_mutex_destroy(&mtx); 34 | fiber_destroy(); 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 National Cheng Kung University, Taiwan. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = \ 2 | context \ 3 | yield \ 4 | mutex \ 5 | cond 6 | TESTS := $(addprefix tests/test-,$(TESTS)) 7 | deps := $(TESTS:%=%.o.d) 8 | 9 | .PHONY: all check clean 10 | GIT_HOOKS := .git/hooks/applied 11 | all: $(GIT_HOOKS) $(TESTS) 12 | 13 | $(GIT_HOOKS): 14 | @scripts/install-git-hooks 15 | @echo 16 | 17 | include common.mk 18 | 19 | CFLAGS = -I./include 20 | CFLAGS += -std=gnu99 -Wall -W 21 | CFLAGS += -O2 -g 22 | CFLAGS += -DUNUSED="__attribute__((unused))" 23 | LDFLAGS = -lpthread 24 | 25 | TESTS_OK = $(TESTS:=.ok) 26 | check: $(TESTS_OK) 27 | 28 | $(TESTS_OK): %.ok: % 29 | $(Q)$(PRINTF) "*** Validating $< ***\n" 30 | $(Q)./$< && $(PRINTF) "\t$(PASS_COLOR)[ Verified ]$(NO_COLOR)\n" 31 | @touch $@ 32 | 33 | # standard build rules 34 | .SUFFIXES: .o .c 35 | .c.o: 36 | $(VECHO) " CC\t$@\n" 37 | $(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $< 38 | 39 | OBJS = \ 40 | src/fiber.o 41 | deps += $(OBJS:%.o=%.o.d) 42 | 43 | $(TESTS): %: %.o $(OBJS) 44 | $(VECHO) " LD\t$@\n" 45 | $(Q)$(CC) -o $@ $^ $(LDFLAGS) 46 | 47 | clean: 48 | $(VECHO) " Cleaning...\n" 49 | $(Q)$(RM) $(TESTS) $(TESTS_OK) $(TESTS:=.o) $(OBJS) $(deps) 50 | 51 | -include $(deps) 52 | -------------------------------------------------------------------------------- /scripts/pre-push.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | protected_branch='master' 4 | current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') 5 | RED='\033[0;31m' 6 | GREEN='\033[1;32m' 7 | YELLOW='\033[1;33m' 8 | NC='\033[0m' # No Color 9 | 10 | # Show hints 11 | echo -e "${YELLOW}Hint${NC}: You might want to know why Git is always ${GREEN}asking for my password${NC}." 12 | echo -e " https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password" 13 | echo "" 14 | 15 | # only run this if you are pushing to master 16 | if [[ $current_branch = $protected_branch ]] ; then 17 | echo -e "${YELLOW}Running pre push to master check...${NC}" 18 | 19 | echo -e "${YELLOW}Trying to build tests project...${NC}" 20 | 21 | # build the project 22 | make 23 | 24 | # $? is a shell variable which stores the return code from what we just ran 25 | rc=$? 26 | if [[ $rc != 0 ]] ; then 27 | echo -e "${RED}Failed to build the project, please fix this and push again${NC}" 28 | echo "" 29 | exit $rc 30 | fi 31 | 32 | # Everything went OK so we can exit with a zero 33 | echo -e "${GREEN}Pre-push check passed!${NC}" 34 | echo "" 35 | fi 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /tests/test-cond.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fiber.h" 5 | 6 | static fiber_mutex_t mtx; 7 | static fiber_cond_t cond; 8 | 9 | static int i = 0; 10 | 11 | static void func1(void *data) 12 | { 13 | (void) data; 14 | for (i = 1; i <= 6; i++) { 15 | fiber_mutex_lock(&mtx); 16 | printf("thread1: lock %d\n", __LINE__); 17 | if (i % 3 == 0) { 18 | printf("thread1:signal 1 %d\n", __LINE__); 19 | fiber_cond_signal(&cond); 20 | printf("thread1:signal 2 %d\n", __LINE__); 21 | } 22 | fiber_mutex_unlock(&mtx); 23 | printf("thread1: unlock %d\n\n", __LINE__); 24 | fiber_yield(); 25 | } 26 | } 27 | 28 | static void func2(void *data) 29 | { 30 | (void) data; 31 | while (i < 6) { 32 | fiber_mutex_lock(&mtx); 33 | printf("thread2: lock %d\n", __LINE__); 34 | if (i % 3 != 0) { 35 | printf("thread2: wait 1 %d\n", __LINE__); 36 | fiber_cond_wait(&cond, &mtx); 37 | printf("thread2: wait 2 %d\n", __LINE__); 38 | } 39 | fiber_mutex_unlock(&mtx); 40 | printf("thread2: unlock %d\n\n", __LINE__); 41 | fiber_yield(); 42 | } 43 | } 44 | 45 | int main() 46 | { 47 | fiber_init(2); 48 | fiber_mutex_init(&mtx); 49 | fiber_cond_init(&cond); 50 | 51 | fiber_t t1, t2; 52 | fiber_create(&t1, func1, NULL); 53 | fiber_create(&t2, func2, NULL); 54 | 55 | fiber_cond_destroy(&cond); 56 | fiber_mutex_destroy(&mtx); 57 | fiber_destroy(); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /tests/test-context.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Purpose: the use of getcontext, swapcontext, makecontext, etc. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static ucontext_t ping_ctx, pong_ctx; 12 | static double p = 0.9; 13 | static void ping() 14 | { 15 | puts("Ping!"); 16 | fflush(stdout); 17 | 18 | while (rand() / (RAND_MAX + 1.0) < p) { 19 | puts(" ping >"); 20 | swapcontext(&ping_ctx, &pong_ctx); 21 | } 22 | } 23 | 24 | static void pong() 25 | { 26 | puts("Pong!"); 27 | fflush(stdout); 28 | 29 | while (rand() / (RAND_MAX + 1.0) < p) { 30 | puts("< pong"); 31 | swapcontext(&pong_ctx, &ping_ctx); 32 | } 33 | } 34 | 35 | int main() 36 | { 37 | ucontext_t main_ctx; 38 | 39 | srand(getpid()); 40 | 41 | /* getcontext must be called on a context object before makecontext */ 42 | if (getcontext(&ping_ctx) == -1 || getcontext(&pong_ctx) == -1) { 43 | perror("getcontext"); 44 | exit(EXIT_FAILURE); 45 | } 46 | 47 | /* Allocate a new stacks */ 48 | ping_ctx.uc_stack.ss_sp = malloc(SIGSTKSZ); 49 | ping_ctx.uc_stack.ss_size = SIGSTKSZ; 50 | 51 | pong_ctx.uc_stack.ss_sp = malloc(SIGSTKSZ); 52 | pong_ctx.uc_stack.ss_size = SIGSTKSZ; 53 | 54 | /* Set the successor context */ 55 | ping_ctx.uc_link = &main_ctx; 56 | pong_ctx.uc_link = &main_ctx; 57 | 58 | /* makecontext sets the starting routine for the context */ 59 | makecontext(&ping_ctx, ping, 1, &p); 60 | makecontext(&pong_ctx, pong, 1, &p); 61 | 62 | /* ping serves. When we switch back to main_ctx, control resumes here. */ 63 | if (swapcontext(&main_ctx, &ping_ctx) == -1) { 64 | perror("swapcontext"); 65 | exit(EXIT_FAILURE); 66 | } 67 | 68 | free(ping_ctx.uc_stack.ss_sp); 69 | free(pong_ctx.uc_stack.ss_sp); 70 | 71 | printf("main: exiting\n"); 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /scripts/aspell-pws: -------------------------------------------------------------------------------- 1 | personal_ws-1.1 en 500 2 | usr 3 | lib 4 | sbin 5 | env 6 | bash 7 | etc 8 | var 9 | dudect 10 | runtime 11 | todo 12 | fixme 13 | hotfix 14 | qtest 15 | vscode 16 | sanitizer 17 | unix 18 | linux 19 | valgrind 20 | ubuntu 21 | gdb 22 | sdk 23 | aspell 24 | cppcheck 25 | glibc 26 | git 27 | pre 28 | gcc 29 | clang 30 | enqueue 31 | dequeue 32 | fifo 33 | lifo 34 | stdin 35 | stdout 36 | stderr 37 | strdup 38 | strcmp 39 | strcasecmp 40 | snprintf 41 | sprintf 42 | strcat 43 | strchr 44 | strcmp 45 | strcoll 46 | strcpy 47 | strcspn 48 | strerror 49 | strlen 50 | strncasecmp 51 | strncat 52 | strncmp 53 | strncpy 54 | strpbrk 55 | strrchr 56 | strspn 57 | strstr 58 | strtod 59 | strtof 60 | strtok 61 | strtol 62 | strtold 63 | strtoul 64 | atexit 65 | atof 66 | atoi 67 | atol 68 | bsearch 69 | calloc 70 | fclose 71 | fdopen 72 | feof 73 | ferror 74 | fflush 75 | fgetc 76 | fgetpos 77 | fgets 78 | fileno 79 | fopen 80 | fprintf 81 | fputc 82 | fputs 83 | fread 84 | freopen 85 | fscanf 86 | fseek 87 | fsetpos 88 | ftell 89 | fwrite 90 | getc 91 | getchar 92 | getenv 93 | gets 94 | isalnum 95 | isalpha 96 | isascii 97 | iscntrl 98 | isdigit 99 | isgraph 100 | islower 101 | isprint 102 | ispunct 103 | isspace 104 | isupper 105 | longjmp 106 | memchr 107 | memcmp 108 | memcpy 109 | memmove 110 | memset 111 | printf 112 | putc 113 | putchar 114 | putenv 115 | puts 116 | qsort 117 | rand 118 | realloc 119 | regcomp 120 | regerror 121 | regexec 122 | regfree 123 | rewind 124 | scanf 125 | setbuf 126 | setjmp 127 | signal 128 | srand 129 | sscanf 130 | macOS 131 | Fibonacci 132 | fib 133 | pow 134 | Binet 135 | Vorobev 136 | GMP 137 | MPFR 138 | mutex 139 | trylock 140 | unlock 141 | lseek 142 | llseek 143 | cdev 144 | inode 145 | sysfs 146 | printk 147 | clz 148 | ctz 149 | popcount 150 | fops 151 | init 152 | alloc 153 | ktime 154 | getres 155 | gettime 156 | settime 157 | ns 158 | timespec 159 | timeval 160 | NaN 161 | livepatch 162 | MathEx 163 | vec 164 | expr 165 | httpd 166 | daemon 167 | bench 168 | benchmark 169 | htstress 170 | workqueue 171 | percpu 172 | cmwq 173 | wq 174 | epoll 175 | kthread 176 | sock 177 | tcp 178 | udp 179 | msg 180 | http 181 | kecho 182 | khttpd 183 | KLT 184 | ULT 185 | mutex 186 | sem 187 | cond 188 | condvar 189 | timeslice 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fiber: A User Space Threading Library 2 | 3 | Fiber is a lightweight thread library with M:N mapping between user-level 4 | thread (ULT) and Linux native thread (or kernel-level thread, KLT). 5 | 6 | ## Features 7 | * Preemptive user-level threads 8 | * Familiar threading concepts are available 9 | - Mutexes 10 | - Condition variables 11 | 12 | ## Implementation Details 13 | 14 | The preemptive scheduler is implemented through timer and signal functions. 15 | In `k_thread_exec_func()` function, a timer is initiated through the following: 16 | ```c 17 | setitimer(ITIMER_PROF, ×lice, NULL) 18 | ``` 19 | 20 | When the timer expires, signal `SIGPROF` is sent to the process. 21 | `sigaction()` would invoke the scheduling routine `schedule()` to run, which 22 | chooses a thread from a run queue to run. The scheduler maintains a run queue. 23 | The new created threads are pushed into the end of the queue. The first thread 24 | in the head of the queue is the thread currently running. Each time the 25 | scheduler receives signal `SIGPROF`, it interrupts the running thread at the 26 | head by pushing this thread into the end of the queue. In addition, it swaps 27 | the context between this thread and next thread which is the new head of the 28 | queue. 29 | 30 | A userspace program/process may not create a kernel thread. Instead, it could 31 | create a *native* thread using `pthread_create`, which invokes the `clone` 32 | system call to do so. Inside Fiber, `clone` system call is used for creating 33 | kernel-level threads. With a kernel that understands threads, we use `clone`, 34 | but we still have to create the new thread's stack. The kernel does not 35 | create/assign a stack for a new thread. The `clone` system call accepts a 36 | `child_stack` argument. Therefore, `fiber_create` must allocate a stack for 37 | the new thread and pass that to clone: 38 | ```c 39 | /* invoke the clone system call to create a native thread */ 40 | if (-1 == clone((int (*)(void *)) k_thread_exec_func, 41 | (char *) stack + _THREAD_STACK, 42 | SIGCHLD | CLONE_SIGHAND | CLONE_VM | CLONE_PTRACE, NULL)) { 43 | perror("Failed to invoke clone system call."); 44 | ... 45 | } 46 | ``` 47 | 48 | Only a process or main thread is assigned its initial stack by the kernel, 49 | usually at a high memory address. Thus, if the process does not use threads, 50 | normally, it just uses that pre-assigned stack. But, if a thread is created, 51 | i.e., the native thread, the starting process/thread must pre-allocate the 52 | area for the proposed thread with malloc. 53 | 54 | ## License 55 | `fiber` is released under the MIT License. Use of this source code is governed 56 | by a MIT License that can be found in the LICENSE file. 57 | -------------------------------------------------------------------------------- /scripts/pre-commit.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CPPCHECK_suppress="--suppress=missingInclude \ 4 | --suppress=unusedFunction:src/fiber.c \ 5 | --inline-suppr" 6 | CPPCHECK_OPTS="-I. --enable=all --error-exitcode=1 --force $CPPCHECK_suppress ." 7 | 8 | RETURN=0 9 | CLANG_FORMAT=$(which clang-format) 10 | if [ $? -ne 0 ]; then 11 | echo "[!] clang-format not installed. Unable to check source file format policy." >&2 12 | exit 1 13 | fi 14 | 15 | CPPCHECK=$(which cppcheck) 16 | if [ $? -ne 0 ]; then 17 | echo "[!] cppcheck not installed. Unable to perform static analysis." >&2 18 | exit 1 19 | fi 20 | 21 | ASPELL=$(which aspell) 22 | if [ $? -ne 0 ]; then 23 | echo "[!] aspell not installed. Unable to do spelling check." >&2 24 | exit 1 25 | fi 26 | 27 | DIFF=$(which colordiff) 28 | if [ $? -ne 0 ]; then 29 | DIFF=diff 30 | fi 31 | 32 | FILES=`git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(c|cpp|h)$"` 33 | for FILE in $FILES; do 34 | nf=`git checkout-index --temp $FILE | cut -f 1` 35 | tempdir=`mktemp -d` || exit 1 36 | newfile=`mktemp ${tempdir}/${nf}.XXXXXX` || exit 1 37 | basename=`basename $FILE` 38 | 39 | source="${tempdir}/${basename}" 40 | mv $nf $source 41 | cp .clang-format $tempdir 42 | $CLANG_FORMAT $source > $newfile 2>> /dev/null 43 | $DIFF -u -p -B --label="modified $FILE" --label="expected coding style" \ 44 | "${source}" "${newfile}" 45 | r=$? 46 | rm -rf "${tempdir}" 47 | if [ $r != 0 ] ; then 48 | echo "[!] $FILE does not follow the consistent coding style." >&2 49 | RETURN=1 50 | fi 51 | if [ $RETURN -eq 1 ]; then 52 | echo "" >&2 53 | echo "Make sure you indent as the following:" >&2 54 | echo " clang-format -i $FILE" >&2 55 | echo 56 | fi 57 | done 58 | 59 | # Prevent unsafe functions 60 | root=$(git rev-parse --show-toplevel) 61 | banned="([^f]gets\()|(sprintf\()|(strcpy\()" 62 | status=0 63 | for file in $(git diff --staged --name-only | grep -E "\.(c|cc|cpp|h|hh|hpp)\$") 64 | do 65 | filepath="${root}/${file}" 66 | output=$(grep -nrE "${banned}" "${filepath}") 67 | if [ ! -z "${output}" ]; then 68 | echo "Dangerous function detected in ${filepath}" 69 | echo "${output}" 70 | echo 71 | echo "Read 'Common vulnerabilities guide for C programmers' carefully." 72 | echo " https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml" 73 | RETURN=1 74 | fi 75 | done 76 | 77 | # static analysis 78 | $CPPCHECK $CPPCHECK_OPTS >/dev/null 79 | if [ $? -ne 0 ]; then 80 | RETURN=1 81 | echo "" >&2 82 | echo "Fail to pass static analysis." >&2 83 | echo 84 | fi 85 | 86 | exit $RETURN 87 | -------------------------------------------------------------------------------- /include/fiber.h: -------------------------------------------------------------------------------- 1 | #ifndef FIBER_H 2 | #define FIBER_H 3 | 4 | #include 5 | 6 | typedef uint fiber_t; 7 | 8 | /* Task linked list */ 9 | typedef struct list_node { 10 | struct list_node *next, *prev; 11 | } list_node; 12 | 13 | /* Fiber status */ 14 | typedef enum { 15 | NOT_STARTED = 0, 16 | RUNNING, 17 | SUSPENDED, 18 | TERMINATED, 19 | FINISHED, 20 | } fiber_status; 21 | 22 | typedef enum { 23 | RR = 0, /**< round-robin */ 24 | } fiber_sched_policy; 25 | 26 | /* user_level thread control block (TCB) */ 27 | typedef struct _tcb_internal _tcb; 28 | 29 | typedef struct { 30 | _tcb *owner; 31 | uint lock; 32 | list_node wait_list; 33 | } fiber_mutex_t; 34 | 35 | typedef struct { 36 | list_node wait_list; 37 | fiber_mutex_t list_mutex; 38 | } fiber_cond_t; 39 | 40 | /** 41 | * @brief Initialize Fiber internal data structure. 42 | * 43 | * @param num Specify the number of pre-allocated kernel-level thread, mapping 44 | * beteen user-level thread and kernel-level implementation. 45 | */ 46 | int fiber_init(int num); 47 | void fiber_destroy(void); 48 | 49 | /** 50 | * @brief Create a new thread. 51 | */ 52 | int fiber_create(fiber_t *tid, void (*start_func)(void *), void *arg); 53 | 54 | /** 55 | * @brief Yield the processor to other user level threads voluntarily. 56 | */ 57 | int fiber_yield(); 58 | 59 | /** 60 | * @brief Wait for thread termination. 61 | */ 62 | int fiber_join(fiber_t thread, void **value_ptr); 63 | 64 | /** 65 | * @brief Terminate a thread. 66 | */ 67 | void fiber_exit(void *retval); 68 | 69 | /** 70 | * @brief Initialize the mutex lock. 71 | */ 72 | int fiber_mutex_init(fiber_mutex_t *mutex); 73 | 74 | /** 75 | * @brief Acquire the mutex lock. 76 | */ 77 | int fiber_mutex_lock(fiber_mutex_t *mutex); 78 | 79 | /** 80 | * @brief Release the mutex lock. 81 | */ 82 | int fiber_mutex_unlock(fiber_mutex_t *mutex); 83 | 84 | /** 85 | * @brief Destory the mutex lock. 86 | */ 87 | int fiber_mutex_destroy(fiber_mutex_t *mutex); 88 | 89 | /** 90 | * @brief Initialize condition variable. 91 | */ 92 | int fiber_cond_init(fiber_cond_t *condvar); 93 | 94 | /** 95 | * @brief Wake up all threads on waiting list. 96 | */ 97 | int fiber_cond_broadcast(fiber_cond_t *condvar); 98 | 99 | /** 100 | * @brief Wake up a thread on waiting list. 101 | */ 102 | int fiber_cond_signal(fiber_cond_t *condvar); 103 | 104 | /** 105 | * @brief Wait on a condition. 106 | * Current thread would go to sleep until other thread wakes it up. 107 | */ 108 | int fiber_cond_wait(fiber_cond_t *condvar, fiber_mutex_t *mutex); 109 | 110 | /** 111 | * @brief Destory condition variable. 112 | */ 113 | int fiber_cond_destroy(fiber_cond_t *condvar); 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /tests/test-yield.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "fiber.h" 6 | 7 | static int a[0xFFFF][8]; 8 | 9 | static void test_yield(void *arg) 10 | { 11 | char *t_name = (char *) arg; 12 | fprintf(stdout, "User Level Thread \"%s\" start !\n", t_name); 13 | for (int i = 0; i < 0xFFFF; i++) { 14 | if (i == 0) 15 | fprintf(stdout, 16 | "User Level Thread \"%s\" is running in tid = %d \n", 17 | t_name, (int) syscall(SYS_gettid)); 18 | } 19 | fprintf(stdout, "User Level Thread \"%s\" pause !\n", t_name); 20 | fiber_yield(); 21 | fprintf(stdout, "User Level Thread \"%s\" resume !\n", t_name); 22 | for (int i = 0; i < 0xFFFF; i++) { 23 | if (i == 0) 24 | fprintf(stdout, 25 | "User Level Thread \"%s\" is running in tid = %d \n", 26 | t_name, (int) syscall(SYS_gettid)); 27 | } 28 | fprintf(stdout, "User Level Thread \"%s\" finish !\n", t_name); 29 | } 30 | 31 | static void calc(void *arg) 32 | { 33 | char *t_name = (char *) arg; 34 | int row = atoi(t_name) - 1; 35 | int sum = 0; 36 | int i = 0, j = 0, k = 0; 37 | fprintf(stdout, "User Level Thread \"%s\" start in tid = %d !\n", t_name, 38 | (int) syscall(SYS_gettid)); 39 | while (i < 0x7FFF) { 40 | sum += a[i][row]; 41 | i++; 42 | /* Just do some thing to make it runs longer */ 43 | while (j < 0xFFFFFFF) { 44 | j += 2; 45 | j -= 1; 46 | if (j % 0xFFFFFF == 0) 47 | fprintf(stdout, 48 | "User Level Thread \"%s\" is running in tid = %d !\n", 49 | t_name, (int) syscall(SYS_gettid)); 50 | } 51 | } 52 | fprintf(stdout, "User Level Thread \"%s\" pause !\n", t_name); 53 | fiber_yield(); 54 | fprintf(stdout, "User Level Thread \"%s\" resume in tid = %d !\n", t_name, 55 | (int) syscall(SYS_gettid)); 56 | while (i < 0xFFFF) { 57 | sum += a[i][row]; 58 | i++; 59 | /* Just do some thing to make it runs longer */ 60 | while (k < 0xFFFFFFF) { 61 | k += 2; 62 | k -= 1; 63 | if (k % 0xFFFFFF == 0) 64 | fprintf(stdout, 65 | "User Level Thread \"%s\" is running in tid = %d !\n", 66 | t_name, (int) syscall(SYS_gettid)); 67 | } 68 | } 69 | fprintf(stdout, "User Level Thread \"%s\" finish ! sum[%d] = %d\n", t_name, 70 | row, sum); 71 | } 72 | 73 | int main() 74 | { 75 | fiber_t u1, u2, u3, u4; 76 | 77 | for (int j = 0; j < 0xFFFF; j++) 78 | for (int i = 0; i < 8; i++) 79 | a[j][i] = i; 80 | 81 | fiber_init(2); 82 | 83 | fiber_create(&u1, &test_yield, "1"); 84 | fiber_create(&u2, &test_yield, "2"); 85 | fiber_create(&u3, &calc, "3"); 86 | fiber_create(&u4, &calc, "4"); 87 | 88 | fprintf(stdout, "Main Thread Starts !\n"); 89 | 90 | fiber_join(u1, NULL); 91 | fiber_join(u2, NULL); 92 | fiber_join(u3, NULL); 93 | fiber_join(u4, NULL); 94 | 95 | fiber_destroy(); 96 | 97 | fprintf(stdout, "Main Thread Ends !\n"); 98 | 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /scripts/commit-msg.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # git-good-commit(1) - Git hook to help you write good commit messages. 4 | # Released under the MIT License. 5 | # 6 | # https://github.com/tommarshall/git-good-commit 7 | 8 | COMMIT_MSG_FILE="$1" 9 | COMMIT_MSG_LINES= 10 | HOOK_EDITOR= 11 | SKIP_DISPLAY_WARNINGS=0 12 | WARNINGS= 13 | 14 | RED= 15 | YELLOW= 16 | BLUE= 17 | WHITE= 18 | CYAN= 19 | NC= 20 | 21 | # 22 | # Set colour variables if the output should be coloured. 23 | # 24 | 25 | set_colors() { 26 | local default_color=$(git config --get hooks.goodcommit.color || git config --get color.ui || echo 'auto') 27 | if [[ $default_color == 'always' ]] || [[ $default_color == 'auto' && -t 1 ]]; then 28 | RED='\033[1;31m' 29 | YELLOW='\033[1;33m' 30 | BLUE='\033[1;34m' 31 | WHITE='\033[1;37m' 32 | CYAN='\033[1;36m' 33 | NC='\033[0m' # No Color 34 | fi 35 | } 36 | 37 | # 38 | # Set the hook editor, using the same approach as git. 39 | # 40 | 41 | set_editor() { 42 | # $GIT_EDITOR appears to always be set to `:` when the hook is executed by Git? 43 | # ref: http://stackoverflow.com/q/41468839/885540 44 | # ref: https://github.com/tommarshall/git-good-commit/issues/11 45 | # HOOK_EDITOR=$GIT_EDITOR 46 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$(git config --get core.editor) 47 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$VISUAL 48 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR=$EDITOR 49 | test -z "${HOOK_EDITOR}" && HOOK_EDITOR='vi' 50 | } 51 | 52 | # 53 | # Output prompt help information. 54 | # 55 | 56 | prompt_help() { 57 | echo -e "${RED}$(cat <<-EOF 58 | e - edit commit message 59 | n - abort commit 60 | ? - print help 61 | EOF 62 | )${NC}" 63 | } 64 | 65 | # 66 | # Add a warning with and . 67 | # 68 | 69 | add_warning() { 70 | local line_number=$1 71 | local warning=$2 72 | WARNINGS[$line_number]="${WARNINGS[$line_number]}$warning;" 73 | } 74 | 75 | # 76 | # Output warnings. 77 | # 78 | 79 | display_warnings() { 80 | if [ $SKIP_DISPLAY_WARNINGS -eq 1 ]; then 81 | # if the warnings were skipped then they should be displayed next time 82 | SKIP_DISPLAY_WARNINGS=0 83 | return 84 | fi 85 | 86 | for i in "${!WARNINGS[@]}"; do 87 | printf "%-74s ${WHITE}%s${NC}\n" "${COMMIT_MSG_LINES[$(($i-1))]}" "[line ${i}]" 88 | IFS=';' read -ra WARNINGS_ARRAY <<< "${WARNINGS[$i]}" 89 | for ERROR in "${WARNINGS_ARRAY[@]}"; do 90 | echo -e " ${YELLOW}- ${ERROR}${NC}" 91 | done 92 | done 93 | 94 | echo 95 | echo -e "${RED}$(cat <<-EOF 96 | How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/ 97 | EOF 98 | )${NC}" 99 | } 100 | 101 | # 102 | # Read the contents of the commit msg into an array of lines. 103 | # 104 | 105 | read_commit_message() { 106 | # reset commit_msg_lines 107 | COMMIT_MSG_LINES=() 108 | 109 | # read commit message into lines array 110 | while IFS= read -r; do 111 | 112 | # trim trailing spaces from commit lines 113 | shopt -s extglob 114 | REPLY="${REPLY%%*( )}" 115 | shopt -u extglob 116 | 117 | # ignore comments 118 | [[ $REPLY =~ ^# ]] 119 | test $? -eq 0 || COMMIT_MSG_LINES+=("$REPLY") 120 | 121 | done < $COMMIT_MSG_FILE 122 | } 123 | 124 | # 125 | # Validate the contents of the commmit msg agains the good commit guidelines. 126 | # 127 | 128 | validate_commit_message() { 129 | # reset warnings 130 | WARNINGS=() 131 | 132 | # capture the subject, and remove the 'squash! ' prefix if present 133 | COMMIT_SUBJECT=${COMMIT_MSG_LINES[0]/#squash! /} 134 | 135 | # if the commit is empty there's nothing to validate, we can return here 136 | COMMIT_MSG_STR="${COMMIT_MSG_LINES[*]}" 137 | test -z "${COMMIT_MSG_STR[*]// }" && return; 138 | 139 | # if the commit subject starts with 'fixup! ' there's nothing to validate, we can return here 140 | [[ $COMMIT_SUBJECT == 'fixup! '* ]] && return; 141 | 142 | # skip first token in subject (e.g. issue ID from bugtracker which is validated otherwise) 143 | skipfirsttokeninsubject=$(git config --get hooks.goodcommit.subjectskipfirsttoken || echo 'false') 144 | if [ "$skipfirsttokeninsubject" == "true" ]; then 145 | COMMIT_SUBJECT_TO_PROCESS=${COMMIT_SUBJECT#* } 146 | else 147 | COMMIT_SUBJECT_TO_PROCESS=$COMMIT_SUBJECT 148 | fi 149 | 150 | # 0. Check spelling 151 | # ------------------------------------------------------------------------------ 152 | ASPELL=$(which aspell) 153 | if [ $? -ne 0 ]; then 154 | echo "Aspell not installed - unable to check spelling" 155 | else 156 | LINE_NUMBER=1 157 | MISSPELLED_WORDS=`echo "$COMMIT_MSG_LINES[LINE_NUMBER]" | $ASPELL --lang=en --list --home-dir=scripts --personal=aspell-pws` 158 | if [ -n "$MISSPELLED_WORDS" ]; then 159 | add_warning LINE_NUMBER "Possible misspelled word(s): $MISSPELLED_WORDS" 160 | fi 161 | fi 162 | 163 | # 1. Separate subject from body with a blank line 164 | # ------------------------------------------------------------------------------ 165 | 166 | test ${#COMMIT_MSG_LINES[@]} -lt 1 || test -z "${COMMIT_MSG_LINES[1]}" 167 | test $? -eq 0 || add_warning 2 "Separate subject from body with a blank line" 168 | 169 | # 2. Limit the subject line to configured number of characters 170 | # ------------------------------------------------------------------------------ 171 | 172 | subject_max_length=$(git config --get hooks.goodcommit.subjectmaxlength || echo '60') 173 | test "${#COMMIT_SUBJECT}" -le $subject_max_length 174 | test $? -eq 0 || add_warning 1 "Limit the subject line to $subject_max_length characters (${#COMMIT_SUBJECT} chars)" 175 | 176 | # 3. Capitalize the subject line 177 | # ------------------------------------------------------------------------------ 178 | 179 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*([[:upper:]]{1}[[:lower:]]*|[[:digit:]]+)([[:blank:]]|[[:punct:]]|$) ]] 180 | test $? -eq 0 || add_warning 1 "Capitalize the subject line" 181 | 182 | # 4. Do not end the subject line with a period 183 | # ------------------------------------------------------------------------------ 184 | 185 | [[ ${COMMIT_SUBJECT} =~ [^\.]$ ]] 186 | test $? -eq 0 || add_warning 1 "Do not end the subject line with a period" 187 | 188 | # 5. Use the imperative mood in the subject line 189 | # ------------------------------------------------------------------------------ 190 | 191 | IMPERATIVE_MOOD_BLACKLIST=( 192 | added adds adding 193 | adjusted adjusts adjusting 194 | amended amends amending 195 | avoided avoids avoiding 196 | bumped bumps bumping 197 | changed changes changing 198 | checked checks checking 199 | committed commits committing 200 | copied copies copying 201 | corrected corrects correcting 202 | created creates creating 203 | decreased decreases decreasing 204 | deleted deletes deleting 205 | disabled disables disabling 206 | dropped drops dropping 207 | duplicated duplicates duplicating 208 | enabled enables enabling 209 | excluded excludes excluding 210 | fixed fixes fixing 211 | handled handles handling 212 | implemented implements implementing 213 | improved improves improving 214 | included includes including 215 | increased increases increasing 216 | installed installs installing 217 | introduced introduces introducing 218 | merged merges merging 219 | moved moves moving 220 | pruned prunes pruning 221 | refactored refactors refactoring 222 | released releases releasing 223 | removed removes removing 224 | renamed renames renaming 225 | replaced replaces replacing 226 | resolved resolves resolving 227 | reverted reverts reverting 228 | showed shows showing 229 | tested tests testing 230 | tidied tidies tidying 231 | updated updates updating 232 | used uses using 233 | ) 234 | 235 | # enable case insensitive match 236 | shopt -s nocasematch 237 | 238 | for BLACKLISTED_WORD in "${IMPERATIVE_MOOD_BLACKLIST[@]}"; do 239 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]*$BLACKLISTED_WORD ]] 240 | test $? -eq 0 && add_warning 1 "Use the imperative mood in the subject line, e.g 'fix' not 'fixes'" && break 241 | done 242 | 243 | # disable case insensitive match 244 | shopt -u nocasematch 245 | 246 | # 6. Wrap the body at 72 characters 247 | # ------------------------------------------------------------------------------ 248 | 249 | URL_REGEX='^[[:blank:]]*(https?|ftp|file)://[-A-Za-z0-9\+&@#/%?=~_|!:,.;]*[-A-Za-z0-9\+&@#/%=~_|]' 250 | 251 | for i in "${!COMMIT_MSG_LINES[@]}"; do 252 | LINE_NUMBER=$((i+1)) 253 | test "${#COMMIT_MSG_LINES[$i]}" -le 72 || [[ ${COMMIT_MSG_LINES[$i]} =~ $URL_REGEX ]] 254 | test $? -eq 0 || add_warning $LINE_NUMBER "Wrap the body at 72 characters (${#COMMIT_MSG_LINES[$i]} chars)" 255 | done 256 | 257 | # 7. Use the body to explain what and why vs. how 258 | # ------------------------------------------------------------------------------ 259 | 260 | # ? 261 | 262 | # 8. Do no write single worded commits 263 | # ------------------------------------------------------------------------------ 264 | 265 | COMMIT_SUBJECT_WORDS=(${COMMIT_SUBJECT_TO_PROCESS}) 266 | test "${#COMMIT_SUBJECT_WORDS[@]}" -gt 1 267 | test $? -eq 0 || add_warning 1 "Do no write single worded commits" 268 | 269 | # 9. Do not start the subject line with whitespace 270 | # ------------------------------------------------------------------------------ 271 | 272 | [[ ${COMMIT_SUBJECT_TO_PROCESS} =~ ^[[:blank:]]+ ]] 273 | test $? -eq 1 || add_warning 1 "Do not start the subject line with whitespace" 274 | } 275 | 276 | # 277 | # It's showtime. 278 | # 279 | 280 | set_colors 281 | 282 | set_editor 283 | 284 | if tty >/dev/null 2>&1; then 285 | TTY=$(tty) 286 | else 287 | TTY=/dev/tty 288 | fi 289 | 290 | while true; do 291 | 292 | read_commit_message 293 | 294 | validate_commit_message 295 | 296 | # if there are no WARNINGS are empty then we're good to break out of here 297 | test ${#WARNINGS[@]} -eq 0 && exit 0; 298 | 299 | display_warnings 300 | 301 | # Ask the question (not using "read -p" as it uses stderr not stdout) 302 | echo -en "${CYAN}Proceed with commit? [e/n/?] ${NC}" 303 | 304 | # Read the answer 305 | read REPLY < "$TTY" 306 | 307 | # Check if the reply is valid 308 | case "$REPLY" in 309 | E*|e*) $HOOK_EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;; 310 | N*|n*) exit 1 ;; 311 | *) SKIP_DISPLAY_WARNINGS=1; prompt_help; continue ;; 312 | esac 313 | 314 | done 315 | -------------------------------------------------------------------------------- /src/fiber.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "fiber.h" 18 | 19 | #define _THREAD_STACK 1024 * 32 20 | #define U_THREAD_MAX 16 21 | #define K_THREAD_MAX 4 22 | #define K_CONTEXT_MASK 0b11 /* bitmask for native thread ID */ 23 | #define PRIORITY 16 24 | #define TIME_SLICE 50000 /* in us */ 25 | 26 | /* user-level thread control block (TCB) */ 27 | struct _tcb_internal { 28 | fiber_t tid; /* thread ID */ 29 | fiber_status status; /* thread status */ 30 | ucontext_t context; /* thread contex */ 31 | uint prio; /* thread priority */ 32 | list_node node; /* thread node in queue */ 33 | char stack[1]; /* thread stack pointer */ 34 | }; 35 | 36 | #define GET_TCB(ptr) \ 37 | ((_tcb *) ((char *) (ptr) - (unsigned long long) (&((_tcb *) 0)->node))) 38 | 39 | /* user-level thread queue */ 40 | static list_node thread_queue[PRIORITY]; 41 | 42 | /* current user-level thread context */ 43 | static list_node *cur_thread_node[K_THREAD_MAX]; 44 | 45 | /* native thread context */ 46 | static ucontext_t context_main[K_THREAD_MAX]; 47 | 48 | /* number of active threads */ 49 | static int user_thread_num = 0; 50 | 51 | static __thread int preempt_disable_count = 0; 52 | 53 | /* global spinlock for critical section _queue */ 54 | static uint _spinlock = 0; 55 | 56 | typedef struct { 57 | sem_t semaphore; 58 | unsigned long *val; 59 | } sig_sem; 60 | 61 | /* global semaphore for user-level thread */ 62 | static sig_sem sigsem_thread[U_THREAD_MAX]; 63 | 64 | /* timer management */ 65 | static struct itimerval timeslice; 66 | static struct itimerval zero_timer = {0}; 67 | 68 | #ifndef unlikely 69 | #define unlikely(x) __builtin_expect((x), 0) 70 | #endif 71 | 72 | /* diable schedule of native thread */ 73 | static inline void preempt_disable() 74 | { 75 | __atomic_add_fetch(&preempt_disable_count, 1, __ATOMIC_ACQUIRE); 76 | } 77 | 78 | static inline void preempt_enable() 79 | { 80 | __atomic_sub_fetch(&preempt_disable_count, 1, __ATOMIC_RELEASE); 81 | } 82 | 83 | static inline void spin_lock(uint *lock) 84 | { 85 | preempt_disable(); 86 | while (__atomic_test_and_set(lock, __ATOMIC_ACQUIRE)) 87 | ; 88 | } 89 | 90 | static inline void spin_unlock(uint *lock) 91 | { 92 | __atomic_store_n(lock, 0, __ATOMIC_RELEASE); 93 | preempt_enable(); 94 | } 95 | 96 | static inline bool is_queue_empty(list_node *q) 97 | { 98 | return (bool) (q->prev == q) && (q->next == q); 99 | } 100 | 101 | static inline void enqueue(list_node *q, list_node *node) 102 | { 103 | node->next = q; 104 | q->prev->next = node; 105 | node->prev = q->prev; 106 | q->prev = node; 107 | } 108 | 109 | static inline bool dequeue(list_node *q, list_node **node) 110 | { 111 | if (is_queue_empty(q)) 112 | return false; 113 | 114 | *node = q->next; 115 | q->next = q->next->next; 116 | q->next->prev = q; 117 | 118 | (*node)->next = (*node)->prev = NULL; 119 | return true; 120 | } 121 | 122 | /* Fiber internals */ 123 | 124 | /* FIXME: avoid the use of global variables */ 125 | static int thread_nums = 0; 126 | 127 | int fiber_init(int num) 128 | { 129 | if (num <= 0) 130 | return -1; 131 | thread_nums = num; /* FIXME: validate the number of native threads */ 132 | return 0; 133 | } 134 | 135 | void fiber_destroy() 136 | { 137 | /* FIXME: destroy allocated resources */ 138 | sleep(1); 139 | } 140 | 141 | static int k_thread_exec_func(void *arg); 142 | static void u_thread_exec_func(void (*thread_func)(void *), 143 | void *arg, 144 | _tcb *thread); 145 | 146 | /* create a new thread */ 147 | int fiber_create(fiber_t *tid, void (*start_func)(void *), void *arg) 148 | { 149 | if (user_thread_num == U_THREAD_MAX) { 150 | /* exceed ceiling limit of user lever threads */ 151 | perror("User level threads limit exceeded!"); 152 | return -1; 153 | } 154 | 155 | /* create a TCB for the new thread */ 156 | _tcb *thread = malloc(sizeof(_tcb) + 1 + _THREAD_STACK); 157 | if (!thread) { 158 | perror("Failed to allocate space for thread!"); 159 | return -1; 160 | } 161 | 162 | /* prepare for first user-level thread */ 163 | if (0 == user_thread_num) { 164 | /* Initialize timeslice */ 165 | timeslice.it_value.tv_sec = 0; 166 | timeslice.it_value.tv_usec = TIME_SLICE; 167 | timeslice.it_interval.tv_sec = 0; 168 | timeslice.it_interval.tv_usec = TIME_SLICE; 169 | 170 | thread_queue->prev = thread_queue->next = thread_queue; 171 | 172 | for (int i = 0; i < thread_nums; i++) { 173 | /* allocate space for the newly created thread on stack */ 174 | void *stack = malloc(_THREAD_STACK); 175 | if (!stack) { 176 | perror("Failed to allocate space for stack!"); 177 | free(thread); 178 | return -1; 179 | } 180 | 181 | /* invoke the clone system call to create a native thread */ 182 | if (-1 == clone(k_thread_exec_func, (char *) stack + _THREAD_STACK, 183 | SIGCHLD | CLONE_SIGHAND | CLONE_VM | CLONE_PTRACE, 184 | NULL)) { 185 | perror("Failed to invoke clone system call."); 186 | free(thread); 187 | free(stack); 188 | return -1; 189 | } 190 | } 191 | } 192 | 193 | /* set thread id and level */ 194 | thread->tid = user_thread_num++; 195 | *tid = thread->tid; 196 | 197 | /* set initial priority to be the highest */ 198 | thread->prio = 0; 199 | 200 | /* set node in thread run queue */ 201 | thread->node.next = thread->node.prev = NULL; 202 | 203 | /* initialize sigsem_thread */ 204 | sigsem_thread[thread->tid].val = NULL; 205 | sem_init(&(sigsem_thread[thread->tid].semaphore), 0, 0); 206 | 207 | /* create a context for this user-level thread */ 208 | if (-1 == getcontext(&thread->context)) { 209 | perror("Failed to get uesr context!"); 210 | return -1; 211 | } 212 | 213 | /* set the context to a newly allocated stack */ 214 | thread->context.uc_link = &context_main[0]; 215 | thread->context.uc_stack.ss_sp = thread->stack; 216 | thread->context.uc_stack.ss_size = _THREAD_STACK; 217 | thread->context.uc_stack.ss_flags = 0; 218 | 219 | /* set the context, which calls a wrapper function and then start_func */ 220 | makecontext(&thread->context, (void (*)(void)) & u_thread_exec_func, 3, 221 | start_func, arg, thread); 222 | 223 | /* add newly created thread to the user-level thread run queue */ 224 | spin_lock(&_spinlock); 225 | 226 | enqueue(thread_queue + thread->prio, &thread->node); 227 | spin_unlock(&_spinlock); 228 | 229 | return 0; 230 | } 231 | 232 | /* give CPU pocession to other user-level threads voluntarily */ 233 | int fiber_yield() 234 | { 235 | uint k_tid = (uint) syscall(SYS_gettid); 236 | _tcb *cur_tcb = GET_TCB(cur_thread_node[k_tid & K_CONTEXT_MASK]); 237 | spin_lock(&_spinlock); 238 | 239 | if (RUNNING == cur_tcb->status) { 240 | cur_tcb->status = SUSPENDED; 241 | enqueue(thread_queue + cur_tcb->prio, 242 | cur_thread_node[k_tid & K_CONTEXT_MASK]); 243 | spin_unlock(&_spinlock); 244 | 245 | swapcontext(&(cur_tcb->context), &context_main[k_tid & K_CONTEXT_MASK]); 246 | } else 247 | spin_unlock(&_spinlock); 248 | return 0; 249 | } 250 | 251 | /* wait for thread termination */ 252 | int fiber_join(fiber_t thread, void **value_ptr) 253 | { 254 | /* do P() in thread semaphore until the certain user-level thread is done */ 255 | sem_wait(&(sigsem_thread[thread].semaphore)); 256 | /* get the value's location passed to fiber_exit */ 257 | if (value_ptr && sigsem_thread[thread].val) 258 | memcpy((unsigned long *) *value_ptr, sigsem_thread[thread].val, 259 | sizeof(unsigned long)); 260 | return 0; 261 | } 262 | 263 | /* terminate a thread */ 264 | void fiber_exit(void *retval) 265 | { 266 | uint k_tid = (uint) syscall(SYS_gettid); 267 | _tcb *cur_tcb = GET_TCB(cur_thread_node[k_tid & K_CONTEXT_MASK]); 268 | fiber_t currefiber_id = cur_tcb->tid; 269 | 270 | cur_tcb->status = TERMINATED; 271 | /* When this thread finished, delete TCB and yield CPU control */ 272 | user_thread_num--; 273 | 274 | sigsem_thread[currefiber_id].val = malloc(sizeof(unsigned long)); 275 | memcpy(sigsem_thread[currefiber_id].val, retval, sizeof(unsigned long)); 276 | 277 | spin_lock(&_spinlock); 278 | enqueue(thread_queue + cur_tcb->prio, 279 | cur_thread_node[k_tid & K_CONTEXT_MASK]); 280 | spin_unlock(&_spinlock); 281 | 282 | swapcontext(&(cur_tcb->context), &context_main[k_tid & K_CONTEXT_MASK]); 283 | } 284 | 285 | /* schedule the user-level threads */ 286 | static void schedule() 287 | { 288 | uint k_tid = (uint) syscall(SYS_gettid); 289 | _tcb *cur_tcb = GET_TCB(cur_thread_node[k_tid & K_CONTEXT_MASK]); 290 | 291 | if (preempt_disable_count) 292 | return; 293 | 294 | spin_lock(&_spinlock); 295 | cur_tcb->status = SUSPENDED; 296 | enqueue(thread_queue + cur_tcb->prio, 297 | cur_thread_node[k_tid & K_CONTEXT_MASK]); 298 | spin_unlock(&_spinlock); 299 | 300 | swapcontext(&(cur_tcb->context), &context_main[k_tid & K_CONTEXT_MASK]); 301 | } 302 | 303 | /* start user-level thread wrapper function */ 304 | static void u_thread_exec_func(void (*thread_func)(void *), 305 | void *arg, 306 | _tcb *thread) 307 | { 308 | uint k_tid = 0; 309 | _tcb *u_thread = thread; 310 | 311 | thread_func(arg); 312 | u_thread->status = FINISHED; 313 | 314 | k_tid = (uint) syscall(SYS_gettid); 315 | 316 | u_thread->context.uc_link = &context_main[k_tid & K_CONTEXT_MASK]; 317 | 318 | /* When this thread finished, delete TCB and yield CPU control */ 319 | spin_lock(&_spinlock); 320 | enqueue(thread_queue + u_thread->prio, 321 | cur_thread_node[k_tid & K_CONTEXT_MASK]); 322 | spin_unlock(&_spinlock); 323 | 324 | swapcontext(&u_thread->context, &context_main[k_tid & K_CONTEXT_MASK]); 325 | } 326 | 327 | /* run native thread (or kernel-level thread) function */ 328 | static int k_thread_exec_func(void *arg UNUSED) 329 | { 330 | uint k_tid = (uint) syscall(SYS_gettid); 331 | 332 | list_node *run_node = NULL; 333 | _tcb *run_tcb = NULL; 334 | 335 | /* timer and signal for user-level thread scheduling */ 336 | struct sigaction sched_handler = { 337 | .sa_handler = &schedule, /* set signal handler to call scheduler */ 338 | }; 339 | sigaction(SIGPROF, &sched_handler, NULL); 340 | 341 | setitimer(ITIMER_PROF, ×lice, NULL); 342 | 343 | /* obtain and run a user-level thread from the user-level thread queue, 344 | * until no available user-level thread 345 | */ 346 | while (1) { 347 | spin_lock(&_spinlock); 348 | 349 | if (!dequeue(thread_queue, &run_node)) { 350 | spin_unlock(&_spinlock); 351 | 352 | setitimer(ITIMER_PROF, &zero_timer, ×lice); 353 | return 0; 354 | } 355 | spin_unlock(&_spinlock); 356 | 357 | run_tcb = GET_TCB(run_node); 358 | 359 | /* current user thread is already terminated or finished by 360 | * fiber_exit() 361 | */ 362 | if (TERMINATED == run_tcb->status || FINISHED == run_tcb->status) { 363 | /* do V() in thread semaphore implies that current user-level 364 | * thread is done. 365 | */ 366 | sem_post(&(sigsem_thread[run_tcb->tid].semaphore)); 367 | free(run_tcb); 368 | user_thread_num--; 369 | continue; 370 | } 371 | 372 | run_tcb->status = RUNNING; 373 | cur_thread_node[k_tid & K_CONTEXT_MASK] = run_node; 374 | swapcontext(&context_main[k_tid & K_CONTEXT_MASK], &(run_tcb->context)); 375 | } 376 | return 0; 377 | } 378 | 379 | /* initialize the mutex lock */ 380 | int fiber_mutex_init(fiber_mutex_t *mutex) 381 | { 382 | mutex->owner = NULL; 383 | mutex->lock = 0; 384 | 385 | (&(mutex->wait_list))->prev = &(mutex->wait_list); 386 | (&(mutex->wait_list))->next = &(mutex->wait_list); 387 | 388 | return 0; 389 | } 390 | 391 | /* acquire the mutex lock */ 392 | int fiber_mutex_lock(fiber_mutex_t *mutex) 393 | { 394 | uint k_tid = (uint) syscall(SYS_gettid); 395 | 396 | /* avoid recursive locks */ 397 | if (unlikely(mutex->owner == 398 | GET_TCB(cur_thread_node[k_tid & K_CONTEXT_MASK]))) 399 | return -1; 400 | 401 | /* Use "test-and-set" atomic operation to acquire the mutex lock */ 402 | while (__atomic_test_and_set(&mutex->lock, __ATOMIC_ACQUIRE)) { 403 | enqueue(&mutex->wait_list, cur_thread_node[k_tid & K_CONTEXT_MASK]); 404 | swapcontext( 405 | &(GET_TCB(cur_thread_node[k_tid & K_CONTEXT_MASK])->context), 406 | &context_main[k_tid & K_CONTEXT_MASK]); 407 | } 408 | mutex->owner = GET_TCB(cur_thread_node[k_tid & K_CONTEXT_MASK]); 409 | 410 | return 0; 411 | } 412 | 413 | /* release the mutex lock */ 414 | int fiber_mutex_unlock(fiber_mutex_t *mutex) 415 | { 416 | list_node *next_node = NULL; 417 | _tcb *cur_tcb = NULL; 418 | if (!dequeue(&(mutex->wait_list), &next_node)) { 419 | __atomic_store_n(&mutex->lock, 0, __ATOMIC_RELEASE); 420 | mutex->owner = NULL; 421 | return 0; 422 | } 423 | cur_tcb = GET_TCB(next_node); 424 | cur_tcb->prio = 0; 425 | spin_lock(&_spinlock); 426 | enqueue(thread_queue + cur_tcb->prio, next_node); 427 | spin_unlock(&_spinlock); 428 | __atomic_store_n(&mutex->lock, 0, __ATOMIC_RELEASE); 429 | mutex->owner = NULL; 430 | return 0; 431 | } 432 | 433 | /* destory the mutex lock */ 434 | int fiber_mutex_destroy(fiber_mutex_t *mutex UNUSED) 435 | { 436 | /* FIXME: deallocate */ 437 | return 0; 438 | } 439 | 440 | /* initial condition variable */ 441 | int fiber_cond_init(fiber_cond_t *condvar) 442 | { 443 | (&(condvar->wait_list))->prev = &(condvar->wait_list); 444 | (&(condvar->wait_list))->next = &(condvar->wait_list); 445 | 446 | return 0; 447 | } 448 | 449 | /* wake up all threads on waiting list of condition variable */ 450 | int fiber_cond_broadcast(fiber_cond_t *condvar) 451 | { 452 | list_node *next_node = NULL; 453 | _tcb *cur_tcb = NULL; 454 | while (!dequeue(&(condvar->wait_list), &next_node)) { 455 | cur_tcb = GET_TCB(next_node); 456 | cur_tcb->prio = 0; 457 | while (__atomic_test_and_set(&_spinlock, __ATOMIC_ACQUIRE)) 458 | ; 459 | enqueue(thread_queue + cur_tcb->prio, next_node); 460 | __atomic_store_n(&_spinlock, 0, __ATOMIC_RELEASE); 461 | } 462 | return 0; 463 | } 464 | 465 | /* wake up a thread on waiting list of condition variable */ 466 | int fiber_cond_signal(fiber_cond_t *condvar) 467 | { 468 | list_node *next_node = NULL; 469 | _tcb *cur_tcb = NULL; 470 | if (!dequeue(&(condvar->wait_list), &next_node)) 471 | return 0; 472 | 473 | cur_tcb = GET_TCB(next_node); 474 | cur_tcb->prio = 0; 475 | spin_lock(&_spinlock); 476 | enqueue(thread_queue + cur_tcb->prio, next_node); 477 | spin_unlock(&_spinlock); 478 | 479 | return 0; 480 | } 481 | 482 | /* current thread go to sleep until other thread wakes it up */ 483 | int fiber_cond_wait(fiber_cond_t *condvar, fiber_mutex_t *mutex) 484 | { 485 | uint k_tid = (uint) syscall(SYS_gettid); 486 | 487 | list_node *node = cur_thread_node[k_tid & K_CONTEXT_MASK]; 488 | enqueue(&condvar->wait_list, node); 489 | 490 | fiber_mutex_unlock(mutex); 491 | swapcontext(&(GET_TCB(cur_thread_node[k_tid & K_CONTEXT_MASK])->context), 492 | &context_main[k_tid & K_CONTEXT_MASK]); 493 | fiber_mutex_lock(mutex); 494 | 495 | return 0; 496 | } 497 | 498 | /* destory condition variable */ 499 | int fiber_cond_destroy(fiber_cond_t *condvar UNUSED) 500 | { 501 | /* FIXME: deallocate */ 502 | return 0; 503 | } 504 | --------------------------------------------------------------------------------