> 文档中心 > 掌握进程,此文足矣(详细教程)

掌握进程,此文足矣(详细教程)

目录

1.什么是进程

2.操作系统是如何管理进程的?

3.进程标识符(PID)

3.1查看进程标识符      

3.2getpid() 和getppid()

4.进程当前状态

4.1进程状态粗分

4.2Linux下进程状态

4.2.1linux下进程状态概念   

4.2.2僵尸状态模拟 

5.程序计数器

6.上下文信息

7.内存指针

8.记账信息

9.IO信息

10.总结


1.什么是进程?

        进程是程序的一次动态执行过程。我们写的源码是一个文件,和文本文件一样都是存储在磁盘上的。未编译之前,它就是文本串。在编译运行后,他就变成一个可执行程序。这个程序跑起来之后就变成了进程。

2.操作系统是如何管理进程的?

        操作系统使用进程控制块(PCB)记录进程信息,然后由双向链表将一个个进程控制块组织起来。每个进程都有属于自己的PCB,而PCB实际上就是一个结构体,其中包含许多成员变量,这些成员变量记录着进程的各种信息。

 以上为Linux中PCB具体组织方式。

    上图为struct task_struct 结构体中的具体成员变量,下文将详细解释每个部分的含义。

3.进程标识符(PID)

3.1查看进程标识符      

        每个进程都有唯一的进程标识符,它能唯一的标识一台计算机的所有进程。进程标识符用PID标识,进程的父进程标识符用PPID表示。

        在一台计算机中,进程标识符不是固定的,但它是唯一的。在Linux下,我们可以使用ps aux命令查看进程信息,当然也包括进程号。也可以使用ps -ef查看父进程标识符。

3.2getpid() 和getppid()

在进程中我们也可以通过函数返回进程号和父进程号,比如我们需要通过进程号操作进程时,我们就可以通过getpid()和getppid()函数。

getpid()和getppid()函数返回值类型是pid_t,功能是谁调用返回谁的PID和PPID,这两个函数是系统调用,但是在使用是需要加头文件unistd.h。

#include    #include int main(){      printf("pid is %d\n",getpid());      printf("ppid is %d\n",getppid());    }   

4.进程当前状态

4.1进程状态粗分

进程状态粗分一般为就绪状态,运行状态,阻塞状态

         在一个cpu的情况下,一般处理任务方式为并发,即同时处理多个任务。比如有A,B,C三个任务需要占用cpu,那cpu会先执行一段时间A,然后切出去执行B,然后切出去执行C,再切到执行A,这个“一段时间”非常小,所以我们是没有感知的。这就是时间片轮转算法,当然具体算法还有很多,但是并发这个处理方式是不会变的。

        所以cpu在同一时刻也只能执行一个任务,比如在执行A,那么B,C就在就绪或者阻塞状态。假如B时间所需资源已经到位,就差cpu了,B就进入就绪状态,直到cpu执行A的时间片结束,B才进入。C此刻需要一些资源,但是资源没到位,他就进入阻塞状态等待资源,直到资源到来才进入就绪状态。

4.2Linux下进程状态细分

    4.2.1linux下进程状态概念    

        Linux将进程状态细化了,我们可以通过上边提到的ps aux查看进程状态,我们发现它细分为R,S,D,T,t,X,Z几种状态。

(1)R:运行状态

                处于R状态的程序要不就是在执行代码,要不就是在就绪队列

(2)S:可中断睡眠状态

                进程正在睡眠,等待资源到来或是唤醒,也可以通过其他进程信号或者时钟中断唤醒

(3)D:不可中断睡眠状态

                通常等待一个IO结束

(4)T:暂停状态

                进程暂停,Linux使用ctrl+z

  (5) X:死亡状态

                在PCB被系统内核释放时,状态会被质为X,用户看不到这个状态

(6)Z:僵尸状态

                子进程先于父进程退出,父进程未回收子进程信息,就进入僵尸状态

   

4.2.2僵尸状态模拟 

#include    #include int main(){      pid_t pid=fork();      if(pid>0){ while(1){   sleep(1);   printf("I am parent\n"); }    }      else if(pid==0){     printf("I am child!");      }else{ return -1;      }    

         fork()函数功能是创建一个子进程,我们创建了一个子进程,让子进程退出,让父进程循环且不回收子进程信息,子进程进程状态变成了Z,意味着它进入了僵尸状态。僵尸状态危害非常大,所以kill -9也无法杀死,所以我们写代码要谨慎僵尸状态,如何解决僵尸状态我之后会详细讲解。

5.程序计数器

程序计数器的作用是保存下一条指令的执行信息。

        从上边的进程状态粗分模块,我们可以知道cpu并发执行时需要轮转切换进程,比如A进程执行了三分之一被切出cpu了,那么下一次A进入cpu后,cpu就得知道上一次A执行到哪行代码被切出了。所以在每次切出时,每个进程的程序计数器记录代码的执行位置,以便于下次提供给cpu,让cpu知道从哪开始执行。

每个进程都有自己的PCB,每个PCB都会有自己的程序计数器。

6.上下文信息

上下文信息功能是保存寄存器中的内容。

        因为cpu和内存执行速度相差很多,所以cpu并不是直接从内存中拿资源,而是借助了中间商,即寄存器,cpu从寄存器拿东西速度是比较快的,所以在进程切换时也需要保存寄存器当中的内容,方便下次执行。

寄存器是一种硬件设备,不是进程独有的,在寄存器之上还有缓存,然后才是内存。形成这个局势原因是硬件读取速度不一致。

7.内存指针

          在早期的计算机中,操作系统想从内存中读取资源是直接读取,这样子有可能会造成内存错误。现代的计算机中使用了进程虚拟空间这个概念,即操作系统将给每个进程制造一个虚拟的内存空间,在32位系统中是4G,这只是让进程以为有这么多,实际是虚拟的。进程需要内存就朝这个虚拟内存要,虚拟内存空间在通过算法合理分配实际内存地址给进程。所以我们代码打印出的变量地址其实都是这个虚拟内存空间,实际内存地址是需要根据这个虚拟地址转化的。

       虚拟地址空间进行地址转换时,借助页表进行地址转换。具体算法大家可以查看其他博主的文章。使用虚拟地址空间解决了地址空间隔离,运行效率低,运行地址不确定等问题。

8.记账信息

        记录使用cpu的时长,占用内存大小。这些信息我使用ps aux可以查看,此处的TIAME就是记账信息保存的。

9.IO信息

        进程控制块中成员变量之一存储的是进程打开文件的信息,这个存储结构也是个结构体数组,下图为结构体内部图。

        我的另一篇博客操作系统角度看文件操作,里边对IO有具体展开,大家需要的话,可以了解一下喔。

10.总结

        进程在操作系统中描述是用双向链表和PCB来完成的,每个进程都有自己的PCB,每个PCB结构体有七个成员变量,分别为进程标识符、进程状态、程序计数器、上下文信息、内存指针、记账信息、IO信息等,这些部分共同描组成了进程信息。