linux - cały rdzeń dla mnie

0

Witam!

W epoce, gdzie mamy kilka procesorów w jednym, jest możliwość przejęcia jednego przez program?

Potrzebuję modyfikować pewien obszar w pamięci w precyzyjnych odstępach czasu.
Instalując kernela "PREEMPT RT" i bazując na clock_nanosleep udało mi się uzyskać dokładność ok. 20us.

Po małych przeróbkach:

int clock_nanosleep2(clockid_t clock_id, int flags,
                   const struct timespec *request,
                   struct timespec *remain){
	struct timespec t;
	int ret;
	do{
		ret = clock_gettime(clock_id, &t);
	}while(t.tv_sec < request->tv_sec);
	return ret;
}

Udało mi się uzyskać 2-krotnie lepszy czas.

Chciałbym wycisnąć taką dokładność jaką się tylko da, kosztem 100%-towej jazdy procesora.

1

Możesz ustawić thread affinity dla wątku albo użyć spinlocka.

0

Może coś źle kombinuję.
Zastanawia mnie, czemu jest taki czas po nanosleep3 jeśli pomiędzy wywołaniami clock_gettime czas jest mniejszy od 1us.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <sched.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <limits.h>
#include <linux/unistd.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <sys/syscall.h>

#define NSEC_PER_SEC    (1000000000) 
void timespec_add_nanosec(struct timespec * t, long int interval){
	t->tv_nsec += interval;
	while (t->tv_nsec >= NSEC_PER_SEC){
		t->tv_nsec -= NSEC_PER_SEC;
		t->tv_sec++;
	}	
}

int clock_nanosleep2(clockid_t clock_id, int flags,
                   const struct timespec *request,
                   struct timespec *remain){
	struct timespec t;
	int ret;
	do{
		ret = clock_gettime(clock_id, &t);
	}while(t.tv_sec <= request->tv_sec);
	return ret;
}

void clock_nanosleep3(const struct timespec *request){
	struct timespec t;
	do{
		clock_gettime(CLOCK_MONOTONIC, &t);
	}while(t.tv_sec <= request->tv_sec);
}


int main(){
	int i;
	struct sched_param param;
#if 1
	param.sched_priority = sched_get_priority_max(SCHED_FIFO);
	if(sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
		perror("sched_setscheduler failed");
		exit(-1);
	}
	mlockall(MCL_CURRENT | MCL_FUTURE);
	setpriority(PRIO_PROCESS, getpid(), -20);
#endif
#if 1
	cpu_set_t set;

	CPU_ZERO(&set);
	CPU_SET(0, &set);
	if (sched_setaffinity( getpid(), sizeof( cpu_set_t ), &set )){
		perror("sched_setaffinity failed");
		exit(-1);
	}
#endif

	struct timespec t, t2, t3;
	clock_gettime(CLOCK_MONOTONIC ,&t);
	t.tv_sec++;
	t.tv_nsec = 0;
	

	//! TEST1
#if 1
	for(i=0; i<10; ++i){
		//clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
		//clock_nanosleep2(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
		clock_nanosleep3(&t);
		clock_gettime(CLOCK_MONOTONIC ,&t2);
		clock_gettime(CLOCK_MONOTONIC ,&t3);
		printf("%ld %ld\n", t2.tv_nsec, t3.tv_nsec);
		t.tv_sec++;
	}
#endif

	//! TEST2
#if 0
	int max = 0;
	for(i=0; i<5000; ++i){
		//clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
		//clock_nanosleep2(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
		clock_nanosleep3(&t);
		clock_gettime(CLOCK_MONOTONIC ,&t2);
		if( (t2.tv_nsec - t.tv_nsec) > max)
			max =  t2.tv_nsec - t.tv_nsec;
		timespec_add_nanosec(&t, 1000*1000 );
	}
	printf("max: %d\n", max);
#endif
	return 0;
}

Wynik (czas w nanosekundach):

$ sudo ./main
7654 8352
8096 8795
7766 8535
7851 8619
8071 8769
7238 7937
8496 9195
7515 8284
8054 8822
1

Nie jestem pewien, czy przerwania Ci nie będą robiły kuku. Poza tym - za pomocą taskset ustaw sobie affinity wszystkiego w systemie na pozostałe wolne procki / rdzenie.

Edit:
20 us to jeszcze stosunkowo dużo. Użyj czegoś do zliczania cykli typu rdtsc i skalibruj sobie to względem zegara procesora. gettimeofday może być wolne, bo zdaje się, że masz context switche do kernela (chyba).

Edit 2:
Z głowy mogę Ci jeszcze podpowiedzieć, że da się kontrolować które rdzenie mają obsługiwać przerwania. Robiło się to bodaj przez x2APIC i chyba da się to nawet ustawić przez modyfikację MSR-ów z user space. Poczytaj to: http://www.intel.com/content/dam/doc/specification-update/64-architecture-x2apic-specification.pdf

Jeśli mógłbyś nam coś więcej powiedzieć co dokładnie potrzebujesz, to powiedz, bo ja widzę jeszcze kolejne problemy: na współczesnych prockach z NUMĄ musisz się jeszcze upewnić, że w czasie dostępu do pamięci masz wyłączność, tzn. dotykasz lokalnej pamięci i nie ma nic co przyszło z innych fizycznych CPU w buforach, bo inaczej wzrośnie Ci opóźnienie. Jeśli dotykasz tej samej linijki cache z wielu rdzeni/cpu to dojdzie Ci problem z false sharing, co pewnie też się może odbić na opóźnieniach. Do tego nie wiem, jak wygląda sprawa z cachem dla zapisu. Tutaj jest coś więcej na ten temat: http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-91-12.pdf

1 użytkowników online, w tym zalogowanych: 0, gości: 1