Page 1 of 10

Fast System Call

Posted: Sat Feb 24, 2007 3:21 pm
by Ghost
В современной x86 архитектуре используется три варианта вызва API :
- дальний вызов (Solaris, BSD - селектор 7 смешение 0)
- шлюз прерывания (Kolibri, Linux, Windows до w2k)
- Fast System Call (Linux, WinXP и старше)

При реализации быстрых вызовов я остановился на SYSENTER/SYSEXIT от Intel, за подробностями : Intel® Architecture Software Developer’s Manual, или http://www.codeguru.com/cpp/w-p/system/ ... /c8223__1/

Краткая справка:

SYSENTER - Fast System Call
Обеспечивает максимально эффективный переход к коду на нулевок кольце, устанавливая регистры CS, EIP, SS, ESP следуюшим образом:

CS - SYSENTER_CS_MSR
SS - SYSENTER_CS_MSR + 8
ESP - SYSENTER_ESP_MSR
EIP - SYSENTER_EIP_MSR


SYSEXIT - Fast Return from Fast System Call
Обеспечивает максимально эффективный возврат из кода на нулевок кольце к коду на кольце 3, устанавливая регистры CS, EIP, SS, ESP следуюшим образом:

CS - SYSENTER_CS_MSR + 16
SS - SYSENTER_CS_MSR + 24
ESP - ECX
EIP - EDX

SYSENTER_CS_MSR - MSR[0x174]
SYSENTER_ESP_MSR - MSR[0x175]
SYSENTER_EIP_MSR - MSR[0x176]

За поддержку быстрых вызовов отвечает CPUID(1).EDX.bit_11 - SEP(SYSENTER/SYSEXIT present)

Как видно, особенностью бустрых вызовов является то что этот механизм не сохраняет адресс возврата в код кольца 3, в Linux/Win аргументы передаются через стек, поэтому в этих системах необходимые для возврата занчения ECX и EDX устанавливаются кодом приложения, но так как в KolibriOS большая часть функций использует эти регистры для передачи аргументов я предлагаю сохранять регистры в память приложения, например заголовок (адрес DS:0), тогда вызов функций будет выглядеть так:

Code: Select all

SYSENTER_VAR	equ	0
...
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:
Из этих же "особенностей" вытекает и то, что значения реистров EDX и ECX результата будут испорчены, это можно обойти аналогично вышеописанному, но пока я этого не реализовал - жду дискусси. Кроме того не реализованно сохранение eflags.

Итак, список изменений:

const.inc
kernel.asm
boot\bootcode.inc
core\syscall.inc

##############[const.inc]##########################

Константы

Code: Select all

.............
.............

; CPU MSR names
MSR_SYSENTER_CS		equ	0x174
MSR_SYSENTER_ESP	equ	0x175
MSR_SYSENTER_EIP	equ	0x176

.............
.............
##############[kernel.asm]#########################

Настройка MSR регистров

Code: Select all

.............
.............

; ENABLE PAGING

           call test_cpu
           bts [cpu_caps], CAPS_TSC     ;force use rdtsc
	   
; -------- SYSENTER/SYSEXIT init ----------
	bt	[cpu_caps], CAPS_SEP
	jnc	.SEnP			; SysEnter not Present
	xor	edx, edx
	mov	ecx, MSR_SYSENTER_CS
	mov	eax, os_code
	wrmsr
	mov	ecx, MSR_SYSENTER_ESP
	mov	eax, sysenter_stack	; Check it
	wrmsr
	mov	ecx, MSR_SYSENTER_EIP
	mov	eax, sysenter_entry
	wrmsr
.SEnP:
; -----------------------------------------

.............
.............
##############[boot\bootcode.inc]##################

Изменён порядок следования дескрипторов

Code: Select all

.............
.............
; GDT TABLE

gdts:

        dw     gdte-$-1
        dd     gdts
        dw     0

; Внимание! порядок следуюшил четырёх селекторов не менять, используется в SYSENTER/SYSEXIT
; должно быть os_code, os_data, app_code, app_data
int_code_l:
os_code_l:

        dw     0xffff
        dw     0x0000
        db     0x00
        dw     11011111b *256 +10011010b
        db     0x00

int_data_l:
os_data_l:

        dw     0xffff
        dw     0x0000
        db     0x00
        dw     11011111b *256 +10010010b
        db     0x00

app_code_l:
        dw 0xFFFF;((0x80000000-std_application_base_address) shr 12) and 0xffff
        dw 0
        db 0x40
        db cpl3
        dw G32+D32+0x6000+0x7;

app_data_l:
        dw 0xFFFF;(0x80000000-std_application_base_address) shr 12 and 0xffff
        dw 0
        db 0x40
        db drw3
        dw G32+D32+0x6000+0x7;
      
; --------------- APM ---------------------
apm_code_32:
        dw     0x0f        ; limit 64kb
        db     0, 0, 0
        dw     11010000b *256 +10011010b
        db     0x00
apm_code_16:
        dw     0x0f
        db     0, 0, 0
        dw     10010000b *256 +10011010b
        db     0x00
apm_data_16:
        dw     0x0f
        db     0, 0, 0
        dw     10010000b *256 +10010010b
        db     0x00
; -----------------------------------------

graph_data_l:

        dw     0x7ff
        dw     0x0000
        db     0x00
        dw     11010000b *256 +11110010b
        db     0x00
.............
.............
##############[core\syscall.inc]###################

Точка входа быстрого вызова

Code: Select all

.............
.............
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                            ;;
;;                     SYSENTER ENTRY                         ;;
;;                                                            ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

uglobal
times	100 db ?
sysenter_stack:
endg

align 32
SYSENTER_VAR	equ	0
sysenter_entry:
	; Настраиваем стек
	cli
	push	ds
	push	eax
	mov	ax, os_data
	mov	ds, ax
	mov	eax, [0x3010]
        mov	eax, [eax + TASKDATA.pid]
	call	pid_to_slot			; не портит регистры
	shl	eax, 12
	add	eax, sysint_stack_data + 4096
	mov	esp, eax
	mov	eax, [sysenter_stack - 4]
	mov	ds, ax
	mov	eax, [ss:sysenter_stack - 8]
	;------------------
	push	ds es
	pushad
	cld

	mov	ax, word os_data
	mov	ds, ax
	mov	es, ax
	
        mov     eax, ebx
        mov     ebx, ecx
        mov     ecx, edx
        mov     edx, esi
        mov     esi, edi
        mov     edi, [esp + 28]

	sti
	push	eax
	and	edi, 0xff
	call	dword [servetable + edi * 4]
	pop	eax

	popad
	pop	es ds
	;------------------
	mov	edx, [SYSENTER_VAR]		; eip
	mov	ecx, [SYSENTER_VAR + 4]	; esp
	sysexit


iglobal
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; SYSTEM FUNCTIONS TABLE ;;
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.............
.............
Шаблон программы:

Code: Select all

; <--- description --->
; compiler:     FASM 1.50
; name:         Basic window example for MenuetOS
; version:      1.01
; last update:  25/08/2004
; written by:   Ivan Poddubny
; e-mail:       ivan-yar@bk.ru
; adapted by:	Mihailov Ilia


; <--- include all MeOS stuff --->
include "lang.inc"
include "macros.inc"
SYSENTER_VAR	equ	0

; <--- start of MenuetOS application --->
MEOS_APP_START


; <--- start of code --->
CODE
	call	draw_window		; at first create and draw the window

wait_event:				; main cycle
	mov	eax, 10
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:
	cmp	eax, 1			;   if event == 1
	je	redraw			;     jump to redraw handler
	cmp	eax, 2			;   else if event == 2
	je	key			;     jump to key handler
	cmp	eax, 3			;   else if event == 3
	je	button			;     jump to button handler
	jmp	wait_event		;   else return to the start of main cycle

redraw:					; redraw event handler
	call	draw_window
	jmp	wait_event

key:					; key event handler
	mov	eax, 2			;   get key code
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:

	jmp	wait_event

button:					; button event handler
	mov	eax, 17			;   get button identifier
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:

	cmp	ah, 1
	jne	wait_event		;   return if button id != 1

	or	eax, -1		;   exit application
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:

draw_window:
	mov	eax, 12			; start drawing
	mov	ebx, 1
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:

	mov	eax, 0			; create and draw the window
	mov	ebx, 100 * 65536 + 300	;   (window_cx)*65536+(window_sx)
	mov	ecx, 100 * 65536 + 200	;   (window_cy)*65536+(window_sy)
	mov	edx, 0x03ffffff		;   work area color & window type 3
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:

	mov	eax, 4			; window header
	mov	ebx, 8 * 65536 + 8	;   coordinates
	mov	ecx, 0x10ffffff		;   color & font N1
	mov	edx, header		;   address of text
	mov	esi, header.size	;   length of text
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:

	mov	eax, 12			; finish drawing
	mov	ebx, 2
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter
@@:
	ret

; <--- initialised data --->
DATA
lsz header,\
	ru, "&#152; Ў«®­ Їа®Ја ¬¬л",\
	en, "Template program",\
	fr, "La programme poncive"

; <--- uninitialised data --->
UDATA

MEOS_APP_END
; <--- end of MenuetOS application --->
P.S. Ну а теперь ложка дёгтя, при добавлении фунции пустышки, и вызов её 0x100000 раз, старый метод (через шлюз прерывания) оказался быстрее ), видимо ошибка в реализации (.

Posted: Sat Feb 24, 2007 3:38 pm
by andrew_programmer
А если программа, написанная с использованием sysenter запускается на процессоре, который не поддерживает fast system call, то как тогда быть ?

Posted: Sat Feb 24, 2007 4:02 pm
by mike.dld
В программах, использующих макрос mcall, изменять вообще ничего не нужно будет, насколько я понимаю? Кроме самого макроса, естественно, в котором вместо `int 0x40` будет `mov dword[SYSENTER_VAR], ..mcall // mov [SYSENTER_VAR + 4], esp // sysenter // ..mcall: `.

Posted: Sat Feb 24, 2007 4:21 pm
by Ghost
Да, с макросами вообше проблем не будет, кроме того можно писать проверку, если нет быстрых вызовов - то прерывание.

Posted: Sat Feb 24, 2007 5:02 pm
by diamond
Тогда уж быстрее делать call dword ptr [using_syscall] и инициализировать using_syscall либо адресом процедуры int 0x40, либо предлагаемым вариантом. Хотя предложение что-то пихать по нулевому адресу мне категорически не нравится.
Ещё вариант: на старых процессорах sysenter вызывает исключение, но его ведь ядро может перехватить и проэмулировать...

Posted: Sat Feb 24, 2007 5:37 pm
by Serge
Хорошо что решил этим заняться. У меня было желание но времени нет.

Сохранять регистры в DS:0 нельзя, многопоточные программы не будут работать. Тормозила наверняка pid_to_slot тем более она совсем не нужна. Номер слота текущей задачи хранится в переменной [CURRENT_TASK], она есть в const.inc. sysint_stack_data больше нет память под PL_0 стек выделяется динамически, странно что код работал. Поэтому надо изменить на
mov eax,[CURRENT_TASK]
shl eax,8
mov eax,[PROC_BASE+eax+APPDATA.pl0_stack]
lea esp, [eax+RING0_STACK_SIZE]

RING0_STACK_SIZE сейчас не определена её надо добавить в const.inc
RING0_STACK_SIZE equ 0x2000-512 ;512 байт для контекста FPU

Ещё ты несколько раз перегружаешь сегментные регистры хотя хватает одного раза
mov eax, [ss:sysenter_stack - 8]
задавать явно сегмент не зачем это ведь тот же ds

Posted: Sat Feb 24, 2007 6:24 pm
by Serge
Ghost
если сохранять регистра в стеке то можно так:

Code: Select all

   push ecx
   push edx
   push ebp
   mov ebp, esp
   push $+4   ;адрес возврата   ;2 байта ->|
   sysenter                     ;2 байта   |
;@@:                            ;       <--|
   pop ebp
   pop edx
   pop ecx
@@ и @F лучше не использовать или могут быть проблемы с другими метками в программе


адрес возврата будет в [ebp-4]
после загрузки правильного esp надо сохранить ebp в стеке
тогда для возврата
pop ecx ; ebp он же ring_3 esp
mov edx, [ecx-4]
sysexit

Posted: Sat Feb 24, 2007 9:46 pm
by Ghost
http://iam.gorodok.net/fc_ker.zip
Вот изменённые файлы для текущего ядра (#369) с добавленной функцией - заглушкой + тестовое приложение (test), оно выводит (на доску отладки) примерное количество тактов, затраченных на вызов в цикле 0x100000 раз фунции заглушки методом быстрого вызова и по прерыванию. У меня в среднем такие значения:

000000001F700000 <- Fast call
000000000E000000 <- Interrupt

И того сами вызовы дольше в два раза, т.е. каждый вызов примерно на 280 тактов дольше. Понятно что они вылазят в надбавках (теперь уже):

Code: Select all

sysenter_entry:
	; Настраиваем стек
	cli
	push	eax
	mov	eax, [ss:CURRENT_TASK]
	shl	eax, 8
	mov	eax, [ss:PROC_BASE + eax + APPDATA.pl0_stack]
	lea	esp, [ss:eax + RING0_STACK_SIZE]	; configure ESP
	mov	eax, [ss:sysenter_stack - 4]		; eax - original eax, from app
	sti
	;------------------
	...
	;------------------
	mov	edx, [SYSENTER_VAR]		; eip
	mov	ecx, [SYSENTER_VAR + 4]	; esp
	sysexit
Но выигрыш от использывания быстрых вызовов ожидался больше... А может всё дело в моём AMD K7...

Проверьте кто может, и сообшите результаты.

P.S. Сохранение регистров пока оставил как было, переделаю на стек (это видимо лучший вариант т.к. в основном программы не хранят данные в [ESP - 4|8], и это будет прозрачно относительно варианта с прерыванием) + нормальное сохранение/передачу чегистров после того как скорость хотябы останется на уровне прерываний.

Про DS - в том месте кода DS - селектор кода программы, кстати замене сегмента много кушает? Если да то можно и попыхтеть над кодом.

Про Menuet OS, сколько можно тянуть его наследство? Ктонибудь мне обьяснит смысл переворачивания регистров? Да, уйти от этого не просто но с каждым месяцем API растёт и это становится ещё сложнее.
Или например :

Code: Select all

pop   eax 
popad
в обработчике сис вызова? без этого конечно все смешения в возвр. регистрам изменятся, но тянуть этот баласт тоже не дело. Или я не прав?

Posted: Sat Feb 24, 2007 11:43 pm
by Serge
Ghost

У меня было тоже самое. Проверял разные варианты, запрещал прерывание и отключал кеш, нифига не помогло. Прерывание работало в два раза быстрее. Тогда начал читать описание команд и вспомнил почему не стал их делать.
Ржал полчаса.
Команды расчитаны на плоскую память и устанавливают базу сегментов в 0 а у приложений база 0х60400000.
Происходит страничная ошибка с нарушением права доступа и в дело вступает обработчик страничных ошибок. Но он не обрабатывает такие ошибки потому что их никогда не возникало. Поэтому он просто возвращает управление назад. Из стека загружаются сохранённые сегментные регистры и на этот раз там правильные значения и программа продолжает работу.
Во всёи есть один положительный момент. Защита ядра таблицами страниц работает правильно. Надо залить код на SVN. Я закончил драйвер и скоро выложу ядро с новым распределением памяти под плоскую модель.
Можно будет сделать пробную "плоскую" версию и проверить ещё раз.

Posted: Sun Feb 25, 2007 12:03 am
by Ghost
Хех. Буду ждать "плоскую" версию.

По поводу SVN : куда лить? В trunk вроде этому коду пока не место, в branches - тоже не особо, изменений не много.

Думаю плоскую версию ты в branches выложиш, туда и вызовы пойдут, а пока допишу сохранение регистров на текущем ядре, отпишусь здесь.

Ещё есть идея про SEH (Structured|Sexual Exception Handler), он и компиллерам (C/C++ - try/catch, etc) на пользу пойдёт и несуществуюший КОП обойти позволит, но это только идея :)

Posted: Sun Feb 25, 2007 9:39 am
by Serge
Ghost
Пиши в транк. Код хороший и уже работает :) Можно обкатать разные варианты вызовов и макросов. У АМД есть своя пара syscal/sysret. Они ее делали позже Интел и продумана она лучше. Тоже стоит добавить.
> несуществуюший КОП обойти позволит
Что такое КОП ?

Posted: Sun Feb 25, 2007 2:11 pm
by Ghost
КОП - Код ОПерации (OpCode)

Дописать поддержку SYSCALL/SYSRET не сложно, вызовы должны даже проше выглядеть, т.к. EIP автоматом копируется в ECX, а заморочек с ESP вообше нет. Но пока остается проблема с нулевой базой:

A new descriptor is loaded for CS to specify a fixed 4-Gbyte flat segment as follows:
- The CS_base is set to zero.
- The CS_limit is set to 4 Gbyte.
- The CS segment attributes are set to Read-only.

Сегодня - завтра напишу код, надеюсь что он хоть и со страничными нарушениями, но будет работать.

Posted: Sun Feb 25, 2007 5:23 pm
by Ghost
Добавил SYSCALL/SYSRET, кое как работают.
Всё на SVN

Вызов через SYSENTER, вместо int 0x40:

Code: Select all

SYSENTER_VAR	equ	0
	mov	dword[SYSENTER_VAR], @f
	mov	[SYSENTER_VAR + 4], esp
	sysenter		; портятся ecx, edx
@@:
Вызов через SYSCALL, вместо int 0x40:

Code: Select all

	push	ecx
	syscall
	pop	ecx

Posted: Tue Feb 27, 2007 9:00 am
by <Lrz>
У меня в эмуляторе VMWARE 5.5.0
E42093F49 <-Fast call
143B6C251 <- Interrupt

Posted: Tue Feb 27, 2007 3:39 pm
by Heavyiron
На реальной машине с ядром 378:
Image