[LINUX] Qu'est-ce qu'un appel système

Pourquoi se soucier des appels système

Un appel système est un mécanisme par lequel une application utilise les fonctions fournies par le système d'exploitation, mais il est important de connaître les appels système afin de comprendre le fonctionnement de l'application.

En effet, presque toutes les choses importantes dans le fonctionnement de l'application sont réalisées à l'aide d'appels système. Par exemple, la communication réseau, l'entrée / sortie de fichier, la création de nouveaux processus, la communication inter-processus, la création de conteneurs, etc. sont réalisées à l'aide d'appels système. Au contraire, la seule chose qu'une application peut faire sans utiliser d'appels système est le calcul sur le CPU et l'entrée / sortie vers / depuis la mémoire.

Ce qui est expliqué dans cet article

Le contenu abordé dans cet article concerne la nature générale et la mécanique des appels système. En premier lieu, j'expliquerai ce qu'est un appel système et comment il est réalisé. De plus, nous allons vous montrer comment utiliser directement les appels système sans passer par une bibliothèque, ou comment approfondir le noyau pour examiner l'implémentation des appels système.

Le contenu est le suivant.

Objectif et rôle de l'appel système

Selon Linux Kernel Development etc., un mécanisme appelé appel système est introduit. Il y a deux avantages:

  1. Fournir une interface simple pour le matériel d'exploitation
  2. Les applications peuvent utiliser les ressources du système d'exploitation en toute sécurité et en toute sécurité

1. Fournir une interface simple pour le matériel d'exploitation

Les appels système fournissent aux applications une interface simple et abstraite pour manipuler le matériel. Cela élimine la nécessité pour le code d'application de connaître les détails matériels sous-jacents. Par exemple, l'appel système write est une interface courante pour écrire une chaîne d'octets sur quelque chose. Il existe une grande variété de choses qui peuvent être spécifiées comme cibles d'écriture, c'est-à-dire qui peuvent être traitées comme des fichiers. Par exemple, divers périphériques de sortie tels que les canaux utilisés pour la communication inter-processus, les sockets utilisés pour la communication réseau, les moniteurs, etc., ainsi que divers types de systèmes de fichiers. Etc. La philosophie de base de Linux et Unix est «tout est un fichier», mais divers types sont utilisés pour l'entrée et la sortie, y compris «write». On peut dire que l'appel système en est l'incarnation.

2. Les applications peuvent utiliser les ressources du système d'exploitation en toute sécurité et en toute sécurité

Les appels système servent d'intermédiaire entre l'application et les ressources gérées par le système d'exploitation et ont pour rôle d'empêcher l'application d'utiliser la ressource de manière incorrecte ou de l'utiliser d'une manière qui pose un problème de sécurité. Cela permet aux applications d'utiliser les ressources en toute sécurité.

Comme exemple d'utilisation sûre des ressources, par exemple, sécuriser une zone mémoire par un processus (en utilisant mmap) Il y aura. Une ressource matérielle appelée mémoire est partagée avec d'autres processus et systèmes d'exploitation, et si elle est utilisée de manière incorrecte, il existe un risque de destruction d'autres processus et systèmes d'exploitation. Cet appel système permet au système d'exploitation d'allouer une zone mémoire à chaque processus de manière sécurisée lorsque le processus souhaite allouer une zone mémoire. En outre, un exemple d'utilisation sécurisée des ressources serait le contrôle d'accès aux fichiers basé sur les informations d'autorisation.

Comment l'application appelle l'appel système

Que fait l'application lors de l'appel d'un appel système? Quelle est la différence avec un appel de fonction normal? Ici, nous allons expliquer comment l'application appelle l'appel système.

Une application doit suivre trois étapes lors de l'appel d'un appel système:

  1. Définissez le numéro qui spécifie l'appel système à exécuter dans le registre (mémoire minimale intégrée à la CPU).
  2. Définissez l'argument à transmettre à l'appel système dans le registre
  3. Exécutez l'instruction / l'instruction qui appelle l'appel système

Dans ce qui suit, nous expliquerons la procédure ci-dessus en détail en utilisant le code qui génère des caractères à l'écran (sortie standard) à titre d'exemple.

Exemple utilisant la sortie de caractères comme exemple

L'appel système write est utilisé pour la sortie standard et l'écriture dans un fichier, mais un exemple qui appelle cet appel système Jetons un coup d'œil aux étapes dans lesquelles une application appelle un appel système à l'aide de code.

Tout d'abord, lancez image gcc, qui est utilisée pour exécuter l'exemple de code, en tant que conteneur.

$ docker container run -it --rm gcc /bin/bash

Créez un fichier hi.s avec le code d'assemblage suivant dans le conteneur lancé. J'expliquerai le code plus tard.

# cat <<EOF > hi.s
.intel_syntax noprefix
.global main

main:
    push rbp
    mov rbp, rsp
    push 0xA216948
    mov rax, 1
    mov rdi, 1
    mov rsi, rsp
    mov rdx, 4
    syscall
    mov rsp, rbp
    pop rbp
    ret
EOF

Lorsque vous le compilez et l'exécutez, les caractères sont affichés à l'écran comme indiqué ci-dessous.

# gcc -o hi.o hi.s; ./hi.o
Hi!

La méthode d'appel des appels système est légèrement différente selon les spécifications / l'architecture du processeur, mais le code ci-dessus est le plus populaire x86-64. C'est un exemple d'appel de l'appel système write dans l'architecture.

Avant d'expliquer l'exemple de code présenté ci-dessus, expliquons d'abord comment appeler un appel système sur x86-64. Ce que vous faites avec d'autres architectures ne change pas grand-chose.

Comment appeler un appel système sur x86-64

L'appel d'un appel système sur x86-64 se fait dans les trois étapes suivantes.

  1. Définissez le numéro d'appel système dans le registre «rax»
  2. Définissez l'argument (le cas échéant) que vous souhaitez transmettre à l'appel système dans le registre
  3. Appelez l'instruction / l'instruction de syscall

étape 1

Tout d'abord, à l'étape 1, spécifiez ici l'appel système appelé numéro d'appel système dans le registre appelé rax (= la mémoire de 16 à 64 bits intégrée au CPU, accessible à une vitesse extrêmement élevée depuis le CPU). Stocke le numéro à utiliser. Ce numéro vous permet d'identifier l'appel système que le noyau doit effectuer.

L'appel système correspondant au numéro d'appel système est spécifié comme suit, par exemple.

system call number Nom de l'appel système Contenu
0 read Lis
1 write Exportation
2 open Fichier ouvert
57 fork Lancer un nouveau processus

Un tableau complet qui comprend plus que les exemples ci-dessus peut être trouvé à ici.

Dans l'exemple de code, les parties correspondant à cette étape sont:

    mov rax, 1

Étape 2

Ensuite, à l'étape 2, nous stockerons ici les arguments à passer à l'appel système dans le registre. Vous pouvez passer jusqu'à 6 arguments, et les registres rdi, rsi, rdx, r10, r9 et r8 doivent être utilisés dans l'ordre du premier argument.

Pour l'appel système write, les arguments et registres à passer sont spécifiés comme suit:

Nom du registre rdi rsi rdx
argument Descripteur de fichier Adresse de début de la chaîne d'octets à écrire Taille de la chaîne d'octets à exporter

Vous pouvez vérifier les arguments à utiliser pour les autres appels système ici [https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/).

À propos, ce qu'est un descripteur de fichier (défini dans le registre rdi) est comme un numéro de port associé à chaque processus qui contrôle l'entrée / la sortie du processus. Par défaut, le descripteur de fichier peut utiliser trois valeurs de 0 à 2 et correspond respectivement à 0: entrée standard, 1: sortie standard et 2: erreur standard. Lorsque vous ouvrez un fichier ou un socket, de nouveaux descripteurs de fichier sont assignés successivement, 3, 4, 5, etc., et des appels système tels que write write et read read sont exécutés pour eux (appel système). Peut être passé comme argument à).

Dans l'exemple de code, les parties correspondant à cette étape sont:

    mov rdi, 1
    mov rsi, rsp
    mov rdx, 4

Étape 3

L'étape 3 émet une instruction / instruction appelée syscall. Cela amène le CPU à interrompre l'exécution du code de l'application, à basculer vers un mode qui exécute le code du noyau, puis à sauter au code qui exécute l'appel système dans le noyau (gestionnaire d'appels système). Lorsque vous quittez l'instruction syscall, la valeur de retour est définie dans le registre rax et peut être référencée si nécessaire. Cette partie sera expliquée en détail dans [Comment implémenter les appels système et l'implémentation interne] plus loin dans l'article.

Dans l'exemple de code, les parties correspondant à cette étape sont:

    syscall

Explication de l'exemple de code

J'ai ajouté un commentaire pour comprendre la signification de l'exemple de code. Le point est la ligne intitulée syscall et juste avant.

.intel_syntax noprefix #Format de code
.global main #Libellé du point de départ de l'exécution

main:
#Prétraitement
    push rbp
    mov rbp, rsp

#Chaîne'Hi!'Sur la pile
    push 0xA216948

#Étape 1 avec le numéro d'appel système'1'(=Prend en charge l'écriture)Spécifier
    mov rax, 1

#Étape 2 Définissez les arguments à transmettre à l'appel système d'écriture dans le registre
    mov rdi, 1  #1 dans le descripteur de fichier à exporter(Sortie standard)Ensemble
    mov rsi, rsp  #Adresse de départ de la pile(lettre`H`Est inclus)Ensemble
    mov rdx, 4  #Définir la taille de la chaîne de sortie

#Étape 3 Émettez un appel système
    syscall

#Post-traitement
    mov rsp, rbp
    pop rbp
    ret

En passant, si vous ajoutez un peu sur la partie du code ci-dessus qui transmet l'adresse de début de la chaîne de caractères à l'étape 2.

    mov rsi, rsp  #Adresse de départ de la pile(lettre`H`Est inclus)Ensemble

La chaîne est stockée sur la pile, mais le registre rsp est un registre spécial qui pointe vers l'adresse de début de la pile. Par conséquent, en définissant la valeur de «rsp» sur «rsi», cela signifie que l'adresse de début de la chaîne de caractères est transmise à l'appel système.

[Supplément] Comment fournir des appels système

Les appels système sont généralement fournis sous forme de bibliothèque en langage C. Lorsque vous utilisez des appels système, vous pouvez essentiellement utiliser cette bibliothèque d'encapsuleurs et vous n'avez pas besoin d'écrire directement le code d'assemblage.

Par exemple, «write (2)» est défini comme une fonction C avec la signature suivante: ](Http://man7.org/linux/man-pages/man2/write.2.html)

ssize_t write(int fd, const void *buf, size_t count);

D'un autre côté, si vous ne souhaitez pas utiliser la bibliothèque C, vous devrez implémenter vos propres appels système en tant que code d'assemblage, comme vous l'avez fait dans l'exemple ci-dessus. Le langage Go, par exemple, adopte une telle approche.

golang/sys/unix/asm_linux_amd64.s

TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
	CALL	runtime·entersyscall(SB)
	MOVQ	a1+8(FP), DI
	MOVQ	a2+16(FP), SI
	MOVQ	a3+24(FP), DX
	MOVQ	$0, R10
	MOVQ	$0, R8
	MOVQ	$0, R9
	MOVQ	trap+0(FP), AX	// syscall entry
	SYSCALL
	MOVQ	AX, r1+32(FP)
	MOVQ	DX, r2+40(FP)
	CALL	runtime·exitsyscall(SB)
	RET

Pour compléter le code ci-dessus, un registre inconnu tel que «AX» est un alias qui pointe vers la [partie 16 bits] comme le registre «rax». ](Https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture)

[Supplément] Comment la CPU exécute le langage machine

Lorsqu'un appel système est émis, le CPU ** saute ** du code en cours d'exécution au code dans le noyau. Comment une telle fonction pour sauter le code à exécuter au milieu est-elle réalisée sur le CPU? Pour comprendre cela, nous devons comprendre comment le CPU exécute le code (langage machine) en premier lieu.

L'exécution du langage machine sur la CPU est effectuée en répétant les étapes suivantes.

  1. Lire une instruction / instruction de la mémoire
  2. Exécutez les instructions et mettez à jour les valeurs des registres, etc.
  3. Mettez à jour la valeur du registre qui stocke l'adresse de l'instruction en cours d'exécution pour préparer la lecture de l'instruction suivante.

Les instructions sont des octets que l'UC peut interpréter comme une seule instruction et avoir une correspondance un à un avec une seule ligne de code d'assemblage. Par exemple, dans le code d'assemblage donné ci-dessus, la correspondance entre le langage machine et les instructions est la suivante.

# objdump -d -M intel ./hi.o | grep syscall -B 4
 66c:	48 c7 c0 01 00 00 00 	mov    rax,0x1
 673:	48 c7 c7 01 00 00 00 	mov    rdi,0x1
 67a:	48 89 e6             	mov    rsi,rsp
 67d:	48 c7 c2 20 00 00 00 	mov    rdx,0x20
 684:	0f 05                	syscall

De plus, le registre qui stocke l'adresse de l'instruction en cours d'exécution est appelé registre de compteur de programme ou registre de pointeur d'instruction, et lorsqu'une instruction est exécutée, elle est ajoutée et mise à jour à la valeur de l'adresse de l'instruction immédiatement après. Cela provoque l'exécution séquentielle du code.

C'est une fonction pour sauter le code exécuté par le CPU au milieu, mais cela émet une instruction (par exemple, jmp) qui réécrit directement le registre du compteur de programme. Vous pouvez le faire en faisant. Ce que vous pouvez faire avec cela comprend non seulement le saut de code dans le noyau avec des appels système, mais plus généralement le branchement conditionnel, la boucle, les appels de fonction, etc.

Méthode d'implémentation des appels système et implémentation interne

Le flux général de traitement des appels système est le suivant.

  1. Exécutez une instruction pour appeler un appel système et passez au code dans le noyau
  2. Appelez l'implémentation de l'appel système spécifié par le numéro d'appel système dans le noyau
  3. Exécutez une instruction pour revenir à l'appelant de l'appel système dans le noyau et revenir au code d'origine.

Les étapes 1 et 3 entrent et reviennent du noyau, ce qui est réalisé avec des instructions dédiées pour x86-64, «syscall» et «sysretq». En d'autres termes, cette partie est implémentée en matériel, c'est-à-dire par un circuit logique dans le CPU conçu pour répondre aux spécifications de x86-64.

À l'étape 2, cela se fait dans le code appelé le gestionnaire d'appels système dans le noyau. Dans le gestionnaire d'appels système, une répartition est effectuée vers la mise en œuvre de chaque appel système en fonction du numéro d'appel système.

De manière un peu plus détaillée, le traitement des appels système peut être décomposé comme suit.

  1. Exécutez l'instruction syscall et lisez la valeur du registre contenant l'adresse du gestionnaire d'appels système dans le registre du compteur de programme.
  2. Le gestionnaire d'appels système accepte le traitement
  3. Envoi à l'implémentation de chaque appel système en fonction du numéro d'appel système dans le gestionnaire d'appels système
  4. L'instruction sysretq restaure le code d'origine sur le processus utilisateur.
  5. Enregistrez l'adresse du gestionnaire d'appels système dans le registre au démarrage

Cela ressemble à ceci sur la figure.

syscall (4).png

Nous expliquerons chaque élément de (1) à (5) ci-dessous.

(1) Exécution de l'instruction syscall

Lorsque le processeur exécute l'instruction syscall, ce qui suit est à peu près fait.

  1. Sauvegardez la valeur du registre «FLAGS», qui représente le mode d'exécution actuel de la CPU, dans le registre «R11».
  2. Masquez la valeur du registre FLAGS avec la valeur du registre ʻIA32_FMASK MSR` et passez à un mode dans lequel le CPU peut exécuter le code du noyau.
  3. Enregistrez la valeur du registre du compteur de programme «RIP» dans le registre «RCX»
  4. Lisez l'adresse du gestionnaire d'appels système du registre ʻIA32_LSTAR MSRdans le registre du compteur de programmeRIP` et passez au gestionnaire d'appels système.

** 1. ** Le registre FLAGS est un registre qui représente l'état actuel du CPU / le mode d'exécution du CPU, par exemple, le CPU est Protection Ring //ja.wikipedia.org/wiki/%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%97%E3%83%AD%E3%83%86%E3% 82% AF% E3% 82% B7% E3% 83% A7% E3% 83% B3) Il montre où vous êtes. Puisque nous voulons restaurer l'état d'origine lors du retour de l'appel système au processus utilisateur, enregistrez la valeur actuelle dans le registre R11.

** 2. ** En masquant la valeur du registre FLAGS avec la valeur du registre ʻIA32_FMASK MSR`, le mode CPU est commuté et passe au niveau de privilège 0, c'est-à-dire le mode dans lequel le code du noyau peut être exécuté.

Le niveau de privilège est représenté par 12 ~ 13 bits du registre FLAGS, mais pour faire ces «00» (niveau de privilège 0), c'est comme suit. Les opérations sont effectuées lors de l'appel de syscall.

x86/syscall

RFLAGS ← RFLAGS AND NOT(IA32_FMASK);

L'ensemble des valeurs dans le registre ʻIA32_FMASK`, qui agit comme un masque (notez qu'il prend "NON"), est fait dans le noyau avec le code suivant.

linux/arch/x86/kernel/cpu/common.c

	/* Flags to clear on syscall */
	wrmsrl(MSR_SYSCALL_MASK,
	       X86_EFLAGS_TF|X86_EFLAGS_DF|X86_EFLAGS_IF|
	       X86_EFLAGS_IOPL|X86_EFLAGS_AC|X86_EFLAGS_NT);

Le X86_EFLAGS_IOPL ci-dessus est un masque pour le rôle de changer 12 \ ~ 13bit en 00, mais vous pouvez voir qu'il est en fait défini comme une valeur de sorte que seulement 12 \ ~ 13bit devienne 1.

linux/arch/x86/include/uapi/asm/processor-flags.h

#define X86_EFLAGS_IOPL_BIT	12 /* I/O Privilege Level (2 bits) */
#define X86_EFLAGS_IOPL		(_AC(3,UL) << X86_EFLAGS_IOPL_BIT)

** 3. ** Sauvegardez la valeur du registre du compteur de programme RIP dans le registre RCX. Si vous ne le faites pas, vous ne connaîtrez pas l'emplacement de l'instruction d'origine (= la valeur du compteur de programme) lors du retour du gestionnaire d'appels système au processus utilisateur.

** 4. ** Lit l'adresse du gestionnaire d'appels système défini dans ʻIA32_LSTAR MSR dans le registre du compteur de programme RIP et saute au gestionnaire d'appels système. La façon dont l'adresse du gestionnaire est lue dans ʻIA32_LSTAR MSR est présentée dans (5).

Pour un autre comportement détaillé du processeur lorsque syscall est appelé, veuillez voir ici.

(2) Gestionnaire d'appels système

Un gestionnaire d'appels système est un code dans le noyau qui est exécuté après l'appel de l'instruction syscall, dans lequel l'appel système est traité. Ici, nous allons introduire la partie pré-traitement du gestionnaire d'appels système, la partie qui emballe la valeur passée au registre en tant qu'argument dans la structure et la transmet au traitement suivant.

Tout d'abord, le point d'entrée / d'entrée du gestionnaire d'appels système ressemble à ceci:

linux/arch/x86/entry/entry_64.S

ENTRY(entry_SYSCALL_64)
	UNWIND_HINT_EMPTY
	/*
	 * Interrupts are off on entry.

Divers processus sont exécutés dans cette ʻentry_SYSCALL_64`, et l'un d'eux construit une structure sur la pile dont le champ de valeur est passé en argument au registre comme indiqué ci-dessous. Je vais.

linux/arch/x86/entry/entry_64.S

	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS				/* pt_regs->ss */
	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs->sp */
	pushq	%r11					/* pt_regs->flags */
	pushq	$__USER_CS				/* pt_regs->cs */
	pushq	%rcx					/* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
	pushq	%rax					/* pt_regs->orig_ax */

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS

Dans le code ci-dessus, les valeurs des registres «rcx» et «r11» (qui ont été utilisés pour la sauvegarde lors de l'appel de l'instruction «syscall») et du registre «rax» sont assignées à la structure «pt_regs» sur la pile. De plus, l'attribution de valeurs telles que rdi et rsi utilisées comme arguments de l'appel système à la structure est la dernière [macro PUSH_AND_CLEAR_REGS](https://github.com/torvalds/linux/blob/ Il est défini dans bfeffd155283772bbe78c6a05dec7c0128ee500c / arch / x86 / entry / calling.h # L100-L145).

La structure créée ci-dessus et le numéro d'appel système sont transmis à la fonction do_syscall_64 qui traite l'appel système ci-dessous.

linux/arch/x86/entry/entry_64.S#L173-L175

	movq	%rax, %rdi
	movq	%rsp, %rsi
	call	do_syscall_64		/* returns with IRQs disabled */

Il faut noter que la méthode de passage de l'argument à do_syscall_64 se fait à l'aide d'un registre. En tant que règle lors du passage d'arguments à une fonction sur x86-64, dans l'ordre à partir du premier argument, rdi, rsi, rdx,rcx Vous êtes censé utiliser les registres, r8 et r9. Donc si vous voulez passer un argument à la fonction do_syscall_64 avec la signature suivante,

linux/arch/x86/entry/common.c

__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)

Vous pouvez le faire en définissant les valeurs que vous voulez passer aux deux registres rdi et rsi.

--Dans movq% rax,% rdi, transmettez le numéro d'appel système défini dans rax au registre rdi correspondant au premier argument.

(3) Envoi à chaque appel système

L'appel d'implémentation pour chaque appel système est effectué dans do_syscall_64. Plus précisément, en spécifiant un élément avec le numéro d'appel système pour le tableau sys_call_table qui contient la fonction qui implémente chaque appel système, la distribution est effectuée vers l'implémentation de l'appel système correspondant. Aussi, l'implémentation de chaque appel système est définie dans la macro SYSCALL_DEFINE *, que vous pouvez trouver comme repère.

Jetons un coup d'œil au code du noyau pertinent ci-dessous.

En tant qu'argument de la fonction do_syscall_64, la structure constituée du numéro d'appel système dans le premier argument nr et de la valeur du registre au moment de l'appel système est passée dans le deuxième argument regs.

linux/arch/x86/entry/common.c

__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
	struct thread_info *ti;

	enter_from_user_mode();
	local_irq_enable();

sys_call_table implémente le traitement de chaque appel système tel que défini dans here C'est un tableau de fonctions, mais en spécifiant les éléments de ce tableau avec le numéro d'appel système "nr" et en passant la structure "regs", il est envoyé au traitement de chaque appel système.

linux/arch/x86/entry/common.c

	if (likely(nr < NR_syscalls)) {
		nr = array_index_nospec(nr, NR_syscalls);
		regs->ax = sys_call_table[nr](regs);
	}

regs-> ax = sys_call_table [nr](regs); est la partie qui distribue. De plus, la valeur de retour de la fonction est définie dans le champ de la structure correspondant au registre «AX» (= registre rax), mais après que cette valeur quitte l'instruction «syscall», il s'agit en fait de «rax» comme valeur de retour. Il sera inscrit dans le registre.

C'est une fonction qui traite en fait les appels système, qui est un élément du tableau sys_call_table. Par exemple, le traitement de write est implémenté ci-dessous.

linux/fs/read_write.c

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
		size_t, count)
{
	return ksys_write(fd, buf, count);
}

En général, l'implémentation de chaque appel système est définie dans la macro SYSCALL_DEFINE *. Vous pouvez donc l'utiliser comme guide pour trouver chaque implémentation.

Pour plus d'informations sur les macros sys_call_table et SYSCALL_DEFINE *, consultez cet article (https://lwn.net/Articles/604287/).

(4) Retour avec l'instruction sysretq

Le retour du gestionnaire d'appels système au code d'origine dans le processus utilisateur est effectué par l'instruction sysretq. Le traitement effectué par l'instruction "sysetq" est presque l'inverse de "syscall".

--Pour le registre RFLAGS, lisez la valeur du registre R11 (qui a sauvegardé la valeur d'origine de RFLAGS) pour la ramener à la valeur d'origine, et le mode (niveau de privilège) de la CPU qui exécute le processus utilisateur. Retour à 3) --Pour le registre du compteur de programme «RIP», lisez la valeur du registre «RCX» (qui a sauvegardé la valeur d'origine de «RIP») pour le renvoyer à la valeur d'origine, et l'instruction d'origine qui a appelé «syscall». Revenir au point de

Plusieurs autres processus sont en cours d'exécution, mais veuillez vous reporter à ici pour plus de détails.

Le code qui appelle sysretq dans le noyau, mais le code qui arrive en dernier dans le gestionnaire d'appels système est ci-dessous.

linux/arch/x86/entry/entry_64.S

	popq	%rdi
	popq	%rsp
	USERGS_SYSRET64
END(entry_SYSCALL_64)

Sysretq est appelé par ʻUSERGS_SYSRET64` ici.

linux/arch/x86/include/asm/irqflags.h

#define USERGS_SYSRET64				\
	swapgs;					\
	sysretq;

(5) Lire l'adresse du gestionnaire d'appels système dans le registre au démarrage

Lorsque l'instruction syscall a été appelée, l'adresse du gestionnaire d'appels système a été lue à partir du registre ʻIA32_LSTAR MSR pour le registre de compteur de programme RIP, et un saut vers le gestionnaire d'appels système a été effectué. Alors, comment le registre ʻIA32_LSTAR MSR connaît-il l'adresse du gestionnaire d'appels système? L'adresse du gestionnaire d'appels système est lue dans le registre ʻIA32_LSTAR MSR` lors de l'initialisation du CPU. Ci-dessous, nous présenterons le code du noyau qui prend en charge ce processus d'initialisation.

L'initialisation du CPU se fait ci-dessous,

linux/arch/x86/kernel/cpu/common.c

/*
 * cpu_init() initializes state that is per-CPU. Some data is already
 * initialized (naturally) in the bootstrap process, such as the GDT
 * and IDT. We reload them nevertheless, this function acts as a
 * 'CPU state barrier', nothing should get across.
 */
#ifdef CONFIG_X86_64

void cpu_init(void)
{

Définissez l'adresse avec syscall_init (); appelé dans ce Sera fait.

linux/arch/x86/kernel/cpu/common.c

void syscall_init(void)
{
	wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
	wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

Dans le code ci-dessus

wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

A écrit l'adresse du gestionnaire d'appels système dans le registre ʻIA32_LSTAR MSR`.

Les constantes MSR_LSTAR et ʻentry_SYSCALL_64, mais la constante MSR_LSTAR spécifie le registre ʻIA32_LSTAR MSR comme registre cible d'écriture.

linux/arch/x86/include/asm/msr-index.h

#define MSR_LSTAR		0xc0000082 /* long mode SYSCALL target */

ʻEntry_SYSCALL_64` est la valeur prototypée dans ici Point d'entrée du gestionnaire d'appels](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/entry_64.S#L145-L147).

[Supplément] Relation entre l'appel système et l'interruption

Si vous regardez les appels système, vous rencontrerez souvent le concept des interruptions. Par exemple, dans le noyau, le commentaire dans le code ci-dessous dit «Les interruptions sont désactivées», mais qu'est-ce que «les interruptions» exactement? Qu'est-ce que cela a à voir avec les appels système?

linux/arch/x86/entry/entry_64.S

ENTRY(entry_SYSCALL_64)
	UNWIND_HINT_EMPTY
	/*
	 * Interrupts are off on entry.
	 * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
	 * it is too small to ever cause noticeable irq latency.
	 */

Premièrement, en ce qui concerne la relation entre les interruptions et les appels système, les appels système peuvent être considérés comme un type d'interruption. Lorsqu'une interruption se produit, le CPU interrompt le code en cours d'exécution (que ce soit dans le processus utilisateur ou dans le noyau), enregistre l'état d'exécution afin qu'il puisse être redémarré plus tard, puis du code spécifique dans le noyau (= inter). Aller à Rapto Handler). C'est à peu près la même chose que les appels système que nous avons vus jusqu'à présent. Il existe deux manières de générer une interruption: celle provoquée par le logiciel est appelée interruption logicielle et celle provoquée par le matériel est appelée interruption matérielle.

Exemple d'interruption de logiciel

--Appeler un appel système

Exemple d'interruption de processus

--Réponse à la saisie au clavier

Grâce au mécanisme d'interruption, il est possible d'implémenter une fonction qui "quand quelque chose se passe, le CPU saute à un code spécifique sans questions et réponses", ce qui permet, par exemple, d'entrer à partir du matériel. Vous serez en mesure de répondre rapidement.

Notez que x86-64 a des instructions dédiées (syscall et sysretq) pour les appels système, mais dans x86-32 et d'autres architectures d'il y a une génération, les appels système sont dans le mécanisme d'interruption. Il est réalisé en. Par exemple, pour implémenter un appel système sur x86-32, spécifiez le vecteur 0x80 dans Instruction ʻint](https://www.felixcloutier.com/x86/intn:into:int3:int1) [ʻint 0x80 Cela a été fait en appelant l'instruction .

Que signifie désactiver l'interruption / irq?

Certains des gestionnaires d'appels système sont exécutés avec les interruptions désactivées, c'est-à-dire «irq» désactivées. ʻIrq est une demande d'interruption, et lorsque la CPU la reçoit, une interruption est générée. En général, tout ou partie de ʻirq peut être désactivé lorsque le gestionnaire d'interruption gère l'interruption. Cela évitera la situation où une interruption se produit pendant le traitement d'une interruption. Si ʻirq est désactivé, il ne peut pas accepter les interruptions, donc il ne peut pas répondre à une saisie au clavier, par exemple. Donc, le code qui tourne avec ʻirq off devrait être suffisamment court pour ne pas prendre longtemps à traiter.

Si vous voulez en savoir plus sur les interruptions, vous pouvez vous référer à cette page.

Pour passer à l'étape suivante

Présentation de matériaux et de mots clés pour approfondir les recherches sur les appels système et les noyaux. Il sert également d'introduction à la littérature qui a servi de référence lors de la rédaction de cet article.

Quels types d'appels système existe-t-il?

À propos de x86-64 et du code d'assemblage

J'ai fait référence à ici lorsque j'ai consulté les instructions. Pour une introduction au code d'assemblage x86-64, nous vous recommandons également Introduction à la création d'un compilateur C pour ceux qui veulent connaître les couches inférieures.

À propos du noyau Linux

Développement du noyau Linux est très bien noté et est à l'intérieur du noyau. Probablement le meilleur livre de commentaires sur la mise en œuvre. Ce n'est pas facile en termes de contenu (du moins pour moi), mais je le recommande car le récit est parfois intéressant et l'explication est très polie. Même si vous n'êtes pas intéressé par le noyau, le chapitre 10 qui explique la technique de programmation parallèle dans le noyau peut être utile. Nous recommandons également cette note, qui résume le contenu de LKD.

Comment lire le code source du noyau

En fait, j'ai lu correctement le code du noyau pour la première fois lors de la rédaction de cet article. L'impression est, bien sûr, que je ne peux pas comprendre tout ce qui est écrit dans le code, mais ce n'est pas si difficile de suivre le flux général du traitement. Les commentaires sont soigneusement rédigés dans plusieurs parties. Pour le lire, recherchez des mots-clés (appuyez sur la touche /) sur github, ou ici. Je lisais en utilisant la fonction de saut de source de définition de (dernière / source). Je suis aussi un amateur en ce qui concerne les noyaux, il y a donc peut-être une meilleure façon de le lire, mais ...

À propos du mécanisme et de la fonction du système d'exploitation

Je pense qu'il est normal de supprimer les processus, la mémoire virtuelle et le multitâche en tant que concepts de base importants liés au système d'exploitation. Les mots clés recommandés sont les suivants.

--Processus --Compteur de programme

--Mémoire virtuelle --Adresse virtuelle --Adresse physique --Unité de gestion de la mémoire --Pagination

--Multitâche

Autres références

Recommended Posts

Qu'est-ce qu'un appel système
Qu'est-ce que __call__
Qu'est-ce qu'une distribution?
Qu'est-ce qu'un terminal?
Qu'est-ce qu'un hacker?
Qu'est-ce qu'un pointeur?
Qu'est-ce qu'un arbre de décision?
Qu'est-ce qu'un changement de contexte?
Qu'est-ce qu'un super utilisateur?
[Définition] Qu'est-ce qu'un cadre?
Qu'est-ce qu'une fonction de rappel?
[Python] Qu'est-ce qu'une fonction zip?
[Python] Qu'est-ce qu'une instruction with?
Qu'est-ce qu'une portée lexicale / une portée dynamique?
Qu'est-ce que le réseau neuronal convolutif?
Qu'est-ce que le système X Window?
Qu'est-ce que l'espace de noms
Qu'est-ce que copy.copy ()
Qu'est-ce que Django? .. ..
Qu'est-ce que dotenv?
Qu'est-ce que POSIX
Qu'est-ce que le klass?
Qu'est-ce que SALOME?
Qu'est-ce qu'un chien? Volume d'installation de Django
Qu'est-ce qu'un chien? Volume d'installation Python
Qu'est-ce que Linux?
Qu'est-ce que python
Qu'est-ce que l'hyperopt?
Qu'est-ce que Linux
Qu'est-ce que pyvenv
Qu'est-ce que Linux
Qu'est-ce que Python
Qu'est-ce qu'un chien? Django - Créer un modèle utilisateur personnalisé
Qu'est-ce qu'un chien? Défiez le modèle Django! Le volume
C'est un Mac. Qu'est-ce que la commande Linux Linux?
Qu'est-ce qu'un chien? Django - Créer un modèle utilisateur personnalisé 2
Dites-moi ce qu'est une cartographie équiangulaire, Python!
Qu'est-ce que le F-Score de Piotroski?
Qu'est-ce que Raspberry Pi?
[Python] Qu'est-ce que Pipeline ...
Qu'est-ce que Calmar Ratio?
[Tutoriel PyTorch ①] Qu'est-ce que PyTorch?
Qu'est-ce que le réglage des hyper paramètres?
Qu'est-ce que JSON? .. [Remarque]
Une note sur __call__
À quoi sert Linux?
Qu'est-ce que l'apprentissage d'ensemble?
Qu'est-ce que TCP / IP?
Qu'est-ce que __init__.py de Python?
Qu'est-ce qu'un itérateur?
Qu'est-ce que UNIT-V Linux?
[Python] Qu'est-ce que virtualenv
Qu'est-ce que l'apprentissage automatique?
Qu'est-ce qu'un chien? Volume de transmission POST à l'aide de Django--forms.py
Qu'est-ce qu'un chien? Volume de démarrage de la création de l'application Django --startapp
Qu'est-ce qu'un chien? Volume de démarrage de la création de l'application Django - startproject
Qu'est-ce qu'un moteur de recommandation? Résumé des types
Qu'est-ce que Dieu? Créez un chatbot simple avec python
Pour moi en tant que débutant Django (2) - Qu'est-ce que MTV?