Real-time application development
Real-time application development with PREEMPT_RT
- No special library is needed, the POSIX real-time API is part of the standard C library
- The glibc C library is recommended, as support for some real-time features is not mature in other C libraries
- Priority inheritance mutexes or NPTL on some architectures, for example
- Compile a program
- ARCH-linux-gcc -o myprog myprog.c -lrt
- To get the documentation of the POSIX API
- Install the manpages-posix-dev package
- Run man function-name
Process, thread?
- Confusion about the terms process, thread and task
- In Unix, a process is created using fork() and is composed of
- An address space, which contains the program code, data, stack, shared libraries, etc.
- One thread, that starts executing the main() function.
- Upon creation, a process contains one thread
- Additional threads can be created inside an existing process, using pthread_create()
- They run in the same address space as the initial thread of the process
- They start executing a function passed as argument to pthread_create()
Process, thread: kernel point of view
- The kernel represents each thread running in the system by a structure of type task_struct
- From a scheduling point of view, it makes no difference between the initial thread of a process and all additional threads created dynamically using pthread_create()
Creating threads
- Linux support the POSIX thread API
To create a new thread pthread_create(pthread_t thread, pthread_attr_t attr, void (routine)(void), void arg);
The new thread will run in the same address space, but will be scheduled independently
- Exiting from a thread pthread_exit(void . *value_ptr);
- Waiting for the termination of a thread pthread_join(pthread_t . thread, void *value_ptr);
Scheduling classes
- The Linux kernel scheduler support different scheduling classes
- The default class, in which processes are started by default is a time-sharing class
- All processes, regardless of their priority, get some CPU time
- The proportion of CPU time they get is dynamic and affected by the nice value, which ranges from -20 (highest) to 19 (lowest). Can be set using the nice or renice commands
The real-time classes SCHED_FIFO and SCHED_RR
- The highest priority process gets all the CPU time, until it blocks.
- In SCHED_RR, round-robin scheduling between the processes of the same priority. All must block before lower priority processes get CPU time.
- Priorities ranging from 0 (lowest) to 99 (highest)
An existing program can be started in a specific scheduling class with a specific priority using the chrt command line tool
- Example: chrt -f 99 ./myprog -f: SCHED_FIFO -r: SCHED_RR
- The sched_setscheduler() API can be used to change the scheduling class and priority of a process
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
- policy can be SCHED_OTHER, SCHED_FIFO, SCHED_RR, etc.
- param is a structure containing the priority
The priority can be set on a per-thread basis when a thread is created
struct sched_param parm; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(&attr, SCHED_FIFO); parm.sched_priority = 42; pthread_attr_setschedparam(&attr,&parm);
Then the thread can be created using pthread_create(), passing the attr structure.
- Several other attributes can be defined this way: stack size, etc.
Memory locking
- In order to solve the non-determinism introduced by virtualmemory, memory can be locked
- Guarantee that the system will keep it allocated
- Guarantee that the system has pre-loaded everything into memory
- mlockall(MCL_CURRENT | MCL_FUTURE);
- Locks all the memory of the current address space, for currently mapped pages and pages mapped in the future
- Other, less useful parts of the API: munlockall, mlock,munlock.
- Watch out for non-currently mapped pages
- Stack pages
- Dynamically-allocated memory
Mutexes
- Allows mutual exclusion between two threads in the same address space
Initialization/destruction
pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); pthread_mutex_destroy(pthread_mutex_t *mutex);
Lock/unlock
pthread_mutex_lock(pthread_mutex_t *mutex); pthread_mutex_unlock(pthread_mutex_t *mutex);
Priority inheritance must be activated explicitly
pthread_mutexattr_t attr; pthread_mutexattr_init (&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
Timers
- Timer creation
timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);
- clockid is usually CLOCK_MONOTONIC. sigevent defines what happens upon timer expiration: send a signal or start a function in a new thread. timerid is the returned timer identifier.
- Configure the timer for expiration at a given time
timer_settime(timer_t timerid, int flags,struct itimerspec *newvalue, struct itimerspec *oldvalue);
- Delete a timer
timer_delete(timer_t timerid)
- Get the resolution of a clock, clock_getres
- Other functions: timer_getoverrun(), timer_gettime()
Signals
- Signals are asynchronous notification mechanisms
- Notification occurs either
- By the call of a signal handler. Be careful with the limitations of signal handlers!
- By being unblocked from the sigwait(), sigtimedwait() or sigwaitinfo() functions. Usually better.
- Signal behaviour can be configured using sigaction()
- The mask of blocked signals can be changed with pthread_sigmask()
- Delivery of a signal using pthread_kill() or tgkill()
- All signals between SIGRTMIN and SIGRTMAX, 32 signals under Linux.
Inter-process communication
- Semaphores
- Usable between different processes using named semaphores
- sem_open(), sem_close(), sem_unlink(), sem_init(), sem_destroy(), sem_wait(), sem_post(), etc.
- Message queues
- Allows processes to exchange data in the form of messages.
- mq_open(), mq_close(), mq_unlink(), mq_send(), mq_receive(), etc.
- Shared memory
- Allows processes to communicate by sharing a segment of memory
- shm_open(), ftruncate(), mmap(), munmap(), close(), shm_unlink()