Uno che non esiste, e quindi restituisce rapidamente -ENOSYS.
Da arch/x86/entry/entry_64.S:
#if __SYSCALL_MASK == ~0
cmpq $__NR_syscall_max, %rax
#else
andl $__SYSCALL_MASK, %eax
cmpl $__NR_syscall_max, %eax
#endif
ja 1f /* return -ENOSYS (already in pt_regs->ax) */
movq %r10, %rcx
/*
* This call instruction is handled specially in stub_ptregs_64.
* It might end up jumping to the slow path. If it jumps, RAX
* and all argument registers are clobbered.
*/
#ifdef CONFIG_RETPOLINE
movq sys_call_table(, %rax, 8), %rax
call __x86_indirect_thunk_rax
#else
call *sys_call_table(, %rax, 8)
#endif
.Lentry_SYSCALL_64_after_fastpath_call:
movq %rax, RAX(%rsp)
1:
Utilizza un numero telefonico di sistema non valido in modo che il codice di invio restituisca semplicemente con
eax = -ENOSYS
invece di inviare a una funzione di gestione delle chiamate di sistema.
A meno che questo non faccia sì che il kernel utilizzi il iret
percorso lento invece di sysret
/ sysexit
. Ciò potrebbe spiegare le misurazioni che mostrano che un numero non valido è 17 cicli più lento di syscall(SYS_getpid)
, perché la gestione degli errori di glibc (impostazione errno
) probabilmente non lo spiega. Ma dalla mia lettura del sorgente del kernel, non vedo alcun motivo per cui non dovrebbe ancora utilizzare sysret
durante la restituzione di -ENOSYS
.
Questa risposta è per sysenter
, non syscall
. La domanda originariamente diceva sysenter
/ sysret
(che era strano perché sysexit
va con sysenter
, mentre sysret
va con syscall
). Ho risposto basandomi su sysenter
per un processo a 32 bit su un kernel x86-64.
syscall
nativo a 64 bit viene gestito in modo più efficiente all'interno del kernel. (Aggiornamento; con le patch di mitigazione Meltdown / Spectre, invia ancora tramite C do_syscall_64
in 4.16-rc2).
My Cosa succede se si utilizza l'ABI Linux 0x80 int a 32 bit nel codice a 64 bit? Domande e risposte offre una panoramica del lato kernel dei punti di ingresso delle chiamate di sistema dalla modalità compat a un kernel x86-64 (entry_64_compat.S
). Questa risposta ne prende solo le parti rilevanti.
I collegamenti in quella risposta e questo sono alle fonti di Linux 4.12, che non contengono la manipolazione della tabella delle pagine di mitigazione di Meltdown, quindi sarà significativo costi aggiuntivi.
int 0x80
e sysenter
hanno diversi punti di ingresso. Stai cercando entry_SYSENTER_compat
. AFAIK, sysenter
va sempre lì, anche se lo esegui in un processo in spazio utente a 64 bit. Il punto di ingresso di Linux invia un __USER32_CS
costante come valore CS salvato, quindi tornerà sempre nello spazio utente in modalità a 32 bit.
Dopo aver spinto i registri per costruire un struct pt_regs
nello stack del kernel, c'è un TRACE_IRQS_OFF
hook (non ho idea di quante istruzioni equivalgano), quindi call do_fast_syscall_32
che è scritto in C. (Native 64-bit syscall
l'invio viene effettuato direttamente da asm, ma le chiamate di sistema compatibili a 32 bit vengono sempre inviate tramite C).
do_syscall_32_irqs_on
in arch/x86/entry/common.c
è abbastanza leggero:solo un controllo se il processo viene tracciato (penso che questo sia il modo in cui strace
può agganciare le chiamate di sistema tramite ptrace
), quindi
...
if (likely(nr < IA32_NR_syscalls)) {
regs->ax = ia32_sys_call_table[nr]( ... arg );
}
syscall_return_slowpath(regs);
}
AFAIK, il kernel può usare sysexit
dopo che questa funzione ritorna.
Quindi il percorso di ritorno è lo stesso indipendentemente dal fatto che EAX avesse o meno un numero di chiamata di sistema valido, e ovviamente il ritorno senza invio è il percorso più veloce attraverso quella funzione, specialmente in un kernel con mitigazione Spectre dove il ramo indiretto sulla tabella dei puntatori di funzione passerebbe attraverso una retpoline e prevederebbe sempre errori.
Se vuoi davvero testare sysenter/sysexit senza tutto quel sovraccarico extra, dovrai modificare Linux per mettere un punto di ingresso molto più semplice senza controllare la traccia o spingere/poppare tutti i registri.
Probabilmente vorrai anche modificare l'ABI per passare un indirizzo di ritorno in un registro (come syscall
fa da solo) invece di essere salvato nello stack dello spazio utente che l'attuale sysenter
di Linux L'ABI lo fa; deve get_user()
per leggere il valore EIP a cui dovrebbe tornare.
Se tutto questo sovraccarico fa parte di ciò che vuoi misurare, sei decisamente pronto con un eax che ti dà -ENOSYS
; nel peggiore dei casi otterrai un errore di ramo in più dal controllo dell'intervallo se i predittori di ramo sono attivi per quel ramo in base alle normali chiamate di sistema a 32 bit.
In questo benchmark di Brendan Gregg (collegato da questo post del blog che è una lettura interessante sull'argomento) close(999)
(o qualche altro fd non in uso) è consigliato.