Line data Source code
1 1 : /**
2 : * @file arch/thread.c
3 : *
4 : * @brief Generic thread management facilities.
5 : *
6 : * This module provides generic facilities for thread management.
7 : * In particular, helper functions to startup worker threads are exposed,
8 : * and a function to synchronize multiple threads on a software barrier.
9 : *
10 : * The software barrier also offers a leader election facility, so that
11 : * once all threads are synchronized on the barrier, the function returns
12 : * true to only one of them.
13 : *
14 : * @copyright
15 : * Copyright (C) 2008-2019 HPDCS Group
16 : * https://hpdcs.github.io
17 : *
18 : * This file is part of ROOT-Sim (ROme OpTimistic Simulator).
19 : *
20 : * ROOT-Sim is free software; you can redistribute it and/or modify it under the
21 : * terms of the GNU General Public License as published by the Free Software
22 : * Foundation; only version 3 of the License applies.
23 : *
24 : * ROOT-Sim is distributed in the hope that it will be useful, but WITHOUT ANY
25 : * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
26 : * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
27 : *
28 : * You should have received a copy of the GNU General Public License along with
29 : * ROOT-Sim; if not, write to the Free Software Foundation, Inc.,
30 : * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31 : *
32 : * @author Alessandro Pellegrini
33 : * @date Jan 25, 2012
34 : */
35 :
36 : #include <stdbool.h>
37 : #include <arch/thread.h>
38 : #include <core/init.h>
39 : #include <mm/mm.h>
40 :
41 : /**
42 : * An OS-level thread id. We never do any join on worker threads, so
43 : * there is no need to keep track of system ids. Internally, each thread
44 : * is given a unique id in [0, n_thr], which is used to know who is who.
45 : * This variable is used only when spawning threads, because the system
46 : * wants us to know who is what thread from its point of view, although
47 : * we don't care at all.
48 : */
49 1 : static tid_t os_tid;
50 :
51 :
52 : /**
53 : * The id of the thread.
54 : *
55 : * @todo This is global across all kernel instances in a distributed
56 : * run. We don't actually care at all about this, so this can be
57 : * safely made per-machine. Once this is done, local_tid should
58 : * go away.
59 : */
60 1 : __thread unsigned int tid;
61 :
62 :
63 : /**
64 : * The "local" id of the thread. This is a per-instance value which
65 : * uniquely identifies a thread on a compute node.
66 : *
67 : * @todo This is global across all kernel instances in a distributed
68 : * run. We don't actually care at all about this, so this can be
69 : * safely made per-machine. Once this is done, local_tid should
70 : * go away.
71 : */
72 1 : __thread unsigned int local_tid;
73 :
74 :
75 : /**
76 : * The thread counter is a global variable which is incremented atomically
77 : * to assign a unique thread ID. Each spawned thread will compete using a
78 : * CAS to update this counter. Once a thread succeeds, it can use the
79 : * value it was keeping as its private local id.
80 : * This variable is used only as a synchronization point upon initialization,
81 : * later no one cares about its value.
82 : */
83 1 : static unsigned int thread_counter = 0;
84 :
85 :
86 : /**
87 : * This helper function is the actual entry point for every thread created
88 : * using the provided internal services. The goal of this function is to
89 : * start a race on the thread_counter shared variable using a CAS. This
90 : * race across all the threads is used to determine unique identifiers
91 : * across all the worker threads. This is done in this helper function to
92 : * silently set the new thread's tid before any simulation-specific function
93 : * is ever called, so that any place in the simulator will find that
94 : * already set.
95 : * Additionally, when the created thread returns, it frees the memory used
96 : * to maintain the real entry point and the pointer to its arguments.
97 : *
98 : * @param arg A pointer to an internally defined structure keeping the
99 : * real thread's entry point and its arguments
100 : *
101 : * @return This function always returns NULL
102 : */
103 1 : static void *__helper_create_thread(void *arg)
104 : {
105 :
106 : struct _helper_thread *real_arg = (struct _helper_thread *)arg;
107 :
108 : // Get a unique local thread id...
109 : unsigned int old_counter;
110 : unsigned int _local_tid;
111 :
112 : while (true) {
113 : old_counter = thread_counter;
114 : _local_tid = old_counter + 1;
115 : if (iCAS(&thread_counter, old_counter, _local_tid)) {
116 : break;
117 : }
118 : }
119 : local_tid = _local_tid;
120 :
121 : // ...and make it globally unique
122 : tid = to_global_tid(kid, _local_tid);
123 :
124 : // Set the affinity on a CPU core, for increased performance
125 : if (likely(rootsim_config.core_binding))
126 : set_affinity(local_tid);
127 :
128 : // Now get into the real thread's entry point
129 : real_arg->start_routine(real_arg->arg);
130 :
131 : // Free arg and return (we don't really need any return value)
132 : rsfree(arg);
133 : return NULL;
134 : }
135 :
136 : /**
137 : * This function creates n threads, all having the same entry point and
138 : * the same arguments.
139 : * It creates a new thread starting from the __helper_create_thread
140 : * function which silently sets the new thread's tid.
141 : * Note that the arguments passed to __helper_create_thread are malloc'd
142 : * here, and free'd there. This means that if start_routine does not
143 : * return, there is a memory leak.
144 : * Additionally, note that we don't make a copy of the arguments pointed
145 : * by arg, so all the created threads will share them in memory. Changing
146 : * passed arguments from one of the newly created threads will result in
147 : * all the threads seeing the change.
148 : *
149 : * @param n The number of threads which should be created
150 : * @param start_routine The new threads' entry point
151 : * @param arg A pointer to an array of arguments to be passed to the new
152 : * threads' entry point
153 : *
154 : */
155 1 : void create_threads(unsigned short int n, void *(*start_routine)(void *), void *arg)
156 : {
157 : int i;
158 :
159 : // We create our thread within our helper function, which accepts just
160 : // one parameter. We thus have to create one single parameter containing
161 : // the original pointer and the function to be used as real entry point.
162 : // This malloc'd array is free'd by the helper function.
163 : struct _helper_thread *new_arg = rsalloc(sizeof(struct _helper_thread));
164 : new_arg->start_routine = start_routine;
165 : new_arg->arg = arg;
166 :
167 : // n threads are created simply looping...
168 : for (i = 0; i < n; i++) {
169 : new_thread(__helper_create_thread, (void *)new_arg);
170 : }
171 : }
172 :
173 : /**
174 : * This function initializes a thread barrier. If more than the hereby specified
175 : * number of threads try to synchronize on the barrier, the behaviour is undefined.
176 : *
177 : * @param b the thread barrier to initialize
178 : * @param t the number of threads which will synchronize on the barrier
179 : */
180 1 : void barrier_init(barrier_t * b, int t)
181 : {
182 : b->num_threads = t;
183 : thread_barrier_reset(b);
184 : }
185 :
186 : /**
187 : * This function synchronizes all the threads. After a thread leaves this function,
188 : * it is guaranteed that no other thread has (at least) not entered the function,
189 : * therefore allowing to create a separation between the execution in portions of the code.
190 : * If more threads than specified in the initialization of the barrier try to synchronize
191 : * on it, the behaviour is undefined.
192 : * The function additionally returns 'true' only to one of the calling threads, allowing
193 : * the execution of portions of code in isolated mode after the barrier itself. This is
194 : * like a leader election for free.
195 : *
196 : * @param b A pointer to the thread barrier to synchronize on
197 : *
198 : * @return false to all threads, except for one which is elected as the leader
199 : */
200 1 : bool thread_barrier(barrier_t * b)
201 : {
202 : // Wait for the leader to finish resetting the barrier
203 : while (atomic_read(&b->barr) != -1) ;
204 :
205 : // Wait for all threads to synchronize
206 : atomic_dec(&b->c1);
207 : while (atomic_read(&b->c1)) ;
208 :
209 : // Leader election
210 : if (unlikely(atomic_inc_and_test(&b->barr))) {
211 :
212 : // I'm sync'ed!
213 : atomic_dec(&b->c2);
214 :
215 : // Wait all the other threads to leave the first part of the barrier
216 : while (atomic_read(&b->c2)) ;
217 :
218 : // Reset the barrier to its initial values
219 : thread_barrier_reset(b);
220 :
221 : return true;
222 : }
223 : // I'm sync'ed!
224 : atomic_dec(&b->c2);
225 :
226 : return false;
227 : }
|