quickthreads internals - modrpc/info GitHub Wiki
qt_t *QT_SP(void *sto, // pointer to the user-allocated stack area
int stacksize // size of stack
);
- Given a stack object (qt_t *), find the top of the stack.
- Given that stack grows down (e.g. i386),
+--------- <======== returns this stack top (sto + stacksize)
|
|
| stk
|
|
+--------- <===== sto (user allocated memory here)
qt_t *QT-ARGS(qt_t *sp, /* user-alloc stack top (i.e. QT_SP(sto, size)) */
void *u, /* user func arg */
void *t, /* ptr to thread-impl object */
qt_userf_t *userf, /* user func */
qt_only_t *only /* special func */
);
- Arranges stack contents s.t. the stack data contains suspended state (i.e. saved regs)
- Returns initialized stack pointer. Initial stack layout is given below
+---------
| arg[2] === `userf' on startup
| arg[1] === `t' on startup
| arg[0] === `u' on startup
+---------
| ret pc === qt_error
+---------
| ret pc === `only' on startup
+---------
| %ebp
| %esi
| %edi
| %ebx <================================== qt_t.sp
+---------
qt_t *QT_VARGS(qt_t *sp,
int nbytes,
void *vargs,
void *pt,
qt_startup_t *startup,
qt_vuserf_t *vuserf,
qt_cleanup_t *cleanup);
- After initialization, initial stack layout is given below.
+-----------
| arg[n-1]
| ..
| arg[0]
+-----------
| ret pc === `qt_vstart'
+-----------
| %ebp === `startup'
| %esi === `cleanup'
| %edi === `pt'
| %ebx === `vuserf' <================ qt_t.sp
+-----------
void *QT_BLOCK(qt_helper_t *helper,
void *arg0, /* typically, ptr to old thread-impl (e.g. stp_t) */
void *arg1, /* some satellite data for yielding (e.g. runq) */
qt_t *newthread /* new thread stack */
);
- Saves the state of the thread and call the helper function using the stack of the new thread. In detail,
- (1) save the current (old) thread regs (push %ebp, %esi, %edi %ebx)
- (2) remember current (old) stack pointer (movl %esp, %eax)
- (3) move to the stack of the thread
newthread
(movl 32(%esp), %esp).- Note that in old thread stack, last frame is for call to qt_block(qt_helper_t *, void *a0, void *a1, qt_t *newthread)
- That is 4th argument (i.e. 32(%esp)) in call frame is newthread
- (4) Now we want to call the helper function; for this, push push 3 arguments to helper function
- (a) qt_t *old_sp
- (b) void *old (arg0)
- (c) void *runq (arg1)
- (3) call the
helper
function using the stack pointer of the old (blocked) thread AND the argumentsarg0<code> and <code>arg1
- (4) when
helper
returns, now the new thread is unblocked (with the new stack) - (5) restore previously-saved (for new) regs and then return (from QT_BLOCK!)
- after all, QT_BLOCK caller is OLD THREAD but returns to the NEW THREAD
+------------
|
+-------------
| saved ebp === caller save this before calling QT_BLOCK
| arg3 === qt_t *newthread
| arg2 === void *a1 (i.e. oldthread)
| arg1 === void *a0 (i.e. runq)
| arg0 === qt_helper_t* <======== %esp before calling QT_BLOCK
+--------------
+------------
| OLD STACK
+-------------+---------------------- <stack frame for QT_BLOCK call>
| saved ebp === caller save this before calling QT_BLOCK
| arg3 === qt_t *newthread
| arg2 === void *a1 (i.e. oldthread)
| arg1 === void *a0 (i.e. runq)
| arg0 === qt_helper_t*
| return eip <======== %esp right after CALL instr
+-------------+-------------------------------------
+------------
| OLD STACK
+-------------
| saved ebp === caller save this before calling QT_BLOCK
| arg3 === qt_t *newthread
| arg2 === void *a1 (i.e. oldthread)
| arg1 === void *a0 (i.e. runq)
| arg0 === qt_helper_t*
| return eip <======== %esp right after CALL instr
+-------------+-------------------------------------
| <NOW, i386 qt_block execution begins> which doesn't necesaarily
| follow C calling convention
+------------
| OLD STACK
+-------------
| saved ebp === caller save this before calling QT_BLOCK
| arg3 32 === qt_t *newthread
| arg2 28 === void *a1 (i.e. oldthread)
| arg1 24 === void *a0 (i.e. runq)
| arg0 20 === qt_helper_t*
| return eip 16
+-------------+-------------------------------------
| ebp 12
| esi 8
| edi sp+4
| ebx <============= %esp after pushing callee-save regs
NEW STACK should have similar stack layout since it would also have called QT_BLOCK previously
+------------
| NEW STACK
+-------------o
| saved ebp === caller save this before calling QT_BLOCK
| arg3 32 === qt_t *newthread
| arg2 28 === void *a1 (i.e. oldthread)
| arg1 24 === void *a0 (i.e. runq)
| arg0 20 === qt_helper_t*
| return eip 16
+-------------+-------------------------------------
| ebp 12
| esi 8
| edi sp+4
| ebx <============= %esp after pushing callee-save regs
(PREVIOUSLY during 'new thread blocking')
+------------
| NEW STACK
+-------------o
| saved ebp === caller save this before calling QT_BLOCK
| arg3 32 === qt_t *newthread
| arg2 28 === void *a1 (i.e. oldthread)
| arg1 24 === void *a0 (i.e. runq)
| arg0 20 === qt_helper_t*
| return eip 16
+-------------+-------------------------------------
| ebp 12
| esi 8
| edi sp+4
| ebx <============= %esp after pushing callee-save regs
| (PREVIOUSLY during 'new thread blocking')
+----------------------------------------------------
| helper_arg2 === oldthread (a1)
| helper_arg1 === runq (a0)
| hepler_arg2 === qt_t *sp (esp of old thread saved in %eax)
| ret eip
+---------------------------------<stack frame for worker>
|
Return to original QT_BLOCK call (called by NEW thread previously) and continue/resume from that point on
+------------
| NEW STACK
+-------------
| saved ebp === caller save this before calling QT_BLOCK
| arg3 32 === qt_t *newthread
| arg2 28 === void *a1 (i.e. oldthread)
| arg1 24 === void *a0 (i.e. runq)
| arg0 20 === qt_helper_t*
| return eip 16
+-------------+-------------------------------------