blob: f878428a2f05e89785616ace0fe77147a268973c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Core-scheduling selftests.
*
* Copyright (C) 2020, Joel Fernandes.
*/
// XXX: On failures, clean up of cgroup, memory alloc, pthread barrier etc is needed.
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
pthread_barrier_t *start_barrier;
pthread_barrierattr_t attr;
unsigned long nr_busy_loops = 100000000; /* 100M (~200ms)- Controls userspace cpu time (no syscalls) */
size_t zero_read_size = 50000000; /* 50 MB. */
int nr_tasks_per_group = 2;
int zero_read_ms = 200;
int nr_groups = 2;
char mntpath[256];
char *zero_buff;
struct group {
pid_t *child_pids;
};
struct group *all_groups;
/* For debugging pthread_barrier, dumps bytes. */
char dump_pb(char *ctx)
{
int i = 0;
printf("%s: ", ctx);
for (; i < sizeof(pthread_barrier_t); i++)
printf("%x ", ((char *)start_barrier)[i]);
printf("\n");
}
static uint64_t to_ns(struct timespec ts) {
return ts.tv_sec * (uint64_t)1000000000L + ts.tv_nsec;
}
void read_zeros_for_ms(int ms)
{
struct timespec ts_start, ts_current;
uint64_t ns;
int fd;
ns = 1000000 * ms;
fd = open("/dev/zero", O_RDONLY);
clock_gettime(CLOCK_MONOTONIC, &ts_start);
for(;;) {
clock_gettime(CLOCK_MONOTONIC, &ts_current);
if ((to_ns(ts_current) - to_ns(ts_start)) > ns)
break;
if (read(fd, zero_buff, zero_read_size) != zero_read_size) {
perror("Failed to read /dev/zero: ");
exit(-1);
}
}
close(fd);
}
void child_func(int group_num)
{
int i;
pthread_barrier_wait(start_barrier);
printf("child in %d group starting\n", group_num);
while(1) {
read_zeros_for_ms(zero_read_ms);
for(i = 0; i < nr_busy_loops; i++);
}
}
int make_child(int group_num)
{
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed: ");
exit(-1);
} else if (pid != 0) {
return pid;
}
child_func(group_num);
}
void make_cgroup(void)
{
char *mnt;
int ret;
sprintf(mntpath, "/tmp/coresched-test-XXXXXX");
mnt = mkdtemp(mntpath);
if (!mnt) {
perror("Failed to create mount: ");
exit(-1);
}
ret = mount("nodev", mnt, "cgroup", 0, "cpu");
if (ret == -1) {
perror("Failed to mount cgroup: ");
exit(-1);
}
return;
}
int make_group(int group_num)
{
int i, ret, tfd;
char cgroup_path[256], tasks_path[256], tag_path[256];
char wrbuf[32];
/* Make the cgroup node for this group */
sprintf(cgroup_path, "%s/group%d", mntpath, group_num);
ret = mkdir(cgroup_path, 0777);
if (ret == -1) {
perror("Failed to create group in cgroup: ");
exit(-1);
}
sprintf(tasks_path, "%s/tasks", cgroup_path);
/* Enable core-scheduling on the group */
sprintf(tag_path, "%s/group%d/cpu.tag", mntpath, group_num);
tfd = open(tag_path, O_WRONLY, 0666);
if (tfd == -1) {
perror("Open of cgroup tag path failed: ");
exit(-1);
}
if (write(tfd, "1", 1) != 1) {
perror("Failed to enable coresched on cgroup: ");
exit(-1);
}
if (close(tfd) == -1) {
perror("Failed to close tag fd: ");
exit(-1);
}
/* Allocate child pids */
all_groups[group_num].child_pids =
calloc(nr_tasks_per_group, sizeof(struct group));
if (!all_groups[group_num].child_pids) {
perror("Memory alloc failed: ");
exit(-1);
}
for (i = 0; i < nr_tasks_per_group; i++) {
/* Fork */
all_groups[group_num].child_pids[i] = make_child(i);
/* Add child pid to group */
tfd = open(tasks_path, O_WRONLY, 0666);
if (tfd == -1) {
perror("Open of cgroup tasks path failed: ");
exit(-1);
}
sprintf(wrbuf, "%d\n", all_groups[group_num].child_pids[i]);
if (write(tfd, wrbuf, strlen(wrbuf)) != strlen(wrbuf)) {
perror("Failed to add task to cgroup: ");
exit(-1);
}
if (close(tfd) == -1) {
perror("Failed to close task fd: ");
exit(-1);
}
}
}
/* pthread barrier ready to after this, call pthread_barrier_wait() */
void init_pthread_barrier()
{
start_barrier = mmap(NULL, 8192, PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANONYMOUS, -1, 0);
memset(start_barrier, 0, 8192);
pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_barrier_init(start_barrier, &attr, (nr_groups * nr_tasks_per_group) + 1);
}
int main()
{
int i;
init_pthread_barrier();
/* Share memory all processes can read into. */
zero_buff = mmap(NULL, zero_read_size, PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANONYMOUS, -1, 0);
/* Before calling make_group() */
make_cgroup();
/* Before calling make_group() */
all_groups = calloc(nr_groups, sizeof(struct group));
if (!all_groups) {
perror("Memory alloc failed: ");
exit(-ENOMEM);
}
for (i = 0; i < nr_groups; i++) {
make_group(i);
}
pthread_barrier_wait(start_barrier);
printf("parent starting\n");
while(1);
}