14 Commits
v1.0.1 ... main

Author SHA1 Message Date
05cb47ca3d add README.md to explain the purposes of the project better. 2025-08-23 22:19:44 +02:00
0cf95e888f write documentation about getoptions 2025-08-20 13:27:40 +02:00
47b3f041bf remove branch 2025-08-20 13:25:22 +02:00
ed1794ffbf improve number parsing in opts.c 2025-08-20 13:18:45 +02:00
6ce633b06b fix opts.c to use correct integer types 2025-08-20 13:18:27 +02:00
dbcf29dc82 remove duplicate checks and put everything on a single line. 2025-08-20 12:49:27 +02:00
53472e6fc6 add help option 2025-08-20 12:48:56 +02:00
d44235ce0f fatal macro is not printing to stderr 2025-08-20 11:46:05 +02:00
96ec279d9a correct main.c to use correct integer types, and optimise cpu_setter. 2025-08-19 10:45:02 +02:00
4200d859aa fix: .editorconfig used indent_size, which should be equal to tab 2025-08-19 10:44:02 +02:00
82a33791de fix: cpu.c was not coded correctly; some edge cases and using static-width integers 2025-08-19 10:44:02 +02:00
bdccc05440 rework some (minor) things 2025-08-19 10:44:02 +02:00
2731f908b3 update makefile
makefile is now more compiler-agnostic and consistent with how one
expects a makefile to be built.
2025-08-11 15:44:40 +02:00
782e78760b update formatting 2025-08-11 15:34:48 +02:00
13 changed files with 221 additions and 151 deletions

View File

@@ -5,8 +5,8 @@ trim_trailing_whitespace = true
[*.{c,h}] [*.{c,h}]
indent_style = tab indent_style = tab
indent_size = 4 indent_size = tab
[{makefile,Makefile}] [{makefile,Makefile}]
indent_style = tab indent_style = tab
indent_size = 2 indent_size = tab

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# cpusetcores
This is a utility application for managing CPU cores / threads on Linux machines.
It allows you to enable/disable threads (via reading/writing to `/sys/devices/system/cpu/cpuX/online`).
The plan is to eventually through deamons or some kernel magic to manage the amount of cores automatically, in hopes of saving power.
How well this'll work and how much power is gained from it is still researched and the project is not actively being worked on.
So for now it just serves as something faster than a shell script.
I assume the kernel likely already has things for this, or it is being worked on.
I just haven't looked into it that much yet, since I haven't really started the project yet.

View File

@@ -1,72 +1,32 @@
# project name = the workspace directory name SHELL = bash -e -o pipefail -O globstar
NAME := $(shell basename $(PWD))
# compiler settings NAME = cpusetcores
CC := clang VERSION = 0.0.2
STD := c17
LANG = c
CFLAGS := -target x86_64-pc-linux-gnu -Wall -Wextra -Wpedantic -Wno-pointer-arith
LDFLAGS := -lc
DEBUG ?= 0 DEBUG ?= 0
CC ?= cc
ifeq ($(DEBUG),1) CFLAGS = -c -std=gnu99 -Wall -Wextra -Wpedantic -MMD -MP
CFLAGS += -DDEBUG -fsanitize=address,undefined -g -Og LFLAGS = -flto
PROF := dbg
else
REL_FLAGS += -O2
PROF := rel
endif
# dirs SRC := $(wildcard src/*.c) $(wildcard src/**/*.c)
DIR_BIN := bin OBJ := $(SRC:%.c=obj/%.o)
DIR_OBJ := obj BIN := bin/$(NAME)
DIR_BUILD := $(DIR_BIN)/$(PROF)
DIR := $(DIR_BIN)/$(PROF) $(DIR_OBJ)/$(PROF)
# source files .PHONY:
SRC := $(wildcard src/*.c) $(wildcard src/**/*.c) $(wildcard src/**/**/*.c) $(wildcard src/**/**/**/*.c) $(wildcard src/**/**/**/**/*.c) compile: $(BIN)
# output locations $(BIN): $(OBJ)
OBJ := $(patsubst src/%,$(DIR_OBJ)/$(PROF)/%,$(SRC:.c=.o)) $(info [CC/LD] $@)
DEP := $(OBJ:.o=.d) @mkdir -p $(@D)
TARGET := $(DIR_BUILD)/$(NAME) @$(CC) -o $@ $^ $(LDFLAGS)
define wr_colour obj/%.o: %.c
@echo -e "\033[$(2)m$(1)\033[0m" $(info [CC] $@)
endef @mkdir -p $(@D)
@$(CC) $(CFLAGS) -o $@ $<
compile: compile_commands.json $(DIR) $(TARGET)
clean: clean:
rm -rf $(DIR_BIN) $(DIR_OBJ) compile_commands.json rm -rv obj/ bin/
# create the binary (linking step)
$(TARGET): $(OBJ)
@$(call wr_colour,"CC: '$(CC)'",94)
@$(call wr_colour,"CFLAGS: '$(CFLAGS)'",94)
@$(call wr_colour,"LDFLAGS: '$(LDFLAGS)'",94)
@$(call wr_colour,"linking to: '$@'",92)
@$(CC) -o $(TARGET) $^ $(CFLAGS) $(LDFLAGS)
@$(call wr_colour,"current profile: '$(PROF)'",93)
# create .o and .d files
$(DIR_OBJ)/$(PROF)/%.o: src/%.c
@$(call wr_colour,"compiling $(notdir $@) from $(notdir $<)",92)
@mkdir -p $(dir $@)
@$(CC) -o $@ -MD -MP -c $< $(CFLAGS) -std=$(STD) -x $(LANG) -Wno-unused-command-line-argument
# create directories
$(DIR):
mkdir -p $@
# update compile commands if the makefile has been updated (for linting)
ifeq ($(DEBUG),1)
compile_commands.json: makefile
$(MAKE) clean
@touch compile_commands.json
bear -- make compile
else
compile_commands.json:
endif
# include the dependencies # include the dependencies
-include $(DEP) -include $(DEP)

59
src/atrb.h Normal file
View File

@@ -0,0 +1,59 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#if defined(__GNUC__)
#if __has_attribute(__pure__)
#define PURE __attribute__((__pure__))
#else
#define PURE
#endif
#if __has_attribute(__const__)
#define CONST __attribute__((__const__))
#else
#define CONST
#endif
#if __has_attribute(__noreturn__)
#define NORET __attribute__((__noreturn__))
#else
#define NORET
#endif
#if __has_attribute(__malloc__)
#define MALLOC __attribute__((__malloc__))
#else
#define MALLOC
#endif
#if __has_attribute(__used__)
#define USED __attribute__((__used__))
#else
#define USED
#endif
#if __has_attribute(__unused__)
#define UNUSED __attribute__((__unused__))
#else
#define UNUSED
#endif
#if __has_attribute(__deprecated__)
#define DEPRECATED __attribute__((__deprecated__))
#else
#define DEPRECATED
#endif
#if __has_attribute(__format__)
#define FORMAT(...) __attribute__((format(__VA_ARGS__)))
#else
#define FORMAT(...)
#endif
#if __has_attribute(__nonnull__)
#define NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
#else
#define NONNULL(...)
#endif
#endif

View File

@@ -1,39 +1,44 @@
#include "cpu.h" #include "cpu.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
char const* const cpu_path = "/sys/devices/system/cpu/cpu%i/online"; const char cpu_path[] = "/sys/devices/system/cpu/cpu%i/online";
bool getcpu(uint32_t id) { int cpu_getenabled(uint id) {
// get the file path const size_t pathlen = sizeof(cpu_path) + (uint)(log10(-1u) + 1);
char path[64]; // contains the file path (max length is 64 due to the path and a bunch of extra wiggle room) char path[pathlen];
snprintf(path, 64, cpu_path, id); // writes the path using the id snprintf(path, pathlen, cpu_path, id);
// if the file doesn't exist; return true
if (access(path, R_OK) != 0) if (access(path, R_OK) != 0)
return true; return 1;
// read a character from the file, store in state
char state = '\0'; char state = '\0';
FILE* f = fopen(path, "r"); FILE *f = fopen(path, "r");
assert(f);
fread(&state, 1, 1, f); fread(&state, 1, 1, f);
fclose(f); fclose(f);
// return whether state is truthy return state != '0';
return !!(state - 0x30);
} }
void setcpu(uint32_t id, bool state) { int cpu_setenabled(uint id, int state) {
char path[64]; const size_t pathlen = sizeof(cpu_path) + (uint)(log10(-1u) + 1);
snprintf(path, 64, cpu_path, id); char path[pathlen];
snprintf(path, pathlen, cpu_path, id);
char s = 0x30 + (char)state; // convert the state to a character char s = 0x30 + (state & 1);
// write the state to the file (creates file if it doesn't exist) if (access(path, W_OK) != 0)
FILE* f = fopen(path, "w"); return 1;
FILE *f = fopen(path, "w");
assert(f);
fwrite(&s, 1, 1, f); fwrite(&s, 1, 1, f);
fclose(f); fclose(f);
return 0;
} }

View File

@@ -1,6 +1,10 @@
#pragma once #pragma once
#include <stdbool.h> #include "util/intdef.h"
#include <stdint.h>
bool getcpu(uint32_t); // gets the state of core (id) /* gets the current state of a CPU thread,
void setcpu(uint32_t, bool); // sets the state of core (id) * returns a boolean value (`1` for enabled, `0` for disabled) */
int cpu_getenabled(uint id);
/* sets the current state of a CPU thread.
* returns `0` upon success, `1` upon failure */
int cpu_setenabled(uint id, int state);

View File

@@ -1,18 +0,0 @@
#include "error.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
noreturn void fatal(char const* fmt, ...) {
char buf[128];
va_list args;
va_start(args, fmt);
vsnprintf(buf, 128, fmt, args);
va_end(args);
fprintf(stderr, "\033[91mE: %s\033[0m\n", buf);
exit(1);
}

View File

@@ -1,7 +1,21 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once #pragma once
#include <stdarg.h>
#include <stdint.h>
#include <stdnoreturn.h>
// prints an error message and aborts execution #if __INCLUDE_LEVEL__ > 0
noreturn void fatal(char const*, ...); #include <stdio.h>
#include <stdlib.h>
#include "util/macro.h"
#endif
#define debug(s, ...) printf("\033[95m" __FILE__ ":" MACRO_STR2(__LINE__) ": [DBG]: " s "\033[0m\n", ##__VA_ARGS__)
#define info(s, ...) printf(__FILE__ ":" MACRO_STR2(__LINE__) ": [INF]: " s "\n", ##__VA_ARGS__)
#define warn(s, ...) fprintf(stderr, "\033[93m" __FILE__ ":" MACRO_STR2(__LINE__) ": [WAR]: " s "\033[0m\n", ##__VA_ARGS__)
#define error(s, ...) fprintf(stderr, "\033[91m" __FILE__ ":" MACRO_STR2(__LINE__) ": [ERR]: " s "\033[0m\n", ##__VA_ARGS__)
#define fatal(s, ...) \
do { \
fprintf(stderr, "\033[101m" __FILE__ ":" MACRO_STR2(__LINE__) ": [FAT]: " s "\033[0m\n", ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (0)

View File

@@ -1,3 +1,4 @@
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
@@ -7,28 +8,25 @@
#include "error.h" #include "error.h"
#include "opts.h" #include "opts.h"
static inline bool cpu_setter(uint32_t id, bool nstate, uint8_t opts) { /* sets CPU `id` to `nstate`, if it hasn't been set yet.
bool cstate = !nstate; * If `opt` contains `OPT_SET_ALL`, this check is ignored.
if (!(opts & OPT_SET_ALL)) * Returns `0` upon success, `1` upon failure. */
cstate = getcpu(id); static int setcpu(uint id, int nstate, u8 opts) {
bool nsetall = !(opts & OPT_SET_ALL); // if SET_ALL has not been set
if (cstate != nstate) { int cstate = nsetall && cpu_getenabled(id); // get state if nsetall=1
setcpu(id, nstate); return (nsetall && cstate == nstate) || cpu_setenabled(id, nstate); // set if cstate matches nstate, unless nsetall=1
return true;
}
return false;
} }
static inline void print_cpu_count(int32_t mcpus) { static inline void print_cpu_count(uint mcpus) {
printf("%i/%i cpus enabled.\n", get_nprocs(), mcpus); // get the number of available processors printf("%i/%i cpus enabled.\n", get_nprocs(), mcpus); // get the number of available processors
} }
int32_t main(int32_t argc, char** argv) { int main(int argc, char **argv) {
if (geteuid() != 0) fatal("must be executed as the root user!"); if (geteuid() != 0) fatal("must be executed as the root user!");
int32_t ncpus; // the number of CPUs to activate int mcpus, ncpus; // the number of CPUs to activate
uint8_t opts = getoptions(argc, argv, &ncpus); // the options to use u8 opts = getoptions(argc, argv, &ncpus); // the options to use
int32_t mcpus = get_nprocs_conf(); // the max number of CPUs that are available mcpus = get_nprocs_conf(); // the max number of CPUs that are available
if (opts & OPT_LIST_CORES && ncpus < 0) { if (opts & OPT_LIST_CORES && ncpus < 0) {
print_cpu_count(mcpus); print_cpu_count(mcpus);
@@ -42,18 +40,18 @@ int32_t main(int32_t argc, char** argv) {
if (ncpus > mcpus) fatal("%i exeeds the maximum numbers of cpus available (%i)", ncpus, mcpus); if (ncpus > mcpus) fatal("%i exeeds the maximum numbers of cpus available (%i)", ncpus, mcpus);
if (ncpus < 1) fatal("may not keep less than 1 cpu enabled, requested to enable %i", ncpus); if (ncpus < 1) fatal("may not keep less than 1 cpu enabled, requested to enable %i", ncpus);
char const* const cpu_set_log = "set cpu %i to %hi\n"; const char *const cpu_set_log = "set cpu %i to %hi\n";
for (int32_t id = 1; id < mcpus; id++) { // start at CPU 1, as CPU 0 is not writeable for (int id = 1; id < mcpus; id++) { // start at CPU 1, as CPU 0 is not writeable
// whilst the id is less then the amount of cpus to enable // whilst the id is less then the amount of cpus to enable
if (id < ncpus) { if (id < ncpus) {
// enable the cpu & print if it was actually set (if -v was given) // enable the cpu & print if it was actually set (if -v was given)
if (cpu_setter(id, true, opts) && (opts & OPT_VERBOSE)) if (!setcpu(id, true, opts) && (opts & OPT_VERBOSE))
printf(cpu_set_log, id, 1); printf(cpu_set_log, id, 1);
continue; continue;
} }
// disable the cpu & print if it was actually set (if -v was given) // disable the cpu & print if it was actually set (if -v was given)
if (cpu_setter(id, false, opts) && (opts & OPT_VERBOSE)) { if (!setcpu(id, false, opts) && (opts & OPT_VERBOSE)) {
printf(cpu_set_log, id, 0); printf(cpu_set_log, id, 0);
continue; continue;
} }

View File

@@ -1,45 +1,49 @@
#include "opts.h" #include "opts.h"
#include <errno.h>
#include <getopt.h> #include <getopt.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include "error.h" #include "error.h"
#include "util/intdef.h"
uint8_t getoptions(int32_t argc, char* const* argv, int32_t* ncpus) { u8 getoptions(int argc, char *const *argv, int *ncpus) {
*ncpus = -1;
uint8_t opts = 0; uint8_t opts = 0;
char opt; char opt;
char const* const msg = "-%c has already been set"; while ((opt = getopt(argc, argv, "lavih")) != -1) {
while ((opt = getopt(argc, argv, "lavi")) != -1) {
switch (opt) { switch (opt) {
case 'l': case 'l': opts |= OPT_LIST_CORES; break;
if (opts & OPT_LIST_CORES) fatal(msg, 'l'); case 'a': opts |= OPT_SET_ALL; break;
opts |= OPT_LIST_CORES; case 'v': opts |= OPT_VERBOSE | OPT_LIST_CORES; break;
break; case 'i': opts |= OPT_INVERT; break;
case 'a':
if (opts & OPT_SET_ALL) fatal(msg, 'a');
opts |= OPT_SET_ALL;
break;
case 'v':
if (opts & OPT_VERBOSE) fatal(msg, 'v');
opts |= OPT_VERBOSE | OPT_LIST_CORES;
break;
case 'i':
if (opts & OPT_INVERT) fatal(msg, 'i');
opts |= OPT_INVERT;
break;
case '?': case '?':
fatal("usage: %s [number] [-l] [-a] [-v] [-i]", argv[0]); printf("specify -h for help\n");
exit(EXIT_FAILURE);
case 'h':
printf(
"%s: a tool for turning on/off CPU threads\n"
"USAGE: %s [integer] [-l] [-a] [-v] [-i] [-h]\n"
"ARGUMENTS:\n"
"\t-l : List cores after executing the command. May be specified without integer.\n"
"\t-a : Writes to all cores, regardless of their state.\n"
"\t-v : Print the cores that are written. (implies -l)\n"
"\t-i : Instead of the given number.\n"
"\t-h : Shows this dialogue.\n",
argv[0], argv[0]);
exit(EXIT_SUCCESS);
} }
} }
if (optind < argc) { if (optind < argc) {
char* num = argv[optind]; char *end;
*ncpus = atoi(num); char *num = argv[optind];
} else if (opts & OPT_LIST_CORES) { *ncpus = strtol(num, &end, 0);
*ncpus = -1; if (errno || end == num || *end != '\0')
} else fatal("failed to parse numeric value from string '%s'", num);
} else if (!(opts & OPT_LIST_CORES))
fatal("must execute with either a number or [-l]"); fatal("must execute with either a number or [-l]");
return opts; return opts;

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include "util/intdef.h"
enum opt { enum opt {
OPT_LIST_CORES = 1, // option that lists the total amount of cores that are set OPT_LIST_CORES = 1, // option that lists the total amount of cores that are set
OPT_SET_ALL = 2, // option to set all cores, regardless of their assumed state OPT_SET_ALL = 2, // option to set all cores, regardless of their assumed state
@@ -8,4 +10,7 @@ enum opt {
OPT_INVERT = 8, // 'num' now represents the amount of cores to disable OPT_INVERT = 8, // 'num' now represents the amount of cores to disable
}; };
uint8_t getoptions(int32_t, char* const*, int32_t*); /* acquires the options given by the user through arguments.
* returns the bitmap of these options.
* `ncpus` is set to `-1`, or the amount of CPUs that the user requested to enable.*/
u8 getoptions(int argc, char *const *argv, int *ncpus);

20
src/util/intdef.h Normal file
View File

@@ -0,0 +1,20 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
/* variable-width integer types */
typedef unsigned int uint; // ≥16 bit unsigned integer
typedef unsigned long ulong; // ≥32 bit unsigned integer
typedef signed long long llong; // ≥64 bit signed integer
typedef unsigned long long ullong; // ≥64 bit unsigned integer
/* fixed-width integer types */
#include <stdint.h>
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

9
src/util/macro.h Normal file
View File

@@ -0,0 +1,9 @@
// Copyright (c) 2025 Quinn
// Licensed under the MIT Licence. See LICENSE for details
#pragma once
#define WIDTHOF(t) (sizeof(t) * 8) // gets the bit width of a type
#define MACRO_CAT(x, y) x##y // concatenate two macro variables together
#define MACRO_CAT2(x, y) MACRO_CAT(x, y) // concatenate two macro variables together
#define MACRO_STR(v) #v // for converting macro variable into a string
#define MACRO_STR2(v) MACRO_STR(v) // for a recursive string generation