4. týždeň

Ovládanie zariadení

Ciele

  1. Spoznať, ako komunikovať so zariadením.
  2. Spoznať rozhranie na nastavenie zariadení.
  3. Funkcie na ovládanie terminálu.
  4. Režimy terminálu.

Postup

Krok 1: Zariadenia

Príprava

Stiahnite si archív k cvičeniu .

Obsah archívu.

.
└── 04
    ├── Makefile
    ├── pr2-1.c
    ├── pr4-1.c
    ├── u2-1.c
    ├── u2-2.c
    ├── u3-1.c
    └── u4-1.c

Teória - zariadenia

Na minulom cvičení boli predstavené rôzne typy súborov.

OS UNIX/Linux poskytuje mechanizmus, pomocou ktorého môžu procesy komunikovať s ovládačmi zariadení prostredníctvom objektov podobných súborom. Tieto objekty sa nachádzajú v súborovom systéme (špeciálne súbory) a programy ich môžu otvárať, čítať z nich a zapisovať do nich, akoby to boli obyčajné súbory.

Poznámka

Ak chcete zistiť meno súboru, ktorým je reprezentovaný terminál môžete použiť príkaz tty.

Súbory sú umiestnené v adresári /dev. V časti výpisu príkazu ls -l si všimnite typ súborov.

Príklad: ls -l /dev

brw-rw----  1 root  disk      8,   9 mar 12 11:59 sda9
crw-rw----  1 root  disk     21,   0 mar 12 11:59 sg0
crw-rw----+ 1 root  cdrom    21,   1 mar 12 11:59 sg1
drwxrwxrwt  2 root  root          40 mar 12 12:53 shm
crw-------  1 root  root     10, 231 mar 12 11:59 snapshot
drwxr-xr-x  3 root  root         220 mar 12 11:59 snd
brw-rw----+ 1 root  cdrom    11,   0 mar 12 11:59 sr0
lrwxrwxrwx  1 root  root          15 mar 12 11:59 stderr -> /proc/self/fd/2
lrwxrwxrwx  1 root  root          15 mar 12 11:59 stdin -> /proc/self/fd/0
lrwxrwxrwx  1 root  root          15 mar 12 11:59 stdout -> /proc/self/fd/1
crw-rw-rw-  1 root  tty       5,   0 mar 12 11:59 tty
crw-------  1 root  root      5,   3 mar 12 11:59 ttyprintk
crw-rw----  1 root  dialout   4,  64 mar 12 11:59 ttyS0
crw-rw----  1 root  dialout   4,  65 mar 12 11:59 ttyS1

Aktivita - zariadenia - o1-1 u1-1 u1-2

Otázka 1.1

Ktorý z úryvku výpisov ls -l patrí súboru predstavujúcemu zariadenie?

Odoslať odpoveď
Správna odpoveď: crw-rw----
Nesprávna odpoveď

Úloha 1.1

Otvorte si dvakrát terminál (dve okná) a zistite meno súboru, ktorým je reprezentovaný jeden z nich.
Výstup môže vyzerať takto:

/dev/pts/1

Úloha 1.2

Pokúste sa urobiť výpis v druhom termináli zadaním príkazu v prvom (napr. pomocou príkazu echo a presmerovania >).

Poznámka

Ak vás prekvapil počet zariadení uvedených v /dev, možné vysvetlenie je, že Linux má viacero virtuálnych terminálov. Medzi terminálmi viete prepínať skratkou <Ctrl>+<Alt>+<Fx>, kde Fx je funkčná klávesa a x je číslo terminálu.

  • terminál 1 <Ctrl>+<Alt>+<F1>
  • terminál 7 <Ctrl>+<Alt>+<F7>

Je zaužívané, že konzoly 1-6 sú textové a 7. konzola je grafická.

Poznámka

Ak chcete vidieť, kto je prihlásený v akom termináli môžete použiť príkaz w.

Krok 2: Kontrola zariadení

Teória - ioctl

Zariadenia zvyčajne ponúkajú rôzne nastavenia. Pre prístup k nim môže byť použitá služba ioctl().

Služba jadra ioctl()

Služba jadra ioctl() poskytuje rozhranie pre riadenie technických zariadení (terminály, disky, ...).

#include <sys/ioctl.h>
int ioctl(int fildes, int cmd, ...);
Parametere Popis
filedes deskriptor objektu, nad ktorým je služba volaná
cmd definovanie činnosti
tretí parameter nepovinný, použitie závisí od konkrétneho zariadenia
Ukončenie Návratová hodnota
úspešné iná ako -1
chyba -1, nastaví sa príslušná hodnota ERRNO

Poznámka

Hodnoty parametra cmd: man 2 ioctl_list

Aktivita - ioctl - o2-1

Otázka 2.1

Aký je minimálny počet parametrov pri službe ioctl().

Správna odpoveď: 2
Nesprávna odpoveď

Príklad - ioctl - pr2-1

Príklad 2.1

Získanie aktuálnych nastavení terminálu.

štruktúra termios

#include <termios.h>
struct termios {
tcflag_t c_iflag; //vstupný režim
tcflag_t c_oflag; //výstupný režim
tcflag_t c_cflag; //riadiaci režim 
tcflag_t c_lflag; //lokálny režim
cc_t c_cc[NCCS];  //špeciálne riadiace znaky
}

//pr2-1.c
#include <sys/ioctl.h>
#include <termios.h>
#include <stdio.h>

int main(int argc, char const *argv[]){
  struct termio nastavenia;
  //získanie nastaveí terminálu
  ioctl(0,TCGETA,&nastavenia); 
  if (nastavenia.c_lflag & ECHO){
    printf("echo je zapnuté, jeho bit je 1\n");  
  }
    else {
      printf("echo je vypnuté, jeho bit je 0\n"); 
    }
  return 0;
}

Zmenu nastavení terminálu je možné dosiahnuť zmenou príznakov, načítaných do štruktúry termios a nahradením pôvodných nastavení novými.

Aktivita - ioctl - u2-1

Úloha 2.1

V programe sú menené nastavenia terminálu. Upravte ho tak, aby pred ukončením programu bol terminál nastavený do pôvodného stavu. Pracujte so súborom u2-1.c.

//u2-1.c
#include <termio.h>
#include <stdio.h>
#define SIZE 120

int main()
{
  struct termio d_zaciatok,d_nove;
  char meno[SIZE];
  char heslo[SIZE];

  printf("\nZadaj svoje prihlasovacie meno:");
  //načítanie z klávesnice
  scanf("%s",meno);
  //uloženie nastavení terminálu  
  ioctl(0,TCGETA,&d_zaciatok);  
  d_nove = d_zaciatok;
  //vypnutie echa
  d_nove.c_lflag=~ECHO; 
  //aplikovanie zmenených nastavení
  ioctl(0,TCSETA,&d_nove); 
  printf("Zadaj heslo:");
  scanf("%s",heslo);  
  //obnovenie nastavení
  //DOPLNTE RIEŠENIE  

  printf("\nEcho bolo znovu zapnute");
  printf("\nTvoje heslo je: %s\n", heslo);

  printf("\nTeraz sú zadané znaky zobrazené:");
  printf("\nZadaj heslo:");
  scanf("%s",heslo);  
  printf("\nTvoje heslo je: %s\n", heslo);
  return 0;
}

Aktivita - ioctl - u2-2

Úloha 2.2

Vytvorte program, ktorý bude 10 sekúnd sledovať rozmery terminálu (počet riadkov a stĺpcov) a vypisovať ich. Pracujte so súborom u2-2.c.

//u2-2.c
#include <stdio.h>

int main()
{
  struct /*DOPLNTE*/
  > 
    for(int i = 0; i < 10; i++){
      /*DOPLNTE*/
      printf ("rows: %d cols: %d\n", );
      sleep(1);
    }
 >   
    return 0;
}

Príklad výstupu

rows: 28 cols: 107
rows: 28 cols: 107
rows: 27 cols: 106
rows: 23 cols: 92
rows: 23 cols: 92
rows: 23 cols: 92
rows: 19 cols: 92
rows: 19 cols: 92
rows: 19 cols: 92
rows: 19 cols: 92

Poznámka

Ukážka hodnôt pre parameter cmd (kompletný zoznam man 2 ioctl_list)

0x00005405   TCGETA           struct termio *
0x00005316   CDROMSEEK        const struct cdrom_msf *
0x00005410   TIOCSPGRP        const pid_t *
0x00005413   TIOCGWINSZ       struct winsize *
0x00005411   TIOCOUTQ         int *
0x00005412   TIOCSTI          const char *
struct winsize{
  unsigned short  ws_row;   /* rows, in characters */
  unsigned short  ws_col;   /* columns, in characters */
  unsigned short  ws_xpixel;  /* horizontal size, pixels */
  unsigned short  ws_ypixel;  /* vertical size, pixels */
}

podrobnejšie

Aktivita - stty - o2-2

Pre vykonanie zmien v nastavení terminálu a zistenie aktuálneho stavu je možné použiť aj príkaz stty.

Otázka 2.2

Ako bude vyzerať príkaz, ktorý zobrazí výpis všetkých aktuálnych nastavení terminálu?(Odpoveď uveďte v tvare: "stty -prepinac" bez úvodzoviek.)

Správna odpoveď: stty -a
Nesprávna odpoveď

Príklad - stty - pr2-2 u2-3

Príklad 2.2
Zapnutie a vypnutie echa.

//vypnutie echa
$ stty -echo
//zapnutie echa
$ stty echo

Úloha 2.3

Vyskúšajte použiť príkazy z príkladu. Všimnite si, ako sa prejavia zmeny vo výpise aktuálnych nastavení.

Upozornenie

Príkaz je možné zadať, aj keď zápis nie je viditeľný.

Krok 3: Funkcie na ovládanie terminálu

Teória - tcgetattr, tcsetattr

Funkcie tcgetattr() a tcsetattr()

#include <termios.h>

int tcgetattr(int fd, struct termios *termptr);
int tcsetattr(int fd, int act, const struct termios *termptr);
Parametere Popis
fd deskriptor priradený terminálu
act spôsob aplikovania zmien, môže mať hodnotu
  • TCSANOW
  • TCSADRAIN
  • TCSAFLUSH
termptr smerník na štruktúru termios
Ukončenie Návratová hodnota
úspešné 0
chyba -1

Funkcia tcgetattr() zapíše aktuálne parametre terminálu do štruktúry, na ktorú odkazuje termptr. Parametre terminálu po úprave nastavíme prostredníctvom funkcie tcsetattr(). Dôležité je zabezpečiť nastavenie pôvodných hodnôt na konci programu.

Príklad - potlačenie (vypnutie) vypisovania echa:

...
struct termios nastavenia;
tcgetattr(fileno(stdin), &nastavenia);
nastavenia.c_lflag &= ~ECHO;
...

Príklad - povolenie (zapnutie) vypisovania echa:

...
struct termios nastavenia;
tcgetattr(fileno(stdin), &nastavenia);
nastavenia.c_lflag |= ECHO;
...

Aktivita - funkcie - o3-1

Otázka 3.1

Pomocou akej funkcie získate nastavenia terminálu? (Odpoveď uveďte v tvare: "nazov" bez úvodzoviek a medzier.)

Správna odpoveď: tcgetattr
Nesprávna odpoveď

Aktivita - funkcie - u3-1

Úloha 3.1

Zmente nastavenia terminálu tak, aby bol text na výstupe veľkými písmenami. Zmeny nech sa prejavia ihneď. Na konci obnovte pôvodné nastavenia. Pracujte so súborom u3-1.c.

//u3-1.c
#include <termios.h> //struct termios
#include <unistd.h> //isatty
#include <stdio.h>

#define NOTATTY 1

int main(int argc, char const *argv[])
{
  int ttyZariadenie = STDIN_FILENO;
  struct termios term_old, term_new;
  // test či deskriptor súboru odkazuje na terminál
  if ( ! isatty(ttyZariadenie) ) {
        return(NOTATTY);
  }
  else {
    if (tcgetattr(ttyZariadenie, &term_old) != 0)
           perror("tcgetattr error");
        else {
          term_new = term_old;
          //doplnte riešenie
        
          printf("tento text bol malými písmenami\n");
            //doplnte riešenie
         
          printf("obnovené pôvodné nastavenia\n");
        }
  }

  return 0;

Poznámka

man termios c_oflag flag constants

 OPOST  Enable implementation-defined output processing.
 OLCUC  (not in POSIX) Map lowercase characters to uppercase on output.
 ONLCR  (XSI) Map NL to CR-NL on output.
 OCRNL  Map CR to NL on output.

Krok 4: Získanie vstupu cez terminál

Teória - režimy - o4-1

Termínál má dva režimy:

  • KANONICKÝ - vstup z terminálu je odoslaný až po stlačení klávesy enter
  • NEKANONICKÝ - programu je odoslaný každý stlačeý znak, nie sú rozdelené do riadkov

Otázka 4.1

Ktorý režim je prednastavený?

Odoslať odpoveď
Správna odpoveď: kanonický
Nesprávna odpoveď

Príklad - režimy - pr4-1

Príklad 4.1

Program priebežne vypisuje znaky zadané v prvom termináli - je v nekanonickom režime na druhý - zadaný ako argument pri spustení.

//príprava
- otvorené dve okná terminálu,
- známy názov jedného z nich, zistený príkazom `tty`.
//spustenie  
$ ./pr4-1 /dev/pts/1`

//pr4-1.c
#include <termios.h> //struct termios
#include <unistd.h> //isatty
#include <fcntl.h> //open
#include <stdio.h>

#define NOTATTY 1

int main(int argc, char const *argv[])
{
  //deskriptor pre terminál, z ktorého je program spustený
  int ttyZariadenie = STDIN_FILENO;

  //získanie deskriptora pre druhý terminál
  int fd = open(argv[1], O_WRONLY);
  if(fd == -1){
    perror("open");
    return -1;
  }
 
  struct termios term_old, term_new;
  char buffer[255]; 
  int nacitane;

  // test či deskriptor súboru odkazuje na terminál
  if ( ! isatty(ttyZariadenie) ) {
        return(NOTATTY);
  }
  else {
    if (tcgetattr(ttyZariadenie, &term_old) != 0){
      perror("tcgetattr error");
    }     
    else {
      term_new = term_old;
      //vypnutie echa a nastavenie nekanonického režimu
      term_new.c_lflag &= ~( ICANON | ECHO );
      tcsetattr(ttyZariadenie, TCSANOW, &term_new); 

      while(1){
        nacitane = read(ttyZariadenie, &buffer, 255);
        //sledovaie, kedy sa vykoná read()
        printf("read\n");
        //ukončenie načítavania C + d */
        if (buffer[0] == '\004')          
          break;
        else{
          //zapísanie do druhého terminálu
          write(fd, &buffer, nacitane);
        }   
      }
.            
      tcsetattr(ttyZariadenie, TCSANOW, &term_old);
      printf("obnovené pôvodné nastavenia\n");
    }
  }

  return 0;
}

Aktivita - režimy - o4-2

Otázka 4.2

Ktorý režim použijete, ak chcete umožniť používateľovi upraviť vstup, kým ho dostane program?

Odoslať odpoveď
Správna odpoveď: kanonický
Nesprávna odpoveď

Aktivita - režim - u4-1, u4-2

V nekanonickom režime je zaujímavé aj nastavenie min a time.

struct termios term_new;

term_new.c_cc[VMIN] = MIN;   // počet bajtov, znakov
term_new.c_cc[VTIME] = TIME; // 1s TIME = 10

Úloha 4.1

Upravte program tak, aby čakal na načítanie 5 znakov. Pracujte so súborom u4-1.c.

//u4-1.c
#include <termios.h> 
#include <unistd.h> 
#include <stdio.h>

int main(int argc, char const *argv[])
{
  int ttyZariadenie = STDIN_FILENO;
  struct termios term_old, term_new;
  char buffer[10]; 
  int nacitane;

  if (tcgetattr(ttyZariadenie, &term_old) != 0)
    perror("tcgetattr error");
  else {
    term_new = term_old;
    //vypnutie echa a nastavenie nekanonického režimu
    term_new.c_lflag &= ~( ICANON | ECHO );
    //DOPLNTE nastavenie počtu bajtov, po ktorom sa spusti read
   
    tcsetattr(ttyZariadenie, TCSANOW, &term_new); 
    while(1){
      nacitane = read(ttyZariadenie, &buffer, 255);              
      //ukončenie načítavania C + d */
      if (buffer[0] == '\004')          
       break;
      else{
        //zapísanie do druhého terminálu
       write(ttyZariadenie, &buffer, nacitane);
      }   
    }     
    tcsetattr(ttyZariadenie, TCSANOW, &term_old);
    printf("\nobnovené pôvodné nastavenia\n");
  }

  return 0;
}

Upozornenie

Pre ukončenie programu použite kombináciu CTRL + D hneď po zobrazení výpisu zásobníka. Potom doplnte ďalšie 4 znaky.

Úloha 4.2

Kombinujte rôzne hodnoty a sledujte zmeny v správaní programu - zobrazenie výpisu po zadaí určitého počtu znakov, po uplynutí časového intervalu.

MIN = 0; TIME = 0;
MIN = 0; TIME = 40;
MIN = 4; TIME = 0;
MIN = 4; TIME = 40;

Zdroje

  1. Sofia - Ovládanie zariadení