start_kernel
Sur 4.2, start_kernel
à partir de init/main.c
est un processus d'initialisation considérable et pourrait être comparé à un main
fonction.
C'est le premier code indépendant de l'architecture à s'exécuter et il configure une grande partie du noyau. Tellement comme main
, start_kernel
est précédé d'un code de configuration de niveau inférieur (fait dans le crt*
objets dans userland main
), après quoi le code C générique "principal" s'exécute.
Comment start_kernel
est appelé en x86_64
arch/x86/kernel/vmlinux.lds.S
, un script de l'éditeur de liens, définit :
ENTRY(phys_startup_64)
et
phys_startup_64 = startup_64 - LOAD_OFFSET;
et :
#define LOAD_OFFSET __START_KERNEL_map
arch/x86/include/asm/page_64_types.h
définit __START_KERNEL_map
comme :
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)
qui est l'adresse d'entrée du noyau. TODO comment cette adresse est-elle atteinte exactement ? Je dois comprendre l'interface que Linux expose aux chargeurs de démarrage.
arch/x86/kernel/vmlinux.lds.S
définit la toute première section du chargeur de démarrage comme :
.text : AT(ADDR(.text) - LOAD_OFFSET) {
_text = .;
/* bootstrapping code */
HEAD_TEXT
include/asm-generic/vmlinux.lds.h
définit HEAD_TEXT
:
#define HEAD_TEXT *(.head.text)
arch/x86/kernel/head_64.S
définit startup_64
. C'est le tout premier code du noyau x86 qui s'exécute. Il fait beaucoup de configuration de bas niveau, y compris la segmentation et la pagination.
C'est alors la première chose qui s'exécute car le fichier commence par :
.text
__HEAD
.code64
.globl startup_64
et include/linux/init.h
définit __HEAD
comme :
#define __HEAD .section ".head.text","ax"
donc la même chose que la toute première chose du script de l'éditeur de liens.
À la fin, il appelle x86_64_start_kernel
un peu maladroitement avec et lretq
:
movq initial_code(%rip),%rax
pushq $0 # fake return address to stop unwinder
pushq $__KERNEL_CS # set correct cs
pushq %rax # target address in negative space
lretq
et :
.balign 8
GLOBAL(initial_code)
.quad x86_64_start_kernel
arch/x86/kernel/head64.c
définit x86_64_start_kernel
qui appelle x86_64_start_reservations
qui appelle start_kernel
.
point d'entrée arm64
Le tout premier arm64 qui s'exécute sur un noyau v5.7 non compressé est défini sur https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L72 donc soit le add x13, x18, #0x16
ou b stext
en fonction de CONFIG_EFI
:
__HEAD
_head:
/*
* DO NOT MODIFY. Image header expected by Linux boot-loaders.
*/
#ifdef CONFIG_EFI
/*
* This add instruction has no meaningful effect except that
* its opcode forms the magic "MZ" signature required by UEFI.
*/
add x13, x18, #0x16
b stext
#else
b stext // branch to kernel start, magic
.long 0 // reserved
#endif
le64sym _kernel_offset_le // Image load offset from start of RAM, little-endian
le64sym _kernel_size_le // Effective size of kernel image, little-endian
le64sym _kernel_flags_le // Informative flags, little-endian
.quad 0 // reserved
.quad 0 // reserved
.quad 0 // reserved
.ascii ARM64_IMAGE_MAGIC // Magic number
#ifdef CONFIG_EFI
.long pe_header - _head // Offset to the PE header.
Il s'agit également du tout premier octet d'une image de noyau non compressée.
Ces deux cas passent à stext
qui démarre la "vraie" action.
Comme mentionné dans le commentaire, ces deux instructions sont les 64 premiers octets d'un en-tête documenté décrit sur :https://github.com/cirosantilli/linux/blob/v5.7/Documentation/arm64/booting.rst#4-call -l'image-du-noyau
arm64 première instruction MMU activée :__primary_switched
Je pense que c'est __primary_switched
dans head.S :
/*
* The following fragment of code is executed with the MMU enabled.
*
* x0 = __PHYS_OFFSET
*/
__primary_switched:
À ce stade, le noyau semble créer des tables de pages + peut-être se déplacer de manière à ce que les adresses du PC correspondent aux symboles du fichier vmlinux ELF. Par conséquent, à ce stade, vous devriez pouvoir voir des noms de fonction significatifs dans GDB sans magie supplémentaire.
point d'entrée du processeur secondaire arm64
secondary_holding_pen
défini sur :https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691
Procédure d'entrée décrite plus en détail sur :https://github.com/cirosantilli/linux/blob/v5.7/arch/arm64/kernel/head.S#L691
Avec main()
vous voulez probablement dire quoi main()
est à un programme, à savoir son "point d'entrée".
Pour un module init_module()
.
À partir de la 2e édition du pilote de périphérique Linux :
Alors qu'une application exécute une seule tâche du début à la fin, un module s'enregistre pour servir les demandes futures, et sa fonction "principale" se termine immédiatement. En d'autres termes, la tâche de la fonction init_module (le point d'entrée du module) est de préparer l'invocation ultérieure des fonctions du module ; c'est comme si le module disait :« Me voici, et voici ce que je peux faire. Le deuxième point d'entrée d'un module, cleanup_module, est invoqué juste avant le déchargement du module. Il doit dire au noyau :"Je ne suis plus là ; ne me demandez pas de faire autre chose."
Fondamentalement, il n'y a rien de spécial à ce qu'une routine soit nommée main()
. Comme mentionné ci-dessus, main()
sert de point d'entrée pour un module de chargement exécutable. Cependant, vous pouvez définir différents points d'entrée pour un module chargeable. En fait, vous pouvez définir plus d'un point d'entrée, par exemple, vous référer à votre dll préférée.
Du point de vue du système d'exploitation (OS), tout ce dont il a vraiment besoin est l'adresse du point d'entrée du code qui fonctionnera comme un pilote de périphérique. Le système d'exploitation passe le contrôle à ce point d'entrée lorsque le pilote de périphérique est requis pour effectuer des E/S sur le périphérique.
Un programmeur système définit (chaque système d'exploitation a sa propre méthode) la connexion entre un périphérique, un module de chargement qui fonctionne comme le pilote du périphérique et le nom du point d'entrée dans le module de chargement.
Chaque système d'exploitation a son propre noyau (évidemment) et certains pourraient/peut-être commencer par main()
mais je serais surpris de trouver un noyau qui utilise main()
autre que dans un simple, comme UNIX ! Au moment où vous écrivez le code du noyau, vous avez depuis longtemps dépassé l'obligation de nommer chaque module que vous écrivez comme main()
.
J'espère que cela vous aidera ?
Trouvé cet extrait de code du noyau pour Unix version 6. Comme vous pouvez le voir main()
est juste un autre programme, essayant de démarrer !
main()
{
extern schar;
register i, *p;
/*
* zero and free all of core
*/
updlock = 0;
i = *ka6 + USIZE;
UISD->r[0] = 077406;
for(;;) {
if(fuibyte(0) < 0) break;
clearsig(i);
maxmem++;
mfree(coremap, 1, i);
i++;
}
if(cputype == 70)
for(i=0; i<62; i=+2) {
UBMAP->r[i] = i<<12;
UBMAP->r[i+1] = 0;
}
// etc. etc. etc.
Plusieurs façons de le voir :
-
Les pilotes de périphériques ne sont pas des programmes. Ce sont des modules qui sont chargés dans un autre programme (le noyau). En tant que tels, ils n'ont pas de
main()
fonction. -
Le fait que tous les programmes doivent avoir un
main()
La fonction n'est vraie que pour les applications de l'espace utilisateur. Cela ne s'applique pas au noyau, ni aux pilotes de périphériques.