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

Programování v UNIXu. Cvičení probíhá v laboratoři UNIX a poskytuje posluchačům průpravu v programování v jazyce C v prostředí UNIX.
Jenda_

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

Příspěvek od Jenda_ »

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"

Kód: Vybrat vše

#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

Kód: Vybrat vše

#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: $ ./server 6666
$ date | nc -u :: 6666
Vrátí datum s čísly přepsanými křížky.

Kód: Vybrat vše

#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);
		else
			fprintf(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_

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

Příspěvek od Jenda_ »

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"…)
Odpovědět

Zpět na „SWI015 Programování v Unixu“