Řešené příklady - kolona, thready, UDP server

Jenda_ at 2016-02-02 00:43:15

Ahoj,
v rámci přípravy jsem si vypracoval pár příkladů podobných minulým zkouškám. Upozorňuji, že jsem UNIXové API nikdy moc nepoužíval, takže tam budou různé věci blbě.

Budete potřebovat nsautil.h a jcc z blobutils

Zdroje: http://mff.devnull.cz/pvu/common/zkousk ... ousek.html a toto fórum.

Příklad: Nasimulujte shellovou kolonu cat /proc/cpuinfo | grep model | tr a-z A-Z | sed -r "s/(A|E|I|O|U)/_/g"

#include <nsautil.h>

int main(int argc, char **argv) {
pid_t child;int filedes[2];int pipeprev = -1;
pipe(filedes);child = fork();if(child == 0) {dup2(filedes[1], STDOUT_FILENO);close(filedes[1]);execlp("cat", "cat", "/proc/cpuinfo", NULL);}close(filedes[1]);close(STDIN_FILENO);pipeprev = filedes[0];
pipe(filedes);child = fork();if(child == 0) {dup2(pipeprev, STDIN_FILENO);dup2(filedes[1], STDOUT_FILENO);close(filedes[1]);execlp("grep", "grep", "model", NULL);}close(filedes[1]);close(pipeprev);pipeprev = filedes[0];
pipe(filedes);child = fork();if(child == 0) {dup2(pipeprev, STDIN_FILENO);dup2(filedes[1], STDOUT_FILENO);close(filedes[1]);execlp("tr", "tr", "a-z", "A-Z", NULL);}close(filedes[1]);close(pipeprev);
child = fork();if(child == 0) {dup2(filedes[0], STDIN_FILENO);//close(filedes[0]);execlp("sed", "sed", "-r", "s/(A|E|I|O|U)/_/g", NULL);}close(filedes[0]);
return 0;
}

(jako cvičení by bylo vhodné parametrizovat to příkazy v poli + execvp)

Příklad: Hlavní vlákno vytvoří počet vláken zadaný jako parametr příkazové řádky. Každé vlákno simuluje činnost pomocí sleep() na náhodnou dobu, 1 až 10 sekund. Po vykonání činnosti, tj. po ukočení funkce sleep(), vlákno zahlásí hlavnímu vláknu, že skončilo. Hlavní vlákno okamžitě vytvoří další vlákno.

Cílem je tedy udržovat stále stejný počet vláken v běhu. Každé vlákno má navíc svoje pořadové číslo, při vytvoření a zániku vlákna se vypíše na termínál hlášení o této skutečnosti, s použitím pořadového čísla.

Pro synchronizaci použijte mutexy a podmínkové proměnné. Aktivní čekání je nepřípustné. Velký důraz je kladen na korektnost použití synchronizačních primitiv.

Usage: ./pthreads 10

#include <nsautil.h>
#include <pthread.h>
#include <poll.h>

pthread_mutex_t mujmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t mojecond = PTHREAD_COND_INITIALIZER;

int numt;

int * r;

typedef struct tr {int tid;int toffs;char * message;pthread_t thr;
} tr;

tr * t;

void * mujthread(void *ptr) {tr * ctx = (tr*)ptr;printf("Thread %i(%i) is running
", ctx->toffs, ctx->tid);
poll(NULL, 0, random() % 10000);
// for(volatile int i = 0; i<100*1000*1000; i++) {}
printf("Thread %i(%i) says: %s
", ctx->toffs, ctx->tid, ctx->message);
poll(NULL, 0, random() % 10000);
printf("Thread %i(%i) is finishing
", ctx->toffs, ctx->tid);
r[ctx->tid] = 0;
pthread_cond_signal(&mojecond);
return(NULL);
}

int main(int argc, char ** argv) {
numt = atoi(argv[1]);
r = malloc(sizeof(int) * numt);t = malloc(sizeof(tr) * numt);

int iter = 0;int toffs = 0;while (1) {pthread_mutex_lock(&mujmutex);iter++;for (int i = 0; i<numt; i++) {if (r[i] == 0) {pthread_join(t[i].thr, NULL);pthread_t thr;t[i].message = (char*)malloc(100);sprintf(t[i].message, "This is iter %i", iter);t[i].tid = i;t[i].toffs = toffs;int ret = pthread_create(&thr, NULL, &mujthread, (void*) (t + i));t[i].thr = thr;toffs++;if(ret == 0) {r[i] = 1;} else {printf("Cannot create thread!
");}}}pthread_cond_wait(&mojecond, &mujmutex);pthread_mutex_unlock(&mujmutex);}
exit(EXIT_SUCCESS);
}

Příklad: datagram server, IP agnostic. Kvůli bugu v glibc (https://sourceware.org/bugzilla/show_bug.cgi?id=9981) to není úplně agnostic, je tam ten if(rp->ai_family == AF_INET).

Usage: ./server6666 ./server 6666 date | nc -u :: 6666
Vrátí datum s čísly přepsanými křížky.

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#define BUF_SIZE 500

/* Zkopírováno z manpage getaddrinfo z glibc a z https://stackoverflow.com/questions/5956516/getaddrinfo-and-ipv6 */

int main(int argc, char *argv[]) {
struct addrinfo hints;struct addrinfo *result, *rp;int sfd, s;struct sockaddr_storage peer_addr;socklen_t peer_addr_len;ssize_t nread;char buf[BUF_SIZE];
if (argc != 2) {fprintf(stderr, "Usage: %s port
", argv[0]);exit(EXIT_FAILURE);}
memset(&hints, 0, sizeof(struct addrinfo));hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */hints.ai_socktype = SOCK_DGRAM;hints.ai_flags = AI_PASSIVE;hints.ai_protocol = 0;          /* Any protocol */hints.ai_canonname = NULL;hints.ai_addr = NULL;hints.ai_next = NULL;
s = getaddrinfo(NULL, argv[1], &hints, &result);//s = getaddrinfo("localhost", argv[1], &hints, &result);if (s != 0) {fprintf(stderr, "getaddrinfo: %s
", gai_strerror(s));exit(EXIT_FAILURE);}
/* getaddrinfo() returns a list of address structures.   Try each address until we successfully bind(2).   If socket(2) (or bind(2)) fails, we (close the socket   and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) {
if(rp->ai_family == AF_INET6) {char * buf = malloc(INET6_ADDRSTRLEN);struct sockaddr_in* saddr = (struct sockaddr_in*)rp->ai_addr;if(inet_ntop(AF_INET6, &(saddr->sin_addr), buf, INET6_ADDRSTRLEN)) {printf("Trying (v6) %s
", buf);} else {printf("ntop err
");}} else if (rp->ai_family == AF_INET) {char * buf = malloc(INET_ADDRSTRLEN);struct sockaddr_in* saddr = (struct sockaddr_in*)rp->ai_addr;if(inet_ntop(AF_INET, &(saddr->sin_addr), buf, INET_ADDRSTRLEN)) {printf("Trying (v4) %s
", buf);} else {printf("ntop err
");}}
if(rp->ai_family == AF_INET) {continue;}
sfd = socket(rp->ai_family, rp->ai_socktype,rp->ai_protocol);if (sfd == -1) {continue;}
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) {printf(" ...bound OK
");break;}
close(sfd);}
if (rp == NULL) {               /* No address succeeded */fprintf(stderr, "Could not bind
");exit(EXIT_FAILURE);}
freeaddrinfo(result);           /* No longer needed */
/* Read datagrams and echo them back to sender */
for (;;) {peer_addr_len = sizeof(struct sockaddr_storage);nread = recvfrom(sfd, buf, BUF_SIZE, 0,(struct sockaddr *) &peer_addr, &peer_addr_len);if (nread == -1)continue;               /* Ignore failed request */
char host[NI_MAXHOST], service[NI_MAXSERV];
s = getnameinfo((struct sockaddr *) &peer_addr,peer_addr_len, host, NI_MAXHOST,service, NI_MAXSERV, NI_NUMERICSERV);if (s == 0)printf("Received %ld bytes from %s:%s
",(long) nread, host, service);elsefprintf(stderr, "getnameinfo: %s
", gai_strerror(s));
for(int i = 0; i<nread; i++) {if(buf[i] >= '0' &&buf[i] <= '9') {buf[i] = '#';}}
if (sendto(sfd, buf, nread, 0,(struct sockaddr *) &peer_addr,peer_addr_len) != nread)fprintf(stderr, "Error sending response
");}
}
Jenda_ at 2016-02-02 01:07:43

Poznámky k otázkám z webu:

Napište sekvenci příkazů pro vytvoření dynamické knihovny a programu který na ní závisí (ideálně na Linuxu a Solarisu).
cc -G -o libadd.so add.c (gcc -shared)
cc -L. -ladd -R. main.c (gcc -Xlinker -R. nebo LD_LIBRARY_PATH=.)

Proč není dobrý nápad používat proměnnou LD_LIBRARY_PATH pro něco jiného než ladění programů ?
Obcházení problémů nečistou metodou. A protože se dědí.

Vysvětlete význam parametrů funkce main() a proměnné environ.
main může mít 3. parametr a to bude stejně jako extern char** environ

Jak se zpracovávají argumenty programu pomocí funkce getopt() ?
getopt(int argc, char * argv[], char* optstring)

  • extern int optind

proces má kontext, vlákna jsou uvnitř procesu, program je spustitelný soubor na disku, např. ve formátu elf
paměť se přiděluje procesům
čas/procesor se přiděluje vláknům (pokud to není library-level threading)

syscall: vrací signed: -1 chyba, >=0 OK, vrací pointer: NULL chyba; nastavuje se errno
když čekám -1, musím nastavit errno na 0 a pak se kouknout
pthreads vracé 0 OK, >0 fail

/bin/ping má ruid=jenda, euid=root

testují se v tomto pořadí práva roota, vlastníka a skupiny, takže "---rwxrwx jenda jenda" nepřečtu, ale můžu chmodnout a pak to přečtu.

svazek = instance FS

block devices se bufferují

files: special, regular (hardlink), dir, fifo, socket, symlink

s5: boot block, superblock, inody, data - taky ve stromech, omezená jména a velikosti souborů, sloooow

ffs/ufs: cylgrupy (ne že by to v době LBA pomohlo) s kopiemi superbloku, bitmapy, jména 255, ufs2: dirhash cache

obrázek str. 80 - tree traversal

přístup k souborům: vždy open(), nebo creat(); vı́ce deskriptorů může sdı́let jedno otevřenı́ souboru (mód čtenı́/zápis, ukazatel pozice)
creat - bacha na umask

Popište paměťový prostor procesu v uživatelském režimu a v režimu jádra.
text - data - bss - - stack - oblast user (nepřístupná) (ne vždy v tomto pořadí)
kernel text - data a bss jádra, uvnitř struktura user, pod tím stack jádra

Nakreslete a popište stavový diagram procesu.
obrázek 123

Popište základy mechanismu plánování procesů. Jaké jsou prioritní třídy ?
preemptivní (interrupty timerem)
timeshared ("normální"), ladí se velikost kvanta, system, realtime - definovaná časová kvanta

FIXME: signal handling u vláken

Jaká pravidla existují pro psaní signal handlerů ? Jakým způsobem se vyrovnat s omezeními při jejich implementaci ?
reentrantní; globální přepínač

155: alarm signál

Jak je možné vytvořit globální proměnnou privátní pro jedno vlákno ?
pthread_key_create vyrobí pointr, který si každé vlákno může nastavit jinam.

Které atributy jsou společné pro proces, které jsou privátní pro každé vlákno ?
Soukromé: IP, stack, thread ID, plánovacı́ priorita a politika, errno, klíče

Jak fungují destruktory klíčovaných hodnot a zásobník úklidových handlerů ?
Klíčové hodnotě prostě předám funkci.
pthread_cleanup_push a pop

Co se stane když jedno vlákno zavolá fork() ? Jaký problém při tom může vzniknout a jak ho lze řešit ?
Neuvolněná paměť, navždy zamčené mutexy a deadlocky. Je dobré v prefork handleru vše zamknout (tedy počkat, až to ostatní vlákna uvolní) a v postfork odemknout.

Signály: masknout a jedno vlákno se věnuje jen signálům přes sigwait.

Uveďte nástroje pro synchronizaci vláken.
Mutexy, podmínkové proměnné, RW zámky

Popište způsob použití podmínkových proměnných, důvody proč se používají právě daným způsobem a co by se mohlo stát kdyby tomu bylo jinak.
Zamknu mutex, otestuji neplatnost podmínky, pthread_cond_wait. Jinak bych se mohl zaseknout - probudím se opravdu jen při změně, není tam fronta jako u signálů.
Probuzení nemusí nutně znamenat, že podmínka platí.
typicky je dobré volat wait ve while(!podminka)

231: použití

Popište zamykání souborů pomocí fcntl().
dokonce se dá zamykat rozsah
advisory a mandatory (ne vždy je implementován) locking
při zavření se všechny moje zámky uvolní - a to i když to třeba otevřela a zavřela subrutina

Vysvětlete zamykání pomocí lock souborů.
open(rdwr | creat | excl), vrátí -1 při souběhu
unlock: unlink

Popište semafory, implementované v UNIX System V IPC. Jakými příkazy lze zobrazit jejich výpis a stav ?
pracuje atomicky nad polem! semaforů
semctl(semnum), GETVAL, GETNCNT (kolik drží procesů)
použití: semop, operace snížit, zvýšit, čekat na 0, a operace jsou v sembuf, kde je vždycky pro který semafor jakou operaci
jo a než s tím vůbec můžu začít pracovat, tak si samozřejmě musím pořídit IPC klíč přes ftok()

Popište činnost serveru a klienta (posloupnost systémových volání) pro spojované síťové služby.
server: socket, bind, listen, accept, read/write, close
klient: socket, connect, read/write, close; bind pokud by chtěl extra odchozí port (může vyžadovat vyšší oprávnění)

nespojované: socket, bind, sendto/recvfrom

Co je to soket? Jaké jsou varianty adresace soketů (v rámci jednoho stroje a po síti) ?
AF_UNIX, AF_INET6; UNIX má filename

Jaké funkce slouží pro převod mezi číselnými a symbolickými jmény protokolů, portů a IP adres?
pton, ntop (network, presentable format)
getprotobyname, getservbyname, pozor, proto je IP protokol, serv je HTTP/SSH/IMAP

Popište standardní funkce pro převod jména na adresu a naopak a způsob jejich použití pro klient a server.
getaddrinfo, getnameinfo
bývaly gethostbyname a gethostbyaddr

Jak lze čekat na příchod dat z několika deskriptorů souborů zároveň?
select - čtecí, zapisovací, chybové
poll - čeká na událost a nastaví pro fd události ("lze číst", "lze zapisovat"…)