nullplan wrote:
rdos wrote:Futexes are great, but not that easy to get right with SMP.
Do you mean the futex implementation, or their use in synchronization primitives? Because the former is rather easy to get right:
The primary problem is that data associated with the futex is shared between the application and kernel. For instance, you cannot keep task lists associated with a futex accessible to the application since then corruption of memory by the applications could lead to a crashed scheduler. You definitely don't want spinlocks used by kernel to be in the application memory space either. Therefore, the manipulation of the futex counter use locked instructions that are atomic which means spinlocks are not needed on the application side. You also don't want resources for unused or non-contended futexes in the application to be associated with kernel resources.
I solve this by having a separate futex area where data for futexes are allocated which normally won't get corrupted by the application even if it misuses the heap. This area will keep the counter, the name (if used) and a handle to kernel. The handle is initially set to zero and will only be allocated if locking the futex fails. In kernel, the handle is associated with a list of threads that are blocked on the futex, and a pointer to the userspace structure. This way, the application can sabotage the counter, and make the futex fail to work as expected, but it cannot manipulate the list of blocked threads directly.
Another problem is that I want to use generic API calls for criticial sections in the application and the use of futuxes requires allocation of the futex area in the application address space and patching these syscalls to use futexes instead of doing direct API calls to kernel. I suppose another solution to this is to have this code in libc, but then if you use pure assembly or another compiler & linker then futexes won't be used. Therefore, I let the application loader handle it by patching code in a different manner than normal. Another potential problem of having these structures compiled with the application is that you cannot change it if you decide you need to modify the implementation. With patching this is a non-issue since the implementation is not linked with the application, rather constructed by the application loader.
The application loaders implementation of futexes looks like this (p? labels are changed to the address of the futex area):
Code: Select all
create_us_section Proc near
push eax
push ecx
push edx
push edi
p1:
mov edx,12345678h
mov edi,edx
mov ecx,MAX_SECTIONS
xor eax,eax
repnz scasd
stc
jnz csDone
;
mov ebx,MAX_SECTIONS
sub ebx,ecx
push ebx
;
dec ebx
sub edi,4
mov eax,16
mul ebx
add eax,4 * MAX_SECTIONS
mov edx,eax
p2:
add edx,12345678h
mov [edi],edx
;
mov [edx].fs_handle,0
mov [edx].fs_val,-1
mov [edx].fs_counter,0
mov [edx].fs_owner,0
mov [edx].fs_sect_name,0
;
pop ebx
clc
csDone:
pop edi
pop edx
pop ecx
pop eax
ret
create_us_section Endp
create_named_us_section Proc near
push eax
push ecx
push edx
push edi
push ebp
;
mov ebp,edi
p1n:
mov edx,12345678h
mov edi,edx
mov ecx,MAX_SECTIONS
xor eax,eax
repnz scasd
stc
jnz cnsDone
;
mov ebx,MAX_SECTIONS
sub ebx,ecx
push ebx
;
dec ebx
sub edi,4
mov eax,16
mul ebx
add eax,4 * MAX_SECTIONS
mov edx,eax
p2n:
add edx,12345678h
mov [edi],edx
;
mov [edx].fs_handle,0
mov [edx].fs_val,-1
mov [edx].fs_counter,0
mov [edx].fs_owner,0
mov [edx].fs_sect_name,ebp
;
pop ebx
clc
cnsDone:
pop ebp
pop edi
pop edx
pop ecx
pop eax
ret
create_named_us_section Endp
free_us_section Proc near
push edx
;
sub ebx,1
jc fusDone
;
cmp ebx,MAX_SECTIONS
jae fusDone
;
shl ebx,2
p3:
add ebx,12345678h
xor eax,eax
xchg eax,[ebx]
or eax,eax
jz fusDone
;
mov ebx,eax
mov eax,[ebx].fs_handle
or eax,eax
jz fusDone
;
UserGateApp cleanup_futex_nr
fusDone:
xor ebx,ebx
;
pop edx
ret
free_us_section Endp
enter_us_section Proc near
push eax
push ebx
;
sub ebx,1
jc eusDone
;
cmp ebx,MAX_SECTIONS
jae eusDone
;
shl ebx,2
p4:
add ebx,12345678h
mov ebx,[ebx]
or ebx,ebx
jz eusDone
;
str ax
cmp ax,[ebx].fs_owner
jne eusLock
;
inc [ebx].fs_counter
jmp eusDone
eusLock:
lock add [ebx].fs_val,1
jc eusTake
;
mov eax,1
xchg ax,[ebx].fs_val
cmp ax,-1
jne eusBlock
eusTake:
str ax
mov [ebx].fs_owner,ax
mov [ebx].fs_counter,1
jmp eusDone
eusBlock:
push edi
mov edi,[ebx].fs_sect_name
or edi,edi
jnz eusNamedBlock
;
UserGateApp acquire_futex_nr
jmp eusBlockPop
eusNamedBlock:
UserGateApp acquire_named_futex_nr
eusBlockPop:
pop edi
eusDone:
pop ebx
pop eax
ret
enter_us_section Endp
leave_us_section Proc near
push eax
push ebx
;
sub ebx,1
jc lusDone
;
cmp ebx,MAX_SECTIONS
jae lusDone
;
shl ebx,2
p5:
add ebx,12345678h
mov ebx,[ebx]
or ebx,ebx
jz lusDone
;
str ax
cmp ax,[ebx].fs_owner
jne lusDone
;
sub [ebx].fs_counter,1
jnz lusDone
;
mov [ebx].fs_owner,0
lock sub [ebx].fs_val,1
jc lusDone
;
mov [ebx].fs_val,-1
UserGateApp release_futex_nr
lusDone:
pop ebx
pop eax
ret
leave_us_section Endp
At the kernel side it looks like this:
Code: Select all
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;
; NAME: acquire_futex
;
; DESCRIPTION: Acquire futex
;
; PARAMS: ES:(E)BX address to futex struct
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
acquire_futex_name DB 'Acquire Futex',0
acquire_futex Proc near
push ds
push fs
push eax
push ebx
push cx
push esi
;
mov esi,ebx
mov ebx,es:[esi].fs_handle
mov ax,FUTEX_HANDLE
DerefHandle
jnc acquire_no_sect
;
mov ax,SEG data
mov ds,ax
EnterSection ds:futex_section
;
mov ebx,es:[esi].fs_handle
mov ax,FUTEX_HANDLE
DerefHandle
jnc acquire_handle_ok
;
mov cx,SIZE futex_handle_seg
AllocateHandle
mov ds:[ebx].fh_list,0
mov ds:[ebx].fh_lock,0
mov [ebx].hh_sign,FUTEX_HANDLE
;
movzx eax,[ebx].hh_handle
mov es:[esi].fs_handle,eax
acquire_handle_ok:
push ds
mov ax,SEG data
mov ds,ax
LeaveSection ds:futex_section
pop ds
acquire_no_sect:
call LockCore
sti
call cs:lock_futex_proc
mov ax,1
xchg ax,es:[esi].fs_val
cmp ax,-1
je acquire_take
;
mov ax,ds
push OFFSET acquire_done
call SaveLockedThread
mov ds,ax
;
lea edi,[ebx].fh_list
mov es,fs:cs_curr_thread
mov fs:cs_curr_thread,0
;
call InsertBlock32
call cs:unlock_futex_proc
;
mov es:p_sleep_type,SLEEP_TYPE_FUTEX
mov es:p_sleep_sel,ds
mov es:p_sleep_offset,esi
jmp LoadThread
acquire_take:
push es
mov es,fs:cs_curr_thread
mov ax,es:p_futex_id
pop es
mov es:[esi].fs_owner,ax
mov es:[esi].fs_counter,1
;
call cs:unlock_futex_proc
call UnlockCore
acquire_done:
pop esi
pop cx
pop ebx
pop eax
pop fs
pop ds
ret
acquire_futex Endp
acquire_futex16 Proc far
push ebx
movzx ebx,bx
call acquire_futex
pop ebx
retf32
acquire_futex16 Endp
acquire_futex32 Proc far
call acquire_futex
retf32
acquire_futex32 Endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;
; NAME: acquire_named_futex
;
; DESCRIPTION: Acquire named futex
;
; PARAMS: ES:(E)BX address to futex struct
; ES:(E)DI name of section
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
acquire_named_futex_name DB 'Acquire Named Futex',0
acquire_named_futex Proc near
push ds
push fs
push eax
push ebx
push ecx
push esi
push edi
;
GetThread
mov fs,ax
mov esi,OFFSET p_list_name
mov ecx,31
acquire_named_copy:
mov al,es:[edi]
mov fs:[esi],al
inc esi
inc edi
or al,al
jz acquire_named_copied
;
loop acquire_named_copy
acquire_named_copied:
xor al,al
mov fs:[esi],al
;
mov esi,ebx
mov ebx,es:[esi].fs_handle
mov ax,FUTEX_HANDLE
DerefHandle
jnc acquire_named_no_sect
;
mov ax,SEG data
mov ds,ax
EnterSection ds:futex_section
;
mov ebx,es:[esi].fs_handle
mov ax,FUTEX_HANDLE
DerefHandle
jnc acquire_named_handle_ok
;
mov cx,SIZE futex_handle_seg
AllocateHandle
mov ds:[ebx].fh_list,0
mov ds:[ebx].fh_lock,0
mov [ebx].hh_sign,FUTEX_HANDLE
;
movzx eax,[ebx].hh_handle
mov es:[esi].fs_handle,eax
acquire_named_handle_ok:
push ds
mov ax,SEG data
mov ds,ax
LeaveSection ds:futex_section
pop ds
acquire_named_no_sect:
call LockCore
sti
call cs:lock_futex_proc
mov ax,1
xchg ax,es:[esi].fs_val
cmp ax,-1
je acquire_named_take
;
mov ax,ds
push OFFSET acquire_named_done
call SaveLockedThread
mov ds,ax
;
lea edi,[ebx].fh_list
mov es,fs:cs_curr_thread
mov fs:cs_curr_thread,0
;
call InsertBlock32
call cs:unlock_futex_proc
;
mov es:p_sleep_type,SLEEP_TYPE_FUTEX
mov es:p_sleep_sel,ds
mov es:p_sleep_offset,esi
jmp LoadThread
acquire_named_take:
push es
mov es,fs:cs_curr_thread
mov ax,es:p_futex_id
pop es
mov es:[esi].fs_owner,ax
mov es:[esi].fs_counter,1
;
call cs:unlock_futex_proc
call UnlockCore
acquire_named_done:
pop edi
pop esi
pop ecx
pop ebx
pop eax
pop fs
pop ds
ret
acquire_named_futex Endp
acquire_named_futex16 Proc far
push ebx
push edi
movzx edi,di
movzx ebx,bx
call acquire_named_futex
pop edi
pop ebx
retf32
acquire_named_futex16 Endp
acquire_named_futex32 Proc far
call acquire_named_futex
retf32
acquire_named_futex32 Endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;
; NAME: release_futex
;
; DESCRIPTION: Release futex
;
; PARAMS: ES:(E)BX address to futex struct
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
release_futex_name DB 'Release Futex',0
release_futex Proc near
push ds
push fs
push eax
push ebx
push cx
push esi
;
mov esi,ebx
mov ebx,es:[esi].fs_handle
mov ax,FUTEX_HANDLE
DerefHandle
jc release_done
;
call LockCore
sti
call cs:lock_futex_proc
;
mov ax,ds:[ebx].fh_list
or ax,ax
jz release_unlock
;
mov ax,1
xchg ax,es:[esi].fs_val
cmp ax,-1
jne release_unlock
;
push di
;
push es
push esi
lea esi,ds:[ebx].fh_list
call RemoveBlock32
mov es:p_data,0
mov cx,es
mov di,es:p_futex_id
pop esi
pop es
;
mov es:[esi].fs_owner,di
mov es:[esi].fs_counter,1
call cs:unlock_futex_proc
;
push es
mov es,cx
call InsertWakeup
pop es
;
pop di
;
call UnlockCore
jmp release_done
release_unlock:
call cs:unlock_futex_proc
call UnlockCore
release_done:
pop esi
pop cx
pop ebx
pop eax
pop fs
pop ds
ret
release_futex Endp
release_futex16 Proc far
push ebx
movzx ebx,bx
call release_futex
pop ebx
retf32
release_futex16 Endp
release_futex32 Proc far
call release_futex
retf32
release_futex32 Endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;
; NAME: cleanup_futex
;
; DESCRIPTION: Cleanup futex
;
; PARAMS: ES:(E)BX address to futex struct
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
cleanup_futex_name DB 'Cleanup Futex',0
cleanup_futex Proc near
push ds
push eax
push ebx
;
mov ebx,es:[ebx].fs_handle
mov ax,FUTEX_HANDLE
DerefHandle
jc cleanup_done
;
FreeHandle
cleanup_done:
pop ebx
pop eax
pop ds
ret
cleanup_futex Endp
cleanup_futex16 Proc far
push ebx
movzx ebx,bx
call cleanup_futex
pop ebx
retf32
cleanup_futex16 Endp
cleanup_futex32 Proc far
call cleanup_futex
retf32
cleanup_futex32 Endp