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}]
indent_style = tab
indent_size = 4
indent_size = tab
[{makefile,Makefile}]
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
NAME := $(shell basename $(PWD))
SHELL = bash -e -o pipefail -O globstar
# compiler settings
CC := clang
STD := c17
LANG = c
CFLAGS := -target x86_64-pc-linux-gnu -Wall -Wextra -Wpedantic -Wno-pointer-arith
LDFLAGS := -lc
NAME = cpusetcores
VERSION = 0.0.2
DEBUG ?= 0
CC ?= cc
ifeq ($(DEBUG),1)
CFLAGS += -DDEBUG -fsanitize=address,undefined -g -Og
PROF := dbg
else
REL_FLAGS += -O2
PROF := rel
endif
CFLAGS = -c -std=gnu99 -Wall -Wextra -Wpedantic -MMD -MP
LFLAGS = -flto
# dirs
DIR_BIN := bin
DIR_OBJ := obj
DIR_BUILD := $(DIR_BIN)/$(PROF)
DIR := $(DIR_BIN)/$(PROF) $(DIR_OBJ)/$(PROF)
SRC := $(wildcard src/*.c) $(wildcard src/**/*.c)
OBJ := $(SRC:%.c=obj/%.o)
BIN := bin/$(NAME)
# source files
SRC := $(wildcard src/*.c) $(wildcard src/**/*.c) $(wildcard src/**/**/*.c) $(wildcard src/**/**/**/*.c) $(wildcard src/**/**/**/**/*.c)
.PHONY:
compile: $(BIN)
# output locations
OBJ := $(patsubst src/%,$(DIR_OBJ)/$(PROF)/%,$(SRC:.c=.o))
DEP := $(OBJ:.o=.d)
TARGET := $(DIR_BUILD)/$(NAME)
$(BIN): $(OBJ)
$(info [CC/LD] $@)
@mkdir -p $(@D)
@$(CC) -o $@ $^ $(LDFLAGS)
define wr_colour
@echo -e "\033[$(2)m$(1)\033[0m"
endef
obj/%.o: %.c
$(info [CC] $@)
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -o $@ $<
compile: compile_commands.json $(DIR) $(TARGET)
clean:
rm -rf $(DIR_BIN) $(DIR_OBJ) compile_commands.json
# 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
rm -rv obj/ bin/
# include the dependencies
-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 <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.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) {
// get the file path
char path[64]; // contains the file path (max length is 64 due to the path and a bunch of extra wiggle room)
snprintf(path, 64, cpu_path, id); // writes the path using the id
int cpu_getenabled(uint id) {
const size_t pathlen = sizeof(cpu_path) + (uint)(log10(-1u) + 1);
char path[pathlen];
snprintf(path, pathlen, cpu_path, id);
// if the file doesn't exist; return true
if (access(path, R_OK) != 0)
return true;
return 1;
// read a character from the file, store in state
char state = '\0';
FILE *f = fopen(path, "r");
assert(f);
fread(&state, 1, 1, f);
fclose(f);
// return whether state is truthy
return !!(state - 0x30);
return state != '0';
}
void setcpu(uint32_t id, bool state) {
char path[64];
snprintf(path, 64, cpu_path, id);
int cpu_setenabled(uint id, int state) {
const size_t pathlen = sizeof(cpu_path) + (uint)(log10(-1u) + 1);
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);
if (access(path, W_OK) != 0)
return 1;
// write the state to the file (creates file if it doesn't exist)
FILE *f = fopen(path, "w");
assert(f);
fwrite(&s, 1, 1, f);
fclose(f);
return 0;
}

View File

@@ -1,6 +1,10 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "util/intdef.h"
bool getcpu(uint32_t); // gets the state of core (id)
void setcpu(uint32_t, bool); // sets the state of core (id)
/* gets the current state of a CPU thread,
* 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
#include <stdarg.h>
#include <stdint.h>
#include <stdnoreturn.h>
// prints an error message and aborts execution
noreturn void fatal(char const*, ...);
#if __INCLUDE_LEVEL__ > 0
#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 <stdio.h>
#include <sys/sysinfo.h>
@@ -7,28 +8,25 @@
#include "error.h"
#include "opts.h"
static inline bool cpu_setter(uint32_t id, bool nstate, uint8_t opts) {
bool cstate = !nstate;
if (!(opts & OPT_SET_ALL))
cstate = getcpu(id);
if (cstate != nstate) {
setcpu(id, nstate);
return true;
}
return false;
/* sets CPU `id` to `nstate`, if it hasn't been set yet.
* If `opt` contains `OPT_SET_ALL`, this check is ignored.
* Returns `0` upon success, `1` upon failure. */
static int setcpu(uint id, int nstate, u8 opts) {
bool nsetall = !(opts & OPT_SET_ALL); // if SET_ALL has not been set
int cstate = nsetall && cpu_getenabled(id); // get state if nsetall=1
return (nsetall && cstate == nstate) || cpu_setenabled(id, nstate); // set if cstate matches nstate, unless nsetall=1
}
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
}
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!");
int32_t ncpus; // the number of CPUs to activate
uint8_t opts = getoptions(argc, argv, &ncpus); // the options to use
int32_t mcpus = get_nprocs_conf(); // the max number of CPUs that are available
int mcpus, ncpus; // the number of CPUs to activate
u8 opts = getoptions(argc, argv, &ncpus); // the options to use
mcpus = get_nprocs_conf(); // the max number of CPUs that are available
if (opts & OPT_LIST_CORES && ncpus < 0) {
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 < 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";
for (int32_t id = 1; id < mcpus; id++) { // start at CPU 1, as CPU 0 is not writeable
const char *const cpu_set_log = "set cpu %i to %hi\n";
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
if (id < ncpus) {
// 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);
continue;
}
// 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);
continue;
}

View File

@@ -1,45 +1,49 @@
#include "opts.h"
#include <errno.h>
#include <getopt.h>
#include <stdint.h>
#include <stdlib.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;
char opt;
char const* const msg = "-%c has already been set";
while ((opt = getopt(argc, argv, "lavi")) != -1) {
while ((opt = getopt(argc, argv, "lavih")) != -1) {
switch (opt) {
case 'l':
if (opts & OPT_LIST_CORES) fatal(msg, 'l');
opts |= OPT_LIST_CORES;
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 'l': opts |= OPT_LIST_CORES; break;
case 'a': opts |= OPT_SET_ALL; break;
case 'v': opts |= OPT_VERBOSE | OPT_LIST_CORES; break;
case 'i': opts |= OPT_INVERT; break;
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) {
char *end;
char *num = argv[optind];
*ncpus = atoi(num);
} else if (opts & OPT_LIST_CORES) {
*ncpus = -1;
} else
*ncpus = strtol(num, &end, 0);
if (errno || end == num || *end != '\0')
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]");
return opts;

View File

@@ -1,6 +1,8 @@
#pragma once
#include <stdint.h>
#include "util/intdef.h"
enum opt {
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
@@ -8,4 +10,7 @@ enum opt {
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