Browse Source

PubSub: Realtime publisher example

Julius Pfrommer 5 years ago
parent
commit
1b14ad1d28

+ 82 - 0
examples/pubsub_realtime/README.md

@@ -0,0 +1,82 @@
+# open62541 Realtime OPC UA PubSub Publisher
+
+This example is a self-contained PubSub publisher over raw Ethernet. It
+showcases the realtime-capabilities of OPC UA PubSub. The core idea is that the
+publisher callback can be triggered from a time-triggered system interrupt and
+sends out the PubSub message within the interrupt.
+
+The publisher retrieves its configuration and the payload data from the
+information model of an OPC UA server. (It could also be run in a standalone
+mode without an OPC UA server.) Since the publisher interrupt preempts the
+execution of the normal OPC UA server, the information model needs to be
+consistent at every time (reentrant). The specific techniques used to make the
+OPC UA server reentrant are described in this publication:
+
+```
+@inproceedings{pfrommer2018open,
+  title={Open source OPC UA PubSub over TSN for realtime industrial communication},
+  author={Pfrommer, Julius and Ebner, Andreas and Ravikumar, Siddharth and Karunakaran, Bhagath},
+  booktitle={2018 IEEE 23rd International Conference on Emerging Technologies and Factory Automation (ETFA)},
+  pages={1087--1090},
+  year={2018},
+  organization={IEEE}
+}
+```
+
+Please cite if you use this work.
+
+OPC UA PubSub for open62541 is funded by an industry consortium in the context
+of an OSADL project (Open Source Automation Development Lab). Technical
+development is conducted by Fraunhofer IOSB and Kalycito Infotech.
+
+https://www.osadl.org/OPC-UA-TSN.opcua-tsn.0.html
+
+## Realtime communication with Time-Sensitive Networking (TSN)
+
+OPC UA PubSub can be used together with TSN for hard-realtime Ethernet-based
+communication. Vendor-specific APIs are commonly used for TSN capabilities. This
+example only uses the standard Linux API for raw Ethernet. Vendor-specific
+examples may be added at a later time.
+
+## Building the RT Publisher
+
+The main open62541 library needs to be built with the following build options
+enabled for the realtime PubSub example. Note that some of the other examples
+supplied with open62541 will not link against the library with these build
+options. For good timings, ensure that the `CMAKE_BUILD_TYPE` is set to
+`Release`.
+
+- UA_ENABLE_PUBSUB
+- UA_ENABLE_PUBSUB_ETH_UADP
+- UA_ENABLE_PUBSUB_CUSTOM_PUBLISH_HANDLING
+- UA_ENABLE_MALLOC_SINGLETON
+- UA_ENABLE_IMMUTABLE_NODES
+
+The publisher contains some hard-coded values that need to be adjusted to
+specific systems. Please check the top definitions in
+`pubsub_interrupt_publish.c` and `start_rt_publish.sh`.
+
+The publisher code is built and linked against the main open62541 library as follows:
+
+`gcc -O2 ../examples/pubsub_realtime/pubsub_interrupt_publish.c ../examples/pubsub_realtime/bufmalloc.c -I../include -I../plugins/include -Isrc_generated -I../arch/posix -I../arch -I../plugins/networking bin/libopen62541.a -lrt -o rt_publisher`
+
+## Running the RT Publisher
+
+The publisher must be run as root for direct access to the Ethernet interface.
+
+`# ./rt_publisher`
+
+The example contains a script to be used with RT-Preempt Linux kernel. The
+following command starts the publisher, locks the process to a specific CPU, and
+sets the scheduling policy.
+
+`# start_rt_publish.sh ./rt_publisher`
+
+The measurements are written to a file (publisher_measurement.csv) with the
+following fields for every publish callback:
+
+- Counter
+- Publication Interval
+- Nominal time for the current publish
+- Start delay from the nominal time
+- Duration of the publish callback

+ 66 - 0
examples/pubsub_realtime/bufmalloc.c

@@ -0,0 +1,66 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
+ *
+ *    Copyright 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
+ */
+
+#include "bufmalloc.h"
+
+#define MALLOCMEMBUFSIZE 16384
+
+/* Every element has the memory layout [length (size_t) | buf (length * sizeof(char)) ... ].
+ * The pointer to buf is returned. */
+static char membuf[MALLOCMEMBUFSIZE];
+static size_t pos;
+
+static void * membufMalloc(size_t size) {
+    if(pos + size + sizeof(size_t) > MALLOCMEMBUFSIZE)
+        return NULL;
+    char *begin = &membuf[pos];
+    *((size_t*)begin) = size;
+    pos += size + sizeof(size_t);
+    return &begin[sizeof(size_t)];
+}
+
+static void membufFree(void *ptr) {
+    /* Don't do anyting */
+}
+
+static void * membufCalloc(size_t nelem, size_t elsize) {
+    size_t total = nelem * elsize;
+    void *mem = membufMalloc(total);
+    if(!mem)
+        return NULL;
+    memset(mem, 0, total);
+    return mem;
+}
+
+static void * (membufRealloc)(void *ptr, size_t size) {
+    size_t orig_size = ((size_t*)ptr)[-1];
+    if(size <= orig_size)
+        return ptr;
+    void *mem = membufMalloc(size);
+    if(!mem)
+        return NULL;
+    memcpy(mem, ptr, orig_size);
+    return mem;
+}
+
+void resetMembuf(void) {
+    pos = 0;
+}
+
+void useMembufAlloc(void) {
+    pos = 0;
+    UA_globalMalloc = membufMalloc;
+    UA_globalFree = membufFree;
+    UA_globalCalloc = membufCalloc;
+    UA_globalRealloc = membufRealloc;
+}
+
+void useNormalAlloc(void) {
+    UA_globalMalloc = malloc;
+    UA_globalFree = free;
+    UA_globalCalloc = calloc;
+    UA_globalRealloc = realloc;
+}

+ 11 - 0
examples/pubsub_realtime/bufmalloc.h

@@ -0,0 +1,11 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
+ *
+ *    Copyright 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
+ */
+
+#include <open62541/config.h>
+
+void resetMembuf(void);
+void useMembufAlloc(void);
+void useNormalAlloc(void);

BIN
examples/pubsub_realtime/etfa18-pfrommer-tsn-pubsub.pdf


+ 322 - 0
examples/pubsub_realtime/pubsub_interrupt_publish.c

@@ -0,0 +1,322 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
+ *
+ *    Copyright 2018-2019 (c) Kalycito Infotech
+ *    Copyright 2019 (c) Fraunhofer IOSB (Author: Andreas Ebner)
+ *    Copyright 2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <open62541/server.h>
+#include <open62541/server_config_default.h>
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/plugin/pubsub_ethernet.h>
+#include "bufmalloc.h"
+
+#define ETH_PUBLISH_ADDRESS      "opc.eth://0a-00-27-00-00-08"
+#define ETH_INTERFACE            "enp0s8"
+#define MAX_MEASUREMENTS         10000
+#define MILLI_AS_NANO_SECONDS    (1000 * 1000)
+#define SECONDS_AS_NANO_SECONDS  (1000 * 1000 * 1000)
+#define CLOCKID                  CLOCK_REALTIME
+#define SIG                      SIGUSR1
+#define PUB_INTERVAL             0.25 /* Publish interval in milliseconds */
+#define DATA_SET_WRITER_ID       62541
+#define MEASUREMENT_OUTPUT       "publisher_measurement.csv"
+
+UA_NodeId counterNodePublisher = {1, UA_NODEIDTYPE_NUMERIC, {1234}};
+UA_Int64 pubIntervalNs;
+UA_ServerCallback pubCallback = NULL;
+UA_Server *pubServer;
+UA_Boolean running = true;
+void *pubData;
+timer_t pubEventTimer;
+struct sigevent pubEvent;
+struct sigaction signalAction;
+
+/* Arrays to store measurement data */
+UA_Int32 currentPublishCycleTime[MAX_MEASUREMENTS+1];
+struct timespec calculatedCycleStartTime[MAX_MEASUREMENTS+1];
+struct timespec cycleStartDelay[MAX_MEASUREMENTS+1];
+struct timespec cycleDuration[MAX_MEASUREMENTS+1];
+size_t publisherMeasurementsCounter  = 0;
+
+static void
+timespec_diff(struct timespec *start, struct timespec *stop,
+              struct timespec *result) {
+    if((stop->tv_nsec - start->tv_nsec) < 0) {
+        result->tv_sec = stop->tv_sec - start->tv_sec - 1;
+        result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
+    } else {
+        result->tv_sec = stop->tv_sec - start->tv_sec;
+        result->tv_nsec = stop->tv_nsec - start->tv_nsec;
+    }
+}
+
+/* Used to adjust the nanosecond > 1s field value */
+static void
+nanoSecondFieldConversion(struct timespec *timeSpecValue) {
+    while(timeSpecValue->tv_nsec > (SECONDS_AS_NANO_SECONDS - 1)) {
+        timeSpecValue->tv_sec += 1;
+        timeSpecValue->tv_nsec -= SECONDS_AS_NANO_SECONDS;
+    }
+}
+
+/* Signal handler */
+static void
+publishInterrupt(int sig, siginfo_t* si, void* uc) {
+    if(si->si_value.sival_ptr != &pubEventTimer) {
+        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "stray signal");
+        return;
+    }
+
+    /* Execute the publish callback in the interrupt */
+    struct timespec begin, end;
+    clock_gettime(CLOCKID, &begin);
+    useMembufAlloc();
+    pubCallback(pubServer, pubData);
+    useNormalAlloc();
+    clock_gettime(CLOCKID, &end);
+
+    if(publisherMeasurementsCounter >= MAX_MEASUREMENTS)
+        return;
+
+    /* Save current configured publish interval */
+    currentPublishCycleTime[publisherMeasurementsCounter] = pubIntervalNs;
+
+    /* Save the difference to the calculated time */
+    timespec_diff(&calculatedCycleStartTime[publisherMeasurementsCounter],
+                  &begin, &cycleStartDelay[publisherMeasurementsCounter]);
+
+    /* Save the duration of the publish callback */
+    timespec_diff(&begin, &end, &cycleDuration[publisherMeasurementsCounter]);
+
+    /* Save the calculated starting time for the next cycle */
+    calculatedCycleStartTime[publisherMeasurementsCounter + 1].tv_nsec +=
+        calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec + pubIntervalNs;
+    calculatedCycleStartTime[publisherMeasurementsCounter + 1].tv_sec =
+        calculatedCycleStartTime[publisherMeasurementsCounter].tv_sec;
+    nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter + 1]);
+
+    publisherMeasurementsCounter++;
+
+    if(publisherMeasurementsCounter == MAX_MEASUREMENTS) {
+        /* Write the pubsub measurement data */
+        FILE *fpPublisher = fopen(MEASUREMENT_OUTPUT, "w");
+        for(UA_UInt32 i = 0; i < publisherMeasurementsCounter; i++) {
+            fprintf(fpPublisher, "%u, %u, %ld.%09ld, %ld.%09ld, %ld.%09ld\n",
+                    i,
+                    currentPublishCycleTime[i],
+                    calculatedCycleStartTime[i].tv_sec,
+                    calculatedCycleStartTime[i].tv_nsec,
+                    cycleStartDelay[i].tv_sec,
+                    cycleStartDelay[i].tv_nsec,
+                    cycleDuration[i].tv_sec,
+                    cycleDuration[i].tv_nsec);
+        }
+        fclose(fpPublisher);
+    }
+}
+
+/* The following three methods are originally defined in
+ * /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
+ * use system interrupts instead if time-triggered callbacks in the OPC UA
+ * server control flow. */
+
+UA_StatusCode
+UA_PubSubManager_addRepeatedCallback(UA_Server *server,
+                                     UA_ServerCallback callback,
+                                     void *data, UA_Double interval_ms,
+                                     UA_UInt64 *callbackId) {
+    if(pubCallback) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                       "At most one publisher can be registered for interrupt callbacks");
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    /* Set global values for the publish callback */
+    int resultTimerCreate = 0;
+    pubServer = server;
+    pubCallback = callback;
+    pubData = data;
+    pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
+
+    /* Handle the signal */
+    memset(&signalAction, 0, sizeof(signalAction));
+    signalAction.sa_flags = SA_SIGINFO;
+    signalAction.sa_sigaction = publishInterrupt;
+    sigemptyset(&signalAction.sa_mask);
+    sigaction(SIG, &signalAction, NULL);
+
+    /* Create the timer */
+    memset(&pubEventTimer, 0, sizeof(pubEventTimer));
+    pubEvent.sigev_notify = SIGEV_SIGNAL;
+    pubEvent.sigev_signo = SIG;
+    pubEvent.sigev_value.sival_ptr = &pubEventTimer;
+    resultTimerCreate = timer_create(CLOCKID, &pubEvent, &pubEventTimer);
+    if(resultTimerCreate != 0) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                       "Failed to create a system event");
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    /* Arm the timer */
+    struct itimerspec timerspec;
+    timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
+    timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
+    timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
+    timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
+    resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
+    if(resultTimerCreate != 0) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                       "Failed to arm the system timer");
+        timer_delete(pubEventTimer);
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    /* Start taking measurements */
+    publisherMeasurementsCounter = 0;
+    clock_gettime(CLOCKID, &calculatedCycleStartTime[0]);
+    calculatedCycleStartTime[0].tv_nsec += pubIntervalNs;
+    nanoSecondFieldConversion(&calculatedCycleStartTime[0]);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+UA_StatusCode
+UA_PubSubManager_changeRepeatedCallbackInterval(UA_Server *server,
+                                                UA_UInt64 callbackId,
+                                                UA_Double interval_ms) {
+    struct itimerspec timerspec;
+    int resultTimerCreate = 0;
+    pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
+    timerspec.it_interval.tv_sec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
+    timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
+    timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
+    timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
+    resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
+    if(resultTimerCreate != 0) {
+        UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
+                       "Failed to arm the system timer");
+        timer_delete(pubEventTimer);
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    clock_gettime(CLOCKID, &calculatedCycleStartTime[publisherMeasurementsCounter]);
+    calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec += pubIntervalNs;
+    nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+void
+UA_PubSubManager_removeRepeatedPubSubCallback(UA_Server *server, UA_UInt64 callbackId) {
+    timer_delete(pubEventTimer);
+    pubCallback = NULL; /* So that a new callback can be registered */
+}
+
+static void
+addPubSubConfiguration(UA_Server* server) {
+    UA_NodeId connectionIdent;
+    UA_NodeId publishedDataSetIdent;
+    UA_NodeId writerGroupIdent;
+
+    UA_PubSubConnectionConfig connectionConfig;
+    memset(&connectionConfig, 0, sizeof(connectionConfig));
+    connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
+    connectionConfig.transportProfileUri =
+        UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
+    connectionConfig.enabled = true;
+    UA_NetworkAddressUrlDataType networkAddressUrl =
+        {UA_STRING(ETH_INTERFACE), UA_STRING(ETH_PUBLISH_ADDRESS)};
+    UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
+                         &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
+    connectionConfig.publisherId.numeric = UA_UInt32_random();
+    UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
+
+    UA_PublishedDataSetConfig publishedDataSetConfig;
+    memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
+    publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
+    publishedDataSetConfig.name = UA_STRING("Demo PDS");
+    /* Create new PublishedDataSet based on the PublishedDataSetConfig. */
+    UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
+                                  &publishedDataSetIdent);
+
+    UA_NodeId dataSetFieldIdentCounter;
+    UA_DataSetFieldConfig counterValue;
+    memset(&counterValue, 0, sizeof(UA_DataSetFieldConfig));
+    counterValue.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
+    counterValue.field.variable.fieldNameAlias = UA_STRING ("Counter Variable 1");
+    counterValue.field.variable.promotedField = UA_FALSE;
+    counterValue.field.variable.publishParameters.publishedVariable = counterNodePublisher;
+    counterValue.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
+    UA_Server_addDataSetField(server, publishedDataSetIdent, &counterValue,
+                              &dataSetFieldIdentCounter);
+
+    UA_WriterGroupConfig writerGroupConfig;
+    memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
+    writerGroupConfig.name = UA_STRING("Demo WriterGroup");
+    writerGroupConfig.publishingInterval = PUB_INTERVAL;
+    writerGroupConfig.enabled = UA_FALSE;
+    writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
+    UA_Server_addWriterGroup(server, connectionIdent,
+                             &writerGroupConfig, &writerGroupIdent);
+
+    UA_NodeId dataSetWriterIdent;
+    UA_DataSetWriterConfig dataSetWriterConfig;
+    memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
+    dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
+    dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
+    dataSetWriterConfig.keyFrameCount = 10;
+    UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
+                               &dataSetWriterConfig, &dataSetWriterIdent);
+}
+
+static void
+addServerNodes(UA_Server* server) {
+    UA_UInt64 publishValue = 0;
+    UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
+    UA_Variant_setScalar(&publisherAttr.value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]);
+    publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter");
+    publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+    UA_Server_addVariableNode(server, counterNodePublisher,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                              UA_QUALIFIEDNAME(1, "Publisher Counter"),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                              publisherAttr, NULL, NULL);
+}
+
+/* Stop signal */
+static void stopHandler(int sign) {
+    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
+    running = UA_FALSE;
+}
+
+int main(void) {
+    signal(SIGINT, stopHandler);
+    signal(SIGTERM, stopHandler);
+
+    UA_ServerConfig* config = UA_ServerConfig_new_minimal(4840, NULL);
+    config->pubsubTransportLayers = (UA_PubSubTransportLayer *)
+        UA_malloc(sizeof(UA_PubSubTransportLayer));
+    if(!config->pubsubTransportLayers) {
+        UA_ServerConfig_delete(config);
+        return -1;
+    }
+    config->pubsubTransportLayers[0] = UA_PubSubTransportLayerEthernet();
+    config->pubsubTransportLayersSize++;
+
+    UA_Server *server = UA_Server_new(config);
+    addServerNodes(server);
+    addPubSubConfiguration(server);
+
+    /* Run the server */
+    UA_StatusCode retval = UA_Server_run(server, &running);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+
+    return (int)retval;
+}

+ 51 - 0
examples/pubsub_realtime/start_rt_publish.sh

@@ -0,0 +1,51 @@
+#!/bin/bash
+
+#program path or install dir
+IF=enp0s8
+CPU_NR=3
+PRIO=90
+OLDDIR=$(pwd)
+
+reset() {
+  #standard on most systems. ondemand -> Dynamic CPU-Freq.
+  echo ondemand >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
+  cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
+  for i in *
+  do
+    #~disable sleep state
+    echo 0 >$i/disable
+  done
+
+  phy=$IF #get interface name
+  #get pid's from interface irq
+  for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
+  do
+    #retrive or set a process's CPU affinity
+    taskset -pc 0-$CPU_NR $i >/dev/null
+    #manipulate the real-time attributes of a process -p priority -f scheduling policy to SCHED_FIFO
+    chrt -pf 50 $i
+  done
+  #distribute hardware interrupts across processsors on a muliprocessor system
+  systemctl start irqbalance
+}
+
+trap reset ERR
+systemctl stop irqbalance
+
+phy=$IF
+for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
+do
+  taskset -pc $CPU_NR $i >/dev/null
+  chrt -pf $PRIO $i
+done
+
+cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
+for i in `ls -1r`
+do
+  echo 1 >$i/disable
+done
+
+echo performance >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
+
+cd $OLDDIR
+taskset -c $CPU_NR chrt -f $PRIO $1