Both parent and child returns different values because of manipulation of CPU registers in child's context.
Each process in linux kernel represented by task_struct. task_struct is encased(pointer) in thread_info structure which lies at the end of kernel mode stack.Whole CPU context(registers) are stored in this thread_info structure.
struct thread_info {
struct task_struct *task; /* main task structure */
struct cpu_context_save cpu_context; /* cpu context */
}
All fork/clone() system calls calls kernel equivalent function do_fork().
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
Here is the sequence of execution
do_fork()->copy_process->copy_thread()
(copy_thread is arch specific function call)
copy_thread() copies the register values from the parent and changes the return value to 0
(In case of arm)
struct pt_regs *childregs = task_pt_regs(p);
*childregs = *regs; /* Copy register value from parent process*/
childregs->ARM_r0 = 0; /*Change the return value*/
thread->cpu_context.sp = (unsigned long)childregs;/*Write back the value to thread info*/
thread->cpu_context.pc = (unsigned long)ret_from_fork;
When the child gets scheduled it executes a assembly routine ret_from_fork() which will returns zero.
For the parent it gets the return value from the do_fork() which is pid of process
nr = task_pid_vnr(p);
return nr;