Skip to content

Commit be17d50

Browse files
committed
fix:cpu affinity
Signed-off-by: ningmingxiao <[email protected]>
1 parent a85b5fc commit be17d50

File tree

5 files changed

+89
-8
lines changed

5 files changed

+89
-8
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ jobs:
168168

169169
- name: integration test (systemd driver)
170170
run: |
171+
sudo taskset -pc 0-1 1
171172
# Delegate all cgroup v2 controllers to rootless user via --systemd-cgroup.
172173
# The default (since systemd v252) is "pids memory cpu".
173174
sudo mkdir -p /etc/systemd/system/[email protected]

libcontainer/process_linux.go

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,7 @@ func (p *setnsProcess) startWithCPUAffinity() error {
188188
}
189189

190190
func (p *setnsProcess) setFinalCPUAffinity() error {
191-
aff := p.config.CPUAffinity
192-
if aff == nil || aff.Final == nil {
193-
return nil
194-
}
195-
if err := unix.SchedSetaffinity(p.pid(), aff.Final); err != nil {
191+
if err := unix.SchedSetaffinity(p.pid(), p.config.CPUAffinity.Final); err != nil {
196192
return fmt.Errorf("error setting final CPU affinity: %w", err)
197193
}
198194
return nil
@@ -254,9 +250,16 @@ func (p *setnsProcess) start() (retErr error) {
254250
}
255251
}
256252
}
257-
// Set final CPU affinity right after the process is moved into container's cgroup.
258-
if err := p.setFinalCPUAffinity(); err != nil {
259-
return err
253+
254+
if aff := p.config.CPUAffinity; aff == nil || aff.Final == nil {
255+
if err := setAffinityAll(p.pid()); err != nil {
256+
return err
257+
}
258+
} else {
259+
// Set final CPU affinity right after the process is moved into container's cgroup.
260+
if err := p.setFinalCPUAffinity(); err != nil {
261+
return err
262+
}
260263
}
261264
if p.intelRdtPath != "" {
262265
// if Intel RDT "resource control" filesystem path exists
@@ -615,6 +618,11 @@ func (p *initProcess) start() (retErr error) {
615618
return fmt.Errorf("unable to apply cgroup configuration: %w", err)
616619
}
617620
}
621+
622+
if err := setAffinityAll(p.pid()); err != nil {
623+
return err
624+
}
625+
618626
if p.intelRdtManager != nil {
619627
if err := p.intelRdtManager.Apply(p.pid()); err != nil {
620628
return fmt.Errorf("unable to apply Intel RDT configuration: %w", err)
@@ -981,3 +989,24 @@ func (p *Process) InitializeIO(rootuid, rootgid int) (i *IO, err error) {
981989
}
982990
return i, nil
983991
}
992+
993+
// Set all inherited cpu affinity. Old kernels do that automatically, but
994+
// new kernels remember the affinity that was set before the cgroup move.
995+
// This is undesirable, because it inherits the systemd affinity when the container
996+
// should really move to the container space cpus.
997+
// here we can't use runtime.NumCPU() to get cpu counts because it call sched_getaffinity to get cpu counts.
998+
// If systemd set CPUAffinity then use runtime.NumCPU() can't get real cpu counts.
999+
func setAffinityAll(pid int) error {
1000+
cpus, err := utils.SystemCPUCores()
1001+
if err != nil {
1002+
return err
1003+
}
1004+
cpuset := unix.CPUSet{}
1005+
for i := 0; i < int(cpus); i++ {
1006+
cpuset.Set(i)
1007+
}
1008+
if err := unix.SchedSetaffinity(pid, &cpuset); err != nil {
1009+
return fmt.Errorf("error resetting pid %d affinity: %w", pid, err)
1010+
}
1011+
return nil
1012+
}

libcontainer/utils/utils.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package utils
22

33
import (
4+
"bufio"
45
"encoding/json"
6+
"fmt"
57
"io"
68
"os"
79
"path/filepath"
@@ -113,3 +115,25 @@ func Annotations(labels []string) (bundle string, userAnnotations map[string]str
113115
}
114116
return
115117
}
118+
119+
// SystemCPUCores parses CPU usage information from a reader providing
120+
// /proc/stat format data. It returns the number of CPUs.
121+
func SystemCPUCores() (cpuNum uint32, _ error) {
122+
file, _ := os.Open("/proc/stat")
123+
defer file.Close()
124+
125+
reader := bufio.NewReader(file)
126+
for {
127+
line, err := reader.ReadString('\n')
128+
if err != nil {
129+
return 0, fmt.Errorf("error scanning /proc/stat file: %w", err)
130+
}
131+
if line[:3] != "cpu" {
132+
break
133+
}
134+
if '0' <= line[3] && line[3] <= '9' {
135+
cpuNum++
136+
}
137+
}
138+
return cpuNum, nil
139+
}

libcontainer/utils/utils_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,13 @@ func TestStripRoot(t *testing.T) {
137137
}
138138
}
139139
}
140+
141+
func TestSystemCPUCores(t *testing.T) {
142+
numCpusNew, err := SystemCPUCores()
143+
if err != nil {
144+
t.Errorf("failed to run SystemCPUCores err:%v", err)
145+
}
146+
if numCpusNew == 0 {
147+
t.Errorf("numCpusNew can't equal 0")
148+
}
149+
}

tests/integration/cpu_affinity.bats

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,20 @@ function cpus_to_mask() {
9999
[[ "$output" == *"nsexec"*": affinity: $mask"* ]]
100100
[[ "$output" == *"Cpus_allowed_list: $final"* ]] # Mind the literal tab.
101101
}
102+
103+
@test "runc exec [CPU affinity set from config.json]" {
104+
update_config '.process.args = [ "/bin/grep", "-F", "Cpus_allowed_list", "/proc/self/status"]'
105+
cpus=$(grep -c "^processor" /proc/cpuinfo)
106+
cpus_minus_one=$((cpus - 1))
107+
runc run ct1
108+
[ "$status" -eq 0 ]
109+
last_col=$(echo "$output" | awk '{print $NF}')
110+
[[ "$last_col" == *"0-$cpus_minus_one"* ]] # Mind the literal tab.
111+
update_config '.process.args = ["/bin/sleep", "100"]'
112+
runc run -d --console-socket "$CONSOLE_SOCKET" ct2
113+
[ "$status" -eq 0 ]
114+
runc exec ct2 grep -F "Cpus_allowed_list:" /proc/self/status
115+
[ "$status" -eq 0 ]
116+
last_col=$(echo "$output" | awk '{print $NF}')
117+
[[ "$last_col" == *"0-$cpus_minus_one"* ]] # Mind the literal tab.
118+
}

0 commit comments

Comments
 (0)