C
Sisukord
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!