C++程序  |  392行  |  10.53 KB

/*
 * Copyright (c) 2018 Google, Inc.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Six RT FIFO tasks are created and affined to the same CPU. They execute
 * with a particular pattern of overlapping eligibility to run. The resulting
 * execution pattern is checked to see that the tasks execute as expected given
 * their priorities.
 */

#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <time.h>

#include "tst_test.h"
#include "tst_safe_file_ops.h"
#include "tst_safe_pthread.h"

#include "trace_parse.h"
#include "util.h"

#define TRACE_EVENTS "sched_wakeup sched_switch sched_process_exit"

static int rt_task_tids[6];

/*
 * Create two of each RT FIFO task at each priority level. Ensure that
 * - higher priority RT tasks preempt lower priority RT tasks
 * - newly woken RT tasks at the same priority level do not preempt currently
 *   running RT tasks
 *
 * Affine all tasks to CPU 0.
 * Have rt_low_fn 1 run first. It wakes up rt_low_fn 2, which should not run
 * until rt_low_fn 1 sleeps/exits.
 * rt_low_fn2 wakes rt_med_fn1. rt_med_fn1 should run immediately, then sleep,
 * allowing rt_low_fn2 to complete.
 * rt_med_fn1 wakes rt_med_fn2, which should not run until rt_med_fn 2
 * sleeps/exits... (etc)
 */
static sem_t sem_high_b;
static sem_t sem_high_a;
static sem_t sem_med_b;
static sem_t sem_med_a;
static sem_t sem_low_b;
static sem_t sem_low_a;

enum {
	RT_LOW_FN_A_TID = 0,
	RT_LOW_FN_B_TID,
	RT_MED_FN_A_TID,
	RT_MED_FN_B_TID,
	RT_HIGH_FN_A_TID,
	RT_HIGH_FN_B_TID,
};

struct expected_event {
	int event_type;
	/*
	 * If sched_wakeup, pid being woken.
	 * If sched_switch, pid being switched to.
	 */
	int event_data;
};
static struct expected_event events[] = {
	/* rt_low_fn_a wakes up rt_low_fn_b:
	 *   sched_wakeup(rt_low_fn_b) */
	{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
		.event_data = RT_LOW_FN_B_TID},
	/* TODO: Expect an event for the exit of rt_low_fn_a. */
	/* 3ms goes by, then rt_low_fn_a exits and rt_low_fn_b starts running */
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_LOW_FN_B_TID},
	/* rt_low_fn_b wakes rt_med_fn_a which runs immediately */
	{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
		.event_data = RT_MED_FN_A_TID},
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_MED_FN_A_TID},
	/* rt_med_fn_a sleeps, allowing rt_low_fn_b time to exit */
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_LOW_FN_B_TID},
	/* TODO: Expect an event for the exit of rt_low_fn_b. */
	{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
		.event_data = RT_MED_FN_A_TID},
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_MED_FN_A_TID},
	/* rt_med_fn_a wakes rt_med_fn_b */
	{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
		.event_data = RT_MED_FN_B_TID},
	/* 3ms goes by, then rt_med_fn_a exits and rt_med_fn_b starts running */
	/* TODO: Expect an event for the exit of rt_med_fn_a */
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_MED_FN_B_TID},
	/* rt_med_fn_b wakes up rt_high_fn_a which runs immediately */
	{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
		.event_data = RT_HIGH_FN_A_TID},
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_HIGH_FN_A_TID},
	/* rt_high_fn_a sleeps, allowing rt_med_fn_b time to exit */
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_MED_FN_B_TID},
	/* TODO: Expect an event for the exit of rt_med_fn_b */
	{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
		.event_data = RT_HIGH_FN_A_TID},
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_HIGH_FN_A_TID},
	/* rt_high_fn_a wakes up rt_high_fn_b */
	{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
		.event_data = RT_HIGH_FN_B_TID},
	/* 3ms goes by, then rt_high_fn_a exits and rt_high_fn_b starts running */
	/* TODO: Expect an event for the exit of rt_high_fn_a */
	{ .event_type = TRACE_RECORD_SCHED_SWITCH,
		.event_data = RT_HIGH_FN_B_TID},
};

static void *rt_high_fn_b(void *arg LTP_ATTRIBUTE_UNUSED)
{
	rt_task_tids[RT_HIGH_FN_B_TID] = gettid();
	affine(0);

	/* Wait for rt_high_fn_a to wake us up. */
	sem_wait(&sem_high_b);
	/* Run after rt_high_fn_a exits. */

	return NULL;
}

static void *rt_high_fn_a(void *arg LTP_ATTRIBUTE_UNUSED)
{
	rt_task_tids[RT_HIGH_FN_A_TID] = gettid();
	affine(0);

	/* Wait for rt_med_fn_b to wake us up. */
	sem_wait(&sem_high_a);

	/* Sleep, allowing rt_med_fn_b a chance to exit. */
	usleep(1000);

	/* Wake up rt_high_fn_b. We should continue to run though. */
	sem_post(&sem_high_b);

	/* Busy wait for just a bit. */
	burn(3000, 0);

	return NULL;
}

static void *rt_med_fn_b(void *arg LTP_ATTRIBUTE_UNUSED)
{
	rt_task_tids[RT_MED_FN_B_TID] = gettid();
	affine(0);

	/* Wait for rt_med_fn_a to wake us up. */
	sem_wait(&sem_med_b);
	/* Run after rt_med_fn_a exits. */

	/* This will wake up rt_high_fn_a which will run immediately, preempting
	 * us. */
	sem_post(&sem_high_a);

	return NULL;
}

static void *rt_med_fn_a(void *arg LTP_ATTRIBUTE_UNUSED)
{
	rt_task_tids[RT_MED_FN_A_TID] = gettid();
	affine(0);

	/* Wait for rt_low_fn_b to wake us up. */
	sem_wait(&sem_med_a);

	/* Sleep, allowing rt_low_fn_b a chance to exit. */
	usleep(3000);

	/* Wake up rt_med_fn_b. We should continue to run though. */
	sem_post(&sem_med_b);

	/* Busy wait for just a bit. */
	burn(3000, 0);

	return NULL;
}

static void *rt_low_fn_b(void *arg LTP_ATTRIBUTE_UNUSED)
{
	rt_task_tids[RT_LOW_FN_B_TID] = gettid();
	affine(0);

	/* Wait for rt_low_fn_a to wake us up. */
	sem_wait(&sem_low_b);
	/* Run after rt_low_fn_a exits. */

	/* This will wake up rt_med_fn_a which will run immediately, preempting
	 * us. */
	sem_post(&sem_med_a);

	/* So the previous sem_post isn't actually causing a sched_switch
	 * to med_a immediately - this is running a bit longer and exiting.
	 * Delay here. */
	burn(1000, 0);

	return NULL;
}

/* Put real task tids into the expected events. */
static void fixup_expected_events(void)
{
	int i;
	int size = sizeof(events)/sizeof(struct expected_event);

	for (i = 0; i < size; i++)
		events[i].event_data = rt_task_tids[events[i].event_data];
}

static void *rt_low_fn_a(void *arg LTP_ATTRIBUTE_UNUSED)
{
	rt_task_tids[RT_LOW_FN_A_TID] = gettid();
	affine(0);

	/* Give all other tasks a chance to set their tids and block. */
	usleep(3000);

	fixup_expected_events();

	SAFE_FILE_PRINTF(TRACING_DIR "trace_marker", "TEST START");

	/* Wake up rt_low_fn_b. We should continue to run though. */
	sem_post(&sem_low_b);

	/* Busy wait for just a bit. */
	burn(3000, 0);

	return NULL;
}

/* Returns whether the given tid is a tid of one of the RT tasks in this
 * testcase. */
static int rt_tid(int tid)
{
	int i;
	for (i = 0; i < 6; i++)
		if (rt_task_tids[i] == tid)
			return 1;
	return 0;
}

static int parse_results(void)
{
	int i;
	int test_start = 0;
	int event_idx = 0;
	int events_size = sizeof(events)/sizeof(struct expected_event);

	for (i = 0; i < num_trace_records; i++) {
		if (trace[i].event_type == TRACE_RECORD_TRACING_MARK_WRITE &&
		    !strcmp(trace[i].event_data, "TEST START"))
			test_start = 1;

		if (!test_start)
			continue;

		if (trace[i].event_type != TRACE_RECORD_SCHED_WAKEUP &&
		    trace[i].event_type != TRACE_RECORD_SCHED_SWITCH)
			continue;

		if (trace[i].event_type == TRACE_RECORD_SCHED_SWITCH) {
			struct trace_sched_switch *t = trace[i].event_data;
			if (!rt_tid(t->next_pid))
				continue;
			if (events[event_idx].event_type !=
			    TRACE_RECORD_SCHED_SWITCH ||
			    events[event_idx].event_data !=
			    t->next_pid) {
				printf("Test case failed, expecting event "
				       "index %d type %d for tid %d, "
				       "got sched switch to tid %d\n",
				       event_idx,
				       events[event_idx].event_type,
				       events[event_idx].event_data,
				       t->next_pid);
				return -1;
			}
			event_idx++;
		}

		if (trace[i].event_type == TRACE_RECORD_SCHED_WAKEUP) {
			struct trace_sched_wakeup *t = trace[i].event_data;
			if (!rt_tid(t->pid))
				continue;
			if (events[event_idx].event_type !=
			    TRACE_RECORD_SCHED_WAKEUP ||
			    events[event_idx].event_data !=
			    t->pid) {
				printf("Test case failed, expecting event "
				       "index %d type %d for tid %d, "
				       "got sched wakeup to tid %d\n",
				       event_idx,
				       events[event_idx].event_type,
				       events[event_idx].event_data,
				       t->pid);
				return -1;
			}
			event_idx++;
		}

		if (event_idx == events_size)
			break;
	}

	if (event_idx != events_size) {
		printf("Test case failed, "
		       "did not complete all expected events.\n");
		printf("Next expected event: event type %d for tid %d\n",
		       events[event_idx].event_type,
		       events[event_idx].event_data);
		return -1;
	}

	return 0;
}

static void create_rt_thread(int prio, void *fn, pthread_t *rt_thread)
{
	pthread_attr_t rt_thread_attrs;
	struct sched_param rt_thread_sched_params;

	ERROR_CHECK(pthread_attr_init(&rt_thread_attrs));
	ERROR_CHECK(pthread_attr_setinheritsched(&rt_thread_attrs,
						 PTHREAD_EXPLICIT_SCHED));
	ERROR_CHECK(pthread_attr_setschedpolicy(&rt_thread_attrs,
						SCHED_FIFO));
	rt_thread_sched_params.sched_priority = prio;
	ERROR_CHECK(pthread_attr_setschedparam(&rt_thread_attrs,
					       &rt_thread_sched_params));

	SAFE_PTHREAD_CREATE(rt_thread, &rt_thread_attrs, fn, NULL);
}

static void run(void)
{
	pthread_t rt_low_a, rt_low_b;
	pthread_t rt_med_a, rt_med_b;
	pthread_t rt_high_a, rt_high_b;

	sem_init(&sem_high_b, 0, 0);
	sem_init(&sem_high_a, 0, 0);
	sem_init(&sem_med_b, 0, 0);
	sem_init(&sem_med_a, 0, 0);
	sem_init(&sem_low_b, 0, 0);
	sem_init(&sem_low_a, 0, 0);

	/* configure and enable tracing */
	SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0");
	SAFE_FILE_PRINTF(TRACING_DIR "buffer_size_kb", "16384");
	SAFE_FILE_PRINTF(TRACING_DIR "set_event", TRACE_EVENTS);
	SAFE_FILE_PRINTF(TRACING_DIR "trace", "\n");
	SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "1");

	create_rt_thread(70, rt_low_fn_a, &rt_low_a);
	create_rt_thread(70, rt_low_fn_b, &rt_low_b);
	create_rt_thread(75, rt_med_fn_a, &rt_med_a);
	create_rt_thread(75, rt_med_fn_b, &rt_med_b);
	create_rt_thread(80, rt_high_fn_a, &rt_high_a);
	create_rt_thread(80, rt_high_fn_b, &rt_high_b);

	SAFE_PTHREAD_JOIN(rt_low_a, NULL);
	SAFE_PTHREAD_JOIN(rt_low_b, NULL);
	SAFE_PTHREAD_JOIN(rt_med_a, NULL);
	SAFE_PTHREAD_JOIN(rt_med_b, NULL);
	SAFE_PTHREAD_JOIN(rt_high_a, NULL);
	SAFE_PTHREAD_JOIN(rt_high_b, NULL);

	/* disable tracing */
	SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0");
	LOAD_TRACE();

	if (parse_results())
		tst_res(TFAIL, "RT FIFO tasks did not execute in the expected "
			"pattern.\n");
	else
		tst_res(TPASS, "RT FIFO tasks executed in the expected "
			"pattern.\n");
}

static struct tst_test test = {
	.test_all = run,
	.cleanup = trace_cleanup,
};