I'm writing a simple program that reads urls from a text file, and checks the validity of them using multi threaded programming. I used mutexes and condition variables to synchronize my threads but my app still crashes and after some debugging sessions I decided to get some help :)
The input of the file is a text file containing urls like:
http://www.youtube.com/
http://www.facebook.com/
And the output should be the aggregated result of the curl request on each url (whether it returns OK, UNKOWN or ERROR)
This is my code:
/*
* ex3.c
*/
#define _GNU_SOURCE
#include <curl/curl.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define REQUEST_TIMEOUT_SECONDS 2L
#define URL_OK 0
#define URL_ERROR 1
#define URL_UNKNOWN 2
#define QUEUE_SIZE 32
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
typedef struct {
int ok, error, unknown;
} UrlStatus;
typedef struct {
void **array;
int size;
int capacity;
int head;
int tail;
pthread_mutex_t mutex;
pthread_cond_t cv_empty; /* get notified when the queue is not full */
pthread_cond_t cv_full; /* get notified when the queue is not empty */
} Queue;
void queue_init(Queue *queue, int capacity) {
/*
* Initializes the queue with the specified capacity.
* This function should allocate the internal array, initialize its properties
* and also initialize its mutex and condition variables.
*/
queue->array = (void**)malloc(sizeof(void*) * capacity);
if (queue->array == NULL) {
perror("unable to allocate memory");
exit(EXIT_FAILURE);
}
queue->capacity = capacity;
queue->size = 0;
queue->head = 0;
queue->tail = 0;
pthread_mutex_init(&(queue->mutex), NULL);
pthread_cond_init(&(queue->cv_empty), NULL);
pthread_cond_init(&(queue->cv_full), NULL);
}
void enqueue(Queue *queue, void *data) {
/*
* Enqueue an object to the queue.
*
* TODO:
* 1. This function should be synchronized on the queue's mutex
* 2. If the queue is full, it should wait until it is not full
* (i.e. cv_empty)
* 3. Add an element to the tail of the queue, and update the tail & size
* parameters
* 4. Signal that the queue is not empty (i.e. cv_full)
*/
pthread_mutex_lock(&(queue->mutex));
while (queue->size >= QUEUE_SIZE) {
pthread_cond_wait(&(queue->cv_empty), &(queue->mutex));
}
if(queue->size == 0) {
queue->head = 0;
}
queue->array[queue->tail] = data;
queue->size++;
queue->tail++;
pthread_cond_signal(&(queue->cv_full));
pthread_mutex_unlock(&(queue->mutex));
}
void *dequeue(Queue *queue) {
/*
* Dequeue an object from the queue.
*
* TODO:
* 1. This function should be synchronized on the queue's mutex
* 2. If the queue is empty, it should wait until it is not empty (i.e. cv_full)
* 3. Read the head element, and update the head & size parameters
* 4. Signal that the queue is not full (i.e. cv_empty)
* 5. Return the dequeued item
*/
void *data;
pthread_mutex_lock(&(queue->mutex));
while (queue->size <= 0) {
pthread_cond_wait(&(queue->cv_full), &(queue->mutex));
}
queue->head++;
data = queue->array[queue->head];
queue->size--;
pthread_cond_signal(&(queue->cv_empty));
pthread_mutex_unlock(&(queue->mutex));
return data;
}
void queue_destroy(Queue *queue) {
/*
* Free the queue memory and destroy the mutex and the condition variables.
*/
int ret;
free(queue->array);
ret = pthread_mutex_destroy(&(queue->mutex));
if (ret != 0) {
handle_error_en(ret, "unable to destroy mutex");
}
ret = pthread_cond_destroy(&(queue->cv_empty));
if (ret != 0) {
handle_error_en(ret, "unable to destroy cv_empty condition variable");
}
ret = pthread_cond_destroy(&(queue->cv_full));
if (ret != 0) {
handle_error_en(ret, "unable to destroy cv_full condition variable");
}
}
void usage() {
fprintf(stderr, "usage:\n\t./ex3 FILENAME NUMBER_OF_THREADS\n");
exit(EXIT_FAILURE);
}
int count = 0;
int check_url(const char *url) {
CURL *curl;
CURLcode res;
long response_code = 0L;
int http_status = URL_UNKNOWN;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, REQUEST_TIMEOUT_SECONDS);
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); /* do a HEAD request */
res = curl_easy_perform(curl);
if(res == CURLE_OK) {
res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
if (res == CURLE_OK &&
response_code >= 200 &&
response_code < 400) {
http_status = URL_OK;
} else {
http_status = URL_ERROR;
}
}
curl_easy_cleanup(curl);
}
return http_status;
}
typedef struct {
Queue *url_queue;
Queue *result_queue;
} WorkerArguments;
void *worker(void *args) {
/*
* TODO:
* 1. Initialize a UrlStatus (allocate memory using malloc, and zero it
* using memset)
* 2. Dequeue URL from url_queue, run check_url on it, and update results
* (don't forget to free() the url)
* 3. After dequeuing a NULL value:
* Enqueue results to result_queue and return
*/
WorkerArguments *worker_args = (WorkerArguments *)args;
UrlStatus *results = NULL;
char *url;
results = malloc(sizeof(UrlStatus));
if(results == NULL) {
perror("Could not allocate memory");
exit(-1);
}
memset(results, 0, sizeof(UrlStatus));
while((url = (char *)dequeue(worker_args->url_queue)) != NULL) {
switch(check_url(url)) {
case URL_OK:
results->ok++;
break;
case URL_UNKNOWN:
results->unknown++;
break;
case URL_ERROR:
results->error++;
break;
}
/*free(url);*/
}
enqueue(worker_args->result_queue, results);
return NULL;
}
typedef struct {
const char *filename;
Queue *url_queue;
} FileReaderArguments;
void *file_reader(void *args) {
/*
* TODO:
* 1. Open filename (use fopen, check for errors)
* 2. Use getline() to read lines (i.e. URLs) from the file (use errno to check for errors)
* 3. Copy each url to the heap (use malloc and strncpy)
* 4. Enqueue the URL to url_queue
* 5. Don't forget to free the line variable, and close the file (and check for errors!)
*/
FileReaderArguments *file_reader_args = (FileReaderArguments *)args;
FILE *toplist_file;
char *line = NULL;
char *url = NULL;
size_t len = 0;
ssize_t read = 0;
toplist_file = fopen(file_reader_args->filename, "r");
if (toplist_file == NULL) {
exit(EXIT_FAILURE);
}
while ((read = getline(&line, &len, toplist_file)) != -1) {
if (read == -1) {
perror("error reading file");
}
if(read == 1) continue; /*empty line*/
url = malloc(read);
if(url == NULL) {
perror("Could not allocate memory");
}
line[read-1] = '\0'; /* null-terminate the URL */
strncpy(url, line, read);
enqueue(file_reader_args->url_queue, url);
}
fclose(toplist_file);
return NULL;
}
typedef struct {
int number_of_threads;
Queue *url_queue;
Queue *result_queue;
} CollectorArguments;
void *collector(void *args) {
/*
* TODO:
* 1. Enqueue number_of_threads NULLs to the url_queue
* 2. Dequeue and aggregate number_of_threads thread_results
* from result_queue into results (don't forget to free() thread_results)
* 3. Print aggregated results to the screen
*/
CollectorArguments *collector_args = (CollectorArguments *)args;
UrlStatus results = {0};
UrlStatus *thread_results;
int i;
for(i= 0; i < collector_args->number_of_threads; i++) {
}
for(i= 0; i < collector_args->number_of_threads; i++) {
thread_results = dequeue(collector_args->result_queue);
results.ok += thread_results->ok;
results.error += thread_results->error;
results.unknown += thread_results->unknown;
/* free(thread_results);*/
}
printf("%d OK, %d Error, %d Unknown\n",
results.ok,
results.error,
results.unknown);
return NULL;
}
void parallel_checker(const char *filename, int number_of_threads) {
/*
* TODO:
* 1. Initialize a Queue for URLs, a Queue for results (use QUEUE_SIZE)
* 2. Start number_of_threads threads running worker()
* 3. Start a thread running file_reader(), and join it
* 4. Start a thread running collector(), and join it
* 5. Join all worker threads
* 6. Destroy both queues
*/
Queue url_queue, result_queue;
WorkerArguments worker_arguments = {0};
FileReaderArguments file_reader_arguments = {0};
CollectorArguments collector_arguments = {0};
pthread_t *worker_threads;
pthread_t file_reader_thread, collector_thread;
int i;
int err;
queue_init(&url_queue, QUEUE_SIZE);
queue_init(&result_queue, QUEUE_SIZE);
worker_arguments.url_queue = &url_queue;
worker_arguments.result_queue = &result_queue;
file_reader_arguments.filename = filename;
file_reader_arguments.url_queue = &url_queue;
collector_arguments.url_queue = &url_queue;
collector_arguments.result_queue = &result_queue;
collector_arguments.number_of_threads = number_of_threads;
worker_threads = (pthread_t *) malloc(sizeof(pthread_t) * number_of_threads);
if (worker_threads == NULL) {
perror("unable to allocate memory");
return;
}
curl_global_init(CURL_GLOBAL_ALL); /* init libcurl before starting threads */
for(i=0; i<number_of_threads; i++) {
err = pthread_create(&(worker_threads[i]), NULL, &worker, &worker_arguments);
if (err != 0) {
fprintf(stderr, "can't create thread :[%s]\n", strerror(err));
}
}
err = pthread_create(&file_reader_thread, NULL, &file_reader, &file_reader_arguments);
if (err != 0) {
fprintf(stderr, "can't create thread :[%s]\n", strerror(err));
}
err = pthread_join(file_reader_thread, NULL);
if (err != 0) {
fprintf(stderr, "can't join thread :[%s]\n", strerror(err));
}
err = pthread_create(&collector_thread, NULL, &collector, &collector_arguments);
if (err != 0) {
fprintf(stderr, "can't create thread :[%s]\n", strerror(err));
}
err = pthread_join(collector_thread, NULL);
if (err != 0) {
fprintf(stderr, "can't join thread :[%s]\n", strerror(err));
}
for(i=0; i<number_of_threads; i++) {
err = pthread_join(worker_threads[i], NULL);
if (err != 0) {
fprintf(stderr, "can't join thread :[%s]\n", strerror(err));
}
}
queue_destroy(&url_queue);
queue_destroy(&result_queue);
free(worker_threads);
}
int main(int argc, char **argv) {
if (argc != 3) {
usage();
} else {
parallel_checker(argv[1], atoi(argv[2]));
}
return EXIT_SUCCESS;
}
I think that I'm missing something with the synchronization mechanisms, can anyone spot where I was wrong?
Thanks a lot!!!
One multi-threading logic flaw is definitely:
There is no distinction between "worker input queue temporarily empty" and "file reader thread finished" - Your workers simply exit when there is nothing to read (even temporarily) on their input queue. So, if the file_reader thread produces queue entries slower than the workers consume them for any reason, the consumers will starve and die, leaving the producer with no consumer and, finally, a hang.
Related
In this code, I am trying to monitor two paths at the same time. I used while(1) for this purpose. But the problem that I am facing is that whenever I run the code, it gives me the same result two times like this.
Giving result
Pathname1 "file" is modified
Pathname1 "file" is modified
Expected result
Pathname1 "file" is modified
I debugged the code. After breaking the main function and stepping over it, the next command stops at this line length = read(fd, buffer, EVENT_BUF_LEN ). Whenever I break a line after this length variable command, the program starts and after modifying the file, the program stops at this line struct inotify_event *event = ( struct inotify_event *)&buffer[i]; Although the program should not break.
I also used IN_CLOSE_WRITE instead of IN_MODIFY but no change in the result.
typedef struct{
int length, fd, wd1, wd2;
char buffer[4096] __attribute__ ((aligned(__alignof__(struct inotify_event))));
} notification;
notification inotify;
int getNotified(char *pathname1, char *pathname2){
inotify.fd = inotify_init();
inotify.wd1 = inotify_add_watch(inotify.fd, pathname1, IN_MODIFY);
inotify.wd2 = inotify_add_watch(inotify.fd, pathname2, IN_MODIFY);
while(1){
inotify.length = read(inotify.fd, inotify.buffer, EVENT_BUF_LEN);
int i = 0;
while(i < inotify.length){
struct inotify_event *event = (struct inotify_event *)&inotify.buffer[i];
if(event->len){
if(event->mask & IN_MODIFY){
if(event->wd == inotify.wd1){
printf("Pathname1 '%s' is modified\n", event->name);
break;
}
if(event->wd == inotify.wd2){
printf("Pathname2 '%s' is modified\n", event->name);
break;
}
}
}
i += EVENT_SIZE + event->len;
}
}
inotify_rm_watch(inotify.fd, inotify.wd1);
inotify_rm_watch(inotify.fd, inotify.wd2);
close(inotify.fd);
exit(0);
}
Some remarks:
You should not "break" as you go out of the inside "while" loop and call read() again
The IN_MODIFY event does not fill the event->name field (i.e. event->len = 0)
The number of IN_MODIFY events depends on how you modify the files ("echo xxx >> file" = write only = 1 IN_MODIFY; "echo xxx > file" = truncate + write = 2 IN_MODIFY)
Here is a proposition for your program (with additional prints):
#include <stdio.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <stdlib.h>
#define EVENT_BUF_LEN 4096
#define EVENT_SIZE sizeof(struct inotify_event)
typedef struct{
int length, fd, wd1, wd2;
char buffer[EVENT_BUF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));
} notification;
notification inotify;
int getNotified(char *pathname1, char *pathname2){
inotify.fd = inotify_init();
inotify.wd1 = inotify_add_watch(inotify.fd, pathname1, IN_MODIFY);
printf("wd1 = %d\n", inotify.wd1);
inotify.wd2 = inotify_add_watch(inotify.fd, pathname2, IN_MODIFY);
printf("wd2 = %d\n", inotify.wd2);
while(1){
inotify.length = read(inotify.fd, inotify.buffer, EVENT_BUF_LEN);
int i = 0;
printf("read() = %d\n", inotify.length);
while(i < inotify.length){
struct inotify_event *event = (struct inotify_event *)&inotify.buffer[i];
printf("event->len = %u\n", event->len);
if(event->mask & IN_MODIFY){
if(event->wd == inotify.wd1){
printf("Pathname1 is modified\n");
} else if (event->wd == inotify.wd2){
printf("Pathname2 is modified\n");
}
}
i += (EVENT_SIZE + event->len);
printf("i=%d\n", i);
}
}
inotify_rm_watch(inotify.fd, inotify.wd1);
inotify_rm_watch(inotify.fd, inotify.wd2);
close(inotify.fd);
exit(0);
}
int main(void)
{
getNotified("/tmp/foo", "/tmp/bar");
return 0;
} // main
Here is an example of execution:
$ gcc notif.c -o notif
$ > /tmp/foo
$ > /tmp/bar
$ ./notif
wd1 = 1
wd2 = 2
====== Upon "> /tmp/foo": 1 event (truncate operation)
read() = 16
event->len = 0
Pathname1 is modified
i=16
====== Upon "echo qwerty > /tmp/foo": 2 events (write operation, one event for truncate operation and one for the write of "qwerty" at the beginning of the file)
read() = 16
event->len = 0
Pathname1 is modified
i=16
read() = 16
event->len = 0
Pathname1 is modified
i=16
====== Upon "echo qwerty >> /tmp/foo": 1 event (write of "qwerty" at the end of the file)
read() = 16
event->len = 0
Pathname1 is modified
i=16
If the two pathnames refer to the same inode, then inotify.wd1 == inotify.wd2. In that case, because you have
if (event->wd == inotify.wd1) {
printf("Pathname1 '%s' is modified\n", event->name);
}
if (event->wd == inotify.wd2) {
printf("Pathname2 '%s' is modified\n", event->name);
}
both bodies would be executed; but the outputs would differ (Pathname1 ... and Pathname2 ...).
You really should be monitoring the directories the files reside in, rather than the actual pathnames, so you can catch rename events also. (Many editors create a temporary file, and rename or hardlink the temporary file over the old file, so that programs see either the old or the new file, and never a mix of the two.)
Consider the following program, which does not exhibit any undue duplication of events:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
done = signum;
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
return sigaction(signum, &act, NULL);
}
struct monitor {
/* Supplied by caller */
const char *directory;
const char *pathname;
int (*modified)(struct monitor *);
int (*completed)(struct monitor *);
/* Reserved for internal use */
int dirwatch;
};
int monitor_files(struct monitor *const list, const size_t count)
{
char *events_ptr = NULL;
size_t events_len = 65536;
size_t i;
int err;
/* Verify sane parameters */
if (count < 1) {
errno = ENOENT;
return -1;
} else
if (!list) {
errno = EINVAL;
return -1;
}
for (i = 0; i < count; i++) {
if (!list[i].directory || !list[i].directory[0]) {
errno = EINVAL;
return -1;
}
if (!list[i].pathname || !list[i].pathname[0]) {
errno = EINVAL;
return -1;
}
list[i].dirwatch = -1;
}
/* Obtain a descriptor for inotify event queue */
int queue = inotify_init1(IN_CLOEXEC);
if (queue == -1) {
/* errno set by inotify_init1() */
return -1;
}
/* Use a reasonable dynamically allocated buffer for events */
events_ptr = malloc(events_len);
if (!events_ptr) {
close(queue);
errno = ENOMEM;
return -1;
}
/* Add a watch for each directory to be watched */
for (i = 0; i < count; i++) {
list[i].dirwatch = inotify_add_watch(queue, list[i].directory, IN_CLOSE_WRITE | IN_MOVED_TO | IN_MODIFY);
if (list[i].dirwatch == -1) {
err = errno;
close(queue);
free(events_ptr);
errno = err;
return -1;
}
}
/* inotify event loop */
err = 0;
while (!done) {
ssize_t len = read(queue, events_ptr, events_len);
if (len == -1) {
/* Interrupted due to signal delivery? */
if (errno == EINTR)
continue;
/* Error */
err = errno;
break;
} else
if (len < -1) {
/* Should never occur */
err = EIO;
break;
} else
if (len == 0) {
/* No events watched anymore */
err = 0;
break;
}
char *const end = events_ptr + len;
char *ptr = events_ptr;
while (ptr < end) {
struct inotify_event *event = (struct inotify_event *)ptr;
/* Advance buffer pointer for next event */
ptr += sizeof (struct inotify_event) + event->len;
if (ptr > end) {
close(queue);
free(events_ptr);
errno = EIO;
return -1;
}
/* Call all event handlers, even duplicate ones */
for (i = 0; i < count; i++) {
if (event->wd == list[i].dirwatch && !strcmp(event->name, list[i].pathname)) {
if ((event->mask & (IN_MOVED_TO | IN_CLOSE_WRITE)) && list[i].completed) {
err = list[i].completed(list + i);
if (err)
break;
} else
if ((event->mask & IN_MODIFY) && list[i].modified) {
err = list[i].modified(list + i);
if (err)
break;
}
}
}
if (err)
break;
}
if (err)
break;
}
close(queue);
free(events_ptr);
errno = 0;
return err;
}
static int report_modified(struct monitor *m)
{
printf("%s/%s: Modified\n", m->directory, m->pathname);
fflush(stdout);
return 0;
}
static int report_completed(struct monitor *m)
{
printf("%s/%s: Completed\n", m->directory, m->pathname);
fflush(stdout);
return 0;
}
int main(void)
{
struct monitor watch[2] = {
{ .directory = ".",
.pathname = "file1",
.modified = report_modified,
.completed = report_completed },
{ .directory = ".",
.pathname = "file2",
.modified = report_modified,
.completed = report_completed }
};
int err;
if (install_done(SIGINT) == -1 ||
install_done(SIGHUP) == -1 ||
install_done(SIGTERM) == -1) {
fprintf(stderr, "Cannot set signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
fprintf(stderr, "To stop this program, press Ctrl+C, or send\n");
fprintf(stderr, "INT, HUP, or TERM signal (to process %ld).\n", (long)getpid());
fflush(stderr);
err = monitor_files(watch, 2);
if (err == -1) {
fprintf(stderr, "Error monitoring files: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else
if (err) {
fprintf(stderr, "Monitoring files failed [%d].\n", err);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
If you compile it using e.g. gcc -Wall -Wextra -O2 example.c -o example and run it via ./example, it will report IN_MODIFY events (as "Modified") and IN_CLOSE_WRITE and IN_MOVED_TO events (as "Completed") for file1 and file2 in the same directory (current working directory). To exit the program, press Ctrl+C.
(Note that we should probably add third event type, "Deleted", corresponding to IN_DELETE and IN_MOVED_FROM events.)
If you open file1 or file2 in say a text editor, saving the file generates one or more Modified (IN_MODIFY) events, and exactly one Completed (IN_CLOSE_WRITE or IN_MOVED_TO) event. This is because each open()/truncate()/ftruncate() syscall that causes the file to be truncated, generates one IN_MODIFY event for that file; as does every underlying write() syscall that modifies the file contents. Thus, it is natural to receive multiple IN_MODIFY events when some process modifies the file.
If you have multiple struct monitor entries in the list with the same effective directory and the same pathname, the example program will provide multiple events for them. If you wish to avoid this, just make sure that whenever .pathname matches, the two get differing .dirwatches.
rewriting the while() loop into a for() loop, and flattening out the if()s"
while(1){
int i ;
struct inotify_event *event ;
inotify.length = read(inotify.fd, inotify.buffer, EVENT_BUF_LEN);
for(i=0; i < inotify.length; i += EVENT_SIZE + event->len ) {
event = (struct inotify_event *)&inotify.buffer[i];
if (!event->len) continue;
if (!(event->mask & IN_MODIFY)) continue;
if (event->wd == inotify.wd1){
printf("Pathname1 '%s' is modified\n", event->name);
continue;
}
if (event->wd == inotify.wd2){
printf("Pathname2 '%s' is modified\n", event->name);
continue;
}
}
}
PS: I am very new to threads.
I have a problem where i need to wait for connection requests(completely arbitrary number of times) from clients, accept a connection on a socket, create a worker thread after connection. The created thread then creates a char array, works on it and needs to pass it to the parent process.
I have been able to create the threads in a while loop like
while ((new_socket = accept(srv_sock, (struct sockaddr *)&client, &c)) != INVALID_SOCKET)
{
puts("\nConnection accepted");
_beginthreadex(0, 0, handle_client, &new_socket, 0, 0);
}
I have seen that pthread_join() can be used to pass data from thread to parent process(in unix). My question is, how can I integrate it into a loop in the main process.
I expect the following approach will result in a situation where no more than one connection can be established between client and server at a time,which is not desired.
while ((new_socket = accept(srv_sock, (struct sockaddr *)&client, &c)) != INVALID_SOCKET)
{
puts("\nConnection accepted");
_beginthreadex(0, 0, handle_client, &new_socket, 0, 0);
pthread_join(thread_id,&my_array);
}
EDIT: I would be happy to know if what I want is impossible or if there are alternatives to pthread_join(). or its windows equivalent.
EDIT: I know that pthread_join() is for Unix and have read that WaitForMultipleObjects() is its equivalent for windows. In any case I still haven't been able to figure out a solution.
I have seen that pthread_join() can be used to pass data from thread to parent process.
That is not entirely correct. You can pass a pointer when you exit a thread, and collect that pointer using pthread_join. You have to implement all the logic yourself. The API does not know (or care) what the pointer is. Threads don't have parents and children, they are siblings.
Example for a creator and a reaper:
global
struct VarLengthArray {
size_t count;
MyElem data[1];
};
exiting thread:
// allocate the result
size_t count = ...;
VarLengthArray *retval = malloc(
sizeof(VarLengthArray) +
sizeof(MyElem) * (count > 0 ? count - 1 : 0)
);
// fill the result
retval->count = count;
for (size_t i = 0; i < retval->count; ++i) {
retval->data[i] = ...;
}
pthread_exit(retval);
collecting thread:
// collect the result
void *retval_;
if (pthread_join(thread_one_id, &retval_) != 0) {
// handle error
}
VarLengthArray *retval = retval_;
// use the result
for (size_t i = 0; i < retval->count; ++i) {
printf("retval->[%u] = %s\n", (unsigned) i, retval->data[i].string_value);
}
// deallocate the result
free(retval);
A full example using a condition variable and multiple creators:
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct Datum {
struct Datum *next;
char some_data[32];
} Datum;
typedef struct SharedData {
pthread_mutex_t mutex;
pthread_cond_t cond_empty;
unsigned seed;
Datum *head, *tail;
unsigned children_alive;
} SharedData;
static void *thread_logic(void *argv_);
int main(int argc, char **argv) {
unsigned thread_count = 2;
if (argc > 1) {
if (sscanf(argv[1], " %u ", &thread_count) != 1) {
fprintf(stderr, "Usage: %s [thread_count]\n", argv[0]);
return 1;
}
}
// initialize shared data
SharedData shared_data;
pthread_mutex_init(&shared_data.mutex, NULL);
pthread_cond_init(&shared_data.cond_empty, NULL);
shared_data.seed = time(NULL);
shared_data.head = NULL;
shared_data.tail = NULL;
shared_data.children_alive = 0;
// start threads detached, so you don't have to call pthread_join
pthread_t *child_ids = malloc(sizeof(pthread_t) * thread_count);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// start the threads
pthread_mutex_lock(&shared_data.mutex);
for (unsigned i = 0; i < thread_count; ++i) {
if (pthread_create(&child_ids[i], &attr, thread_logic, &shared_data) != 0) {
perror("pthread_create");
} else {
++shared_data.children_alive;
}
}
pthread_mutex_unlock(&shared_data.mutex);
pthread_attr_destroy(&attr);
// loop until all threads are dead
while (shared_data.children_alive > 0) {
// a condition variable: wait until there is data you can read
pthread_mutex_lock(&shared_data.mutex);
while (shared_data.head == NULL) {
pthread_cond_wait(&shared_data.cond_empty, &shared_data.mutex);
}
// collect a first datum
Datum *datum = shared_data.head;
if (datum->next != NULL) {
shared_data.head = datum->next;
} else {
shared_data.head = shared_data.tail = NULL;
}
pthread_mutex_unlock(&shared_data.mutex);
// handle the data (outside of the mutex lock)
printf("Got data: %s\n", datum->some_data);
free(datum);
}
return 0;
}
static void *thread_logic(void *shared_data_) {
SharedData *shared_data = shared_data_;
while (1) {
pthread_mutex_lock(&shared_data->mutex);
// create some data
useconds_t timeout = (
(((float) (unsigned) rand_r(&shared_data->seed)) / UINT_MAX) *
1000000
);
Datum *datum = malloc(sizeof(Datum));
datum->next = NULL;
if (timeout < 1000000 / 25) {
--shared_data->children_alive;
snprintf(datum->some_data, sizeof(datum->some_data), "I'm done\n");
} else {
snprintf(
datum->some_data, sizeof(datum->some_data),
"Sleeping for %uus\n", timeout
);
}
// append the datum
if (shared_data->head) {
shared_data->tail->next = datum;
} else {
shared_data->head = datum;
pthread_cond_signal(&shared_data->cond_empty);
}
shared_data->tail = datum;
pthread_mutex_unlock(&shared_data->mutex);
// most likely it takes some time to create the data
// do lengthly tasks outside of the mutex lock
if (timeout < 1000000 / 25) {
return NULL;
} else {
usleep(timeout);
}
}
}
I'm trying to use nl80211.h for scanning access points for a simple WLAN manager. I can't find any example code and only documentation I can find is kerneldoc. I have been trying to study from iw and wpa_supplicant source but it's rather complex.
This is only documentation I can find:
NL80211_CMD_GET_SCAN get scan results
NL80211_CMD_TRIGGER_SCAN trigger a new scan with the given parameters
NL80211_ATTR_TX_NO_CCK_RATE is used to decide whether to send the
probe requests at CCK rate or not.
How can I scan access points with nl80211? I think I need to use enum nl80211_commands {NL80211_CMD_GET_SCAN NL80211_CMD_TRIGGER_SCAN}. How can I use them?
I know this is an old question but I ran across it while I was trying to do the same thing. Being new to C and libnl I struggled to get a simple C program to just spit out access points. I'm posting this here for others who were also trying to write a simple program. iw was a great reference but it was challenging following the code around since it does so much more than scan for access points.
/*
* scan_access_points.c: Prints all detected access points with wlan0 using NL80211 (netlink).
*
* Only works on network interfaces whose drivers are compatible with Netlink. Test this by running `iw list`.
*
* Since only privileged users may submit NL80211_CMD_TRIGGER_SCAN, you'll have to run the compiled program as root.
*
* Build with: gcc $(pkg-config --cflags --libs libnl-genl-3.0) scan_access_points.c
*
* Raspbian prerequisites:
* sudo apt-get install libnl-genl-3-dev
*
* Resources:
* http://git.kernel.org/cgit/linux/kernel/git/jberg/iw.git/tree/scan.c
* http://stackoverflow.com/questions/21601521/how-to-use-the-libnl-library-to-trigger-nl80211-commands
* http://stackoverflow.com/questions/23760780/how-to-send-single-channel-scan-request-to-libnl-and-receive-single-
*
* Expected output (as root):
* NL80211_CMD_TRIGGER_SCAN sent 36 bytes to the kernel.
* Waiting for scan to complete...
* Got NL80211_CMD_NEW_SCAN_RESULTS.
* Scan is done.
* NL80211_CMD_GET_SCAN sent 28 bytes to the kernel.
* 47:be:34:f0:bb:be, 2457 MHz, NETGEAR16
* 6b:db:ed:85:ef:42, 2432 MHz, NETGEAR31
* d8:06:ef:a7:f9:80, 2412 MHz, ATT912
* a7:0d:af:0a:19:08, 2462 MHz, ATT185
*
* Expected output (without root):
* NL80211_CMD_TRIGGER_SCAN sent 36 bytes to the kernel.
* Waiting for scan to complete...
* error_handler() called.
* WARNING: err has a value of -1.
* ERROR: nl_recvmsgs() returned -28 (Operation not permitted).
* do_scan_trigger() failed with -28.
*
*/
#include <errno.h>
#include <netlink/errno.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <linux/nl80211.h>
struct trigger_results {
int done;
int aborted;
};
struct handler_args { // For family_handler() and nl_get_multicast_id().
const char *group;
int id;
};
static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) {
// Callback for errors.
printf("error_handler() called.\n");
int *ret = arg;
*ret = err->error;
return NL_STOP;
}
static int finish_handler(struct nl_msg *msg, void *arg) {
// Callback for NL_CB_FINISH.
int *ret = arg;
*ret = 0;
return NL_SKIP;
}
static int ack_handler(struct nl_msg *msg, void *arg) {
// Callback for NL_CB_ACK.
int *ret = arg;
*ret = 0;
return NL_STOP;
}
static int no_seq_check(struct nl_msg *msg, void *arg) {
// Callback for NL_CB_SEQ_CHECK.
return NL_OK;
}
static int family_handler(struct nl_msg *msg, void *arg) {
// Callback for NL_CB_VALID within nl_get_multicast_id(). From http://sourcecodebrowser.com/iw/0.9.14/genl_8c.html.
struct handler_args *grp = arg;
struct nlattr *tb[CTRL_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *mcgrp;
int rem_mcgrp;
nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[CTRL_ATTR_MCAST_GROUPS]) return NL_SKIP;
nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { // This is a loop.
struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mcgrp), nla_len(mcgrp), NULL);
if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) continue;
if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), grp->group,
nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) {
continue;
}
grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
break;
}
return NL_SKIP;
}
int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group) {
// From http://sourcecodebrowser.com/iw/0.9.14/genl_8c.html.
struct nl_msg *msg;
struct nl_cb *cb;
int ret, ctrlid;
struct handler_args grp = { .group = group, .id = -ENOENT, };
msg = nlmsg_alloc();
if (!msg) return -ENOMEM;
cb = nl_cb_alloc(NL_CB_DEFAULT);
if (!cb) {
ret = -ENOMEM;
goto out_fail_cb;
}
ctrlid = genl_ctrl_resolve(sock, "nlctrl");
genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
ret = -ENOBUFS;
NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);
ret = nl_send_auto_complete(sock, msg);
if (ret < 0) goto out;
ret = 1;
nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp);
while (ret > 0) nl_recvmsgs(sock, cb);
if (ret == 0) ret = grp.id;
nla_put_failure:
out:
nl_cb_put(cb);
out_fail_cb:
nlmsg_free(msg);
return ret;
}
void mac_addr_n2a(char *mac_addr, unsigned char *arg) {
// From http://git.kernel.org/cgit/linux/kernel/git/jberg/iw.git/tree/util.c.
int i, l;
l = 0;
for (i = 0; i < 6; i++) {
if (i == 0) {
sprintf(mac_addr+l, "%02x", arg[i]);
l += 2;
} else {
sprintf(mac_addr+l, ":%02x", arg[i]);
l += 3;
}
}
}
void print_ssid(unsigned char *ie, int ielen) {
uint8_t len;
uint8_t *data;
int i;
while (ielen >= 2 && ielen >= ie[1]) {
if (ie[0] == 0 && ie[1] >= 0 && ie[1] <= 32) {
len = ie[1];
data = ie + 2;
for (i = 0; i < len; i++) {
if (isprint(data[i]) && data[i] != ' ' && data[i] != '\\') printf("%c", data[i]);
else if (data[i] == ' ' && (i != 0 && i != len -1)) printf(" ");
else printf("\\x%.2x", data[i]);
}
break;
}
ielen -= ie[1] + 2;
ie += ie[1] + 2;
}
}
static int callback_trigger(struct nl_msg *msg, void *arg) {
// Called by the kernel when the scan is done or has been aborted.
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct trigger_results *results = arg;
//printf("Got something.\n");
//printf("%d\n", arg);
//nl_msg_dump(msg, stdout);
if (gnlh->cmd == NL80211_CMD_SCAN_ABORTED) {
printf("Got NL80211_CMD_SCAN_ABORTED.\n");
results->done = 1;
results->aborted = 1;
} else if (gnlh->cmd == NL80211_CMD_NEW_SCAN_RESULTS) {
printf("Got NL80211_CMD_NEW_SCAN_RESULTS.\n");
results->done = 1;
results->aborted = 0;
} // else probably an uninteresting multicast message.
return NL_SKIP;
}
static int callback_dump(struct nl_msg *msg, void *arg) {
// Called by the kernel with a dump of the successful scan's data. Called for each SSID.
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
char mac_addr[20];
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nlattr *bss[NL80211_BSS_MAX + 1];
static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
[NL80211_BSS_TSF] = { .type = NLA_U64 },
[NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
[NL80211_BSS_BSSID] = { },
[NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 },
[NL80211_BSS_CAPABILITY] = { .type = NLA_U16 },
[NL80211_BSS_INFORMATION_ELEMENTS] = { },
[NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 },
[NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 },
[NL80211_BSS_STATUS] = { .type = NLA_U32 },
[NL80211_BSS_SEEN_MS_AGO] = { .type = NLA_U32 },
[NL80211_BSS_BEACON_IES] = { },
};
// Parse and error check.
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_BSS]) {
printf("bss info missing!\n");
return NL_SKIP;
}
if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy)) {
printf("failed to parse nested attributes!\n");
return NL_SKIP;
}
if (!bss[NL80211_BSS_BSSID]) return NL_SKIP;
if (!bss[NL80211_BSS_INFORMATION_ELEMENTS]) return NL_SKIP;
// Start printing.
mac_addr_n2a(mac_addr, nla_data(bss[NL80211_BSS_BSSID]));
printf("%s, ", mac_addr);
printf("%d MHz, ", nla_get_u32(bss[NL80211_BSS_FREQUENCY]));
print_ssid(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
printf("\n");
return NL_SKIP;
}
int do_scan_trigger(struct nl_sock *socket, int if_index, int driver_id) {
// Starts the scan and waits for it to finish. Does not return until the scan is done or has been aborted.
struct trigger_results results = { .done = 0, .aborted = 0 };
struct nl_msg *msg;
struct nl_cb *cb;
struct nl_msg *ssids_to_scan;
int err;
int ret;
int mcid = nl_get_multicast_id(socket, "nl80211", "scan");
nl_socket_add_membership(socket, mcid); // Without this, callback_trigger() won't be called.
// Allocate the messages and callback handler.
msg = nlmsg_alloc();
if (!msg) {
printf("ERROR: Failed to allocate netlink message for msg.\n");
return -ENOMEM;
}
ssids_to_scan = nlmsg_alloc();
if (!ssids_to_scan) {
printf("ERROR: Failed to allocate netlink message for ssids_to_scan.\n");
nlmsg_free(msg);
return -ENOMEM;
}
cb = nl_cb_alloc(NL_CB_DEFAULT);
if (!cb) {
printf("ERROR: Failed to allocate netlink callbacks.\n");
nlmsg_free(msg);
nlmsg_free(ssids_to_scan);
return -ENOMEM;
}
// Setup the messages and callback handler.
genlmsg_put(msg, 0, 0, driver_id, 0, 0, NL80211_CMD_TRIGGER_SCAN, 0); // Setup which command to run.
nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); // Add message attribute, which interface to use.
nla_put(ssids_to_scan, 1, 0, ""); // Scan all SSIDs.
nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids_to_scan); // Add message attribute, which SSIDs to scan for.
nlmsg_free(ssids_to_scan); // Copied to `msg` above, no longer need this.
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, callback_trigger, &results); // Add the callback.
nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err);
nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err);
nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); // No sequence checking for multicast messages.
// Send NL80211_CMD_TRIGGER_SCAN to start the scan. The kernel may reply with NL80211_CMD_NEW_SCAN_RESULTS on
// success or NL80211_CMD_SCAN_ABORTED if another scan was started by another process.
err = 1;
ret = nl_send_auto(socket, msg); // Send the message.
printf("NL80211_CMD_TRIGGER_SCAN sent %d bytes to the kernel.\n", ret);
printf("Waiting for scan to complete...\n");
while (err > 0) ret = nl_recvmsgs(socket, cb); // First wait for ack_handler(). This helps with basic errors.
if (err < 0) {
printf("WARNING: err has a value of %d.\n", err);
}
if (ret < 0) {
printf("ERROR: nl_recvmsgs() returned %d (%s).\n", ret, nl_geterror(-ret));
return ret;
}
while (!results.done) nl_recvmsgs(socket, cb); // Now wait until the scan is done or aborted.
if (results.aborted) {
printf("ERROR: Kernel aborted scan.\n");
return 1;
}
printf("Scan is done.\n");
// Cleanup.
nlmsg_free(msg);
nl_cb_put(cb);
nl_socket_drop_membership(socket, mcid); // No longer need this.
return 0;
}
int main() {
int if_index = if_nametoindex("wlan0"); // Use this wireless interface for scanning.
// Open socket to kernel.
struct nl_sock *socket = nl_socket_alloc(); // Allocate new netlink socket in memory.
genl_connect(socket); // Create file descriptor and bind socket.
int driver_id = genl_ctrl_resolve(socket, "nl80211"); // Find the nl80211 driver ID.
// Issue NL80211_CMD_TRIGGER_SCAN to the kernel and wait for it to finish.
int err = do_scan_trigger(socket, if_index, driver_id);
if (err != 0) {
printf("do_scan_trigger() failed with %d.\n", err);
return err;
}
// Now get info for all SSIDs detected.
struct nl_msg *msg = nlmsg_alloc(); // Allocate a message.
genlmsg_put(msg, 0, 0, driver_id, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0); // Setup which command to run.
nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); // Add message attribute, which interface to use.
nl_socket_modify_cb(socket, NL_CB_VALID, NL_CB_CUSTOM, callback_dump, NULL); // Add the callback.
int ret = nl_send_auto(socket, msg); // Send the message.
printf("NL80211_CMD_GET_SCAN sent %d bytes to the kernel.\n", ret);
ret = nl_recvmsgs_default(socket); // Retrieve the kernel's answer. callback_dump() prints SSIDs to stdout.
nlmsg_free(msg);
if (ret < 0) {
printf("ERROR: nl_recvmsgs_default() returned %d (%s).\n", ret, nl_geterror(-ret));
return ret;
}
return 0;
}
nl80211.h only provides these enums for you to use with the real wireless library (which is libnl). You can use libnl by downloading it and including it in your c program: http://www.carisma.slowglass.com/~tgr/libnl/
Then with nl80211.h included, you can use all the enums that are defined with the commands defined in libnl.
I have a some multithreading application. This is a part of main function:
/* ...some code before... */
for(i=0; i<THREADS_COUNT; i++){
status = pthread_create(&threads[i], NULL, thread_main, NULL);
if(status < 0){
fprintf(stderr, "threads error\n");
exit(2);
}
}
status = sem_init(&sem, 0, 0);
if(status < 0){
fprintf(stderr, "sem_init error\n");
exit(4);
}
/* recv loop */
while (1) {
rv = recv(fd, buf, BUFSIZE, 0);
if(rv >= 0){
current = malloc(sizeof(struct queue_msg_list));
/* adding to our local queue */
if(current != NULL){
current->rv = rv;
current->h = h;
memcpy(&(current->buf), &buf, BUFSIZE);
current->next = NULL;
if(main_head == NULL){
main_head = main_tail = current;
}
else {
main_tail->next = current;
main_tail = current;
}
count++;
}
/* if we can carry the local queue to the queue for threads then we are doing it */
if(!pthread_mutex_trylock(&mlock)){
if(thread_head == NULL){
/* if the threads-queue is empty then replace queues */
thread_head = main_head;
thread_tail = main_tail;
} else {
/* if the threads-queue is not empty then add the local queue to the threads-queue */
thread_tail->next = main_head;
thread_tail = main_tail;
}
/* we increasing a semaphore of number of added elements */
for(i=0; i<count; i++){
sem_post(&sem);
printf("sem_post \n");
}
count = 0;
pthread_mutex_unlock(&mlock);
main_head = NULL;
main_tail = NULL;
}
}
}
/* ...some code after... */
And this is a function for threads:
void *thread_main(void *arg)
{
struct queue_msg_list *current;
char buf[BUFSIZE] __attribute__ ((aligned));
struct nfq_handle *h;
int rv;
while(1){
sem_wait(&sem);
pthread_mutex_lock(&mlock);
/* if no blocking then we are working with packet and removing it from list after */
current = thread_head;
rv = current->rv;
h = current->h;
memcpy(&buf, &(current->buf), BUFSIZE);
thread_head = thread_head->next;
pthread_mutex_unlock(&mlock);
nfq_handle_packet(h, buf, rv);
free(current);
}
}
This application always works true on PC. I have put this application to some router (linux kernel version in firmware is 2.6.30). It works correctly sometimes but sometimes it works incorrectly. :)
Threads hang on calling sem_wait(&sem); sometimes but the semaphore value is above zero (1, 2, 3, etc). Why?
P.S. I tried to check the return value of sem_wait(&sem); but did not get it.
I have the bounded buffer, producer consumer problem to deal with and can only modify the prod and cons functions. This code runs without issues with only one consumer and producer threads. But with multiple of each it always gives me the same problem, sooner or later:
p5p1: p5p2a.c:207: bb_remove: Assertion `bbp->cnt > 0' failed.
What I don't get is how can this error occur when I check the bbp->cnt variable before calling the bbp_remove() function.
EDIT: The problem has been solved. I was not checking the variable within the lock, in either of the functions.
#include <sys/times.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>
#define DEBUG 0
#define BUF_SIZE 100000
#define ITER 10000000
#define PROD_THRD 3
#define CONS_THRD 3
#define USAGE_STRING "Usage: %s\n"
extern int errno;
/* This is the bounded buffer type */
typedef struct {
int cnt, in, out;
pthread_mutex_t lock; /* Mutex to avoid race conditions */
int buf[BUF_SIZE]; /* The data passed is the id of the
* producer */
} bbuf_t;
typedef struct {
bbuf_t *bbp;
int id;
} parg_t;
/*
* yield()
* Because there is no yield system call in Linux, what we
* do is to put the thread to sleep for 1 ms. Actually, it
* will sleep for at least 1/HZ, which is 10 ms in Linux/386.
*/
#define YIELD_s 0
#define YIELD_ns 1000000
void yield() {
struct timespec st = {YIELD_s, YIELD_ns};
if( (nanosleep(&st, NULL)) == -1 && (errno == EINVAL)) {
perror("nanosleep");
pthread_exit(NULL);
}
}
/* Initialize bounded buffer */
int bbuf_init(bbuf_t *bbp) {
if(bbp == NULL)
return 0;
bbp->in = 0;
bbp->out = 0;
bbp->cnt = 0;
pthread_mutex_init(&(bbp->lock), NULL); /* I do not understand, but the
* compiler complains when I use
* PTHREAD_MUTEX_INITIALIZER */
return 1;
}
/* Print the bounded buffer members that matter */
void print_bbuf(bbuf_t *bbp) {
printf("bbp->in = %d bbp->out = %d bbp_cnt = %d \n",
bbp->in, bbp->out, bbp->cnt);
}
/* To validate the value of the members in, out and cnt of bbuf_t */
int val(int n, int min, int max) {
return( (min <= n) && (n <= max));
}
/* Ensure that the values of the members in, out and cnt are consistent */
int consist(int cnt, int in, int out) {
return ( in == ((out + cnt) % BUF_SIZE) );
}
/* This is the code of the checker thread. It is used to ensure that
* the bounded buffer has not been corrupted.
* Every 100 ms it checks the values of: in, out and cnt.
* This thread exits either if it detects the buffer has been corrupted
* or if it does not detect any activity in 50 consecutive iterations,
* i.e. approximately 5s. */
/* These constants are used with nanosleep() and
* put a process to sleep for 100 ms */
#define SLEEP_s 0
#define SLEEP_ns 100000000
#define MAX_IDLE 50
void *check(void *arg) {
bbuf_t *bbp = arg;
int cnt[2], in[2], out[2]; /* values read */
int idle;
struct timespec st = {SLEEP_s, SLEEP_ns}; /* 100 ms */
while(1) {
pthread_mutex_lock( &(bbp->lock) );
in[1] = bbp->in;
out[1] = bbp->out;
cnt[1] = bbp->cnt;
pthread_mutex_unlock( &(bbp->lock) );
if(in[1] == in[0] && out[1] == out[0] && cnt[1] == cnt[0] ) {
idle++;
if( idle >= MAX_IDLE ) {
printf("Checking thread exiting:");
print_bbuf(bbp);
printf("\t no activity detected for some time.\n");
pthread_exit(NULL);
}
} else {
idle = 0;
}
if( !val(in[1], 0, BUF_SIZE - 1) ) {
printf("Invalid value in = %d \n", in[1]);
pthread_exit(NULL);
} else if ( !val(out[1], 0, BUF_SIZE - 1) ) {
printf("Invalid value out = %d \n", out[1]);
pthread_exit(NULL);
} else if ( !val(cnt[1], 0, BUF_SIZE) ) {
printf("Invalid value cnt = %d \n", cnt[1]);
pthread_exit(NULL);
} else if ( !consist(cnt[1], in[1], out[1]) ) {
printf("Inconsistent buffer: cnt = %d in = %d out = %d \n",
cnt[1], in[1], out[1]);
pthread_exit(NULL);
}
if( (nanosleep(&st, NULL) == -1) && (errno == EINVAL)) {
perror("nanosleep");
pthread_exit(NULL);
}
in[0] = in[1];
out[0] = out[1];
cnt[0] = cnt[1];
}
}
/* The producer threads may use this code to
* enter one item into the buffer */
void bb_enter(bbuf_t *bbp, int me) {
assert( bbp->cnt < BUF_SIZE);
(bbp->buf)[bbp->in] = me;
(bbp->in)++;
(bbp->in) %= BUF_SIZE;
(bbp->cnt)++;
//printf("%d\n",bbp->cnt);
}
/* This is the code for the producer threads.
*
* To avoid busy waiting (or at least too much busy waiting) the producers
* should yield, using the yield() defined above, if the buffer is
* full. In that case, they should print a debugging message as well.
*
* Each producer should produce ITER (10 M) items: an integer with
* the id it receives in prod()'s argument.
*/
void *prod(void *arg) {
parg_t *parg = (parg_t *)arg;
bbuf_t *bbp = parg->bbp;
int me = parg->id;
/* Add variables and code, if necessary */
printf("I am a producer and have started\n");
int gcnt = 0;
while( gcnt <= ITER ){
if(bbp->cnt < BUF_SIZE){
pthread_mutex_lock(&(bbp->lock));
bb_enter(bbp,me);
gcnt++;
pthread_mutex_unlock(&(bbp->lock));}
else if( bbp->cnt == (BUF_SIZE-1)) {printf("I shall produce yield()\n"); yield();}
}
printf("I am a producer and have ended\n");
return;
}
/* The consumer threads may use this function to
* remove an item */
int bb_remove(bbuf_t *bbp) {
int val;
assert(bbp->cnt > 0);
val = (bbp->buf)[bbp->out];
(bbp->out)++;
(bbp->out) %= BUF_SIZE;
(bbp->cnt)--;
return val;
}
/* This is the code for the consumer threads.
* To avoid busy waiting (or at least too much busy waiting) consumers
* should yield, using the yield() defined above, if the buffer is
* empty. In that case, they should print a debugging message as well.
*
* Each consumer should consume ITER (10 M) items, and keep track of the
* producers of the items it consumes: use the cnt[] below.
*/
void *cons(void *arg) {
bbuf_t *bbp = (bbuf_t *)arg;
int cnt[PROD_THRD];
/* Add variables and code, if necessary:
* do not forget to initialize cnt */
printf("I am a consumer and have started\n");
int i;
for(i = 0; i < PROD_THRD; i++){
cnt[i] = 0;}
int temp;
int gcnt = 0;
while( gcnt <= ITER ){
if(bbp->cnt > 0){
pthread_mutex_lock(&(bbp->lock));
temp = bb_remove(bbp);
gcnt++;
cnt[temp]++;
pthread_mutex_unlock(&(bbp->lock));}
else if( bbp->cnt == 0) {printf("I shall consume yield()\n"); yield();}
}
printf("I am a consumer and have ended\n");
return;
}
int main(int argc, char *argv[]) {
int i;
pthread_t *tid, ctid;
parg_t *parg;
bbuf_t *bbp;
/* This is to measure the time it takes to run the program */
struct tms t;
clock_t start, end;
long ticks = sysconf(_SC_CLK_TCK);
start = times(&t);
if( argc != 1 ) {
printf(USAGE_STRING, argv[0]);
exit(1);
}
/* Array for pthread_join() */
if((tid = (pthread_t *) malloc((PROD_THRD + CONS_THRD) * sizeof(pthread_t)))
== NULL ) {
printf("Out of memory.\n");
exit(2);
}
/* Allocate Bounded Buffer */
if((bbp = (bbuf_t *) malloc(sizeof(bbuf_t))) == NULL ) {
printf("Out of memory. \n");
exit(2);
}
/* Initialize Bounded Buffer */
if( bbuf_init(bbp) == 0 ) {
printf("Failed to initialize bounded buffer\n");
exit(3);
}
/* Arguments for producer threads */
if((parg = (parg_t *) malloc( PROD_THRD * sizeof (parg_t))) == NULL ) {
printf("Out of memory.\n");
exit(2);
}
/* Create checker thread */
if( pthread_create(&ctid, NULL, check, bbp) )
perror("pthread_create");
printf("Created checker thread %u\n", (unsigned)ctid);
/* Create consumer threads */
for( i = 0; i < CONS_THRD; i++ ) {
/* We pass the same data structure, the bounded buffer,
* to each consumer: they need to synchronize to access it */
if( pthread_create(tid+i, NULL, cons, bbp) )
perror("pthread_create");
printf("Created consumer thread %u\n", (unsigned)tid[i]);
}
/* Create producer threads */
for( i = 0; i < PROD_THRD; i++ ) {
/* Because we want each consumer to keep track of the
* producer of the items it consumes, we assign an
* id to each producer thread */
parg[i].bbp = bbp;
parg[i].id = i;
if( pthread_create(tid+(i+CONS_THRD), NULL, prod, parg+i) )
perror("pthread_create");
printf("Created producer thread %u (%d)\n", (unsigned)tid[i+CONS_THRD], i);
}
/* Join consumer and producer threads */
for( i = 0; i < CONS_THRD + PROD_THRD; i ++ ) {
if( pthread_join(tid[i], NULL) == 0 ) {
printf("Joined thread %u.\n", (unsigned)tid[i]);
} else {
printf("Failed to join thread %u\n", (unsigned)tid[i]);
}
}
/* Join checker thread */
if( pthread_join(ctid, NULL) == 0 ) {
printf("Joined checker thread %u.\n", (unsigned)ctid);
} else {
printf("Failed to join checker thread %u\n", (unsigned)ctid);
}
/* How long did it take to run this ? */
end = times(&t);
printf("Wall time %2.4f s \n", (float)(end - start) / ticks);
return 0;
}
You should enter the mutex lock before you check bbp->cnt. Since you are checking it before you enter the mutex, another thread can reduce the value before you acquire the mutex and try to reduce the value yourself.