From f08f4c7b379532cb4687d6667e4c80323efe0758 Mon Sep 17 00:00:00 2001 From: Nikodemus Siivola Date: Wed, 29 Oct 2008 12:55:33 +0200 Subject: [PATCH] now with a nice lispy interface, and an ASDF definition --- Makefile | 7 -- sb-sched.c => cpu-affinity-wrapper.c | 2 +- cpu-affinity.lisp | 147 +++++++++++++++++++++++++++++++++++ package.lisp | 10 +++ sb-cpu-affinity.asd | 36 +++++++++ sb-sched.lisp | 54 ------------- 6 files changed, 194 insertions(+), 62 deletions(-) delete mode 100644 Makefile rename sb-sched.c => cpu-affinity-wrapper.c (93%) create mode 100644 cpu-affinity.lisp create mode 100644 package.lisp create mode 100644 sb-cpu-affinity.asd delete mode 100644 sb-sched.lisp diff --git a/Makefile b/Makefile deleted file mode 100644 index 2e9affa..0000000 --- a/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -all: sb-sched.so - -sb-sched.so: sb-sched.o - ld -shared -o sb-sched.so sb-sched.o - -sb-sched.o: sb-sched.c - gcc -c sb-sched.c -o sb-sched.o -fPIC diff --git a/sb-sched.c b/cpu-affinity-wrapper.c similarity index 93% rename from sb-sched.c rename to cpu-affinity-wrapper.c index f656580..5f7e488 100644 --- a/sb-sched.c +++ b/cpu-affinity-wrapper.c @@ -2,7 +2,7 @@ #include int cpu_setsize = CPU_SETSIZE; -int cpu_masksize = sizeof(cpu_set_t); +int cpu_mask_size = sizeof(cpu_set_t); int get_cpu_affinity_mask(cpu_set_t *mask) { diff --git a/cpu-affinity.lisp b/cpu-affinity.lisp new file mode 100644 index 0000000..457bf33 --- /dev/null +++ b/cpu-affinity.lisp @@ -0,0 +1,147 @@ +(in-package :sb-cpu-affinity) + +;;;; Alien definitions + +;; This is defined as a #DEFINE constant, but the .so stuff the value +;; into a variable for us. +(eval-when (:compile-toplevel :load-toplevel) + (define-alien-variable cpu-setsize int)) + +;; This is sizeof(cpu_set_t) +(define-alien-variable cpu-mask-size int) + +;; These are convenience wrappers around sched_set/getaffinity. +(define-alien-routine ("get_cpu_affinity_mask" %get-cpu-affinity-mask) int + (mask (* (unsigned 8)))) +(define-alien-routine ("set_cpu_affinity_mask" %set-cpu-affinity-mask) int + (mask (* (unsigned 8)))) + +;; These are wrappers around libc macros that manipulate the mask. Why +;; the hell can't C people provide functions in addition to macros +;; like this? Or just use inline functions? +(define-alien-routine zero-cpu-affinity-mask void + (mask (* (unsigned 8)))) +(define-alien-routine ("cpu_affinity_p" %cpu-affinity-p) int + (cpu int) + (mask (* (unsigned 8)))) +(define-alien-routine set-cpu-affinity void + (cpu int) + (mask (* (unsigned 8)))) +(define-alien-routine clear-cpu-affinity void + (cpu int) + (mask (* (unsigned 8)))) + +;;;; Nice lispy interface + +(defconstant +cpu-limit+ cpu-setsize + "Upper exclusive limit on the number of CPUs. Based on CPU_SETSIZE +from sched.h.") + +(defvar *cpu-count* nil) + +(defun cpu-count () + "Number of CPUs available in the system. Based on /proc/cpuinfo." + (or *cpu-count* + (setf *cpu-count* + (let* ((key "processor") + (len (length key))) + (with-open-file (f "/proc/cpuinfo") + (loop for line = (read-line f nil nil) + while line + count (when (> (length line) len) + (string= key line :end2 len)))))))) + +(defstruct cpu-affinity-mask + "CPU affinity mask." + %mask) + +(defmethod print-object ((mask cpu-affinity-mask) stream) + (print-unreadable-object (mask stream :type t) + ;; Print the locally interesting part of the mask. + (dotimes (i (cpu-count)) + (if (cpu-affinity-p i mask) + (write-char #\1 stream) + (write-char #\0 stream))))) + +(defun make-cpu-mask () + (make-alien (unsigned 8) cpu-mask-size)) + +(defun get-cpu-affinity-mask () + "Returns the CPU affinity mask of the current thread. The mask can +be inspected and mutated using CPU-AFFINITY-P, \(SETF CPU-AFFINITY-P), +and CLEAR-CPU-AFFINITY-MASK. To make any changes take effect, the mask +must be saved using SET-CPU-AFFINITY-MASK. + +Using WITH-CPU-AFFINITY-MASK instead is recommended." + ;; FIXME: Malloc'ed mask is nasty, but libc doesn't seem to like + ;; stack allocated ones, nor does it seem to like freeing masks that + ;; have been used. So we never do. Gah. + (let ((mask (make-cpu-mask))) + (unless (zerop (%get-cpu-affinity-mask mask)) + (error "Could not read CPU affinity mask: ~A" (sb-int:strerror))) + (make-cpu-affinity-mask :%mask mask))) + +(defun set-cpu-affinity-mask (mask) + "Sets the CPU affinity mask for the current thread. + +Using WITH-CPU-AFFINITY-MASK :SAVE T instead is recommended." + (unless (zerop (%set-cpu-affinity-mask (cpu-affinity-mask-%mask mask))) + (error "Coud not write CPU affinity mask: ~A" (sb-int:strerror)))) + +(defun cpu-affinity-p (cpu mask) + "Returns T if the CPU \(a numeric indentifier between 0 and + +CPU-LIMIT+) is part of the MASK." + (plusp (%cpu-affinity-p cpu (cpu-affinity-mask-%mask mask)))) + +(defun (setf cpu-affinity-p) (bool cpu mask) + "Toggles presence of the CPU \(a numeric identifier between 0 and +CPU-LIMIT+) +in the MASK." + (let ((%mask (cpu-affinity-mask-%mask mask))) + (if bool + (set-cpu-affinity cpu %mask) + (clear-cpu-affinity cpu %mask))) + bool) + +(defun clear-cpu-affinity-mask (mask) + "Removes all CPUs from the MASK." + (zero-cpu-affinity-mask (cpu-affinity-mask-%mask mask)) + mask) + +(defmacro with-cpu-affinity-mask ((mask &key save) &body body) + "Reads the CPU affinity mask of the the current thread and binds it +to MASK. The mask can be inspected and mutated using CPU-AFFINITY-P, +\(SETF CPU-AFFINITY-P, and CLEAR-CPU-AFFINITY-MASK. Any changes take +effect only if SAVE is true (default is NIL)." + (let ((ok-n (gensym "OK"))) + `(let (,mask ,ok-n) + (unwind-protect + (progn + (setf ,mask (get-cpu-affinity-mask)) + (multiple-value-prog1 (progn ,@body) + (setf ,ok-n t))) + (when (and ,ok-n ,save) + (set-cpu-affinity-mask ,mask)))))) + +;;;; Usage examples +#+nil +(progn + + (with-cpu-affinity-mask (mask) + (print mask)) + + (with-cpu-affinity-mask (mask :save t) + ;; Remove all + (clear-cpu-affinity-mask mask) + ;; Set CPU 0. + (setf (cpu-affinity-p 0 mask) t)) + + (with-cpu-affinity-mask (mask) + (print mask)) + + (with-cpu-affinity-mask (mask :save t) + ;; Only odd CPUs in mask. + (dotimes (cpu (cpu-count)) + (setf (cpu-affinity-p cpu mask) (oddp cpu)))) + + (with-cpu-affinity-mask (mask) + (print mask))) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..de58310 --- /dev/null +++ b/package.lisp @@ -0,0 +1,10 @@ +(defpackage :sb-cpu-affinity + (:use :sb-alien :cl) + (:export + "WITH-CPU-AFFINITY-MASK" + "CLEAR-CPU-AFFINITY-MASK" + "GET-CPU-AFFINITY-MASK" + "SET-CPU-AFFINITY-MASK" + "CPU-AFFINITY-P" + "CPU-COUNT" + "+CPU-LIMIT+")) diff --git a/sb-cpu-affinity.asd b/sb-cpu-affinity.asd new file mode 100644 index 0000000..d0ba752 --- /dev/null +++ b/sb-cpu-affinity.asd @@ -0,0 +1,36 @@ +#-(and linux sbcl) +(error "SB-CPU-AFFINITY is SBCL/Linux only.") + +(defpackage :sb-cpu-affinity-system + (:use :cl :asdf)) + +(in-package :sb-cpu-affinity-system) + +(defclass c-so-source-file (c-source-file) ()) + +(defvar *gcc* "/usr/bin/gcc") +(defvar *gcc-options* '("-shared" "-fPIC")) + +(defmethod output-files ((o compile-op) (c c-so-source-file)) + (list (make-pathname :type "so" + :defaults (component-pathname c)))) + +(defmethod perform ((o load-op) (c c-so-source-file)) + (destructuring-bind (so) (input-files o c) + (sb-alien:load-shared-object so))) + +(defmethod perform ((o compile-op) (c c-so-source-file)) + (destructuring-bind (so) (output-files o c) + (unless (zerop (run-shell-command + "~A ~A ~{~A ~}-o ~A" + *gcc* + (sb-ext:native-namestring (component-pathname c) :as-file t) + *gcc-options* + (sb-ext:native-namestring so :as-file t))) + (error 'operation-error :operation o :component c)))) + +(defsystem :sb-cpu-affinity + :components + ((c-so-source-file "cpu-affinity-wrapper") + (:file "package") + (:file "cpu-affinity" :depends-on ("package" "cpu-affinity-wrapper")))) diff --git a/sb-sched.lisp b/sb-sched.lisp deleted file mode 100644 index 12f304a..0000000 --- a/sb-sched.lisp +++ /dev/null @@ -1,54 +0,0 @@ -(load-shared-object "/home/nikodemus/Desktop/sb-sched.so") - -(define-alien-variable cpu-setsize int) - -(define-alien-variable cpu-masksize int) - -(defun make-cpu-mask () - (make-alien (unsigned 8) cpu-masksize)) - -(define-alien-routine get-cpu-affinity-mask int (mask (* (unsigned 8)))) - -(define-alien-routine set-cpu-affinity-mask int (mask (* (unsigned 8)))) - -(define-alien-routine clear-cpu-affinity-mask void (mask (* (unsigned 8)))) - -(define-alien-routine cpu-affinity-p int (cpu int) (mask (* (unsigned 8)))) - -(define-alien-routine set-cpu-affinity void (cpu int) (mask (* (unsigned 8)))) - -(define-alien-routine clear-cpu-affinity void (cpu int) (mask (* (unsigned 8)))) - -(defmacro with-cpu-affinity-mask ((mask &key save) &body body) - `(let (,mask) - (unwind-protect - (progn - (setf ,mask (make-cpu-mask)) - (unless (zerop (get-cpu-affinity-mask ,mask)) - (error "Could not read CPU affinity mask.")) - ,@body) - (when ,mask - (when ,save - (unless (zerop (set-cpu-affinity-mask ,mask)) - (error "Could not set CPU affinity mask."))) - ;; FIXME: This leaks 128 bytes per call, but glibc complains about - ;; double free if we free this! Not sure what is going on. - #+nil - (free-alien ,mask))))) - -;; Usage examples -#+nil -(progn - - (with-cpu-affinity-mask (mask) - (dotimes (i cpu-setsize) - (when (plusp (cpu-affinity-p i mask)) - (print (list :cpu i))))) - - (with-cpu-affinity-mask (mask :save t) - (clear-cpu-affinity 0 mask)) - - (with-cpu-affinity-mask (mask) - (dotimes (i cpu-setsize) - (when (plusp (cpu-affinity-p i mask)) - (print (list :cpu i)))))) -- 2.11.4.GIT