linux系统之进程的创建与上下文切换

【摘要】

【写作原因】

【正文一】用户创建线程

【正文二】 glbic中代码分析

【正文三】进程创建函数do_fork

【正文四】进程遍历

【总结】

 

注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)

 

【摘要】

本文将从用户、glbic库和linux 内核几个角度分析一下进程的创建过程。

【写作原因】

希望能通过几篇博文对进程的创建、执行、调度几个过程做一个详细阐述。

其中:1)实时进程的调度原理请参考博文:点击打开链接;

   2)普通进程的调度原理请参考博文:点击打开链接;

【正文一】用户创建线程。

用户态创建线程的示例:

void pthread_create_example(void)
{
    pthread_attr_t     thread_attr;
    struct sched_param schedprm;
/*
glibc中:__pthread_attr_init_2_0 (attr)
iattr->flags |= ATTR_FLAG_OLDATTR
*/
    status = pthread_attr_init(&thread_attr);
/*glibc中设置线程栈大小*/
    status |= pthread_attr_setstack(&thread_attr,
                                            pCreate->pStackAddr,
                                            pCreate->stackSize);
/*glibc中设置线程栈地址*/
    status |= pthread_attr_setstack(&thread_attr,
                                            pCreate->pStackAddr,
                                            pCreate->stackSize);
                                            
    status |= pthread_attr_setinheritsched(&thread_attr,
                                           PTHREAD_EXPLICIT_SCHED);
/*glibc中设置线程调度策略*/
    status |= pthread_attr_setschedpolicy(&thread_attr,
                                OSA_thrGetPolicy(pCreate->thrPol));
/*
glibc中设置线程优先级,此处修改并未真正生效,在创建进程时才真正设置:            
__pthread_attr_setschedparam (attr, param)
memcpy (&iattr->schedparam, param, sizeof (struct sched_param));
iattr->flags |= ATTR_FLAG_SCHED_SET;
*/
    status |= pthread_attr_setschedparam(&thread_attr, &schedprm);
    status = pthread_create(&pThrObj->thread,
                            &thread_attr,
                            (OSA_thrFuncType)pCreate->OpThrRun,
                             pCreate->pUsrArgs);
error_exit:
    pthread_attr_destroy(&thread_attr);
}

易见,用户在创建线程时,直接调用了标准线程库的接口,一般来说创建过程和我们的示例程序大同小异,不做过多介绍。

直接进入下一环节,用户在创建线程时标准c库和内核中都做了什么?

【正文二】 glbic中代码分析。

1 关键源文件:pthread_create.c
2 用户到内核的线程创建过程:pthread_create->__pthread_create_2_1->create_thread->do_clone->__clone->do_fork:

标红部分为glibc中关键的实现函数:

3 代码实现: 

3.1 入口函数__pthread_create_2_1,此处只列出了该函数的关键几个实现点:

__pthread_create_2_1 (newthread, attr, start_routine, arg)
     pthread_t *newthread;
     const pthread_attr_t *attr;
     void *(*start_routine) (void *);
     void *arg;
{
  struct pthread *pd = NULL;
  /* 分配栈 */
  int err = ALLOCATE_STACK (iattr, &pd);
  pd->schedpolicy = self->schedpolicy;
  pd->schedparam = self->schedparam;
 /* 用户线程回调函数 */
  pd->start_routine = start_routine;
  pd->arg = arg;  
 {
  /*如果用户未设置调度策略 则获取父进程的*/
   pd->schedpolicy = INTERNAL_SYSCALL (sched_getscheduler, scerr, 1, 0);
   pd->flags |= ATTR_FLAG_POLICY_SET;
 }
 {
   /*如果用户未设置优先级 则获取父进程的*/
   INTERNAL_SYSCALL (sched_getparam, scerr, 2, 0, &pd->schedparam);
   pd->flags |= ATTR_FLAG_SCHED_SET;
 }
  /* Start the thread.  */
  retval = create_thread (pd, iattr, STACK_VARIABLES_ARGS);
}

ALLOCATE_STACK (iattr, &pd);

申请栈地址:

1 分配了栈空间:通过__phtread_attr_setstack()接口设置了栈空间地址,注意栈是向下增长的,此处设置栈顶;

 glibc库中在栈上分配了一个struct pthread结构,用于线程维护:pd = (struct pthread*)stackaddr-__static_tls_size-adj-TLS_PRE_TCB_SIZE;

其中:TLS_PRE_TCB_SIZE=sizeof(struct pthread);

__static_tls_size: tls为线程局部静态变量的大小;adj为tls的对齐量;

TLS--线程局部存储.

GLibc TLS实现

2 未分配占空间:ALLOCATE_STACK (iattr, &pd);通过mmap申请一段内存作为栈;

3.2 接下来介绍一下__pthread_create_2_1->create_thread函数:

static int create_thread (struct pthread *pd, const struct pthread_attr *attr,
        STACK_VARIABLES_PARMS)
{
  /* 创建线程 */
  /* Actually create the thread.  */
  int res = do_clone (pd, attr, clone_flags, start_thread,
        STACK_VARIABLES_ARGS, stopped);
}

3.3 create_thread->do_clone的实现:

static int do_clone (struct pthread *pd, const struct pthread_attr *attr,
   int clone_flags, int (*fct) (void *), STACK_VARIABLES_PARMS,
   int stopped)
{
/* 系统调用__clone->sys_clone;fct=start_thread*/
  int rc = ARCH_CLONE (fct, STACK_VARIABLES_ARGS, clone_flags,
         pd, &pd->tid, TLS_VALUE, &pd->tid);
      /* We have to translate error codes.  */
      return errno == ENOMEM ? EAGAIN : errno;
    }
      /* Set the scheduling parameters.  */
      if ((attr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0)
 { /* 设置线程的调度策略 */
   res = INTERNAL_SYSCALL (sched_setscheduler, err, 3, pd->tid,
      pd->schedpolicy, &pd->schedparam);
   if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (res, err), 0))
     goto err_out;
 }
  return 0;
}

3.4 create_thread->do_clone->__clone:

/* int clone(int (*fn)(void *arg), void *child_stack, int flags, void *arg,
      pid_t *ptid, struct user_desc *tls, pid_t *ctid); 
*/
/*
__clone (fct, STACK_VARIABLES_ARGS, clone_flags,
     pd, &pd->tid, TLS_VALUE, &pd->tid);
1 用户态系统调用之前:r0=start_thread;r1=child_stack;r2=clone_flags
r3=struct pthread *pd ;r4=tid;r5=tls_value;r6=pd->tid
2 用户态系统调用后
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
   int __user *, parent_tidptr,
   int, tls_val,
   int __user *, child_tidptr)
用户态系统调用后,内核中:r0=clone_flags,r1=newsp;r2=parent_tidptr;r3=tls_val;
                 r4=child_tidptr
*/
        .text
ENTRY(__clone)
 @ sanity check args
 cmp r0, #0  /* r0与0比较 r0=start_thread */
 ite ne
 cmpne r1, #0 /*stackaddr !=0*/
 moveq r0, #-EINVAL //if (r0!=0){if(r1==0) r0=-EINVAL}
 beq PLTJMP(syscall_error)
 @ insert the args onto the new stack
  /* 
   将r0=start_thread,r3=pd保存到子进程的栈上。
  */
 str r3, [r1, #-4]! //r1-4=r3=pd;r1=r1-4
 str r0, [r1, #-4]! //r1-4=r0=start_thread;r1=r1-4;此时child_sp=r0;child_sp+4=r3
 @ do the system call
 @ get flags
 mov r0, r2 //r0=clone_flags
#ifdef RESET_PID
 mov ip, r2 //ip=flags
#endif
 @ new sp is already in r1  //r1=newsp=child_stack
/*
入栈:sp=sp-8;[sp]=r4=&pd->tid;[sp+4]=r7;
1 此时sp地址上保存的是pd->tid的地址
*/ 
 push {r4, r7}
 cfi_adjust_cfa_offset (8)
 cfi_rel_offset (r4, 0)
 cfi_rel_offset (r7, 4)
  /*r2=*(sp+8)=parent_tidptr;  入参中对应r4*/
 ldr r2, [sp, #8] ldr r3, [sp, #12]//r3=sp+12=tls_val   入参中对应r5 
  ldr r4, [sp, #16]//r4=sp+16=child_tidptr  入参中对应r6 
  ldr r7, =SYS_ify(clone) //r7=syscall id 
/*
系统调用之前:
r0=clone_flags
r1=child_sp:child_sp=start_thread;child_sp+4=pd
r5=tls_value
r7=syscallId
*/
  swi 0x0 //软中断嵌入内核 
  cfi_endproc 
  cmp r0, #0 //r0=0 fork成功了,当前在子进程中 
  beq 1f  //跳转到start_thread,pthread_create中注册的回调函数。 
  pop {r4, r7} 
  blt PLTJMP(C_SYMBOL_NAME(__syscall_error)) 
  RETINSTR(, lr) //bx lr 
  cfi_startproc
PSEUDO_END (__clone)
1: 
  .fnstart 
  .cantunwind 
  @ pick the function arg and call address off the stack and execute 
  //child_sp+4=r3=pd
  ldr r0, [sp, #4] 
  //执行start_thread ip=sp;sp=sp+8 
  ldr  ip, [sp], #8 
  BLX (ip) 
  @ and we are done, passing the return value through r0
  b PLTJMP(HIDDEN_JUMPTARGET(_exit)) 
  .fnend

【正文三】进程创建函数do_fork。

 首先,无论用户态还是内核态创建线程都会调用到do_fork函数。接下来,我们将分别从用户态和内核态两个方面介绍一下线程创建过程,从中看一下二者区别。

1 用户态创建线程

ptread_create->sys_clone->do_fork
1)sys_clone系统调用: clone_flags与系统里创建线程时不同的。
第一:系统调用时,内核保存用户进程的栈 vector_swi保存用户态进程的上下文信息到栈上:可以参考arm异常处理中对系统调用的介绍。

第二:系统调用返回时,恢复用户进程的栈

ret_fast_syscall:
 UNWIND(.fnstart )
 UNWIND(.cantunwind )
 disable_irq    @ disable interrupts
 ldr r1, [tsk, #TI_FLAGS]
 tst r1, #_TIF_WORK_MASK
 bne fast_work_pending
 asm_trace_hardirqs_on
 /* perform architecture specific actions before user return */
 arch_ret_to_user r1, lr
 ct_user_enter
/*恢复用户态的栈*/
 restore_user_regs fast = 1, offset = S_OFF
 UNWIND(.fnend  )

2)sys_clone->do_fork创建线程,其实内核态创建线程也会调用到此。

long do_fork(unsigned long clone_flags,
       unsigned long stack_start,
       unsigned long stack_size,
       int __user *parent_tidptr,
       int __user *child_tidptr)
{
#if 0 
//用户态进程创建线程时pthread_create:
 {
/*
int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
     | CLONE_SETTLS | CLONE_PARENT_SETTID
     | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
     | 0);
clone_flags=0x3d0f00;stack_start=0x76d8bf98;stack_size=0x0;
parent_tidptr=0x76d8c4c8;child_tidptr=0x76d8c4c8
*/
  printk("clone_flags=0x%x;stack_start=0x%x;stack_size=0x%x;parent_tidptr=0x%x;child_tidptr=0x%x\n",
   clone_flags,stack_start,stack_size,parent_tidptr,child_tidptr);
 }
#endif   
 /* 实际创建线程 */
 p = copy_process(clone_flags, stack_start, stack_size,
    child_tidptr, NULL, trace);
/* 
1 调度到子进程 ,此处并未马上切换到子进程去执行,只是把当前进程标记为可调度,并把新进程加入到调度队列中
                  设置调度标记set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
2 do_work_pending->schedule->context_switch中切换进程
*/
  wake_up_new_task(p);
 return nr;
}

3)do_fork->copy_process介绍:

static struct task_struct *copy_process(unsigned long clone_flags,
     unsigned long stack_start,
     unsigned long stack_size,
     int __user *child_tidptr,
     struct pid *pid,
     int trace)
{
//为新创建的线程分配task_struct和thread_info
 p = dup_task_struct(current);
 p->did_exec = 0; //进程未执行
/* 子进程的时间戳 ;
根据静态优先级确定进程的时间片;
该函数主要初始化以后调度需要的信息;
*/
 sched_fork(p);
//新线程的cpu信息保存到thread->cpu_context,以便进程上下文切换时,新建线程能够运行
 retval = copy_thread(clone_flags, stack_start, stack_size, p);
 if (likely(p->pid)) {
  ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);
  if (thread_group_leader(p)) {
   /* 内核态线程 */
   list_add_tail_rcu(&p->tasks, &init_task.tasks);
   __this_cpu_inc(process_counts);
  } else {
   /* 用户态线程需要添加 */
   list_add_tail_rcu(&p->thread_group,
       &p->group_leader->thread_group);
   list_add_tail_rcu(&p->thread_node,
       &p->signal->thread_head);
  }
  attach_pid(p, PIDTYPE_PID, pid);
  /*sysinfo中可以获取线程数*/
  nr_threads++;
 }
}                              

4)do_fork->copy_process->dup_task_struct分配进程在内核态使用的栈

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
 tsk = alloc_task_struct_node(node);
 if (!tsk)
  return NULL;
/* 申请2 pages大小 */
 ti = alloc_thread_info_node(tsk, node);
 if (!ti)
  goto free_tsk;
/* 子进程继承父进程:*tsk=*org */
 err = arch_dup_task_struct(tsk, orig);
 if (err)
  goto free_ti;
/* 进程task_struct的stack字段保存的是thread_info 
 tsk->stack = sp & ~(8192-1);   |sizeof(thread_info)|stack=8192-其他|sizeof(pt_regs)|8bytes|
thread_info->cpu_context_save是进程上下文切换时,保存被替换进程cpu信息的所在。再切换回该进程,会恢复这些信息到cpu。
*/
 tsk->stack = ti;
}

5)do_fork->copy_process->copy_thread新建进程cpu信息保存到thread->cpu_context,以便进程上下文切换时,新建进程能够运行

int copy_thread(unsigned long clone_flags, unsigned long stack_start,
     unsigned long stk_sz, struct task_struct *p)
{
 struct thread_info *thread = task_thread_info(p);
 struct pt_regs *childregs = task_pt_regs(p);
 memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));
 if (likely(!(p->flags & PF_KTHREAD))) {
/*
1 当前进程子进程的栈中保存当前进程的current_pt_regs
2 子进程的栈地址由用户态入参。
*/  
  *childregs = *current_pt_regs();
  childregs->ARM_r0 = 0;
/* 
保存用户态子进程的栈地址 
其中childregs是用户态进程内核态下的栈地址。
stack_start是用户态进程在用户态的栈地址。
*/
  if (stack_start)
   childregs->ARM_sp = stack_start;
 } else {
//内核态下r5=stack_start=kernel_thread->kthread();
  memset(childregs, 0, sizeof(struct pt_regs));
  thread->cpu_context.r4 = stk_sz;
  thread->cpu_context.r5 = stack_start;
  childregs->ARM_cpsr = SVC_MODE;
 }
/*
1 子进程的pc指针为ret_from_fork ;switch_to-> __switch_to中切换到pc处执行
2 thread_info中的cpu_context 与pt_regs->ARM_pc的区别
cpu_context 进程上下文中cpu的状态。
pt_regs(childregs) 记录了当前cpu的状态。
do_work_pending->schedule->context_switch中切换进程->switch_to中使用
*/
 
 thread->cpu_context.pc = (unsigned long)ret_from_fork;
/*
childregs保存cpu的运行状态,子进程继承父进程的。
但用户态进程的子进程sp保存的是用户的栈
*/
 thread->cpu_context.sp = (unsigned long)childregs;
}                    

2 内核态创建线程

1) kthread_create仅仅是将线程信息添加到kthread_create_list链表

struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
        void *data, int node,
        const char namefmt[],
        ...)
{
 struct kthread_create_info create;
 create.threadfn = threadfn;
 create.data = data;
 create.node = node;
 init_completion(&create.done);
 //添加链表
 spin_lock(&kthread_create_lock);
 list_add_tail(&create.list, &kthread_create_list);
 spin_unlock(&kthread_create_lock);
 wake_up_process(kthreadd_task);
 wait_for_completion(&create.done);
 return create.result;
}

2) kthreadd->create_kthread->kernel_thread->do_fork线程负责根据kthread_create_list
链表上的线程信息,创建线程。kernel_thread中注册的回调函数是khtread();
内核态的线程创建后都会执行这个回调khtread(),这个khtread()中调用用户在kthread_create时注册的回调。
注意:kthread()是内核态创建线程时,系统注册的回调,在它内部继续调用用户注册的回调。
进程创建过程中copy_thread:

thread->cpu_context.pc = (unsigned long)ret_from_fork;
thread->cpu_context.r5 = stack_start;此时stack_start=kthread()
进程切换过程中context_switch()时切换到ret_from_fork->kthread();
用户注册的回调一般是个while循环。所以用户线程挂死时都有如下堆栈:
[<bf3f781c>](USER_thread) from [<c0037178>] (kthread+0xd0/0xe8)?
[<c0037178>] (kthread) from [<c000e910>] (ret_from_fork+0x14/0x24)??

3 进程上下文切换函数

内核线程:__schedule->context_switch->__switch_to->ret_from_fork->kthread()
用户线程:__schedule->context_switch->__switch_to->ret_from_fork->ret_fast_syscall

1)进程调度时机:中断处理完成、系统调用返回时:ret_fast_syscall->do_work_pending->schedule();

static void __sched __schedule(void)
{
/* 
当前处理器上没有可运行的进程 
enqueue_task_fair时进行增加
*/
 if (unlikely(!rq->nr_running))
  idle_balance(cpu, rq);
 put_prev_task(rq, prev);
/*
1 实时进程:rt_sched_class->put_prev_task_rt->pick_next_rt_entity
从rt_prio_array->active上选择下一个进程。
   put_prev_task_rt ->update_curr_rt更新进程的运行时间
2 分时进程:fair_sched_class->put_prev_task_fair
*/ 
 next = pick_next_task(rq);
 clear_tsk_need_resched(prev);
 rq->skip_clock_update = 0;
/* 
当前进程调度到下一进程时
当前进程在此阻塞->switch_to->__switch_to
待再次调度回当前进程时,当前进程从
switch_to开始执行。
注意:bl指令跳转时,lr同时保存返回地址
*/
 context_switch(rq, prev, next); /* unlocks the rq */
}

2)进程上下文切换函数:switch_to .

/* switch_to(prev, next, prev);
完成进程的上下文切换
r0=current;r1当前进程的thread_info;r2下一个进程的thread_info */

ENTRY(__switch_to)
 UNWIND(.fnstart )
 UNWIND(.cantunwind )
 //ip保存当前线程的cpu_context(thread_info->cpu_context)
 add ip, r1, #TI_CPU_SAVE 
 ldr r3, [r2, #TI_TP_VALUE]
//把当前进程的cpu状态保存到栈(thread_info->cpu_context); r0=prev--当前进程;r1=当前进程的thread_info
/*ip=r4;ip=ip-4 ;ip是当前进程thread_info->cpu_context;当前进程(test)的cpu_context->pc中保存lr,当前进程被调度走后
再次切换回来时,这个lr就是当前进程(test)再次执行时的pc地址。
*/
 ARM( stmia ip!, {r4 - sl, fp, sp, lr} ) @ Store most regs on stack
 THUMB( stmia ip!, {r4 - sl, fp}    ) @ Store most regs on stack
//sp寄存器保存当前进程的栈 thread_info->cpu_context->sp=sp;ip=ip+4;
 THUMB( str sp, [ip], #4     )
//当前进程thread_info->cpu_context->pc=lr  lr=context_switch
 THUMB( str lr, [ip], #4     )
 set_tls r3, r4, r5
 mov r5, r0
//r4=下一个进程的thread_info->cpu_context
 add r4, r2, #TI_CPU_SAVE
 ldr r0, =thread_notify_head
 mov r1, #THREAD_NOTIFY_SWITCH
 //thumbee_notifier
 bl atomic_notifier_call_chain
 THUMB( mov ip, r4      )
 mov r0, r5
//把下一个进程的堆栈信息保存到cpu寄存器,进程开始切换
//进程切换到 thread_info->cpu_context->pc中执行; 进程创建过程:cpu_context->pc=ret_from_fork
//r4=[r4];r4=r4+4;r4中是,下一个进程的cpu_context,此处跳转到ret_from_fork
 ARM( ldmia r4, {r4 - sl, fp, sp, pc}  ) @ Load all regs saved previously
 THUMB( ldmia ip!, {r4 - sl, fp}    ) @ Load all regs saved previously
//sp出栈 此处arm模式,所以形如THUMB指令其实都不执行
 THUMB( ldr sp, [ip], #4     )
//进程切换到 thread_info->cpu_context->pc中执行; 进程创建过程,该值=ret_from_fork
 THUMB( ldr pc, [ip]     )
 UNWIND(.fnend  )
ENDPROC(__switch_to)

3) 进程上下文切换函数:__switch_to->ret_from_fork

ENTRY(ret_from_fork)
 bl schedule_tail
 cmp r5, #0
 movne r0, r4  //if(r5!=0)  r0=r4;
 adrne lr, BSYM(1f) //r5!=0  时 lr=lf
/*
1 用户态创建线程时,copy_thread中r5=0,因此跳转到ret_slow_syscall执行。
glibc:ptread_create中用户态子进程的栈 r1=child_stack-8=用户态子进程的回调函数;
示例:用户态线程的栈:
sh[30898]:
__schedule+0x260/0x3a0 --在此切换到下个进程与内核态相同。
schedule_hrtimeout_range_clock+0x44/0x100 --系统调用内核态执行部分
poll_schedule_timeout+0x38/0x50--系统调用内核态执行部分
do_sys_poll+0x298/0x33c--系统调用内核态执行部分
SyS_poll+0x58/0xbc--新进程中系统调用内核态执行部分
ret_fast_syscall+0x0/0x30--
系统调用返回时切换到新线程开始执行,因此大多时候调度栈都是ret_fast_syscall开始,注意此处的系统调用不是sys_poll
而是用户创建线程时的系统调用 sys_clone或者用户切换时的sys_*系统调用。
*/
/* 2 内核态创建线程时:r5=create_kthread->kthread(),其中执行创建线程时注册的回调函数,在copy_thread中赋值。
内核态下stack_start=kernel_thread->kthread();
注意此处kthread只在线程创建时执行一次,所以一般回调函数中都有while循环,否则线程执行一次就会退出。
内核态线程的栈:
kworker/0:1[13534]:
__schedule+0x260/0x3a0  --在此切换到下个进程。
worker_thread+0x344/0x36c --我们注册的回调函数,在系统中的回调函数中调用
kthread+0xa0/0xac   --系统中的线程回调函数
ret_from_fork+0x14/0x3c --调度中下一进程 从此开始执行。
因为新线程的回调kthread只在线程创建时执行一次,因此任何时候调度栈都是ret_from_fork开始
*/
 movne pc, r5  //r5!=0  时 pc=r5 此处会跳转到kthread()函数执行。
1: get_thread_info tsk
 b ret_slow_syscall
ENDPROC(ret_from_fork)

【正文四】进程遍历

 1 遍历系统中所以进程:for_each_process(p);

 2 遍历进程p中所有线程:for_each_thread(p,t);

 3 遍历系统中所有线程:for_each_process_thread(p,t);

 4 用法请参考内核代码,搜索 for_each_process(p);

 

【总结】

本文简单介绍了linux创建进程的过程,其实只是针对主干流程予以介绍,很多知识点都没有涉及。以后文章中涉及到具体的点,可以再具体分析。本文主要目的 是抛砖引玉,希望能带您走进linux进程管理的大门。

有关栈的管理,进程上下文 可以总结如下:

1  task->stack与sp.

进程在fork()创建过程,dup_task_struct()中申请2 pages大小的栈空间,并赋值给task->stack,此处注意task->stack与pt_regs->sp即cpu的sp寄存器之间的关联.sp可以是task->stack上的任何地址,一般在进程的创建过程copy_thread()中将sp指定为childregs,即该进程在内核态里的栈地址,cpu_context->sp=childregs=task_pt_regs()=task->stack+4096*2-8-sizeof(struct pt_regs); 当新创建的进程执行时,switch_to上下文中的sp就加载到了cpu中sp寄存器。

2   进程在用户态陷入内核态之前,用户态sp上保存的信息不变。在从内核态返回时继续使用该sp,或者使用子进程的sp(只有sys_clone系列函数返回时)。用户进程在陷入内核态之后,vector_swi将在陷入内核态之前的cpu关键寄存器信息保存到内核态的栈sp上(注意r0-r12在陷入内核态后,依然保存的是cpu在陷入内核态之前的值,未来得及改变,但sp、spsr、lr已经改变),这个sp就是1中的sp.进程从用户态返回时,ret_from_fork或其他系统调用时,通过restore_user_regs从内核态的栈上恢复cpu信息,即进程在陷入内涵态之前的cpu寄存器信息.回到用户态后,则进程继续使用用户态的栈。

3 进程用户态的栈地址记录在内核态:task_struct->mm->start_stack;中

线程的栈地址和用户进程的栈地址空间虽然不同,但在内核态看来却是相同的,这是因为start_stack在进程启动load_elf_binary中被指定mm->start_stack=bprm->p,而在fork过程不对start_stack做改动,用户创建线程时copy_mm中因为指定了CLONE_VM标志线程的mm沿用父进程的mm,所以所有用户线程在用户态的栈,在内核看来都是其父进程在用户态的栈。start_stack是栈顶地址。

【附录】

进程切换分析(2):TLB处理

init_task{

    .flags = PF_KTHREAD; //内核线程标志,区分用户线程

    .mm = NULL;//内核线程为空 

}

2 获取当前进程的栈:thread_info.h: current_thread_info(void)

ARM64:THREAD_SIZE =16K

3 prctl()用户对线程的控制.

【Linux】进程间同步(进程间互斥锁、文件锁)

进程控制函数(3)-getsid()和setsid()获取当前会话和建立新会话

进程间关系:进程组、作业、会话

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值