Make more granular process-running so that cheap syscalls can be done without as much cost as expensive ones

This commit is contained in:
raymoo 2016-11-16 10:00:08 -08:00
parent 0d89245021
commit 77d222b237
3 changed files with 65 additions and 36 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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