进程基础

1、进程的概念
  我们编译的代码可执⾏⽂件只是储存在硬盘的静态⽂件,运⾏时被加载到内存,CPU执⾏内存中指令,这个运⾏的程序被称为进程。
  进程是对运⾏时程序的封装,操作系统进⾏资源调度和分配的基本单位。
2、进程的实现
  中断发⽣后操作系统底层的⼯作步骤
  1. 硬件压⼊堆栈程序计数器等
  2. 硬件从中断向量装⼊新的程序计数器
  3. 汇编语⾔过程保存寄存器值
  4. 汇编语⾔过程设置新的堆栈
  5. C中断服务例程运⾏(典型地读和缓冲输⼊)
  6. 调度程序决定下⼀个将运⾏的进程
  7. C过程返回⾄汇编代码
  8. 汇编语⾔过程开始运⾏新的当前进程
进程表:
  为了实现进程模型,操作系统维护着⼀张表格(⼀个结构数组),即进程表。
  每个进程占有⼀个进程表项。(有些著作称这些为进程控制块)
  该表项包含了⼀个进程状态的重要信息
  包括程序计数器、堆栈指针、内存分配状况、所打开⽂件的状态、账号的调度信息,以及其他在进程由运⾏态转换
  到就绪态或阻塞态时必须保存的信息,从⽽保证该进程随后能再次启动,就像从未中断过⼀样
3、并发与并⾏
  (1)单个核⼼在很短时间内分别执⾏多个进程,称为并发
  (2)多个核⼼同时执⾏多个进程称为并⾏
  (3)对于并发来说,CPU需要从⼀个进程切换到另⼀个进程,这个过程需要保存进程的状态信息
4、进程的状态
  某个进程在某个时刻所处的状态分为以下⼏种,运⾏态、就绪态、阻塞态。对于阻塞状态;
⽐如read系统调⽤阻塞,进程会占⽤内存空间,这是⼀种浪费⾏为,于是操作系统会有跟内存管理中物理页置换到
磁盘⼀样的⾏为,把阻塞的进程置换到磁盘中,此时进程未占⽤物理内存,我们称之为挂起;
挂起不仅仅可能是物理内存不⾜,⽐如sleep系统调⽤过着⽤户执⾏Ctrl+Z也可能导致挂起。
  除了创建和结束⼀般有三个状态:
    运⾏态: 该时刻进程占⽤CPU
    就绪态: 可运⾏,由于其他进程处于运⾏状态⽽暂时停⽌运⾏
    阻塞态: 该进程正在等待某⼀事件发⽣(如等待输⼊/输出操作的完成)⽽暂时停⽌运⾏
阻塞态的进程占⽤着物理内存,在虚拟内存管理的操作系统中,通常会把阻塞态的进程的物理内存空间换出到硬
盘,等需要再次运⾏的时候,再从硬盘换⼊到物理内存。
    挂起态:新的状态,描述进程没有占⽤实际的物理内存空间的情况,这个状态就是挂起状态
    阻塞挂起状态: 进程在外存(硬盘)并等待某个事件的出现
    就绪挂起状态: 进程在外存(硬盘),但只要进⼊内存,马上运⾏ 特点:
  1. 就绪态和运⾏态可以相互转换,其它的都是单向转换。就绪态的进程通过调度算法从⽽获得CPU 时间,转为运⾏状态;
  2. 运⾏态的进程,在分配给它的 CPU 时间⽚⽤完之后就会转为就绪状态,等待下⼀次调度。
  3. 阻塞态是缺少需要的资源从⽽由运⾏态转换⽽来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运⾏态转换为就绪态。
5、进程控制块(PCB)
  操作系统对进程的感知,是通过进程控制块PCB数据结构来描述的。它是进程存在的唯⼀标识,其包括以下信息:
  1. 进程描述信息: 进程标识符、⽤户标识符等;
  2. 进程控制和管理信息: 进程状态,进程优先级等;
  3. 进程资源分配清单: 虚拟内存地址空间信息,打开⽂件列表,IO设备信息等;
  4. CPU相关信息: 当进程切换时,CPU寄存器的值都被保存在相应PCB中,以便CPU重新执⾏该进程时能从断点
处继续执⾏;
  PCB通过链表形式组织起来,⽐如有就绪队列、阻塞队列等,⽅便增删,⽅便进程管理。
6、进程状态的切换
  进程的状态分类:
  1. 就绪状态(ready):等待被调度
  2. 运⾏状态(running)
  3. 阻塞状态(waiting):等待资源
  转换关系:
  只有就绪态和运⾏态可以互相转换,其他都是单向转换。
  就绪态的进程通过调度算法从⽽获得CPU时间,转为运⾏状态;⽽运⾏状态的进程,在分配给它的CPU时间⽚完之
后就会转为就绪状态,等待下⼀次调度。
  进程因为等待资源⽽阻塞,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运⾏态转换为就绪态。
  当进程等待的外部事件发⽣时(如⼀些输⼊到达),则又阻塞态转换为就绪态,如果此时没有其他进程运⾏,变转
换为运⾏态,否则该进程将处于就绪态,等待CPU空闲轮到它运⾏。
7、进程切换为何⽐线程慢
  涉及到虚拟内存的问题,进程切换涉及虚拟地址空间的切换⽽线程不会。
因为每个进程都有⾃⼰的虚拟地址空间,⽽线程是共享所在进程的虚拟地址空间的,所以同⼀个进程中的线程进⾏
线程切换时不涉及虚拟地址空间的转换。
  把虚拟地址转换为物理地址需要查找页表,页表查找是⼀个很慢的过程(⾄少访问2次内存),因此通常使⽤Cache
来缓存常⽤的地址映射,这样可以加速页表查找,这个cache就是TLB(快表)。
  由于每个进程都有⾃⼰的虚拟地址空间,那么显然每个进程都有⾃⼰的页表,那么当进程切换后页表也要进⾏切
换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就
是程序运⾏会变慢,⽽线程切换则不会导致TLB失效,因为线程线程⽆需切换地址空间,这也就是进程切换要⽐同
进程下线程切换慢的原因。
10、守护进程
  守护进程是指在后台运⾏的,没有控制终端与它相连的进程。它独⽴于控制终端,周期性地执⾏某种任务。
Linux的⼤多数服务器就是⽤守护进程的⽅式实现的,如web服务器进程http等。
  创建守护进程要点:
  (1)让程序在后台执⾏。
⽅法是调⽤fork()产⽣⼀个⼦进程,然后使⽗进程退出。
  (2)调⽤setsid()创建⼀个新对话期。
守护进程需要摆脱⽗进程的影响,⽅法是调⽤setsid()使进程成为⼀个会话组长。setsid()调⽤成功后,进程成为新的
会话组长和进程组长,并与原来的登录会话、进程组和控制终端脱离。
  (3)禁⽌进程重新打开控制终端。
经过1和2,进程已经成为⼀个⽆终端的会话组长,但是它可以重新申请打开⼀个终端。为了避免这种情况发⽣,可
以通过使进程不再是会话组长来实现。再⼀次通过fork()创建新的⼦进程,使调⽤fork的进程退出。
  (4)关闭不再需要的⽂件描述符。
⼦进程从⽗进程继承打开的⽂件描述符。如不关闭,将会浪费系统资源,造成进程所在的⽂件系统⽆法卸下以及引
起⽆法预料的错误。⾸先获得最⾼⽂件描述符值,然后⽤⼀个循环程序,关闭0到最⾼⽂件描述符值的所有⽂件描
述符。
  (5)将当前⽬录更改为根⽬录。
  (6)⼦进程从⽗进程继承的⽂件创建屏蔽字可能会拒绝某些许可权。
为防⽌这⼀点,使⽤unmask(0)将屏蔽字清零。
  (7)处理SIGCHLD信号。
对于服务器进程,在请求到来时往往⽣成⼦进程处理请求。如果⼦进程等待⽗进程捕获状态,则⼦进程将成为僵⼫
进程(zombie),从⽽占⽤系统资源。如果⽗进程等待⼦进程结束,将增加⽗进程的负担,影响服务器进程的并发
性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。这样,⼦进程结束时不会产⽣僵⼫进程。
11、僵⼫进程多进程程序
  ⽗进程⼀般需要跟踪⼦进程的退出状态,当⼦进程退出,⽗进程在运⾏,⼦进程必须等到⽗进程捕获
到了⼦进程的退出状态才真正结束。在⼦进程结束后,⽗进程读取状态前,此时⼦进程为僵⼫进程。
  设置僵⼫进程的⽬的是维护⼦进程的信息,以便⽗进程在以后某个时候获取。这些信息⾄少包括进程ID,进程的终
⽌状态,以及该进程使⽤的CPU时间。所以当终⽌⼦进程的⽗进程调⽤wait或waitpid时就可以得到这些信息。
  但是⼦进程停⽌在僵⼫态会占据内核资源,所以需要避免僵⼫进程的产⽣或⽴即结束⼦进程的僵⼫态。
  1. ⽗进程调⽤wait/waitpid等函数等待⼦进程结束,如果尚⽆⼦进程退出wait会导致⽗进程阻塞。waitpid只会等
待由pid参数指定的⼦进程,同时也是⾮阻塞,⽬标进程正常退出返回⼦进程PID,还没结束返回0。
  2. 在事件已经发⽣情况下执⾏⾮阻塞调⽤可以提⾼程序效率。对waitpid,最好在⼦进程退出后调⽤。使⽤
SIGCHLD信号通知⽗进程,⼦进程结束。
  ⽗进程中捕获信号,然后在信号处理函数中调⽤waitpid以彻底结束⼦进程
  1. 通过signal(SIGCHLD, SIG_IGN)通知内核对⼦进程的结束不关⼼,由内核回收。如果不想让⽗进程挂起,可以
在⽗进程中加⼊⼀条语句:signal(SIGCHLD,SIG_IGN);表⽰⽗进程忽略SIGCHLD信号,该信号是⼦进程退出的
时候向⽗进程发送的。
  2. 忽略SIGCHLD信号,这常⽤于并发服务器的性能的⼀个技巧因为并发服务器常常fork很多⼦进程,⼦进程终结
之后需要服务器进程去wait清理资源。如果将此信号的处理⽅式设为忽略,可让内核把僵⼫⼦进程转交给init进
程去处理,省去了⼤量僵⼫进程占⽤系统资源。
12、多进程
  进程结构由以下⼏个部分组成:代码段、堆栈段、数据段。代码段是静态的⼆进制代码,多个程序可以共享。
⽗进程创建⼦进程之后,⽗、⼦进程除了pid外,⼏乎所有的部分⼏乎⼀样。
  ⽗、⼦进程共享全部数据,⼦进程在写数据时会使⽤写时复制技术将公共的数据重新拷贝⼀份,之后在拷贝出的数
据上进⾏操作;不是对同⼀块数据进⾏操作;
  如果⼦进程想要运⾏⾃⼰的代码段,还可以通过调⽤execv()函数重新加载新的代码段,之后就和⽗进程独⽴开了。
posted @ 2023-08-28 10:16  好像流沙  阅读(14)  评论(0编辑  收藏  举报