Programowanie w systemie LINUX
Igor Skalski
skalgo@cto.gda.pl
1999..2000
v.0.1.0 (md) (27.01.2002)

Spis treści

1. Kompilacja
2. Pobieranie argumentów z linii poleceń
3. Wprowadzanie danych z klawiatury
4. Wyświetlanie tekstu na ekranie
5. Generowanie dźwięków
6. Filtr pliku tekstowego
7. Zwracanie wartości liczbowej do systemu
8. Wskaźniki
9. Alokacja i zwalnianie pamięci
10. Odczyt i zapis bloku danych
11. Odczyt zawartości katalogu
12. Czas systemowy
13. Wykonanie polecenia systemowego
14. Biblioteka ncurses
14.1. Inicjalizacja ncurses
14.2. Zakończenie pracy z ncurses
14.3. Podstawowe funkcje biblioteki
14.4. Program korzystający z biblioteki ncurses
15. SVGAlib
16. X Window
17. Literatura

1. Kompilacja

Przedstawione programy zostały napisane w języku C i były kompilowane kompilatorem gcc w systemie Linux z jądrem v. 2.0.29 za pomocą instrukcji:


gcc -ansi -pedantic -Wall filename.c 

Wynikiem tak dokonanej kompilacji jest utworzenie pliku binarnego o nazwie a.out, który można wywołać wydając polecenie:


./a.out

2. Pobieranie argumentów z linii poleceń

Przedstawiony program demonstruje wykorzystanie argumentów przekazanych do programu w linii poleceń.

W początku programu znajdują się instrukcje przyłączające pliki nagłówkowe dwóch standardowych bibliotek. Pliki te powinny być przyłączone do każdego programu. Parametrami wywołania funkcji main są: zmienna argc określająca liczbę argumentów przekazanych do programu i wskaźnik do tablicy argv zawierającej argumenty w postaci zmiennych łańcuchowych (ang. null terminated string). Parametry te powinny wystąpić w przedstawionej kolejności. Do programu w systemie Linux zostaje przekazany co najmniej jeden argument (umieszczony w tablicy argv pod indeksem zerowym): nazwa wywoływanego programu przedstawiona w sposób, w jaki program ten został wywołany (argument ten może więc zawierać ścieżkę dostępu lub jej fragment). Sposób przekazywania argumentów do programu w innych systemach UNIX (``UNIX'' jest nazwą zastrzeżoną AT&T Bell Laboratories) może się różnić od przedstawionego w tym miejscu.

Program wyświetla wartość zmiennej argc oraz wszystkie argumenty zawarte w tablicy argv ujęte w znaki cudzysłowu.


#include<stdio.h>
#include<stdlib.h>

void main(int argc,char *argv[])
{
  char i;
  printf("Arguments number: %d\n",argc);
  for(i=0;i<argc;i++)
    printf("Argument No %d is \"%s\"\n",i,argv[i]);
}

3. Wprowadzanie danych z klawiatury

Standardowe zestawy biblioteczne języka C zapewniają bardzo ograniczone możliwości współpracy z układem klawiatury (albo termianala). Niżej przedstawiono UNIX'owe wersje funkcji kbhitgetch. Funkcje wykorzystują zmienną globalną buff.


char buff=0;
 
int kbhit()
{
   int i;
   struct termio w,v;
   if(buff)return(1);
   ioctl(fileno(stdin),TCGETA,&w);
   v=w;
   v.c_iflag=v.c_lflag=0;
   v.c_cc[VMIN]=0;
   v.c_cc[VTIME]=1;
   ioctl(fileno(stdin),TCSETA,&v);
   i=read(fileno(stdin),&buff,1);
   ioctl(fileno(stdin),TCSETA,&w);
   return(r>0);
}

int getch()
{
   char c;
   struct termio w,v;
   if(buff){c=buff;buff=0;return(c&127);}
   ioctl(fileno(stdin),TCGETA,&w);
   v=w;
   v.c_iflag=v.c_lflag=0;
   v.c_cc[VMIN]=1;
   v.c_cc[VTIME]=0;
   ioctl(fileno(stdin),TCSETA,&v);
   while(read(fileno(stdin),&c,1)!=1);
   ioctl(fileno(stdin),TCSETA,&w);
   return(c&127);
}

4. Wyświetlanie tekstu na ekranie

Wysłanie znaków do wyjścia standardowego stdout (standardowo skojarzonego z ekranem) zazwyczaj nie powoduje natychmiastowego pojawienia się ich na ekranie. Wyjście to jest bowiem buforowane. Natychmiastowe wyprowadzenie tekstu może zostać wymuszone przez:

5. Generowanie dźwięków

Komputer PC jest wyposażony w układ pozwalający na generowanie dźwięków słyszalnych z wbudowanego głośnika. Układ ten jest w Linux-ie częścią konsoli i może być obsługiwany na dwa sposoby za pomocą funkcji ioctl.

Pierwszy sposób polega na włączaniu dżwięku z ustaleniem jego częstotliwości, przy czym wyłączenie dźwięku następuje po przesłaniu wartości zerowej:


#include<stdio.h>
#include<stdlib.h>
#include<sys/ioctl.h>
#include<linux/kd.h>

int main()
{
  FILE *fd;
  int frequency=800; /* Hz */
  if((fd=fopen("/dev/console","w"))==NULL)return(1);
  ioctl(fileno(fd),KIOCSOUND,1190000/frequency);
  sleep(1);
  ioctl(fileno(fd),KIOCSOUND,0);
  return(0);
}

W drugim przypadku funkcję wywołuje się jednorazowo, przekazując jej informację o częstotliwości i czasie trwania dźwięku, przy czym funkcja ioctl nie blokuje działania programu na czs trwania dźwięku.


#include<stdio.h>
#include<stdlib.h>
#include<sys/ioctl.h>
#include<linux/kd.h>

int main()
{
  FILE *fd;
  int frequency=800; /* Hz */
  int duration=1000; /* ms */
  long int var=duration;
  if((fd=fopen("/dev/console","w"))==NULL)return(1);
  var<<=16;
  var+=1190000/frequency;
  ioctl(fileno(fd),KDMKTONE,var);
  if(fclose(fd))return(1);
  return(0);
}

6. Filtr pliku tekstowego

Przykładem prostego fltru jest program untab.c zamieniający znaki tabulacji (9) na odpowiednią liczbę znaków spacji.


#include<stdio.h>
#include<stdlib.h>

void main()
{
  int c,n=0;
  while((c=getchar())!=EOF)
  {
    if(c==9){while((n++)%9)putchar(' ');}
    else{if((c==10)||(c==13))n=0;putchar(c);n++;}
  }
}

Program, który czyta dane ze standardowego urządzenia wejściowego i zapisuje do standardowego urządzenia wyjściowego należy wywołać w następujący sposób:


untab < inputfile > outputfile

7. Zwracanie wartości liczbowej do systemu

W zależności od sposobu zakończenia pracy, program powinien zwracać do systemu odpowiednią wartość liczbową po zakończeniu działania programu wartość ta jest zawarta w zmiennej shell-a $? i można ją odczytać n.p. za pomocą polecenia shell-a echo $?. Przyjęto, że zerowa wartość oznacza zakończenie bezbłędne. Każda inna wartość oznacza wystąpienie błędu. Zwracane wartości zazwyczaj określają w sposób szczegółowy rodzaj błędu i są charakterystyczne dla danego programu. W programie przedstawiono sposób zwrócenia informacji o wystąpieniu błędu nr 1.


#include<stdio.h>
#include<stdlib.h>

int main()
{
  exit(1);
}

exit powoduje bezwzględne zakończenie pracy programu. W funkcji main dla przerwania pracy programu możliwe jest również zastosowanie instrukcji return.

8. Wskaźniki

Wskaźnik jest specyficzną zmienną przechowującą adres i typ (wskaźniki typu void zawierają wyłącznie adresy) innej zmiennej (... lub innego obiektu istniejącego w pamięci komputera).

Po zadeklarowaniu tablicy t zawierającej elementy typu int} oraz wskaźnika p wskazującego na zmienną typu int:


int t[5],*p;

i zainicjowaniu wskaźnika (przypisaniu wskaźnikowi adresu pierwszego elementu tablicy):


p=&t;

wartość elementu o indeksie 0 można zmienić w sposób bezpośredni, lub pośredni -- z wykorzystaniem wskaźnika:


t[0]=0; *p=0; 

Powyższe zapisy są równoważne.

Zmiany wartości elementu o indeksie 1 można teraz dokonać na różne sposoby:


t[1]=0; *(p+1)=0; *(p++)=0

Inkrementacja wskaźnika jest jednoznaczna ze zwiększeniem przechowywanego w nim adresu o rozmiar typu (!).

Po zadeklarowaniu struktury:


struct s 
{ 
  int x; 
  int y; 
} 

i wskaźnika do niej:


struct s *p;

oraz zainicjowaniu wskaźnika:


p=&s;

możliwe jest odwołanie się do elementu struktury w następujący sposób:


(*p).x=0

lub


p->x=0

9. Alokacja i zwalnianie pamięci

W sytuacjach, gdy określenie rozmiaru niezbędnej pamięci nie jest możliwe na etapie programowania, koniecznym się staje zastosowanie funkcji dynamicznie przydzielających i zwalniających pamięć: mallocfree. W przykładowym programie funkcję malloc wywołano z argumentem określającym rozmiar zdefiniowanej wcześniej struktury data. malloc zwraca wskaźnik do zaalokowanej pamięci lub - w przypadku niepowodzenia - wartość NULL. W końcowej części programu zaalokowana pamięć zostaje zwolniona za pomocą funkcji free, której argumentem jest wskaźnik do zwalnianej pamięci.


#include<stdio.h>
#include<stdlib.h>
int main()
{
  typedef struct _data
  {
    char data1;
    short int data2;
    long int data3;
  }data;

  data *dataptr;

  if(!(dataptr=(data*)malloc(sizeof(data))))exit(1);
  /* ... */

  free(dataptr);
  exit(0);
}

W celu wstępnego wyzerowania zaalokowanej pamięci możliwe jest zastosowanie w przedstawionym programie funkcji calloc w postaci:


if(!(dataptr=(data*)calloc(1,sizeof(data))))exit(1); 

Funkcja calloc alokuje pamięć dla tablicy składającej się z elementów, których liczbę określa pierwszy argument, a rozmiar jednego elementu jest określony argumentem drugim.

10. Odczyt i zapis bloku danych

Przed dokonaniem dowolnej operacji plikowej należy dany plik otworzyć (lub - jeżeli plik nie istnieje - utworzyć). Służy do tego funkcja FILE *fopen(char *filename, char *mode), gdzie filename jest wskaźnikiem do tekstu zawierającego nazwę pliku (która może być poprzedzona ścieżką dostępu), a mode jest wskaźnikiem do tekstu określającego tryb działania funkcji:

Funkcja open zwraca deskryptor pliku (lub strumienia) albo wartość NULL w przypadku niepowodzenia.

Do odczytu i zapisu bloku danych stosowane są funkcje:


size_t fread(void *ptr, size_t size, size_t n, FILE *fd)
size_t fwrite(void *ptr, size_t size, size_t n, FILE *fd)

gdzie:

Powyższe funkcje zwracają wartość będącą liczbą odczytanych lub zapisanych obiektów.

Poniżej przedstawiono fragment programu, w którym po otwarciu pliku (r+ - otwarcie do odczytu i zapisu) i przesunięciu wskaźnika do setnego bajtu następuje odczyt bloku danych i wpisanie tych danych do zmiennej variable. Następnie wskaźnik pozycji w pliku zostaje ponownie umieszczony w poprzednim miejscu i - po zapisaniu zawartości zmiennej na dysku - następuje zamknięcie pliku. Wszystkie operacje są prowadzone z kontrolą poprawności działania - wystąpienie błędu powoduje powrót do funkcji nadrzędnej. Przed opuszczeniem funkcji wskazane jest dokonanie próby zamknięcia pliku w celu zwolnienia zajętego deskryptora.


FILE *fd;

/* ... */
if(!(fd=fopen("name","r+")))return(1);
if(fseek(fd,100,SEEK_SET))return(2);
if(fread(&variable,1,sizeof(variable),fd)!=sizeof(variable))return(3);
/* ... */
if(fseek(fd,100,SEEK_SET))return(4);
if(fwrite(&variable,1,sizeof(variable),fd)!=sizeof(variable))return(5);
if(fclose(fd))return(6);
/* ... */

11. Odczyt zawartości katalogu

Niżej przedstawony program wyświetla zawartość katalogu wskazanego ścieżką dostępu zawartą w zmiennej directory. Program wypisuje rozmiar każdego pliku. W przypadku wystąpienia dowiązania, program wyświetli informacje dotyczące pliku, na który dowiązanie wskazuje. Brak pliku, na który wskazuje dowiązanie spowoduje przerwanie pracy tego programu.


#include<stdio.h>
#include<dirent.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>

void main()
{
  DIR *dirp;
  struct dirent *direntp;
  struct stat statbuf;

  char directory[]=".";

  if(!(dirp=opendir(directory)))exit(1);
  if(chdir(directory))exit(2);

  while(direntp=readdir(dirp))
  {
    if(stat(direntp->d_name,&statbuf)==-1)exit(3);
    if(S_ISREG(statbuf.st_mode))
      printf("file: %s, size: %ld\n",direntp->d_name,statbuf.st_size);
    else
    if(S_ISDIR(statbuf.st_mode))
      printf("directory: %s\n",direntp->d_name);
  }
  closedir(dirp);
}

12. Czas systemowy

Do odczytania czasu systemowego używa się trzech funkcji zawartych w bibliotece dołączanej time.h.

time_t time(time_t *tptr) zwraca liczbę sekund, jaka upłynęła od 00:00:00 GMT (ang. Greenwich Mean Time) 01.01.1970 roku lub wartość -1, jeżeli informacja ta jest niedostępna. Jeżeli wskaźnik tptr nie ma wartości NULL, a więc wskazuje na zmienną typu time_t, wartość zwracana przez funkcję jest równocześnie umieszczana w tej zmiennej. W celu wyświetlenia czasu w dowolnie wybranym formacie, dane uzyskane za pomocą funkcji time należy przepisać - stosując funkcję


struct tm *localtime(const time_t *time) 

do struktury typu tm (równocześnie przekształcając ten czas do czasu lokalnego) oraz sformatować wywołując funkcję


size_t strftime(char *s,size_t maxs,
                const char *fmt, const struct tm *tmptr)

gdzie: s jest wskaźnikiem do łańcucha znaków, maxs określa maksymalną długość tego łańcucha, fmt ustala zastosowany format zapisu, a tmptr jest wskaźnikiem do struktury tm.

Format fmt jest tworzony podobnie jak w przypadku funkcji printf i może zawierać m.in.:

Funkcja strftime zwraca wartość określającą długość utworzonego łańcucha znaków: s (łączne ze znakiem \n) albo wartość NULL w przypadku, gdy wartość maxs nie umożliwia utworzenia prawidłowego łańcucha.

Przykład wykorzystania opisanych procedur zawarto w programie:


#include<stdio.h>
#include<time.h>

main()
{
  time_t t;
  struct tm *tmptr;
  char s[80];

  time(&t);
  tmptr=localtime(&t);    
  strftime(s,80,"%d.%m.%y %H:%M:%S",tmptr);
  printf("\n%s\n",s);
}

13. Wykonanie polecenia systemowego

[...]

14. Biblioteka ncurses

Biblioteka ekranowa ncurses (Szczegółowy opis biblioteki ncurses znajduje się w dostępnej w sieci Internet pracy `Linux programmer's Guide' tworzonej w ramach `Linux Documentation Project') jest wersją Unix-owej biblioteki curses stworzoną dla systemu Linux. ncurses zarządza obsługą terminali znakowych.

14.1. Inicjalizacja ncurses

Przed użyciem jakiejkolwiek funkcji z ncurses należy dokonać inicjalizacji tej biblioteki. Inicjalizacji dokonuje się za pomocą funkcji initscr(), która m.in. odczytuje z bazy terminfo dane dotyczące zastosowanego terminala, alokuje pamięć i inicjalizuje zmienne LINES i COLS, określające odpowiednio: liczbę lini i kolumn terminala).

initscr() zwraca wskaźnik do struktury stdscr (która jest domyślnym argumentem wielu wywoływanych funkcji lub - w przypadku wystąpienia błędu - zwraca wartość ERR (-1).

14.2. Zakończenie pracy z ncurses

Przed zakończeniem pracy programu wykorzystującego bibliotekę ncurses koniecznym jest wywołanie funkcji endwin().

14.3. Podstawowe funkcje biblioteki

Za pomocą funkcji z biblioteki ncurses w buforze obrazu (w pamięci komputera) jest tworzony obraz, którego wyświetlenia na fizycznym ekranie terminala dokonuje się wywołując funkcję refresh() - dla okna stdscr albo wrefresh(win) - dla okna określonego wskaźnikiem win.

Najczęściej stosowanymi funkcjami ncurses są:

(Funkcje, których nazwa rozpoczyna się od znaków mv wymagają podania współrzędnych ekranu yx. Znak w umieszczony w początkowej części nazwy funkcji oznacza konieczność określenia okna za pomocą wskaźnika do struktury typu WINDOW.)

14.4. Program korzystający z biblioteki ncurses

Kompilując programy korzystające z biblioteki ncurses należy w linii poleceń kompilatora gcc umieścić zapis: -lncurses, n.p.:


gcc -lncurses filename.c

(...)

15. SVGAlib

Program wykorzystujący funkcje należące do biblioteki SVGALIB powinien zawierać dyrektywę:


#include<vga.h> 

Podczas kompilacji do programu należy dołączyć bibliotekę vga:


gcc -lvga filename.c

Biblioteka SVGAlib zawiera funkcje (przedstawiono najczęściej wykorzystywane funkcje);


        TEXT          -  0
        G640x200x16   -  2
        G640x350x16   -  3
        G640x480x16   -  4
        G640x480x2    -  9
        G640x480x256  - 10
        G800x600x256  - 11
        G1024x768x256 - 12 
        G800x600x16   - 29
        G1024x768x16  - 30
        G720x348x2    - 32


        0 - black     8 - bright black
        1 - blue      9 - bright blue
        2 - green    10 - bright green
        3 - cyan     11 - bright cyan
        4 - red      12 - bright red 
        5 - magenta  13 - bright magenta 
        6 - brown    14 - bright brown 
        7 - white    15 - bright white


#include<stdio.h>
#include<stdlib.h>
#include <vga.h>

int main()
{
  if(vga_init())return(1);
  if(vga_hasmode(G320x200x256)!=1)return(2);
  if(vga_setmode(G320x200x256))return(3);
  vga_setcolor(int color);
  int vga_drawpixel(int x,int y)
}

16. X Window

Niżej przedstawiony program został skompilowany za pomocą polecenia


cc -ansi -pedantic -Wall -LusrŻ11R6łib -lX11 filename.c 

Program otwiera okno w systemie X Window (System `X Window' został zaprojektowany w Massachusetts Institute of Technology. `X Window System' jest nazwą zastrzeżoną X Consortium, Inc.) oraz demonstruje wykorzystanie podstawowych funkcji umożliwiających rysowanie punktów, linii oraz pisanie tekstów. Zamknięcie okna i zakończenie pracy programu następuje po naciśnięciu dowolnego klawisza.


#include<stdio.h>
#include<stdlib.h>
#include<X11/Xlib.h>

int main()
{
  Display     *display;
  Window      window;
  XEvent      event;
  GC          gc;
  int         screen;
  long int    border,background;

  if(!(display=XOpenDisplay(NULL)))
    {fprintf(stderr,"XOpenDisplay failed.\n");exit(1);};
  screen=DefaultScreen(display);
  background=WhitePixel(display,screen);
  border=BlackPixel(display,screen);
  window=XCreateSimpleWindow(
         display,RootWindow(display,screen),0,0,200,200,30,
         BlackPixel(display,screen),WhitePixel(display,screen));
  XSelectInput(display,window,ExposureMask|KeyPressMask);
  XStoreName(display,window,"Sample Window");
  gc=XCreateGC(display,window,0,NULL);
  XSetForeground(display,gc,BlackPixel(display,screen));
  XSetBackground(display,gc,WhitePixel(display,screen));
  XMapWindow(display,window);
  XFlush(display);

  while(1)
  {
    XNextEvent(display,&event);
    switch(event.type)
    {
      case Expose:
      {
        unsigned int maxx,maxy;
        char *text1=" Text #1 ";
        char *text2=" Text #2 ";
        {
          Window w;
          int i;
          unsigned int u;
          XGetGeometry(display,window,&w,&i,&i,&maxx,&maxy,&u,&u);
        }
        XDrawPoint(display,window,gc,maxx/2,maxy/2);
        XDrawRectangle(display,window,gc,10,10,maxx-20,maxy-20);
        XDrawLine(display,window,gc,20,26,maxx-20,26);
        XDrawString(display,window,gc,40,30,text1,strlen(text1));
        XDrawLine(display,window,gc,20,46,maxx-20,46);
        XDrawImageString(display,window,gc,40,50,text2,strlen(text2));
        while(XCheckTypedWindowEvent(display,window,Expose,&event));
        break;
      }

      case KeyPress:
        XFreeGC(display,gc);
        XDestroyWindow(display,window);
        XCloseDisplay(display);
        exit(0);
    }
  }
}

W programach pracujących w X Window do czyszczenia ekranu wykorzystywana jest funkcja


XClearWindow(display,window)

17. Literatura

[1] ``Język ANSI C'', B. W. Kernighan, D. M. Ritchie, Wydawnictwa Naukowo -- Techniczne, Warszawa, 1997.

[2] ``Sztuka programowania w języku C'', R. Jones, I. Steward, Wydawnictwa Naukowo -- Techniczne, Warszawa, 1992.

[3] ``Biblia Turbo C++'', N. Barkakati, Oficyna Wydawnicza LT&P.

[4] ``The Linux Programmer's Guide'', S. Goldt, S. van der Merr, S. Burkett, M. Welsh, Publikacja dostępna w sieci Internet.

[5] ``C Traps and Pitfalls'', A. Koenig, Publikacja dostępna w sieci Internet.

[6] ``ANSI C for programmers on UNIX Systems'', T. Love, Publikacja dostępna w sieci Internet.

[7] ``Recommended C Coding Standards'', L. W. Cannon, R. A. Elliott, L. W. Kirchoff, J. H. Miller, J. M. Milner, R. W. Mitze, E. P. Schan, N. O. Whittington, H. Spencer, D. Keppel, M. Brader, Publikacja dostępna w sieci Internet.

[8] ``Notes on Writing Portable Programs in C'', A. Dolenc, A. Lemmke, D. Keppel, G. V. Reilly, Publikacja dostępna w sieci Internet.

[9] ``A Quick Tutorial on C Programming using Berkeley UNIX'', P. C. J. Graham, Publikacja dostępna w sieci Internet.

[10] ``A Tutorial on Pointers and Arrays in C'', T. Jensen, Publikacja dostępna w sieci Internet.

[11] ``X Windows Version 11.5, A Concise Description'', T. Love -- CUED, Publikacja dostępna w sieci Internet.


http://www.skalgo.5v.pl