forked from security/hakurei
container: sched policy string representation
This also uses priority obtained via sched_get_priority_min, and improves bounds checking. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -40,7 +40,7 @@ type (
|
|||||||
AllowOrphan bool
|
AllowOrphan bool
|
||||||
// Scheduling policy to set via sched_setscheduler(2). The zero value
|
// Scheduling policy to set via sched_setscheduler(2). The zero value
|
||||||
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
|
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
|
||||||
SchedPolicy int
|
SchedPolicy SchedPolicy
|
||||||
// Cgroup fd, nil to disable.
|
// Cgroup fd, nil to disable.
|
||||||
Cgroup *int
|
Cgroup *int
|
||||||
// ExtraFiles passed through to initial process in the container, with
|
// ExtraFiles passed through to initial process in the container, with
|
||||||
@@ -373,12 +373,23 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// sched_setscheduler: thread-directed but acts on all processes
|
// sched_setscheduler: thread-directed but acts on all processes
|
||||||
// created from the calling thread
|
// created from the calling thread
|
||||||
if p.SchedPolicy > 0 {
|
if p.SchedPolicy > 0 && p.SchedPolicy <= _SCHED_LAST {
|
||||||
|
var param schedParam
|
||||||
|
if priority, err := p.SchedPolicy.GetPriorityMin(); err != nil {
|
||||||
|
return &StartError{
|
||||||
|
Fatal: true,
|
||||||
|
Step: "get minimum priority",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param.priority = priority
|
||||||
|
}
|
||||||
|
|
||||||
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
|
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
|
||||||
if err := schedSetscheduler(
|
if err := schedSetscheduler(
|
||||||
0, // calling thread
|
0, // calling thread
|
||||||
p.SchedPolicy,
|
p.SchedPolicy,
|
||||||
&schedParam{0},
|
¶m,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Fatal: true,
|
Fatal: true,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
. "syscall"
|
. "syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -43,18 +46,132 @@ func Isatty(fd int) bool {
|
|||||||
return r == 0
|
return r == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SchedPolicy denotes a scheduling policy defined in include/uapi/linux/sched.h.
|
||||||
|
type SchedPolicy int
|
||||||
|
|
||||||
// include/uapi/linux/sched.h
|
// include/uapi/linux/sched.h
|
||||||
const (
|
const (
|
||||||
SCHED_NORMAL = iota
|
SCHED_NORMAL SchedPolicy = iota
|
||||||
SCHED_FIFO
|
SCHED_FIFO
|
||||||
SCHED_RR
|
SCHED_RR
|
||||||
SCHED_BATCH
|
SCHED_BATCH
|
||||||
_ // SCHED_ISO: reserved but not implemented yet
|
_SCHED_ISO // SCHED_ISO: reserved but not implemented yet
|
||||||
SCHED_IDLE
|
SCHED_IDLE
|
||||||
SCHED_DEADLINE
|
SCHED_DEADLINE
|
||||||
SCHED_EXT
|
SCHED_EXT
|
||||||
|
|
||||||
|
_SCHED_LAST SchedPolicy = iota - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ encoding.TextMarshaler = _SCHED_LAST
|
||||||
|
var _ encoding.TextUnmarshaler = new(SchedPolicy)
|
||||||
|
|
||||||
|
// String returns a unique representation of policy, also used in encoding.
|
||||||
|
func (policy SchedPolicy) String() string {
|
||||||
|
switch policy {
|
||||||
|
case SCHED_NORMAL:
|
||||||
|
return ""
|
||||||
|
case SCHED_FIFO:
|
||||||
|
return "fifo"
|
||||||
|
case SCHED_RR:
|
||||||
|
return "rr"
|
||||||
|
case SCHED_BATCH:
|
||||||
|
return "batch"
|
||||||
|
case SCHED_IDLE:
|
||||||
|
return "idle"
|
||||||
|
case SCHED_DEADLINE:
|
||||||
|
return "deadline"
|
||||||
|
case SCHED_EXT:
|
||||||
|
return "ext"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "invalid policy " + strconv.Itoa(int(policy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText performs bounds checking and returns the result of String.
|
||||||
|
func (policy SchedPolicy) MarshalText() ([]byte, error) {
|
||||||
|
if policy == _SCHED_ISO || policy < 0 || policy > _SCHED_LAST {
|
||||||
|
return nil, EINVAL
|
||||||
|
}
|
||||||
|
return []byte(policy.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidSchedPolicyError is an invalid string representation of a [SchedPolicy].
|
||||||
|
type InvalidSchedPolicyError string
|
||||||
|
|
||||||
|
func (InvalidSchedPolicyError) Unwrap() error { return EINVAL }
|
||||||
|
func (e InvalidSchedPolicyError) Error() string {
|
||||||
|
return "invalid scheduling policy " + strconv.Quote(string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText is the inverse of MarshalText.
|
||||||
|
func (policy *SchedPolicy) UnmarshalText(text []byte) error {
|
||||||
|
switch string(text) {
|
||||||
|
case "fifo":
|
||||||
|
*policy = SCHED_FIFO
|
||||||
|
case "rr":
|
||||||
|
*policy = SCHED_RR
|
||||||
|
case "batch":
|
||||||
|
*policy = SCHED_BATCH
|
||||||
|
case "idle":
|
||||||
|
*policy = SCHED_IDLE
|
||||||
|
case "deadline":
|
||||||
|
*policy = SCHED_DEADLINE
|
||||||
|
case "ext":
|
||||||
|
*policy = SCHED_EXT
|
||||||
|
|
||||||
|
case "":
|
||||||
|
*policy = 0
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return InvalidSchedPolicyError(text)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// for sched_get_priority_max and sched_get_priority_min
|
||||||
|
var (
|
||||||
|
schedPriority [_SCHED_LAST + 1][2]std.Int
|
||||||
|
schedPriorityErr [_SCHED_LAST + 1][2]error
|
||||||
|
schedPriorityOnce [_SCHED_LAST + 1][2]sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPriorityMax returns the maximum priority value that can be used with the
|
||||||
|
// scheduling algorithm identified by policy.
|
||||||
|
func (policy SchedPolicy) GetPriorityMax() (std.Int, error) {
|
||||||
|
schedPriorityOnce[policy][0].Do(func() {
|
||||||
|
priority, _, errno := Syscall(
|
||||||
|
SYS_SCHED_GET_PRIORITY_MAX,
|
||||||
|
uintptr(policy),
|
||||||
|
0, 0,
|
||||||
|
)
|
||||||
|
schedPriority[policy][0] = std.Int(priority)
|
||||||
|
if schedPriority[policy][0] < 0 {
|
||||||
|
schedPriorityErr[policy][0] = errno
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return schedPriority[policy][0], schedPriorityErr[policy][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPriorityMin returns the minimum priority value that can be used with the
|
||||||
|
// scheduling algorithm identified by policy.
|
||||||
|
func (policy SchedPolicy) GetPriorityMin() (std.Int, error) {
|
||||||
|
schedPriorityOnce[policy][1].Do(func() {
|
||||||
|
priority, _, errno := Syscall(
|
||||||
|
SYS_SCHED_GET_PRIORITY_MIN,
|
||||||
|
uintptr(policy),
|
||||||
|
0, 0,
|
||||||
|
)
|
||||||
|
schedPriority[policy][1] = std.Int(priority)
|
||||||
|
if schedPriority[policy][1] < 0 {
|
||||||
|
schedPriorityErr[policy][1] = errno
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return schedPriority[policy][1], schedPriorityErr[policy][1]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
|
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
|
||||||
type schedParam struct {
|
type schedParam struct {
|
||||||
// sched_priority
|
// sched_priority
|
||||||
@@ -74,7 +191,7 @@ type schedParam struct {
|
|||||||
// this if you do not have something similar in place!
|
// this if you do not have something similar in place!
|
||||||
//
|
//
|
||||||
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
|
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
|
||||||
func schedSetscheduler(tid, policy int, param *schedParam) error {
|
func schedSetscheduler(tid int, policy SchedPolicy, param *schedParam) error {
|
||||||
if r, _, errno := Syscall(
|
if r, _, errno := Syscall(
|
||||||
SYS_SCHED_SETSCHEDULER,
|
SYS_SCHED_SETSCHEDULER,
|
||||||
uintptr(tid),
|
uintptr(tid),
|
||||||
|
|||||||
100
container/syscall_test.go
Normal file
100
container/syscall_test.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package container_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/container/std"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSchedPolicyJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
policy container.SchedPolicy
|
||||||
|
want string
|
||||||
|
encodeErr error
|
||||||
|
decodeErr error
|
||||||
|
}{
|
||||||
|
{container.SCHED_NORMAL, `""`, nil, nil},
|
||||||
|
{container.SCHED_FIFO, `"fifo"`, nil, nil},
|
||||||
|
{container.SCHED_RR, `"rr"`, nil, nil},
|
||||||
|
{container.SCHED_BATCH, `"batch"`, nil, nil},
|
||||||
|
{4, `"invalid policy 4"`, syscall.EINVAL, container.InvalidSchedPolicyError("invalid policy 4")},
|
||||||
|
{container.SCHED_IDLE, `"idle"`, nil, nil},
|
||||||
|
{container.SCHED_DEADLINE, `"deadline"`, nil, nil},
|
||||||
|
{container.SCHED_EXT, `"ext"`, nil, nil},
|
||||||
|
{math.MaxInt, `"iso"`, syscall.EINVAL, container.InvalidSchedPolicyError("iso")},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
name := tc.policy.String()
|
||||||
|
if tc.policy == container.SCHED_NORMAL {
|
||||||
|
name = "normal"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := json.Marshal(tc.policy)
|
||||||
|
if !errors.Is(err, tc.encodeErr) {
|
||||||
|
t.Fatalf("Marshal: error = %v, want %v", err, tc.encodeErr)
|
||||||
|
}
|
||||||
|
if err == nil && string(got) != tc.want {
|
||||||
|
t.Fatalf("Marshal: %s, want %s", string(got), tc.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v container.SchedPolicy
|
||||||
|
if err = json.Unmarshal([]byte(tc.want), &v); !reflect.DeepEqual(err, tc.decodeErr) {
|
||||||
|
t.Fatalf("Unmarshal: error = %v, want %v", err, tc.decodeErr)
|
||||||
|
}
|
||||||
|
if err == nil && v != tc.policy {
|
||||||
|
t.Fatalf("Unmarshal: %d, want %d", v, tc.policy)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchedPolicyMinMax(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
policy container.SchedPolicy
|
||||||
|
min, max std.Int
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{container.SCHED_NORMAL, 0, 0, nil},
|
||||||
|
{container.SCHED_FIFO, 1, 99, nil},
|
||||||
|
{container.SCHED_RR, 1, 99, nil},
|
||||||
|
{container.SCHED_BATCH, 0, 0, nil},
|
||||||
|
{4, -1, -1, syscall.EINVAL},
|
||||||
|
{container.SCHED_IDLE, 0, 0, nil},
|
||||||
|
{container.SCHED_DEADLINE, 0, 0, nil},
|
||||||
|
{container.SCHED_EXT, 0, 0, nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
name := tc.policy.String()
|
||||||
|
if tc.policy == container.SCHED_NORMAL {
|
||||||
|
name = "normal"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if priority, err := tc.policy.GetPriorityMax(); !reflect.DeepEqual(err, tc.err) {
|
||||||
|
t.Fatalf("GetPriorityMax: error = %v, want %v", err, tc.err)
|
||||||
|
} else if priority != tc.max {
|
||||||
|
t.Fatalf("GetPriorityMax: %d, want %d", priority, tc.max)
|
||||||
|
}
|
||||||
|
if priority, err := tc.policy.GetPriorityMin(); !reflect.DeepEqual(err, tc.err) {
|
||||||
|
t.Fatalf("GetPriorityMin: error = %v, want %v", err, tc.err)
|
||||||
|
} else if priority != tc.min {
|
||||||
|
t.Fatalf("GetPriorityMin: %d, want %d", priority, tc.min)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ type ExecPath struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SchedPolicy is the [container] scheduling policy.
|
// SchedPolicy is the [container] scheduling policy.
|
||||||
var SchedPolicy int
|
var SchedPolicy container.SchedPolicy
|
||||||
|
|
||||||
// PromoteLayers returns artifacts with identical-by-content layers promoted to
|
// PromoteLayers returns artifacts with identical-by-content layers promoted to
|
||||||
// the highest priority instance, as if mounted via [ExecPath].
|
// the highest priority instance, as if mounted via [ExecPath].
|
||||||
|
|||||||
Reference in New Issue
Block a user