]> rtime.felk.cvut.cz Git - frescor/fosa.git/blob - src_aquosa/fosa_threads_and_signals.c
03e31160e9c69f305f4c32b842db9d9048554086
[frescor/fosa.git] / src_aquosa / fosa_threads_and_signals.c
1 // -----------------------------------------------------------------------
2 //  Copyright (C) 2006 - 2007 FRESCOR consortium partners:
3 //
4 //    Universidad de Cantabria,              SPAIN
5 //    University of York,                    UK
6 //    Scuola Superiore Sant'Anna,            ITALY
7 //    Kaiserslautern University,             GERMANY
8 //    Univ. Politecnica  Valencia,           SPAIN
9 //    Czech Technical University in Prague,  CZECH REPUBLIC
10 //    ENEA                                   SWEDEN
11 //    Thales Communication S.A.              FRANCE
12 //    Visual Tools S.A.                      SPAIN
13 //    Rapita Systems Ltd                     UK
14 //    Evidence                               ITALY
15 //    
16 //    See http://www.frescor.org for a link to partners' websites
17 //
18 //           FRESCOR project (FP6/2005/IST/5-034026) is funded
19 //        in part by the European Union Sixth Framework Programme
20 //        The European Union is not liable of any use that may be
21 //        made of this code.
22 //
23 //  This file is part of the FRSH implementation
24 //
25 //  FRSH is free software; you can  redistribute it and/or  modify
26 //  it under the terms of  the GNU General Public License as published by
27 //  the Free Software Foundation;  either  version 2, or (at  your option)
28 //  any later version.
29 //
30 //  FRSH  is distributed  in  the hope  that  it  will  be useful,  but
31 //  WITHOUT  ANY  WARRANTY;     without  even the   implied   warranty  of
32 //  MERCHANTABILITY  or  FITNESS FOR  A  PARTICULAR PURPOSE. See  the  GNU
33 //  General Public License for more details.
34 //
35 //  You should have  received a  copy of  the  GNU  General Public License
36 //  distributed  with  FRSH;  see file COPYING.   If not,  write to the
37 //  Free Software  Foundation,  59 Temple Place  -  Suite 330,  Boston, MA
38 //  02111-1307, USA.
39 //
40 //  As a special exception, if you include this header file into source
41 //  files to be compiled, this header file does not by itself cause
42 //  the resulting executable to be covered by the GNU General Public
43 //  License.  This exception does not however invalidate any other
44 //  reasons why the executable file might be covered by the GNU General
45 //  Public License.
46 // -----------------------------------------------------------------------
47 //==============================================
48 //  ********  ******    ********  **********
49 //  **///// /**    **  **//////  /**     /**
50 //  **      /**    ** /**        /**     /**
51 //  ******* /**    ** /********* /**********
52 //  **////  /**    ** ////////** /**//////**
53 //  **      /**    **        /** /**     /**
54 //  **      /**    **  ********  /**     /**
55 //  //       /******/  ////////   //      // 
56 //
57 // FOSA(Frescor Operating System Adaptation layer)
58 //================================================
59
60 #include "fosa_time.h"
61 #include "fosa_configuration_parameters.h"
62 #include "fosa_threads_and_signals.h"
63
64 /*************************
65  * Storage of thread-specific keys
66  *************************/
67
68 static pthread_key_t key_list[FOSA_MAX_KEYS];
69 static bool key_in_use[FOSA_MAX_KEYS];
70 static pthread_mutex_t key_lock;
71
72
73 /* Initialize the keys data structure */
74 int init_keys()
75 {
76         int i, ret;
77         pthread_mutexattr_t attr;
78
79         for(i = 0; i < FOSA_MAX_KEYS; i++)
80                 key_in_use[i] = false;
81
82         ret = pthread_mutexattr_init(&attr);
83         if (ret) return errno;
84
85         ret = pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
86         if (ret) return errno;
87
88         ret = pthread_mutex_init(&key_lock,&attr);
89         if (ret) return errno;
90
91         return 0;
92 }
93
94 /*************************
95  * Thread identification
96  *************************/ 
97
98 /**
99  * fosa_thread_equal()
100  *
101  * Compare two thread identifiers to determine if they refer to the 
102  * same thread
103  **/
104 bool fosa_thread_equal(fosa_thread_id_t t1, fosa_thread_id_t t2)
105 {
106         return t1.linux_pid == t2.linux_pid &&
107                t1.linux_tid == t2.linux_tid &&
108                pthread_equal(t1.pthread_id, t2.pthread_id);
109 }
110
111 /**
112  * fosa_thread_self()
113  *
114  * Return the thread id of the calling thread
115  **/
116 fosa_thread_id_t fosa_thread_self()
117 {
118         fosa_thread_id_t thread_self;
119         /** 
120          * NB. Remember:
121          *  fosa_thread_id_t => struct {
122          *                       pthread_t pthread_id;
123          *                       pid_t linux_pid;
124          *                       tid_t linux_tid;
125          * };
126          **/
127         thread_self.pthread_id = pthread_self();
128         thread_self.linux_pid = getpid();
129         thread_self.linux_tid = syscall(__NR_gettid);   /* gettid() */
130
131         return thread_self;
132 }
133
134 /*************************
135  * Thread attributes
136  *************************/
137
138 static inline int __fosa_check_thread(const fosa_thread_id_t *tid)
139 {
140         if (tid->linux_pid == tid->linux_tid)
141                 return 0;
142
143         return 1;
144 }
145
146 /**
147  * fosa_thread_attr_init()
148  *
149  * Initialize a thread attributes object
150  *
151  * This function initializes the object pointed to by attr to all 
152  * the default values defined by FRSH
153  *
154  * return 0 if successful; otherwise it returns
155  *   FOSA_ENOMEM: insufficient memory exists to initialize the thread 
156  *           attributes object
157  **/
158 int fosa_thread_attr_init(fosa_thread_attr_t *attr)
159 {
160         return pthread_attr_init(attr);
161 }
162
163 /**
164  * fosa_thread_attr_destroy()
165  *
166  * Destroy a thread attributes object
167  *
168  * This function is used to destroy the thread attributes object,
169  * pointed to by attr, and deallocate any system resources allocated for it
170  * 
171  * Returns 0
172  **/
173 int fosa_thread_attr_destroy(fosa_thread_attr_t *attr)
174 {
175         return pthread_attr_destroy(attr);
176 }
177
178 /**
179  * fosa_thread_attr_set_stacksize()
180  *
181  * Set the thread minimum stack size in a thread attributes object
182  *
183  * This function sets the minimum stack size of the thread attributes
184  * object attr to the value given by stacksize, in bytes. This
185  * function has no runtime effect on the stack size, except when the
186  * attributes object is used to create a thread, when it will be
187  * created with the specified minimum stack size
188  * 
189  * return 0 if successful, or the following error code:
190  *    FOSA_EINVAL: the specified stacksize  value is not supported in
191  *            this implementation
192  **/
193 int fosa_thread_attr_set_stacksize(fosa_thread_attr_t *attr,
194                                    size_t stacksize)
195 {
196         return pthread_attr_setstacksize(attr, stacksize);
197 }
198
199 /**
200  * fosa_thread_attr_get_stacksize()
201  *
202  * Get the thread minimum stack size from a thread attributes object
203  *
204  * This function sets the variable pointed to by stacksize to the
205  * minimum stack size stored in the thread attributes object attr.
206  * 
207  * return 0
208  **/
209 int fosa_thread_attr_get_stacksize(const fosa_thread_attr_t *attr,
210                                    size_t *stacksize)
211 {
212         return pthread_attr_getstacksize(attr, stacksize);
213 }
214
215 /*************************
216  * Thread creation and termination
217  *************************/
218
219 /**
220  * fosa_thread_create()
221  *
222  * This function creates a new thread using the attributes specified
223  * in attr. If attr is NULL, default attributes are used. The new
224  * thread starts running immediately, executing the function specified
225  * by code, with an argument equal to arg. Upon successful return, the
226  * variable pointed to by tid will contain the identifier of the newly
227  * created thread. The set of signals that may be synchronously
228  * accepted is inherited from the parent thread.
229  *
230  * Returns 0 if successful; otherwise it returs a code error:
231  *
232  *     EAGAIN: the system lacks the necessary resources to create a
233  *             new thread or the maximum number of threads has been
234  *             reached
235  *
236  *     EINVAL: the value specified by attr is invalid (for instance,
237  *              it has not been correctly initialized)
238  *
239  *     EREJECT: the cretion of the thread was rejected by the frsh scheduler
240  *               possibly because of incorrect attributes, or because the 
241  *               requested minimum capacity cannot be guaranteed
242  **/
243 int fosa_thread_create(fosa_thread_id_t *tid,
244                        const fosa_thread_attr_t *attr,
245                        fosa_thread_code_t code,
246                        void *arg)
247 {
248         return pthread_create(&tid->pthread_id, attr, code, arg);
249 }
250
251 /**
252  * Note: no thread termination primitive is provided. The termination
253  * of a thread will be notified by the system to the FRSH scheduler
254  * through the scheduler API
255  **/
256
257 /**************************************************
258  * Thread-specific data
259  *  (extended with access from a different thread)
260  *
261  * Several data items (pointers) may be associated with each thread
262  * Each item is identified through a key, an integer value between 0
263  * and FOSA_MAX_KEYS-1. The caller is responsible of allocating and
264  * deallocating the memory area pointed to by the pointer
265  **************************************************/ 
266
267 /**
268  * fosa_key_create()
269  *
270  * Create a new key for thread specific data.
271  *
272  * Prior to setting data in a key, we need ask the system to create
273  * one for us.
274  *
275  * return 0 if successful \n
276  *   FOSA_EINVAL If we already have reached the FOSA_MAX_KEYS limit.
277  *   FOSA_ENOMEM If there are no enough memory resources to 
278  *               create the key.
279  **/
280 int fosa_key_create(int *key)
281 {
282         int i, ret;
283         bool found = false;
284
285         ret = pthread_mutex_lock(&key_lock);
286         if (ret) return ret;
287
288         /* find an unused key */
289         for (i = 0; i < FOSA_MAX_KEYS; i++) {
290                 if (!key_in_use[i]) {
291                         ret = pthread_key_create(&(key_list[i]), NULL);
292                         if (ret) return ret;
293
294                         *key = i;
295                         key_in_use[i] = true;
296                         found = true;
297
298                         break;
299                 }
300         }
301
302         ret = pthread_mutex_unlock(&key_lock);
303         if (ret) return ret;
304
305         return (!found ? FOSA_EINVAL : ret);
306 }
307
308 /**
309  * fosa_key_destroy()
310  *
311  * Destroy a key
312  *
313  * This destroys the key and isables its use in the system
314  *
315  * return 0 if successful
316  *   FOSA_EINVAL The key is not initialised or is not in FOSA key range.
317  **/
318 int fosa_key_destroy(int key)
319 {
320         int ret;
321
322         ret = pthread_mutex_lock(&key_lock);
323         if (ret) return ret;
324
325         ret = pthread_key_delete(key_list[key]);
326         if (ret) return ret;
327
328         key_in_use[key]=false;
329
330         ret = pthread_mutex_unlock(&key_lock);
331         if (ret) return ret;
332
333         return 0;
334 }
335
336
337 /**
338  * fosa_thread_set_specific_data()
339  *
340  * Set thread-specific data
341  *
342  * For the thread identified by tid, the thread-specifid data field
343  * identified by key will be set to the value specified by value
344  * 
345  * In this implementation, according to POSIX, the accessed data field
346  * is the one of the calling thread, not the one specified via tid.
347  *
348  * Returns 0 if successful; otherwise, an error code is returned
349  *     EINVAL: the value of key is not between 0 and FOSA_MAX_KEYS-1
350  **/
351  int fosa_thread_set_specific_data(int key,
352                                    fosa_thread_id_t tid,
353                                    const void * value)
354 {
355         int ret;
356
357         /* only POSIX threads can have specific data */
358         if (!__fosa_check_thread(&tid))
359                 return FOSA_EINVAL;
360
361         ret = pthread_setspecific(key_list[key], value);
362
363         return ret ? ret : 0;
364 }
365
366 /**
367  * fosa_thread_get_specific_data()
368  *
369  * Get thread-specific data
370  *
371  * For the thread identified by tid, the thread-specifid data field
372  * identified by key will be copied to the variable pointed to by value
373  * 
374  * In this implementation, according to POSIX, the accessed data field
375  * is the one of the calling thread, not the one specified via tid.
376  *
377  * Returns 0 if successful; otherwise, an error code is returned
378  *     EINVAL: the value of key is not between 0 and FOSA_MAX_KEYS-1
379  **/
380 int fosa_thread_get_specific_data(int key,
381                                   fosa_thread_id_t tid,
382                                   void ** value)
383 {
384         /* only POSIX threads can have specific data */
385         if (!__fosa_check_thread(&tid))
386                 return EINVAL;
387
388         value = pthread_getspecific(key_list[key]);
389
390         return !value ? FOSA_EINVAL : 0;
391 }
392
393 /******************************************************************
394  * Thread scheduling
395  * 
396  * This implementation of FRSH assumes an underlying fixed priority
397  * scheduler with priorities in a range, with a minimum and a
398  * maximumm, a number of priority levels with at least 31
399  * priorities. A larger number implies a larger priority. In systems
400  * in which the underlying scheduler uses the opposite convention, a
401  * mapping is automatically provided by the OS adaptation layer.
402  *******************************************************************/
403
404 /**
405  * fosa_get_priority_max()
406  *
407  * Return the maximum priority value used in this implementation
408  **/
409 int fosa_get_priority_max()
410 {
411         int ret;
412
413         ret = sched_get_priority_max(SCHED_RR);
414
415         return ret ? errno : 0;
416 }
417
418 /**
419  * fosa_get_priority_min()
420  *
421  * Return the minimum priority value used in this implementation
422  **/
423 int fosa_get_priority_min()
424 {
425         int ret;
426
427         ret = sched_get_priority_min(SCHED_RR);
428
429         return ret ? errno : 0;
430 }
431
432 /**
433  * fosa_thread_attr_set_prio()
434  *
435  * Change the priority of a thread attributes object
436  *
437  * The priority of the thread attriutes object specified by attr is
438  * set to the value specified by prio. This function has no runtime
439  * effect on the priority, except when the attributes object is used
440  * to create a thread, when it will be created with the specified
441  * priority
442  * 
443  * Returns 0 if successful, or the following error code:
444  *    EINVAL: the specified priority value is not between the 
445  *            minimum and the maximum priorities defined in this
446  *            FRSH implementation
447  **/
448 int fosa_thread_attr_set_prio(fosa_thread_attr_t *attr, int prio)
449 {
450         int ret;
451         struct sched_param param;
452
453         param.sched_priority = prio;
454         ret = pthread_attr_setschedpolicy(attr, SCHED_RR);
455         if (ret) return ret;
456
457         return pthread_attr_setschedparam(attr, &param);
458 }
459
460 /**
461  * fosa_thread_attr_get_prio()
462  *
463  * Get the priority from a thread attributes object
464  *
465  * This function sets the variable pointed to by prio to the
466  * priority stored in the thread attributes object attr.
467  * 
468  * Returns 0
469  **/
470 int fosa_thread_attr_get_prio(const fosa_thread_attr_t *attr, int *prio)
471 {
472         int ret;
473         struct sched_param param;
474
475         ret = pthread_attr_getschedparam(attr, &param);
476         if (ret) return ret;
477
478         *prio = param.sched_priority;
479
480         return 0;
481 }
482
483 /**
484  * fosa_thread_set_prio()
485  *
486  * Dynamically change the priority of a thread
487  *
488  * The priority of the thread identified by tid is
489  * set to the value specified by prio. 
490  * 
491  * Returns 0 if successful, or the following error code:
492  *    EINVAL: the specified priority value is not between the 
493  *            minimum and the maximum priorities defined in this
494  *            FRSH implementation
495  **/
496 int fosa_thread_set_prio(fosa_thread_id_t tid, int prio)
497 {
498         int ret;
499         struct sched_param param;
500
501         param.sched_priority = prio;
502
503         ret = sched_setscheduler(0, SCHED_RR, &param);
504
505         return ret ? errno : 0;
506 }
507
508 /**
509  * fosa_thread_get_prio()
510  *
511  * Dynamically get the priority of a thread
512  *
513  * This function sets the variable pointed to by prio to the
514  * priority of the thread identified by tid
515  * 
516  * Returns 0
517  **/
518 int fosa_thread_get_prio(fosa_thread_id_t tid, int *prio)
519 {
520         struct sched_param param;
521         int ret;
522
523         ret = sched_getparam(0, &param);
524         *prio = param.sched_priority;
525
526         return ret ? errno : 0;
527 }
528
529 /*******************************************************************
530  * Signals
531  *
532  * Signals represent events that may be notified by the system, or
533  * sent explicitly by the application, and for which a thread may
534  * synchronously wait. Signals carry an associated piece of
535  * information (an integer or a pointer) and are queued until they are
536  * accepted.  Signals are identified by an integer signal number (of
537  * the type fosa_signal_t) in the range FOSA_SIGNAL_MIN,
538  * FOSA_SIGNAL_MAX.  This range is required to have at least <tbd>
539  * values.
540  *******************************************************************/
541
542 /**
543  * fosa_set_accepted_signals()
544  *
545  * Establish the set of signals that may be synchronously accepted 
546  * by the calling thread
547  *
548  * The function uses the array of signal numbers specified by set,
549  * which must be of size equal to size
550  *
551  * Returns 0 if successful; otherwise it returns an error code:
552  *     EINVAL: the array contains one or more values which are not
553  *             between FOSA_SIGNAL_MIN and FOSA_SIGNAL_MAX, or size
554  *             is less than 0
555  *
556  * Alternatively, in case of error the implementation is allowed to
557  * notify it to the system console and then terminate the FRSH
558  * implementation and dependant applications
559  **/
560 int fosa_set_accepted_signals(fosa_signal_t set[], int size)
561 {
562         int i, ret;
563         fosa_thread_id_t self;
564         sigset_t sigset;
565         struct sigaction action;
566
567         ret = sigemptyset(&sigset);
568         if (ret) goto err;
569
570         action.sa_handler = SIG_DFL;
571         action.sa_mask = sigset;
572         action.sa_flags = SA_SIGINFO;
573         action.sa_sigaction = NULL;
574
575         for (i = 0; i < size; i++) {
576                 ret = sigaddset(&sigset, set[i]);
577                 if (ret) goto err;
578                 ret = sigaction(set[i], &action, NULL);
579                 if (ret) goto err;
580         }
581
582         self = fosa_thread_self();
583         if (__fosa_check_thread(&self)) {
584                 ret = pthread_sigmask(SIG_BLOCK, &sigset, NULL);
585                 if (ret) return ret;
586         } else {
587                 ret = sigprocmask(SIG_BLOCK, &sigset, NULL);
588                 if (ret) goto err;
589         }
590
591         return 0;
592 err:
593         return errno;
594 }
595
596 /**
597  * fosa_signal_queue()
598  *
599  * Queue a signal
600  *
601  * This function is used to explicitly send a signal with a specified
602  * value
603  * 
604  * The signal number specified by signal is sent together with the
605  * information specified by info, to the thread identified by
606  * receiver. In those implementations that do not support queueing a
607  * signal with information to a thread (such as POSIX), the signal may
608  * be sent to any thread that is waiting for this signal via
609  * fosa_signal_wait(). Portability can be ensured by having the receiver
610  * thread be the one who is waiting for the signal. 
611  *
612  * In this implementation, this limitation has been overcome by means
613  * of the Linux specific capability of sending a timer event directly
614  * to a specific thread. Thus, we program a fake timer to fire immediately
615  * and notify such event to the requested receiver thread.
616  *
617  * Returns 0 if successful; otherwise it returns an error code:
618  *     EINVAL: the signal specified by signal is not
619  *              between FOSA_SIGNAL_MIN and FOSA_SIGNAL_MAX
620  *
621  *     EAGAIN: no resources are available to queue the signal; the
622  *             maximum number of queued signals has been reached, or a
623  *             systemwide resource limit has been exceeded
624  *
625  * Alternatively, in case of error the implementation is allowed to
626  * notify it to the system console and then terminate the FRSH
627  * implementation and dependant applications
628  **/
629 int fosa_signal_queue(fosa_signal_t signal,
630                       fosa_signal_info_t info,
631                       fosa_thread_id_t receiver)
632 {
633         int ret;
634         timer_t fake_timer;
635         struct itimerspec fake_time;
636         struct sigevent fake_event;
637
638         ret = timer_create(FOSA_CLOCK_REALTIME, &fake_event, &fake_timer);
639         if (ret) goto err;
640
641         fake_time.it_value.tv_sec = fake_time.it_value.tv_nsec = 0;
642         fake_time.it_interval.tv_sec = fake_time.it_interval.tv_nsec = 0;
643         fake_event.sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL;
644         fake_event.sigev_signo  = SIGRTMIN;
645         fake_event.sigev_value.sival_int = info.sival_int;
646         fake_event._sigev_un._tid = receiver.linux_tid;
647
648         ret = timer_settime(fake_timer, TIMER_ABSTIME, &fake_time, NULL);
649         if (ret) {
650                 timer_delete(fake_timer);
651                 goto err;
652         }
653
654         ret = timer_delete(fake_timer);
655         if (ret) goto err;
656
657         return 0;
658 err:
659         return errno;
660 }
661
662 /**
663  * fosa_signal_wait()
664  *
665  * Wait for a signal
666  * 
667  * The function waits for the arrival of one of the signals in the
668  * array of signal numbers specified by set, which must be of size
669  * equal to size. If there is a signal already queued, the function
670  * returns immediately. If there is no signal of the specified set
671  * queued, the calling thread is suspended until a signal from that
672  * set arrives. Upon return, if signal_received is not NULL the number
673  * of the signal received is stored in the variable pointed to by
674  * signal_received; and if info is not NULL the associated information
675  * is stored in the variable pointed to by info.
676  *
677  * Returns 0 if successful; otherwise it returns an error code:
678  *     EINVAL: the array contains one or more values which are not
679  *             between FOSA_SIGNAL_MIN and FOSA_SIGNAL_MAX, or size
680  *             is less than 0
681  *
682  * Alternatively, in case of error the implementation is allowed to
683  * notify it to the system console and then terminate the FRSH
684  * implementation and dependant applications
685  **/
686 int fosa_signal_wait(fosa_signal_t set[], int size,
687                      fosa_signal_t *signal_received,
688                      fosa_signal_info_t *info)
689 {
690         int i, ret;
691         sigset_t sigset;
692         siginfo_t siginfo;
693
694         ret = sigemptyset(&sigset);
695         if (ret) goto err;
696
697         for (i = 0; i < size; i++) {
698                 ret = sigaddset(&sigset,set[i]);
699                 if (ret) goto err;
700         }
701
702         ret = sigwaitinfo(&sigset, &siginfo);
703         if (ret) goto err;
704
705         if (info != NULL && signal_received != NULL)
706                 *signal_received = siginfo.si_signo;
707         if (info != NULL)
708                 *info = (fosa_signal_info_t) siginfo.si_value.sival_int;
709
710         return 0;
711 err:
712         return errno;
713 }
714
715 /**
716  * fosa_signal_timedwait()
717  *
718  * Timed wait for a signal
719  * 
720  * This function behaves the same as fosa_signal_wait(), except that
721  * the suspension time is limited to the time interval specified in
722  * the timespec structure referenced by timeout.
723  * 
724  * Returns 0 if successful; otherwise it returns an error code:
725  *     EINVAL: the array contains one or more values which are not
726  *             between FOSA_SIGNAL_MIN and FOSA_SIGNAL_MAX, or size
727  *             is less than 0, or timeout is invalid
728  *     EAGAIN: The timeout expired
729  *
730  * Alternatively, in case of error the implementation is allowed to
731  * notify it to the system console and then terminate the FRSH
732  * implementation and dependant applications
733  **/
734 int fosa_signal_timedwait(fosa_signal_t set[], int size,
735                           fosa_signal_t *signal_received,
736                           fosa_signal_info_t *info,
737                           const struct timespec *timeout)
738 {
739         int i, ret;
740         sigset_t signalset;
741         siginfo_t siginfo;
742
743         ret = sigemptyset(&signalset);
744         if (ret) goto err;
745
746         for (i = 0; i < size; i++) {
747                 ret = sigaddset(&signalset,set[i]);
748                 if (ret) goto err;
749         }
750
751         ret = sigtimedwait(&signalset,&siginfo,timeout);
752         if (ret) goto err;
753
754         if (signal_received != NULL)
755                 *signal_received = siginfo.si_signo;
756         if (info != NULL)
757                 *info = (fosa_signal_info_t) siginfo.si_value.sival_int;
758
759         return 0;
760 err:
761         return errno;
762 }
763