I'm reading about the source code of linux kernel of version 2.6.11 and getting confused about the inline-assembly kernel_thread_helper
in arch/i386/kernel/process.c
.
Following is the assembly code. Initially, register %ebx
stores the address of a function and register %edx
stores a pointer which is going to be passed to the function.
extern void kernel_thread_helper(void);
__asm__(".section .text\n"
".align 4\n"
"kernel_thread_helper:\n\t"
"movl %edx,%eax\n\t"
"pushl %edx\n\t"
"call *%ebx\n\t"
"pushl %eax\n\t"
"call do_exit\n"
".previous");
I'm wondering:
Why does this piece of code execute movl %edx,%eax
? Since pushl %edx
has pushed the value of %edx
in stack, the function pointed to by %ebx
should then retrieve needed parameters from the stack successfully. So is there no need to copy %edx
to %eax
?
I know that when __attribute__((regparm(3)))
is added in front of a function definition, the function will retrieve parameters in the order %eax
, %edx
, %ecx
and stack. So I'm wondering, is the instruction copying the value in %edx
to %eax
designed to be compatible with functions that use this __attribute__
? Function with such an attribute should retrieve the needed parameters from %eax
and go on running as well! But this causes a problem: the previously pushed pointer by pushl %edx
could remain in the stack if no one pops it off.
I'm new to Linux Kernel and maybe misunderstood something. I want to know how these assembly instructions work actually and what's wrong with my guess.
A comment in an earlier Linux kernel version (v2.5.0) explains this (the movl
into eax
was added in v2.1.131 to really be precise):
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
long retval, d0;
__asm__ __volatile__(
"movl %%esp,%%esi\n\t"
"int $0x80\n\t" /* Linux/i386 system call */
"cmpl %%esp,%%esi\n\t" /* child or parent? */
"je 1f\n\t" /* parent - jump */
/* Load the argument into eax, and push it. That way, it does
* not matter whether the called function is compiled with
* -mregparm or not. */
"movl %4,%%eax\n\t"
"pushl %%eax\n\t"
"call *%5\n\t" /* call fn */
"movl %3,%0\n\t" /* exit */
"int $0x80\n"
"1:\t"
:"=&a" (retval), "=&S" (d0)
:"0" (__NR_clone), "i" (__NR_exit),
"r" (arg), "r" (fn),
"b" (flags | CLONE_VM)
: "memory");
return retval;
}
So yes, as you suspect, the movl
into eax
is exactly to provide compatibility for both functions compiled with -mregparam
or an equivalent __attribute__
and without (i386 System V ABI calling convention, i.e. all parameters on the stack).
But this causes a problem: the previously pushed pointer by
pushl %edx
could remain in the stack if no one pops it off.
Well, true, but who cares about popping it off if the only thing we are doing right after calling the kernel thread function is pushl %eax; call do_exit
? Doing movl %eax, 0(%esp)
would work as well, but if there's enough stack space to run a kernel thread then surely there are 4 bytes to push a register after it returns. Additionally pushl %eax
is only 1 byte vs 3 for the movl
, and I'm not sure about latency or throughput of the two, but I don't really think that was a concern in such a piece of code.