661 | | 1. Vous avez appris à écrire des programmes assembleur, mais parfois il est plus simple, voire nécessaire, de mélanger le code C et le code assembleur. Dans l'exemple ci-dessous, nous voyons comment la fonction `kinit()` procède pour entrer dans la fonction placée à l'adresse `__crt0` définie dans le fichier `kernel.ld`.\\Remarquez la syntaxe, ici `volatile` permet de dire au compilateur d'insérer le code tel que sans le modifier. Notez aussi l'absence de `,` entre les chaînes de caractères. Le premier argument de `__asm__` est une chaîne de caractères **unique** dans laquelle les instructions sont séparées par de `\n`. Il peut y avoir d'autres arguments, nous n'en aurons pas besoin.\\ \\Dans quelle section se trouve l'adresse `__crt0`? Combien vaut-elle? Est-ce que cette valeur est imposée par le processeur MIPS comme l'adresse de boot ou d'entrée dans le kernel? Quelle fonction est à cette adresse? Pourquoi doit-on écrire ce code en assembleur? |
662 | | {{{#!c |
663 | | 9 void kinit (void) |
664 | | 10 { |
665 | | 11 kprintf (0, banner); |
| 661 | 1. Vous avez appris à écrire des programmes assembleur, mais parfois il est plus simple, voire nécessaire, de mélanger le code C et le code assembleur. Dans l'exemple ci-dessous, nous voyons comment la fonction `syscall()` est écrite. Cette fonction utilise l'instruction `syscall`.\\Deux exemples d'usage de la fonction `syscall()` pris dans le fichier `tp2/4_libc/ulib/libc.c` |
| 662 | {{{#!c |
| 663 | 1 int fprintf (int tty, char *fmt, ...) |
| 664 | 2 { |
| 665 | 3 int res; |
| 666 | 4 char buffer[PRINTF_MAX]; |
| 667 | 5 va_list ap; |
| 668 | 6 va_start (ap, fmt); |
| 669 | 7 res = vsnprintf(buffer, sizeof(buffer), fmt, ap); |
| 670 | 8 res = syscall (tty, (int)buffer, 0, 0, SYSCALL_TTY_PUTS); |
| 671 | 9 va_end(ap); |
| 672 | 10 return res; |
| 673 | 11 } |
667 | | 13 // put bss sections to zero. bss contains uninitialised global variables |
668 | | 14 extern int __bss_origin; // first int of bss section |
669 | | 15 extern int __bss_end; // first int of above bss section |
670 | | 16 for (int *a = &__bss_origin; a != &__bss_end; *a++ = 0); |
671 | | 17 |
672 | | 18 // this code allows to exit the kernel to go to user code |
673 | | 19 __asm__ volatile ( "la $26, __text_origin \n" // get first address of user code |
674 | | 20 "mtc0 $26, $14 \n" // put it in c0_EPC |
675 | | 21 "li $26, 0b00010010 \n" // next status [UM,0,ERL,EXL,IE] |
676 | | 22 "mtc0 $26, $12 \n" // UM <- 1, IE <- 0, EXL <- 1 |
677 | | 23 "la $29, __data_end \n" // define new user stack pointer |
678 | | 24 "eret \n"); // j EPC and EXL <- 0 |
679 | | 25 } |
680 | | }}} |
681 | | {{{#!protected ------------------------------------------------------------------------------------ |
682 | | ''''''''''''''' |
683 | | - L'adresse `__crt0` est la première adresse de la section `text` dans laquelle se trouve le code de l'application. |
684 | | - Elle vaut `0X7F400000`. |
685 | | - Cette adresse n'est pas imposée par le MIPS. C'est le choix des architectes du SoC. La seule condition est que cette adresse soit |
686 | | dans la partie accessible en mode user. |
687 | | - A cette adresse, on place la fonction `__start()`. |
688 | | - On est obligé d'écrire ce code en assembleur parce que la manière de changer de mode (de `kernel` à `user`) est propre à chaque processeur. Il n'y a aucun moyen de le faire en C. |
689 | | ''''''''''''''' |
690 | | }}} |
691 | | 1. Dans le code C de la question précédente, à quoi servent les lignes 14 à 16? Pourquoi faire des déclarations `extern`? |
692 | | {{{#!protected ------------------------------------------------------------------------------------ |
693 | | ''''''''''''''' |
694 | | - Ces lignes servent à mettre à 0 la zone des variables globales non initialisées explicitement par le programme. |
695 | | - Les déclarations `extern` permettent d'informer le compilateur que les adresses `__bss_orgin` et `__bss_end` |
696 | | existent ailleurs. De fait, elles sont définies dans le fichier `kernel.ld`. |
697 | | ''''''''''''''' |
| 675 | 13 void exit (int status) |
| 676 | 14 { |
| 677 | 15 syscall( status, 0, 0, 0, SYSCALL_EXIT); // never returns |
| 678 | 16 } |
| 679 | }}} |
| 680 | Le code de cette fonction est dans le fichier `04_libc/ulib/crt0.c` |
| 681 | {{{#!c |
| 682 | 1 //int syscall (int a0, int a1, int a2, int a3, int syscall_code) |
| 683 | 2 __asm__ ( |
| 684 | 3 ".globl syscall \n" |
| 685 | 4 "syscall: \n" |
| 686 | 5 " lw $2,16($29) \n" |
| 687 | 6 " syscall \n" |
| 688 | 7 " jr $31 \n" |
| 689 | 8 ); |
| 690 | }}} |
| 691 | Combien d'arguments a la fonction `syscall()`? |
| 692 | Comment la fonction `syscall()` reçoit-elle ses arguments ? |
| 693 | A quoi sert la ligne 3 de la fonction `syscall()` et que se passe-t-il si on la retire ? |
| 694 | Expliquer la ligne 5 de la fonction `syscall()`. |
| 695 | Aurait-il été possible de mettre le code de la fonction `syscall()` dans un fichier `.S` ? |
| 696 | {{{#!protected ------------------------------------------------------------------------------------ |
| 697 | '' |
| 698 | - La fonction `syscall()` a 5 a arguments |
| 699 | - Elle reçoit ses 4 premiers arguments dans les registres $4 à $7 et le 5e (le numéro de service) dans la pile. |
| 700 | - La ligne 3 sert à dire que syscall est une étiquette utilisée dans un autre fichier. `.globl` signifie **glob**al **l**abel. Si on la retire, il y aura un problème lors de l'édition de lien. `syscall()` ne sera pas trouvé par l'éditeur de liens. |
| 701 | - Le noyau attend le numéro de service dans `$2`. Or le numéro du service est le 5e argument de la fonction `syscall()`. La ligne 5 permet d'aller le chercher dans la pile. |
| 702 | - oui, ce code de la fonction `syscall()` qui fait appel à l'instruction `syscall` aurait pu être mis dans un fichier en assembleur, mais cela aurait demandé d'avoir un fichier de plus, pour une seule fonction. Dans une version plus évoluée du système, il y aura un d'autres fonctions assembleur, alors on créera un fichier assembleur pour les réunir. |
| 703 | '' |