简体   繁体   中英

How is it working with threads, pipes and interrupts in C with linux

How is it working with threads, pipes and interrupts with linux(pi)? I created the worker rfm69_InterruptWorker which is waiting in read-func. I set a high prio to this thread.

What do i want/expected? I expect that when i write something to the pipe it will immediately call the thread to read from the pipe. But unfortunately it will return when the main-thread goes to sleep.

#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <poll.h>

int pipe_test[2];

static struct pollfd gl_pfd;

pthread_t WorkerThread;       // Thread to compute log messages
struct data* WorkerThreadRet; // Storage for thread return value


static void* rfm69_InterruptWorker(void* dummy)
{
  uint8_t status;
  uint8_t retval;
  uint32_t timeout = 0;
  struct sched_param params; // struct sched_param is used to store the scheduling priority
  int ret;

  if (pipe(pipe_test) < 0) {
    printf("pipe error\r\n");
  }

  printf("created pipe \r\n");

  gl_pfd.fd = pipe_test[0];
  gl_pfd.events = POLLIN;

  printf("setting prio\r\n");

  // We'll set the priority to the minimum
  params.sched_priority = sched_get_priority_max(SCHED_FIFO);
  // Attempt to set thread priority to the SCHED_OTHER policy
  ret = pthread_setschedparam(pthread_self(), SCHED_FIFO, &params);

  printf("before  poll/read\r\n");
  //retval = poll(&gl_pfd, 1, -1);
  //retval = poll(gl_pdf,2, -1); //When with polling gpio-pin and pipe to break
  retval = read(pipe_test[0], &status, sizeof(status));

  printf("after poll\r\n");

}


void main() {

  uint8_t status;

  pthread_create(&WorkerThread, NULL, rfm69_InterruptWorker, WorkerThreadRet);

  usleep(1000);


  printf("write to pipe\r\n");
  write(pipe_test[1], &status, sizeof(status));

  printf("Go to sleep\r\n");
  usleep(1);
  printf("back to sleep\r\n");

}

output:

created pipe 
setting prio
before  poll/read
write to pipe
Go to sleep
after poll
back to sleep

Usually you would have synchronize your threads using condition variables or semaphores. But in this case a pipe can also help to synchronize the threads, as long the communication direction remains the same, otherwise you would need 2 pipes.

First, I'd create the pipe before the worker thread is created. This way, the main thread does not need to wait for the worker to create the pipe, it can start writing to it immediately. We can use this behaviour to synchronize the threads because a read will block until the other end writes something in the pipe. So the worker thread essentially waits until there is data in the pipe.

I've modifided your example a litte bit to demonstrate this. Note that I also use sleep in the main thread, but I don't use it to syncronize the threads but to show that the worker thread indeed waits until the main thread writes something:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>

typedef struct th_data {
    int fd[2];
    // add other arguments as needed
} th_data;

void *myworker(void *args)
{
    int payload;
    th_data *data = args;
    time_t now;

    time_t start, stop;

    start = time(NULL);

    ssize_t ret = read(data->fd[0], &payload, sizeof payload);
    now = time(NULL);

    if(ret == -1)
    {
        perror("read, terminating worker");
        pthread_exit(0);
    }

    stop = time(NULL);

    printf("* worker [%zu]: waited %zu seconds, payload: %d\n", now, stop-start, payload);

    //////////////////

    start = time(NULL);

    ret = read(data->fd[0], &payload, sizeof payload);
    now = time(NULL);

    if(ret == -1)
    {
        perror("read, terminating worker");
        pthread_exit(0);
    }

    stop = time(NULL);

    printf("* worker [%zu]: waited %zu seconds, payload: %d\n", now, stop-start, payload);



    //////////////////

    start = time(NULL);

    ret = read(data->fd[0], &payload, sizeof payload);
    now = time(NULL);

    if(ret == -1)
    {
        perror("read, terminating worker");
        pthread_exit(0);
    }

    stop = time(NULL);

    printf("* worker [%zu]: waited %zu seconds, payload: %d\n", now, stop-start, payload);


    pthread_exit(0);
}

int main(void)
{

    pthread_t th;
    th_data data;
    time_t now;

    if(pipe(data.fd) < 0)
    {
        perror("pipe");
        return 1;
    }

    pthread_create(&th, NULL, myworker, &data);

    int payload = 88;

    printf("+ Main thread: sleep 1 second\n");
    sleep(1);

    now = time(NULL);
    printf("+ Main thread [%zu], writing...\n", now);
    write(data.fd[1], &payload, sizeof payload);

    printf("+ Main thread: sleep 2 seconds\n");
    sleep(2);

    payload = -12;
    now = time(NULL);
    printf("+ Main thread [%zu], writing...\n", now);
    write(data.fd[1], &payload, sizeof payload);

    printf("+ Main thread: sleep 3 seconds\n");
    sleep(3);

    payload = 1024;
    now = time(NULL);
    printf("+ Main thread [%zu], writing...\n", now);
    write(data.fd[1], &payload, sizeof payload);

    pthread_join(th, NULL);

    return 0;
}

The output of this program is

+ Main thread: sleep 1 second
+ Main thread [1524698241], writing...
+ Main thread: sleep 2 seconds
* worker [1524698241]: waited 1 seconds, payload: 88
+ Main thread [1524698243], writing...
+ Main thread: sleep 3 seconds
* worker [1524698243]: waited 2 seconds, payload: -12
+ Main thread [1524698246], writing...
* worker [1524698246]: waited 3 seconds, payload: 1024

and as you can see from the numbers in the square brackets, the worker thread resumes working immediately after the main thread writes something in the pipe. The fact that the lines are a little bit out of sync is because printf is buffered. But the number in the square brackets tell you exactly when which part of the threads was executed.

I don't like this approach too much, to be honest, if both the main thread and the worker have to read and write to each other, then one pipe is not enough. In that case you need another pipe for the other direction or you need a better synchronization strategy. I'd use conditional variables, because they also work very well with multiple threads. A similar example with condition variables

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

int payload; // shared resource

void *worker(void *args)
{
    int i = 0;

    int payloads[] = { 1, 3, -9};

    size_t len = sizeof payloads / sizeof payloads[0];

    int oldpayload;

    while(1)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cv, &mutex);
        printf("* worker[%d]: main thread signal cond variable, payload is %d\n", i, payload);

        oldpayload = payload;

        payload = payloads[i % len];
        printf("* worker[%d]: setting payload to %d\n", i, payload);
        pthread_mutex_unlock(&mutex);


        // now waking up master
        pthread_cond_signal(&cv);

        if(oldpayload == 99)
            break;

        i++;
    }

    printf("* worker:    My work is done, bye\n");

    pthread_exit(0);
}

int main(void)
{
    pthread_t th;
    pthread_create(&th, NULL, worker, NULL);

    int payloads[] = { 19, -12, 110, 1024, 99 };

    for(size_t i = 0; i < sizeof payloads / sizeof payloads[0]; ++i)
    {
        printf("+ main[%zu]:   doing some work, setting payload to %d\n", i, payloads[i]);
        fflush(stdout);
        usleep(10000); // simulation work

        payload = payloads[i];
        pthread_cond_signal(&cv);

        // time for master to wait
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cv, &mutex);
        printf("+ main[%zu]:   worker set payload to %d\n\n", i, payload);
        fflush(stdout);
        pthread_mutex_unlock(&mutex);
    }

    pthread_join(th, NULL);

    return 0;
}

And the output for this is

+ main[0]:   doing some work, setting payload to 19
* worker[0]: main thread signal cond variable, payload is 19
* worker[0]: setting payload to 1
+ main[0]:   worker set payload to 1

+ main[1]:   doing some work, setting payload to -12
* worker[1]: main thread signal cond variable, payload is -12
* worker[1]: setting payload to 3
+ main[1]:   worker set payload to 3

+ main[2]:   doing some work, setting payload to 110
* worker[2]: main thread signal cond variable, payload is 110
* worker[2]: setting payload to -9
+ main[2]:   worker set payload to -9

+ main[3]:   doing some work, setting payload to 1024
* worker[3]: main thread signal cond variable, payload is 1024
* worker[3]: setting payload to 1
+ main[3]:   worker set payload to 1

+ main[4]:   doing some work, setting payload to 99
* worker[4]: main thread signal cond variable, payload is 99
* worker[4]: setting payload to 3
* worker:    My work is done, bye
+ main[4]:   worker set payload to 3

One thing to notice here: the variable payload is our shared resource that is read and written by both the main and the worker thread. Usually you would need a mutex to protect the reading and writing of the shared resource. Note that the main thread does not lock the mutex when writing to payload . The reason why I can omit that here is because of the condition variable: I synchronized the threads so that only one thread at a time can write payload , so I know that when

payload = payloads[i];

is executed on the main thread, the worker thread is locked and waiting for the signal of the condition variable, so it is not writing payload at the same time as the main thread. Of course I could use a second mutex just for that, but I don't think it is necessary.

Final remarks:

The correct way of defining the main is one of these three:

  • int main(void);
  • int main(int argc, char **argv);
  • int main(int argc, char *argv[]);

Also bear in mind that the variable WorkerThreadRet is pointing to NULL . You would need to allocate memory for it before passing it to the thread.

In you code before doing

write(pipe_test[1], &status, sizeof(status));

you might want to initialize the status variable before the write operation, otherwise you are sending some uninitialized value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM