I am aiming a simple code which looks like this:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <linux/input.h>
int main()
{
int fd, bytes;
unsigned char data[3];
fd = open("/dev/input/mice", O_RDWR);
int left;
while(1)
{
bytes = read(fd, data, sizeof(data));
if(bytes > 0)
{
left = data[0] & 0x1;
right = data[0] & 0x2;
if(left==1){
goto reset_EVENT;
}
}
reset_EVENT:
printf("hey 1");
sleep(1);
printf("hey 2");
sleep(1);
printf("hey 3");
sleep(1);
}
return 0;
}
My goal here is to restart the print sequence from "hey 1" immediately when left mouse is clicked.
However, I am not able to achieve this without using "if" conditions before every sleep. a "goto" is not working for me across different functions too.
Can anyone suggest an optimal solution for this one please ?
You could achieve this with some kind of state machine: a function which has an
internal static state to know whick was the last hey printed
void print_event(int reset)
{
/* by default, print nothing */
static int state = 4;
/* if reset, restart counting */
if (reset)
state = 1;
/* if state under 4, display `hey` */
if (state <= 3)
printf("hey %d\n", state++);
}
int main()
{
int fd, bytes;
unsigned char data[3];
fd = open("/dev/input/mice", O_RDWR);
int left, right;
while(1)
{
/* by default, event is not reset */
int reset = 0;
bytes = read(fd, data, sizeof(data));
if(bytes > 0)
{
left = data[0] & 0x1;
right = data[0] & 0x2;
if(left==1){
/* left click: reset event*/
reset = 1;
}
}
/* ask function to print if needed */
print_event(reset);
sleep(1);
}
return 0;
}
EDIT
one problem in your loop is that you will wait a long time before reading new
value from fd.
You can use signal to achieve this:
continously reading input from mice, without sleeping
when left click is detected, arm an alarm signal to display event:
Thus, your code could be:
/* print event function: can be called from alarm signal handler
or directly from main function */
void print_event(int reset)
{
/* by default, print nothing */
static int state = 4;
/* if reset, restart counting */
if (reset)
state = 1;
/* if state under 4, display `hey` */
if (state <= 3)
{
printf("hey %d", state++);
/* if all `hey` have not been printed, prepare the next */
alarm(1);
}
}
/* alarm signal receiver*/
void handle(int sig) {
/* print event without reset */
print_event(0);
}
int main()
{
int fd, bytes;
unsigned char data[3];
fd = open("/dev/input/mice", O_RDWR);
int left;
/* tell what function is to be called when alarm signal is raised */
signal(SIGALRM, handle);
/* if you want to display one sequence before the first click, uncomment the following line */
/* print_event(1); */
while(1)
{
bytes = read(fd, data, sizeof(data));
if(bytes > 0)
{
left = data[0] & 0x1;
if(left==1){
/* left click: reset event*/
print_event(1);
}
}
}
return 0;
}
Related
I have developped a C program for my embedded Board. This program make the green LED lights on when I push and release the BUTTON.
The green LED is defined under "/sys/class/leds" and the BUTTON is under "/dev/input/event0".
This is the code :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <linux/input.h>
#define BTN_FILE_PATH "/dev/input/event0"
#define LED_PATH "/sys/class/leds"
#define green "green"
void change_led_state(char *led_path, int led_value)
{
char lpath[64];
FILE *led_fd;
strncpy(lpath, led_path, sizeof(lpath) - 1);
lpath[sizeof(lpath) - 1] = '\0';
led_fd = fopen(lpath, "w");
if (led_fd == NULL) {
fprintf(stderr, "simplekey: unable to access led\n");
return;
}
fprintf(led_fd, "%d\n", led_value);
fclose(led_fd);
}
void reset_leds(void)
{
change_led_state(LED_PATH "/" green "/brightness", 0);
}
int configure_leds(void)
{
FILE *l_fd;
FILE *r_fd;
char *none_str = "none";
/* Configure leds for hand control */
r_fd = fopen(LED_PATH "/" green "/trigger", "w");
fprintf(r_fd, "%s\n", none_str);
fclose(r_fd);
/* Switch off leds */
reset_leds();
return 0;
}
void eval_keycode(int code)
{
static int green_state = 0;
switch (code) {
case 260:
printf("BTN pressed\n");
/* figure out green state */
green_state = green_state ? 0 : 1;
change_led_state(LED_PATH "/" green "/brightness", green_state);
break;
}
}
int main(void)
{
int file;
/* how many bytes were read */
size_t rb;
int ret;
int yalv;
/* the events (up to 64 at once) */
struct input_event ev[64];
char *str = BTN_FILE_PATH;
printf("Starting simplekey app\n");
ret = configure_leds();
if (ret < 0)
exit(1);
printf("File Path: %s\n", str);
if((file = open(str, O_RDONLY)) < 0) {
perror("simplekey: File can not open");
exit(1);
}
for (;;) {
/* Blocking read */
rb= read(file, &ev, sizeof(ev));
for (yalv = 0;
yalv < (int) (rb / sizeof(struct input_event));
yalv++) {
if (ev[yalv].type == EV_KEY) {
/* Change state on button pressed */
if (ev[yalv].value == 0)
eval_keycode(ev[yalv].code);
}
}
}
close(file);
reset_leds();
exit(0);
}
The compilation is going well.
When I execute the code it shows me this :
Starting simplekey app
File Path: /dev/input/event0
When I push the BUTTON nothing happens and when I release it the LED changes state and it shows me this in the terminal :
BTN pressed
The problem is that the code continue executing until I press CTRL+C to exit.
I just want it to wait until the event ( pressing BUTTON ) happens then change LED state and finally exit automatically.
My question is how to modify the program for this purpose ? I thought about using Threads and Signals but I don't have any idea about them. Thank you!
I presume you are running all this under a system complying either SVr4, or 4.3BSD ot POSIX.1-2001 (or later versions).
You are missing the the check on the read() return value, which is not size_t but rather ssize_t (that is it is signed).
Your code could then be changed like this:
/* ... */
ssize_t rb; /* !!! */
/* ... */
for (;;) {
/* Blocking read */
rb= read(file, &ev, sizeof(ev));
if (rb <= 0) /* Check for the EOF */
break;
for (yalv = 0;
yalv < (int) (rb / sizeof(struct input_event));
yalv++) {
if (ev[yalv].type == EV_KEY) {
/* Change state on button pressed */
if (ev[yalv].value == 0)
eval_keycode(ev[yalv].code);
}
}
}
Please, refer to the friendly man pages.
I think I may have a threading problem in c, but I'm not sure.
My goal is to execute two separate functions inside a while(1) loop as such:
One of these functions is kbget() to retrieve the key pressed in a terminal in non-canonical mode.
The second one is to constantly get the terminal window size with the ioctl(1, TIOCGWINSZ,...) function.
It normally doesn't work because the while(1) loop stops to get a keypress from the user before executing the second function to reevaluate the terminal window size. If the terminal window is resized before a key is pressed, the function to evaluate its size isn't executed unless a random key is pressed again.
In other words, resizing the terminal window doesn't update the size values in the Window struct below unless a key is pressed.
I want the program to update the y_size & x_size values 'live' as the terminal is resized.
Here's the issue in code without POSIX threads:
Executing with :
gcc -Wall scr.h main.c -o main && ./main
(scr.h below has kbget() to change terminal mode):
main.c:
#include "scr.h"
#include <sys/ioctl.h>
#define gotoyx(y, x) printf("\033[%d;%dH", (y), (x)) // equivalent to move(y, x) in ncurses
#define del_from_cursor(x) printf("\033[%dX", (x)) // delete x characters from cursor position
typedef struct {
int y_size;
int x_size;
} Window;
int main(void)
{
printf("\033[?1049h\033[2J\033[H"); // remember position & clear screen
gotoyx(1, 10);
printf("Press <ESC> to stop program.");
gotoyx(2, 10);
printf("Resizing the terminal window does not 'automatically' update the size shown on screen");
Window w;
struct winsize w_s;
while (1) {
// evaluate terminal size
if (!ioctl(1, TIOCGWINSZ, &w_s)) {
w.y_size = w_s.ws_row;
w.x_size = w_s.ws_col;
}
// print terminal size and center it
gotoyx(w.y_size / 2, w.x_size / 2);
del_from_cursor(5);
printf("w.y_size: %d", w.y_size);
gotoyx((w.y_size / 2) + 1, w.x_size / 2);
del_from_cursor(5);
printf("w.x_size: %d", w.x_size);
// get key pressed by user in terminal & exit if <ESC> is pressed
if (kbget() == 0x001b) { break; }
}
printf("\033[2J\033[H\033[?1049l"); // clear screen & restore
return 0;
}
I have tried solving this using threads but I was unsuccessful so far.
I have modified the main.c file above by adding 2 functions (get_window_size & get_key):
(scr.h has the kbget() function in get_key() to change the terminal to canonical mode)
main.c:
#include "scr.h"
#include <sys/ioctl.h>
#include <pthread.h>
#define gotoyx(y, x) printf("\033[%d;%dH", (y), (x))
#define del_from_cursor(x) printf("\033[%dX", (x))
typedef struct {
int y_size;
int x_size;
} Window;
void *get_window_size(void *arg)
{
Window *w = (Window *)arg;
struct winsize w_s;
if (!ioctl(1, TIOCGWINSZ, &w_s)) {
w->y_size = w_s.ws_row;
w->x_size = w_s.ws_col;
}
pthread_exit(0);
}
void *get_key(void *arg)
{
int *key = (int *)arg;
free(arg);
*key = kbget();
int *entered_key = malloc(sizeof(*key));
*entered_key = *key;
pthread_exit(entered_key);
}
int main(void)
{
printf("\033[?1049h\033[2J\033[H");
Window w;
pthread_t tid[3];
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&tid[0], &attr, get_window_size, &w);
int *c = malloc(sizeof(*c));
int *key_pressed;
while (1) {
// for initial size
pthread_join(tid[0], NULL);
// printing size to screen
gotoyx(w.y_size / 2, w.x_size / 2);
del_from_cursor(5);
printf("w.y_size: %d", w.y_size);
gotoyx((w.y_size / 2) + 1, w.x_size / 2);
del_from_cursor(5);
printf("w.x_size: %d", w.x_size);
// get window size
pthread_attr_t attr1;
pthread_attr_init(&attr1);
pthread_create(&tid[1], &attr1, get_window_size, &w);
// get key entered by user
pthread_attr_t attr2;
pthread_attr_init(&attr2);
pthread_create(&tid[2], &attr2, get_key, c);
pthread_join(tid[1], NULL);
pthread_join(tid[2], (void **)&key_pressed);
if (*key_pressed == 0x001b) {
break;
} else {
free(key_pressed);
}
}
if (key_pressed != NULL) {
free(key_pressed);
}
printf("\033[2J\033[H\033[?1049l");
return 0;
}
The scr.h file changes the terminal mode to non-canonical (the kbget() function above is called from here):
I don't think there's any problems in scr.h as it is taken from here (Move the cursor in a C program).
scr.h:
#ifndef SCR_H
#define SCR_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
struct termios term, oterm;
int getch(void)
{
int c = 0;
tcgetattr(STDIN_FILENO, &oterm);
memcpy(&term, &oterm, sizeof(term));
term.c_lflag &= ~(ICANON | ECHO);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oterm);
return c;
}
int kbhit(void)
{
int c = 0;
tcgetattr(STDIN_FILENO, &oterm);
memcpy(&term, &oterm, sizeof(term));
term.c_lflag &= ~(ICANON | ECHO);
term.c_cc[VMIN] = 0;
term.c_cc[VTIME] = 1;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oterm);
if (c != -1) { ungetc(c, stdin); }
return c != -1 ? 1 : 0;
}
int kbesc(void)
{
int c = 0;
if (!kbhit()) { return 0x001b; } // 0x001b is the <ESC> key
c = getch();
if (c == 0) { while (kbhit()) { getch(); } }
return c;
}
int kbget(void)
{
int c = getch();
return c == 0x001b ? kbesc() : c; // 0x001b is the <ESC> key
}
#endif // SCR_H
I also get errors Invalid write of size 4 in the code above with pthread while executing with valgrind:
gcc -Wall scr.h main.c -pthread -o main
valgrind -v --leak-check=yes ./main
I am aware of the existence of ncurses and pdcurses. I am only doing this as an exercise for myself.
UPDATE
I have changed my code to the following, unfortunately the ret variable never changes to -1:
#include "scr.h"
#include <errno.h>
#include <sys/ioctl.h>
#define gotoyx(y, x) printf("\033[%d;%dH", (y), (x))
#define del_from_cursor(x) printf("\033[%dX", (x))
typedef struct {
int y_size;
int x_size;
} Window;
static int sigwinch_arrived = 0;
void sigwinch_handler(int signum)
{ sigwinch_arrived = 1; }
void on_window_size_change(Window *w)
{
struct winsize w_s;
// evaluate terminal size
if (!ioctl(1, TIOCGWINSZ, &w_s)) {
w->y_size = w_s.ws_row;
w->x_size = w_s.ws_col;
}
// print terminal size in its center
gotoyx(w->y_size / 2, w->x_size / 2);
del_from_cursor(15);
printf("w.y_size: %d", w->y_size);
gotoyx((w->y_size / 2) + 1, w->x_size / 2);
del_from_cursor(15);
printf("w.x_size: %d", w->x_size);
}
int main(void)
{
printf("\033[?1049h\033[2J\033[H");
gotoyx(1, 10);
printf("Press <ESC> to stop program.");
gotoyx(2, 10);
printf("Resizing the terminal window does not 'automatically' update the size shown on screen");
Window w;
int ret;
while (1) {
// get key pressed by user in terminal & exit if <ESC> is pressed
ret = kbget();
gotoyx(10, 10);
del_from_cursor(8);
printf("ret: %d", ret);
if (ret == -1) {
if (errno == EAGAIN) {
if (sigwinch_arrived) {
sigwinch_arrived = 0;
on_window_size_change(&w);
}
}
} else if (ret == 0x001b) {
break;
}
}
printf("\033[2J\033[H\033[?1049l");
return 0;
}
Extension: as per this answer, if your ncurses was compiled with the --enable-sigwinch flag, it does the solution below automatically (if you did not override SIGWINCH before ncurses_init() yet). In this case, getch() (wgetch()) will simply return KEY_RESIZE if a resize event is happened.
If the size of your controlling character terminal changes, your process should get a SIGWINCH signal (window size change, signal 28 on Linux).
It can be sent by the kernel (if there is mode switch on a character desktop), or by the virtual terminal software (xterm, gnome-terminal, screen, etc).
If your process gets a signal, its blocking kernel calls, including getch(), stop with the -EAGAIN error number. It means, that the blocking call stopped before time due to an arrived signal.
Note, from a signal handler, you can't do too much (for example: no malloc()), and the best to do if you make the least possible. Typical signal handlers change a static, global variable, whose value is checked by the main program.
Untested example code:
static int sigwinch_arrived = 0;
// this is called from the signal handler - nothing complex is allowed here
void sigwinch_handler(int signum) {
sigwinch_arrived = 1;
}
// callback if there is a window size change
void on_window_size_change() {
...
}
// main program
...
while (1) { // your main event handler loop
int ret = getch();
if (ret == ERR) {
if (errno == EAGAIN) {
if (sigwinch_arrived) {
sigwinch_arrived = 0;
on_window_size_change();
}
}
}
...
}
I want to display a header every X lines where X makes the header show up as the last one scrolls off the screen. The user may change the terminal size and the program should know the answer. Something roughly like
i = get_lines()+1;
while (1) {
if (i > get_lines()) {
printf("header");
i = 0;
} else {
i++;
}
do_stuff();
}
You can read the current terminal height with TIOCGWINSZ:
#include <sys/ioctl.h> /* needed for lines */
#include <signal.h> /* needed for lines */
#include <stdio.h> /* needed for printf */
#include <time.h> /* needed for sleep */
unsigned short lines;
static void get_lines(int signo) {
struct winsize ws;
ioctl(fileno(stdout), TIOCGWINSZ, &ws);
lines = ws.ws_row;
}
int main(int argc, char** argv) {
int i;
struct timespec ts;
get_lines(SIGWINCH);
signal(SIGWINCH, get_lines);
i = lines;
while (1) {
if (i >= lines) {
printf("header\n");
i = 3; /* 3 not 1 because header + last empty line */
} else {
i++;
}
printf("line\n");
ts.tv_sec = 0;
ts.tv_nsec = 500000000;
nanosleep(&ts, NULL);
}
}
The number of lines is now in ws.ws_row.
When the user changes the terminal size (i.e. resizes his terminal window), a SIGWINCH is sent to the foreground process. So you should establish a signal handler for this event and re-read the window size.
My C program creates a producer thread, saving data as fast as possible. The main thread consumes and prints these.
After days of bug finding, I noticed that if the mutex was initialized, then the program stops within 30 seconds (deadlock?).
However if the mutex is left uninitialized it works perfectly.
Can anyone explain this?? To avoid undefined behavior, I would prefer to initialize them if possible.
Further Info: Specifically it's locking up if "pthread_mutex_t signalM" (the signaling mutex) is initialized
Initialized
#include <stdlib.h> // exit_failure, exit_success
#include <stdio.h> // stdin, stdout, printf
#include <pthread.h> // threads
#include <string.h> // string
#include <unistd.h> // sleep
#include <stdbool.h> // bool
#include <fcntl.h> // open
struct event {
pthread_mutex_t critical;
pthread_mutex_t signalM;
pthread_cond_t signalC;
int eventCount;
};
struct allVars {
struct event inEvents;
struct event outEvents;
int bufferSize;
char buffer[10][128];
};
/**
* Advance the EventCount
*/
void advance(struct event *event) {
// increment the event counter
pthread_mutex_lock(&event->critical);
event->eventCount++;
pthread_mutex_unlock(&event->critical);
// signal await to continue
pthread_mutex_lock(&event->signalM);
pthread_cond_signal(&event->signalC);
pthread_mutex_unlock(&event->signalM);
}
/**
* Wait for ticket and buffer availability
*/
void await(struct event *event, int ticket) {
int eventCount;
// get the counter
pthread_mutex_lock(&event->critical);
eventCount = event->eventCount;
pthread_mutex_unlock(&event->critical);
// lock signaling mutex
pthread_mutex_lock(&event->signalM);
// loop until the ticket machine shows your number
while (ticket > eventCount) {
// wait until a ticket is called
pthread_cond_wait(&event->signalC, &event->signalM);
// get the counter
pthread_mutex_lock(&event->critical);
eventCount = event->eventCount;
pthread_mutex_unlock(&event->critical);
}
// unlock signaling mutex
pthread_mutex_unlock(&event->signalM);
}
/**
* Add to buffer
*/
void putBuffer(struct allVars *allVars, char data[]) {
// get the current write position
pthread_mutex_lock(&allVars->inEvents.critical);
int in = allVars->inEvents.eventCount;
pthread_mutex_unlock(&allVars->inEvents.critical);
// wait until theres a space free in the buffer
await(&allVars->outEvents, in - allVars->bufferSize + 1); // set to 2 to keep 1 index distance
// add data to buffer
strcpy(allVars->buffer[in % allVars->bufferSize], data);
// increment the eventCounter and signal
advance(&allVars->inEvents);
}
/**
* Get from buffer
*/
char *getBuffer(struct allVars *allVars) {
// get the current read position
pthread_mutex_lock(&allVars->outEvents.critical);
int out = allVars->outEvents.eventCount;
pthread_mutex_unlock(&allVars->outEvents.critical);
// wait until theres something in the buffer
await(&allVars->inEvents, out + 1);
// allocate memory for returned string
char *str = malloc(128);
// get the buffer data
strcpy(str, allVars->buffer[out % allVars->bufferSize]);
// increment the eventCounter and signal
advance(&allVars->outEvents);
return str;
}
/** child thread (producer) */
void *childThread(void *allVars) {
char str[10];
int count = 0;
while (true) {
sprintf(str, "%d", count++);
putBuffer(allVars, str);
}
pthread_exit(EXIT_SUCCESS);
}
int main(void) {
// init structs
struct event inEvents = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER,
0
};
struct event outEvents = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER,
0
};
struct allVars allVars = {
inEvents, // events
outEvents,
10, // buffersize
{"", {""}} // buffer[][]
};
// create child thread (producer)
pthread_t thread;
if (pthread_create(&thread, NULL, childThread, &allVars)) {
fprintf(stderr, "failed to create child thread");
exit(EXIT_FAILURE);
}
// (consumer)
while (true) {
char *out = getBuffer(&allVars);
printf("buf: %s\n", out);
free(out);
}
return (EXIT_SUCCESS);
}
Uninitialized
#include <stdlib.h> // exit_failure, exit_success
#include <stdio.h> // stdin, stdout, printf
#include <pthread.h> // threads
#include <string.h> // string
#include <unistd.h> // sleep
#include <stdbool.h> // bool
#include <fcntl.h> // open
struct event {
pthread_mutex_t critical;
pthread_mutex_t signalM;
pthread_cond_t signalC;
int eventCount;
};
struct allVars {
struct event inEvents;
struct event outEvents;
int bufferSize;
char buffer[10][128];
};
/**
* Advance the EventCount
*/
void advance(struct event *event) {
// increment the event counter
pthread_mutex_lock(&event->critical);
event->eventCount++;
pthread_mutex_unlock(&event->critical);
// signal await to continue
pthread_mutex_lock(&event->signalM);
pthread_cond_signal(&event->signalC);
pthread_mutex_unlock(&event->signalM);
}
/**
* Wait for ticket and buffer availability
*/
void await(struct event *event, int ticket) {
int eventCount;
// get the counter
pthread_mutex_lock(&event->critical);
eventCount = event->eventCount;
pthread_mutex_unlock(&event->critical);
// lock signaling mutex
pthread_mutex_lock(&event->signalM);
// loop until the ticket machine shows your number
while (ticket > eventCount) {
// wait until a ticket is called
pthread_cond_wait(&event->signalC, &event->signalM);
// get the counter
pthread_mutex_lock(&event->critical);
eventCount = event->eventCount;
pthread_mutex_unlock(&event->critical);
}
// unlock signaling mutex
pthread_mutex_unlock(&event->signalM);
}
/**
* Add to buffer
*/
void putBuffer(struct allVars *allVars, char data[]) {
// get the current write position
pthread_mutex_lock(&allVars->inEvents.critical);
int in = allVars->inEvents.eventCount;
pthread_mutex_unlock(&allVars->inEvents.critical);
// wait until theres a space free in the buffer
await(&allVars->outEvents, in - allVars->bufferSize + 1); // set to 2 to keep 1 index distance
// add data to buffer
strcpy(allVars->buffer[in % allVars->bufferSize], data);
// increment the eventCounter and signal
advance(&allVars->inEvents);
}
/**
* Get from buffer
*/
char *getBuffer(struct allVars *allVars) {
// get the current read position
pthread_mutex_lock(&allVars->outEvents.critical);
int out = allVars->outEvents.eventCount;
pthread_mutex_unlock(&allVars->outEvents.critical);
// wait until theres something in the buffer
await(&allVars->inEvents, out + 1);
// allocate memory for returned string
char *str = malloc(128);
// get the buffer data
strcpy(str, allVars->buffer[out % allVars->bufferSize]);
// increment the eventCounter and signal
advance(&allVars->outEvents);
return str;
}
/** child thread (producer) */
void *childThread(void *allVars) {
char str[10];
int count = 0;
while (true) {
sprintf(str, "%d", count++);
putBuffer(allVars, str);
}
pthread_exit(EXIT_SUCCESS);
}
int main(void) {
// init structs
struct event inEvents; /* = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER,
0
}; */
struct event outEvents; /* = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER,
0
}; */
struct allVars allVars = {
inEvents, // events
outEvents,
10, // buffersize
{"", {""}} // buffer[][]
};
// create child thread (producer)
pthread_t thread;
if (pthread_create(&thread, NULL, childThread, &allVars)) {
fprintf(stderr, "failed to create child thread");
exit(EXIT_FAILURE);
}
// (consumer)
while (true) {
char *out = getBuffer(&allVars);
printf("buf: %s\n", out);
free(out);
}
return (EXIT_SUCCESS);
}
Jonathan explained why the code that didn't initialize mutexes didn't deadlock (essentially because trying to use an uninitialized mutex would never block, it would just immediately return an error).
The problem causing the infinite wait in the version of the program that does properly initialize mutexes is that you aren't using your condition variables properly. The check of the predicate expression and the wait on the condition variable must be done atomically with respect to whatever other thread might be modifying the predicate. You code is checking a predicate that is a local variable that the other thread doesn't even have access to. Your code reads the actual predicate into a local variable within a critical section, but then the mutex for reading the predicate is released and a different mutex is acquired to read the 'false' predicate (which cannot be modified by the other thread anyway) atomically with the condition variable wait.
So you have a situation where the actual predicate, event->eventCount, can be modified and the signal for that modification be issued in between when the waiting thread reads the predicate and blocks on the condition variable.
I think the following will fix your deadlock, but I haven't had a chance to perform much testing. The change is essentially to remove the signalM mutex from struct event and replace any use of it with the critical mutex:
#include <stdlib.h> // exit_failure, exit_success
#include <stdio.h> // stdin, stdout, printf
#include <pthread.h> // threads
#include <string.h> // string
#include <unistd.h> // sleep
#include <stdbool.h> // bool
#include <fcntl.h> // open
struct event {
pthread_mutex_t critical;
pthread_cond_t signalC;
int eventCount;
};
struct allVars {
struct event inEvents;
struct event outEvents;
int bufferSize;
char buffer[10][128];
};
/**
* Advance the EventCount
*/
void advance(struct event *event) {
// increment the event counter
pthread_mutex_lock(&event->critical);
event->eventCount++;
pthread_mutex_unlock(&event->critical);
// signal await to continue
pthread_cond_signal(&event->signalC);
}
/**
* Wait for ticket and buffer availability
*/
void await(struct event *event, int ticket) {
// get the counter
pthread_mutex_lock(&event->critical);
// loop until the ticket machine shows your number
while (ticket > event->eventCount) {
// wait until a ticket is called
pthread_cond_wait(&event->signalC, &event->critical);
}
// unlock signaling mutex
pthread_mutex_unlock(&event->critical);
}
/**
* Add to buffer
*/
void putBuffer(struct allVars *allVars, char data[]) {
// get the current write position
pthread_mutex_lock(&allVars->inEvents.critical);
int in = allVars->inEvents.eventCount;
pthread_mutex_unlock(&allVars->inEvents.critical);
// wait until theres a space free in the buffer
await(&allVars->outEvents, in - allVars->bufferSize + 1); // set to 2 to keep 1 index distance
// add data to buffer
strcpy(allVars->buffer[in % allVars->bufferSize], data);
// increment the eventCounter and signal
advance(&allVars->inEvents);
}
/**
* Get from buffer
*/
char *getBuffer(struct allVars *allVars) {
// get the current read position
pthread_mutex_lock(&allVars->outEvents.critical);
int out = allVars->outEvents.eventCount;
pthread_mutex_unlock(&allVars->outEvents.critical);
// wait until theres something in the buffer
await(&allVars->inEvents, out + 1);
// allocate memory for returned string
char *str = malloc(128);
// get the buffer data
strcpy(str, allVars->buffer[out % allVars->bufferSize]);
// increment the eventCounter and signal
advance(&allVars->outEvents);
return str;
}
/** child thread (producer) */
void *childThread(void *allVars) {
char str[10];
int count = 0;
while (true) {
sprintf(str, "%d", count++);
putBuffer(allVars, str);
}
pthread_exit(EXIT_SUCCESS);
}
int main(void) {
// init structs
struct event inEvents = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER,
0
};
struct event outEvents = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER,
0
};
struct allVars allVars = {
inEvents, // events
outEvents,
10, // buffersize
{"", {""}} // buffer[][]
};
// create child thread (producer)
pthread_t thread;
if (pthread_create(&thread, NULL, childThread, &allVars)) {
fprintf(stderr, "failed to create child thread");
exit(EXIT_FAILURE);
}
// (consumer)
while (true) {
char *out = getBuffer(&allVars);
printf("buf: %s\n", out);
free(out);
}
return (EXIT_SUCCESS);
}
I modified the getBuffer() and putBuffer() routines as shown (in both the initialized and uninitialized versions of the code):
static
void putBuffer(struct allVars *allVars, char data[])
{
int lock_ok = pthread_mutex_lock(&allVars->inEvents.critical);
if (lock_ok != 0)
printf("%s(): lock error %d (%s)\n", __func__, lock_ok, strerror(lock_ok));
int in = allVars->inEvents.eventCount;
int unlock_ok = pthread_mutex_unlock(&allVars->inEvents.critical);
if (unlock_ok != 0)
printf("%s(): unlock error %d (%s)\n", __func__, unlock_ok, strerror(unlock_ok));
await(&allVars->outEvents, in - allVars->bufferSize + 1);
strcpy(allVars->buffer[in % allVars->bufferSize], data);
advance(&allVars->inEvents);
}
static
char *getBuffer(struct allVars *allVars)
{
int lock_ok = pthread_mutex_lock(&allVars->outEvents.critical);
if (lock_ok != 0)
printf("%s(): lock error %d (%s)\n", __func__, lock_ok, strerror(lock_ok));
int out = allVars->outEvents.eventCount;
int unlock_ok = pthread_mutex_unlock(&allVars->outEvents.critical);
if (unlock_ok != 0)
printf("%s(): unlock error %d (%s)\n", __func__, unlock_ok, strerror(unlock_ok));
await(&allVars->inEvents, out + 1);
char *str = malloc(128);
strcpy(str, allVars->buffer[out % allVars->bufferSize]);
advance(&allVars->outEvents);
return str;
}
Then running the uninitialized code yields a lot of messages like:
buf: 46566
putBuffer(): lock error 22 (Invalid argument)
getBuffer(): lock error 22 (Invalid argument)
putBuffer(): unlock error 22 (Invalid argument)
getBuffer(): unlock error 22 (Invalid argument)
Basically, it appears to me that your locking and unlocking is being ignored. There are other places in your code that you should check too.
Fundamentally, if you ignore the errors reported, you don't notice that the locking and unlocking is not working at all, and there's no reason for the code to stop running.
Always check the return values from system calls that can fail.
I don't have an immediate explanation for why the initialized code locks up. It does for me, running on Mac OS X 10.10.3 with GCC 5.1.0, after anywhere from about 100,000 to 800,000 iterations.
I have a multi threaded application which uses ncurses on a single thread to report information to the user. My code basically looks like this:
const unsigned int refresh_cycle = 180;
unsigned int refresh_count = refresh_cycle;
while(killswitch != 1) {
if (refresh_count >= refresh_cycle) {
// critical section which obtains some data worked on by a thread. only does this once every refresh cycle times
// mtx lock, fetch, mtx unlock
refresh_count = 0;
}
refresh_count++;
// get input
// draw some stuff
// refresh
}
What I notice is that the ncurses window gets refreshed lots and lots of times. Way more than is really needed for a user who could probably get by with only 15-30 refreshes in a second.
But now I am worrying this might 'steal' unnecesary processing power from one of the threads that is doing work. Is this a reasonable assertion?
Should I build in a sort of frame limiter with usleep() or would that be going overboard?
As per the comments, if in between the refreshes you need to deal with user input, then the easiest way is probably to call select() on STDIN_FILENO with a suitably small timeout. When select() returns, either because there is user input or because it timed out, do a refresh at that point.
Here's an example that'll let you see how to set this up, and shows when when and how many times select() is returning so you can visualize what's going on. Try letting it sit and run for a while, and then try holding down a key, and watch how the select() has returned [n] times message behaves in each case. The comments in the code explain what's happening:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/select.h>
#include <ncurses.h>
/* struct to store curses info for cleanup */
struct curinfo {
WINDOW * main_window;
int old_cursor;
};
/* curses helper functions */
void start_curses(struct curinfo * info);
void stop_curses(struct curinfo * info);
/* main function */
int main(int argc, char * argv[])
{
/* Set default timeout */
int secs = 0;
int usecs = 500000;
/* Set timeout based on command line args, if provided */
if ( argc > 1 ) {
if ( !strcmp(argv[1], "veryshort") ) {
secs = 0;
usecs = 200000;
}
else if ( !strcmp(argv[1], "short") ) {
secs = 1;
usecs = 0;
}
else if ( !strcmp(argv[1], "medium") ) {
secs = 2;
usecs = 0;
}
else if ( !strcmp(argv[1], "long") ) {
secs = 5;
usecs = 0;
}
}
struct curinfo cinfo;
start_curses(&cinfo);
int input = '0'; /* Set to something printable */
int num_sel = 0; /* Number of times select() has returned */
while ( input != 'q' && input != 'Q' ) {
/* Output messages */
mvprintw(3, 3, "select() has returned %d times", num_sel);
mvprintw(4, 3, "Last character input was %c", input);
mvprintw(5, 3, "Press 'q' to quit");
refresh();
/* select() modifies the fd_sets passed to it,
* so zero and set them prior to each call. */
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
/* Same deal for the struct timeval, select() may
* modify it, it may not, so recreate to be portable. */
struct timeval tv;
tv.tv_sec = secs;
tv.tv_usec = usecs;
/* Store the return so we can check it */
int status = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
/* Check for error */
if ( status == -1 ) {
/* select() returned with an error. */
if ( errno != EINTR ) {
/* If interrupted by a signal, no problem,
* keep going. Otherwise, let's just quit. */
stop_curses(&cinfo);
perror("error calling select()");
return EXIT_FAILURE;
}
}
else if ( FD_ISSET(STDIN_FILENO, &fds) ) {
/* Only call getch() if input is ready.
* getch() will not block when we do it this way. */
if ( (input = getch()) == ERR ) {
stop_curses(&cinfo);
fprintf(stderr, "ERR returned from getch()\n");
return EXIT_FAILURE;
}
}
/* Increment number of times select() has returned */
++num_sel;
}
stop_curses(&cinfo);
return 0;
}
/* Starts curses and populates the passed struct */
void start_curses(struct curinfo * info)
{
if ( (info->main_window = initscr()) == NULL ) {
fprintf(stderr, "Error calling initscr()\n");
exit(EXIT_FAILURE);
}
keypad(stdscr, TRUE);
timeout(0);
raw();
nonl();
noecho();
info->old_cursor = curs_set(0);
refresh();
}
/* Stops curses and cleans up */
void stop_curses(struct curinfo * info)
{
delwin(info->main_window);
curs_set(info->old_cursor);
endwin();
refresh();
}