操作系统:进程创建/终止/实现/线程

一、进程创建

四种原因导致创建进程:

1、系统初始化

2、执行了从事创建进程的一个系统调用,该系统调用被正在运行的进程所调用

3、用户请求创建一个进程

4、一个批处理作业的初始化

从技术上看,这些情形中,新进程都是由于一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。这个已经存在的进程可以是一个运行的用户进程,一个系统进程或者一个批处理管理进程。

在UNIX中,只能有一个系统调用创建新进程,fork。这个系统调用会创建一个与调用进程相同的副本。在调用了fork后,这两个进程(父进程和子进程)拥有相同的存储映像,同样的环境字符串和同样的打开文件。

子进程接着指向execve或者一个类似的系统调用,已修改其存储映像并运行一个新的程序。例如,当一个用户在shell中键入命令sort时,shell就创建一个子进程,然后子进程指向sort。

进程创建之后,父进程和子进程都有各自不同的地址空间。如果其中某个进程在地址空间修改了一个字,这个修改对其他进程而言是不可见的。在UNIX中,子进程的初始地址空间时父进程的一个副本,但是涉及两个不同的地址空间。

二、进程的终止

终止方式:

1、正常退出:完成给定程序,编译器执行系统调用,通知其退出,UNIX中的exit

2、出错退出:如用户输入的命令不存在

3、严重错误:如程序执行非法指令,整数除零等

4、被其他线程杀死:某个进程执行系统调用杀死其他线程,如UNIX的 kill

三、进程的实现

3.1 进程结构

UNIX的进程将存储空间划分为三段:

正文段:程序代码

数据段:已初始化的全局变量、静态变量(全局和局部)、常量数据

堆栈段存放临时数据、如函数参数、返回地址和局部变量,运行过程中实时分配和释放,栈区由操作系统自动管理。存放动态分配的内存区域,需要自己手动释放

操作系统维护着一张表格,即进程表。每个进程占用一个进程表项(进程控制块)

进程控制块(PCB)主要字段信息:

3.2 栈的作用

1,调用现场的保护:假设函数A调用函数B,一旦程序执行进入函数B中,当函数B执行结束后,我们肯定需要执行流继续从函数A调用现场(callsite)的下一条语句继续执行函数A。当然,各种应用程序在操作系统上也是这样运行,否则的话,程序不就飞了?这不符合操作系统有始有终的性格。这一点特别像递归函数,虽然一层层嵌套,但是最终还是需要一层层返回的,即从哪个函数开始,就从哪个函数结束。
      所以,当函数A准备调用函数B时,需要先把A中调用语句的下一条语句保存起来,通常都是保存到栈里面,这样当函数B返回后,将之前压入栈中的待执行语句从栈中弹出,然后执行流从这条语句接着执行,即函数A继续执行。
      2,关于栈的增长方向:栈是从高地址向低地址方向增长的。即栈底处于高地址,栈顶处于低地址,每次入栈需要将栈指针减小,每次入栈需要将栈指针增加。这点可以查看Ubuntu系统中的程序运行时空间布局得知。在Windows系统中也是一样,通过相关的程序分析工具可以直观的看到。
      3,参数和返回值:函数调用在通常情况下都是需要传递参数和返回值的。系统API大都是采用_stdcall调用约定,函数入口参数按从右到左的顺序入栈,由被调用者清理栈中的参数,返回值放在eax寄存器中。而C代码中的子程序采用的是C调用约定,函数入口参数按从右到左的顺序入栈,由调用者清理栈中的参数。
      4,ebp和esp:在x86指令集中,ebp寄存器为栈帧寄存器,用来保存每一个函数的栈底位置(内存地址);esp寄存器为栈顶寄存器,用来保存每一个函数当前的栈顶位置。eip表示当前程序执行的指令地址。

3.3 用户空间和内核空间 

进程栈是属于用户态栈,和进程虚拟地址空间密切相关。那我们先了解下什么是虚拟地址空间:在 32 位机器下,虚拟地址空间大小为 4G。这些虚拟地址通过页表映射到物理内存,页表由操作系统维护,并被处理器的内存管理单元 (MMU) 硬件引用。每个进程都拥有一套属于它自己的页表,因此对于每个进程而言都好像独享了整个虚拟地址空间。

Linux 内核将这 4G 字节的空间分为两部分,将最高的 1G 字节(0xC0000000-0xFFFFFFFF)供内核使用,称为内核空间。而将较低的3G字节(0x00000000-0xBFFFFFFF)供各个进程使用,称为用户空间。每个进程可以通过系统调用陷入内核态,因此内核空间是由所有进程共享的。虽然说内核和用户态进程占用了这么大地址空间,但是并不意味它们使用了这么多物理内存,仅表示它可以支配这么大的地址空间。它们是根据需要,将物理内存映射到虚拟地址空间中使用。

四、线程

4.1 线程和进程的关系

进程拥有一个执行的线程,在线程中有一个程序计数器用来记录接着要执行哪一条指令。线程拥有寄存器,来保存线程当前的工作变量。线程还拥有一个栈,用来记录执行历史,其中每一帧保存了一个已调用的但是还没有从中返回的过程(过程的局部变量,调用完成的返回地址等)。每个线程都有自己的堆栈

尽管线程必须要在某个进程中执行,但是和进程是不同的概念。进程用于把资源集中到一起(资源管理单位),而线程则是CPU上被调度执行的实体。线程试图实现的是,共享一组资源的多个线程的执行能力,以便于这些线程可以为完成某一个任务而共同努力。

同一个进程环境中,允许多个线程同时执行。在同一个进程中并行运行多个线程,是对在同一台计算机上并行运行多个进程的模拟。

多个线程共享同一个地址空间,打开的文件以及其他资源,而多个进程共享物理内存,磁盘,打印机和其他资源。

  • 每个进程中的内容:地址空间、全局变量、打开文件、子进程、即将发生的警报、信号与信号处理程序、账户信息等(对进程中的线程可见)
  • 每个线程中的内容:程序计数器、寄存器、堆栈、状态

进程中的不同线程不像不同进程之间存在很大的独立性,所有的线程都有完全一样的地址空间,这意味着它们也会共享相同的全局变量、打开文件集、子进程、报警以及相关信号等。虽然一个线程可以创建新的线程,但线程之间是平等的。

4.2 线程栈和线程内核栈

在每一个进程的生命周期中,必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后,这些内核代码所使用的栈并不是原先进程用户空间中的栈,而是一个单独内核空间的栈,这个称作进程内核栈。进程内核栈在进程创建的时候,通过 slab 分配器从 thread_info_cache 缓存池中分配出来,其大小为 THREAD_SIZE,一般来说是一个页大小 4K;
 

4.2 线程的使用

为什么需要线程?

1、实现多个并行实体共享同一个地址空间和所有可用数据的能力

2、线程附带的资源并不多,创建、撤销更容易

3、若多个线程都是CPU密集型的,那么并不能获得性能上的增强(CPU已经很忙了),但如果存在大量I/O处理(CPU很闲,可以执行多个线程),拥有多个线程允许这些活动彼此重叠进行

4.3 用户空间实现线程

把整个线程包放在用户空间,内核对线程包一无所知。从内核的角度,就是按正常的方式管理,即单线程进程。这种方法的优点是用户级线程包可以在不支持线程操作的操作系统上实现。

在用户空间管理线程时,每个进程需要有专用的线程表,来跟踪进程中的线程。这些表和内核中的进程表类似,不过它仅仅记录各个线程的属性,包括每个线程的程序计数器、堆栈指针、寄存器和状态等。该线程表有运行时系统管理。当一个线程转换到就绪状态或者阻塞状态时,在该线程表中存放重新启动该线程所需的信息,与内核在进程表中存放进程的信息完全一样。

4.3.1 用户级线程切换

当某个线程做了一些会引起本地阻塞的事情后,它调用一个运行时系统的过程,这个过程检查该线程是否必须进入阻塞状态。如果是,它在线程表中保存该线程的寄存器,查看表中可运行的就绪线程(本地的线程调度程序),并把新线程的保存值重新装入机器的寄存器。只要堆栈指针和程序计数器一被切换,新的线程就自动投入运行。整个线程的切换可以在几条指令内完成,进行类似这样的线程切换比陷入内核要快一个数量级(不需要陷入内核,不需要上下文切换),这时使用用户级线程的极大优点。

此外,用户级线程允许每个进程有自己定制的调度算法。

4.3.2 用户级线程的问题

1、一个用户级线程的阻塞将会引起整个进程的阻塞。

如果某个线程引起页面故障,内核由于不知道有线程存在,通常会把整个进程阻塞,直到磁盘IO完成为止,尽管这个进程的其他线程是可以允许的。

2、用户级线程不能利用系统的多重处理,仅有一个用户级线程可以被执行

在多线程Web服务器中,有某个用户级线程一旦进行系统调用,进入内核态,如果线程阻塞(如read系统调用),内核是很难进行线程切换的。

4.4 内核中实现线程

内核态是操作系统所处的一种状态,可以访问更多的内存空间和设备。

如果使用内核级线程,内核调度的单位是线程,线程既有用户态又有内核态(操作系统内核状态下能感知到线程的存在),当线程发生系统调用时,线程会从用户态进入内核态

每个进程中没有线程表了,在内核中有用来记录系统中所有线程的线程表(内核态下,CPU可以调度线程了)。内核中的线程表保存了每个线程的寄存器、状态、和其他信息。当某个线程希望创建一个新线程或撤销一个已有线程时,它进行一个系统调用来实现。

当一个内核线程阻塞时,内核状态下,CPU选择运行同一个进程中的另一个就绪线程,或者允许运行另一个进程的线程。而在用户级线程中,系统始终运行自己进程中的线程,直到它的CPU时间片用完为止。

如果某个进程的线程引起页面故障,内核可以很方便的检查该进程是否有其他可运行的线程,如果有,在等待所需要的页面从磁盘读入时,就选择一个可运行的线程允许。

缺点:在内核创建和撤销线程代价比较大。

4.5 混合实现

以后再说

5、用户态和内核态

内核态:CPU可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,CPU也可以进程切换。

用户态:只能受限的访问内存,且不允许访问外围设备,占用CPU的能力被剥夺,CPU资源可以被其他程序获取。

为什么要有用户态和内核态?

由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 -- 用户态和内核态。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值