Λειτουργικά Συστήματα (ΗΥ-345)
Χειμερινό Εξάμηνο 2009
Άσκηση 1



chrond - A light-weight cron service

Εισαγωγή

Παραδοσιακά στο Unix, η εκτέλεση συγκεκριμένων λειτουργιών σε μια προκαθορισμένη χρονική στιγμή γίνεται με τη βοήθεια του cron(8). Για παράδειγμα, αν κάποιος θέλει να τρέχει καθε βράδυ ένα εργαλείο που θα παίρνει ένα αντίγραφο του σκληρού δίσκου για backup, μια φορά το μήνα να ανανεώνεται το index της locate(1) ή, στη γενική περίπτωση, να τρέχει ένα πρόγραμμα σε συγκεκριμένο χρόνο, τότε τροποποιεί κατάλληλα το configuration του cron(8).

Σε αυτή την άσκηση θα πρέπει να κατασκευάσετε μια απλουστευμένη έκδοση του cron(8), την οποία θα αποκαλούμε chrond.

Κατά την εκπόνηση της άσκησης θα έχετε την ευκαιρία να εξοικειωθείτε με τις ακόλουθες βασικές έννοιες σύγχρονων λειτουργικών συστημάτων: processes, daemon processes, fork(), files, named pipes (FIFOs), sleep, polling, signals (SIG_CHLD).

Αρχιτεκτονική του chrond

Ο chrond που θα κατασκευάσετε θα πρέπει να είναι ένα daemon process. Ως daemon process στο Unix ορίζεται ένα process που εκτελείται μόνιμα στο υπόβαθρο (background) του συστήματος, χωρίς να έχει άμεση αλληλεπίδραση με το χρήστη μέσω του standard input/output ή κάποιου γραφικού interface. Πολλές υπηρεσίες στο Unix (για παράδειγμα η υπηρεσία που εκτυπώνει έγγραφα ή που αναλαμβάνει την αποστολή/υποδοχή της ηλεκτρονικής αλληλογραφίας) είναι υλοποιημένες ως daemon processes. Η υπηρεσία του cron(8) είναι επίσης υλοποιημένη με τον ίδιο τρόπο.

Ο chrond θα έχει ένα resource, ένα named pipe (chrond_fifo, δείτε πιο κάτω το API), το οποίο θα πρέπει να χειρίζεται περιοδικά. Ας το δούμε πιο αναλυτικά.

Έλεγχος του chrond μέσω ενός named pipe (FIFO)

Επειδή ο chrond δεν μπορεί να αλληλεπιδρά με χρήστες μέσω του standard input/output θα πρέπει να χρησιμοποιηθεί κάποιος εναλλακτικός τρόπος επικοινωνίας. Ένας από τους πλέον συνηθισμένους τρόπους για InterProcess Communication (IPC) είναι η χρήση named pipes. Τα named pipes είναι κλασσικά pipes, αλλά παράλληλα έχουν αναπαράσταση στο file system, ως κλασσικά αρχεία. Επίσης υποστηρίζουν την επικοινωνία (read/write) οποιονδήποτε processes, τα οποία έχουν τα κατάλληλα permissions για την αλληλεπίδραση με το named pipe.

Κατά την εκκίνησή του, o chrond θα πρέπει (αν δεν έχει κατασκευαστεί ήδη) να δημιουργήσει ένα νέο named pipe, το οποίο και θα ελέγχει για νέα μηνύματα περιοδικά ανά ένα σταθερό χρονικό διάστημα (POLL_CMD_INTERVAL). Ο chrond είναι υπεύθυνος για την υποστήριξη δύο μηνυμάτων, R και Q. Ας τα δούμε αναλυτικά.

Μήνυμα Q

Q:= Όπου το 'Q' προκύπτει από τη λέξη 'Quit'.

Σε αυτή την περίπτωση, ο chrond θα πρέπει να τερματίζει τη λειτουργία του.

Μήνυμα R

R t:program:= Όπου το 'R' προκύπτει από τη λέξη 'Run', το t είναι χρόνος σε δευτερόλεπτα και το program είναι το όνομα ενός προγράμματος. Για παράδειγμα, το ακόλουθο:

R 30:ls

σημαίνει "τρέξε το πρόγραμμα ls σε 30 δευτερόλεπτα". Η έξοδος του προγράμματος (στη συγκεκριμένη περίπτωση, του ls) θα πρέπει να αποθηκεύεται σε ένα μοναδικό αρχείο στο directory chrond_out_dir (δείτε πιο κάτω το API). Για να εγγυηθείτε ότι το αρχείο που περιέχει την έξοδο είναι μοναδικό, μπορείτε να χρησιμοποιήσετε το CHROND_PREFIX (δείτε πιο κάτω το API), το οποίο θα συνενώσετε με έναν ακέραιo μετρητή. Για παράδειγμα η έξοδος του πρώτου προγράμματος μπορεί να αποθηκευθεί στο αρχείο με όνομα chrond__out__1.

Κατά τη διάρκεια που ένα πρόγραμμα έχει χρονοπρογραμματιστεί για εκτέλεση (για παράδειγμα μετά απο 60 δευτερόλεπτα), ο chrond θα πρέπει να είναι σε θέση να πάρει νέα μηνύματα προς εκτέλεση (δηλαδή δεν θα πρέπει να "μπλοκάρει"). Μια πρώτη ιδέα είναι ο chrond να χρησιμοποιεί το system call fork() για κάθε πρόγραμμα, το οποίο προορίζεται προς εκτέλεση. Όπως γνωρίζουμε όμως, κατά τη διάρκεια ενός fork() η μητρική διεργασία πρέπει να περιμένει (με wait()/waitpid()) τη θυγατρική. Στην περίπτωσή μας, αυτό δεν είναι θεμιτό, γιατί όπως είπαμε δεν θέλουμε ο chrond να "μπλοκάρει". Μια δεύτερη σκέψη είναι να αναγκάσουμε τη μητρική διεργασία να μην περιμένει καθόλου τη θυγατρική. Δυστυχώς, αυτό θα έχει ως αποτέλεσμα η θυγατρική διεργασία να καταλήξει σε zombie process. Ως zombie process αναφερόμαστε σε μια διεργασία που τερματίζει χωρίς να την περιμένει η μητρική της. Παρακάτω σας παραθέτουμε ένα παράδειγμα προς αποφυγή, το οποίο κατασκευάζει ένα zombie process.

/* Zombie process demonstration.  */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  if (fork < 0) {
    fprintf(stderr, "fork() failed.\n");
    exit(0);
  }
  if (fork() == 0)
    exit(1);

  while(1) { /* Infinite loop.  */ };

  return 1;
}
Σωστή προσέγγιση. Κάθε φορά που τερματίζει μια διεργασία στέλνει το signal SIG_CHLD στη μητρική της. Αυτό μας επιτρέπει να κάνουμε το ακόλουθο. Η μητρική διεργασία δεν περιμένει (με wait()/waitpid()) τη θυγατρική, παρά μόνο όταν η τελευταία τερματίσει (η μητρική διεργασία μπορεί να ξέρει πότε η θυγατρική τερμάτισε αφού θα δεχθεί το SIG_CHLD). Για να το κάνετε αυτό θα πρέπει να δημιουργήσετε το δικό σας signal handler που χειρίζεται το SIG_CHLD κατάλληλα.

Έλεγχος του named pipe

Για να ελέγξετε τη λειτουργία του named pipe μπορείτε να ανακατευθύνετε σε αυτό εντολές μέσω του shell. Για παράδειγμα:

% echo R 30:ls >> comm

Υποθέτουμε ότι το named pipe αναπαρίσταται στο file system με το αρχείο 'comm'.

chrond API

Παρακάτω σας δίνουμε ένα API για την κατασκευή του chrond. Είστε ελεύθεροι να χρησιμοποιήσετε το παρακάτω API ή κάποιο δικό σας.


/* chrond API.  */

/* Constants.  */
const char *chrond_fifo = "comm";
const char *chrond_out_dir = "./out/";

#define POLL_CMD_INTERVAL   5   /* Poll for status cmds every 5 secs.    */   

#define CHROND_PREFIX "chrond__out__"
#define CHROND_QUEUE_MAX 500    /* Max number of jobs.  */

/* chrond API.  */
void chrond_init(void);                         /* Initialization stuff.  */
int chrond_daemonize(void);                     /* Become daemon.  */
int chrond_dispatch(char *jobname, int delay);  /* Dispatch a job.  */
void chrond_handle_msg(char *msg);              /* Handle a command from the FIFO communication
                                                     channel.  */


Daemon Coding Rules

Tεχνικές λεπτομέριες για την κατασκευή της chrond_daemonize() από το Advanced Programming in the UNIX Environment (W. Richard Stevens).

Reference - man pages

Ένα man page περιγράφει τον τρόπο λειτουργίας ενός προγράμματος, ενός system call ή μιας library function. Η εμφάνιση ενός man page γίνεται με τη χρήση της εντολής:

man(1)

Για να δείτε το man page (σε SunOS) που αναφέρεται στη foo(N), κάνετε:

% man -s N foo

Για να δείτε το man page (σε Linux) που αναφέρεται στη foo(N), κάνετε:

% man -S N foo

Ο συμβολισμός foo(N) αναφέρεται στο man page που περιγράφει τη foo στη κατηγορία (section) 'Ν'.

Λίστα με χρήσιμα man pages για την άσκηση 1

Η παρακάτω λίστα δεν είναι δεσμευτική. Μπορείτε να χρησιμοποιήσετε και εναλλακτικούς τρόπους.

fork(2)
setsid(2)
chdir(2)
umask(2)
opendir(3C)
readdir(3C)
fstat(3C)
system(3C)
alarm(2)
signal(3C)
mkfifo(3C)
sleep(3C)

Παρατηρήσεις

  1. Η άσκηση είναι ατομική. Τυχόν αντιγραφές μπορούν να ανιχνευθούν εύκολα από κατάλληλο πρόγραμμα και θα μηδενιστούν. Συμπεριλάβετε το όνομα σας και το λογαριασμό σας (account) σε όλα τα αρχεία. Μην παραδώσετε εκτυπώσεις των προγραμμάτων.
  2. Κατασκευάστε ένα αρχείο Makefile, έτσι ώστε πληκτρολογώντας make all να γίνεται η μεταγλώττιση (compilation) του προγράμματος και να παράγεται το εκτελέσιμο αρχείο. Επίσης πληκτρολογώντας make clean να καθαρίζονται όλα τα περιττά αρχεία, και να μένουν μόνο τα αρχεία που χρειάζονται για τη μεταγλώττιση.
  3. Επιπλέον, γράψτε και ένα αρχείο readme.txt το πολύ 30 γραμμών που να περιέχει επεξηγήσεις για τον τρόπο υλοποίησης.
  4. Κατασκευάστε το αρχείο assign1.tar.gz που θα περιέχει όλα τα αρχεία που χρειάζονται για τη μεταγλώττιση της άσκησης 1 (χρησιμοποιώντας την εντολή tar και για συμπίεση το πρόγραμμα gzip). Παραδώστε το παραπάνω αρχείο χρησιμοποιώντας το πρόγραμμα submit (πληκτρολογήστε ~hy345/bin/submit 1 από το directory που περιέχει το αρχείο assign1.tar.gz και από μηχάνημα SunOS).
  5. Το πρόγραμμα σας θα πρέπει να τρέχει σε SunOS (π.χ. hades).
  6. Σε πολλές περιπτώσεις τα ονόματα των συναρτήσεων βιβλιοθήκης είναι ενδεικτικά. Μπορείτε να χρησιμοποιήσετε όποια σας βολεύουν.