MiniTerm

harja 28.12.03 10:02

 Tekstiversio  Arvo: 2 (3 ääntä)  Äänestä: +  -
/* MiniTERM, a small "terminal emulator"
 * Demonstrates the usage of
 * open(), write(), read(), select(), multithreading
 * and mutexes.
 *
 * To disable the echoing of the commands, type ATE0 at the terminal
 * by Mikko Harju <maharj@utu.fi> */


#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>

#include <sys/select.h>
#include <sys/time.h>

const int READ_BUFFER_SIZE=512, WRITE_BUFFER_SIZE;

char *write_buffer, *read_buffer;
int fd, quit=0;
pthread_mutex_t write_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t fd_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t read_lock=PTHREAD_MUTEX_INITIALIZER;

void replace_cr(char* buf) {
  int i=0;
  for(i=0;buf[i];i++) {
    if(buf[i]=='\n') {
      buf[i]='\r';
    }
  }
}

void* rthread(void* param) {
  fd_set rfds;
  struct timeval tv;
  int data_available=0, n=0;

  while(1) {
    pthread_mutex_lock(&fd_lock);
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    tv.tv_sec=0;
    tv.tv_usec=0;

    data_available=select(fd+1, &rfds, NULL, NULL, &tv);

    if(FD_ISSET(fd, &rfds) && data_available) {
      pthread_mutex_lock(&read_lock);

      /* LISÄTTY: Varmistetaan, että saadaan jotain validia */
      if((n=read(fd, read_buffer, READ_BUFFER_SIZE))<=0)
        continue;

      read_buffer[n-1]='\0';
      printf("%s", read_buffer);
      pthread_mutex_unlock(&read_lock);
    }
    pthread_mutex_unlock(&fd_lock);

    if(quit) { printf("Read thread exiting...\n"); pthread_exit(NULL); }
  }
}

void* wthread(void* param) {
  int write_buffer_len=0, n=0;

  while(1) {
    pthread_mutex_lock(&write_lock);
    write_buffer_len=strlen(write_buffer);
    if(write_buffer_len<=0)
      {
        pthread_mutex_unlock(&write_lock);
        sleep(1);
        continue;
      }


    pthread_mutex_lock(&fd_lock);

    /* LISÄTTY: Mikäli write epäonnistuu, yritetään uudestaan */
    if((n=write(fd, write_buffer, write_buffer_len))<=0)
      continue;

    pthread_mutex_unlock(&fd_lock);

    // reset the write buffer
    *write_buffer='\0';

    pthread_mutex_unlock(&write_lock);

    if(quit) { printf("Write thread exiting...\n");  pthread_exit(NULL); }
  }
}

int init_threads(pthread_t *read_thread, pthread_t *write_thread) {
  pthread_attr_t attr;

  pthread_attr_init(&attr);

  if(pthread_create(read_thread, &attr, rthread, NULL)!=0)
    return 0;
  if(pthread_create(write_thread, &attr, wthread, NULL)!=0)
    return 0;

  pthread_attr_destroy(&attr);

  // initialize mutexes
  pthread_mutex_init(&fd_lock, NULL);
  pthread_mutex_init(&write_lock, NULL);
  pthread_mutex_init(&read_lock, NULL);
   
  return 1;
}

int init_com(int* fd, char* port) {
  struct termios options;

  if(-1 == (*fd=open(port, O_RDWR|O_NOCTTY|O_NDELAY)))
    return 0;

  fcntl(*fd, F_SETFL, 0);

  tcgetattr(*fd, &options);
  options.c_cflag|=(CLOCAL|CREAD);
  options.c_lflag&=~(ICANON|ECHO|ECHOE|ISIG);
  options.c_oflag&=~OPOST;
  options.c_cc[VMIN]=0;
  options.c_cc[VTIME]=10;
  tcsetattr(*fd, TCSANOW, &options);
 
  return 1;
}

int main(int argc, char** argv) {
  pthread_t write_thread, read_thread;
  char buffer[255]={0}; // stdin read buffer

  printf("MiniTERM 0.1\n");
  if(argc<2)
    {
      fprintf(stderr, "Error: Usage: %s <device>\n", argv[0]);
      return 1;
    }

  printf("Initializing com port...\n");
  // initialize the COM port
  if(!init_com(&fd, argv[1]))
    {
      perror("Error initializing the COM port");
      return 1;
    }

  printf("Initializing buffers...\n");
  // allocate the memory for the buffers
  write_buffer=(char*)malloc(WRITE_BUFFER_SIZE);
  read_buffer=(char*)malloc(READ_BUFFER_SIZE);
  memset(buffer, 0, sizeof(buffer));

  if(NULL == write_buffer || NULL == read_buffer)
    {
      fprintf(stderr, "Could not allocate memory for the buffers!\n");
      return 1;
    }


  printf("Initializing threads...\n");

  // start the read and write threads
  if(!init_threads(&read_thread, &write_thread))
    {
      fprintf(stderr, "Could not initialize the threads!\n");
      return 1;
    }

  quit=0;
  while(buffer[0]!='q') {
    memset(buffer, 0, sizeof(buffer));
    fgets(buffer,sizeof(buffer),stdin);
    replace_cr(buffer);

    pthread_mutex_lock(&write_lock);
    strcpy(write_buffer, buffer);
    pthread_mutex_unlock(&write_lock);
  }

  printf("Deinitializing...\n");

  quit=1;
  // give the time to the threads to end themselves
  sleep(2);

  // deallocate the buffers
  free(write_buffer);
  free(read_buffer);

  // destroy the mutexes
  pthread_mutex_destroy(&fd_lock);
  pthread_mutex_destroy(&write_lock);
  pthread_mutex_destroy(&read_lock)

  // close the COM port
  close(fd);

  return 0;
}

empty 23:58 28.12.03 
Hienoa, että joku tekee vaihteeksi systeemiohjelmointiesimerkin iänikuisten "muuttuja pyörähtää ympäri" -pelleilyiden sijaan. Yleisesti ottaen näyttää toimivalta, mutta kiinnittäisin huomiota tähän:

n=read(fd, read_buffer, READ_BUFFER_SIZE);
read_buffer[n-1]='\0';

Ensinnäkin: n-1 on alle 0, jos read() palauttaa arvon
0 tai -1. Mitä käy?

Toiseksi: On mahdollista, että read() ei pysty syystä tai
toisesta lukemaan kaikkia merkkejä kerralla. Koodi ei
tutki, saatiinko kaikki jo luettua. Sama pätee myös write():een. Kavalin bugi on se, joka tulee esiin
yhdessä tuhannesta ajosta.
harja 00:26 30.12.03 
Jep. Olihan siinä joskus iffikin ympärillä, mutta otin sen syystä tai toisesta pois :)

Toiseen vastaan, että sillä ei ole väliä, koska se puskuri luetaan sieltä, ja sen jälkeen tulostetaan ruudulle. Ei ole väliä, saadaanko kaikki tuonne puskuriin luettua, pääasia on, että jotain tulee, ja sen jälkeen saadaan loput toisella lukukerralla. select():hän pitää huolen siitä, että aina, kun luettavaa on, se myös otetaan sieltä. Mikäli ymmärsin tämän väärin, niin kerro.

Kiitos huomautuksesta tuon iffin suhteen. Voisin vaikka korjata sen tuohon.