Merge pull request #3800 from YossiYad/carry/libkernel-setitimer
feat(libkernel): add setitimer on the alarm timer
This commit is contained in:
commit
5382635cd0
|
|
@ -620,6 +620,15 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_sigtimedwait PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_itimer_tests ${LIBS_INCLUDE_DIR}/tests/GuestItimer.cpp)
|
||||
target_include_directories(guest_itimer_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_itimer_tests PRIVATE libkernel libc)
|
||||
configure_windows_unwind(guest_itimer_tests)
|
||||
add_test(NAME guest_itimer COMMAND guest_itimer_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_itimer PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_parent_process_tests ${LIBS_INCLUDE_DIR}/tests/GuestParentProcess.cpp)
|
||||
target_include_directories(guest_parent_process_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_parent_process_tests PRIVATE libkernel libc)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ struct AlarmState {
|
|||
std::mutex lock;
|
||||
std::condition_variable changed;
|
||||
std::optional<std::chrono::steady_clock::time_point> deadline;
|
||||
std::chrono::microseconds interval{0};
|
||||
bool threadStarted = false;
|
||||
};
|
||||
|
||||
|
|
@ -98,19 +99,28 @@ AlarmState& Alarms() {
|
|||
return *state;
|
||||
}
|
||||
|
||||
void ExpireAlarm() {
|
||||
std::string DescribeInterval(std::chrono::microseconds interval) {
|
||||
char text[64];
|
||||
std::snprintf(text, sizeof(text), "%lld.%06lld s", static_cast<long long>(interval.count() / 1000000),
|
||||
static_cast<long long>(interval.count() % 1000000));
|
||||
return text;
|
||||
}
|
||||
|
||||
void ExpireAlarm(std::chrono::microseconds interval) {
|
||||
std::uintptr_t handler = 0;
|
||||
{
|
||||
std::lock_guard lock(registration);
|
||||
handler = dispositions[GuestSigalrm].handler;
|
||||
}
|
||||
if (handler == 1) return;
|
||||
if ((blockedMask.load() & (1u << (GuestSigalrm - 1))) != 0)
|
||||
throw std::runtime_error("ITIMER_REAL (it_interval " + DescribeInterval(interval) + ") expired while SIGALRM is blocked: pending signals are not implemented");
|
||||
if (handler == 0) {
|
||||
std::fprintf(stderr, "SIGALRM: the alarm expired with the default disposition; the process terminates\n");
|
||||
std::fflush(stderr);
|
||||
std::_Exit(128 + GuestSigalrm);
|
||||
}
|
||||
throw std::runtime_error("alarm: delivering SIGALRM to a guest handler is not implemented");
|
||||
throw std::runtime_error("ITIMER_REAL (it_interval " + DescribeInterval(interval) + ") expired: delivering SIGALRM to a guest handler is not implemented");
|
||||
}
|
||||
|
||||
void RunAlarms() {
|
||||
|
|
@ -123,30 +133,43 @@ void RunAlarms() {
|
|||
}
|
||||
const auto deadline = *alarms.deadline;
|
||||
if (alarms.changed.wait_until(lock, deadline) != std::cv_status::timeout || alarms.deadline != deadline) continue;
|
||||
alarms.deadline.reset();
|
||||
const auto interval = alarms.interval;
|
||||
if (interval.count() == 0) alarms.deadline.reset();
|
||||
else {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto periods = now < deadline ? 1 : (now - deadline) / interval + 1;
|
||||
alarms.deadline = deadline + periods * interval;
|
||||
}
|
||||
lock.unlock();
|
||||
ExpireAlarm();
|
||||
ExpireAlarm(interval);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::microseconds RemainingAlarm(const AlarmState& alarms, std::chrono::steady_clock::time_point now) {
|
||||
if (!alarms.deadline || *alarms.deadline <= now) return std::chrono::microseconds(0);
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(*alarms.deadline - now);
|
||||
}
|
||||
|
||||
void NotifyAlarms(AlarmState& alarms) {
|
||||
if (!alarms.threadStarted) {
|
||||
std::thread(RunAlarms).detach();
|
||||
alarms.threadStarted = true;
|
||||
}
|
||||
alarms.changed.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds) {
|
||||
auto& alarms = Alarms();
|
||||
std::lock_guard lock(alarms.lock);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
unsigned int remaining = 0;
|
||||
if (alarms.deadline && *alarms.deadline > now) {
|
||||
const auto micros = std::chrono::duration_cast<std::chrono::microseconds>(*alarms.deadline - now).count();
|
||||
remaining = static_cast<unsigned int>((micros + 999999) / 1000000);
|
||||
}
|
||||
const auto micros = RemainingAlarm(alarms, now).count();
|
||||
const auto remaining = static_cast<unsigned int>((micros + 999999) / 1000000);
|
||||
alarms.interval = std::chrono::microseconds(0);
|
||||
if (seconds == 0) alarms.deadline.reset();
|
||||
else alarms.deadline = now + std::chrono::seconds(seconds);
|
||||
if (!alarms.threadStarted) {
|
||||
std::thread(RunAlarms).detach();
|
||||
alarms.threadStarted = true;
|
||||
}
|
||||
alarms.changed.notify_all();
|
||||
NotifyAlarms(alarms);
|
||||
return remaining;
|
||||
}
|
||||
|
||||
|
|
@ -176,11 +199,73 @@ extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output
|
|||
result.value.tv_sec = micros / 1000000;
|
||||
result.value.tv_usec = micros % 1000000;
|
||||
}
|
||||
const auto interval = alarms.interval.count();
|
||||
result.interval.tv_sec = interval / 1000000;
|
||||
result.interval.tv_usec = interval % 1000000;
|
||||
}
|
||||
*output = result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr int ItimerReal = 0;
|
||||
constexpr int ItimerProf = 2;
|
||||
constexpr std::int64_t MaxItimerSeconds = 0x7fffffff / 2;
|
||||
constexpr std::int64_t MinExactIntervalMicros = 1000;
|
||||
|
||||
bool ValidItimer(const KernelTimeval& time) {
|
||||
return time.tv_sec >= 0 && time.tv_usec >= 0 && time.tv_usec < 1000000 && time.tv_sec <= MaxItimerSeconds;
|
||||
}
|
||||
|
||||
bool ItimerSet(const KernelTimeval& time) {
|
||||
return time.tv_sec != 0 || time.tv_usec != 0;
|
||||
}
|
||||
|
||||
std::chrono::microseconds ItimerDuration(const KernelTimeval& time) {
|
||||
return std::chrono::seconds(time.tv_sec) + std::chrono::microseconds(time.tv_usec);
|
||||
}
|
||||
|
||||
KernelTimeval ItimerTimeval(std::chrono::microseconds duration) {
|
||||
return {duration.count() / 1000000, duration.count() % 1000000};
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int APS5_VABI setitimer_nid_postfix(int which, const GuestItimerval* value, GuestItimerval* previous) {
|
||||
if (which < ItimerReal || which > ItimerProf) { *__error_nid_postfix() = 22; return -1; }
|
||||
if (value == nullptr && previous == nullptr) { *__error_nid_postfix() = 14; return -1; }
|
||||
GuestItimerval requested{};
|
||||
if (value != nullptr) {
|
||||
requested = *value;
|
||||
if (!ValidItimer(requested.value)) { *__error_nid_postfix() = 22; return -1; }
|
||||
if (!ItimerSet(requested.value)) requested.interval = {};
|
||||
else if (!ValidItimer(requested.interval)) { *__error_nid_postfix() = 22; return -1; }
|
||||
}
|
||||
if (which != ItimerReal) {
|
||||
if (ItimerSet(requested.value))
|
||||
throw std::runtime_error(std::string("setitimer: ") + (which == ItimerProf ? "ITIMER_PROF" : "ITIMER_VIRTUAL") + " needs process CPU time accounting, which is not implemented");
|
||||
if (previous != nullptr) *previous = {};
|
||||
return 0;
|
||||
}
|
||||
if (requested.interval.tv_sec == 0 && requested.interval.tv_usec != 0 && requested.interval.tv_usec < MinExactIntervalMicros)
|
||||
throw std::runtime_error("setitimer: it_interval " + DescribeInterval(ItimerDuration(requested.interval)) + " is below 1 ms, where the kernel rounds it up to a clock tick that is not known");
|
||||
GuestItimerval old{};
|
||||
{
|
||||
auto& alarms = Alarms();
|
||||
std::lock_guard lock(alarms.lock);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
old.interval = ItimerTimeval(alarms.interval);
|
||||
old.value = ItimerTimeval(RemainingAlarm(alarms, now));
|
||||
if (value != nullptr) {
|
||||
alarms.interval = ItimerDuration(requested.interval);
|
||||
if (ItimerSet(requested.value)) alarms.deadline = now + ItimerDuration(requested.value);
|
||||
else alarms.deadline.reset();
|
||||
NotifyAlarms(alarms);
|
||||
}
|
||||
}
|
||||
if (previous != nullptr) *previous = old;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct GuestStack {
|
||||
void* sp;
|
||||
std::size_t size;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <csignal>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <typeinfo>
|
||||
|
||||
struct GuestItimerval {
|
||||
KernelTimeval interval;
|
||||
KernelTimeval value;
|
||||
};
|
||||
static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16);
|
||||
|
||||
using Handler = void (APS5_VABI *)(int);
|
||||
using TerminateHandler = void (*)();
|
||||
extern "C" {
|
||||
int APS5_VABI setitimer_nid_postfix(int, const GuestItimerval*, GuestItimerval*);
|
||||
int APS5_VABI getitimer_nid_postfix(int, GuestItimerval*);
|
||||
unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int);
|
||||
Handler APS5_VABI signal_nid_postfix(int, Handler);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
void* APS5_VABI __cxa_current_primary_exception_nid_postfix();
|
||||
std::type_info* APS5_VABI __cxa_current_exception_type_nid_postfix();
|
||||
TerminateHandler APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(TerminateHandler);
|
||||
}
|
||||
|
||||
static void Require(bool value, const char* message) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "%s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
static std::int64_t Micros(const KernelTimeval& time) {
|
||||
Require(time.tv_sec >= 0 && time.tv_usec >= 0 && time.tv_usec < 1000000, "timeval is not normalized");
|
||||
return time.tv_sec * 1000000 + time.tv_usec;
|
||||
}
|
||||
|
||||
static GuestItimerval Read(int which) {
|
||||
GuestItimerval current{{11, 22}, {33, 44}};
|
||||
*__error_nid_postfix() = 12345;
|
||||
Require(setitimer_nid_postfix(which, nullptr, ¤t) == 0, "reading the timer failed");
|
||||
Require(*__error_nid_postfix() == 12345, "reading the timer changed errno");
|
||||
return current;
|
||||
}
|
||||
|
||||
static void RequireFailure(int which, const GuestItimerval* value, int error, const char* message) {
|
||||
GuestItimerval previous{{11, 22}, {33, 44}};
|
||||
const auto saved = previous;
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setitimer_nid_postfix(which, value, &previous) == -1 && *__error_nid_postfix() == error, message);
|
||||
Require(std::memcmp(&previous, &saved, sizeof(previous)) == 0, "a failed setitimer wrote the previous value");
|
||||
}
|
||||
|
||||
volatile std::sig_atomic_t delivered = 0;
|
||||
static void APS5_VABI OnAlarm(int signal) {
|
||||
delivered = signal;
|
||||
}
|
||||
|
||||
static void OnTerminate() {
|
||||
const auto* type = __cxa_current_exception_type_nid_postfix();
|
||||
const auto* error = static_cast<const std::runtime_error*>(__cxa_current_primary_exception_nid_postfix());
|
||||
if (type != nullptr && error != nullptr && std::strcmp(type->name(), typeid(std::runtime_error).name()) == 0
|
||||
&& std::strstr(error->what(), "it_interval 0.250000 s") != nullptr && delivered == 0) {
|
||||
std::printf("expiry threw: %s\n", error->what());
|
||||
std::fflush(stdout);
|
||||
std::_Exit(0);
|
||||
}
|
||||
std::fprintf(stderr, "unexpected termination at expiry\n");
|
||||
std::_Exit(1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto ignore = reinterpret_cast<Handler>(std::uintptr_t{1});
|
||||
Require(signal_nid_postfix(14, ignore) == nullptr, "SIGALRM was not initially defaulted");
|
||||
const GuestItimerval valid{{0, 0}, {30, 0}};
|
||||
for (int invalid : {-1, 3, INT_MIN, INT_MAX}) {
|
||||
RequireFailure(invalid, &valid, 22, "an unknown timer did not report EINVAL");
|
||||
RequireFailure(invalid, nullptr, 22, "an unknown timer read did not report EINVAL");
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setitimer_nid_postfix(invalid, nullptr, nullptr) == -1 && *__error_nid_postfix() == 22, "EINVAL is not checked before EFAULT");
|
||||
}
|
||||
for (int which : {0, 1, 2}) {
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(setitimer_nid_postfix(which, nullptr, nullptr) == -1 && *__error_nid_postfix() == 14, "a read into null did not report EFAULT");
|
||||
for (const GuestItimerval bad : {GuestItimerval{{0, 0}, {-1, 0}}, GuestItimerval{{0, 0}, {0, -1}},
|
||||
GuestItimerval{{0, 0}, {0, 1000000}}, GuestItimerval{{0, 0}, {INT_MAX / 2 + 1, 0}},
|
||||
GuestItimerval{{-1, 0}, {1, 0}}, GuestItimerval{{0, 1000000}, {1, 0}}, GuestItimerval{{INT_MAX / 2 + 1, 0}, {1, 0}}})
|
||||
RequireFailure(which, &bad, 22, "an invalid timeval did not report EINVAL");
|
||||
const GuestItimerval disarm{{-5, 7}, {0, 0}};
|
||||
GuestItimerval previous{{11, 22}, {33, 44}};
|
||||
Require(setitimer_nid_postfix(which, &disarm, &previous) == 0, "disarming with an invalid interval failed");
|
||||
Require(Micros(previous.interval) == 0 && Micros(previous.value) == 0, "an idle timer reported a value");
|
||||
Require(Micros(Read(which).interval) == 0, "disarming kept the interval");
|
||||
}
|
||||
for (int cpu : {1, 2}) {
|
||||
GuestItimerval previous{{11, 22}, {33, 44}};
|
||||
const auto saved = previous;
|
||||
bool threw = false;
|
||||
try {
|
||||
setitimer_nid_postfix(cpu, &valid, &previous);
|
||||
} catch (const std::runtime_error&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw && std::memcmp(&previous, &saved, sizeof(previous)) == 0, "a CPU timer was armed");
|
||||
Require(Micros(Read(cpu).value) == 0, "a CPU timer kept a value");
|
||||
}
|
||||
bool threw = false;
|
||||
const GuestItimerval tiny{{0, 999}, {1, 0}};
|
||||
try {
|
||||
setitimer_nid_postfix(0, &tiny, nullptr);
|
||||
} catch (const std::runtime_error&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw && Micros(Read(0).value) == 0, "an interval below the clock tick was armed");
|
||||
|
||||
GuestItimerval previous{};
|
||||
Require(setitimer_nid_postfix(0, &valid, &previous) == 0 && Micros(previous.value) == 0, "arming ITIMER_REAL failed");
|
||||
const auto first = Micros(Read(0).value);
|
||||
Require(first > 29000000 && first <= 30000000, "the armed value is not visible");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(25));
|
||||
const auto second = Micros(Read(0).value);
|
||||
Require(second > 0 && second < first, "the remaining time does not count down");
|
||||
const GuestItimerval repeating{{2, 500000}, {20, 0}};
|
||||
Require(setitimer_nid_postfix(0, &repeating, &previous) == 0, "re-arming ITIMER_REAL failed");
|
||||
Require(Micros(previous.interval) == 0 && Micros(previous.value) > 0 && Micros(previous.value) <= second, "re-arming did not return the previous value");
|
||||
GuestItimerval queried{{11, 22}, {33, 44}};
|
||||
Require(getitimer_nid_postfix(0, &queried) == 0, "getitimer failed");
|
||||
Require(Micros(queried.interval) == 2500000 && Micros(queried.value) > 19000000 && Micros(queried.value) <= 20000000,
|
||||
"getitimer did not report the setitimer interval");
|
||||
const GuestItimerval zero{{0, 0}, {0, 0}};
|
||||
Require(setitimer_nid_postfix(0, &zero, &previous) == 0, "disarming ITIMER_REAL failed");
|
||||
Require(Micros(previous.interval) == 2500000 && Micros(previous.value) > 19000000 && Micros(previous.value) <= 20000000,
|
||||
"disarming did not return the previous value");
|
||||
const auto idle = Read(0);
|
||||
Require(Micros(idle.interval) == 0 && Micros(idle.value) == 0, "the disarmed timer is still set");
|
||||
|
||||
const GuestItimerval periodic{{0, 300000}, {0, 100000}};
|
||||
Require(setitimer_nid_postfix(0, &periodic, nullptr) == 0, "arming a periodic timer failed");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
const auto rearmed = Read(0);
|
||||
Require(Micros(rearmed.interval) == 300000 && Micros(rearmed.value) > 0 && Micros(rearmed.value) <= 300000,
|
||||
"the periodic timer was not re-armed by its interval");
|
||||
Require(GuestAlarm_nid_no_patch(5) == 1, "alarm did not report the periodic timer");
|
||||
const auto alarmed = Read(0);
|
||||
Require(Micros(alarmed.interval) == 0 && Micros(alarmed.value) > 4000000 && Micros(alarmed.value) <= 5000000, "alarm did not replace the timer");
|
||||
Require(setitimer_nid_postfix(0, &zero, &previous) == 0 && Micros(previous.value) > 0, "disarming the alarm failed");
|
||||
Require(GuestAlarm_nid_no_patch(0) == 0, "setitimer did not cancel the alarm");
|
||||
|
||||
Require(signal_nid_postfix(14, OnAlarm) == ignore, "SIGALRM handler was not installed");
|
||||
_ZSt13set_terminatePFvvE_nid_postfix(OnTerminate);
|
||||
const GuestItimerval handled{{0, 250000}, {0, 50000}};
|
||||
Require(setitimer_nid_postfix(0, &handled, nullptr) == 0, "arming the handled timer failed");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
std::fprintf(stderr, "the expiry with a handler neither delivered nor threw\n");
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -96,6 +96,7 @@
|
|||
- [tcsendbreak, tcdrain, tcflush, tcflow, tcgetpgrp, tcsetpgrp](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) follow FreeBSD 11.4's [`termios.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/termios.c): `tcflush` answers `EINVAL` for a queue other than `TCIFLUSH`, `TCOFLUSH` or `TCIOFLUSH` and `tcflow` for an action outside `TCOOFF`..`TCION` before the descriptor is looked at, and every other call fails as its terminal ioctl does on a descriptor that is not a terminal, with the errors `tcgetattr` gives, so `tcsendbreak` never reaches its 0.4 s pause. FreeBSD's `pipe_ioctl` answers `TIOCGPGRP` and `TIOCSPGRP` on a pipe or FIFO with the descriptor's SIGIO owner, which is not modelled, so `tcgetpgrp` and `tcsetpgrp` throw on a host pipe or FIFO above descriptor 2; descriptors 0 to 2 are taken as non-terminals that are not pipes, whatever the host attaches to them, so a pipe the guest creates on a freed descriptor 0 to 2 gets `ENOTTY` there. The PS5 `libkernel` is assumed to use this FreeBSD code, and `tcdrain` is not a cancellation point here
|
||||
- [Windows file timestamps](../../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes` and `futimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Guest directory descriptors still resolve through their recorded path. Filesystems with coarser timestamp resolution cannot retain every guest microsecond
|
||||
- [Windows file identity](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) - `stat` and `fstat` report the volume serial number as `st_dev` and the 64-bit file index folded into the guest's 32-bit `st_ino` (never 0), so two different files can in principle share an `st_ino`; the fold is not taken from a reference. `st_nlink` is clamped to 65535, the width of the guest field. The call keeps the `_wstat64` values when `GetFileInformationByHandle` fails
|
||||
- [setitimer](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follows FreeBSD's `sys_setitimer` and `kern_setitimer`: `EINVAL` for timers other than `ITIMER_REAL`, `ITIMER_VIRTUAL` and `ITIMER_PROF`, and for a value, or the interval of a non-zero value, with a negative field, `tv_usec` of 1000000 or more, or more than `INT32_MAX / 2` seconds; a null value reads the timer as `getitimer` does, with `EFAULT` when the output is null too. `ITIMER_REAL` shares the timer thread of `alarm`, which clears its interval: a zero value disarms it, the previous value is returned, and each expiry re-arms it by `it_interval`. An expiry delivers nothing: with `SIG_IGN` it is dropped, with the default disposition the process exits with status 142, and with a handler, or with `SIGALRM` blocked, it throws, since guest `SIGALRM` handlers are never run and signals are never left pending. The value is not raised to `tick / 16` as `itimerfix` does, and an interval below 1 ms throws, since the console's `hz`, and so that rounding, is unknown (1 ms covers any `hz` of 63 or more). `ITIMER_VIRTUAL` and `ITIMER_PROF` throw when armed, as process CPU time is not accounted, and read and disarm as zero. PPSA99420 imports it and fails to load without it
|
||||
- On Linux, [direct memory](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) lives in one sparse memfd at file offset = physical address, so the [GPU imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) can share one udmabuf buffer object per 64 MiB of it. Released direct memory is not punched out of the file, because a live import of its chunk pins the pages and the GPU would keep reading the old ones: its pages stay committed (the process keeps the high-water mark of the direct memory it used) and are zeroed when the range is allocated again.
|
||||
- [system](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) runs no command. It follows FreeBSD 11's `lib/libc/stdlib/system.c` for a sandbox without `/bin/sh`: a null command returns 1 unconditionally, as that version does, and any command returns the wait status of a child that could not exec the shell and called `_exit(127)` (`0x7f00`). Whether the console's application sandbox has no shell, and whether `vfork` succeeds there (FreeBSD returns -1 when it fails), is unverified
|
||||
- [sceKernelWriteThrottlingStatus](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports no write throttling: the host SSD has no write budget, so the first qword of the 32-byte status is set to `UINT32_MAX` and the rest is zeroed. PPSA17221 reads only that qword: it multiplies it by 64 KiB and compares the product with byte thresholds up to 0x4AF00000 to pick one of five tiers. When the call fails the title assumes 0x4B000001 bytes, which selects the same top tier as `UINT32_MAX`. What the value measures on hardware (a remaining budget or a rate) and the meaning of the other three qwords are unknown
|
||||
|
|
|
|||
Loading…
Reference in New Issue