初识进程

一、进程的概念

  • 进程:程序的一次动态执行过程
  • 程序:为了完成特定任务的一系列指令的有序集合
  • 进程和程序的区别:
    • 程序是静态的,进程是动态的
    • 一个程序可以对应多个进程,一个进成只能对应一个程序
    • 程序是由代码和数据组成的,但是进程就比程序更为复杂一点,组成进程的不止有代码和数据除此之外还有堆栈和PCB(进程控制块)
    • 进程的特点:动态性、并发性、独立性、制约性
    • 每一个进程都有自己的地址空间以及运行状态
      想要知道进程的地址空间分布那我们首先来认识一下虚拟内存。

二、虚拟内存

1 .为什么会有虚拟内存的存在?

  • 物理内存的使用效率低,程序运行的地址不确定,进程地址空间不隔离,具有使用空间的限制,因为物理内存的大小只有512M
  • 其实程序中访问的地址并不是物理内存地址而是虚拟内存地址,操作系统会在虚拟内存地址上划分相应的段来处理程序中的资源,然后经过页表映射到物理内存中。不同的程序映射到的物理内存地址是不同的。
  • 当创建一个进程时操作系统会为其分配4G大小的虚拟地址空间,这个4G的地址空间是虚拟的,并不是真实存在
  • 虚拟地址空间被分成了4部分:NULL指针区、用户区、64KB禁入区、内核区。应用程序只能使用用户区。
    虚拟内存的分布图:
    初识进程_第1张图片
    其实我们知道物理内存的大小是比虚拟内存的大小要小的多,那么操作系统是怎样将虚拟内存上的内容映射到物理内存上的?

三、页表

  • 页表:内核需要为每个进程维护一张页表,该页表描述了每页在进程虚拟地址的空间。
  • 虚拟内存划分为4k大小的页,物理内存划分为4k大小的页帧
    虚拟内存和物理内存之间通过页表映射的关系图:
    初识进程_第2张图片
    上图描述的情况是虚拟内存对应的内容在物理内存对应的地址存储着,如果进程预要访问的页面目前并未驻留在物理内中,将会发生页面错误,内核即刻挂起进程的执行,同时从磁盘中将页面载入内存,这就是所谓的缺页中断。
    初识进程_第3张图片

四、进程控制块

进程控制块在内核中是一个叫做task_struct 的结构体对于进程控制块中包含的信息在之前的博客中进行了解析,这里就不再赘述了。

五、进程的执行过程

  • 进程的状态
    进程包括六个状态:
  • 就绪态
  • 阻塞态
  • 可中断睡眠状态
  • 不可中断睡眠状态
  • 僵尸态
  • 暂停态
    初识进程_第4张图片

  • 进程的执行过程:创建–>执行–>等待–>抢占–>唤醒–>结束
    初识进程_第5张图片

    六、使用frok创建进程

pid_t fork(void);//创建一个进程,子进程返回0,父进程中返回大于0的数(实际上就是子进程的pid),当无法创建时返回-1

执行fork之后:

  1. 子进程是父进程的翻版,具体做法:子进程 获得父进程的栈、数据段、堆和执行文本段的拷贝
  2. 父进程和子进程相同的程序文本段,但却各自拥有不同的栈段、数据段和堆段拷贝
  3. 执行fork()之后,每个进程均可修改各自的栈数据以及堆段中的变量,而不影响 另一进程
  4. getpid()可以获取进程的PID,getppid()可以获取进程的父进程的PID
  5. fork()创建的子进程是对父进程的栈、数据段、堆和执行文本的拷贝,但是这样做会浪费内存空间。避免这种浪费有两种方法:
    1. 内核将每一个进程的代码段标记为只读,从而使进程无法修改自身代码,这样子进程和父进程可以共享同一代码段,指向相同的物理内存页帧
    2. 对于父进程数据段、堆段和栈段的各页,内核采用写时复制技术,起初内核将进程的代码段标记为只读,使父进程和子进程指向相同的物理内存,调用fork()后内核会捕获所有子进程和父进程对代码段的修改企图,然后将要修改的代码段拷贝分配给造内核捕获的进程,之后父进程和资金成就可以修改各自的代码段拷贝,不再相互影响。

初识进程_第6张图片

  • 孤儿进程
  • fork创建子进程之后,父子进程是交替运行的,但父进程死亡,子进程编程孤儿进程,由1号(init)进程领养,下面我们来创建一个孤儿进程:
    初识进程_第7张图片
    这里写图片描述
    2.当子进程死亡的时候父进程没有接收到子进程死亡的信号时,子进程会变成僵尸进程
    初识进程_第8张图片
    初识进程_第9张图片
    这里写图片描述
    我们发现僵尸进程是没有办法被kill掉的,所以当僵尸问题过多的时候会导致一下问题:
  • 造成内存资源的浪费
  • 内存泄漏
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,僵尸状态一直不退出,PCB一直都要维护。
    要想回收僵尸子进程,那么我们就得使用wait这个函数
pid_t wait(int *status);//返回值:被回收的子进程的ID

系统调用wait()时会执行如下动作:

  1. 如果调用进程并无之前未被等待的子进程终止,调用将一直阻塞,直至某个子进程终止。如果调用时已有子进程终止,wait()即刻返回。
  2. 如果status非空,那么关于子进程如何终止的信息则会通过status指向的整型变量返回
  3. 内核将会为父进程下所有子进程的运行总量追加进程CPU时间以及资源使用数据。
  4. 将终止子进程的PID作为wait()的结果返回。
    代码示例:
    初识进程_第10张图片
    运行结果:
    这里写图片描述
    当查看进程信息的时候发现僵尸子进程被回收,只剩下父进程
    这里写图片描述
    除了wait()函数之外还有一个函数可以回收僵尸子进程那就是waitpid()
pid_t   waitpid(pid_t pid,int *status,int options);//等待相应pid的子进程死亡

参数:
- pid > 0:等待pid子进程死亡
- pid==0:等待本进程组的任何一个子进程死亡
- pid==-1:等待当前进程任何一个子进程死亡
- pid < -1: |pid|进程组的任何一个子进程死亡
- options:options是一个位掩码,可以包含0个或者多个标志,一般写0
返回值:如果有子进程死亡,就回收返回子进程ID,否则就返回0

你可能感兴趣的:(进程概念,孤儿进程,僵尸进程,进程的执行过程,进程的状态)