Quickstart: Adaptive Partitioning Thread Scheduler

This example gives you a quick hands-on introduction to the thread scheduler. First you need to determine whether your target system includes the adaptive partitioning module. Next, we'll take a look at the programs you'll use, and then you can try adaptive partitioning for yourself.

Is adaptive partitioning running on your system?

To determine whether adaptive partitioning is running on your target system, type:

aps show

If you see an error message, you need to add adaptive partitioning to your OS image. On your development host, do the following:

  1. Go to the directory that contains the buildfile for your system's boot image.
  2. Create a copy of the buildfile. For example:
    cp my_buildfile.build apsdma.build
    
  3. Edit the copy and search for the line that starts procnto. The line might look like this:
    PATH=/proc/boot:/bin:/usr/bin:/opt/bin \
    LD_LIBRARY_PATH=/proc/boot:/lib:/usr/lib:/lib/dll:/opt/lib \
    procnto-smp-instr
    
    Note: In a real buildfile, you can't use a backslash (\) to divide a long line into shorter segments; we've done that here to make the command easier to read.
  4. Add [module=aps] to the beginning of the line:
    [module=aps] PATH=/proc/boot:/bin:/usr/bin:/opt/bin \
    LD_LIBRARY_PATH=/proc/boot:/lib:/usr/lib:/lib/dll:/opt/lib \
    procnto-smp-instr
    

    You can add commands to your buildfile to create partitions and start programs in them, but when you're experimenting with scheduler partitions, it's better to do it at runtime, so that you can easily make changes. For more information, see the Setting Up and Using the Adaptive Partitioning Thread Scheduler chapter.

  5. Save your changes to the buildfile.
  6. Generate a new boot image:
    mkifs apsdma.build apsdma.ifs
    
  7. Transfer the new image to your target system.
  8. Reboot your target system.

The code

You'll need some programs to run for this tutorial. If your system has a GUI, you can use a graphical program (e.g., animated gears) that runs continuously; otherwise, you'll need a program such as this, which we'll call looper.c:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <time.h>

int main( int argc, 
          const char *argv[] )
{
   struct timespec t;
   uint64_t nsec = 0, old_nsec = 0;
   unsigned int delay_msec = 0;

   delay_msec = atoi (argv[2]);
   clock_gettime (CLOCK_REALTIME, &t);
   old_nsec = timespec2nsec(&t);

   while (1)
   {
      delay (delay_msec);
      clock_gettime (CLOCK_REALTIME, &t);
      nsec = timespec2nsec(&t);
      printf ("%s %ld\n", argv[1], (long unsigned) (nsec - old_nsec));
      old_nsec = nsec;
   }
   return EXIT_SUCCESS;
}

You'll run multiple instances of looper in this tutorial, so argv[1] is a string that you can use to determine which instance you're looking at. The next argument (argv[2]) is the number of milliseconds to wait between iterations; you might need to experiment with this number so that the program uses a significant amount of CPU time, without using it all.

You'll also need a program (which we'll call greedy.c) that simply loops forever, so as to consume as much CPU time as possible:

#include <stdlib.h>

int main( void )
{
   while (1)
   {}

   return EXIT_SUCCESS;
}

Compile and link the programs, as appropriate for your target. For example:

qcc -V gcc_ntox86_64 -o greedy greedy.c 
qcc -V gcc_ntox86_64 -o looper looper.c

Transfer the binaries from your development host to your target machine.

Let's try it!

For this tutorial, you can log in as any user. You don't have to be root to manipulate the partitions because the security options aren't initially set. If you use the thread scheduler in a “real” system, you should choose the appropriate level of security. For more information, see the Security for Scheduler Partitions chapter in this guide, and the entry for SchedCtl() in the QNX Neutrino C Library Reference.

You might need to use multiple virtual consoles, in order to run several foreground programs simultaneously:

To go to: Press:
The next active console CtrlAltEnter or CtrlAlt+ (plus)
The previous active console CtrlAlt- (minus)
A specific console CtrlAltn, where n is the console number
Note: Use the + (plus) and - (minus) keys on the numeric keypad.

Let's create some adaptive partitions and run some programs in them:

  1. The thread scheduler automatically creates one partition, called System. Create some additional partitions:
    • one called partitionA, with a budget of 10% of the CPU time
    • another called partitionB, with a budget of 30%

    by using the aps utility:

    aps create -b10 partitionA
    aps create -b30 partitionB
    

    The new partitions' budgets are subtracted from their parent partition's budget (the System partition in this case).

  2. Use the aps utility to list the partitions on your system:
    $ aps show -l
                        +-------- CPU Time ------+-- Critical Time --
    Partition name   id | Budget |  Max |   Used | Budget |      Used
    --------------------+------------------------+-------------------
    System            0 |    60% | 100% |  0.21% |  100ms |   0.000ms
    partitionA        1 |    10% | 100% |  0.00% |    0ms |   0.000ms
    partitionB        2 |    30% | 100% |  0.00% |    0ms |   0.000ms
    --------------------+------------------------+-------------------
    Total               |   100% |      |  0.21% |
    
    Note: The -l option makes this command loop until you terminate it (e.g., by pressing CtrlC). For this example, leave it running, and switch to another console.
  3. Use the on command to start a process in partitionA:
    on -Xaps=partitionA ./looper A 5
    
  4. In another console, start a second instance of looper in PartitionB:
    on -Xaps=partitionB ./looper B 5
    

    If you switch between consoles, you'll see that the programs are running at approximately the same speed.

  5. To determine which partitions your processes are running in, use the pidin sched command. For scheduler partitions, the ExtSched column displays the partition name. For example:
         pid tid name               prio cpu ExtSched             STATE      
           1   1 /procnto-smp-instr   0f   0 System               READY      
           1   2 /procnto-smp-instr 255r   0 System               RECEIVE    
           1   3 /procnto-smp-instr 255r   0 System               RECEIVE    
           1   4 /procnto-smp-instr  10r   0 System               RECEIVE    
                    .
                    .
                    .
      110613   1 ./looper            10r   0 partitionA           NANOSLEEP  
      118807   1 ./looper            10r   0 partitionB           NANOSLEEP  
      127000   1 proc/boot/pidin     10r   0 System               REPLY      
    
  6. Run the greedy program in partitionB, where it will consume as much CPU as possible:
    on -Xaps=partitionB ./greedy &
    
  7. You might be surprised to find that both instances of looper continue to run at roughly the same speed. The output from the aps utility might explain why:
                        +-------- CPU Time ------+-- Critical Time --
    Partition name   id | Budget |  Max |   Used | Budget |      Used
    --------------------+------------------------+-------------------
    System            0 |    60% | 100% |  0.29% |  100ms |   0.000ms
    partitionA        1 |    10% | 100% |  2.26% |    0ms |   0.000ms
    partitionB        2 |    30% | 100% | 97.46% |    0ms |   0.000ms
    --------------------+------------------------+-------------------
    Total               |   100% |      |100.00% |
    

    Note that partitionB is using more than its budget of 30%. This occurs because the other partitions aren't using their budgets. Instead of running an idle thread in the other partitions, the thread scheduler gives unused time to the partitions that need it.

  8. Start another instance of the greedy application in partitionA:
    on -Xaps=partitionA ./greedy &
    

    The instance of looper in partitionA is now slower than the one in partitionB. The output of aps looks something like this:

                        +-------- CPU Time ------+-- Critical Time --
    Partition name   id | Budget |  Max |   Used | Budget |      Used
    --------------------+------------------------+-------------------
    System            0 |    60% | 100% |  0.17% |  100ms |   0.000ms
    partitionA        1 |    10% | 100% | 26.89% |    0ms |   0.000ms
    partitionB        2 |    30% | 100% | 72.94% |    0ms |   0.000ms
    --------------------+------------------------+-------------------
    Total               |   100% |      |100.00% |
    

    The System partition's unused time is divided between the other two partitions, according to their shares of the CPU time.

  9. Start another instance of the greedy application in the System partition:
    on -Xaps=System ./greedy &
    

    There's now no free time left in the system, so each partition gets only its minimum guaranteed CPU time. The instance of looper in partitionA should be noticeably slower than the one in partitionB. The output of the aps utility looks something like this:

                        +-------- CPU Time ------+-- Critical Time --
    Partition name   id | Budget |  Max |   Used | Budget |      Used
    --------------------+------------------------+-------------------
    System            0 |    60% | 100% | 55.67% |  100ms |   0.000ms
    partitionA        1 |    10% | 100% | 11.84% |    0ms |   0.000ms
    partitionB        2 |    30% | 100% | 32.49% |    0ms |   0.000ms
    --------------------+------------------------+-------------------
    Total               |   100% |      |100.00% |
    
  10. If you slay the instances of greedy, the instances of looper speed up, and the consumption of CPU time drops.

If you wish, you can continue to experiment, creating other partitions, modifying their budgets, and so on. Because you created the partitions at runtime instead of in your OS image, the new partitions will disappear when you restart the system.