Updated: October 28, 2024 |
Control a thread
#include <sys/neutrino.h> int ThreadCtl( int cmd, void * data ); int ThreadCtl_r( int cmd, void * data ); int ThreadCtlExt( pid_t pid, int tid, int cmd, void * data ); int ThreadCtlExt_r( pid_t pid, int tid, int cmd, void * data );
If pid is not 0, tid must not be 0 either.
You can execute _NTO_TCTL_IO, _NTO_TCTL_IO_LEVEL, and _NTO_TCTL_IO_PRIV only on the calling thread.
For more information, see below.
libc
Use the -l c option to qcc to link against this library. This library is usually included automatically.
These kernel calls allow you to make OS-specific changes to a thread. ThreadCtl() and ThreadCtl_r() always target the calling thread; ThreadCtlExt() and ThreadCtlExt_r() let you specify the process ID and thread ID.
The ThreadCtl() and ThreadCtl_r() functions are identical except in the way they indicate errors. The same goes for ThreadCtlExt() and ThreadCtlExt_r(). See the Returns section for details.
The sections that follow describe the possible commands.
_NTO_TCTL_ADD_EXIT_EVENT
ThreadCtl( _NTO_TCTL_ADD_EXIT_EVENT, data );
(QNX Neutrino 7.0.1 or later) This command adds a event that's to be delivered when the given thread is terminated. The data argument must be a pointer to a sigevent that specifies how the notification is to be made. The event doesn't need to be registered.
_NTO_TCTL_ALIGN_FAULT
ThreadCtl( _NTO_TCTL_ALIGN_FAULT, data );
This command controls the response to a misaligned access. The data argument must be a pointer to an int whose value indicates how you want to respond:
The function sets data to a positive or negative number, indicating the previous state of the alignment-fault handling.
_NTO_TCTL_DEL_EXIT_EVENT
ThreadCtl( _NTO_TCTL_DEL_EXIT_EVENT, 0 );
(QNX Neutrino 7.0.1 or later) This command deletes a event that was meant to be delivered when the given thread is terminated. See _NTO_TCTL_ADD_EXIT_EVENT, above.
_NTO_TCTL_IO, _NTO_TCTL_IO_LEVEL, _NTO_TCTL_IO_PRIV
ThreadCtl( _NTO_TCTL_IO, 0 ); ThreadCtl( _NTO_TCTL_IO_LEVEL, iolevel); ThreadCtl( _NTO_TCTL_IO_PRIV, 0 );
The commands _NTO_TCTL_IO and _NTO_TCTL_IO_PRIV are no longer recommended, though we still support them (see Legacy privilege commands below for details). Instead, use _NTO_TCTL_IO_LEVEL, as it is now the preferred method to request an I/O privilege level. The iolevel argument is used to specify the level of I/O privilege. All threads are created with the level _NTO_IO_LEVEL_NONE unless a thread inherits I/O privileges from its parent thread:
I/O privileges let you: | See: |
---|---|
Execute the in, ins, out, and outs I/O opcodes on x86_64 | in16(), inbe16(), inle16(), in16s(), in32(), inbe32(), inle32(), in32s(), in8(), in8s(), out16(), outbe16(), outle16(), out16s(), out32(), outbe32(), outle32(), out32s(), out8(), out8s() |
Disable and enable interrupts | InterruptDisable(), InterruptEnable(), InterruptLock(), InterruptUnlock() |
Tell the kernel not to track interrupt maskings and unmaskings for each interrupt handler | InterruptMask(), InterruptUnmask() |
Add event handlers | TraceEvent() |
On AARCH64, when your thread acquires _NTO_IO_LEVEL_2 privileges, it then runs at Exception Level 1 (EL1). This is a major security vulnerability, because the thread has all CPU privileges associated with this level, including the ability to access and modify kernel data. Your thread should acquire these privileges only as long as necessary, and relinquish them as soon as possible.
Therefore, you must be extra careful to not have programming errors in code running with this level of execution privilege, as the damage to your system could be substantial.
If iolevel is invalid, ThreadCtl() returns EINVAL.
If a thread had _NTO_IO_LEVEL_1 or _NTO_IO_LEVEL_2, you can drop to _NTO_IO_LEVEL_NONE even if the thread has an interrupt handler attached. In addition, the process's memory stays superlocked.
By default, the I/O level isn't inherited by child threads. If you want it to be inherited, OR _NTO_TCTL_IO_LEVEL_INHERIT with the iolevel. For example:
ThreadCtl(_NTO_TCTL_IO_LEVEL, (void*)iolevel|_NTO_TCTL_IO_LEVEL_INHERIT);
To turn off the inheritance, issue the command again with the same iolevel but without _NTO_TCTL_IO_LEVEL_INHERIT:
ThreadCtl(_NTO_TCTL_IO_LEVEL, (void*)iolevel);
If you specify a level of _NTO_IO_LEVEL_NONE, then _NTO_TCTL_IO_LEVEL_INHERIT is ignored.
Legacy privilege commands
The _NTO_TCTL_IO and _NTO_TCTL_IO_PRIV forms of this command are respectively converted to the following:
ThreadCtl( _NTO_TCTL_IO_LEVEL, _NTO_IO_LEVEL_1 | _NTO_TCTL_IO_LEVEL_INHERIT ) ThreadCtl( _NTO_TCTL_IO_LEVEL, _NTO_IO_LEVEL_2 | _NTO_TCTL_IO_LEVEL_INHERIT )
_NTO_TCTL_LOW_LATENCY
ThreadCtl( _NTO_TCTL_LOW_LATENCY, NULL );
(QNX Neutrino 7.0 or later) This flag hints to the scheduler that this thread needs to be scheduled on the same CPU as the one that the kernel is on when the scheduling decision is made. This saves the time it would take to schedule the thread on a different CPU.
For example, let's say a thread T is blocked on a call to InterruptWait(). When the interrupt that the thread is waiting on is asserted, the kernel executes a user-supplied Interrupt Service Routine. If the ISR sends a sigevent to the thread waiting on InterruptWait(), thread T is typically scheduled next. During this scheduling decision, if the kernel is on CPU 0, thread T could get selected to run on CPU 1, which would cause an interprocessor interrupt (IPI) and could waste valuable time. However, if you set _NTO_TCTL_LOW_LATENCY for thread T, it's scheduled to run on CPU 0 (the same CPU as the kernel).
Note that this flag doesn't determine whether a thread gets scheduled to run at all on any of the CPUs. That's determined by the runmask and the priority of the thread. This flag simply says that if the kernel does decide to schedule this thread on a CPU, that the CPU it chooses should be the same CPU as the kernel.
This flag and the thread's runmask could conflict. Suppose on a two-CPU system, a thread sets its runmask so that it can run only on CPU 0, and it also sets the _NTO_TCTL_LOW_LATENCY flag. If the kernel enters CPU 1, the thread isn't scheduled on CPU 1 because runmask settings are higher in rank than this flag. In this case, the thread is scheduled on CPU 0 if the priority of this thread is higher than what was running on that CPU (i.e., this thread flag is simply ignored). If you set this flag, don't set a runmask; let the thread run on any CPU if you want this flag to be strictly followed.
Note that priorities are completely separate from this flag. The scheduler will always schedule the highest priority thread that is in a runnable state. As such, if you really care about a particular thread's response time, make it runnable on any CPU, set its priority as the highest, and then set _NTO_TCTL_LOW_LATENCY in order to get the lowest latency possible (this is typically the case with threads handling high priority interrupts).
_NTO_TCTL_NAME
ThreadCtl( _NTO_TCTL_NAME, data );
(QNX Neutrino Core OS 6.3.2 or later) Set or retrieve the name of the current thread. The data argument must be a pointer to a _thread_name structure, which is defined as follows:
struct _thread_name { int new_name_len; int name_buf_len; char name_buf[1]; };
The name_buf member is a contiguous buffer that extends the structure; name_buf_len is the size of this buffer.
If new_name_len is: | The command: |
---|---|
Less than 0 | Copies the thread's current name into name_buf, up to the number of bytes specified by name_buf_len |
0 | Deletes the thread's name |
Greater than 0 | Sets the thread's name to the value in name_buf, up to the number of bytes specified by new_name_len, which must be less than or equal to name_buf_len |
If you're setting or deleting the thread's name, the old name is copied as a NULL-terminated string into name_buf, up to the number of bytes specified by name_buf_len.
Here's an example:
#include <stdio.h> #include <sys/neutrino.h> #include <stdlib.h> #include <string.h> int main () { struct _thread_name *tname; int size; size = sizeof(*tname) * 2 + _NTO_THREAD_NAME_MAX * sizeof(char); tname = malloc (size); if (tname == NULL) { perror ("malloc"); return EXIT_FAILURE; } else { memset (tname, 0x00, size); tname->name_buf_len = _NTO_THREAD_NAME_MAX; /* To change the name, put the name into name_buf and set new_name_len to the length of the new name. */ strcpy (tname->name_buf, "Hello!"); tname->new_name_len = strlen (tname->name_buf); if (ThreadCtl (_NTO_TCTL_NAME, tname) == -1) { perror ("ThreadCtl()"); return EXIT_FAILURE; } else { printf ("The old name was: '%s'.\n", tname->name_buf); } /* To get the current name, set new_name_len to -1. */ tname->new_name_len = -1; if (ThreadCtl (_NTO_TCTL_NAME, tname) == -1) { perror ("ThreadCtl()"); return EXIT_FAILURE; } else { printf ("The current name is: '%s'.\n", tname->name_buf); } /* To delete the name, set new_name_len to 0. */ tname->new_name_len = 0; if (ThreadCtl (_NTO_TCTL_NAME, tname) == -1) { perror ("ThreadCtl()"); return EXIT_FAILURE; } else { printf ("The old name was: '%s'.\n", tname->name_buf); } free (tname); } return EXIT_SUCCESS; }
_NTO_TCTL_ONE_THREAD_CONT
ThreadCtl( _NTO_TCTL_ONE_THREAD_CONT, data );
(QNX Neutrino Core OS 6.3.2 or later) Unfreeze the thread with the given thread ID, which was frozen by an earlier _NTO_TCTL_ONE_THREAD_HOLD command. The data is the thread ID, cast to be a pointer (i.e. (void *) tid). This command returns an error of ESRCH if there's no thread with an ID of tid.
_NTO_TCTL_ONE_THREAD_HOLD
ThreadCtl( _NTO_TCTL_ONE_THREAD_HOLD, data );
Hold the thread with the given thread ID in the calling process. The data is the thread ID, cast to be a pointer (i.e., (void *) tid). This command returns an error of ESRCH if there's no thread with an ID of tid.
This mechanism interacts with the SCHED_CONT_APP and SCHED_STOP_APP commands for SchedCtl() as follows:
_NTO_TCTL_RCM_GET_AND_SET
ThreadCtl( _NTO_TCTL_RCM_GET_AND_SET, data );
(QNX Neutrino 6.6 or later) Enter or leave resource-constrained mode. The data is a pointer to an integer; set the integer to 1 to enter constrained mode, or to 0 to leave it. The call stores the previous value in the memory pointed to by data:
int value = 1; ThreadCtl(_NTO_TCTL_RCM_GET_AND_SET, &value); /* swaps current state with value */ /* Handle the request... */ ThreadCtl(_NTO_TCTL_RCM_GET_AND_SET, &value); /* restores original state */
For more information, see Resource constraint thresholds in the Processes chapter of the QNX Neutrino Programmer's Guide.
_NTO_TCTL_RUNMASK
ThreadCtl( _NTO_TCTL_RUNMASK, data );
uint64_t runmask = 0x01;
Each set bit in runmask represents a processor that the thread can run on. A value of 0x01 would, for example, force the thread to run only on the first processor. The given runmask must match a cluster (i.e., a group of associated processors) that's predefined by the system or defined by the startup program; otherwise, the call fails with EINVAL. If the call succeeds, the new runmask takes effect immediately.
You can use _NTO_TCTL_RUNMASK to optimize the runtime performance of your system by, for example, relegating nonrealtime threads to a slower processor. For information about runmasks, see Processor affinity, runmasks, and inherit masks in the Programmer's Guide.
_NTO_TCTL_RUNMASK_GET_AND_SET
ThreadCtl( _NTO_TCTL_RUNMASK_GET_AND_SET, data );
Get and set the processor affinity for the calling thread in a multiprocessor system. The data parameter is a pointer to an unsigned int that specifies the new runmask for the thread, or zero (0) if you don't want to change the runmask. For example:
unsigned int runmask = 0x01; ThreadCtl( _NTO_TCTL_RUNMASK_GET_AND_SET, &runmask);
Each set bit in runmask represents a processor that the thread can run on. A value of 0x01 would, for example, force the thread to run only on the first processor. A nonzero runmask must match a cluster (i.e., a group of associated processors) that's predefined by the system or defined by the startup program; otherwise, the call fails with EINVAL. If the calls succeeds, the new runmask takes effect immediately and the contents of *data are replaced with the previous runmask for the thread. Calling ThreadCtl() again with the same pointer restores the runmask to the state before the call.
For information about runmasks, see Processor affinity, runmasks, and inherit masks in the Programmer's Guide.
_NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT
ThreadCtl( _NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT, data );
Manipulate the calling thread's runmask (the processor affinity) and inherit mask (the runmask to use for any threads created by this thread, including implicitly by process creation). The data argument must be a pointer to a struct _thread_runmask:
struct _thread_runmask { int size; /* unsigned runmask[size]; */ /* unsigned inherit_mask[size]; */ };
The size of the masks (and hence the size of the structure) depends on the number of processors on your system. We've defined the following macros to make it easier for you to work with this structure:
The p argument must be a pointer of type unsigned * to the appropriate mask. For more information about using these macros, see Processor affinity, runmasks, and inherit masks in the Multicore Processing chapter of the Programmer's Guide.
The _NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT command saves the values for both masks at the time of the call in their respective members of this structure. If you pass zero (0) for the masks, they are left unaltered; otherwise the function attempts to set them to the specified value(s). A nonzero runmask must match a cluster (i.e., a group of associated processors) that's predefined by the system or defined by the startup program; otherwise, the call fails with EINVAL. If the call succeeds, the new runmasks take effect immediately.
For information about runmasks, see Processor affinity, runmasks, and inherit masks in the Programmer's Guide.
Here's an example:
#include <sys/neutrino.h> #include <sys/syspage.h> #include <malloc.h> #include <stdio.h> int main(void) { int *rsizep, rsize, size_tot; unsigned *rmaskp, *inheritp; unsigned buf[8]; void *freep; /* * struct _thread_runmask is not * uniquely sized, so we construct * our own. */ rsize = RMSK_SIZE(_syspage_ptr->num_cpu); size_tot = sizeof(*rsizep); size_tot += sizeof(*rmaskp) * rsize; size_tot += sizeof(*inheritp) * rsize; if (size_tot <= sizeof(buf)) { rsizep = buf; freep = NULL; } else if ((rsizep = freep = malloc(size_tot)) == NULL) { perror("malloc"); return 1; } memset(rsizep, 0x00, size_tot); *rsizep = rsize; rmaskp = (unsigned *)(rsizep + 1); inheritp = rmaskp + rsize; /* * Both masks set to 0 means get the current * values without alteration. */ if (ThreadCtl(_NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT, rsizep) == -1) { perror("_NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT"); free(freep); return 1; } /* * Restrict our inherit mask to the last cpu; leave the * runmask unaltered. */ memset(rsizep, 0x00, size_tot); *rsizep = rsize; RMSK_SET(_syspage_ptr->num_cpu - 1, inheritp); if (ThreadCtl(_NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT, rsizep) == -1) { perror("_NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT"); free(freep); return 1; } free(freep); return 0; }
_NTO_TCTL_SHR_MUTEX
ThreadCtl( _NTO_TCTL_SHR_MUTEX, (void*)share_value );
QNX Neutrino stores the state of a mutex in user-space memory and, when needed, in a kernel object. As an important optimization, the locking and unlocking of uncontended mutexes (i.e., those where no other thread has them locked or is waiting for them to be unlocked) is handled locally (in user space) by atomic operations, without involving the kernel. This does allow for a possible spoofing attack, where a (hostile) thread creates user-space data that falsely claims that a thread in another process has locked the mutex, then attempts to lock the mutex, possibly causing an unexpected and unnecessary priority increase for the specified thread.
The best protection against this attack is to run procnto with the -s option (safe shared mutexes) and ensure all shared mutexes are initialized with the PTHREAD_PROCESS_SHARED flag. This flag forces every mutex operation (lock and unlock) to go through the kernel, though, so this optimization is lost for interprocess mutexes. If that is not possible, then _NTO_TCTL_SHR_MUTEX may be used to isolate the calling thread from operations on spoofed mutexes.
(QNX Neutrino 7.0 or later) The shared mutex restriction (SHR_MUTEX) command controls what other threads are allowed to lock a mutex where the user-space data indicates that the thread that called ThreadCtl() has locked the mutex and no kernel object indicates this.
The data argument is a void* whose value is one of the following, in preferential order:
The default setting for procnto's threads is 0; for threads in other processes, the default is _NTO_TF_SHR_MUTEX.
None of these settings will affect the behavior of a properly shared mutex, that is, a mutex where PTHREAD_PROCESS_SHARED has been set, because in that case there will always be a kernel object, and the user-space and kernel object state will always be in agreement.
_NTO_TCTL_THREADS_CONT
ThreadCtl( _NTO_TCTL_THREADS_CONT, 0 );
Unfreeze all threads in the current process that were frozen using the _NTO_TCTL_THREADS_HOLD command.
_NTO_TCTL_THREADS_HOLD
ThreadCtl( _NTO_TCTL_THREADS_HOLD, 0 );
Hold all threads in the current process except the calling thread.
Blocking states
These calls don't block for other _NTO_TCTL_* commands.
The only difference between ThreadCtl() and ThreadCtl_r() and between ThreadCtlExt() and ThreadCtlExt_r() is the way they indicate errors:
Safety: | |
---|---|
Cancellation point | No |
Interrupt handler | No |
Signal handler | Yes |
Thread | Yes |