# Lista 5 ###### tags: `SO2022` ![](https://i.imgur.com/uYESwAX.png) :::spoiler ![](https://i.imgur.com/6Jlw3Nc.png) ::: ## Deklaracja :::spoiler | Zadanie | 1 | 2 | 3 | 4 | 5 | 6 | 7 (2p) | |-----------|---|---|---|---|---|---|--------| |Deklaracja |:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| ::: ## Zadanie 1 ![](https://i.imgur.com/e0tey6n.png) * ### Co robi operacja `read(2)` i `write(2)`, jeśli bufor rury jest: * pusty: `read` - proces jest zablokowany (uśpiony) do momentu aż nie pojawią się dane * pełny: `write` - proces jest zablokowany (uśpiony) do momentu aż nie zwolni się miejce w buforze * ### Jakie gwarancje daje nam operacja `write` na rurze, do której pisze wiele procesów – każdy z nich wiersze tekstu nie dłuższe niż `PIPE_BUF`? `write` dopisuje wtedy do bufora atomowo, tj. jeżeli wiersz tekstu z procesów `A` i `B` mieszczą się w `PIPE_BUF`, to każdy z wierszy jest zapisywany do bufora bez rozbicia * ### Czemu wszystkie procesy należące do potoku zakończą się bez interwencji powłoki, jeśli co najmniej jeden z nich umrze? `ps -ef | grep sh | wc -l` Jeśli wszystkie deskryptory piszące do pipe'a zostaną zamknięte `read odczyta znak końca pliku` Jeśli wszystkie deskryptory czytające zostaną zamknięte wysłany zostanie sygnał `SIGPIPE` do procesu próbującego wpisać coś do pipe'a :::spoiler If all file descriptors referring to the **write end** of a pipe have been **closed**, then an attempt to read(2) from the **pipe will see end-of-file (read(2) will return 0). If all file descriptors** referring to the read end of a pipe have been closed, then a **write(2) will cause a SIGPIPE sig‐ nal to be generated for the calling process.** If the calling process is **ignoring** this signal, then **write(2)** fails with the error **EPIPE**. An application that uses pipe(2) and fork(2) should use suitable close(2) calls to close unnecessary duplicate file descriptors; this ensures that **end-of-file** and **SIGPIPE/EPIPE** are delivered when appropriate. ::: * ### Kiedy operacje read i write na rurze zwracają *„short count”*? Rury zachowują się przy odczycie jak zwykłe pliki, zatem `short count` jest zwracany wtedy gdy przeczytaliśmy mniej danych niż oczekiwaliśmy np. osiągneliśmy znak końca pliku '\0' lub w buforze jeszcze nie ma tylu danych ile oczekiwaliśmy Prz zapisie nigdy nie dostaniemy `„short count` zapiszemy co chemy lub dotaniemy error ![](https://i.imgur.com/vC3cuRW.png) ![](https://i.imgur.com/NR1eCI4.png) ![](https://i.imgur.com/1WSX5uQ.png) ![](https://i.imgur.com/J8nl8LB.png) * ### Jak można połączyć rodzica i dziecko rurą, która została utworzona po uruchomieniu dziecka? Nie można połączyć istniejących procesów pipem ![](https://i.imgur.com/9lvrwvm.png) ## Zadanie 2 ![](https://i.imgur.com/EzZnCcX.png) * ### [*ioctl and fcntl Are an Embarrassment*](http://www.catb.org/~esr/writings/taoup/html/ch20s03.html) * ### Do czego służy wywołanie systemowe `ioctl(2)`? Manipuluje paramtertami sterowników urządzeń I/O (TTY, urządzenia szeregowe, itp.) Wysyła sygnały do innych funkcji urządzeń oraz systemu plików. * ### Na podstawie pliku [`ioccom.h`](https://nxr.netbsd.org/xref/src/sys/sys/ioccom.h) wyjaśnij znaczenie drugiego i trzeciego parametru wywołania ioctl. * ```c= #ifndef _SYS_IOCCOM_H_ #define _SYS_IOCCOM_H_ /* * Ioctl's have the command encoded in the lower word, and the size of * any in or out parameters in the upper word. The high 3 bits of the * upper word are used to encode the in/out status of the parameter. * * 31 29 28 16 15 8 7 0 * +---------------------------------------------------------------+ * | I/O | Parameter Length | Command Group | Command | * +---------------------------------------------------------------+ */ #define IOCPARM_MASK 0x1fff /* parameter length, at most 13 bits */ #define IOCPARM_SHIFT 16 #define IOCGROUP_SHIFT 8 #define IOCPARM_LEN(x) (((x) >> IOCPARM_SHIFT) & IOCPARM_MASK) #define IOCBASECMD(x) ((x) & ~(IOCPARM_MASK << IOCPARM_SHIFT)) #define IOCGROUP(x) (((x) >> IOCGROUP_SHIFT) & 0xff) #define IOCPARM_MAX NBPG /* max size of ioctl args, mult. of NBPG */ /* no parameters */ #define IOC_VOID (unsigned long)0x20000000 /* copy parameters out */ #define IOC_OUT (unsigned long)0x40000000 /* copy parameters in */ #define IOC_IN (unsigned long)0x80000000 /* copy parameters in and out */ #define IOC_INOUT (IOC_IN|IOC_OUT) /* mask for IN/OUT/VOID */ #define IOC_DIRMASK (unsigned long)0xe0000000 #define _IOC(inout, group, num, len) \ ((inout) | (((len) & IOCPARM_MASK) << IOCPARM_SHIFT) | \ ((group) << IOCGROUP_SHIFT) | (num)) #define _IO(g,n) _IOC(IOC_VOID, (g), (n), 0) #define _IOR(g,n,t) _IOC(IOC_OUT, (g), (n), sizeof(t)) #define _IOW(g,n,t) _IOC(IOC_IN, (g), (n), sizeof(t)) /* this should be _IORW, but stdio got there first */ #define _IOWR(g,n,t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) #define IOCSNPRINTF(buf, len, cmd) \ snprintf((buf), (len), "_IO%s%s('%c', %hhu)", \ (((cmd) >> 30) & 1) ? "R" : "", \ (((cmd) >> 30) & 2) ? "W" : "", \ (char)IOCGROUP(cmd), (unsigned char)(cmd)) #endif /* !_SYS_IOCCOM_H_ */ ``` Makro `_IOC` definiuje mam schemat w jakim ma zostać przekazany drugi argument wywołania `Ioctl` widzmy w nim informację czy ma to być odczut czy zapis długość argumentu przekazywanego w trzecim parametrze oraz iformacje o grupie i konkretnej instrukcji specyficznej dla dajego urządzenia * ### Używając przeglądarki kodu jądra NetBSD znajdź definicje operacji `DIOCEJECT`, `KIOCTYPE` i `SIOCGIFCONF`, a następnie wytłumacz co one robią. * [DIOCEJECT](https://nxr.netbsd.org/xref/src/sys/sys/dkio.h) `#define DIOCEJECT _IOW('d', 112, int) /* wysuwa dysk zewnętrzny */` * [KIOCTYPE](https://nxr.netbsd.org/xref/src/sys/dev/sun/kbio.h) `#define KIOCTYPE _IOR('k', 9, int) /* pobiera typ klawiatury */` * [SIOCGIFCONF](https://nxr.netbsd.org/xref/src/sys/sys/sockio.h) `#define SIOCGIFCONF _IOWR('i', 38, struct ifconf) /* pobiera listę ifnet (interfejsy sieciowe) */` ## Zadanie 3 ![](https://i.imgur.com/G6Yxsa5.png) ![](https://i.imgur.com/stEme2D.png) * ### Przedstaw reprezentację katalogu, a następnie wyjaśnij jak przebiegają operacje: [Dokumentacja ext4](https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Block_and_Inode_Allocation_Policy) * *usuwania pliku*: * Standardowo: Pozostawiamy nieużytek i zwiększamy rozmiar poprzedzającego go wpisu * Gdy w reprezentacji katalogu za lub przed usuwanym wpisem istnieją nieużytki: Zwiększamy rozmiar pierwszego ważnego poprzedzającego wpisu * *dodawania pliku*: * Standardowo: Znajdujemy pierwsze wystarczająco duże miejsce w strukturze i wpisujemy tam katalog * Gdy w pliku katalogu nie udaje się znaleźć wystarczająco dużo miejsca na przechowanie wpisu. Przeprowadzamy kompaktowanie katalogu, tzn. przenosimy wprzystkie wpisy jak najbardziej na lewo, a końcowe miejsce zwalniamy Jeśli dalej nie będzie miejsca to allokujemy nowy blok * ### Jądro leniwie wykonuje operację kompaktowania na katalogach – kiedy opłaca się ją zrobić? W wyniku konkretnego zdarzenia, zależne od implementacji np. gdy nie można znaleść wolnego miejsca ## Zadanie 4 ![](https://i.imgur.com/tW6oRTx.png) * ### Korzystając z poleceń `stat` i `ls -lia` zaprezentuj jak jądro systemu operacyjnego trawersuje ścieżkę bezwzględną `/usr/bin/cc`. ![](https://i.imgur.com/3pHg4gu.png) ![](https://i.imgur.com/JdLsxsx.png) * ### Od jakiego numeru i-węzła algorytm zaczyna działanie? * ext4: inode: 2 * btrfs: inode 256 * ### Skąd sterownik uniksowego systemu plików wie gdzie na dysku znajduje się i-ty bajt pliku? Sterownik wie z informacji zawartych w inodzie danego pliku - rozmiar pliku, adresy do bloków (są różne poziomy w razie, gdyby nie mieściły się bloki) * ext2/ext3: ![](https://i.imgur.com/hT5KBlA.png) ![](https://i.imgur.com/qjMYrJx.png) ![](https://i.imgur.com/UdYbYCp.png) ![](https://i.imgur.com/InlnrS9.png) * ext4: ![](https://i.imgur.com/ZnJTH2Y.png) * btrfs: ![](https://btrfs.wiki.kernel.org/images-btrfs/0/0d/References.png) * ### Próba utworzenia dowiązania do pliku `/proc/version` kończy się błędem `EXDEV`. Czemu nie możemy tworzyć dowiązań do plików znajdujących się w obrębie innych zamontowanych systemów plików? Osobne systemy plików posiadają osobnie numerowane i-node'y, więc nie możemy tworzyć linków między systemami plików ![](https://i.imgur.com/HyCAzxR.png) ![](https://i.imgur.com/Hh0Qmrw.png) ## Zadanie 5 ![](https://i.imgur.com/TlGOCWS.png) ```c= #include "csapp.h" #define DIRBUFSZ 256 static void print_mode(mode_t m) { char t; if (S_ISDIR(m)) t = 'd'; else if (S_ISCHR(m)) t = 'c'; else if (S_ISBLK(m)) t = 'b'; else if (S_ISREG(m)) t = '-'; else if (S_ISFIFO(m)) t = 'f'; else if (S_ISLNK(m)) t = 'l'; else if (S_ISSOCK(m)) t = 's'; else t = '?'; char ur = (m & S_IRUSR) ? 'r' : '-'; char uw = (m & S_IWUSR) ? 'w' : '-'; char ux = (m & S_IXUSR) ? 'x' : '-'; char gr = (m & S_IRGRP) ? 'r' : '-'; char gw = (m & S_IWGRP) ? 'w' : '-'; char gx = (m & S_IXGRP) ? 'x' : '-'; char or = (m & S_IROTH) ? 'r' : '-'; char ow = (m & S_IWOTH) ? 'w' : '-'; char ox = (m & S_IXOTH) ? 'x' : '-'; /* TODO: Fix code to report set-uid/set-gid/sticky bit as 'ls' does. */ // S_ISUID - set-user-ID bit // S_ISGID - set-group-ID bit // S_ISVTX - sticky bit // set-uid - pozwala wykonywać program na prawach właściciela dowolnemu urzytkownikowi posiadającegmu dostęp ux = (m & S_ISUID) ? ((m & S_IXUSR) ? 's' : 'S') : ux; // set-gid - pozwala wykonywać program na prawach grupy dowolnemu urzytkownikowi posiadającegmu dostęp gx = (m & S_ISGID) ? ((m & S_IXGRP) ? 's' : 'S') : gx; // sticky - pozwala usuwać pliki w katalogu tylko właścicielowi ox = (m & S_ISVTX) ? ((m & S_IXOTH) ? 't' : 'T') : ox; /* TODO: End */ printf("%c%c%c%c%c%c%c%c%c%c", t, ur, uw, ux, gr, gw, gx, or, ow, ox); } static void print_uid(uid_t uid) { struct passwd *pw = getpwuid(uid); if (pw) printf(" %10s", pw->pw_name); else printf(" %10d", uid); } static void print_gid(gid_t gid) { struct group *gr = getgrgid(gid); if (gr) printf(" %10s", gr->gr_name); else printf(" %10d", gid); } static void file_info(int dirfd, const char *name) { struct stat sb[1]; /* TODO: Read file metadata. */ // Czytamy konkretne wejście z podanego deskryptora katalogu, // AT_SYMLINK_NOFOLLOW - wyłącza domyślne rozwiązywanie linków symblicznych if (fstatat(dirfd, name, sb, AT_SYMLINK_NOFOLLOW) == -1) { perror("fstatat"); exit(EXIT_FAILURE); } /* TODO: End */ print_mode(sb->st_mode); printf("%4ld", sb->st_nlink); print_uid(sb->st_uid); print_gid(sb->st_gid); /* TODO: For devices: print major/minor pair; for other files: size. */ // S_IFCHR - character device // S_IFBLK - block device if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode)) printf("%2u, %2u", major(sb->st_rdev), minor(sb->st_rdev)); else printf("%6lu", (size_t)sb->st_size); /* TODO: End */ char *now = ctime(&sb->st_mtime); now[strlen(now) - 1] = '\0'; printf("%26s", now); printf(" %s", name); if (S_ISLNK(sb->st_mode)) { /* TODO: Read where symlink points to and print '-> destination' string. */ // S_ISLNK - symbolic link char buf[DIRBUFSZ]; int n; if ((n = readlinkat(dirfd, name, buf, DIRBUFSZ)) == -1) { perror("readlink"); exit(EXIT_FAILURE); } buf[n] = '\0'; // buf nie musi być zakończony znakiem końca printf(" -> %s", buf); /* TODO: End */ } putchar('\n'); } int main(int argc, char *argv[]) { if (!argv[1]) argv[1] = "."; int dirfd = Open(argv[1], O_RDONLY | O_DIRECTORY, 0); char buf[DIRBUFSZ]; int n; while ((n = Getdents(dirfd, (void *)buf, DIRBUFSZ))) { struct linux_dirent *d; /* TODO: Iterate over directory entries and call file_info on them. */ // Sposób iterowania po wartościach z man gendents for (long bpos = 0; bpos < n;) { d = (struct linux_dirent *) (buf + bpos); bpos += d->d_reclen; file_info(dirfd, d->d_name); } /* TODO: End */ } Close(dirfd); return EXIT_SUCCESS; } ``` ## Zadanie 6 ![](https://i.imgur.com/5g3kdba.png) ```c= #include "csapp.h" typedef struct { int read; int write; } pipe_t; static inline pipe_t MakePipe(void) { int fds[2]; Pipe(fds); return (pipe_t){.read = fds[0], .write = fds[1]}; } static inline void CloseReadEnd(pipe_t p) { Close(p.read); } static inline void CloseWriteEnd(pipe_t p) { Close(p.write); } static bool ReadNum(pipe_t p, long *valp) { return Read(p.read, valp, sizeof(long)) == sizeof(long); } static bool WriteNum(pipe_t p, long val) { return Write(p.write, &val, sizeof(long)) == sizeof(long); } static noreturn void generator(pipe_t out, long maxprime) { for (long n = 2; n <= maxprime; n++) WriteNum(out, n); exit(EXIT_SUCCESS); } static void filter(pipe_t in, pipe_t out, long prime) { long num; while (ReadNum(in, &num)) { if (num % prime != 0) WriteNum(out, num); } } static noreturn void filter_chain(pipe_t in) { long prime; /* TODO: Something is missing here! */ pipe_t in_pipe = in; pipe_t out_pipe; pid_t res; while (true) { if (ReadNum(in_pipe, &prime)) //opczytujemy pierwszą liczbę jako naszą liczbę pierwszą printf("PID:%d: %ld\n", getpid(), prime); else //jeśli readnum nic otrzymał kończymy proces exit(EXIT_SUCCESS); out_pipe = MakePipe(); // tworzymy nowe połączenie if ((res = Fork())) { /* parent */ CloseReadEnd(out_pipe); //zamykamy niepotrzebne wejśćie filter(in_pipe, out_pipe, prime); //tworzymy filtr break; } else { /* child */ CloseReadEnd(in_pipe); //zamykamy starego pipa wejściowego in_pipe = out_pipe; // nowo utworzony zostaje wejściem syna CloseWriteEnd(in_pipe); // zamykamy niepotrzebne wyjście } } CloseWriteEnd(out_pipe); //Zamykamy potok jawnie żeby zakomunikowac dziecku że to koniec Wait(NULL); /* TODO: End */ exit(EXIT_SUCCESS); } int main(int argc, char *argv[]) { if (argc != 2) app_error("Usage: %s [MAXPRIME]", argv[0]); long maxprime = atol(argv[1]); if (maxprime < 2 || maxprime > 10000) app_error("Give maximum prime number in range from 2 to 10000!"); /* Spawn generator. */ pipe_t gen_pipe = MakePipe(); if (Fork()) { /* parent */ CloseWriteEnd(gen_pipe); } else { /* child */ CloseReadEnd(gen_pipe); generator(gen_pipe, maxprime); } /* Spawn filter chain. */ if (Fork()) { /* parent */ CloseReadEnd(gen_pipe); } else { /* child */ filter_chain(gen_pipe); } for (int i = 0; i < 2; i++) Wait(NULL); return 0; } ``` ## Zadanie 7 ![](https://i.imgur.com/CE13r1O.png) ```c= #include "csapp.h" typedef struct { int child_fd; int parent_fd; } sockpair_t; static sockpair_t MakeSocketPair(void) { int sv[2]; Socketpair(AF_UNIX, SOCK_STREAM, 0, sv); return (sockpair_t){.child_fd = sv[0], .parent_fd = sv[1]}; } static bool MaybeReadNum(int fd, int *num_p) { return Read(fd, num_p, sizeof(int)) == sizeof(int); } static int ReadNum(int fd) { int num; if (Read(fd, &num, sizeof(int)) < sizeof(int)) app_error("ReadNum error"); return num; } static void WriteNum(int fd, int num) { if (Write(fd, &num, sizeof(int)) < sizeof(int)) app_error("WriteNum error"); } static void SendElem(int parent_fd, int child_fd, int nelem) { WriteNum(child_fd, nelem); for (int i = 0; i < nelem; i++) WriteNum(child_fd, ReadNum(parent_fd)); } static void Merge(int left_fd, int right_fd, int parent_fd) { bool has_left = true; bool has_right = true; int left = ReadNum(left_fd); int right = ReadNum(right_fd); do { if ((has_left && has_right) ? left < right : has_left) { WriteNum(parent_fd, left); has_left = MaybeReadNum(left_fd, &left); } else { WriteNum(parent_fd, right); has_right = MaybeReadNum(right_fd, &right); } } while (has_left || has_right); } static void Sort(int parent_fd) { int nelem = ReadNum(parent_fd); if (nelem < 2) { WriteNum(parent_fd, ReadNum(parent_fd)); Close(parent_fd); return; } sockpair_t left = MakeSocketPair(); /* TODO: Spawn left child. */ if (Fork()) {/* parent */ Close(left.child_fd); SendElem(parent_fd, left.parent_fd, nelem/2); } else { /* child */ Close(left.parent_fd); Sort(left.child_fd); exit(EXIT_SUCCESS); } /* TODO: End */ sockpair_t right = MakeSocketPair(); /* TODO: Spawn right child. */ if (Fork()) {/* parent */ Close(right.child_fd); SendElem(parent_fd, right.parent_fd, nelem/2 + nelem % 2); //zakresy nieparzyste niekoniecznie równe } else { /* child */ Close(right.parent_fd); Sort(right.child_fd); exit(EXIT_SUCCESS); } /* TODO: End */ /* TODO: Send elements to children and merge returned values afterwards. */ Merge(left.parent_fd, right.parent_fd, parent_fd); /* TODO: End */ /* Wait for both children. */ Wait(NULL); Wait(NULL); } static int GetNumber(void) { char buf[20]; if (fgets(buf, sizeof(buf), stdin) == NULL) app_error("GetNumber error"); return strtol(buf, NULL, 10); } int main(void) { sockpair_t sort = MakeSocketPair(); if (Fork()) { /* parent */ int nelem = GetNumber(); if (nelem < 2) app_error("Number of sorted elements must be at least 2!\n"); Close(sort.child_fd); /* Write unsorted numbers to mergesort */ WriteNum(sort.parent_fd, nelem); for (int i = 0; i < nelem; i++) WriteNum(sort.parent_fd, GetNumber()); /* Read sorted numbers from mergesort */ int prev = INT_MIN; for (int i = 0; i < nelem; i++) { int elem = ReadNum(sort.parent_fd); fprintf(stderr, "%d\n", elem); assert(prev <= elem); prev = elem; } Close(sort.parent_fd); Wait(NULL); } else { /* child */ Close(sort.parent_fd); Sort(sort.child_fd); } return 0; } ``` * ### Posługując się rysunkiem wyjaśnij strukturę programu. NotImplemented * ### Kiedy tworzysz podprocesy i gniazda? Tworzymy nowe gniazdo zaraz przed stworzeniem procesu, ten tworzymy gdy chcemy posotrować część danych * ### Kiedy zamykasz niepotrzebne gniazda? Każdy proces ma swój deskryptor, zatem niepotrzebne gniazda zamykamy odrazu po forku * ### Jak wygląda przepływ danych? Dane płyną od procesu startowego przez kolejne pokolenia do najmłodzeszego dziecka, a następnie pracają tą samą drogą do procesu startowego