linux系统之进程的创建与上下文切换
2017-04-30 02:22
183 查看
【摘要】
【写作原因】
【正文一】用户创建线程
【正文二】 glbic中代码分析
【正文三】进程创建函数do_fork
【正文四】进程遍历
【总结】
[b]注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)
[/b]
【摘要】
本文将从用户、glbic库和linux 内核几个角度分析一下进程的创建过程。
【写作原因】
希望能通过几篇博文对进程的创建、执行、调度几个过程做一个详细阐述。
其中:1)实时进程的调度原理请参考博文:点击打开链接;
2)普通进程的调度原理请参考博文:点击打开链接;
【正文一】用户创建线程。
用户态创建线程的示例:
2 用户到内核的线程创建过程:pthread_create->__pthread_create_2_1->create_thread->do_clone->__clone->do_fork:标红部分为glibc中关键的实现函数:3 代码实现: 3.1 入口函数__pthread_create_2_1,此处只列出了该函数的关键几个实现点:
首先,无论用户态还是内核态创建线程都会调用到do_fork函数。接下来,我们将分别从用户态和内核态两个方面介绍一下线程创建过程,从中看一下二者区别。1 用户态创建线程ptread_create->sys_clone->do_fork
1)sys_clone系统调用: clone_flags与系统里创建线程时不同的。
第一:系统调用时,内核保存用户进程的栈 vector_swi保存用户态进程的上下文信息到栈上:可以参考arm异常处理中对系统调用的介绍。第二:系统调用返回时,恢复用户进程的栈
链表上的线程信息,创建线程。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_syscall1)进程调度时机:中断处理完成、系统调用返回时:ret_fast_syscall->do_work_pending->schedule();
完成进程的上下文切换
r0=current;r1当前进程的thread_info;r2下一个进程的thread_info */
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进程管理的大门。
【附录】
进程切换分析(2):TLB处理
【写作原因】
【正文一】用户创建线程
【正文二】 glbic中代码分析
【正文三】进程创建函数do_fork
【正文四】进程遍历
【总结】
[b]注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)
[/b]
【摘要】
本文将从用户、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); }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;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) 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_syscall1)进程调度时机:中断处理完成、系统调用返回时: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进程管理的大门。
【附录】
进程切换分析(2):TLB处理
相关文章推荐
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- linux中关于创建子进程系统堆栈的分析
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- 简单掌握Linux系统中fork()函数创建子进程的用法
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- linux系统中进程的创建
- 用C语言在Linux系统下创建守护进程(Daemon)
- 课程设计——LINUX系统下多进程的创建与通信
- shell监控linux系统进程创建脚本分享
- Linux系统创建一个新的进程
- 八、Linux系统编程-进程(一)进程概念、进程数据结构、进程状态变迁、进程创建和撤销
- [Linux]C语言Linux系统编程创建进程
- linux中关于创建子进程系统堆栈的分析
- Linux系统学习(进程)——1.进程的创建
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
- 《LINUX内核分析》第六周作业:Linux系统如何创建一个新进程
- Linux系统编程之--守护进程的创建和详解【转】
- 《Linux内核分析》(六)——Linux系统进程创建
- Linux 系统调用之 fork()——进程的创建
- shell监控linux系统进程创建脚本分享