avoid_linux.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. //go:build linux && !android && !e2e_testing
  2. package cpupick
  3. import (
  4. "errors"
  5. "fmt"
  6. "os"
  7. "strconv"
  8. "unsafe"
  9. "golang.org/x/sys/unix"
  10. )
  11. // Avoid 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. Not wired up yet: tun.avoid_cpus
  15. // only steers Default for now.
  16. func Avoid(cpus []int, apply bool) error {
  17. const capacity = int(unsafe.Sizeof(unix.CPUSet{})) * 8
  18. for _, cpu := range cpus {
  19. if cpu < 0 || cpu >= capacity {
  20. return fmt.Errorf("CPU %d is out of range", cpu)
  21. }
  22. }
  23. if len(cpus) == 0 {
  24. return nil
  25. }
  26. for {
  27. masks, err := avoidingMasks(cpus)
  28. if err != nil || !apply || len(masks) == 0 {
  29. return err
  30. }
  31. for tid, mask := range masks {
  32. if err := unix.SchedSetaffinity(tid, &mask); err != nil && !errors.Is(err, unix.ESRCH) {
  33. return fmt.Errorf("set thread %d affinity: %w", tid, err)
  34. }
  35. }
  36. }
  37. }
  38. // avoidingMasks returns the mask without cpus of every thread whose mask has any of them.
  39. func avoidingMasks(cpus []int) (map[int]unix.CPUSet, error) {
  40. entries, err := os.ReadDir("/proc/self/task")
  41. if err != nil {
  42. return nil, err
  43. }
  44. masks := make(map[int]unix.CPUSet, len(entries))
  45. for _, entry := range entries {
  46. tid, err := strconv.Atoi(entry.Name())
  47. if err != nil {
  48. return nil, err
  49. }
  50. var mask unix.CPUSet
  51. err = unix.SchedGetaffinity(tid, &mask)
  52. if errors.Is(err, unix.ESRCH) {
  53. continue
  54. }
  55. if err != nil {
  56. return nil, fmt.Errorf("read thread %d affinity: %w", tid, err)
  57. }
  58. next := mask
  59. for _, cpu := range cpus {
  60. next.Clear(cpu)
  61. }
  62. if next.Count() == 0 {
  63. return nil, fmt.Errorf("avoid_cpus leaves thread %d with no allowed CPU", tid)
  64. }
  65. if next != mask {
  66. masks[tid] = next
  67. }
  68. }
  69. return masks, nil
  70. }