Skip to content

Commit 5f7e7ba

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).
1 parent 0a0e806 commit 5f7e7ba

File tree

5 files changed

+204
-3
lines changed

5 files changed

+204
-3
lines changed

ext/pcntl/config.m4

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@ 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([
11+
getpriority
12+
setpriority
13+
wait3
14+
wait4
15+
sigwaitinfo
16+
sigtimedwait
17+
unshare
18+
rfork
19+
forkx
20+
pidfd_open
21+
sched_setaffinity])
1122

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

ext/pcntl/pcntl.c

Lines changed: 103 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,103 @@ 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+
zend_ulong i, maxcpus;
1491+
1492+
ZEND_PARSE_PARAMETERS_START(0, 1)
1493+
Z_PARAM_OPTIONAL
1494+
Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1495+
ZEND_PARSE_PARAMETERS_END();
1496+
1497+
// 0 == getpid in this context, we re just saving a syscall
1498+
pid = pid_is_null ? 0 : pid;
1499+
1500+
CPU_ZERO(&mask);
1501+
1502+
if (sched_getaffinity(pid, sizeof(mask), &mask) != 0) {
1503+
PCNTL_G(last_error) = errno;
1504+
switch (errno) {
1505+
case ESRCH:
1506+
zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
1507+
RETURN_THROWS();
1508+
case EPERM:
1509+
php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
1510+
break;
1511+
default:
1512+
php_error_docref(NULL, E_WARNING, "Error %d", errno);
1513+
}
1514+
1515+
RETURN_EMPTY_ARRAY();
1516+
}
1517+
1518+
maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_ONLN);
1519+
array_init(return_value);
1520+
1521+
for (i = 0; i < maxcpus; i ++) {
1522+
if (CPU_ISSET(i, &mask)) {
1523+
add_next_index_long(return_value, i);
1524+
}
1525+
}
1526+
}
1527+
1528+
PHP_FUNCTION(pcntl_setcpuaffinity)
1529+
{
1530+
zend_long pid;
1531+
bool pid_is_null = 1;
1532+
cpu_set_t mask;
1533+
zval *hmask = NULL, *cpu;
1534+
zend_ulong maxcpus;
1535+
1536+
ZEND_PARSE_PARAMETERS_START(0, 2)
1537+
Z_PARAM_OPTIONAL
1538+
Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1539+
Z_PARAM_ARRAY(hmask)
1540+
ZEND_PARSE_PARAMETERS_END();
1541+
1542+
if (!hmask || Z_ARRVAL_P(hmask)->nNumOfElements == 0) {
1543+
zend_argument_value_error(2, "must not be empty");
1544+
RETURN_THROWS();
1545+
}
1546+
1547+
// 0 == getpid in this context, we re just saving a syscall
1548+
pid = pid_is_null ? 0 : pid;
1549+
maxcpus = sysconf(_SC_NPROCESSORS_ONLN);
1550+
CPU_ZERO(&mask);
1551+
1552+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(hmask), cpu) {
1553+
if (Z_TYPE_P(cpu) == IS_LONG && Z_LVAL_P(cpu) >= 0 &&
1554+
Z_LVAL_P(cpu) < maxcpus && !CPU_ISSET(Z_LVAL_P(cpu), &mask)) {
1555+
CPU_SET(Z_LVAL_P(cpu), &mask);
1556+
}
1557+
} ZEND_HASH_FOREACH_END();
1558+
1559+
if (!CPU_COUNT(&mask)) {
1560+
zend_argument_value_error(2, "invalid cpu affinity mapping");
1561+
RETURN_THROWS();
1562+
}
1563+
1564+
if (sched_setaffinity(pid, sizeof(mask), &mask) != 0) {
1565+
PCNTL_G(last_error) = errno;
1566+
switch (errno) {
1567+
case ESRCH:
1568+
zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
1569+
RETURN_THROWS();
1570+
case EPERM:
1571+
php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
1572+
break;
1573+
}
1574+
RETURN_FALSE;
1575+
} else {
1576+
RETURN_TRUE;
1577+
}
1578+
}
1579+
#endif
1580+
14791581
static void pcntl_interrupt_function(zend_execute_data *execute_data)
14801582
{
14811583
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 {}
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: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
18+
try {
19+
pcntl_setcpuaffinity(null, []);
20+
} catch (\ValueError $e) {
21+
echo $e->getMessage() . PHP_EOL;
22+
}
23+
24+
try {
25+
pcntl_setcpuaffinity(null, ["abc" => "def", 0 => "cpuid"]);
26+
} catch (\ValueError $e) {
27+
echo $e->getMessage() . PHP_EOL;
28+
}
29+
30+
try {
31+
pcntl_setcpuaffinity(null, [PHP_INT_MAX]);
32+
} catch (\ValueError $e) {
33+
echo $e->getMessage() . PHP_EOL;
34+
}
35+
36+
try {
37+
pcntl_setcpuaffinity(null, [-1024, 64, -2]);
38+
} catch (\ValueError $e) {
39+
echo $e->getMessage() . PHP_EOL;
40+
}
41+
42+
try {
43+
pcntl_getcpuaffinity(-1024);
44+
} catch (\ValueError $e) {
45+
echo $e->getMessage();
46+
}
47+
?>
48+
--EXPECT--
49+
bool(true)
50+
array(0) {
51+
}
52+
array(0) {
53+
}
54+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) must not be empty
55+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) invalid cpu affinity mapping
56+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) invalid cpu affinity mapping
57+
pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) invalid cpu affinity mapping
58+
pcntl_getcpuaffinity(): Argument #1 ($process_id) invalid process (-1024)

0 commit comments

Comments
 (0)