mirror of
https://github.com/gatieme/LDD-LinuxDeviceDrivers.git
synced 2026-09-22 04:33:37 +08:00
...
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
Linux下进程的创建过程
|
||||
Linux内核线程、轻量级进程和用户进程以及其创建方式
|
||||
=======
|
||||
|
||||
|
||||
@@ -9,68 +9,65 @@ Linux下进程的创建过程
|
||||
| ------------- |:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|
|
||||
| 2016-05-12 | [Linux-4.5](http://lxr.free-electrons.com/source/?v=4.5) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux进程管理与调度-之-进程的创建](http://blog.csdn.net/gatieme/article/category/6225543) |
|
||||
|
||||
#Linux进程类别
|
||||
-------
|
||||
虽然我们在区分Linux进程, 但是我还是想说Linux下只有一种类型的进程,那就是task_struct, 当然我也想说Linux下其实本质上没有线程的概念, 线程其实上是
|
||||
|
||||
##Linux下进程和线程的区别
|
||||
|
||||
而由于其运行空间的不同,从而演变出内核线程
|
||||
|
||||
##内核线程
|
||||
-------
|
||||
|
||||
内核线程只运行在内核态,不受用户态上下文的拖累。
|
||||
处理器竞争:可以在全系统范围内竞争处理器资源;
|
||||
使用资源:唯一使用的资源是内核栈和上下文切换时保持寄存器的空间
|
||||
调度:调度的开销可能和进程自身差不多昂贵
|
||||
同步效率:资源的同步和数据共享比整个进程的数据同步和共享要低一些。
|
||||
|
||||
|
||||
##轻量级进程
|
||||
-------
|
||||
|
||||
|
||||
http://www.linuxidc.com/Linux/2013-07/87011.htm
|
||||
http://www.linuxeye.com/Linux/1827.html
|
||||
http://bbs.csdn.net/topics/390872515
|
||||
http://blog.csdn.net/yjzl1911/article/details/5613569
|
||||
http://blog.csdn.net/dagouaofei/article/details/5644119
|
||||
http://blog.chinaunix.net/uid-23769728-id-3129443.html
|
||||
http://baike.baidu.com/link?url=sCsQDvMUaAikV5W_eKrEL3RVijNHJtOJk8nsCnjlxtnU7yoJ9svp6cwaerQ6Dqc0I-kdoAYrOtMcocCUnzyggK
|
||||
|
||||
http://blog.chinaunix.net/uid-21718047-id-3070635.html
|
||||
|
||||
http://blog.163.com/boneshunter_1234/blog/static/340762320084472122207/
|
||||
|
||||
http://blog.sina.com.cn/s/blog_626aed8b0100hws6.html
|
||||
|
||||
|
||||
轻量级进程(LWP)是建立在内核之上并由内核支持的用户线程,它是内核线程的高度抽象,每一个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程一样被调度。
|
||||
轻量级进程由clone()系统调用创建,参数是CLONE_VM,即与父进程是共享进程地址空间和系统资源。
|
||||
与普通进程区别:LWP只有一个最小的执行上下文和调度程序所需的统计信息。
|
||||
处理器竞争:因与特定内核线程关联,因此可以在全系统范围内竞争处理器资源
|
||||
使用资源:与父进程共享进程地址空间
|
||||
调度:像普通进程一样调度
|
||||
用户线程
|
||||
用户线程是完全建立在用户空间的线程库,用户线程的创建、调度、同步和销毁全又库函数在用户空间完成,不需要内核的帮助。因此这种线程是极其低消耗和高效的。
|
||||
处理器竞争:单纯的用户线程是建立在用户空间,其对内核是透明的,因此其所属进程单独参与处理器的竞争,而进程的所有线程参与竞争该进程的资源。
|
||||
使用资源:与所属进程共享进程地址空间和系统资源。
|
||||
调度:由在用户空间实现的线程库,在所属进程内进行调度
|
||||
|
||||
#Linux下进程的创建流程
|
||||
|
||||
-------
|
||||
|
||||
|
||||
|
||||
##进程的复制fork和加载execve
|
||||
|
||||
-------
|
||||
|
||||
|
||||
|
||||
我们在Linux下进行进行编程,往往都是通过fork出来一个新的程序,fork从化字面意义上理解就是说"分叉", 这其实就意味着我们的fork进程并不是真正从无到有被创建出来的。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
一个进程,包括代码、数据和分配给进程的资源,它其实是从现有的进程(父进程)复制出的一个副本(子进程),fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,然后如果我们通过execve为子进程加载新的应用程序后,那么新的进程将开始执行新的应用
|
||||
|
||||
|
||||
|
||||
简单来说,<font color = 0x00ffff>新的进程是通过fork和execve创建的,首先通过fork从父进程分叉出一个基本一致的副本,然后通过execve来加载新的应用程序镜像</font>
|
||||
|
||||
|
||||
|
||||
* fork生成当前进程的的一个相同副本,该副本成为子进程
|
||||
|
||||
|
||||
|
||||
> 原进程(父进程)的所有资源都以适当的方法复制给新的进程(子进程)。因此该系统调用之后,原来的进程就有了两个独立的实例,这两个实例的联系包括:同一组打开文件, 同样的工作目录, 进程虚拟空间(内存)中同样的数据(当然两个进程各有一份副本, 也就是说他们的虚拟地址相同, 但是所对应的物理地址不同)等等。
|
||||
|
||||
|
||||
|
||||
* execve从一个可执行的二进制程序镜像加载应用程序, 来代替当前运行的进程
|
||||
|
||||
|
||||
|
||||
> 换句话说, 加载了一个新的应用程序。因此execv并不是创建新进程
|
||||
|
||||
|
||||
|
||||
所以<font color = 0x00ffff>我们在linux要创建一个应用程序的时候,其实执行的操作就是
|
||||
|
||||
|
||||
@@ -130,106 +127,3 @@ do_wp_page()会对这块导致写入异常中断的物理页面进行取消共
|
||||
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值(比如PID)不同。相当于克隆了一个自己。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#0号进程与1号进程
|
||||
|
||||
-------
|
||||
|
||||
|
||||
|
||||
前面我们了解到linux下的进程创建式通过父进程复制自身分叉出的一个副本, 那么我们的系统中就必然存在一个进程是所有进程的祖先,要不然我们从哪里fork分叉出一个个子进程呢,linux下这个进程就是init进程
|
||||
|
||||
|
||||
|
||||
但是问题也来了,我们的祖先进程init进程,是从哪里来的?
|
||||
|
||||
|
||||
|
||||
他总不能也是被分叉来的吧?
|
||||
|
||||
|
||||
|
||||
如果是,那么分叉它的进程是谁,那么它为什么没有成为祖先进程
|
||||
|
||||
|
||||
|
||||
如果不是, 那么好了, 它是怎么创建出来的(真正的从无到有)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<font color=0x009966>Linux下有两个特殊的进程,idel进程($PID = 0$)和init进程($PID = 1$)
|
||||
|
||||
|
||||
|
||||
<font color=#A52A2A>
|
||||
|
||||
* idel进程由系统自动创建, 运行在内核态
|
||||
|
||||
</font>
|
||||
|
||||
idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork()产生的进程。完成加载系统后,演变为进程调度、交换
|
||||
|
||||
|
||||
|
||||
<font color=#A52A2A>
|
||||
|
||||
* init进程由idel创建,
|
||||
|
||||
</font>
|
||||
|
||||
由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程
|
||||
|
||||
Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。
|
||||
|
||||
</font>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。**树根是系统自动构造的(或者说是由内核黑客手动创建的)**,即在内核态下执行的0号进程,它是所有进程的远古先祖。
|
||||
|
||||
|
||||
|
||||
由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号...的若干终端注册进程getty。
|
||||
|
||||
|
||||
|
||||
每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
上述过程可描述为:0号进程->1号内核进程->1号用户进程(init进程)->getty进程->shell进程
|
||||
|
||||
|
||||
|
||||
注意,上述过程描述中提到:1号内核进程调用执行init函数并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。两者容易混淆,区别如下:
|
||||
|
||||
|
||||
|
||||
1. init()函数在内核态运行,是内核代码
|
||||
|
||||
|
||||
|
||||
2. init进程是内核启动并运行的第一个用户进程,运行在用户态下。
|
||||
|
||||
|
||||
|
||||
3. 一号内核进程调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user