[SOS] changement de privileges + interrupt manager

stephane duverger duvergers at chello.fr
Dim 6 Mar 14:52:04 CET 2005


Merci david c'est vraiment sympa d'avoir pris le temps d'ecrire une
réponse comme ca. Ca sort un peu du cadre de sos, car cela concerne l'os
que je developpe, donc j'espere que ca te saoule pas trop d'y jetter un
oeil et que mes questions ne te font pas perdre trop de temps.

- Concernant le changement de contexte et de privilege:

En effet je parlais d'une interruption logicielle (en fait j'ai tilté en
fin de paragraphe de l'article sur le changement de contexte, ou vous
donniez differentes techniques) pour le changement de contexte mais je
me retrouve dans une situation un peu louche. Explanation :

- Je voulais avoir une fonction capable de faire un changement de
contexte vers un process user depuis un etat kernel.
Un truc du genre:
	switch_ctx(){... int $task_interrupt }

handler_task_interrupt:
	save_tss_current (dans une structure tss)
	place sp sur sp0 tss_next
	iret

Comme ca je pourrais l'appeler directement dans du code ou qu'elle soit
et par exemple lancer un thread immediatement sans attendre une
interruption d'horloge.

- A present il fallait pouvoir utiliser la fonction dans le handler de
l'irq d'horloge et la ca devient le bazarre:

un process user s'execute
|
|
|
----> int horloge ( place Flags, CS user, EIP user )
	sage registers ( comme tout handler d'interruption )
	handler_timer()
	  sched()
	    int $task_interrupt (empile aussi Flags, CS kernel, Eip )
	      /* ici je peux pas sauver le tss courrant car les
		registres n'ont plus la meme valeur et de plus ca me
		ferait sauvegarder les valeurs 2 fois
	      */


Donc du coup je ne souhaite pas utiliser une interruption soft pour le
changement de contexte durant une interruption d'horloge.
Je pense que vous voyez mon soucis. Mais c'est pas mechant, je me suis
dit que j'allais faire une gestion specifique pour l'interruption
d'horloge.

Des l'entree dans l'interruption d'horloge je sauvegarde les registres
et le sp0 dans le tss courant, et le changement de contexte appelé dans
sched() ne fait que charger "tr" avec le selecteur de TSS du nouveau
process, mettre "sp" a la valeur du sp0 de ce nouveau process et lancer
un iret.

Mais du coup je ne peux plus utiliser cette fonction de changement de
contexte n'importe ou (sauf si je passe un parametre pour signaler qu'on
a ete appelé dans une interruption mais ca fait beaucoup de si).

Chez vous ca ne pose pas de soucis car vous sauvez l'etat dans la pile
noyau alors que moi je voulais le faire a la sauce linux. En fait pour
chaque process créé je reserve 2 pages physiques pour la pile kernel du
process et le descripteur de ce process (contenant une structure TSS
plus des infos de "pid" ...) et je stocke l'etat du process interrompu
non pas dans sa pile kernel mais dans cette structure.
En fait en vous lisant tout coullait de source et je comprennais pas
pourquoi je m'emmelais les pinceaux et trouvais des problemes la ou il
n'y a pas lieu d'en avoir :)


- Concernant le probleme de valeur immediate pour "ljmp":

J'ai une macro pareille qui prepare un selecteur de TSS en fonction
d'indice passé en parametre. Mais le probleme c'est que la macro ne
substitue pas la variable par sa valeur au moment ou elle est passee en
assembleur inline.

Lors de vos premiers articles pour la mise en place de la segmentation
où il fallait faire un longjump pour changer cs, ca ne posait pas de
soucis car l'index du selecteur de code du noyau etait lui meme un
define: #define K_CODE_SEL_IDX 1 et donc substitue par sa valeur dans la
macro qui construit un selecteur de segment.

Mais la apparement pour mes TSS, la macro laisse l'operande memoire.

#define set_kgdt_sel(index)	(((index)<<3))
#define	switch_to_imm(sel)	asm volatile( "ljmp %0, $0"::"i"(sel) )

et le code l'appelle ainsi:

int	tss_desc_idx;

tss_desc_idx = create_process( prog1 );
switch_to_imm( set_kgdt_sel( (uint16)tss_desc_idx ) );

et du coup a la compilation:

	attention : asm operand 0 probably doesn't match constraints
	error: impossible constraint in `asm'

Si "sel" est un define (ie: #define sel 6), ca passe. J'ai essayé de
passer par des registres mais je ne suis pas sur qu'il fasse reellement
un far jmp avec la syntaxe suivante, en tout cas je recois #GP

#define switch_to(sel_addr) \
	asm volatile( "ljmp *(%%eax)"::"a"(sel_addr) )

uint16 sel = set_kgdt_sel( (uint16)tss_desc_idx );
switch_to( &sel );

Tu vois où est le soucis je pense. C'est un peu pour ca que j'ai
abandonné le changement de contexte "hard".


- Sinon concernant la gestion des exceptions/interruptions:

Je vous disais dans un vieux mail que j'avais fait un truc inspiré de
Plan9. J'ai vu en recuperant le code de sos-6.5 que vous faisiez
toujours votre boucle en asm gas qui generait "n" fois le code des
wrappers qui appelle le vrai handler d'interruptions.

Ce que j'ai fait permet de reduire la place memoire en ne mettant qu'une
seule fois le code.

En fait, mon IDT est initialisée a partir d'un label qui ressemble a
ceci:

idt_vector_table_label:
	push	$0
	call	interrupt_mgr_nocode
	push	$1
	call	interrupt_mgr_nocode
	...
	push	$10
	call	interrupt_mgr_errcode
	...
	push	$15	/* intel reserved */
	call	exception_fake_nocode
	...
	push	$35
	call	interrupt_mgr_nocode
	...

Chaque entree de l'IDT contient l'addresse de idt_vector_table_label +
index interruption*Taille "push+call" (7 octets sur IA32).
Donc l'entree 0 pointe sur le push $0, la 1 sur push $1 ...

Ensuite :

/*
** interrupts without error code
** it could be exception, irq or soft int
** stack looks like:
**		FLAGS
**		CS
**		EIP
**		interrupt number
**		EIP (after the call in idt_vector_table_label)
*/
interrupt_mgr_nocode:
	movl	%eax, (%esp) /* replace last eip with eax to save it */
	xchg	4(%esp), %ebx /* ebx <=> syscall/irq/exception number */
	movl	%ebx,	%eax /* eax = ebx */
	jmp	interrupt_mgr

/*
** interrupts with an error code
** it is always an exception
** Stack looks like:
**		FLAGS
**		CS
**		EIP
**		ERRORCODE
**		exception number
**		EIP (after the call in idt_vector_table_label)
*/
interrupt_mgr_errcode:
	add	$4, %esp	/* remove last eip */
	xchg	(%esp), %eax	/* eax <=> exception number */
	xchg	4(%esp), %ebx	/* ebx <=> errorcode */

/*
** Real interrupt manager
** call the interrupt related Interrupt Service Routine
** Stack looks like:
**		FLAGS
**		CS
**		EIP
**		EBX
**		EAX
**
** ebx has the error code (or interrupt number if no code)
** eax has the interrupt number
*/
interrupt_mgr:
	/* save 32 bits registers */
	push	%ecx
	push	%edx
	push	%esi
	push	%edi
	/* save 16 bits registers 2 per stack entry */
	pushw	%ds
	pushw	%es
	pushw	%fs
	pushw	%gs
	pushw	%ss
	sub	$2, %esp

	/* cmp will change flags */
	pushf

/* if( eax < 32 )
   call_isr because nothing special to do
*/
is_exception:
	cmp	$32, %eax
	jl	call_isr

/* else if( eax < 40 )
send EOI to PIC1 */
is_irq_pic1:
	cmp	$40, %eax
	jge	is_irq_pic2
	movb	$0x20, %al
	outb	%al, $0x20
	jmp	call_isr

/* else if( eax < 48 )
send EOI to PIC1 and PIC2
*/
is_irq_pic2:
	cmp	$48, %eax
	jge	exception_fake_nocode
	movb	$0x20, %al
	outb	%al, $0x20
	movb	$0x20, %al
	outb	%al, $0xA0

call_isr:
	/* restore flags as they were before cmp */
	popf
	
	/* call the isr for this interrupt */
	/* error code or interrupt number */
	push %ebx
	
	/* interrupt number parameter */
	push %eax
	
	/* interrupt_vector_table+interrupt_num*4
	   => addr real isr in C code
	*/
	lea  interrupt_vector_table, %edx
	call	*(%edx,%eax,4)

	/* restore registers */
	add	$10, %esp /* last pushed ebx,eax + 2 bytes for esp */
	popw	%ss
	popw	%gs
	popw	%fs
	popw	%es
	popw	%ds
	pop	%edi
	pop	%esi
	pop	%edx
	pop	%ecx

	pop	%eax
	pop	%ebx
	iret	/* remove eip, cs, flags and return */

/*
** for intel reserved exceptions and invalid int numbers
*/
exception_fake_nocode:
	add	$8, %esp
	iret

Et voila. C'est un peu sux le coup des 7 octets mais comme dirait
Stallman ca fait un peu "Subversive kind of hack" :)

Merci encore pour vos articles vraiment tres pedagogues. Et je vous
encourage vraiment à en faire un livre pour faire suite un peu à ce qui
a été dit sur la mailing. C'est une mine d'or pour tout programmeur
système ce que vous expliquez et c'est vraiment bien écrit.

++

On Sun, 2005-03-06 at 12:47 +0100, David Decotigny wrote: 
> Bonjour,
> 
> Normalement le code est sur le CD. Malheureusement je n'ai pas pu 
> verifier car je n'ai pas pu me procurer le numéro de Mars. Je pense 
> qu'avec le code sous les yeux ça ira beaucoup mieux.
> 
> Quelqu'un peut confirmer que le code n'est pas sur le CD ?
> 
> stephane duverger wrote:
> > Comment faites-vous pour lancer le premier programme utilisateur en ce
> > qui concerne la gestion des privileges ?
> 
> Dans le code, on met en place la pile d'un nouveau thread user avec un 
> CS pointant vers un segment user :
>    uctxt->regs.cs
>      = SOS_BUILD_SEGMENT_REG_VALUE(3, FALSE, SOS_SEG_UCODE); /* Code */
> (cf hwcore/cpu_context.c:sos_cpu_ustate_init)
> 
> Pour rappel, quand on ecrit ça, ca correspond a initialiser le contenu 
> de la pile utilisateur. Le contenu de cette pile, et tout 
> particulierement cet emplacement, est pris en charge par les 
> instructions ret/iret. On utilise precisement cette caracteristique pour 
> changer de niveau de privilege : quand on fera iret, c'est cette valeur 
> de CS qui sera chargee dans le processeur, entrainant un chgt de 
> privilege. Et voila. Point c'est tout.
> 
> > "The cpl of the calling procedure must be equal to the dpl of the
> > destination code segment" dans le cas de segment non-conformes.
> 
> Pour l'acces aux segments de code/donnee, c'est surtout le "RPL" qui 
> intervient, lui meme contraint par le CPL. Voir doc intel, paragraphe 
> 4.8.1.1.
> 
> Mais ce paragraphe concerne bien l'*acces* a des segments dont le niveau 
> de privilege est different du CPL. Il n'est pas question de *changement* 
> de privilege, puisque le CPL ne change pas.
> 
> Les changements de privilege sont expliques sections 4.8.5 et 4.8.6 de 
> la doc Intel vol 3. La figure explique la configuration de la pile utile 
> pour le changement de privilege. Cette configuration de la pile explique 
> d'ailleurs la difference entre les structures sos_cpu_kstate et 
> sos_cpu_ustate de hwcore/cpu_context.c
> 
> > Or le probleme avec les TSS c'est que la syntaxe at&t de la commande
> > "ljmp" ne prend que des valeurs immediates, donc difficile a gerer
> > dynamiquement sans modifier le code a la volee. Apparement elle peut
> 
>    ljmp $SEG_VAL, $offset
> 
> Pour etablir SEG_VAL, voir macro SOS_BUILD_SEGMENT_REG_VALUE dans 
> hwcore/segment.h
> 
> > Alors je me suis dit que le mecanisme des interruptions passait outre
> > cette gestion de privilege bien corriace.
> 
> C'est une solution. Tu parles surement des interruptions logicielles. 
> Dans la solution que tu proposes plus loin, tu parles d'utiliser les 
> interruptions materielles : c'est a mon avis trop complique, pas elegant 
> et ca introduit une latence inutile.
> 
> > J'ai bon :) ? Vous ne le mentionnez pas et je n'ai pas pu voir votre
> > code, j'aimerais savoir comment vous avez fait, ca m'illuminerait :)
> 
> Mieux vaudrait voir le code, la solution a base d'IRQ timer me parait 
> machine_a_café.
> 
> > En fait le probleme est apparu lorsque je me suis mis à lire
> > "Understanding the linux Kernel" et que j'ai vu qu'un thread noyau
> ...
> > - l'interruption d'horloge finit par arriver.
> ...
> > - comment basculer vers le monde utilisateur ? On peut pas appeler le
> > point d'entrée du programme simplement car nous aurions #GP en regard de
> > ce qu'explique la doc intel.
> 
> Comme dans SOS : c'est iret qui fait le travail, execute en mode kernel 
> par le handler de l'IRQ. Puisque le thread interrompu etait en mode 
> user, il y a eu chgt de privilege vers kernel, donc le CPU a empile 
> automatiquement le necessaire sur la pile noyau pour que iret puisse 
> revenir en mode user. Voir ref intel precedente.
> 
> Un peu d'histoire de sos maintenant, pour l'anecdote. Si on prend la 
> philosophie inverse, si on veut passer "a la main" du mode kernel vers 
> le mode user, alors il suffit de trafiquer la pile noyau pour qu'un 
> simple iret s'occupe du chgt de privilege. Pour la petite histoire, au 
> debut c'est comme ca que SOS devait fonctionner pour creer des threads 
> user :
>   - au debut on cree un thread noyau, en passant l'adresse d'une 
> fonction a executer,
>   - la fonction en question fait ce qu'elle veut, y compris mettre en 
> place un nouvel espace d'adressage, y copier le prog utilisateur, mais 
> est toujours en mode noyau.
>   - la fonction en question alloue une pile utilisateur en espace 
> utilisateur
>   - la fonction en question appelle une fonction speciale chargee 
> d'effectuer le changement de privilege :
> 
> La fonction de chgt de privilege etait la suivante :
> ----------------------------------------------------------------------------
> .globl sos_cpu_context_switch_to_user_mode
> .type sos_cpu_context_switch_to_user_mode, @function
> sos_cpu_context_switch_to_user_mode:
>          // arg2= user_initial_SP -- esp+8
>          // arg1= user_initial_PC -- esp+4
>          // caller ip             -- esp
> 
>    /*
>     * Store the kernel SP where the CPU will have to return to when
>     * returning from user to kernel mode. That SP is the value of
>     * the SP before the call to this function
>     */
>    movl  %esp, %eax
>    addl  $12,  %eax /* SP before function call is 12B above current SP */
>    pushl %eax
>    call  sos_cpu_context_update_kernel_tss_switch_to_user
>    addl  $4, %esp
> 
>    /*
>     * Setup the stack so as to suit the iret instruction for a "return"
>     * with privilege change. See figure 6-5 of Intel x86 vol 1. In our We
>     * could have used the "far ret" instruction, the only difference
>     * would have been that the IF flag of the EFLAGS register would
>     * not have been setup so that the IRQ inside the user thread are
>     * allowed
>     */
> 
>    /* Push SS register of the user context */
>    pushl $SOS_BUILD_SEGMENT_REG_VALUE(3, 0, SOS_SEG_UDATA) // ==> esp += 4
> 
>    /* Push the initial user SP */
>    pushl 12(%esp) // ==> esp += 4
> 
>    /* Push the initial user EFLAGS register */
>    pushl $(1 << 9) // IF flag set ==> esp += 4
> 
>    /* Push the CS register of the user context */
>    pushl $SOS_BUILD_SEGMENT_REG_VALUE(0, 0, SOS_SEG_UCODE) // ==> esp += 4
> 
>    /* Push the PC of the user context */
>    pushl 20(%esp)
> 
>    /* No error code */
>    pushl $0
> 
>    /*
>     * Reset the general purpose user segment registers
>     */
>    /* Build the contents of these registers */
>    movw $SOS_BUILD_SEGMENT_REG_VALUE(3, 0, SOS_SEG_UDATA), %ax
>    pushw %ax ; popw %ds
>    pushw %ax ; popw %es
>    pushw %ax ; popw %fs
>    pushw %ax ; popw %gs
> 
>    /*
>     * Reset the general purpose registers
>     */
>    movl $0, %eax
>    movl $0, %ebx
>    movl $0, %ecx
>    movl $0, %edx
> 
>    /* Now switch to user context ! */
>    iret
> ----------------------------------------------------------------------------
> 
> Son prototype etait le suivant :
> ----------------------------------------------------------------------------
> /**
>   * The function that switches the current kernel thread to the given
>   * user thread.
>   *
>   * @note This is a non-returning function because once in user mode,
>   * we never return in this function. Actually, when the user thread
>   * will return back in kernel context, this will take place inside an
>   * interrupt handler (ie IRQ, syscall or exception), not inside the
>   * following function.
>   */
> void
> sos_cpu_context_switch_to_user_mode(sos_uaddr_t user_initial_PC,
>                                      sos_uaddr_t user_initial_SP)
>       __attribute__((noreturn));
> ----------------------------------------------------------------------------
> 
> > D'autre part j'avais mis un mail le mois dernier en ce qui concernait
> > les rencontres. Est-ce tombé à l'eau ? Ca ferait plaisir de vous voir et
> > de pouvoir discuter avec vous et les personnes interessées par le
> > développement de noyaux.
> 
> Ce n'est pas tombé a l'eau. Au debut, c'est Thomas qui lancait l'idée, 
> je n'etais pas specialement chaud. Maintenant je suis plutot decide a 
> participer aussi, mais ce qui bloque c'est nos emplois du temps. On vous 
> tiendra au courant. Probable que ca intervienne plutot vers la fin de la 
> serie.
> 
> Bonne journee,
> _______________________________________________
> Sos mailing list
> Sos at the-doors.enix.org
> http://the-doors.enix.org/cgi-bin/mailman/listinfo/sos
-- 
stephane duverger <duvergers at chello.fr>



Plus d'informations sur la liste de diffusion Sos