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).
2. 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
100
0 89 1 0
0 1176 632 setitimer S
1 0:00 -bash
100
0 91 1 0
0 840 296 UMSDOS_link S
3 0:00 /sbin/agetty 38400 tty3 li
100
0 92 1 0
0 840 296 UMSDOS_link S
4 0:00 /sbin/agetty 38400 tty4 li
100
0 93 1 0
0 840 296 UMSDOS_link S
5 0:00 /sbin/agetty 38400 tty5 li
100
0 94 1 0
0 840 288 UMSDOS_link S
6 0:00 /sbin/agetty 38400 tty6 li
100100
0 141 1 0 0
2456 1480 posix_lock_ S 1 0:00 xterm -geometry
80x30+90+0
100
0 144 1 0 0
20520 16444 posix_lock_ S 1 2:13 /usr/local/netscape/netsca
100100
0 676 1 0 0
2524 1444 posix_lock_ S 1 0:00 /usr/bin/X11/xterm
-sb -sl
100000
0 128 89 0 0
1132 492 setitimer S 1
0:00 sh /usr/X11/bin/startx
100
0 129 128 0 0
1888 620 setitimer S 1
0:00 xinit /usr/X11R6/lib/X11/x
100
0 131 129 0 0
1128 476 setitimer S 1
0:00 sh /usr/X11R6/lib/X11/xini
100
0 136 131 0 0
1684 796 posix_lock_ S 1 0:04 fvwm
100100
0 138 136 0 0
1496 604 posix_lock_ S 1 0:00 /usr/lib/X11/fvwm/FvwmPage
100
0 142 141 10 0 1184
648 setitimer S p0 0:00 bash
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
100100
0 892 142 12 0
988 376
R p0 0:00 ps l
100100
0 145 144 0 0 12496
2496 posix_lock_ S 1 0:00 (dns helper)
100
0 677 676 0 0
1188 640 setitimer S p1 0:00
-bash
100
0 835 677 0 0
1228 600 UMSDOS_link S p1 0:00 joe f2.c
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
3. Ü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 145 144 3 0 12496
2496 posix_lock_ S 1 0:00 (dns helper)
100
0 677 676 11 0 1188
640 setitimer S p1 0:00 -bash
100100
0 951 677 13 0
988 376
R p1 0:00 ps l
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.
4. Ü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
100
0 142 141 8 0
1184 652 setitimer S p0 0:00
bash
100000
0 999 142 9 0
828 184 setitimer S p0 0:00 f1
100100
0 145 144 0 0 12496
2496 posix_lock_ S 1 0:00 (dns helper)
100
0 677 676 13 0 1188
640 setitimer S p1 0:00 -bash
100100
0 1001 677 15 0 988
376
R p1 0:00 ps -l
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.
5. Ü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).
6. 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!'
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);
}
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<br>\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 ei tahks komenteerida kuna ma ei oska :(
, aga vaadake orginaalallikat sisukorrast!