Linux - Real-Time (?)

Materiały ze spotkania Barcamp, które odbyło się 6 marca 2012.

Opowiadałem o Linuksie Real-Time przy użyciu praktycznych przykładów.

Zagadnieniom czasu rzeczywistego w Linuksie poświęcany jest z reguły jeden lub dwa rozdziały książek dotyczących zastosowań tego systemu w komputerach wbudowanych - to zdecydowanie za mało!

Podczas prezentacji omówione zostanie zastosowanie mikrokomputera z procesorem ARM do pracy w środowisku wymagającym niezawodności oraz gwarantowanego czasu reakcji na różne zdarzenia, oczywiście pod kontrolą Linuksa. Omówimy typowe problemy, wyniki pomiarów prawdziwych systemów, oraz sztuczki programistyczne (zarówno w jądrze, jak i w przestrzeni użytkownika), pozwalające te problemy zniwelować. Zaprezenrowany zostanie wzorzec projektowy aplikacji sterującej procesem przemysłowym, oraz jego przykładowa implementacja.

Marcin Bis - wystąpienie na Barcampie

Dodatkowe listingi

inout1.c

inout1.c
/*
 * Example 1 - kernel module.
 * Egde-trigerred interrupt is requested on INPUT_PIN.
 * When IRQ is triggered, the state of OUTPUT_PIN changes.
 *
 * Logic is implemented in interrupt service sequest function.
*/
 
#include <linux/module.h>
#include <linux/interrupt.h>
#include <mach/gpio.h>
#include <linux/slab.h>
 
#define ATMEL 1
//#define DEVKIT 1
 
#ifdef ATMEL
#define INPUT_PIN AT91_PIN_PB19
#define OUTPUT_PIN AT91_PIN_PB20
/* led */
//#define OUTPUT_PIN AT91_PIN_PE17
#else
#ifdef DEVKIT
#define INPUT_PIN 149
#define OUTPUT_PIN 150
#else
#error Board not defined!
#endif
#endif
 
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marcin Bis");
MODULE_DESCRIPTION("IO latency test module");
 
struct inout_data {
	int counter;
};
struct inout_data * data;
 
static irqreturn_t inout_isr(int irq, void *dev_id)
{
	struct inout_data * data = dev_id;
	gpio_set_value(OUTPUT_PIN,data->counter++ % 2);
//	printk(KERN_INFO "Event occured %d times.\n", data->counter++);
	return IRQ_HANDLED;
}
 
static int __init inout_init(void)
{
	int irq, error;
	int ipin = INPUT_PIN;
	int opin = OUTPUT_PIN;
	char * desc = "inout latency test";
 
	data = kzalloc(sizeof(struct inout_data), GFP_KERNEL);
	data->counter = 0;
 
	/* claim and configure output pin */
	error = gpio_request(opin, desc);
	if (error < 0) {
		printk(KERN_ERR "Failed to request GPIO %d, error %d\n",opin,error);
		goto err0;
	}
 
	error = gpio_direction_output(opin,1);
	gpio_set_value(opin,0);
 
	/* claim and configure input pin */
	error = gpio_request(ipin, desc);
	if (error < 0) {
		printk(KERN_ERR "Failed to request GPIO %d, error %d\n",ipin,error);
		goto err1;
	}
 
	error = gpio_direction_input(ipin);
	if (error < 0) {
		printk(KERN_ERR "Failed to set direction of GPIO %d, error %d\n",ipin,error);
		goto err2;
	}
 
	irq = gpio_to_irq(ipin);
	if (error < 0) {
		error = irq;
		printk(KERN_ERR "Failed to get IRQ number for GPIO %d, error %d\n",ipin,error);
		goto err2;
	}
 
	/* request interrupt on input pin - the fun starts from now */
	error = request_irq(irq, inout_isr,
                            IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                            desc, data);
 
	if (error) {
		printk(KERN_ERR "Failed to request interrupt %d, error %d\n",irq,error);
		goto err2;
	}
 
	return 0;
 
err2:
	gpio_free(ipin);
err1:
	gpio_free(opin);
err0:
	return error;
}
 
module_init(inout_init);
 
static void __exit inout_exit(void)
{
	int irq;
 
	irq = gpio_to_irq(INPUT_PIN);
	free_irq(irq, data);
	gpio_free(INPUT_PIN);
	gpio_free(OUTPUT_PIN);
	return;
}
module_exit(inout_exit);

inout2.c

inout2.c
/*
Example 2 - kernel module.
Egde-trigerred interrupt is requested on INPUT_PIN.
When IRQ is triggered, the state of OUTPUT_PIN changes.
 
This module implenets only mechanism, logic must be implemented in userspace.
 
When IRQ is trigerred:
1. ISR - shedules bottom halve.
2. Bottom halve unlocks processess waiting on waitqueue.
 
Process:
1. Opens characted device.
2. Reads from character device - this blocks the process on waitqueue.
3. When process is waken up, it wtites to characted device, which changes state of OUTPUT_PIN.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <mach/gpio.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
 
/* copy_from/to_user */
#include <asm/uaccess.h>
 
/* kolejka oczekiwania */
#include <linux/sched.h>
 
#define ATMEL 1
//#define DEVKIT 1
 
#ifdef ATMEL
#define INPUT_PIN AT91_PIN_PB19
#define OUTPUT_PIN AT91_PIN_PB20
/* led */
//#define OUTPUT_PIN AT91_PIN_PE17
#else
#ifdef DEVKIT
#define INPUT_PIN 149
#define OUTPUT_PIN 150
#else
#error Board not defined!
#endif
#endif
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marcin Bis");
MODULE_DESCRIPTION("IO latency test module - userspace connector.");
 
/* numery główny i poboczny urządzenia */
dev_t cinout_dev_num;
static struct cdev cinout_dev;
 
/* kolejka oczekiwania dla operacji odczytu */
DECLARE_WAIT_QUEUE_HEAD(cinout_queue);
 
/* struktura do przekazywania danych do BH */
struct uinout_data {
	struct work_struct work;
	int * c;
};
 
/* Licznik zdarzeń. W systemach wieloprocesorowych, musi być chroniony
   (mutex lub zmienna atomowa) */
int counter = 0;
 
/* kolejka zadań */
static struct workqueue_struct *inout_wq;
 
 
/* BH */
static void inout_job_function(struct work_struct * work)
{
	struct uinout_data * uinout_dat = 
		container_of(work, struct uinout_data, work);
 
	int * d = uinout_dat->c;
 
	(*d)++;
 
	wake_up(&cinout_queue);
 
	kfree(work);
}
 
static irqreturn_t inout_isr(int irq, void *dev_id)
{
	struct uinout_data * job;
	int ret;
 
	int * d = dev_id;
 
	job = kmalloc(sizeof(struct uinout_data), GFP_ATOMIC);
	INIT_WORK(&(job->work), inout_job_function);
	job->c = d;
 
	ret = queue_work(inout_wq, &(job->work));
 
	return IRQ_HANDLED;
}
 
static ssize_t cinout_read(struct file *file, char __user * userbuf, size_t count, loff_t * ppos)
{
	int cur = counter;
	wait_event(cinout_queue, (counter > cur));
	return 0;
}
 
static ssize_t cinout_write(struct file *file, const char __user * userbuf, size_t count, loff_t * ppos)
{
	char tmpbuf[2] = "0";
	int val = 0;
	int res;
 
	res = copy_from_user(tmpbuf, userbuf, 1);
	val = simple_strtol(tmpbuf, NULL, 10);
 
	//printk(KERN_INFO "Requested value: %d\n",val);
 
	gpio_set_value(OUTPUT_PIN,val);
	return count;
}
 
static struct file_operations cinout_fops = {
	.read = cinout_read,
	.write = cinout_write,
};
 
static int __init cinout_init(void)
{
	int irq, error;
	int ipin = INPUT_PIN;
	int opin = OUTPUT_PIN;
	char * desc = "inout latency test";
 
	/* claim and configure output pin */
	error = gpio_request(opin, desc);
	if (error < 0) {
		printk(KERN_ERR "Failed to request GPIO %d, error %d\n",opin,error);
		goto err0;
	}
 
	error = gpio_direction_output(opin,1);
	if (error < 0) {
		printk(KERN_ERR "Failed to set direction of GPIO %d, error %d\n",opin,error);
		goto err1;
	}
	gpio_set_value(opin,0);
 
	/* create workqueue to handle jobs */
	inout_wq = create_workqueue("inout_queue");
 
	/* claim and configure input pin */
	error = gpio_request(ipin, desc);
	if (error < 0) {
		printk(KERN_ERR "Failed to request GPIO %d, error %d\n",ipin,error);
		goto err1;
	}
 
	error = gpio_direction_input(ipin);
	if (error < 0) {
		printk(KERN_ERR "Failed to set direction of GPIO %d, error %d\n",ipin,error);
		goto err2;
	}
 
	irq = gpio_to_irq(ipin);
	if (error < 0) {
		error = irq;
		printk(KERN_ERR "Failed to get IRQ number for GPIO %d, error %d\n",ipin,error);
		goto err2;
	}
 
	/* urządzenie znakowe */
	error = alloc_chrdev_region(&cinout_dev_num, 0, 1, desc);
	if (error) {
		printk(KERN_ERR "Failed to allocate device number.\n");
		goto err3;
	}
 
	cdev_init(&cinout_dev, &cinout_fops);
	error = cdev_add(&cinout_dev, cinout_dev_num, 1);
	if (error) {
		printk(KERN_ERR "Failed to register device\n");
		goto err3;
	}
 
	/* request interrupt on input pin - the fun starts from now */
	error = request_irq(irq, inout_isr,
                            IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                            desc, (void *)  &counter);
 
	if (error) {
		printk(KERN_ERR "Failed to request interrupt %d, error %d\n",irq,error);
		goto err4;
	}
 
	return 0;
 
err4:
	cdev_del(&cinout_dev);
err3:
	unregister_chrdev_region(cinout_dev_num, 1);
err2:
	gpio_free(ipin);
err1:
	gpio_free(opin);
err0:
	return error;
}
 
module_init(cinout_init);
 
static void __exit cinout_exit(void)
{
	int irq;
 
	cdev_del(&cinout_dev);
	unregister_chrdev_region(cinout_dev_num, 1);
 
	irq = gpio_to_irq(INPUT_PIN);
	free_irq(irq, (void *) &counter);
	flush_workqueue(inout_wq);
	destroy_workqueue(inout_wq);
	gpio_free(INPUT_PIN);
	gpio_free(OUTPUT_PIN);
	printk(KERN_INFO "Event was trigerred %d times\n", counter);
	return;
}
module_exit(cinout_exit);

inout2_worker.c

inout2_worker.c
/*
Example 2 - userspace part.
*/
 
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
 
int main(int argc, char ** argv)
{
	int f;
	char buf[2]; //rozmiar nie ma znaczenia
	int ret;
	int val = 0;
 
	f = open("/dev/inout", O_RDWR);
	if (!f) {
		printf("Cannot open device file.\n");
		return 1;
	}
 
	while (1) {
		ret = read(f, buf, 1);
		val++;
		if (val % 2 == 0) 
			strcpy(buf, "0");
		else
			strcpy(buf,"1");
		ret = write(f, buf, 1);
	}
 
	return 0;
}

inout2_rt.c

inout2_rt.c
/*
Example 3 - another use of inout2 module.
 
This userspace program generates a square wave.
It is utilizing some POSIX RT API tricks to make it be Real-Time process.
*/
 
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sched.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
/* Priorytet procesu zostanie ustawiony na 49.
   Jądro z nałożoną łatą PRREMPT_RT ustawia 50 dla zadań
   obsługujących przerwania (więcej o PREEMPT_RT w dalszej części */
#define MY_PRIORITY (99)
 
/* Maksymalny rozmiar stosu, dostęp do którego będzie gwarantowany
   w czasie deterministycznym (bez powodowania błędu strony) */
#define MAX_SAFE_STACK (8*1024)
 
/* Ilość nanosekund w sekundzie - stała używana do testu. */
#define NSEC_PER_SEC    (1000000000)
 
/* Prealokacja stosu. */
void stack_prefault(void) {
        unsigned char dummy[MAX_SAFE_STACK];
 
        memset(dummy, 0, MAX_SAFE_STACK);
        return;
}
 
/* Funkcja, która coś robi, na przykład generuje sygnał kwadratowy 
   na jednym z PIN-ów GPIO */
int f;
int counter = 0;
 
void out() {
	counter = (counter + 1) % 2;
	if (counter == 0)
        	write(f,"0",1);
	else
		write(f,"1",1);
}
 
int main(int argc, char* argv[])
{
        struct timespec t;
        struct sched_param param;
        int interval = 30518; /* 50000ns = 50us */
 
	/* Obsługa parametrów */
	if (argc > 1) 
		interval = strtol(argv[1],NULL,10);
 
	printf("Interval set to %dns\n",interval);
 
        /* Proces ustawia się jako zadanie czasu rzeczywistego */
        param.sched_priority = MY_PRIORITY;
        if(sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
                perror("sched_setscheduler failed");
                exit(-1);
        }
 
        /* Blokowanie całej pamięci wirtualnej procesu */
        if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
                perror("mlockall failed");
                exit(-2);
        }
 
        /* Prealokacja stosu */
        stack_prefault();
 
        /* Przygotowanie sterowników dla funcji out() */
        f = open("/dev/inout",O_WRONLY);
 
        /* Pobierz aktualny czas */
        clock_gettime(CLOCK_MONOTONIC ,&t);
 
        /* ustaw alarm za sekundę */
        t.tv_sec++;
 
        while(1) {
                /* czekaj do wystąpienia alarmu */
                clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
 
                /* wykonaj operację */
                out();
 
                /* ustaw alarm (za 50us) */
                t.tv_nsec += interval;
 
                /* Struktura timescpec zawiera licznik nanosekund i sekund
                   jeżeli licznik naosekund się przepełnia należy odjąć od
                   niego ilość odpowiadającą jednej sekundzie i dodać jeden
                   do licznuka sekund */
                while (t.tv_nsec >= NSEC_PER_SEC) {
                       t.tv_nsec -= NSEC_PER_SEC;
                       t.tv_sec++;
                }
   }
}
ostatnio zmienione: 2012/03/16 10:34