C

Allikas: Kuutõrvaja

Sissejuhatus.

Ilmselt on programeerimine erinevates programmeerimiskeeltes nagu tavaliste keelte rääkimine. Võib vabalt ära õppida nt. prantsuse keele (st. sõnad ja põhilised grammatikareeglid) kuid ikkagi rääkida prantsuse keelt nagu eesti keelt; või nagu inglise või vene keelt. Sarnane on asi ka programmeerimises. Võib küll tunda C keele süntaksit, aga kui enne on omandatud kõva harjumus Basic'us või Pascal'is programmeeida võib mitte märgata C keelele iseloomulikke võimalusi ning tulemusena on C keele programmid kirjutatud sisuliselt Pascalis kuigi kasutati C süntaksit :) Üks asi, mis on C-le iseloomulik on nt. pointeritega vaba ümberkäimine.

Järgnevas vaatame võimalusi, mida saab kasutada programmeerides C keeles talle kõige loomulikumas keskkonnas: UNIX'is sh Linux'is. Samas peaks siin selginema arusaam protssidest selles mõttes, et

  • kes kelle sünnitab
  • kuidas protsessid saavad omavahel infot vahetada

Kahjuks paljud OS-id ei võimaldagi neid probleeme käisitleda, vähemalt praktiliselt on see neis keeruline.

Fakt on see, et kes on ennast kasvõi linux'isse sisse loginud on kasutanud exec'it ja kes on käsurealt teinud ls on kasutanud neid mõlemat. Niisiis, praktilised asjad. Vaatame näite varal kuidas süsteem töötab.

  • programm - fail infokandljal, mida saaks käivitada
  • protsess - käivitatud faili koopia mälus kusjuures ühte programmi saab mitu korda käivitada. St ühele programmile võib vastata mitu sarnast koopiat mälus.

Igal protsessil on olemas identifikaator (PID) ja parent identificator (PPID). Idee on selles, et kui on olemas protsess, siis peab olema või olnud olema mingi teine protsess mis on ta nö. sünnitanud. Kõneldakse emast ja tütardest. Kui tütar ära tappa jääb ema ellu ja vastupidi (kill PID).

Protsessi PID, PPID ja ps -l

Vaatame kuidas on seotud ja paistavad sissejuhatuse lõpus öeldud väited praktikas.

Eesmärk: loome programmi loputa_while.c ja käivitame ta. Vaatame mis on ta PID ja PPID.

loputa_while.c:

#include<stdio.h>
main()
{
while(1);
return 0;
}

Käivitada on hea

bash# loputa_while &

nii läheb ta kohe backgroundi

Käsu ps -l abil peaks PID ja PPID näha olema

bash# ps l
 FLAGS   UID   PID  PPID PRI  NI   SIZE   RSS WCHAN       STA TTY TIME COMMAND
100000     0   888   142  12   0    828   184             R   p0  0:23 loputa_while
100000     0   889   142  13   0    828   184             R   p0  0:17 loputa_while
bash# 

Antud juhul on käivitatud programm kahel korral (PID 888, 889); xterm'i aken on 141 milles on bash 142. Pange tähele, et PPID on mõlemal loputa_while'l sama.

Ja tappa saab nad:

bash# kill 888
bash# kill 889

või

bash# killall loputa_while

Ühe programi seest teise käivitamine - system()

Näide 2:

Kutsume programmi seest teise programmi välja:

Illusreerime juhtumit kahe programmi koos kasutamisega

ma_magan.c

#include<stdio.h>
main()
{
printf("hrr.. hrrr....\n");
sleep(5);
return 0;
}

tegeleb_magajaga.c

#include<stdio.h>
main()
{
printf("Siin on magajaga tegeleja algus ..\n");
system("ma_magan");
printf("Ja siin on magajaga tegeleja lõpp\n");
return 0;
}

Kui need samas kataloogin ära kompilleerida ja viimane tööl panna peaks juhtuma järgmine:

bash# tegeleb_magajaga
Siin on magajaga tegeleja algus ..
hrr.. hrrr....
(paus 5 sekundit)
Ja siin on magajaga tegeleja lõpp
bash#

Loo moraal on selles, et seni kuni system() 'i täidetakse peab allpool olev programmi osa ootama. Vaadake mida teevad PID ja PPID'd (seda peab tegema teises aknas kui programmid veel töötavad):

bash# ps l
 FLAGS   UID   PID  PPID PRI  NI   SIZE   RSS WCHAN       STA TTY TIME COMMAND
100100     0   949   142   8   0    832   232 setitimer   S   p0  0:00 tegeleb_magajaga
100100     0   950   949   9   0    832   232 dump_fpu    S   p0  0:00 ma_magan

Näeb, et tegeleb _magajaga on emaks ma_magan 'ile.


Ühe programi seest teise käivitamine - fork()

vaatame aga ka teist võimalust (huvitavamat ?) teist programmi esimesest välja kutsuda:

#include<stdio.h>
main()
{
    int pid, status;
    pid = fork();
    if (pid == -1)
        {
            printf("jama\n"); exit(1);
        }

    if (pid == 0)
        { /* Juurde tekkinud protsess mille PID'i teab ema */
            sleep(1);
            printf("Mina olen tütar\n");
            sleep(4);
        }

    else
        {
            wait(&status);  /* Ema ootab kuni tütar oma tegevuse lõpetab */
            printf("Mina olen emme, tütre status: %d tütre PID: %d\n", status, pid);
        }

    return 0;
}

Kui siin programmi täitmise ajal vaadata PID ja PPID'e näeb:

bash# ps l
 FLAGS   UID   PID  PPID PRI  NI   SIZE   RSS WCHAN       STA TTY TIME COMMAND
100000     0   999   142   9   0    828   184 setitimer   S   p0  0:00 f1
   140     0  1000   999   9   0    832   232 dump_fpu    S   p0  0:00 f1

Pange tähele, et on tekkinud kaks f1'te: ema ja tütar.

Tütre nö. sünnitab käsk

pid = fork()

kus juures pid on muutuja mis omab ühe programmis sees juskui kahte väärtust ?? Fakt on see, et toodud konstruktsioon tekitab juurde teise (nö. tütre) protsessi.

Mis aga on oluline, ema ei pruugi tütrse tegevuse lõppu ära ootama!

kommenteerige rida wait .. välja:

   /* wait(&status);  Ema ootab kuni tütar oma tegevuse lõpetab */

Tulemusena jätkab ema kohe edasi ja tütar samal ajal kah. Siin võib kiirust reguleerida nt. sleep'iga.

Kui see asi teil toimib, siis olete võimelised küll väga primitiivsel kombel,aga siiski looma protsesse C keeles programmeerides. wait'i mitte väljakommenteerimine on analoogiline käsurealt nt. 'updatedb' andmisega; kui ta ga väljakommenteerida siis on see sama hea kui 'updatedb &' - saab prompti kohe tagasi.


Ühe programmi teisega asendamine execl()

Ja veel, on kõelemata execl'i roll. Nimelt, linux suudab asendada ühe protsessi teisega, kusjuures teine omandab esimese PID'i ja veel palju muid esimese omadusi.

Eesmärk: loome neli programmi mis kutsuvad üksteist ringiratast välja, ad infinitum

emme.c

#include<stdio.h>
main()
{
printf("Mina olen emme, muutun tütreks, onuks, pojaks ja tagasi emmeks ..\n");
sleep(3);
execl("/root/net/tytar", "tytar", 0);
return 0;
}

tytar.c

#include<stdio.h>
main()
{
printf("Mina olen tütar ..\n");
sleep(3);
execl("/root/net/onu", "onu", 0);
return 0;
}

onu.c

#include<stdio.h>
main()
{
printf("Mina olen onu ..\n");
sleep(3);
execl("/root/net/poeg", "poeg", 0);
return 0;
}

poeg.c

#include<stdio.h>
main()
{
printf("Mina olen poeg ..\n");
sleep(3);
execl("/root/net/emme", "emme", 0);
return 0;
}

Kui nüüd emme (tegelikult ükskõik milline neist) käivitada, siis algab ring pihta ja jälgides ps -ga võib veenduda,et sama PID number identifitseerib vaheldumisi erinevad programme. Paraku pole ma suutnud mõelda välja sellisele asjale praktilist rakendust aga kas arvutitel üldse on praktilist rakendust? Küllap on.

Midagi sarnast toimub arvutisse sisselogimisel:

- ekraanil on ees login prompt mile viis sinna getty - peale ime sisestust asenub getty programmmiga login mis tegeleb teie kasutajatunnusega - lõpuks asendub login bash'iga või mis iganes teie shelliks on pandud (nt. pine, passwd).


fifo - named pipe

Pobleemiks on saada kaks samal ajal töötavat programmi panna omavahel infot vahetama. Paraku küll ühes suunas, st. üks annab, teine saab.

Muide võtet 'who | wc -l' 'i nimetatakse unnamed pipe'ks. See langeb samuti kategooriasse kahe programmi vaheline info vahetamine. Tegelikult saab fifo-ga kombineerida ka enam kui kaks programmi suhtlema panna.

Teeme ühe näite:

fifo_server.c:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO "fifo.1"
#define MAXBUFF 80
main()
{
int readfd, n;
char buff[MAXBUFF];
if (mknod(FIFO, S_IFIFO | 0666, 0) < 0) {
        printf("Ei saa FIFOt luua\n"); exit(1);}

if ((readfd=open(FIFO, O_RDONLY)) < 0) {
        printf("Ei saa avada FIFOt\n"); exit(1);}

while ((n=read(readfd, buff, MAXBUFF)) > 0)
        {
        if (write(1, buff, n) !=n)
        {
                printf("Sisestamisel viga\n"); exit(1);
       }
        }
       close (readfd);
       exit(0);
}

fifo_client.c:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO "fifo.1"
main()
{
int writefd, n;
if ((writefd=open(FIFO, O_WRONLY)) < 0){
        printf("Ei saa avada FIFOt\n"); exit(1);}

if (write(writefd, "Tere Maailm!\n", 13) != 13){
       printf ("Kirjutamisel viga\n"); exit(1);}

close(writefd);
if (unlink(FIFO) < 0){
        printf("Ei saa unlinkida FIFOt\n"); exit(1);}

exit(0);
}

Kasutamiseks tuleb nad mõlemad ära kompilleerida ja ühes X-i aknas käivitada serv ja seejärel teises klient. Tulemusena peaks serveri aknasse ilmuma tekst 'Tere Maailm!'


Message

Järgnev tehnika võimaldab samuti protsesside vahel infot vahetada. Kopileerige ära, käivitage enne ühes aknas server ja teises klient ning veenduge, et midagi toimub!

mesg.h:

#define MAXBUFF 80
#define PERM 0666
typedef struct our_msgbuf {
        long mtype;
        char buff[MAXBUFF];
        } Message;

mess_server.c:

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include "mesg.h"

main()
{
    Message message;
    key_t   key;
    int msgid, length, n;

    if ((key=ftok("server", 'A')) < 0)
        {
            printf("jama 1\n"); exit(1);
        }

    message.mtype=1;

    if ((msgid=msgget(key, PERM | IPC_CREAT)) < 0)
        {
            printf("jama 2\n"); exit (1);
        }

    while (1)
        {
            n=msgrcv(msgid, (void *) &message, sizeof(message), message.mtype, 0);
            if (n > 0)
                {
                    if (write(1, message.buff, n) !=n )
                        {
                            printf("jama 3\n"); exit (1);
                       }
               }
           else
               {
                   printf("jama 4\n"); exit (1);
               }
       }
   exit(0);
}

mess_client.c:

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include "mesg.h"

main()
{
    Message message;
    key_t   key;
    int msgid, length, i=0;
    char mess[40];

    message.mtype=1;

    if ((key=ftok("server", 'A')) < 0)
        {
            printf("jama 1\n"); exit(1);
        }

   if ((msgid=msgget(key, 0)) < 0)
       {
           printf("jama 2\n"); exit (1);
       }

   while (i < 7)
       {
           i=i++;
           sleep(1);
           sprintf(mess, "%d. No on ..\n", i);
           if ((length=sprintf(message.buff, mess)) < 0)
               {
                   printf("jama jalle\n"); exit (1);
               }

           if (msgsnd(msgid, (void *) &message, length, 0) !=0)
               {
                   printf("jalle jamm\n"); exit(1);
               }
       }
   if (msgctl(msgid, IPC_RMID, 0) < 0)
       {
           printf("jah, jalle\n"); exit(1);
       }

    exit(0);
}

Socets - AF_INET

Ka see on tehnika, mis võimaldab protsesside vahel infot vahetada: need protsessid aga ei pruugi toimuda ühes samas masinas. Praktilistest rakendustest võiks mainida

- ftpd ja ftp
- httpd ja lynx
- inetd ja telnet

Tundes tehnikat saab selles liinis palju korda saata. Mõned autorid toovad paralleeli telefoniga helistamisest. Järgnevat võiks saata selline ettekujutus: On kaks arvutit, IP numbritega ja töötava võrgutarkvaraga (st. n - seeria); neil kummalgi on selliseid infot sisse-väljalaskvaid mulke nö. porte ehk soketeid 2^16 tükki. Esimesed 1024 on reserveeritud ja nende kasutust näeb failist /etc/services:

ftp             21/tcp
# 22 - unassigned
telnet          23/tcp
# 24 - private
smtp            25/tcp          mail
# 26 - unassigned
time            37/tcp          timserver
time            37/udp          timserver
rlp             39/udp          resource        # resource location
nameserver      42/tcp          name            # IEN 116
whois           43/tcp          nicname
domain          53/tcp          nameserver      # name-domain server
domain          53/udp          nameserver
mtp             57/tcp                          # deprecated
bootps          67/tcp          # BOOTP server
bootps          67/udp
bootpc          68/tcp          # BOOTP client
bootpc          68/udp
tftp            69/udp
gopher          70/tcp          # Internet Gopher
gopher          70/udp
rje             77/tcp          netrjs
finger          79/tcp
www             80/tcp          http    # WorldWideWeb HTTP
www             80/udp                  # HyperText Transfer Protocol
link            87/tcp          ttylink
kerberos        88/tcp          krb5    # Kerberos v5
kerberos        88/udp
supdup          95/tcp
# 100 - reserved
hostnames       101/tcp         hostname        # usually from sri-nic
iso-tsap        102/tcp         tsap            # part of ISODE.
csnet-ns        105/tcp         cso-ns  # also used by CSO name server
csnet-ns        105/udp         cso-ns
rtelnet         107/tcp         # Remote Telnet
rtelnet         107/udp
pop2            109/tcp         postoffice      # POP version 2
pop2            109/udp
pop3            110/tcp         # POP version 3
pop3            110/udp

Eesmärk:

- panna ühte arvutisse kuulama server mõne pordi taha ja
- teisest arvutist lasta mingi pordi kaudu tolle esimese masina serveri porti klient peale.

Kui server on valmis ja töötab, siis saab teda kontrollida kasutades kliendina nt. netscape'i, lynx'i või telnet'i.

Niisiis,

socket_server.c:

/*
** server.c -- a stream socket server demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>

#define MYPORT 3491    /* the port users will be connecting to */

#define BACKLOG 10     /* how many pending connections queue will hold */

main()
{
    int sockfd, new_fd, i;  /* listen on sock_fd, new connection on new_fd */
    struct sockaddr_in my_addr;    /* my address information */
    struct sockaddr_in their_addr; /* connector's address information */
    int sin_size;
    char teele[30];
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
         perror("socket");
         exit(1);
    }

    my_addr.sin_family = AF_INET;         /* host byte order */
    my_addr.sin_port = htons(MYPORT);     /* short, network byte order */
    my_addr.sin_addr.s_addr = INADDR_ANY; /* automatically fill with my IP */
    bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */

    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
   }

    while(1) {  /* main accept() loop */
        sin_size = sizeof(struct sockaddr_in);
        if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
            perror("accept");
            continue;
        }
        printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
        if (!fork())

        { /* this is the child process */

        for (i=0; i < 10; i++)
        {
                sprintf(teele, "Hallo World %d
\n", i); if (send(new_fd, teele, 18, 0) == -1) perror("send"); } close(new_fd); exit(0); } close(new_fd); /* parent doesn't need this */ while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up all child processes */ } }

serverit saab kontrollida nt. nii:

bash# telnet  3491
bash# lynx localhost:3491
Netscape location: locahost:3491

Või sellise klient programmiga:

socket_client.c:

/*
** client.c -- a stream socket client demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define PORT 3491    /* the port client will be connecting to */

#define MAXDATASIZE 100 /* max number of bytes we can get at once */

int main(int argc, char *argv[])
{
    int sockfd, numbytes;
    char buf[MAXDATASIZE];
    struct hostent *he;
    struct sockaddr_in their_addr; /* connector's address information */

    if (argc != 2) { 
        fprintf(stderr,"usage: client hostname\n");
        exit(1);
    }

    if ((he=gethostbyname(argv[1])) == NULL) {  /* get the host info */
        perror("gethostbyname");
        exit(1);
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

  their_addr.sin_family = AF_INET;         /* host byte order */
    their_addr.sin_port = htons(PORT);     /* short, network byte order */
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    bzero(&(their_addr.sin_zero), 8);        /* zero the rest of the struct */

    if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
        perror("connect");
        exit(1);
   }

   if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
       perror("recv");
       exit(1);
   }

   buf[numbytes] = '\0';

   printf("Received: %s",buf);

   close(sockfd);

   return 0;
}

Kasutamine: käivitage ühes X-i aknas server ja teises klient ning veenduge, et suhtlemine toimub. Modifitseerige ja püüdke aru saada :)

Seda ma tahaks komenteerida kuid ei oska :(

aga vaadake orginaalallikat sisukorrast!