]>
Commit | Line | Data |
---|---|---|
12c86877 BH |
1 | /* |
2 | PowerDNS Versatile Database Driven Nameserver | |
3 | Copyright (C) 2002 PowerDNS.COM BV | |
4 | ||
5 | This program is free software; you can redistribute it and/or modify | |
6 | it under the terms of the GNU General Public License as published by | |
7 | the Free Software Foundation; either version 2 of the License, or | |
8 | (at your option) any later version. | |
9 | ||
10 | This program is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | GNU General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU General Public License | |
16 | along with this program; if not, write to the Free Software | |
17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
18 | */ | |
19 | #include "mtasker.hh" | |
20 | #include <stdio.h> | |
21 | #include <iostream> | |
22 | ||
bdc9f8d2 | 23 | /** \page MTasker |
12c86877 BH |
24 | Simple system for implementing cooperative multitasking of functions, with |
25 | support for waiting on events which can return values. | |
26 | ||
27 | \section copyright Copyright and License | |
28 | MTasker is (c) 2002 by bert hubert. It is licensed to you under the terms of the GPL version 2. | |
29 | ||
30 | \section overview High level overview | |
31 | MTasker is designed to support very simple cooperative multitasking to facilitate writing | |
32 | code that would ordinarily require a statemachine, for which the author does not consider | |
33 | himself smart enough. | |
34 | ||
35 | This class does not perform any magic it only makes calls to makecontext() and swapcontext(). | |
36 | Getting the details right however is complicated and MTasker does that for you. | |
37 | ||
38 | If preemptive multitasking or more advanced concepts such as semaphores, locks or mutexes | |
39 | are required, the use of POSIX threads is advised. | |
40 | ||
41 | MTasker is designed to offer the performance of statemachines while maintaining simple thread semantics. It is not | |
42 | a replacement for a full threading system. | |
43 | ||
44 | \section concepts Concepts | |
45 | ||
46 | There are two important concepts, the 'kernel' and the 'thread'. Each thread starts out as a function, | |
47 | which is passed to MTasker::makeThread(), together with a possible argument. | |
48 | ||
49 | This function is now free to do whatever it wants, but realise that MTasker implements cooperative | |
50 | multitasking, which means that the coder has the responsiblilty of not taking the CPU overly long. | |
51 | Other threads can only get the CPU if MTasker::yield() is called or if a thread sleeps to wait for an event, | |
52 | using the MTasker::waitEvent() method. | |
53 | ||
54 | \section kernel The Kernel | |
55 | The Kernel consists of functions that do housekeeping, but also of code that the client coder | |
56 | can call to report events. A minimal kernel loop looks like this: | |
57 | \code | |
58 | for(;;) { | |
59 | MT.schedule(); | |
60 | if(MT.noProcesses()) // exit if no processes are left | |
61 | break; | |
62 | } | |
63 | \endcode | |
64 | ||
65 | The kernel typically starts from the main() function of your program. New threads are also | |
66 | created from the kernel. This can also happen before entering the main loop. To start a thread, | |
67 | the method MTasker::makeThread is provided. | |
68 | ||
69 | \section events Events | |
70 | By default, Events are recognized by an int and their value is also an int. | |
71 | This can be overriden by specifying the EventKey and EventVal template parameters. | |
72 | ||
73 | An event can be a keypress, but also a UDP packet, or a bit of data from a TCP socket. The | |
74 | sample code provided works with keypresses, but that is just a not very useful example. | |
75 | ||
76 | A thread can also wait for an event only for a limited time, and receive a timeout of that | |
77 | event did not occur within the specified timeframe. | |
78 | ||
79 | \section example A simple menu system | |
80 | \code | |
81 | MTasker<> MT; | |
82 | ||
83 | void menuHandler(void *p) | |
84 | { | |
85 | int num=(int)p; | |
86 | cout<<"Key handler for key "<<num<<" launched"<<endl; | |
87 | ||
88 | MT.waitEvent(num); | |
89 | cout<<"Key "<<num<<" was pressed!"<<endl; | |
90 | } | |
91 | ||
92 | ||
93 | int main() | |
94 | { | |
95 | char line[10]; | |
96 | ||
97 | for(int i=0;i<10;++i) | |
98 | MT.makeThread(menuHandler,(void *)i); | |
99 | ||
100 | for(;;) { | |
101 | while(MT.schedule()); // do everything we can do | |
102 | if(MT.noProcesses()) // exit if no processes are left | |
103 | break; | |
104 | ||
105 | if(!fgets(line,sizeof(line),stdin)) | |
106 | break; | |
107 | ||
108 | MT.sendEvent(*line-'0'); | |
109 | } | |
110 | } | |
111 | \endcode | |
112 | ||
113 | \section example2 Canonical multitasking example | |
114 | This implements the canonical multitasking example, alternately printing an 'A' and a 'B'. The Linux kernel | |
115 | started this way too. | |
116 | \code | |
117 | void printer(void *p) | |
118 | { | |
119 | char c=(char)p; | |
120 | for(;;) { | |
121 | cout<<c<<endl; | |
122 | MT.yield(); | |
123 | } | |
124 | ||
125 | } | |
126 | ||
127 | int main() | |
128 | { | |
129 | MT.makeThread(printer,(void*)'a'); | |
130 | MT.makeThread(printer,(void*)'b'); | |
131 | ||
132 | for(;;) { | |
133 | while(MT.schedule()); // do everything we can do | |
134 | if(MT.noProcesses()) // exit if no processes are left | |
135 | break; | |
136 | } | |
137 | } | |
138 | \endcode | |
139 | ||
140 | */ | |
141 | ||
142 | //! puts a thread to sleep waiting until a specified event arrives | |
143 | /** Threads can call waitEvent to register that they are waiting on an event with a certain key. | |
144 | If so desidered, the event can carry data which is returned in val in case that is non-zero. | |
145 | ||
146 | Furthermore, a timeout can be specified in seconds. | |
147 | ||
148 | Only one thread can be waiting on a key, results of trying to have more threads | |
149 | waiting on the same key are undefined. | |
150 | ||
151 | \param key Event key to wait for. Needs to match up to a key reported to sendEvent | |
152 | \param val If non-zero, the value of the event will be stored in *val | |
153 | \param timeout If non-zero, number of seconds to wait for an event. | |
154 | ||
155 | \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer | |
156 | */ | |
157 | ||
158 | template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::waitEvent(const EventKey &key, EventVal *val, unsigned int timeout) | |
159 | { | |
160 | Waiter w; | |
161 | w.context=new ucontext_t; | |
162 | w.ttd= timeout ? time(0)+timeout : 0; | |
163 | w.tid=d_tid; | |
164 | ||
165 | d_waiters[key]=w; | |
166 | ||
167 | swapcontext(d_waiters[key].context,&d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first | |
168 | if(val && d_waitstatus==Answer) | |
169 | *val=d_waitval; | |
170 | return d_waitstatus; | |
171 | } | |
172 | ||
173 | //! yields control to the kernel or other threads | |
174 | /** Hands over control to the kernel, allowing other processes to run, or events to arrive */ | |
175 | ||
176 | template<class Key, class Val>void MTasker<Key,Val>::yield() | |
177 | { | |
178 | d_runQueue.push(d_tid); | |
179 | swapcontext(d_threads[d_tid],&d_kernel); // give control to the kernel | |
180 | } | |
181 | ||
182 | //! reports that an event took place for which threads may be waiting | |
183 | /** From the kernel loop, sendEvent can be called to report that something occured for which there may be waiters. | |
184 | \param key Key of the event for which threads may be waiting | |
185 | \param val If non-zero, pointer to the content of the event | |
186 | \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up. | |
187 | */ | |
188 | template<class EventKey, class EventVal>int MTasker<EventKey,EventVal>::sendEvent(const EventKey& key, const EventVal* val) | |
189 | { | |
190 | if(!d_waiters.count(key)) { | |
191 | return 0; | |
192 | } | |
193 | ||
194 | d_waitstatus=Answer; | |
195 | if(val) | |
196 | d_waitval=*val; | |
197 | ||
198 | ucontext_t *userspace=d_waiters[key].context; | |
199 | d_tid=d_waiters[key].tid; // set tid | |
200 | ||
201 | d_waiters.erase(key); // removes the waitpoint | |
202 | swapcontext(&d_kernel,userspace); // swaps back to the above point 'A' | |
203 | ||
204 | delete userspace; | |
205 | return 1; | |
206 | } | |
207 | ||
208 | //! launches a new thread | |
209 | /** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer. | |
210 | \param start Pointer to the function which will form the start of the thread | |
211 | \param val A void pointer that can be used to pass data to the thread | |
212 | */ | |
213 | template<class Key, class Val>void MTasker<Key,Val>::makeThread(tfunc_t *start, void* val) | |
214 | { | |
215 | ucontext_t *uc=new ucontext_t; | |
216 | getcontext(uc); | |
217 | ||
218 | uc->uc_link = &d_kernel; // come back to kernel after dying | |
219 | uc->uc_stack.ss_sp = new char[d_stacksize]; | |
220 | ||
221 | uc->uc_stack.ss_size = d_stacksize; | |
222 | makecontext (uc, (void (*)(void))threadWrapper, 4, this, start, d_maxtid, val); | |
223 | d_threads[d_maxtid]=uc; | |
224 | d_runQueue.push(d_maxtid++); // will run at next schedule invocation | |
225 | } | |
226 | ||
227 | ||
228 | //! needs to be called periodically so threads can run and housekeeping can be performed | |
229 | /** The kernel should call this function every once in a while. It makes sense | |
230 | to call this function if you: | |
231 | - reported an event | |
232 | - called makeThread | |
233 | - want to have threads running waitEvent() to get a timeout if enough time passed | |
234 | ||
235 | \return Returns if there is more work scheduled and recalling schedule now would be useful | |
236 | ||
237 | */ | |
238 | template<class Key, class Val>bool MTasker<Key,Val>::schedule() | |
239 | { | |
240 | ||
241 | if(!d_runQueue.empty()) { | |
242 | d_tid=d_runQueue.front(); | |
243 | swapcontext(&d_kernel, d_threads[d_tid]); | |
244 | d_runQueue.pop(); | |
245 | return true; | |
246 | } | |
247 | if(!d_zombiesQueue.empty()) { | |
248 | delete (char *)d_threads[d_zombiesQueue.front()]->uc_stack.ss_sp; | |
249 | delete d_threads[d_zombiesQueue.front()]; | |
250 | d_threads.erase(d_zombiesQueue.front()); | |
251 | d_zombiesQueue.pop(); | |
252 | return true; | |
253 | } | |
254 | if(!d_waiters.empty()) { | |
255 | time_t now=time(0); | |
256 | for(typename waiters_t::const_iterator i=d_waiters.begin();i!=d_waiters.end();++i) { | |
257 | if(i->second.ttd && i->second.ttd<now) { | |
258 | d_waitstatus=TimeOut; | |
259 | swapcontext(&d_kernel,i->second.context); // swaps back to the above point 'A' | |
260 | delete i->second.context; | |
261 | d_waiters.erase(i->first); // removes the waitpoint | |
262 | } | |
263 | } | |
264 | } | |
265 | return false; | |
266 | } | |
267 | ||
268 | //! returns true if there are no processes | |
269 | /** Call this to check if no processes are running anymore | |
270 | \return true if no processes are left | |
271 | */ | |
272 | template<class Key, class Val>bool MTasker<Key,Val>::noProcesses() | |
273 | { | |
274 | return d_threads.empty(); | |
275 | } | |
276 | ||
277 | //! gives access to the list of Events threads are waiting for | |
278 | /** The kernel can call this to get a list of Events threads are waiting for. This is very useful | |
279 | to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests. | |
280 | getEvents clears the events parameter before filling it. | |
281 | ||
282 | \param events Vector which is to be filled with keys threads are waiting for | |
283 | */ | |
284 | template<class Key, class Val>void MTasker<Key,Val>::getEvents(std::vector<Key>& events) | |
285 | { | |
286 | events.clear(); | |
287 | for(typename waiters_t::const_iterator i=d_waiters.begin();i!=d_waiters.end();++i) { | |
288 | events.push_back(i->first); | |
289 | } | |
290 | } | |
291 | ||
292 | ||
293 | template<class Key, class Val>void MTasker<Key,Val>::threadWrapper(MTasker *self, tfunc_t *tf, int tid, void* val) | |
294 | { | |
295 | (*tf)(val); | |
296 | self->d_zombiesQueue.push(tid); | |
297 | ||
298 | // we now jump to &kernel, automatically | |
299 | } | |
300 |