Ciele
- Spoznať, ako komunikovať so zariadením.
- Spoznať rozhranie na nastavenie zariadení.
- Funkcie na ovládanie terminálu.
- 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
Ú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
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 */
}
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
.
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
|
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
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
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
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;