1
0

cpuavoid_linux.go 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. //go:build linux && !android && !e2e_testing
  2. package util
  3. import (
  4. "errors"
  5. "fmt"
  6. "os"
  7. "strconv"
  8. "unsafe"
  9. "golang.org/x/sys/unix"
  10. )
  11. // AvoidCPUs removes cpus from the affinity of every thread of the process, for use at startup. New threads inherit
  12. // their parent's mask, so it repeats until a pass finds nothing left to change; a thread that exits meanwhile is
  13. // skipped. Every mask is checked before any is changed. With apply false it only validates, for configuration tests.
  14. // This affects the entire process, including other users of an embedded Nebula.
  15. func AvoidCPUs(cpus []int, apply bool) error {
  16. const capacity = int(unsafe.Sizeof(unix.CPUSet{})) * 8
  17. for _, cpu := range cpus {
  18. if cpu < 0 || cpu >= capacity {
  19. return fmt.Errorf("CPU %d is out of range", cpu)
  20. }
  21. }
  22. if len(cpus) == 0 {
  23. return nil
  24. }
  25. for {
  26. masks, err := avoidingMasks(cpus)
  27. if err != nil || !apply || len(masks) == 0 {
  28. return err
  29. }
  30. for tid, mask := range masks {
  31. if err := unix.SchedSetaffinity(tid, &mask); err != nil && !errors.Is(err, unix.ESRCH) {
  32. return fmt.Errorf("set thread %d affinity: %w", tid, err)
  33. }
  34. }
  35. }
  36. }
  37. // avoidingMasks returns the mask without cpus of every thread whose mask has any of them.
  38. func avoidingMasks(cpus []int) (map[int]unix.CPUSet, error) {
  39. entries, err := os.ReadDir("/proc/self/task")
  40. if err != nil {
  41. return nil, err
  42. }
  43. masks := make(map[int]unix.CPUSet, len(entries))
  44. for _, entry := range entries {
  45. tid, err := strconv.Atoi(entry.Name())
  46. if err != nil {
  47. return nil, err
  48. }
  49. var mask unix.CPUSet
  50. err = unix.SchedGetaffinity(tid, &mask)
  51. if errors.Is(err, unix.ESRCH) {
  52. continue
  53. }
  54. if err != nil {
  55. return nil, fmt.Errorf("read thread %d affinity: %w", tid, err)
  56. }
  57. next := mask
  58. for _, cpu := range cpus {
  59. next.Clear(cpu)
  60. }
  61. if next.Count() == 0 {
  62. return nil, fmt.Errorf("avoid_cpus leaves thread %d with no allowed CPU", tid)
  63. }
  64. if next != mask {
  65. masks[tid] = next
  66. }
  67. }
  68. return masks, nil
  69. }