diff options
Diffstat (limited to 'linuxthreads/man')
22 files changed, 2095 insertions, 0 deletions
diff --git a/linuxthreads/man/Makefile b/linuxthreads/man/Makefile new file mode 100644 index 0000000000..4875c3d765 --- /dev/null +++ b/linuxthreads/man/Makefile @@ -0,0 +1,31 @@ +SOURCES=pthread_atfork.man pthread_attr_init.man pthread_cancel.man \ + pthread_cleanup_push.man pthread_cond_init.man \ + pthread_condattr_init.man pthread_create.man pthread_detach.man \ + pthread_equal.man pthread_exit.man pthread_join.man \ + pthread_key_create.man pthread_mutex_init.man \ + pthread_mutexattr_init.man pthread_once.man pthread_self.man \ + pthread_setschedparam.man pthread_sigmask.man sem_init.man \ + pthread_kill_other_threads_np.man + +MANPAGES=$(SOURCES:.man=.3thr) + +PREPRO=perl troffprepro + +MANDIR=/usr/man/man3 + +all: $(MANPAGES) + +.SUFFIXES: .man .3thr + +.man.3thr: + $(PREPRO) $*.man $*.3thr + +$(MANPAGES): troffprepro + +clean: + rm -f *.3thr + rm -f *~ + +install: + install *.3thr $(MANDIR) + @echo "*** Remember to run /usr/sbin/makewhatis `dirname $(MANDIR)` at some point" diff --git a/linuxthreads/man/pthread_atfork.man b/linuxthreads/man/pthread_atfork.man new file mode 100644 index 0000000000..4d06a56f8b --- /dev/null +++ b/linuxthreads/man/pthread_atfork.man @@ -0,0 +1,58 @@ +.TH PTHREAD_ATFORK 3 LinuxThreads + +.SH NAME +pthread_atfork \- register handlers to be called at fork(2) time + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)); + +.SH DESCRIPTION + +!pthread_atfork! registers handler functions to be called just before +and just after a new process is created with !fork!(2). The |prepare| +handler will be called from the parent process, just before the new +process is created. The |parent| handler will be called from the parent +process, just before !fork!(2) returns. The |child| handler will be +called from the child process, just before !fork!(2) returns. + +One or several of the three handlers |prepare|, |parent| and |child| +can be given as !NULL!, meaning that no handler needs to be called at +the corresponding point. + +!pthread_atfork! can be called several times to install several sets +of handlers. At !fork!(2) time, the |prepare| handlers are called in +LIFO order (last added with !pthread_atfork!, first called before !fork!), +while the |parent| and |child| handlers are called in FIFO order +(first added, first called). + +To understand the purpose of !pthread_atfork!, recall that !fork!(2) +duplicates the whole memory space, including mutexes in their current +locking state, but only the calling thread: other threads are not +running in the child process. Thus, if a mutex is locked by a thread +other than the thread calling !fork!, that mutex will remain locked +forever in the child process, possibly blocking the execution of the +child process. To avoid this, install handlers with !pthread_atfork! +as follows: the |prepare| handler locks the global mutexes (in locking +order), and the |parent| and |child| handlers unlock them (in +reverse order). Alternatively, |prepare| and |parent| can be set to +!NULL! and |child| to a function that calls !pthread_mutex_init! on +the global mutexes. + +.SH "RETURN VALUE" + +!pthread_atfork! returns 0 on success and a non-zero error code on error. + +.SH ERRORS +.TP +!ENOMEM! +insufficient memory available to register the handlers. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!fork!(2), +!pthread_mutex_lock!(3), +!pthread_mutex_unlock!(3). diff --git a/linuxthreads/man/pthread_attr_init.man b/linuxthreads/man/pthread_attr_init.man new file mode 100644 index 0000000000..bd5a169242 --- /dev/null +++ b/linuxthreads/man/pthread_attr_init.man @@ -0,0 +1,221 @@ +.TH PTHREAD_ATTR_INIT 3 LinuxThreads + +.XREF pthread_attr_destroy +.XREF pthread_attr_setdetachstate +.XREF pthread_attr_getdetachstate +.XREF pthread_attr_setschedparam +.XREF pthread_attr_getschedparam +.XREF pthread_attr_setschedpolicy +.XREF pthread_attr_getschedpolicy +.XREF pthread_attr_setinheritsched +.XREF pthread_attr_getinheritsched +.XREF pthread_attr_setscope +.XREF pthread_attr_getscope + +.SH NAME +pthread_attr_init, pthread_attr_destroy, pthread_attr_setdetachstate, pthread_attr_getdetachstate, pthread_attr_setschedparam, pthread_attr_getschedparam, pthread_attr_setschedpolicy, pthread_attr_getschedpolicy, pthread_attr_setinheritsched, pthread_attr_getinheritsched, pthread_attr_setscope, pthread_attr_getscope \- thread creation attributes + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_attr_init(pthread_attr_t *attr); + +int pthread_attr_destroy(pthread_attr_t *attr); + +int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); + +int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); + +int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy); + +int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy); + +int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param); + +int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param); + +int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit); + +int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inherit); + +int pthread_attr_setscope(pthread_attr_t *attr, int scope); + +int pthread_attr_getscope(const pthread_attr_t *attr, int *scope); + +.SH DESCRIPTION + +Setting attributes for threads is achieved by filling a +thread attribute object |attr| of type !pthread_attr_t!, then passing it as +second argument to !pthread_create!(3). Passing !NULL! is equivalent to +passing a thread attribute object with all attributes set to their +default values. + +!pthread_attr_init! initializes the thread attribute object |attr| and +fills it with default values for the attributes. (The default values +are listed below for each attribute.) + +Each attribute |attrname| (see below for a list of all attributes) can +be individually set using the function !pthread_attr_set!|attrname| +and retrieved using the function !pthread_attr_get!|attrname|. + +!pthread_attr_destroy! destroys a thread attribute object, which +must not be reused until it is reinitialized. !pthread_attr_destroy! +does nothing in the LinuxThreads implementation. + +Attribute objects are consulted only when creating a new thread. The +same attribute object can be used for creating several +threads. Modifying an attribute object after a call to +!pthread_create! does not change the attributes of the thread +previously created. + +The following thread attributes are supported: + +.SS detachstate + +Control whether the thread is created in the joinable state (value +!PTHREAD_CREATE_JOINABLE!) or in the detached state +(!PTHREAD_CREATE_DETACHED!). + +Default value: !PTHREAD_CREATE_JOINABLE!. + +In the joinable state, another thread can synchronize on the thread +termination and recover its termination code using !pthread_join!(3), +but some of the thread resources are kept allocated after the thread +terminates, and reclaimed only when another thread performs +!pthread_join!(3) on that thread. + +In the detached state, the thread resources are immediately freed when +it terminates, but !pthread_join!(3) cannot be used to synchronize on +the thread termination. + +A thread created in the joinable state can later be put in the +detached thread using !pthread_detach!(3). + +.SS schedpolicy + +Select the scheduling policy for the thread: one of +!SCHED_OTHER! (regular, non-realtime scheduling), +!SCHED_RR! (realtime, round-robin) or +!SCHED_FIFO! (realtime, first-in first-out). See +!sched_setpolicy!(2) for more information on scheduling policies. + +Default value: !SCHED_OTHER!. + +The realtime scheduling policies !SCHED_RR! and !SCHED_FIFO! are +available only to processes with superuser privileges. + +The scheduling policy of a thread can be changed after creation with +!pthread_setschedparam!(3). + +.SS schedparam + +Contain the scheduling parameters (essentially, the scheduling +priority) for the thread. See !sched_setparam!(2) for more information +on scheduling parameters. + +Default value: priority is 0. + +This attribute is not significant if the scheduling policy is !SCHED_OTHER!; +it only matters for the realtime policies !SCHED_RR! and !SCHED_FIFO!. + +The scheduling priority of a thread can be changed after creation with +!pthread_setschedparam!(3). + +.SS inheritsched + +Indicate whether the scheduling policy and scheduling parameters for +the newly created thread are determined by the values of the +|schedpolicy| and |schedparam| attributes (value +!PTHREAD_EXPLICIT_SCHED!) or are inherited from the parent thread +(value !PTHREAD_INHERIT_SCHED!). + +Default value: !PTHREAD_EXPLICIT_SCHED!. + +.SS scope + +Define the scheduling contention scope for the created thread. The +only value supported in the LinuxThreads implementation is +!PTHREAD_SCOPE_SYSTEM!, meaning that the threads contend for CPU time +with all processes running on the machine. In particular, thread +priorities are interpreted relative to the priorities of all other +processes on the machine. The other value specified by the standard, +!PTHREAD_SCOPE_PROCESS!, means that scheduling contention occurs only +between the threads of the running process: thread priorities are +interpreted relative to the priorities of the other threads of the +process, regardless of the priorities of other processes. +!PTHREAD_SCOPE_PROCESS! is not supported in LinuxThreads. + +Default value: !PTHREAD_SCOPE_SYSTEM!. + +.SH "RETURN VALUE" + +All functions return 0 on success and a non-zero error code on error. +On success, the !pthread_attr_get!|attrname| functions also store the +current value of the attribute |attrname| in the location pointed to +by their second argument. + +.SH ERRORS + +The !pthread_attr_setdetachstate! function returns the following error +codes on error: +.RS +.TP +!EINVAL! +the specified |detachstate| is not one of !PTHREAD_CREATE_JOINABLE! or +!PTHREAD_CREATE_DETACHED!. +.RE + +The !pthread_attr_setschedparam! function returns the following error +codes on error: +.RS +.TP +!EINVAL! +the priority specified in |param| is outside the range of allowed +priorities for the scheduling policy currently in |attr| +(1 to 99 for !SCHED_FIFO! and !SCHED_RR!; 0 for !SCHED_OTHER!). +.RE + +The !pthread_attr_setschedpolicy! function returns the following error +codes on error: +.RS +.TP +!EINVAL! +the specified |policy| is not one of !SCHED_OTHER!, !SCHED_FIFO!, or +!SCHED_RR!. + +.TP +!ENOTSUP! +|policy| is !SCHED_FIFO! or !SCHED_RR!, and the effective user of the +calling process is not super-user. +.RE + +The !pthread_attr_setinheritsched! function returns the following error +codes on error: +.RS +.TP +!EINVAL! +the specified |inherit| is not one of !PTHREAD_INHERIT_SCHED! or +!PTHREAD_EXPLICIT_SCHED!. +.RE + +The !pthread_attr_setscope! function returns the following error +codes on error: +.RS +.TP +!EINVAL! +the specified |scope| is not one of !PTHREAD_SCOPE_SYSTEM! or +!PTHREAD_SCOPE_PROCESS!. + +.TP +!ENOTSUP! +the specified |scope| is !PTHREAD_SCOPE_PROCESS! (not supported). +.RE + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_create!(3), +!pthread_join!(3), +!pthread_detach!(3), +!pthread_setschedparam!(3). diff --git a/linuxthreads/man/pthread_cancel.man b/linuxthreads/man/pthread_cancel.man new file mode 100644 index 0000000000..202d5c9b26 --- /dev/null +++ b/linuxthreads/man/pthread_cancel.man @@ -0,0 +1,155 @@ +.TH PTHREAD_CANCEL 3 LinuxThreads + +.XREF pthread_setcancelstate +.XREF pthread_setcanceltype +.XREF pthread_testcancel + +.SH NAME +pthread_cancel, pthread_setcancelstate, pthread_setcanceltype, pthread_testcancel \- thread cancellation + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_cancel(pthread_t thread); + +int pthread_setcancelstate(int state, int *oldstate); + +int pthread_setcanceltype(int type, int *oldtype); + +void pthread_testcancel(void); + +.SH DESCRIPTION + +Cancellation is the mechanism by which a thread can terminate the +execution of another thread. More precisely, a thread can send a +cancellation request to another thread. Depending on its settings, the +target thread can then either ignore the request, honor it +immediately, or defer it till it reaches a cancellation point. + +When a thread eventually honors a cancellation request, it performs as +if !pthread_exit(PTHREAD_CANCELED)! has been called at that point: +all cleanup handlers are executed in reverse order, finalization +functions for thread-specific data are called, and finally the thread +stops executing with the return value !PTHREAD_CANCELED!. See +!pthread_exit!(3) for more information. + +!pthread_cancel! sends a cancellation request to the thread denoted +by the |thread| argument. + +!pthread_setcancelstate! changes the cancellation state for the +calling thread -- that is, whether cancellation requests are ignored +or not. The |state| argument is the new cancellation state: either +!PTHREAD_CANCEL_ENABLE! to enable cancellation, or +!PTHREAD_CANCEL_DISABLE! to disable cancellation (cancellation +requests are ignored). If |oldstate| is not !NULL!, the previous +cancellation state is stored in the location pointed to by |oldstate|, +and can thus be restored later by another call to +!pthread_setcancelstate!. + +!pthread_setcanceltype! changes the type of responses to cancellation +requests for the calling thread: asynchronous (immediate) or deferred. +The |type| argument is the new cancellation type: either +!PTHREAD_CANCEL_ASYNCHRONOUS! to cancel the calling thread as soon as +the cancellation request is received, or !PTHREAD_CANCEL_DEFERRED! to +keep the cancellation request pending until the next cancellation +point. If |oldtype| is not !NULL!, the previous +cancellation state is stored in the location pointed to by |oldtype|, +and can thus be restored later by another call to +!pthread_setcanceltype!. + +Threads are always created by !pthread_create!(3) with cancellation +enabled and deferred. That is, the initial cancellation state is +!PTHREAD_CANCEL_ENABLE! and the initial type is +!PTHREAD_CANCEL_DEFERRED!. + +Cancellation points are those points in the program execution where a +test for pending cancellation requests is performed and cancellation +is executed if positive. The following POSIX threads functions +are cancellation points: + +!pthread_join!(3) +.br +!pthread_cond_wait!(3) +.br +!pthread_cond_timedwait!(3) +.br +!pthread_testcancel!(3) +.br +!sem_wait!(3) +.br +!sigwait!(3) + +All other POSIX threads functions are guaranteed not to be +cancellation points. That is, they never perform cancellation in +deferred cancellation mode. + +!pthread_testcancel! does nothing except testing for pending +cancellation and executing it. Its purpose is to introduce explicit +checks for cancellation in long sequences of code that do not call +cancellation point functions otherwise. + +.SH "RETURN VALUE" + +!pthread_cancel!, !pthread_setcancelstate! and +!pthread_setcanceltype! return 0 on success and a non-zero error code +on error. + +.SH ERRORS +!pthread_cancel! returns the following error code on error: +.RS +.TP +!ESRCH! +no thread could be found corresponding to that specified by the |thread| ID. +.RE + +!pthread_setcancelstate! returns the following error code on error: +.RS +.TP +!EINVAL! +the |state| argument is not !PTHREAD_CANCEL_ENABLE! nor +!PTHREAD_CANCEL_DISABLE! +.RE + +!pthread_setcanceltype! returns the following error code on error: +.RS +.TP +!EINVAL! +the |type| argument is not !PTHREAD_CANCEL_DEFERRED! nor +!PTHREAD_CANCEL_ASYNCHRONOUS! +.RE + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_exit!(3), +!pthread_cleanup_push!(3), +!pthread_cleanup_pop!(3). + +.SH BUGS + +POSIX specifies that a number of system calls (basically, all +system calls that may block, such as !read!(2), !write!(2), !wait!(2), +etc.) and library functions that may call these system calls (e.g. +!fprintf!(3)) are cancellation points. LinuxThreads is not yet +integrated enough with the C library to implement this, and thus none +of the C library functions is a cancellation point. + +For system calls at least, there is a workaround. Cancellation +requests are transmitted to the target thread by sending it a +signal. That signal will interrupt all blocking system calls, causing +them to return immediately with the !EINTR! error. So, checking for +cancellation during a !read! system call, for instance, can be +achieved as follows: + +.RS +.ft 3 +.nf +.sp +pthread_testcancel(); +retcode = read(fd, buffer, length); +pthread_testcancel(); +.ft +.LP +.RE +.fi diff --git a/linuxthreads/man/pthread_cleanup_push.man b/linuxthreads/man/pthread_cleanup_push.man new file mode 100644 index 0000000000..1591431c9c --- /dev/null +++ b/linuxthreads/man/pthread_cleanup_push.man @@ -0,0 +1,194 @@ +.TH PTHREAD_CLEANUP 3 LinuxThreads + +.XREF pthread_cleanup_pop +.XREF pthread_cleanup_push_defer_np +.XREF pthread_cleanup_pop_restore_np + +.SH NAME +pthread_cleanup_push, pthread_cleanup_pop, pthread_cleanup_push_defer_np, pthread_cleanup_pop_restore_np \- install and remove cleanup handlers + +.SH SYNOPSIS +#include <pthread.h> + +void pthread_cleanup_push(void (*routine) (void *), void *arg); + +void pthread_cleanup_pop(int execute); + +void pthread_cleanup_push_defer_np(void (*routine) (void *), void *arg); + +void pthread_cleanup_pop_restore_np(int execute); + +.SH DESCRIPTION + +Cleanup handlers are functions that get called when a thread +terminates, either by calling !pthread_exit!(3) or because of +cancellation. Cleanup handlers are installed and removed following a +stack-like discipline. + +The purpose of cleanup handlers is to free the resources that a thread +may hold at the time it terminates. In particular, if a thread +exits or is cancelled while it owns a locked mutex, the mutex will +remain locked forever and prevent other threads from executing +normally. The best way to avoid this is, just before locking the +mutex, to install a cleanup handler whose effect is to unlock the +mutex. Cleanup handlers can be used similarly to free blocks allocated +with !malloc!(3) or close file descriptors on thread termination. + +!pthread_cleanup_push! installs the |routine| function with argument +|arg| as a cleanup handler. From this point on to the matching +!pthread_cleanup_pop!, the function |routine| will be called with +arguments |arg| when the thread terminates, either through !pthread_exit!(3) +or by cancellation. If several cleanup handlers are active at that +point, they are called in LIFO order: the most recently installed +handler is called first. + +!pthread_cleanup_pop! removes the most recently installed cleanup +handler. If the |execute| argument is not 0, it also executes the +handler, by calling the |routine| function with arguments |arg|. If +the |execute| argument is 0, the handler is only removed but not +executed. + +Matching pairs of !pthread_cleanup_push! and !pthread_cleanup_pop! +must occur in the same function, at the same level of block nesting. +Actually, !pthread_cleanup_push! and !pthread_cleanup_pop! are macros, +and the expansion of !pthread_cleanup_push! introduces an open brace !{! +with the matching closing brace !}! being introduced by the expansion +of the matching !pthread_cleanup_pop!. + +!pthread_cleanup_push_defer_np! is a non-portable extension that +combines !pthread_cleanup_push! and !pthread_setcanceltype!(3). +It pushes a cleanup handler just as !pthread_cleanup_push! does, but +also saves the current cancellation type and sets it to deferred +cancellation. This ensures that the cleanup mechanism is effective +even if the thread was initially in asynchronous cancellation mode. + +!pthread_cleanup_pop_restore_np! pops a cleanup handler introduced by +!pthread_cleanup_push_defer_np!, and restores the cancellation type to +its value at the time !pthread_cleanup_push_defer_np! was called. + +!pthread_cleanup_push_defer_np! and !pthread_cleanup_pop_restore_np! +must occur in matching pairs, at the same level of block nesting. + +The following sequence + +.RS +.ft 3 +.nf +.sp +pthread_cleanup_push_defer_np(routine, arg); +... +pthread_cleanup_pop_defer_np(execute); +.ft +.LP +.RE +.fi + +is functionally equivalent to (but more compact and more efficient than) + +.RS +.ft 3 +.nf +.sp +{ int oldtype; + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); + pthread_cleanup_push(routine, arg); + ... + pthread_cleanup_pop(execute); + pthread_setcanceltype(oldtype, NULL); +} +.ft +.LP +.RE +.fi + +.SH "RETURN VALUE" + +None. + +.SH ERRORS + +None. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_exit!(3), +!pthread_cancel!(3), +!pthread_setcanceltype!(3). + +.SH EXAMPLE + +Here is how to lock a mutex |mut| in such a way that it will be +unlocked if the thread is canceled while |mut| is locked: + +.RS +.ft 3 +.nf +.sp +pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut); +pthread_mutex_lock(&mut); +/* do some work */ +pthread_mutex_unlock(&mut); +pthread_cleanup_pop(0); +.ft +.LP +.RE +.fi + +Equivalently, the last two lines can be replaced by + +.RS +.ft 3 +.nf +.sp +pthread_cleanup_pop(1); +.ft +.LP +.RE +.fi + +Notice that the code above is safe only in deferred cancellation mode +(see !pthread_setcanceltype!(3)). In asynchronous cancellation mode, +a cancellation can occur between !pthread_cleanup_push! and +!pthread_mutex_lock!, or between !pthread_mutex_unlock! and +!pthread_cleanup_pop!, resulting in both cases in the thread trying to +unlock a mutex not locked by the current thread. This is the main +reason why asynchronous cancellation is difficult to use. + +If the code above must also work in asynchronous cancellation mode, +then it must switch to deferred mode for locking and unlocking the +mutex: + +.RS +.ft 3 +.nf +.sp +pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); +pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut); +pthread_mutex_lock(&mut); +/* do some work */ +pthread_cleanup_pop(1); +pthread_setcanceltype(oldtype, NULL); +.ft +.LP +.RE +.fi + +The code above can be rewritten in a more compact and more +efficient way, using the non-portable functions +!pthread_cleanup_push_defer_np! and !pthread_cleanup_pop_restore_np!: + +.RS +.ft 3 +.nf +.sp +pthread_cleanup_push_restore_np(pthread_mutex_unlock, (void *) &mut); +pthread_mutex_lock(&mut); +/* do some work */ +pthread_cleanup_pop_restore_np(1); +.ft +.LP +.RE +.fi + diff --git a/linuxthreads/man/pthread_cond_init.man b/linuxthreads/man/pthread_cond_init.man new file mode 100644 index 0000000000..b803f08361 --- /dev/null +++ b/linuxthreads/man/pthread_cond_init.man @@ -0,0 +1,235 @@ +.TH PTHREAD_COND 3 LinuxThreads + +.XREF pthread_cond_signal +.XREF pthread_cond_broadcast +.XREF pthread_cond_wait +.XREF pthread_cond_timedwait +.XREF pthread_cond_destroy + +.SH NAME +pthread_cond_init, pthread_cond_destroy, pthread_cond_signal, pthread_cond_broadcast, pthread_cond_wait, pthread_cond_timedwait \- operations on conditions + +.SH SYNOPSIS +#include <pthread.h> + +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + +int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); + +int pthread_cond_signal(pthread_cond_t *cond); + +int pthread_cond_broadcast(pthread_cond_t *cond); + +int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); + +int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); + +int pthread_cond_destroy(pthread_cond_t *cond); + +.SH DESCRIPTION + +A condition (short for ``condition variable'') is a synchronization +device that allows threads to suspend execution and relinquish the +processors until some predicate on shared data is satisfied. The basic +operations on conditions are: signal the condition (when the +predicate becomes true), and wait for the condition, suspending the +thread execution until another thread signals the condition. + +A condition variable must always be associated with a mutex, to avoid +the race condition where a thread prepares to wait on a condition +variable and another thread signals the condition just before the +first thread actually waits on it. + +!pthread_cond_init! initializes the condition variable |cond|, using the +condition attributes specified in |cond_attr|, or default attributes +if |cond_attr| is !NULL!. The LinuxThreads implementation supports no +attributes for conditions, hence the |cond_attr| parameter is actually +ignored. + +Variables of type !pthread_cond_t! can also be initialized +statically, using the constant !PTHREAD_COND_INITIALIZER!. + +!pthread_cond_signal! restarts one of the threads that are waiting on +the condition variable |cond|. If no threads are waiting on |cond|, +nothing happens. If several threads are waiting on |cond|, exactly one +is restarted, but it is not specified which. + +!pthread_cond_broadcast! restarts all the threads that are waiting on +the condition variable |cond|. Nothing happens if no threads are +waiting on |cond|. + +!pthread_cond_wait! atomically unlocks the |mutex| (as per +!pthread_unlock_mutex!) and waits for the condition variable |cond| to +be signaled. The thread execution is suspended and does not consume +any CPU time until the condition variable is signaled. The |mutex| +must be locked by the calling thread on entrance to +!pthread_cond_wait!. Before returning to the calling thread, +!pthread_cond_wait! re-acquires |mutex| (as per !pthread_lock_mutex!). + +Unlocking the mutex and suspending on the condition variable is done +atomically. Thus, if all threads always acquire the mutex before +signaling the condition, this guarantees that the condition cannot be +signaled (and thus ignored) between the time a thread locks the mutex +and the time it waits on the condition variable. + +!pthread_cond_timedwait! atomically unlocks |mutex| and waits on +|cond|, as !pthread_cond_wait! does, but it also bounds the duration +of the wait. If |cond| has not been signaled within the amount of time +specified by |abstime|, the mutex |mutex| is re-acquired and +!pthread_cond_timedwait! returns the error !ETIMEDOUT!. +The |abstime| parameter specifies an absolute time, with the same +origin as !time!(2) and !gettimeofday!(2): an |abstime| of 0 +corresponds to 00:00:00 GMT, January 1, 1970. + +!pthread_cond_destroy! destroys a condition variable, freeing the +resources it might hold. No threads must be waiting on the condition +variable on entrance to !pthread_cond_destroy!. In the LinuxThreads +implementation, no resources are associated with condition variables, +thus !pthread_cond_destroy! actually does nothing except checking that +the condition has no waiting threads. + +.SH CANCELLATION + +!pthread_cond_wait! and !pthread_cond_timedwait! are cancellation +points. If a thread is cancelled while suspended in one of these +functions, the thread immediately resumes execution, then locks again +the |mutex| argument to !pthread_cond_wait! and +!pthread_cond_timedwait!, and finally executes the cancellation. +Consequently, cleanup handlers are assured that |mutex| is locked when +they are called. + +.SH "ASYNC-SIGNAL SAFETY" + +The condition functions are not async-signal safe, and should not be +called from a signal handler. In particular, calling +!pthread_cond_signal! or !pthread_cond_broadcast! from a signal +handler may deadlock the calling thread. + +.SH "RETURN VALUE" + +All condition variable functions return 0 on success and a non-zero +error code on error. + +.SH ERRORS + +!pthread_cond_init!, !pthread_cond_signal!, !pthread_cond_broadcast!, +and !pthread_cond_wait! never return an error code. + +The !pthread_cond_timedwait! function returns the following error codes +on error: +.RS +.TP +!ETIMEDOUT! +the condition variable was not signaled until the timeout specified by +|abstime| + +.TP +!EINTR! +!pthread_cond_timedwait! was interrupted by a signal +.RE + +The !pthread_cond_destroy! function returns the following error code +on error: +.RS +.TP +!EBUSY! +some threads are currently waiting on |cond|. +.RE + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_condattr_init!(3), +!pthread_mutex_lock!(3), +!pthread_mutex_unlock!(3), +!gettimeofday!(2), +!nanosleep!(2). + +.SH EXAMPLE + +Consider two shared variables |x| and |y|, protected by the mutex |mut|, +and a condition variable |cond| that is to be signaled whenever |x| +becomes greater than |y|. + +.RS +.ft 3 +.nf +.sp +int x,y; +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +.ft +.LP +.RE +.fi + +Waiting until |x| is greater than |y| is performed as follows: + +.RS +.ft 3 +.nf +.sp +pthread_mutex_lock(&mut); +while (x <= y) { + pthread_cond_wait(&cond, &mut); +} +/* operate on x and y */ +pthread_mutex_unlock(&mut); +.ft +.LP +.RE +.fi + +Modifications on |x| and |y| that may cause |x| to become greater than +|y| should signal the condition if needed: + +.RS +.ft 3 +.nf +.sp +pthread_mutex_lock(&mut); +/* modify x and y */ +if (x > y) pthread_mutex_broadcast(&cond); +pthread_mutex_unlock(&mut); +.ft +.LP +.RE +.fi + +If it can be proved that at most one waiting thread needs to be waken +up (for instance, if there are only two threads communicating through +|x| and |y|), !pthread_cond_signal! can be used as a slightly more +efficient alternative to !pthread_cond_broadcast!. In doubt, use +!pthread_cond_broadcast!. + +To wait for |x| to becomes greater than |y| with a timeout of 5 +seconds, do: + +.RS +.ft 3 +.nf +.sp +struct timeval now; +struct timespec timeout; +int retcode; + +pthread_mutex_lock(&mut); +gettimeofday(&now); +timeout.tv_sec = now.tv_sec + 5; +timeout.tv_nsec = now.tv_usec * 1000; +retcode = 0; +while (x <= y && retcode != ETIMEDOUT) { + retcode = pthread_cond_timedwait(&cond, &mut, &timeout); +} +if (retcode == ETIMEDOUT) { + /* timeout occurred */ +} else { + /* operate on x and y */ +} +pthread_mutex_unlock(&mut); +.ft +.LP +.RE +.fi + diff --git a/linuxthreads/man/pthread_condattr_init.man b/linuxthreads/man/pthread_condattr_init.man new file mode 100644 index 0000000000..f491cbedbe --- /dev/null +++ b/linuxthreads/man/pthread_condattr_init.man @@ -0,0 +1,39 @@ +.TH PTHREAD_CONDATTR 3 LinuxThreads + +.XREF pthread_condattr_destroy + +.SH NAME +pthread_condattr_init, pthread_condattr_destroy \- condition creation attributes + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_condattr_init(pthread_condattr_t *attr); + +int pthread_condattr_destroy(pthread_condattr_t *attr); + +.SH DESCRIPTION + +Condition attributes can be specified at condition creation time, by passing a +condition attribute object as second argument to !pthread_cond_init!(3). +Passing !NULL! is equivalent to passing a condition attribute object with +all attributes set to their default values. + +The LinuxThreads implementation supports no attributes for +conditions. The functions on condition attributes are included only +for compliance with the POSIX standard. + +!pthread_condattr_init! initializes the condition attribute object +|attr| and fills it with default values for the attributes. +!pthread_condattr_destroy! destroys a condition attribute object, +which must not be reused until it is reinitialized. Both functions do +nothing in the LinuxThreads implementation. + +.SH "RETURN VALUE" +!pthread_condattr_init! and !pthread_condattr_destroy! always return 0. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_cond_init!(3). diff --git a/linuxthreads/man/pthread_create.man b/linuxthreads/man/pthread_create.man new file mode 100644 index 0000000000..a94004767a --- /dev/null +++ b/linuxthreads/man/pthread_create.man @@ -0,0 +1,46 @@ +.TH PTHREAD_CREATE 3 LinuxThreads + +.SH NAME +pthread_create \- create a new thread + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg); + +.SH DESCRIPTION +!pthread_create! creates a new thread of control that executes +concurrently with the calling thread. The new thread applies the +function |start_routine| passing it |arg| as first argument. The new +thread terminates either explicitly, by calling !pthread_exit!(3), +or implicitly, by returning from the |start_routine| function. The +latter case is equivalent to calling !pthread_exit!(3) with the result +returned by |start_routine| as exit code. + +The |attr| argument specifies thread attributes to be applied to the +new thread. See !pthread_attr_init!(3) for a complete list of thread +attributes. The |attr| argument can also be !NULL!, in which case +default attributes are used: the created thread is joinable (not +detached) and has default (non real-time) scheduling policy. + +.SH "RETURN VALUE" +On success, the identifier of the newly created thread is stored in +the location pointed by the |thread| argument, and a 0 is returned. On +error, a non-zero error code is returned. + +.SH ERRORS +.TP +!EAGAIN! +not enough system resources to create a process for the new thread. +.TP +!EAGAIN! +more than !PTHREAD_THREADS_MAX! threads are already active. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_exit!(3), +!pthread_join!(3), +!pthread_detach!(3), +!pthread_attr_init!(3). diff --git a/linuxthreads/man/pthread_detach.man b/linuxthreads/man/pthread_detach.man new file mode 100644 index 0000000000..7b43f45faa --- /dev/null +++ b/linuxthreads/man/pthread_detach.man @@ -0,0 +1,44 @@ +.TH PTHREAD_DETACH 3 LinuxThreads + +.SH NAME +pthread_detach \- put a running thread in the detached state + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_detach(pthread_t th); + +.SH DESCRIPTION +!pthread_detach! put the thread |th| in the detached state. This +guarantees that the memory resources consumed by |th| will be freed +immediately when |th| terminates. However, this prevents other threads +from synchronizing on the termination of |th| using !pthread_join!. + +A thread can be created initially in the detached state, using the +!detachstate! attribute to !pthread_create!(3). In contrast, +!pthread_detach! applies to threads created in the joinable state, and +which need to be put in the detached state later. + +After !pthread_detach! completes, subsequent attempts to perform +!pthread_join! on |th| will fail. If another thread is already joining +the thread |th| at the time !pthread_detach! is called, +!pthread_detach! does nothing and leaves |th| in the joinable state. + +.SH "RETURN VALUE" +On success, 0 is returned. On error, a non-zero error code is returned. + +.SH ERRORS +.TP +!ESRCH! +No thread could be found corresponding to that specified by |th| +.TP +!EINVAL! +the thread |th| is already in the detached state + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_create!(3), +!pthread_join!(3), +!pthread_attr_setdetachstate!(3).
\ No newline at end of file diff --git a/linuxthreads/man/pthread_equal.man b/linuxthreads/man/pthread_equal.man new file mode 100644 index 0000000000..1a0396515a --- /dev/null +++ b/linuxthreads/man/pthread_equal.man @@ -0,0 +1,23 @@ +.TH PTHREAD_EQUAL 3 LinuxThreads + +.SH NAME +pthread_equal \- compare two thread identifiers + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_equal(pthread_t thread1, pthread_t thread2); + +.SH DESCRIPTION +!pthread_equal! determines if two thread identifiers refer to the same +thread. + +.SH "RETURN VALUE" +A non-zero value is returned if |thread1| and |thread2| refer to the +same thread. Otherwise, 0 is returned. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_self!(3). diff --git a/linuxthreads/man/pthread_exit.man b/linuxthreads/man/pthread_exit.man new file mode 100644 index 0000000000..54751e9d05 --- /dev/null +++ b/linuxthreads/man/pthread_exit.man @@ -0,0 +1,32 @@ +.TH PTHREAD_EXIT 3 LinuxThreads + +.SH NAME +pthread_exit \- terminate the calling thread + +.SH SYNOPSIS +#include <pthread.h> + +void pthread_exit(void *retval); + +.SH DESCRIPTION +!pthread_exit! terminates the execution of the calling thread. +All cleanup handlers that have been set for the calling thread with +!pthread_cleanup_push!(3) are executed in reverse order (the most +recently pushed handler is executed first). Finalization functions for +thread-specific data are then called for all keys that have non-!NULL! +values associated with them in the calling thread (see +!pthread_key_create!(3)). Finally, execution of the calling thread is +stopped. + +The |retval| argument is the return value of the thread. It can be +consulted from another thread using !pthread_join!(3). + +.SH "RETURN VALUE" +The !pthread_exit! function never returns. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_create!(3), +!pthread_join!(3). diff --git a/linuxthreads/man/pthread_join.man b/linuxthreads/man/pthread_join.man new file mode 100644 index 0000000000..d587093841 --- /dev/null +++ b/linuxthreads/man/pthread_join.man @@ -0,0 +1,70 @@ +.TH PTHREAD_JOIN 3 LinuxThreads + +.SH NAME +pthread_join \- wait for termination of another thread + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_join(pthread_t th, void **thread_return); + +.SH DESCRIPTION +!pthread_join! suspends the execution of the calling thread until the +thread identified by |th| terminates, either by calling !pthread_exit!(3) +or by being cancelled. + +If |thread_return| is not !NULL!, the return value of |th| is stored +in the location pointed to by |thread_return|. The return value of +|th| is either the argument it gave to !pthread_exit!(3), or +!PTHREAD_CANCELED! if |th| was cancelled. + +The joined thread !th! must be in the joinable state: it must not have +been detached using !pthread_detach!(3) or the +!PTHREAD_CREATE_DETACHED! attribute to !pthread_create!(3). + +When a joinable thread terminates, its memory resources (thread +descriptor and stack) are not deallocated until another thread +performs !pthread_join! on it. Therefore, !pthread_join! must be +called once for each joinable thread created to avoid memory leaks. + +At most one thread can wait for the termination of a given +thread. Calling !pthread_join! on a thread |th| on which another +thread is already waiting for termination returns an error. + +.SH CANCELLATION + +!pthread_join! is a cancellation point. If a thread is canceled while +suspended in !pthread_join!, the thread execution resumes immediately +and the cancellation is executed without waiting for the |th| thread +to terminate. If cancellation occurs during !pthread_join!, the |th| +thread remains not joined. + +.SH "RETURN VALUE" +On success, the return value of |th| is stored in the location pointed +to by |thread_return|, and 0 is returned. On error, a non-zero error +code is returned. + +.SH ERRORS +.TP +!ESRCH! +No thread could be found corresponding to that specified by |th|. +.TP +!EINVAL! +The |th| thread has been detached. +.TP +!EINVAL! +Another thread is already waiting on termination of |th|. +.TP +!EDEADLK! +The |th| argument refers to the calling thread. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_exit!(3), +!pthread_detach!(3), +!pthread_create!(3), +!pthread_attr_setdetachstate!(3), +!pthread_cleanup_push!(3), +!pthread_key_create!(3). diff --git a/linuxthreads/man/pthread_key_create.man b/linuxthreads/man/pthread_key_create.man new file mode 100644 index 0000000000..6823e304c9 --- /dev/null +++ b/linuxthreads/man/pthread_key_create.man @@ -0,0 +1,151 @@ +.TH PTHREAD_SPECIFIC 3 LinuxThreads + +.SH NAME +pthread_key_create, pthread_key_delete, pthread_setspecific, pthread_getspecific \- management of thread-specific data + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)); + +int pthread_key_delete(pthread_key_t key); + +int pthread_setspecific(pthread_key_t key, const void *pointer); + +void * pthread_getspecific(pthread_key_t key); + +.SH DESCRIPTION + +Programs often need global or static variables that have different +values in different threads. Since threads share one memory space, +this cannot be achieved with regular variables. Thread-specific data +is the POSIX threads answer to this need. + +Each thread possesses a private memory block, the thread-specific data +area, or TSD area for short. This area is indexed by TSD keys. The TSD +area associates values of type !void *! to TSD keys. TSD keys are +common to all threads, but the value associated with a given TSD key +can be different in each thread. + +For concreteness, the TSD areas can be viewed as arrays of !void *! +pointers, TSD keys as integer indices into these arrays, and the value +of a TSD key as the value of the corresponding array element in the +calling thread. + +When a thread is created, its TSD area initially associates !NULL! +with all keys. + +!pthread_key_create! allocates a new TSD key. The key is stored in the +location pointed to by |key|. There is a limit of !PTHREAD_KEYS_MAX! +on the number of keys allocated at a given time. The value initially +associated with the returned key is !NULL! in all currently executing +threads. + +The |destr_function| argument, if not !NULL!, specifies a destructor +function associated with the key. When a thread terminates via +!pthread_exit! or by cancellation, |destr_function| is called with +arguments the value associated with the key in that thread. The +|destr_function| is not called if that value is !NULL!. The order in +which destructor functions are called at thread termination time is +unspecified. + +Before the destructor function is called, the !NULL! value is +associated with the key in the current thread. A destructor function +might, however, re-associate non-!NULL! values to that key or some +other key. To deal with this, if after all the destructors have been +called for all non-!NULL! values, there are still some non-!NULL! +values with associated destructors, then the process is repeated. The +LinuxThreads implementation stops the process after +!PTHREAD_DESTRUCTOR_ITERATIONS! iterations, even if some non-!NULL! +values with associated descriptors remain. Other implementations may +loop indefinitely. + +!pthread_key_delete! deallocates a TSD key. It does not check whether +non-!NULL! values are associated with that key in the currently +executing threads, nor call the destructor function associated with +the key. + +!pthread_setspecific! changes the value associated with |key| in the +calling thread, storing the given |pointer| instead. + +!pthread_getspecific! returns the value currently associated with +|key| in the calling thread. + +.SH "RETURN VALUE" + +!pthread_key_create!, !pthread_key_delete!, and !pthread_setspecific! +return 0 on success and a non-zero error code on failure. If +successful, !pthread_key_create! stores the newly allocated key in the +location pointed to by its |key| argument. + +!pthread_getspecific! returns the value associated with |key| on +success, and !NULL! on error. + +.SH ERRORS +!pthread_key_create! returns the following error code on error: +.RS +.TP +!EAGAIN! +!PTHREAD_KEYS_MAX! keys are already allocated +.RE + +!pthread_key_delete! and !pthread_setspecific! return the following +error code on error: +.RS +.TP +!EINVAL! +|key| is not a valid, allocated TSD key +.RE + +!pthread_getspecific! returns !NULL! if |key| is not a valid, +allocated TSD key. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +pthread_create(3), pthread_exit(3), pthread_testcancel(3). + +.SH EXAMPLE + +The following code fragment allocates a thread-specific array of 100 +characters, with automatic reclaimation at thread exit: + +.RS +.ft 3 +.nf +.sp +/* Key for the thread-specific buffer */ +static pthread_key_t buffer_key; + +/* Once-only initialisation of the key */ +static pthread_once_t buffer_key_once = PTHREAD_ONCE_INIT; + +/* Allocate the thread-specific buffer */ +void buffer_alloc(void) +{ + pthread_once(&buffer_key_once, buffer_key_alloc); + pthread_setspecific(buffer_key, malloc(100)); +} + +/* Return the thread-specific buffer */ +char * get_buffer(void) +{ + return (char *) pthread_getspecific(buffer_key); +} + +/* Allocate the key */ +static void buffer_key_alloc() +{ + pthread_key_create(&buffer_key, buffer_destroy); +} + +/* Free the thread-specific buffer */ +static void buffer_destroy(void * buf) +{ + free(buf); +} +.ft +.LP +.RE +.fi diff --git a/linuxthreads/man/pthread_kill_other_threads_np.man b/linuxthreads/man/pthread_kill_other_threads_np.man new file mode 100644 index 0000000000..0de42d52d5 --- /dev/null +++ b/linuxthreads/man/pthread_kill_other_threads_np.man @@ -0,0 +1,40 @@ +.TH PTHREAD_KILL_OTHER_THREADS_NP 3 LinuxThreads + +.SH NAME +pthread_kill_other_threads_np \- terminate all threads in program except calling thread + +.SH SYNOPSIS +#include <pthread.h> + +void pthread_kill_other_threads_np(void); + +.SH DESCRIPTION +!pthread_kill_other_threads_np! is a non-portable LinuxThreads extension. +It causes all threads in the program to terminate immediately, except +the calling thread which proceeds normally. It is intended to be +called just before a thread calls one of the !exec! functions, +e.g. !execve!(2). + +Termination of the other threads is not performed through +!pthread_cancel!(3) and completely bypasses the cancellation +mechanism. Hence, the current settings for cancellation state and +cancellation type are ignored, and the cleanup handlers are not +executed in the terminated threads. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!execve!(2), +!pthread_setcancelstate!(3), +!pthread_setcanceltype!(3), +!pthread_cancel!(3). + +.SH BUGS + +According to POSIX 1003.1c, a successful !exec*! in one of the threads +should terminate automatically all other threads in the program. +This behavior is not yet implemented in LinuxThreads. +Calling !pthread_kill_other_threads_np! before !exec*! achieves much +of the same behavior, except that if !exec*! ultimately fails, then +all other threads are already killed. diff --git a/linuxthreads/man/pthread_mutex_init.man b/linuxthreads/man/pthread_mutex_init.man new file mode 100644 index 0000000000..bda4ec6c4d --- /dev/null +++ b/linuxthreads/man/pthread_mutex_init.man @@ -0,0 +1,213 @@ +.TH PTHREAD_MUTEX 3 LinuxThreads + +.XREF pthread_mutex_lock +.XREF pthread_mutex_unlock +.XREF pthread_mutex_trylock +.XREF pthread_mutex_destroy + +.SH NAME +pthread_mutex_init, pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutex_destroy \- operations on mutexes + +.SH SYNOPSIS +#include <pthread.h> + +pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; + +pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + +pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; + +int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); + +int pthread_mutex_lock(pthread_mutex_t *mutex)); + +int pthread_mutex_trylock(pthread_mutex_t *mutex); + +int pthread_mutex_unlock(pthread_mutex_t *mutex); + +int pthread_mutex_destroy(pthread_mutex_t *mutex); + +.SH DESCRIPTION +A mutex is a MUTual EXclusion device, and is useful for protecting +shared data structures from concurrent modifications, and implementing +critical sections and monitors. + +A mutex has two possible states: unlocked (not owned by any thread), +and locked (owned by one thread). A mutex can never be owned by two +different threads simultaneously. A thread attempting to lock a mutex +that is already locked by another thread is suspended until the owning +thread unlocks the mutex first. + +!pthread_mutex_init! initializes the mutex object pointed to by +|mutex| according to the mutex attributes specified in |mutexattr|. +If |mutexattr| is !NULL!, default attributes are used instead. + +The LinuxThreads implementation supports only one mutex attributes, +the |mutex kind|, which is either ``fast'', ``recursive'', or +``error checking''. The kind of a mutex determines whether +it can be locked again by a thread that already owns it. +The default kind is ``fast''. See !pthread_mutexattr_init!(3) for more +information on mutex attributes. + +Variables of type !pthread_mutex_t! can also be initialized +statically, using the constants !PTHREAD_MUTEX_INITIALIZER! (for fast +mutexes), !PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP! (for recursive +mutexes), and !PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP! (for error checking +mutexes). + +!pthread_mutex_lock! locks the given mutex. If the mutex is currently +unlocked, it becomes locked and owned by the calling thread, and +!pthread_mutex_lock! returns immediately. If the mutex is already +locked by another thread, !pthread_mutex_lock! suspends the calling +thread until the mutex is unlocked. + +If the mutex is already locked by the calling thread, the behavior of +!pthread_mutex_lock! depends on the kind of the mutex. If the mutex is +of the ``fast'' kind, the calling thread is suspended until the mutex +is unlocked, thus effectively causing the calling thread to +deadlock. If the mutex is of the ``error checking'' kind, +!pthread_mutex_lock! returns immediately with the error code !EDEADLK!. +If the mutex is of the ``recursive'' kind, !pthread_mutex_lock! +succeeds and returns immediately, recording the number of times the +calling thread has locked the mutex. An equal number of +!pthread_mutex_unlock! operations must be performed before the mutex +returns to the unlocked state. + +!pthread_mutex_trylock! behaves identically to !pthread_mutex_lock!, +except that it does not block the calling thread if the mutex is +already locked by another thread (or by the calling thread in the case +of a ``fast'' mutex). Instead, !pthread_mutex_trylock! returns +immediately with the error code !EBUSY!. + +!pthread_mutex_unlock! unlocks the given mutex. The mutex is assumed +to be locked and owned by the calling thread on entrance to +!pthread_mutex_unlock!. If the mutex is of the ``fast'' kind, +!pthread_mutex_unlock! always returns it to the unlocked state. If it +is of the ``recursive'' kind, it decrements the locking count of the +mutex (number of !pthread_mutex_lock! operations performed on it by +the calling thread), and only when this count reaches zero is the +mutex actually unlocked. + +On ``error checking'' mutexes, !pthread_mutex_unlock! actually checks +at run-time that the mutex is locked on entrance, and that it was +locked by the same thread that is now calling !pthread_mutex_unlock!. +If these conditions are not met, an error code is returned and the +mutex remains unchanged. ``Fast'' and ``recursive'' mutexes perform +no such checks, thus allowing a locked mutex to be unlocked by a +thread other than its owner. This is non-portable behavior and must +not be relied upon. + +!pthread_mutex_destroy! destroys a mutex object, freeing the resources +it might hold. The mutex must be unlocked on entrance. In the +LinuxThreads implementation, no resources are associated with mutex +objects, thus !pthread_mutex_destroy! actually does nothing except +checking that the mutex is unlocked. + +.SH CANCELLATION + +None of the mutex functions is a cancellation point, not even +!pthread_mutex_lock!, in spite of the fact that it can suspend a +thread for arbitrary durations. This way, the status of mutexes at +cancellation points is predictable, allowing cancellation handlers to +unlock precisely those mutexes that need to be unlocked before the +thread stops executing. Consequently, threads using deferred +cancellation should never hold a mutex for extended periods of time. + +.SH "ASYNC-SIGNAL SAFETY" + +The mutex functions are not async-signal safe. What this means is that +they should not be called from a signal handler. In particular, +calling !pthread_mutex_lock! or !pthread_mutex_unlock! from a signal +handler may deadlock the calling thread. + +.SH "RETURN VALUE" + +!pthread_mutex_init! always returns 0. The other mutex functions +return 0 on success and a non-zero error code on error. + +.SH ERRORS + +The !pthread_mutex_lock! function returns the following error code +on error: +.RS +.TP +!EINVAL! +the mutex has not been properly initialized. + +.TP +!EDEADLK! +the mutex is already locked by the calling thread +(``error checking'' mutexes only). +.RE + +The !pthread_mutex_trylock! function returns the following error codes +on error: +.RS +.TP +!EBUSY! +the mutex could not be acquired because it was currently locked. + +.TP +!EINVAL! +the mutex has not been properly initialized. +.RE + +The !pthread_mutex_unlock! function returns the following error code +on error: +.RS +.TP +!EINVAL! +the mutex has not been properly initialized. + +.TP +!EPERM! +the calling thread does not own the mutex (``error checking'' mutexes only). +.RE + +The !pthread_mutex_destroy! function returns the following error code +on error: +.RS +.TP +!EBUSY! +the mutex is currently locked. +.RE + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_mutexattr_init!(3), +!pthread_mutexattr_setkind_np!(3), +!pthread_cancel!(3). + +.SH EXAMPLE + +A shared global variable |x| can be protected by a mutex as follows: + +.RS +.ft 3 +.nf +.sp +int x; +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; +.ft +.LP +.RE +.fi + +All accesses and modifications to |x| should be bracketed by calls to +!pthread_mutex_lock! and !pthread_mutex_unlock! as follows: + +.RS +.ft 3 +.nf +.sp +pthread_mutex_lock(&mut); +/* operate on x */ +pthread_mutex_unlock(&mut); +.ft +.LP +.RE +.fi + + diff --git a/linuxthreads/man/pthread_mutexattr_init.man b/linuxthreads/man/pthread_mutexattr_init.man new file mode 100644 index 0000000000..5ceefdbb56 --- /dev/null +++ b/linuxthreads/man/pthread_mutexattr_init.man @@ -0,0 +1,84 @@ +.TH PTHREAD_MUTEXATTR 3 LinuxThreads + +.XREF pthread_mutexattr_destroy +.XREF pthread_mutexattr_setkind_np +.XREF pthread_mutexattr_getkind_np + +.SH NAME +pthread_mutexattr_init, pthread_mutexattr_destroy, pthread_mutexattr_setkind_np, pthread_mutexattr_getkind_np \- mutex creation attributes + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_mutexattr_init(pthread_mutexattr_t *attr); + +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); + +int pthread_mutexattr_setkind_np(pthread_mutexattr_t *attr, int kind); + +int pthread_mutexattr_getkind_np(const pthread_mutexattr_t *attr, int *kind); + +.SH DESCRIPTION + +Mutex attributes can be specified at mutex creation time, by passing a +mutex attribute object as second argument to !pthread_mutex_init!(3). +Passing !NULL! is equivalent to passing a mutex attribute object with +all attributes set to their default values. + +!pthread_mutexattr_init! initializes the mutex attribute object |attr| +and fills it with default values for the attributes. + +!pthread_mutexattr_destroy! destroys a mutex attribute object, which +must not be reused until it is reinitialized. !pthread_mutexattr_destroy! +does nothing in the LinuxThreads implementation. + +LinuxThreads supports only one mutex attribute: the mutex kind, which +is either !PTHREAD_MUTEX_FAST_NP! for ``fast'' mutexes, +!PTHREAD_MUTEX_RECURSIVE_NP! for ``recursive'' mutexes, +or !PTHREAD_MUTEX_ERRORCHECK_NP! for ``error checking'' mutexes. +As the !NP! suffix indicates, this is a non-portable extension to the +POSIX standard and should not be employed in portable programs. + +The mutex kind determines what happens if a thread attempts to lock a +mutex it already owns with !pthread_mutex_lock!(3). If the mutex is of +the ``fast'' kind, !pthread_mutex_lock!(3) simply suspends the calling +thread forever. If the mutex is of the ``error checking'' kind, +!pthread_mutex_lock!(3) returns immediately with the error code +!EDEADLK!. If the mutex is of the ``recursive'' kind, the call to +!pthread_mutex_lock!(3) returns immediately with a success return +code. The number of times the thread owning the mutex has locked it is +recorded in the mutex. The owning thread must call +!pthread_mutex_unlock!(3) the same number of times before the mutex +returns to the unlocked state. + +The default mutex kind is ``fast'', that is, !PTHREAD_MUTEX_FAST_NP!. + +!pthread_mutexattr_setkind_np! sets the mutex kind attribute in |attr| +to the value specified by |kind|. + +!pthread_mutexattr_getkind_np! retrieves the current value of the +mutex kind attribute in |attr| and stores it in the location pointed +to by |kind|. + +.SH "RETURN VALUE" +!pthread_mutexattr_init!, !pthread_mutexattr_destroy! and +!pthread_mutexattr_getkind_np! always return 0. + +!pthread_mutexattr_setkind_np! returns 0 on success and a non-zero +error code on error. + +.SH ERRORS + +On error, !pthread_mutexattr_setkind_np! returns the following error code: +.TP +!EINVAL! +|kind| is neither !PTHREAD_MUTEX_FAST_NP! nor !PTHREAD_MUTEX_RECURSIVE_NP! +nor !PTHREAD_MUTEX_ERRORCHECK_NP! + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_mutex_init!(3), +!pthread_mutex_lock!(3), +!pthread_mutex_unlock!(3). diff --git a/linuxthreads/man/pthread_once.man b/linuxthreads/man/pthread_once.man new file mode 100644 index 0000000000..e9d117b656 --- /dev/null +++ b/linuxthreads/man/pthread_once.man @@ -0,0 +1,34 @@ +.TH PTHREAD_ONCE 3 LinuxThreads + +.SH NAME +pthread_once \- once-only initialization + +.SH SYNOPSIS +#include <pthread.h> + +pthread_once_t once_control = PTHREAD_ONCE_INIT; + +int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)); + +.SH DESCRIPTION + +The purpose of !pthread_once! is to ensure that a piece of +initialization code is executed at most once. The |once_control| +argument points to a static or extern variable statically initialized +to !PTHREAD_ONCE_INIT!. + +The first time !pthread_once! is called with a given |once_control| +argument, it calls |init_routine| with no argument and changes the +value of the |once_control| variable to record that initialization has +been performed. Subsequent calls to !pthread_once! with the same +!once_control! argument do nothing. + +.SH "RETURN VALUE" +!pthread_once! always returns 0. + +.SH ERRORS +None. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + diff --git a/linuxthreads/man/pthread_self.man b/linuxthreads/man/pthread_self.man new file mode 100644 index 0000000000..3aa4a0021e --- /dev/null +++ b/linuxthreads/man/pthread_self.man @@ -0,0 +1,23 @@ +.TH PTHREAD_SELF 3 LinuxThreads + +.SH NAME +pthread_self \- return identifier of current thread + +.SH SYNOPSIS +#include <pthread.h> + +pthread_t pthread_self(void); + +.SH DESCRIPTION +!pthread_self! return the thread identifier for the calling thread. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_equal!(3), +!pthread_join!(3), +!pthread_detach!(3), +!pthread_setschedparam!(3), +!pthread_getschedparam!(3). + diff --git a/linuxthreads/man/pthread_setschedparam.man b/linuxthreads/man/pthread_setschedparam.man new file mode 100644 index 0000000000..3992927837 --- /dev/null +++ b/linuxthreads/man/pthread_setschedparam.man @@ -0,0 +1,79 @@ +.TH PTHREAD_SETSCHEDPARAM 3 LinuxThreads + +.XREF pthread_getschedparam + +.SH NAME +pthread_setschedparam, pthread_getschedparam \- control thread scheduling parameters + +.SH SYNOPSIS +#include <pthread.h> + +int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param); + +int pthread_getschedparam(pthread_t target_thread, int *policy, struct sched_param *param); + +.SH DESCRIPTION + +!pthread_setschedparam! sets the scheduling parameters for the thread +|target_thread| as indicated by |policy| and |param|. |policy| can be +either !SCHED_OTHER! (regular, non-realtime scheduling), !SCHED_RR! +(realtime, round-robin) or !SCHED_FIFO! (realtime, first-in +first-out). |param| specifies the scheduling priority for the two +realtime policies. See !sched_setpolicy!(2) for more information on +scheduling policies. + +The realtime scheduling policies !SCHED_RR! and !SCHED_FIFO! are +available only to processes with superuser privileges. + +!pthread_getschedparam! retrieves the scheduling policy and scheduling +parameters for the thread |target_thread| and store them in the +locations pointed to by |policy| and |param|, respectively. + +.SH "RETURN VALUE" +!pthread_setschedparam! and !pthread_getschedparam! return 0 on +success and a non-zero error code on error. + +.SH ERRORS +On error, !pthread_setschedparam! returns the following error codes: +.RS +.TP +!EINVAL! +|policy| is not one of !SCHED_OTHER!, !SCHED_RR!, !SCHED_FIFO! + +.TP +!EINVAL! +the priority value specified by |param| is not valid for the specified policy + +.TP +!EPERM! +the calling process does not have superuser permissions + +.TP +!ESRCH! +the |target_thread| is invalid or has already terminated + +.TP +!EFAULT! +|param| points outside the process memory space +.RE + +On error, !pthread_getschedparam! returns the following error codes: +.RS +.TP +!ESRCH! +the |target_thread| is invalid or has already terminated + +.TP +!EFAULT! +|policy| or |param| point outside the process memory space +.RE + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!sched_setscheduler!(2), +!sched_getscheduler!(2), +!sched_getparam!(2), +!pthread_attr_setschedpolicy!(3), +!pthread_attr_setschedparam!(3). diff --git a/linuxthreads/man/pthread_sigmask.man b/linuxthreads/man/pthread_sigmask.man new file mode 100644 index 0000000000..784161da2b --- /dev/null +++ b/linuxthreads/man/pthread_sigmask.man @@ -0,0 +1,123 @@ +.TH PTHREAD_SIGNAL 3 LinuxThreads + +.XREF pthread_kill +.XREF sigwait + +.SH NAME +pthread_sigmask, pthread_kill, sigwait \- handling of signals in threads + +.SH SYNOPSIS +#include <pthread.h> +.br +#include <signal.h> + +int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask); + +int pthread_kill(pthread_t thread, int signo); + +int sigwait(const sigset_t *set, int *sig); + +.SH DESCRIPTION + +!pthread_sigmask! changes the signal mask for the calling thread as +described by the |how| and |newmask| arguments. If |oldmask| is not +!NULL!, the previous signal mask is stored in the location pointed to +by |oldmask|. + +The meaning of the |how| and |newmask| arguments is the same as for +!sigprocmask!(2). If |how| is !SIG_SETMASK!, the signal mask is set to +|newmask|. If |how| is !SIG_BLOCK!, the signals specified to |newmask| +are added to the current signal mask. If |how| is !SIG_UNBLOCK!, the +signals specified to |newmask| are removed from the current signal +mask. + +Recall that signal masks are set on a per-thread basis, but signal +actions and signal handlers, as set with !sigaction!(2), are shared +between all threads. + +!pthread_kill! send signal number |signo| to the thread +|thread|. The signal is delivered and handled as described in +!kill!(2). + +!sigwait! suspends the calling thread until one of the signals +in |set| is delivered to the calling thread. It then stores the number +of the signal received in the location pointed to by |sig| and +returns. The signals in |set| must be blocked and not ignored on +entrance to !sigwait!. If the delivered signal has a signal handler +function attached, that function is |not| called. + +.SH CANCELLATION + +!sigwait! is a cancellation point. + +.SH "RETURN VALUE" + +On success, 0 is returned. On failure, a non-zero error code is returned. + +.SH ERRORS + +The !pthread_sigmask! function returns the following error codes +on error: +.RS +.TP +!EINVAL! +|how| is not one of !SIG_SETMASK!, !SIG_BLOCK!, or !SIG_UNBLOCK! + +.TP +!EFAULT! +|newmask| or |oldmask| point to invalid addresses +.RE + +The !pthread_kill! function returns the following error codes +on error: +.RS +.TP +!EINVAL! +|signo| is not a valid signal number + +.TP +!ESRCH! +the thread |thread| does not exist (e.g. it has already terminated) +.RE + +The !sigwait! function never returns an error. + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!sigprocmask!(2), +!kill!(2), +!sigaction!(2), +!sigsuspend!(2). + +.SH NOTES + +For !sigwait! to work reliably, the signals being waited for must be +blocked in all threads, not only in the calling thread, since +otherwise the POSIX semantics for signal delivery do not guarantee +that it's the thread doing the !sigwait! that will receive the signal. +The best way to achieve this is block those signals before any threads +are created, and never unblock them in the program other than by +calling !sigwait!. + +.SH BUGS + +Signal handling in LinuxThreads departs significantly from the POSIX +standard. According to the standard, ``asynchronous'' (external) +signals are addressed to the whole process (the collection of all +threads), which then delivers them to one particular thread. The +thread that actually receives the signal is any thread that does +not currently block the signal. + +In LinuxThreads, each thread is actually a kernel process with its own +PID, so external signals are always directed to one particular thread. +If, for instance, another thread is blocked in !sigwait! on that +signal, it will not be restarted. + +The LinuxThreads implementation of !sigwait! installs dummy signal +handlers for the signals in |set| for the duration of the wait. Since +signal handlers are shared between all threads, other threads must not +attach their own signal handlers to these signals, or alternatively +they should all block these signals (which is recommended anyway -- +see the Notes section). diff --git a/linuxthreads/man/sem_init.man b/linuxthreads/man/sem_init.man new file mode 100644 index 0000000000..e3a1a63e36 --- /dev/null +++ b/linuxthreads/man/sem_init.man @@ -0,0 +1,132 @@ +.TH SEMAPHORES 3 LinuxThreads + +.XREF sem_wait +.XREF sem_trywait +.XREF sem_post +.XREF sem_getvalue +.XREF sem_destroy + +.SH NAME +sem_init, sem_wait, sem_trywait, sem_post, sem_getvalue, sem_destroy \- operations on semaphores + +.SH SYNOPSIS +#include <semaphore.h> + +int sem_init(sem_t *sem, int pshared, unsigned int value); + +int sem_wait(sem_t * sem); + +int sem_trywait(sem_t * sem); + +int sem_post(sem_t * sem); + +int sem_getvalue(sem_t * sem, int * sval); + +int sem_destroy(sem_t * sem); + +.SH DESCRIPTION +This manual page documents POSIX 1003.1b semaphores, not to be +confused with SystemV semaphores as described in !ipc!(5), !semctl!(2) +and !semop!(2). + +Semaphores are counters for resources shared between threads. The +basic operations on semaphores are: increment the counter atomically, +and wait until the counter is non-null and decrement it atomically. + +!sem_init! initializes the semaphore object pointed to by |sem|. The +count associated with the semaphore is set initially to |value|. The +|pshared| argument indicates whether the semaphore is local to the +current process (|pshared| is zero) or is to be shared between several +processes (|pshared| is not zero). LinuxThreads currently does not +support process-shared semaphores, thus !sem_init! always returns with +error !ENOSYS! if |pshared| is not zero. + +!sem_wait! suspends the calling thread until the semaphore pointed to +by |sem| has non-zero count. It then atomically decreases the +semaphore count. + +!sem_trywait! is a non-blocking variant of !sem_wait!. If the +semaphore pointed to by |sem| has non-zero count, the count is +atomically decreased and !sem_trywait! immediately returns 0. +If the semaphore count is zero, !sem_trywait! immediately returns with +error !EAGAIN!. + +!sem_post! atomically increases the count of the semaphore pointed to +by |sem|. This function never blocks and can safely be used in +asynchronous signal handlers. + +!sem_getvalue! stores in the location pointed to by |sval| the current +count of the semaphore |sem|. + +!sem_destroy! destroys a semaphore object, freeing the resources it +might hold. No threads should be waiting on the semaphore at the time +!sem_destroy! is called. In the LinuxThreads implementation, no +resources are associated with semaphore objects, thus !sem_destroy! +actually does nothing except checking that no thread is waiting on the +semaphore. + +.SH CANCELLATION + +!sem_wait! is a cancellation point. + +.SH "ASYNC-SIGNAL SAFETY" + +On processors supporting atomic compare-and-swap (Intel 486, Pentium +and later, Alpha, PowerPC, MIPS II, Motorola 68k), the !sem_post! +function is async-signal safe and can therefore be +called from signal handlers. This is the only thread synchronization +function provided by POSIX threads that is async-signal safe. + +On the Intel 386 and the Sparc, the current LinuxThreads +implementation of !sem_post! is not async-signal safe by lack of the +required atomic operations. + +.SH "RETURN VALUE" + +The !sem_wait! and !sem_getvalue! functions always return 0. +All other semaphore functions return 0 on success and -1 on error, in +addition to writing an error code in !errno!. + +.SH ERRORS + +The !sem_init! function sets !errno! to the following codes on error: +.RS +.TP +!EINVAL! +|value| exceeds the maximal counter value !SEM_VALUE_MAX! +.TP +!ENOSYS! +|pshared| is not zero +.RE + +The !sem_trywait! function sets !errno! to the following error code on error: +.RS +.TP +!EAGAIN! +the semaphore count is currently 0 +.RE + +The !sem_post! function sets !errno! to the following error code on error: +.RS +.TP +!ERANGE! +after incrementation, the semaphore value would exceed !SEM_VALUE_MAX! +(the semaphore count is left unchanged in this case) +.RE + +The !sem_destroy! function sets !errno! to the following error code on error: +.RS +.TP +!EBUSY! +some threads are currently blocked waiting on the semaphore. +.RE + +.SH AUTHOR +Xavier Leroy <Xavier.Leroy@inria.fr> + +.SH "SEE ALSO" +!pthread_mutex_init!(3), +!pthread_cond_init!(3), +!pthread_cancel!(3), +!ipc!(5). + diff --git a/linuxthreads/man/troffprepro b/linuxthreads/man/troffprepro new file mode 100755 index 0000000000..ba564fefbe --- /dev/null +++ b/linuxthreads/man/troffprepro @@ -0,0 +1,68 @@ +#!/usr/local/bin/perl + +$insynopsis = 0; + +open(INPUT, $ARGV[0]) || die("cannot open $ARGV[0]"); +open(OUTPUT, "> $ARGV[1]") || die("cannot create $ARGV[1]"); + +select(OUTPUT); + +line: +while(<INPUT>) { + if (/^\.XREF (.*)$/) { + $xref = $1; + $_ = $ARGV[1]; + m/^.*\.(([1-8]).*)$/; + $suffix = $1; + $extension = $2; + open(XREF, "> $xref.$suffix"); + print XREF ".so man$extension/$ARGV[1]\n"; + close(XREF); + next line; + } + if (/^\.SH/) { + $insynopsis = /SYNOPSIS/; + print $_; + next; + } + if ($insynopsis) { + if (/^#/) { + print ".B ", $_; + } + elsif (/^[a-z]/) { + chop; +# if (m/^([a-zA-Z][a-zA-Z0-9_]*\s+[a-zA-Z][a-zA-Z0-9_]*)\(/) { +# print ".B \"", $1, "\"\n"; +# $_ = '(' . $'; +# } +# s/([a-zA-Z][a-zA-Z0-9_]*)(\s*[,()=])/" \1 "\2/g; + s/([ *])([a-zA-Z][a-zA-Z0-9_]*)(\s*[,)=])/\1" \2 "\3/g; + print ".BI \"", $_, "\"\n"; + } + else { + print $_; + } + next; + } + chop; + s/!([^!]+)!\|([^|]+)\|([^\s]*)\s*/\n.BI "\1" "\2\3"\n/g; + s/([!|])([^!|]+)\1([^\s]*)\s*/do subst($1,$2,$3)/eg; + s/^\n+//; + s/\n+$//; + s/\n\n+/\n/g; + print $_, "\n"; +} + +close(INPUT); +close(OUTPUT); + +sub subst { + local ($a, $b, $c) = @_; + if ($c) { + "\n" . ($a eq "!" ? ".BR " : ".IR ") . "\"$b\" $c\n" + } else { + "\n" . ($a eq "!" ? ".B " : ".I ") . "\"$b\"\n" + } +} + + |