ext/pcntl: cpu affinity api introduction.

For now, working on Linux, FreeBSD >= 13.x and DragonFlyBSD.
Handy wrapper to assign an array of cpu ids or to retrieve the cpu ids
assigned to a given process.

pcntl_setaffinity inserts valid unique cpu ids (within the range of available
cpus).

Close GH-13893
This commit is contained in:
David Carlier 2024-04-05 21:37:59 +01:00
parent c96b975f67
commit 1cf8291c85
No known key found for this signature in database
GPG Key ID: D308BD11AB42D054
7 changed files with 227 additions and 3 deletions

1
NEWS
View File

@ -124,6 +124,7 @@ PHP NEWS
- PCNTL:
. Added pcntl_setns for Linux. (David Carlier)
. Added pcntl_getaffinity/pcntl_setaffinity. (David Carlier)
- PCRE:
. Upgrade bundled pcre2lib to version 10.43. (nielsdos)

View File

@ -445,6 +445,8 @@ PHP 8.4 UPGRADE NOTES
- PCNTL:
. Added pcntl_setns allowing a process to be reassociated with a namespace in order
to share resources with other processes within this context.
. Added pcntl_getaffinity to get the cpu(s) bound to a process and
pcntl_setaffinity to bind 1 or more cpus to a process.
- Sodium:
. Added the sodium_crypto_aead_aegis128l_*() and sodium_crypto_aead_aegis256l_*()

View File

@ -7,7 +7,7 @@ if test "$PHP_PCNTL" != "no"; then
AC_CHECK_FUNCS([fork], [], [AC_MSG_ERROR([pcntl: fork() not supported by this platform])])
AC_CHECK_FUNCS([waitpid], [], [AC_MSG_ERROR([pcntl: waitpid() not supported by this platform])])
AC_CHECK_FUNCS([sigaction], [], [AC_MSG_ERROR([pcntl: sigaction() not supported by this platform])])
AC_CHECK_FUNCS([getpriority setpriority wait3 wait4 sigwaitinfo sigtimedwait unshare rfork forkx pidfd_open])
AC_CHECK_FUNCS([getpriority setpriority wait3 wait4 sigwaitinfo sigtimedwait unshare rfork forkx pidfd_open sched_setaffinity])
AC_CHECK_TYPE([siginfo_t],[PCNTL_CFLAGS="-DHAVE_STRUCT_SIGINFO_T"],,[#include <signal.h>])

View File

@ -42,8 +42,13 @@
#endif
#include <errno.h>
#ifdef HAVE_UNSHARE
#if defined(HAVE_UNSHARE) || defined(HAVE_SCHED_SETAFFINITY)
#include <sched.h>
#if defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/cpuset.h>
typedef cpuset_t cpu_set_t;
#endif
#endif
#ifdef HAVE_PIDFD_OPEN
@ -1476,6 +1481,123 @@ PHP_FUNCTION(pcntl_setns)
}
#endif
#ifdef HAVE_SCHED_SETAFFINITY
PHP_FUNCTION(pcntl_getcpuaffinity)
{
zend_long pid;
bool pid_is_null = 1;
cpu_set_t mask;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
ZEND_PARSE_PARAMETERS_END();
// 0 == getpid in this context, we're just saving a syscall
pid = pid_is_null ? 0 : pid;
CPU_ZERO(&mask);
if (sched_getaffinity(pid, sizeof(mask), &mask) != 0) {
PCNTL_G(last_error) = errno;
switch (errno) {
case ESRCH:
zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
RETURN_THROWS();
case EPERM:
php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
break;
default:
php_error_docref(NULL, E_WARNING, "Error %d", errno);
}
RETURN_FALSE;
}
zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF);
array_init(return_value);
for (zend_ulong i = 0; i < maxcpus; i ++) {
if (CPU_ISSET(i, &mask)) {
add_next_index_long(return_value, i);
}
}
}
PHP_FUNCTION(pcntl_setcpuaffinity)
{
zend_long pid;
bool pid_is_null = 1;
cpu_set_t mask;
zval *hmask = NULL, *ncpu;
ZEND_PARSE_PARAMETERS_START(0, 2)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
Z_PARAM_ARRAY(hmask)
ZEND_PARSE_PARAMETERS_END();
if (!hmask || zend_hash_num_elements(Z_ARRVAL_P(hmask)) == 0) {
zend_argument_value_error(2, "must not be empty");
RETURN_THROWS();
}
// 0 == getpid in this context, we're just saving a syscall
pid = pid_is_null ? 0 : pid;
zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF);
CPU_ZERO(&mask);
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(hmask), ncpu) {
ZVAL_DEREF(ncpu);
zend_long cpu;
if (Z_TYPE_P(ncpu) != IS_LONG) {
if (Z_TYPE_P(ncpu) == IS_STRING) {
zend_ulong tmp;
if (!ZEND_HANDLE_NUMERIC(Z_STR_P(ncpu), tmp)) {
zend_argument_value_error(2, "cpu id invalid value (%s)", ZSTR_VAL(Z_STR_P(ncpu)));
RETURN_THROWS();
}
cpu = (zend_long)tmp;
} else {
zend_string *wcpu = zval_get_string_func(ncpu);
zend_argument_value_error(2, "cpu id invalid type (%s)", ZSTR_VAL(wcpu));
zend_string_release(wcpu);
RETURN_THROWS();
}
} else {
cpu = Z_LVAL_P(ncpu);
}
if (cpu < 0 || cpu >= maxcpus) {
zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_ULONG_FMT " (" ZEND_LONG_FMT ")", maxcpus, cpu);
RETURN_THROWS();
}
if (!CPU_ISSET(cpu, &mask)) {
CPU_SET(cpu, &mask);
}
} ZEND_HASH_FOREACH_END();
if (sched_setaffinity(pid, sizeof(mask), &mask) != 0) {
PCNTL_G(last_error) = errno;
switch (errno) {
case ESRCH:
zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
RETURN_THROWS();
case EPERM:
php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
break;
default:
php_error_docref(NULL, E_WARNING, "Error %d", errno);
}
RETURN_FALSE;
} else {
RETURN_TRUE;
}
}
#endif
static void pcntl_interrupt_function(zend_execute_data *execute_data)
{
pcntl_signal_dispatch();

View File

@ -994,3 +994,8 @@ function pcntl_forkx(int $flags): int{}
#ifdef HAVE_PIDFD_OPEN
function pcntl_setns(?int $process_id = null, int $nstype = CLONE_NEWNET): bool {}
#endif
#ifdef HAVE_SCHED_SETAFFINITY
function pcntl_getcpuaffinity(?int $process_id = null): array|false {}
function pcntl_setcpuaffinity(?int $process_id = null, array $cpu_ids = []): bool {}
#endif

View File

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: e5204cee68c41ff1201992f2572940c8f87980a3 */
* Stub hash: a61b0327f5c36ca91e19c5f370377794b7950dee */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_fork, 0, 0, IS_LONG, 0)
ZEND_END_ARG_INFO()
@ -139,6 +139,19 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_setns, 0, 0, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
#endif
#if defined(HAVE_SCHED_SETAFFINITY)
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_pcntl_getcpuaffinity, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, process_id, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()
#endif
#if defined(HAVE_SCHED_SETAFFINITY)
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_setcpuaffinity, 0, 0, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, process_id, IS_LONG, 1, "null")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, cpu_ids, IS_ARRAY, 0, "[]")
ZEND_END_ARG_INFO()
#endif
ZEND_FUNCTION(pcntl_fork);
ZEND_FUNCTION(pcntl_waitpid);
ZEND_FUNCTION(pcntl_wait);
@ -186,6 +199,12 @@ ZEND_FUNCTION(pcntl_forkx);
#if defined(HAVE_PIDFD_OPEN)
ZEND_FUNCTION(pcntl_setns);
#endif
#if defined(HAVE_SCHED_SETAFFINITY)
ZEND_FUNCTION(pcntl_getcpuaffinity);
#endif
#if defined(HAVE_SCHED_SETAFFINITY)
ZEND_FUNCTION(pcntl_setcpuaffinity);
#endif
static const zend_function_entry ext_functions[] = {
ZEND_FE(pcntl_fork, arginfo_pcntl_fork)
@ -235,6 +254,12 @@ static const zend_function_entry ext_functions[] = {
#endif
#if defined(HAVE_PIDFD_OPEN)
ZEND_FE(pcntl_setns, arginfo_pcntl_setns)
#endif
#if defined(HAVE_SCHED_SETAFFINITY)
ZEND_FE(pcntl_getcpuaffinity, arginfo_pcntl_getcpuaffinity)
#endif
#if defined(HAVE_SCHED_SETAFFINITY)
ZEND_FE(pcntl_setcpuaffinity, arginfo_pcntl_setcpuaffinity)
#endif
ZEND_FE_END
};

View File

@ -0,0 +1,69 @@
--TEST--
pcntl_getcpuaffinity() and pcntl_setcpuaffinity()
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (!function_exists("pcntl_setcpuaffinity")) die("skip pcntl_setcpuaffinity is not available");
?>
--FILE--
<?php
$mask = [0, 1];
var_dump(pcntl_setcpuaffinity(null, $mask));
$act_mask = pcntl_getcpuaffinity();
var_dump(array_diff($mask, $act_mask));
$n_act_mask = pcntl_getcpuaffinity();
var_dump(array_diff($act_mask, $n_act_mask));
var_dump(pcntl_setcpuaffinity(null, ["0", "1"]));
try {
pcntl_setcpuaffinity(null, []);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
pcntl_setcpuaffinity(null, ["abc" => "def", 0 => "cpuid"]);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
pcntl_setcpuaffinity(null, [PHP_INT_MAX]);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
pcntl_setcpuaffinity(null, [-1024, 64, -2]);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
pcntl_getcpuaffinity(-1024);
} catch (\ValueError $e) {
echo $e->getMessage() . PHP_EOL;
}
try {
pcntl_setcpuaffinity(null, [1, array(1)]);
} catch (\ValueError $e) {
echo $e->getMessage();
}
?>
--EXPECTF--
bool(true)
array(0) {
}
array(0) {
}
bool(true)
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) must not be empty
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id invalid value (def)
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id must be between 0 and %d (%d)
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id must be between 0 and %d (-1024)
pcntl_getcpuaffinity(): Argument #1 ($process_id) invalid process (-1024)
Warning: Array to string conversion in %s on line %d
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id invalid type (Array)