The Pedigree Project 0.1
scheduling-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/PerProcessorScheduler.h"
4#include "pedigree/kernel/process/Scheduler.h"
5#include "pedigree/kernel/process/Thread.h"
6#include "pedigree/kernel/process/Uninterruptible.h"
7#include "pedigree/kernel/processor/Processor.h"
8#include "pedigree/kernel/processor/ProcessorInformation.h"
9#include "pedigree/kernel/syscallError.h"
10
11#include "PosixProcess.h"
12#include "PosixSubsystem.h"
14#include "scheduling-syscalls.h"
15
16namespace {
17constexpr int Other = 0, Fifo = 1, RoundRobin = 2, Batch = 3, Idle = 5, Deadline = 6;
18constexpr int ResetOnFork = 0x40000000;
19
20class SchedulingResult {
21 public:
22 SchedulingResult() : m_Thread(*Processor::information().getCurrentThread()) {}
23 ~SchedulingResult() {
24 m_Thread.setErrno(m_Error);
25 }
26 int finish(int value) {
27 m_Error = value < 0 ? m_Thread.getErrno() : 0;
28 return value;
29 }
30
31 private:
32 Thread& m_Thread;
33 size_t m_Error = 0;
34};
35
36bool acquireTarget(int pid, Process::ThreadLease& target) {
37 Thread* caller = Processor::information().getCurrentThread();
38 const bool found = pid == 0
39 ? caller->getParent()->acquireThread(target, caller)
40 : pid > 0 && Scheduler::instance().acquireThreadByUserspaceId(target, pid);
41 if (found && target->getParent()->getType() == Process::Posix &&
42 target->getParent()->getSubsystem() && target->getUnwindState() != Thread::TerminateThread) {
43 ThreadPlacement placement;
44 target->snapshotPlacement(placement);
45 if (placement.migratable)
46 return true;
47 }
48 SYSCALL_ERROR(NoSuchProcess);
49 return false;
50}
51
52bool authorized(Thread& target) {
53 Process* caller = Processor::information().getCurrentThread()->getParent();
54 if (caller->getType() == Process::Posix) {
55 const auto source = static_cast<PosixProcess*>(caller)->snapshotCredentials();
56 const auto destination = static_cast<PosixProcess*>(target.getParent())->snapshotCredentials();
57 if (!source.euid || source.euid == destination.ruid || source.euid == destination.euid)
58 return true;
59 }
60 SYSCALL_ERROR(NotEnoughPermissions);
61 return false;
62}
63
64int priorityBound(int policy, bool maximum) {
65 switch (policy) {
66 case Fifo:
67 case RoundRobin:
68 return maximum ? 99 : 1;
69 case Other:
70 case Batch:
71 case Idle:
72 case Deadline:
73 return 0;
74 default:
75 SYSCALL_ERROR(InvalidArgument);
76 return -1;
77 }
78}
79
80int setPolicy(int pid, int policy, const void* user) {
81 SchedulingResult result;
82 Uninterruptible lifetime;
83 if (pid < 0 || !user) {
84 SYSCALL_ERROR(InvalidArgument);
85 return result.finish(-1);
86 }
87 // The Linux kernel ABI has one int; musl reserves additional library fields.
88 int priority;
89 if (!PosixSubsystem::copyFromUser(&priority, user, sizeof(priority))) {
90 SYSCALL_ERROR(BadAddress);
91 return result.finish(-1);
92 }
94 if (!acquireTarget(pid, target))
95 return result.finish(-1);
96 const int basePolicy = policy & ~ResetOnFork;
97 const int minimum = priorityBound(basePolicy, false);
98 const int maximum = minimum < 0 ? -1 : priorityBound(basePolicy, true);
99 if (minimum < 0 || priority < minimum || priority > maximum) {
100 SYSCALL_ERROR(InvalidArgument);
101 return result.finish(-1);
102 }
104 if (!authorized(*target.get()))
105 return result.finish(-1);
106 if (policy != Other) {
107 SYSCALL_ERROR(OperationNotSupported);
108 return result.finish(-1);
109 }
110 // Ordinary tasks already use the sole admitted policy with RT priority zero.
111 return result.finish(0);
112}
113} // namespace
114
115int posix_sched_setparam(int pid, const void* parameter) {
116 return setPolicy(pid, Other, parameter);
117}
118
119int posix_sched_setscheduler(int pid, int policy, const void* parameter) {
120 if (policy < 0) {
121 SYSCALL_ERROR(InvalidArgument);
122 return -1;
123 }
124 return setPolicy(pid, policy, parameter);
125}
126
127int posix_sched_getparam(int pid, void* parameter) {
128 SchedulingResult result;
129 Uninterruptible lifetime;
130 if (pid < 0 || !parameter) {
131 SYSCALL_ERROR(InvalidArgument);
132 return result.finish(-1);
133 }
135 if (!acquireTarget(pid, target))
136 return result.finish(-1);
137 const int priority = 0;
138 target.reset();
139 if (!PosixSubsystem::copyToUser(parameter, &priority, sizeof(priority))) {
140 SYSCALL_ERROR(BadAddress);
141 return result.finish(-1);
142 }
143 return result.finish(0);
144}
145
146int posix_sched_getscheduler(int pid) {
147 SchedulingResult result;
148 Uninterruptible lifetime;
149 if (pid < 0) {
150 SYSCALL_ERROR(InvalidArgument);
151 return result.finish(-1);
152 }
154 return result.finish(acquireTarget(pid, target) ? Other : -1);
155}
156
157int posix_sched_get_priority_max(int policy) {
158 return priorityBound(policy, true);
159}
160
161int posix_sched_get_priority_min(int policy) {
162 return priorityBound(policy, false);
163}
164
165int posix_sched_rr_get_interval(int pid, void* interval) {
166 SchedulingResult result;
167 Uninterruptible lifetime;
168 if (pid < 0) {
169 SYSCALL_ERROR(InvalidArgument);
170 return result.finish(-1);
171 }
173 if (!acquireTarget(pid, target))
174 return result.finish(-1);
175 uint64_t quantum;
176 {
177 LockGuard<Spinlock> guard(target->getLock());
178 quantum = target->getScheduler()->nominalQuantumNs();
179 }
180 target.reset();
181 if (!quantum) {
182 SYSCALL_ERROR(OperationNotSupported);
183 return result.finish(-1);
184 }
185 const int64_t timespec[2] = {static_cast<int64_t>(quantum / 1000000000),
186 static_cast<int64_t>(quantum % 1000000000)};
187 if (!PosixSubsystem::copyToUser(interval, timespec, sizeof(timespec))) {
188 SYSCALL_ERROR(BadAddress);
189 return result.finish(-1);
190 }
191 return result.finish(0);
192}
193
194int posix_sched_setaffinity(int pid, unsigned int length, const void* user) {
195 SchedulingResult result;
196 Uninterruptible lifetime;
197 CpuAffinityMask mask;
198 size_t copied = length;
199 const size_t bytes = Scheduler::affinityBytes();
200 if (copied > bytes)
201 copied = bytes;
202 if (copied && !PosixSubsystem::copyFromUser(mask.data(), user, copied)) {
203 SYSCALL_ERROR(BadAddress);
204 return result.finish(-1);
205 }
206 mask.intersect(Scheduler::onlineAffinity());
208 if (!acquireTarget(pid, target))
209 return result.finish(-1);
210 for (;;) {
211 uint64_t generation = 0;
212 AffinityResult admitted;
213 {
215 if (!authorized(*target.get()))
216 return result.finish(-1);
217 auto* subsystem = static_cast<PosixSubsystem*>(target->getParent()->getSubsystem());
218 if (!subsystem->affinityPolicyAllowed()) {
219 SYSCALL_ERROR(OperationNotSupported);
220 return result.finish(-1);
221 }
222 admitted = target->requestAffinity(mask, generation);
223 if (admitted == AffinityResult::Success || admitted == AffinityResult::Busy)
224 subsystem->recordAffinityPolicyUse();
225 }
226 if (admitted == AffinityResult::Invalid || admitted == AffinityResult::Pinned) {
227 SYSCALL_ERROR(InvalidArgument);
228 return result.finish(-1);
229 }
230 if (admitted == AffinityResult::Unsupported) {
231 SYSCALL_ERROR(OperationNotSupported);
232 return result.finish(-1);
233 }
234 if (admitted == AffinityResult::Terminal) {
235 SYSCALL_ERROR(NoSuchProcess);
236 return result.finish(-1);
237 }
238 // No VM/credential gate survives this wait. Exec can cancel the request
239 // before it drains the external target lease retained by this caller.
240 if (target->waitAffinity(generation) != AffinityResult::Success) {
241 SYSCALL_ERROR(NoSuchProcess);
242 return result.finish(-1);
243 }
244 if (admitted != AffinityResult::Busy)
245 return result.finish(0);
246 }
247}
248
249int posix_sched_getaffinity(int pid, unsigned int length, void* user) {
250 SchedulingResult result;
251 Uninterruptible lifetime;
252 const size_t bytes = Scheduler::affinityBytes();
253 if (length < bytes || length % sizeof(unsigned long)) {
254 SYSCALL_ERROR(InvalidArgument);
255 return result.finish(-1);
256 }
258 if (!acquireTarget(pid, target))
259 return result.finish(-1);
260 ThreadPlacement placement;
261 target->snapshotPlacement(placement);
262 target.reset();
263 if (!PosixSubsystem::copyToUser(user, placement.allowed.data(), bytes)) {
264 SYSCALL_ERROR(BadAddress);
265 return result.finish(-1);
266 }
267 return result.finish(static_cast<int>(bytes));
268}
269
270int posix_getcpu(unsigned int* cpu, unsigned int* node) {
271 SchedulingResult result;
272 Uninterruptible lifetime;
273 const unsigned int current = Processor::index(), locality = 0;
274 bool copied = true;
275 if (cpu && !PosixSubsystem::copyToUser(cpu, &current, sizeof(current)))
276 copied = false;
277 if (node && !PosixSubsystem::copyToUser(node, &locality, sizeof(locality)))
278 copied = false;
279 if (!copied) {
280 SYSCALL_ERROR(BadAddress);
281 return result.finish(-1);
282 }
283 return result.finish(0);
284}
Memory-mapped file interface.
static MemoryMapManager & instance()
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1385
static ProcessorInformation & information()
static size_t index()
static Scheduler & instance()
Definition Scheduler.h:96
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:519
AffinityResult waitAffinity(uint64_t generation)
UnwindType getUnwindState()
Definition Thread.h:535
Process * getParent() const
Definition Thread.h:340
class PerProcessorScheduler * getScheduler() const
Definition Thread.h:929
AffinityResult requestAffinity(const CpuAffinityMask &mask, uint64_t &generation)
Spinlock & getLock()
Definition Thread.h:613