Tandis que le "monde" se construit sous vos yeux ébahis

, je vous propose, pour patienter, d'ouvrir une autre console et d'aller faire un tour dans le dossier
/usr/src pour jeter un coup d'oeil au code-source que vous êtes en train de compiler. Si vous venez de Windows ou de Mac OS X, ce sera une grande première pour vous. Et si vous venez de Linux, il y a de fortes chances pour que ce soit une grande première quand-même.

Vous vous souvenez certainement du programme
loader, celui qui charge le noyau, et que vous configurez via
/boot/loader.conf. Nous allons examiner le fichier principal de son code-source :
Code : Console | % less /usr/src/sys/boot/i386/loader/main.c |
Voici donc la version "compréhensible par les êtres humains". Bon alors, évidemment, il faut être bien plus calé en informatique que vous et moi pour comprendre tout ça en détails. Voyons si nous pouvons tout de même y saisir quelque chose.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | /*-
* Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
|
Toutes les lignes qui apparaîssent ici en bleu se situent entre un
/* et un
*/. Ce sont des commentaires. L'ordinateur les ignore. Ils sont là pour qu'un programmeur lisant ce code comprenne tout de suite ce qu'il fait. Les 25 premières lignes sont donc un long commentaire indiquant que la première version de ce programme a été écrite en 1998 par un certain
Michael Smith. Vous voyez même son e-mail si vous voulez lui demander des précisions.

Puis, il y a quelques mentions légales.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/boot/i386/loader/main.c,v 1.44.2.1.2.1 2009/10/25 01:10:29 kensmith Exp $");
/*
* MD bootstrap main() and assorted miscellaneous
* commands.
*/
#include <stand.h>
#include <string.h>
#include <machine/bootinfo.h>
#include <machine/psl.h>
#include <sys/reboot.h>
#include "bootstrap.h"
#include "libi386/libi386.h"
#include "btxv86.h"
#define KARGS_FLAGS_CD 0x1
#define KARGS_FLAGS_PXE 0x2
#define KARGS_FLAGS_ZFS 0x4
|
Les lignes en orange, qui commencent par des
#, sont des
directives de préprocesseur. Les
#include appellent des
bibliothèques : des petits bouts de programme dont celui-ci a besoin. Vous voyez par exemple un appel à la bibliothèque
<string.h> qui aide à gérer les chaînes de caractères. Celles dont le nom est entouré de
< > sont des bibliothèques standards (très répandues et situées dans le dossier
/usr/include/). Les autres, dont le nom est entouré de
" " sont implémentées dans un autre fichier de ce code-source. Les
#define, enfin, fixent la valeur de quelques constantes.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | /* Arguments passed in from the boot1/boot2 loader */
static struct
{
u_int32_t howto;
u_int32_t bootdev;
u_int32_t bootflags;
union {
struct {
u_int32_t pxeinfo;
u_int32_t res2;
};
uint64_t zfspool;
};
u_int32_t bootinfo;
} *kargs;
|
Ensuite, nous trouvons la définition de la
structure *kargs et nous apprenons qu'un *kargs est un ensemble de données composé de 4 nombres entiers (de type
u_int32_t) qu'on appelle respectivement
howto,
bootdev,
bootflags et
bootinfo, ainsi que d'une structure plus petite contenant elle-même deux autres nombres entiers :
pxeinfo et
res2. Le commentaire au dessus nous indique que tous ces nombres sont des informations transmises à
loader par les programmes
boot1 et
boot2. En effet, vous vous souvenez peut-être (chapitre
L'envers du décor) que sont eux qui lancent le programme
loader.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | static u_int32_t initial_howto;
static u_int32_t initial_bootdev;
static struct bootinfo *initial_bootinfo;
struct arch_switch archsw; /* MI/MD interface boundary */
static void extract_currdev(void);
static int isa_inb(int port);
static void isa_outb(int port, int value);
void exit(int code);
/* from vers.c */
extern char bootprog_name[], bootprog_rev[], bootprog_date[], bootprog_maker[];
/* XXX debugging */
extern char end[];
static void *heap_top;
static void *heap_bottom;
|
Puis viennent des déclarations de
variables globales, des variables auxquelles toutes les fonctions du programme ont accès. Une
fonction est une suite d'instructions à exécuter, instructions qui peuvent dépendre des
arguments qu'on donne à la fonction. Elle peut être appelée plusieurs fois dans un programme. Certaines ont un nom commençant par
*. Ce sont des
pointeurs : elles désignent une certaine case de la mémoire de l'ordinateur.
Il y a aussi quatre
prototypes de fonctions (reconnaissables au fait qu'une partie de la ligne est entre parenthèses). Ils indiquent que les fonctions
extract_currdev,
isa_inb,
isa_out et
exit seront
implémentées plus bas dans le programme. Autrement dit, on y trouvera la suite d'instructions à exécuter quand la fonction est appelée.
Code : C | int
main(void)
{
int i;
/* Pick up arguments */
kargs = (void *)__args;
initial_howto = kargs->howto;
initial_bootdev = kargs->bootdev;
initial_bootinfo = kargs->bootinfo ? (struct bootinfo *)PTOV(kargs->bootinfo) : NULL;
|
Nous en arrivons au coeur du programme : la fonction
main. C'est là qu'il va commencer à exécuter des instructions. Les premières consistent à affecter aux variables
initial_howto,
initial_bootdev et
initial_bootinfo les valeurs contenues dans le
karg et venant de boot1 et boot2.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 | /* Initialize the v86 register set to a known-good state. */
bzero(&v86, sizeof(v86));
v86.efl = PSL_RESERVED_DEFAULT | PSL_I;
/*
* Initialise the heap as early as possible. Once this is done, malloc() is usable.
*/
bios_getmem();
#if defined(LOADER_BZIP2_SUPPORT) || defined(LOADER_FIREWIRE_SUPPORT) || defined(LOADER_GPT_SUPPORT) || defined(LOADER_ZFS_SUPPORT)
heap_top = PTOV(memtop_copyin);
memtop_copyin -= 0x300000;
heap_bottom = PTOV(memtop_copyin);
#else
heap_top = (void *)bios_basemem;
heap_bottom = (void *)end;
#endif
setheap(heap_bottom, heap_top);
/*
* XXX Chicken-and-egg problem; we want to have console output early, but some
* console attributes may depend on reading from eg. the boot device, which we
* can't do yet.
*
* We can use printf() etc. once this is done.
* If the previous boot stage has requested a serial console, prefer that.
*/
bi_setboothowto(initial_howto);
if (initial_howto & RB_MULTIPLE) {
if (initial_howto & RB_SERIAL)
setenv("console", "comconsole vidconsole", 1);
else
setenv("console", "vidconsole comconsole", 1);
} else if (initial_howto & RB_SERIAL)
setenv("console", "comconsole", 1);
else if (initial_howto & RB_MUTE)
setenv("console", "nullconsole", 1);
cons_probe();
/*
* Initialise the block cache
*/
bcache_init(32, 512); /* 16k cache XXX tune this */
/*
* Special handling for PXE and CD booting.
*/
if (kargs->bootinfo == 0) {
/*
* We only want the PXE disk to try to init itself in the below
* walk through devsw if we actually booted off of PXE.
*/
if (kargs->bootflags & KARGS_FLAGS_PXE)
pxe_enable(kargs->pxeinfo ? PTOV(kargs->pxeinfo) : NULL);
else if (kargs->bootflags & KARGS_FLAGS_CD)
bc_add(initial_bootdev);
}
archsw.arch_autoload = i386_autoload;
archsw.arch_getdev = i386_getdev;
archsw.arch_copyin = i386_copyin;
archsw.arch_copyout = i386_copyout;
archsw.arch_readin = i386_readin;
archsw.arch_isainb = isa_inb;
archsw.arch_isaoutb = isa_outb;
/*
* March through the device switch probing for things.
*/
for (i = 0; devsw[i] != NULL; i++)
if (devsw[i]->dv_init != NULL)
(devsw[i]->dv_init)();
printf("BIOS %dkB/%dkB available memory\n", bios_basemem / 1024, bios_extmem / 1024);
if (initial_bootinfo != NULL) {
initial_bootinfo->bi_basemem = bios_basemem / 1024;
initial_bootinfo->bi_extmem = bios_extmem / 1024;
}
/* detect ACPI for future reference */
biosacpi_detect();
/* detect SMBIOS for future reference */
smbios_detect();
printf("\n");
printf("%s, Revision %s\n", bootprog_name, bootprog_rev);
printf("(%s, %s)\n", bootprog_maker, bootprog_date);
extract_currdev(); /* set $currdev and $loaddev */
setenv("LINES", "24", 1); /* optional */
bios_getsmap();
interact(); /* doesn't return */
/* if we ever get here, it is an error */
return (1);
}
|
Voici (ci-dessus) le reste de la fonction main. Je n'ai pas le temps de vous le détailler (d'autant que le sens de plusieurs de ces lignes m'échappe). Après avoir appris le
langage C, vous y reconnaîtrez des tests de conditions (commençant par
if), l'affectation de
variables d'environnement (avec
setenv), une boucle
for qui exécute encore et encore certaines instructions et parcourt une à une les cases d'un tableau appelé
devsw (pour initialiser les périphériques) jusqu'à en trouver une vide, des appels à la fonction
printf pour afficher certaines informations dans la console, et l'instruction finale
return qui renvoie
1 en cas de problème.
Ce
main fait aussi appel à quelques fonctions qui sont implémentées par la suite. Les voici justement :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 | /*
* Set the 'current device' by (if possible) recovering the boot device as
* supplied by the initial bootstrap.
*
* XXX should be extended for netbooting.
*/
static void
extract_currdev(void)
{
struct i386_devdesc new_currdev;
int biosdev = -1;
/* Assume we are booting from a BIOS disk by default */
new_currdev.d_dev = &biosdisk;
/* new-style boot loaders such as pxeldr and cdldr */
if (kargs->bootinfo == 0) {
if ((kargs->bootflags & KARGS_FLAGS_CD) != 0) {
/* we are booting from a CD with cdboot */
new_currdev.d_dev = &bioscd;
new_currdev.d_unit = bc_bios2unit(initial_bootdev);
} else if ((kargs->bootflags & KARGS_FLAGS_PXE) != 0) {
/* we are booting from pxeldr */
new_currdev.d_dev = &pxedisk;
new_currdev.d_unit = 0;
} else {
/* we don't know what our boot device is */
new_currdev.d_kind.biosdisk.slice = -1;
new_currdev.d_kind.biosdisk.partition = 0;
biosdev = -1;
}
} else if ((initial_bootdev & B_MAGICMASK) != B_DEVMAGIC) {
/* The passed-in boot device is bad */
new_currdev.d_kind.biosdisk.slice = -1;
new_currdev.d_kind.biosdisk.partition = 0;
biosdev = -1;
} else {
new_currdev.d_kind.biosdisk.slice = B_SLICE(initial_bootdev) - 1;
new_currdev.d_kind.biosdisk.partition = B_PARTITION(initial_bootdev);
biosdev = initial_bootinfo->bi_bios_dev;
/*
* If we are booted by an old bootstrap, we have to guess at the BIOS
* unit number. We will lose if there is more than one disk type
* and we are not booting from the lowest-numbered disk type
* (ie. SCSI when IDE also exists).
*/
if ((biosdev == 0) && (B_TYPE(initial_bootdev) != 2)) /* biosdev doesn't match major */
biosdev = 0x80 + B_UNIT(initial_bootdev); /* assume harddisk */
}
new_currdev.d_type = new_currdev.d_dev->dv_type;
/*
* If we are booting off of a BIOS disk and we didn't succeed in determining
* which one we booted off of, just use disk0: as a reasonable default.
*/
if ((new_currdev.d_type == biosdisk.dv_type) &&
((new_currdev.d_unit = bd_bios2unit(biosdev)) == -1)) {
printf("Can't work out which disk we are booting from.\n"
"Guessed BIOS device 0x%x not found by probes, defaulting to disk0:\n", biosdev);
new_currdev.d_unit = 0;
}
env_setenv("currdev", EV_VOLATILE, i386_fmtdev(&new_currdev),
i386_setcurrdev, env_nounset);
env_setenv("loaddev", EV_VOLATILE, i386_fmtdev(&new_currdev), env_noset,
env_nounset);
#ifdef LOADER_ZFS_SUPPORT
/*
* If we were started from a ZFS-aware boot2, we can work out
* which ZFS pool we are booting from.
*/
if (kargs->bootflags & KARGS_FLAGS_ZFS) {
/*
* Dig out the pool guid and convert it to a 'unit number'
*/
uint64_t guid;
int unit;
char devname[32];
extern int zfs_guid_to_unit(uint64_t);
guid = kargs->zfspool;
unit = zfs_guid_to_unit(guid);
if (unit >= 0) {
sprintf(devname, "zfs%d", unit);
setenv("currdev", devname, 1);
}
}
#endif
}
|
La fonction
extract_currdev, avec la suite d'instructions à exécuter quand on l'appelle.
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 | COMMAND_SET(reboot, "reboot", "reboot the system", command_reboot);
static int
command_reboot(int argc, char *argv[])
{
int i;
for (i = 0; devsw[i] != NULL; ++i)
if (devsw[i]->dv_cleanup != NULL)
(devsw[i]->dv_cleanup)();
printf("Rebooting...\n");
delay(1000000);
__exit(0);
}
/* provide this for panic, as it's not in the startup code */
void
exit(int code)
{
__exit(code);
}
COMMAND_SET(heap, "heap", "show heap usage", command_heap);
static int
command_heap(int argc, char *argv[])
{
mallocstats();
printf("heap base at %p, top at %p, upper limit at %p\n", heap_bottom,
sbrk(0), heap_top);
return(CMD_OK);
}
/* ISA bus access functions for PnP, derived from <machine/cpufunc.h> */
static int
isa_inb(int port)
{
u_char data;
if (__builtin_constant_p(port) &&
(((port) & 0xffff) < 0x100) &&
((port) < 0x10000)) {
__asm __volatile("inb %1,%0" : "=a" (data) : "id" ((u_short)(port)));
} else {
__asm __volatile("inb %%dx,%0" : "=a" (data) : "d" (port));
}
return(data);
}
static void
isa_outb(int port, int value)
{
u_char al = value;
if (__builtin_constant_p(port) &&
(((port) & 0xffff) < 0x100) &&
((port) < 0x10000)) {
__asm __volatile("outb %0,%1" : : "a" (al), "id" ((u_short)(port)));
} else {
__asm __volatile("outb %0,%%dx" : : "a" (al), "d" (port));
}
}
|
D'autres fonctions, implémentées à leur tour.
Le code-source de FreeBSD comporte des centaines de programmes comme celui-ci. Voila pourquoi il est si long de tout recompiler. Encore un peu de patience : il a bientôt fini.

D'ici là, je vous conseille quand même de quitter la pièce car, en plus d'être un processus long (il paraît qu'il y en a à qui ça prend 7 jours

), la création du monde est un processus bruyant

sur certaines machines.