Pytania do projektu shell

Pytania do projektu shell

Napisane przez: Mateusz Małowiecki ()
Liczba odpowiedzi: 29

Otwieram wątek z pytaniami do projektu shell. Ja zacznę:

1. Jakiego rodzaju polecenia będą kierowane do shell'a ? W jaki sposób przetwarza je lexer ? Prosiłbym o pokazanie tego na kilku(3-4) przykładach.

2. Co ma robić procedura do_redir? W szczególności czym jest zwracana wartość n i  jaka ma być zawartość tablic inputp i outputp?

3. Czy dobrze rozumiem że procedura do_job wykonuje jedną pracę, która ma jeden proces, a do_pipeline wykonuje pracę z wieloma procesami? Jeśli tak, to jak ma wyglądać potok między procesami?

 

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

Zakładamy, że shell akceptuje poprawnie uformowane wyrażenia według poniższej gramatyki (w formacie dla programu bison):

%start program
%%
program          : pipe_sequence '&'
                 | pipe_sequence
                 ;
pipe_sequence    : command
                 | pipe_sequence '|' command
                 ;
command          : cmd_words cmd_suffix
                 | cmd_words
                 ;
cmd_words        : cmd_words WORD
                 | WORD
                 ;
cmd_suffix       : io_redirect
                 | cmd_suffix io_redirect
                 ;
io_redirect      : '<' WORD
                 | '>' WORD
                 ;
%%
W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

1) Lekser dzieli ciąg znaków na tokeny i zwraca tablicę wskaźników do ciągów znakowych. Specjalne tokeny zastępuje wskaźnikami o wartościach numerycznych (np. T_BGJOB, T_OUTPUT, ...)

Kilka przykładów istotnie różnych poleceń akceptowanych przez powłokę:

  • cd .. (polecenie wewnętrzne)
  • cd .. & (polecenie wewnętrzne wykonane w tle)
  • ls -l (polecenie zewnętrzne)
  • cd .. | ls -l (użycie polecenia wewnętrznego i zewnętrznego w potoku)
  • cd .. | ls -l & (j.w. ale zadanie wykonane w tle)
  • ls -l | wc -l (użycie poleceń zewnętrznych w potoku)
W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

2) Zadaniem procedury do_redir jest przetworzenie i usunięcie tokenów odpowiedzialnych za przekierowania. Zwracana wartość n jest liczbą tokenów, które zostaną przekazane do execve. Argumenty intputp i outputp to nie tablice, a wskaźniki na deskryptory plików otwartych przez procedurę do_redir. Przykład poniżej...

Ciąg tokenów na wejściu (ntokens = 6):

grep < test.in foo > test.out

powinien zostać przetworzony do ciągu tokenów (n = 2):

grep foo NULL NULL NULL NULL

W wyniku tego deskryptor output ma odpowiadać plikowi test.out otwartemu do zapisu i analogicznie input plikowi test.in otwartemu do odczytu.

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

3) Tak, do_job ma wykonać zadanie składające się z jednego podprocesu, o ile nie chcemy wykonać polecenia wbudowanego builtin_command. Dodatkowo możemy zażyczyć sobie, żeby proces był wykonany w tle, wtedy dla polecenia wbudowanego ma zostać utworzony podproces.

Procedura do_pipeline zarządza tworzeniem i monitorowaniem potoku. Pojedynczy proces potoku ma być tworzony procedrą do_stage. Wszystkie procesy potoku muszą należeć do jednej grupy procesów (różnej od grupy powłoki) i mają być bezpośrednimi dziećmi procesu powłoki.

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

Na stronie przedmiotu umieściłem dwustronicowy dokument opisujący pewne aspekty działania powłoki.

Proszę zadawać pytania na forum. Postaram się na nie możliwie szybko odpowiedzieć, a potem ze szczegółami wyjaśnić w nieco bardzie kompletnej wersji treści zadania.

W odpowiedzi na Krystian Bacławski

Odp: Pytania do projektu shell

Napisane przez: Mateusz Małowiecki ()

Mam jeszcze kilka pytań co do których odpowiedzi nie znalazłem w załączonym dokumencie:

1. W procedurach do_job i do_pipeline tworzymy nowe zadania składające się z jednego lub wielu procesów. Zarówno każdy z tych procesów jak i samo zadanie ma swój identyfikator(pid w przypadku procesu i pgid w przypadku zadania). Czy dobrze rozumiem że identyfikatorami procesów mają być kolejne identyfikatory zwracane przez Fork()? Jeśli nie to co ma być tymi identyfikatorami? Jaki powinien być identyfikator nowo utworzonego zadania?

2. Pytanie dotyczy obsługi polecenia "fg" w procedurze resumejob. Nie jestem pewien, co trzeba zrobić jeżeli jakieś zadanie wykonuje się na pierwszym planie, a my chcemy wrzucić na pierwszy plan inne zadanie? Czy mamy poczekać aż tamto zadanie(wykonujące się na pierwszym planie) skończy się wykonywać(wait/waitpid), czy może przenieść tamto zadanie na drugi plan?

3. Co ma robić procedura monitorjob? W jaki sposób mamy wyliczać zwracaną wartość exitcode? Czy zwrot "Monitor job" występujący(w postaci różnych parafraz) w różnych częściach kodu(m.in. w do_job i do_pipeline) oznacza konieczność użycia tej procedury?

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

1) Wyróżniamy dwa rodzaje identyfikatorów: używane przez system operacyjny (pgid i pid), oraz używane przez procedury z pliku jobs.c (indeksy tablic jobs i job.proc). Procedura addjob przyjmuje numer grupy procesów zadania, a zwraca numer zadania (czyli numer wpisu w tablicy jobs). Procedura addproc przyjmuje numer zadania i identyfikator procesu.

Identyfikator nowo utworzonego zadania musi zostać utworzony przez addjob.

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

2) Jeśli wykonujemy zadanie pierwszoplanowe, to shell czeka na jego zakończenie. W związku z tym nie przyjmuje poleceń do wykonania – w tym również fg.

W opisanym scenariuszu należy wcisnąć klawisze CTRL+Z, żeby zawiesić zadanie A i zmienić jego status na drugoplanowe. Następnie należy wznowić zadanie A poleceniem "bg A" i wznowić wybrane zadanie drugoplanowe B poleceniem "fg B".

W odpowiedzi na Krystian Bacławski

Odp: Pytania do projektu shell

Napisane przez: Mateusz Małowiecki ()

Nie rozumiem jednej rzeczy. Napisał Pan że w podanej przeze mnie sytuacji jedną z czynności którą należy zrobić jest naciśnięcie kombinacji klawiszy Ctrl+z. Tylko że w main'ie ustawiamy że SIGTSTP ma być ignorowany. Oznacza to że kombinacja Ctrl+z nic nie zrobi w terminalu(w szczególności nie zawiesi zadania A i nie zmieni jego statusu na drugoplanowe)

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

Ogólnie rzecz biorąc chcemy, żeby SIGTSTP nie dochodził do powłoki, a wyłącznie do grupy pierwszoplanowej (o ile ta istnieje) utworzonej przez powłokę.

CTRL+Z jest zamieniane na wysłanie sygnału SIGTSTP przez jądro, a konkretniej sterownik terminala. Natomiast my ten terminal musimy odpowiednio skonfigurować.

Proponuję przeczytać jeszcze raz odpowiednie fragmenty książek APUE i LPI.

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

3) Zadaniem procedury monitorjob jest oczekiwanie na zmianę stanu zadania na FINISHED albo STOPPED. W przypadku tego pierwszego ma zwrócić exitcode, które wprost odpowiada wartości zwróconej przez waitpid w drugim parametrze. W przypadku drugiego musi zmienić identyfikator zadania z zera na liczbę dodatnią.

W moim rozwiązaniu monitorjob jest używane w pliku shell.c dwa razy.

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Bartłomiej Betka ()

Podpinam się pod wątek. Ja z kolei mam problem z biblioteką readline. Używam Archa, w którym readline jest w wersji 8 a problem jest taki, że jak na "gołym" kodzie shella puszczam make to dostaję długą laurkę błędów, która zaczyna się:

 

In file included from /usr/include/readline/readline.h:35,
                 from shell.c:1:
/usr/include/readline/rltypedefs.h:71:28: error: unknown type name ‘FILE’
   71 | typedef int rl_getc_func_t PARAMS((FILE *));
      |                            ^~~~~~
In file included from /usr/include/readline/readline.h:36,
                 from shell.c:1:
/usr/include/readline/rltypedefs.h:1:1: note: ‘FILE’ is defined in header ‘<stdio.h>’; did you forget to ‘#include <stdio.h>’?
  +++ |+#include <stdio.h>
    1 | /* rltypedefs.h -- Type declarations for readline functions. */
 
i tak dalej o brakujących definicjach. Includowanie stdio.h w shell.c rozwiązuje problem, można skompilować program i readline działa. Chciałem spytać czy taki fix jest "legalny"? Istnieje jakieś lepsze rozwiązanie (inne niż instalacja Debiana)?
W odpowiedzi na Bartłomiej Betka

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

Taki fix jest legalny, o ile zostanie właściwie opisany w kodzie źródłowym. Zawarcie w komentarzu powyższego komunikatu diagnostycznego z krótkim uzasadnieniem powinno wystarczyć.

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Maciej Malicki ()

W którym miejscu dostarczonego kodu powinno się użyć wywołania systemowego `tcsetpgrp` podając grupę nowego zadania pierwszoplanowego, skoro zmienna `tty_fd` jest dostępna tylko w pliku `jobs.c`, a żadne TODO w tym pliku nie dotyczy takiej sytuacji?

W odpowiedzi na Maciej Malicki

Odp: Pytania do projektu shell

Napisane przez: Jacek Bizub ()

Hmm. Przeciez w monitorjob masz wprost podane, ze tam trzeba uzyc 

W odpowiedzi na Jacek Bizub

Odp: Pytania do projektu shell

Napisane przez: Anna Dąbrowska ()

Hym, ale tam oddajemy terminal do shell'a gdy dziecko jest STOPPED lub FINISHED, a gdzie mamy przekazać terminal dziecku?

W odpowiedzi na Anna Dąbrowska

Odp: Pytania do projektu shell

Napisane przez: Jacek Bizub ()

Terminal oddajemy procesowi kiedy chcemy na niego czekac. A jak juz sie skonczy/zastopuje to zabieramy

W odpowiedzi na Jacek Bizub

Odp: Pytania do projektu shell

Napisane przez: Maciej Malicki ()

Moim zdaniem ustawienie grupy procesów terminalu na grupę nowego zadania pierwszoplanowego powinno się odbyć w procesie dziecka między wywołaniem fork, a exec. To jedyny sposób zapewnienia ustawienia grupy przed exec. Jednak nie mogę tego zrobić explicite w tym miejscu, bo zmienna tty_fd jest dostępna tylko w pliku jobs.c. Wywołanie w tym miejscu (w dziecku między fork, a exec) żadnej funkcji z jobs.c nie wydaje się sensowne. Komentarze sugerują, by grupę ustawić na dziecka (a gdy się skończy przywrócić na rodzica) w funkcji monitorjobs, ale czy nie jest to wyścig polegający na ustawieniu tej grupy w momencie, gdy exec mógł się już wykonać?

Chętnie usłyszę zdanie osób, które zdecydują się w jakikolwiek sposób odnieść się do tych wątpliwości, o ile nie uznają tego za spoiler.

W odpowiedzi na Maciej Malicki

Odp: Pytania do projektu shell

Napisane przez: Arkadiusz Kozdra ()

Sugerowane rozwiązanie ma parę miejsc, gdzie zdarzają się wyścigi, a najłatwiej zaobserwować je programem strace (śledząc np ioctl, setpgid, execve i kill). Przykładowo, program cat uruchomiony w tle nie zawsze daje się wznowić na pierwszym planie.

W odpowiedzi na Maciej Malicki

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

Najpierw odniosę się do ustawienia grupy procesów, tj. setpgrp., które musimy wykonać na podprocesie zanim ten wywoła execve. Przytoczę ustęp z rozdziału 9.4 APUE:

In most job-control shells, this function is called after a fork to have the parent set the process group ID of the child, and to have the child set its own process group ID. One of these calls is redundant, but by doing both, we are guaranteed that the child is placed into its own process group before either process assumes that this has happened. If we didn’t do this, we would have a race condition, since the child’s process group membership would depend on which process executes first.

Jeśli to jest zrozumiałe, to pozostaje kwestia, czy wołać tcsetpgrp od razu za setpgrp, czy dopiero w monitorjobs.

Jeśli nie zdążymy wypromować grupy procesów do pierwszoplanowej zanim wszystkie procesy ją opuszczą, to tcsetpgrp powinno zawieść (?) i to nie jest błąd.

Na razie nie znajduję argumentów za tym by w tym samym miejscu robić setpgrp i tcsetpgrp. Gdyby jednak okazało się to niezbędne, to należy dorobić prostego wrappera w pliku jobs.c i wystawić jego deklarację do shell.h.

W odpowiedzi na Krystian Bacławski

Tcsetpgrp w do_job i do_stage

Napisane przez: Marek Glück ()

Znalazłem taki argument, że dobrze by było gdyby sygnały wysyłane za pomocą klawiatury (np. Ctrl+C) docierały do dziecka, a nie do shella i były ignorowane. Jeżeli zrobię 'tcsetpgrp' w 'monitorjob', to dziecko mogło już się zacząć wykonywać. Jest jeszcze kłopot z 'do_stage', które nie ma argumentu 'bg' w przeciwieństwie do 'do_job'.

To według mnie drobna sprawa. Ważniejsze jest to, że nie rozumiem, po co jest tworzony 'tty_fd'. W 'do_job' w dziecku mogę zrobić 'Tcsetpgrp(STDIN_FILENO, getpgrp());'. Chyba 'tty_fd' i 'STDIN_FILENO' są tym samym.

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Maciej Malicki ()

Dlaczego funkcja `do_quit` odpowiadająca poleceniu wewnętrznemu "quit" z pliku `command.c` nie wywołuje funkcji `shutdownjobs`? Czy oczekiwanym zachowaniem kiedy powłoka zamyka się jest wysłanie SIGTERM do procesów pozostałych w tle?

W odpowiedzi na Mateusz Małowiecki

Odp: Pytania do projektu shell

Napisane przez: Jacek Bizub ()

W lexerze [lexer.c] brakuje obslugi przekierowania >> mimo, ze && czy || sa uwzglednione.

Ale mozna to szybko naprawic wzorujac sie na dostarczonym kodzie.

W odpowiedzi na Jacek Bizub

Odp: Pytania do projektu shell

Napisane przez: Krystian Bacławski ()

Zgadza się. W trakcie implementacji powłoki doszedłem do wniosku, że wsparcie dla w/w operatorów raczej wymagałoby już zmiany architektury programu i użycia parsera z prawdziwego zdarzenia. Na obecną chwilę parsowanie i interpretacja poleceń są ze sobą złączone, co nie jest rozwiązaniem eleganckim, ale za to wystarczająco prostym.