ΚΛΗΣΕΙΣ ΣΥΣΤΗΜΑΤΟΣ ΣΤΗΝ C

...ασύγχρονα μαθήματα γλώσσας C

ΚΛΗΣΕΙΣ ΣΥΣΤΗΜΑΤΟΣ ΣΤΗΝ C

Δημοσίευσηαπό linuxs » 29 Φεβ 2012, 20:19

Ανοίγω αυτό το νέο θέμα για να καταγράψω κάποια πράγματα που θεωρώ σημαντικά σχετικά με τις κλήσεις συστήματος στην C κυρίως, στο επίπεδο του πυρήνα και όχι του χρήστη! Θα προσθέτω συνεχώς νέες συναρτήσεις/πληροφορίες.




Διεργασίες
Όταν θέλουμε να δούμε τι γίνεται στο επίπεδο του πυρήνα και ξεφεύγουμε απο το επίπεδο του χρήστη χρειαζόμαστε έναν "ορισμό" μια Α κατανόηση αν θέλετε για το τι είναι μια διεργασία. Δεν είναι κάτι τρομερό, θα μπορούσαμε να πούμε οτι είναι ένα κομμάτι κώδικα έτοιμο για χρήση/εκτέλεση! Στα συστήματα Linux μπορούμε να έχουμε πολλές διεργασίες που τρέχουν η μία μετά την άλλη η ακόμη και παράλληλα. Στην πραγματικότητα οι διεργασίες τρέχουν ψευδο-παράλληλα με την έννοια οτι εκτελείται μια και πριν τελειώσει εκτελείται ένα κομμάτι κώδικα απο την επόμενη και πριν τελειώσει απο την επόμενη κτλ. Αυτό γίνεται κυρίως γιατί πολλές φορές για να εκτελεστεί πλήρως μια διεργασία χρειαζόμαστε δεδομένα/πληροφορίες απο κάποια άλλη διεργασία. Ένας άλλος λόγος είναι γιατί, μια διεργασία μπορεί να είναι πολύ μεγάλη συνεπώς αν εκτελείται μόνο αυτή όλο το υπόλοιπο σύστημα θα βρίσκεται σε αναστολή(κάτι που δεν το θελουμε)! Υπάρχουν πολλές συναρτήσεις που έχουν πρόσβαση στον πυρήνα γνωστές ως κλήσεις συστήματος και παρακάτω θα δούμε μερικές.
Επιστροφή στην αρχή


Δημιουργία θυγατρικής διεργασίας - fork()
Η fork() είναι μια απο τις βασικές συναρτήσεις που θα πρέπει να ξέρει κανείς σχετικά με τις κλήσεις συστήματος και όπως αναφέρει και ο τίτλος αυτό που κάνει είναι να δημιουργέι μια θυγατρική διεργασία. Θα εξηγήσω αναλυτικότατα τι και πως ακριβώς γίνεται αυτό. Ποια είναι η γενική ιδέα? Έχουμε ένα κομμάτι κώδικα που εκτελείται, καλούμε σε ένα σημείο την fork(), όλος ο κώδικας απο εκεί και κάτω(κάτω απο την κλήση της fork()) "αντιγράφεται" και εκτελείται ψευδο-παράλληλα με την γονική διεργασία. Με άλλα λόγια έχουμε έναν κώδικα που εκτελείται κανονικά αλλα σε κάποιο σημείο υπάρχει η fork() οπότε απο εκεί και κάτω αρχίζει και εκτελείται παράλληλα και ένας άλλος κώδικας.



Τιμή επιστροφής της συνάρτησης:
  • Αν fork() επιστρέψει αρνητική τιμή, σημαίνει οτι απέτυχε η δημιουργία θυγατρικής διεργασίας.
  • Αν fork() επιστρέψει μηδέν, σημαίνει οτι βρισκόμαστε στην θυγατρική διεργασία/παιδί.
  • Αν fork() επιστρέψει θετική τιμή, σημαίνει οτι βρισκόμαστε στον πατέρα.

Τι μας χρησιμεύουν τα παραπάνω? Φυσικά η τιμή που επιστρέφεται αποθηκεύεται σε κάποια μεταβλητή και ανάλογα με τι επιστρέφει εκτελείται ο κατάλληλος κώδικας. Έχετε αυτό στο μυαλό σας και θα καταλάβετε παρακάτω τι ακριβώς εννοώ!

Ας αναλύσουμε λίγο τον παρακάτω κώδικα. Προφανώς δεν είναι ολοκληρωμένος αλλα δεν έχει σημασία καθώς εμείς θέλουμε να επικεντρωθούμε στην χρήση της fork() και μόνο. Η fork() επιστρέφει έναν ακέραιο οπότε χρειαζόμαστε μια μεταβλητή int έστω 'pid'. Το επόμενο βήμα είναι να εκτελέσουμε την fork(). Όπως έχουμε πει δημιουργείται μια θυγατρική διεργασία με κώδικα ακριβώς όπως και η γονική. Σημαντικό εδώ είναι να καταλάβουμε οτι η θυγατρική αφού δημιουργηθεί δεν τρέχει απο την αρχή αλλα απο την εντολή της fork() κάτω. Δεν εκτελείται πάλι όλος ο κώδικας απο την αρχή δηλαδή. Προηγουμένως είπατε οτι επιστρέφεται και μια τιμή. Η τιμή αυτή επιστρέφεται και στην γονική και στην θυγατρική διεργασία οπότε ελέγχοντας με κάποιο if else την τιμή επιστροφής μπορούμε να εκτελέσουμε την κατάληλο κώδικα. Με απλά λόγια έστω οτι βρισκόμαστε στην γονική διεργασία και εκτελείται ο κώδικας μετά την fork(). Το πρόγραμμα εξετάζει την τιμή επιστροφής της fork() που στην προκειμένη περίπτωση είναι το else αρα ο μόνος κώδικας που θα εκτελεστεί θα είναι το τελευταίο μπλόκ στον πατέρα. Άς υποθέσουμε τώρα οτι είμαστε την θυγατρική διεργασία/κώδικα και συνεχίζουμε μετά την fork(). Ξανά έχουμε κάποιο if και εδώ ο κώδικας που θα εκτελεστεί θα είναι στο δεύτερο μπλόκ καθώς το pid ισούται με μηδέν. Η τρίτη περίπτωση είναι αν έχει συμβεί κάποιο error συνήθως απο έλλειψη RAM μνήμης(μαζί με το swap space). Συνοψίζοντας, καταλαβαίνετε οτι ανάλογα με το pid εκτελείται ο κατάληλος κώδικας!
Κώδικας: Επιλογή όλων
int main(void) {
   int pid_t,pid;
   pid = fork();
   if (pid == -1){   
      /* Error! */
      exit(EXIT_FAILURE);
   }else if(pid == 0){
      /* Child code */
      exit(0);
   }else{
      /* Parent code */
      exit(0);
   }
   return 0;
}


Παρατηρήσεις:
  1. Όταν ελέγχουμε τα pid και μπαίνουμε σε κάποιο μπλόκ στο τέλος καλό είναι να υπάρχει ένα exit() ώστε να είμαστε σίγουροι τι ακριβώς εκτελείται!
  2. Ένα λεπτό σημείο στο οποίο χρειάζεται διευκρίνηση είναι το θέμα του χρόνου. Όταν γίνεται η fork() μετά είπαμε οτι οι δυο διεργασίες εκτελούνται παράλληλα ΑΛΛΑ ποιά εκτελείται πρώτη? :P Η απάντηση είναι ΔΕΝ ΞΕΡΟΥΜΕ :lol: Οπότε εδώ δημιουργείται το πρόβλημα ποια διεργασία να εκτελεστεί πρώτη. Αυτό το λύνουμε με μια συνάρτηση ονόματι wait(), η οποία ουσιαστικά αυτό που κάνει είναι να "παγώνει" μια διεργασία(πχ. την γονική) μέχρι να εκτελεστεί η θυγατρική έτσι ξέρουμε την σειρά εκτέλεσης! :) Δεν θα αναφερθώ παραπάνω στην wait() γιατί θα την δείτε παρακάτω...
Δείτε και ένα ολοκληρωμένο παράδειγμα που βρήκα στη wikipedia:
Μορφοποιημένος Κώδικας: Επιλογή όλων
#include <stdio.h>   /* printf, stderr, fprintf */
#include <sys/types.h> /* pid_t */
#include <unistd.h> /* _exit, fork */
#include <stdlib.h> /* exit */
#include <errno.h> /* errno */

int main(void)
{
int pid_t,pid;

/* Output from both the child and the parent process
* will be written to the standard output,
* as they both run at the same time.
*/
pid = fork();
if (pid == -1)
{
/* Error:
* When fork() returns -1, an error happened
* (for example, number of processes reached the limit).
*/
fprintf(stderr, "can't fork, error %d\n", errno);
exit(EXIT_FAILURE);
}

if (pid == 0)
{
/* Child process:
* When fork() returns 0, we are in
* the child process.
*/
int j;
for (j = 0; j < 10; j++)
{
printf("child: %d\n", j);
sleep(1);
}
_exit(0); /* Note that we do not use exit() */
}
else
{

/* When fork() returns a positive number, we are in the parent process
* (the fork return value is the PID of the newly created child process)
* Again we count up to ten.
*/
int i;
for (i = 0; i < 10; i++)
{
printf("parent: %d\n", i);
sleep(1);
}
exit(0);
}
return 0;
}

Επιστροφή στην αρχή


Διακοπη και εκτέλεση κώδικα με την exec()
Η exec() είναι μια οικογένεια που ανήκει στην βιβλιοθήκη "unistd.h" και αντικαθιστά το πρόγραμμα με κάποιον συγκεκριμένο κώδικα που δίνεται ως όρισμα με όνομα path. Απο την στιγμή που δεν δημιουργείται νέα διεργασία, το pid δεν αλλάζει και το μόνο που αλλάζει είναι τα δεδομένα, το heap και το stack απο την αρχική διεργασία.

Συντάσεται όπως βλέπετε παρακάτω:
Κώδικας: Επιλογή όλων
int execl(char const *path, char const *arg0, ...);

  • path: Το όρισμα path περιγράφει το που θα βρίσκεται το εκτελέσιμο αρχείο.
  • arg0: Το arg0 είναι το όνομα απο το εκτελέσιμο αρχείο και συνήθως είναι το ίδιο με το όνομα του path αλλα δεν είναι στανταρ σε όλες τις πλατφόρμες.

Τιμή επιστροφής της συνάρτησης:
Η συνάρτηση αυτή επιστρέφει -1 ΜΟΝΟ αν κάτι πάει στραβα.

Δείτε παρακάτω ένα παράδειγμα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

int main(void) {
int _pid;

printf("Code executed before fork!\n");
_pid = fork();
if(_pid < 0){ /* Memomy error */
printf("Error in memory!\n");
exit(1);
}else if(_pid == 0){ /* Child */
printf("exec() example here!\n");
execl("/bin/ls", "/bin/ls", "-a", (char *) 0);
printf("This message shouldnt have been seen!\n");
printf("Child ended correctly!\n");
}else{ /* Parent */
printf("Code executed in parent!\n");
printf("Parent ended correctly!\n");
}
return 0;
}


Το αποτέλεσμα του παραπάνω κώδικα είναι:
Κώδικας: Επιλογή όλων
dragon@dragon-laptop:~/Desktop/Operating_Systems$ .   a.out       exec_example.c~   fork_simple_example.c   time_counter.c
..  exec_example.c  exec_repl_code.c  fork_simple_example.c~  time_counter.c~

με άλλα λόγια εκεί που εκτελείται η exec() διακόπτεται το πρόγραμμα και εκτελείται η εντολή ls -a στον τρέχοντα κατάλογο!
Επιστροφή στην αρχή


Παύση εκτέλεσης κώδικα με την wait()
Η wait() είναι μια κλήση συστήματος που μπορεί να χρησιμοποιήσει μια διεργασία για να περιμένει κάποια άλλη. Σήμερα, συνήθως έχουν μια γονική διεργασία(τον πατέρα) που μέσω της fork() δημιουργεί μια θυγατρική(το παιδί) και χρησιμοποιούμε την wait() στον πατέρα για να πειμένουμε την εκτέλεση του παιδιού. Όταν εκτελείται ο κώδικας του παιδιού και τελειώνει επιστρέφεται μια τιμή με την exit() στην wait() και συνεχίζουμε με την εκτέλεση του κώδικα του πατέρα. Αυτό που επιστρέφεται απο την θυγατρική λέγεται status και έχει πληροφορίες για το αν υπήρξε κάποιο error η εκτελέστηκε σωστά ο κώδικας του παιδιού. Στην πραγματικότητα αυτά που μπορεί να γίνουν είναι να τερματιστεί ο κώδικας του παιδιού, να τερματιστεί απο κάποιο σήμα και να συνεχίζει την εκτέλεση του απο κάποιο σήμα. Όταν το παιδί αλλάξει για οποιονδήποτε λόγο κατάσταση κατευθείαν ενημερώνεται η wait(). Κάθε διεργασία καταλαμβάνει κάποιους πόρους στο σύστημα και με την wait() αυτοί οι πόροι ελευθερώνονται! Εάν απο την άλλοι δεν χρησιμοποιήοσυμε την wait() θα δημιουργηθει zombie.
Η waitpid() κάνει παύση απο την διαργασία μέχρ κάπιο παιδί με κάποιο PID αλλάξει κατασταση.until a child specified by pid argument has changed state. By default, η waitpid() περιμένει μόνο για τερματισμένα παιδιά.

<Θα προσπθεθεί κώδικας-παράδειγμα>
Επιστροφή στην αρχή


Παύση για λίγο με την sleep()
Η sleep() είναι μια κλήση που κάνει μη ενεργή την τρέχων διεργασία(απο την οποία εκτελείται) για τόσο χρόνο όσο λέει το όρισμά της. Μπορεί να διακοπεί μόνο αν τελειώνει ο χρονομετρητής της η έρθει κάποιο σήμα διακοπής. Την χρησιμοποιούμε συνήθως σε κάποια διεργασία για να περιμένουμε κάποιο πλήθος απο sec ώστε να τελειώσει η εκτέλεση του παιδιού. Το όρισμά της αναφέρεται σε sec στο linux και Milisec στα windows.
Επιστροφή στην αρχή

Ελπίζω να βοήθησαν και να έλυσαν κάποιες απορίες που ίσως είχατε :)
Τελευταία επεξεργασία από linuxs και 02 Μαρ 2012, 21:48, έχει επεξεργασθεί 11 φορά/ες συνολικά
Αιτία: Προσθήκη περιεχομένου
Αν το πρόβλημά μας επιλυθεί. Επιλέγουμε το θέμα που βοήθησε στην επίλυση και πατάμε το κουμπάκι Εικόνα.
Γνώσεις ⇛ Linux: Μέτριο┃Προγραμματισμός: C┃Αγγλικά: Καλά
Λειτουργικό ⇛ Linux Ubuntu 10.4 LTS
Προδιαγραφές ⇛ Intel Pentium @T4500 2.3GHz│ 512GB VRAM│ 500 HDD│ ATI RADEON HD545v 512 MB │ Screen: 15.6''
Άβαταρ μέλους
linuxs
daemonTUX
daemonTUX
 
Δημοσιεύσεις: 1060
Εγγραφή: 02 Ιούλ 2010, 13:19
Τοποθεσία: GR
IRC: linuxs
Εκτύπωση

Re: ΚΛΗΣΕΙΣ ΣΥΣΤΗΜΑΤΟΣ ΣΤΗΝ C

Δημοσίευσηαπό Star_Light » 02 Μαρ 2012, 02:03

Eνδιαφερον κεφάλαιο οι κλήσεις συστήματος στην C !!!!

Καλη ιδεα ;)
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: ΚΛΗΣΕΙΣ ΣΥΣΤΗΜΑΤΟΣ ΣΤΗΝ C

Δημοσίευσηαπό linuxs » 02 Μαρ 2012, 02:17

Star_Light έγραψε:Eνδιαφερον κεφάλαιο οι κλήσεις συστήματος στην C !!!!

Καλη ιδεα ;)

Ναι, καλό είναι κάποιοι που θέλουν να προχωρήσουν παρακάτω απο το επίπεδο του χρήστη να ρίξουν μια ματιά. :)
Αν το πρόβλημά μας επιλυθεί. Επιλέγουμε το θέμα που βοήθησε στην επίλυση και πατάμε το κουμπάκι Εικόνα.
Γνώσεις ⇛ Linux: Μέτριο┃Προγραμματισμός: C┃Αγγλικά: Καλά
Λειτουργικό ⇛ Linux Ubuntu 10.4 LTS
Προδιαγραφές ⇛ Intel Pentium @T4500 2.3GHz│ 512GB VRAM│ 500 HDD│ ATI RADEON HD545v 512 MB │ Screen: 15.6''
Άβαταρ μέλους
linuxs
daemonTUX
daemonTUX
 
Δημοσιεύσεις: 1060
Εγγραφή: 02 Ιούλ 2010, 13:19
Τοποθεσία: GR
IRC: linuxs
Εκτύπωση


  • ΣΧΕΤΙΚΑ ΘΕΜΑΤΑ
    ΑΠΑΝΤΗΣΕΙΣ
    ΠΡΟΒΟΛΕΣ
    ΣΥΓΓΡΑΦΕΑΣ

Επιστροφή στο Μαθήματα C