Skip to content

Commit 1cf8291

Browse files
committed
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
1 parent c96b975 commit 1cf8291

File tree

7 files changed

+227
-3
lines changed

7 files changed

+227
-3
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ PHP NEWS
124124

125125
- PCNTL:
126126
. Added pcntl_setns for Linux. (David Carlier)
127+
. Added pcntl_getaffinity/pcntl_setaffinity. (David Carlier)
127128

128129
- PCRE:
129130
. Upgrade bundled pcre2lib to version 10.43. (nielsdos)

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ PHP 8.4 UPGRADE NOTES
445445
- PCNTL:
446446
. Added pcntl_setns allowing a process to be reassociated with a namespace in order
447447
to share resources with other processes within this context.
448+
. Added pcntl_getaffinity to get the cpu(s) bound to a process and
449+
pcntl_setaffinity to bind 1 or more cpus to a process.
448450

449451
- Sodium:
450452
. Added the sodium_crypto_aead_aegis128l_*() and sodium_crypto_aead_aegis256l_*()

ext/pcntl/config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if test "$PHP_PCNTL" != "no"; then
77
AC_CHECK_FUNCS([fork], [], [AC_MSG_ERROR([pcntl: fork() not supported by this platform])])
88
AC_CHECK_FUNCS([waitpid], [], [AC_MSG_ERROR([pcntl: waitpid() not supported by this platform])])
99
AC_CHECK_FUNCS([sigaction], [], [AC_MSG_ERROR([pcntl: sigaction() not supported by this platform])])
10-
AC_CHECK_FUNCS([getpriority setpriority wait3 wait4 sigwaitinfo sigtimedwait unshare rfork forkx pidfd_open])
10+
AC_CHECK_FUNCS([getpriority setpriority wait3 wait4 sigwaitinfo sigtimedwait unshare rfork forkx pidfd_open sched_setaffinity])
1111

1212
AC_CHECK_TYPE([siginfo_t],[PCNTL_CFLAGS="-DHAVE_STRUCT_SIGINFO_T"],,[#include <signal.h>])
1313

ext/pcntl/pcntl.c

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@
4242
#endif
4343

4444
#include <errno.h>
45-
#ifdef HAVE_UNSHARE
45+
#if defined(HAVE_UNSHARE) || defined(HAVE_SCHED_SETAFFINITY)
4646
#include <sched.h>
47+
#if defined(__FreeBSD__)
48+
#include <sys/types.h>
49+
#include <sys/cpuset.h>
50+
typedef cpuset_t cpu_set_t;
51+
#endif
4752
#endif
4853

4954
#ifdef HAVE_PIDFD_OPEN
@@ -1476,6 +1481,123 @@ PHP_FUNCTION(pcntl_setns)
14761481
}
14771482
#endif
14781483

1484+
#ifdef HAVE_SCHED_SETAFFINITY
1485+
PHP_FUNCTION(pcntl_getcpuaffinity)
1486+
{
1487+
zend_long pid;
1488+
bool pid_is_null = 1;
1489+
cpu_set_t mask;
1490+
1491+
ZEND_PARSE_PARAMETERS_START(0, 1)
1492+
Z_PARAM_OPTIONAL
1493+
Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1494+
ZEND_PARSE_PARAMETERS_END();
1495+
1496+
// 0 == getpid in this context, we're just saving a syscall
1497+
pid = pid_is_null ? 0 : pid;
1498+
1499+
CPU_ZERO(&mask);
1500+
1501+
if (sched_getaffinity(pid, sizeof(mask), &mask) != 0) {
1502+
PCNTL_G(last_error) = errno;
1503+
switch (errno) {
1504+
case ESRCH:
1505+
zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
1506+
RETURN_THROWS();
1507+
case EPERM:
1508+
php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
1509+
break;
1510+
default:
1511+
php_error_docref(NULL, E_WARNING, "Error %d", errno);
1512+
}
1513+
1514+
RETURN_FALSE;
1515+
}
1516+
1517+
zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF);
1518+
array_init(return_value);
1519+
1520+
for (zend_ulong i = 0; i < maxcpus; i ++) {
1521+
if (CPU_ISSET(i, &mask)) {
1522+
add_next_index_long(return_value, i);
1523+
}
1524+
}
1525+
}
1526+
1527+
PHP_FUNCTION(pcntl_setcpuaffinity)
1528+
{
1529+
zend_long pid;
1530+
bool pid_is_null = 1;
1531+
cpu_set_t mask;
1532+
zval *hmask = NULL, *ncpu;
1533+
1534+
ZEND_PARSE_PARAMETERS_START(0, 2)
1535+
Z_PARAM_OPTIONAL
1536+
Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1537+
Z_PARAM_ARRAY(hmask)
1538+
ZEND_PARSE_PARAMETERS_END();
1539+
1540+
if (!hmask || zend_hash_num_elements(Z_ARRVAL_P(hmask)) == 0) {
1541+
zend_argument_value_error(2, "must not be empty");
1542+
RETURN_THROWS();
1543+
}
1544+
1545+
// 0 == getpid in this context, we're just saving a syscall
1546+
pid = pid_is_null ? 0 : pid;
1547+
zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF);
1548+
CPU_ZERO(&mask);
1549+
1550+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(hmask), ncpu) {
1551+
ZVAL_DEREF(ncpu);
1552+
zend_long cpu;
1553+
if (Z_TYPE_P(ncpu) != IS_LONG) {
1554+
if (Z_TYPE_P(ncpu) == IS_STRING) {
1555+
zend_ulong tmp;
1556+
if (!ZEND_HANDLE_NUMERIC(Z_STR_P(ncpu), tmp)) {
1557+
zend_argument_value_error(2, "cpu id invalid value (%s)", ZSTR_VAL(Z_STR_P(ncpu)));
1558+
RETURN_THROWS();
1559+
}
1560+
1561+
cpu = (zend_long)tmp;
1562+
} else {
1563+
zend_string *wcpu = zval_get_string_func(ncpu);
1564+
zend_argument_value_error(2, "cpu id invalid type (%s)", ZSTR_VAL(wcpu));
1565+
zend_string_release(wcpu);
1566+
RETURN_THROWS();
1567+
}
1568+
} else {
1569+
cpu = Z_LVAL_P(ncpu);
1570+
}
1571+
1572+
if (cpu < 0 || cpu >= maxcpus) {
1573+
zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_ULONG_FMT " (" ZEND_LONG_FMT ")", maxcpus, cpu);
1574+
RETURN_THROWS();
1575+
}
1576+
1577+
if (!CPU_ISSET(cpu, &mask)) {
1578+
CPU_SET(cpu, &mask);
1579+
}
1580+
} ZEND_HASH_FOREACH_END();
1581+
1582+
if (sched_setaffinity(pid, sizeof(mask), &mask) != 0) {
1583+
PCNTL_G(last_error) = errno;
1584+
switch (errno) {
1585+
case ESRCH:
1586+
zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
1587+
RETURN_THROWS();
1588+
case EPERM:
1589+
php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
1590+
break;
1591+
default:
1592+
php_error_docref(NULL, E_WARNING, "Error %d", errno);
1593+
}
1594+
RETURN_FALSE;
1595+
} else {
1596+
RETURN_TRUE;
1597+
}
1598+
}
1599+
#endif
1600+
14791601
static void pcntl_interrupt_function(zend_execute_data *execute_data)
14801602
{
14811603
pcntl_signal_dispatch();

ext/pcntl/pcntl.stub.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,3 +994,8 @@ function pcntl_forkx(int $flags): int{}
994994
#ifdef HAVE_PIDFD_OPEN
995995
function pcntl_setns(?int $process_id = null, int $nstype = CLONE_NEWNET): bool {}
996996
#endif
997+
998+
#ifdef HAVE_SCHED_SETAFFINITY
999+
function pcntl_getcpuaffinity(?int $process_id = null): array|false {}
1000+
function pcntl_setcpuaffinity(?int $process_id = null, array $cpu_ids = []): bool {}
1001+
#endif

ext/pcntl/pcntl_arginfo.h

Lines changed: 26 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
--TEST--
2+
pcntl_getcpuaffinity() and pcntl_setcpuaffinity()
3+
--EXTENSIONS--
4+
pcntl
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists("pcntl_setcpuaffinity")) die("skip pcntl_setcpuaffinity is not available");
8+
?>
9+
--FILE--
10+
<?php
11+
$mask = [0, 1];
12+
var_dump(pcntl_setcpuaffinity(null, $mask));
13+
$act_mask = pcntl_getcpuaffinity();
14+
var_dump(array_diff($mask, $act_mask));
15+
$n_act_mask = pcntl_getcpuaffinity();
16+
var_dump(array_diff($act_mask, $n_act_mask));
17+
var_dump(pcntl_setcpuaffinity(null, ["0", "1"]));
18+
19+
try {
20+
pcntl_setcpuaffinity(null, []);
21+
} catch (\ValueError $e) {
22+
echo $e->getMessage() . PHP_EOL;
23+
}
24+
25+
try {
26+
pcntl_setcpuaffinity(null, ["abc" => "def", 0 => "cpuid"]);
27+
} catch (\ValueError $e) {
28+
echo $e->getMessage() . PHP_EOL;
29+
}
30+
31+
try {
32+
pcntl_setcpuaffinity(null, [PHP_INT_MAX]);
33+
} catch (\ValueError $e) {
34+
echo $e->getMessage() . PHP_EOL;
35+
}
36+
37+
try {
38+
pcntl_setcpuaffinity(null, [-1024, 64, -2]);
39+
} catch (\ValueError $e) {
40+
echo $e->getMessage() . PHP_EOL;
41+
}
42+
43+
try {
44+
pcntl_getcpuaffinity(-1024);
45+
} catch (\ValueError $e) {
46+
echo $e->getMessage() . PHP_EOL;
47+
}
48+
49+
try {
50+
pcntl_setcpuaffinity(null, [1, array(1)]);
51+
} catch (\ValueError $e) {
52+
echo $e->getMessage();
53+
}
54+
?>
55+
--EXPECTF--
56+
bool(true)
57+
array(0) {
58+
}
59+
array(0) {
60+
}
61+
bool(true)
62+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) must not be empty
63+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id invalid value (def)
64+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id must be between 0 and %d (%d)
65+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id must be between 0 and %d (-1024)
66+
pcntl_getcpuaffinity(): Argument #1 ($process_id) invalid process (-1024)
67+
68+
Warning: Array to string conversion in %s on line %d
69+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id invalid type (Array)

0 commit comments

Comments
 (0)