注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

蒙奇D小豌豆的博客

蒙奇D小豌豆的学习记录

 
 
 

日志

 
 

linux进程与线程  

2011-08-17 23:40:11|  分类: userspace |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

一.linux进程,轻量级进程,线程

最初的进程定义都包含程序、资源及其执行三部分,其中程序通常指代码,资源在操作系统层面上通常包括内存资源、IO资源、信号处理等部分,而程序的执行通常理解为执行上下文,包括对cpu的占用,后来发展为线程。在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。
      Linux
线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行, 而调度交给内核处理。轻量级进程和普通进程的区别在于:前者没有独立的用户空间(内核态线程无用户空间,用户态线程共享用户空间),而普通进程有独立的内存空间

二.实现

Linux的进程,线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了 三个系统调用__clone()fork()以及vfort(),最终都用不同的参数调用do_fork()核内API do_fork() 提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、 CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。

Linux下不管是多线程编程还是多进程编程,linux通过clone()系统调用实现产生进程或者线程。无论是fork(),还是vfork(),__clone()最后都根据各自需要的参数标志去调用clone().然后有clone()去调用do_fork().这样一说,最终都是用do_fork实现的多进程编程,只是进程创建时的参数不同,从而导致有不同的共享环境。

 

Clone()

clone函数功能强大,带了众多参数。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的剧本", child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值

CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了兄弟而不是父子

CLONE_FS子进程与父进程共享相同的文件系统,包括root、当前目录、umask

CLONE_FILES子进程与父进程共享相同的文件描述符(file descriptor)表

CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy

CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表

CLONE_PTRACE 若父进程被trace,子进程也被trace

CLONE_VFORK父进程被挂起,直至子进程释放虚拟内存资源

CLONE_VM子进程与父进程运行于相同的内存空间

CLONE_PID 子进程在创建时PID与父进程一致

CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

 

fork(),vfork(),_clone(),exec()

1.fork()

fork 创造的子进程复制了父亲进程的资源,子进程是父进程的一个拷贝。具体说,子进程从父进程那得到了数据段和堆栈段,但不是与父进程共享而是单独分配内存. Linux下使用了一种叫做写时拷贝(copy-on-write)页实现。这种技术原理是:内存并不复制整个进程地址空间,而是让父进程和子进程共享同一拷贝,只有在需要写入的时候,数据才会被复制当使用fork系统调用产生多进程时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境。

调用clone时候flag指定为SIGCHLD和清除所有CLONE_XX标志调用clone().然后有clone()去调用do_fork().这样一说,我想大家明白我的意思了,问题的关键纠结于do_fork(),它定义在kernel/fork.c中,完成了大部分工作,该函数调用copy_process()函数,然后让进城开始运行,copy_precess()函数完成的工作很有意思:

1.调用dup_task_struct()为新进程创建一个内核栈,它的定义在kernel/fork.c文件中。该函数调用copy_process()函数。然后让进程开始运行。从函数的名字dup就可知,此时,子进程和父进程的描述符是完全相同的。
2.
检查这个新创建的的子进程后,当前用户所拥有的进程数目没有超过给他分配的资源的限制。
3.
现在,子进程开始使自己与父进程区别开来。进程描述符内的许多成员都要被清0或设为初始值。
4.
接下来,子进程的状态被设置为TASK_UNINTERRUPTIBLE以保证它不会投入运行。
5.
调用copy_flags()以更新task_structflags成员,表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec函数的PF_FORKNOEXEC标志。
6.
调用get_pid()为新进程获取一个有效的PID.
7.
根据传递给clone()的参数标志,拷贝或共享打开的文件,文件系统信息,信号处理函数。进程地址空间和命名空间等。一般情况下,这些资源会被给定进程的所有线程共享;否则,这些资源对每个进程是不同的,因此被拷贝到这里.
8.
让父进程和子进程平分剩余的时间片
9.
最后,作扫尾工作并返回一个指向子进程的指针。

经过上面的操作,再回到do_fork()函数,如果copy_process()函数成功返回。新创建的子进程被唤醒并让其投入运行。内核有意选择子进程先运行。因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销。如果父进程首先执行的话,有可能会开始向地址空间写入

2.vfork()

vfork创建新进程的主要目的在于用exec函数执行另外的程序,实际上,在没调用execexit之前子进程的运行中是与父进程共享数据段的。在vfork调用中,子进程先运行,父进程挂起,直到子进程调用execexit,在这以后,父子进程的执行顺序不再有限制。调用clone时候flag指定为SIGCHLDCLONE_VM, CLONE_VFORK

vfork()不拷贝父进程的页表项。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec(),子进程不能向地址空间写入。按照刚才的方法,分析一下vfork(),它是通过向clone()系统调用传递一个特殊标志来进行的,过程如下:

1.在调用copy_process时,task_structvfor_done成员被设置为NULL
2.
在执行do_fork()时,如果给定特别标志,则vfork_done会指向一个特殊地址。
3.
子进程开始执行后,父进程不是马上恢复执行,而是一直等待,直到子进程通过vfork_done指针向它发送信号。
4.
在调用mm_release()时,该函数用于进程退出内存地址空间,如果vfork_done不为空,会向父进程发送信号。
5.
回到do_fork(),父进程醒来并返回。

 

3.__clone()

当使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由 __clone()传入。我们说在linux中,线程仅仅是一个使用共享资源的轻量级进程。调用clone时候flag指定为CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND

4.__clone()

该函数从一个bin文件加载一个app,它不创建新的进程,只是把当前进程映像替换成新的bin程序文件,并执行它。因为调用e x e c并不创建新进程,所以前后的进程I D并未改变。e x e c只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。


内核线程

内核线程和用户级线程的区别在于内核线程没有独立的地址空间(实际上它的mm指针被设置为NULL).它也可以被调度也可以被抢占。内核线程也只能由其他内核线程创建。方法如下:int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags).新的任务也是通过像普通的clone()系统调用传递特定的flags参数而创建的。上面函数返回时,父进程退出,并返回一个子线程task_struct的指针。子进程开始运行fn指向的函数,arg是运行时需要用到的参数。一个特殊的clone标志CLONE_KERNEL定义了内核线程常用到参数标志:CLONE_FS, CLONE_FILES, CLONE_SIGHAND.大部分的内核线程把这个标志传递给它们的flags参数。


进程标识符:pid

每个进程都有一个标识符叫做pid,通过getpid获得,在多线程编程中,对于内核来说就是一组轻量级进程,因而他们都有各自的pid,但是为了满足posix要求每个线程组必须有相同的进程id,因而getpid返回的不是各个轻量级进程的pid,而是他们的tgid即领头轻量级进程(主线程)的pid

  评论这张
 
阅读(1856)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018