进程是具有独立地址空间的;线程没有独立地址空间,一种说法是内核里面只有线程,因为内核的地址空间是共享的(其实操作系统里面很多实现没有非黑即白的概念)。
有两篇文章写得很好
http://blog.csdn.net/u012927281/article/details/51602898
http://www.2ndmoon.net/weblog/?p=603
关于进程的子进程的补充:
parent、sibling、children都是task_struct中的成员,其中parent是task_struct指针,sibling和children都是list_head结构体。
有趣是因为,除了parent比较容易理解之外,另外两个都“不太正常”。
- sibling.next指向进程的下一个兄弟进程的进程描述符sibling成员,若其后没有其他兄弟进程,则指向父进程;而sibling.prev指向进程的上一个兄弟进程,若其之前没有兄弟进程,则指向父进程。
- children.next指向父进程的第一个子进程的sibling成员(而不是children成员!),而children.prev却指向父进程的最后一个子进程的sibling成员。
创建子进程
do_fork
=>p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL, trace);
=>p = dup_task_struct(current);
=>ti = alloc_thread_info_node(tsk, node);
=>err = arch_dup_task_struct(tsk, orig);
=>setup_thread_stack(tsk, orig);//8K内核堆栈区
=>sched_fork(p);
=>p->state = TASK_RUNNING;//子进程为就绪态
=>pid = alloc_pid(p->nsproxy->pid_ns);//申请pid
=>pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
=>nr = alloc_pidmap(tmp);
=>hlist_add_head_rcu(&upid->pid_chain,
&pid_hash[pid_hashfn(upid->nr, upid->ns)]);
=>retval = copy_thread(clone_flags, stack_start, stack_size, p, regs);
=>wake_up_new_task(p);//将新的子进程加入就绪队列
=>rq = __task_rq_lock(p);
=>activate_task(rq, p, 0);
=>enqueue_task(rq, p, flags);
=>p->sched_class->enqueue_task(rq, p, flags);//进入就绪队列
创建内核线程
//powerpc的做法
kernel_thread
=>mr r30,r3 /* function *///压栈,系统调用sys_clone
mr r31,r4 /* argument */
ori r3,r5,CLONE_VM /* flags */
oris r3,r3,CLONE_UNTRACED>>16
li r4,0 /* new sp (unused) */
li r0,__NR_clone
sc
=>sys_clone
=>do_fork(clone_flags, usp, regs, 0, parent_tidp, child_tidp);
//X86的做法
kernel_thread
=>regs.orig_ax = -1;
regs.ip = (unsigned long) kernel_thread_helper;
regs.cs = __KERNEL_CS | get_kernel_rpl();
regs.flags = X86_EFLAGS_IF | 0x2;
=>do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
关于switch_to
schedule
=>preempt_disable();
=>cpu = smp_processor_id();
rq = cpu_rq(cpu);
=>prev = rq->curr;
=>next = pick_next_task(rq);//按照策略算法选择下一个要切换的进程
=>context_switch(rq, prev, next);//切换
=>switch_to(prev, next, prev);
=>(last) = __switch_to((prev), (next))
=>new_thread = &new->thread;
old_thread = ¤t->thread;
=>last = _switch(old_thread, new_thread);
=>stw r1,KSP(r3) /* Set old stack pointer */
=>lwz r1,KSP(r4) /* Load new stack pointer */
=>lwz r4,_NIP(r1) /* Return to _switch caller in new task */
=>DEFINE(_NIP, STACK_FRAME_OVERHEAD+offsetof(struct pt_regs, nip));
=>#define DEFINE(sym, val) \
asm volatile("\n->" #sym " %0 " #val : : "i" (val))
thread_struct {
...
struct pt_regs *regs;
...
}
struct pt_regs {
...
unsigned long nip;
unsigned long msr;
...
}
mtlr r4
addi r1,r1,INT_FRAME_SIZE
blr
=>preempt_enable_no_resched();
copy_thread初始化子进程的内核栈,和新进程的正文段在linux的核心空间,与其它核心进程以及linux内核共享相同的核心正文段。
进程切换只发生在内核态,在执行进程切换之前,用户态进程使用的所有寄存器内容都已经保存在内核态堆栈上,包括用户态进程的SS和ESP寄存器。
Linux系统存在两类用户进程,用户进程和用户线程。严格意义上说,只有拥有了进程描述符,PID,进程正文端,用户进程的数据段和栈段,核心栈段的进程才被称为用户进程,二没有独立数据段和栈段的进程被称为线程。
LINUX系统中,进程和线程都有自己的进程描述符与核心栈段。其中,核心栈段用来支持用户进程或线程在LINUX核心中执行。
关于switch_to,下面这篇文章讲得挺好
linux内核——进程切换宏switch_to
http://www.cnblogs.com/ISeeIC/p/3625556.html
进程状态可以通过show_state_filter函数查看
参考如下博客:
task_struct与进程关系
http://blog.csdn.net/seayoungsjtu/article/details/51077840
关于thread_info结构
Linux进程内核栈与thread_info结构详解–Linux进程的管理与调度(九)
http://blog.csdn.net/gatieme/article/details/51577479
linux内核分析笔记----进程管理
http://www.cnblogs.com/hanyan225/archive/2011/07/09/2101962.html