Make more granular process-running so that cheap syscalls can be done without as much cost as expensive ones
This commit is contained in:
parent
0d89245021
commit
77d222b237
42
computer.lua
42
computer.lua
@ -131,30 +131,42 @@ function Computer:error(errstr, violating_pid)
|
||||
self:interrupt("error", errstr)
|
||||
end
|
||||
|
||||
-- Returns fuel units consumed by the syscall
|
||||
function Computer:handle_syscall(syscall_name, arg, pid, pos)
|
||||
local handler = syscalls[syscall_name]
|
||||
if handler then
|
||||
handler(self, arg, pid, pos)
|
||||
return handler(self, arg, pid, pos)
|
||||
else
|
||||
self:error("Bad syscall type: " .. tostring(syscall_name), pid)
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
function Computer:run_helper(pos)
|
||||
if self.state ~= "on" then return end
|
||||
local pid, process = self:dequeue_process(self.ready_queue)
|
||||
if not pid then return end
|
||||
|
||||
local success, syscall_name, syscall_arg =
|
||||
process:run(self.capabilities.cpu)
|
||||
if success then
|
||||
self:handle_syscall(syscall_name, syscall_arg, pid, pos)
|
||||
else
|
||||
self:error(syscall_name, pid)
|
||||
function Computer:run_helper(pos, elapsed_units)
|
||||
if self.state ~= "on" then
|
||||
error("Can't run computer that is off")
|
||||
end
|
||||
|
||||
if self.process_count == 0 then
|
||||
self:die()
|
||||
local timestep = self.capabilities.cpu % 10
|
||||
local fuel = 10
|
||||
while fuel > 0 and self.state == "on" do
|
||||
local pid, process = self:dequeue_process(self.ready_queue)
|
||||
if not pid then break end
|
||||
|
||||
local units_elapsed, syscall_name, syscall_arg =
|
||||
process:run(timestep, fuel)
|
||||
if units_elapsed then
|
||||
local more_elapsed =
|
||||
self:handle_syscall(syscall_name, syscall_arg, pid, pos) or 0
|
||||
fuel = fuel - units_elapsed - more_elapsed - 1
|
||||
else
|
||||
self:error(syscall_name, pid)
|
||||
fuel = 0
|
||||
end
|
||||
|
||||
if self.process_count == 0 then
|
||||
self:die()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -320,6 +332,7 @@ Computer.register_syscall("display", function(self, text, pid, pos)
|
||||
display_at_pos(pos, text)
|
||||
self.processes[pid]:respond()
|
||||
self.ready_queue:enqueue(pid)
|
||||
return 10
|
||||
end
|
||||
end)
|
||||
|
||||
@ -333,6 +346,7 @@ Computer.register_syscall("send_digiline", function(self, data, pid, pos)
|
||||
data.data)
|
||||
self.processes[pid]:respond()
|
||||
self.ready_queue:enqueue(pid)
|
||||
return 10
|
||||
end
|
||||
end)
|
||||
|
||||
|
34
preempter.c
34
preempter.c
@ -7,10 +7,15 @@
|
||||
lua_Hook old_hook;
|
||||
int old_mask;
|
||||
int old_count;
|
||||
int unit_count;
|
||||
int curr_count;
|
||||
|
||||
void yield_hook(lua_State *L, lua_Debug *ar) {
|
||||
lua_pushstring(L, "_preempt");
|
||||
lua_yield(L, 1);
|
||||
curr_count++;
|
||||
if (curr_count >= unit_count) {
|
||||
lua_pushstring(L, "_preempt");
|
||||
lua_yield(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void activate_preempt(lua_State *L, int interval) {
|
||||
@ -24,28 +29,37 @@ void disable_preempt(lua_State *L) {
|
||||
lua_sethook(L, old_hook, old_mask, old_count);
|
||||
}
|
||||
|
||||
// Arguments: instruction count, thread, other arguments
|
||||
// Returns: Whatever the thread yields, or error data
|
||||
// Arguments: unit size, unit count, thread, other arguments.
|
||||
// Returns: Number of units elapsed and yielded values, or else false and error
|
||||
// message.
|
||||
// SHOULD NOT be called from inside the thread.
|
||||
// Resuming another coroutine with coroutine.resume inside this thread can
|
||||
// defeat preemption. If you want to provide coroutine facilities in the thread's
|
||||
// environment, wrap coroutine.resume in a version that will pass through
|
||||
// preemption yields (the first yielded value is "_preempt").
|
||||
int sandboxed_resume(lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TNUMBER);
|
||||
luaL_checktype(L, 2, LUA_TTHREAD);
|
||||
luaL_checktype(L, 2, LUA_TNUMBER);
|
||||
luaL_checktype(L, 3, LUA_TTHREAD);
|
||||
|
||||
int total_args = lua_gettop(L);
|
||||
int resume_args = total_args - 2;
|
||||
int instr_count = lua_tointeger(L, 1);
|
||||
int resume_args = total_args - 3;
|
||||
int unit_size = lua_tointeger(L, 1);
|
||||
unit_count = lua_tointeger(L, 2);
|
||||
curr_count = 0;
|
||||
|
||||
// Push args onto the thread stack
|
||||
lua_State *thread_state = lua_tothread(L, 2);
|
||||
lua_State *thread_state = lua_tothread(L, 3);
|
||||
lua_xmove(L, thread_state, resume_args);
|
||||
|
||||
// Run with preemption
|
||||
activate_preempt(thread_state, instr_count);
|
||||
activate_preempt(thread_state, unit_size);
|
||||
int res = lua_resume(thread_state, resume_args);
|
||||
disable_preempt(thread_state);
|
||||
|
||||
// Success
|
||||
if (res == LUA_YIELD || res == 0) {
|
||||
lua_pushboolean(L, 1);
|
||||
lua_pushinteger(L, curr_count);
|
||||
int resnum = lua_gettop(thread_state);
|
||||
lua_xmove(thread_state, L, resnum);
|
||||
return 1 + resnum;
|
||||
|
25
process.lua
25
process.lua
@ -5,11 +5,12 @@ Functions:
|
||||
an argument to the function.
|
||||
|
||||
Methods:
|
||||
Process:run(timestep): Runs the process. `timestep`
|
||||
is a number specifying how many instructions to limit the run to.
|
||||
If no errors occur, returns true as the first value. If the process
|
||||
was preempted (ran out of instructions), the second value will be
|
||||
`"preempt"`. If the process finished, the second value will
|
||||
Process:run(unit, num_units): Runs the process. `unit`
|
||||
is a number specifying how many instructions a "unit" of time should be.
|
||||
`units` is how many time units to run for.
|
||||
If no errors occur, returns the number of units elapsed as the first
|
||||
value. If the process was preempted (ran out of instructions), the second
|
||||
value will be `"_preempt"`. If the process finished, the second value will
|
||||
be `"exit". If the process made a system call, the second value will be
|
||||
some string code identifying the type of syscall. Additionally, the
|
||||
argument to the syscall will be the third return value.
|
||||
@ -60,30 +61,30 @@ function Process.new(env, prog, arg)
|
||||
return process
|
||||
end
|
||||
|
||||
function Process:run(timestep)
|
||||
function Process:run(unit, num_units)
|
||||
if self.status ~= "ready" then
|
||||
error("Process is " .. self.status .. ", not ready")
|
||||
end
|
||||
|
||||
local thread = self.thread
|
||||
local success, request, request_arg =
|
||||
datamine.sandboxed_resume(timestep, thread, self.response)
|
||||
if not success then
|
||||
local units_elapsed, request, request_arg =
|
||||
datamine.sandboxed_resume(unit, num_units, thread, self.response)
|
||||
if not units_elapsed then
|
||||
self.status = "errored"
|
||||
return false, request
|
||||
else
|
||||
local real_status = coroutine.status(thread)
|
||||
if real_status == "dead" then
|
||||
self.status = "finished"
|
||||
return true, "exit"
|
||||
return units_elapsed, "exit"
|
||||
elseif request == "_preempt" then
|
||||
return true, "_preempt"
|
||||
return units_elapsed, "_preempt"
|
||||
elseif type(request) ~= "string" then
|
||||
self.status = "errored"
|
||||
return false, "Bad syscall id"
|
||||
else
|
||||
self.status = "waiting"
|
||||
return true, request, request_arg
|
||||
return units_elapsed, request, request_arg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user