ThreadCtl(), ThreadCtl_r(), ThreadCtlExt(), ThreadCtlExt_r()
Control a thread
Synopsis:
#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 );
Arguments:
- pid
- (ThreadCtlExt() and ThreadCtlExt_r() only) The ID of the process whose thread you want to control, or 0 for the calling process.
- tid
- (ThreadCtlExt() and ThreadCtlExt_r() only) The ID of the
thread you want to control, or 0 for the calling thread.Note:
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.
- cmd
- The command you want to execute; one of the following:
- _NTO_TCTL_ADD_EXIT_EVENT
- _NTO_TCTL_ALIGN_FAULT
- _NTO_TCTL_DEL_EXIT_EVENT
- _NTO_TCTL_IO, _NTO_TCTL_IO_LEVEL, _NTO_TCTL_IO_PRIV
- _NTO_TCTL_NAME
- _NTO_TCTL_ONE_THREAD_CONT
- _NTO_TCTL_ONE_THREAD_HOLD
- _NTO_TCTL_RUNMASK
- _NTO_TCTL_RUNMASK_GET_AND_SET
- _NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT
- _NTO_TCTL_SIGSTACK
- _NTO_TCTL_THREADS_CONT
- _NTO_TCTL_THREADS_HOLD
For more information, see below.
- data
- A pointer to data associated with the specific command; see below.
Library:
libc
Use the -l c option to qcc to link against this library. This library is usually included automatically.
Description:
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 );
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.
- A process can have only one such event at a time; registering a second event replaces any event added earlier. You can use _NTO_TCTL_DEL_EXIT_EVENT to delete the event.
- By using ThreadCtlExt(), you can arrange to be notified when threads in another process exit.
_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:
- greater than 0 — make a misaligned access fault with a SIGBUS, if the architecture permits it.
- less than 0 — make the kernel attempt to emulate an instruction with a misaligned access. If the attempt fails, it also faults with a SIGBUS.
- 0 — don't change the alignment-fault handling for the thread.
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 );
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:
- _NTO_IO_LEVEL_NONE
- No special privileges.
- _NTO_IO_LEVEL_1
- Request I/O privileges.
I/O privileges let you: See: Execute the in
,ins
,out
, andouts
I/O opcodes on x86_64in16(), 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() - _NTO_IO_LEVEL_2
- Everything that _NTO_TCTL_LEVEL_1 includes, plus the following:
- on x86_64, read the CR0 and CR4 control registers. This operation causes an exception that's handled by the kernel.
- on x86_64, use the
RDMSR
andWRMSR
instructions to read and write model-specific registers. These operations cause an exception that's handled by the kernel. - on AARCH64, use the
SMC
instruction - on AARCH64, use the
MRS
andMSR
instructions (for*_EL1
system registers)
Notice: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 has level _NTO_IO_LEVEL_1 or _NTO_IO_LEVEL_2, you can change its level to _NTO_IO_LEVEL_NONE. 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 )
- Because these commands set the _NTO_TCTL_IO_LEVEL_INHERIT flag (for backwards compatbility), and must always apply to the calling thread (even when using ThreadCtlExt*()), any subsequently created child threads will inherit the I/O privilege level of the parent. However, this might not be obvious from the surrounding code. By not using these commands, you avoid this subtle inheritance and are forced to be clear about acquiring the right I/O privilege level in any thread by calling ThreadCtl*(). Keeping the privileges to a minimum means that if things go wrong, you can help contain the damage.
- You can target these commands only at the calling thread.
- In order to use these commands, your process must have the PROCMGR_AID_IO ability enabled. The level of this ability must be 0 for _NTO_IO_LEVEL_1 or _NTO_TCTL_IO, and 1 for _NTO_IO_LEVEL_2 or _NTO_TCTL_IO_PRIV. For more information, see procmgr_ability().
- If a thread with I/O privileges calls fork(), the child process doesn't inherit them.
_NTO_TCTL_NAME
ThreadCtl( _NTO_TCTL_NAME, data );
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.
- Currently, the thread names are limited to _NTO_THREAD_NAME_MAX.
- You can also use the pthread_getname_np() and pthread_setname_np() functions instead of calling ThreadCtl() directly.
- In order to use ThreadCtlExt() to get the name of a thread in a different process, the calling process must have the PROCMGR_AID_XPROCESS_QUERY ability enabled. For more information, see procmgr_ability(). You can't set the name of a thread in a different process.
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 );
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:
- SCHED_STOP_APP stops threads in the specified application that are running.
- SCHED_CONT_APP starts threads in the specified application that were stopped by SCHED_STOP_APP, but not those that were stopped by _NTO_TCTL_ONE_THREAD_HOLD.
- _NTO_TCTL_ONE_THREAD_CONT starts threads that were stopped by _NTO_TCTL_ONE_THREAD_HOLD or SCHED_STOP_APP.
_NTO_TCTL_RUNMASK
ThreadCtl( _NTO_TCTL_RUNMASK, data );
uint64_t runmask = 0x01;
ThreadCtl( _NTO_TCTL_RUNMASK, (void *)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. 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 specific cluster with slower
cores. For information about runmasks and clusters, see Processor affinity, clusters, runmasks, and inherit masks
in the Programmer's Guide.
- Threads created by the calling thread don't inherit the specified runmask. To affect the runmask assigned to child threads, use _NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT, which is described below.
- It's not possible to use ThreadCtlExt() with the _NTO_TCTL_RUNMASK command to adjust the runmask for a thread in a different process. The specified pid must be 0 to indicate the calling process (or set to its actual PID); otherwise, the command fails.
_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 and clusters, see Processor affinity, clusters, runmasks, and inherit masks
in the
Programmer's Guide.
- It's not possible to use ThreadCtlExt() with the _NTO_TCTL_RUNMASK_GET_AND_SET command to adjust the runmask for a thread in a different process. The specified pid must be 0 to indicate the calling process (or set to its actual PID); otherwise, the command fails.
- Calling ThreadCtl() with _NTO_TCTL_RUNMASK_GET_AND_SET on a system with more than 32 cores always returns EINVAL.
_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:
- RMSK_SIZE(num_cpu)
- Determine the number of integers required to hold each mask, given the number of CPUs on your system (which you can find in _syspage_ptr->num_cpu).
- RMSK_SET(cpu, p)
- Set the bit for cpu (where cpu is zero-based) in the mask p.
- RMSK_CLR(cpu, p)
- Clear the bit for cpu (where cpu is zero-based) in the mask p.
- RMSK_ISSET(cpu, p)
- Determine the value of the bit for cpu in the mask p.
The p argument must be a pointer of type unsigned * to
the appropriate mask. For more information about using these macros, see Processor
affinity, clusters, 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 and clusters, see Processor affinity, clusters, 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_SIGSTACK
ThreadCtl( _NTO_TCTL_SIGSTACK, data );
Set and retrieve the alternate signal stack for the calling thread. The data argument must be a pointer to a stack_t structure. Use the sigaltstack() function instead of calling ThreadCtl() directly.
_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.
- Further threads created by the calling thread after invoking the _NTO_TCTL_THREADS_HOLD command aren't frozen.
- In order to use ThreadCtlExt() to invoke this command for a thread in a different process, the calling process must have the PROCMGR_AID_SIGNAL ability enabled for the SIGSTOP signal. For more information, see procmgr_ability().
Blocking states
- STATE_WAITPAGE
- The _NTO_TCTL_IO_LEVEL command with a privilege level of _NTO_IO_LEVEL_1 or _NTO_IO_LEVEL_2 superlocks memory, which generally puts the first thread to use this command into STATE_WAITPAGE while physical memory is allocated.
These calls don't block for other _NTO_TCTL_* commands.
Returns:
The only difference between ThreadCtl() and ThreadCtl_r() and between ThreadCtlExt() and ThreadCtlExt_r() is the way they indicate errors:
- ThreadCtl(), ThreadCtlExt()
- If an error occurs, these functions return -1 and set errno. Any other value returned indicates success.
- ThreadCtl_r(), ThreadCtlExt_r()
- These functions return EOK on success. They don't set errno. If an error occurs, they may return any value listed in the Errors section.
Errors:
- E2BIG
- The name is larger than the accepted size.
- EINVAL
- An argument is invalid; the possible cause could be one of the following:
- The name buffer length is invalid or smaller than the new name length.
- The specified runmask is invalid.
- The pid argument was not 0 but tid was 0 (i.e., a specific process was specified but no valid thread was specified).
- ENOTSUP
- You tried to execute an _NTO_TCTL_IO_LEVEL command with a privilege level of _NTO_IO_LEVEL_1 or _NTO_IO_LEVEL_2 on a thread other than the calling thread.
- EPERM
- The calling process doesn't have the required permission; if you're using ThreadCtlExt(), ThreadCtlExt_r(), or _NTO_TCTL_IO, see procmgr_ability(). Not even root can target a command at a thread in a different process.
- ESRCH
- There's no thread with an ID of tid.
Classification:
Safety: | |
---|---|
Cancellation point | No |
Signal handler | Yes |
Thread | Yes |